use alloc::{boxed::Box, vec::Vec};
use log::{error, trace, warn};
use super::{cached_device::BlockDev, traits::BlockDevice};
use crate::{
bmalloc::AbsoluteBN,
config::{BLOCK_SIZE, JBD2_BUFFER_MAX},
disknode::Ext4Timestamp,
error::{Ext4Error, Ext4Result},
jbd2::{
jbd2::ReplayStatus,
jbdstruct::{JBD2DEVSYSTEM, Jbd2Update, JournalSuperBllockS},
},
};
pub enum Jbd2RunState {
Commit,
Replay,
}
pub struct Jbd2Dev<B: BlockDevice> {
_mode: u8,
inner: BlockDev<B>,
journal_use: bool,
_state: Jbd2RunState,
system: Option<JBD2DEVSYSTEM>,
journal_blocks: Vec<AbsoluteBN>,
}
impl<B: BlockDevice> Jbd2Dev<B> {
fn enqueue_journal_update(
system: &mut JBD2DEVSYSTEM,
raw_dev: &mut B,
update: Jbd2Update,
) -> Ext4Result<()> {
if let Some(existing) = system
.commit_queue
.iter_mut()
.find(|queued| queued.0 == update.0)
{
*existing = update;
return Ok(());
}
if system.commit_queue.len() >= JBD2_BUFFER_MAX {
system.commit_transaction(raw_dev)?;
}
system.commit_queue.push(update);
Ok(())
}
fn make_system(
super_block: JournalSuperBllockS,
journal_start_block: AbsoluteBN,
) -> JBD2DEVSYSTEM {
JBD2DEVSYSTEM {
start_block: journal_start_block,
max_len: super_block.s_maxlen,
head: 0,
sequence: super_block.s_sequence,
jbd2_super_block: super_block,
commit_queue: Vec::new(),
}
}
pub fn initial_jbd2dev(_mode: u8, block_dev: B, use_journal: bool) -> Self {
let block_dev = BlockDev::new(block_dev);
Self {
_mode,
inner: block_dev,
journal_use: use_journal,
_state: Jbd2RunState::Commit,
system: None,
journal_blocks: Vec::new(),
}
}
pub fn is_use_journal(&self) -> bool {
self.journal_use
}
pub fn journal_sequence(&self) -> Option<u32> {
self.system.as_ref().map(|s| s.sequence)
}
pub fn journal_replay(&mut self) {
let _ = self.journal_replay_checked();
}
pub(crate) fn journal_replay_checked(&mut self) -> ReplayStatus {
if !self.journal_use {
warn!("journal replay requested while journaling is disabled");
return ReplayStatus::Complete;
}
let Some(jbd_sys) = self.system.as_mut() else {
error!("journal replay requested before JBD2 state was initialized");
return ReplayStatus::Incomplete;
};
jbd_sys.replay_with_mapping(self.inner.device_mut(), &self.journal_blocks)
}
pub fn set_journal_use(&mut self, use_journal: bool) {
self.journal_use = use_journal;
}
pub fn set_journal_superblock(
&mut self,
super_block: JournalSuperBllockS,
journal_start_block: AbsoluteBN,
) {
self.journal_blocks.clear();
self.system = Some(Self::make_system(super_block, journal_start_block));
}
pub(crate) fn set_journal_superblock_with_mapping(
&mut self,
super_block: JournalSuperBllockS,
journal_blocks: Vec<AbsoluteBN>,
) -> Ext4Result<()> {
let Some(&journal_start_block) = journal_blocks.first() else {
self.journal_blocks.clear();
self.system = None;
return Err(Ext4Error::corrupted());
};
self.journal_blocks = journal_blocks;
self.system = Some(Self::make_system(super_block, journal_start_block));
Ok(())
}
pub fn umount_commit(&mut self) {
if !self.journal_use {
trace!("Journal disabled, skip commit");
return;
}
if let Some(system) = self.system.as_mut() {
system
.commit_transaction_with_mapping(self.inner.device_mut(), &self.journal_blocks)
.expect("journal transaction commit failed");
} else {
trace!("Journal enabled but system uninitialized, skip commit");
}
}
pub fn write_block(&mut self, block_id: AbsoluteBN, is_metadata: bool) -> Ext4Result<()> {
if !self.journal_use || !is_metadata {
return self.inner.write_block(block_id);
}
let meta_vec = self.inner.buffer();
let mut new_buf = Box::new([0; BLOCK_SIZE]);
new_buf[..].copy_from_slice(meta_vec);
let updates = Jbd2Update(block_id, new_buf);
let Some(system) = self.system.as_mut() else {
error!(
"journal is enabled but JBD2 state is not initialized; writing block {block_id} \
directly"
);
return self.inner.write_block(block_id);
};
let raw_dev = self.inner.device_mut();
Self::enqueue_journal_update(system, raw_dev, updates)?;
trace!("[JBD2 buffer] queued metadata block {block_id}");
Ok(())
}
pub fn read_block(&mut self, block_id: AbsoluteBN) -> Ext4Result<()> {
self.inner.read_block(block_id)
}
pub fn buffer(&self) -> &[u8] {
self.inner.buffer()
}
pub fn buffer_mut(&mut self) -> &mut [u8] {
self.inner.buffer_mut()
}
pub fn read_blocks(
&mut self,
buf: &mut [u8],
block_id: AbsoluteBN,
count: u32,
) -> Ext4Result<()> {
self.inner.read_blocks(buf, block_id, count)
}
pub fn write_blocks(
&mut self,
buf: &[u8],
block_id: AbsoluteBN,
count: u32,
is_metadata: bool,
) -> Ext4Result<()> {
if !self.journal_use || !is_metadata {
return self.inner.write_blocks(buf, block_id, count);
}
let Some(system) = self.system.as_mut() else {
error!(
"journal is enabled but JBD2 state is not initialized; writing {count} block(s) \
starting at {block_id} directly"
);
return self.inner.write_blocks(buf, block_id, count);
};
let raw_dev = self.inner.device_mut();
let required = count as usize * BLOCK_SIZE;
if buf.len() < required {
return Err(Ext4Error::buffer_too_small(buf.len(), required));
}
for i in 0..count {
let off = (i as usize) * BLOCK_SIZE;
let mut boxbuf = Box::new([0; BLOCK_SIZE]);
boxbuf[..].copy_from_slice(&buf[off..off + BLOCK_SIZE]);
let updates = Jbd2Update(block_id.checked_add(i)?, boxbuf);
Self::enqueue_journal_update(system, raw_dev, updates)?;
}
Ok(())
}
pub fn cantflush(&mut self) -> Ext4Result<()> {
self.inner.flush()
}
pub fn total_blocks(&self) -> u64 {
self.inner.total_blocks()
}
pub fn block_size(&self) -> u32 {
self.inner.block_size()
}
pub fn current_time(&self) -> Ext4Result<Ext4Timestamp> {
self.inner._device().current_time()
}
}