Skip to content

Commit f4fab76

Browse files
More journal work
1 parent 032493d commit f4fab76

File tree

5 files changed

+351
-11
lines changed

5 files changed

+351
-11
lines changed

src/error.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,18 @@ pub enum Corrupt {
214214
u32,
215215
),
216216

217+
/// Journal size is invalid.
218+
JournalSize,
219+
220+
/// Journal magic is invalid.
221+
JournalMagic,
222+
223+
/// Journal contains a block of unknown type.
224+
JournalBlockType(
225+
/// Raw block type.
226+
u32,
227+
),
228+
217229
/// An inode's checksum is invalid.
218230
InodeChecksum(
219231
/// Inode number.
@@ -324,6 +336,18 @@ impl Display for Corrupt {
324336
f,
325337
"invalid checksum for block group descriptor {block_group_num}"
326338
),
339+
Self::JournalSize => {
340+
write!(f, "journal size is invalid")
341+
}
342+
Self::JournalMagic => {
343+
write!(f, "journal magic is invalid")
344+
}
345+
Self::JournalBlockType(block_type) => {
346+
write!(
347+
f,
348+
"journal contains an unknown block type: {block_type}"
349+
)
350+
}
327351
Self::InodeChecksum(inode) => {
328352
write!(f, "invalid checksum for inode {inode}")
329353
}
@@ -422,6 +446,12 @@ pub enum Incompatible {
422446
/// Inode number.
423447
u32,
424448
),
449+
450+
/// Journal superblock type is not supported.
451+
JournalSuperblockType(
452+
/// Raw journal block type.
453+
u32,
454+
),
425455
}
426456

427457
impl Display for Incompatible {
@@ -442,6 +472,9 @@ impl Display for Incompatible {
442472
Self::DirectoryEncrypted(inode) => {
443473
write!(f, "directory in inode {inode} is encrypted")
444474
}
475+
Self::JournalSuperblockType(val) => {
476+
write!(f, "journal superblock type is not supported: {val}")
477+
}
445478
}
446479
}
447480
}

src/journal.rs

Lines changed: 297 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,318 @@
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
88

9-
use crate::{Ext4, Ext4Error};
9+
// TODO
10+
#![expect(dead_code)]
11+
12+
use crate::inode::Inode;
13+
use crate::iters::file_blocks::FileBlocks;
14+
use crate::util::{read_u32be, u64_from_hilo};
15+
use crate::{Corrupt, Ext4, Ext4Error, Incompatible};
16+
use alloc::collections::BTreeMap;
17+
use alloc::vec;
18+
use alloc::vec::Vec;
19+
use bitflags::bitflags;
1020

1121
#[derive(Debug)]
1222
pub(crate) struct Journal {
13-
// TODO: add journal data.
23+
blocks: BTreeMap<u64, u64>,
1424
}
1525

1626
impl Journal {
17-
/// Create an empty journal.
1827
pub(crate) fn empty() -> Self {
19-
Self {}
28+
Self {
29+
blocks: BTreeMap::new(),
30+
}
2031
}
2132

22-
/// Load a journal from the filesystem.
2333
pub(crate) fn load(fs: &Ext4) -> Result<Self, Ext4Error> {
24-
let Some(_journal_inode) = fs.0.superblock.journal_inode else {
34+
// Note: ext4 is all little-endian, except for the journal,
35+
// which is all big-endian. 😬
36+
37+
let Some(journal_inode) = fs.0.superblock.journal_inode else {
2538
// Return an empty journal if this filesystem does not have
2639
// a journal.
2740
return Ok(Self::empty());
2841
};
2942

30-
// TODO: actually load the journal.
43+
let journal_inode = Inode::read(fs, journal_inode)?;
44+
45+
let mut journal_block_iter =
46+
FileBlocks::new(fs.clone(), &journal_inode)?;
47+
let block_index =
48+
journal_block_iter.next().ok_or(Corrupt::JournalSize)??;
49+
50+
let block_size = fs.0.superblock.block_size;
51+
let mut block = vec![0; block_size.to_usize()];
52+
fs.read_from_block(block_index, 0, &mut block)?;
53+
54+
let header = JournalBlockHeader::read_bytes(&block)?;
55+
56+
// Check superblock type.
57+
if header.block_type != JournalBlockType::SUPERBLOCK_V2 {
58+
return Err(Incompatible::JournalSuperblockType(
59+
header.block_type.0,
60+
)
61+
.into());
62+
}
63+
64+
let s_blocksize = read_u32be(&block, 0xc);
65+
let _s_maxlen = read_u32be(&block, 0x10);
66+
// TODO: what's the difference between first and start?
67+
let _s_first = read_u32be(&block, 0x14);
68+
let _s_sequence = read_u32be(&block, 0x18);
69+
let s_start = read_u32be(&block, 0x1c);
70+
let s_feature_compat = read_u32be(&block, 0x24);
71+
let s_feature_incompat = read_u32be(&block, 0x28);
72+
73+
// TODO: check features.
74+
// TODO: checksum type
75+
76+
// TODO
77+
assert_eq!(s_blocksize, block_size);
78+
79+
let compat_features =
80+
JournalCompatibleFeatures::from_bits_retain(s_feature_compat);
81+
let incompat_features =
82+
JournalIncompatibleFeatures::from_bits_retain(s_feature_incompat);
83+
84+
// TODO
85+
assert_eq!(compat_features, JournalCompatibleFeatures::empty());
86+
assert!(
87+
incompat_features.contains(JournalIncompatibleFeatures::IS_64BIT)
88+
);
89+
//JournalIncompatibleFeatures::BLOCK_REVOCATIONS |
90+
//| JournalIncompatibleFeatures::CHECKSUM_V3
91+
92+
let mut blocks = BTreeMap::new();
93+
// TODO... minus 1 because already read the journal superblock
94+
for _ in 1..s_start {
95+
// TODO: unwrap
96+
journal_block_iter.next().unwrap()?;
97+
}
98+
while let Some(block_index) = journal_block_iter.next() {
99+
let block_index = block_index?;
100+
101+
// TODO: not all blocks need to be read...
102+
fs.read_from_block(block_index, 0, &mut block)?;
103+
104+
// TODO: validate checksums.
105+
106+
let h_magic = read_u32be(&block, 0x0);
107+
if h_magic != 0xc03b3998 {
108+
// No magic.
109+
// dbg!("no magic");
110+
break;
111+
}
112+
113+
let header = JournalBlockHeader::read_bytes(&block)?;
114+
115+
if header.block_type == JournalBlockType::DESCRIPTOR {
116+
let tags =
117+
JournalDescriptorBlockTag::read_bytes_to_vec(&block[12..])
118+
.unwrap();
119+
120+
// TODO: are these blocks the size of the filesystem or
121+
// of the journal? Or always the same?
122+
123+
for tag in &tags {
124+
// TODO: unwrap
125+
let block_index = journal_block_iter.next().unwrap()?;
126+
127+
blocks.insert(tag.block_number, block_index);
128+
}
129+
} else if header.block_type == JournalBlockType::COMMIT {
130+
// TODO: do stuff with the commit block
131+
} else {
132+
todo!()
133+
}
134+
}
135+
136+
Ok(Self { blocks })
137+
}
138+
139+
pub(crate) fn map_block_index(&self, block_index: u64) -> u64 {
140+
*self.blocks.get(&block_index).unwrap_or(&block_index)
141+
}
142+
}
143+
144+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145+
struct JournalBlockType(u32);
146+
147+
impl JournalBlockType {
148+
const DESCRIPTOR: Self = Self(1);
149+
const COMMIT: Self = Self(2);
150+
const SUPERBLOCK_V1: Self = Self(3);
151+
const SUPERBLOCK_V2: Self = Self(4);
152+
const REVOCATION: Self = Self(5);
153+
}
154+
155+
#[derive(Debug)]
156+
struct JournalBlockHeader {
157+
block_type: JournalBlockType,
158+
sequence: u32,
159+
}
160+
161+
impl JournalBlockHeader {
162+
fn read_bytes(bytes: &[u8]) -> Result<Self, Ext4Error> {
163+
assert!(bytes.len() >= 12);
164+
165+
let h_magic = read_u32be(bytes, 0x0);
166+
let h_blocktype = read_u32be(bytes, 0x4);
167+
let h_sequence = read_u32be(bytes, 0x8);
168+
169+
// Check journal magic.
170+
if h_magic != 0xc03b3998 {
171+
return Err(Corrupt::JournalMagic.into());
172+
}
173+
174+
let block_type = JournalBlockType(h_blocktype);
175+
176+
Ok(Self {
177+
block_type,
178+
sequence: h_sequence,
179+
})
180+
}
181+
}
182+
183+
bitflags! {
184+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
185+
pub struct JournalCompatibleFeatures: u32 {
186+
const CHECKSUMS = 0x1;
187+
}
188+
}
189+
190+
bitflags! {
191+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
192+
pub struct JournalIncompatibleFeatures: u32 {
193+
const BLOCK_REVOCATIONS = 0x1;
194+
const IS_64BIT = 0x2;
195+
const ASYNC_COMMITS = 0x4;
196+
const CHECKSUM_V2 = 0x8;
197+
const CHECKSUM_V3 = 0x10;
198+
const FAST_COMMITS = 0x20;
199+
}
200+
}
201+
202+
enum JournalChecksumType {
203+
Crc32 = 1,
204+
Md5 = 2,
205+
Sha1 = 3,
206+
Crc32c = 4,
207+
}
208+
209+
// TODO: the kernel docs for this are a mess
210+
#[derive(Debug)]
211+
struct JournalDescriptorBlockTag {
212+
block_number: u64,
213+
flags: JournalDescriptorBlockTagFlags,
214+
checksum: u32,
215+
uuid: [u8; 16],
216+
}
217+
218+
impl JournalDescriptorBlockTag {
219+
fn read_bytes(bytes: &[u8]) -> (Self, usize) {
220+
// TODO: for now assuming the `incompat_features` assert above.
221+
222+
let t_blocknr = read_u32be(bytes, 0);
223+
let t_flags = read_u32be(bytes, 4);
224+
let t_blocknr_high = read_u32be(bytes, 8);
225+
let t_checksum = read_u32be(bytes, 12);
226+
227+
let flags = JournalDescriptorBlockTagFlags::from_bits_retain(t_flags);
228+
let mut size: usize = 16;
229+
230+
let mut uuid = [0; 16];
231+
if !flags.contains(JournalDescriptorBlockTagFlags::UUID_OMITTED) {
232+
// OK to unwrap: length is 16.
233+
uuid = bytes[16..32].try_into().unwrap();
234+
// TODO: unwrap
235+
size = size.checked_add(16).unwrap();
236+
}
237+
238+
(
239+
Self {
240+
block_number: u64_from_hilo(t_blocknr_high, t_blocknr),
241+
flags,
242+
checksum: t_checksum,
243+
uuid,
244+
},
245+
size,
246+
)
247+
}
248+
249+
fn read_bytes_to_vec(mut bytes: &[u8]) -> Result<Vec<Self>, Ext4Error> {
250+
let mut v = Vec::new();
251+
while !bytes.is_empty() {
252+
let (tag, size) = Self::read_bytes(bytes);
253+
let is_end =
254+
tag.flags.contains(JournalDescriptorBlockTagFlags::LAST_TAG);
255+
v.push(tag);
256+
257+
if is_end {
258+
return Ok(v);
259+
}
260+
261+
bytes = &bytes[size..];
262+
}
263+
// TODO: return a Corrupt error.
264+
todo!("missing end tag")
265+
}
266+
}
267+
268+
bitflags! {
269+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
270+
pub struct JournalDescriptorBlockTagFlags: u32 {
271+
const ESCAPED = 0x1;
272+
const UUID_OMITTED = 0x2;
273+
const DELETED = 0x4;
274+
const LAST_TAG = 0x8;
275+
}
276+
}
277+
278+
// TODO
279+
#[cfg(feature = "std")]
280+
#[cfg(test)]
281+
mod tests {
282+
use super::*;
283+
use crate::test_util::load_compressed_filesystem;
284+
use alloc::rc::Rc;
285+
286+
// TODO
287+
#[test]
288+
fn test_journal() {
289+
let mut fs =
290+
load_compressed_filesystem("test_disk_4k_block_journal.bin.zst");
291+
292+
let block_size = fs.0.superblock.block_size;
293+
let mut b1 = vec![0; block_size.to_usize()];
294+
let mut b2 = vec![0; block_size.to_usize()];
295+
println!("looking for mismatches...");
296+
for (dst, src) in &fs.0.journal.blocks {
297+
fs.read_from_block(*dst, 0, &mut b1).unwrap();
298+
fs.read_from_block(*src, 0, &mut b2).unwrap();
299+
if b1 != b2 {
300+
dbg!(dst, src);
301+
}
302+
//dbg!(dst, src, b1 == b2);
303+
}
304+
println!("done looking");
305+
306+
let entries = fs
307+
.read_dir("/")
308+
.unwrap()
309+
.map(|e| e.unwrap().file_name().as_str().unwrap().to_owned())
310+
.collect::<Vec<_>>();
311+
dbg!(entries);
312+
//todo!();
313+
314+
let test_dir = "/dir500";
315+
316+
// With the journal in place, this directory exists.
317+
assert!(fs.exists(test_dir).unwrap());
31318

32-
Ok(Self {})
319+
// Clear the journal, and verify that the directory no longer exists.
320+
Rc::get_mut(&mut fs.0).unwrap().journal.blocks.clear();
321+
assert!(!fs.exists(test_dir).unwrap());
33322
}
34323
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ impl Ext4 {
285285
})
286286
};
287287

288+
let block_index = self.0.journal.map_block_index(block_index);
289+
288290
// The first 1024 bytes are reserved for non-filesystem
289291
// data. This conveniently allows for something like a null
290292
// pointer check.

0 commit comments

Comments
 (0)