summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2023-03-14 21:31:51 -0600
committerLuke Shumaker <lukeshu@lukeshu.com>2023-03-14 21:31:51 -0600
commit697d79fb12b79b65a501ec90dbb45ea165b3457d (patch)
tree7b8275fe12a2f2d7c57f1e9419b97e693b715f36 /lib
parent9e9b4e8ac67052d667f6e7fae0a6620b6dbc50c7 (diff)
parentafd2fe91ec604491bd8978b1880c6482e7394240 (diff)
Merge branch 'lukeshu/reorg-cli'
Diffstat (limited to 'lib')
-rw-r--r--lib/btrfsutil/listnodes.go66
-rw-r--r--lib/btrfsutil/scan.go148
2 files changed, 214 insertions, 0 deletions
diff --git a/lib/btrfsutil/listnodes.go b/lib/btrfsutil/listnodes.go
new file mode 100644
index 0000000..16300da
--- /dev/null
+++ b/lib/btrfsutil/listnodes.go
@@ -0,0 +1,66 @@
+// Copyright (C) 2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package btrfsutil
+
+import (
+ "context"
+
+ "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/containers"
+ "git.lukeshu.com/btrfs-progs-ng/lib/diskio"
+ "git.lukeshu.com/btrfs-progs-ng/lib/maps"
+ "git.lukeshu.com/btrfs-progs-ng/lib/textui"
+)
+
+type nodeScanner struct {
+ nodes containers.Set[btrfsvol.LogicalAddr]
+}
+
+type nodeStats struct {
+ numNodes int
+}
+
+func (s nodeStats) String() string {
+ return textui.Sprintf("found: %d nodes", s.numNodes)
+}
+
+var _ DeviceScanner[nodeStats, containers.Set[btrfsvol.LogicalAddr]] = (*nodeScanner)(nil)
+
+func newNodeScanner(ctx context.Context, sb btrfstree.Superblock, numBytes btrfsvol.PhysicalAddr, numSectors int) DeviceScanner[nodeStats, containers.Set[btrfsvol.LogicalAddr]] {
+ s := new(nodeScanner)
+ s.nodes = make(containers.Set[btrfsvol.LogicalAddr])
+ return s
+}
+
+func (s *nodeScanner) ScanStats() nodeStats {
+ return nodeStats{numNodes: len(s.nodes)}
+}
+
+func (*nodeScanner) ScanSector(ctx context.Context, dev *btrfs.Device, paddr btrfsvol.PhysicalAddr) error {
+ return nil
+}
+
+func (s *nodeScanner) ScanNode(ctx context.Context, nodeRef *diskio.Ref[btrfsvol.PhysicalAddr, btrfstree.Node]) error {
+ s.nodes.Insert(nodeRef.Data.Head.Addr)
+ return nil
+}
+
+func (s *nodeScanner) ScanDone(ctx context.Context) (containers.Set[btrfsvol.LogicalAddr], error) {
+ return s.nodes, nil
+}
+
+func ListNodes(ctx context.Context, fs *btrfs.FS) ([]btrfsvol.LogicalAddr, error) {
+ perDev, err := ScanDevices[nodeStats, containers.Set[btrfsvol.LogicalAddr]](ctx, fs, newNodeScanner)
+ if err != nil {
+ return nil, err
+ }
+ set := make(containers.Set[btrfsvol.LogicalAddr])
+ for _, devSet := range perDev {
+ set.InsertFrom(devSet)
+ }
+ return maps.SortedKeys(set), nil
+}
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)
+}