diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 3326c34de14..49efb77efd3 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -32,6 +32,8 @@ use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write}; use std::os::unix::fs::FileTypeExt; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::unix::fs::OpenOptionsExt; +#[cfg(unix)] +use std::os::unix::io::AsRawFd; use std::path::Path; use std::sync::mpsc; use std::thread; @@ -43,7 +45,7 @@ use nix; use nix::errno::Errno; 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"); @@ -726,6 +728,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); }; @@ -733,6 +750,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 @@ -752,6 +776,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 5deeb12f013..e3fc957325c 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1514,3 +1514,23 @@ fn test_skip_input_fifo() { assert!(output.stdout.is_empty()); assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\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"); +}