From 12d10ff2e3317c280e5f6ebfd913c73a6a1d896b Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 6 Jul 2022 03:19:40 -0600 Subject: subvolumes working, kinda --- cmd/btrfs-mount/main.go | 4 +- cmd/btrfs-mount/subvol_fuse.go | 104 +++++++++++++++++++++++++++++++---- pkg/btrfs/btrfsitem/item_inoderef.go | 33 +---------- pkg/btrfs/btrfsitem/items.txt | 2 +- pkg/btrfs/btrfsitem/items_gen.go | 4 +- pkg/btrfs/io3_fs.go | 52 ++++++++++++++++-- pkg/btrfsmisc/print_tree.go | 8 +-- 7 files changed, 152 insertions(+), 55 deletions(-) diff --git a/cmd/btrfs-mount/main.go b/cmd/btrfs-mount/main.go index 64cae30..4451e7a 100644 --- a/cmd/btrfs-mount/main.go +++ b/cmd/btrfs-mount/main.go @@ -68,7 +68,7 @@ func Main(ctx context.Context, mountpoint string, imgfilenames ...string) (err e } return fuse.Unmount(os.Args[1]) }) - grp.Go("main", func(ctx context.Context) error { + grp.Go("mount", func(ctx context.Context) error { defer atomic.StoreUint32(&mounted, 0) rootSubvol := &Subvolume{ Subvolume: btrfs.Subvolume{ @@ -78,7 +78,7 @@ func Main(ctx context.Context, mountpoint string, imgfilenames ...string) (err e DeviceName: tryAbs(imgfilenames[0]), Mountpoint: mountpoint, } - return rootSubvol.Run(ctx) + return rootSubvol.Run(ctx, false) }) return grp.Wait() } diff --git a/cmd/btrfs-mount/subvol_fuse.go b/cmd/btrfs-mount/subvol_fuse.go index 3a22c9a..522da85 100644 --- a/cmd/btrfs-mount/subvol_fuse.go +++ b/cmd/btrfs-mount/subvol_fuse.go @@ -2,11 +2,16 @@ package main import ( "context" + "errors" "fmt" + "io" + "path/filepath" + "sync" "sync/atomic" "syscall" "github.com/datawire/dlib/dcontext" + "github.com/datawire/dlib/dgroup" "github.com/datawire/dlib/dlog" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" @@ -14,6 +19,7 @@ import ( "lukeshu.com/btrfs-tools/pkg/btrfs" "lukeshu.com/btrfs-tools/pkg/btrfs/btrfsitem" + "lukeshu.com/btrfs-tools/pkg/linux" "lukeshu.com/btrfs-tools/pkg/util" ) @@ -34,13 +40,16 @@ type Subvolume struct { lastHandle uint64 dirHandles util.SyncMap[fuseops.HandleID, *dirState] fileHandles util.SyncMap[fuseops.HandleID, *fileState] + + subvolMu sync.Mutex + subvols map[string]struct{} + grp *dgroup.Group } -func (sv *Subvolume) Run(ctx context.Context) error { - mount, err := fuse.Mount( - sv.Mountpoint, - fuseutil.NewFileSystemServer(sv), - &fuse.MountConfig{ +func (sv *Subvolume) Run(ctx context.Context, isSubvol bool) error { + sv.grp = dgroup.NewGroup(ctx, dgroup.GroupConfig{}) + sv.grp.Go("self", func(ctx context.Context) error { + cfg := &fuse.MountConfig{ OpContext: ctx, ErrorLogger: dlog.StdLogger(ctx, dlog.LogLevelError), DebugLogger: dlog.StdLogger(ctx, dlog.LogLevelDebug), @@ -49,11 +58,24 @@ func (sv *Subvolume) Run(ctx context.Context) error { Subtype: "btrfs", ReadOnly: true, - }) - if err != nil { - return err - } - return mount.Join(dcontext.HardContext(ctx)) + + Options: map[string]string{ + "allow_other": "", + }, + } + if isSubvol { + //cfg.Options["nonempty"] = "" + } + mount, err := fuse.Mount( + sv.Mountpoint, + fuseutil.NewFileSystemServer(sv), + cfg) + if err != nil { + return err + } + return mount.Join(dcontext.HardContext(ctx)) + }) + return sv.grp.Wait() } func (sv *Subvolume) newHandle() fuseops.HandleID { @@ -75,6 +97,54 @@ func inodeItemToFUSE(itemBody btrfsitem.Inode) fuseops.InodeAttributes { } } +func (sv *Subvolume) LoadDir(inode btrfs.ObjID) (val *btrfs.Dir, err error) { + val, err = sv.Subvolume.LoadDir(inode) + if val != nil { + haveSubvolumes := false + for _, index := range util.SortedMapKeys(val.ChildrenByIndex) { + entry := val.ChildrenByIndex[index] + if entry.Location.ItemType == btrfsitem.ROOT_ITEM_KEY { + haveSubvolumes = true + break + } + } + if haveSubvolumes { + abspath, _err := val.AbsPath() + if _err != nil { + return + } + sv.subvolMu.Lock() + for _, index := range util.SortedMapKeys(val.ChildrenByIndex) { + entry := val.ChildrenByIndex[index] + if entry.Location.ItemType != btrfsitem.ROOT_ITEM_KEY { + continue + } + if sv.subvols == nil { + sv.subvols = make(map[string]struct{}) + } + subMountpoint := filepath.Join(abspath, string(entry.Name)) + if _, alreadyMounted := sv.subvols[subMountpoint]; !alreadyMounted { + sv.subvols[subMountpoint] = struct{}{} + workerName := fmt.Sprintf("%d-%s", val.Inode, filepath.Base(subMountpoint)) + sv.grp.Go(workerName, func(ctx context.Context) error { + subSv := &Subvolume{ + Subvolume: btrfs.Subvolume{ + FS: sv.FS, + TreeID: entry.Location.ObjectID, + }, + DeviceName: sv.DeviceName, + Mountpoint: filepath.Join(sv.Mountpoint, subMountpoint[1:]), + } + return subSv.Run(ctx, true) + }) + } + } + sv.subvolMu.Unlock() + } + } + return +} + func (sv *Subvolume) StatFS(_ context.Context, op *fuseops.StatFSOp) error { // See linux.git/fs/btrfs/super.c:btrfs_statfs() sb, err := sv.FS.Superblock() @@ -116,7 +186,16 @@ func (sv *Subvolume) LookUpInode(_ context.Context, op *fuseops.LookUpInodeOp) e 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) + op.Entry = fuseops.ChildInodeEntry{ + Child: 2, // an inode number that a real file will never have + Attributes: fuseops.InodeAttributes{ + Nlink: 1, + Mode: uint32(linux.ModeFmtDir | 0700), + //Uid: 1000, // TODO + //Gid: 1000, // TODO + }, + } + return nil } bareInode, err := sv.LoadBareInode(entry.Location.ObjectID) if err != nil { @@ -240,6 +319,9 @@ func (sv *Subvolume) ReadFile(_ context.Context, op *fuseops.ReadFileOp) error { var err error op.BytesRead, err = state.File.ReadAt(dat, op.Offset) + if errors.Is(err, io.EOF) { + err = nil + } return err } diff --git a/pkg/btrfs/btrfsitem/item_inoderef.go b/pkg/btrfs/btrfsitem/item_inoderef.go index 78dd677..2c26df6 100644 --- a/pkg/btrfs/btrfsitem/item_inoderef.go +++ b/pkg/btrfs/btrfsitem/item_inoderef.go @@ -4,36 +4,9 @@ import ( "lukeshu.com/btrfs-tools/pkg/binstruct" ) -type InodeRefList []InodeRef // INODE_REF=12 - -func (o *InodeRefList) UnmarshalBinary(dat []byte) (int, error) { - *o = nil - n := 0 - for n < len(dat) { - var ref InodeRef - _n, err := binstruct.Unmarshal(dat, &ref) - n += _n - if err != nil { - return n, err - } - *o = append(*o, ref) - } - return n, nil -} - -func (o InodeRefList) MarshalBinary() ([]byte, error) { - var ret []byte - for _, ref := range o { - bs, err := binstruct.Marshal(ref) - ret = append(ret, bs...) - if err != nil { - return ret, err - } - } - return ret, nil -} - -type InodeRef struct { +// key.objectid = inode number of the file +// key.offset = inode number of the parent file +type InodeRef struct { // INODE_REF=12 Index int64 `bin:"off=0x0, siz=0x8"` NameLen uint16 `bin:"off=0x8, siz=0x2"` // [ignored-when-writing] binstruct.End `bin:"off=0xa"` diff --git a/pkg/btrfs/btrfsitem/items.txt b/pkg/btrfs/btrfsitem/items.txt index 2820788..7898775 100644 --- a/pkg/btrfs/btrfsitem/items.txt +++ b/pkg/btrfs/btrfsitem/items.txt @@ -12,7 +12,7 @@ FREE_SPACE_BITMAP=200 FreeSpaceBitmap FREE_SPACE_EXTENT=199 Empty FREE_SPACE_INFO=198 FreeSpaceInfo INODE_ITEM=1 Inode -INODE_REF=12 InodeRefList +INODE_REF=12 InodeRef METADATA_ITEM=169 Metadata ORPHAN_ITEM=48 Empty PERSISTENT_ITEM=249 DevStats diff --git a/pkg/btrfs/btrfsitem/items_gen.go b/pkg/btrfs/btrfsitem/items_gen.go index 030202b..b5b64bd 100644 --- a/pkg/btrfs/btrfsitem/items_gen.go +++ b/pkg/btrfs/btrfsitem/items_gen.go @@ -55,7 +55,7 @@ var keytype2gotype = map[Type]reflect.Type{ FREE_SPACE_EXTENT_KEY: reflect.TypeOf(Empty{}), FREE_SPACE_INFO_KEY: reflect.TypeOf(FreeSpaceInfo{}), INODE_ITEM_KEY: reflect.TypeOf(Inode{}), - INODE_REF_KEY: reflect.TypeOf(InodeRefList{}), + INODE_REF_KEY: reflect.TypeOf(InodeRef{}), METADATA_ITEM_KEY: reflect.TypeOf(Metadata{}), ORPHAN_ITEM_KEY: reflect.TypeOf(Empty{}), PERSISTENT_ITEM_KEY: reflect.TypeOf(DevStats{}), @@ -89,7 +89,7 @@ func (FreeSpaceBitmap) isItem() {} func (FreeSpaceHeader) isItem() {} func (FreeSpaceInfo) isItem() {} func (Inode) isItem() {} -func (InodeRefList) isItem() {} +func (InodeRef) isItem() {} func (Metadata) isItem() {} func (Root) isItem() {} func (RootRef) isItem() {} diff --git a/pkg/btrfs/io3_fs.go b/pkg/btrfs/io3_fs.go index a418b05..75b973b 100644 --- a/pkg/btrfs/io3_fs.go +++ b/pkg/btrfs/io3_fs.go @@ -3,6 +3,7 @@ package btrfs import ( "fmt" "io" + "path/filepath" "reflect" "sort" "sync" @@ -25,10 +26,17 @@ type FullInode struct { OtherItems []Item } +type InodeRef struct { + Inode ObjID + btrfsitem.InodeRef +} + type Dir struct { FullInode + DotDot *InodeRef ChildrenByName map[string]btrfsitem.DirEntry ChildrenByIndex map[uint64]btrfsitem.DirEntry + SV *Subvolume } type FileExtent struct { @@ -39,7 +47,7 @@ type FileExtent struct { type File struct { FullInode Extents []FileExtent - FS *FS + SV *Subvolume } type Subvolume struct { @@ -182,6 +190,7 @@ func (sv *Subvolume) LoadDir(inode ObjID) (*Dir, error) { return } val.FullInode = *fullInode + val.SV = sv val.populate() return }) @@ -197,7 +206,17 @@ func (ret *Dir) populate() { for _, item := range ret.OtherItems { switch item.Head.Key.ItemType { case btrfsitem.INODE_REF_KEY: - // TODO + ref := InodeRef{ + Inode: ObjID(item.Head.Key.Offset), + InodeRef: item.Body.(btrfsitem.InodeRef), + } + if ret.DotDot != nil { + if !reflect.DeepEqual(ref, *ret.DotDot) { + ret.Errs = append(ret.Errs, fmt.Errorf("multiple INODE_REF items on a directory")) + } + continue + } + ret.DotDot = &ref case btrfsitem.DIR_ITEM_KEY: body := item.Body.(btrfsitem.DirEntries) if len(body) != 1 { @@ -265,6 +284,28 @@ func (ret *Dir) populate() { return } +func (dir *Dir) AbsPath() (string, error) { + rootInode, err := dir.SV.GetRootInode() + if err != nil { + return "", err + } + if rootInode == dir.Inode { + return "/", nil + } + if dir.DotDot == nil { + return "", fmt.Errorf("missing .. entry in dir inode %v", dir.Inode) + } + parent, err := dir.SV.LoadDir(dir.DotDot.Inode) + if err != nil { + return "", err + } + parentName, err := parent.AbsPath() + if err != nil { + return "", err + } + return filepath.Join(parentName, string(dir.DotDot.Name)), nil +} + func (sv *Subvolume) LoadFile(inode ObjID) (*File, error) { val := sv.fileCache.GetOrElse(inode, func() (val *File) { val = new(File) @@ -274,7 +315,7 @@ func (sv *Subvolume) LoadFile(inode ObjID) (*File, error) { return } val.FullInode = *fullInode - val.FS = sv.FS + val.SV = sv val.populate() return }) @@ -370,12 +411,15 @@ func (file *File) maybeShortReadAt(dat []byte, off int64) (int, error) { 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], + return file.SV.FS.ReadAt(dat[:readSize], extent.BodyExtent.DiskByteNr. Add(extent.BodyExtent.Offset). Add(btrfsvol.AddrDelta(offsetWithinExt))) } } + if file.InodeItem != nil && off >= file.InodeItem.Size { + return 0, io.EOF + } return 0, fmt.Errorf("read: could not map position %v", off) } diff --git a/pkg/btrfsmisc/print_tree.go b/pkg/btrfsmisc/print_tree.go index 61c5423..c03cb4a 100644 --- a/pkg/btrfsmisc/print_tree.go +++ b/pkg/btrfsmisc/print_tree.go @@ -57,11 +57,9 @@ func PrintTree(fs *btrfs.FS, root btrfsvol.LogicalAddr) error { fmt.Printf("\t\tctime %v\n", fmtTime(body.CTime)) fmt.Printf("\t\tmtime %v\n", fmtTime(body.MTime)) fmt.Printf("\t\totime %v\n", fmtTime(body.OTime)) - case btrfsitem.InodeRefList: - for _, ref := range body { - fmt.Printf("\t\tindex %v namelen %v name: %s\n", - ref.Index, ref.NameLen, ref.Name) - } + case btrfsitem.InodeRef: + fmt.Printf("\t\tindex %v namelen %v name: %s\n", + body.Index, body.NameLen, body.Name) //case btrfsitem.INODE_EXTREF_KEY: // // TODO case btrfsitem.DirEntries: -- cgit v1.2.3-2-g168b