use crate::Result;
use crate::block::BlockDevice;
use super::Xfs;
use super::dir::{dahashname, encode_v5_block_dir, stamp_v5_dir_block_crc};
use super::inode::{V3DinodeBuilder, XfsTimestamp, stamp_v3_inode_crc};
use super::journal::{DEFAULT_LOG_BLOCKS, write_empty_log};
use super::superblock::Superblock;
#[derive(Debug, Clone, Default)]
pub struct FormatOpts {
pub uuid: [u8; 16],
pub label: [u8; 12],
pub mtime: u32,
}
pub const XFS_AGF_MAGIC: u32 = 0x5841_4746;
pub const XFS_AGI_MAGIC: u32 = 0x5841_4749;
pub const XFS_AGFL_MAGIC: u32 = 0x5841_464c;
pub const XFS_ABTB_CRC_MAGIC: u32 = 0x4142_3342;
pub const XFS_ABTC_CRC_MAGIC: u32 = 0x4142_3343;
pub const XFS_IBT_CRC_MAGIC: u32 = 0x4941_4233;
pub const XFS_AGF_VERSION: u32 = 1;
pub const XFS_AGI_VERSION: u32 = 1;
pub const XFS_BTREE_SBLOCK_V5_SIZE: usize = 56;
pub const XFS_AGFL_NSLOTS_512: usize = (512 - 36) / 4;
pub const XFS_INODES_PER_CHUNK: u32 = 64;
pub const XFS_BLOCKSIZE: u32 = 4096;
pub const XFS_SECTSIZE: u16 = 512;
pub const XFS_INODESIZE: u16 = 512;
pub const XFS_INOPBLOCK: u16 = (XFS_BLOCKSIZE / XFS_INODESIZE as u32) as u16;
pub const AG0_METADATA_BLOCKS: u32 = 7;
pub const ROOT_CHUNK_AGBLOCK: u32 = 8;
pub const ROOT_DIR_AGBLOCK: u32 = ROOT_CHUNK_AGBLOCK + 8;
pub const LOG_AGBLOCK: u32 = ROOT_DIR_AGBLOCK + 1;
pub const MULTI_AG_THRESHOLD_BYTES: u64 = 256 * 1024 * 1024;
pub const DEFAULT_AGBLOCKS_PER_AG: u32 = 65_536;
pub fn choose_agcount(dev_bytes: u64) -> u32 {
if dev_bytes <= MULTI_AG_THRESHOLD_BYTES {
return 1;
}
let dblocks = dev_bytes / (XFS_BLOCKSIZE as u64);
let count = dblocks / (DEFAULT_AGBLOCKS_PER_AG as u64);
count.clamp(1, u32::MAX as u64) as u32
}
pub fn format(dev: &mut dyn BlockDevice, opts: &FormatOpts) -> Result<Xfs> {
let dev_bytes = dev.total_size();
let blocksize = XFS_BLOCKSIZE as u64;
let log_blocks_u64 = DEFAULT_LOG_BLOCKS as u64;
let min_blocks = LOG_AGBLOCK as u64 + log_blocks_u64 + 1;
if dev_bytes < blocksize * min_blocks {
return Err(crate::Error::InvalidArgument(format!(
"xfs: device of {dev_bytes} bytes is too small to format a fresh XFS image",
)));
}
let total_blocks = (dev_bytes / blocksize) as u32;
let agcount = choose_agcount(dev_bytes);
let agblocks = if agcount == 1 {
total_blocks
} else {
total_blocks.div_ceil(agcount)
};
let agblklog = ceil_log2_u32(agblocks);
let dblocks = total_blocks as u64;
let metadata_end = (LOG_AGBLOCK as u64 + log_blocks_u64 + 1) * blocksize;
dev.zero_range(0, metadata_end.min(dev_bytes))?;
let rootino = (ROOT_CHUNK_AGBLOCK as u64) * (XFS_INOPBLOCK as u64);
let logstart_fsb = LOG_AGBLOCK as u64;
let logblocks = DEFAULT_LOG_BLOCKS;
let free_pool_ag0 = LOG_AGBLOCK + logblocks;
let free_pool_other = AG0_METADATA_BLOCKS;
let sb_buf = build_v5_superblock(
&opts.uuid,
&opts.label,
dblocks,
agcount,
agblocks,
agblklog,
rootino,
logstart_fsb,
logblocks,
opts.mtime,
);
let mut sb_buf = sb_buf;
stamp_v5_superblock_crc(&mut sb_buf);
dev.write_at(0, &sb_buf)?;
for ag in 0..agcount {
let ag_byte = (ag as u64) * (agblocks as u64) * blocksize;
let this_ag_blocks = if ag == agcount - 1 {
(total_blocks.saturating_sub(ag * agblocks)).max(1)
} else {
agblocks
};
let (post_chunk, allocated_inodes, free_inodes_in_ag) = if ag == 0 {
(free_pool_ag0, 3u32, 61u32)
} else {
(free_pool_other, 0u32, 0u32)
};
let free_blocks = this_ag_blocks.saturating_sub(post_chunk);
if ag != 0 {
let zero_end = ((AG0_METADATA_BLOCKS + 1) as u64) * blocksize;
dev.zero_range(ag_byte, zero_end.min(dev_bytes - ag_byte))?;
}
if ag != 0 {
let mut sb_copy = sb_buf.clone();
stamp_v5_superblock_crc(&mut sb_copy);
dev.write_at(ag_byte, &sb_copy)?;
}
let mut agf_buf = build_agf(
&opts.uuid,
ag,
this_ag_blocks,
free_blocks,
4,
5,
);
stamp_v5_agf_crc(&mut agf_buf);
dev.write_at(ag_byte + (XFS_SECTSIZE as u64), &agf_buf)?;
let mut agi_buf = build_agi(
&opts.uuid,
ag,
this_ag_blocks,
allocated_inodes,
free_inodes_in_ag,
6,
if ag == 0 {
ROOT_CHUNK_AGBLOCK << trailing_zeros_u16(XFS_INOPBLOCK)
} else {
0
},
ag == 0,
);
stamp_v5_agi_crc(&mut agi_buf);
dev.write_at(ag_byte + 2 * (XFS_SECTSIZE as u64), &agi_buf)?;
let mut agfl_buf = build_agfl(&opts.uuid, ag);
stamp_v5_agfl_crc(&mut agfl_buf);
dev.write_at(ag_byte + 3 * (XFS_SECTSIZE as u64), &agfl_buf)?;
let mut bno_buf = build_alloc_btree_root_leaf(
XFS_ABTB_CRC_MAGIC,
&opts.uuid,
ag,
agblocks,
4,
post_chunk,
free_blocks,
);
stamp_v5_btree_block_crc(&mut bno_buf);
dev.write_at(ag_byte + 4 * blocksize, &bno_buf)?;
let mut cnt_buf = build_alloc_btree_root_leaf(
XFS_ABTC_CRC_MAGIC,
&opts.uuid,
ag,
agblocks,
5,
post_chunk,
free_blocks,
);
stamp_v5_btree_block_crc(&mut cnt_buf);
dev.write_at(ag_byte + 5 * blocksize, &cnt_buf)?;
let inopblog = trailing_zeros_u16(XFS_INOPBLOCK);
let (startino_ag, freecount, ir_free, numrecs) = if ag == 0 {
let ir_free = !0b111u64;
(ROOT_CHUNK_AGBLOCK << inopblog, 61u32, ir_free, 1u16)
} else {
(0u32, 0u32, 0u64, 0u16)
};
let mut inobt_buf = build_inobt_root_leaf(
&opts.uuid,
ag,
agblocks,
6,
startino_ag,
freecount,
ir_free,
numrecs,
);
stamp_v5_btree_block_crc(&mut inobt_buf);
dev.write_at(ag_byte + 6 * blocksize, &inobt_buf)?;
}
let chunk_byte = (ROOT_CHUNK_AGBLOCK as u64) * blocksize;
let chunk_bytes = (XFS_INODES_PER_CHUNK as u64) * (XFS_INODESIZE as u64);
dev.zero_range(chunk_byte, chunk_bytes)?;
let log_byte_off = (LOG_AGBLOCK as u64) * blocksize;
let log_bytes = log_blocks_u64 * blocksize;
dev.zero_range(log_byte_off, log_bytes)?;
write_empty_log(dev, log_byte_off, log_bytes, &opts.uuid)?;
let dir_block_size = XFS_BLOCKSIZE as usize; let entries: Vec<(String, u64, u8)> = Vec::new(); let dir_block_basic_blkno = ((ROOT_DIR_AGBLOCK as u64) * blocksize) / 512;
let dir_block = encode_v5_block_dir(
dir_block_size,
rootino,
rootino, &entries,
&opts.uuid,
dir_block_basic_blkno,
)?;
let dir_block_byte = (ROOT_DIR_AGBLOCK as u64) * blocksize;
dev.write_at(dir_block_byte, &dir_block)?;
let ts = XfsTimestamp {
sec: opts.mtime,
nsec: 0,
};
let mut root_inode = V3DinodeBuilder {
inodesize: XFS_INODESIZE as usize,
mode: super::inode::S_IFDIR | 0o755,
format: 2,
uid: 0,
gid: 0,
nlink: 2,
atime: ts,
mtime: ts,
ctime: ts,
crtime: ts,
size: dir_block_size as u64,
nblocks: 1,
extsize: 0,
nextents: 1,
forkoff: 0,
aformat: 2, flags: 0,
generation: 1,
di_ino: rootino,
uuid: opts.uuid,
}
.build();
let ext = super::bmbt::Extent {
offset: 0,
startblock: ROOT_DIR_AGBLOCK as u64,
blockcount: 1,
unwritten: false,
};
root_inode[176..176 + 16].copy_from_slice(&ext.encode());
stamp_v3_inode_crc(&mut root_inode);
let root_byte = chunk_byte; dev.write_at(root_byte, &root_inode)?;
for (slot, ino_num) in [(1u32, rootino + 1), (2u32, rootino + 2)] {
let mut buf = V3DinodeBuilder {
inodesize: XFS_INODESIZE as usize,
mode: super::inode::S_IFREG | 0o600,
format: 2,
uid: 0,
gid: 0,
nlink: 1,
atime: ts,
mtime: ts,
ctime: ts,
crtime: ts,
size: 0,
nblocks: 0,
extsize: 0,
nextents: 0,
forkoff: 0,
aformat: 2,
flags: 0,
generation: 1,
di_ino: ino_num,
uuid: opts.uuid,
}
.build();
stamp_v3_inode_crc(&mut buf);
let byte = chunk_byte + (slot as u64) * (XFS_INODESIZE as u64);
dev.write_at(byte, &buf)?;
}
for slot in 3u32..64u32 {
let mut buf = vec![0u8; XFS_INODESIZE as usize];
buf[0..2].copy_from_slice(&super::inode::XFS_DINODE_MAGIC.to_be_bytes());
buf[4] = 3; buf[96..100].copy_from_slice(&u32::MAX.to_be_bytes());
let ino_num = rootino + (slot as u64);
buf[152..160].copy_from_slice(&ino_num.to_be_bytes());
buf[160..176].copy_from_slice(&opts.uuid);
stamp_v3_inode_crc(&mut buf);
let byte = chunk_byte + (slot as u64) * (XFS_INODESIZE as u64);
dev.write_at(byte, &buf)?;
}
let _ = dahashname; let _ = stamp_v5_dir_block_crc;
let xfs = Xfs::open(dev)?;
Ok(xfs)
}
#[allow(clippy::too_many_arguments)]
fn build_v5_superblock(
uuid: &[u8; 16],
label: &[u8; 12],
dblocks: u64,
agcount: u32,
agblocks: u32,
agblklog: u8,
rootino: u64,
logstart_fsb: u64,
logblocks: u32,
_mtime: u32,
) -> Vec<u8> {
let mut buf = vec![0u8; XFS_BLOCKSIZE as usize];
buf[0..4].copy_from_slice(&super::superblock::XFS_SB_MAGIC.to_be_bytes());
buf[4..8].copy_from_slice(&XFS_BLOCKSIZE.to_be_bytes());
buf[8..16].copy_from_slice(&dblocks.to_be_bytes());
buf[32..48].copy_from_slice(uuid);
buf[48..56].copy_from_slice(&logstart_fsb.to_be_bytes());
buf[56..64].copy_from_slice(&rootino.to_be_bytes());
buf[64..72].copy_from_slice(&(rootino + 1).to_be_bytes());
buf[72..80].copy_from_slice(&(rootino + 2).to_be_bytes());
buf[80..84].copy_from_slice(&1u32.to_be_bytes());
buf[84..88].copy_from_slice(&agblocks.to_be_bytes());
buf[88..92].copy_from_slice(&agcount.to_be_bytes());
buf[96..100].copy_from_slice(&logblocks.to_be_bytes());
buf[100..102].copy_from_slice(&0xb4a5u16.to_be_bytes());
buf[102..104].copy_from_slice(&XFS_SECTSIZE.to_be_bytes());
buf[104..106].copy_from_slice(&XFS_INODESIZE.to_be_bytes());
buf[106..108].copy_from_slice(&XFS_INOPBLOCK.to_be_bytes());
buf[108..120].copy_from_slice(label);
buf[120] = trailing_zeros_u32(XFS_BLOCKSIZE);
buf[121] = trailing_zeros_u32(XFS_SECTSIZE as u32);
buf[122] = trailing_zeros_u32(XFS_INODESIZE as u32);
buf[123] = trailing_zeros_u16(XFS_INOPBLOCK);
buf[124] = agblklog;
buf[127] = 25; buf[128..136].copy_from_slice(&64u64.to_be_bytes());
buf[136..144].copy_from_slice(&61u64.to_be_bytes());
let ag0_free = agblocks.saturating_sub(LOG_AGBLOCK + logblocks) as u64;
let other_free = agblocks.saturating_sub(AG0_METADATA_BLOCKS) as u64;
let total_free = ag0_free + other_free * ((agcount as u64).saturating_sub(1));
buf[144..152].copy_from_slice(&total_free.to_be_bytes());
buf[160..168].copy_from_slice(&0u64.to_be_bytes());
buf[168..176].copy_from_slice(&0u64.to_be_bytes());
buf[180..184].copy_from_slice(&4u32.to_be_bytes());
buf[192] = 0;
buf[200..204].copy_from_slice(&0x0000_018au32.to_be_bytes());
buf[204..208].copy_from_slice(&0x0000_018au32.to_be_bytes()); buf[212..216].copy_from_slice(&0u32.to_be_bytes());
buf[216..220].copy_from_slice(&0x0000_0001u32.to_be_bytes());
buf[232..240].copy_from_slice(&0u64.to_be_bytes());
buf[248..264].copy_from_slice(uuid);
buf
}
fn build_agf(
uuid: &[u8; 16],
ag: u32,
ag_length_blocks: u32,
free_blocks: u32,
bno_root: u32,
cnt_root: u32,
) -> Vec<u8> {
let mut buf = vec![0u8; XFS_SECTSIZE as usize];
buf[0..4].copy_from_slice(&XFS_AGF_MAGIC.to_be_bytes());
buf[4..8].copy_from_slice(&XFS_AGF_VERSION.to_be_bytes());
buf[8..12].copy_from_slice(&ag.to_be_bytes()); buf[12..16].copy_from_slice(&ag_length_blocks.to_be_bytes()); buf[16..20].copy_from_slice(&bno_root.to_be_bytes());
buf[20..24].copy_from_slice(&cnt_root.to_be_bytes());
buf[24..28].copy_from_slice(&0u32.to_be_bytes()); buf[28..32].copy_from_slice(&1u32.to_be_bytes());
buf[32..36].copy_from_slice(&1u32.to_be_bytes());
buf[36..40].copy_from_slice(&0u32.to_be_bytes()); buf[40..44].copy_from_slice(&0u32.to_be_bytes()); buf[44..48].copy_from_slice(&0u32.to_be_bytes()); buf[48..52].copy_from_slice(&0u32.to_be_bytes()); buf[52..56].copy_from_slice(&free_blocks.to_be_bytes()); buf[56..60].copy_from_slice(&free_blocks.to_be_bytes()); buf[60..64].copy_from_slice(&0u32.to_be_bytes()); buf[64..80].copy_from_slice(uuid);
buf
}
pub const AGF_CRC_OFFSET: usize = 216;
#[allow(clippy::too_many_arguments)]
fn build_agi(
uuid: &[u8; 16],
ag: u32,
ag_length_blocks: u32,
allocated_inodes: u32,
free_inodes: u32,
inobt_root: u32,
newino_hint: u32,
has_chunks: bool,
) -> Vec<u8> {
let mut buf = vec![0u8; XFS_SECTSIZE as usize];
buf[0..4].copy_from_slice(&XFS_AGI_MAGIC.to_be_bytes());
buf[4..8].copy_from_slice(&XFS_AGI_VERSION.to_be_bytes());
buf[8..12].copy_from_slice(&ag.to_be_bytes()); buf[12..16].copy_from_slice(&ag_length_blocks.to_be_bytes()); buf[16..20].copy_from_slice(&(allocated_inodes + free_inodes).to_be_bytes()); buf[20..24].copy_from_slice(&inobt_root.to_be_bytes()); let _ = has_chunks;
buf[24..28].copy_from_slice(&1u32.to_be_bytes()); buf[28..32].copy_from_slice(&free_inodes.to_be_bytes()); if has_chunks {
buf[32..36].copy_from_slice(&newino_hint.to_be_bytes());
} else {
buf[32..36].copy_from_slice(&u32::MAX.to_be_bytes());
}
buf[36..40].copy_from_slice(&u32::MAX.to_be_bytes());
for i in 0..64 {
let off = 40 + i * 4;
buf[off..off + 4].copy_from_slice(&u32::MAX.to_be_bytes());
}
buf[296..312].copy_from_slice(uuid);
buf[328..332].copy_from_slice(&0u32.to_be_bytes());
buf[332..336].copy_from_slice(&0u32.to_be_bytes()); buf
}
pub const AGI_CRC_OFFSET: usize = 312;
fn build_agfl(uuid: &[u8; 16], ag: u32) -> Vec<u8> {
let mut buf = vec![0u8; XFS_SECTSIZE as usize];
buf[0..4].copy_from_slice(&XFS_AGFL_MAGIC.to_be_bytes());
buf[4..8].copy_from_slice(&ag.to_be_bytes()); buf[8..24].copy_from_slice(uuid);
for i in 0..XFS_AGFL_NSLOTS_512 {
let off = 36 + i * 4;
buf[off..off + 4].copy_from_slice(&u32::MAX.to_be_bytes());
}
buf
}
pub const AGFL_CRC_OFFSET: usize = 32;
#[allow(clippy::too_many_arguments)]
fn build_alloc_btree_root_leaf(
magic: u32,
uuid: &[u8; 16],
owner_ag: u32,
agblocks: u32,
blkno_ag: u32,
free_startblock: u32,
free_blockcount: u32,
) -> Vec<u8> {
let mut buf = vec![0u8; XFS_BLOCKSIZE as usize];
buf[0..4].copy_from_slice(&magic.to_be_bytes());
buf[4..6].copy_from_slice(&0u16.to_be_bytes()); buf[6..8].copy_from_slice(&1u16.to_be_bytes()); buf[8..12].copy_from_slice(&u32::MAX.to_be_bytes()); buf[12..16].copy_from_slice(&u32::MAX.to_be_bytes()); let basic_blkno =
((owner_ag as u64) * (agblocks as u64) + blkno_ag as u64) * (XFS_BLOCKSIZE as u64 / 512);
buf[16..24].copy_from_slice(&basic_blkno.to_be_bytes());
buf[32..48].copy_from_slice(uuid);
buf[48..52].copy_from_slice(&owner_ag.to_be_bytes());
let rec_off = XFS_BTREE_SBLOCK_V5_SIZE;
buf[rec_off..rec_off + 4].copy_from_slice(&free_startblock.to_be_bytes());
buf[rec_off + 4..rec_off + 8].copy_from_slice(&free_blockcount.to_be_bytes());
buf
}
#[allow(clippy::too_many_arguments)]
fn build_inobt_root_leaf(
uuid: &[u8; 16],
owner_ag: u32,
agblocks: u32,
blkno_ag: u32,
startino_ag: u32,
freecount: u32,
ir_free: u64,
numrecs: u16,
) -> Vec<u8> {
let mut buf = vec![0u8; XFS_BLOCKSIZE as usize];
buf[0..4].copy_from_slice(&XFS_IBT_CRC_MAGIC.to_be_bytes());
buf[4..6].copy_from_slice(&0u16.to_be_bytes()); buf[6..8].copy_from_slice(&numrecs.to_be_bytes());
buf[8..12].copy_from_slice(&u32::MAX.to_be_bytes());
buf[12..16].copy_from_slice(&u32::MAX.to_be_bytes());
let basic_blkno =
((owner_ag as u64) * (agblocks as u64) + blkno_ag as u64) * (XFS_BLOCKSIZE as u64 / 512);
buf[16..24].copy_from_slice(&basic_blkno.to_be_bytes());
buf[32..48].copy_from_slice(uuid);
buf[48..52].copy_from_slice(&owner_ag.to_be_bytes());
if numrecs > 0 {
let rec_off = XFS_BTREE_SBLOCK_V5_SIZE;
buf[rec_off..rec_off + 4].copy_from_slice(&startino_ag.to_be_bytes());
buf[rec_off + 4..rec_off + 8].copy_from_slice(&freecount.to_be_bytes());
buf[rec_off + 8..rec_off + 16].copy_from_slice(&ir_free.to_be_bytes());
}
buf
}
pub const SB_CRC_OFFSET: usize = 224;
pub fn stamp_v5_superblock_crc(buf: &mut [u8]) {
buf[SB_CRC_OFFSET..SB_CRC_OFFSET + 4].copy_from_slice(&[0u8; 4]);
let crc = crc32c::crc32c(&buf[..XFS_SECTSIZE as usize]);
buf[SB_CRC_OFFSET..SB_CRC_OFFSET + 4].copy_from_slice(&crc.to_le_bytes());
}
pub fn stamp_v5_agf_crc(buf: &mut [u8]) {
buf[AGF_CRC_OFFSET..AGF_CRC_OFFSET + 4].copy_from_slice(&[0u8; 4]);
let crc = crc32c::crc32c(&buf[..XFS_SECTSIZE as usize]);
buf[AGF_CRC_OFFSET..AGF_CRC_OFFSET + 4].copy_from_slice(&crc.to_le_bytes());
}
pub fn stamp_v5_agi_crc(buf: &mut [u8]) {
buf[AGI_CRC_OFFSET..AGI_CRC_OFFSET + 4].copy_from_slice(&[0u8; 4]);
let crc = crc32c::crc32c(&buf[..XFS_SECTSIZE as usize]);
buf[AGI_CRC_OFFSET..AGI_CRC_OFFSET + 4].copy_from_slice(&crc.to_le_bytes());
}
pub fn stamp_v5_agfl_crc(buf: &mut [u8]) {
buf[AGFL_CRC_OFFSET..AGFL_CRC_OFFSET + 4].copy_from_slice(&[0u8; 4]);
let crc = crc32c::crc32c(&buf[..XFS_SECTSIZE as usize]);
buf[AGFL_CRC_OFFSET..AGFL_CRC_OFFSET + 4].copy_from_slice(&crc.to_le_bytes());
}
pub const BTREE_SBLOCK_CRC_OFFSET: usize = 52;
pub fn stamp_v5_btree_block_crc(buf: &mut [u8]) {
buf[BTREE_SBLOCK_CRC_OFFSET..BTREE_SBLOCK_CRC_OFFSET + 4].copy_from_slice(&[0u8; 4]);
let crc = crc32c::crc32c(buf);
buf[BTREE_SBLOCK_CRC_OFFSET..BTREE_SBLOCK_CRC_OFFSET + 4].copy_from_slice(&crc.to_le_bytes());
}
fn ceil_log2_u32(n: u32) -> u8 {
if n <= 1 {
return 0;
}
let mut l = 0u8;
let mut x: u64 = 1;
while x < n as u64 {
x <<= 1;
l += 1;
}
l
}
fn trailing_zeros_u32(n: u32) -> u8 {
n.trailing_zeros() as u8
}
fn trailing_zeros_u16(n: u16) -> u8 {
n.trailing_zeros() as u8
}
#[allow(dead_code)]
pub(crate) fn sb_of(xfs: &Xfs) -> &Superblock {
xfs.superblock()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::MemoryBackend;
#[test]
fn format_writes_valid_superblock() {
let mut dev = MemoryBackend::new(64 * 1024 * 1024);
let opts = FormatOpts::default();
let xfs = format(&mut dev, &opts).unwrap();
assert_eq!(xfs.block_size(), 4096);
assert_eq!(xfs.inode_size(), 512);
assert_eq!(xfs.ag_count(), 1);
let mut dev2 = dev; assert!(super::super::probe(&mut dev2).unwrap());
}
#[test]
fn format_root_inode_is_readable() {
let mut dev = MemoryBackend::new(64 * 1024 * 1024);
let opts = FormatOpts::default();
let xfs = format(&mut dev, &opts).unwrap();
let entries = xfs.list_path(&mut dev, "/").unwrap();
assert_eq!(entries.len(), 2);
let names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
assert!(names.contains(&"."));
assert!(names.contains(&".."));
}
#[test]
fn ceil_log2_examples() {
assert_eq!(ceil_log2_u32(1), 0);
assert_eq!(ceil_log2_u32(2), 1);
assert_eq!(ceil_log2_u32(3), 2);
assert_eq!(ceil_log2_u32(4), 2);
assert_eq!(ceil_log2_u32(5), 3);
assert_eq!(ceil_log2_u32(2048), 11);
}
#[test]
fn choose_agcount_thresholds() {
assert_eq!(choose_agcount(0), 1);
assert_eq!(choose_agcount(64 * 1024 * 1024), 1);
assert_eq!(choose_agcount(256 * 1024 * 1024), 1);
assert_eq!(choose_agcount(512 * 1024 * 1024), 2);
assert_eq!(choose_agcount(1024 * 1024 * 1024), 4);
}
#[test]
fn format_writes_journal_stub() {
let mut dev = MemoryBackend::new(64 * 1024 * 1024);
let opts = FormatOpts::default();
let xfs = format(&mut dev, &opts).unwrap();
let mut sb = [0u8; 512];
dev.read_at(0, &mut sb).unwrap();
let logstart = u64::from_be_bytes(sb[48..56].try_into().unwrap());
let logblocks = u32::from_be_bytes(sb[96..100].try_into().unwrap());
assert!(logstart > 0);
assert!(logblocks >= super::super::journal::DEFAULT_LOG_BLOCKS);
let log_byte = logstart * 4096;
let mut hdr = [0u8; 4];
dev.read_at(log_byte, &mut hdr).unwrap();
assert_eq!(u32::from_be_bytes(hdr), 0xFEED_BABE);
let _ = xfs;
}
}