diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index ea369e4ef..209456092 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -612,9 +612,23 @@ jobs: run: | ## Install/setup prerequisites case '${{ matrix.job.target }}' in - arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; - aarch64-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; - *-redox*) sudo apt-get -y update ; sudo apt-get -y install fuse3 libfuse-dev ;; + arm-unknown-linux-gnueabihf) + sudo apt-get -y update + sudo apt-get -y install gcc-arm-linux-gnueabihf + ;; + aarch64-unknown-linux-*) + sudo apt-get -y update + sudo apt-get -y install gcc-aarch64-linux-gnu + ;; + *-redox*) + sudo apt-get -y update + sudo apt-get -y install fuse3 libfuse-dev + ;; + # Update binutils if MinGW due to https://github.com/rust-lang/rust/issues/112368 + x86_64-pc-windows-gnu) + C:/msys64/usr/bin/pacman.exe -Syu --needed mingw-w64-x86_64-gcc --noconfirm + echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH + ;; esac case '${{ matrix.job.os }}' in macos-latest) brew install coreutils ;; # needed for testing @@ -984,6 +998,10 @@ jobs: echo "foo" > /home/runner/.plan ;; esac + case '${{ matrix.job.os }}' in + # Update binutils if MinGW due to https://github.com/rust-lang/rust/issues/112368 + windows-latest) C:/msys64/usr/bin/pacman.exe -Syu --needed mingw-w64-x86_64-gcc --noconfirm ; echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH ;; + esac - name: Initialize toolchain-dependent workflow variables id: dep_vars shell: bash diff --git a/Cargo.lock b/Cargo.lock index 9190d1be3..55ec59e2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,15 +320,15 @@ checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3" [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -813,14 +813,14 @@ checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5" [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall", + "windows-sys 0.52.0", ] [[package]] @@ -1293,14 +1293,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1455,13 +1455,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", "windows-targets 0.48.0", ] @@ -1589,7 +1589,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.28", + "rustix 0.38.30", ] [[package]] @@ -1689,18 +1689,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded0bce2d41cc3c57aefa284708ced249a64acb01745dbbe72bd78610bfd644c" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] @@ -1831,9 +1822,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.0", "errno", @@ -1995,9 +1986,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" [[package]] name = "smawk" @@ -2045,15 +2036,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.0", - "rustix 0.38.28", - "windows-sys 0.48.0", + "redox_syscall", + "rustix 0.38.30", + "windows-sys 0.52.0", ] [[package]] @@ -2072,7 +2063,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.28", + "rustix 0.38.30", "windows-sys 0.48.0", ] @@ -2616,6 +2607,7 @@ dependencies = [ "unicode-width", "uucore", "uutils_term_grid", + "xattr", ] [[package]] @@ -3014,7 +3006,7 @@ version = "0.0.23" dependencies = [ "clap", "libc", - "redox_syscall 0.4.0", + "redox_syscall", "uucore", ] @@ -3574,13 +3566,13 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "xattr" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys 0.4.12", - "rustix 0.38.28", + "rustix 0.38.30", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f45d0b2f4..17efcb6a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -318,8 +318,8 @@ same-file = "1.0.6" self_cell = "1.0.3" selinux = "0.4" signal-hook = "0.3.17" -smallvec = { version = "1.11", features = ["union"] } -tempfile = "3.8.1" +smallvec = { version = "1.12", features = ["union"] } +tempfile = "3.9.0" uutils_term_grid = "0.3" terminal_size = "0.3.0" textwrap = { version = "0.16.0", features = ["terminal_size"] } @@ -331,7 +331,7 @@ utf-8 = "0.7.6" walkdir = "2.4" winapi-util = "0.1.6" windows-sys = { version = "0.48.0", default-features = false } -xattr = "1.2.0" +xattr = "1.3.1" zip = { version = "0.6.6", default-features = false, features = ["deflate"] } hex = "0.4.3" diff --git a/deny.toml b/deny.toml index df970c2c6..d7c04ad2d 100644 --- a/deny.toml +++ b/deny.toml @@ -62,7 +62,7 @@ skip = [ { name = "linux-raw-sys", version = "0.3.8" }, # terminal_size { name = "rustix", version = "0.37.26" }, - # various crates + # notify { name = "windows-sys", version = "0.45.0" }, # various crates { name = "windows-sys", version = "0.48.0" }, @@ -102,8 +102,6 @@ skip = [ { name = "syn", version = "1.0.109" }, # various crates { name = "bitflags", version = "1.3.2" }, - # various crates - { name = "redox_syscall", version = "0.3.5" }, # clap_builder, textwrap { name = "terminal_size", version = "0.2.6" }, ] diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index f0de0b605..e8cac8d40 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -232,7 +232,10 @@ pub fn run_gnu_cmd( let output = if let Some(input_str) = pipe_input { // We have an pipe input - command.stdin(Stdio::piped()).stdout(Stdio::piped()); + command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); let mut child = command.spawn().expect("Failed to execute command"); let child_stdin = child.stdin.as_mut().unwrap(); diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 77f274944..36dfbbe1e 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -5,15 +5,18 @@ // spell-checker:ignore (ToDO) fname, algo use clap::{crate_version, value_parser, Arg, ArgAction, Command}; +use hex::decode; use hex::encode; +use std::error::Error; use std::ffi::OsStr; +use std::fmt::Display; use std::fs::File; -use std::io::{self, stdin, BufReader, Read}; +use std::io::{self, stdin, stdout, BufReader, Read, Write}; use std::iter; use std::path::Path; use uucore::{ - error::{FromIo, UResult}, - format_usage, help_about, help_section, help_usage, + error::{FromIo, UError, UResult, USimpleError}, + format_usage, help_about, help_section, help_usage, show, sum::{ div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3, BSD, CRC, SYSV, @@ -36,6 +39,31 @@ const ALGORITHM_OPTIONS_SHA512: &str = "sha512"; const ALGORITHM_OPTIONS_BLAKE2B: &str = "blake2b"; const ALGORITHM_OPTIONS_SM3: &str = "sm3"; +#[derive(Debug)] +enum CkSumError { + RawMultipleFiles, +} + +impl UError for CkSumError { + fn code(&self) -> i32 { + match self { + Self::RawMultipleFiles => 1, + } + } +} + +impl Error for CkSumError {} + +impl Display for CkSumError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::RawMultipleFiles => { + write!(f, "the --raw option is not supported with multiple files") + } + } + } +} + fn detect_algo( program: &str, length: Option, @@ -110,6 +138,7 @@ struct Options { output_bits: usize, untagged: bool, length: Option, + raw: bool, } /// Calculate checksum @@ -123,6 +152,11 @@ fn cksum<'a, I>(mut options: Options, files: I) -> UResult<()> where I: Iterator, { + let files: Vec<_> = files.collect(); + if options.raw && files.len() > 1 { + return Err(Box::new(CkSumError::RawMultipleFiles)); + } + for filename in files { let filename = Path::new(filename); let stdin_buf; @@ -134,13 +168,35 @@ where } else if filename.is_dir() { Box::new(BufReader::new(io::empty())) as Box } else { - file_buf = - File::open(filename).map_err_context(|| filename.to_str().unwrap().to_string())?; + file_buf = match File::open(filename) { + Ok(file) => file, + Err(err) => { + show!(err.map_err_context(|| filename.to_string_lossy().to_string())); + continue; + } + }; Box::new(file_buf) as Box }); let (sum, sz) = digest_read(&mut options.digest, &mut file, options.output_bits) .map_err_context(|| "failed to read input".to_string())?; - + if filename.is_dir() { + show!(USimpleError::new( + 1, + format!("{}: Is a directory", filename.display()) + )); + continue; + } + if options.raw { + let bytes = match options.algo_name { + ALGORITHM_OPTIONS_CRC => sum.parse::().unwrap().to_be_bytes().to_vec(), + ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => { + sum.parse::().unwrap().to_be_bytes().to_vec() + } + _ => decode(sum).unwrap(), + }; + stdout().write_all(&bytes)?; + return Ok(()); + } // The BSD checksum output is 5 digit integer let bsd_width = 5; match (options.algo_name, not_file) { @@ -169,13 +225,6 @@ where (ALGORITHM_OPTIONS_CRC, true) => println!("{sum} {sz}"), (ALGORITHM_OPTIONS_CRC, false) => println!("{sum} {sz} {}", filename.display()), (ALGORITHM_OPTIONS_BLAKE2B, _) if !options.untagged => { - if filename.is_dir() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("{}: Is a directory", filename.display()), - ) - .into()); - } if let Some(length) = options.length { // Multiply by 8 here, as we want to print the length in bits. println!("BLAKE2b-{} ({}) = {sum}", length * 8, filename.display()); @@ -238,6 +287,7 @@ mod options { pub const FILE: &str = "file"; pub const UNTAGGED: &str = "untagged"; pub const LENGTH: &str = "length"; + pub const RAW: &str = "raw"; } #[uucore::main] @@ -298,6 +348,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { output_bits: bits, length, untagged: matches.get_flag(options::UNTAGGED), + raw: matches.get_flag(options::RAW), }; match matches.get_many::(options::FILE) { @@ -354,5 +405,11 @@ pub fn uu_app() -> Command { .help("digest length in bits; must not exceed the max for the blake2 algorithm and must be a multiple of 8") .action(ArgAction::Set), ) + .arg( + Arg::new(options::RAW) + .long(options::RAW) + .help("emit a raw binary digest, not hexadecimal") + .action(ArgAction::SetTrue), + ) .after_help(AFTER_HELP) } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 8a4c5623a..52d59ff61 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -150,6 +150,7 @@ pub enum TargetType { } /// Copy action to perform +#[derive(PartialEq)] pub enum CopyMode { Link, SymLink, @@ -1509,7 +1510,7 @@ fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult { /// /// Copying to the same file is only allowed if both `--backup` and /// `--force` are specified and the file is a regular file. -fn is_forbidden_copy_to_same_file( +fn is_forbidden_to_copy_to_same_file( source: &Path, dest: &Path, options: &Options, @@ -1521,6 +1522,7 @@ fn is_forbidden_copy_to_same_file( options.dereference(source_in_command_line) || !source.is_symlink(); paths_refer_to_same_file(source, dest, dereference_to_compare) && !(options.force() && options.backup != BackupMode::NoBackup) + && !(dest.is_symlink() && options.backup != BackupMode::NoBackup) } /// Back up, remove, or leave intact the destination file, depending on the options. @@ -1532,7 +1534,7 @@ fn handle_existing_dest( ) -> CopyResult<()> { // Disallow copying a file to itself, unless `--force` and // `--backup` are both specified. - if is_forbidden_copy_to_same_file(source, dest, options, source_in_command_line) { + if is_forbidden_to_copy_to_same_file(source, dest, options, source_in_command_line) { return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); } @@ -1714,6 +1716,7 @@ fn copy_file( && !options.force() && options.backup == BackupMode::NoBackup && source != dest + || (source == dest && options.copy_mode == CopyMode::Link) { return Ok(()); } diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 0d5f79b35..2a3196d00 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -510,6 +510,7 @@ pub fn uu_app() -> Command { .about(ABOUT) .after_help(AFTER_HELP) .infer_long_args(true) + .args_override_self(true) .arg( Arg::new(options::BYTES) .short('b') diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index d11eeb27c..38312eefc 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -34,6 +34,9 @@ once_cell = { workspace = true } selinux = { workspace = true, optional = true } hostname = { workspace = true } +[target.'cfg(unix)'.dependencies] +xattr = { workspace = true } + [[bin]] name = "ls" path = "src/main.rs" diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0e9b25722..1c89cd353 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.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 (ToDO) somegroup nlink tabsize dired subdired dtype colorterm +// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm getxattr use clap::{ builder::{NonEmptyStringValueParser, ValueParser}, @@ -36,6 +36,7 @@ use std::{ }; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use unicode_width::UnicodeWidthStr; + #[cfg(any( target_os = "linux", target_os = "macos", @@ -2620,6 +2621,18 @@ fn display_grid( Ok(()) } +#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +fn file_has_acl>(file: P) -> bool { + // don't use exacl here, it is doing more getxattr call then needed + match xattr::list(file) { + Ok(acl) => { + // if we have extra attributes, we have an acl + acl.count() > 0 + } + Err(_) => false, + } +} + /// This writes to the BufWriter out a single string of the output of `ls -l`. /// /// It writes the following keys, in order: @@ -2663,9 +2676,14 @@ fn display_item_long( output_display += " "; } if let Some(md) = item.get_metadata(out) { + #[cfg(any(not(unix), target_os = "android", target_os = "macos"))] + // TODO: See how Mac should work here + let is_acl_set = false; + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + let is_acl_set = file_has_acl(item.display_name.as_os_str()); write!( output_display, - "{}{} {}", + "{}{}{} {}", display_permissions(md, true), if item.security_context.len() > 1 { // GNU `ls` uses a "." character to indicate a file with a security context, @@ -2674,6 +2692,12 @@ fn display_item_long( } else { "" }, + if is_acl_set { + // if acl has been set, we display a "+" at the end of the file permissions + "+" + } else { + "" + }, pad_left(&display_symlink_count(md), padding.link_count) ) .unwrap(); diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 7d7688650..c7e72f6e2 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -7,8 +7,8 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; -use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_section, help_usage}; +use uucore::error::{set_exit_code, FromIo, UResult, USimpleError}; +use uucore::{format_usage, help_about, help_section, help_usage, show_error}; mod helper; @@ -205,9 +205,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { nl(&mut buffer, &mut stats, &settings)?; } else { let path = Path::new(file); - let reader = File::open(path).map_err_context(|| file.to_string())?; - let mut buffer = BufReader::new(reader); - nl(&mut buffer, &mut stats, &settings)?; + + if path.is_dir() { + show_error!("{}: Is a directory", path.display()); + set_exit_code(1); + } else { + let reader = File::open(path).map_err_context(|| file.to_string())?; + let mut buffer = BufReader::new(reader); + nl(&mut buffer, &mut stats, &settings)?; + } } } diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index ad42e3894..c98bb59a1 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -11,7 +11,7 @@ use std::ops::ControlFlow; use clap::{crate_version, Arg, ArgAction, Command}; use uucore::error::{UResult, UUsageError}; -use uucore::format::{parse_spec_and_escape, FormatArgument}; +use uucore::format::{parse_spec_and_escape, FormatArgument, FormatItem}; use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; @@ -38,14 +38,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { None => vec![], }; + let mut format_seen = false; let mut args = values.iter().peekable(); for item in parse_spec_and_escape(format_string.as_ref()) { + if let Ok(FormatItem::Spec(_)) = item { + format_seen = true; + } match item?.write(stdout(), &mut args)? { ControlFlow::Continue(()) => {} ControlFlow::Break(()) => return Ok(()), }; } + // Without format specs in the string, the iter would not consume any args, + // leading to an infinite loop. Thus, we exit early. + if !format_seen { + return Ok(()); + } + while args.peek().is_some() { for item in parse_spec_and_escape(format_string.as_ref()) { match item?.write(stdout(), &mut args)? { diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 92d6c1603..ef81fc353 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -45,7 +45,7 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { }; match next { FormatArgument::Char(c) => *c, - FormatArgument::Unparsed(s) => s.chars().next().unwrap_or('\0'), + FormatArgument::Unparsed(s) => s.bytes().next().map_or('\0', char::from), _ => '\0', } } diff --git a/src/uucore/src/lib/features/format/escape.rs b/src/uucore/src/lib/features/format/escape.rs index d20da3e7e..9420507f3 100644 --- a/src/uucore/src/lib/features/format/escape.rs +++ b/src/uucore/src/lib/features/format/escape.rs @@ -108,6 +108,7 @@ pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar { *rest = new_rest; match c { b'\\' => EscapedChar::Byte(b'\\'), + b'"' => EscapedChar::Byte(b'"'), b'a' => EscapedChar::Byte(b'\x07'), b'b' => EscapedChar::Byte(b'\x08'), b'c' => EscapedChar::End, diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 9b7e805f0..607c028c3 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -146,10 +146,10 @@ impl Formatter for UnsignedInt { // We also need to take into account that 0 should not be 00 // Since this is an unsigned int, we do not need to take the minus // sign into account. - if x != 0 { - format!("0{x:o}") - } else { + if x == 0 { format!("{x:o}") + } else { + format!("0{x:o}") } } UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::No) => { diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index fd55a69e4..464de9474 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -81,15 +81,19 @@ fn test_nonexisting_file() { } #[test] -fn test_folder() { +fn test_one_nonexisting_file() { let (at, mut ucmd) = at_and_ucmd!(); - let folder_name = "a_folder"; - at.mkdir(folder_name); + at.touch("abc.txt"); + at.touch("xyz.txt"); - ucmd.arg(folder_name) - .succeeds() - .stdout_only(format!("4294967295 0 {folder_name}\n")); + ucmd.arg("abc.txt") + .arg("asdf.txt") + .arg("xyz.txt") + .fails() + .stdout_contains_line("4294967295 0 xyz.txt") + .stderr_contains("asdf.txt: No such file or directory") + .stdout_contains_line("4294967295 0 abc.txt"); } // Make sure crc is correct for files larger than 32 bytes @@ -288,15 +292,76 @@ fn test_length_is_zero() { } #[test] -fn test_blake2b_fail_on_directory() { +fn test_raw_single_file() { + for algo in ALGOS { + new_ucmd!() + .arg("--raw") + .arg("lorem_ipsum.txt") + .arg(format!("--algorithm={algo}")) + .succeeds() + .no_stderr() + .stdout_is_fixture_bytes(format!("raw/{algo}_single_file.expected")); + } +} +#[test] +fn test_raw_multiple_files() { + new_ucmd!() + .arg("--raw") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .fails() + .no_stdout() + .stderr_contains("cksum: the --raw option is not supported with multiple files") + .code_is(1); +} + +#[test] +fn test_fail_on_folder() { let (at, mut ucmd) = at_and_ucmd!(); let folder_name = "a_folder"; at.mkdir(folder_name); - ucmd.arg("--algorithm=blake2b") - .arg(folder_name) + ucmd.arg(folder_name) .fails() .no_stdout() .stderr_contains(format!("cksum: {folder_name}: Is a directory")); } + +#[test] +fn test_all_algorithms_fail_on_folder() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + + let folder_name = "a_folder"; + at.mkdir(folder_name); + + for algo in ALGOS { + scene + .ucmd() + .arg(format!("--algorithm={algo}")) + .arg(folder_name) + .fails() + .no_stdout() + .stderr_contains(format!("cksum: {folder_name}: Is a directory")); + } +} + +#[test] +fn test_folder_and_file() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + + let folder_name = "a_folder"; + at.mkdir(folder_name); + + scene + .ucmd() + .arg(folder_name) + .arg("lorem_ipsum.txt") + .fails() + .stderr_contains(format!("cksum: {folder_name}: Is a directory")) + .stdout_is_fixture("crc_single_file.expected"); +} diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index cb6b7a8dc..5d78c5996 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -566,6 +566,22 @@ fn test_cp_arg_link_with_dest_hardlink_to_source() { assert!(at.file_exists(hardlink)); } +#[test] +#[cfg(target_os = "linux")] +fn test_cp_arg_link_with_same_file() { + use std::os::linux::fs::MetadataExt; + + let (at, mut ucmd) = at_and_ucmd!(); + let file = "file"; + + at.touch(file); + + ucmd.args(&["--link", file, file]).succeeds(); + + assert_eq!(at.metadata(file).st_nlink(), 1); + assert!(at.file_exists(file)); +} + #[test] fn test_cp_arg_symlink() { let (at, mut ucmd) = at_and_ucmd!(); @@ -715,6 +731,25 @@ fn test_cp_arg_backup_with_dest_a_symlink() { assert_eq!(original, at.resolve_link(backup)); } +#[test] +fn test_cp_arg_backup_with_dest_a_symlink_to_source() { + let (at, mut ucmd) = at_and_ucmd!(); + let source = "source"; + let source_content = "content"; + let symlink = "symlink"; + let backup = "symlink~"; + + at.write(source, source_content); + at.symlink_file(source, symlink); + + ucmd.arg("-b").arg(source).arg(symlink).succeeds(); + + assert!(!at.symlink_exists(symlink)); + assert_eq!(source_content, at.read(symlink)); + assert!(at.symlink_exists(backup)); + assert_eq!(source, at.resolve_link(backup)); +} + #[test] fn test_cp_arg_backup_with_other_args() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 112dc0fd3..57e6666d3 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -255,3 +255,13 @@ fn test_equal_as_delimiter3() { .succeeds() .stdout_only_bytes("abZcd\n"); } + +#[test] +fn test_multiple() { + let result = new_ucmd!() + .args(&["-f2", "-d:", "-d="]) + .pipe_in("a=b\n") + .succeeds(); + assert_eq!(result.stdout_str(), "b\n"); + assert_eq!(result.stderr_str(), ""); +} diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index cc17cdf95..0b0e98aa1 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -391,7 +391,7 @@ fn test_read_backwards_bytes_proc_fs_version() { let args = ["-c", "-1", "/proc/version"]; let result = ts.ucmd().args(&args).succeeds(); - assert!(result.stdout().len() > 0); + assert!(!result.stdout().is_empty()); } #[cfg(all( @@ -406,7 +406,7 @@ fn test_read_backwards_bytes_proc_fs_modules() { let args = ["-c", "-1", "/proc/modules"]; let result = ts.ucmd().args(&args).succeeds(); - assert!(result.stdout().len() > 0); + assert!(!result.stdout().is_empty()); } #[cfg(all( @@ -421,7 +421,7 @@ fn test_read_backwards_lines_proc_fs_modules() { let args = ["--lines", "-1", "/proc/modules"]; let result = ts.ucmd().args(&args).succeeds(); - assert!(result.stdout().len() > 0); + assert!(!result.stdout().is_empty()); } #[cfg(all( diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 0162b0170..1262c2ab9 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -4293,3 +4293,39 @@ fn test_term_colorterm() { "exe" ); } + +#[cfg(all(unix, not(target_os = "macos")))] +#[test] +fn test_acl_display() { + use std::process::Command; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let path = "a42"; + at.mkdir(path); + + let path = at.plus_as_string(path); + // calling the command directly. xattr requires some dev packages to be installed + // and it adds a complex dependency just for a test + match Command::new("setfacl") + .args(["-d", "-m", "group::rwx", &path]) + .status() + .map(|status| status.code()) + { + Ok(Some(0)) => {} + Ok(_) => { + println!("test skipped: setfacl failed"); + return; + } + Err(e) => { + println!("test skipped: setfacl failed with {}", e); + return; + } + } + + scene + .ucmd() + .args(&["-lda", &path]) + .succeeds() + .stdout_contains("+"); +} diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index 78c8975a8..a00e37a47 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -634,3 +634,20 @@ fn test_empty_section_delimiter() { .stdout_is(" 1\ta\n \n 2\tb\n"); } } + +#[test] +fn test_directory_as_input() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir = "dir"; + let file = "file"; + let content = "aaa"; + + at.mkdir(dir); + at.write(file, content); + + ucmd.arg(dir) + .arg(file) + .fails() + .stderr_is(format!("nl: {dir}: Is a directory\n")) + .stdout_contains(content); +} diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index d66ecb9ef..d09c8a2e1 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -4,6 +4,14 @@ // file that was distributed with this source code. use crate::common::util::TestScenario; +#[test] +fn test_no_args() { + new_ucmd!() + .fails() + .no_stdout() + .stderr_contains("pathchk: missing operand"); +} + #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); @@ -11,8 +19,6 @@ fn test_invalid_arg() { #[test] fn test_default_mode() { - // test the default mode - // accept some reasonable default new_ucmd!().args(&["dir/file"]).succeeds().no_stdout(); @@ -48,8 +54,6 @@ fn test_default_mode() { #[test] fn test_posix_mode() { - // test the posix mode - // accept some reasonable default new_ucmd!().args(&["-p", "dir/file"]).succeeds().no_stdout(); @@ -74,8 +78,6 @@ fn test_posix_mode() { #[test] fn test_posix_special() { - // test the posix special mode - // accept some reasonable default new_ucmd!().args(&["-P", "dir/file"]).succeeds().no_stdout(); @@ -115,8 +117,6 @@ fn test_posix_special() { #[test] fn test_posix_all() { - // test the posix special mode - // accept some reasonable default new_ucmd!() .args(&["-p", "-P", "dir/file"]) @@ -164,10 +164,3 @@ fn test_posix_all() { // fail on empty path new_ucmd!().args(&["-p", "-P", ""]).fails().no_stdout(); } - -#[test] -fn test_args_parsing() { - // fail on no args - let empty_args: [String; 0] = []; - new_ucmd!().args(&empty_args).fails().no_stdout(); -} diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index f162df490..db4c5aa7f 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -36,6 +36,11 @@ fn escaped_slash() { .stdout_only("hello\\ world"); } +#[test] +fn unescaped_double_quote() { + new_ucmd!().args(&["\\\""]).succeeds().stdout_only("\""); +} + #[test] fn escaped_hex() { new_ucmd!().args(&["\\x41"]).succeeds().stdout_only("A"); @@ -665,3 +670,13 @@ fn sub_alternative_upper_hex() { .succeeds() .stdout_only("0x2A"); } + +#[test] +fn char_as_byte() { + new_ucmd!().args(&["%c", "🙃"]).succeeds().stdout_only("ð"); +} + +#[test] +fn no_infinite_loop() { + new_ucmd!().args(&["a", "b"]).succeeds().stdout_only("a"); +} diff --git a/tests/fixtures/cksum/raw/blake2b_single_file.expected b/tests/fixtures/cksum/raw/blake2b_single_file.expected new file mode 100644 index 000000000..19b311220 --- /dev/null +++ b/tests/fixtures/cksum/raw/blake2b_single_file.expected @@ -0,0 +1 @@ +`x fWEtBlW^J0aX´85B9m \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/bsd_single_file.expected b/tests/fixtures/cksum/raw/bsd_single_file.expected new file mode 100644 index 000000000..538ab19f7 --- /dev/null +++ b/tests/fixtures/cksum/raw/bsd_single_file.expected @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/crc_single_file.expected b/tests/fixtures/cksum/raw/crc_single_file.expected new file mode 100644 index 000000000..de175b045 --- /dev/null +++ b/tests/fixtures/cksum/raw/crc_single_file.expected @@ -0,0 +1 @@ +Ph \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/md5_single_file.expected b/tests/fixtures/cksum/raw/md5_single_file.expected new file mode 100644 index 000000000..e7b47aebb Binary files /dev/null and b/tests/fixtures/cksum/raw/md5_single_file.expected differ diff --git a/tests/fixtures/cksum/raw/sha1_single_file.expected b/tests/fixtures/cksum/raw/sha1_single_file.expected new file mode 100644 index 000000000..0f634be47 --- /dev/null +++ b/tests/fixtures/cksum/raw/sha1_single_file.expected @@ -0,0 +1 @@ +к؈:=m毽(%, \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/sha224_single_file.expected b/tests/fixtures/cksum/raw/sha224_single_file.expected new file mode 100644 index 000000000..bc3fa6bf1 Binary files /dev/null and b/tests/fixtures/cksum/raw/sha224_single_file.expected differ diff --git a/tests/fixtures/cksum/raw/sha256_single_file.expected b/tests/fixtures/cksum/raw/sha256_single_file.expected new file mode 100644 index 000000000..b9ce06f39 --- /dev/null +++ b/tests/fixtures/cksum/raw/sha256_single_file.expected @@ -0,0 +1 @@ + PP 0P g^ SkEC[+? \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/sha384_single_file.expected b/tests/fixtures/cksum/raw/sha384_single_file.expected new file mode 100644 index 000000000..4b337f859 --- /dev/null +++ b/tests/fixtures/cksum/raw/sha384_single_file.expected @@ -0,0 +1,3 @@ +K + 2iJMϸ/gLZ{WZ3S +H \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/sha512_single_file.expected b/tests/fixtures/cksum/raw/sha512_single_file.expected new file mode 100644 index 000000000..79f78cf00 --- /dev/null +++ b/tests/fixtures/cksum/raw/sha512_single_file.expected @@ -0,0 +1 @@ +Td%VՎsؚ!Yyu)f|T,Bn^OऊVgD k!= \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/sm3_single_file.expected b/tests/fixtures/cksum/raw/sm3_single_file.expected new file mode 100644 index 000000000..3506406dc --- /dev/null +++ b/tests/fixtures/cksum/raw/sm3_single_file.expected @@ -0,0 +1 @@ +m)k] (0CyMg@pi \ No newline at end of file diff --git a/tests/fixtures/cksum/raw/sysv_single_file.expected b/tests/fixtures/cksum/raw/sysv_single_file.expected new file mode 100644 index 000000000..b975da49c --- /dev/null +++ b/tests/fixtures/cksum/raw/sysv_single_file.expected @@ -0,0 +1 @@ +I \ No newline at end of file