From ba8fd9042ad4a5da3f5d2e3db6350793666ea403 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Mon, 20 Mar 2023 12:28:36 -0400 Subject: btrfstree: LookupTreeRoot: Don't discard the context of an error --- lib/btrfs/btrfstree/btree_forrest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/btrfs/btrfstree/btree_forrest.go b/lib/btrfs/btrfstree/btree_forrest.go index 8f8e2de..197edc2 100644 --- a/lib/btrfs/btrfstree/btree_forrest.go +++ b/lib/btrfs/btrfstree/btree_forrest.go @@ -59,7 +59,7 @@ func LookupTreeRoot(_ context.Context, fs TreeOperator, sb Superblock, treeID bt rootItem, err := fs.TreeSearch(btrfsprim.ROOT_TREE_OBJECTID, SearchRootItem(treeID)) if err != nil { if errors.Is(err, ErrNoItem) { - err = ErrNoTree + err = fmt.Errorf("%w: %s", ErrNoTree, err) } return nil, fmt.Errorf("tree %s: %w", treeID.Format(btrfsprim.ROOT_TREE_OBJECTID), err) } -- cgit v1.1-4-g5e80 From bbabc9ab42accbd37072735419cc0552dc8917ee Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Mon, 20 Mar 2023 12:29:20 -0400 Subject: btrfs: Subvolume: Use LookupTreeRoot --- cmd/btrfs-rec/inspect/lsfiles/lsfiles.go | 3 +++ cmd/btrfs-rec/inspect/mount/mount.go | 1 + cmd/btrfs-rec/inspect_lsfiles.go | 1 + lib/btrfs/btrfstree/btree_forrest.go | 3 +++ lib/btrfs/io4_fs.go | 31 ++++++++++++++++--------------- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/cmd/btrfs-rec/inspect/lsfiles/lsfiles.go b/cmd/btrfs-rec/inspect/lsfiles/lsfiles.go index a713b8a..e42050f 100644 --- a/cmd/btrfs-rec/inspect/lsfiles/lsfiles.go +++ b/cmd/btrfs-rec/inspect/lsfiles/lsfiles.go @@ -8,6 +8,7 @@ package lsfiles import ( + "context" "errors" "fmt" "io" @@ -27,6 +28,7 @@ import ( ) func LsFiles( + ctx context.Context, out io.Writer, fs interface { btrfstree.TreeOperator @@ -42,6 +44,7 @@ func LsFiles( }() printSubvol(out, "", true, "/", btrfs.NewSubvolume( + ctx, fs, btrfsprim.FS_TREE_OBJECTID, false, diff --git a/cmd/btrfs-rec/inspect/mount/mount.go b/cmd/btrfs-rec/inspect/mount/mount.go index 4049393..d4d2e0a 100644 --- a/cmd/btrfs-rec/inspect/mount/mount.go +++ b/cmd/btrfs-rec/inspect/mount/mount.go @@ -53,6 +53,7 @@ func MountRO(ctx context.Context, fs *btrfs.FS, mountpoint string, noChecksums b rootSubvol := &subvolume{ Subvolume: btrfs.NewSubvolume( + ctx, btrfsutil.NewOldRebuiltForrest(ctx, fs), btrfsprim.FS_TREE_OBJECTID, noChecksums, diff --git a/cmd/btrfs-rec/inspect_lsfiles.go b/cmd/btrfs-rec/inspect_lsfiles.go index 04b5ec5..00a4873 100644 --- a/cmd/btrfs-rec/inspect_lsfiles.go +++ b/cmd/btrfs-rec/inspect_lsfiles.go @@ -30,6 +30,7 @@ func init() { }() return lsfiles.LsFiles( + cmd.Context(), out, btrfsutil.NewOldRebuiltForrest(cmd.Context(), fs)) }), diff --git a/lib/btrfs/btrfstree/btree_forrest.go b/lib/btrfs/btrfstree/btree_forrest.go index 197edc2..38a2721 100644 --- a/lib/btrfs/btrfstree/btree_forrest.go +++ b/lib/btrfs/btrfstree/btree_forrest.go @@ -21,6 +21,8 @@ type TreeRoot struct { RootNode btrfsvol.LogicalAddr Level uint8 Generation btrfsprim.Generation + + RootInode btrfsprim.ObjID // only for subvolume trees } // LookupTreeRoot is a utility function to help with implementing the @@ -70,6 +72,7 @@ func LookupTreeRoot(_ context.Context, fs TreeOperator, sb Superblock, treeID bt RootNode: rootItemBody.ByteNr, Level: rootItemBody.Level, Generation: rootItemBody.Generation, + RootInode: rootItemBody.RootDirID, }, nil case *btrfsitem.Error: return nil, fmt.Errorf("malformed ROOT_ITEM for tree %v: %w", treeID, rootItemBody.Err) diff --git a/lib/btrfs/io4_fs.go b/lib/btrfs/io4_fs.go index 0445441..4a68695 100644 --- a/lib/btrfs/io4_fs.go +++ b/lib/btrfs/io4_fs.go @@ -5,6 +5,7 @@ package btrfs import ( + "context" "fmt" "io" "path/filepath" @@ -62,7 +63,8 @@ type File struct { } type Subvolume struct { - fs interface { + ctx context.Context //nolint:containedctx // don't have an option while keeping the same API + fs interface { btrfstree.TreeOperator Superblock() (*btrfstree.Superblock, error) diskio.ReaderAt[btrfsvol.LogicalAddr] @@ -70,8 +72,8 @@ type Subvolume struct { TreeID btrfsprim.ObjID noChecksums bool - rootVal btrfsitem.Root - rootErr error + rootInfo btrfstree.TreeRoot + rootErr error bareInodeCache containers.ARCache[btrfsprim.ObjID, *BareInode] fullInodeCache containers.ARCache[btrfsprim.ObjID, *FullInode] @@ -80,6 +82,7 @@ type Subvolume struct { } func NewSubvolume( + ctx context.Context, fs interface { btrfstree.TreeOperator Superblock() (*btrfstree.Superblock, error) @@ -94,19 +97,17 @@ func NewSubvolume( noChecksums: noChecksums, } - root, err := sv.fs.TreeSearch(btrfsprim.ROOT_TREE_OBJECTID, btrfstree.SearchRootItem(sv.TreeID)) + sb, err := sv.fs.Superblock() if err != nil { sv.rootErr = err - } else { - switch rootBody := root.Body.(type) { - case *btrfsitem.Root: - sv.rootVal = rootBody.Clone() - case *btrfsitem.Error: - sv.rootErr = fmt.Errorf("FS_TREE ROOT_ITEM has malformed body: %w", rootBody.Err) - default: - panic(fmt.Errorf("should not happen: ROOT_ITEM has unexpected item type: %T", rootBody)) - } + return sv + } + rootInfo, err := btrfstree.LookupTreeRoot(ctx, sv.fs, *sb, sv.TreeID) + if err != nil { + sv.rootErr = err + return sv } + sv.rootInfo = *rootInfo sv.bareInodeCache.MaxLen = textui.Tunable(128) sv.fullInodeCache.MaxLen = textui.Tunable(128) @@ -117,11 +118,11 @@ func NewSubvolume( } func (sv *Subvolume) NewChildSubvolume(childID btrfsprim.ObjID) *Subvolume { - return NewSubvolume(sv.fs, childID, sv.noChecksums) + return NewSubvolume(sv.ctx, sv.fs, childID, sv.noChecksums) } func (sv *Subvolume) GetRootInode() (btrfsprim.ObjID, error) { - return sv.rootVal.RootDirID, sv.rootErr + return sv.rootInfo.RootInode, sv.rootErr } func (sv *Subvolume) LoadBareInode(inode btrfsprim.ObjID) (*BareInode, error) { -- cgit v1.1-4-g5e80 From 5520c20c493d52d83288bc2e321117006e38fd1a Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 22 Mar 2023 21:28:59 -0400 Subject: btrfsutil: RebuiltTree: Allow nodes with .Gen == .ParentGen --- lib/btrfs/btrfsitem/item_root.go | 5 +++++ lib/btrfsutil/rebuilt_tree.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/btrfs/btrfsitem/item_root.go b/lib/btrfs/btrfsitem/item_root.go index 4ffad9a..8bdc3df 100644 --- a/lib/btrfs/btrfsitem/item_root.go +++ b/lib/btrfs/btrfsitem/item_root.go @@ -22,6 +22,11 @@ import ( // key.offset = one of: // - 0 if objectid is one of the BTRFS_*_TREE_OBJECTID defines or a non-snapshot volume; or // - transaction_id of when this snapshot was created +// +// This tree might contain nodes with node.Head.Owner set to the +// root.ParentUUID tree, *if* the node.Head.Generation is +// less-than-or-equal-to the root's key.offset. The "or-equal-to" +// part of that might be surprising, which is why I called it out. type Root struct { // trivial ROOT_ITEM=132 Inode Inode `bin:"off=0x000, siz=0xa0"` // ??? Generation btrfsprim.Generation `bin:"off=0x0a0, siz=0x08"` diff --git a/lib/btrfsutil/rebuilt_tree.go b/lib/btrfsutil/rebuilt_tree.go index 3fff9b2..96d5a75 100644 --- a/lib/btrfsutil/rebuilt_tree.go +++ b/lib/btrfsutil/rebuilt_tree.go @@ -122,7 +122,7 @@ func (tree *RebuiltTree) isOwnerOK(owner btrfsprim.ObjID, gen btrfsprim.Generati if owner == tree.ID { return true } - if tree.Parent == nil || gen >= tree.ParentGen { + if tree.Parent == nil || gen > tree.ParentGen { return false } tree = tree.Parent -- cgit v1.1-4-g5e80 From d11a5357df7d155a7a3e92a67971d3d52ff6ad86 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 22 Mar 2023 13:43:15 -0400 Subject: btrfstree: Add a 'generation' argument the owner validation Validating a node's owner properly requires the generation. But don't worry about actually doing it properly yet, just update the API first. --- lib/btrfs/btrfstree/readnode.go | 2 +- lib/btrfs/btrfstree/types_node.go | 4 ++-- lib/btrfsutil/old_rebuilt_forrest.go | 2 +- lib/btrfsutil/rebuilt_readitem.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/btrfs/btrfstree/readnode.go b/lib/btrfs/btrfstree/readnode.go index c2e3b0f..7cc42f5 100644 --- a/lib/btrfs/btrfstree/readnode.go +++ b/lib/btrfs/btrfstree/readnode.go @@ -41,7 +41,7 @@ func FSReadNode( } var treeParents []btrfsprim.ObjID - checkOwner := func(owner btrfsprim.ObjID) error { + checkOwner := func(owner btrfsprim.ObjID, _ btrfsprim.Generation) error { exp := path.Node(-1).FromTree for { if owner == exp { diff --git a/lib/btrfs/btrfstree/types_node.go b/lib/btrfs/btrfstree/types_node.go index 622f23c..bfcbbf4 100644 --- a/lib/btrfs/btrfstree/types_node.go +++ b/lib/btrfs/btrfstree/types_node.go @@ -417,7 +417,7 @@ type NodeExpectations struct { // Things knowable from the parent. Level containers.Optional[uint8] Generation containers.Optional[btrfsprim.Generation] - Owner func(btrfsprim.ObjID) error + Owner func(btrfsprim.ObjID, btrfsprim.Generation) error MinItem containers.Optional[btrfsprim.Key] // Things knowable from the structure of the tree. MaxItem containers.Optional[btrfsprim.Key] @@ -547,7 +547,7 @@ func (exp NodeExpectations) Check(node *Node) error { exp.Generation.Val, node.Head.Generation)) } if exp.Owner != nil { - if err := exp.Owner(node.Head.Owner); err != nil { + if err := exp.Owner(node.Head.Owner, node.Head.Generation); err != nil { errs = append(errs, err) } } diff --git a/lib/btrfsutil/old_rebuilt_forrest.go b/lib/btrfsutil/old_rebuilt_forrest.go index bb5ab59..a3fc0e2 100644 --- a/lib/btrfsutil/old_rebuilt_forrest.go +++ b/lib/btrfsutil/old_rebuilt_forrest.go @@ -240,7 +240,7 @@ func (bt *OldRebuiltForrest) readNode(nodeInfo nodeInfo) *btrfstree.Node { LAddr: containers.OptionalValue(nodeInfo.LAddr), Level: containers.OptionalValue(nodeInfo.Level), Generation: containers.OptionalValue(nodeInfo.Generation), - Owner: func(treeID btrfsprim.ObjID) error { + Owner: func(treeID btrfsprim.ObjID, _ btrfsprim.Generation) error { if treeID != nodeInfo.Owner { return fmt.Errorf("expected owner=%v but claims to have owner=%v", nodeInfo.Owner, treeID) diff --git a/lib/btrfsutil/rebuilt_readitem.go b/lib/btrfsutil/rebuilt_readitem.go index ff919f0..5de6864 100644 --- a/lib/btrfsutil/rebuilt_readitem.go +++ b/lib/btrfsutil/rebuilt_readitem.go @@ -42,7 +42,7 @@ func (ts *RebuiltForrest) readNode(ctx context.Context, laddr btrfsvol.LogicalAd LAddr: containers.OptionalValue(laddr), Level: containers.OptionalValue(graphInfo.Level), Generation: containers.OptionalValue(graphInfo.Generation), - Owner: func(treeID btrfsprim.ObjID) error { + Owner: func(treeID btrfsprim.ObjID, _ btrfsprim.Generation) error { if treeID != graphInfo.Owner { return fmt.Errorf("expected owner=%v but claims to have owner=%v", graphInfo.Owner, treeID) -- cgit v1.1-4-g5e80 From 418553acc64567ebc95122e28b07657526c92923 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 22 Mar 2023 17:25:54 -0400 Subject: btrfs: Consider the generation when checking if a node owner is OK --- lib/btrfs/btrfstree/readnode.go | 34 ++++++++++++++++++++------------- lib/btrfs/io2_lv.go | 3 +-- lib/btrfs/io3_btree.go | 37 ++++++++++++++++++++++-------------- lib/btrfsutil/old_rebuilt_forrest.go | 9 +++++---- lib/btrfsutil/rebuilt_readitem.go | 9 +++++---- 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/lib/btrfs/btrfstree/readnode.go b/lib/btrfs/btrfstree/readnode.go index 7cc42f5..ac82c62 100644 --- a/lib/btrfs/btrfstree/readnode.go +++ b/lib/btrfs/btrfstree/readnode.go @@ -20,13 +20,13 @@ type NodeFile interface { // ParentTree, given a tree ID, returns that tree's parent // tree, if it has one. // - // - non-zero, true : the parent tree ID + // - non-zero, ?, true : the parent tree ID // - // - 0, true : the tree does not have a parent + // - 0, 0, true : the tree does not have a parent // - // - any, false : the tree's parent information could not be + // - ?, ?, false : the tree's parent information could not be // looked up - ParentTree(btrfsprim.ObjID) (btrfsprim.ObjID, bool) + ParentTree(btrfsprim.ObjID) (btrfsprim.ObjID, btrfsprim.Generation, bool) } // FSReadNode is a utility function to help with implementing the @@ -40,25 +40,33 @@ func FSReadNode( return nil, fmt.Errorf("btrfs.FS.ReadNode: %w", err) } - var treeParents []btrfsprim.ObjID - checkOwner := func(owner btrfsprim.ObjID, _ btrfsprim.Generation) error { - exp := path.Node(-1).FromTree + checkOwner := func(owner btrfsprim.ObjID, gen btrfsprim.Generation) error { + var treeParents []btrfsprim.ObjID + + tree := path.Node(-1).FromTree for { - if owner == exp { + if owner == tree { + // OK! return nil } - treeParents = append(treeParents, exp) - var ok bool - exp, ok = fs.ParentTree(exp) - if !ok { + + treeParents = append(treeParents, tree) + parent, parentGen, parentOK := fs.ParentTree(tree) + if !parentOK { // Failed look up parent info; fail open. return nil } - if exp == 0 { + + if parent == 0 { // End of the line. return fmt.Errorf("expected owner in %v but claims to have owner=%v", treeParents, owner) } + if gen > parentGen { + return fmt.Errorf("claimed owner=%v might be acceptable in this tree (if generation<=%v) but not with claimed generation=%v", + owner, parentGen, gen) + } + tree = parent } } diff --git a/lib/btrfs/io2_lv.go b/lib/btrfs/io2_lv.go index 9f4e53f..03b2107 100644 --- a/lib/btrfs/io2_lv.go +++ b/lib/btrfs/io2_lv.go @@ -27,9 +27,8 @@ type FS struct { cacheSuperblocks []*diskio.Ref[btrfsvol.PhysicalAddr, btrfstree.Superblock] cacheSuperblock *btrfstree.Superblock - cacheObjID2UUID map[btrfsprim.ObjID]btrfsprim.UUID + cacheObjID2All map[btrfsprim.ObjID]treeInfo cacheUUID2ObjID map[btrfsprim.UUID]btrfsprim.ObjID - cacheTreeParent map[btrfsprim.ObjID]btrfsprim.UUID } var _ diskio.File[btrfsvol.LogicalAddr] = (*FS)(nil) diff --git a/lib/btrfs/io3_btree.go b/lib/btrfs/io3_btree.go index 6df88f5..b60f54a 100644 --- a/lib/btrfs/io3_btree.go +++ b/lib/btrfs/io3_btree.go @@ -25,13 +25,18 @@ var _ btrfstree.NodeSource = (*FS)(nil) // btrfstree.NodeFile ////////////////////////////////////////////////////////// +type treeInfo struct { + UUID btrfsprim.UUID + ParentUUID btrfsprim.UUID + ParentGen btrfsprim.Generation +} + func (fs *FS) populateTreeUUIDs(ctx context.Context) { - if fs.cacheObjID2UUID != nil && fs.cacheUUID2ObjID != nil && fs.cacheTreeParent != nil { + if fs.cacheObjID2All != nil && fs.cacheUUID2ObjID != nil { return } - fs.cacheObjID2UUID = make(map[btrfsprim.ObjID]btrfsprim.UUID) + fs.cacheObjID2All = make(map[btrfsprim.ObjID]treeInfo) fs.cacheUUID2ObjID = make(map[btrfsprim.UUID]btrfsprim.ObjID) - fs.cacheTreeParent = make(map[btrfsprim.ObjID]btrfsprim.UUID) fs.TreeWalk(ctx, btrfsprim.ROOT_TREE_OBJECTID, func(err *btrfstree.TreeError) { // do nothing @@ -42,8 +47,11 @@ func (fs *FS) populateTreeUUIDs(ctx context.Context) { if !ok { return } - fs.cacheObjID2UUID[item.Key.ObjectID] = itemBody.UUID - fs.cacheTreeParent[item.Key.ObjectID] = itemBody.ParentUUID + fs.cacheObjID2All[item.Key.ObjectID] = treeInfo{ + UUID: itemBody.UUID, + ParentUUID: itemBody.ParentUUID, + ParentGen: btrfsprim.Generation(item.Key.Offset), + } fs.cacheUUID2ObjID[itemBody.UUID] = item.Key.ObjectID }, }, @@ -51,27 +59,28 @@ func (fs *FS) populateTreeUUIDs(ctx context.Context) { } // ParentTree implements btrfstree.NodeFile. -func (fs *FS) ParentTree(tree btrfsprim.ObjID) (btrfsprim.ObjID, bool) { +func (fs *FS) ParentTree(tree btrfsprim.ObjID) (btrfsprim.ObjID, btrfsprim.Generation, bool) { if tree < btrfsprim.FIRST_FREE_OBJECTID || tree > btrfsprim.LAST_FREE_OBJECTID { // no parent - return 0, true + return 0, 0, true } fs.populateTreeUUIDs(context.TODO()) - parentUUID, ok := fs.cacheTreeParent[tree] + + all, ok := fs.cacheObjID2All[tree] if !ok { // could not look up parent info - return 0, false + return 0, 0, false } - if parentUUID == (btrfsprim.UUID{}) { + if all.ParentUUID == (btrfsprim.UUID{}) { // no parent - return 0, true + return 0, 0, true } - parentObjID, ok := fs.cacheUUID2ObjID[parentUUID] + parentObjID, ok := fs.cacheUUID2ObjID[all.ParentUUID] if !ok { // could not look up parent info - return 0, false + return 0, 0, false } - return parentObjID, true + return parentObjID, all.ParentGen, true } var _ btrfstree.NodeFile = (*FS)(nil) diff --git a/lib/btrfsutil/old_rebuilt_forrest.go b/lib/btrfsutil/old_rebuilt_forrest.go index a3fc0e2..5b99892 100644 --- a/lib/btrfsutil/old_rebuilt_forrest.go +++ b/lib/btrfsutil/old_rebuilt_forrest.go @@ -240,10 +240,11 @@ func (bt *OldRebuiltForrest) readNode(nodeInfo nodeInfo) *btrfstree.Node { LAddr: containers.OptionalValue(nodeInfo.LAddr), Level: containers.OptionalValue(nodeInfo.Level), Generation: containers.OptionalValue(nodeInfo.Generation), - Owner: func(treeID btrfsprim.ObjID, _ btrfsprim.Generation) error { - if treeID != nodeInfo.Owner { - return fmt.Errorf("expected owner=%v but claims to have owner=%v", - nodeInfo.Owner, treeID) + Owner: func(treeID btrfsprim.ObjID, gen btrfsprim.Generation) error { + if treeID != nodeInfo.Owner || gen != nodeInfo.Generation { + return fmt.Errorf("expected owner=%v generation=%v but claims to have owner=%v generation=%v", + nodeInfo.Owner, nodeInfo.Generation, + treeID, gen) } return nil }, diff --git a/lib/btrfsutil/rebuilt_readitem.go b/lib/btrfsutil/rebuilt_readitem.go index 5de6864..03a7cdc 100644 --- a/lib/btrfsutil/rebuilt_readitem.go +++ b/lib/btrfsutil/rebuilt_readitem.go @@ -42,10 +42,11 @@ func (ts *RebuiltForrest) readNode(ctx context.Context, laddr btrfsvol.LogicalAd LAddr: containers.OptionalValue(laddr), Level: containers.OptionalValue(graphInfo.Level), Generation: containers.OptionalValue(graphInfo.Generation), - Owner: func(treeID btrfsprim.ObjID, _ btrfsprim.Generation) error { - if treeID != graphInfo.Owner { - return fmt.Errorf("expected owner=%v but claims to have owner=%v", - graphInfo.Owner, treeID) + Owner: func(treeID btrfsprim.ObjID, gen btrfsprim.Generation) error { + if treeID != graphInfo.Owner || gen != graphInfo.Generation { + return fmt.Errorf("expected owner=%v generation=%v but claims to have owner=%v generation=%v", + graphInfo.Owner, graphInfo.Generation, + treeID, gen) } return nil }, -- cgit v1.1-4-g5e80