1use 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
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
33#[must_use]
37pub fn super_mirror_offset(index: u32) -> u64 {
38 if index == 0 {
39 SUPER_INFO_OFFSET
40 } else {
41 (16 * 1024u64) << (SUPER_MIRROR_SHIFT * index)
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum ChecksumType {
50 Crc32,
52 Xxhash,
54 Sha256,
56 Blake2,
58 Unknown(u16),
60}
61
62impl ChecksumType {
63 #[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 #[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, }
87 }
88
89 #[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 #[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#[derive(Debug, Clone, Default)]
161pub struct BackupRoot {
162 pub tree_root: u64,
164 pub tree_root_gen: u64,
166 pub chunk_root: u64,
168 pub chunk_root_gen: u64,
170 pub extent_root: u64,
172 pub extent_root_gen: u64,
174 pub fs_root: u64,
176 pub fs_root_gen: u64,
178 pub dev_root: u64,
180 pub dev_root_gen: u64,
182 pub csum_root: u64,
184 pub csum_root_gen: u64,
186 pub total_bytes: u64,
188 pub bytes_used: u64,
190 pub num_devices: u64,
192 pub tree_root_level: u8,
194 pub chunk_root_level: u8,
196 pub extent_root_level: u8,
198 pub fs_root_level: u8,
200 pub dev_root_level: u8,
202 pub csum_root_level: u8,
204}
205
206#[derive(Debug, Clone)]
213pub struct Superblock {
214 pub csum: [u8; 32],
216 pub fsid: Uuid,
218 pub bytenr: u64,
220 pub flags: u64,
222 pub magic: u64,
224 pub generation: u64,
226 pub root: u64,
228 pub chunk_root: u64,
230 pub log_root: u64,
232 pub log_root_transid: u64,
234 pub total_bytes: u64,
236 pub bytes_used: u64,
238 pub root_dir_objectid: u64,
240 pub num_devices: u64,
242 pub sectorsize: u32,
244 pub nodesize: u32,
246 pub leafsize: u32,
248 pub stripesize: u32,
250 pub sys_chunk_array_size: u32,
252 pub chunk_root_generation: u64,
254 pub compat_flags: u64,
256 pub compat_ro_flags: u64,
258 pub incompat_flags: u64,
260 pub csum_type: ChecksumType,
262 pub root_level: u8,
264 pub chunk_root_level: u8,
266 pub log_root_level: u8,
268 pub dev_item: DeviceItem,
270 pub label: String,
272 pub cache_generation: u64,
274 pub uuid_tree_generation: u64,
276 pub metadata_uuid: Uuid,
278 pub nr_global_roots: u64,
280 pub backup_roots: [BackupRoot; 4],
282 pub sys_chunk_array: [u8; 2048],
284}
285
286impl Superblock {
287 #[must_use]
289 pub fn magic_is_valid(&self) -> bool {
290 self.magic == raw::BTRFS_MAGIC
291 }
292
293 #[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 #[must_use]
306 #[allow(clippy::missing_panics_doc)] 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); 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 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 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 v.resize(SUPER_INFO_SIZE, 0);
363 v.try_into().unwrap()
364 }
365}
366
367impl BackupRoot {
368 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 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 buf.put_bytes(0, 10);
395 }
396}
397
398fn 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
407fn 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 let sb: raw::btrfs_super_block =
421 unsafe { std::ptr::read_unaligned(buf.as_ptr().cast()) };
422 Ok(sb)
423}
424
425macro_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 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 #[allow(clippy::unnecessary_cast)]
512 let bytes: Vec<u8> = raw_label
513 .iter()
514 .take_while(|&&c| c != 0)
515 .map(|&c| c as u8)
516 .collect();
517 String::from_utf8_lossy(&bytes).into_owned()
518}
519
520fn parse_superblock(sb: &raw::btrfs_super_block) -> Superblock {
521 let mut sys_chunk_array = [0u8; 2048];
522 sys_chunk_array.copy_from_slice(&sb.sys_chunk_array);
523
524 Superblock {
525 csum: sb.csum,
526 fsid: Uuid::from_bytes(sb.fsid),
527 bytenr: le64!(sb.bytenr),
528 flags: le64!(sb.flags),
529 magic: le64!(sb.magic),
530 generation: le64!(sb.generation),
531 root: le64!(sb.root),
532 chunk_root: le64!(sb.chunk_root),
533 log_root: le64!(sb.log_root),
534 log_root_transid: le64!(sb.__unused_log_root_transid),
535 total_bytes: le64!(sb.total_bytes),
536 bytes_used: le64!(sb.bytes_used),
537 root_dir_objectid: le64!(sb.root_dir_objectid),
538 num_devices: le64!(sb.num_devices),
539 sectorsize: le32!(sb.sectorsize),
540 nodesize: le32!(sb.nodesize),
541 leafsize: le32!(sb.__unused_leafsize),
542 stripesize: le32!(sb.stripesize),
543 sys_chunk_array_size: le32!(sb.sys_chunk_array_size),
544 chunk_root_generation: le64!(sb.chunk_root_generation),
545 compat_flags: le64!(sb.compat_flags),
546 compat_ro_flags: le64!(sb.compat_ro_flags),
547 incompat_flags: le64!(sb.incompat_flags),
548 csum_type: ChecksumType::from_raw(le16!(sb.csum_type)),
549 root_level: sb.root_level,
550 chunk_root_level: sb.chunk_root_level,
551 log_root_level: sb.log_root_level,
552 dev_item: parse_dev_item(&sb.dev_item),
553 label: parse_label(&sb.label),
554 cache_generation: le64!(sb.cache_generation),
555 uuid_tree_generation: le64!(sb.uuid_tree_generation),
556 metadata_uuid: Uuid::from_bytes(sb.metadata_uuid),
557 nr_global_roots: sb.nr_global_roots,
558 backup_roots: [
559 parse_backup_root(&sb.super_roots[0]),
560 parse_backup_root(&sb.super_roots[1]),
561 parse_backup_root(&sb.super_roots[2]),
562 parse_backup_root(&sb.super_roots[3]),
563 ],
564 sys_chunk_array,
565 }
566}
567
568pub fn read_superblock(
575 reader: &mut (impl Read + Seek),
576 mirror: u32,
577) -> io::Result<Superblock> {
578 let offset = super_mirror_offset(mirror);
579 read_superblock_at(reader, offset)
580}
581
582pub fn read_superblock_at(
588 reader: &mut (impl Read + Seek),
589 offset: u64,
590) -> io::Result<Superblock> {
591 let raw = read_raw_superblock(reader, offset)?;
592 Ok(parse_superblock(&raw))
593}
594
595pub fn read_superblock_bytes(
601 reader: &mut (impl Read + Seek),
602) -> io::Result<[u8; SUPER_INFO_SIZE]> {
603 read_superblock_bytes_at(reader, SUPER_INFO_OFFSET)
604}
605
606pub fn read_superblock_bytes_at(
612 reader: &mut (impl Read + Seek),
613 offset: u64,
614) -> io::Result<[u8; SUPER_INFO_SIZE]> {
615 reader.seek(SeekFrom::Start(offset))?;
616 let mut buf = [0u8; SUPER_INFO_SIZE];
617 reader.read_exact(&mut buf)?;
618 Ok(buf)
619}
620
621#[must_use]
627#[allow(clippy::missing_panics_doc)] pub fn superblock_is_valid(buf: &[u8; SUPER_INFO_SIZE]) -> bool {
629 let magic_off = mem::offset_of!(raw::btrfs_super_block, magic);
630 let magic =
631 u64::from_le_bytes(buf[magic_off..magic_off + 8].try_into().unwrap());
632 if magic != raw::BTRFS_MAGIC {
633 return false;
634 }
635 let csum_type_off = mem::offset_of!(raw::btrfs_super_block, csum_type);
636 let csum_type = ChecksumType::from_raw(u16::from_le_bytes(
637 buf[csum_type_off..csum_type_off + 2].try_into().unwrap(),
638 ));
639 if matches!(csum_type, ChecksumType::Unknown(_)) {
640 return false;
641 }
642 let n = csum_type.size();
643 let expected = &buf[0..n];
644 let actual = csum_type.compute(&buf[32..]);
645 expected == &actual[..n]
646}
647
648#[must_use]
650#[allow(clippy::missing_panics_doc)] pub fn superblock_generation(buf: &[u8; SUPER_INFO_SIZE]) -> u64 {
652 let off = mem::offset_of!(raw::btrfs_super_block, generation);
653 u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
654}
655
656#[allow(clippy::missing_panics_doc)] pub fn csum_superblock(buf: &mut [u8; SUPER_INFO_SIZE]) -> io::Result<()> {
669 let csum_type_off = mem::offset_of!(raw::btrfs_super_block, csum_type);
670 let raw_csum_type = u16::from_le_bytes(
671 buf[csum_type_off..csum_type_off + 2].try_into().unwrap(),
672 );
673 let csum_type = ChecksumType::from_raw(raw_csum_type);
674 if matches!(csum_type, ChecksumType::Unknown(_)) {
675 return Err(io::Error::new(
676 io::ErrorKind::Unsupported,
677 format!("unsupported checksum type {raw_csum_type}"),
678 ));
679 }
680 let hash = csum_type.compute(&buf[32..]);
681 let n = csum_type.size();
682 buf[0..n].copy_from_slice(&hash[..n]);
683 buf[n..32].fill(0);
684 Ok(())
685}
686
687pub fn write_superblock_all_mirrors(
696 file: &mut (impl Read + Write + Seek),
697 buf: &[u8; SUPER_INFO_SIZE],
698) -> io::Result<()> {
699 let device_size = file.seek(SeekFrom::End(0))?;
700 let bytenr_off = mem::offset_of!(raw::btrfs_super_block, bytenr);
701
702 for i in 0..SUPER_MIRROR_MAX {
703 let offset = super_mirror_offset(i);
704 if offset + SUPER_INFO_SIZE as u64 > device_size {
705 break;
706 }
707 let mut mirror_buf = *buf;
708 mirror_buf[bytenr_off..bytenr_off + 8]
709 .copy_from_slice(&offset.to_le_bytes());
710 csum_superblock(&mut mirror_buf)?;
711 file.seek(SeekFrom::Start(offset))?;
712 file.write_all(&mirror_buf)?;
713 }
714 Ok(())
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720 use std::{ffi::c_char, io::Cursor, mem};
721
722 #[test]
725 fn mirror_0_at_64k() {
726 assert_eq!(super_mirror_offset(0), 65536);
727 }
728
729 #[test]
730 fn mirror_1_at_64m() {
731 assert_eq!(super_mirror_offset(1), 64 * 1024 * 1024);
732 }
733
734 #[test]
735 fn mirror_2_at_256g() {
736 assert_eq!(super_mirror_offset(2), 256 * 1024 * 1024 * 1024);
737 }
738
739 #[test]
742 fn csum_type_from_raw_known() {
743 assert_eq!(
744 ChecksumType::from_raw(
745 raw::btrfs_csum_type_BTRFS_CSUM_TYPE_CRC32 as u16
746 ),
747 ChecksumType::Crc32
748 );
749 assert_eq!(
750 ChecksumType::from_raw(
751 raw::btrfs_csum_type_BTRFS_CSUM_TYPE_XXHASH as u16
752 ),
753 ChecksumType::Xxhash
754 );
755 assert_eq!(
756 ChecksumType::from_raw(
757 raw::btrfs_csum_type_BTRFS_CSUM_TYPE_SHA256 as u16
758 ),
759 ChecksumType::Sha256
760 );
761 assert_eq!(
762 ChecksumType::from_raw(
763 raw::btrfs_csum_type_BTRFS_CSUM_TYPE_BLAKE2 as u16
764 ),
765 ChecksumType::Blake2
766 );
767 }
768
769 #[test]
770 fn csum_type_from_raw_unknown() {
771 assert_eq!(ChecksumType::from_raw(99), ChecksumType::Unknown(99));
772 }
773
774 #[test]
775 fn csum_type_size() {
776 assert_eq!(ChecksumType::Crc32.size(), 4);
777 assert_eq!(ChecksumType::Xxhash.size(), 8);
778 assert_eq!(ChecksumType::Sha256.size(), 32);
779 assert_eq!(ChecksumType::Blake2.size(), 32);
780 assert_eq!(ChecksumType::Unknown(99).size(), 32);
781 }
782
783 #[test]
784 fn csum_type_display() {
785 assert_eq!(format!("{}", ChecksumType::Crc32), "crc32c");
786 assert_eq!(format!("{}", ChecksumType::Xxhash), "xxhash64");
787 assert_eq!(format!("{}", ChecksumType::Sha256), "sha256");
788 assert_eq!(format!("{}", ChecksumType::Blake2), "blake2");
789 assert_eq!(format!("{}", ChecksumType::Unknown(99)), "unknown (99)");
790 }
791
792 #[test]
795 fn parse_label_normal() {
796 let mut raw_label = [0 as c_char; 256];
797 for (i, &b) in b"my-volume".iter().enumerate() {
798 raw_label[i] = b as c_char;
799 }
800 assert_eq!(parse_label(&raw_label), "my-volume");
801 }
802
803 #[test]
804 fn parse_label_empty() {
805 let raw_label = [0 as c_char; 256];
806 assert_eq!(parse_label(&raw_label), "");
807 }
808
809 #[test]
810 fn parse_label_stops_at_nul() {
811 let mut raw_label = [0 as c_char; 256];
812 for (i, &b) in b"hello\0world".iter().enumerate() {
813 raw_label[i] = b as c_char;
814 }
815 assert_eq!(parse_label(&raw_label), "hello");
816 }
817
818 #[test]
821 fn read_superblock_crafted() {
822 let total_size = SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE;
823 let mut buf = vec![0u8; total_size];
824
825 let sb_start = SUPER_INFO_OFFSET as usize;
826
827 let magic_off =
829 sb_start + mem::offset_of!(raw::btrfs_super_block, magic);
830 buf[magic_off..magic_off + 8]
831 .copy_from_slice(&raw::BTRFS_MAGIC.to_le_bytes());
832
833 let bytenr_off =
835 sb_start + mem::offset_of!(raw::btrfs_super_block, bytenr);
836 buf[bytenr_off..bytenr_off + 8]
837 .copy_from_slice(&SUPER_INFO_OFFSET.to_le_bytes());
838
839 let gen_off =
841 sb_start + mem::offset_of!(raw::btrfs_super_block, generation);
842 buf[gen_off..gen_off + 8].copy_from_slice(&42u64.to_le_bytes());
843
844 let ns_off =
846 sb_start + mem::offset_of!(raw::btrfs_super_block, nodesize);
847 buf[ns_off..ns_off + 4].copy_from_slice(&16384u32.to_le_bytes());
848
849 let ss_off =
851 sb_start + mem::offset_of!(raw::btrfs_super_block, sectorsize);
852 buf[ss_off..ss_off + 4].copy_from_slice(&4096u32.to_le_bytes());
853
854 let label_off =
856 sb_start + mem::offset_of!(raw::btrfs_super_block, label);
857 buf[label_off..label_off + 4].copy_from_slice(b"test");
858
859 let mut cursor = Cursor::new(buf);
860 let sb = read_superblock(&mut cursor, 0).unwrap();
861
862 assert!(sb.magic_is_valid());
863 assert_eq!(sb.bytenr, SUPER_INFO_OFFSET);
864 assert_eq!(sb.generation, 42);
865 assert_eq!(sb.nodesize, 16384);
866 assert_eq!(sb.sectorsize, 4096);
867 assert_eq!(sb.label, "test");
868 }
869
870 #[test]
871 fn read_superblock_bad_magic() {
872 let total_size = SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE;
873 let buf = vec![0u8; total_size];
874 let mut cursor = Cursor::new(buf);
875 let sb = read_superblock(&mut cursor, 0).unwrap();
876 assert!(!sb.magic_is_valid());
877 }
878
879 #[test]
880 fn read_superblock_too_short() {
881 let buf = vec![0u8; 100]; let mut cursor = Cursor::new(buf);
883 assert!(read_superblock(&mut cursor, 0).is_err());
884 }
885
886 fn make_valid_crc32c_buf() -> [u8; SUPER_INFO_SIZE] {
889 let mut buf = [0u8; SUPER_INFO_SIZE];
890 let magic_off = mem::offset_of!(raw::btrfs_super_block, magic);
892 buf[magic_off..magic_off + 8]
893 .copy_from_slice(&raw::BTRFS_MAGIC.to_le_bytes());
894 let gen_off = mem::offset_of!(raw::btrfs_super_block, generation);
897 buf[gen_off..gen_off + 8].copy_from_slice(&99u64.to_le_bytes());
898 csum_superblock(&mut buf).unwrap();
900 buf
901 }
902
903 #[test]
904 fn superblock_is_valid_good() {
905 let buf = make_valid_crc32c_buf();
906 assert!(superblock_is_valid(&buf));
907 }
908
909 #[test]
910 fn superblock_is_valid_bad_magic() {
911 let mut buf = make_valid_crc32c_buf();
912 let magic_off = mem::offset_of!(raw::btrfs_super_block, magic);
913 buf[magic_off] ^= 0xff; assert!(!superblock_is_valid(&buf));
915 }
916
917 #[test]
918 fn superblock_is_valid_bad_csum() {
919 let mut buf = make_valid_crc32c_buf();
920 buf[0] ^= 0xff; assert!(!superblock_is_valid(&buf));
922 }
923
924 #[test]
925 fn superblock_generation_reads_field() {
926 let buf = make_valid_crc32c_buf();
927 assert_eq!(superblock_generation(&buf), 99);
928 }
929
930 #[test]
931 fn write_superblock_all_mirrors_updates_bytenr() {
932 let buf = make_valid_crc32c_buf();
933 let device_size = 64 * 1024 * 1024 + SUPER_INFO_SIZE;
936 let mut device = vec![0u8; device_size];
937 {
938 let mut cursor = std::io::Cursor::new(&mut device);
939 write_superblock_all_mirrors(&mut cursor, &buf).unwrap();
940 }
941 let bytenr_off = mem::offset_of!(raw::btrfs_super_block, bytenr);
943 let primary_bytenr = u64::from_le_bytes(
944 device[SUPER_INFO_OFFSET as usize + bytenr_off
945 ..SUPER_INFO_OFFSET as usize + bytenr_off + 8]
946 .try_into()
947 .unwrap(),
948 );
949 assert_eq!(primary_bytenr, SUPER_INFO_OFFSET);
950 let mirror1_off = super_mirror_offset(1) as usize;
952 let mirror1_bytenr = u64::from_le_bytes(
953 device[mirror1_off + bytenr_off..mirror1_off + bytenr_off + 8]
954 .try_into()
955 .unwrap(),
956 );
957 assert_eq!(mirror1_bytenr, super_mirror_offset(1));
958 let primary_buf: [u8; SUPER_INFO_SIZE] = device[SUPER_INFO_OFFSET
960 as usize
961 ..SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE]
962 .try_into()
963 .unwrap();
964 assert!(superblock_is_valid(&primary_buf));
965 let mirror1_buf: [u8; SUPER_INFO_SIZE] = device
966 [mirror1_off..mirror1_off + SUPER_INFO_SIZE]
967 .try_into()
968 .unwrap();
969 assert!(superblock_is_valid(&mirror1_buf));
970 }
971
972 #[test]
973 fn to_bytes_roundtrip() {
974 let fsid =
976 Uuid::parse_str("deadbeef-dead-beef-dead-beefdeadbeef").unwrap();
977 let dev_uuid =
978 Uuid::parse_str("cafebabe-cafe-babe-cafe-babecafebabe").unwrap();
979
980 let sb = Superblock {
981 csum: [0; 32],
982 fsid,
983 bytenr: SUPER_INFO_OFFSET,
984 flags: 0,
985 magic: raw::BTRFS_MAGIC,
986 generation: 1,
987 root: 0x10_0000,
988 chunk_root: 0x10_8000,
989 log_root: 0,
990 log_root_transid: 0,
991 total_bytes: 512 * 1024 * 1024,
992 bytes_used: 7 * 16384,
993 root_dir_objectid: 6,
994 num_devices: 1,
995 sectorsize: 4096,
996 nodesize: 16384,
997 leafsize: 16384,
998 stripesize: 4096,
999 sys_chunk_array_size: 0,
1000 chunk_root_generation: 1,
1001 compat_flags: 0,
1002 compat_ro_flags: 0,
1003 incompat_flags: 0,
1004 csum_type: ChecksumType::Crc32,
1005 root_level: 0,
1006 chunk_root_level: 0,
1007 log_root_level: 0,
1008 dev_item: DeviceItem {
1009 devid: 1,
1010 total_bytes: 512 * 1024 * 1024,
1011 bytes_used: 4 * 1024 * 1024,
1012 io_align: 4096,
1013 io_width: 4096,
1014 sector_size: 4096,
1015 dev_type: 0,
1016 generation: 0,
1017 start_offset: 0,
1018 dev_group: 0,
1019 seek_speed: 0,
1020 bandwidth: 0,
1021 uuid: dev_uuid,
1022 fsid,
1023 },
1024 label: "test-label".to_string(),
1025 cache_generation: 0,
1026 uuid_tree_generation: 0,
1027 metadata_uuid: Uuid::nil(),
1028 nr_global_roots: 0,
1029 backup_roots: std::array::from_fn(|_| BackupRoot {
1030 tree_root: 0,
1031 tree_root_gen: 0,
1032 chunk_root: 0,
1033 chunk_root_gen: 0,
1034 extent_root: 0,
1035 extent_root_gen: 0,
1036 fs_root: 0,
1037 fs_root_gen: 0,
1038 dev_root: 0,
1039 dev_root_gen: 0,
1040 csum_root: 0,
1041 csum_root_gen: 0,
1042 total_bytes: 0,
1043 bytes_used: 0,
1044 num_devices: 0,
1045 tree_root_level: 0,
1046 chunk_root_level: 0,
1047 extent_root_level: 0,
1048 fs_root_level: 0,
1049 dev_root_level: 0,
1050 csum_root_level: 0,
1051 }),
1052 sys_chunk_array: [0; 2048],
1053 };
1054
1055 let mut bytes = sb.to_bytes();
1056 csum_superblock(&mut bytes).unwrap();
1057
1058 let mut image = vec![0u8; SUPER_INFO_OFFSET as usize + SUPER_INFO_SIZE];
1060 image[SUPER_INFO_OFFSET as usize..].copy_from_slice(&bytes);
1061 let parsed = read_superblock(&mut Cursor::new(&image[..]), 0).unwrap();
1062
1063 assert!(parsed.magic_is_valid());
1064 assert_eq!(parsed.fsid, fsid);
1065 assert_eq!(parsed.generation, 1);
1066 assert_eq!(parsed.root, 0x10_0000);
1067 assert_eq!(parsed.chunk_root, 0x10_8000);
1068 assert_eq!(parsed.total_bytes, 512 * 1024 * 1024);
1069 assert_eq!(parsed.nodesize, 16384);
1070 assert_eq!(parsed.sectorsize, 4096);
1071 assert_eq!(parsed.label, "test-label");
1072 assert_eq!(parsed.num_devices, 1);
1073 assert_eq!(parsed.dev_item.devid, 1);
1074 assert_eq!(parsed.dev_item.uuid, dev_uuid);
1075 }
1076}