summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-07-05 05:30:43 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-07-05 05:35:12 -0600
commit4e34a9b4c901b0a7bd0256cc7e924c4bb4bf42fa (patch)
tree3f4ac8499017c3dd692137e0cc0a84a4dc46157a /cmd
parentbfbb9057c859b6d019e1a330dc648fe58f0b9a7e (diff)
more fuse
Diffstat (limited to 'cmd')
-rw-r--r--cmd/btrfs-mount/subvol.go206
-rw-r--r--cmd/btrfs-mount/subvol_fuse.go196
2 files changed, 332 insertions, 70 deletions
diff --git a/cmd/btrfs-mount/subvol.go b/cmd/btrfs-mount/subvol.go
index 7c6a7ac..e4e0460 100644
--- a/cmd/btrfs-mount/subvol.go
+++ b/cmd/btrfs-mount/subvol.go
@@ -3,17 +3,19 @@ package main
import (
"context"
"fmt"
+ "reflect"
"sync"
"github.com/datawire/dlib/dcontext"
"github.com/datawire/dlib/dlog"
+ lru "github.com/hashicorp/golang-lru"
"github.com/jacobsa/fuse"
- "github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil"
"lukeshu.com/btrfs-tools/pkg/btrfs"
"lukeshu.com/btrfs-tools/pkg/btrfs/btrfsitem"
"lukeshu.com/btrfs-tools/pkg/btrfs/btrfsvol"
+ "lukeshu.com/btrfs-tools/pkg/util"
)
type Subvolume struct {
@@ -22,11 +24,14 @@ type Subvolume struct {
Mountpoint string
TreeID btrfs.ObjID
- fuseutil.NotImplementedFileSystem
-
rootOnce sync.Once
rootVal btrfsitem.Root
rootErr error
+
+ inodeCache *lru.ARCCache
+ dirCache *lru.ARCCache
+
+ subvolumeFUSE
}
func (sv *Subvolume) Run(ctx context.Context) error {
@@ -49,7 +54,7 @@ func (sv *Subvolume) Run(ctx context.Context) error {
return mount.Join(dcontext.HardContext(ctx))
}
-func (sv *Subvolume) initRoot() {
+func (sv *Subvolume) init() {
sv.rootOnce.Do(func() {
sb, err := sv.FS.Superblock()
if err != nil {
@@ -74,96 +79,157 @@ func (sv *Subvolume) initRoot() {
}
sv.rootVal = rootBody
+
+ sv.inodeCache, _ = lru.NewARC(128)
+ sv.dirCache, _ = lru.NewARC(128)
+
+ sv.subvolumeFUSE.init()
})
}
func (sv *Subvolume) getRootInode() (btrfs.ObjID, error) {
- sv.initRoot()
+ sv.init()
return sv.rootVal.RootDirID, sv.rootErr
}
func (sv *Subvolume) getFSTree() (btrfsvol.LogicalAddr, error) {
- sv.initRoot()
+ sv.init()
return sv.rootVal.ByteNr, sv.rootErr
}
-func (sv *Subvolume) StatFS(_ context.Context, op *fuseops.StatFSOp) error {
- // See linux.git/fs/btrfs/super.c:btrfs_statfs()
- sb, err := sv.FS.Superblock()
- if err != nil {
- return err
- }
-
- op.IoSize = sb.Data.SectorSize
- op.BlockSize = sb.Data.SectorSize
- op.Blocks = sb.Data.TotalBytes / uint64(sb.Data.SectorSize) // TODO: adjust for RAID type
- //op.BlocksFree = TODO
-
- // btrfs doesn't have a fixed number of inodes
- op.Inodes = 0
- op.InodesFree = 0
-
- // jacobsa/fuse doesn't expose namelen, instead hard-coding it
- // to 255. Which is fine by us, because that's what it is for
- // btrfs.
-
- return nil
-}
-
-// func (sv *Subvolume) LookUpInode(_ context.Context, op *fuseops.LookUpInodeOp) error {}
-
-func (sv *Subvolume) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) error {
- if op.Inode == fuseops.RootInodeID {
- inode, err := sv.getRootInode()
- if err != nil {
- return err
- }
- op.Inode = fuseops.InodeID(inode)
- }
-
+func (sv *Subvolume) loadInode(inode btrfs.ObjID) (btrfsitem.Inode, error) {
tree, err := sv.getFSTree()
if err != nil {
- return err
+ 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: btrfs.ObjID(op.Inode),
+ ObjectID: inode,
ItemType: btrfsitem.INODE_ITEM_KEY,
Offset: 0,
})
if err != nil {
- return err
+ return btrfsitem.Inode{}, err
}
itemBody, ok := item.Body.(btrfsitem.Inode)
if !ok {
- return fmt.Errorf("malformed inode")
+ return btrfsitem.Inode{}, fmt.Errorf("malformed inode")
}
- op.Attributes = fuseops.InodeAttributes{
- Size: uint64(itemBody.Size),
- Nlink: uint32(itemBody.NLink),
- Mode: uint32(itemBody.Mode),
- //RDev: itemBody.Rdev, // jacobsa/fuse doesn't expose rdev
- Atime: itemBody.ATime.ToStd(),
- Mtime: itemBody.MTime.ToStd(),
- Ctime: itemBody.CTime.ToStd(),
- //Crtime: itemBody.OTime,
- Uid: uint32(itemBody.UID),
- Gid: uint32(itemBody.GID),
- }
- return nil
+ sv.inodeCache.Add(inode, itemBody)
+ return itemBody, 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) ForgetInode(_ context.Context, op *fuseops.ForgetInodeOp) error {}
-// func (sv *Subvolume) BatchForget(_ context.Context, op *fuseops.BatchForgetOp) error {}
-// func (sv *Subvolume) OpenDir(_ context.Context, op *fuseops.OpenDirOp) error {}
-// func (sv *Subvolume) ReadDir(_ context.Context, op *fuseops.ReadDirOp) error {}
-// func (sv *Subvolume) ReleaseDirHandle(_ context.Context, op *fuseops.ReleaseDirHandleOp) error {}
-// func (sv *Subvolume) OpenFile(_ context.Context, op *fuseops.OpenFileOp) error {}
-// func (sv *Subvolume) ReadFile(_ context.Context, op *fuseops.ReadFileOp) error {}
-// func (sv *Subvolume) ReleaseFileHandle(_ context.Context, op *fuseops.ReleaseFileHandleOp) error {}
-// func (sv *Subvolume) ReadSymlink(_ context.Context, op *fuseops.ReadSymlinkOp) error {}
-// func (sv *Subvolume) GetXattr(_ context.Context, op *fuseops.GetXattrOp) error {}
-// func (sv *Subvolume) ListXattr(_ context.Context, op *fuseops.ListXattrOp) error {}
-// func (sv *Subvolume) Destroy() {}
+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
+ }
+ ret.Errs = append(ret.Errs, err)
+ }
+ for _, item := range items {
+ 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:
+ body := item.Body.(btrfsitem.DirEntries)
+ if len(body) != 1 {
+ ret.Errs = append(ret.Errs, fmt.Errorf("multiple direntries in single DIR_ITEM?"))
+ continue
+ }
+ for _, entry := range body {
+ namehash := btrfsitem.NameHash(entry.Name)
+ if namehash != item.Head.Key.Offset {
+ ret.Errs = append(ret.Errs, fmt.Errorf("direntry crc32c mismatch: key=%#x crc32c(%q)=%#x",
+ item.Head.Key.Offset, entry.Name, namehash))
+ continue
+ }
+ if other, exists := ret.ChildrenByName[string(entry.Name)]; exists {
+ if !reflect.DeepEqual(entry, other) {
+ ret.Errs = append(ret.Errs, fmt.Errorf("multiple instances of direntry name %q", entry.Name))
+ }
+ continue
+ }
+ ret.ChildrenByName[string(entry.Name)] = entry
+ }
+ case btrfsitem.DIR_INDEX_KEY:
+ index := item.Head.Key.Offset
+ body := item.Body.(btrfsitem.DirEntries)
+ if len(body) != 1 {
+ ret.Errs = append(ret.Errs, fmt.Errorf("multiple direntries in single DIR_INDEX?"))
+ continue
+ }
+ for _, entry := range body {
+ if other, exists := ret.ChildrenByIndex[index]; exists {
+ if !reflect.DeepEqual(entry, other) {
+ ret.Errs = append(ret.Errs, fmt.Errorf("multiple instances of direntry index %v", index))
+ }
+ continue
+ }
+ ret.ChildrenByIndex[index] = entry
+ }
+ //case btrfsitem.XATTR_ITEM_KEY:
+ default:
+ ret.Errs = append(ret.Errs, fmt.Errorf("TODO: handle item type %v", item.Head.Key.ItemType))
+ }
+ }
+ entriesWithIndexes := make(map[string]struct{})
+ nextIndex := uint64(2)
+ for index, entry := range ret.ChildrenByIndex {
+ if index+1 > nextIndex {
+ nextIndex = index + 1
+ }
+ entriesWithIndexes[string(entry.Name)] = struct{}{}
+ if other, exists := ret.ChildrenByName[string(entry.Name)]; !exists {
+ ret.Errs = append(ret.Errs, fmt.Errorf("missing by-name direntry for %q", entry.Name))
+ ret.ChildrenByName[string(entry.Name)] = entry
+ } else if !reflect.DeepEqual(entry, other) {
+ ret.Errs = append(ret.Errs, fmt.Errorf("direntry index %v and direntry name %q disagree", index, entry.Name))
+ ret.ChildrenByName[string(entry.Name)] = entry
+ }
+ }
+ for _, name := range util.SortedMapKeys(ret.ChildrenByName) {
+ if _, exists := entriesWithIndexes[name]; !exists {
+ ret.Errs = append(ret.Errs, fmt.Errorf("missing by-index direntry for %q", name))
+ ret.ChildrenByIndex[nextIndex] = ret.ChildrenByName[name]
+ nextIndex++
+ }
+ }
+ sv.dirCache.Add(inode, ret)
+ return ret, nil
+}
diff --git a/cmd/btrfs-mount/subvol_fuse.go b/cmd/btrfs-mount/subvol_fuse.go
new file mode 100644
index 0000000..6ffc7d6
--- /dev/null
+++ b/cmd/btrfs-mount/subvol_fuse.go
@@ -0,0 +1,196 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "sync/atomic"
+ "syscall"
+
+ "github.com/jacobsa/fuse/fuseops"
+ "github.com/jacobsa/fuse/fuseutil"
+
+ "lukeshu.com/btrfs-tools/pkg/btrfs"
+ "lukeshu.com/btrfs-tools/pkg/btrfs/btrfsitem"
+ "lukeshu.com/btrfs-tools/pkg/util"
+)
+
+type dirState struct {
+ Dir *dir
+}
+
+type subvolumeFUSE struct {
+ fuseutil.NotImplementedFileSystem
+ lastHandle uint64
+ dirHandles util.SyncMap[uint64, *dirState]
+}
+
+func (sv *subvolumeFUSE) init() {}
+
+func inodeItemToFUSE(itemBody btrfsitem.Inode) fuseops.InodeAttributes {
+ return fuseops.InodeAttributes{
+ Size: uint64(itemBody.Size),
+ Nlink: uint32(itemBody.NLink),
+ Mode: uint32(itemBody.Mode),
+ //RDev: itemBody.Rdev, // jacobsa/fuse doesn't expose rdev
+ Atime: itemBody.ATime.ToStd(),
+ Mtime: itemBody.MTime.ToStd(),
+ Ctime: itemBody.CTime.ToStd(),
+ //Crtime: itemBody.OTime,
+ Uid: uint32(itemBody.UID),
+ Gid: uint32(itemBody.GID),
+ }
+}
+
+func (sv *Subvolume) StatFS(_ context.Context, op *fuseops.StatFSOp) error {
+ // See linux.git/fs/btrfs/super.c:btrfs_statfs()
+ sb, err := sv.FS.Superblock()
+ if err != nil {
+ return err
+ }
+
+ op.IoSize = sb.Data.SectorSize
+ op.BlockSize = sb.Data.SectorSize
+ op.Blocks = sb.Data.TotalBytes / uint64(sb.Data.SectorSize) // TODO: adjust for RAID type
+ //op.BlocksFree = TODO
+
+ // btrfs doesn't have a fixed number of inodes
+ op.Inodes = 0
+ op.InodesFree = 0
+
+ // jacobsa/fuse doesn't expose namelen, instead hard-coding it
+ // to 255. Which is fine by us, because that's what it is for
+ // btrfs.
+
+ return nil
+}
+
+func (sv *Subvolume) LookUpInode(_ context.Context, op *fuseops.LookUpInodeOp) error {
+ if op.Parent == fuseops.RootInodeID {
+ parent, err := sv.getRootInode()
+ if err != nil {
+ return err
+ }
+ op.Parent = fuseops.InodeID(parent)
+ }
+
+ dir, err := sv.loadDir(btrfs.ObjID(op.Parent))
+ if err != nil {
+ return err
+ }
+ entry, ok := dir.ChildrenByName[op.Name]
+ if !ok {
+ return syscall.ENOENT
+ }
+ 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)
+ if err != nil {
+ return err
+ }
+ op.Entry = fuseops.ChildInodeEntry{
+ Child: fuseops.InodeID(entry.Location.ObjectID),
+ Generation: fuseops.GenerationNumber(inodeItem.Sequence),
+ Attributes: inodeItemToFUSE(inodeItem),
+ }
+ return nil
+}
+
+func (sv *Subvolume) GetInodeAttributes(_ context.Context, op *fuseops.GetInodeAttributesOp) error {
+ if op.Inode == fuseops.RootInodeID {
+ inode, err := sv.getRootInode()
+ if err != nil {
+ return err
+ }
+ op.Inode = fuseops.InodeID(inode)
+ }
+
+ inodeItem, err := sv.loadInode(btrfs.ObjID(op.Inode))
+ if err != nil {
+ return err
+ }
+
+ op.Attributes = inodeItemToFUSE(inodeItem)
+ return nil
+}
+
+func (sv *Subvolume) OpenDir(_ context.Context, op *fuseops.OpenDirOp) error {
+ if op.Inode == fuseops.RootInodeID {
+ inode, err := sv.getRootInode()
+ if err != nil {
+ return err
+ }
+ op.Inode = fuseops.InodeID(inode)
+ }
+
+ dir, err := sv.loadDir(btrfs.ObjID(op.Inode))
+ if err != nil {
+ return err
+ }
+ handle := atomic.AddUint64(&sv.lastHandle, 1)
+ sv.dirHandles.Store(handle, &dirState{
+ Dir: dir,
+ })
+ op.Handle = fuseops.HandleID(handle)
+ return nil
+}
+
+func (sv *Subvolume) ReadDir(_ context.Context, op *fuseops.ReadDirOp) error {
+ state, ok := sv.dirHandles.Load(uint64(op.Handle))
+ if !ok {
+ return syscall.EBADF
+ }
+ indexes := util.SortedMapKeys(state.Dir.ChildrenByIndex)
+ origOffset := op.Offset
+ for _, index := range indexes {
+ if index < uint64(origOffset) {
+ continue
+ }
+ entry := state.Dir.ChildrenByIndex[index]
+ n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fuseutil.Dirent{
+ Offset: fuseops.DirOffset(index + 1),
+ Inode: fuseops.InodeID(entry.Location.ObjectID),
+ Name: string(entry.Name),
+ Type: map[btrfsitem.FileType]fuseutil.DirentType{
+ btrfsitem.FT_UNKNOWN: fuseutil.DT_Unknown,
+ btrfsitem.FT_REG_FILE: fuseutil.DT_File,
+ btrfsitem.FT_DIR: fuseutil.DT_Directory,
+ btrfsitem.FT_CHRDEV: fuseutil.DT_Char,
+ btrfsitem.FT_BLKDEV: fuseutil.DT_Block,
+ btrfsitem.FT_FIFO: fuseutil.DT_FIFO,
+ btrfsitem.FT_SOCK: fuseutil.DT_Socket,
+ btrfsitem.FT_SYMLINK: fuseutil.DT_Link,
+ }[entry.Type],
+ })
+ if n == 0 {
+ break
+ }
+ op.BytesRead += n
+ }
+ return nil
+}
+
+func (sv *Subvolume) ReleaseDirHandle(_ context.Context, op *fuseops.ReleaseDirHandleOp) error {
+ _, ok := sv.dirHandles.LoadAndDelete(uint64(op.Handle))
+ if !ok {
+ return syscall.EBADF
+ }
+ return nil
+}
+
+func (sv *Subvolume) OpenFile(_ context.Context, op *fuseops.OpenFileOp) error { return syscall.ENOSYS }
+func (sv *Subvolume) ReadFile(_ context.Context, op *fuseops.ReadFileOp) error { return syscall.ENOSYS }
+func (sv *Subvolume) ReleaseFileHandle(_ context.Context, op *fuseops.ReleaseFileHandleOp) error {
+ return syscall.ENOSYS
+}
+
+func (sv *Subvolume) ReadSymlink(_ context.Context, op *fuseops.ReadSymlinkOp) error {
+ return syscall.ENOSYS
+}
+
+func (sv *Subvolume) GetXattr(_ context.Context, op *fuseops.GetXattrOp) error { return syscall.ENOSYS }
+func (sv *Subvolume) ListXattr(_ context.Context, op *fuseops.ListXattrOp) error {
+ return syscall.ENOSYS
+}
+
+func (sv *Subvolume) Destroy() {}