Skip to main content

btrfs_disk/
superblock.rs

1//! # Reading and parsing the btrfs superblock from a block device
2//!
3//! The superblock is a 4096-byte structure stored at a fixed offset on disk
4//! (primary at 64 KiB, with mirrors at 64 MiB and 256 GiB). It contains
5//! the root pointers and metadata needed to bootstrap access to the rest
6//! of the filesystem.
7
8use crate::{items::DeviceItem, raw, util::raw_crc32c};
9use bytes::BufMut;
10use std::{
11    fmt,
12    io::{self, Read, Seek, SeekFrom, Write},
13    mem,
14};
15use uuid::Uuid;
16
17/// Size of a superblock on disk (4096 bytes).
18/// From kernel-shared/ctree.h: `BTRFS_SUPER_INFO_SIZE`
19const SUPER_INFO_SIZE: usize = 4096;
20
21/// Byte offset of the primary superblock on disk (64 KiB).
22/// From kernel-shared/ctree.h: `BTRFS_SUPER_INFO_OFFSET`
23const SUPER_INFO_OFFSET: u64 = 65536;
24
25/// Maximum number of superblock mirrors (3: primary + 2 copies).
26/// From kernel-shared/disk-io.h: `BTRFS_SUPER_MIRROR_MAX`
27pub const SUPER_MIRROR_MAX: u32 = 3;
28
29/// Shift used to compute mirror offsets.
30/// From kernel-shared/disk-io.h: `BTRFS_SUPER_MIRROR_SHIFT`
31const SUPER_MIRROR_SHIFT: u32 = 12;
32
33/// Compute the byte offset of the superblock mirror at `index`.
34///
35/// Mirror 0 is at 64 KiB, mirror 1 at 64 MiB, mirror 2 at 256 GiB.
36pub fn super_mirror_offset(index: u32) -> u64 {
37    if index == 0 {
38        SUPER_INFO_OFFSET
39    } else {
40        // 16 KiB << (12 * index)
41        (16 * 1024u64) << (SUPER_MIRROR_SHIFT * index)
42    }
43}
44
45/// Checksum algorithm used by the filesystem, stored in the superblock's
46/// `csum_type` field.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum ChecksumType {
49    Crc32,
50    Xxhash,
51    Sha256,
52    Blake2,
53    Unknown(u16),
54}
55
56impl ChecksumType {
57    /// Parse from the raw on-disk u16 value.
58    #[must_use]
59    pub fn from_raw(val: u16) -> ChecksumType {
60        match u32::from(val) {
61            raw::btrfs_csum_type_BTRFS_CSUM_TYPE_CRC32 => ChecksumType::Crc32,
62            raw::btrfs_csum_type_BTRFS_CSUM_TYPE_XXHASH => ChecksumType::Xxhash,
63            raw::btrfs_csum_type_BTRFS_CSUM_TYPE_SHA256 => ChecksumType::Sha256,
64            raw::btrfs_csum_type_BTRFS_CSUM_TYPE_BLAKE2 => ChecksumType::Blake2,
65            _ => ChecksumType::Unknown(val),
66        }
67    }
68
69    /// Size in bytes of checksums for this algorithm.
70    // Unknown falls back to 32 (BTRFS_CSUM_SIZE); same value as Sha256/Blake2
71    // but for a different reason — suppress the match_same_arms lint.
72    #[allow(clippy::match_same_arms)]
73    pub fn size(&self) -> usize {
74        match self {
75            ChecksumType::Crc32 => 4,
76            ChecksumType::Xxhash => 8,
77            ChecksumType::Sha256 | ChecksumType::Blake2 => 32,
78            ChecksumType::Unknown(_) => 32, // BTRFS_CSUM_SIZE
79        }
80    }
81}
82
83impl ChecksumType {
84    /// Convert to the raw u16 value for on-disk storage.
85    #[must_use]
86    pub fn to_raw(self) -> u16 {
87        match self {
88            ChecksumType::Crc32 => {
89                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_CRC32 as u16
90            }
91            ChecksumType::Xxhash => {
92                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_XXHASH as u16
93            }
94            ChecksumType::Sha256 => {
95                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_SHA256 as u16
96            }
97            ChecksumType::Blake2 => {
98                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_BLAKE2 as u16
99            }
100            ChecksumType::Unknown(v) => v,
101        }
102    }
103}
104
105impl fmt::Display for ChecksumType {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self {
108            ChecksumType::Crc32 => write!(f, "crc32c"),
109            ChecksumType::Xxhash => write!(f, "xxhash64"),
110            ChecksumType::Sha256 => write!(f, "sha256"),
111            ChecksumType::Blake2 => write!(f, "blake2"),
112            ChecksumType::Unknown(v) => write!(f, "unknown ({v})"),
113        }
114    }
115}
116
117/// A single backup root entry from the superblock's `super_roots` array.
118///
119/// The kernel maintains four rotating backup copies of the critical tree root
120/// pointers. On mount failure, these can be used to recover an older consistent
121/// state.
122#[derive(Debug, Clone, Default)]
123pub struct BackupRoot {
124    /// Logical bytenr of the root tree root block.
125    pub tree_root: u64,
126    /// Generation of the root tree root.
127    pub tree_root_gen: u64,
128    /// Logical bytenr of the chunk tree root block.
129    pub chunk_root: u64,
130    /// Generation of the chunk tree root.
131    pub chunk_root_gen: u64,
132    /// Logical bytenr of the extent tree root block.
133    pub extent_root: u64,
134    /// Generation of the extent tree root.
135    pub extent_root_gen: u64,
136    /// Logical bytenr of the FS tree root block.
137    pub fs_root: u64,
138    /// Generation of the FS tree root.
139    pub fs_root_gen: u64,
140    /// Logical bytenr of the device tree root block.
141    pub dev_root: u64,
142    /// Generation of the device tree root.
143    pub dev_root_gen: u64,
144    /// Logical bytenr of the checksum tree root block.
145    pub csum_root: u64,
146    /// Generation of the checksum tree root.
147    pub csum_root_gen: u64,
148    /// Total bytes in the filesystem at backup time.
149    pub total_bytes: u64,
150    /// Bytes used at backup time.
151    pub bytes_used: u64,
152    /// Number of devices at backup time.
153    pub num_devices: u64,
154    /// B-tree level of the root tree root.
155    pub tree_root_level: u8,
156    /// B-tree level of the chunk tree root.
157    pub chunk_root_level: u8,
158    /// B-tree level of the extent tree root.
159    pub extent_root_level: u8,
160    /// B-tree level of the FS tree root.
161    pub fs_root_level: u8,
162    /// B-tree level of the device tree root.
163    pub dev_root_level: u8,
164    /// B-tree level of the checksum tree root.
165    pub csum_root_level: u8,
166}
167
168/// Parsed btrfs superblock.
169///
170/// The superblock is the entry point for reading a btrfs filesystem. It lives
171/// at a fixed offset on each device (see [`super_mirror_offset`]) and contains
172/// the root pointers, feature flags, and embedded system chunk array needed to
173/// bootstrap access to all other on-disk structures.
174#[derive(Debug, Clone)]
175pub struct Superblock {
176    /// Checksum of everything past this field (bytes 32..4096).
177    pub csum: [u8; 32],
178    /// Filesystem UUID. Shared by all devices in a multi-device filesystem.
179    pub fsid: Uuid,
180    /// Physical byte offset where this superblock is stored on disk.
181    pub bytenr: u64,
182    /// Superblock flags (`BTRFS_SUPER_FLAG_*`).
183    pub flags: u64,
184    /// Magic number (`_BHRfS_M`). See [`Superblock::magic_is_valid`].
185    pub magic: u64,
186    /// Transaction generation of this superblock write.
187    pub generation: u64,
188    /// Logical bytenr of the root tree root block.
189    pub root: u64,
190    /// Logical bytenr of the chunk tree root block.
191    pub chunk_root: u64,
192    /// Logical bytenr of the log tree root block (0 if no log tree).
193    pub log_root: u64,
194    /// Transaction ID of the log tree root.
195    pub log_root_transid: u64,
196    /// Total usable bytes across all devices.
197    pub total_bytes: u64,
198    /// Total bytes used by data and metadata.
199    pub bytes_used: u64,
200    /// Objectid of the root directory (always 6).
201    pub root_dir_objectid: u64,
202    /// Number of devices in this filesystem.
203    pub num_devices: u64,
204    /// Minimum I/O alignment (typically 4096).
205    pub sectorsize: u32,
206    /// Size of tree blocks in bytes (typically 16384).
207    pub nodesize: u32,
208    /// Legacy field, equal to `nodesize` in modern filesystems.
209    pub leafsize: u32,
210    /// Stripe size for RAID (typically 65536).
211    pub stripesize: u32,
212    /// Number of valid bytes in the `sys_chunk_array`.
213    pub sys_chunk_array_size: u32,
214    /// Generation of the chunk tree root.
215    pub chunk_root_generation: u64,
216    /// Compatible feature flags.
217    pub compat_flags: u64,
218    /// Compatible read-only feature flags.
219    pub compat_ro_flags: u64,
220    /// Incompatible feature flags (e.g. `MIXED_GROUPS`, `SKINNY_METADATA`).
221    pub incompat_flags: u64,
222    /// Checksum algorithm for this filesystem.
223    pub csum_type: ChecksumType,
224    /// B-tree level of the root tree root.
225    pub root_level: u8,
226    /// B-tree level of the chunk tree root.
227    pub chunk_root_level: u8,
228    /// B-tree level of the log tree root.
229    pub log_root_level: u8,
230    /// Embedded device item describing this device.
231    pub dev_item: DeviceItem,
232    /// Filesystem label (up to 255 bytes, NUL-terminated on disk).
233    pub label: String,
234    /// Generation when the free space cache was written (v1 cache).
235    pub cache_generation: u64,
236    /// Generation when the UUID tree was last updated.
237    pub uuid_tree_generation: u64,
238    /// Metadata UUID (differs from `fsid` when `METADATA_UUID` incompat flag is set).
239    pub metadata_uuid: Uuid,
240    /// Number of global root entries (extent-tree-v2, not yet used).
241    pub nr_global_roots: u64,
242    /// Four rotating backup copies of critical tree root pointers.
243    pub backup_roots: [BackupRoot; 4],
244    /// Embedded chunk tree entries for bootstrapping the chunk cache.
245    pub sys_chunk_array: [u8; 2048],
246}
247
248impl Superblock {
249    /// Whether the magic bytes match `BTRFS_MAGIC`.
250    pub fn magic_is_valid(&self) -> bool {
251        self.magic == raw::BTRFS_MAGIC
252    }
253
254    /// Whether the `METADATA_UUID` incompat flag is set.
255    pub fn has_metadata_uuid(&self) -> bool {
256        self.incompat_flags
257            & u64::from(raw::BTRFS_FEATURE_INCOMPAT_METADATA_UUID)
258            != 0
259    }
260
261    /// Serialize the superblock to a 4096-byte buffer.
262    ///
263    /// The checksum field is written as-is from `self.csum`; call
264    /// `csum_superblock` on the result to recompute it.
265    pub fn to_bytes(&self) -> [u8; SUPER_INFO_SIZE] {
266        type S = raw::btrfs_super_block;
267        let mut v = Vec::with_capacity(SUPER_INFO_SIZE);
268
269        v.put_slice(&self.csum); // 32 bytes
270        debug_assert_eq!(v.len(), mem::offset_of!(S, fsid));
271        v.put_slice(self.fsid.as_bytes());
272        v.put_u64_le(self.bytenr);
273        v.put_u64_le(self.flags);
274        v.put_u64_le(self.magic);
275        v.put_u64_le(self.generation);
276        v.put_u64_le(self.root);
277        v.put_u64_le(self.chunk_root);
278        v.put_u64_le(self.log_root);
279        v.put_u64_le(self.log_root_transid);
280        v.put_u64_le(self.total_bytes);
281        v.put_u64_le(self.bytes_used);
282        v.put_u64_le(self.root_dir_objectid);
283        v.put_u64_le(self.num_devices);
284        debug_assert_eq!(v.len(), mem::offset_of!(S, sectorsize));
285        v.put_u32_le(self.sectorsize);
286        v.put_u32_le(self.nodesize);
287        v.put_u32_le(self.leafsize);
288        v.put_u32_le(self.stripesize);
289        v.put_u32_le(self.sys_chunk_array_size);
290        v.put_u64_le(self.chunk_root_generation);
291        v.put_u64_le(self.compat_flags);
292        v.put_u64_le(self.compat_ro_flags);
293        v.put_u64_le(self.incompat_flags);
294        v.put_u16_le(self.csum_type.to_raw());
295        v.put_u8(self.root_level);
296        v.put_u8(self.chunk_root_level);
297        v.put_u8(self.log_root_level);
298        debug_assert_eq!(v.len(), mem::offset_of!(S, dev_item));
299        self.dev_item.write_bytes(&mut v);
300        debug_assert_eq!(v.len(), mem::offset_of!(S, label));
301        v.put_slice(&label_to_bytes(&self.label));
302        debug_assert_eq!(v.len(), mem::offset_of!(S, cache_generation));
303        v.put_u64_le(self.cache_generation);
304        v.put_u64_le(self.uuid_tree_generation);
305        v.put_slice(self.metadata_uuid.as_bytes());
306        debug_assert_eq!(v.len(), mem::offset_of!(S, nr_global_roots));
307        v.put_u64_le(self.nr_global_roots);
308        // Zero-fill through remap_root, remap_root_generation,
309        // remap_root_level, and reserved[] up to sys_chunk_array.
310        let sys_chunk_off = mem::offset_of!(S, sys_chunk_array);
311        v.put_bytes(0, sys_chunk_off - v.len());
312        debug_assert_eq!(v.len(), sys_chunk_off);
313        v.put_slice(&self.sys_chunk_array);
314        // Backup roots come after sys_chunk_array.
315        debug_assert_eq!(v.len(), mem::offset_of!(S, super_roots));
316        for root in &self.backup_roots {
317            root.write_bytes(&mut v);
318        }
319        // Pad with zeros to SUPER_INFO_SIZE (padding field).
320        v.resize(SUPER_INFO_SIZE, 0);
321        v.try_into().unwrap()
322    }
323}
324
325impl BackupRoot {
326    /// Serialize the backup root to a `BufMut`.
327    fn write_bytes(&self, buf: &mut impl BufMut) {
328        buf.put_u64_le(self.tree_root);
329        buf.put_u64_le(self.tree_root_gen);
330        buf.put_u64_le(self.chunk_root);
331        buf.put_u64_le(self.chunk_root_gen);
332        buf.put_u64_le(self.extent_root);
333        buf.put_u64_le(self.extent_root_gen);
334        buf.put_u64_le(self.fs_root);
335        buf.put_u64_le(self.fs_root_gen);
336        buf.put_u64_le(self.dev_root);
337        buf.put_u64_le(self.dev_root_gen);
338        buf.put_u64_le(self.csum_root);
339        buf.put_u64_le(self.csum_root_gen);
340        buf.put_u64_le(self.total_bytes);
341        buf.put_u64_le(self.bytes_used);
342        buf.put_u64_le(self.num_devices);
343        // unused_64[4] — 32 reserved bytes
344        buf.put_bytes(0, 32);
345        buf.put_u8(self.tree_root_level);
346        buf.put_u8(self.chunk_root_level);
347        buf.put_u8(self.extent_root_level);
348        buf.put_u8(self.fs_root_level);
349        buf.put_u8(self.dev_root_level);
350        buf.put_u8(self.csum_root_level);
351        // 10 unused bytes to fill btrfs_root_backup (168 bytes total)
352        buf.put_bytes(0, 10);
353    }
354}
355
356/// Convert a label string to the 256-byte on-disk format (NUL-terminated).
357fn label_to_bytes(label: &str) -> [u8; 256] {
358    let mut out = [0u8; 256];
359    let bytes = label.as_bytes();
360    let len = bytes.len().min(255);
361    out[..len].copy_from_slice(&bytes[..len]);
362    out
363}
364
365/// Read the raw on-disk bytes into a packed bindgen struct.
366fn read_raw_superblock(
367    reader: &mut (impl Read + Seek),
368    offset: u64,
369) -> io::Result<raw::btrfs_super_block> {
370    reader.seek(SeekFrom::Start(offset))?;
371
372    let mut buf = [0u8; SUPER_INFO_SIZE];
373    reader.read_exact(&mut buf)?;
374
375    // SAFETY: btrfs_super_block is #[repr(C, packed)], exactly 4096 bytes,
376    // and all-zeroes is a valid bit pattern. We just read into a byte buffer
377    // so alignment is not an issue for the copy.
378    let sb: raw::btrfs_super_block =
379        unsafe { std::ptr::read_unaligned(buf.as_ptr().cast()) };
380    Ok(sb)
381}
382
383/// Helper: read a LE u64 field from a packed struct field (avoids misaligned reference).
384macro_rules! le64 {
385    ($field:expr) => {{
386        let val = $field;
387        u64::from_le(val)
388    }};
389}
390
391macro_rules! le32 {
392    ($field:expr) => {{
393        let val = $field;
394        u32::from_le(val)
395    }};
396}
397
398macro_rules! le16 {
399    ($field:expr) => {{
400        let val = $field;
401        u16::from_le(val)
402    }};
403}
404
405fn parse_dev_item(d: &raw::btrfs_dev_item) -> DeviceItem {
406    // Copy all fields to locals first — the struct is packed.
407    let devid = le64!(d.devid);
408    let total_bytes = le64!(d.total_bytes);
409    let bytes_used = le64!(d.bytes_used);
410    let io_align = le32!(d.io_align);
411    let io_width = le32!(d.io_width);
412    let sector_size = le32!(d.sector_size);
413    let dev_type = le64!(d.type_);
414    let generation = le64!(d.generation);
415    let start_offset = le64!(d.start_offset);
416    let dev_group = le32!(d.dev_group);
417    let seek_speed = d.seek_speed;
418    let bandwidth = d.bandwidth;
419    let uuid = Uuid::from_bytes(d.uuid);
420    let fsid = Uuid::from_bytes(d.fsid);
421
422    DeviceItem {
423        devid,
424        total_bytes,
425        bytes_used,
426        io_align,
427        io_width,
428        sector_size,
429        dev_type,
430        generation,
431        start_offset,
432        dev_group,
433        seek_speed,
434        bandwidth,
435        uuid,
436        fsid,
437    }
438}
439
440fn parse_backup_root(r: &raw::btrfs_root_backup) -> BackupRoot {
441    BackupRoot {
442        tree_root: le64!(r.tree_root),
443        tree_root_gen: le64!(r.tree_root_gen),
444        chunk_root: le64!(r.chunk_root),
445        chunk_root_gen: le64!(r.chunk_root_gen),
446        extent_root: le64!(r.extent_root),
447        extent_root_gen: le64!(r.extent_root_gen),
448        fs_root: le64!(r.fs_root),
449        fs_root_gen: le64!(r.fs_root_gen),
450        dev_root: le64!(r.dev_root),
451        dev_root_gen: le64!(r.dev_root_gen),
452        csum_root: le64!(r.csum_root),
453        csum_root_gen: le64!(r.csum_root_gen),
454        total_bytes: le64!(r.total_bytes),
455        bytes_used: le64!(r.bytes_used),
456        num_devices: le64!(r.num_devices),
457        tree_root_level: r.tree_root_level,
458        chunk_root_level: r.chunk_root_level,
459        extent_root_level: r.extent_root_level,
460        fs_root_level: r.fs_root_level,
461        dev_root_level: r.dev_root_level,
462        csum_root_level: r.csum_root_level,
463    }
464}
465
466fn parse_label(raw_label: &[std::os::raw::c_char; 256]) -> String {
467    let bytes: Vec<u8> = raw_label
468        .iter()
469        .take_while(|&&c| c != 0)
470        .map(|&c| c.cast_unsigned())
471        .collect();
472    String::from_utf8_lossy(&bytes).into_owned()
473}
474
475fn parse_superblock(sb: &raw::btrfs_super_block) -> Superblock {
476    let mut sys_chunk_array = [0u8; 2048];
477    sys_chunk_array.copy_from_slice(&sb.sys_chunk_array);
478
479    Superblock {
480        csum: sb.csum,
481        fsid: Uuid::from_bytes(sb.fsid),
482        bytenr: le64!(sb.bytenr),
483        flags: le64!(sb.flags),
484        magic: le64!(sb.magic),
485        generation: le64!(sb.generation),
486        root: le64!(sb.root),
487        chunk_root: le64!(sb.chunk_root),
488        log_root: le64!(sb.log_root),
489        log_root_transid: le64!(sb.__unused_log_root_transid),
490        total_bytes: le64!(sb.total_bytes),
491        bytes_used: le64!(sb.bytes_used),
492        root_dir_objectid: le64!(sb.root_dir_objectid),
493        num_devices: le64!(sb.num_devices),
494        sectorsize: le32!(sb.sectorsize),
495        nodesize: le32!(sb.nodesize),
496        leafsize: le32!(sb.__unused_leafsize),
497        stripesize: le32!(sb.stripesize),
498        sys_chunk_array_size: le32!(sb.sys_chunk_array_size),
499        chunk_root_generation: le64!(sb.chunk_root_generation),
500        compat_flags: le64!(sb.compat_flags),
501        compat_ro_flags: le64!(sb.compat_ro_flags),
502        incompat_flags: le64!(sb.incompat_flags),
503        csum_type: ChecksumType::from_raw(le16!(sb.csum_type)),
504        root_level: sb.root_level,
505        chunk_root_level: sb.chunk_root_level,
506        log_root_level: sb.log_root_level,
507        dev_item: parse_dev_item(&sb.dev_item),
508        label: parse_label(&sb.label),
509        cache_generation: le64!(sb.cache_generation),
510        uuid_tree_generation: le64!(sb.uuid_tree_generation),
511        metadata_uuid: Uuid::from_bytes(sb.metadata_uuid),
512        nr_global_roots: sb.nr_global_roots,
513        backup_roots: [
514            parse_backup_root(&sb.super_roots[0]),
515            parse_backup_root(&sb.super_roots[1]),
516            parse_backup_root(&sb.super_roots[2]),
517            parse_backup_root(&sb.super_roots[3]),
518        ],
519        sys_chunk_array,
520    }
521}
522
523/// Read and parse a btrfs superblock from a reader at the given mirror index
524/// (0, 1, or 2).
525pub fn read_superblock(
526    reader: &mut (impl Read + Seek),
527    mirror: u32,
528) -> io::Result<Superblock> {
529    let offset = super_mirror_offset(mirror);
530    read_superblock_at(reader, offset)
531}
532
533/// Read and parse a btrfs superblock from a reader at an explicit byte offset.
534pub fn read_superblock_at(
535    reader: &mut (impl Read + Seek),
536    offset: u64,
537) -> io::Result<Superblock> {
538    let raw = read_raw_superblock(reader, offset)?;
539    Ok(parse_superblock(&raw))
540}
541
542/// Read the raw 4096-byte superblock from the primary mirror into a byte buffer.
543pub fn read_superblock_bytes(
544    reader: &mut (impl Read + Seek),
545) -> io::Result<[u8; SUPER_INFO_SIZE]> {
546    read_superblock_bytes_at(reader, SUPER_INFO_OFFSET)
547}
548
549/// Read the raw 4096-byte superblock at an explicit byte offset.
550pub fn read_superblock_bytes_at(
551    reader: &mut (impl Read + Seek),
552    offset: u64,
553) -> io::Result<[u8; SUPER_INFO_SIZE]> {
554    reader.seek(SeekFrom::Start(offset))?;
555    let mut buf = [0u8; SUPER_INFO_SIZE];
556    reader.read_exact(&mut buf)?;
557    Ok(buf)
558}
559
560/// Return `true` if the superblock buffer has valid magic and a matching CRC32C checksum.
561///
562/// Only CRC32C (`csum_type` == 0) is validated; other checksum types always return `false`.
563pub fn superblock_is_valid(buf: &[u8; SUPER_INFO_SIZE]) -> bool {
564    let magic_off = mem::offset_of!(raw::btrfs_super_block, magic);
565    let magic =
566        u64::from_le_bytes(buf[magic_off..magic_off + 8].try_into().unwrap());
567    if magic != raw::BTRFS_MAGIC {
568        return false;
569    }
570    let csum_type_off = mem::offset_of!(raw::btrfs_super_block, csum_type);
571    let csum_type = u16::from_le_bytes(
572        buf[csum_type_off..csum_type_off + 2].try_into().unwrap(),
573    );
574    if u32::from(csum_type) != raw::btrfs_csum_type_BTRFS_CSUM_TYPE_CRC32 {
575        return false;
576    }
577    let expected = u32::from_le_bytes(buf[0..4].try_into().unwrap());
578    let actual = raw_crc32c(0, &buf[32..]);
579    expected == actual
580}
581
582/// Extract the generation field from a raw superblock byte buffer.
583pub fn superblock_generation(buf: &[u8; SUPER_INFO_SIZE]) -> u64 {
584    let off = mem::offset_of!(raw::btrfs_super_block, generation);
585    u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
586}
587
588/// Recompute the CRC32C checksum of a superblock byte buffer in place.
589///
590/// Stores the 4-byte LE checksum at bytes 0..4, computed over bytes 32..4096.
591/// Only CRC32C (`csum_type` == 0) is supported; returns an error for other
592/// checksum types.
593pub fn csum_superblock(buf: &mut [u8; SUPER_INFO_SIZE]) -> io::Result<()> {
594    let csum_type_off = mem::offset_of!(raw::btrfs_super_block, csum_type);
595    let csum_type = u16::from_le_bytes(
596        buf[csum_type_off..csum_type_off + 2].try_into().unwrap(),
597    );
598    if u32::from(csum_type) != raw::btrfs_csum_type_BTRFS_CSUM_TYPE_CRC32 {
599        return Err(io::Error::new(
600            io::ErrorKind::Unsupported,
601            format!("unsupported checksum type {csum_type}"),
602        ));
603    }
604    let csum = raw_crc32c(0, &buf[32..]);
605    buf[0..4].copy_from_slice(&csum.to_le_bytes());
606    buf[4..32].fill(0);
607    Ok(())
608}
609
610/// Write a superblock buffer to all mirrors that fit within the device.
611///
612/// Updates the `bytenr` field per mirror before writing, then recomputes the
613/// checksum. Queries the device size via `Seek::seek(End(0))`.
614pub fn write_superblock_all_mirrors(
615    file: &mut (impl Read + Write + Seek),
616    buf: &[u8; SUPER_INFO_SIZE],
617) -> io::Result<()> {
618    let device_size = file.seek(SeekFrom::End(0))?;
619    let bytenr_off = mem::offset_of!(raw::btrfs_super_block, bytenr);
620
621    for i in 0..SUPER_MIRROR_MAX {
622        let offset = super_mirror_offset(i);
623        if offset + SUPER_INFO_SIZE as u64 > device_size {
624            break;
625        }
626        let mut mirror_buf = *buf;
627        mirror_buf[bytenr_off..bytenr_off + 8]
628            .copy_from_slice(&offset.to_le_bytes());
629        csum_superblock(&mut mirror_buf)?;
630        file.seek(SeekFrom::Start(offset))?;
631        file.write_all(&mirror_buf)?;
632    }
633    Ok(())
634}
635
636#[cfg(test)]
637mod tests {
638    use super::*;
639    use std::{io::Cursor, mem};
640
641    // --- super_mirror_offset ---
642
643    #[test]
644    fn mirror_0_at_64k() {
645        assert_eq!(super_mirror_offset(0), 65536);
646    }
647
648    #[test]
649    fn mirror_1_at_64m() {
650        assert_eq!(super_mirror_offset(1), 64 * 1024 * 1024);
651    }
652
653    #[test]
654    fn mirror_2_at_256g() {
655        assert_eq!(super_mirror_offset(2), 256 * 1024 * 1024 * 1024);
656    }
657
658    // --- ChecksumType ---
659
660    #[test]
661    fn csum_type_from_raw_known() {
662        assert_eq!(
663            ChecksumType::from_raw(
664                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_CRC32 as u16
665            ),
666            ChecksumType::Crc32
667        );
668        assert_eq!(
669            ChecksumType::from_raw(
670                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_XXHASH as u16
671            ),
672            ChecksumType::Xxhash
673        );
674        assert_eq!(
675            ChecksumType::from_raw(
676                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_SHA256 as u16
677            ),
678            ChecksumType::Sha256
679        );
680        assert_eq!(
681            ChecksumType::from_raw(
682                raw::btrfs_csum_type_BTRFS_CSUM_TYPE_BLAKE2 as u16
683            ),
684            ChecksumType::Blake2
685        );
686    }
687
688    #[test]
689    fn csum_type_from_raw_unknown() {
690        assert_eq!(ChecksumType::from_raw(99), ChecksumType::Unknown(99));
691    }
692
693    #[test]
694    fn csum_type_size() {
695        assert_eq!(ChecksumType::Crc32.size(), 4);
696        assert_eq!(ChecksumType::Xxhash.size(), 8);
697        assert_eq!(ChecksumType::Sha256.size(), 32);
698        assert_eq!(ChecksumType::Blake2.size(), 32);
699        assert_eq!(ChecksumType::Unknown(99).size(), 32);
700    }
701
702    #[test]
703    fn csum_type_display() {
704        assert_eq!(format!("{}", ChecksumType::Crc32), "crc32c");
705        assert_eq!(format!("{}", ChecksumType::Xxhash), "xxhash64");
706        assert_eq!(format!("{}", ChecksumType::Sha256), "sha256");
707        assert_eq!(format!("{}", ChecksumType::Blake2), "blake2");
708        assert_eq!(format!("{}", ChecksumType::Unknown(99)), "unknown (99)");
709    }
710
711    // --- parse_label ---
712
713    #[test]
714    fn parse_label_normal() {
715        let mut raw_label = [0i8; 256];
716        for (i, &b) in b"my-volume".iter().enumerate() {
717            raw_label[i] = b as i8;
718        }
719        assert_eq!(parse_label(&raw_label), "my-volume");
720    }
721
722    #[test]
723    fn parse_label_empty() {
724        let raw_label = [0i8; 256];
725        assert_eq!(parse_label(&raw_label), "");
726    }
727
728    #[test]
729    fn parse_label_stops_at_nul() {
730        let mut raw_label = [0i8; 256];
731        for (i, &b) in b"hello\0world".iter().enumerate() {
732            raw_label[i] = b as i8;
733        }
734        assert_eq!(parse_label(&raw_label), "hello");
735    }
736
737    // --- read_superblock with crafted image ---
738
739    #[test]
740    fn read_superblock_crafted() {
741        let total_size = SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE;
742        let mut buf = vec![0u8; total_size];
743
744        let sb_start = SUPER_INFO_OFFSET as usize;
745
746        // Set magic.
747        let magic_off =
748            sb_start + mem::offset_of!(raw::btrfs_super_block, magic);
749        buf[magic_off..magic_off + 8]
750            .copy_from_slice(&raw::BTRFS_MAGIC.to_le_bytes());
751
752        // Set bytenr = SUPER_INFO_OFFSET.
753        let bytenr_off =
754            sb_start + mem::offset_of!(raw::btrfs_super_block, bytenr);
755        buf[bytenr_off..bytenr_off + 8]
756            .copy_from_slice(&SUPER_INFO_OFFSET.to_le_bytes());
757
758        // Set generation.
759        let gen_off =
760            sb_start + mem::offset_of!(raw::btrfs_super_block, generation);
761        buf[gen_off..gen_off + 8].copy_from_slice(&42u64.to_le_bytes());
762
763        // Set nodesize.
764        let ns_off =
765            sb_start + mem::offset_of!(raw::btrfs_super_block, nodesize);
766        buf[ns_off..ns_off + 4].copy_from_slice(&16384u32.to_le_bytes());
767
768        // Set sectorsize.
769        let ss_off =
770            sb_start + mem::offset_of!(raw::btrfs_super_block, sectorsize);
771        buf[ss_off..ss_off + 4].copy_from_slice(&4096u32.to_le_bytes());
772
773        // Set a label.
774        let label_off =
775            sb_start + mem::offset_of!(raw::btrfs_super_block, label);
776        buf[label_off..label_off + 4].copy_from_slice(b"test");
777
778        let mut cursor = Cursor::new(buf);
779        let sb = read_superblock(&mut cursor, 0).unwrap();
780
781        assert!(sb.magic_is_valid());
782        assert_eq!(sb.bytenr, SUPER_INFO_OFFSET);
783        assert_eq!(sb.generation, 42);
784        assert_eq!(sb.nodesize, 16384);
785        assert_eq!(sb.sectorsize, 4096);
786        assert_eq!(sb.label, "test");
787    }
788
789    #[test]
790    fn read_superblock_bad_magic() {
791        let total_size = SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE;
792        let buf = vec![0u8; total_size];
793        let mut cursor = Cursor::new(buf);
794        let sb = read_superblock(&mut cursor, 0).unwrap();
795        assert!(!sb.magic_is_valid());
796    }
797
798    #[test]
799    fn read_superblock_too_short() {
800        let buf = vec![0u8; 100]; // way too short for mirror 0
801        let mut cursor = Cursor::new(buf);
802        assert!(read_superblock(&mut cursor, 0).is_err());
803    }
804
805    // --- superblock_is_valid / superblock_generation ---
806
807    fn make_valid_crc32c_buf() -> [u8; SUPER_INFO_SIZE] {
808        let mut buf = [0u8; SUPER_INFO_SIZE];
809        // Set magic.
810        let magic_off = mem::offset_of!(raw::btrfs_super_block, magic);
811        buf[magic_off..magic_off + 8]
812            .copy_from_slice(&raw::BTRFS_MAGIC.to_le_bytes());
813        // csum_type is already 0 (CRC32C).
814        // Set generation = 99.
815        let gen_off = mem::offset_of!(raw::btrfs_super_block, generation);
816        buf[gen_off..gen_off + 8].copy_from_slice(&99u64.to_le_bytes());
817        // Compute and store checksum.
818        csum_superblock(&mut buf).unwrap();
819        buf
820    }
821
822    #[test]
823    fn superblock_is_valid_good() {
824        let buf = make_valid_crc32c_buf();
825        assert!(superblock_is_valid(&buf));
826    }
827
828    #[test]
829    fn superblock_is_valid_bad_magic() {
830        let mut buf = make_valid_crc32c_buf();
831        let magic_off = mem::offset_of!(raw::btrfs_super_block, magic);
832        buf[magic_off] ^= 0xff; // corrupt magic
833        assert!(!superblock_is_valid(&buf));
834    }
835
836    #[test]
837    fn superblock_is_valid_bad_csum() {
838        let mut buf = make_valid_crc32c_buf();
839        buf[0] ^= 0xff; // corrupt checksum
840        assert!(!superblock_is_valid(&buf));
841    }
842
843    #[test]
844    fn superblock_generation_reads_field() {
845        let buf = make_valid_crc32c_buf();
846        assert_eq!(superblock_generation(&buf), 99);
847    }
848
849    #[test]
850    fn write_superblock_all_mirrors_updates_bytenr() {
851        let buf = make_valid_crc32c_buf();
852        // Device large enough for all 3 mirrors (256 GiB + 4096).
853        // Use only primary + secondary (64 MiB + 4096) to keep test fast.
854        let device_size = 64 * 1024 * 1024 + SUPER_INFO_SIZE;
855        let mut device = vec![0u8; device_size];
856        {
857            let mut cursor = std::io::Cursor::new(&mut device);
858            write_superblock_all_mirrors(&mut cursor, &buf).unwrap();
859        }
860        // Primary mirror (offset 64 KiB): bytenr should be 65536.
861        let bytenr_off = mem::offset_of!(raw::btrfs_super_block, bytenr);
862        let primary_bytenr = u64::from_le_bytes(
863            device[SUPER_INFO_OFFSET as usize + bytenr_off
864                ..SUPER_INFO_OFFSET as usize + bytenr_off + 8]
865                .try_into()
866                .unwrap(),
867        );
868        assert_eq!(primary_bytenr, SUPER_INFO_OFFSET);
869        // Secondary mirror (offset 64 MiB): bytenr should be 64 MiB.
870        let mirror1_off = super_mirror_offset(1) as usize;
871        let mirror1_bytenr = u64::from_le_bytes(
872            device[mirror1_off + bytenr_off..mirror1_off + bytenr_off + 8]
873                .try_into()
874                .unwrap(),
875        );
876        assert_eq!(mirror1_bytenr, super_mirror_offset(1));
877        // Both mirrors should be valid after the write.
878        let primary_buf: [u8; SUPER_INFO_SIZE] = device[SUPER_INFO_OFFSET
879            as usize
880            ..SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE]
881            .try_into()
882            .unwrap();
883        assert!(superblock_is_valid(&primary_buf));
884        let mirror1_buf: [u8; SUPER_INFO_SIZE] = device
885            [mirror1_off..mirror1_off + SUPER_INFO_SIZE]
886            .try_into()
887            .unwrap();
888        assert!(superblock_is_valid(&mirror1_buf));
889    }
890
891    #[test]
892    fn to_bytes_roundtrip() {
893        // Build a Superblock, serialize, then parse back and compare.
894        let fsid =
895            Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
896        let dev_uuid =
897            Uuid::parse_str("cafebabe-cafe-babe-cafe-babecafebabe").unwrap();
898
899        let sb = Superblock {
900            csum: [0; 32],
901            fsid,
902            bytenr: SUPER_INFO_OFFSET,
903            flags: 0,
904            magic: raw::BTRFS_MAGIC,
905            generation: 1,
906            root: 0x10_0000,
907            chunk_root: 0x10_8000,
908            log_root: 0,
909            log_root_transid: 0,
910            total_bytes: 512 * 1024 * 1024,
911            bytes_used: 7 * 16384,
912            root_dir_objectid: 6,
913            num_devices: 1,
914            sectorsize: 4096,
915            nodesize: 16384,
916            leafsize: 16384,
917            stripesize: 4096,
918            sys_chunk_array_size: 0,
919            chunk_root_generation: 1,
920            compat_flags: 0,
921            compat_ro_flags: 0,
922            incompat_flags: 0,
923            csum_type: ChecksumType::Crc32,
924            root_level: 0,
925            chunk_root_level: 0,
926            log_root_level: 0,
927            dev_item: DeviceItem {
928                devid: 1,
929                total_bytes: 512 * 1024 * 1024,
930                bytes_used: 4 * 1024 * 1024,
931                io_align: 4096,
932                io_width: 4096,
933                sector_size: 4096,
934                dev_type: 0,
935                generation: 0,
936                start_offset: 0,
937                dev_group: 0,
938                seek_speed: 0,
939                bandwidth: 0,
940                uuid: dev_uuid,
941                fsid,
942            },
943            label: "test-label".to_string(),
944            cache_generation: 0,
945            uuid_tree_generation: 0,
946            metadata_uuid: Uuid::nil(),
947            nr_global_roots: 0,
948            backup_roots: std::array::from_fn(|_| BackupRoot {
949                tree_root: 0,
950                tree_root_gen: 0,
951                chunk_root: 0,
952                chunk_root_gen: 0,
953                extent_root: 0,
954                extent_root_gen: 0,
955                fs_root: 0,
956                fs_root_gen: 0,
957                dev_root: 0,
958                dev_root_gen: 0,
959                csum_root: 0,
960                csum_root_gen: 0,
961                total_bytes: 0,
962                bytes_used: 0,
963                num_devices: 0,
964                tree_root_level: 0,
965                chunk_root_level: 0,
966                extent_root_level: 0,
967                fs_root_level: 0,
968                dev_root_level: 0,
969                csum_root_level: 0,
970            }),
971            sys_chunk_array: [0; 2048],
972        };
973
974        let mut bytes = sb.to_bytes();
975        csum_superblock(&mut bytes).unwrap();
976
977        // Parse back via read_superblock.
978        let mut image = vec![0u8; SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE];
979        image[SUPER_INFO_OFFSET as usize..].copy_from_slice(&bytes);
980        let parsed = read_superblock(&mut Cursor::new(&image[..]), 0).unwrap();
981
982        assert!(parsed.magic_is_valid());
983        assert_eq!(parsed.fsid, fsid);
984        assert_eq!(parsed.generation, 1);
985        assert_eq!(parsed.root, 0x10_0000);
986        assert_eq!(parsed.chunk_root, 0x10_8000);
987        assert_eq!(parsed.total_bytes, 512 * 1024 * 1024);
988        assert_eq!(parsed.nodesize, 16384);
989        assert_eq!(parsed.sectorsize, 4096);
990        assert_eq!(parsed.label, "test-label");
991        assert_eq!(parsed.num_devices, 1);
992        assert_eq!(parsed.dev_item.devid, 1);
993        assert_eq!(parsed.dev_item.uuid, dev_uuid);
994    }
995}