use crate::config::DEFAULT_ROOT_SIZE;
const DEFAULT_SS_MAGIC: u32 = 0x48465353;
const DEFAULT_INODE_BMAP_SIZE: usize = DEFAULT_ROOT_SIZE;
pub type BMapRawType = [u8; DEFAULT_INODE_BMAP_SIZE];
#[derive(Debug, Clone, Copy)]
#[repr(C, align(8))]
pub struct InodeRaw {
pub i_ino: u64,
pub i_atime: u64,
pub i_ctime: u64,
pub i_mtime: u64,
pub i_atime_nsec: u32,
pub i_ctime_nsec: u32,
pub i_mtime_nsec: u32,
pub i_meta_config: u32,
pub i_uid: u32,
pub i_gid: u32,
pub i_mode: u32,
pub i_flags: u32,
pub i_nlink: u64,
pub i_size: u64,
pub i_blocks: u64,
pub i_last_seq: u64,
pub i_last_cno: u64,
pub i_bmap: BMapRawType,
}
impl Default for InodeRaw {
fn default() -> Self {
Self {
i_ino: 0,
i_atime: 0,
i_ctime: 0,
i_mtime: 0,
i_atime_nsec: 0,
i_ctime_nsec: 0,
i_mtime_nsec: 0,
i_meta_config: 0,
i_uid: 0,
i_gid: 0,
i_mode: 0,
i_flags: 0,
i_nlink: 0,
i_size: 0,
i_blocks: 0,
i_last_seq: 0,
i_last_cno: 0,
i_bmap: [0u8; DEFAULT_INODE_BMAP_SIZE],
}
}
}
impl InodeRaw {
pub fn as_mut_u8_slice(&mut self) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(
(self as *mut Self) as *mut u8,
std::mem::size_of::<Self>()
)
}
}
pub fn as_u8_slice(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(
(self as *const Self) as *const u8,
std::mem::size_of::<Self>()
)
}
}
pub fn from_u8_slice(buf: &[u8]) -> Self {
let mut raw: InodeRaw = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
raw.as_mut_u8_slice().copy_from_slice(buf);
raw
}
pub const FLAG_INLINE: u32 = 0x1;
pub const fn inline_offset() -> usize { std::mem::offset_of!(InodeRaw, i_blocks) }
pub const fn inline_cap() -> usize { std::mem::size_of::<InodeRaw>() - Self::inline_offset() }
pub fn is_inline(&self) -> bool { self.i_flags & Self::FLAG_INLINE != 0 }
pub fn inline_data(&self) -> &[u8] {
if !self.is_inline() { return &[]; }
let off = Self::inline_offset();
let n = (self.i_size as usize).min(Self::inline_cap());
&self.as_u8_slice()[off..off + n]
}
pub fn set_inline(&mut self, data: &[u8]) {
debug_assert!(data.len() <= Self::inline_cap());
let off = Self::inline_offset();
self.i_flags |= Self::FLAG_INLINE;
self.i_blocks = 0;
self.i_last_seq = 0;
self.i_last_cno = 0;
self.i_size = data.len() as u64;
let buf = self.as_mut_u8_slice();
buf[off..].fill(0);
buf[off..off + data.len()].copy_from_slice(data);
}
pub fn clear_inline(&mut self) { self.i_flags &= !Self::FLAG_INLINE; }
}
#[derive(Debug, Clone, Copy)]
#[repr(C, align(8))]
pub(crate) struct SegmentBlockEntryRaw {
pub(crate) e_blkidx: u64,
pub(crate) e_blkptr: u64,
}
impl SegmentBlockEntryRaw {
pub(crate) fn new() -> Self {
Self { e_blkidx: 0, e_blkptr: 0 }
}
pub(crate) fn write_to(&self, buf: &mut [u8]) {
const SIZE: usize = std::mem::size_of::<SegmentBlockEntryRaw>();
assert!(buf.len() >= SIZE,
"SegmentBlockEntryRaw::write_to buf len {} < {}", buf.len(), SIZE);
buf[0..8].copy_from_slice(&self.e_blkidx.to_ne_bytes());
buf[8..16].copy_from_slice(&self.e_blkptr.to_ne_bytes());
}
pub(crate) fn read_from(buf: &[u8]) -> Self {
const SIZE: usize = std::mem::size_of::<SegmentBlockEntryRaw>();
assert!(buf.len() >= SIZE,
"SegmentBlockEntryRaw::read_from buf len {} < {}", buf.len(), SIZE);
let mut idx_bytes = [0u8; 8];
let mut ptr_bytes = [0u8; 8];
idx_bytes.copy_from_slice(&buf[0..8]);
ptr_bytes.copy_from_slice(&buf[8..16]);
Self {
e_blkidx: u64::from_ne_bytes(idx_bytes),
e_blkptr: u64::from_ne_bytes(ptr_bytes),
}
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C, align(8))]
pub struct SegmentHeader {
pub s_magic: u32, pub s_chksum: u32,
pub s_bytes: u32, pub s_flags: u16, pub s_meta_blk_shift: u8, pub s_data_blk_shift: u8,
pub s_next: u64,
pub s_inode: InodeRaw,
pub s_ino: u64, pub s_cno: u64, pub s_nmetablk: u32, pub s_ndatablk: u32, s_blocks: [SegmentBlockEntryRaw; 0], }
impl SegmentHeader {
pub fn new() -> Self {
Self {
s_magic: DEFAULT_SS_MAGIC,
s_chksum: 0,
s_bytes: 0,
s_flags: 0,
s_meta_blk_shift: 0,
s_data_blk_shift: 0,
s_next: 0,
s_inode: unsafe { std::mem::MaybeUninit::zeroed().assume_init() },
s_ino: 0,
s_cno: 0,
s_nmetablk: 0,
s_ndatablk: 0,
s_blocks: [SegmentBlockEntryRaw::new(); 0],
}
}
pub fn from_slice(buf: &[u8]) -> Self {
let hdrsz = std::mem::size_of::<Self>();
let bufsz = buf.len();
if bufsz != hdrsz {
panic!("failed to segment header from buf, buf len {} != hdr size {}", bufsz, hdrsz);
}
Self::read_from(buf)
}
#[inline]
pub fn size() -> usize {
std::mem::size_of::<Self>()
}
#[inline]
pub fn aligned_ss_bytes(&self) -> usize {
(self.s_bytes as usize + 4096 - 1) >> 12 << 12
}
pub fn write_to(&self, buf: &mut [u8]) {
let sz = Self::size();
assert!(buf.len() >= sz,
"SegmentHeader::write_to buf len {} < {}", buf.len(), sz);
let src = unsafe {
std::slice::from_raw_parts(
std::ptr::addr_of!(*self) as *const u8,
sz,
)
};
buf[..sz].copy_from_slice(src);
}
pub fn read_from(buf: &[u8]) -> Self {
let sz = Self::size();
assert!(buf.len() >= sz,
"SegmentHeader::read_from buf len {} < {}", buf.len(), sz);
let mut hdr: SegmentHeader = unsafe {
std::mem::MaybeUninit::zeroed().assume_init()
};
let dst = unsafe {
std::slice::from_raw_parts_mut(
std::ptr::addr_of_mut!(hdr) as *mut u8,
sz,
)
};
dst.copy_from_slice(&buf[..sz]);
hdr
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inode_raw_size() {
assert_eq!(std::mem::size_of::<InodeRaw>(), 160);
}
#[test]
fn inode_inline_region() {
assert_eq!(InodeRaw::inline_offset(), 80);
assert_eq!(InodeRaw::inline_cap(), 80);
let mut raw = InodeRaw::default();
assert!(!raw.is_inline());
assert_eq!(raw.inline_data(), b"");
let data = b"hello inline world";
raw.set_inline(data);
assert!(raw.is_inline());
assert_eq!(raw.i_size as usize, data.len());
assert_eq!(raw.inline_data(), data);
let raw2 = InodeRaw::from_u8_slice(raw.as_u8_slice());
assert!(raw2.is_inline());
assert_eq!(raw2.inline_data(), data);
let full = vec![0xABu8; InodeRaw::inline_cap()];
raw.set_inline(&full);
assert_eq!(raw.inline_data(), &full[..]);
raw.clear_inline();
assert!(!raw.is_inline());
}
#[test]
fn inode_raw_round_trip_via_slice() {
let mut raw = InodeRaw::default();
raw.i_ino = 42;
raw.i_size = 1024 * 1024;
raw.i_blocks = 2048;
raw.i_uid = 1000;
raw.i_gid = 1000;
raw.i_mode = 0o100644;
raw.i_last_seq = 99;
raw.i_last_cno = 99;
raw.i_bmap[0] = 0xAB;
raw.i_bmap[55] = 0xCD;
let bytes = raw.as_u8_slice().to_vec();
let restored = InodeRaw::from_u8_slice(&bytes);
assert_eq!(restored.i_ino, 42);
assert_eq!(restored.i_size, 1024 * 1024);
assert_eq!(restored.i_blocks, 2048);
assert_eq!(restored.i_uid, 1000);
assert_eq!(restored.i_gid, 1000);
assert_eq!(restored.i_mode, 0o100644);
assert_eq!(restored.i_last_seq, 99);
assert_eq!(restored.i_last_cno, 99);
assert_eq!(restored.i_bmap[0], 0xAB);
assert_eq!(restored.i_bmap[55], 0xCD);
}
#[test]
fn inode_raw_as_mut_u8_slice() {
let mut raw = InodeRaw::default();
let slice = raw.as_mut_u8_slice();
assert_eq!(slice.len(), std::mem::size_of::<InodeRaw>());
slice[0] = 0xFF;
assert_eq!(raw.as_u8_slice()[0], 0xFF);
}
#[test]
fn inode_raw_default_is_zeroed() {
let raw = InodeRaw::default();
assert!(raw.as_u8_slice().iter().all(|&b| b == 0));
}
#[test]
fn segment_header_new_has_magic() {
let hdr = SegmentHeader::new();
assert_eq!(hdr.s_magic, 0x48465353); }
#[test]
fn segment_header_aligned_ss_bytes() {
let mut hdr = SegmentHeader::new();
hdr.s_bytes = 100; assert_eq!(hdr.aligned_ss_bytes(), 4096);
hdr.s_bytes = 4096;
assert_eq!(hdr.aligned_ss_bytes(), 4096);
hdr.s_bytes = 4097;
assert_eq!(hdr.aligned_ss_bytes(), 8192);
}
#[test]
fn segment_block_entry_size() {
assert_eq!(std::mem::size_of::<SegmentBlockEntryRaw>(), 16);
}
#[test]
fn segment_block_entry_round_trip() {
let entry = SegmentBlockEntryRaw { e_blkidx: 0xDEAD_BEEF_CAFE_BABE, e_blkptr: 0x1234_5678_9ABC_DEF0 };
let mut buf = [0u8; 16];
entry.write_to(&mut buf);
let decoded = SegmentBlockEntryRaw::read_from(&buf);
assert_eq!(decoded.e_blkidx, entry.e_blkidx);
assert_eq!(decoded.e_blkptr, entry.e_blkptr);
}
#[test]
fn segment_block_entry_round_trip_zero() {
let entry = SegmentBlockEntryRaw::new();
let mut buf = [0u8; 16];
entry.write_to(&mut buf);
let decoded = SegmentBlockEntryRaw::read_from(&buf);
assert_eq!(decoded.e_blkidx, 0);
assert_eq!(decoded.e_blkptr, 0);
}
#[test]
fn segment_block_entry_write_accepts_oversized_buf() {
let entry = SegmentBlockEntryRaw { e_blkidx: 7, e_blkptr: 42 };
let mut buf = [0u8; 32]; entry.write_to(&mut buf);
assert_eq!(&buf[16..], &[0u8; 16]); }
#[test]
#[should_panic(expected = "SegmentBlockEntryRaw::write_to buf len")]
fn segment_block_entry_write_panics_on_short_buf() {
let entry = SegmentBlockEntryRaw::new();
let mut buf = [0u8; 10];
entry.write_to(&mut buf);
}
#[test]
#[should_panic(expected = "SegmentBlockEntryRaw::read_from buf len")]
fn segment_block_entry_read_panics_on_short_buf() {
SegmentBlockEntryRaw::read_from(&[0u8; 10]);
}
#[test]
fn segment_header_write_read_round_trip() {
let mut hdr = SegmentHeader::new();
hdr.s_cno = 42;
hdr.s_ino = 7;
hdr.s_nmetablk = 3;
hdr.s_ndatablk = 5;
hdr.s_meta_blk_shift = 12;
hdr.s_data_blk_shift = 12;
hdr.s_next = 100;
hdr.s_bytes = 4096;
hdr.s_chksum = 0xABCDu32;
hdr.s_flags = 0x42;
hdr.s_inode.i_ino = 77;
hdr.s_inode.i_size = 8192;
let mut buf = vec![0u8; SegmentHeader::size()];
hdr.write_to(&mut buf);
let decoded = SegmentHeader::read_from(&buf);
assert_eq!(decoded.s_cno, 42);
assert_eq!(decoded.s_ino, 7);
assert_eq!(decoded.s_nmetablk, 3);
assert_eq!(decoded.s_ndatablk, 5);
assert_eq!(decoded.s_next, 100);
assert_eq!(decoded.s_bytes, 4096);
assert_eq!(decoded.s_chksum, 0xABCDu32);
assert_eq!(decoded.s_flags, 0x42);
assert_eq!(decoded.s_magic, hdr.s_magic);
assert_eq!(decoded.s_inode.i_ino, 77);
assert_eq!(decoded.s_inode.i_size, 8192);
}
#[test]
fn segment_header_read_accepts_oversized_buf() {
let hdr = SegmentHeader::new();
let mut buf = vec![0u8; SegmentHeader::size() * 2];
hdr.write_to(&mut buf);
let decoded = SegmentHeader::read_from(&buf);
assert_eq!(decoded.s_magic, hdr.s_magic);
}
#[test]
#[should_panic(expected = "SegmentHeader::write_to buf len")]
fn segment_header_write_panics_on_short_buf() {
let hdr = SegmentHeader::new();
let mut buf = vec![0u8; SegmentHeader::size() - 1];
hdr.write_to(&mut buf);
}
#[test]
#[should_panic(expected = "SegmentHeader::read_from buf len")]
fn segment_header_read_panics_on_short_buf() {
let buf = vec![0u8; 10];
SegmentHeader::read_from(&buf);
}
#[test]
fn segment_header_size_is_208_no_padding() {
let fields_sum: usize = 4 + 4 + 4 + 2 + 1 + 1 + 8 + 160 + 8 + 8 + 4 + 4;
assert_eq!(
std::mem::size_of::<SegmentHeader>(),
fields_sum,
"SegmentHeader has padding — on-disk format may be unstable"
);
assert_eq!(std::mem::size_of::<SegmentHeader>(), 208);
assert_eq!(std::mem::align_of::<SegmentHeader>(), 8);
}
#[test]
fn segment_block_entry_byte_layout() {
let entry = SegmentBlockEntryRaw {
e_blkidx: 0x0102_0304_0506_0708,
e_blkptr: 0x1122_3344_5566_7788,
};
let mut buf = [0u8; 16];
entry.write_to(&mut buf);
assert_eq!(&buf[0..8], &0x0102_0304_0506_0708u64.to_ne_bytes());
assert_eq!(&buf[8..16], &0x1122_3344_5566_7788u64.to_ne_bytes());
}
#[test]
fn segment_block_entry_preserves_high_bits() {
let entry = SegmentBlockEntryRaw {
e_blkidx: u64::MAX,
e_blkptr: 0xC000_0000_1234_5678, };
let mut buf = [0u8; 16];
entry.write_to(&mut buf);
let decoded = SegmentBlockEntryRaw::read_from(&buf);
assert_eq!(decoded.e_blkidx, u64::MAX);
assert_eq!(decoded.e_blkptr, 0xC000_0000_1234_5678);
}
#[test]
fn segment_header_byte_layout_key_offsets() {
let mut hdr = SegmentHeader::new();
hdr.s_magic = 0x4847_4647; hdr.s_chksum = 0xDEAD_BEEF;
hdr.s_bytes = 0x1234_5678;
hdr.s_flags = 0xABCD;
hdr.s_meta_blk_shift = 0x13;
hdr.s_data_blk_shift = 0x14;
hdr.s_next = 0x7777_7777_7777_7777;
let mut buf = vec![0u8; SegmentHeader::size()];
hdr.write_to(&mut buf);
assert_eq!(&buf[0..4], &0x4847_4647u32.to_ne_bytes(), "s_magic");
assert_eq!(&buf[4..8], &0xDEAD_BEEFu32.to_ne_bytes(), "s_chksum");
assert_eq!(&buf[8..12], &0x1234_5678u32.to_ne_bytes(), "s_bytes");
assert_eq!(&buf[12..14], &0xABCDu16.to_ne_bytes(), "s_flags");
assert_eq!(buf[14], 0x13, "s_meta_blk_shift");
assert_eq!(buf[15], 0x14, "s_data_blk_shift");
assert_eq!(&buf[16..24], &0x7777_7777_7777_7777u64.to_ne_bytes(), "s_next");
}
#[test]
fn segment_header_inode_fields_round_trip() {
let mut hdr = SegmentHeader::new();
hdr.s_inode.i_ino = 99;
hdr.s_inode.i_size = 1_048_576;
hdr.s_inode.i_blocks = 2048;
hdr.s_inode.i_uid = 1001;
hdr.s_inode.i_gid = 1002;
hdr.s_inode.i_mode = libc::S_IFREG | 0o644;
hdr.s_inode.i_nlink = 3;
hdr.s_inode.i_last_seq = 42;
hdr.s_inode.i_last_cno = 42;
hdr.s_inode.i_bmap[0] = 0xAA;
hdr.s_inode.i_bmap[55] = 0xBB;
let mut buf = vec![0u8; SegmentHeader::size()];
hdr.write_to(&mut buf);
let decoded = SegmentHeader::read_from(&buf);
assert_eq!(decoded.s_inode.i_ino, 99);
assert_eq!(decoded.s_inode.i_size, 1_048_576);
assert_eq!(decoded.s_inode.i_blocks, 2048);
assert_eq!(decoded.s_inode.i_uid, 1001);
assert_eq!(decoded.s_inode.i_gid, 1002);
assert_eq!(decoded.s_inode.i_mode, libc::S_IFREG | 0o644);
assert_eq!(decoded.s_inode.i_nlink, 3);
assert_eq!(decoded.s_inode.i_last_seq, 42);
assert_eq!(decoded.s_inode.i_last_cno, 42);
assert_eq!(decoded.s_inode.i_bmap[0], 0xAA);
assert_eq!(decoded.s_inode.i_bmap[55], 0xBB);
}
#[test]
fn segment_header_write_is_idempotent() {
let mut hdr = SegmentHeader::new();
hdr.s_cno = 7;
hdr.s_ino = 3;
hdr.s_bytes = 4096;
hdr.s_inode.i_size = 1234;
let mut buf1 = vec![0u8; SegmentHeader::size()];
let mut buf2 = vec![0u8; SegmentHeader::size()];
hdr.write_to(&mut buf1);
hdr.write_to(&mut buf2);
assert_eq!(buf1, buf2, "write_to is not byte-identical on repeat");
}
}