Skip to content

Commit 89c8a8b

Browse files
Add code for parsing journal superblocks
This is `expect(unused)` until more of the journal code is added.
1 parent 0d772e0 commit 89c8a8b

File tree

3 files changed

+317
-0
lines changed

3 files changed

+317
-0
lines changed

src/error.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,15 @@ pub(crate) enum CorruptKind {
227227
u32,
228228
),
229229

230+
/// Journal size is invalid.
231+
JournalSize,
232+
233+
/// Journal magic is invalid.
234+
JournalMagic,
235+
236+
/// Journal superblock checksum is invalid.
237+
JournalSuperblockChecksum,
238+
230239
/// An inode's checksum is invalid.
231240
InodeChecksum(InodeIndex),
232241

@@ -301,6 +310,15 @@ impl Display for CorruptKind {
301310
f,
302311
"invalid checksum for block group descriptor {block_group_num}"
303312
),
313+
Self::JournalSize => {
314+
write!(f, "journal size is invalid")
315+
}
316+
Self::JournalMagic => {
317+
write!(f, "journal magic is invalid")
318+
}
319+
Self::JournalSuperblockChecksum => {
320+
write!(f, "journal superblock checksum is invalid")
321+
}
304322
Self::InodeChecksum(inode) => {
305323
write!(f, "invalid checksum for inode {inode}")
306324
}
@@ -358,6 +376,16 @@ impl PartialEq<CorruptKind> for Ext4Error {
358376
}
359377
}
360378

379+
impl PartialEq<Incompatible> for Ext4Error {
380+
fn eq(&self, other: &Incompatible) -> bool {
381+
if let Self::Incompatible(i) = self {
382+
i == other
383+
} else {
384+
false
385+
}
386+
}
387+
}
388+
361389
impl From<Corrupt> for Ext4Error {
362390
fn from(c: Corrupt) -> Self {
363391
Self::Corrupt(c)
@@ -418,6 +446,24 @@ pub enum Incompatible {
418446
/// Inode number.
419447
u32,
420448
),
449+
450+
/// The journal superblock type is not supported.
451+
JournalSuperblockType(
452+
/// Raw journal block type.
453+
u32,
454+
),
455+
456+
/// The journal checksum type is not supported.
457+
JournalChecksumType(
458+
/// Raw journal checksum type.
459+
u8,
460+
),
461+
462+
/// The journal uses features not supported by this library.
463+
JournalIncompatibleFeatures(
464+
/// Raw feature bits.
465+
u32,
466+
),
421467
}
422468

423469
impl Display for Incompatible {
@@ -438,6 +484,15 @@ impl Display for Incompatible {
438484
Self::DirectoryEncrypted(inode) => {
439485
write!(f, "directory in inode {inode} is encrypted")
440486
}
487+
Self::JournalSuperblockType(val) => {
488+
write!(f, "journal superblock type is not supported: {val}")
489+
}
490+
Self::JournalChecksumType(val) => {
491+
write!(f, "journal checksum type is not supported: {val}")
492+
}
493+
Self::JournalIncompatibleFeatures(val) => {
494+
write!(f, "unsupported journal features: {val:08x}")
495+
}
441496
}
442497
}
443498
}

src/journal.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#[expect(unused)] // TODO
1010
mod block_header;
11+
#[expect(unused)] // TODO
12+
mod superblock;
1113

1214
use crate::{Ext4, Ext4Error};
1315

src/journal/superblock.rs

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
use crate::checksum::Checksum;
10+
use crate::inode::Inode;
11+
use crate::iters::file_blocks::FileBlocks;
12+
use crate::journal::block_header::{JournalBlockHeader, JournalBlockType};
13+
use crate::util::read_u32be;
14+
use crate::uuid::Uuid;
15+
use crate::{CorruptKind, Ext4, Ext4Error, Incompatible};
16+
use alloc::vec;
17+
use bitflags::bitflags;
18+
19+
/// Size in bytes of the journal superblock. (Note that the underlying
20+
/// block size may be larger, but only the first 1024 bytes are used.)
21+
const SUPERBLOCK_SIZE: usize = 1024;
22+
23+
const CHECKSUM_TYPE_CRC32C: u8 = 4;
24+
25+
// Field offsets within the superblock.
26+
const SUPERBLOCK_BLOCKSIZE_OFFSET: usize = 0xc;
27+
const SUPERBLOCK_SEQUENCE_OFFSET: usize = 0x18;
28+
const SUPERBLOCK_START_OFFSET: usize = 0x1c;
29+
const SUPERBLOCK_FEATURE_INCOMPAT_OFFSET: usize = 0x28;
30+
const SUPERBLOCK_UUID_OFFSET: usize = 0x30;
31+
const SUPERBLOCK_CHECKSUM_TYPE_OFFSET: usize = 0x50;
32+
const SUPERBLOCK_CHECKSUM_OFFSET: usize = 0xfc;
33+
34+
#[derive(Debug, Eq, PartialEq)]
35+
pub(super) struct JournalSuperblock {
36+
/// Size in bytes of journal blocks. This must be the same block
37+
/// size as the main filesystem.
38+
pub(super) block_size: u32,
39+
40+
/// Sequence number of the first journal commit to apply.
41+
pub(super) sequence: u32,
42+
43+
/// Index of the journal block from which to start reading
44+
/// data. This index is relative to the journal superblock.
45+
pub(super) start_block: u32,
46+
47+
/// Journal UUID used for checksums.
48+
pub(super) uuid: Uuid,
49+
}
50+
51+
impl JournalSuperblock {
52+
/// Load the journal superblock from the filesystem.
53+
///
54+
/// An error is returned if:
55+
/// * The superblock cannot be read from the filesystem.
56+
/// * `JournalSuperblock::read_bytes` fails.
57+
pub(super) fn load(
58+
fs: &Ext4,
59+
journal_inode: &Inode,
60+
) -> Result<Self, Ext4Error> {
61+
// Get an iterator over the journal's block indices.
62+
let mut journal_block_iter =
63+
FileBlocks::new(fs.clone(), journal_inode)?;
64+
65+
// Read the first 1024 bytes of the first block. This is the
66+
// journal's superblock.
67+
let block_index = journal_block_iter
68+
.next()
69+
.ok_or(CorruptKind::JournalSize)??;
70+
let mut block = vec![0; SUPERBLOCK_SIZE];
71+
fs.read_from_block(block_index, 0, &mut block)?;
72+
73+
let superblock = Self::read_bytes(&block)?;
74+
75+
Ok(superblock)
76+
}
77+
78+
/// Read superblock data from `bytes`.
79+
///
80+
/// An error is returned if:
81+
/// * The superblock magic number is not present.
82+
/// * The superblock type is unsupported.
83+
/// * The checksum type is unsupported.
84+
/// * The superblock's checksum is incorrect.
85+
fn read_bytes(bytes: &[u8]) -> Result<Self, Ext4Error> {
86+
assert_eq!(bytes.len(), SUPERBLOCK_SIZE);
87+
88+
let header = JournalBlockHeader::read_bytes(bytes)?
89+
.ok_or(CorruptKind::JournalMagic)?;
90+
91+
// For now only superblock v2 is supported.
92+
if header.block_type != JournalBlockType::SUPERBLOCK_V2 {
93+
return Err(Incompatible::JournalSuperblockType(
94+
header.block_type.0,
95+
)
96+
.into());
97+
}
98+
99+
let s_blocksize = read_u32be(bytes, SUPERBLOCK_BLOCKSIZE_OFFSET);
100+
let s_sequence = read_u32be(bytes, SUPERBLOCK_SEQUENCE_OFFSET);
101+
let s_start = read_u32be(bytes, SUPERBLOCK_START_OFFSET);
102+
let s_feature_incompat =
103+
read_u32be(bytes, SUPERBLOCK_FEATURE_INCOMPAT_OFFSET);
104+
let s_uuid =
105+
&bytes[SUPERBLOCK_UUID_OFFSET..SUPERBLOCK_UUID_OFFSET + 16];
106+
let s_checksum_type = bytes[SUPERBLOCK_CHECKSUM_TYPE_OFFSET];
107+
let s_checksum = read_u32be(bytes, SUPERBLOCK_CHECKSUM_OFFSET);
108+
109+
// Check that features required by this library are present, and
110+
// that no unsupported features are present.
111+
let incompat_features =
112+
JournalIncompatibleFeatures::from_bits_retain(s_feature_incompat);
113+
let required_incompat_features = JournalIncompatibleFeatures::IS_64BIT
114+
| JournalIncompatibleFeatures::CHECKSUM_V3;
115+
if incompat_features != required_incompat_features {
116+
return Err(Incompatible::JournalIncompatibleFeatures(
117+
s_feature_incompat,
118+
)
119+
.into());
120+
}
121+
122+
// For now only one checksum type is supported.
123+
if s_checksum_type != CHECKSUM_TYPE_CRC32C {
124+
return Err(
125+
Incompatible::JournalChecksumType(s_checksum_type).into()
126+
);
127+
}
128+
129+
// Validate the superblock checksum.
130+
let mut checksum = Checksum::new();
131+
checksum.update(&bytes[..SUPERBLOCK_CHECKSUM_OFFSET]);
132+
checksum.update_u32_le(0);
133+
checksum
134+
.update(&bytes[SUPERBLOCK_CHECKSUM_OFFSET + 4..SUPERBLOCK_SIZE]);
135+
if checksum.finalize() != s_checksum {
136+
return Err(CorruptKind::JournalSuperblockChecksum.into());
137+
}
138+
139+
// OK to unwrap: `s_uuid` is always 16 bytes.
140+
let uuid = Uuid(s_uuid.try_into().unwrap());
141+
142+
Ok(Self {
143+
block_size: s_blocksize,
144+
sequence: s_sequence,
145+
start_block: s_start,
146+
uuid,
147+
})
148+
}
149+
}
150+
151+
bitflags! {
152+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
153+
pub struct JournalIncompatibleFeatures: u32 {
154+
const BLOCK_REVOCATIONS = 0x1;
155+
const IS_64BIT = 0x2;
156+
const ASYNC_COMMITS = 0x4;
157+
const CHECKSUM_V2 = 0x8;
158+
const CHECKSUM_V3 = 0x10;
159+
const FAST_COMMITS = 0x20;
160+
}
161+
}
162+
163+
#[cfg(all(test, feature = "std"))]
164+
mod tests {
165+
use super::*;
166+
167+
fn write_u32be(bytes: &mut [u8], offset: usize, value: u32) {
168+
bytes[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
169+
}
170+
171+
fn create_test_superblock() -> Vec<u8> {
172+
let mut block = vec![0; 1024];
173+
// Set magic.
174+
write_u32be(&mut block, 0, JournalBlockHeader::MAGIC);
175+
// Set superblock type.
176+
write_u32be(&mut block, 4, 4);
177+
// Set block size.
178+
write_u32be(&mut block, SUPERBLOCK_BLOCKSIZE_OFFSET, 4096);
179+
// Set sequence.
180+
write_u32be(&mut block, SUPERBLOCK_SEQUENCE_OFFSET, 123);
181+
// Set start block.
182+
write_u32be(&mut block, SUPERBLOCK_START_OFFSET, 456);
183+
// Set features.
184+
write_u32be(&mut block, SUPERBLOCK_FEATURE_INCOMPAT_OFFSET, 0x12);
185+
// Set UUID.
186+
block[SUPERBLOCK_UUID_OFFSET..SUPERBLOCK_UUID_OFFSET + 16]
187+
.copy_from_slice(&[0xab; 16]);
188+
// Set checksum type.
189+
block[SUPERBLOCK_CHECKSUM_TYPE_OFFSET] = CHECKSUM_TYPE_CRC32C;
190+
// Set checksum.
191+
write_u32be(&mut block, SUPERBLOCK_CHECKSUM_OFFSET, 0x78a2_c32b);
192+
block
193+
}
194+
195+
#[test]
196+
fn test_journal_superblock_read_success() {
197+
let block = create_test_superblock();
198+
assert_eq!(
199+
JournalSuperblock::read_bytes(&block).unwrap(),
200+
JournalSuperblock {
201+
block_size: 4096,
202+
sequence: 123,
203+
start_block: 456,
204+
uuid: Uuid([0xab; 16]),
205+
}
206+
);
207+
}
208+
209+
#[test]
210+
fn test_journal_superblock_invalid_magic() {
211+
let mut block = create_test_superblock();
212+
// Override magic in the block header.
213+
write_u32be(&mut block, 0, 0);
214+
assert_eq!(
215+
JournalSuperblock::read_bytes(&block).unwrap_err(),
216+
CorruptKind::JournalMagic
217+
);
218+
}
219+
220+
#[test]
221+
fn test_journal_superblock_unsupported_type() {
222+
let mut block = create_test_superblock();
223+
// Override type in the block header.
224+
write_u32be(&mut block, 4, 0);
225+
assert_eq!(
226+
JournalSuperblock::read_bytes(&block).unwrap_err(),
227+
Incompatible::JournalSuperblockType(0)
228+
);
229+
}
230+
231+
#[test]
232+
fn test_journal_superblock_missing_required_features() {
233+
let mut block = create_test_superblock();
234+
write_u32be(&mut block, SUPERBLOCK_FEATURE_INCOMPAT_OFFSET, 0x10);
235+
assert_eq!(
236+
JournalSuperblock::read_bytes(&block).unwrap_err(),
237+
Incompatible::JournalIncompatibleFeatures(0x10),
238+
);
239+
}
240+
241+
#[test]
242+
fn test_journal_superblock_unsupported_checksum_type() {
243+
let mut block = create_test_superblock();
244+
block[SUPERBLOCK_CHECKSUM_TYPE_OFFSET] = 0;
245+
assert_eq!(
246+
JournalSuperblock::read_bytes(&block).unwrap_err(),
247+
Incompatible::JournalChecksumType(0)
248+
);
249+
}
250+
251+
#[test]
252+
fn test_journal_superblock_incorrect_checksum() {
253+
let mut block = create_test_superblock();
254+
write_u32be(&mut block, SUPERBLOCK_CHECKSUM_OFFSET, 0);
255+
assert_eq!(
256+
JournalSuperblock::read_bytes(&block).unwrap_err(),
257+
CorruptKind::JournalSuperblockChecksum,
258+
);
259+
}
260+
}

0 commit comments

Comments
 (0)