use std::fs::File;
use std::io;
use std::os::unix::fs::FileExt;
use std::path::{Path, PathBuf};
pub const RINGS_MAGIC: &[u8; 8] = b"RING0001";
pub const RINGS_HEADER_LEN: u64 = 8;
pub const RECORD_LEN: usize = 48;
pub const TRUNK_EXT: &str = "trunk";
pub const RINGS_EXT: &str = "rings";
pub const GRAIN_EXT: &str = "grain";
pub const BARK_EXT: &str = "bark";
pub fn trunk_path(dir: &Path, name: &str) -> PathBuf {
dir.join(format!("{name}.{TRUNK_EXT}"))
}
pub fn rings_path(dir: &Path, name: &str) -> PathBuf {
dir.join(format!("{name}.{RINGS_EXT}"))
}
pub fn grain_path(dir: &Path, name: &str) -> PathBuf {
dir.join(format!("{name}.{GRAIN_EXT}"))
}
pub fn bark_path(dir: &Path, name: &str) -> PathBuf {
dir.join(format!("{name}.{BARK_EXT}"))
}
#[derive(Debug, Clone, Copy)]
pub struct ChunkRecord {
pub uncomp_start: u64,
pub uncomp_len: u64,
pub comp_start: u64,
pub comp_len: u64,
pub first_write_ms: u64,
pub last_write_ms: u64,
}
impl ChunkRecord {
pub fn uncomp_end(&self) -> u64 {
self.uncomp_start + self.uncomp_len
}
pub fn comp_end(&self) -> u64 {
self.comp_start + self.comp_len
}
pub fn to_bytes(self) -> [u8; RECORD_LEN] {
let mut b = [0u8; RECORD_LEN];
b[0..8].copy_from_slice(&self.uncomp_start.to_le_bytes());
b[8..16].copy_from_slice(&self.uncomp_len.to_le_bytes());
b[16..24].copy_from_slice(&self.comp_start.to_le_bytes());
b[24..32].copy_from_slice(&self.comp_len.to_le_bytes());
b[32..40].copy_from_slice(&self.first_write_ms.to_le_bytes());
b[40..48].copy_from_slice(&self.last_write_ms.to_le_bytes());
b
}
pub fn from_bytes(b: &[u8]) -> ChunkRecord {
let u64_at = |i: usize| u64::from_le_bytes(b[i..i + 8].try_into().unwrap());
ChunkRecord {
uncomp_start: u64_at(0),
uncomp_len: u64_at(8),
comp_start: u64_at(16),
comp_len: u64_at(24),
first_write_ms: u64_at(32),
last_write_ms: u64_at(40),
}
}
}
pub fn read_index(path: &Path) -> io::Result<Vec<ChunkRecord>> {
let f = File::open(path)?;
read_index_file(&f)
}
pub fn read_index_file(f: &File) -> io::Result<Vec<ChunkRecord>> {
let len = f.metadata()?.len() as usize;
let mut buf = vec![0u8; len];
f.read_exact_at(&mut buf, 0)?;
parse_index_bytes(&buf)
}
pub fn parse_index_bytes(buf: &[u8]) -> io::Result<Vec<ChunkRecord>> {
if buf.len() < RINGS_HEADER_LEN as usize || &buf[..8] != RINGS_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"not a timberfs index (bad magic)",
));
}
let n = (buf.len() - RINGS_HEADER_LEN as usize) / RECORD_LEN;
let mut out = Vec::with_capacity(n);
for i in 0..n {
let off = RINGS_HEADER_LEN as usize + i * RECORD_LEN;
out.push(ChunkRecord::from_bytes(&buf[off..off + RECORD_LEN]));
}
Ok(out)
}