summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-07-06 03:19:40 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-07-08 00:16:00 -0600
commit12d10ff2e3317c280e5f6ebfd913c73a6a1d896b (patch)
tree25d3decc4677d1d1f24b88e09e22ba958f6d082e
parent90e26899daa3013e4dd18df0ab507b6b59a20406 (diff)
subvolumes working, kinda
-rw-r--r--cmd/btrfs-mount/main.go4
-rw-r--r--cmd/btrfs-mount/subvol_fuse.go104
-rw-r--r--pkg/btrfs/btrfsitem/item_inoderef.go33
-rw-r--r--pkg/btrfs/btrfsitem/items.txt2
-rw-r--r--pkg/btrfs/btrfsitem/items_gen.go4
-rw-r--r--pkg/btrfs/io3_fs.go52
-rw-r--r--pkg/btrfsmisc/print_tree.go8
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: