Skip to content

Commit ebba762

Browse files
xtask: Create test filesystem with journal data
1 parent e0faee1 commit ebba762

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed
42.5 KB
Binary file not shown.

xtask/src/main.rs

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
// except according to those terms.
88

99
mod big_fs;
10-
#[expect(unused)] // TODO
1110
mod dmsetup;
12-
#[expect(unused)] // TODO
1311
mod losetup;
1412

1513
use anyhow::{bail, Context, Result};
1614
use clap::{Parser, Subcommand};
15+
use dmsetup::{DmDevice, DmFlakey};
16+
use losetup::LoopDevice;
1717
use nix::fcntl::{self, FallocateFlags};
1818
use std::fs::{self, OpenOptions};
1919
use std::os::fd::AsRawFd;
@@ -258,6 +258,103 @@ impl DiskParams {
258258
Ok(())
259259
}
260260

261+
/// Create a filesystem that was not unmounted cleanly. The root
262+
/// directory contains a number of subdirectories that are only in
263+
/// the journal.
264+
fn create_with_journal(&self) -> Result<()> {
265+
// Multiple attempts may be needed to get a filesystem with the
266+
// desired journal state.
267+
for i in 1..=10 {
268+
println!("creating filesystem with journal, attempt {i}");
269+
270+
self.create()?;
271+
self.make_filesystem_need_recovery()?;
272+
273+
// Verify that the journal contains at least one block. The
274+
// full output should look something like this:
275+
//
276+
// ```
277+
// Journal starts at block 1, transaction 2
278+
// Found expected sequence 2, type 1 (descriptor block) at block 1
279+
// [...]
280+
// Found expected sequence 2, type 1 (descriptor block) at block 1303
281+
// Found expected sequence 2, type 2 (commit block) at block 1311
282+
// ```
283+
let logdump = self.run_debugfs("logdump")?;
284+
let logdump = str::from_utf8(&logdump)?;
285+
if logdump.contains("Found expected sequence") {
286+
return Ok(());
287+
}
288+
}
289+
290+
bail!("failed to create filesystem");
291+
}
292+
293+
/// Modify the filesystem so that some data is written to the
294+
/// journal, but not yet flushed to the main filesystem.
295+
///
296+
/// This uses losetup and dmsetup to simulate a power failure after
297+
/// data is written to the journal.
298+
fn make_filesystem_need_recovery(&self) -> Result<()> {
299+
// Get the number of sectors in the filesystem.
300+
let num_sectors = {
301+
let sector_size = 512;
302+
u64::from(self.size_in_kilobytes) * 1024 / sector_size
303+
};
304+
305+
// Use losetup to create a block device from the file containing
306+
// the filesystem.
307+
let loop_dev = LoopDevice::new(&self.path)?;
308+
309+
// Create a device-mapper device using the dm-flakey
310+
// target. This target allows us to cut off writes at a certain
311+
// point, simulating a power failure. In its initial state
312+
// however, this acts as a simple pass-through device.
313+
let table = DmFlakey {
314+
start_sector: 0,
315+
num_sectors,
316+
block_dev: loop_dev.path().to_owned(),
317+
offset: 0,
318+
up_interval: 100,
319+
down_interval: 0,
320+
features: Vec::new(),
321+
};
322+
let dm_device = DmDevice::create("flakey-dev", &table.as_string())?;
323+
324+
// Mount the filesystem from the flakey device, and create a
325+
// bunch of directories.
326+
let mount = Mount::new(&dm_device.path(), ReadOnly(false))?;
327+
for i in 0..1000 {
328+
fs::create_dir(mount.path().join(format!("dir{i}")))?;
329+
}
330+
331+
// At this point, the directory blocks have likely been written
332+
// to the journal, but not yet written to their final locations
333+
// on disk. (This is somewhat timing dependant however, so this
334+
// whole function is called in a loop until the desired
335+
// conditions are met.)
336+
337+
// Change the device configuration so that all writes are
338+
// dropped. When the filesystem is unmounted below, any data not
339+
// already written will be lost.
340+
dm_device.suspend()?;
341+
let drop_writes_table = DmFlakey {
342+
up_interval: 0,
343+
down_interval: 100,
344+
features: vec!["drop_writes"],
345+
..table
346+
};
347+
dm_device.load_table(&drop_writes_table.as_string())?;
348+
dm_device.resume()?;
349+
350+
// Clean up.
351+
mount.unmount()?;
352+
dm_device.remove()?;
353+
loop_dev.detach()?;
354+
355+
Ok(())
356+
}
357+
261358
/// Check some properties of the filesystem.
262359
fn check(&self) -> Result<()> {
263360
self.check_dir_htree_depth("/medium_dir", 0)?;
@@ -412,6 +509,16 @@ fn create_test_data() -> Result<()> {
412509
disk.fill_ext2()?;
413510
zstd_compress(&disk.path)?;
414511

512+
let path = dir.join("test_disk_4k_block_journal.bin");
513+
let disk = DiskParams {
514+
path: path.to_owned(),
515+
size_in_kilobytes: 1024 * 64,
516+
fs_type: FsType::Ext4,
517+
block_size: 4096,
518+
};
519+
disk.create_with_journal()?;
520+
zstd_compress(&disk.path)?;
521+
415522
Ok(())
416523
}
417524

0 commit comments

Comments
 (0)