diff options
-rw-r--r-- | cmd/btrfs-mount/lru.go | 73 | ||||
-rw-r--r-- | cmd/btrfs-mount/subvol.go | 207 | ||||
-rw-r--r-- | cmd/btrfs-mount/subvol_fuse.go | 18 | ||||
-rw-r--r-- | pkg/btrfs/btrfsitem/item_fileextent.go | 29 | ||||
-rw-r--r-- | pkg/btrfsmisc/print_tree.go | 4 |
5 files changed, 239 insertions, 92 deletions
diff --git a/cmd/btrfs-mount/lru.go b/cmd/btrfs-mount/lru.go new file mode 100644 index 0000000..bf91c5e --- /dev/null +++ b/cmd/btrfs-mount/lru.go @@ -0,0 +1,73 @@ +package main + +import ( + "sync" + + lru "github.com/hashicorp/golang-lru" +) + +type LRUCache[K comparable, V any] struct { + initOnce sync.Once + inner *lru.ARCCache +} + +func (c *LRUCache[K, V]) init() { + c.initOnce.Do(func() { + c.inner, _ = lru.NewARC(128) + }) +} + +func (c *LRUCache[K, V]) Add(key K, value V) { + c.init() + c.inner.Add(key, value) +} +func (c *LRUCache[K, V]) Contains(key K) bool { + c.init() + return c.inner.Contains(key) +} +func (c *LRUCache[K, V]) Get(key K) (value V, ok bool) { + c.init() + _value, ok := c.inner.Get(key) + if ok { + value = _value.(V) + } + return value, ok +} +func (c *LRUCache[K, V]) Keys() []K { + c.init() + untyped := c.inner.Keys() + typed := make([]K, len(untyped)) + for i := range untyped { + typed[i] = untyped[i].(K) + } + return typed +} +func (c *LRUCache[K, V]) Len() int { + c.init() + return c.inner.Len() +} +func (c *LRUCache[K, V]) Peek(key K) (value V, ok bool) { + c.init() + _value, ok := c.inner.Peek(key) + if ok { + value = _value.(V) + } + return value, ok +} +func (c *LRUCache[K, V]) Purge() { + c.init() + c.inner.Purge() +} +func (c *LRUCache[K, V]) Remove(key K) { + c.init() + c.inner.Remove(key) +} + +func (c *LRUCache[K, V]) GetOrElse(key K, fn func() V) V { + var value V + var ok bool + for value, ok = c.Get(key); !ok; value, ok = c.Get(key) { + c.Add(key, fn()) + } + return value +} diff --git a/cmd/btrfs-mount/subvol.go b/cmd/btrfs-mount/subvol.go index e4e0460..4e741ad 100644 --- a/cmd/btrfs-mount/subvol.go +++ b/cmd/btrfs-mount/subvol.go @@ -7,8 +7,8 @@ import ( "sync" "github.com/datawire/dlib/dcontext" + "github.com/datawire/dlib/derror" "github.com/datawire/dlib/dlog" - lru "github.com/hashicorp/golang-lru" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseutil" @@ -18,6 +18,28 @@ import ( "lukeshu.com/btrfs-tools/pkg/util" ) +type bareInode struct { + Inode btrfs.ObjID + InodeItem *btrfsitem.Inode + Errs derror.MultiError +} + +type fullInode struct { + bareInode + OtherItems []btrfs.Item +} + +type dir struct { + fullInode + ChildrenByName map[string]btrfsitem.DirEntry + ChildrenByIndex map[uint64]btrfsitem.DirEntry +} + +type file struct { + fullInode + // TODO +} + type Subvolume struct { FS *btrfs.FS DeviceName string @@ -28,8 +50,10 @@ type Subvolume struct { rootVal btrfsitem.Root rootErr error - inodeCache *lru.ARCCache - dirCache *lru.ARCCache + bareInodeCache LRUCache[btrfs.ObjID, *bareInode] + fullInodeCache LRUCache[btrfs.ObjID, *fullInode] + dirCache LRUCache[btrfs.ObjID, *dir] + fileCache LRUCache[btrfs.ObjID, *file] subvolumeFUSE } @@ -79,11 +103,6 @@ func (sv *Subvolume) init() { } sv.rootVal = rootBody - - sv.inodeCache, _ = lru.NewARC(128) - sv.dirCache, _ = lru.NewARC(128) - - sv.subvolumeFUSE.init() }) } @@ -97,73 +116,108 @@ func (sv *Subvolume) getFSTree() (btrfsvol.LogicalAddr, error) { return sv.rootVal.ByteNr, sv.rootErr } -func (sv *Subvolume) loadInode(inode btrfs.ObjID) (btrfsitem.Inode, error) { - tree, err := sv.getFSTree() - if err != nil { - return btrfsitem.Inode{}, nil - } - if ret, ok := sv.inodeCache.Get(inode); ok { - return ret.(btrfsitem.Inode), nil - } - item, err := sv.FS.TreeLookup(tree, btrfs.Key{ - ObjectID: inode, - ItemType: btrfsitem.INODE_ITEM_KEY, - Offset: 0, - }) - if err != nil { - return btrfsitem.Inode{}, err - } +func (sv *Subvolume) loadBareInode(inode btrfs.ObjID) (*bareInode, error) { + val := sv.bareInodeCache.GetOrElse(inode, func() (val *bareInode) { + val = &bareInode{ + Inode: inode, + } + tree, err := sv.getFSTree() + if err != nil { + val.Errs = append(val.Errs, err) + return + } + item, err := sv.FS.TreeLookup(tree, btrfs.Key{ + ObjectID: inode, + ItemType: btrfsitem.INODE_ITEM_KEY, + Offset: 0, + }) + if err != nil { + val.Errs = append(val.Errs, err) + return + } - itemBody, ok := item.Body.(btrfsitem.Inode) - if !ok { - return btrfsitem.Inode{}, fmt.Errorf("malformed inode") - } + itemBody, ok := item.Body.(btrfsitem.Inode) + if !ok { + val.Errs = append(val.Errs, fmt.Errorf("malformed inode")) + return + } + val.InodeItem = &itemBody - sv.inodeCache.Add(inode, itemBody) - return itemBody, nil + return + }) + if val.InodeItem == nil { + return nil, val.Errs + } + return val, nil } -type dir struct { - Inode btrfs.ObjID - InodeDat *btrfsitem.Inode - ChildrenByName map[string]btrfsitem.DirEntry - ChildrenByIndex map[uint64]btrfsitem.DirEntry - Errs []error +func (sv *Subvolume) loadFullInode(inode btrfs.ObjID) (*fullInode, error) { + val := sv.fullInodeCache.GetOrElse(inode, func() (val *fullInode) { + val = &fullInode{ + bareInode: bareInode{ + Inode: inode, + }, + } + tree, err := sv.getFSTree() + if err != nil { + val.Errs = append(val.Errs, err) + return + } + items, err := sv.FS.TreeSearchAll(tree, func(key btrfs.Key) int { + return util.CmpUint(inode, key.ObjectID) + }) + if err != nil { + val.Errs = append(val.Errs, err) + if len(items) == 0 { + return + } + } + for _, item := range items { + switch item.Head.Key.ItemType { + case btrfsitem.INODE_ITEM_KEY: + itemBody := item.Body.(btrfsitem.Inode) + if val.InodeItem != nil { + if !reflect.DeepEqual(itemBody, *val.InodeItem) { + val.Errs = append(val.Errs, fmt.Errorf("multiple inodes")) + } + continue + } + val.InodeItem = &itemBody + default: + val.OtherItems = append(val.OtherItems, item) + } + } + return + }) + if val.InodeItem == nil && val.OtherItems == nil { + return nil, val.Errs + } + return val, nil } func (sv *Subvolume) loadDir(inode btrfs.ObjID) (*dir, error) { - tree, err := sv.getFSTree() - if err != nil { - return nil, err - } - if ret, ok := sv.dirCache.Get(inode); ok { - return ret.(*dir), nil - } - ret := &dir{ - Inode: inode, - ChildrenByName: make(map[string]btrfsitem.DirEntry), - ChildrenByIndex: make(map[uint64]btrfsitem.DirEntry), - } - items, err := sv.FS.TreeSearchAll(tree, func(key btrfs.Key) int { - return util.CmpUint(inode, key.ObjectID) - }) - if err != nil { - if len(items) == 0 { - return nil, err + val := sv.dirCache.GetOrElse(inode, func() (val *dir) { + val = new(dir) + fullInode, err := sv.loadFullInode(inode) + if err != nil { + val.Errs = append(val.Errs, err) + return } - ret.Errs = append(ret.Errs, err) + val.fullInode = *fullInode + val.populate() + return + }) + if val.Inode == 0 { + return nil, val.Errs } - for _, item := range items { + return val, nil +} + +func (ret *dir) populate() { + ret.ChildrenByName = make(map[string]btrfsitem.DirEntry) + ret.ChildrenByIndex = make(map[uint64]btrfsitem.DirEntry) + for _, item := range ret.OtherItems { switch item.Head.Key.ItemType { - case btrfsitem.INODE_ITEM_KEY: - itemBody := item.Body.(btrfsitem.Inode) - if ret.InodeDat != nil { - if !reflect.DeepEqual(itemBody, *ret.InodeDat) { - ret.Errs = append(ret.Errs, fmt.Errorf("multiple inodes")) - } - continue - } - ret.InodeDat = &itemBody case btrfsitem.INODE_REF_KEY: // TODO case btrfsitem.DIR_ITEM_KEY: @@ -205,7 +259,7 @@ func (sv *Subvolume) loadDir(inode btrfs.ObjID) (*dir, error) { } //case btrfsitem.XATTR_ITEM_KEY: default: - ret.Errs = append(ret.Errs, fmt.Errorf("TODO: handle item type %v", item.Head.Key.ItemType)) + panic(fmt.Errorf("TODO: handle item type %v", item.Head.Key.ItemType)) } } entriesWithIndexes := make(map[string]struct{}) @@ -230,6 +284,23 @@ func (sv *Subvolume) loadDir(inode btrfs.ObjID) (*dir, error) { nextIndex++ } } - sv.dirCache.Add(inode, ret) - return ret, nil + return +} + +func (sv *Subvolume) loadFile(inode btrfs.ObjID) (*file, error) { + val := sv.fileCache.GetOrElse(inode, func() (val *file) { + val = new(file) + fullInode, err := sv.loadFullInode(inode) + if err != nil { + val.Errs = append(val.Errs, err) + return + } + val.fullInode = *fullInode + // TODO + return + }) + if val.Inode == 0 { + return nil, val.Errs + } + return val, nil } diff --git a/cmd/btrfs-mount/subvol_fuse.go b/cmd/btrfs-mount/subvol_fuse.go index 8227ee2..7500e0b 100644 --- a/cmd/btrfs-mount/subvol_fuse.go +++ b/cmd/btrfs-mount/subvol_fuse.go @@ -19,7 +19,7 @@ type dirState struct { } type fileState struct { - InodeItem btrfsitem.Inode + File *file } type subvolumeFUSE struct { @@ -29,8 +29,6 @@ type subvolumeFUSE struct { fileHandles util.SyncMap[fuseops.HandleID, *fileState] } -func (sv *subvolumeFUSE) init() {} - func (sv *subvolumeFUSE) newHandle() fuseops.HandleID { return fuseops.HandleID(atomic.AddUint64(&sv.lastHandle, 1)) } @@ -93,14 +91,14 @@ func (sv *Subvolume) LookUpInode(_ context.Context, op *fuseops.LookUpInodeOp) e if entry.Location.ItemType != btrfsitem.INODE_ITEM_KEY { return fmt.Errorf("child %q is not an inode: %w", op.Name, syscall.ENOSYS) } - inodeItem, err := sv.loadInode(entry.Location.ObjectID) + bareInode, err := sv.loadBareInode(entry.Location.ObjectID) if err != nil { return err } op.Entry = fuseops.ChildInodeEntry{ Child: fuseops.InodeID(entry.Location.ObjectID), - Generation: fuseops.GenerationNumber(inodeItem.Sequence), - Attributes: inodeItemToFUSE(inodeItem), + Generation: fuseops.GenerationNumber(bareInode.InodeItem.Sequence), + Attributes: inodeItemToFUSE(*bareInode.InodeItem), } return nil } @@ -114,12 +112,12 @@ func (sv *Subvolume) GetInodeAttributes(_ context.Context, op *fuseops.GetInodeA op.Inode = fuseops.InodeID(inode) } - inodeItem, err := sv.loadInode(btrfs.ObjID(op.Inode)) + bareInode, err := sv.loadBareInode(btrfs.ObjID(op.Inode)) if err != nil { return err } - op.Attributes = inodeItemToFUSE(inodeItem) + op.Attributes = inodeItemToFUSE(*bareInode.InodeItem) return nil } @@ -186,13 +184,13 @@ func (sv *Subvolume) ReleaseDirHandle(_ context.Context, op *fuseops.ReleaseDirH } func (sv *Subvolume) OpenFile(_ context.Context, op *fuseops.OpenFileOp) error { - inodeItem, err := sv.loadInode(btrfs.ObjID(op.Inode)) + file, err := sv.loadFile(btrfs.ObjID(op.Inode)) if err != nil { return err } handle := sv.newHandle() sv.fileHandles.Store(handle, &fileState{ - InodeItem: inodeItem, + File: file, }) op.Handle = handle op.KeepPageCache = true diff --git a/pkg/btrfs/btrfsitem/item_fileextent.go b/pkg/btrfs/btrfsitem/item_fileextent.go index 5e87021..f3cbffe 100644 --- a/pkg/btrfs/btrfsitem/item_fileextent.go +++ b/pkg/btrfs/btrfsitem/item_fileextent.go @@ -4,9 +4,12 @@ import ( "fmt" "lukeshu.com/btrfs-tools/pkg/binstruct" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfsvol" "lukeshu.com/btrfs-tools/pkg/btrfs/internal" ) +// key.objectid = inode +// key.offset = offset within file type FileExtent struct { // EXTENT_DATA=108 Generation internal.Generation `bin:"off=0x0, siz=0x8"` // transaction ID that created this extent RAMBytes int64 `bin:"off=0x8, siz=0x8"` // upper bound of what compressed data will decompress to @@ -23,23 +26,25 @@ type FileExtent struct { // EXTENT_DATA=108 // only one of these, depending on .Type BodyInline []byte `bin:"-"` BodyReg struct { - // Position within the device - DiskByteNr int64 `bin:"off=0x0, siz=0x8"` - DiskNumBytes int64 `bin:"off=0x8, siz=0x8"` + // 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"` - // Position within the file - Offset int64 `bin:"off=0x10, siz=0x8"` - NumBytes int64 `bin:"off=0x18, siz=0x8"` binstruct.End `bin:"off=0x20"` } `bin:"-"` BodyPrealloc struct { - // Position within the device - DiskByteNr int64 `bin:"off=0x0, siz=0x8"` - DiskNumBytes int64 `bin:"off=0x8, siz=0x8"` + // 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"` - // Position within the file - Offset int64 `bin:"off=0x10, siz=0x8"` - NumBytes int64 `bin:"off=0x18, siz=0x8"` binstruct.End `bin:"off=0x20"` } `bin:"-"` } diff --git a/pkg/btrfsmisc/print_tree.go b/pkg/btrfsmisc/print_tree.go index f78f653..6b4ff96 100644 --- a/pkg/btrfsmisc/print_tree.go +++ b/pkg/btrfsmisc/print_tree.go @@ -162,10 +162,10 @@ func PrintTree(fs *btrfs.FS, root btrfsvol.LogicalAddr) error { body.BodyPrealloc.Offset, body.BodyPrealloc.NumBytes) case btrfsitem.FILE_EXTENT_REG: - fmt.Printf("\t\textent data disk byte %v nr %v\n", + fmt.Printf("\t\textent data disk byte %d nr %d\n", body.BodyReg.DiskByteNr, body.BodyReg.DiskNumBytes) - fmt.Printf("\t\textent data offset %v nr %v ram %v\n", + fmt.Printf("\t\textent data offset %d nr %d ram %v\n", body.BodyReg.Offset, body.BodyReg.NumBytes, body.RAMBytes) |