Skip to content

Commit

Permalink
ext4
Browse files Browse the repository at this point in the history
Signed-off-by: Avi Deitcher <[email protected]>
  • Loading branch information
deitch committed May 31, 2024
1 parent db32762 commit 2ac863a
Show file tree
Hide file tree
Showing 34 changed files with 5,042 additions and 1 deletion.
3 changes: 3 additions & 0 deletions disk/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
log "github.com/sirupsen/logrus"

"github.com/diskfs/go-diskfs/filesystem"
"github.com/diskfs/go-diskfs/filesystem/ext4"
"github.com/diskfs/go-diskfs/filesystem/fat32"
"github.com/diskfs/go-diskfs/filesystem/iso9660"
"github.com/diskfs/go-diskfs/filesystem/squashfs"
Expand Down Expand Up @@ -185,6 +186,8 @@ func (d *Disk) CreateFilesystem(spec FilesystemSpec) (filesystem.FileSystem, err
return fat32.Create(d.File, size, start, d.LogicalBlocksize, spec.VolumeLabel)
case filesystem.TypeISO9660:
return iso9660.Create(d.File, size, start, d.LogicalBlocksize, spec.WorkDir)
case filesystem.TypeExt4:
return ext4.Create(d.File, size, start, d.LogicalBlocksize, nil)
case filesystem.TypeSquashfs:
return nil, errors.New("squashfs is a read-only filesystem")
default:
Expand Down
104 changes: 104 additions & 0 deletions filesystem/ext4/bitmaps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ext4

import "fmt"

// bitmap is a structure holding a bitmap
type bitmap struct {
bits []byte
}

// bitmapFromBytes create a bitmap struct from bytes
func bitmapFromBytes(b []byte) *bitmap {
// just copy them over
bits := make([]byte, len(b))
copy(bits, b)
bm := bitmap{
bits: bits,
}

return &bm
}

// toBytes returns raw bytes ready to be written to disk
func (bm *bitmap) toBytes() []byte {
b := make([]byte, len(bm.bits))
copy(b, bm.bits)

return b
}

func (bm *bitmap) checkFree(location int) (bool, error) {
byteNumber, bitNumber := findBitForIndex(location)
if byteNumber > len(bm.bits) {
return false, fmt.Errorf("location %d is not in %d size bitmap", location, len(bm.bits)*8)
}
mask := byte(0x1) << bitNumber
return bm.bits[byteNumber]&mask == mask, nil
}

func (bm *bitmap) free(location int) error {
byteNumber, bitNumber := findBitForIndex(location)
if byteNumber > len(bm.bits) {
return fmt.Errorf("location %d is not in %d size bitmap", location, len(bm.bits)*8)
}
mask := byte(0x1) << bitNumber
mask = ^mask
bm.bits[byteNumber] &= mask
return nil
}

func (bm *bitmap) use(location int) error {
byteNumber, bitNumber := findBitForIndex(location)
if byteNumber > len(bm.bits) {
return fmt.Errorf("location %d is not in %d size bitmap", location, len(bm.bits)*8)
}
mask := byte(0x1) << bitNumber
bm.bits[byteNumber] |= mask
return nil
}

func (bm *bitmap) findFirstFree() int {
var location = -1
for i, b := range bm.bits {
// if all used, continue to next
if b&0xff == 0xff {
continue
}
// not all used, so find first bit set to 0
for j := uint8(0); j < 8; j++ {
mask := byte(0x1) << j
if b&mask != mask {
location = 8*i + (8 - int(j))
break
}
}
break
}
return location
}

//nolint:revive // params are unused as of yet, but will be used in the future
func (bm *bitmap) findFirstUsed() int {
var location int = -1
for i, b := range bm.bits {
// if all free, continue to next
if b == 0x00 {
continue
}
// not all free, so find first bit set to 1
for j := uint8(0); j < 8; j++ {
mask := byte(0x1) << j
mask = ^mask
if b|mask != mask {
location = 8*i + (8 - int(j))
break
}
}
break
}
return location
}

func findBitForIndex(index int) (byteNumber int, bitNumber uint8) {
return index / 8, uint8(index % 8)
}
53 changes: 53 additions & 0 deletions filesystem/ext4/blockgroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package ext4

import (
"fmt"
)

// blockGroup is a structure holding the data about a single block group
//
//nolint:unused // will be used in the future, not yet
type blockGroup struct {
inodeBitmap *bitmap
blockBitmap *bitmap
blockSize int
number int
inodeTableSize int
firstDataBlock int
}

// blockGroupFromBytes create a blockGroup struct from bytes
// it does not load the inode table or data blocks into memory, rather holding pointers to where they are
//
//nolint:unused // will be used in the future, not yet
func blockGroupFromBytes(b []byte, blockSize, groupNumber int) (*blockGroup, error) {
expectedSize := 2 * blockSize
actualSize := len(b)
if actualSize != expectedSize {
return nil, fmt.Errorf("expected to be passed %d bytes for 2 blocks of size %d, instead received %d", expectedSize, blockSize, actualSize)
}
inodeBitmap := bitmapFromBytes(b[0:blockSize])
blockBitmap := bitmapFromBytes(b[blockSize : 2*blockSize])

bg := blockGroup{
inodeBitmap: inodeBitmap,
blockBitmap: blockBitmap,
number: groupNumber,
blockSize: blockSize,
}
return &bg, nil
}

// toBytes returns bitmaps ready to be written to disk
//
//nolint:unused // will be used in the future, not yet
func (bg *blockGroup) toBytes() ([]byte, error) {
b := make([]byte, 2*bg.blockSize)
inodeBitmapBytes := bg.inodeBitmap.toBytes()
blockBitmapBytes := bg.blockBitmap.toBytes()

b = append(b, inodeBitmapBytes...)
b = append(b, blockBitmapBytes...)

return b, nil
}
32 changes: 32 additions & 0 deletions filesystem/ext4/directory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ext4

// Directory represents a single directory in an ext4 filesystem
type Directory struct {
directoryEntry
root bool
entries []*directoryEntry
}

// dirEntriesFromBytes loads the directory entries from the raw bytes
func (d *Directory) entriesFromBytes(b []byte) error {
entries, err := parseDirEntries(b)
if err != nil {
return err
}
d.entries = entries
return nil
}

// toBytes convert our entries to raw bytes
func (d *Directory) toBytes(bytesPerBlock int) []byte {
b := make([]byte, 0)
for _, de := range d.entries {
b2 := de.toBytes()
b = append(b, b2...)
}
remainder := len(b) % bytesPerBlock
extra := bytesPerBlock - remainder
zeroes := make([]byte, extra)
b = append(b, zeroes...)
return b
}
73 changes: 73 additions & 0 deletions filesystem/ext4/directoryentry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package ext4

import (
"encoding/binary"
"fmt"
)

const (
minDirEntryLength int = 12 // actually 9 for 1-byte file length, but must be multiple of 4 bytes
maxDirEntryLength int = 263
)

// directoryEntry is a single directory entry
type directoryEntry struct {
inode uint32
filename string
fileType fileType
}

func directoryEntryFromBytes(b []byte) (*directoryEntry, error) {
if len(b) < minDirEntryLength {
return nil, fmt.Errorf("directory entry of length %d is less than minimum %d", len(b), minDirEntryLength)
}
if len(b) > maxDirEntryLength {
return nil, fmt.Errorf("directory entry of length %d is greater than maximum %d", len(b), maxDirEntryLength)
}

//nolint:gocritic // keep this here for future reference
// length := binary.LittleEndian.Uint16(b[0x4:0x6])
nameLength := b[0x6]
name := b[0x8 : 0x8+nameLength]
de := directoryEntry{
inode: binary.LittleEndian.Uint32(b[0x0:0x4]),
fileType: fileType(b[0x7]),
filename: string(name),
}
return &de, nil
}

func (de *directoryEntry) toBytes() []byte {
// it must be the header length + filename length rounded up to nearest multiple of 4
nameLength := uint8(len(de.filename))
entryLength := uint16(nameLength) + 8
if leftover := entryLength % 4; leftover > 0 {
entryLength += leftover
}
b := make([]byte, 0, entryLength)

binary.LittleEndian.PutUint32(b[0x0:0x4], de.inode)
binary.LittleEndian.PutUint16(b[0x4:0x6], entryLength)
b[0x6] = nameLength
b[0x7] = byte(de.fileType)
copy(b[0x8:], de.filename)

return b
}

// parse the data blocks to get the directory entries
func parseDirEntries(b []byte) ([]*directoryEntry, error) {
entries := make([]*directoryEntry, 4)
count := 0
for i := 0; i < len(b); count++ {
// read the length of the first entry
length := binary.LittleEndian.Uint16(b[i+0x4 : i+0x6])
de, err := directoryEntryFromBytes(b[i : i+int(length)])
if err != nil {
return nil, fmt.Errorf("failed to parse directory entry %d: %v", count, err)
}
entries = append(entries, de)
i += int(length)
}
return entries, nil
}
Loading

0 comments on commit 2ac863a

Please sign in to comment.