use crate::BLOCK_DEVICES;
use crate::integrity::checksum::Checksum;
use alloc::collections::VecDeque;
use alloc::vec::Vec;
use lazy_static::lazy_static;
use spin::Mutex;
const ZIL_BLOCK_SIZE: usize = 512;
const ZIL_MAX_RECORDS: usize = 64;
static ZIL_SLOG_ID: Mutex<Option<usize>> = Mutex::new(None);
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ZilOpcode {
Create = 1,
Remove = 2,
Write = 3,
Truncate = 4,
Setattr = 5,
AclV0 = 6,
AclV1 = 7,
Mkdir = 8,
Rmdir = 9,
Link = 10,
Symlink = 11,
Rename = 12,
WriteTrunc = 13, CloneRange = 14,
Commit = 99, }
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct ZilHeader {
pub magic: u64, pub txg: u64, pub timestamp: u64, pub opcode: u32, pub len: u32, pub object_id: u64, pub offset: u64, pub checksum: u64, pub seq: u64, pub prev_blk: u64, pub reserved: [u64; 2], }
const ZIL_MAGIC: u64 = 0x5A494C00;
impl ZilHeader {
fn new(txg: u64, opcode: ZilOpcode, len: u32, object_id: u64, offset: u64, seq: u64) -> Self {
Self {
magic: ZIL_MAGIC,
txg,
timestamp: 0, opcode: opcode as u32,
len,
object_id,
offset,
checksum: 0, seq,
prev_blk: 0,
reserved: [0; 2],
}
}
fn calculate_checksum(&self, payload: &[u8]) -> u64 {
let mut data = Vec::new();
unsafe {
let ptr = self as *const Self as *const u8;
data.extend_from_slice(core::slice::from_raw_parts(
ptr,
core::mem::size_of::<Self>(),
));
}
data.extend_from_slice(payload);
Checksum::calculate(&data).first()
}
}
#[derive(Debug, Clone)]
pub struct ZilRecord {
pub header: ZilHeader,
pub payload: Vec<u8>,
}
lazy_static! {
pub static ref ZIL_BUFFER: Mutex<VecDeque<ZilRecord>> = Mutex::new(VecDeque::new());
static ref ZIL_SEQ: Mutex<u64> = Mutex::new(1);
static ref ZIL_LAST_BLK: Mutex<u64> = Mutex::new(0);
}
pub struct ZilEngine;
impl ZilEngine {
pub fn set_slog_device(dev_id: usize) {
*ZIL_SLOG_ID.lock() = Some(dev_id);
crate::lcpfs_println!("[ ZIL ] SLOG device configured: {}", dev_id);
}
fn get_slog_device() -> usize {
ZIL_SLOG_ID.lock().unwrap_or(0)
}
pub fn log_operation(
txg: u64,
opcode: ZilOpcode,
object_id: u64,
offset: u64,
data: &[u8],
) -> Result<(), &'static str> {
let seq = {
let mut seq_guard = ZIL_SEQ.lock();
let current = *seq_guard;
*seq_guard += 1;
current
};
let header = ZilHeader::new(txg, opcode, data.len() as u32, object_id, offset, seq);
let checksum = header.calculate_checksum(data);
let mut final_header = header;
final_header.checksum = checksum;
let record = ZilRecord {
header: final_header,
payload: data.to_vec(),
};
let mut buffer = ZIL_BUFFER.lock();
buffer.push_back(record);
if buffer.len() >= ZIL_MAX_RECORDS {
drop(buffer); Self::flush_to_slog()?;
}
Ok(())
}
pub fn flush_to_slog() -> Result<(), &'static str> {
let mut buffer = ZIL_BUFFER.lock();
if buffer.is_empty() {
return Ok(()); }
let record_count = buffer.len();
let mut commit_block = Vec::new();
while let Some(record) = buffer.pop_front() {
unsafe {
let header_bytes = core::slice::from_raw_parts(
&record.header as *const ZilHeader as *const u8,
core::mem::size_of::<ZilHeader>(),
);
commit_block.extend_from_slice(header_bytes);
}
commit_block.extend_from_slice(&record.payload);
let padding = (64 - (commit_block.len() % 64)) % 64;
commit_block.resize(commit_block.len() + padding, 0);
}
drop(buffer);
let slog_id = Self::get_slog_device();
let mut devices = BLOCK_DEVICES.lock();
let slog = devices.get_mut(slog_id).ok_or("SLOG device not found")?;
let mut last_blk = ZIL_LAST_BLK.lock();
let blk_num = *last_blk + 1;
let blocks_needed = commit_block.len().div_ceil(ZIL_BLOCK_SIZE);
for i in 0..blocks_needed {
let offset = i * ZIL_BLOCK_SIZE;
let end = core::cmp::min(offset + ZIL_BLOCK_SIZE, commit_block.len());
let mut block = [0u8; 512];
let chunk = &commit_block[offset..end];
block[..chunk.len()].copy_from_slice(chunk);
slog.write_block(blk_num as usize + i, &block)?;
}
*last_blk = blk_num + blocks_needed as u64 - 1;
drop(last_blk);
drop(devices);
crate::lcpfs_println!(
"[ ZIL ] Flushed {} records to SLOG (blocks {}-{})",
record_count,
blk_num,
blk_num + blocks_needed as u64 - 1
);
Ok(())
}
pub fn replay_on_mount() -> Result<usize, &'static str> {
crate::lcpfs_println!("[ ZIL ] Replaying intent log...");
let slog_id = Self::get_slog_device();
let mut devices = BLOCK_DEVICES.lock();
let slog = devices.get_mut(slog_id).ok_or("SLOG device not found")?;
let last_blk = *ZIL_LAST_BLK.lock();
if last_blk == 0 {
crate::lcpfs_println!("[ ZIL ] No uncommitted transactions found.");
return Ok(0);
}
let mut replayed = 0;
for blk in 1..=last_blk as usize {
let mut block = [0u8; 512];
slog.read_block(blk, &mut block)?;
let mut offset = 0;
while offset + core::mem::size_of::<ZilHeader>() <= 512 {
let header: ZilHeader = unsafe {
core::ptr::read_unaligned(block.as_ptr().add(offset) as *const ZilHeader)
};
if header.magic != ZIL_MAGIC {
break; }
let payload_start = offset + core::mem::size_of::<ZilHeader>();
let payload_end = payload_start + header.len as usize;
if payload_end > 512 {
break; }
let payload = &block[payload_start..payload_end];
let expected_checksum = header.calculate_checksum(payload);
if header.checksum != expected_checksum {
let seq = header.seq; crate::lcpfs_println!("[ ZIL ] Checksum mismatch at seq {}, skipping", seq);
break;
}
Self::replay_record(&header, payload)?;
replayed += 1;
offset = payload_end;
offset = (offset + 63) & !63; }
}
drop(devices);
Self::clear_log()?;
crate::lcpfs_println!("[ ZIL ] Replayed {} transactions successfully", replayed);
Ok(replayed)
}
fn replay_record(header: &ZilHeader, payload: &[u8]) -> Result<(), &'static str> {
let opcode = match header.opcode {
1 => ZilOpcode::Create,
2 => ZilOpcode::Remove,
3 => ZilOpcode::Write,
4 => ZilOpcode::Truncate,
5 => ZilOpcode::Setattr,
8 => ZilOpcode::Mkdir,
9 => ZilOpcode::Rmdir,
12 => ZilOpcode::Rename,
_ => return Ok(()), };
let object_id = header.object_id;
let offset = header.offset;
match opcode {
ZilOpcode::Write => {
crate::lcpfs_println!(
"[ ZIL ] Replay: WRITE obj={} offset={} len={}",
object_id,
offset,
payload.len()
);
}
ZilOpcode::Create => {
crate::lcpfs_println!("[ ZIL ] Replay: CREATE obj={}", object_id);
}
ZilOpcode::Remove => {
crate::lcpfs_println!("[ ZIL ] Replay: REMOVE obj={}", object_id);
}
ZilOpcode::Mkdir => {
crate::lcpfs_println!("[ ZIL ] Replay: MKDIR obj={}", object_id);
}
_ => {}
}
Ok(())
}
fn clear_log() -> Result<(), &'static str> {
*ZIL_LAST_BLK.lock() = 0;
*ZIL_SEQ.lock() = 1;
ZIL_BUFFER.lock().clear();
let slog_id = Self::get_slog_device();
let mut devices = BLOCK_DEVICES.lock();
if let Some(slog) = devices.get_mut(slog_id) {
let zero_block = [0u8; 512];
slog.write_block(0, &zero_block)?;
}
Ok(())
}
pub fn commit_txg(txg: u64) -> Result<(), &'static str> {
Self::log_operation(txg, ZilOpcode::Commit, 0, 0, &[])?;
Self::flush_to_slog()?;
Self::clear_log()?;
crate::lcpfs_println!("[ ZIL ] Committed TXG {} and cleared log", txg);
Ok(())
}
pub fn stats() -> (usize, u64, u64) {
let buffer_size = ZIL_BUFFER.lock().len();
let last_blk = *ZIL_LAST_BLK.lock();
let seq = *ZIL_SEQ.lock();
(buffer_size, last_blk, seq)
}
}