summaryrefslogtreecommitdiff
path: root/lib/btrfsutil/scan.go
diff options
context:
space:
mode:
Diffstat (limited to 'lib/btrfsutil/scan.go')
-rw-r--r--lib/btrfsutil/scan.go148
1 files changed, 148 insertions, 0 deletions
diff --git a/lib/btrfsutil/scan.go b/lib/btrfsutil/scan.go
new file mode 100644
index 0000000..97220aa
--- /dev/null
+++ b/lib/btrfsutil/scan.go
@@ -0,0 +1,148 @@
+// Copyright (C) 2022-2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package btrfsutil
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "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/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/diskio"
+ "git.lukeshu.com/btrfs-progs-ng/lib/textui"
+)
+
+var sbSize = btrfsvol.PhysicalAddr(binstruct.StaticSize(btrfstree.Superblock{}))
+
+type DeviceScannerFactory[Stats comparable, Result any] func(ctx context.Context, sb btrfstree.Superblock, numBytes btrfsvol.PhysicalAddr, numSectors int) DeviceScanner[Stats, Result]
+
+type DeviceScanner[Stats comparable, Result any] interface {
+ ScanStats() Stats
+ ScanSector(ctx context.Context, dev *btrfs.Device, paddr btrfsvol.PhysicalAddr) error
+ ScanNode(ctx context.Context, nodeRef *diskio.Ref[btrfsvol.PhysicalAddr, btrfstree.Node]) error
+ ScanDone(ctx context.Context) (Result, error)
+}
+
+type scanStats[T comparable] struct {
+ portion textui.Portion[btrfsvol.PhysicalAddr]
+ stats T
+}
+
+func (s scanStats[T]) String() string {
+ return textui.Sprintf("scanned %v (%v)",
+ s.portion, s.stats)
+}
+
+func ScanDevices[Stats comparable, Result any](ctx context.Context, fs *btrfs.FS, newScanner DeviceScannerFactory[Stats, Result]) (map[btrfsvol.DeviceID]Result, error) {
+ grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{})
+ var mu sync.Mutex
+ result := make(map[btrfsvol.DeviceID]Result)
+ for id, dev := range fs.LV.PhysicalVolumes() {
+ id := id
+ dev := dev
+ grp.Go(fmt.Sprintf("dev-%d", id), func(ctx context.Context) error {
+ devResult, err := ScanOneDevice[Stats, Result](ctx, dev, newScanner)
+ 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
+}
+
+func ScanOneDevice[Stats comparable, Result any](ctx context.Context, dev *btrfs.Device, newScanner DeviceScannerFactory[Stats, Result]) (Result, error) {
+ ctx = dlog.WithField(ctx, "scandevices.dev", dev.Name())
+
+ sb, err := dev.Superblock()
+ if err != nil {
+ var zero Result
+ return zero, err
+ }
+ numBytes := dev.Size()
+ if sb.NodeSize < sb.SectorSize {
+ var zero Result
+ return zero, fmt.Errorf("node_size(%v) < sector_size(%v)",
+ sb.NodeSize, sb.SectorSize)
+ }
+ if sb.SectorSize != btrfssum.BlockSize {
+ // TODO: probably handle this?
+ var zero Result
+ return zero, fmt.Errorf("sector_size(%v) != btrfssum.BlockSize",
+ sb.SectorSize)
+ }
+ numSectors := int(numBytes / btrfssum.BlockSize)
+
+ scanner := newScanner(ctx, *sb, numBytes, numSectors)
+
+ progressWriter := textui.NewProgress[scanStats[Stats]](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second))
+ var stats scanStats[Stats]
+ stats.portion.D = numBytes
+
+ var minNextNode btrfsvol.PhysicalAddr
+ for i := 0; i < numSectors; i++ {
+ if ctx.Err() != nil {
+ var zero Result
+ return zero, ctx.Err()
+ }
+ pos := btrfsvol.PhysicalAddr(i * btrfssum.BlockSize)
+ stats.portion.N = pos
+ stats.stats = scanner.ScanStats()
+ progressWriter.Set(stats)
+
+ if err := scanner.ScanSector(ctx, dev, pos); err != nil {
+ var zero Result
+ return zero, err
+ }
+
+ checkForNode := pos >= minNextNode && pos+btrfsvol.PhysicalAddr(sb.NodeSize) <= numBytes
+ if checkForNode {
+ for _, sbAddr := range btrfs.SuperblockAddrs {
+ if sbAddr <= pos && pos < sbAddr+sbSize {
+ checkForNode = false
+ break
+ }
+ }
+ }
+
+ 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 {
+ if err := scanner.ScanNode(ctx, nodeRef); err != nil {
+ var zero Result
+ return zero, err
+ }
+ minNextNode = pos + btrfsvol.PhysicalAddr(sb.NodeSize)
+ }
+ btrfstree.FreeNodeRef(nodeRef)
+ }
+ }
+
+ stats.portion.N = numBytes
+ stats.stats = scanner.ScanStats()
+ progressWriter.Set(stats)
+ progressWriter.Done()
+
+ return scanner.ScanDone(ctx)
+}