Skip to content

Commit

Permalink
dd: support the [io]flag=nocache option
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jfinkels committed Mar 28, 2023
1 parent e2cc1d4 commit 8a7cdf7
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 3 deletions.
38 changes: 38 additions & 0 deletions src/uu/dd/src/dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -785,13 +785,35 @@ 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.
if i.settings.iflags.nocache {
let offset = 0;
let len = i.src.len()?;
i.discard_cache(offset, len);
}
// Similarly, discard the system cache for the output file.
if i.settings.oflags.nocache {
let offset = 0;
let len = o.dst.len()?;
o.discard_cache(offset, len);
}
return finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread);
};

// Create a common buffer with a capacity of the block size.
// 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
Expand All @@ -811,6 +833,22 @@ 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.
let read_len: i64 = rstat_update.bytes_total.try_into().unwrap();
if i.settings.iflags.nocache {
i.discard_cache(read_offset, read_len);
}
read_offset += read_len;

// Discard the system file cache for the written portion
// of the output file.
let write_len: i64 = wstat_update.bytes_total.try_into().unwrap();
if o.settings.oflags.nocache {
o.discard_cache(write_offset, write_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
Expand Down
4 changes: 2 additions & 2 deletions src/uu/dd/src/parseargs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,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),
Expand Down Expand Up @@ -334,7 +334,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),
Expand Down
2 changes: 1 addition & 1 deletion src/uu/dd/src/parseargs/unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
22 changes: 22 additions & 0 deletions tests/by-util/test_dd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

0 comments on commit 8a7cdf7

Please sign in to comment.