1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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)
}
|