diff options
-rw-r--r-- | cmd/btrfs-clear-bad-nodes/main.go | 94 | ||||
-rw-r--r-- | pkg/btrfs/io3_btree.go | 44 | ||||
-rw-r--r-- | pkg/btrfs/io4_fs.go (renamed from pkg/btrfs/io3_fs.go) | 0 | ||||
-rw-r--r-- | pkg/btrfs/types_node.go | 11 | ||||
-rw-r--r-- | pkg/btrfsmisc/walk.go | 59 | ||||
-rwxr-xr-x | scripts/run-clear-nodes.sh | 5 |
6 files changed, 191 insertions, 22 deletions
diff --git a/cmd/btrfs-clear-bad-nodes/main.go b/cmd/btrfs-clear-bad-nodes/main.go new file mode 100644 index 0000000..754e08b --- /dev/null +++ b/cmd/btrfs-clear-bad-nodes/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + "os" + + "lukeshu.com/btrfs-tools/pkg/btrfs" + "lukeshu.com/btrfs-tools/pkg/btrfs/btrfsvol" + "lukeshu.com/btrfs-tools/pkg/btrfsmisc" + "lukeshu.com/btrfs-tools/pkg/util" +) + +func main() { + if err := Main(os.Args[1:]...); err != nil { + fmt.Fprintf(os.Stderr, "%v: error: %v\n", os.Args[0], err) + os.Exit(1) + } +} + +func Main(imgfilenames ...string) (err error) { + maybeSetErr := func(_err error) { + if _err != nil && err == nil { + err = _err + } + } + + fs, err := btrfsmisc.Open(os.O_RDWR, imgfilenames...) + if err != nil { + return err + } + defer func() { + maybeSetErr(fs.Close()) + }() + + var treeName string + btrfsmisc.WalkFS(fs, btrfsmisc.WalkFSHandler{ + PreTree: func(name string, _ btrfsvol.LogicalAddr) { + treeName = name + }, + Err: func(err error) { + fmt.Printf("error: %v\n", err) + }, + UnsafeNodes: true, + TreeWalkHandler: btrfs.TreeWalkHandler{ + Node: func(path btrfs.TreePath, node *util.Ref[btrfsvol.LogicalAddr, btrfs.Node], err error) error { + if node == nil || err == nil { + return nil + } + origErr := err + if len(path) < 2 { + // TODO(lukeshu): Get info from the superblock and such + // instead of the parent node, so that we can repair broken + // root nodes. + return fmt.Errorf("root node: %w", err) + } + parentNode, err := fs.ReadNode(path[len(path)-2].NodeAddr) + if err != nil { + return err + } + node.Data = btrfs.Node{ + Size: node.Data.Size, + ChecksumType: node.Data.ChecksumType, + Head: btrfs.NodeHeader{ + //Checksum: filled below, + MetadataUUID: parentNode.Data.Head.MetadataUUID, + Addr: node.Addr, + Flags: btrfs.NodeWritten, + BackrefRev: parentNode.Data.Head.BackrefRev, + Generation: 0, + Owner: parentNode.Data.Head.Owner, + NumItems: 0, + Level: parentNode.Data.Head.Level - 1, + }, + } + node.Data.Head.Checksum, err = node.Data.CalculateChecksum() + if err != nil { + return btrfsmisc.WalkErr{ + TreeName: treeName, + Path: path, + Err: err, + } + } + if err := node.Write(); err != nil { + return err + } + + fmt.Printf("fixed node@%v (err was %v)\n", node.Addr, origErr) + return nil + }, + }, + }) + + return nil +} diff --git a/pkg/btrfs/io3_btree.go b/pkg/btrfs/io3_btree.go index 01da746..3c1535b 100644 --- a/pkg/btrfs/io3_btree.go +++ b/pkg/btrfs/io3_btree.go @@ -19,9 +19,49 @@ import ( // - For .Item() callbacks, the last element will always have a // NodeAddr of 0. // -// ex: +// For example, given the tree structure // -// {-1, 0x01, 3}→{9, 0x02, 2}→{9, 0x03, 1}→{9, 0x04, 0}→{9, 0, 0} +// [superblock] +// | +// | <------------------------------------------ pathElem={idx:-1, addr:0x01, lvl:3} +// | +// +[0x01]-----------+ +// | lvl=3 | +// +-+-+-+-+-+-+-+-+-+ +// |1|2|3|4|5|6|7|8|9| +// +---+---+---+---+-+ +// | +// | <------------------------------ pathElem={idx:8, addr:0x02, lvl:2} +// | +// +[0x02]-----------+ +// | lvl=2 | +// +-+-+-+-+-+-+-+-+-+ +// |1|2|3|4|5|6|7|8|9| +// +---+---+---+---+-+ +// | +// | <-------------------- pathElem={idx:7, addr:0x03, lvl:1} +// | +// +[0x03]-----------+ +// | lvl=1 | +// +-+-+-+-+-+-+-+-+-+ +// |1|2|3|4|5|6|7|8|9| +// +---+---+---+---+-+ +// | +// | <---------------- pathElem={idx:4, addr:0x04, lvl:0} +// | +// +[0x04]-----------+ +// | lvl=0 | +// +-+-+-+-+-+-+-+-+-+ +// |1|2|3|4|5|6|7|8|9| +// +---+---+---+---+-+ +// | +// | <--------------- pathElem={idx:5, addr:0, lvl:0} +// | +// [item] +// +// the path would be +// +// {-1, 0x01, 3}→{8, 0x02, 2}→{7, 0x03, 1}→{4, 0x04, 0}→{2, 0, 0} type TreePath []TreePathElem // A TreePathElem essentially represents a KeyPointer. diff --git a/pkg/btrfs/io3_fs.go b/pkg/btrfs/io4_fs.go index 75b973b..75b973b 100644 --- a/pkg/btrfs/io3_fs.go +++ b/pkg/btrfs/io4_fs.go diff --git a/pkg/btrfs/types_node.go b/pkg/btrfs/types_node.go index 8aafc49..92f7513 100644 --- a/pkg/btrfs/types_node.go +++ b/pkg/btrfs/types_node.go @@ -49,6 +49,13 @@ var nodeFlagNames = []string{ func (f NodeFlags) Has(req NodeFlags) bool { return f&req == req } func (f NodeFlags) String() string { return util.BitfieldString(f, nodeFlagNames, util.HexLower) } +type BackrefRev uint8 + +const ( + OldBackrefRev = BackrefRev(iota) + MixedBackrefRev = BackrefRev(iota) +) + // Node: main ////////////////////////////////////////////////////////////////////////////////////// type Node struct { @@ -72,7 +79,7 @@ type NodeHeader struct { MetadataUUID UUID `bin:"off=0x20, siz=0x10"` Addr btrfsvol.LogicalAddr `bin:"off=0x30, siz=0x8"` // Logical address of this node Flags NodeFlags `bin:"off=0x38, siz=0x7"` - BackrefRev uint8 `bin:"off=0x3f, siz=0x1"` + BackrefRev BackrefRev `bin:"off=0x3f, siz=0x1"` ChunkTreeUUID UUID `bin:"off=0x40, siz=0x10"` Generation Generation `bin:"off=0x50, siz=0x8"` Owner ObjID `bin:"off=0x58, siz=0x8"` // The ID of the tree that contains this node @@ -347,7 +354,7 @@ func ReadNode[Addr ~int64](fs util.File[Addr], sb Superblock, addr Addr, laddrCB // sanity checking if nodeRef.Data.Head.MetadataUUID != sb.EffectiveMetadataUUID() { - return nil, fmt.Errorf("btrfs.ReadNode: node@%v: %w", addr, ErrNotANode) + return nodeRef, fmt.Errorf("btrfs.ReadNode: node@%v: %w", addr, ErrNotANode) } stored := nodeRef.Data.Head.Checksum diff --git a/pkg/btrfsmisc/walk.go b/pkg/btrfsmisc/walk.go index 7d08394..0f3f811 100644 --- a/pkg/btrfsmisc/walk.go +++ b/pkg/btrfsmisc/walk.go @@ -30,6 +30,7 @@ type WalkFSHandler struct { PreTree func(name string, laddr btrfsvol.LogicalAddr) PostTree func(name string, laddr btrfsvol.LogicalAddr) // Callbacks for nodes or smaller + UnsafeNodes bool btrfs.TreeWalkHandler } @@ -73,15 +74,17 @@ func WalkFS(fs *btrfs.FS, cbs WalkFSHandler) { return nil } - origNode := cbs.Node - cbs.Node = func(path btrfs.TreePath, node *util.Ref[btrfsvol.LogicalAddr, btrfs.Node], err error) error { - if err != nil { - handleErr(path, err) - } - if node != nil && origNode != nil { - return origNode(path, node, nil) + if !cbs.UnsafeNodes { + origNode := cbs.Node + cbs.Node = func(path btrfs.TreePath, node *util.Ref[btrfsvol.LogicalAddr, btrfs.Node], err error) error { + if err != nil { + handleErr(path, err) + } + if node != nil && origNode != nil { + return origNode(path, node, nil) + } + return nil } - return nil } treeName = "superblock" @@ -92,39 +95,59 @@ func WalkFS(fs *btrfs.FS, cbs WalkFSHandler) { } treeName = "root tree" - cbs.PreTree(treeName, superblock.Data.RootTree) + if cbs.PreTree != nil { + cbs.PreTree(treeName, superblock.Data.RootTree) + } if err := fs.TreeWalk(superblock.Data.RootTree, cbs.TreeWalkHandler); err != nil { handleErr(nil, err) } - cbs.PostTree(treeName, superblock.Data.RootTree) + if cbs.PostTree != nil { + cbs.PostTree(treeName, superblock.Data.RootTree) + } treeName = "chunk tree" - cbs.PreTree(treeName, superblock.Data.ChunkTree) + if cbs.PreTree != nil { + cbs.PreTree(treeName, superblock.Data.ChunkTree) + } if err := fs.TreeWalk(superblock.Data.ChunkTree, cbs.TreeWalkHandler); err != nil { handleErr(nil, err) } - cbs.PostTree(treeName, superblock.Data.ChunkTree) + if cbs.PostTree != nil { + cbs.PostTree(treeName, superblock.Data.ChunkTree) + } treeName = "log tree" - cbs.PreTree(treeName, superblock.Data.LogTree) + if cbs.PreTree != nil { + cbs.PreTree(treeName, superblock.Data.LogTree) + } if err := fs.TreeWalk(superblock.Data.LogTree, cbs.TreeWalkHandler); err != nil { handleErr(nil, err) } - cbs.PostTree(treeName, superblock.Data.LogTree) + if cbs.PostTree != nil { + cbs.PostTree(treeName, superblock.Data.LogTree) + } treeName = "block group tree" - cbs.PreTree(treeName, superblock.Data.BlockGroupRoot) + if cbs.PreTree != nil { + cbs.PreTree(treeName, superblock.Data.BlockGroupRoot) + } if err := fs.TreeWalk(superblock.Data.BlockGroupRoot, cbs.TreeWalkHandler); err != nil { handleErr(nil, err) } - cbs.PostTree(treeName, superblock.Data.BlockGroupRoot) + if cbs.PostTree != nil { + cbs.PostTree(treeName, superblock.Data.BlockGroupRoot) + } for _, tree := range foundTrees { treeName = tree.Name - cbs.PreTree(treeName, tree.Root) + if cbs.PreTree != nil { + cbs.PreTree(treeName, tree.Root) + } if err := fs.TreeWalk(tree.Root, cbs.TreeWalkHandler); err != nil { handleErr(nil, err) } - cbs.PostTree(treeName, tree.Root) + if cbs.PostTree != nil { + cbs.PostTree(treeName, tree.Root) + } } } diff --git a/scripts/run-clear-nodes.sh b/scripts/run-clear-nodes.sh new file mode 100755 index 0000000..2f390ea --- /dev/null +++ b/scripts/run-clear-nodes.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e +rm -f ../scratch/dump-clearnodes.img +cp --reflink=always ../scratch/dump-{scratch,clearnodes}.img +time go run ./cmd/btrfs-clear-bad-nodes ../scratch/dump-clearnodes.img |