btrfs-cli 0.13.0

User-space command-line tool for inspecting and managing Btrfs filesystems
Documentation
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
use super::errors::{CheckError, CheckResults};
use btrfs_disk::{
    items::{ExtentItem, InlineRef, ItemPayload, parse_item_payload},
    reader::{self, BlockReader},
    tree::{KeyType, TreeBlock},
};
use std::{
    collections::{HashMap, HashSet},
    io::{Read, Seek},
};

/// Header size in a btrfs tree block (bytes before item data area).
const HEADER_SIZE: usize = std::mem::size_of::<btrfs_disk::raw::btrfs_header>();

/// Check extent tree: verify reference counts, detect overlapping extents,
/// and cross-check tree block ownership against extent tree backrefs.
///
/// `tree_block_owners` maps each tree block logical address to the tree
/// objectid that actually owns it (collected during tree walks).
pub fn check_extent_tree<R: Read + Seek>(
    reader: &mut BlockReader<R>,
    extent_root: u64,
    tree_block_owners: &HashMap<u64, u64>,
    results: &mut CheckResults,
) {
    let mut state = ExtentCheckState::default();

    let mut read_errors: Vec<(u64, String)> = Vec::new();

    let mut visitor = |_raw: &[u8], block: &TreeBlock| {
        if let TreeBlock::Leaf { items, data, .. } = block {
            for item in items {
                let start = HEADER_SIZE + item.offset as usize;
                let item_data = &data[start..][..item.size as usize];
                process_extent_item(&item.key, item_data, &mut state, results);
            }
        }
    };

    let mut on_error = |logical: u64, err: &std::io::Error| {
        read_errors.push((logical, err.to_string()));
    };

    if let Err(e) = reader::tree_walk_tolerant(
        reader,
        extent_root,
        &mut visitor,
        &mut on_error,
    ) {
        results.report(CheckError::ReadError {
            logical: extent_root,
            detail: format!("extent tree root: {e}"),
        });
        return;
    }

    // Flush the last pending extent.
    flush_pending(&mut state, results);

    for (logical, detail) in read_errors {
        results.report(CheckError::ReadError { logical, detail });
    }

    // Cross-check tree block ownership against extent tree backrefs.
    // Sort by address for deterministic error ordering.
    let mut sorted_addrs: Vec<u64> =
        tree_block_owners.keys().copied().collect();
    sorted_addrs.sort_unstable();

    // Direction 1: every tree block from walks must have a backref in the
    // extent tree claiming the correct owner.
    for &addr in &sorted_addrs {
        let actual_owner = tree_block_owners[&addr];
        if !state.extent_item_addrs.contains(&addr) {
            results.report(CheckError::MissingExtentItem { bytenr: addr });
        } else if let Some(claimed_owners) =
            state.extent_backref_owners.get(&addr)
            && !claimed_owners.contains(&actual_owner)
        {
            results.report(CheckError::BackrefOwnerMismatch {
                bytenr: addr,
                actual_owner,
                claimed_owners: claimed_owners.clone(),
            });
        }
    }

    // Direction 2: every tree block backref in the extent tree must
    // correspond to an actual tree block owned by that tree.
    let mut extent_addrs: Vec<u64> =
        state.extent_backref_owners.keys().copied().collect();
    extent_addrs.sort_unstable();
    for &addr in &extent_addrs {
        let claimed_owners = &state.extent_backref_owners[&addr];
        for &claimed in claimed_owners {
            let actual = tree_block_owners.get(&addr).copied();
            if actual != Some(claimed) {
                results.report(CheckError::BackrefOrphan {
                    bytenr: addr,
                    claimed_owner: claimed,
                });
            }
        }
    }

    results.data_bytes_allocated = state.data_bytes_allocated;
    results.data_bytes_referenced = state.data_bytes_referenced;
}

#[derive(Default)]
struct ExtentCheckState {
    /// Currently tracked extent bytenr (0 = none pending).
    pending_bytenr: u64,
    /// Length of the pending extent (from the key offset for `EXTENT_ITEM`).
    pending_length: u64,
    /// Declared ref count from the `ExtentItem`.
    pending_refs: u64,
    /// Counted refs (inline + standalone).
    pending_counted: u64,
    /// Whether the pending extent is a data extent.
    pending_is_data: bool,
    /// End of the previous extent (for overlap detection).
    prev_end: u64,
    /// Accumulated stats.
    data_bytes_allocated: u64,
    data_bytes_referenced: u64,
    /// All bytenrs that have a `METADATA_ITEM` or `EXTENT_ITEM` entry.
    extent_item_addrs: HashSet<u64>,
    /// For tree block extents: address → list of claimed owner roots
    /// (from `TREE_BLOCK_REF` inline backrefs and standalone backrefs).
    extent_backref_owners: HashMap<u64, Vec<u64>>,
}

fn process_extent_item(
    key: &btrfs_disk::tree::DiskKey,
    data: &[u8],
    state: &mut ExtentCheckState,
    results: &mut CheckResults,
) {
    match key.key_type {
        KeyType::ExtentItem | KeyType::MetadataItem => {
            // New extent: flush the previous one.
            flush_pending(state, results);

            let bytenr = key.objectid;
            state.extent_item_addrs.insert(bytenr);
            let length = if key.key_type == KeyType::ExtentItem {
                key.offset
            } else {
                // MetadataItem: length is nodesize, but we don't have it
                // here. Use 0 to skip overlap checks for metadata items
                // (they use skinny refs where offset = level, not length).
                0
            };

            // Overlap detection.
            if length > 0 && bytenr < state.prev_end && state.prev_end > 0 {
                results.report(CheckError::OverlappingExtent {
                    bytenr,
                    length,
                    prev_end: state.prev_end,
                });
            }

            if length > 0 {
                state.prev_end = bytenr + length;
            }

            // Parse the extent item.
            let payload = parse_item_payload(key, data);
            let (refs, inline_count, is_data) = match &payload {
                ItemPayload::ExtentItem(ei) => {
                    // Collect tree block backref owners from inline refs.
                    if !ei.is_data() {
                        for iref in &ei.inline_refs {
                            if let InlineRef::TreeBlockBackref {
                                root, ..
                            } = iref
                            {
                                state
                                    .extent_backref_owners
                                    .entry(bytenr)
                                    .or_default()
                                    .push(*root);
                            }
                        }
                    }
                    let count = count_inline_refs(ei);
                    (ei.refs, count, ei.is_data())
                }
                _ => (0, 0, false),
            };

            state.pending_bytenr = bytenr;
            state.pending_length = length;
            state.pending_refs = refs;
            state.pending_counted = inline_count;
            state.pending_is_data = is_data;

            if is_data {
                state.data_bytes_allocated += length;
            }
        }

        // Standalone backref items: add to the count of the current extent.
        KeyType::TreeBlockRef if key.objectid == state.pending_bytenr => {
            state.pending_counted += 1;
            // key.offset is the root objectid for standalone TreeBlockRef.
            state
                .extent_backref_owners
                .entry(key.objectid)
                .or_default()
                .push(key.offset);
        }
        KeyType::SharedBlockRef | KeyType::ExtentOwnerRef
            if key.objectid == state.pending_bytenr =>
        {
            state.pending_counted += 1;
        }

        KeyType::ExtentDataRef if key.objectid == state.pending_bytenr => {
            // ExtentDataRef has a count field inside.
            if let ItemPayload::ExtentDataRef(edr) =
                parse_item_payload(key, data)
            {
                state.pending_counted += u64::from(edr.count);
                state.data_bytes_referenced +=
                    state.pending_length * u64::from(edr.count);
            } else {
                state.pending_counted += 1;
            }
        }

        KeyType::SharedDataRef if key.objectid == state.pending_bytenr => {
            if let ItemPayload::SharedDataRef(sdr) =
                parse_item_payload(key, data)
            {
                state.pending_counted += u64::from(sdr.count);
                state.data_bytes_referenced +=
                    state.pending_length * u64::from(sdr.count);
            } else {
                state.pending_counted += 1;
            }
        }

        // Block group items (and any other key types) are not relevant
        // for extent ref counting — block groups are checked in chunks.rs.
        _ => {}
    }
}

fn flush_pending(state: &mut ExtentCheckState, results: &mut CheckResults) {
    if state.pending_bytenr == 0 {
        return;
    }

    // For data extents with inline refs only (no standalone ExtentDataRef),
    // account the referenced bytes from the inline ref count.
    if state.pending_is_data && state.data_bytes_referenced == 0 {
        state.data_bytes_referenced +=
            state.pending_length * state.pending_counted;
    }

    if state.pending_refs != state.pending_counted {
        results.report(CheckError::ExtentRefMismatch {
            bytenr: state.pending_bytenr,
            expected: state.pending_refs,
            found: state.pending_counted,
        });
    }

    state.pending_bytenr = 0;
}

/// Count the number of references from inline backrefs in an `ExtentItem`.
fn count_inline_refs(ei: &ExtentItem) -> u64 {
    let mut count = 0u64;
    for iref in &ei.inline_refs {
        match iref {
            btrfs_disk::items::InlineRef::ExtentDataBackref {
                count: c,
                ..
            }
            | btrfs_disk::items::InlineRef::SharedDataBackref {
                count: c,
                ..
            } => count += u64::from(*c),
            _ => count += 1,
        }
    }
    count
}

#[cfg(test)]
mod tests {
    use super::*;
    use btrfs_disk::items::{ExtentFlags, ExtentItem, InlineRef};

    fn make_extent_item(
        refs: u64,
        flags: ExtentFlags,
        inline_refs: Vec<InlineRef>,
    ) -> ExtentItem {
        ExtentItem {
            refs,
            generation: 1,
            flags,
            tree_block_key: None,
            tree_block_level: None,
            skinny_level: None,
            inline_refs,
        }
    }

    #[test]
    fn count_inline_refs_tree_block_backrefs() {
        let ei = make_extent_item(
            2,
            ExtentFlags::TREE_BLOCK,
            vec![
                InlineRef::TreeBlockBackref {
                    ref_offset: 0,
                    root: 1,
                },
                InlineRef::SharedBlockBackref {
                    ref_offset: 0,
                    parent: 4096,
                },
            ],
        );
        assert_eq!(count_inline_refs(&ei), 2);
    }

    #[test]
    fn count_inline_refs_data_backrefs_with_counts() {
        let ei = make_extent_item(
            5,
            ExtentFlags::DATA,
            vec![
                InlineRef::ExtentDataBackref {
                    ref_offset: 0,
                    root: 5,
                    objectid: 256,
                    offset: 0,
                    count: 3,
                },
                InlineRef::SharedDataBackref {
                    ref_offset: 0,
                    parent: 8192,
                    count: 2,
                },
            ],
        );
        assert_eq!(count_inline_refs(&ei), 5);
    }

    #[test]
    fn count_inline_refs_empty() {
        let ei = make_extent_item(0, ExtentFlags::DATA, vec![]);
        assert_eq!(count_inline_refs(&ei), 0);
    }

    #[test]
    fn count_inline_refs_owner_ref() {
        let ei = make_extent_item(
            1,
            ExtentFlags::TREE_BLOCK,
            vec![InlineRef::ExtentOwnerRef {
                ref_offset: 0,
                root: 2,
            }],
        );
        assert_eq!(count_inline_refs(&ei), 1);
    }

    #[test]
    fn flush_pending_no_op_when_empty() {
        let mut state = ExtentCheckState::default();
        let mut results = CheckResults::new(0);
        flush_pending(&mut state, &mut results);
        assert_eq!(results.error_count, 0);
    }

    #[test]
    fn flush_pending_matching_refs() {
        let mut state = ExtentCheckState {
            pending_bytenr: 1048576,
            pending_length: 4096,
            pending_refs: 1,
            pending_counted: 1,
            pending_is_data: true,
            ..Default::default()
        };
        let mut results = CheckResults::new(0);
        flush_pending(&mut state, &mut results);
        assert_eq!(results.error_count, 0);
        // Should reset pending_bytenr.
        assert_eq!(state.pending_bytenr, 0);
    }

    #[test]
    fn flush_pending_ref_mismatch_reports_error() {
        let mut state = ExtentCheckState {
            pending_bytenr: 1048576,
            pending_length: 4096,
            pending_refs: 2,
            pending_counted: 1,
            pending_is_data: false,
            ..Default::default()
        };
        let mut results = CheckResults::new(0);
        flush_pending(&mut state, &mut results);
        assert_eq!(results.error_count, 1);
    }

    #[test]
    fn flush_pending_accounts_data_referenced_bytes() {
        let mut state = ExtentCheckState {
            pending_bytenr: 1048576,
            pending_length: 4096,
            pending_refs: 1,
            pending_counted: 1,
            pending_is_data: true,
            data_bytes_referenced: 0,
            ..Default::default()
        };
        let mut results = CheckResults::new(0);
        flush_pending(&mut state, &mut results);
        assert_eq!(state.data_bytes_referenced, 4096);
    }
}