Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "blkreader"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
authors = ["SF-Zhou"]
description = "Read file data directly from block device using extent information"
Expand Down
25 changes: 24 additions & 1 deletion src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ pub struct Options {
/// using regular file I/O instead of direct block device I/O.
/// This avoids the need for root privileges in such cases.
pub allow_fallback: bool,

/// Require reading the exact requested length.
///
/// When enabled, the read operation will return an error if the amount
/// of data read is less than the requested buffer size.
/// This is similar to the behavior of [`std::io::Read::read_exact`].
///
/// 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,
}

impl Default for Options {
Expand All @@ -41,6 +51,7 @@ impl Default for Options {
fill_holes: false,
zero_unwritten: false,
allow_fallback: false,
read_exact: false,
}
}
}
Expand Down Expand Up @@ -77,6 +88,15 @@ impl Options {
self.allow_fallback = allow;
self
}

/// Enable or disable requiring exact read length.
///
/// When enabled, the read will fail if less than the requested number of
/// bytes are read. When disabled, partial reads are allowed.
pub fn with_read_exact(mut self, exact: bool) -> Self {
self.read_exact = exact;
self
}
}

#[cfg(test)]
Expand All @@ -90,6 +110,7 @@ mod tests {
assert!(!opts.fill_holes);
assert!(!opts.zero_unwritten);
assert!(!opts.allow_fallback);
assert!(!opts.read_exact);
}

#[test]
Expand All @@ -98,11 +119,13 @@ mod tests {
.with_cache(false)
.with_fill_holes(true)
.with_zero_unwritten(true)
.with_allow_fallback(true);
.with_allow_fallback(true)
.with_read_exact(true);

assert!(!opts.enable_cache);
assert!(opts.fill_holes);
assert!(opts.zero_unwritten);
assert!(opts.allow_fallback);
assert!(opts.read_exact);
}
}
34 changes: 32 additions & 2 deletions src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,14 @@ impl<'a> ReadContext<'a> {
offset: u64,
extents: Vec<FiemapExtent>,
) -> io::Result<State> {
let bytes_read = FileExt::read_at(self.file, buf, offset)?;
// Check if we read the exact requested length
let bytes_read = if self.options.read_exact {
self.file.read_exact_at(buf, offset)?;
buf.len()
} else {
self.file.read_at(buf, offset)?
};

Ok(State::fallback(extents, bytes_read))
}

Expand Down Expand Up @@ -305,6 +312,18 @@ impl<'a> ReadContext<'a> {
}
}

// Check if we read the exact requested length
if self.options.read_exact && bytes_read < buf.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!(
"failed to fill entire buffer: expected {} bytes, got {} bytes",
buf.len(),
bytes_read
),
));
}

Ok(bytes_read)
}
}
Expand Down Expand Up @@ -370,12 +389,14 @@ mod tests {
.with_cache(false)
.with_fill_holes(true)
.with_zero_unwritten(true)
.with_allow_fallback(true);
.with_allow_fallback(true)
.with_read_exact(false);

assert!(!opts.enable_cache);
assert!(opts.fill_holes);
assert!(opts.zero_unwritten);
assert!(opts.allow_fallback);
assert!(!opts.read_exact);
}

#[test]
Expand Down Expand Up @@ -416,4 +437,13 @@ mod tests {
}];
assert!(!ctx.can_use_fallback(&extents, 0, 200));
}

#[test]
fn test_read_exact_builder() {
let opts = Options::new().with_read_exact(false);
assert!(!opts.read_exact);

let opts = opts.with_read_exact(true);
assert!(opts.read_exact);
}
}