use crate::error::{Error, Result};
use crate::superblock::{ChecksumType, CSUM_SIZE};
pub mod offsets {
pub const CSUM: usize = 0x000;
pub const GENERATION: usize = 0x048;
pub const ROOT: usize = 0x050;
pub const CHUNK_ROOT: usize = 0x058;
pub const BYTES_USED: usize = 0x078;
pub const CHUNK_ROOT_GENERATION: usize = 0x0a4;
pub const ROOT_LEVEL: usize = 0x0c6;
pub const CACHE_GENERATION: usize = 0x22b;
pub const UUID_TREE_GENERATION: usize = 0x233;
pub const BYTENR: usize = 0x30;
pub const COMPAT_RO_FLAGS: usize = 0xb4;
pub const ROOT_BACKUPS: usize = 0xb2b;
}
pub const ROOT_BACKUP_SIZE: usize = 168;
pub const ROOT_BACKUP_SLOTS: u64 = 4;
pub const ROOT_BACKUPS_END: usize =
offsets::ROOT_BACKUPS + (ROOT_BACKUP_SLOTS as usize) * ROOT_BACKUP_SIZE;
pub mod backup_offsets {
pub const TREE_ROOT: usize = 0;
pub const TREE_ROOT_GEN: usize = 8;
pub const CHUNK_ROOT: usize = 16;
pub const CHUNK_ROOT_GEN: usize = 24;
pub const EXTENT_ROOT: usize = 32;
pub const EXTENT_ROOT_GEN: usize = 40;
pub const FS_ROOT: usize = 48;
pub const FS_ROOT_GEN: usize = 56;
pub const DEV_ROOT: usize = 64;
pub const DEV_ROOT_GEN: usize = 72;
pub const CSUM_ROOT: usize = 80;
pub const CSUM_ROOT_GEN: usize = 88;
pub const TOTAL_BYTES: usize = 96;
pub const BYTES_USED: usize = 104;
pub const NUM_DEVICES: usize = 112;
pub const TREE_ROOT_LEVEL: usize = 152;
pub const CHUNK_ROOT_LEVEL: usize = 153;
pub const EXTENT_ROOT_LEVEL: usize = 154;
pub const FS_ROOT_LEVEL: usize = 155;
pub const DEV_ROOT_LEVEL: usize = 156;
pub const CSUM_ROOT_LEVEL: usize = 157;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BackupRoot {
pub bytenr: u64,
pub generation: u64,
pub level: u8,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BackupRoots {
pub tree: BackupRoot,
pub chunk: BackupRoot,
pub extent: BackupRoot,
pub fs: BackupRoot,
pub dev: BackupRoot,
pub csum: BackupRoot,
pub total_bytes: u64,
pub bytes_used: u64,
pub num_devices: u64,
}
pub fn write_backup(raw: &mut [u8], generation: u64, roots: &BackupRoots) {
use backup_offsets as b;
let at = backup_slot_offset(generation);
let slot = &mut raw[at..at + ROOT_BACKUP_SIZE];
slot.fill(0);
let mut put = |off: usize, v: u64| slot[off..off + 8].copy_from_slice(&v.to_le_bytes());
for (root, bytenr, gen) in [
(&roots.tree, b::TREE_ROOT, b::TREE_ROOT_GEN),
(&roots.chunk, b::CHUNK_ROOT, b::CHUNK_ROOT_GEN),
(&roots.extent, b::EXTENT_ROOT, b::EXTENT_ROOT_GEN),
(&roots.fs, b::FS_ROOT, b::FS_ROOT_GEN),
(&roots.dev, b::DEV_ROOT, b::DEV_ROOT_GEN),
(&roots.csum, b::CSUM_ROOT, b::CSUM_ROOT_GEN),
] {
put(bytenr, root.bytenr);
put(gen, root.generation);
}
put(b::TOTAL_BYTES, roots.total_bytes);
put(b::BYTES_USED, roots.bytes_used);
put(b::NUM_DEVICES, roots.num_devices);
for (root, level) in [
(&roots.tree, b::TREE_ROOT_LEVEL),
(&roots.chunk, b::CHUNK_ROOT_LEVEL),
(&roots.extent, b::EXTENT_ROOT_LEVEL),
(&roots.fs, b::FS_ROOT_LEVEL),
(&roots.dev, b::DEV_ROOT_LEVEL),
(&roots.csum, b::CSUM_ROOT_LEVEL),
] {
slot[level] = root.level;
}
}
pub const SUPERBLOCK_SIZE: usize = 4096;
pub fn backup_slot(generation: u64) -> usize {
((generation.wrapping_sub(1)) % ROOT_BACKUP_SLOTS) as usize
}
pub fn backup_slot_offset(generation: u64) -> usize {
offsets::ROOT_BACKUPS + backup_slot(generation) * ROOT_BACKUP_SIZE
}
#[derive(Debug, Clone, Default)]
pub struct Commit {
pub generation: u64,
pub root: u64,
pub root_level: Option<u8>,
pub bytes_used: Option<u64>,
pub chunk_root: Option<u64>,
pub chunk_root_generation: Option<u64>,
pub invalidate_free_space_tree: bool,
}
pub fn apply(raw: &mut [u8], csum_type: ChecksumType, commit: &Commit) -> Result<()> {
if raw.len() < SUPERBLOCK_SIZE {
return Err(Error::UnsupportedFeature(format!(
"a superblock is {SUPERBLOCK_SIZE} bytes and this is {}",
raw.len()
)));
}
put64(raw, offsets::GENERATION, commit.generation);
put64(raw, offsets::ROOT, commit.root);
put64(raw, offsets::UUID_TREE_GENERATION, commit.generation);
if let Some(level) = commit.root_level {
raw[offsets::ROOT_LEVEL] = level;
}
if let Some(used) = commit.bytes_used {
put64(raw, offsets::BYTES_USED, used);
}
if let Some(chunk_root) = commit.chunk_root {
put64(raw, offsets::CHUNK_ROOT, chunk_root);
}
if let Some(gen) = commit.chunk_root_generation {
put64(raw, offsets::CHUNK_ROOT_GENERATION, gen);
}
if commit.invalidate_free_space_tree {
use crate::superblock::compat_ro::FREE_SPACE_TREE_VALID;
let flags = u64::from_le_bytes(
raw[offsets::COMPAT_RO_FLAGS..offsets::COMPAT_RO_FLAGS + 8]
.try_into()
.expect("8 bytes"),
);
put64(
raw,
offsets::COMPAT_RO_FLAGS,
flags & !FREE_SPACE_TREE_VALID,
);
}
stamp_checksum(raw, csum_type);
Ok(())
}
pub fn set_bytenr(raw: &mut [u8], offset: u64) {
put64(raw, offsets::BYTENR, offset);
}
pub fn stamp_checksum(raw: &mut [u8], csum_type: ChecksumType) {
let digest = csum_type.digest(&raw[CSUM_SIZE..SUPERBLOCK_SIZE]);
raw[..CSUM_SIZE].copy_from_slice(&digest);
}
pub const MAX_LABEL_BYTES: usize = 255;
pub fn set_label(dev: &dyn fs_core::BlockDevice, label: &str) -> Result<()> {
use crate::superblock::{offsets as o, Superblock};
if label.len() > MAX_LABEL_BYTES || label.contains('\0') {
return Err(Error::UnsupportedFeature(format!(
"a label is at most {MAX_LABEL_BYTES} bytes, with no NUL; this one is {}",
label.len()
)));
}
const COPIES: [u64; 3] = [64 << 10, 64 << 20, 256 << 30];
let mut primary = vec![0u8; SUPERBLOCK_SIZE];
dev.read_at(COPIES[0], &mut primary)?;
let sb = Superblock::parse(&primary)?;
if sb.num_devices > 1 {
return Err(Error::UnsupportedFeature(format!(
"this filesystem spans {} devices, and every one of them carries the label",
sb.num_devices
)));
}
let size = dev.size_bytes();
let mut copies = Vec::new();
for &at in &COPIES {
if at + SUPERBLOCK_SIZE as u64 > size {
continue;
}
let mut raw = vec![0u8; SUPERBLOCK_SIZE];
dev.read_at(at, &mut raw)?;
match Superblock::parse(&raw) {
Ok(copy) if copy.fsid == sb.fsid => copies.push((at, raw)),
_ => {}
}
}
for (at, raw) in copies.iter_mut().rev() {
raw[o::LABEL..o::LABEL + 256].fill(0);
raw[o::LABEL..o::LABEL + label.len()].copy_from_slice(label.as_bytes());
stamp_checksum(raw, sb.csum_type);
dev.write_at(*at, raw)?;
dev.flush()?;
}
Ok(())
}
fn put64(raw: &mut [u8], at: usize, value: u64) {
raw[at..at + 8].copy_from_slice(&value.to_le_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_backup_slot_is_the_generation_before_it_modulo_four() {
assert_eq!(backup_slot(1), 0);
assert_eq!(backup_slot(2), 1);
assert_eq!(backup_slot(3), 2);
assert_eq!(backup_slot(4), 3);
assert_eq!(backup_slot(5), 0, "and round again");
assert_eq!(backup_slot(7), 2);
assert_eq!(backup_slot(8), 3);
}
#[test]
fn the_ring_fits_in_the_superblock() {
assert_eq!(backup_slot_offset(1), offsets::ROOT_BACKUPS);
assert_eq!(
backup_slot_offset(2),
offsets::ROOT_BACKUPS + ROOT_BACKUP_SIZE
);
let last = backup_slot_offset(4) + ROOT_BACKUP_SIZE;
assert!(
last <= SUPERBLOCK_SIZE,
"the four slots end at {last}, past the superblock"
);
}
#[test]
fn a_short_buffer_is_refused() {
let mut small = vec![0u8; 512];
assert!(apply(&mut small, ChecksumType::Crc32c, &Commit::default()).is_err());
}
}