summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-07-06 01:48:48 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-07-08 00:15:58 -0600
commit5647659f27f8aa18bc10ca4742f8856162325d5c (patch)
tree249fd2d4ea5e0e8bf6dab24d305a4daa5bf0cc6e
parentb392ad64a8fd04d20b35ad21d2d4ea3ff2778e3f (diff)
file reading!
-rw-r--r--cmd/btrfs-mount/subvol.go110
-rw-r--r--cmd/btrfs-mount/subvol_fuse.go18
-rw-r--r--pkg/btrfs/btrfsitem/item_fileextent.go52
-rw-r--r--pkg/btrfsmisc/print_tree.go16
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)