From b950a7e04fd60d15d59c0015650abdd58968dde8 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 | 42 ++++++++++++++++++++++++++- 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, 64 insertions(+), 4 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index ca09880ee36..d4a2e606140 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; @@ -45,7 +47,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"); @@ -746,6 +748,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); }; @@ -753,6 +770,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 @@ -772,6 +796,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"); +}