Skip to content

Commit

Permalink
Implement block addressing format (#16)
Browse files Browse the repository at this point in the history
* WIP

* define GetBlockAddress method

* support i-node without extents

* append entry

* consider flag

* implement error handling

* fix error

* Remove unnecessary code

* add error check

* implement logic to resolve indirect address mapping

* implement the logic to read a file from file blocks

* fix loop condition

* fix alignment for directory reader

* wrap the error message

Co-authored-by: Masahiro331 <[email protected]>

* extract same logic

* print block address in an error message

---------

Co-authored-by: yusuke.koyoshi <[email protected]>
Co-authored-by: Masahiro331 <[email protected]>
  • Loading branch information
3 people authored Jun 20, 2024
1 parent 13e5911 commit ca14e63
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 22 deletions.
125 changes: 103 additions & 22 deletions ext4/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,80 @@ func (ext4 *FileSystem) listFileInfo(ino int64) ([]FileInfo, error) {
return fileInfos, nil
}

func extractDirectoryEntries(directoryReader *bytes.Buffer) ([]DirectoryEntry2, error) {
var dirEntries []DirectoryEntry2

for {
dirEntry := DirectoryEntry2{}

err := struc.Unpack(directoryReader, &dirEntry)
if err != nil {
if err == io.EOF {
break
}
return nil, xerrors.Errorf("failed to parse directory entry: %w", err)
}

if dirEntry.RecLen == 0 {
break
}

align := dirEntry.RecLen - uint16(dirEntry.NameLen+8)
_, err = directoryReader.Read(make([]byte, align))
if err != nil {
return nil, xerrors.Errorf("failed to read align: %w", err)
}

if dirEntry.Name == "." || dirEntry.Name == ".." {
continue
}
if dirEntry.Flags == 0xDE {
continue
}
if dirEntry.Flags == 0 {
continue
}

dirEntries = append(dirEntries, dirEntry)
}

return dirEntries, nil
}

func (ext4 *FileSystem) listEntries(ino int64) ([]DirectoryEntry2, error) {
inode, err := ext4.getInode(ino)
if err != nil {
return nil, xerrors.Errorf("failed to get root inode: %w", err)
}

if !inode.UsesExtents() {
var dirEntries []DirectoryEntry2

blockAddresses, err := inode.GetBlockAddresses(ext4)
if err != nil {
return nil, xerrors.Errorf("failed to get block address: %w", err)
}

for _, blockAddress := range blockAddresses {
_, err = ext4.r.Seek(int64(blockAddress)*ext4.sb.GetBlockSize(), 0)
if err != nil {
return nil, xerrors.Errorf("failed to seek: %w", err)
}

directoryReader, err := readBlock(ext4.r, ext4.sb.GetBlockSize())
if err != nil {
return nil, xerrors.Errorf("failed to read directory entry: %w", err)
}

extracted, err := extractDirectoryEntries(directoryReader)
if err != nil {
return nil, xerrors.Errorf("failed to extract directory entries: %w", err)
}
dirEntries = append(dirEntries, extracted...)
}
return dirEntries, nil
}

extents, err := ext4.Extents(inode)
if err != nil {
return nil, xerrors.Errorf("failed to get extents: %w", err)
Expand All @@ -203,28 +272,11 @@ func (ext4 *FileSystem) listEntries(ino int64) ([]DirectoryEntry2, error) {
return nil, xerrors.Errorf("failed to read directory entry: %w", err)
}

for {
dirEntry := DirectoryEntry2{}
err = struc.Unpack(directoryReader, &dirEntry)
if err != nil {
if err == io.EOF {
break
}
return nil, xerrors.Errorf("failed to parse directory entry: %w", err)
}
align := dirEntry.RecLen - uint16(dirEntry.NameLen+8)
_, err := directoryReader.Read(make([]byte, align))
if err != nil {
return nil, xerrors.Errorf("failed to read align: %w", err)
}
if dirEntry.Name == "." || dirEntry.Name == ".." {
continue
}
if dirEntry.Flags == 0xDE {
continue
}
entries = append(entries, dirEntry)
dirEntries, err := extractDirectoryEntries(directoryReader)
if err != nil {
return nil, xerrors.Errorf("failed to extract directory entries: %w", err)
}
entries = append(entries, dirEntries...)
}
return entries, nil
}
Expand Down Expand Up @@ -305,7 +357,12 @@ func (ext4 *FileSystem) Open(name string) (fs.File, error) {
inode: dir.inode,
mode: fs.FileMode(dir.inode.Mode),
}
f, err := ext4.file(fi, name)
var f *File
if fi.inode.UsesExtents() {
f, err = ext4.file(fi, name)
} else {
f, err = ext4.fileFromBlock(fi, name)
}
if err != nil {
return nil, xerrors.Errorf("failed to get file(inode: %d): %w", dir.ino, err)
}
Expand All @@ -314,6 +371,30 @@ func (ext4 *FileSystem) Open(name string) (fs.File, error) {
return nil, fs.ErrNotExist
}

func (ext4 *FileSystem) fileFromBlock(fi FileInfo, filePath string) (*File, error) {
blockAddresses, err := fi.inode.GetBlockAddresses(ext4)
if err != nil {
return nil, xerrors.Errorf("failed to get block addresses: %w", err)
}

dt := make(dataTable)
for i, blockAddress := range blockAddresses {
offset := int64(blockAddress) * ext4.sb.GetBlockSize()
dt[int64(i)] = offset
}

return &File{
fs: ext4,
FileInfo: fi,
currentBlock: -1,
buffer: bytes.NewBuffer(nil),
filePath: filePath,
blockSize: ext4.sb.GetBlockSize(),
table: dt,
size: fi.Size(),
}, nil
}

func (ext4 *FileSystem) file(fi FileInfo, filePath string) (*File, error) {
extents, err := ext4.extents(fi.inode.BlockOrExtents[:], nil)
if err != nil {
Expand Down
138 changes: 138 additions & 0 deletions ext4/inode.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package ext4

import (
"bytes"
"encoding/binary"

"golang.org/x/xerrors"
)

// ExtentHeader is ...
type ExtentHeader struct {
Magic uint16 `struc:"uint16,little"`
Expand Down Expand Up @@ -66,6 +73,13 @@ type Inode struct {
Reserved [96]uint8 `struc:"[96]uint32,little"`
}

type BlockAddressing struct {
DirectBlock [12]uint32 `struc:"[12]uint32,little"`
SingleIndirectBlock uint32 `struc:"uint32,little"`
DoubleIndirectBlock uint32 `struc:"uint32,little"`
TripleIndirectBlock uint32 `struc:"uint32,little"`
}

func (i Inode) IsDir() bool {
return i.Mode&0x4000 != 0 && i.Mode&0x8000 == 0
}
Expand Down Expand Up @@ -97,6 +111,130 @@ func (i *Inode) GetSize() int64 {
return (int64(i.SizeHigh) << 32) | int64(i.SizeLo)
}

func resolveSingleIndirectBlockAddress(ext4 *FileSystem, singleIndirectBlockAddress uint32) ([]uint32, error) {
var blockAddresses []uint32

_, err := ext4.r.Seek(int64(singleIndirectBlockAddress)*ext4.sb.GetBlockSize(), 0)
if err != nil {
return nil, xerrors.Errorf("failed to seek: %w", err)
}

singleIndirectBlockAddresses, err := readBlock(ext4.r, ext4.sb.GetBlockSize())
if err != nil {
return nil, xerrors.Errorf("failed to read directory entry at block address %#x: %w", singleIndirectBlockAddress, err)
}

for singleIndirectBlockAddresses.Len() > 0 {
address := binary.LittleEndian.Uint32(singleIndirectBlockAddresses.Next(4))
if address == 0 {
break
}
blockAddresses = append(blockAddresses, address)
}

return blockAddresses, nil
}

func resolveDoubleIndirectBlockAddress(ext4 *FileSystem, doubleIndirectBlockAddress uint32) ([]uint32, error) {
var blockAddresses []uint32

_, err := ext4.r.Seek(int64(doubleIndirectBlockAddress)*ext4.sb.GetBlockSize(), 0)
if err != nil {
return nil, xerrors.Errorf("failed to seek: %w", err)
}

doubleIndirectBlockAddresses, err := readBlock(ext4.r, ext4.sb.GetBlockSize())
if err != nil {
return nil, xerrors.Errorf("failed to read directory entry at block address %#x: %w", doubleIndirectBlockAddress, err)
}

for doubleIndirectBlockAddresses.Len() > 0 {
singleIndirectBlockAddress := binary.LittleEndian.Uint32(doubleIndirectBlockAddresses.Next(4))
if singleIndirectBlockAddress == 0 {
break
}

singleIndirectBlockAddresses, err := resolveSingleIndirectBlockAddress(ext4, singleIndirectBlockAddress)
if err != nil {
return nil, xerrors.Errorf("failed to read single indirect block addressing: %w", err)
}
blockAddresses = append(blockAddresses, singleIndirectBlockAddresses...)
}

return blockAddresses, nil
}

func resolveTripleIndirectBlockAddress(ext4 *FileSystem, tripleIndirectBlockAddress uint32) ([]uint32, error) {
var blockAddresses []uint32

_, err := ext4.r.Seek(int64(tripleIndirectBlockAddress)*ext4.sb.GetBlockSize(), 0)
if err != nil {
return nil, xerrors.Errorf("failed to seek: %w", err)
}

tripleIndirectBlockAddresses, err := readBlock(ext4.r, ext4.sb.GetBlockSize())
if err != nil {
return nil, xerrors.Errorf("failed to read directory entry at block address %#x: %w", tripleIndirectBlockAddress, err)
}

for tripleIndirectBlockAddresses.Len() > 0 {
doubleIndirectBlockAddress := binary.LittleEndian.Uint32(tripleIndirectBlockAddresses.Next(4))
if doubleIndirectBlockAddress == 0 {
break
}

doubleIndirectBlockAddresses, err := resolveDoubleIndirectBlockAddress(ext4, doubleIndirectBlockAddress)
if err != nil {
return nil, xerrors.Errorf("failed to read double indirect block addressing: %w", err)
}
blockAddresses = append(blockAddresses, doubleIndirectBlockAddresses...)
}

return blockAddresses, nil
}

func (i *Inode) GetBlockAddresses(ext4 *FileSystem) ([]uint32, error) {
addresses := BlockAddressing{}
err := binary.Read(bytes.NewReader(i.BlockOrExtents[:]), binary.LittleEndian, &addresses)
if err != nil {
return nil, xerrors.Errorf("failed to read block addressing: %w", err)
}

var blockAddresses []uint32
for _, blockAddress := range addresses.DirectBlock {
if blockAddress == 0 {
break
}
blockAddresses = append(blockAddresses, blockAddress)
}

if addresses.SingleIndirectBlock != 0 {
singleIndirectBlockAddresses, err := resolveSingleIndirectBlockAddress(ext4, addresses.SingleIndirectBlock)
if err != nil {
return nil, xerrors.Errorf("failed to read single indirect block addressing: %w", err)
}
blockAddresses = append(blockAddresses, singleIndirectBlockAddresses...)
}

if addresses.DoubleIndirectBlock != 0 {
doubleIndirectBlockAddresses, err := resolveDoubleIndirectBlockAddress(ext4, addresses.DoubleIndirectBlock)
if err != nil {
return nil, xerrors.Errorf("failed to read double indirect block addressing: %w", err)
}
blockAddresses = append(blockAddresses, doubleIndirectBlockAddresses...)
}

if addresses.TripleIndirectBlock != 0 {
tripleIndirectBlockAddresses, err := resolveTripleIndirectBlockAddress(ext4, addresses.TripleIndirectBlock)
if err != nil {
return nil, xerrors.Errorf("failed to read triple indirect block addressing: %w", err)
}
blockAddresses = append(blockAddresses, tripleIndirectBlockAddresses...)
}

return blockAddresses, nil
}

// ExtentInternal
type ExtentInternal struct {
Block uint32 `struc:"uint32,little"`
Expand Down

0 comments on commit ca14e63

Please sign in to comment.