summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-07-07 02:51:57 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2022-07-08 00:16:01 -0600
commit22c32850798c264b6a20539b9cd1699228368ce9 (patch)
treeeea4fe596bc584cba96245d8bfbe92503d1a4c61
parentfa98ff69f24545e1836201c30ab43cc2527c52e3 (diff)
write btrfs-clear-bad-nodes
-rw-r--r--cmd/btrfs-clear-bad-nodes/main.go94
-rw-r--r--pkg/btrfs/io3_btree.go44
-rw-r--r--pkg/btrfs/io4_fs.go (renamed from pkg/btrfs/io3_fs.go)0
-rw-r--r--pkg/btrfs/types_node.go11
-rw-r--r--pkg/btrfsmisc/walk.go59
-rwxr-xr-xscripts/run-clear-nodes.sh5
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