1use 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
17const SUPER_INFO_SIZE: usize = 4096;
20
21const SUPER_INFO_OFFSET: u64 = 65536;
24
25pub const SUPER_MIRROR_MAX: u32 = 3;
28
29const SUPER_MIRROR_SHIFT: u32 = 12;
32
33pub fn super_mirror_offset(index: u32) -> u64 {
37 if index == 0 {
38 SUPER_INFO_OFFSET
39 } else {
40 (16 * 1024u64) << (SUPER_MIRROR_SHIFT * index)
42 }
43}
44
45#[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 #[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 #[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, }
80 }
81}
82
83impl ChecksumType {
84 #[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#[derive(Debug, Clone, Default)]
123pub struct BackupRoot {
124 pub tree_root: u64,
126 pub tree_root_gen: u64,
128 pub chunk_root: u64,
130 pub chunk_root_gen: u64,
132 pub extent_root: u64,
134 pub extent_root_gen: u64,
136 pub fs_root: u64,
138 pub fs_root_gen: u64,
140 pub dev_root: u64,
142 pub dev_root_gen: u64,
144 pub csum_root: u64,
146 pub csum_root_gen: u64,
148 pub total_bytes: u64,
150 pub bytes_used: u64,
152 pub num_devices: u64,
154 pub tree_root_level: u8,
156 pub chunk_root_level: u8,
158 pub extent_root_level: u8,
160 pub fs_root_level: u8,
162 pub dev_root_level: u8,
164 pub csum_root_level: u8,
166}
167
168#[derive(Debug, Clone)]
175pub struct Superblock {
176 pub csum: [u8; 32],
178 pub fsid: Uuid,
180 pub bytenr: u64,
182 pub flags: u64,
184 pub magic: u64,
186 pub generation: u64,
188 pub root: u64,
190 pub chunk_root: u64,
192 pub log_root: u64,
194 pub log_root_transid: u64,
196 pub total_bytes: u64,
198 pub bytes_used: u64,
200 pub root_dir_objectid: u64,
202 pub num_devices: u64,
204 pub sectorsize: u32,
206 pub nodesize: u32,
208 pub leafsize: u32,
210 pub stripesize: u32,
212 pub sys_chunk_array_size: u32,
214 pub chunk_root_generation: u64,
216 pub compat_flags: u64,
218 pub compat_ro_flags: u64,
220 pub incompat_flags: u64,
222 pub csum_type: ChecksumType,
224 pub root_level: u8,
226 pub chunk_root_level: u8,
228 pub log_root_level: u8,
230 pub dev_item: DeviceItem,
232 pub label: String,
234 pub cache_generation: u64,
236 pub uuid_tree_generation: u64,
238 pub metadata_uuid: Uuid,
240 pub nr_global_roots: u64,
242 pub backup_roots: [BackupRoot; 4],
244 pub sys_chunk_array: [u8; 2048],
246}
247
248impl Superblock {
249 pub fn magic_is_valid(&self) -> bool {
251 self.magic == raw::BTRFS_MAGIC
252 }
253
254 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 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); 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 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 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 v.resize(SUPER_INFO_SIZE, 0);
321 v.try_into().unwrap()
322 }
323}
324
325impl BackupRoot {
326 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 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 buf.put_bytes(0, 10);
353 }
354}
355
356fn 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
365fn 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 let sb: raw::btrfs_super_block =
379 unsafe { std::ptr::read_unaligned(buf.as_ptr().cast()) };
380 Ok(sb)
381}
382
383macro_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 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
523pub 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
533pub 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
542pub 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
549pub 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
560pub 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
582pub 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
588pub 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
610pub 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 #[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 #[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 #[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 #[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 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 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 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 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 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 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]; let mut cursor = Cursor::new(buf);
802 assert!(read_superblock(&mut cursor, 0).is_err());
803 }
804
805 fn make_valid_crc32c_buf() -> [u8; SUPER_INFO_SIZE] {
808 let mut buf = [0u8; SUPER_INFO_SIZE];
809 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 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 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; 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; 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 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 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 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 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 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 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}