summaryrefslogtreecommitdiff
path: root/cmd/btrfs-rec
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2023-03-14 21:31:51 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2023-03-14 21:31:51 -0600
commit697d79fb12b79b65a501ec90dbb45ea165b3457d (patch)
tree7b8275fe12a2f2d7c57f1e9419b97e693b715f36 /cmd/btrfs-rec
parent9e9b4e8ac67052d667f6e7fae0a6620b6dbc50c7 (diff)
parentafd2fe91ec604491bd8978b1880c6482e7394240 (diff)
Merge branch 'lukeshu/reorg-cli'
Diffstat (limited to 'cmd/btrfs-rec')
-rw-r--r--cmd/btrfs-rec/inspect/rebuildmappings/scan.go294
-rw-r--r--cmd/btrfs-rec/inspect/rebuildtrees/rebuild.go5
-rw-r--r--cmd/btrfs-rec/inspect/rebuildtrees/scan.go40
-rw-r--r--cmd/btrfs-rec/inspect/rebuildtrees/util.go10
-rw-r--r--cmd/btrfs-rec/inspect_dumptrees.go14
-rw-r--r--cmd/btrfs-rec/inspect_listnodes.go51
-rw-r--r--cmd/btrfs-rec/inspect_lsfiles.go14
-rw-r--r--cmd/btrfs-rec/inspect_lstrees.go81
-rw-r--r--cmd/btrfs-rec/inspect_mount.go19
-rw-r--r--cmd/btrfs-rec/inspect_rebuildmappings.go137
-rw-r--r--cmd/btrfs-rec/inspect_rebuildtrees.go54
-rw-r--r--cmd/btrfs-rec/inspect_scandevices.go46
-rw-r--r--cmd/btrfs-rec/inspect_spewitems.go14
-rw-r--r--cmd/btrfs-rec/main.go221
14 files changed, 528 insertions, 472 deletions
diff --git a/cmd/btrfs-rec/inspect/rebuildmappings/scan.go b/cmd/btrfs-rec/inspect/rebuildmappings/scan.go
index 2128a48..f9c3bf3 100644
--- a/cmd/btrfs-rec/inspect/rebuildmappings/scan.go
+++ b/cmd/btrfs-rec/inspect/rebuildmappings/scan.go
@@ -6,55 +6,26 @@ package rebuildmappings
import (
"context"
- "errors"
"fmt"
"strings"
- "sync"
- "time"
- "github.com/datawire/dlib/dgroup"
"github.com/datawire/dlib/dlog"
- "git.lukeshu.com/btrfs-progs-ng/lib/binstruct"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfssum"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfstree"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsutil"
"git.lukeshu.com/btrfs-progs-ng/lib/containers"
+ "git.lukeshu.com/btrfs-progs-ng/lib/diskio"
"git.lukeshu.com/btrfs-progs-ng/lib/textui"
)
-type ScanDevicesResult map[btrfsvol.DeviceID]ScanOneDeviceResult
+// Result types ////////////////////////////////////////////////////////////////
-func ScanDevices(ctx context.Context, fs *btrfs.FS) (ScanDevicesResult, error) {
- grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{})
- var mu sync.Mutex
- result := make(map[btrfsvol.DeviceID]ScanOneDeviceResult)
- for id, dev := range fs.LV.PhysicalVolumes() {
- id := id
- dev := dev
- grp.Go(fmt.Sprintf("dev-%d", id), func(ctx context.Context) error {
- sb, err := dev.Superblock()
- if err != nil {
- return err
- }
- devResult, err := ScanOneDevice(ctx, dev, *sb)
- if err != nil {
- return err
- }
- mu.Lock()
- result[id] = devResult
- mu.Unlock()
- return nil
- })
- }
- if err := grp.Wait(); err != nil {
- return nil, err
- }
- return result, nil
-}
+type ScanDevicesResult = map[btrfsvol.DeviceID]ScanOneDeviceResult
type ScanOneDeviceResult struct {
Checksums btrfssum.SumRun[btrfsvol.PhysicalAddr]
@@ -85,9 +56,27 @@ func (a SysExtentCSum) Compare(b SysExtentCSum) int {
return containers.NativeCompare(a.Sums.Addr, b.Sums.Addr)
}
-type scanStats struct {
- textui.Portion[btrfsvol.PhysicalAddr]
+// Convenience functions for those types ///////////////////////////////////////
+
+func ScanDevices(ctx context.Context, fs *btrfs.FS) (ScanDevicesResult, error) {
+ return btrfsutil.ScanDevices[scanStats, ScanOneDeviceResult](ctx, fs, newDeviceScanner)
+}
+// ScanOneDevice mostly mimics btrfs-progs
+// cmds/rescue-chunk-recover.c:scan_one_device().
+func ScanOneDevice(ctx context.Context, dev *btrfs.Device) (ScanOneDeviceResult, error) {
+ return btrfsutil.ScanOneDevice[scanStats, ScanOneDeviceResult](ctx, dev, newDeviceScanner)
+}
+
+// scanner implementation //////////////////////////////////////////////////////
+
+type deviceScanner struct {
+ alg btrfssum.CSumType
+ sums strings.Builder
+ result ScanOneDeviceResult
+}
+
+type scanStats struct {
NumFoundNodes int
NumFoundChunks int
NumFoundBlockGroups int
@@ -96,8 +85,7 @@ type scanStats struct {
}
func (s scanStats) String() string {
- return textui.Sprintf("scanned %v (found: %v nodes, %v chunks, %v block groups, %v dev extents, %v sum items)",
- s.Portion,
+ return textui.Sprintf("found: %v nodes, %v chunks, %v block groups, %v dev extents, %v sum items",
s.NumFoundNodes,
s.NumFoundChunks,
s.NumFoundBlockGroups,
@@ -105,156 +93,104 @@ func (s scanStats) String() string {
s.NumFoundExtentCSums)
}
-var sbSize = btrfsvol.PhysicalAddr(binstruct.StaticSize(btrfstree.Superblock{}))
-
-// ScanOneDevice mostly mimics btrfs-progs
-// cmds/rescue-chunk-recover.c:scan_one_device().
-func ScanOneDevice(ctx context.Context, dev *btrfs.Device, sb btrfstree.Superblock) (ScanOneDeviceResult, error) {
- ctx = dlog.WithField(ctx, "btrfs.inspect.rebuild-mappings.scan.dev", dev.Name())
-
- result := ScanOneDeviceResult{
- FoundNodes: make(map[btrfsvol.LogicalAddr][]btrfsvol.PhysicalAddr),
+func (scanner *deviceScanner) ScanStats() scanStats {
+ return scanStats{
+ NumFoundNodes: len(scanner.result.FoundNodes),
+ NumFoundChunks: len(scanner.result.FoundChunks),
+ NumFoundBlockGroups: len(scanner.result.FoundBlockGroups),
+ NumFoundDevExtents: len(scanner.result.FoundDevExtents),
+ NumFoundExtentCSums: len(scanner.result.FoundExtentCSums),
}
+}
- devSize := dev.Size()
- if sb.NodeSize < sb.SectorSize {
- return result, fmt.Errorf("node_size(%v) < sector_size(%v)",
- sb.NodeSize, sb.SectorSize)
- }
- if sb.SectorSize != btrfssum.BlockSize {
- // TODO: probably handle this?
- return result, fmt.Errorf("sector_size(%v) != btrfssum.BlockSize",
- sb.SectorSize)
- }
- alg := sb.ChecksumType
- csumSize := alg.Size()
- numSums := int(devSize / btrfssum.BlockSize)
- var sums strings.Builder
- sums.Grow(numSums * csumSize)
+func newDeviceScanner(ctx context.Context, sb btrfstree.Superblock, numBytes btrfsvol.PhysicalAddr, numSectors int) btrfsutil.DeviceScanner[scanStats, ScanOneDeviceResult] {
+ scanner := new(deviceScanner)
+ scanner.alg = sb.ChecksumType
+ scanner.result.FoundNodes = make(map[btrfsvol.LogicalAddr][]btrfsvol.PhysicalAddr)
+ scanner.result.Checksums.ChecksumSize = scanner.alg.Size()
+ scanner.sums.Grow(scanner.result.Checksums.ChecksumSize * numSectors)
+ return scanner
+}
- progressWriter := textui.NewProgress[scanStats](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second))
- progress := func(pos btrfsvol.PhysicalAddr) {
- progressWriter.Set(scanStats{
- Portion: textui.Portion[btrfsvol.PhysicalAddr]{
- N: pos,
- D: devSize,
- },
- NumFoundNodes: len(result.FoundNodes),
- NumFoundChunks: len(result.FoundChunks),
- NumFoundBlockGroups: len(result.FoundBlockGroups),
- NumFoundDevExtents: len(result.FoundDevExtents),
- NumFoundExtentCSums: len(result.FoundExtentCSums),
- })
+func (scanner *deviceScanner) ScanSector(ctx context.Context, dev *btrfs.Device, paddr btrfsvol.PhysicalAddr) error {
+ sum, err := btrfs.ChecksumPhysical(dev, scanner.alg, paddr)
+ if err != nil {
+ return err
}
+ scanner.sums.Write(sum[:scanner.result.Checksums.ChecksumSize])
+ return nil
+}
- var minNextNode btrfsvol.PhysicalAddr
- for i := 0; i < numSums; i++ {
- if ctx.Err() != nil {
- return result, ctx.Err()
- }
- pos := btrfsvol.PhysicalAddr(i * btrfssum.BlockSize)
- progress(pos)
-
- sum, err := btrfs.ChecksumPhysical(dev, alg, pos)
- if err != nil {
- return result, err
- }
- sums.Write(sum[:csumSize])
-
- checkForNode := pos >= minNextNode && pos+btrfsvol.PhysicalAddr(sb.NodeSize) <= devSize
- if checkForNode {
- for _, sbAddr := range btrfs.SuperblockAddrs {
- if sbAddr <= pos && pos < sbAddr+sbSize {
- checkForNode = false
- break
- }
+func (scanner *deviceScanner) ScanNode(ctx context.Context, nodeRef *diskio.Ref[btrfsvol.PhysicalAddr, btrfstree.Node]) error {
+ scanner.result.FoundNodes[nodeRef.Data.Head.Addr] = append(scanner.result.FoundNodes[nodeRef.Data.Head.Addr], nodeRef.Addr)
+ for i, item := range nodeRef.Data.BodyLeaf {
+ switch item.Key.ItemType {
+ case btrfsitem.CHUNK_ITEM_KEY:
+ switch itemBody := item.Body.(type) {
+ case *btrfsitem.Chunk:
+ dlog.Tracef(ctx, "node@%v: item %v: found chunk",
+ nodeRef.Addr, i)
+ scanner.result.FoundChunks = append(scanner.result.FoundChunks, btrfstree.SysChunk{
+ Key: item.Key,
+ Chunk: *itemBody,
+ })
+ case *btrfsitem.Error:
+ dlog.Errorf(ctx, "node@%v: item %v: error: malformed CHUNK_ITEM: %v",
+ nodeRef.Addr, i, itemBody.Err)
+ default:
+ panic(fmt.Errorf("should not happen: CHUNK_ITEM has unexpected item type: %T", itemBody))
}
- }
-
- if checkForNode {
- nodeRef, err := btrfstree.ReadNode[btrfsvol.PhysicalAddr](dev, sb, pos, btrfstree.NodeExpectations{})
- if err != nil {
- if !errors.Is(err, btrfstree.ErrNotANode) {
- dlog.Errorf(ctx, "error: %v", err)
- }
- } else {
- result.FoundNodes[nodeRef.Data.Head.Addr] = append(result.FoundNodes[nodeRef.Data.Head.Addr], nodeRef.Addr)
- for i, item := range nodeRef.Data.BodyLeaf {
- switch item.Key.ItemType {
- case btrfsitem.CHUNK_ITEM_KEY:
- switch itemBody := item.Body.(type) {
- case *btrfsitem.Chunk:
- dlog.Tracef(ctx, "node@%v: item %v: found chunk",
- nodeRef.Addr, i)
- result.FoundChunks = append(result.FoundChunks, btrfstree.SysChunk{
- Key: item.Key,
- Chunk: *itemBody,
- })
- case *btrfsitem.Error:
- dlog.Errorf(ctx, "node@%v: item %v: error: malformed CHUNK_ITEM: %v",
- nodeRef.Addr, i, itemBody.Err)
- default:
- panic(fmt.Errorf("should not happen: CHUNK_ITEM has unexpected item type: %T", itemBody))
- }
- case btrfsitem.BLOCK_GROUP_ITEM_KEY:
- switch itemBody := item.Body.(type) {
- case *btrfsitem.BlockGroup:
- dlog.Tracef(ctx, "node@%v: item %v: found block group",
- nodeRef.Addr, i)
- result.FoundBlockGroups = append(result.FoundBlockGroups, SysBlockGroup{
- Key: item.Key,
- BG: *itemBody,
- })
- case *btrfsitem.Error:
- dlog.Errorf(ctx, "node@%v: item %v: error: malformed BLOCK_GROUP_ITEM: %v",
- nodeRef.Addr, i, itemBody.Err)
- default:
- panic(fmt.Errorf("should not happen: BLOCK_GROUP_ITEM has unexpected item type: %T", itemBody))
- }
- case btrfsitem.DEV_EXTENT_KEY:
- switch itemBody := item.Body.(type) {
- case *btrfsitem.DevExtent:
- dlog.Tracef(ctx, "node@%v: item %v: found dev extent",
- nodeRef.Addr, i)
- result.FoundDevExtents = append(result.FoundDevExtents, SysDevExtent{
- Key: item.Key,
- DevExt: *itemBody,
- })
- case *btrfsitem.Error:
- dlog.Errorf(ctx, "node@%v: item %v: error: malformed DEV_EXTENT: %v",
- nodeRef.Addr, i, itemBody.Err)
- default:
- panic(fmt.Errorf("should not happen: DEV_EXTENT has unexpected item type: %T", itemBody))
- }
- case btrfsitem.EXTENT_CSUM_KEY:
- switch itemBody := item.Body.(type) {
- case *btrfsitem.ExtentCSum:
- dlog.Tracef(ctx, "node@%v: item %v: found csums",
- nodeRef.Addr, i)
- result.FoundExtentCSums = append(result.FoundExtentCSums, SysExtentCSum{
- Generation: nodeRef.Data.Head.Generation,
- Sums: *itemBody,
- })
- case *btrfsitem.Error:
- dlog.Errorf(ctx, "node@%v: item %v: error: malformed is EXTENT_CSUM: %v",
- nodeRef.Addr, i, itemBody.Err)
- default:
- panic(fmt.Errorf("should not happen: EXTENT_CSUM has unexpected item type: %T", itemBody))
- }
- }
- }
- minNextNode = pos + btrfsvol.PhysicalAddr(sb.NodeSize)
+ case btrfsitem.BLOCK_GROUP_ITEM_KEY:
+ switch itemBody := item.Body.(type) {
+ case *btrfsitem.BlockGroup:
+ dlog.Tracef(ctx, "node@%v: item %v: found block group",
+ nodeRef.Addr, i)
+ scanner.result.FoundBlockGroups = append(scanner.result.FoundBlockGroups, SysBlockGroup{
+ Key: item.Key,
+ BG: *itemBody,
+ })
+ case *btrfsitem.Error:
+ dlog.Errorf(ctx, "node@%v: item %v: error: malformed BLOCK_GROUP_ITEM: %v",
+ nodeRef.Addr, i, itemBody.Err)
+ default:
+ panic(fmt.Errorf("should not happen: BLOCK_GROUP_ITEM has unexpected item type: %T", itemBody))
+ }
+ case btrfsitem.DEV_EXTENT_KEY:
+ switch itemBody := item.Body.(type) {
+ case *btrfsitem.DevExtent:
+ dlog.Tracef(ctx, "node@%v: item %v: found dev extent",
+ nodeRef.Addr, i)
+ scanner.result.FoundDevExtents = append(scanner.result.FoundDevExtents, SysDevExtent{
+ Key: item.Key,
+ DevExt: *itemBody,
+ })
+ case *btrfsitem.Error:
+ dlog.Errorf(ctx, "node@%v: item %v: error: malformed DEV_EXTENT: %v",
+ nodeRef.Addr, i, itemBody.Err)
+ default:
+ panic(fmt.Errorf("should not happen: DEV_EXTENT has unexpected item type: %T", itemBody))
+ }
+ case btrfsitem.EXTENT_CSUM_KEY:
+ switch itemBody := item.Body.(type) {
+ case *btrfsitem.ExtentCSum:
+ dlog.Tracef(ctx, "node@%v: item %v: found csums",
+ nodeRef.Addr, i)
+ scanner.result.FoundExtentCSums = append(scanner.result.FoundExtentCSums, SysExtentCSum{
+ Generation: nodeRef.Data.Head.Generation,
+ Sums: *itemBody,
+ })
+ case *btrfsitem.Error:
+ dlog.Errorf(ctx, "node@%v: item %v: error: malformed is EXTENT_CSUM: %v",
+ nodeRef.Addr, i, itemBody.Err)
+ default:
+ panic(fmt.Errorf("should not happen: EXTENT_CSUM has unexpected item type: %T", itemBody))
}
- btrfstree.FreeNodeRef(nodeRef)
}
}
- progress(devSize)
- progressWriter.Done()
-
- result.Checksums = btrfssum.SumRun[btrfsvol.PhysicalAddr]{
- ChecksumSize: csumSize,
- Sums: btrfssum.ShortSum(sums.String()),
- }
+ return nil
+}
- return result, nil
+func (scanner *deviceScanner) ScanDone(ctx context.Context) (ScanOneDeviceResult, error) {
+ scanner.result.Checksums.Sums = btrfssum.ShortSum(scanner.sums.String())
+ return scanner.result, nil
}
diff --git a/cmd/btrfs-rec/inspect/rebuildtrees/rebuild.go b/cmd/btrfs-rec/inspect/rebuildtrees/rebuild.go
index bbfcdde..708b504 100644
--- a/cmd/btrfs-rec/inspect/rebuildtrees/rebuild.go
+++ b/cmd/btrfs-rec/inspect/rebuildtrees/rebuild.go
@@ -14,7 +14,6 @@ import (
"github.com/datawire/dlib/dgroup"
"github.com/datawire/dlib/dlog"
- "git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildmappings"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim"
@@ -74,9 +73,9 @@ type Rebuilder interface {
ListRoots(context.Context) map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr]
}
-func NewRebuilder(ctx context.Context, fs *btrfs.FS, nodeScanResults rebuildmappings.ScanDevicesResult) (Rebuilder, error) {
+func NewRebuilder(ctx context.Context, fs *btrfs.FS, nodeList []btrfsvol.LogicalAddr) (Rebuilder, error) {
ctx = dlog.WithField(ctx, "btrfs.inspect.rebuild-trees.step", "read-fs-data")
- sb, nodeGraph, keyIO, err := ScanDevices(ctx, fs, nodeScanResults) // ScanDevices does its own logging
+ sb, nodeGraph, keyIO, err := ScanDevices(ctx, fs, nodeList) // ScanDevices does its own logging
if err != nil {
return nil, err
}
diff --git a/cmd/btrfs-rec/inspect/rebuildtrees/scan.go b/cmd/btrfs-rec/inspect/rebuildtrees/scan.go
index 2995a2e..ba56c5b 100644
--- a/cmd/btrfs-rec/inspect/rebuildtrees/scan.go
+++ b/cmd/btrfs-rec/inspect/rebuildtrees/scan.go
@@ -10,17 +10,15 @@ import (
"github.com/datawire/dlib/dlog"
- "git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildmappings"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfstree"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfsutil"
"git.lukeshu.com/btrfs-progs-ng/lib/containers"
- "git.lukeshu.com/btrfs-progs-ng/lib/maps"
"git.lukeshu.com/btrfs-progs-ng/lib/textui"
)
-func ScanDevices(ctx context.Context, fs *btrfs.FS, scanResults rebuildmappings.ScanDevicesResult) (btrfstree.Superblock, btrfsutil.Graph, *btrfsutil.KeyIO, error) {
+func ScanDevices(ctx context.Context, fs *btrfs.FS, nodeList []btrfsvol.LogicalAddr) (btrfstree.Superblock, btrfsutil.Graph, *btrfsutil.KeyIO, error) {
dlog.Info(ctx, "Reading superblock...")
sb, err := fs.Superblock()
if err != nil {
@@ -30,7 +28,7 @@ func ScanDevices(ctx context.Context, fs *btrfs.FS, scanResults rebuildmappings.
dlog.Infof(ctx, "Reading node data from FS...")
var stats textui.Portion[int]
- stats.D = countNodes(scanResults)
+ stats.D = len(nodeList)
progressWriter := textui.NewProgress[textui.Portion[int]](
dlog.WithField(ctx, "btrfs.inspect.rebuild-trees.read.substep", "read-nodes"),
dlog.LogLevelInfo, textui.Tunable(1*time.Second))
@@ -39,27 +37,25 @@ func ScanDevices(ctx context.Context, fs *btrfs.FS, scanResults rebuildmappings.
keyIO := btrfsutil.NewKeyIO(fs, *sb)
progressWriter.Set(stats)
- for _, devResults := range scanResults {
- for _, laddr := range maps.SortedKeys(devResults.FoundNodes) {
- if err := ctx.Err(); err != nil {
- return btrfstree.Superblock{}, btrfsutil.Graph{}, nil, err
- }
- nodeRef, err := btrfstree.ReadNode[btrfsvol.LogicalAddr](fs, *sb, laddr, btrfstree.NodeExpectations{
- LAddr: containers.Optional[btrfsvol.LogicalAddr]{OK: true, Val: laddr},
- })
- if err != nil {
- btrfstree.FreeNodeRef(nodeRef)
- return btrfstree.Superblock{}, btrfsutil.Graph{}, nil, err
- }
+ for _, laddr := range nodeList {
+ if err := ctx.Err(); err != nil {
+ return btrfstree.Superblock{}, btrfsutil.Graph{}, nil, err
+ }
+ nodeRef, err := btrfstree.ReadNode[btrfsvol.LogicalAddr](fs, *sb, laddr, btrfstree.NodeExpectations{
+ LAddr: containers.Optional[btrfsvol.LogicalAddr]{OK: true, Val: laddr},
+ })
+ if err != nil {
+ btrfstree.FreeNodeRef(nodeRef)
+ return btrfstree.Superblock{}, btrfsutil.Graph{}, nil, err
+ }
- nodeGraph.InsertNode(nodeRef)
- keyIO.InsertNode(nodeRef)
+ nodeGraph.InsertNode(nodeRef)
+ keyIO.InsertNode(nodeRef)
- btrfstree.FreeNodeRef(nodeRef)
+ btrfstree.FreeNodeRef(nodeRef)
- stats.N++
- progressWriter.Set(stats)
- }
+ stats.N++
+ progressWriter.Set(stats)
}
if stats.N != stats.D {
panic("should not happen")
diff --git a/cmd/btrfs-rec/inspect/rebuildtrees/util.go b/cmd/btrfs-rec/inspect/rebuildtrees/util.go
index 71caee0..842fb55 100644
--- a/cmd/btrfs-rec/inspect/rebuildtrees/util.go
+++ b/cmd/btrfs-rec/inspect/rebuildtrees/util.go
@@ -6,18 +6,8 @@ package rebuildtrees
import (
"golang.org/x/exp/constraints"
-
- "git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildmappings"
)
-func countNodes(nodeScanResults rebuildmappings.ScanDevicesResult) int {
- var cnt int
- for _, devResults := range nodeScanResults {
- cnt += len(devResults.FoundNodes)
- }
- return cnt
-}
-
func roundDown[T constraints.Integer](n, d T) T {
return (n / d) * d
}
diff --git a/cmd/btrfs-rec/inspect_dumptrees.go b/cmd/btrfs-rec/inspect_dumptrees.go
index efdc380..fd152d3 100644
--- a/cmd/btrfs-rec/inspect_dumptrees.go
+++ b/cmd/btrfs-rec/inspect_dumptrees.go
@@ -16,18 +16,16 @@ import (
)
func init() {
- inspectors = append(inspectors, subcommand{
- Command: cobra.Command{
- Use: "dump-trees",
- Short: "A clone of `btrfs inspect-internal dump-tree`",
- Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error {
+ inspectors.AddCommand(&cobra.Command{
+ Use: "dump-trees",
+ Short: "A clone of `btrfs inspect-internal dump-tree`",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error {
const version = "6.1.3"
out := os.Stdout
textui.Fprintf(out, "btrfs-progs v%v\n", version)
dumptrees.DumpTrees(cmd.Context(), out, fs)
return nil
- },
+ }),
})
}
diff --git a/cmd/btrfs-rec/inspect_listnodes.go b/cmd/btrfs-rec/inspect_listnodes.go
new file mode 100644
index 0000000..d9b24ed
--- /dev/null
+++ b/cmd/btrfs-rec/inspect_listnodes.go
@@ -0,0 +1,51 @@
+// Copyright (C) 2022-2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package main
+
+import (
+ "os"
+
+ "git.lukeshu.com/go/lowmemjson"
+ "github.com/datawire/dlib/dlog"
+ "github.com/datawire/ocibuild/pkg/cliutil"
+ "github.com/spf13/cobra"
+
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsutil"
+)
+
+func init() {
+ inspectors.AddCommand(&cobra.Command{
+ Use: "list-nodes",
+ Short: "Scan the filesystem for btree nodes",
+ Long: "" +
+ "This scans the filesystem sector-by-sector looking for nodes. " +
+ "If you are needing to rebuild the chunk/dev-extent/blockgroup " +
+ "trees with `btrfs-rec inspect rebuild-mappings` anyway, you may " +
+ "want to instead use `btrfs-rec inspect rebuild-mappings list-nodes` " +
+ "to take advantage of the sector-by-sector scan that's already " +
+ "performed by `btrfs-rec inspect rebuild-mappings scan`.",
+ Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
+ ctx := cmd.Context()
+
+ nodeList, err := btrfsutil.ListNodes(ctx, fs)
+ if err != nil {
+ return err
+ }
+
+ dlog.Infof(ctx, "Writing nodes to stdout...")
+ if err := writeJSONFile(os.Stdout, nodeList, lowmemjson.ReEncoderConfig{
+ Indent: "\t",
+ ForceTrailingNewlines: true,
+ }); err != nil {
+ return err
+ }
+ dlog.Info(ctx, "... done writing")
+
+ return nil
+ }),
+ })
+}
diff --git a/cmd/btrfs-rec/inspect_lsfiles.go b/cmd/btrfs-rec/inspect_lsfiles.go
index 4f985ff..a2b46ab 100644
--- a/cmd/btrfs-rec/inspect_lsfiles.go
+++ b/cmd/btrfs-rec/inspect_lsfiles.go
@@ -26,13 +26,11 @@ import (
)
func init() {
- inspectors = append(inspectors, subcommand{
- Command: cobra.Command{
- Use: "ls-files",
- Short: "A listing of all files in the filesystem",
- Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) (err error) {
+ inspectors.AddCommand(&cobra.Command{
+ Use: "ls-files",
+ Short: "A listing of all files in the filesystem",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, _ []string) (err error) {
out := bufio.NewWriter(os.Stdout)
defer func() {
if _err := out.Flush(); _err != nil && err == nil {
@@ -53,7 +51,7 @@ func init() {
})
return nil
- },
+ }),
})
}
diff --git a/cmd/btrfs-rec/inspect_lstrees.go b/cmd/btrfs-rec/inspect_lstrees.go
index df2473c..05c3a57 100644
--- a/cmd/btrfs-rec/inspect_lstrees.go
+++ b/cmd/btrfs-rec/inspect_lstrees.go
@@ -12,7 +12,6 @@ import (
"github.com/datawire/ocibuild/pkg/cliutil"
"github.com/spf13/cobra"
- "git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildmappings"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim"
@@ -27,22 +26,26 @@ import (
)
func init() {
- var scandevicesFilename string
- cmd := subcommand{
- Command: cobra.Command{
- Use: "ls-trees",
- Short: "A brief view what types of items are in each tree",
- Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error {
+ var nodeListFilename string
+ cmd := &cobra.Command{
+ Use: "ls-trees",
+ Short: "A brief view what types of items are in each tree",
+ Long: "" +
+ "If no --node-list is given, then a slow sector-by-sector scan " +
+ "will be used to find all lost+found nodes.",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
- var scanResults rebuildmappings.ScanDevicesResult
- if scandevicesFilename != "" {
- var err error
- scanResults, err = readJSONFile[rebuildmappings.ScanDevicesResult](ctx, scandevicesFilename)
- if err != nil {
- return err
- }
+
+ var nodeList []btrfsvol.LogicalAddr
+ var err error
+ if nodeListFilename != "" {
+ nodeList, err = readJSONFile[[]btrfsvol.LogicalAddr](ctx, nodeListFilename)
+ } else {
+ nodeList, err = btrfsutil.ListNodes(ctx, fs)
+ }
+ if err != nil {
+ return err
}
var treeErrCnt int
@@ -93,39 +96,37 @@ func init() {
},
})
- if scandevicesFilename != "" {
+ {
treeErrCnt = 0
treeItemCnt = make(map[btrfsitem.Type]int)
textui.Fprintf(os.Stdout, "lost+found\n")
sb, _ := fs.Superblock()
- for _, devResults := range scanResults {
- for laddr := range devResults.FoundNodes {
- if visitedNodes.Has(laddr) {
- continue
- }
- visitedNodes.Insert(laddr)
- node, err := btrfstree.ReadNode[btrfsvol.LogicalAddr](fs, *sb, laddr, btrfstree.NodeExpectations{
- LAddr: containers.Optional[btrfsvol.LogicalAddr]{OK: true, Val: laddr},
- })
- if err != nil {
- treeErrCnt++
- continue
- }
- for _, item := range node.Data.BodyLeaf {
- typ := item.Key.ItemType
- treeItemCnt[typ]++
- }
+ for _, laddr := range nodeList {
+ if visitedNodes.Has(laddr) {
+ continue
+ }
+ visitedNodes.Insert(laddr)
+ node, err := btrfstree.ReadNode[btrfsvol.LogicalAddr](fs, *sb, laddr, btrfstree.NodeExpectations{
+ LAddr: containers.Optional[btrfsvol.LogicalAddr]{OK: true, Val: laddr},
+ })
+ if err != nil {
+ treeErrCnt++
+ continue
+ }
+ for _, item := range node.Data.BodyLeaf {
+ typ := item.Key.ItemType
+ treeItemCnt[typ]++
}
}
flush()
}
return nil
- },
+ }),
}
- cmd.Command.Flags().StringVar(&scandevicesFilename, "scandevices", "", "Output of 'scandevices' to use for a lost+found tree")
- if err := cmd.Command.MarkFlagFilename("scandevices"); err != nil {
- panic(err)
- }
- inspectors = append(inspectors, cmd)
+ cmd.Flags().StringVar(&nodeListFilename, "node-list", "",
+ "Output of 'btrfs-recs inspect [rebuild-mappings] list-nodes' to use for a lost+found tree")
+ noError(cmd.MarkFlagFilename("node-list"))
+
+ inspectors.AddCommand(cmd)
}
diff --git a/cmd/btrfs-rec/inspect_mount.go b/cmd/btrfs-rec/inspect_mount.go
index f3fda34..4582f9f 100644
--- a/cmd/btrfs-rec/inspect_mount.go
+++ b/cmd/btrfs-rec/inspect_mount.go
@@ -14,17 +14,16 @@ import (
func init() {
var skipFileSums bool
- cmd := subcommand{
- Command: cobra.Command{
- Use: "mount MOUNTPOINT",
- Short: "Mount the filesystem read-only",
- Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
+ cmd := &cobra.Command{
+ Use: "mount MOUNTPOINT",
+ Short: "Mount the filesystem read-only",
+ Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
return mount.MountRO(cmd.Context(), fs, args[0], skipFileSums)
- },
+ }),
}
- cmd.Command.Flags().BoolVar(&skipFileSums, "skip-filesums", false,
+ cmd.Flags().BoolVar(&skipFileSums, "skip-filesums", false,
"ignore checksum failures on file contents; allow such files to be read")
- inspectors = append(inspectors, cmd)
+
+ inspectors.AddCommand(cmd)
}
diff --git a/cmd/btrfs-rec/inspect_rebuildmappings.go b/cmd/btrfs-rec/inspect_rebuildmappings.go
index 005fd5d..43c45b1 100644
--- a/cmd/btrfs-rec/inspect_rebuildmappings.go
+++ b/cmd/btrfs-rec/inspect_rebuildmappings.go
@@ -14,24 +14,86 @@ import (
"git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildmappings"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol"
+ "git.lukeshu.com/btrfs-progs-ng/lib/containers"
+ "git.lukeshu.com/btrfs-progs-ng/lib/maps"
)
func init() {
- inspectors = append(inspectors, subcommand{
- Command: cobra.Command{
- Use: "rebuild-mappings SCAN_RESULT.json",
- Short: "Rebuild broken chunk/dev-extent/blockgroup trees",
- Long: "" +
- "The rebuilt information is printed as JSON on stdout, and can\n" +
- "be loaded by the --mappings flag.\n" +
- "\n" +
- "This is very similar to `btrfs rescue chunk-recover`, but (1)\n" +
- "does a better job, (2) is less buggy, and (3) doesn't actually\n" +
- "write the info back to the filesystem; instead writing it\n" +
- "out-of-band to stdout.",
- Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
+ cmd := &cobra.Command{
+ Use: "rebuild-mappings",
+ Short: "Rebuild broken chunk/dev-extent/blockgroup trees",
+ Long: "" +
+ "The rebuilt information is printed as JSON on stdout, and can " +
+ "be loaded by the --mappings flag.\n" +
+ "\n" +
+ "This is very similar to `btrfs rescue chunk-recover`, but (1) " +
+ "does a better job, (2) is less buggy, and (3) doesn't actually " +
+ "write the info back to the filesystem; instead writing it " +
+ "out-of-band to stdout.\n" +
+ "\n" +
+ "The I/O and the CPU parts of this can be split up as:\n" +
+ "\n" +
+ "\tbtrfs-rec inspect rebuild-mappings scan > SCAN.json # read\n" +
+ "\tbtrfs-rec inspect rebuild-mappings process SCAN.json # CPU\n",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
+ ctx := cmd.Context()
+
+ scanResults, err := rebuildmappings.ScanDevices(ctx, fs)
+ if err != nil {
+ return err
+ }
+
+ if err := rebuildmappings.RebuildMappings(ctx, fs, scanResults); err != nil {
+ return err
+ }
+
+ dlog.Infof(ctx, "Writing reconstructed mappings to stdout...")
+ if err := writeJSONFile(os.Stdout, fs.LV.Mappings(), lowmemjson.ReEncoderConfig{
+ Indent: "\t",
+ ForceTrailingNewlines: true,
+ CompactIfUnder: 120, //nolint:gomnd // This is what looks nice.
+ }); err != nil {
+ return err
+ }
+ dlog.Info(ctx, "... done writing")
+
+ return nil
+ }),
+ }
+
+ cmd.AddCommand(&cobra.Command{
+ Use: "scan",
+ Short: "Read from the filesystem all data nescessary to rebuild the mappings",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, _ []string) (err error) {
+ ctx := cmd.Context()
+
+ scanResults, err := rebuildmappings.ScanDevices(ctx, fs)
+ if err != nil {
+ return err
+ }
+
+ dlog.Info(ctx, "Writing scan results to stdout...")
+ if err := writeJSONFile(os.Stdout, scanResults, lowmemjson.ReEncoderConfig{
+ Indent: "\t",
+ ForceTrailingNewlines: true,
+ CompactIfUnder: 16, //nolint:gomnd // This is what looks nice.
+ }); err != nil {
+ return err
+ }
+ dlog.Info(ctx, "... done writing")
+
+ return nil
+ }),
+ })
+
+ cmd.AddCommand(&cobra.Command{
+ Use: "process",
+ Short: "Rebuild the mappings based on previously read data",
+ Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
dlog.Infof(ctx, "Reading %q...", args[0])
@@ -56,6 +118,49 @@ func init() {
dlog.Info(ctx, "... done writing")
return nil
- },
+ }),
})
+
+ cmd.AddCommand(&cobra.Command{
+ Use: "list-nodes",
+ Short: "Produce a listing of btree nodes from previously read data",
+ Long: "" +
+ "This is a variant of `btrfs-rec inspect list-nodes` that takes " +
+ "advantage of using previously read data from " +
+ "`btrfs-rec inspect rebuild-nodes scan`.",
+ Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
+ RunE: run(func(cmd *cobra.Command, args []string) error {
+ ctx := cmd.Context()
+
+ scanResults, err := readJSONFile[rebuildmappings.ScanDevicesResult](ctx, args[0])
+ if err != nil {
+ return err
+ }
+
+ var cnt int
+ for _, devResults := range scanResults {
+ cnt += len(devResults.FoundNodes)
+ }
+ set := make(containers.Set[btrfsvol.LogicalAddr], cnt)
+ for _, devResults := range scanResults {
+ for laddr := range devResults.FoundNodes {
+ set.Insert(laddr)
+ }
+ }
+ nodeList := maps.SortedKeys(set)
+
+ dlog.Infof(ctx, "Writing nodes to stdout...")
+ if err := writeJSONFile(os.Stdout, nodeList, lowmemjson.ReEncoderConfig{
+ Indent: "\t",
+ ForceTrailingNewlines: true,
+ }); err != nil {
+ return err
+ }
+ dlog.Info(ctx, "... done writing")
+
+ return nil
+ }),
+ })
+
+ inspectors.AddCommand(cmd)
}
diff --git a/cmd/btrfs-rec/inspect_rebuildtrees.go b/cmd/btrfs-rec/inspect_rebuildtrees.go
index 0b41dd9..676533a 100644
--- a/cmd/btrfs-rec/inspect_rebuildtrees.go
+++ b/cmd/btrfs-rec/inspect_rebuildtrees.go
@@ -5,7 +5,6 @@
package main
import (
- "context"
"os"
"runtime"
"time"
@@ -15,33 +14,41 @@ import (
"github.com/datawire/ocibuild/pkg/cliutil"
"github.com/spf13/cobra"
- "git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildmappings"
"git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildtrees"
"git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol"
+ "git.lukeshu.com/btrfs-progs-ng/lib/btrfsutil"
"git.lukeshu.com/btrfs-progs-ng/lib/textui"
)
func init() {
- inspectors = append(inspectors, subcommand{
- Command: cobra.Command{
- Use: "rebuild-nodes NODESCAN.json",
- Args: cliutil.WrapPositionalArgs(cobra.ExactArgs(1)),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
+ var nodeListFilename string
+ cmd := &cobra.Command{
+ Use: "rebuild-trees",
+ Long: "" +
+ "Rebuild broken btrees based on missing items that are implied " +
+ "by present items. This requires functioning " +
+ "chunk/dev-extent/blockgroup trees, which can be rebuilt " +
+ "separately with `btrfs-rec inspect rebuild-mappings`.\n" +
+ "\n" +
+ "If no --node-list is given, then a slow sector-by-sector scan " +
+ "will be used to find all nodes.",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
- // This is wrapped in a func in order to *ensure* that `nodeScanResults` goes out of scope once
- // `rebuilder` has been created.
- rebuilder, err := func(ctx context.Context) (rebuildtrees.Rebuilder, error) {
- dlog.Infof(ctx, "Reading %q...", args[0])
- nodeScanResults, err := readJSONFile[rebuildmappings.ScanDevicesResult](ctx, args[0])
- if err != nil {
- return nil, err
- }
- dlog.Infof(ctx, "... done reading %q", args[0])
+ var nodeList []btrfsvol.LogicalAddr
+ var err error
+ if nodeListFilename != "" {
+ nodeList, err = readJSONFile[[]btrfsvol.LogicalAddr](ctx, nodeListFilename)
+ } else {
+ nodeList, err = btrfsutil.ListNodes(ctx, fs)
+ }
+ if err != nil {
+ return err
+ }
- return rebuildtrees.NewRebuilder(ctx, fs, nodeScanResults)
- }(ctx)
+ rebuilder, err := rebuildtrees.NewRebuilder(ctx, fs, nodeList)
if err != nil {
return err
}
@@ -70,6 +77,11 @@ func init() {
dlog.Info(ctx, "... done writing")
return rebuildErr
- },
- })
+ }),
+ }
+ cmd.Flags().StringVar(&nodeListFilename, "node-list", "",
+ "Output of 'btrfs-recs inspect [rebuild-mappings] list-nodes' to use for the node list")
+ noError(cmd.MarkFlagFilename("node-list"))
+
+ inspectors.AddCommand(cmd)
}
diff --git a/cmd/btrfs-rec/inspect_scandevices.go b/cmd/btrfs-rec/inspect_scandevices.go
deleted file mode 100644
index 0542d6a..0000000
--- a/cmd/btrfs-rec/inspect_scandevices.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2022-2023 Luke Shumaker <lukeshu@lukeshu.com>
-//
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package main
-
-import (
- "os"
-
- "git.lukeshu.com/go/lowmemjson"
- "github.com/datawire/dlib/dlog"
- "github.com/datawire/ocibuild/pkg/cliutil"
- "github.com/spf13/cobra"
-
- "git.lukeshu.com/btrfs-progs-ng/cmd/btrfs-rec/inspect/rebuildmappings"
- "git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
-)
-
-func init() {
- inspectors = append(inspectors, subcommand{
- Command: cobra.Command{
- Use: "scandevices",
- Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) (err error) {
- ctx := cmd.Context()
-
- results, err := rebuildmappings.ScanDevices(ctx, fs)
- if err != nil {
- return err
- }
-
- dlog.Info(ctx, "Writing scan results to stdout...")
- if err := writeJSONFile(os.Stdout, results, lowmemjson.ReEncoderConfig{
- Indent: "\t",
- ForceTrailingNewlines: true,
- CompactIfUnder: 16, //nolint:gomnd // This is what looks nice.
- }); err != nil {
- return err
- }
- dlog.Info(ctx, "... done writing")
-
- return nil
- },
- })
-}
diff --git a/cmd/btrfs-rec/inspect_spewitems.go b/cmd/btrfs-rec/inspect_spewitems.go
index 4abb2b0..d8a65ae 100644
--- a/cmd/btrfs-rec/inspect_spewitems.go
+++ b/cmd/btrfs-rec/inspect_spewitems.go
@@ -19,13 +19,11 @@ import (
)
func init() {
- inspectors = append(inspectors, subcommand{
- Command: cobra.Command{
- Use: "spew-items",
- Short: "Spew all items as parsed",
- Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
- },
- RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error {
+ inspectors.AddCommand(&cobra.Command{
+ Use: "spew-items",
+ Short: "Spew all items as parsed",
+ Args: cliutil.WrapPositionalArgs(cobra.NoArgs),
+ RunE: runWithRawFS(func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
spew := spew.NewDefaultConfig()
@@ -51,6 +49,6 @@ func init() {
},
})
return nil
- },
+ }),
})
}
diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go
index bf89fbc..15f1964 100644
--- a/cmd/btrfs-rec/main.go
+++ b/cmd/btrfs-rec/main.go
@@ -6,6 +6,7 @@ package main
import (
"context"
+ "fmt"
"os"
"github.com/datawire/dlib/dgroup"
@@ -20,19 +21,46 @@ import (
"git.lukeshu.com/btrfs-progs-ng/lib/textui"
)
-type subcommand struct {
- cobra.Command
- RunE func(*btrfs.FS, *cobra.Command, []string) error
+var (
+ inspectors = &cobra.Command{
+ Use: "inspect {[flags]|SUBCOMMAND}",
+ Short: "Inspect (but don't modify) a broken btrfs filesystem",
+
+ Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
+ RunE: cliutil.RunSubcommands,
+ }
+ repairers = &cobra.Command{
+ Use: "repair {[flags]|SUBCOMMAND}",
+ Short: "Repair a broken btrfs filesystem",
+
+ Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
+ RunE: cliutil.RunSubcommands,
+
+ PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
+ globalFlags.openFlag = os.O_RDWR
+ return nil
+ },
+ }
+)
+
+var globalFlags struct {
+ logLevel textui.LogLevelFlag
+ pvs []string
+ mappings string
+
+ stopProfiling profile.StopFunc
+
+ openFlag int
}
-var inspectors, repairers []subcommand
+func noError(err error) {
+ if err != nil {
+ panic(fmt.Errorf("should not happen: %w", err))
+ }
+}
func main() {
- logLevelFlag := textui.LogLevelFlag{
- Level: dlog.LogLevelInfo,
- }
- var pvsFlag []string
- var mappingsFlag string
+ // Base argparser
argparser := &cobra.Command{
Use: "btrfs-rec {[flags]|SUBCOMMAND}",
@@ -50,107 +78,98 @@ func main() {
}
argparser.SetFlagErrorFunc(cliutil.FlagErrorFunc)
argparser.SetHelpTemplate(cliutil.HelpTemplate)
- argparser.PersistentFlags().Var(&logLevelFlag, "verbosity", "set the verbosity")
- argparser.PersistentFlags().StringArrayVar(&pvsFlag, "pv", nil, "open the file `physical_volume` as part of the filesystem")
- if err := argparser.MarkPersistentFlagFilename("pv"); err != nil {
- panic(err)
- }
- if err := argparser.MarkPersistentFlagRequired("pv"); err != nil {
- panic(err)
- }
- argparser.PersistentFlags().StringVar(&mappingsFlag, "mappings", "", "load chunk/dev-extent/blockgroup data from external JSON file `mappings.json`")
- if err := argparser.MarkPersistentFlagFilename("mappings"); err != nil {
- panic(err)
- }
- stopProfiling := profile.AddProfileFlags(argparser.PersistentFlags(), "profile.")
- openFlag := os.O_RDONLY
+ // Global flags
- argparserInspect := &cobra.Command{
- Use: "inspect {[flags]|SUBCOMMAND}",
- Short: "Inspect (but don't modify) a broken btrfs filesystem",
+ globalFlags.logLevel.Level = dlog.LogLevelInfo
+ argparser.PersistentFlags().Var(&globalFlags.logLevel, "verbosity", "set the verbosity")
- Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
- RunE: cliutil.RunSubcommands,
- }
- argparser.AddCommand(argparserInspect)
+ argparser.PersistentFlags().StringArrayVar(&globalFlags.pvs, "pv", nil, "open the file `physical_volume` as part of the filesystem")
+ noError(argparser.MarkPersistentFlagFilename("pv"))
- argparserRepair := &cobra.Command{
- Use: "repair {[flags]|SUBCOMMAND}",
- Short: "Repair a broken btrfs filesystem",
+ argparser.PersistentFlags().StringVar(&globalFlags.mappings, "mappings", "", "load chunk/dev-extent/blockgroup data from external JSON file `mappings.json`")
+ noError(argparser.MarkPersistentFlagFilename("mappings"))
- Args: cliutil.WrapPositionalArgs(cliutil.OnlySubcommands),
- RunE: cliutil.RunSubcommands,
+ globalFlags.stopProfiling = profile.AddProfileFlags(argparser.PersistentFlags(), "profile.")
- PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
- openFlag = os.O_RDWR
- return nil
- },
- }
- argparser.AddCommand(argparserRepair)
-
- for _, cmdgrp := range []struct {
- parent *cobra.Command
- children []subcommand
- }{
- {argparserInspect, inspectors},
- {argparserRepair, repairers},
- } {
- for _, child := range cmdgrp.children {
- cmd := child.Command
- runE := child.RunE
- cmd.RunE = func(cmd *cobra.Command, args []string) error {
- ctx := cmd.Context()
- logger := textui.NewLogger(os.Stderr, logLevelFlag.Level)
- ctx = dlog.WithLogger(ctx, logger)
- if logLevelFlag.Level >= dlog.LogLevelDebug {
- ctx = dlog.WithField(ctx, "mem", new(textui.LiveMemUse))
- }
- dlog.SetFallbackLogger(logger.WithField("btrfs-progs.THIS_IS_A_BUG", true))
-
- grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
- EnableSignalHandling: true,
- })
- grp.Go("main", func(ctx context.Context) (err error) {
- maybeSetErr := func(_err error) {
- if _err != nil && err == nil {
- err = _err
- }
- }
- defer func() {
- maybeSetErr(stopProfiling())
- }()
- fs, err := btrfsutil.Open(ctx, openFlag, pvsFlag...)
- if err != nil {
- return err
- }
- defer func() {
- maybeSetErr(fs.Close())
- }()
-
- if mappingsFlag != "" {
- mappingsJSON, err := readJSONFile[[]btrfsvol.Mapping](ctx, mappingsFlag)
- if err != nil {
- return err
- }
- for _, mapping := range mappingsJSON {
- if err := fs.LV.AddMapping(mapping); err != nil {
- return err
- }
- }
- }
-
- cmd.SetContext(ctx)
- return runE(fs, cmd, args)
- })
- return grp.Wait()
- }
- cmdgrp.parent.AddCommand(&cmd)
- }
- }
+ globalFlags.openFlag = os.O_RDONLY
+
+ // Sub-commands
+
+ argparser.AddCommand(inspectors)
+ argparser.AddCommand(repairers)
+
+ // Run
if err := argparser.ExecuteContext(context.Background()); err != nil {
textui.Fprintf(os.Stderr, "%v: error: %v\n", argparser.CommandPath(), err)
os.Exit(1)
}
}
+
+func run(runE func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
+ return func(cmd *cobra.Command, args []string) error {
+ ctx := cmd.Context()
+ logger := textui.NewLogger(os.Stderr, globalFlags.logLevel.Level)
+ ctx = dlog.WithLogger(ctx, logger)
+ if globalFlags.logLevel.Level >= dlog.LogLevelDebug {
+ ctx = dlog.WithField(ctx, "mem", new(textui.LiveMemUse))
+ }
+ dlog.SetFallbackLogger(logger.WithField("btrfs-progs.THIS_IS_A_BUG", true))
+
+ grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
+ EnableSignalHandling: true,
+ })
+ grp.Go("main", func(ctx context.Context) (err error) {
+ maybeSetErr := func(_err error) {
+ if _err != nil && err == nil {
+ err = _err
+ }
+ }
+
+ defer func() {
+ maybeSetErr(globalFlags.stopProfiling())
+ }()
+ cmd.SetContext(ctx)
+ return runE(cmd, args)
+ })
+ return grp.Wait()
+ }
+}
+
+func runWithRawFS(runE func(*btrfs.FS, *cobra.Command, []string) error) func(*cobra.Command, []string) error {
+ return run(func(cmd *cobra.Command, args []string) (err error) {
+ maybeSetErr := func(_err error) {
+ if _err != nil && err == nil {
+ err = _err
+ }
+ }
+
+ if len(globalFlags.pvs) == 0 {
+ // We do this here instead of calling argparser.MarkPersistentFlagRequired("pv") so that
+ // it doesn't interfere with the `help` sub-command.
+ return cliutil.FlagErrorFunc(cmd, fmt.Errorf("must specify 1 or more physical volumes with --pv"))
+ }
+ fs, err := btrfsutil.Open(cmd.Context(), globalFlags.openFlag, globalFlags.pvs...)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ maybeSetErr(fs.Close())
+ }()
+
+ if globalFlags.mappings != "" {
+ mappingsJSON, err := readJSONFile[[]btrfsvol.Mapping](cmd.Context(), globalFlags.mappings)
+ if err != nil {
+ return err
+ }
+ for _, mapping := range mappingsJSON {
+ if err := fs.LV.AddMapping(mapping); err != nil {
+ return err
+ }
+ }
+ }
+
+ return runE(fs, cmd, args)
+ })
+}