use std::io::{self, Write};
use std::os::unix::fs::FileExt;
use std::path::Path;
use zerocopy::{AsBytes, FromBytes, FromZeroes};
pub const SUPERBLOCK_MAGIC: u32 = 0x53555052;
pub const SUPERBLOCK_VERSION: u32 = 1;
const SUPERBLOCK_SIZE: usize = std::mem::size_of::<Superblock>();
pub const FREELIST_SNAPSHOT_MAGIC: u32 = 0x314C5346;
pub const FREELIST_SNAPSHOT_VERSION: u16 = 1;
pub const FREELIST_SNAPSHOT_HEADER_SIZE: usize = std::mem::size_of::<FreeListSnaphotHeader>();
pub fn write_freepages_snapshot(
path: &Path,
version: u16,
next_pid: u64,
ids: &[u64],
) -> Result<(), std::io::Error> {
let mut f = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let hdr = FreeListSnaphotHeader {
magic: FREELIST_SNAPSHOT_MAGIC,
version,
_pad: 0,
next_page_id: next_pid,
count: ids.len() as u32,
_pad2: 0,
};
f.write_all(hdr.as_bytes())?;
for &pid in ids {
f.write_all(&pid.to_le_bytes())?;
}
Ok(())
}
pub fn read_freepages_snapshot(
path: &Path,
offset: u64,
) -> Result<(u64, Vec<u64>), std::io::Error> {
let f = std::fs::OpenOptions::new().read(true).open(path)?;
let mut buf = [0u8; FREELIST_SNAPSHOT_HEADER_SIZE];
f.read_exact_at(&mut buf, offset)?;
let hdr = FreeListSnaphotHeader::from_bytes(&buf)?;
hdr.validate()?;
let mut ids = vec![0u64; hdr.count as usize];
let mut pos = offset + FREELIST_SNAPSHOT_HEADER_SIZE as u64;
for slot in &mut ids {
let mut b = [0u8; 8];
f.read_exact_at(&mut b, pos)?;
pos += 8;
*slot = u64::from_le_bytes(b);
}
Ok((hdr.next_page_id, ids))
}
#[repr(C)]
#[derive(AsBytes, FromBytes, FromZeroes, Debug, Clone, Copy)]
pub struct Superblock {
pub magic: u32,
pub version: u32,
pub gen_id: u64,
pub page_size: u64,
pub next_page_id: u64,
pub freelist_head: u64,
pub crc32c: u32,
pub _pad: u32,
}
#[repr(C)]
#[derive(AsBytes, FromBytes, FromZeroes, Debug, Clone, Copy)]
pub struct FreeListSnaphotHeader {
pub magic: u32,
pub version: u16,
pub _pad: u16,
pub next_page_id: u64,
pub count: u32,
pub _pad2: u32,
}
const CRC_OFFSET: usize = 40;
impl Superblock {
pub fn from_bytes(buf: &[u8; SUPERBLOCK_SIZE]) -> Result<&Self, std::io::Error> {
Superblock::ref_from(buf).ok_or(io::Error::new(
io::ErrorKind::InvalidData,
"Failed to decode Superblock",
))
}
pub fn compute_crc(&self) -> u32 {
let bytes = self.as_bytes();
crc32fast::hash(&bytes[..CRC_OFFSET])
}
pub fn with_crc(mut self) -> Self {
self.crc32c = self.compute_crc();
self
}
pub fn validate(&self) -> Result<(), std::io::Error> {
if self.magic != SUPERBLOCK_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid Superblock magic",
));
}
if self.version != SUPERBLOCK_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Unsupported manifest version",
));
}
let expected = self.compute_crc();
if self.crc32c != expected {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Superblock CRC mismatch: stored {:#010x}, computed {:#010x}",
self.crc32c, expected
),
));
}
Ok(())
}
}
impl FreeListSnaphotHeader {
pub fn from_bytes(buf: &[u8; FREELIST_SNAPSHOT_HEADER_SIZE]) -> Result<&Self, std::io::Error> {
FreeListSnaphotHeader::ref_from(buf).ok_or(io::Error::new(
io::ErrorKind::InvalidData,
"Failed to decode FreeListSnaphotHeader",
))
}
pub fn validate(&self) -> Result<(), std::io::Error> {
if self.magic != FREELIST_SNAPSHOT_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Invalid Snapshot header magic",
));
}
if self.version != FREELIST_SNAPSHOT_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Unsupported manifest version",
));
}
Ok(())
}
}