From 01419f82f112e0fd790995c6ca223a5bc7e9c926 Mon Sep 17 00:00:00 2001 From: SF-Zhou Date: Sun, 18 Jan 2026 21:11:56 +0800 Subject: [PATCH] bump version to v0.1.2 1. add `dry_run` option --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 11 +++++++++++ src/bin/blkreader.rs | 7 ++++++- src/options.rs | 30 +++++++++++++++++++++++++++++- src/reader.rs | 24 +++++++++++++++++++----- 6 files changed, 67 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 443fcb8..b0ac402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "blkreader" -version = "0.1.1" +version = "0.1.2" dependencies = [ "blkmap", "blkpath", diff --git a/Cargo.toml b/Cargo.toml index b29308e..01f6504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blkreader" -version = "0.1.1" +version = "0.1.2" edition = "2021" authors = ["SF-Zhou"] description = "Read file data directly from block device using extent information" diff --git a/README.md b/README.md index 481ca1e..27a00de 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ blkreader /path/to/file --allow-fallback | `--zero-unwritten` | Fill unwritten extents with zeros instead of reading raw block data | | `--allow-fallback` | Allow fallback to regular file I/O when safe | | `--no-cache` | Disable block device caching | +| `--dry-run` | Skip actual device reads (for testing extent mapping) | ## Options @@ -175,6 +176,16 @@ When disabled (default), unwritten extents are read directly from the block devi When enabled, if the queried extents fully cover the read range and contain no unwritten extents, the read will be performed using regular file I/O instead of direct block device I/O. This avoids the need for root privileges in such cases. +### `dry_run` (default: `false`) + +When enabled, no actual I/O operations are performed on block devices or files. Instead, the operation pretends to successfully read the requested amount of data. This is useful for: + +- Testing extent mapping logic without performing time-consuming I/O operations +- Validating that a file's extents are accessible +- Debugging and development without needing root privileges + +The extent information is still queried via FIEMAP to ensure the file structure is valid, but the actual data reading step is skipped. + ## Direct I/O Alignment Requirements When using the library API to read directly from block devices (not using fallback mode), the following alignment requirements must be met: diff --git a/src/bin/blkreader.rs b/src/bin/blkreader.rs index e3279e6..59da7e3 100644 --- a/src/bin/blkreader.rs +++ b/src/bin/blkreader.rs @@ -57,6 +57,10 @@ struct Args { #[arg(long)] no_cache: bool, + /// Dry run mode - skip actual device reads + #[arg(long)] + dry_run: bool, + /// Alignment for direct IO. #[arg(long, default_value_t = 512)] alignment: u64, @@ -130,7 +134,8 @@ fn run(args: &Args) -> io::Result<()> { .with_cache(!args.no_cache) .with_fill_holes(args.fill_holes) .with_zero_unwritten(args.zero_unwritten) - .with_allow_fallback(args.allow_fallback); + .with_allow_fallback(args.allow_fallback) + .with_dry_run(args.dry_run); // Open output file or use stdout let mut output: Box = if let Some(output_path) = &args.output { diff --git a/src/options.rs b/src/options.rs index e31ab23..3fe3bc8 100644 --- a/src/options.rs +++ b/src/options.rs @@ -42,6 +42,19 @@ pub struct Options { /// When disabled, partial reads are allowed and the actual number of /// bytes read is returned (similar to [`std::io::Read::read`]). pub read_exact: bool, + + /// Dry run mode - skip actual device reads. + /// + /// When enabled, no actual I/O operations are performed on block devices + /// or files. Instead, the operation pretends to successfully read the + /// requested amount of data. + /// + /// This is useful for testing the extent mapping logic and validating + /// that a file's extents are accessible without performing time-consuming + /// I/O operations. + /// + /// When disabled (default), normal read operations are performed. + pub dry_run: bool, } impl Default for Options { @@ -52,6 +65,7 @@ impl Default for Options { zero_unwritten: false, allow_fallback: false, read_exact: false, + dry_run: false, } } } @@ -97,6 +111,17 @@ impl Options { self.read_exact = exact; self } + + /// Enable or disable dry run mode. + /// + /// When enabled, no actual I/O operations are performed. Instead, the + /// operation pretends to successfully read the requested amount of data. + /// This is useful for testing extent mapping logic without performing + /// time-consuming I/O operations. + pub fn with_dry_run(mut self, dry_run: bool) -> Self { + self.dry_run = dry_run; + self + } } #[cfg(test)] @@ -111,6 +136,7 @@ mod tests { assert!(!opts.zero_unwritten); assert!(!opts.allow_fallback); assert!(!opts.read_exact); + assert!(!opts.dry_run); } #[test] @@ -120,12 +146,14 @@ mod tests { .with_fill_holes(true) .with_zero_unwritten(true) .with_allow_fallback(true) - .with_read_exact(true); + .with_read_exact(true) + .with_dry_run(true); assert!(!opts.enable_cache); assert!(opts.fill_holes); assert!(opts.zero_unwritten); assert!(opts.allow_fallback); assert!(opts.read_exact); + assert!(opts.dry_run); } } diff --git a/src/reader.rs b/src/reader.rs index 7de59b3..1a31071 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -182,7 +182,10 @@ impl<'a> ReadContext<'a> { extents: Vec, ) -> io::Result { // Check if we read the exact requested length - let bytes_read = if self.options.read_exact { + let bytes_read = if self.options.dry_run { + // In dry run mode, simulate read without actual I/O + buf.len() + } else if self.options.read_exact { self.file.read_exact_at(buf, offset)?; buf.len() } else { @@ -290,7 +293,11 @@ impl<'a> ReadContext<'a> { // Read from device let buf_start = bytes_read; let buf_end = buf_start + read_len; - let actual_read = device.read_at(&mut buf[buf_start..buf_end], physical_offset)?; + let actual_read = device.read_at( + &mut buf[buf_start..buf_end], + physical_offset, + self.options.dry_run, + )?; bytes_read += actual_read; current_offset = read_start + actual_read as u64; @@ -344,13 +351,18 @@ impl DeviceHandle { } /// Read data from the device at the specified physical offset. - fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result { + fn read_at(&self, buf: &mut [u8], offset: u64, dry_run: bool) -> io::Result { let file = match self { DeviceHandle::Cached(cached) => &cached.file, DeviceHandle::Uncached(uncached) => &uncached.file, }; - let bytes = FileExt::read_at(file, buf, offset)?; + let bytes = if dry_run { + // In dry run mode, simulate read without actual I/O + buf.len() + } else { + FileExt::read_at(file, buf, offset)? + }; Ok(bytes) } } @@ -390,13 +402,15 @@ mod tests { .with_fill_holes(true) .with_zero_unwritten(true) .with_allow_fallback(true) - .with_read_exact(false); + .with_read_exact(false) + .with_dry_run(true); assert!(!opts.enable_cache); assert!(opts.fill_holes); assert!(opts.zero_unwritten); assert!(opts.allow_fallback); assert!(!opts.read_exact); + assert!(opts.dry_run); } #[test]