diff options
Diffstat (limited to 'lib/btrfsprogs/btrfsinspect/scandevices.go')
-rw-r--r-- | lib/btrfsprogs/btrfsinspect/scandevices.go | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/lib/btrfsprogs/btrfsinspect/scandevices.go b/lib/btrfsprogs/btrfsinspect/scandevices.go new file mode 100644 index 0000000..13c5d60 --- /dev/null +++ b/lib/btrfsprogs/btrfsinspect/scandevices.go @@ -0,0 +1,224 @@ +// 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/containers" + "git.lukeshu.com/btrfs-progs-ng/lib/diskio" + "git.lukeshu.com/btrfs-progs-ng/lib/maps" +) + +type ScanOneDeviceResult 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 ScanOneDeviceResult) 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 maps.SortedKeys(found.FoundNodes) { + for _, paddr := range found.FoundNodes[laddr] { + if err := fs.LV.AddMapping(btrfsvol.Mapping{ + LAddr: laddr, + PAddr: btrfsvol.QualifiedPhysicalAddr{ + Dev: sb.DevItem.DevID, + Addr: paddr, + }, + Size: btrfsvol.AddrDelta(sb.NodeSize), + SizeLocked: false, + }); 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 := maps.Keys(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) + mapping := btrfsvol.Mapping{ + LAddr: bg.LAddr, + PAddr: otherPAddr.Add(-offsetWithinChunk), + Size: bg.Size, + SizeLocked: true, + Flags: containers.Optional[btrfsvol.BlockGroupFlags]{ + OK: true, + Val: bg.Flags, + }, + } + if err := fs.LV.AddMapping(mapping); err != nil { + dlog.Errorf(ctx, "... dev[%q] error: adding flags from blockgroup: %v", + dev.Name(), err) + } + } +} + +// ScanOneDevice mostly mimics btrfs-progs +// cmds/rescue-chunk-recover.c:scan_one_device(). +func ScanOneDevice(ctx context.Context, dev *btrfs.Device, superblock btrfs.Superblock) (ScanOneDeviceResult, error) { + result := ScanOneDeviceResult{ + FoundNodes: make(map[btrfsvol.LogicalAddr][]btrfsvol.PhysicalAddr), + } + + devSize := dev.Size() + lastProgress := -1 + + err := ScanForNodes(ctx, dev, superblock, func(nodeRef *diskio.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.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.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.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.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 +} |