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