summaryrefslogtreecommitdiff
path: root/cmd/btrfs-mount/subvol_fuse.go
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/btrfs-mount/subvol_fuse.go
parentbfbb9057c859b6d019e1a330dc648fe58f0b9a7e (diff)
more fuse
Diffstat (limited to 'cmd/btrfs-mount/subvol_fuse.go')
-rw-r--r--cmd/btrfs-mount/subvol_fuse.go196
1 files changed, 196 insertions, 0 deletions
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() {}