summaryrefslogtreecommitdiff
path: root/cmd/btrfs-rec/inspect/rebuildmappings/process.go
blob: e42acf907fd9d172b835d42a57d4031d979f5d33 (plain)
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// Copyright (C) 2022-2023  Luke Shumaker <lukeshu@lukeshu.com>
//
// SPDX-License-Identifier: GPL-2.0-or-later

// Package rebuildmappings is the guts of the `btrfs-rec inspect
// rebuild-mappings` command, which rebuilds broken
// chunk/dev-extent/blockgroup trees.
package rebuildmappings

import (
	"context"
	"fmt"

	"github.com/datawire/dlib/dlog"

	"git.lukeshu.com/btrfs-progs-ng/lib/btrfs"
	"git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol"
	"git.lukeshu.com/btrfs-progs-ng/lib/containers"
	"git.lukeshu.com/btrfs-progs-ng/lib/maps"
	"git.lukeshu.com/btrfs-progs-ng/lib/textui"
)

func getNodeSize(fs *btrfs.FS) (btrfsvol.AddrDelta, error) {
	sb, err := fs.Superblock()
	if err != nil {
		return 0, err
	}
	return btrfsvol.AddrDelta(sb.NodeSize), nil
}

func RebuildMappings(ctx context.Context, fs *btrfs.FS, scanResults ScanDevicesResult) error {
	nodeSize, err := getNodeSize(fs)
	if err != nil {
		return err
	}

	var numChunks, numDevExts, numBlockGroups, numNodes int
	devIDs := maps.SortedKeys(scanResults)
	devices := fs.LV.PhysicalVolumes()
	for _, devID := range devIDs {
		if _, ok := devices[devID]; !ok {
			return fmt.Errorf("device ID %v mentioned in scan results is not part of the filesystem", devID)
		}
		devResults := scanResults[devID]
		numChunks += len(devResults.FoundChunks)
		numDevExts += len(devResults.FoundDevExtents)
		numBlockGroups += len(devResults.FoundBlockGroups)
		for _, paddrs := range devResults.FoundNodes {
			numNodes += len(paddrs)
		}
	}
	dlog.Infof(ctx, "plan: 1/6 process %d chunks", numChunks)
	dlog.Infof(ctx, "plan: 2/6 process %d device extents", numDevExts)
	dlog.Infof(ctx, "plan: 3/6 process %d nodes", numNodes)
	dlog.Infof(ctx, "plan: 4/6 process %d block groups", numBlockGroups)
	dlog.Infof(ctx, "plan: 5/6 search for block groups in checksum map (exact)")
	dlog.Infof(ctx, "plan: 6/6 search for block groups in checksum map (fuzzy)")

	_ctx := ctx
	ctx = dlog.WithField(_ctx, "btrfs.inspect.rebuild-mappings.process.step", "1/6")
	dlog.Infof(_ctx, "1/6: Processing %d chunks...", numChunks)
	for _, devID := range devIDs {
		devResults := scanResults[devID]
		for _, chunk := range devResults.FoundChunks {
			for _, mapping := range chunk.Chunk.Mappings(chunk.Key) {
				if err := fs.LV.AddMapping(mapping); err != nil {
					dlog.Errorf(ctx, "error: adding chunk: %v", err)
				}
			}
		}
	}
	dlog.Info(_ctx, "... done processing chunks")

	ctx = dlog.WithField(_ctx, "btrfs.inspect.rebuild-mappings.process.step", "2/6")
	dlog.Infof(_ctx, "2/6: Processing %d device extents...", numDevExts)
	for _, devID := range devIDs {
		devResults := scanResults[devID]
		for _, ext := range devResults.FoundDevExtents {
			if err := fs.LV.AddMapping(ext.DevExt.Mapping(ext.Key)); err != nil {
				dlog.Errorf(ctx, "error: adding devext: %v", err)
			}
		}
	}
	dlog.Info(_ctx, "... done processing device extents")

	// 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.)
	ctx = dlog.WithField(_ctx, "btrfs.inspect.rebuild-mappings.process.step", "3/6")
	dlog.Infof(_ctx, "3/6: Processing %d nodes...", numNodes)
	for _, devID := range devIDs {
		devResults := scanResults[devID]
		// Sort them so that progress numbers are predictable.
		for _, laddr := range maps.SortedKeys(devResults.FoundNodes) {
			for _, paddr := range devResults.FoundNodes[laddr] {
				if err := fs.LV.AddMapping(btrfsvol.Mapping{
					LAddr: laddr,
					PAddr: btrfsvol.QualifiedPhysicalAddr{
						Dev:  devID,
						Addr: paddr,
					},
					Size:       nodeSize,
					SizeLocked: false,
				}); err != nil {
					dlog.Errorf(ctx, "error: adding node ident: %v", err)
				}
			}
		}
	}
	dlog.Info(_ctx, "... done processing nodes")

	// Use block groups to add missing flags (and as a hint to
	// combine node entries).
	ctx = dlog.WithField(_ctx, "btrfs.inspect.rebuild-mappings.process.step", "4/6")
	dlog.Infof(_ctx, "4/6: Processing %d block groups...", numBlockGroups)
	// 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?
	bgs, err := dedupedBlockGroups(scanResults)
	if err != nil {
		return err
	}
	dlog.Infof(ctx, "... de-duplicated to %d block groups", len(bgs))
	for _, bgLAddr := range maps.SortedKeys(bgs) {
		bg := bgs[bgLAddr]
		otherLAddr, otherPAddr := fs.LV.ResolveAny(bg.LAddr, bg.Size)
		if otherLAddr < 0 || otherPAddr.Addr < 0 {
			dlog.Errorf(ctx, "error: could not pair blockgroup laddr=%v (size=%v flags=%v) with a mapping",
				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, "error: adding flags from blockgroup: %v", err)
			continue
		}
		delete(bgs, bgLAddr)
	}
	dlog.Info(_ctx, "... done processing block groups")

	// More than once, I've been tempted to get rid of this exact-search step and just have the fuzzy-search step.
	// After all, looking at the timestamps in the log, it's faster per blockgroup!  For some background, the big-O
	// for each (per blockgroup) looks like:
	//
	//  - exact-search: O(bgSize+physicalBlocks)
	//  - fuzzy-search: O(bgSize*physicalBlocks) worst-case; O(bgSize*log(physicalBlocks)) expected
	//
	// The fuzzy-search is only fast because the exact-search is so good at getting `physicalBlocks` down.
	// Empirically: if I remove the exact-search step, then the fuzzy-match step is more than an order of magnitude
	// slower.
	ctx = dlog.WithField(_ctx, "btrfs.inspect.rebuild-mappings.process.step", "5/6")
	dlog.Infof(_ctx, "5/6: Searching for %d block groups in checksum map (exact)...", len(bgs))
	physicalSums := extractPhysicalSums(scanResults)
	logicalSums := extractLogicalSums(ctx, scanResults)
	if err := matchBlockGroupSumsExact(ctx, fs, bgs, physicalSums, logicalSums); err != nil {
		return err
	}
	dlog.Info(ctx, "... done searching for exact block groups")

	ctx = dlog.WithField(_ctx, "btrfs.inspect.rebuild-mappings.process.step", "6/6")
	dlog.Infof(_ctx, "6/6: Searching for %d block groups in checksum map (fuzzy)...", len(bgs))
	if err := matchBlockGroupSumsFuzzy(ctx, fs, bgs, physicalSums, logicalSums); err != nil {
		return err
	}
	dlog.Info(_ctx, "... done searching for fuzzy block groups")

	ctx = dlog.WithField(_ctx, "btrfs.inspect.rebuild-mappings.process.step", "report")
	dlog.Info(_ctx, "report:")

	unmappedPhysicalRegions := listUnmappedPhysicalRegions(fs)
	var unmappedPhysical btrfsvol.AddrDelta
	var numUnmappedPhysical int
	for _, devRegions := range unmappedPhysicalRegions {
		numUnmappedPhysical += len(devRegions)
		for _, region := range devRegions {
			unmappedPhysical += region.End.Sub(region.Beg)
		}
	}
	dlog.Infof(ctx, "... %d of unmapped physical space (across %d regions)", textui.IEC(unmappedPhysical, "B"), numUnmappedPhysical)

	unmappedLogicalRegions := listUnmappedLogicalRegions(fs, logicalSums)
	var unmappedLogical btrfsvol.AddrDelta
	for _, region := range unmappedLogicalRegions {
		unmappedLogical += region.Size()
	}
	dlog.Infof(ctx, "... %d of unmapped summed logical space (across %d regions)", textui.IEC(unmappedLogical, "B"), len(unmappedLogicalRegions))

	var unmappedBlockGroups btrfsvol.AddrDelta
	for _, bg := range bgs {
		unmappedBlockGroups += bg.Size
	}
	dlog.Infof(ctx, "... %d of unmapped block groups (across %d groups)", textui.IEC(unmappedBlockGroups, "B"), len(bgs))

	dlog.Info(_ctx, "detailed report:")
	for _, devID := range maps.SortedKeys(unmappedPhysicalRegions) {
		for _, region := range unmappedPhysicalRegions[devID] {
			dlog.Infof(ctx, "... unmapped physical region: dev=%v beg=%v end=%v (size=%v)",
				devID, region.Beg, region.End, region.End.Sub(region.Beg))
		}
	}
	for _, region := range unmappedLogicalRegions {
		dlog.Infof(ctx, "... umapped summed logical region:  beg=%v end=%v (size=%v)",
			region.Addr, region.Addr.Add(region.Size()), region.Size())
	}
	for _, laddr := range maps.SortedKeys(bgs) {
		bg := bgs[laddr]
		dlog.Infof(ctx, "... umapped block group:            beg=%v end=%v (size=%v) flags=%v",
			bg.LAddr, bg.LAddr.Add(bg.Size), bg.Size, bg.Flags)
	}

	return nil
}