From a0daaacdd61f196fbc0ca90ed996e7eeb4d4fcdd Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Mon, 11 Jul 2022 22:48:35 -0600 Subject: move chunk reconstruction to btrfsinspect, add --mappings --- cmd/btrfs-fsck/main.go | 9 +- cmd/btrfs-fsck/pass0.go | 20 +- cmd/btrfs-fsck/pass1.go | 313 +------------------------ cmd/btrfs-rec/inspect_recoverchunks.go | 75 ++++++ cmd/btrfs-rec/main.go | 27 ++- lib/btrfs/btrfsvol/lvm.go | 4 +- lib/btrfsprogs/btrfsinspect/print_addrspace.go | 77 ++++++ lib/btrfsprogs/btrfsinspect/recoverchunks.go | 223 ++++++++++++++++++ lib/btrfsprogs/btrfsutil/open.go | 13 +- lib/btrfsprogs/btrfsutil/scan.go | 6 +- 10 files changed, 434 insertions(+), 333 deletions(-) create mode 100644 cmd/btrfs-rec/inspect_recoverchunks.go create mode 100644 lib/btrfsprogs/btrfsinspect/print_addrspace.go create mode 100644 lib/btrfsprogs/btrfsinspect/recoverchunks.go diff --git a/cmd/btrfs-fsck/main.go b/cmd/btrfs-fsck/main.go index 4a230c6..fd9eb1b 100644 --- a/cmd/btrfs-fsck/main.go +++ b/cmd/btrfs-fsck/main.go @@ -5,25 +5,26 @@ package main import ( + "context" "fmt" "os" ) func main() { - if err := Main(os.Args[1:]...); err != nil { + if err := Main(context.Background(), 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) { +func Main(ctx context.Context, imgfilenames ...string) (err error) { maybeSetErr := func(_err error) { if _err != nil && err == nil { err = _err } } - fs, sb, err := pass0(imgfilenames...) + fs, sb, err := pass0(ctx, imgfilenames...) if err != nil { return err } @@ -31,7 +32,7 @@ func Main(imgfilenames ...string) (err error) { maybeSetErr(fs.Close()) }() - foundNodes, err := pass1(fs, sb) + foundNodes, err := pass1(ctx, fs, sb) if err != nil { return err } diff --git a/cmd/btrfs-fsck/pass0.go b/cmd/btrfs-fsck/pass0.go index d69e6c0..ae86003 100644 --- a/cmd/btrfs-fsck/pass0.go +++ b/cmd/btrfs-fsck/pass0.go @@ -5,30 +5,22 @@ package main import ( + "context" "fmt" "os" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil" "git.lukeshu.com/btrfs-progs-ng/lib/util" ) -func pass0(filenames ...string) (*btrfs.FS, *util.Ref[btrfsvol.PhysicalAddr, btrfs.Superblock], error) { +func pass0(ctx context.Context, filenames ...string) (*btrfs.FS, *util.Ref[btrfsvol.PhysicalAddr, btrfs.Superblock], error) { fmt.Printf("\nPass 0: init and superblocks...\n") - fs := new(btrfs.FS) - for _, filename := range filenames { - fmt.Printf("Pass 0: ... adding device %q...\n", filename) - - fh, err := os.OpenFile(filename, os.O_RDWR, 0) - if err != nil { - _ = fs.Close() - return nil, nil, fmt.Errorf("device %q: %w", filename, err) - } - - if err := fs.AddDevice(&btrfs.Device{File: fh}); err != nil { - fmt.Printf("Pass 0: ... add device %q: error: %v\n", filename, err) - } + fs, err := btrfsutil.Open(ctx, os.O_RDWR, filenames...) + if err != nil { + return nil, nil, err } sb, err := fs.Superblock() diff --git a/cmd/btrfs-fsck/pass1.go b/cmd/btrfs-fsck/pass1.go index 0f1ebbf..0899cfc 100644 --- a/cmd/btrfs-fsck/pass1.go +++ b/cmd/btrfs-fsck/pass1.go @@ -5,23 +5,19 @@ package main import ( - "encoding/json" - "errors" + "context" "fmt" - iofs "io/fs" "os" - "sort" - - "golang.org/x/text/message" "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/btrfsvol" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil" "git.lukeshu.com/btrfs-progs-ng/lib/util" ) -func pass1(fs *btrfs.FS, superblock *util.Ref[btrfsvol.PhysicalAddr, btrfs.Superblock]) (map[btrfsvol.LogicalAddr]struct{}, error) { +func pass1(ctx context.Context, fs *btrfs.FS, superblock *util.Ref[btrfsvol.PhysicalAddr, btrfs.Superblock]) (map[btrfsvol.LogicalAddr]struct{}, error) { fmt.Printf("\nPass 1: chunk mappings...\n") fmt.Printf("Pass 1: ... walking fs\n") @@ -47,13 +43,13 @@ func pass1(fs *btrfs.FS, superblock *util.Ref[btrfsvol.PhysicalAddr, btrfs.Super fsFoundNodes := make(map[btrfsvol.LogicalAddr]struct{}) for _, dev := range fs.LV.PhysicalVolumes() { fmt.Printf("Pass 1: ... dev[%q] scanning for nodes...\n", dev.Name()) - devResult, err := pass1ScanOneDev(dev, superblock.Data) + devResult, err := btrfsinspect.ScanOneDev(ctx, dev, superblock.Data) if err != nil { return nil, err } fmt.Printf("Pass 1: ... dev[%q] re-inserting lost+found mappings\n", dev.Name()) - devResult.AddToLV(fs, dev) + devResult.AddToLV(ctx, fs, dev) // merge those results in to the total-fs results for laddr := range devResult.FoundNodes { @@ -62,9 +58,9 @@ func pass1(fs *btrfs.FS, superblock *util.Ref[btrfsvol.PhysicalAddr, btrfs.Super } fmt.Printf("Pass 1: ... logical address space:\n") - pass1PrintLogicalSpace(fs) + btrfsinspect.PrintLogicalSpace(os.Stdout, fs) fmt.Printf("Pass 1: ... physical address space:\n") - pass1PrintPhysicalSpace(fs) + btrfsinspect.PrintPhysicalSpace(os.Stdout, fs) fmt.Printf("Pass 1: ... writing re-constructed chunks\n") pass1WriteReconstructedChunks(fs) @@ -72,301 +68,6 @@ func pass1(fs *btrfs.FS, superblock *util.Ref[btrfsvol.PhysicalAddr, btrfs.Super return fsFoundNodes, nil } -type pass1ScanOneDevResult struct { - FoundNodes map[btrfsvol.LogicalAddr][]btrfsvol.PhysicalAddr - FoundChunks []btrfs.SysChunk - FoundBlockGroups []sysBlockGroup - FoundDevExtents []sysDevExtent -} - -type sysBlockGroup struct { - Key btrfs.Key - BG btrfsitem.BlockGroup -} - -type sysDevExtent struct { - Key btrfs.Key - DevExt btrfsitem.DevExtent -} - -func (found pass1ScanOneDevResult) AddToLV(fs *btrfs.FS, dev *btrfs.Device) { - sb, _ := dev.Superblock() - - total := len(found.FoundChunks) + len(found.FoundDevExtents) - for _, paddrs := range found.FoundNodes { - total += len(paddrs) - } - lastProgress := -1 - done := 0 - printProgress := func() { - pct := int(100 * float64(done) / float64(total)) - if pct != lastProgress || done == total { - fmt.Printf("Pass 1: ... added %v%% of the mappings (%v/%v=>%v)\n", - pct, done, total, len(fs.LV.Mappings())) - lastProgress = pct - } - } - printProgress() - - for _, chunk := range found.FoundChunks { - for _, mapping := range chunk.Chunk.Mappings(chunk.Key) { - if err := fs.LV.AddMapping(mapping); err != nil { - fmt.Printf("Pass 1: ... error: adding chunk: %v\n", err) - } - done++ - printProgress() - } - } - - for _, ext := range found.FoundDevExtents { - if err := fs.LV.AddMapping(ext.DevExt.Mapping(ext.Key)); err != nil { - fmt.Printf("Pass 1: ... error: adding devext: %v\n", err) - } - done++ - printProgress() - } - - // Do the nodes last to avoid bloating the mappings table too - // much. (Because nodes are numerous and small, while the - // others are few and large; so it is likely that many of the - // nodes will be subsumed by other things.) - // - // Sort them so that progress numbers are predictable. - for _, laddr := range util.SortedMapKeys(found.FoundNodes) { - for _, paddr := range found.FoundNodes[laddr] { - if err := fs.LV.AddMapping(btrfsvol.Mapping{ - LAddr: laddr, - PAddr: btrfsvol.QualifiedPhysicalAddr{ - Dev: sb.Data.DevItem.DevID, - Addr: paddr, - }, - Size: btrfsvol.AddrDelta(sb.Data.NodeSize), - SizeLocked: false, - Flags: nil, - }); err != nil { - fmt.Printf("Pass 1: ... error: adding node ident: %v\n", err) - } - done++ - printProgress() - } - } - - // Use block groups to add missing flags (and as a hint to - // combine node entries). - // - // First dedup them, because they change for allocations and - // CoW means that they'll bounce around a lot, so you likely - // have oodles of duplicates? - type blockgroup struct { - LAddr btrfsvol.LogicalAddr - Size btrfsvol.AddrDelta - Flags btrfsvol.BlockGroupFlags - } - bgsSet := make(map[blockgroup]struct{}) - for _, bg := range found.FoundBlockGroups { - bgsSet[blockgroup{ - LAddr: btrfsvol.LogicalAddr(bg.Key.ObjectID), - Size: btrfsvol.AddrDelta(bg.Key.Offset), - Flags: bg.BG.Flags, - }] = struct{}{} - } - bgsOrdered := util.MapKeys(bgsSet) - sort.Slice(bgsOrdered, func(i, j int) bool { - return bgsOrdered[i].LAddr < bgsOrdered[j].LAddr - }) - for _, bg := range bgsOrdered { - otherLAddr, otherPAddr := fs.LV.ResolveAny(bg.LAddr, bg.Size) - if otherLAddr < 0 || otherPAddr.Addr < 0 { - fmt.Printf("Pass 1: ... error: could not pair blockgroup laddr=%v (size=%v flags=%v) with a mapping\n", - bg.LAddr, bg.Size, bg.Flags) - continue - } - - offsetWithinChunk := otherLAddr.Sub(bg.LAddr) - flags := bg.Flags - mapping := btrfsvol.Mapping{ - LAddr: bg.LAddr, - PAddr: btrfsvol.QualifiedPhysicalAddr{ - Dev: otherPAddr.Dev, - Addr: otherPAddr.Addr.Add(-offsetWithinChunk), - }, - Size: bg.Size, - SizeLocked: true, - Flags: &flags, - } - if err := fs.LV.AddMapping(mapping); err != nil { - fmt.Printf("Pass 1: ... error: adding flags from blockgroup: %v\n", err) - } - } -} - -func pass1ScanOneDev(dev *btrfs.Device, superblock btrfs.Superblock) (pass1ScanOneDevResult, error) { - const jsonFilename = "/home/lukeshu/btrfs/pass1v2.json" - - var result pass1ScanOneDevResult - bs, err := os.ReadFile(jsonFilename) - if err != nil { - if errors.Is(err, iofs.ErrNotExist) { - result, err := pass1ScanOneDev_real(dev, superblock) - if err != nil { - panic(err) - } - bs, err := json.Marshal(result) - if err != nil { - panic(err) - } - if err := os.WriteFile(jsonFilename, bs, 0600); err != nil { - panic(err) - } - return result, nil - } - return result, err - } - if err := json.Unmarshal(bs, &result); err != nil { - return result, err - } - pass1ScanOneDev_printProgress(dev, 100, result) - return result, nil -} - -func pass1ScanOneDev_printProgress(dev *btrfs.Device, pct int, result pass1ScanOneDevResult) { - fmt.Printf("Pass 1: ... dev[%q] scanned %v%% (found: %v nodes, %v chunks, %v block groups, %v dev extents)\n", - dev.Name(), pct, - len(result.FoundNodes), - len(result.FoundChunks), - len(result.FoundBlockGroups), - len(result.FoundDevExtents)) -} - -func pass1ScanOneDev_real(dev *btrfs.Device, superblock btrfs.Superblock) (pass1ScanOneDevResult, error) { - result := pass1ScanOneDevResult{ - FoundNodes: make(map[btrfsvol.LogicalAddr][]btrfsvol.PhysicalAddr), - } - - devSize, _ := dev.Size() - lastProgress := -1 - - err := btrfsutil.ScanForNodes(dev, superblock, func(nodeRef *util.Ref[btrfsvol.PhysicalAddr, btrfs.Node], err error) { - if err != nil { - fmt.Printf("Pass 1: ... dev[%q] error: %v\n", dev.Name(), err) - return - } - result.FoundNodes[nodeRef.Data.Head.Addr] = append(result.FoundNodes[nodeRef.Data.Head.Addr], nodeRef.Addr) - for i, item := range nodeRef.Data.BodyLeaf { - switch item.Head.Key.ItemType { - case btrfsitem.CHUNK_ITEM_KEY: - chunk, ok := item.Body.(btrfsitem.Chunk) - if !ok { - fmt.Printf("Pass 1: ... dev[%q] node@%v: item %v: error: type is CHUNK_ITEM_KEY, but struct is %T\n", - dev.Name(), nodeRef.Addr, i, item.Body) - continue - } - //fmt.Printf("Pass 1: ... dev[%q] node@%v: item %v: found chunk\n", - // dev.Name(), nodeRef.Addr, i) - result.FoundChunks = append(result.FoundChunks, btrfs.SysChunk{ - Key: item.Head.Key, - Chunk: chunk, - }) - case btrfsitem.BLOCK_GROUP_ITEM_KEY: - bg, ok := item.Body.(btrfsitem.BlockGroup) - if !ok { - fmt.Printf("Pass 1: ... dev[%q] node@%v: item %v: error: type is BLOCK_GROUP_ITEM_KEY, but struct is %T\n", - dev.Name(), nodeRef.Addr, i, item.Body) - continue - } - //fmt.Printf("Pass 1: ... dev[%q] node@%v: item %v: found block group\n", - // dev.Name(), nodeRef.Addr, i) - result.FoundBlockGroups = append(result.FoundBlockGroups, sysBlockGroup{ - Key: item.Head.Key, - BG: bg, - }) - case btrfsitem.DEV_EXTENT_KEY: - devext, ok := item.Body.(btrfsitem.DevExtent) - if !ok { - fmt.Printf("Pass 1: ... dev[%q] node@%v: item %v: error: type is DEV_EXTENT_KEY, but struct is %T\n", - dev.Name(), nodeRef.Addr, i, item.Body) - continue - } - //fmt.Printf("Pass 1: ... dev[%q] node@%v: item %v: found dev extent\n", - // dev.Name(), nodeRef.Addr, i) - result.FoundDevExtents = append(result.FoundDevExtents, sysDevExtent{ - Key: item.Head.Key, - DevExt: devext, - }) - } - } - }, func(pos btrfsvol.PhysicalAddr) { - pct := int(100 * float64(pos) / float64(devSize)) - if pct != lastProgress || pos == devSize { - pass1ScanOneDev_printProgress(dev, pct, result) - lastProgress = pct - } - }) - - return result, err -} - -func pass1PrintLogicalSpace(fs *btrfs.FS) { - mappings := fs.LV.Mappings() - var prevBeg, prevEnd btrfsvol.LogicalAddr - var sumHole, sumChunk btrfsvol.AddrDelta - for _, mapping := range mappings { - if mapping.LAddr > prevEnd { - size := mapping.LAddr.Sub(prevEnd) - fmt.Printf("logical_hole laddr=%v size=%v\n", prevEnd, size) - sumHole += size - } - if mapping.LAddr != prevBeg { - if mapping.Flags == nil { - fmt.Printf("chunk laddr=%v size=%v flags=(missing)\n", - mapping.LAddr, mapping.Size) - } else { - fmt.Printf("chunk laddr=%v size=%v flags=%v\n", - mapping.LAddr, mapping.Size, *mapping.Flags) - } - } - fmt.Printf("\tstripe dev_id=%v paddr=%v\n", - mapping.PAddr.Dev, mapping.PAddr.Addr) - sumChunk += mapping.Size - prevBeg = mapping.LAddr - prevEnd = mapping.LAddr.Add(mapping.Size) - } - p := message.NewPrinter(message.MatchLanguage("en")) - p.Printf("total logical holes = %v (%d)\n", sumHole, int64(sumHole)) - p.Printf("total logical chunks = %v (%d)\n", sumChunk, int64(sumChunk)) - p.Printf("total logical addr space = %v (%d)\n", prevEnd, int64(prevEnd)) -} - -func pass1PrintPhysicalSpace(fs *btrfs.FS) { - mappings := fs.LV.Mappings() - sort.Slice(mappings, func(i, j int) bool { - return mappings[i].PAddr.Cmp(mappings[j].PAddr) < 0 - }) - - var prevDev btrfsvol.DeviceID = 0 - var prevEnd btrfsvol.PhysicalAddr - var sumHole, sumExt btrfsvol.AddrDelta - for _, mapping := range mappings { - if mapping.PAddr.Dev != prevDev { - prevDev = mapping.PAddr.Dev - prevEnd = 0 - } - if mapping.PAddr.Addr > prevEnd { - size := mapping.PAddr.Addr.Sub(prevEnd) - fmt.Printf("physical_hole paddr=%v size=%v\n", prevEnd, size) - sumHole += size - } - fmt.Printf("devext dev=%v paddr=%v size=%v laddr=%v\n", - mapping.PAddr.Dev, mapping.PAddr.Addr, mapping.Size, mapping.LAddr) - sumExt += mapping.Size - prevEnd = mapping.PAddr.Addr.Add(mapping.Size) - } - p := message.NewPrinter(message.MatchLanguage("en")) - p.Printf("total physical holes = %v (%d)\n", sumHole, int64(sumHole)) - p.Printf("total physical extents = %v (%d)\n", sumExt, int64(sumExt)) - p.Printf("total physical addr space = %v (%d)\n", prevEnd, int64(prevEnd)) -} - func pass1WriteReconstructedChunks(fs *btrfs.FS) { sbRef, _ := fs.Superblock() superblock := sbRef.Data diff --git a/cmd/btrfs-rec/inspect_recoverchunks.go b/cmd/btrfs-rec/inspect_recoverchunks.go new file mode 100644 index 0000000..bc32248 --- /dev/null +++ b/cmd/btrfs-rec/inspect_recoverchunks.go @@ -0,0 +1,75 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "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/btrfsprogs/btrfsinspect" +) + +func init() { + inspectors = append(inspectors, subcommand{ + Command: cobra.Command{ + Use: "recover-chunks", + 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.NoArgs), + }, + RunE: func(fs *btrfs.FS, cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + + dlog.Info(ctx, "Reading superblock...") + superblock, err := fs.Superblock() + if err != nil { + return err + } + + for _, dev := range fs.LV.PhysicalVolumes() { + dlog.Infof(ctx, "dev[%q] Scanning for unreachable nodes...", dev.Name()) + devResult, err := btrfsinspect.ScanOneDev(ctx, dev, superblock.Data) + if err != nil { + return err + } + + dlog.Infof(ctx, "dev[%q] Re-inserting lost+found mappings...", dev.Name()) + devResult.AddToLV(ctx, fs, dev) + } + + dlog.Infof(ctx, "Writing reconstructed mappings to stdout...") + + mappings := fs.LV.Mappings() + _, _ = io.WriteString(os.Stdout, "{\n \"Mappings\": [\n") + for i, mapping := range mappings { + suffix := "," + if i == len(mappings)-1 { + suffix = "" + } + bs, err := json.Marshal(mapping) + if err != nil { + return err + } + fmt.Printf(" %s%s\n", bs, suffix) + } + _, _ = io.WriteString(os.Stdout, " ]\n}\n") + return nil + }, + }) +} diff --git a/cmd/btrfs-rec/main.go b/cmd/btrfs-rec/main.go index b68bdd8..b17c8ec 100644 --- a/cmd/btrfs-rec/main.go +++ b/cmd/btrfs-rec/main.go @@ -6,6 +6,7 @@ package main import ( "context" + "encoding/json" "fmt" "os" @@ -17,6 +18,7 @@ import ( "github.com/spf13/pflag" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil" ) @@ -45,6 +47,7 @@ func main() { Level: logrus.InfoLevel, } var pvsFlag []string + var mappingsFlag string argparser := &cobra.Command{ Use: "btrfs-rec {[flags]|SUBCOMMAND}", @@ -70,6 +73,10 @@ func main() { 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) + } var openFlag int = os.O_RDONLY @@ -121,7 +128,7 @@ func main() { err = _err } } - fs, err := btrfsutil.Open(openFlag, pvsFlag...) + fs, err := btrfsutil.Open(ctx, openFlag, pvsFlag...) if err != nil { return err } @@ -129,6 +136,24 @@ func main() { maybeSetErr(fs.Close()) }() + if mappingsFlag != "" { + bs, err := os.ReadFile(mappingsFlag) + if err != nil { + return err + } + var mappingsJSON struct { + Mappings []btrfsvol.Mapping + } + if err := json.Unmarshal(bs, &mappingsJSON); err != nil { + return err + } + for _, mapping := range mappingsJSON.Mappings { + if err := fs.LV.AddMapping(mapping); err != nil { + return err + } + } + } + cmd.SetContext(ctx) return runE(fs, cmd, args) }) diff --git a/lib/btrfs/btrfsvol/lvm.go b/lib/btrfs/btrfsvol/lvm.go index c25bd43..65636f6 100644 --- a/lib/btrfs/btrfsvol/lvm.go +++ b/lib/btrfs/btrfsvol/lvm.go @@ -99,8 +99,8 @@ type Mapping struct { LAddr LogicalAddr PAddr QualifiedPhysicalAddr Size AddrDelta - SizeLocked bool - Flags *BlockGroupFlags + SizeLocked bool `json:",omitempty"` + Flags *BlockGroupFlags `json:",omitempty"` } func (lv *LogicalVolume[PhysicalVolume]) AddMapping(m Mapping) error { diff --git a/lib/btrfsprogs/btrfsinspect/print_addrspace.go b/lib/btrfsprogs/btrfsinspect/print_addrspace.go new file mode 100644 index 0000000..d291b7c --- /dev/null +++ b/lib/btrfsprogs/btrfsinspect/print_addrspace.go @@ -0,0 +1,77 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package btrfsinspect + +import ( + "fmt" + "io" + "sort" + + "golang.org/x/text/message" + + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" +) + +func PrintLogicalSpace(out io.Writer, fs *btrfs.FS) { + mappings := fs.LV.Mappings() + var prevBeg, prevEnd btrfsvol.LogicalAddr + var sumHole, sumChunk btrfsvol.AddrDelta + for _, mapping := range mappings { + if mapping.LAddr > prevEnd { + size := mapping.LAddr.Sub(prevEnd) + fmt.Fprintf(out, "logical_hole laddr=%v size=%v\n", prevEnd, size) + sumHole += size + } + if mapping.LAddr != prevBeg { + if mapping.Flags == nil { + fmt.Fprintf(out, "chunk laddr=%v size=%v flags=(missing)\n", + mapping.LAddr, mapping.Size) + } else { + fmt.Fprintf(out, "chunk laddr=%v size=%v flags=%v\n", + mapping.LAddr, mapping.Size, *mapping.Flags) + } + } + fmt.Fprintf(out, "\tstripe dev_id=%v paddr=%v\n", + mapping.PAddr.Dev, mapping.PAddr.Addr) + sumChunk += mapping.Size + prevBeg = mapping.LAddr + prevEnd = mapping.LAddr.Add(mapping.Size) + } + p := message.NewPrinter(message.MatchLanguage("en")) + p.Fprintf(out, "total logical holes = %v (%d)\n", sumHole, int64(sumHole)) + p.Fprintf(out, "total logical chunks = %v (%d)\n", sumChunk, int64(sumChunk)) + p.Fprintf(out, "total logical addr space = %v (%d)\n", prevEnd, int64(prevEnd)) +} + +func PrintPhysicalSpace(out io.Writer, fs *btrfs.FS) { + mappings := fs.LV.Mappings() + sort.Slice(mappings, func(i, j int) bool { + return mappings[i].PAddr.Cmp(mappings[j].PAddr) < 0 + }) + + var prevDev btrfsvol.DeviceID = 0 + var prevEnd btrfsvol.PhysicalAddr + var sumHole, sumExt btrfsvol.AddrDelta + for _, mapping := range mappings { + if mapping.PAddr.Dev != prevDev { + prevDev = mapping.PAddr.Dev + prevEnd = 0 + } + if mapping.PAddr.Addr > prevEnd { + size := mapping.PAddr.Addr.Sub(prevEnd) + fmt.Fprintf(out, "physical_hole paddr=%v size=%v\n", prevEnd, size) + sumHole += size + } + fmt.Fprintf(out, "devext dev=%v paddr=%v size=%v laddr=%v\n", + mapping.PAddr.Dev, mapping.PAddr.Addr, mapping.Size, mapping.LAddr) + sumExt += mapping.Size + prevEnd = mapping.PAddr.Addr.Add(mapping.Size) + } + p := message.NewPrinter(message.MatchLanguage("en")) + p.Fprintf(out, "total physical holes = %v (%d)\n", sumHole, int64(sumHole)) + p.Fprintf(out, "total physical extents = %v (%d)\n", sumExt, int64(sumExt)) + p.Fprintf(out, "total physical addr space = %v (%d)\n", prevEnd, int64(prevEnd)) +} diff --git a/lib/btrfsprogs/btrfsinspect/recoverchunks.go b/lib/btrfsprogs/btrfsinspect/recoverchunks.go new file mode 100644 index 0000000..354328e --- /dev/null +++ b/lib/btrfsprogs/btrfsinspect/recoverchunks.go @@ -0,0 +1,223 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package btrfsinspect + +import ( + "context" + "sort" + + "github.com/datawire/dlib/dlog" + + "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/btrfsvol" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsutil" + "git.lukeshu.com/btrfs-progs-ng/lib/util" +) + +type ScanOneDevResult struct { + FoundNodes map[btrfsvol.LogicalAddr][]btrfsvol.PhysicalAddr + FoundChunks []btrfs.SysChunk + FoundBlockGroups []SysBlockGroup + FoundDevExtents []SysDevExtent +} + +type SysBlockGroup struct { + Key btrfs.Key + BG btrfsitem.BlockGroup +} + +type SysDevExtent struct { + Key btrfs.Key + DevExt btrfsitem.DevExtent +} + +func (found ScanOneDevResult) AddToLV(ctx context.Context, fs *btrfs.FS, dev *btrfs.Device) { + sb, _ := dev.Superblock() + + total := len(found.FoundChunks) + len(found.FoundDevExtents) + for _, paddrs := range found.FoundNodes { + total += len(paddrs) + } + lastProgress := -1 + done := 0 + printProgress := func() { + pct := int(100 * float64(done) / float64(total)) + if pct != lastProgress || done == total { + dlog.Infof(ctx, "... dev[%q] added %v%% of the mappings (%v/%v=>%v)", + dev.Name(), pct, done, total, len(fs.LV.Mappings())) + lastProgress = pct + } + } + printProgress() + + for _, chunk := range found.FoundChunks { + for _, mapping := range chunk.Chunk.Mappings(chunk.Key) { + if err := fs.LV.AddMapping(mapping); err != nil { + dlog.Errorf(ctx, "... dev[%q] error: adding chunk: %v", + dev.Name(), err) + } + done++ + printProgress() + } + } + + for _, ext := range found.FoundDevExtents { + if err := fs.LV.AddMapping(ext.DevExt.Mapping(ext.Key)); err != nil { + dlog.Errorf(ctx, "... dev[%q] error: adding devext: %v", + dev.Name(), err) + } + done++ + printProgress() + } + + // Do the nodes last to avoid bloating the mappings table too + // much. (Because nodes are numerous and small, while the + // others are few and large; so it is likely that many of the + // nodes will be subsumed by other things.) + // + // Sort them so that progress numbers are predictable. + for _, laddr := range util.SortedMapKeys(found.FoundNodes) { + for _, paddr := range found.FoundNodes[laddr] { + if err := fs.LV.AddMapping(btrfsvol.Mapping{ + LAddr: laddr, + PAddr: btrfsvol.QualifiedPhysicalAddr{ + Dev: sb.Data.DevItem.DevID, + Addr: paddr, + }, + Size: btrfsvol.AddrDelta(sb.Data.NodeSize), + SizeLocked: false, + Flags: nil, + }); err != nil { + dlog.Errorf(ctx, "... dev[%q] error: adding node ident: %v", + dev.Name(), err) + } + done++ + printProgress() + } + } + + // Use block groups to add missing flags (and as a hint to + // combine node entries). + // + // First dedup them, because they change for allocations and + // CoW means that they'll bounce around a lot, so you likely + // have oodles of duplicates? + type blockgroup struct { + LAddr btrfsvol.LogicalAddr + Size btrfsvol.AddrDelta + Flags btrfsvol.BlockGroupFlags + } + bgsSet := make(map[blockgroup]struct{}) + for _, bg := range found.FoundBlockGroups { + bgsSet[blockgroup{ + LAddr: btrfsvol.LogicalAddr(bg.Key.ObjectID), + Size: btrfsvol.AddrDelta(bg.Key.Offset), + Flags: bg.BG.Flags, + }] = struct{}{} + } + bgsOrdered := util.MapKeys(bgsSet) + sort.Slice(bgsOrdered, func(i, j int) bool { + return bgsOrdered[i].LAddr < bgsOrdered[j].LAddr + }) + for _, bg := range bgsOrdered { + otherLAddr, otherPAddr := fs.LV.ResolveAny(bg.LAddr, bg.Size) + if otherLAddr < 0 || otherPAddr.Addr < 0 { + dlog.Errorf(ctx, "... dev[%q] error: could not pair blockgroup laddr=%v (size=%v flags=%v) with a mapping", + dev.Name(), bg.LAddr, bg.Size, bg.Flags) + continue + } + + offsetWithinChunk := otherLAddr.Sub(bg.LAddr) + flags := bg.Flags + mapping := btrfsvol.Mapping{ + LAddr: bg.LAddr, + PAddr: btrfsvol.QualifiedPhysicalAddr{ + Dev: otherPAddr.Dev, + Addr: otherPAddr.Addr.Add(-offsetWithinChunk), + }, + Size: bg.Size, + SizeLocked: true, + Flags: &flags, + } + if err := fs.LV.AddMapping(mapping); err != nil { + dlog.Errorf(ctx, "... dev[%q] error: adding flags from blockgroup: %v", + dev.Name(), err) + } + } +} + +func ScanOneDev(ctx context.Context, dev *btrfs.Device, superblock btrfs.Superblock) (ScanOneDevResult, error) { + result := ScanOneDevResult{ + FoundNodes: make(map[btrfsvol.LogicalAddr][]btrfsvol.PhysicalAddr), + } + + devSize, _ := dev.Size() + lastProgress := -1 + + err := btrfsutil.ScanForNodes(ctx, dev, superblock, func(nodeRef *util.Ref[btrfsvol.PhysicalAddr, btrfs.Node], err error) { + if err != nil { + dlog.Infof(ctx, "... dev[%q] error: %v", dev.Name(), err) + return + } + result.FoundNodes[nodeRef.Data.Head.Addr] = append(result.FoundNodes[nodeRef.Data.Head.Addr], nodeRef.Addr) + for i, item := range nodeRef.Data.BodyLeaf { + switch item.Head.Key.ItemType { + case btrfsitem.CHUNK_ITEM_KEY: + chunk, ok := item.Body.(btrfsitem.Chunk) + if !ok { + dlog.Errorf(ctx, "... dev[%q] node@%v: item %v: error: type is CHUNK_ITEM_KEY, but struct is %T", + dev.Name(), nodeRef.Addr, i, item.Body) + continue + } + //dlog.Tracef(ctx, "... dev[%q] node@%v: item %v: found chunk", + // dev.Name(), nodeRef.Addr, i) + result.FoundChunks = append(result.FoundChunks, btrfs.SysChunk{ + Key: item.Head.Key, + Chunk: chunk, + }) + case btrfsitem.BLOCK_GROUP_ITEM_KEY: + bg, ok := item.Body.(btrfsitem.BlockGroup) + if !ok { + dlog.Errorf(ctx, "... dev[%q] node@%v: item %v: error: type is BLOCK_GROUP_ITEM_KEY, but struct is %T", + dev.Name(), nodeRef.Addr, i, item.Body) + continue + } + //dlog.Tracef(ctx, "... dev[%q] node@%v: item %v: found block group", + // dev.Name(), nodeRef.Addr, i) + result.FoundBlockGroups = append(result.FoundBlockGroups, SysBlockGroup{ + Key: item.Head.Key, + BG: bg, + }) + case btrfsitem.DEV_EXTENT_KEY: + devext, ok := item.Body.(btrfsitem.DevExtent) + if !ok { + dlog.Errorf(ctx, "... dev[%q] node@%v: item %v: error: type is DEV_EXTENT_KEY, but struct is %T", + dev.Name(), nodeRef.Addr, i, item.Body) + continue + } + //dlog.Tracef(ctx, "... dev[%q] node@%v: item %v: found dev extent", + // dev.Name(), nodeRef.Addr, i) + result.FoundDevExtents = append(result.FoundDevExtents, SysDevExtent{ + Key: item.Head.Key, + DevExt: devext, + }) + } + } + }, func(pos btrfsvol.PhysicalAddr) { + pct := int(100 * float64(pos) / float64(devSize)) + if pct != lastProgress || pos == devSize { + dlog.Infof(ctx, "... dev[%q] scanned %v%% (found: %v nodes, %v chunks, %v block groups, %v dev extents)", + dev.Name(), pct, + len(result.FoundNodes), + len(result.FoundChunks), + len(result.FoundBlockGroups), + len(result.FoundDevExtents)) + lastProgress = pct + } + }) + + return result, err +} diff --git a/lib/btrfsprogs/btrfsutil/open.go b/lib/btrfsprogs/btrfsutil/open.go index cc081a6..80bfe7a 100644 --- a/lib/btrfsprogs/btrfsutil/open.go +++ b/lib/btrfsprogs/btrfsutil/open.go @@ -5,23 +5,26 @@ package btrfsutil import ( + "context" "fmt" "os" + "github.com/datawire/dlib/dlog" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" ) -func Open(flag int, filenames ...string) (*btrfs.FS, error) { +func Open(ctx context.Context, flag int, filenames ...string) (*btrfs.FS, error) { fs := new(btrfs.FS) - for _, filename := range filenames { + for i, filename := range filenames { + dlog.Debugf(ctx, "Adding device file %d/%d %q...", i, len(filenames), filename) fh, err := os.OpenFile(filename, flag, 0) if err != nil { _ = fs.Close() - return nil, fmt.Errorf("file %q: %w", filename, err) + return nil, fmt.Errorf("device file %q: %w", filename, err) } if err := fs.AddDevice(&btrfs.Device{File: fh}); err != nil { - _ = fs.Close() - return nil, fmt.Errorf("file %q: %w", filename, err) + dlog.Errorf(ctx, "device file %q: %v", filename, err) } } return fs, nil diff --git a/lib/btrfsprogs/btrfsutil/scan.go b/lib/btrfsprogs/btrfsutil/scan.go index d83525c..02d4124 100644 --- a/lib/btrfsprogs/btrfsutil/scan.go +++ b/lib/btrfsprogs/btrfsutil/scan.go @@ -5,6 +5,7 @@ package btrfsutil import ( + "context" "errors" "fmt" @@ -17,7 +18,7 @@ import ( // cmds/rescue-chunk-recover.c:scan_one_device(), except rather than // doing something itself when it finds a node, it simply calls a // callback function. -func ScanForNodes(dev *btrfs.Device, sb btrfs.Superblock, fn func(*util.Ref[btrfsvol.PhysicalAddr, btrfs.Node], error), prog func(btrfsvol.PhysicalAddr)) error { +func ScanForNodes(ctx context.Context, dev *btrfs.Device, sb btrfs.Superblock, fn func(*util.Ref[btrfsvol.PhysicalAddr, btrfs.Node], error), prog func(btrfsvol.PhysicalAddr)) error { devSize, err := dev.Size() if err != nil { return err @@ -29,6 +30,9 @@ func ScanForNodes(dev *btrfs.Device, sb btrfs.Superblock, fn func(*util.Ref[btrf } for pos := btrfsvol.PhysicalAddr(0); pos+btrfsvol.PhysicalAddr(sb.NodeSize) < devSize; pos += btrfsvol.PhysicalAddr(sb.SectorSize) { + if ctx.Err() != nil { + return ctx.Err() + } if util.InSlice(pos, btrfs.SuperblockAddrs) { //fmt.Printf("sector@%v is a superblock\n", pos) continue -- cgit v1.2.3-54-g00ecf