1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

dd: support the [io]flag=nocache option

Add support for the `iflag=nocache` and `oflag=nocache` to make `dd`
discard the filesystem cache for the processed portion of the input or
output file.
This commit is contained in:
Jeffrey Finkelstein 2023-02-23 22:05:47 -05:00
parent cc2f97ba0d
commit bd18a2a344
4 changed files with 75 additions and 3 deletions

View file

@ -785,6 +785,25 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
// Optimization: if no blocks are to be written, then don't
// bother allocating any buffers.
if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count {
// Even though we are not reading anything from the input
// file, we still need to honor the `nocache` flag, which
// requests that we inform the system that we no longer
// need the contents of the input file in a system cache.
//
// TODO Better error handling for overflowing `len`.
if i.settings.iflags.nocache {
let offset = 0;
let len = i.src.len()?.try_into().unwrap();
i.discard_cache(offset, len);
}
// Similarly, discard the system cache for the output file.
//
// TODO Better error handling for overflowing `len`.
if i.settings.oflags.nocache {
let offset = 0;
let len = o.dst.len()?.try_into().unwrap();
o.discard_cache(offset, len);
}
return finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread);
};
@ -792,6 +811,13 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
// This is the max size needed.
let mut buf = vec![BUF_INIT_BYTE; bsize];
// Index in the input file where we are reading bytes and in
// the output file where we are writing bytes.
//
// These are updated on each iteration of the main loop.
let mut read_offset = 0;
let mut write_offset = 0;
// The main read/write loop.
//
// Each iteration reads blocks from the input and writes
@ -811,6 +837,30 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
}
let wstat_update = o.write_blocks(&buf)?;
// Discard the system file cache for the read portion of
// the input file.
//
// TODO Better error handling for overflowing `offset` and `len`.
let read_len = rstat_update.bytes_total;
if i.settings.iflags.nocache {
let offset = read_offset.try_into().unwrap();
let len = read_len.try_into().unwrap();
i.discard_cache(offset, len);
}
read_offset += read_len;
// Discard the system file cache for the written portion
// of the output file.
//
// TODO Better error handling for overflowing `offset` and `len`.
let write_len = wstat_update.bytes_total;
if o.settings.oflags.nocache {
let offset = write_offset.try_into().unwrap();
let len = write_len.try_into().unwrap();
o.discard_cache(offset, len);
}
write_offset += write_len;
// Update the read/write stats and inform the progress thread once per second.
//
// If the receiver is disconnected, `send()` returns an

View file

@ -304,7 +304,7 @@ impl Parser {
"directory" => linux_only!(f, i.directory = true),
"dsync" => linux_only!(f, i.dsync = true),
"sync" => linux_only!(f, i.sync = true),
"nocache" => return Err(ParseError::Unimplemented(f.to_string())),
"nocache" => linux_only!(f, i.nocache = true),
"nonblock" => linux_only!(f, i.nonblock = true),
"noatime" => linux_only!(f, i.noatime = true),
"noctty" => linux_only!(f, i.noctty = true),
@ -336,7 +336,7 @@ impl Parser {
"directory" => linux_only!(f, o.directory = true),
"dsync" => linux_only!(f, o.dsync = true),
"sync" => linux_only!(f, o.sync = true),
"nocache" => return Err(ParseError::Unimplemented(f.to_string())),
"nocache" => linux_only!(f, o.nocache = true),
"nonblock" => linux_only!(f, o.nonblock = true),
"noatime" => linux_only!(f, o.noatime = true),
"noctty" => linux_only!(f, o.noctty = true),

View file

@ -55,7 +55,7 @@ fn unimplemented_flags_should_error() {
let mut succeeded = Vec::new();
// The following flags are not implemented
for flag in ["cio", "nocache", "nolinks", "text", "binary"] {
for flag in ["cio", "nolinks", "text", "binary"] {
let args = vec![format!("iflag={flag}")];
if Parser::new()

View file

@ -1536,3 +1536,25 @@ fn test_multiple_processes_reading_stdin() {
.succeeds()
.stdout_only("def\n");
}
/// Test that discarding system file cache fails for stdin.
#[test]
#[cfg(target_os = "linux")]
fn test_nocache_stdin_error() {
new_ucmd!()
.args(&["iflag=nocache", "count=0", "status=noxfer"])
.fails()
.code_is(1)
.stderr_only("dd: failed to discard cache for: 'standard input': Illegal seek\n0+0 records in\n0+0 records out\n");
}
/// Test for discarding system file cache.
#[test]
#[cfg(target_os = "linux")]
fn test_nocache_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.write_bytes("f", b"a".repeat(1 << 20).as_slice());
ucmd.args(&["if=f", "of=/dev/null", "iflag=nocache", "status=noxfer"])
.succeeds()
.stderr_only("2048+0 records in\n2048+0 records out\n");
}