From 616c3f4a7fb199a525bcf148ed799516e13cf739 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 22 Jul 2023 11:25:39 +0200 Subject: [PATCH 1/3] util: extend run_ucmd_as_root for stdin/stdout This enables to give path to files (as str) which are then used as either stdin or stdout. --- tests/common/util.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 0fbc58cd5..6f4e76d42 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -2532,6 +2532,16 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< pub fn run_ucmd_as_root( ts: &TestScenario, args: &[&str], +) -> std::result::Result { + run_ucmd_as_root_with_stdin_stdout(ts, args, None, None) +} + +#[cfg(unix)] +pub fn run_ucmd_as_root_with_stdin_stdout( + ts: &TestScenario, + args: &[&str], + stdin: Option<&str>, + stdout: Option<&str>, ) -> std::result::Result { if is_ci() { Err(format!("{UUTILS_INFO}: {}", "cannot run inside CI")) @@ -2546,16 +2556,21 @@ pub fn run_ucmd_as_root( Ok(output) if String::from_utf8_lossy(&output.stdout).eq("root\n") => { // we can run sudo and we're root // run ucmd as root: - Ok(ts - .cmd("sudo") - .env("PATH", PATH) + let mut cmd = ts.cmd("sudo"); + cmd.env("PATH", PATH) .envs(DEFAULT_ENV) .arg("-E") .arg("--non-interactive") .arg(&ts.bin_path) .arg(&ts.util_name) - .args(args) - .run()) + .args(args); + if let Some(stdin) = stdin { + cmd.set_stdin(File::open(stdin).unwrap()); + } + if let Some(stdout) = stdout { + cmd.set_stdout(File::open(stdout).unwrap()); + } + Ok(cmd.run()) } Ok(output) if String::from_utf8_lossy(&output.stderr).eq("sudo: a password is required\n") => From 8a9b29ddfb5b0486c6461b6d85cebb64c63586a5 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 18 Mar 2023 08:54:47 +0100 Subject: [PATCH 2/3] dd: fix GNU test 'dd/skip-seek-past-dev' --- src/uu/dd/src/dd.rs | 57 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 4ac2aa780..b79ae22da 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -3,7 +3,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, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE +// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE mod datastructures; use datastructures::*; @@ -49,6 +49,8 @@ use nix::{ fcntl::{posix_fadvise, PosixFadviseAdvice}, }; use uucore::display::Quotable; +#[cfg(unix)] +use uucore::error::set_exit_code; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; #[cfg(target_os = "linux")] @@ -200,14 +202,25 @@ impl Source { Err(e) => Err(e), }, #[cfg(unix)] - Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) { - Ok(m) if m < n => { - show_error!("'standard input': cannot skip to specified offset"); - Ok(m) + Self::StdinFile(f) => { + if let Ok(Some(len)) = try_get_len_of_block_device(f) { + if len < n { + // GNU compatibility: + // this case prints the stats but sets the exit code to 1 + show_error!("'standard input': cannot skip: Invalid argument"); + set_exit_code(1); + return Ok(len); + } } - Ok(m) => Ok(m), - Err(e) => Err(e), - }, + match io::copy(&mut f.take(n), &mut io::sink()) { + Ok(m) if m < n => { + show_error!("'standard input': cannot skip to specified offset"); + Ok(m) + } + Ok(m) => Ok(m), + Err(e) => Err(e), + } + } Self::File(f) => f.seek(io::SeekFrom::Start(n)), #[cfg(unix)] Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()), @@ -527,7 +540,19 @@ impl Dest { fn seek(&mut self, n: u64) -> io::Result { match self { Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout), - Self::File(f, _) => f.seek(io::SeekFrom::Start(n)), + Self::File(f, _) => { + #[cfg(unix)] + if let Ok(Some(len)) = try_get_len_of_block_device(f) { + if len < n { + // GNU compatibility: + // this case prints the stats but sets the exit code to 1 + show_error!("'standard output': cannot seek: Invalid argument"); + set_exit_code(1); + return Ok(len); + } + } + f.seek(io::SeekFrom::Start(n)) + } #[cfg(unix)] Self::Fifo(f) => { // Seeking in a named pipe means *reading* from the pipe. @@ -1133,6 +1158,20 @@ fn is_stdout_redirected_to_seekable_file() -> bool { } } +/// Try to get the len if it is a block device +#[cfg(unix)] +fn try_get_len_of_block_device(file: &mut File) -> io::Result> { + let ftype = file.metadata()?.file_type(); + if !ftype.is_block_device() { + return Ok(None); + } + + // FIXME: this can be replaced by file.stream_len() when stable. + let len = file.seek(SeekFrom::End(0))?; + file.rewind()?; + Ok(Some(len)) +} + /// Decide whether the named file is a named pipe, also known as a FIFO. #[cfg(unix)] fn is_fifo(filename: &str) -> bool { From 17f4d17021148b038e2f2c7942eea6004e06a06c Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 22 Jul 2023 11:26:56 +0200 Subject: [PATCH 3/3] tests: dd: add skip-seek-past-dev tests These tests try to read or write past a block device, where the block device is either given as stdin or stdout. It requires access to the block device, and therefore is executed as root. For now, it is assumed that a block device "/dev/sda1" with a size smaller than 10000000000000000 exists. --- tests/by-util/test_dd.rs | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index fe38acca4..8ebf57c1c 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -4,6 +4,8 @@ // 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 +#[cfg(unix)] +use crate::common::util::run_ucmd_as_root_with_stdin_stdout; use crate::common::util::TestScenario; #[cfg(all(not(windows), feature = "printf"))] use crate::common::util::{UCommand, TESTS_BINARY}; @@ -1566,3 +1568,45 @@ fn test_nocache_file() { .succeeds() .stderr_only("2048+0 records in\n2048+0 records out\n"); } + +#[test] +#[cfg(unix)] +fn test_skip_past_dev() { + // NOTE: This test intends to trigger code which can only be reached with root permissions. + let ts = TestScenario::new(util_name!()); + + if let Ok(result) = run_ucmd_as_root_with_stdin_stdout( + &ts, + &["bs=1", "skip=10000000000000000", "count=0", "status=noxfer"], + Some("/dev/sda1"), + None, + ) { + result.stderr_contains("dd: 'standard input': cannot skip: Invalid argument"); + result.stderr_contains("0+0 records in"); + result.stderr_contains("0+0 records out"); + result.code_is(1); + } else { + print!("TEST SKIPPED"); + } +} + +#[test] +#[cfg(unix)] +fn test_seek_past_dev() { + // NOTE: This test intends to trigger code which can only be reached with root permissions. + let ts = TestScenario::new(util_name!()); + + if let Ok(result) = run_ucmd_as_root_with_stdin_stdout( + &ts, + &["bs=1", "seek=10000000000000000", "count=0", "status=noxfer"], + None, + Some("/dev/sda1"), + ) { + result.stderr_contains("dd: 'standard output': cannot seek: Invalid argument"); + result.stderr_contains("0+0 records in"); + result.stderr_contains("0+0 records out"); + result.code_is(1); + } else { + print!("TEST SKIPPED"); + } +}