From 35a044b532a94c5305b68bf3c037d45ea2d24a6d Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Fri, 3 Jan 2025 17:42:28 +0700 Subject: [PATCH 01/24] uucore/buf-copy: delete redundant functions --- src/uucore/src/lib/features/buf_copy.rs | 91 +------------ src/uucore/src/lib/features/buf_copy/linux.rs | 128 +----------------- src/uucore/src/lib/features/buf_copy/other.rs | 2 + 3 files changed, 8 insertions(+), 213 deletions(-) diff --git a/src/uucore/src/lib/features/buf_copy.rs b/src/uucore/src/lib/features/buf_copy.rs index d82f8d4d1c..16138e67fa 100644 --- a/src/uucore/src/lib/features/buf_copy.rs +++ b/src/uucore/src/lib/features/buf_copy.rs @@ -38,7 +38,7 @@ mod tests { }; #[cfg(any(target_os = "linux", target_os = "android"))] - use {nix::unistd, std::os::fd::AsRawFd}; + use std::os::fd::AsRawFd; use std::io::{Read, Write}; @@ -53,67 +53,6 @@ mod tests { .unwrap() } - #[cfg(any(target_os = "linux", target_os = "android"))] - #[test] - fn test_file_is_pipe() { - let temp_file = new_temp_file(); - let (pipe_read, pipe_write) = pipes::pipe().unwrap(); - - assert!(is_pipe(&pipe_read).unwrap()); - assert!(is_pipe(&pipe_write).unwrap()); - assert!(!is_pipe(&temp_file).unwrap()); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - #[test] - fn test_valid_splice_errs() { - use nix::errno::Errno; - use nix::Error; - - let err = Error::from(Errno::EINVAL); - assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); - - let err = Error::from(Errno::ENOSYS); - assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); - - let err = Error::from(Errno::EBADF); - assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); - - let err = Error::from(Errno::EPERM); - assert!(maybe_unsupported(err).is_err()); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - #[test] - fn test_splice_data_to_pipe() { - let (pipe_read, pipe_write) = pipes::pipe().unwrap(); - let data = b"Hello, world!"; - let (bytes, _) = splice_data_to_pipe(data, &pipe_write).unwrap(); - let mut buf = [0; 1024]; - let n = unistd::read(pipe_read.as_raw_fd(), &mut buf).unwrap(); - assert_eq!(&buf[..n], data); - assert_eq!(bytes as usize, data.len()); - } - - #[cfg(any(target_os = "linux", target_os = "android"))] - #[test] - fn test_splice_data_to_file() { - use std::io::{Read, Seek, SeekFrom}; - - let mut temp_file = new_temp_file(); - let (pipe_read, pipe_write) = pipes::pipe().unwrap(); - let data = b"Hello, world!"; - let (bytes, _) = splice_data_to_fd(data, &pipe_read, &pipe_write, &temp_file).unwrap(); - assert_eq!(bytes as usize, data.len()); - - // We would have been at the end already, so seek again to the start. - temp_file.seek(SeekFrom::Start(0)).unwrap(); - - let mut buf = Vec::new(); - temp_file.read_to_end(&mut buf).unwrap(); - assert_eq!(buf, data); - } - #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_copy_exact() { @@ -176,32 +115,4 @@ mod tests { assert_eq!(bytes_copied as usize, data.len()); assert_eq!(buf, data); } - - #[cfg(any(target_os = "linux", target_os = "android"))] - #[test] - fn test_splice_write() { - use std::{ - io::{Read, Seek, SeekFrom, Write}, - thread, - }; - - let (pipe_read, mut pipe_write) = pipes::pipe().unwrap(); - let mut dest_file = new_temp_file(); - let data = b"Hello, world!"; - let thread = thread::spawn(move || { - pipe_write.write_all(data).unwrap(); - }); - let (bytes, _) = splice_write(&pipe_read, &dest_file).unwrap(); - thread.join().unwrap(); - - assert!(bytes == data.len() as u64); - - // We would have been at the end already, so seek again to the start. - dest_file.seek(SeekFrom::Start(0)).unwrap(); - - let mut buf = Vec::new(); - dest_file.read_to_end(&mut buf).unwrap(); - - assert_eq!(buf, data); - } } diff --git a/src/uucore/src/lib/features/buf_copy/linux.rs b/src/uucore/src/lib/features/buf_copy/linux.rs index 7c41f14a58..7ae5b2bd02 100644 --- a/src/uucore/src/lib/features/buf_copy/linux.rs +++ b/src/uucore/src/lib/features/buf_copy/linux.rs @@ -3,20 +3,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use nix::sys::stat::fstat; -use nix::{errno::Errno, libc::S_IFIFO}; +//! Buffer-based copying implementation for Linux and Android. -type Result = std::result::Result; - -/// Buffer-based copying utilities for Linux and Android. use crate::{ error::UResult, - pipes::{pipe, splice, splice_exact, vmsplice}, + pipes::{pipe, splice, splice_exact}, }; /// Buffer-based copying utilities for unix (excluding Linux). use std::{ - fs::File, io::{Read, Write}, os::fd::{AsFd, AsRawFd, RawFd}, }; @@ -51,8 +46,9 @@ impl From for Error { /// not use any intermediate user-space buffer. It falls backs to /// `std::io::copy` when the call fails and is still recoverable. /// -/// # Arguments * `source` - `Read` implementor to copy data from. * `dest` - -/// `Write` implementor to copy data to. +/// # Arguments +/// * `source` - `Read` implementor to copy data from. +/// * `dest` - `Write` implementor to copy data to. /// /// # Returns /// @@ -146,117 +142,3 @@ pub(crate) fn copy_exact( } Ok(written) } - -// The generalization of this function (and other splice_data functions) is not trivial as most -// utilities will just write data finitely. However, `yes`, which is the sole crate using these -// functions as of now, continuously loops the data write. Coupling the `is_pipe` check together -// with the data write logic means that the check has to be done for every single write, which adds -// unnecessary overhead. -// -/// Helper function to determine whether a given handle (such as a file) is a pipe or not. Can be -/// used to determine whether to use the `splice_data_to_pipe` or the `splice_data_to_fd` function. -/// This function is available exclusively to Linux and Android as it is meant to be used at the -/// scope of splice operations. -/// -/// -/// # Arguments -/// * `out` - path of handle -/// -/// # Returns -/// A `bool` indicating whether the given handle is a pipe or not. -#[inline] -pub fn is_pipe

(path: &P) -> Result -where - P: AsRawFd, -{ - Ok(fstat(path.as_raw_fd())?.st_mode as nix::libc::mode_t & S_IFIFO != 0) -} - -/// Write input `bytes` to a handle using a temporary pipe. A `vmsplice()` call -/// is issued to write to the temporary pipe, which then gets written to the -/// final destination using `splice()`. -/// -/// # Arguments * `bytes` - data to be written * `dest` - destination handle -/// -/// # Returns When write succeeds, the amount of bytes written is returned as a -/// `u64`. The `bool` indicates if we need to fall back to normal copying or -/// not. `true` means we need to fall back, `false` means we don't have to. -/// -/// A `UError` error is returned when the operation is not supported or when an -/// I/O error occurs. -pub fn splice_data_to_fd( - bytes: &[u8], - read_pipe: &File, - write_pipe: &File, - dest: &T, -) -> UResult<(u64, bool)> { - let mut n_bytes: u64 = 0; - let mut bytes = bytes; - while !bytes.is_empty() { - let len = match vmsplice(&write_pipe, bytes) { - Ok(n) => n, - Err(e) => return Ok(maybe_unsupported(e)?), - }; - if let Err(e) = splice_exact(&read_pipe, dest, len) { - return Ok(maybe_unsupported(e)?); - } - bytes = &bytes[len..]; - n_bytes += len as u64; - } - Ok((n_bytes, false)) -} - -/// Write input `bytes` to a file descriptor. This uses the Linux-specific -/// `vmsplice()` call to write into a file descriptor directly, which only works -/// if the destination is a pipe. -/// -/// # Arguments -/// * `bytes` - data to be written -/// * `dest` - destination handle -/// -/// # Returns -/// When write succeeds, the amount of bytes written is returned as a -/// `u64`. The `bool` indicates if we need to fall back to normal copying or -/// not. `true` means we need to fall back, `false` means we don't have to. -/// -/// A `UError` error is returned when the operation is not supported or when an -/// I/O error occurs. -#[cfg(any(target_os = "linux", target_os = "android"))] -pub fn splice_data_to_pipe(bytes: &[u8], dest: &T) -> UResult<(u64, bool)> -where - T: AsRawFd + AsFd, -{ - let mut n_bytes: u64 = 0; - let mut bytes = bytes; - while !bytes.is_empty() { - let len = match vmsplice(dest, bytes) { - Ok(n) => n, - // The maybe_unsupported call below may emit an error, when the - // error is considered as unrecoverable error (ones that won't make - // us fall back to other method) - Err(e) => return Ok(maybe_unsupported(e)?), - }; - bytes = &bytes[len..]; - n_bytes += len as u64; - } - Ok((n_bytes, false)) -} - -/// Several error values from `nix::Error` (`EINVAL`, `ENOSYS`, and `EBADF`) get -/// treated as errors indicating that the `splice` call is not available, i.e we -/// can still recover from the error. Thus, return the final result of the call -/// as `Result` and indicate that we have to fall back using other write method. -/// -/// # Arguments -/// * `error` - the `nix::Error` received -/// -/// # Returns -/// Result with tuple containing a `u64` `0` indicating that no data had been -/// written and a `true` indicating we have to fall back, if error is still -/// recoverable. Returns an `Error` implementing `UError` otherwise. -pub(crate) fn maybe_unsupported(error: nix::Error) -> Result<(u64, bool)> { - match error { - Errno::EINVAL | Errno::ENOSYS | Errno::EBADF => Ok((0, true)), - _ => Err(error.into()), - } -} diff --git a/src/uucore/src/lib/features/buf_copy/other.rs b/src/uucore/src/lib/features/buf_copy/other.rs index 6497c9224c..61dd13e6ce 100644 --- a/src/uucore/src/lib/features/buf_copy/other.rs +++ b/src/uucore/src/lib/features/buf_copy/other.rs @@ -2,6 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +//! +//! Buffer-based copying implementation for other platforms. use std::io::{Read, Write}; From 5fe6706c510945524942813159478527f2323568 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Sat, 4 Jan 2025 11:30:57 +0700 Subject: [PATCH 02/24] head: refactor to use specific error enums --- Cargo.lock | 1 + src/uu/head/Cargo.toml | 1 + src/uu/head/src/head.rs | 75 ++++++++++++++++++++++++++++------------- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfca23683a..e40171f46f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2770,6 +2770,7 @@ version = "0.0.28" dependencies = [ "clap", "memchr", + "thiserror 2.0.9", "uucore", ] diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 5e2b977b8a..1ccf3b9851 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -19,6 +19,7 @@ path = "src/head.rs" [dependencies] clap = { workspace = true } memchr = { workspace = true } +thiserror = { workspace = true } uucore = { workspace = true, features = ["ringbuffer", "lines", "fs"] } [[bin]] diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 04751fa71e..70f9653f2f 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -8,8 +8,10 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::ffi::OsString; use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; +use std::num::TryFromIntError; +use thiserror::Error; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::error::{FromIo, UError, UResult}; use uucore::line_ending::LineEnding; use uucore::lines::lines; use uucore::{format_usage, help_about, help_usage, show}; @@ -37,6 +39,36 @@ mod take; use take::take_all_but; use take::take_lines; +#[derive(Error, Debug)] +enum HeadError { + /// Wrapper around `io::Error` + #[error("error reading {name}: {err}")] + Io { name: String, err: io::Error }, + + #[error("parse error: {0}")] + ParseError(String), + + #[error("bad argument encoding")] + BadEncoding, + + #[error("{0}: number of -bytes or -lines is too large")] + NumTooLarge(#[from] TryFromIntError), + + #[error("clap error: {0}")] + Clap(#[from] clap::Error), + + #[error("{0}")] + MatchOption(String), +} + +impl UError for HeadError { + fn code(&self) -> i32 { + 1 + } +} + +type HeadResult = Result; + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) @@ -152,7 +184,7 @@ impl Mode { fn arg_iterate<'a>( mut args: impl uucore::Args + 'a, -) -> UResult + 'a>> { +) -> HeadResult + 'a>> { // argv[0] is always present let first = args.next().unwrap(); if let Some(second) = args.next() { @@ -160,22 +192,19 @@ fn arg_iterate<'a>( match parse::parse_obsolete(s) { Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))), Some(Err(e)) => match e { - parse::ParseError::Syntax => Err(USimpleError::new( - 1, - format!("bad argument format: {}", s.quote()), - )), - parse::ParseError::Overflow => Err(USimpleError::new( - 1, - format!( - "invalid argument: {} Value too large for defined datatype", - s.quote() - ), - )), + parse::ParseError::Syntax => Err(HeadError::ParseError(format!( + "bad argument format: {}", + s.quote() + ))), + parse::ParseError::Overflow => Err(HeadError::ParseError(format!( + "invalid argument: {} Value too large for defined datatype", + s.quote() + ))), }, None => Ok(Box::new(vec![first, second].into_iter().chain(args))), } } else { - Err(USimpleError::new(1, "bad argument encoding".to_owned())) + Err(HeadError::BadEncoding) } } else { Ok(Box::new(vec![first].into_iter())) @@ -247,10 +276,7 @@ fn catch_too_large_numbers_in_backwards_bytes_or_lines(n: u64) -> Option match usize::try_from(n) { Ok(value) => Some(value), Err(e) => { - show!(USimpleError::new( - 1, - format!("{e}: number of -bytes or -lines is too large") - )); + show!(HeadError::NumTooLarge(e)); None } } @@ -511,16 +537,17 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { head_file(&mut file, options) } }; - if res.is_err() { + if let Err(e) = res { let name = if file.as_str() == "-" { "standard input" } else { file }; - show!(USimpleError::new( - 1, - format!("error reading {name}: Input/output error") - )); + return Err(HeadError::Io { + name: name.to_string(), + err: e, + } + .into()); } first = false; } @@ -537,7 +564,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = match HeadOptions::get_from(&matches) { Ok(o) => o, Err(s) => { - return Err(USimpleError::new(1, s)); + return Err(HeadError::MatchOption(s).into()); } }; uu_head(&args) From 9202f23787fdf7fdfdf8e2e603a5b1092b1b6133 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Sat, 4 Jan 2025 11:31:37 +0700 Subject: [PATCH 03/24] tests/head: add test to write to /dev/full --- tests/by-util/test_head.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 1d4b159633..6d7ecffb2d 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -475,3 +475,25 @@ fn test_all_but_last_lines() { .succeeds() .stdout_is_fixture("lorem_ipsum_backwards_15_lines.expected"); } + +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +#[test] +fn test_write_to_dev_full() { + use std::fs::OpenOptions; + + for append in [true, false] { + { + let dev_full = OpenOptions::new() + .write(true) + .append(append) + .open("/dev/full") + .unwrap(); + + new_ucmd!() + .pipe_in_fixture(INPUT) + .set_stdout(dev_full) + .run() + .stderr_contains("No space left on device"); + } + } +} From 509b755b3b19e753cc7bf1d577dec4c7033c8b06 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Sat, 4 Jan 2025 11:33:30 +0700 Subject: [PATCH 04/24] head: make process fail when writing to /dev/full --- src/uu/head/src/head.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 70f9653f2f..52d52f13bb 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -3,11 +3,11 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (vars) BUFWRITER seekable +// spell-checker:ignore (vars) seekable use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::ffi::OsString; -use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; +use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::num::TryFromIntError; use thiserror::Error; use uucore::display::Quotable; @@ -18,9 +18,6 @@ use uucore::{format_usage, help_about, help_usage, show}; const BUF_SIZE: usize = 65536; -/// The capacity in bytes for buffered writers. -const BUFWRITER_CAPACITY: usize = 16_384; // 16 kilobytes - const ABOUT: &str = help_about!("head.md"); const USAGE: &str = help_usage!("head.md"); @@ -255,6 +252,11 @@ where io::copy(&mut reader, &mut stdout)?; + // Make sure we finish writing everything to the target before + // exiting. Otherwise, when Rust is implicitly flushing, any + // error will be silently ignored. + stdout.flush()?; + Ok(()) } @@ -263,11 +265,14 @@ fn read_n_lines(input: &mut impl std::io::BufRead, n: u64, separator: u8) -> std let mut reader = take_lines(input, n, separator); // Write those bytes to `stdout`. - let stdout = std::io::stdout(); - let stdout = stdout.lock(); - let mut writer = BufWriter::with_capacity(BUFWRITER_CAPACITY, stdout); + let mut stdout = std::io::stdout(); + + io::copy(&mut reader, &mut stdout)?; - io::copy(&mut reader, &mut writer)?; + // Make sure we finish writing everything to the target before + // exiting. Otherwise, when Rust is implicitly flushing, any + // error will be silently ignored. + stdout.flush()?; Ok(()) } From 934e85f4cd255cbdc1aa5bc6e02617809ac4eb78 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 6 Jan 2025 09:40:41 +0100 Subject: [PATCH 05/24] cp: make --backup and --no-clobber are mutually exclusive (#7082) * cp: make --backup and --no-clobber are mutually exclusive Should fix tests/cp/cp-i.sh * simplify the test Co-authored-by: Daniel Hofstetter --------- Co-authored-by: Daniel Hofstetter --- src/uu/cp/src/cp.rs | 10 ++++++++++ tests/by-util/test_cp.rs | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index c1ebc20bf8..b87898c783 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -932,6 +932,16 @@ impl Options { }; let update_mode = update_control::determine_update_mode(matches); + if backup_mode != BackupMode::NoBackup + && matches + .get_one::(update_control::arguments::OPT_UPDATE) + .map_or(false, |v| v == "none" || v == "none-fail") + { + return Err(Error::InvalidArgument( + "--backup is mutually exclusive with -n or --update=none-fail".to_string(), + )); + } + let backup_suffix = backup_control::determine_backup_suffix(matches); let overwrite = OverwriteMode::from_matches(matches); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index fc56dc3160..2132b93636 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2422,6 +2422,17 @@ fn test_cp_reflink_bad() { .stderr_contains("error: invalid value 'bad' for '--reflink[=]'"); } +#[test] +fn test_cp_conflicting_update() { + new_ucmd!() + .arg("-b") + .arg("--update=none") + .arg("a") + .arg("b") + .fails() + .stderr_contains("--backup is mutually exclusive with -n or --update=none-fail"); +} + #[test] #[cfg(any(target_os = "linux", target_os = "android"))] fn test_cp_reflink_insufficient_permission() { From d8d635d01429afaae8cf2998289b0bbf7c5416cd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Jan 2025 09:51:41 +0100 Subject: [PATCH 06/24] echo: add support for POSIXLY_CORRECT --- src/uu/echo/src/echo.rs | 56 ++++++++++++++++++++------------------ tests/by-util/test_echo.rs | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 097e4f2e98..228b5a0c12 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -4,8 +4,8 @@ // file that was distributed with this source code. use clap::builder::ValueParser; -use clap::parser::ValuesRef; -use clap::{crate_version, Arg, ArgAction, Command}; +use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; +use std::env; use std::ffi::{OsStr, OsString}; use std::io::{self, StdoutLock, Write}; use std::iter::Peekable; @@ -272,35 +272,37 @@ fn handle_double_hyphens(args: impl uucore::Args) -> impl uucore::Args { result.into_iter() } +fn collect_args(matches: &ArgMatches) -> Vec { + matches + .get_many::(options::STRING) + .map_or_else(Vec::new, |values| values.cloned().collect()) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from(handle_double_hyphens(args)); + let is_posixly_correct = env::var("POSIXLY_CORRECT").is_ok(); - // TODO - // "If the POSIXLY_CORRECT environment variable is set, then when echo’s first argument is not -n it outputs option-like arguments instead of treating them as options." - // https://www.gnu.org/software/coreutils/manual/html_node/echo-invocation.html + let (args, trailing_newline, escaped) = if is_posixly_correct { + let mut args_iter = args.skip(1).peekable(); - let trailing_newline = !matches.get_flag(options::NO_NEWLINE); - let escaped = matches.get_flag(options::ENABLE_BACKSLASH_ESCAPE); + if args_iter.peek() == Some(&OsString::from("-n")) { + let matches = uu_app().get_matches_from(handle_double_hyphens(args_iter)); + let args = collect_args(&matches); + (args, false, true) + } else { + let args: Vec<_> = args_iter.collect(); + (args, true, true) + } + } else { + let matches = uu_app().get_matches_from(handle_double_hyphens(args.into_iter())); + let trailing_newline = !matches.get_flag(options::NO_NEWLINE); + let escaped = matches.get_flag(options::ENABLE_BACKSLASH_ESCAPE); + let args = collect_args(&matches); + (args, trailing_newline, escaped) + }; let mut stdout_lock = io::stdout().lock(); - - match matches.get_many::(options::STRING) { - Some(arguments_after_options) => { - execute( - &mut stdout_lock, - trailing_newline, - escaped, - arguments_after_options, - )?; - } - None => { - // No strings to print, so just handle newline setting - if trailing_newline { - stdout_lock.write_all(b"\n")?; - } - } - } + execute(&mut stdout_lock, args, trailing_newline, escaped)?; Ok(()) } @@ -348,11 +350,11 @@ pub fn uu_app() -> Command { fn execute( stdout_lock: &mut StdoutLock, + arguments_after_options: Vec, trailing_newline: bool, escaped: bool, - arguments_after_options: ValuesRef<'_, OsString>, ) -> UResult<()> { - for (i, input) in arguments_after_options.enumerate() { + for (i, input) in arguments_after_options.into_iter().enumerate() { let Some(bytes) = bytes_from_os_string(input.as_os_str()) else { return Err(USimpleError::new( 1, diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index dd6b412a42..d4430d0565 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -390,3 +390,55 @@ fn slash_eight_off_by_one() { .succeeds() .stdout_only(r"\8"); } + +mod posixly_correct { + use super::*; + + #[test] + fn ignore_options() { + for arg in ["--help", "--version", "-E -n 'foo'", "-nE 'foo'"] { + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg(arg) + .succeeds() + .stdout_only(format!("{arg}\n")); + } + } + + #[test] + fn process_n_option() { + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .args(&["-n", "foo"]) + .succeeds() + .stdout_only("foo"); + + // ignore -E & process escapes + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .args(&["-n", "-E", "foo\\cbar"]) + .succeeds() + .stdout_only("foo"); + } + + #[test] + fn process_escapes() { + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg("foo\\n") + .succeeds() + .stdout_only("foo\n\n"); + + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg("foo\\tbar") + .succeeds() + .stdout_only("foo\tbar\n"); + + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg("foo\\ctbar") + .succeeds() + .stdout_only("foo"); + } +} From 3e1349e75d15a1f12f8388a8af996732d23a308c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:54:29 +0000 Subject: [PATCH 07/24] chore(deps): update rust crate phf_codegen to v0.11.3 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 443d56d15b..32a73172a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1621,9 +1621,9 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", From 74e9bbcc92a11e6133c373599ac4be1334c40e18 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 01:44:09 +0000 Subject: [PATCH 08/24] chore(deps): update rust crate phf to v0.11.3 --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 443d56d15b..1f2b2ac98b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1612,9 +1612,9 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_shared", ] @@ -1641,9 +1641,9 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] @@ -2140,9 +2140,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "siphasher" -version = "0.3.10" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" From 190cc6640635c4b7ad88e759ba2548fc4671da01 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 7 Jan 2025 10:38:32 +0100 Subject: [PATCH 09/24] build-gnu.sh: adapt GNU numfmt error message --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 38df18daeb..0131bae910 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -302,7 +302,7 @@ sed -i -e "s/ginstall: creating directory/install: creating directory/g" tests/i "${SED}" -i -Ez "s/\n([^\n#]*pad-3\.2[^\n]*)\n([^\n]*)\n([^\n]*)/\n# uutils\/numfmt supports padding = LONG_MIN\n#\1\n#\2\n#\3/" tests/misc/numfmt.pl # Update the GNU error message to match the one generated by clap -sed -i -e "s/\$prog: multiple field specifications/error: The argument '--field ' was provided more than once, but cannot be used multiple times\n\nUsage: numfmt [OPTION]... [NUMBER]...\n\n\nFor more information try '--help'/g" tests/misc/numfmt.pl +sed -i -e "s/\$prog: multiple field specifications/error: the argument '--field ' cannot be used multiple times\n\nUsage: numfmt [OPTION]... [NUMBER]...\n\nFor more information, try '--help'./g" tests/misc/numfmt.pl sed -i -e "s/Try 'mv --help' for more information/For more information, try '--help'/g" -e "s/mv: missing file operand/error: the following required arguments were not provided:\n ...\n\nUsage: mv [OPTION]... [-T] SOURCE DEST\n mv [OPTION]... SOURCE... DIRECTORY\n mv [OPTION]... -t DIRECTORY SOURCE...\n/g" -e "s/mv: missing destination file operand after 'no-file'/error: The argument '...' requires at least 2 values, but only 1 was provided\n\nUsage: mv [OPTION]... [-T] SOURCE DEST\n mv [OPTION]... SOURCE... DIRECTORY\n mv [OPTION]... -t DIRECTORY SOURCE...\n/g" tests/mv/diag.sh # our error message is better From 6f35be40e8e187ac1bcbedc46a5f8fac5f13c93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Valdelvira?= Date: Tue, 7 Jan 2025 00:05:25 +0100 Subject: [PATCH 10/24] uucore: Remove crash! macro --- src/uu/csplit/src/csplit.rs | 23 +++++------- src/uu/join/src/join.rs | 27 ++++++++------ src/uu/unexpand/src/unexpand.rs | 17 +++++---- src/uucore/src/lib/features/fsext.rs | 12 +----- src/uucore/src/lib/lib.rs | 5 ++- src/uucore/src/lib/macros.rs | 56 ---------------------------- 6 files changed, 41 insertions(+), 99 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 0602f0deec..329b06d368 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -16,7 +16,7 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use regex::Regex; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::{crash_if_err, format_usage, help_about, help_section, help_usage}; +use uucore::{format_usage, help_about, help_section, help_usage}; mod csplit_error; mod patterns; @@ -51,26 +51,23 @@ pub struct CsplitOptions { } impl CsplitOptions { - fn new(matches: &ArgMatches) -> Self { + fn new(matches: &ArgMatches) -> Result { let keep_files = matches.get_flag(options::KEEP_FILES); let quiet = matches.get_flag(options::QUIET); let elide_empty_files = matches.get_flag(options::ELIDE_EMPTY_FILES); let suppress_matched = matches.get_flag(options::SUPPRESS_MATCHED); - Self { - split_name: crash_if_err!( - 1, - SplitName::new( - matches.get_one::(options::PREFIX).cloned(), - matches.get_one::(options::SUFFIX_FORMAT).cloned(), - matches.get_one::(options::DIGITS).cloned() - ) - ), + Ok(Self { + split_name: SplitName::new( + matches.get_one::(options::PREFIX).cloned(), + matches.get_one::(options::SUFFIX_FORMAT).cloned(), + matches.get_one::(options::DIGITS).cloned(), + )?, keep_files, quiet, elide_empty_files, suppress_matched, - } + }) } } @@ -561,7 +558,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap() .map(|s| s.to_string()) .collect(); - let options = CsplitOptions::new(&matches); + let options = CsplitOptions::new(&matches)?; if file_name == "-" { let stdin = io::stdin(); Ok(csplit(&options, &patterns, stdin.lock())?) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index f01f75b71d..01e1b40fc4 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -20,7 +20,7 @@ use std::os::unix::ffi::OsStrExt; use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; -use uucore::{crash_if_err, format_usage, help_about, help_usage}; +use uucore::{format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("join.md"); const USAGE: &str = help_usage!("join.md"); @@ -587,15 +587,19 @@ impl<'a> State<'a> { !self.seq.is_empty() } - fn initialize(&mut self, read_sep: &Sep, autoformat: bool) -> usize { - if let Some(line) = crash_if_err!(1, self.read_line(read_sep)) { + fn initialize( + &mut self, + read_sep: &Sep, + autoformat: bool, + ) -> std::io::Result { + if let Some(line) = self.read_line(read_sep)? { self.seq.push(line); if autoformat { - return self.seq[0].field_ranges.len(); + return Ok(self.seq[0].field_ranges.len()); } } - 0 + Ok(0) } fn finalize( @@ -1008,20 +1012,21 @@ fn exec(file1: &str, file2: &str, settings: Settings, sep: Sep) let format = if settings.autoformat { let mut format = vec![Spec::Key]; - let mut initialize = |state: &mut State| { - let max_fields = state.initialize(&sep, settings.autoformat); + let mut initialize = |state: &mut State| -> UResult<()> { + let max_fields = state.initialize(&sep, settings.autoformat)?; for i in 0..max_fields { if i != state.key { format.push(Spec::Field(state.file_num, i)); } } + Ok(()) }; - initialize(&mut state1); - initialize(&mut state2); + initialize(&mut state1)?; + initialize(&mut state2)?; format } else { - state1.initialize(&sep, settings.autoformat); - state2.initialize(&sep, settings.autoformat); + state1.initialize(&sep, settings.autoformat)?; + state2.initialize(&sep, settings.autoformat)?; settings.format }; diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 7336376eb6..1e8cede37d 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -16,7 +16,7 @@ use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; -use uucore::{crash_if_err, format_usage, help_about, help_usage, show}; +use uucore::{format_usage, help_about, help_usage, show}; const USAGE: &str = help_usage!("unexpand.md"); const ABOUT: &str = help_about!("unexpand.md"); @@ -244,7 +244,7 @@ fn write_tabs( prevtab: bool, init: bool, amode: bool, -) { +) -> UResult<()> { // This conditional establishes the following: // We never turn a single space before a non-blank into // a tab, unless it's at the start of the line. @@ -255,15 +255,16 @@ fn write_tabs( break; } - crash_if_err!(1, output.write_all(b"\t")); + output.write_all(b"\t")?; scol += nts; } } while col > scol { - crash_if_err!(1, output.write_all(b" ")); + output.write_all(b" ")?; scol += 1; } + Ok(()) } #[derive(PartialEq, Eq, Debug)] @@ -325,7 +326,7 @@ fn unexpand_line( options: &Options, lastcol: usize, ts: &[usize], -) -> std::io::Result<()> { +) -> UResult<()> { let mut byte = 0; // offset into the buffer let mut col = 0; // the current column let mut scol = 0; // the start col for the current span, i.e., the already-printed width @@ -335,7 +336,7 @@ fn unexpand_line( while byte < buf.len() { // when we have a finite number of columns, never convert past the last column if lastcol > 0 && col >= lastcol { - write_tabs(output, ts, scol, col, pctype == CharType::Tab, init, true); + write_tabs(output, ts, scol, col, pctype == CharType::Tab, init, true)?; output.write_all(&buf[byte..])?; scol = col; break; @@ -370,7 +371,7 @@ fn unexpand_line( pctype == CharType::Tab, init, options.aflag, - ); + )?; init = false; // no longer at the start of a line col = if ctype == CharType::Other { // use computed width @@ -391,7 +392,7 @@ fn unexpand_line( } // write out anything remaining - write_tabs(output, ts, scol, col, pctype == CharType::Tab, init, true); + write_tabs(output, ts, scol, col, pctype == CharType::Tab, init, true)?; output.flush()?; buf.truncate(0); // clear out the buffer diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index c161db39fc..e248332c9d 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -587,7 +587,7 @@ impl FsUsage { let mut number_of_free_clusters = 0; let mut total_number_of_clusters = 0; - let success = unsafe { + unsafe { let path = to_nul_terminated_wide_string(path); GetDiskFreeSpaceW( path.as_ptr(), @@ -595,15 +595,7 @@ impl FsUsage { &mut bytes_per_sector, &mut number_of_free_clusters, &mut total_number_of_clusters, - ) - }; - if 0 == success { - // Fails in case of CD for example - // crash!( - // EXIT_ERR, - // "GetDiskFreeSpaceW failed: {}", - // IOError::last_os_error() - // ); + ); } let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64; diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 684de8f74e..9516b5e1bf 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -380,7 +380,10 @@ macro_rules! prompt_yes( eprint!("{}: ", uucore::util_name()); eprint!($($args)+); eprint!(" "); - uucore::crash_if_err!(1, std::io::stderr().flush()); + let res = std::io::stderr().flush().map_err(|err| { + $crate::error::USimpleError::new(1, err.to_string()) + }); + uucore::show_if_err!(res); uucore::read_yes() }) ); diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 6fe6005388..7d428f4e73 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -30,8 +30,6 @@ //! [`crate::show_if_err!`] //! - From custom messages: [`crate::show_error!`] //! - Print warnings: [`crate::show_warning!`] -//! - Terminate util execution -//! - Crash program: [`crate::crash!`], [`crate::crash_if_err!`] // spell-checker:ignore sourcepath targetpath rustdoc @@ -189,57 +187,3 @@ macro_rules! show_warning_caps( eprintln!($($args)+); }) ); - -/// Display an error and [`std::process::exit`] -/// -/// Displays the provided error message using [`show_error!`], then invokes -/// [`std::process::exit`] with the provided exit code. -/// -/// # Examples -/// -/// ```should_panic -/// # #[macro_use] -/// # extern crate uucore; -/// # fn main() { -/// // outputs : Couldn't apply foo to bar -/// // and terminates execution -/// crash!(1, "Couldn't apply {} to {}", "foo", "bar"); -/// # } -/// ``` -#[macro_export] -macro_rules! crash( - ($exit_code:expr, $($args:tt)+) => ({ - $crate::show_error!($($args)+); - std::process::exit($exit_code); - }) -); - -/// Unwrap a [`std::result::Result`], crashing instead of panicking. -/// -/// If the result is an `Ok`-variant, returns the value contained inside. If it -/// is an `Err`-variant, invokes [`crash!`] with the formatted error instead. -/// -/// # Examples -/// -/// ```should_panic -/// # #[macro_use] -/// # extern crate uucore; -/// # fn main() { -/// let is_ok: Result = Ok(1); -/// // Does nothing -/// crash_if_err!(1, is_ok); -/// -/// let is_err: Result = Err("This didn't work..."); -/// // Calls `crash!` -/// crash_if_err!(1, is_err); -/// # } -/// ``` -#[macro_export] -macro_rules! crash_if_err( - ($exit_code:expr, $exp:expr) => ( - match $exp { - Ok(m) => m, - Err(f) => $crate::crash!($exit_code, "{}", f), - } - ) -); From 58f6afdeb4f4b3901a13d92a9945c7d10cc4b812 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 7 Jan 2025 23:53:52 +0100 Subject: [PATCH 11/24] env: handle the error properly instead of: env: unknown error: Os { code: 13, kind: PermissionDenied, message: "Permission denied" } --- src/uu/env/src/env.rs | 23 +++++++++++++---------- tests/by-util/test_env.rs | 9 +++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index ea31c01079..b000857a88 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -539,16 +539,19 @@ impl EnvAppData { } return Err(exit.code().unwrap().into()); } - Err(ref err) - if (err.kind() == io::ErrorKind::NotFound) - || (err.kind() == io::ErrorKind::InvalidInput) => - { - return Err(self.make_error_no_such_file_or_dir(prog.deref())); - } - Err(e) => { - uucore::show_error!("unknown error: {:?}", e); - return Err(126.into()); - } + Err(ref err) => match err.kind() { + io::ErrorKind::NotFound | io::ErrorKind::InvalidInput => { + return Err(self.make_error_no_such_file_or_dir(prog.deref())); + } + io::ErrorKind::PermissionDenied => { + uucore::show_error!("{}: Permission denied", prog.quote()); + return Err(126.into()); + } + _ => { + uucore::show_error!("unknown error: {:?}", err); + return Err(126.into()); + } + }, Ok(_) => (), } Ok(()) diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 2b33f725db..79ca0d2f45 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -80,6 +80,15 @@ fn test_env_version() { .stdout_contains(util_name!()); } +#[test] +fn test_env_permissions() { + new_ucmd!() + .arg(".") + .fails() + .code_is(126) + .stderr_is("env: '.': Permission denied\n"); +} + #[test] fn test_echo() { #[cfg(target_os = "windows")] From 79645d45ce1e041183a8200d64442a3722c52c41 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Jan 2025 00:05:11 +0100 Subject: [PATCH 12/24] stdbuf: better handling of the error message when no perm Tested in tests/misc/stdbuf --- src/uu/stdbuf/src/stdbuf.rs | 19 ++++++++++++++++--- tests/by-util/test_stdbuf.rs | 10 ++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index bc7b239491..e566439f0f 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -157,9 +157,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { set_command_env(&mut command, "_STDBUF_E", &options.stderr); command.args(command_params); - let mut process = command - .spawn() - .map_err_context(|| "failed to execute process".to_string())?; + let mut process = match command.spawn() { + Ok(process) => process, + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + return Err(USimpleError::new( + 126, + "failed to execute process: Permission denied", + )); + } + Err(e) => { + return Err(USimpleError::new( + 1, + format!("failed to execute process: {}", e), + )); + } + }; + let status = process.wait().map_err_context(String::new)?; match status.code() { Some(i) => { diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index a86f893e08..c2d7f9121d 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -10,6 +10,16 @@ fn invalid_input() { new_ucmd!().arg("-/").fails().code_is(125); } +#[test] +fn test_permission() { + new_ucmd!() + .arg("-o1") + .arg(".") + .fails() + .code_is(126) + .stderr_contains("Permission denied"); +} + #[cfg(all(not(target_os = "windows"), not(target_os = "openbsd")))] #[test] fn test_stdbuf_unbuffered_stdout() { From 04d4c9e1ef9824f02d46d72d3c278cf1813b2f54 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Jan 2025 00:11:34 +0100 Subject: [PATCH 13/24] stdbuf: better handling when non existing files Should fix tests/misc/stdbuf --- src/uu/stdbuf/src/stdbuf.rs | 32 ++++++++++++++++++++------------ tests/by-util/test_stdbuf.rs | 10 ++++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index e566439f0f..8c20cc39a2 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -159,18 +159,26 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut process = match command.spawn() { Ok(process) => process, - Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { - return Err(USimpleError::new( - 126, - "failed to execute process: Permission denied", - )); - } - Err(e) => { - return Err(USimpleError::new( - 1, - format!("failed to execute process: {}", e), - )); - } + Err(e) => match e.kind() { + std::io::ErrorKind::PermissionDenied => { + return Err(USimpleError::new( + 126, + "failed to execute process: Permission denied", + )); + } + std::io::ErrorKind::NotFound => { + return Err(USimpleError::new( + 127, + "failed to execute process: No such file or directory", + )); + } + _ => { + return Err(USimpleError::new( + 1, + format!("failed to execute process: {}", e), + )); + } + }, }; let status = process.wait().map_err_context(String::new)?; diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index c2d7f9121d..4bee30fab1 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -20,6 +20,16 @@ fn test_permission() { .stderr_contains("Permission denied"); } +#[test] +fn test_no_such() { + new_ucmd!() + .arg("-o1") + .arg("no_such") + .fails() + .code_is(127) + .stderr_contains("No such file or directory"); +} + #[cfg(all(not(target_os = "windows"), not(target_os = "openbsd")))] #[test] fn test_stdbuf_unbuffered_stdout() { From 20e043c5fce5b899c66e76f5893e82ff1a34eab1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Jan 2025 00:16:37 +0100 Subject: [PATCH 14/24] stdbuf: Improve the code --- src/uu/stdbuf/src/stdbuf.rs | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 8c20cc39a2..4540c60d89 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -157,28 +157,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { set_command_env(&mut command, "_STDBUF_E", &options.stderr); command.args(command_params); + const EXEC_ERROR: &str = "failed to execute process:"; let mut process = match command.spawn() { - Ok(process) => process, - Err(e) => match e.kind() { - std::io::ErrorKind::PermissionDenied => { - return Err(USimpleError::new( + Ok(p) => p, + Err(e) => { + return match e.kind() { + std::io::ErrorKind::PermissionDenied => Err(USimpleError::new( 126, - "failed to execute process: Permission denied", - )); - } - std::io::ErrorKind::NotFound => { - return Err(USimpleError::new( + format!("{EXEC_ERROR} Permission denied"), + )), + std::io::ErrorKind::NotFound => Err(USimpleError::new( 127, - "failed to execute process: No such file or directory", - )); - } - _ => { - return Err(USimpleError::new( - 1, - format!("failed to execute process: {}", e), - )); + format!("{EXEC_ERROR} No such file or directory"), + )), + _ => Err(USimpleError::new(1, format!("{EXEC_ERROR} {}", e))), } - }, + } }; let status = process.wait().map_err_context(String::new)?; From 19d3f574648928cc4c3ff3059ea3360d6ee5c5c5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Jan 2025 00:32:36 +0100 Subject: [PATCH 15/24] doc: rename to md --- util/{why-error.txt => why-error.md} | 0 util/{why-skip.txt => why-skip.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename util/{why-error.txt => why-error.md} (100%) rename util/{why-skip.txt => why-skip.md} (100%) diff --git a/util/why-error.txt b/util/why-error.md similarity index 100% rename from util/why-error.txt rename to util/why-error.md diff --git a/util/why-skip.txt b/util/why-skip.md similarity index 100% rename from util/why-skip.txt rename to util/why-skip.md From 1d2119d6a7f3fa999129a41d78deac2118272657 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Jan 2025 00:35:07 +0100 Subject: [PATCH 16/24] doc: improve the rendering --- util/why-error.md | 168 +++++++++++++++++++++++----------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/util/why-error.md b/util/why-error.md index bbfb0508c0..bceedc8963 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -1,86 +1,86 @@ This file documents why some tests are failing: -gnu/tests/chgrp/from.sh - https://github.com/uutils/coreutils/issues/7039 -gnu/tests/chmod/symlinks.sh - https://github.com/uutils/coreutils/pull/7025 -gnu/tests/chroot/chroot-credentials.sh - https://github.com/uutils/coreutils/issues/7040 -gnu/tests/cp/cp-i.sh -gnu/tests/cp/preserve-gid.sh -gnu/tests/csplit/csplit-suppress-matched.pl -gnu/tests/date/date-debug.sh -gnu/tests/date/date-next-dow.pl -gnu/tests/date/date-tz.sh -gnu/tests/date/date.pl -gnu/tests/dd/direct.sh -gnu/tests/dd/no-allocate.sh -gnu/tests/dd/nocache_eof.sh -gnu/tests/dd/skip-seek-past-file.sh -gnu/tests/dd/stderr.sh -gnu/tests/df/over-mount-device.sh -gnu/tests/du/long-from-unreadable.sh -gnu/tests/du/move-dir-while-traversing.sh -gnu/tests/expr/expr-multibyte.pl -gnu/tests/expr/expr.pl -gnu/tests/fmt/goal-option.sh -gnu/tests/fmt/non-space.sh -gnu/tests/head/head-elide-tail.pl -gnu/tests/head/head-pos.sh -gnu/tests/head/head-write-error.sh -gnu/tests/help/help-version-getopt.sh -gnu/tests/help/help-version.sh -gnu/tests/id/setgid.sh -gnu/tests/ls/ls-misc.pl -gnu/tests/ls/stat-free-symlinks.sh -gnu/tests/misc/close-stdout.sh -gnu/tests/misc/comm.pl -gnu/tests/misc/dircolors.pl -gnu/tests/misc/echo.sh -gnu/tests/misc/kill.sh -gnu/tests/misc/nohup.sh -gnu/tests/misc/numfmt.pl -gnu/tests/misc/stdbuf.sh -gnu/tests/misc/tee.sh -gnu/tests/misc/time-style.sh -gnu/tests/misc/tsort.pl -gnu/tests/misc/write-errors.sh -gnu/tests/misc/xattr.sh - https://github.com/uutils/coreutils/pull/7009 -gnu/tests/mv/hard-link-1.sh -gnu/tests/mv/mv-special-1.sh -gnu/tests/mv/part-fail.sh -gnu/tests/mv/part-hardlink.sh -gnu/tests/od/od-N.sh -gnu/tests/od/od-float.sh -gnu/tests/printf/printf-cov.pl -gnu/tests/printf/printf-indexed.sh -gnu/tests/printf/printf-mb.sh -gnu/tests/printf/printf-quote.sh -gnu/tests/printf/printf.sh -gnu/tests/ptx/ptx-overrun.sh -gnu/tests/ptx/ptx.pl -gnu/tests/rm/empty-inacc.sh - https://github.com/uutils/coreutils/issues/7033 -gnu/tests/rm/ir-1.sh -gnu/tests/rm/one-file-system.sh - https://github.com/uutils/coreutils/issues/7011 -gnu/tests/rm/rm1.sh -gnu/tests/rm/rm2.sh -gnu/tests/seq/seq-precision.sh -gnu/tests/shred/shred-passes.sh -gnu/tests/sort/sort-continue.sh -gnu/tests/sort/sort-debug-keys.sh -gnu/tests/sort/sort-debug-warn.sh -gnu/tests/sort/sort-files0-from.pl -gnu/tests/sort/sort-float.sh -gnu/tests/sort/sort-h-thousands-sep.sh -gnu/tests/sort/sort-merge-fdlimit.sh -gnu/tests/sort/sort-month.sh -gnu/tests/sort/sort.pl -gnu/tests/split/line-bytes.sh -gnu/tests/stat/stat-nanoseconds.sh -gnu/tests/tac/tac-2-nonseekable.sh -gnu/tests/tail/end-of-device.sh -gnu/tests/tail/follow-stdin.sh -gnu/tests/tail/inotify-rotate-resources.sh -gnu/tests/tail/symlink.sh -gnu/tests/touch/now-owned-by-other.sh -gnu/tests/touch/obsolescent.sh -gnu/tests/truncate/truncate-owned-by-other.sh -gnu/tests/tty/tty-eof.pl -gnu/tests/uniq/uniq.pl +* gnu/tests/chgrp/from.sh - https://github.com/uutils/coreutils/issues/7039 +* gnu/tests/chmod/symlinks.sh - https://github.com/uutils/coreutils/pull/7025 +* gnu/tests/chroot/chroot-credentials.sh - https://github.com/uutils/coreutils/issues/7040 +* gnu/tests/cp/cp-i.sh +* gnu/tests/cp/preserve-gid.sh +* gnu/tests/csplit/csplit-suppress-matched.pl +* gnu/tests/date/date-debug.sh +* gnu/tests/date/date-next-dow.pl +* gnu/tests/date/date-tz.sh +* gnu/tests/date/date.pl +* gnu/tests/dd/direct.sh +* gnu/tests/dd/no-allocate.sh +* gnu/tests/dd/nocache_eof.sh +* gnu/tests/dd/skip-seek-past-file.sh +* gnu/tests/dd/stderr.sh +* gnu/tests/df/over-mount-device.sh +* gnu/tests/du/long-from-unreadable.sh +* gnu/tests/du/move-dir-while-traversing.sh +* gnu/tests/expr/expr-multibyte.pl +* gnu/tests/expr/expr.pl +* gnu/tests/fmt/goal-option.sh +* gnu/tests/fmt/non-space.sh +* gnu/tests/head/head-elide-tail.pl +* gnu/tests/head/head-pos.sh +* gnu/tests/head/head-write-error.sh +* gnu/tests/help/help-version-getopt.sh +* gnu/tests/help/help-version.sh +* gnu/tests/id/setgid.sh +* gnu/tests/ls/ls-misc.pl +* gnu/tests/ls/stat-free-symlinks.sh +* gnu/tests/misc/close-stdout.sh +* gnu/tests/misc/comm.pl +* gnu/tests/misc/dircolors.pl +* gnu/tests/misc/echo.sh +* gnu/tests/misc/kill.sh +* gnu/tests/misc/nohup.sh +* gnu/tests/misc/numfmt.pl +* gnu/tests/misc/stdbuf.sh +* gnu/tests/misc/tee.sh +* gnu/tests/misc/time-style.sh +* gnu/tests/misc/tsort.pl +* gnu/tests/misc/write-errors.sh +* gnu/tests/misc/xattr.sh - https://github.com/uutils/coreutils/pull/7009 +* gnu/tests/mv/hard-link-1.sh +* gnu/tests/mv/mv-special-1.sh +* gnu/tests/mv/part-fail.sh +* gnu/tests/mv/part-hardlink.sh +* gnu/tests/od/od-N.sh +* gnu/tests/od/od-float.sh +* gnu/tests/printf/printf-cov.pl +* gnu/tests/printf/printf-indexed.sh +* gnu/tests/printf/printf-mb.sh +* gnu/tests/printf/printf-quote.sh +* gnu/tests/printf/printf.sh +* gnu/tests/ptx/ptx-overrun.sh +* gnu/tests/ptx/ptx.pl +* gnu/tests/rm/empty-inacc.sh - https://github.com/uutils/coreutils/issues/7033 +* gnu/tests/rm/ir-1.sh +* gnu/tests/rm/one-file-system.sh - https://github.com/uutils/coreutils/issues/7011 +* gnu/tests/rm/rm1.sh +* gnu/tests/rm/rm2.sh +* gnu/tests/seq/seq-precision.sh +* gnu/tests/shred/shred-passes.sh +* gnu/tests/sort/sort-continue.sh +* gnu/tests/sort/sort-debug-keys.sh +* gnu/tests/sort/sort-debug-warn.sh +* gnu/tests/sort/sort-files0-from.pl +* gnu/tests/sort/sort-float.sh +* gnu/tests/sort/sort-h-thousands-sep.sh +* gnu/tests/sort/sort-merge-fdlimit.sh +* gnu/tests/sort/sort-month.sh +* gnu/tests/sort/sort.pl +* gnu/tests/split/line-bytes.sh +* gnu/tests/stat/stat-nanoseconds.sh +* gnu/tests/tac/tac-2-nonseekable.sh +* gnu/tests/tail/end-of-device.sh +* gnu/tests/tail/follow-stdin.sh +* gnu/tests/tail/inotify-rotate-resources.sh +* gnu/tests/tail/symlink.sh +* gnu/tests/touch/now-owned-by-other.sh +* gnu/tests/touch/obsolescent.sh +* gnu/tests/truncate/truncate-owned-by-other.sh +* gnu/tests/tty/tty-eof.pl +* gnu/tests/uniq/uniq.pl From 6cb552828c70bf71aff07cd1a8bc786c30a8be9b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Jan 2025 00:39:38 +0100 Subject: [PATCH 17/24] doc: add some details on the failures --- util/why-error.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/util/why-error.md b/util/why-error.md index bceedc8963..0ab38db28a 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -34,17 +34,17 @@ This file documents why some tests are failing: * gnu/tests/misc/comm.pl * gnu/tests/misc/dircolors.pl * gnu/tests/misc/echo.sh -* gnu/tests/misc/kill.sh +* gnu/tests/misc/kill.sh - https://github.com/uutils/coreutils/issues/7066 https://github.com/uutils/coreutils/issues/7067 * gnu/tests/misc/nohup.sh * gnu/tests/misc/numfmt.pl -* gnu/tests/misc/stdbuf.sh -* gnu/tests/misc/tee.sh +* gnu/tests/misc/stdbuf.sh - https://github.com/uutils/coreutils/issues/7072 +* gnu/tests/misc/tee.sh - https://github.com/uutils/coreutils/issues/7073 * gnu/tests/misc/time-style.sh -* gnu/tests/misc/tsort.pl +* gnu/tests/misc/tsort.pl - https://github.com/uutils/coreutils/issues/7074 * gnu/tests/misc/write-errors.sh * gnu/tests/misc/xattr.sh - https://github.com/uutils/coreutils/pull/7009 * gnu/tests/mv/hard-link-1.sh -* gnu/tests/mv/mv-special-1.sh +* gnu/tests/mv/mv-special-1.sh - https://github.com/uutils/coreutils/issues/7076 * gnu/tests/mv/part-fail.sh * gnu/tests/mv/part-hardlink.sh * gnu/tests/od/od-N.sh From 0eb6f9bddbabf927c96784221edf9511a5db4cb0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 7 Jan 2025 21:34:56 -0500 Subject: [PATCH 18/24] seq: re-enable GNU test file precision.sh --- util/build-gnu.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 0131bae910..d29283a69b 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -178,10 +178,6 @@ sed -i -e '/tests\/help\/help-version.sh/ D' \ -e '/tests\/help\/help-version-getopt.sh/ D' \ Makefile -# logs are clotted because of this test -sed -i -e '/tests\/seq\/seq-precision.sh/ D' \ - Makefile - # printf doesn't limit the values used in its arg, so this produced ~2GB of output sed -i '/INT_OFLOW/ D' tests/printf/printf.sh From 83ae0c0f7408118a754349f29e522bfad2a05ebb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 06:04:43 +0000 Subject: [PATCH 19/24] chore(deps): update rust crate clap to v4.5.24 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa1c35a6de..e1db70e8b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,18 +327,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd" dependencies = [ "anstream", "anstyle", From b918f6856c11146672f5502000580bf088a1188d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 06:04:49 +0000 Subject: [PATCH 20/24] chore(deps): update rust crate clap_complete to v4.5.41 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa1c35a6de..0c84f93506 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,9 +349,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9" +checksum = "942dc5991a34d8cf58937ec33201856feba9cbceeeab5adf04116ec7c763bff1" dependencies = [ "clap", ] From 763589c99c6fdeb17d777ca02c4d2dc75957a4e1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 8 Jan 2025 07:28:44 +0100 Subject: [PATCH 21/24] doc: remove fixed tests from why-error.md --- util/why-error.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/util/why-error.md b/util/why-error.md index 0ab38db28a..f0e021f41d 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -3,7 +3,6 @@ This file documents why some tests are failing: * gnu/tests/chgrp/from.sh - https://github.com/uutils/coreutils/issues/7039 * gnu/tests/chmod/symlinks.sh - https://github.com/uutils/coreutils/pull/7025 * gnu/tests/chroot/chroot-credentials.sh - https://github.com/uutils/coreutils/issues/7040 -* gnu/tests/cp/cp-i.sh * gnu/tests/cp/preserve-gid.sh * gnu/tests/csplit/csplit-suppress-matched.pl * gnu/tests/date/date-debug.sh @@ -27,13 +26,11 @@ This file documents why some tests are failing: * gnu/tests/head/head-write-error.sh * gnu/tests/help/help-version-getopt.sh * gnu/tests/help/help-version.sh -* gnu/tests/id/setgid.sh * gnu/tests/ls/ls-misc.pl * gnu/tests/ls/stat-free-symlinks.sh * gnu/tests/misc/close-stdout.sh * gnu/tests/misc/comm.pl * gnu/tests/misc/dircolors.pl -* gnu/tests/misc/echo.sh * gnu/tests/misc/kill.sh - https://github.com/uutils/coreutils/issues/7066 https://github.com/uutils/coreutils/issues/7067 * gnu/tests/misc/nohup.sh * gnu/tests/misc/numfmt.pl @@ -42,7 +39,6 @@ This file documents why some tests are failing: * gnu/tests/misc/time-style.sh * gnu/tests/misc/tsort.pl - https://github.com/uutils/coreutils/issues/7074 * gnu/tests/misc/write-errors.sh -* gnu/tests/misc/xattr.sh - https://github.com/uutils/coreutils/pull/7009 * gnu/tests/mv/hard-link-1.sh * gnu/tests/mv/mv-special-1.sh - https://github.com/uutils/coreutils/issues/7076 * gnu/tests/mv/part-fail.sh @@ -79,8 +75,5 @@ This file documents why some tests are failing: * gnu/tests/tail/follow-stdin.sh * gnu/tests/tail/inotify-rotate-resources.sh * gnu/tests/tail/symlink.sh -* gnu/tests/touch/now-owned-by-other.sh * gnu/tests/touch/obsolescent.sh -* gnu/tests/truncate/truncate-owned-by-other.sh * gnu/tests/tty/tty-eof.pl -* gnu/tests/uniq/uniq.pl From 1560325836170b2d3a8bce4a0d2df85e33fdcc17 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 07:32:17 +0000 Subject: [PATCH 22/24] chore(deps): update rust crate clap_mangen to v0.2.25 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 556bd9603c..e7be3548df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,9 +364,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clap_mangen" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" +checksum = "acbfe6ac42a2438d0968beba18e3c35cacf16b0c25310bc22b1f5f3cffff09f4" dependencies = [ "clap", "roff", From 34f8995ccbda9e7565f73f306320f87ebad4cd2e Mon Sep 17 00:00:00 2001 From: tommi Date: Tue, 7 Jan 2025 18:37:02 +0100 Subject: [PATCH 23/24] tests/csplit: modified test test_up_to_match_offset_option_suppress_matched according to issue #7052 and modified also test_up_to_match_negative_offset_option_suppress_matched --- tests/by-util/test_csplit.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 2315715228..9323b98518 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -469,14 +469,14 @@ fn test_up_to_match_offset_option_suppress_matched() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "--suppress-matched", "/10/+4"]) .succeeds() - .stdout_only("27\n111\n"); + .stdout_only("30\n108\n"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") .count(); assert_eq!(count, 2); - assert_eq!(at.read("xx00"), generate(1, 10) + &generate(11, 14)); - assert_eq!(at.read("xx01"), generate(14, 51)); + assert_eq!(at.read("xx00"), generate(1, 14)); + assert_eq!(at.read("xx01"), generate(15, 51)); } #[test] @@ -484,14 +484,14 @@ fn test_up_to_match_negative_offset_option_suppress_matched() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "--suppress-matched", "/10/-4"]) .succeeds() - .stdout_only("10\n128\n"); + .stdout_only("10\n129\n"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") .count(); assert_eq!(count, 2); assert_eq!(at.read("xx00"), generate(1, 6)); - assert_eq!(at.read("xx01"), generate(6, 10) + &generate(11, 51)); + assert_eq!(at.read("xx01"), generate(7, 51)); } #[test] From c1b950904c9c2836b4c10d2978c3a67dcc8b5896 Mon Sep 17 00:00:00 2001 From: tommi Date: Tue, 7 Jan 2025 18:39:17 +0100 Subject: [PATCH 24/24] csplit: managed the positive and negative offset when the --suppressed-matched flag is active --- src/uu/csplit/src/csplit.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 329b06d368..eaf773916d 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -372,6 +372,7 @@ impl SplitWriter<'_> { while let Some((ln, line)) = input_iter.next() { let l = line?; if regex.is_match(&l) { + let mut next_line_suppress_matched = false; match (self.options.suppress_matched, offset) { // no offset, add the line to the next split (false, 0) => { @@ -382,6 +383,11 @@ impl SplitWriter<'_> { } // a positive offset, some more lines need to be added to the current split (false, _) => self.writeln(&l)?, + // suppress matched option true, but there is a positive offset, so the line is printed + (true, 1..) => { + next_line_suppress_matched = true; + self.writeln(&l)?; + } _ => (), }; offset -= 1; @@ -402,6 +408,11 @@ impl SplitWriter<'_> { offset -= 1; } self.finish_split(); + + // if we have to suppress one line after we take the next and do nothing + if next_line_suppress_matched { + input_iter.next(); + } return Ok(()); } self.writeln(&l)?; @@ -427,7 +438,13 @@ impl SplitWriter<'_> { input_iter.add_line_to_buffer(ln, l).is_none(), "should be big enough to hold every lines" ); + } else { + // since offset_usize is for sure greater than 0 + // the first element of the buffer should be removed and this + // line inserted to be coherent with GNU implementation + input_iter.add_line_to_buffer(ln, l); } + self.finish_split(); if input_iter.buffer_len() < offset_usize { return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string()));