From 5647659f27f8aa18bc10ca4742f8856162325d5c Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 6 Jul 2022 01:48:48 -0600 Subject: file reading! --- cmd/btrfs-mount/subvol.go | 110 ++++++++++++++++++++++++++++++++- cmd/btrfs-mount/subvol_fuse.go | 18 +++++- pkg/btrfs/btrfsitem/item_fileextent.go | 52 +++++++--------- pkg/btrfsmisc/print_tree.go | 16 ++--- 4 files changed, 153 insertions(+), 43 deletions(-) diff --git a/cmd/btrfs-mount/subvol.go b/cmd/btrfs-mount/subvol.go index 4e741ad..1d5e9db 100644 --- a/cmd/btrfs-mount/subvol.go +++ b/cmd/btrfs-mount/subvol.go @@ -3,7 +3,9 @@ package main import ( "context" "fmt" + "io" "reflect" + "sort" "sync" "github.com/datawire/dlib/dcontext" @@ -35,9 +37,15 @@ type dir struct { ChildrenByIndex map[uint64]btrfsitem.DirEntry } +type fileExtent struct { + OffsetWithinFile int64 + btrfsitem.FileExtent +} + type file struct { fullInode - // TODO + Extents []fileExtent + FS *btrfs.FS } type Subvolume struct { @@ -296,7 +304,8 @@ func (sv *Subvolume) loadFile(inode btrfs.ObjID) (*file, error) { return } val.fullInode = *fullInode - // TODO + val.FS = sv.FS + val.populate() return }) if val.Inode == 0 { @@ -304,3 +313,100 @@ func (sv *Subvolume) loadFile(inode btrfs.ObjID) (*file, error) { } return val, nil } + +func (ret *file) populate() { + for _, item := range ret.OtherItems { + switch item.Head.Key.ItemType { + case btrfsitem.INODE_REF_KEY: + // TODO + case btrfsitem.EXTENT_DATA_KEY: + ret.Extents = append(ret.Extents, fileExtent{ + OffsetWithinFile: int64(item.Head.Key.Offset), + FileExtent: item.Body.(btrfsitem.FileExtent), + }) + default: + panic(fmt.Errorf("TODO: handle item type %v", item.Head.Key.ItemType)) + } + } + + // These should already be sorted, because of the nature of + // the btree; but this is a recovery tool for corrupt + // filesystems, so go ahead and ensure that it's sorted. + sort.Slice(ret.Extents, func(i, j int) bool { + return ret.Extents[i].OffsetWithinFile < ret.Extents[j].OffsetWithinFile + }) + + pos := int64(0) + for _, extent := range ret.Extents { + if extent.OffsetWithinFile != pos { + if extent.OffsetWithinFile > pos { + ret.Errs = append(ret.Errs, fmt.Errorf("extent gap from %v to %v", + pos, extent.OffsetWithinFile)) + } else { + ret.Errs = append(ret.Errs, fmt.Errorf("extent overlap from %v to %v", + extent.OffsetWithinFile, pos)) + } + } + size, err := extent.Size() + if err != nil { + ret.Errs = append(ret.Errs, fmt.Errorf("extent %v: %w", extent.OffsetWithinFile, err)) + } + pos += size + } + if ret.InodeItem != nil && pos != ret.InodeItem.Size { + if ret.InodeItem.Size > pos { + ret.Errs = append(ret.Errs, fmt.Errorf("extent gap from %v to %v", + pos, ret.InodeItem.Size)) + } else { + ret.Errs = append(ret.Errs, fmt.Errorf("extent mapped past end of file from %v to %v", + ret.InodeItem.Size, pos)) + } + } +} + +func (file *file) ReadAt(dat []byte, off int64) (int, error) { + // These stateles maybe-short-reads each do an O(n) extent + // lookup, so reading a file is O(n^2), but we expect n to be + // small, so whatev. Turn file.Extents it in to an rbtree if + // it becomes a problem. + done := 0 + for done < len(dat) { + n, err := file.maybeShortReadAt(dat[done:], off+int64(done)) + done += n + if err != nil { + return done, err + } + } + return done, nil +} + +func (file *file) maybeShortReadAt(dat []byte, off int64) (int, error) { + for _, extent := range file.Extents { + extBeg := extent.OffsetWithinFile + if extBeg > off { + break + } + extLen, err := extent.Size() + if err != nil { + continue + } + extEnd := extBeg + extLen + if extEnd <= off { + continue + } + offsetWithinExt := off - extent.OffsetWithinFile + readSize := util.Min(int64(len(dat)), extLen-offsetWithinExt) + switch extent.Type { + case btrfsitem.FILE_EXTENT_INLINE: + return copy(dat, extent.BodyInline[offsetWithinExt:offsetWithinExt+readSize]), nil + case btrfsitem.FILE_EXTENT_REG, btrfsitem.FILE_EXTENT_PREALLOC: + return file.FS.ReadAt(dat[:readSize], + extent.BodyExtent.DiskByteNr. + Add(extent.BodyExtent.Offset). + Add(btrfsvol.AddrDelta(offsetWithinExt))) + } + } + return 0, fmt.Errorf("read: could not map position %v", off) +} + +var _ io.ReaderAt = (*file)(nil) diff --git a/cmd/btrfs-mount/subvol_fuse.go b/cmd/btrfs-mount/subvol_fuse.go index 7500e0b..6f3f267 100644 --- a/cmd/btrfs-mount/subvol_fuse.go +++ b/cmd/btrfs-mount/subvol_fuse.go @@ -197,11 +197,25 @@ func (sv *Subvolume) OpenFile(_ context.Context, op *fuseops.OpenFileOp) error { return nil } func (sv *Subvolume) ReadFile(_ context.Context, op *fuseops.ReadFileOp) error { - _, ok := sv.fileHandles.Load(op.Handle) + state, ok := sv.fileHandles.Load(op.Handle) if !ok { return syscall.EBADF } - return syscall.ENOSYS + + size := op.Size + var dat []byte + if op.Dst != nil { + size = util.Min(int64(len(op.Dst)), size) + dat = op.Dst + } else { + dat = make([]byte, op.Size) + op.Data = [][]byte{dat} + } + + var err error + op.BytesRead, err = state.File.ReadAt(dat, op.Offset) + + return err } func (sv *Subvolume) ReleaseFileHandle(_ context.Context, op *fuseops.ReleaseFileHandleOp) error { _, ok := sv.fileHandles.LoadAndDelete(op.Handle) diff --git a/pkg/btrfs/btrfsitem/item_fileextent.go b/pkg/btrfs/btrfsitem/item_fileextent.go index f3cbffe..f480207 100644 --- a/pkg/btrfs/btrfsitem/item_fileextent.go +++ b/pkg/btrfs/btrfsitem/item_fileextent.go @@ -24,26 +24,17 @@ type FileExtent struct { // EXTENT_DATA=108 binstruct.End `bin:"off=0x15"` // only one of these, depending on .Type - BodyInline []byte `bin:"-"` - BodyReg struct { - // Position of extent within the device + BodyInline []byte `bin:"-"` // .Type == FILE_EXTENT_INLINE + BodyExtent struct { // .Type == FILE_EXTENT_REG or FILE_EXTENT_PREALLOC + // Position and size of extent within the device DiskByteNr btrfsvol.LogicalAddr `bin:"off=0x0, siz=0x8"` DiskNumBytes btrfsvol.AddrDelta `bin:"off=0x8, siz=0x8"` // Position of data within the extent - Offset btrfsvol.AddrDelta `bin:"off=0x10, siz=0x8"` - NumBytes btrfsvol.AddrDelta `bin:"off=0x18, siz=0x8"` + Offset btrfsvol.AddrDelta `bin:"off=0x10, siz=0x8"` - binstruct.End `bin:"off=0x20"` - } `bin:"-"` - BodyPrealloc struct { - // Position of extent within the device - DiskByteNr btrfsvol.LogicalAddr `bin:"off=0x0, siz=0x8"` - DiskNumBytes btrfsvol.AddrDelta `bin:"off=0x8, siz=0x8"` - - // Position of data within the extent - Offset btrfsvol.AddrDelta `bin:"off=0x10, siz=0x8"` - NumBytes btrfsvol.AddrDelta `bin:"off=0x18, siz=0x8"` + // Decompressed/unencrypted size + NumBytes int64 `bin:"off=0x18, siz=0x8"` binstruct.End `bin:"off=0x20"` } `bin:"-"` @@ -58,14 +49,8 @@ func (o *FileExtent) UnmarshalBinary(dat []byte) (int, error) { case FILE_EXTENT_INLINE: o.BodyInline = dat[n:] n += len(o.BodyInline) - case FILE_EXTENT_REG: - _n, err := binstruct.Unmarshal(dat[n:], &o.BodyReg) - n += _n - if err != nil { - return n, err - } - case FILE_EXTENT_PREALLOC: - _n, err := binstruct.Unmarshal(dat[n:], &o.BodyPrealloc) + case FILE_EXTENT_REG, FILE_EXTENT_PREALLOC: + _n, err := binstruct.Unmarshal(dat[n:], &o.BodyExtent) n += _n if err != nil { return n, err @@ -84,14 +69,8 @@ func (o FileExtent) MarshalBinary() ([]byte, error) { switch o.Type { case FILE_EXTENT_INLINE: dat = append(dat, o.BodyInline...) - case FILE_EXTENT_REG: - bs, err := binstruct.Marshal(o.BodyReg) - dat = append(dat, bs...) - if err != nil { - return dat, err - } - case FILE_EXTENT_PREALLOC: - bs, err := binstruct.Marshal(o.BodyPrealloc) + case FILE_EXTENT_REG, FILE_EXTENT_PREALLOC: + bs, err := binstruct.Marshal(o.BodyExtent) dat = append(dat, bs...) if err != nil { return dat, err @@ -110,6 +89,17 @@ const ( FILE_EXTENT_PREALLOC ) +func (o FileExtent) Size() (int64, error) { + switch o.Type { + case FILE_EXTENT_INLINE: + return int64(len(o.BodyInline)), nil + case FILE_EXTENT_REG, FILE_EXTENT_PREALLOC: + return o.BodyExtent.NumBytes, nil + default: + return 0, fmt.Errorf("unknown file extent type %v", o.Type) + } +} + func (fet FileExtentType) String() string { names := map[FileExtentType]string{ FILE_EXTENT_INLINE: "inline", diff --git a/pkg/btrfsmisc/print_tree.go b/pkg/btrfsmisc/print_tree.go index 6b4ff96..61c5423 100644 --- a/pkg/btrfsmisc/print_tree.go +++ b/pkg/btrfsmisc/print_tree.go @@ -156,18 +156,18 @@ func PrintTree(fs *btrfs.FS, root btrfsvol.LogicalAddr) error { len(body.BodyInline), body.RAMBytes, body.Compression) case btrfsitem.FILE_EXTENT_PREALLOC: fmt.Printf("\t\tprealloc data disk byte %v nr %v\n", - body.BodyPrealloc.DiskByteNr, - body.BodyPrealloc.DiskNumBytes) + body.BodyExtent.DiskByteNr, + body.BodyExtent.DiskNumBytes) fmt.Printf("\t\tprealloc data offset %v nr %v\n", - body.BodyPrealloc.Offset, - body.BodyPrealloc.NumBytes) + body.BodyExtent.Offset, + body.BodyExtent.NumBytes) case btrfsitem.FILE_EXTENT_REG: fmt.Printf("\t\textent data disk byte %d nr %d\n", - body.BodyReg.DiskByteNr, - body.BodyReg.DiskNumBytes) + body.BodyExtent.DiskByteNr, + body.BodyExtent.DiskNumBytes) fmt.Printf("\t\textent data offset %d nr %d ram %v\n", - body.BodyReg.Offset, - body.BodyReg.NumBytes, + body.BodyExtent.Offset, + body.BodyExtent.NumBytes, body.RAMBytes) fmt.Printf("\t\textent compression %v\n", body.Compression) -- cgit v1.2.3-2-g168b