summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/btrfs-fsck/main.go9
-rw-r--r--cmd/btrfs-fsck/pass0.go20
-rw-r--r--cmd/btrfs-fsck/pass1.go313
-rw-r--r--cmd/btrfs-rec/inspect_recoverchunks.go75
-rw-r--r--cmd/btrfs-rec/main.go27
-rw-r--r--lib/btrfs/btrfsvol/lvm.go4
-rw-r--r--lib/btrfsprogs/btrfsinspect/print_addrspace.go77
-rw-r--r--lib/btrfsprogs/btrfsinspect/recoverchunks.go223
-rw-r--r--lib/btrfsprogs/btrfsutil/open.go13
-rw-r--r--lib/btrfsprogs/btrfsutil/scan.go6
10 files changed, 434 insertions, 333 deletions
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 <lukeshu@lukeshu.com>
+//
+// 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 <lukeshu@lukeshu.com>
+//
+// 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 <lukeshu@lukeshu.com>
+//
+// 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