Skip to content

Commit

Permalink
Merge pull request #218 from diskfs/ext4
Browse files Browse the repository at this point in the history
ext4 read-only for now
  • Loading branch information
deitch authored Jun 16, 2024
2 parents 8c053c7 + 1d13293 commit dcfddd9
Show file tree
Hide file tree
Showing 42 changed files with 6,830 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ cat $PWD/foo.img | docker run -i --rm $INT_IMAGE mdir -i /file.img /abc
Future plans are to add the following:

* embed boot code in `mbr` e.g. `altmbr.bin` (no need for `gpt` since an ESP with `/EFI/BOOT/BOOT<arch>.EFI` will boot)
* `ext4` filesystem
* `ext4` filesystem writing (read-only already works)
* `Joliet` extensions to `iso9660`
* `Rock Ridge` sparse file support - supports the flag, but not yet reading or writing
* `squashfs` sparse file support - currently treats sparse files as regular files
Expand Down
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
}
48 changes: 48 additions & 0 deletions filesystem/ext4/checksum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ext4

import (
"encoding/binary"

"github.com/diskfs/go-diskfs/filesystem/ext4/crc"
)

// checksumAppender is a function that takes a byte slice and returns a byte slice with a checksum appended
type checksumAppender func([]byte) []byte
type checksummer func([]byte) uint32

// directoryChecksummer returns a function that implements checksumAppender for a directory entries block
// original calculations can be seen for e2fsprogs https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/tree/lib/ext2fs/csum.c#n301
// and in the linux tree https://github.com/torvalds/linux/blob/master/fs/ext4/namei.c#L376-L384
func directoryChecksummer(seed, inodeNumber, inodeGeneration uint32) checksummer {
numBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(numBytes, inodeNumber)
crcResult := crc.CRC32c(seed, numBytes)
genBytes := make([]byte, 4)
binary.LittleEndian.PutUint32(genBytes, inodeGeneration)
crcResult = crc.CRC32c(crcResult, genBytes)
return func(b []byte) uint32 {
checksum := crc.CRC32c(crcResult, b)
return checksum
}
}

// directoryChecksumAppender returns a function that implements checksumAppender for a directory entries block
// original calculations can be seen for e2fsprogs https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/tree/lib/ext2fs/csum.c#n301
// and in the linux tree https://github.com/torvalds/linux/blob/master/fs/ext4/namei.c#L376-L384
func directoryChecksumAppender(seed, inodeNumber, inodeGeneration uint32) checksumAppender {
fn := directoryChecksummer(seed, inodeNumber, inodeGeneration)
return func(b []byte) []byte {
checksum := fn(b)
checksumBytes := make([]byte, 12)
checksumBytes[4] = 12
checksumBytes[7] = 0xde
binary.LittleEndian.PutUint32(checksumBytes[8:12], checksum)
b = append(b, checksumBytes...)
return b
}
}

// nullDirectoryChecksummer does not change anything
func nullDirectoryChecksummer(b []byte) []byte {
return b
}
Loading

0 comments on commit dcfddd9

Please sign in to comment.