From b71c97e7dd301cee8d1150e25048db22a364a7fb Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Wed, 24 Dec 2025 14:25:35 +0000 Subject: [PATCH 1/2] dd: fix nocache flag handling at EOF --- src/uu/dd/src/dd.rs | 44 ++++++++++---------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 7cc4f73924d..833419ca902 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -219,17 +219,6 @@ impl Source { Self::StdinFile(f) } - /// The length of the data source in number of bytes. - /// - /// If it cannot be determined, then this function returns 0. - fn len(&self) -> io::Result { - #[allow(clippy::match_wildcard_for_single_variants)] - match self { - Self::File(f) => Ok(f.metadata()?.len().try_into().unwrap_or(i64::MAX)), - _ => Ok(0), - } - } - fn skip(&mut self, n: u64) -> io::Result { match self { #[cfg(not(unix))] @@ -668,17 +657,6 @@ impl Dest { _ => Err(Errno::ESPIPE), // "Illegal seek" } } - - /// The length of the data destination in number of bytes. - /// - /// If it cannot be determined, then this function returns 0. - fn len(&self) -> io::Result { - #[allow(clippy::match_wildcard_for_single_variants)] - match self { - Self::File(f, _) => Ok(f.metadata()?.len().try_into().unwrap_or(i64::MAX)), - _ => Ok(0), - } - } } /// Decide whether the given buffer is all zeros. @@ -1053,21 +1031,12 @@ impl BlockWriter<'_> { /// depending on the command line arguments, this function /// informs the OS to flush/discard the caches for input and/or output file. fn flush_caches_full_length(i: &Input, o: &Output) -> io::Result<()> { - // TODO Better error handling for overflowing `len`. + // Using len=0 in posix_fadvise means "to end of file" if i.settings.iflags.nocache { - let offset = 0; - #[allow(clippy::useless_conversion)] - let len = i.src.len()?.try_into().unwrap(); - i.discard_cache(offset, len); + i.discard_cache(0, 0); } - // Similarly, discard the system cache for the output file. - // - // TODO Better error handling for overflowing `len`. if i.settings.oflags.nocache { - let offset = 0; - #[allow(clippy::useless_conversion)] - let len = o.dst.len()?.try_into().unwrap(); - o.discard_cache(offset, len); + o.discard_cache(0, 0); } Ok(()) @@ -1175,6 +1144,7 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> { let input_nocache = i.settings.iflags.nocache; let output_nocache = o.settings.oflags.nocache; + let output_direct = o.settings.oflags.direct; // Add partial block buffering, if needed. let mut o = if o.settings.buffered { @@ -1198,6 +1168,12 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> { let loop_bsize = calc_loop_bsize(i.settings.count, &rstat, &wstat, i.settings.ibs, bsize); let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?; if rstat_update.is_empty() { + if input_nocache { + i.discard_cache(read_offset.try_into().unwrap(), 0); + } + if output_nocache || output_direct { + o.discard_cache(write_offset.try_into().unwrap(), 0); + } break; } let wstat_update = o.write_blocks(&buf)?; From ae0b15b2c56da53d1629365f0c2901c926f34ec5 Mon Sep 17 00:00:00 2001 From: Christopher Dryden Date: Tue, 30 Dec 2025 20:59:44 +0000 Subject: [PATCH 2/2] Add tests for nocache EOF handling --- tests/by-util/test_dd.rs | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index a6a52e66fb5..35a1561e498 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg fifoname seekable +// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg fifoname seekable fadvise FADV DONTNEED use uutests::at_and_ucmd; use uutests::new_ucmd; @@ -1840,3 +1840,52 @@ fn test_skip_overflow() { "dd: invalid number: ‘9223372036854775808’: Value too large for defined data type", ); } + +#[test] +#[cfg(target_os = "linux")] +fn test_nocache_eof() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write_bytes("in.f", &vec![0u8; 1234567]); + ucmd.args(&[ + "if=in.f", + "of=out.f", + "bs=1M", + "oflag=nocache,sync", + "status=noxfer", + ]) + .succeeds(); + assert_eq!(at.read_bytes("out.f").len(), 1234567); +} + +#[test] +#[cfg(all(target_os = "linux", feature = "printf"))] +fn test_nocache_eof_fadvise_zero_length() { + use std::process::Command; + let (at, _ucmd) = at_and_ucmd!(); + at.write_bytes("in.f", &vec![0u8; 1234567]); + + let strace_file = at.plus_as_string("strace.out"); + let result = Command::new("strace") + .args(["-o", &strace_file, "-e", "fadvise64,fadvise64_64"]) + .arg(get_tests_binary()) + .args([ + "dd", + "if=in.f", + "of=out.f", + "bs=1M", + "oflag=nocache,sync", + "status=none", + ]) + .current_dir(at.as_string()) + .output(); + + if result.is_err() { + return; // strace not available + } + + let strace = at.read("strace.out"); + assert!( + strace.contains(", 0, POSIX_FADV_DONTNEED"), + "Expected len=0 at EOF: {strace}" + ); +}