diff options
author | Luke Shumaker <lukeshu@lukeshu.com> | 2023-03-14 21:31:51 -0600 |
---|---|---|
committer | Luke Shumaker <lukeshu@lukeshu.com> | 2023-03-14 21:31:51 -0600 |
commit | 697d79fb12b79b65a501ec90dbb45ea165b3457d (patch) | |
tree | 7b8275fe12a2f2d7c57f1e9419b97e693b715f36 /cmd/btrfs-rec | |
parent | 9e9b4e8ac67052d667f6e7fae0a6620b6dbc50c7 (diff) | |
parent | afd2fe91ec604491bd8978b1880c6482e7394240 (diff) |
Merge branch 'lukeshu/reorg-cli'
Diffstat (limited to 'cmd/btrfs-rec')
-rw-r--r-- | cmd/btrfs-rec/inspect/rebuildmappings/scan.go | 294 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect/rebuildtrees/rebuild.go | 5 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect/rebuildtrees/scan.go | 40 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect/rebuildtrees/util.go | 10 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_dumptrees.go | 14 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_listnodes.go | 51 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_lsfiles.go | 14 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_lstrees.go | 81 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_mount.go | 19 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_rebuildmappings.go | 137 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_rebuildtrees.go | 54 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_scandevices.go | 46 | ||||
-rw-r--r-- | cmd/btrfs-rec/inspect_spewitems.go | 14 | ||||
-rw-r--r-- | cmd/btrfs-rec/main.go | 221 |
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) + }) +} |