Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ext4 read-only for now #218

Merged
merged 1 commit into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading