From 96c85a3307750de0ae98db0d3a5e1145ad618bc9 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 23 Feb 2023 22:05:47 -0500 Subject: [PATCH] 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. --- src/uu/dd/src/dd.rs | 40 ++++++++++++++++++++++++++- src/uu/dd/src/parseargs.rs | 4 +-- src/uu/dd/src/parseargs/unit_tests.rs | 2 +- tests/by-util/test_dd.rs | 20 ++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 53e6e190ef9..c99424d5348 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -48,7 +48,7 @@ use nix::{ fcntl::{posix_fadvise, PosixFadviseAdvice}, }; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult}; +use uucore::error::{set_exit_code, FromIo, UResult}; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; const ABOUT: &str = help_about!("dd.md"); @@ -787,6 +787,21 @@ 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); }; @@ -794,6 +809,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 @@ -813,6 +835,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 diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 96f6ebfaa6c..82031d3796d 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -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), @@ -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), diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index a135c3572da..54e17b882e2 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -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() diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index b9a23495587..4aaf46259d9 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1528,3 +1528,23 @@ fn test_multiple_processes_reading_stdin() { .succeeds() .stdout_only("def\n"); } + +/// Test that discarding system file cache fails for stdin. +#[test] +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] +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"); +}