use crate::error::{Error, Result};
use crate::fs::Filesystem;
use crate::inode::Inode;
use crate::jbd2::{self, JournalSuperblock};
use crate::journal::ReplayPlan;
pub fn apply(fs: &Filesystem, plan: &ReplayPlan) -> Result<usize> {
if plan.writes.is_empty() {
return Ok(0);
}
if !fs.dev.is_writable() {
return Err(Error::Corrupt(
"journal_apply: device is not writable; cannot replay",
));
}
let raw = fs.read_inode_raw(fs.sb.journal_inode)?;
let jinode = Inode::parse(&raw)?;
let block_size = fs.sb.block_size() as u64;
let mut applied = 0usize;
for w in &plan.writes {
let phys = jbd2::journal_block_to_physical(fs, &jinode, w.journal_block)?
.ok_or(Error::Corrupt("journal_apply: journal block unmapped"))?;
let mut buf = vec![0u8; block_size as usize];
fs.dev.read_at(phys * block_size, &mut buf)?;
if w.flags & crate::journal::TAG_ESCAPED != 0 {
buf[0..4].copy_from_slice(&crate::jbd2::JBD2_MAGIC_NUMBER.to_be_bytes());
}
fs.dev.write_at(w.fs_block * block_size, &buf)?;
applied += 1;
}
fs.dev.flush()?;
Ok(applied)
}
pub fn replay_if_dirty(fs: &Filesystem) -> Result<usize> {
if !fs.dev.is_writable() {
return Ok(0);
}
let Some(jsb) = jbd2::read_superblock(fs)? else {
return Ok(0); };
if jsb.is_clean() {
return Ok(0);
}
let plan = crate::journal::walk(fs, &jsb)?;
apply(fs, &plan)
}
#[derive(Debug, Clone)]
pub struct ReplaySummary {
pub clean: bool,
pub sequence: u32,
pub start: u32,
pub max_len: u32,
pub has_revoke: bool,
pub uses_64bit: bool,
pub uses_csum_v2_or_v3: bool,
}
impl From<&JournalSuperblock> for ReplaySummary {
fn from(jsb: &JournalSuperblock) -> Self {
Self {
clean: jsb.is_clean(),
sequence: jsb.sequence,
start: jsb.start,
max_len: jsb.max_len,
has_revoke: jsb.has_revoke(),
uses_64bit: jsb.uses_64bit(),
uses_csum_v2_or_v3: jsb.uses_csum_v2_or_v3(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::journal::{ReplayEntry, ReplayPlan};
#[test]
fn empty_plan_applies_zero_blocks() {
let plan = ReplayPlan::default();
assert!(plan.writes.is_empty());
}
#[test]
fn replay_summary_mirrors_jsb() {
let jsb = JournalSuperblock {
block_type: crate::jbd2::JBD2_SUPERBLOCK_V2,
header_sequence: 1,
block_size: 4096,
max_len: 8192,
first: 1,
sequence: 42,
start: 100,
errno: 0,
feature_compat: 0,
feature_incompat: crate::jbd2::JbdIncompat::REVOKE.bits()
| crate::jbd2::JbdIncompat::BIT64.bits()
| crate::jbd2::JbdIncompat::CSUM_V3.bits(),
feature_ro_compat: 0,
uuid: [0; 16],
nr_users: 1,
checksum_type: 4,
num_fc_blocks: 0,
checksum: 0,
};
let s = ReplaySummary::from(&jsb);
assert!(!s.clean);
assert_eq!(s.sequence, 42);
assert_eq!(s.start, 100);
assert!(s.has_revoke);
assert!(s.uses_64bit);
assert!(s.uses_csum_v2_or_v3);
}
#[test]
fn clean_summary() {
let mut jsb = JournalSuperblock {
block_type: crate::jbd2::JBD2_SUPERBLOCK_V2,
header_sequence: 1,
block_size: 4096,
max_len: 8192,
first: 1,
sequence: 1,
start: 0,
errno: 0,
feature_compat: 0,
feature_incompat: 0,
feature_ro_compat: 0,
uuid: [0; 16],
nr_users: 1,
checksum_type: 0,
num_fc_blocks: 0,
checksum: 0,
};
jsb.start = 0;
let s = ReplaySummary::from(&jsb);
assert!(s.clean);
}
#[test]
fn replay_entry_structure_stable() {
let e = ReplayEntry {
transaction: 1,
fs_block: 100,
journal_block: 5,
flags: 0,
};
assert_eq!(e.transaction, 1);
assert_eq!(e.fs_block, 100);
assert_eq!(e.journal_block, 5);
}
}