From ab77eaea454eb247b3fdee20aecc385a270db488 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 14 Nov 2024 22:04:17 +0100 Subject: [PATCH 001/351] fmt: generate an error if the input is a directory tested by tests/misc/read-errors --- src/uu/fmt/src/fmt.rs | 7 +++++++ tests/by-util/test_fmt.rs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 007e75dd64e..bb2e1a9780f 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -189,6 +189,13 @@ fn process_file( _ => { let f = File::open(file_name) .map_err_context(|| format!("cannot open {} for reading", file_name.quote()))?; + if f.metadata() + .map_err_context(|| format!("cannot get metadata for {}", file_name.quote()))? + .is_dir() + { + return Err(USimpleError::new(1, "read error".to_string())); + } + Box::new(f) as Box } }); diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 9bb82ede562..fb641643058 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -9,6 +9,11 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_invalid_input() { + new_ucmd!().arg(".").fails().code_is(1); +} + #[test] fn test_fmt() { new_ucmd!() From 6f43aa3739bbb27ebb95611f0e86203d5e48f21f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 18 Jul 2024 23:42:31 +0200 Subject: [PATCH 002/351] fuzzing: add a function to generate a file with random content --- fuzz/fuzz_targets/fuzz_common.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 4cc04c8dc0b..ba6c0a40253 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -5,10 +5,13 @@ use libc::STDIN_FILENO; use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; +use rand::distributions::Uniform; use rand::prelude::SliceRandom; use rand::Rng; use similar::TextDiff; +use std::env::temp_dir; use std::ffi::OsString; +use std::fs::File; use std::io::{Seek, SeekFrom, Write}; use std::os::fd::{AsRawFd, RawFd}; use std::process::{Command, Stdio}; @@ -392,3 +395,24 @@ pub fn generate_random_string(max_length: usize) -> String { result } + +pub fn generate_random_file() -> Result { + let mut rng = rand::thread_rng(); + + let file_name: String = (0..10) + .map(|_| rng.sample(Uniform::new_inclusive(b'a', b'z')) as char) + .collect(); + let mut file_path = temp_dir(); + file_path.push(file_name); + + let mut file = File::create(&file_path)?; + + let content_length = rng.gen_range(10..1000); + let content: String = (0..content_length) + .map(|_| (rng.gen_range(b' '..b'~' + 1) as char)) + .collect(); + + file.write_all(content.as_bytes())?; + + Ok(file_path.to_str().unwrap().to_string()) +} From 04f130ac08115c013c1603cbdedf0f0fc68599dc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 18 Jul 2024 23:43:24 +0200 Subject: [PATCH 003/351] fuzzing: add a new fuzzer for cksum --- fuzz/Cargo.lock | 289 ++++++++++++++++++++++++++++--- fuzz/Cargo.toml | 7 + fuzz/fuzz_targets/fuzz_cksum.rs | 164 ++++++++++++++++++ fuzz/fuzz_targets/fuzz_common.rs | 6 +- 4 files changed, 439 insertions(+), 27 deletions(-) create mode 100644 fuzz/fuzz_targets/fuzz_cksum.rs diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4f7c5608958..a95718bd4d4 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -81,6 +81,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "autocfg" version = "1.3.0" @@ -121,6 +133,39 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.9.1" @@ -146,13 +191,13 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "cc" -version = "1.0.98" +version = "1.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -245,12 +290,27 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -282,6 +342,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "ctrlc" version = "3.4.4" @@ -292,6 +362,42 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "data-encoding-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +dependencies = [ + "data-encoding", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dlv-list" version = "0.5.2" @@ -335,6 +441,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -358,6 +474,12 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -414,6 +536,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "libc" version = "0.2.161" @@ -449,6 +580,16 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.2" @@ -743,21 +884,79 @@ checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "similar" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +[[package]] +name = "sm3" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebb9a3b702d0a7e33bc4d85a14456633d2b165c2ad839c5fd9a8417c1ab15860" +dependencies = [ + "digest", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.65" @@ -809,7 +1008,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", ] [[package]] @@ -827,6 +1026,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -845,9 +1050,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uu_cksum" +version = "0.0.28" +dependencies = [ + "clap", + "hex", + "regex", + "uucore", +] + [[package]] name = "uu_cut" -version = "0.0.27" +version = "0.0.28" dependencies = [ "bstr", "clap", @@ -857,7 +1072,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.27" +version = "0.0.28" dependencies = [ "chrono", "clap", @@ -869,7 +1084,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "uucore", @@ -877,7 +1092,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "nix 0.29.0", @@ -887,7 +1102,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "num-bigint", @@ -898,7 +1113,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "uucore", @@ -906,7 +1121,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.27" +version = "0.0.28" dependencies = [ "bigdecimal", "clap", @@ -917,7 +1132,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.27" +version = "0.0.28" dependencies = [ "binary-heap-plus", "clap", @@ -937,7 +1152,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "memchr", @@ -946,7 +1161,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "libc", @@ -955,7 +1170,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "nom", @@ -964,7 +1179,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.27" +version = "0.0.28" dependencies = [ "bytecount", "clap", @@ -977,21 +1192,36 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.27" +version = "0.0.28" dependencies = [ + "blake2b_simd", + "blake3", "clap", + "data-encoding", + "data-encoding-macro", + "digest", "dunce", "glob", + "hex", "itertools", "libc", + "md-5", + "memchr", "nix 0.29.0", "number_prefix", "once_cell", "os_display", + "regex", + "sha1", + "sha2", + "sha3", + "sm3", + "thiserror", "uucore_procs", "wild", "winapi-util", "windows-sys 0.59.0", + "z85", ] [[package]] @@ -1003,6 +1233,7 @@ dependencies = [ "rand", "similar", "tempfile", + "uu_cksum", "uu_cut", "uu_date", "uu_echo", @@ -1020,7 +1251,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.27" +version = "0.0.28" dependencies = [ "proc-macro2", "quote", @@ -1029,7 +1260,13 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.27" +version = "0.0.28" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1058,7 +1295,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.65", "wasm-bindgen-shared", ] @@ -1080,7 +1317,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.65", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1265,3 +1502,9 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "z85" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a599daf1b507819c1121f0bf87fa37eb19daac6aff3aefefd4e6e2e0f2020fc" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 3bc5a3433bc..cc2df2d4215 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -27,6 +27,7 @@ uu_cut = { path = "../src/uu/cut/" } uu_split = { path = "../src/uu/split/" } uu_tr = { path = "../src/uu/tr/" } uu_env = { path = "../src/uu/env/" } +uu_cksum = { path = "../src/uu/cksum/" } # Prevent this from interfering with workspaces [workspace] @@ -127,3 +128,9 @@ name = "fuzz_env" path = "fuzz_targets/fuzz_env.rs" test = false doc = false + +[[bin]] +name = "fuzz_cksum" +path = "fuzz_targets/fuzz_cksum.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/fuzz_cksum.rs b/fuzz/fuzz_targets/fuzz_cksum.rs new file mode 100644 index 00000000000..411b21aab52 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_cksum.rs @@ -0,0 +1,164 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore chdir + +#![no_main] +use libfuzzer_sys::fuzz_target; +use std::ffi::OsString; +use uu_cksum::uumain; +mod fuzz_common; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_file, generate_random_string, + run_gnu_cmd, CommandResult, +}; +use rand::Rng; +use std::env::temp_dir; +use std::fs::{self, File}; +use std::io::Write; +use std::process::Command; + +static CMD_PATH: &str = "cksum"; + +fn generate_cksum_args() -> Vec { + let mut rng = rand::thread_rng(); + let mut args = Vec::new(); + + let digests = [ + "sysv", "bsd", "crc", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", "blake2b", + "sm3", + ]; + let digest_opts = [ + "--base64", + "--raw", + "--tag", + "--untagged", + "--text", + "--binary", + ]; + + if rng.gen_bool(0.3) { + args.push("-a".to_string()); + args.push(digests[rng.gen_range(0..digests.len())].to_string()); + } + + if rng.gen_bool(0.2) { + args.push(digest_opts[rng.gen_range(0..digest_opts.len())].to_string()); + } + + if rng.gen_bool(0.15) { + args.push("-l".to_string()); + args.push(rng.gen_range(8..513).to_string()); + } + + if rng.gen_bool(0.05) { + for _ in 0..rng.gen_range(0..3) { + args.push(format!("file_{}", generate_random_string(5))); + } + } else { + args.push("-c".to_string()); + } + + if rng.gen_bool(0.25) { + if let Ok(file_path) = generate_random_file() { + args.push(file_path); + } + } + + if args.is_empty() || !args.iter().any(|arg| arg.starts_with("file_")) { + args.push("-a".to_string()); + args.push(digests[rng.gen_range(0..digests.len())].to_string()); + + if let Ok(file_path) = generate_random_file() { + args.push(file_path); + } + } + + args +} + +fn generate_checksum_file( + algo: &str, + file_path: &str, + digest_opts: &[&str], +) -> Result { + let checksum_file_path = temp_dir().join("checksum_file"); + let mut cmd = Command::new(CMD_PATH); + cmd.arg("-a").arg(algo); + + for opt in digest_opts { + cmd.arg(opt); + } + + cmd.arg(file_path); + let output = cmd.output()?; + + let mut checksum_file = File::create(&checksum_file_path)?; + checksum_file.write_all(&output.stdout)?; + + Ok(checksum_file_path.to_str().unwrap().to_string()) +} + +fn select_random_digest_opts<'a>( + rng: &mut rand::rngs::ThreadRng, + digest_opts: &'a [&'a str], +) -> Vec<&'a str> { + digest_opts + .iter() + .filter(|_| rng.gen_bool(0.5)) + .copied() + .collect() +} + +fuzz_target!(|_data: &[u8]| { + let cksum_args = generate_cksum_args(); + let mut args = vec![OsString::from("cksum")]; + args.extend(cksum_args.iter().map(OsString::from)); + + if let Ok(file_path) = generate_random_file() { + let algo = cksum_args + .iter() + .position(|arg| arg == "-a") + .map_or("md5", |index| &cksum_args[index + 1]); + + let all_digest_opts = ["--base64", "--raw", "--tag", "--untagged"]; + let mut rng = rand::thread_rng(); + let selected_digest_opts = select_random_digest_opts(&mut rng, &all_digest_opts); + + if let Ok(checksum_file_path) = + generate_checksum_file(algo, &file_path, &selected_digest_opts) + { + if let Ok(content) = fs::read_to_string(&checksum_file_path) { + println!("File content: {checksum_file_path}={content}"); + } else { + eprintln!("Error reading the checksum file."); + } + println!("args: {:?}", args); + let rust_result = generate_and_run_uumain(&args, uumain, None); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "cksum", + &format!("{:?}", &args[1..]), + None, + &rust_result, + &gnu_result, + false, + ); + } + } +}); diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index ba6c0a40253..f9d974cf779 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -5,7 +5,6 @@ use libc::STDIN_FILENO; use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; -use rand::distributions::Uniform; use rand::prelude::SliceRandom; use rand::Rng; use similar::TextDiff; @@ -398,9 +397,8 @@ pub fn generate_random_string(max_length: usize) -> String { pub fn generate_random_file() -> Result { let mut rng = rand::thread_rng(); - let file_name: String = (0..10) - .map(|_| rng.sample(Uniform::new_inclusive(b'a', b'z')) as char) + .map(|_| rng.gen_range(b'a'..=b'z') as char) .collect(); let mut file_path = temp_dir(); file_path.push(file_name); @@ -409,7 +407,7 @@ pub fn generate_random_file() -> Result { let content_length = rng.gen_range(10..1000); let content: String = (0..content_length) - .map(|_| (rng.gen_range(b' '..b'~' + 1) as char)) + .map(|_| (rng.gen_range(b' '..=b'~') as char)) .collect(); file.write_all(content.as_bytes())?; From a318b775ce4b3f70cf0bff6509b8adafff7eb195 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 18 Jul 2024 23:44:37 +0200 Subject: [PATCH 004/351] fuzzing/cksum: run in the CI --- .github/workflows/fuzzing.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index df40b123679..3adc5273480 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -55,6 +55,7 @@ jobs: - { name: fuzz_split, should_pass: false } - { name: fuzz_tr, should_pass: false } - { name: fuzz_env, should_pass: false } + - { name: fuzz_cksum, should_pass: false } - { name: fuzz_parse_glob, should_pass: true } - { name: fuzz_parse_size, should_pass: true } - { name: fuzz_parse_time, should_pass: true } From 07b9fb8a2072a59cb2f3d0bce26520c3bc991629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 25 Oct 2024 02:43:39 +0200 Subject: [PATCH 005/351] feat(checksum): group flags under a ChecksumOptions struct --- src/uu/cksum/src/cksum.rs | 21 +++++++------ src/uu/hashsum/src/hashsum.rs | 16 ++++++---- src/uucore/src/lib/features/checksum.rs | 40 ++++++++++++++----------- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index e7d73a3bbf5..2392660ee89 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -13,8 +13,8 @@ use std::iter; use std::path::Path; use uucore::checksum::{ calculate_blake2b_length, detect_algo, digest_reader, perform_checksum_validation, - ChecksumError, ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC, - ALGORITHM_OPTIONS_SYSV, SUPPORTED_ALGORITHMS, + ChecksumError, ChecksumOptions, ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD, + ALGORITHM_OPTIONS_CRC, ALGORITHM_OPTIONS_SYSV, SUPPORTED_ALGORITHMS, }; use uucore::{ encoding, @@ -318,17 +318,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { || iter::once(OsStr::new("-")).collect::>(), |files| files.map(OsStr::new).collect::>(), ); - return perform_checksum_validation( - files.iter().copied(), - strict, - status, - warn, - binary_flag, + let opts = ChecksumOptions { + binary: binary_flag, ignore_missing, quiet, - algo_option, - length, - ); + status, + strict, + warn, + }; + + return perform_checksum_validation(files.iter().copied(), algo_option, length, opts); } let (tag, asterisk) = handle_tag_text_binary_flags(&matches)?; diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 90c8c8adfa7..1d3a758f5ea 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -23,6 +23,7 @@ use uucore::checksum::digest_reader; use uucore::checksum::escape_filename; use uucore::checksum::perform_checksum_validation; use uucore::checksum::ChecksumError; +use uucore::checksum::ChecksumOptions; use uucore::checksum::HashAlgorithm; use uucore::error::{FromIo, UResult}; use uucore::sum::{Digest, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; @@ -239,18 +240,21 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { || iter::once(OsStr::new("-")).collect::>(), |files| files.map(OsStr::new).collect::>(), ); + let opts = ChecksumOptions { + binary, + ignore_missing, + quiet, + status, + strict, + warn, + }; // Execute the checksum validation return perform_checksum_validation( input.iter().copied(), - strict, - status, - warn, - binary, - ignore_missing, - quiet, Some(algo.name), Some(algo.bits), + opts, ); } else if quiet { return Err(ChecksumError::QuietNotCheck.into()); diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index a2de28bc560..2450bf80495 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -75,6 +75,17 @@ struct ChecksumResult { pub failed_open_file: i32, } +/// This struct regroups CLI flags. +#[derive(Debug, Default, Clone, Copy)] +pub struct ChecksumOptions { + pub binary: bool, + pub ignore_missing: bool, + pub quiet: bool, + pub status: bool, + pub strict: bool, + pub warn: bool, +} + #[derive(Debug, Error)] pub enum ChecksumError { #[error("the --raw option is not supported with multiple files")] @@ -505,17 +516,11 @@ fn identify_algo_name_and_length( /*** * Do the checksum validation (can be strict or not) */ -#[allow(clippy::too_many_arguments)] pub fn perform_checksum_validation<'a, I>( files: I, - strict: bool, - status: bool, - warn: bool, - binary: bool, - ignore_missing: bool, - quiet: bool, algo_name_input: Option<&str>, length_input: Option, + opts: ChecksumOptions, ) -> UResult<()> where I: Iterator, @@ -610,7 +615,8 @@ where // manage the input file let file_to_check = - match get_file_to_check(&real_filename_to_check, ignore_missing, &mut res) { + match get_file_to_check(&real_filename_to_check, opts.ignore_missing, &mut res) + { Some(file) => file, None => continue, }; @@ -619,11 +625,11 @@ where let create_fn = &mut algo.create_fn; let mut digest = create_fn(); let (calculated_checksum, _) = - digest_reader(&mut digest, &mut file_reader, binary, algo.bits).unwrap(); + digest_reader(&mut digest, &mut file_reader, opts.binary, algo.bits).unwrap(); // Do the checksum validation if expected_checksum == calculated_checksum { - if !quiet && !status { + if !opts.quiet && !opts.status { print_file_report( std::io::stdout(), filename_to_check, @@ -633,7 +639,7 @@ where } correct_format += 1; } else { - if !status { + if !opts.status { print_file_report( std::io::stdout(), filename_to_check, @@ -648,7 +654,7 @@ where // Don't show any warning for empty or commented lines. continue; } - if warn { + if opts.warn { let algo = if let Some(algo_name_input) = algo_name_input { algo_name_input.to_uppercase() } else { @@ -670,7 +676,7 @@ where // not a single line correctly formatted found // return an error if !properly_formatted { - if !status { + if !opts.status { return Err(ChecksumError::NoProperlyFormattedChecksumLinesFound { filename: get_filename_for_output(filename_input, input_is_stdin), } @@ -682,9 +688,9 @@ where } // if any incorrectly formatted line, show it - cksum_output(&res, status); + cksum_output(&res, opts.status); - if ignore_missing && correct_format == 0 { + if opts.ignore_missing && correct_format == 0 { // we have only bad format // and we had ignore-missing eprintln!( @@ -696,13 +702,13 @@ where } // strict means that we should have an exit code. - if strict && res.bad_format > 0 { + if opts.strict && res.bad_format > 0 { set_exit_code(1); } // if we have any failed checksum verification, we set an exit code // except if we have ignore_missing - if (res.failed_cksum > 0 || res.failed_open_file > 0) && !ignore_missing { + if (res.failed_cksum > 0 || res.failed_open_file > 0) && !opts.ignore_missing { set_exit_code(1); } } From df0da55645fe1062791b53d0fbb1d310e2920ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 25 Oct 2024 02:52:39 +0200 Subject: [PATCH 006/351] feat(checksum): extract line processing into a separate function --- src/uucore/src/lib/features/checksum.rs | 270 ++++++++++++++---------- 1 file changed, 159 insertions(+), 111 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 2450bf80495..a29fe0767cb 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -75,6 +75,23 @@ struct ChecksumResult { pub failed_open_file: i32, } +enum LineCheckError { + UError(Box), + // ImproperlyFormatted, +} + +impl From> for LineCheckError { + fn from(value: Box) -> Self { + Self::UError(value) + } +} + +impl From for LineCheckError { + fn from(value: ChecksumError) -> Self { + Self::UError(Box::new(value)) + } +} + /// This struct regroups CLI flags. #[derive(Debug, Default, Clone, Copy)] pub struct ChecksumOptions { @@ -513,6 +530,132 @@ fn identify_algo_name_and_length( Some((algorithm, bits)) } +#[allow(clippy::too_many_arguments)] +fn process_checksum_line( + filename_input: &OsStr, + line: &OsStr, + i: usize, + chosen_regex: &Regex, + is_algo_based_format: bool, + res: &mut ChecksumResult, + cli_algo_name: Option<&str>, + cli_algo_length: Option, + properly_formatted: &mut bool, + correct_format: &mut usize, + opts: ChecksumOptions, +) -> Result<(), LineCheckError> { + let line_bytes = os_str_as_bytes(line)?; + if let Some(caps) = chosen_regex.captures(line_bytes) { + *properly_formatted = true; + + let mut filename_to_check = caps.name("filename").unwrap().as_bytes(); + + if filename_to_check.starts_with(b"*") + && i == 0 + && chosen_regex.as_str() == SINGLE_SPACE_REGEX + { + // Remove the leading asterisk if present - only for the first line + filename_to_check = &filename_to_check[1..]; + } + + let expected_checksum = get_expected_checksum(filename_to_check, &caps, chosen_regex)?; + + // If the algo_name is provided, we use it, otherwise we try to detect it + let (algo_name, length) = if is_algo_based_format { + identify_algo_name_and_length(&caps, cli_algo_name, res, properly_formatted) + .unwrap_or((String::new(), None)) + } else if let Some(a) = cli_algo_name { + // When a specific algorithm name is input, use it and use the provided bits + // except when dealing with blake2b, where we will detect the length + if cli_algo_name == Some(ALGORITHM_OPTIONS_BLAKE2B) { + // division by 2 converts the length of the Blake2b checksum from hexadecimal + // characters to bytes, as each byte is represented by two hexadecimal characters. + let length = Some(expected_checksum.len() / 2); + (ALGORITHM_OPTIONS_BLAKE2B.to_string(), length) + } else { + (a.to_lowercase(), cli_algo_length) + } + } else { + // Default case if no algorithm is specified and non-algo based format is matched + (String::new(), None) + }; + + if algo_name.is_empty() { + // we haven't been able to detect the algo name. No point to continue + *properly_formatted = false; + + // TODO: return error? + return Ok(()); + } + let mut algo = detect_algo(&algo_name, length)?; + + let (filename_to_check_unescaped, prefix) = unescape_filename(filename_to_check); + + let real_filename_to_check = os_str_from_bytes(&filename_to_check_unescaped)?; + + // manage the input file + let file_to_check = + match get_file_to_check(&real_filename_to_check, opts.ignore_missing, res) { + Some(file) => file, + // TODO: return error? + None => return Ok(()), + }; + let mut file_reader = BufReader::new(file_to_check); + // Read the file and calculate the checksum + let create_fn = &mut algo.create_fn; + let mut digest = create_fn(); + let (calculated_checksum, _) = + digest_reader(&mut digest, &mut file_reader, opts.binary, algo.bits).unwrap(); + + // Do the checksum validation + if expected_checksum == calculated_checksum { + if !opts.quiet && !opts.status { + print_file_report( + std::io::stdout(), + filename_to_check, + FileChecksumResult::Ok, + prefix, + ); + } + *correct_format += 1; + } else { + if !opts.status { + print_file_report( + std::io::stdout(), + filename_to_check, + FileChecksumResult::Failed, + prefix, + ); + } + res.failed_cksum += 1; + } + } else { + if line.is_empty() || line_bytes.starts_with(b"#") { + // Don't show any warning for empty or commented lines. + + // TODO: return error? + return Ok(()); + } + if opts.warn { + let algo = if let Some(algo_name_input) = cli_algo_name { + algo_name_input.to_uppercase() + } else { + "Unknown algorithm".to_string() + }; + eprintln!( + "{}: {}: {}: improperly formatted {} checksum line", + util_name(), + &filename_input.maybe_quote(), + i + 1, + algo + ); + } + + res.bad_format += 1; + } + Ok(()) +} + /*** * Do the checksum validation (can be strict or not) */ @@ -560,117 +703,22 @@ where }; for (i, line) in lines.iter().enumerate() { - let line_bytes = os_str_as_bytes(line)?; - if let Some(caps) = chosen_regex.captures(line_bytes) { - properly_formatted = true; - - let mut filename_to_check = caps.name("filename").unwrap().as_bytes(); - - if filename_to_check.starts_with(b"*") - && i == 0 - && chosen_regex.as_str() == SINGLE_SPACE_REGEX - { - // Remove the leading asterisk if present - only for the first line - filename_to_check = &filename_to_check[1..]; - } - - let expected_checksum = - get_expected_checksum(filename_to_check, &caps, &chosen_regex)?; - - // If the algo_name is provided, we use it, otherwise we try to detect it - let (algo_name, length) = if is_algo_based_format { - identify_algo_name_and_length( - &caps, - algo_name_input, - &mut res, - &mut properly_formatted, - ) - .unwrap_or((String::new(), None)) - } else if let Some(a) = algo_name_input { - // When a specific algorithm name is input, use it and use the provided bits - // except when dealing with blake2b, where we will detect the length - if algo_name_input == Some(ALGORITHM_OPTIONS_BLAKE2B) { - // division by 2 converts the length of the Blake2b checksum from hexadecimal - // characters to bytes, as each byte is represented by two hexadecimal characters. - let length = Some(expected_checksum.len() / 2); - (ALGORITHM_OPTIONS_BLAKE2B.to_string(), length) - } else { - (a.to_lowercase(), length_input) - } - } else { - // Default case if no algorithm is specified and non-algo based format is matched - (String::new(), None) - }; - - if algo_name.is_empty() { - // we haven't been able to detect the algo name. No point to continue - properly_formatted = false; - continue; - } - let mut algo = detect_algo(&algo_name, length)?; - - let (filename_to_check_unescaped, prefix) = unescape_filename(filename_to_check); - - let real_filename_to_check = os_str_from_bytes(&filename_to_check_unescaped)?; - - // manage the input file - let file_to_check = - match get_file_to_check(&real_filename_to_check, opts.ignore_missing, &mut res) - { - Some(file) => file, - None => continue, - }; - let mut file_reader = BufReader::new(file_to_check); - // Read the file and calculate the checksum - let create_fn = &mut algo.create_fn; - let mut digest = create_fn(); - let (calculated_checksum, _) = - digest_reader(&mut digest, &mut file_reader, opts.binary, algo.bits).unwrap(); - - // Do the checksum validation - if expected_checksum == calculated_checksum { - if !opts.quiet && !opts.status { - print_file_report( - std::io::stdout(), - filename_to_check, - FileChecksumResult::Ok, - prefix, - ); - } - correct_format += 1; - } else { - if !opts.status { - print_file_report( - std::io::stdout(), - filename_to_check, - FileChecksumResult::Failed, - prefix, - ); - } - res.failed_cksum += 1; - } - } else { - if line.is_empty() || line_bytes.starts_with(b"#") { - // Don't show any warning for empty or commented lines. - continue; - } - if opts.warn { - let algo = if let Some(algo_name_input) = algo_name_input { - algo_name_input.to_uppercase() - } else { - "Unknown algorithm".to_string() - }; - eprintln!( - "{}: {}: {}: improperly formatted {} checksum line", - util_name(), - &filename_input.maybe_quote(), - i + 1, - algo - ); - } - - res.bad_format += 1; - } + match process_checksum_line( + filename_input, + line, + i, + &chosen_regex, + is_algo_based_format, + &mut res, + algo_name_input, + length_input, + &mut properly_formatted, + &mut correct_format, + opts, + ) { + Ok(_) => (), + Err(LineCheckError::UError(e)) => return Err(e), + }; } // not a single line correctly formatted found From afcf93b3e39ee6fb13b67d1a63d83ea4a9fa6226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 25 Oct 2024 03:17:17 +0200 Subject: [PATCH 007/351] feat(checksum): extract file processing into a separate function --- src/uucore/src/lib/features/checksum.rs | 210 ++++++++++++++---------- 1 file changed, 122 insertions(+), 88 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index a29fe0767cb..81819053325 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -92,6 +92,25 @@ impl From for LineCheckError { } } +#[allow(clippy::enum_variant_names)] +enum FileCheckError { + UError(Box), + NonCriticalError, + CriticalError, +} + +impl From> for FileCheckError { + fn from(value: Box) -> Self { + Self::UError(value) + } +} + +impl From for FileCheckError { + fn from(value: ChecksumError) -> Self { + Self::UError(Box::new(value)) + } +} + /// This struct regroups CLI flags. #[derive(Debug, Default, Clone, Copy)] pub struct ChecksumOptions { @@ -656,6 +675,104 @@ fn process_checksum_line( Ok(()) } +fn process_checksum_file( + filename_input: &OsStr, + cli_algo_name: Option<&str>, + cli_algo_length: Option, + opts: ChecksumOptions, +) -> Result<(), FileCheckError> { + let mut correct_format = 0; + let mut properly_formatted = false; + let mut res = ChecksumResult::default(); + let input_is_stdin = filename_input == OsStr::new("-"); + + let file: Box = if input_is_stdin { + // Use stdin if "-" is specified + Box::new(stdin()) + } else { + match get_input_file(filename_input) { + Ok(f) => f, + Err(e) => { + // Could not read the file, show the error and continue to the next file + show_error!("{e}"); + set_exit_code(1); + return Err(FileCheckError::NonCriticalError); + } + } + }; + + let reader = BufReader::new(file); + let lines = read_os_string_lines(reader).collect::>(); + + let Some((chosen_regex, is_algo_based_format)) = determine_regex(&lines) else { + let e = ChecksumError::NoProperlyFormattedChecksumLinesFound { + filename: get_filename_for_output(filename_input, input_is_stdin), + }; + show_error!("{e}"); + set_exit_code(1); + return Err(FileCheckError::NonCriticalError); + }; + + for (i, line) in lines.iter().enumerate() { + match process_checksum_line( + filename_input, + line, + i, + &chosen_regex, + is_algo_based_format, + &mut res, + cli_algo_name, + cli_algo_length, + &mut properly_formatted, + &mut correct_format, + opts, + ) { + Ok(_) => (), + Err(LineCheckError::UError(e)) => return Err(e.into()), + }; + } + + // not a single line correctly formatted found + // return an error + if !properly_formatted { + if !opts.status { + return Err(ChecksumError::NoProperlyFormattedChecksumLinesFound { + filename: get_filename_for_output(filename_input, input_is_stdin), + } + .into()); + } + set_exit_code(1); + return Err(FileCheckError::CriticalError); + } + + // if any incorrectly formatted line, show it + cksum_output(&res, opts.status); + + if opts.ignore_missing && correct_format == 0 { + // we have only bad format + // and we had ignore-missing + eprintln!( + "{}: {}: no file was verified", + util_name(), + filename_input.maybe_quote(), + ); + set_exit_code(1); + } + + // strict means that we should have an exit code. + if opts.strict && res.bad_format > 0 { + set_exit_code(1); + } + + // if we have any failed checksum verification, we set an exit code + // except if we have ignore_missing + if (res.failed_cksum > 0 || res.failed_open_file > 0) && !opts.ignore_missing { + set_exit_code(1); + } + + Ok(()) +} + /*** * Do the checksum validation (can be strict or not) */ @@ -670,94 +787,11 @@ where { // if cksum has several input files, it will print the result for each file for filename_input in files { - let mut correct_format = 0; - let mut properly_formatted = false; - let mut res = ChecksumResult::default(); - let input_is_stdin = filename_input == OsStr::new("-"); - - let file: Box = if input_is_stdin { - // Use stdin if "-" is specified - Box::new(stdin()) - } else { - match get_input_file(filename_input) { - Ok(f) => f, - Err(e) => { - // Could not read the file, show the error and continue to the next file - show_error!("{e}"); - set_exit_code(1); - continue; - } - } - }; - - let reader = BufReader::new(file); - let lines = read_os_string_lines(reader).collect::>(); - - let Some((chosen_regex, is_algo_based_format)) = determine_regex(&lines) else { - let e = ChecksumError::NoProperlyFormattedChecksumLinesFound { - filename: get_filename_for_output(filename_input, input_is_stdin), - }; - show_error!("{e}"); - set_exit_code(1); - continue; - }; - - for (i, line) in lines.iter().enumerate() { - match process_checksum_line( - filename_input, - line, - i, - &chosen_regex, - is_algo_based_format, - &mut res, - algo_name_input, - length_input, - &mut properly_formatted, - &mut correct_format, - opts, - ) { - Ok(_) => (), - Err(LineCheckError::UError(e)) => return Err(e), - }; - } - - // not a single line correctly formatted found - // return an error - if !properly_formatted { - if !opts.status { - return Err(ChecksumError::NoProperlyFormattedChecksumLinesFound { - filename: get_filename_for_output(filename_input, input_is_stdin), - } - .into()); - } - set_exit_code(1); - - return Ok(()); - } - - // if any incorrectly formatted line, show it - cksum_output(&res, opts.status); - - if opts.ignore_missing && correct_format == 0 { - // we have only bad format - // and we had ignore-missing - eprintln!( - "{}: {}: no file was verified", - util_name(), - filename_input.maybe_quote(), - ); - set_exit_code(1); - } - - // strict means that we should have an exit code. - if opts.strict && res.bad_format > 0 { - set_exit_code(1); - } - - // if we have any failed checksum verification, we set an exit code - // except if we have ignore_missing - if (res.failed_cksum > 0 || res.failed_open_file > 0) && !opts.ignore_missing { - set_exit_code(1); + use FileCheckError::*; + match process_checksum_file(filename_input, algo_name_input, length_input, opts) { + Err(UError(e)) => return Err(e), + Err(CriticalError) => break, + Err(NonCriticalError) | Ok(_) => continue, } } From b6c726602f3f165910aa6e6f61cf3d9ee8a5d083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 25 Oct 2024 04:00:51 +0200 Subject: [PATCH 008/351] feat(checksum): get rid of `correct_format` at the line level --- src/uucore/src/lib/features/checksum.rs | 93 +++++++++++++++---------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 81819053325..c0811df310f 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -77,7 +77,8 @@ struct ChecksumResult { enum LineCheckError { UError(Box), - // ImproperlyFormatted, + Skipped, + ImproperlyFormatted, } impl From> for LineCheckError { @@ -228,6 +229,24 @@ enum FileChecksumResult { CantOpen, } +impl FileChecksumResult { + fn from_bool(checksum_correct: bool) -> Self { + if checksum_correct { + FileChecksumResult::Ok + } else { + FileChecksumResult::Failed + } + } + + fn can_display(&self, opts: ChecksumOptions) -> bool { + match self { + FileChecksumResult::Ok => !opts.status && !opts.quiet, + FileChecksumResult::Failed => !opts.status, + FileChecksumResult::CantOpen => true, + } + } +} + impl Display for FileChecksumResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -245,10 +264,13 @@ fn print_file_report( filename: &[u8], result: FileChecksumResult, prefix: &str, + opts: ChecksumOptions, ) { - let _ = write!(w, "{prefix}"); - let _ = w.write_all(filename); - let _ = writeln!(w, ": {result}"); + if result.can_display(opts) { + let _ = write!(w, "{prefix}"); + let _ = w.write_all(filename); + let _ = writeln!(w, ": {result}"); + } } pub fn detect_algo(algo: &str, length: Option) -> UResult { @@ -439,6 +461,7 @@ fn get_file_to_check( filename: &OsStr, ignore_missing: bool, res: &mut ChecksumResult, + opts: ChecksumOptions, ) -> Option> { let filename_bytes = os_str_as_bytes(filename).expect("UTF-8 error"); let filename_lossy = String::from_utf8_lossy(filename_bytes); @@ -451,6 +474,7 @@ fn get_file_to_check( filename_bytes, FileChecksumResult::CantOpen, "", + opts, ); res.failed_open_file += 1; }; @@ -549,6 +573,12 @@ fn identify_algo_name_and_length( Some((algorithm, bits)) } +/// Parses a checksum line, detect the algorithm to use, read the file and produce +/// its digest, and compare it to the expected value. +/// +/// Returns `Ok(bool)` if the comparison happened, bool indicates if the digest +/// matched the expected. +/// If the comparison didn't happen, return a `LineChecksumError`. #[allow(clippy::too_many_arguments)] fn process_checksum_line( filename_input: &OsStr, @@ -560,9 +590,8 @@ fn process_checksum_line( cli_algo_name: Option<&str>, cli_algo_length: Option, properly_formatted: &mut bool, - correct_format: &mut usize, opts: ChecksumOptions, -) -> Result<(), LineCheckError> { +) -> Result { let line_bytes = os_str_as_bytes(line)?; if let Some(caps) = chosen_regex.captures(line_bytes) { *properly_formatted = true; @@ -604,7 +633,7 @@ fn process_checksum_line( *properly_formatted = false; // TODO: return error? - return Ok(()); + return Err(LineCheckError::ImproperlyFormatted); } let mut algo = detect_algo(&algo_name, length)?; @@ -614,10 +643,10 @@ fn process_checksum_line( // manage the input file let file_to_check = - match get_file_to_check(&real_filename_to_check, opts.ignore_missing, res) { + match get_file_to_check(&real_filename_to_check, opts.ignore_missing, res, opts) { Some(file) => file, // TODO: return error? - None => return Ok(()), + None => return Err(LineCheckError::ImproperlyFormatted), }; let mut file_reader = BufReader::new(file_to_check); // Read the file and calculate the checksum @@ -627,33 +656,19 @@ fn process_checksum_line( digest_reader(&mut digest, &mut file_reader, opts.binary, algo.bits).unwrap(); // Do the checksum validation - if expected_checksum == calculated_checksum { - if !opts.quiet && !opts.status { - print_file_report( - std::io::stdout(), - filename_to_check, - FileChecksumResult::Ok, - prefix, - ); - } - *correct_format += 1; - } else { - if !opts.status { - print_file_report( - std::io::stdout(), - filename_to_check, - FileChecksumResult::Failed, - prefix, - ); - } - res.failed_cksum += 1; - } + let checksum_correct = expected_checksum == calculated_checksum; + print_file_report( + std::io::stdout(), + filename_to_check, + FileChecksumResult::from_bool(checksum_correct), + prefix, + opts, + ); + Ok(checksum_correct) } else { if line.is_empty() || line_bytes.starts_with(b"#") { // Don't show any warning for empty or commented lines. - - // TODO: return error? - return Ok(()); + return Err(LineCheckError::Skipped); } if opts.warn { let algo = if let Some(algo_name_input) = cli_algo_name { @@ -671,8 +686,8 @@ fn process_checksum_line( } res.bad_format += 1; + Err(LineCheckError::ImproperlyFormatted) } - Ok(()) } fn process_checksum_file( @@ -724,10 +739,12 @@ fn process_checksum_file( cli_algo_name, cli_algo_length, &mut properly_formatted, - &mut correct_format, opts, ) { - Ok(_) => (), + Ok(true) => correct_format += 1, + Ok(false) => res.failed_cksum += 1, + Err(LineCheckError::ImproperlyFormatted) => (), + Err(LineCheckError::Skipped) => continue, Err(LineCheckError::UError(e)) => return Err(e.into()), }; } @@ -1227,6 +1244,8 @@ mod tests { #[test] fn test_print_file_report() { + let opts = ChecksumOptions::default(); + let cases: &[(&[u8], FileChecksumResult, &str, &[u8])] = &[ (b"filename", FileChecksumResult::Ok, "", b"filename: OK\n"), ( @@ -1257,7 +1276,7 @@ mod tests { for (filename, result, prefix, expected) in cases { let mut buffer: Vec = vec![]; - print_file_report(&mut buffer, filename, *result, prefix); + print_file_report(&mut buffer, filename, *result, prefix, opts); assert_eq!(&buffer, expected) } } From 4ffedcdac6b9ae22cfe268d531ee67108fea6d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 25 Oct 2024 04:38:52 +0200 Subject: [PATCH 009/351] feat(checksum): get rid of ChecksumResult in get_file_to_check --- src/uucore/src/lib/features/checksum.rs | 45 ++++++++++++++----------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index c0811df310f..1de41232e60 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -79,6 +79,9 @@ enum LineCheckError { UError(Box), Skipped, ImproperlyFormatted, + CantOpenFile, + FileNotFound, + FileIsDirectory, } impl From> for LineCheckError { @@ -459,16 +462,14 @@ fn get_expected_checksum( /// Returns a reader that reads from the specified file, or from stdin if `filename_to_check` is "-". fn get_file_to_check( filename: &OsStr, - ignore_missing: bool, - res: &mut ChecksumResult, opts: ChecksumOptions, -) -> Option> { +) -> Result, LineCheckError> { let filename_bytes = os_str_as_bytes(filename).expect("UTF-8 error"); let filename_lossy = String::from_utf8_lossy(filename_bytes); if filename == "-" { - Some(Box::new(stdin())) // Use stdin if "-" is specified in the checksum file + Ok(Box::new(stdin())) // Use stdin if "-" is specified in the checksum file } else { - let mut failed_open = || { + let failed_open = || { print_file_report( std::io::stdout(), filename_bytes, @@ -476,30 +477,32 @@ fn get_file_to_check( "", opts, ); - res.failed_open_file += 1; }; match File::open(filename) { Ok(f) => { - if f.metadata().ok()?.is_dir() { + if f.metadata() + .map_err(|_| LineCheckError::CantOpenFile)? + .is_dir() + { show!(USimpleError::new( 1, format!("{filename_lossy}: Is a directory") )); // also regarded as a failed open failed_open(); - None + Err(LineCheckError::FileIsDirectory) } else { - Some(Box::new(f)) + Ok(Box::new(f)) } } Err(err) => { - if !ignore_missing { + if !opts.ignore_missing { // yes, we have both stderr and stdout here show!(err.map_err_context(|| filename_lossy.to_string())); failed_open(); } // we could not open the file but we want to continue - None + Err(LineCheckError::FileNotFound) } } } @@ -642,13 +645,9 @@ fn process_checksum_line( let real_filename_to_check = os_str_from_bytes(&filename_to_check_unescaped)?; // manage the input file - let file_to_check = - match get_file_to_check(&real_filename_to_check, opts.ignore_missing, res, opts) { - Some(file) => file, - // TODO: return error? - None => return Err(LineCheckError::ImproperlyFormatted), - }; + let file_to_check = get_file_to_check(&real_filename_to_check, opts)?; let mut file_reader = BufReader::new(file_to_check); + // Read the file and calculate the checksum let create_fn = &mut algo.create_fn; let mut digest = create_fn(); @@ -743,9 +742,17 @@ fn process_checksum_file( ) { Ok(true) => correct_format += 1, Ok(false) => res.failed_cksum += 1, - Err(LineCheckError::ImproperlyFormatted) => (), - Err(LineCheckError::Skipped) => continue, Err(LineCheckError::UError(e)) => return Err(e.into()), + Err(LineCheckError::Skipped) => continue, + Err(LineCheckError::ImproperlyFormatted) => (), + Err(LineCheckError::CantOpenFile | LineCheckError::FileIsDirectory) => { + res.failed_open_file += 1 + } + Err(LineCheckError::FileNotFound) => { + if !opts.ignore_missing { + res.failed_open_file += 1 + } + } }; } From 5309b65867d4899cce7ef27ca4af348c24eb8da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 3 Nov 2024 11:57:32 +0100 Subject: [PATCH 010/351] feat(checksum): change process_checksum_line return type to Result<(), LineCheckError> - Treat digest mismatch as an error --- src/uucore/src/lib/features/checksum.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 1de41232e60..333c76d2a10 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -77,6 +77,7 @@ struct ChecksumResult { enum LineCheckError { UError(Box), + DigestMismatch, Skipped, ImproperlyFormatted, CantOpenFile, @@ -594,7 +595,7 @@ fn process_checksum_line( cli_algo_length: Option, properly_formatted: &mut bool, opts: ChecksumOptions, -) -> Result { +) -> Result<(), LineCheckError> { let line_bytes = os_str_as_bytes(line)?; if let Some(caps) = chosen_regex.captures(line_bytes) { *properly_formatted = true; @@ -663,7 +664,12 @@ fn process_checksum_line( prefix, opts, ); - Ok(checksum_correct) + + if checksum_correct { + Ok(()) + } else { + Err(LineCheckError::DigestMismatch) + } } else { if line.is_empty() || line_bytes.starts_with(b"#") { // Don't show any warning for empty or commented lines. @@ -740,8 +746,8 @@ fn process_checksum_file( &mut properly_formatted, opts, ) { - Ok(true) => correct_format += 1, - Ok(false) => res.failed_cksum += 1, + Ok(()) => correct_format += 1, + Err(LineCheckError::DigestMismatch) => res.failed_cksum += 1, Err(LineCheckError::UError(e)) => return Err(e.into()), Err(LineCheckError::Skipped) => continue, Err(LineCheckError::ImproperlyFormatted) => (), From a09c7cc0d21cb7a44e26914cbc607215f0fd337a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 25 Oct 2024 13:24:43 +0200 Subject: [PATCH 011/351] feat(checksum): add doc --- src/uucore/src/lib/features/checksum.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 333c76d2a10..e7a0a265377 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -75,13 +75,22 @@ struct ChecksumResult { pub failed_open_file: i32, } +/// Represents a reason for which the processing of a checksum line +/// could not proceed to digest comparison. enum LineCheckError { + /// a generic UError was encountered in sub-functions UError(Box), + /// the computed checksum digest differs from the expected one DigestMismatch, + /// the line is empty or is a comment Skipped, + /// the line has a formatting error ImproperlyFormatted, + /// file exists but is impossible to read CantOpenFile, + /// there is nothing at the given path FileNotFound, + /// the given path leads to a directory FileIsDirectory, } @@ -97,10 +106,14 @@ impl From for LineCheckError { } } +/// Represents an error that was encountered when processing a checksum file. #[allow(clippy::enum_variant_names)] enum FileCheckError { + /// a generic UError was encountered in sub-functions UError(Box), + /// the error does not stop the processing of next files NonCriticalError, + /// the error must stop the run of the program CriticalError, } @@ -226,6 +239,8 @@ fn cksum_output(res: &ChecksumResult, status: bool) { } } +/// Represents the different outcomes that can happen to a file +/// that is being checked. #[derive(Debug, Clone, Copy)] enum FileChecksumResult { Ok, @@ -234,6 +249,8 @@ enum FileChecksumResult { } impl FileChecksumResult { + /// Creates a `FileChecksumResult` from a digest comparison that + /// either succeeded or failed. fn from_bool(checksum_correct: bool) -> Self { if checksum_correct { FileChecksumResult::Ok @@ -242,6 +259,8 @@ impl FileChecksumResult { } } + /// The cli options might prevent to display on the outcome of the + /// comparison on STDOUT. fn can_display(&self, opts: ChecksumOptions) -> bool { match self { FileChecksumResult::Ok => !opts.status && !opts.quiet, From 0bc22e8d18b20a3c673d8a36d367fcad883b7b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sat, 17 Aug 2024 01:21:59 +0200 Subject: [PATCH 012/351] test(cksum): add multiple tests test(cksum): add test for blake length gessing test(cksum): add test for hexa/base64 confusion test(cksum): add test for error handling on incorrectly formatted checksum test(cksum): add test for trailing spaces making a line improperly formatted test(cksum): Re-implement GNU test 'cksum-base64' in the testsuite --- tests/by-util/test_cksum.rs | 259 +++++++++++++++++++++++++++++++++++- 1 file changed, 258 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 98366cbec5e..ee1e05292c0 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) asdf algo algos asha mgmt xffname +// spell-checker:ignore (words) asdf algo algos asha mgmt xffname hexa GFYEQ HYQK Yqxb use crate::common::util::TestScenario; @@ -1502,3 +1502,260 @@ mod check_utf8 { .stderr_contains("1 listed file could not be read"); } } + +#[ignore = "not yet implemented"] +#[test] +fn test_check_blake_length_guess() { + let correct_lines = [ + // Correct: The length is not explicit, but the checksum's size + // matches the default parameter. + "BLAKE2b (foo.dat) = ca002330e69d3e6b84a46a56a6533fd79d51d97a3bb7cad6c2ff43b354185d6dc1e723fb3db4ae0737e120378424c714bb982d9dc5bbd7a0ab318240ddd18f8d", + // Correct: The length is explicitly given, and the checksum's size + // matches the length. + "BLAKE2b-512 (foo.dat) = ca002330e69d3e6b84a46a56a6533fd79d51d97a3bb7cad6c2ff43b354185d6dc1e723fb3db4ae0737e120378424c714bb982d9dc5bbd7a0ab318240ddd18f8d", + // Correct: the checksum size is not default but + // the length is explicitly given. + "BLAKE2b-48 (foo.dat) = 171cdfdf84ed", + ]; + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foo.dat", "foo"); + + for line in correct_lines { + at.write("foo.sums", line); + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("foo.sums")) + .succeeds() + .stdout_is("foo.dat: OK\n"); + } + + // Incorrect lines + + // This is incorrect because the algorithm provides no length, + // and the checksum length is not default. + let incorrect = "BLAKE2b (foo.dat) = 171cdfdf84ed"; + at.write("foo.sums", incorrect); + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("foo.sums")) + .fails() + .stderr_contains("foo.sums: no properly formatted checksum lines found"); +} + +#[ignore = "not yet implemented"] +#[test] +fn test_check_confusing_base64() { + let cksum = "BLAKE2b-48 (foo.dat) = fc1f97C4"; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foo.dat", "esq"); + at.write("foo.sums", cksum); + + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("foo.sums")) + .succeeds() + .stdout_is("foo.dat: OK\n"); +} + +/// This test checks that when a file contains several checksum lines +/// with different encoding, the decoding still works. +#[ignore = "not yet implemented"] +#[test] +fn test_check_mix_hex_base64() { + let b64 = "BLAKE2b-128 (foo1.dat) = BBNuJPhdRwRlw9tm5Y7VbA=="; + let hex = "BLAKE2b-128 (foo2.dat) = 04136e24f85d470465c3db66e58ed56c"; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foo1.dat", "foo"); + at.write("foo2.dat", "foo"); + + at.write("hex_b64", &format!("{hex}\n{b64}")); + at.write("b64_hex", &format!("{b64}\n{hex}")); + + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("hex_b64")) + .succeeds() + .stdout_only("foo2.dat: OK\nfoo1.dat: OK\n"); + + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("b64_hex")) + .succeeds() + .stdout_only("foo1.dat: OK\nfoo2.dat: OK\n"); +} + +#[ignore = "not yet implemented"] +#[test] +fn test_check_incorrectly_formatted_checksum_does_not_stop_processing() { + // The first line contains an incorrectly formatted checksum that can't be + // correctly decoded. This must not prevent the program from looking at the + // rest of the file. + let lines = [ + "BLAKE2b-56 (foo1) = GFYEQ7HhAw=", // Should be 2 '=' at the end + "BLAKE2b-56 (foo2) = 18560443b1e103", // OK + ]; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foo1", "foo"); + at.write("foo2", "foo"); + at.write("sum", &lines.join("\n")); + + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("sum")) + .succeeds() + .stderr_contains("1 line is improperly formatted") + .stdout_contains("foo2: OK"); +} + +/// This module reimplements the cksum-base64.pl GNU test. +mod cksum_base64 { + use super::*; + use crate::common::util::log_info; + + const PAIRS: [(&str, &str); 11] = [ + ("sysv", "0 0 f"), + ("bsd", "00000 0 f"), + ("crc", "4294967295 0 f"), + ("md5", "1B2M2Y8AsgTpgAmY7PhCfg=="), + ("sha1", "2jmj7l5rSw0yVb/vlWAYkK/YBwk="), + ("sha224", "0UoCjCo6K8lHYQK7KII0xBWisB+CjqYqxbPkLw=="), + ("sha256", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="), + ( + "sha384", + "OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb", + ), + ( + "sha512", + "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==" + ), + ( + "blake2b", + "eGoC90IBWQPGxv2FJVLScpEvR0DhWEdhiobiF/cfVBnSXhAxr+5YUxOJZESTTrBLkDpoWxRIt1XVb3Aa/pvizg==" + ), + ("sm3", "GrIdg1XPoX+OYRlIMegajyK+yMco/vt0ftA161CCqis="), + ]; + + fn make_scene() -> TestScenario { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("f"); + + scene + } + + fn output_format(algo: &str, digest: &str) -> String { + if ["sysv", "bsd", "crc"].contains(&algo) { + digest.to_string() + } else { + format!("{} (f) = {}", algo.to_uppercase(), digest).replace("BLAKE2B", "BLAKE2b") + } + } + + #[test] + fn test_generating() { + // Ensure that each algorithm works with `--base64`. + let scene = make_scene(); + + for (algo, digest) in PAIRS { + scene + .ucmd() + .arg("--base64") + .arg("-a") + .arg(algo) + .arg("f") + .succeeds() + .stdout_only(format!("{}\n", output_format(algo, digest))); + } + } + + #[test] + fn test_chk() { + // For each algorithm that accepts `--check`, + // ensure that it works with base64 digests. + let scene = make_scene(); + + for (algo, digest) in PAIRS { + if ["sysv", "bsd", "crc"].contains(&algo) { + // These algorithms do not accept `--check` + continue; + } + + let line = output_format(algo, digest); + scene + .ucmd() + .arg("--check") + .arg("--strict") + .pipe_in(line) + .succeeds() + .stdout_only("f: OK\n"); + } + } + + #[test] + fn test_chk_eq1() { + // For digests ending with '=', ensure `--check` fails if '=' is removed. + let scene = make_scene(); + + for (algo, digest) in PAIRS { + if !digest.ends_with('=') { + continue; + } + + let mut line = output_format(algo, digest); + if line.ends_with('=') { + line.pop(); + } + + log_info(format!("ALGORITHM: {algo}, STDIN: '{line}'"), ""); + scene + .ucmd() + .arg("--check") + .pipe_in(line) + .fails() + .no_stdout() + .stderr_contains("no properly formatted checksum lines found"); + } + } + + #[test] + fn test_chk_eq2() { + // For digests ending with '==', + // ensure `--check` fails if '==' is removed. + let scene = make_scene(); + + for (algo, digest) in PAIRS { + if !digest.ends_with("==") { + continue; + } + + let line = output_format(algo, digest); + let line = line.trim_end_matches("=="); + + log_info(format!("ALGORITHM: {algo}, STDIN: '{line}'"), ""); + scene + .ucmd() + .arg("--check") + .pipe_in(line) + .fails() + .no_stdout() + .stderr_contains("no properly formatted checksum lines found"); + } + } +} From 7746c5c6ed7d27f7cfc2f270ffe49657c41ebf25 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 16 Nov 2024 22:52:19 +0100 Subject: [PATCH 013/351] publish: by default, put it as draft to send email only when ready closes: #6859 --- .github/workflows/CICD.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c8993b121eb..7fab8f8eae6 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -753,6 +753,7 @@ jobs: uses: softprops/action-gh-release@v2 if: steps.vars.outputs.DEPLOY with: + draft: true files: | ${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }} ${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.DPKG_NAME }} From 96e42d6dd9cea2a8ab6b2432c2c8c14a3d6ac029 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 09:55:29 +0000 Subject: [PATCH 014/351] fix(deps): update rust crate libc to v0.2.164 --- Cargo.lock | 4 ++-- fuzz/Cargo.lock | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b59405071e6..b4eab129920 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,9 +1305,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libloading" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4f7c5608958..5db73ba316b 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -416,9 +416,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.161" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libfuzzer-sys" @@ -847,7 +847,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_cut" -version = "0.0.27" +version = "0.0.28" dependencies = [ "bstr", "clap", @@ -857,7 +857,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.27" +version = "0.0.28" dependencies = [ "chrono", "clap", @@ -869,7 +869,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "uucore", @@ -877,7 +877,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "nix 0.29.0", @@ -887,7 +887,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "num-bigint", @@ -898,7 +898,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "uucore", @@ -906,7 +906,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.27" +version = "0.0.28" dependencies = [ "bigdecimal", "clap", @@ -917,7 +917,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.27" +version = "0.0.28" dependencies = [ "binary-heap-plus", "clap", @@ -937,7 +937,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "memchr", @@ -946,7 +946,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "libc", @@ -955,7 +955,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "nom", @@ -964,7 +964,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.27" +version = "0.0.28" dependencies = [ "bytecount", "clap", @@ -977,7 +977,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.27" +version = "0.0.28" dependencies = [ "clap", "dunce", @@ -1020,7 +1020,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.27" +version = "0.0.28" dependencies = [ "proc-macro2", "quote", @@ -1029,7 +1029,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.27" +version = "0.0.28" [[package]] name = "wasi" From b9da6087934677149551677d5b4d3673dd10c32a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 10:23:34 +0000 Subject: [PATCH 015/351] chore(deps): update rust crate serde to v1.0.215 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4eab129920..a6c9d1bc153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2138,9 +2138,9 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -2156,9 +2156,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", From 1e47325ba252040db5b2b073007f845675c47ce2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 10:57:11 +0000 Subject: [PATCH 016/351] fix(deps): update rust crate libfuzzer-sys to v0.4.8 --- fuzz/Cargo.lock | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 5db73ba316b..0bd6826a2eb 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -422,13 +422,12 @@ checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] From 0d086edda8655c5af0762202355ab7faa1659174 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 10:58:30 +0000 Subject: [PATCH 017/351] chore(deps): update davidanson/markdownlint-cli2-action action to v18 --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c8993b121eb..ef7c966265f 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -139,7 +139,7 @@ jobs: shell: bash run: | RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - - uses: DavidAnson/markdownlint-cli2-action@v17 + - uses: DavidAnson/markdownlint-cli2-action@v18 with: fix: "true" globs: | From 2f5e7f66a73bfe2f73a2fb3d09b54a5d799fd63e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Nov 2024 11:53:24 +0000 Subject: [PATCH 018/351] fix(deps): update rust crate tempfile to v3.14.0 --- Cargo.lock | 18 +++++++++--------- fuzz/Cargo.lock | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a6c9d1bc153..432dd0bcb62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1795,7 +1795,7 @@ dependencies = [ "bitflags 2.6.0", "hex", "procfs-core", - "rustix 0.38.37", + "rustix 0.38.40", ] [[package]] @@ -2060,9 +2060,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", @@ -2315,14 +2315,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", "once_cell", - "rustix 0.38.37", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -2342,7 +2342,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.37", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -3718,7 +3718,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -3986,7 +3986,7 @@ checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys 0.4.14", - "rustix 0.38.37", + "rustix 0.38.40", ] [[package]] diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 0bd6826a2eb..4903e424d49 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.5.0", "errno", @@ -770,9 +770,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", From 2ac5ad61623ebe875e6d10d26ac9aa07fd527d7d Mon Sep 17 00:00:00 2001 From: Banyc <36535895+Banyc@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:44:08 +0800 Subject: [PATCH 019/351] fix(deps): crates import compatible uucore --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6881abfb1a..2534d466431 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -354,10 +354,10 @@ blake3 = "1.5.1" sm3 = "0.4.2" digest = "0.10.7" -uucore = { version = ">=0.0.19", package = "uucore", path = "src/uucore" } -uucore_procs = { version = ">=0.0.19", package = "uucore_procs", path = "src/uucore_procs" } -uu_ls = { version = ">=0.0.18", path = "src/uu/ls" } -uu_base32 = { version = ">=0.0.18", path = "src/uu/base32" } +uucore = { version = "0.0.28", package = "uucore", path = "src/uucore" } +uucore_procs = { version = "0.0.28", package = "uucore_procs", path = "src/uucore_procs" } +uu_ls = { version = "0.0.28", path = "src/uu/ls" } +uu_base32 = { version = "0.0.28", path = "src/uu/base32" } [dependencies] clap = { workspace = true } From 412d4f4f15f25fe907762b6d3f94072539d596cd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 18 Nov 2024 09:20:51 +0100 Subject: [PATCH 020/351] Bump MSRV to 1.77 --- .clippy.toml | 2 +- .github/workflows/CICD.yml | 2 +- Cargo.toml | 2 +- README.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index 89fd1cccd7f..72e8c35cfd8 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.70.0" +msrv = "1.77.0" cognitive-complexity-threshold = 24 missing-docs-in-crate-items = true check-private-items = true diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 4694f2a3a00..6c7b50995d1 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -11,7 +11,7 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.70.0" + RUST_MIN_SRV: "1.77.0" # * style job configuration STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis diff --git a/Cargo.toml b/Cargo.toml index a6881abfb1a..539f5002e7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://github.com/uutils/coreutils" readme = "README.md" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] -rust-version = "1.70.0" +rust-version = "1.77.0" edition = "2021" build = "build.rs" diff --git a/README.md b/README.md index 22081c68939..9f7d1c2ae09 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) [![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) -![MSRV](https://img.shields.io/badge/MSRV-1.70.0-brightgreen) +![MSRV](https://img.shields.io/badge/MSRV-1.77.0-brightgreen) @@ -70,7 +70,7 @@ the [coreutils docs](https://github.com/uutils/uutils.github.io) repository. ### Rust Version uutils follows Rust's release channels and is tested against stable, beta and -nightly. The current Minimum Supported Rust Version (MSRV) is `1.70.0`. +nightly. The current Minimum Supported Rust Version (MSRV) is `1.77.0`. ## Building From 3281d3ef557639571abe9ebb297e2567555a81dd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 18 Nov 2024 09:45:44 +0100 Subject: [PATCH 021/351] tee: fix warning from ref_as_ptr lint in test --- tests/by-util/test_tee.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index c32759ed4c4..4f2437acea3 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -172,7 +172,7 @@ mod linux_only { let mut fds: [c_int; 2] = [0, 0]; assert!( - (unsafe { libc::pipe(&mut fds as *mut c_int) } == 0), + (unsafe { libc::pipe(std::ptr::from_mut::(&mut fds[0])) } == 0), "Failed to create pipe" ); From 2e85198758edb44defcdc99ad96c5be26f1af1e9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:09:47 +0000 Subject: [PATCH 022/351] chore(deps): update rust crate thiserror to v1.0.69 --- Cargo.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 432dd0bcb62..ba1007c2648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -615,7 +615,7 @@ dependencies = [ "lazy_static", "proc-macro2", "regex", - "syn 2.0.86", + "syn 2.0.87", "unicode-xid", ] @@ -627,7 +627,7 @@ checksum = "3e1a2532e4ed4ea13031c13bc7bc0dbca4aae32df48e9d77f0d1e743179f2ea1" dependencies = [ "lazy_static", "proc-macro2", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -642,7 +642,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -796,7 +796,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -823,7 +823,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -1046,7 +1046,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -1565,7 +1565,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -1765,7 +1765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" dependencies = [ "proc-macro2", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -2014,7 +2014,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.86", + "syn 2.0.87", "unicode-ident", ] @@ -2162,7 +2162,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -2298,9 +2298,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.86" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -2360,22 +2360,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d171f59dbaa811dbbb1aee1e73db92ec2b122911a48e1390dfe327a821ddede" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08be0f17bd307950653ce45db00cd31200d82b624b36e181337d9c7d92765b5" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] @@ -3644,7 +3644,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -3666,7 +3666,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4019,7 +4019,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.86", + "syn 2.0.87", ] [[package]] From 5fe4dee5c429c0438f658097d9f73fff936736e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:05:57 +0000 Subject: [PATCH 023/351] chore(deps): update rust crate bstr to v1.11.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba1007c2648..c95745548c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", "regex-automata", From f572124f1e883c71c060efc530ee4118d7f09351 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 06:18:07 +0000 Subject: [PATCH 024/351] chore(deps): update rust crate clap_mangen to v0.2.24 --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c95745548c9..1ed175eed3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -378,9 +378,9 @@ checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "clap_mangen" -version = "0.2.9" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0f09a0ca8f0dd8ac92c546b426f466ef19828185c6d504c80c48c9c2768ed9" +checksum = "fbae9cbfdc5d4fa8711c09bd7b83f644cb48281ac35bf97af3e47b0675864bdf" dependencies = [ "clap", "roff", @@ -3718,7 +3718,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] From 76477f2ed2b008f269a763186214683c834a34e8 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 19 Nov 2024 09:47:14 +0100 Subject: [PATCH 025/351] Bump crossterm from 0.27.0 to 0.28.1 --- Cargo.lock | 33 +++++++++++++++++++++++---------- Cargo.toml | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ed175eed3e..54b03d6ca4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -703,16 +703,16 @@ checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossterm" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", "filedescriptor", - "libc", - "mio", + "mio 1.0.2", "parking_lot", + "rustix 0.38.40", "signal-hook", "signal-hook-mio", "winapi", @@ -1140,9 +1140,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1435,6 +1435,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nix" version = "0.29.0" @@ -1470,7 +1483,7 @@ dependencies = [ "inotify", "kqueue", "libc", - "mio", + "mio 0.8.11", "walkdir", "windows-sys 0.45.0", ] @@ -2215,12 +2228,12 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 1.0.2", "signal-hook", ] diff --git a/Cargo.toml b/Cargo.toml index a5d45a09baa..f7b180231bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -281,7 +281,7 @@ clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" coz = { version = "0.1.3" } -crossterm = ">=0.27.0" +crossterm = "0.28.1" ctrlc = { version = "3.4.4", features = ["termination"] } dns-lookup = { version = "2.0.4" } exacl = "0.12.0" From 6257cf1793b8627f9a69c83f8e310abd423fce13 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 19 Nov 2024 09:47:49 +0100 Subject: [PATCH 026/351] deny.toml: add mio to skip list --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index 9fefc77276e..1d7524ce2f8 100644 --- a/deny.toml +++ b/deny.toml @@ -104,6 +104,8 @@ skip = [ { name = "terminal_size", version = "0.2.6" }, # ansi-width, console, os_display { name = "unicode-width", version = "0.1.13" }, + # notify + { name = "mio", version = "0.8.11" }, ] # spell-checker: enable From d2fc3914ff436975801a1b57ff5a662d844593a1 Mon Sep 17 00:00:00 2001 From: Jesse Schalken Date: Mon, 28 Oct 2024 20:24:35 +1100 Subject: [PATCH 027/351] du: use metadata from DirEntry where possible --- src/uu/du/src/du.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index a35e9f77e76..cfa53cad4ca 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -12,7 +12,7 @@ use std::error::Error; use std::fmt::Display; #[cfg(not(windows))] use std::fs::Metadata; -use std::fs::{self, File}; +use std::fs::{self, DirEntry, File}; use std::io::{BufRead, BufReader}; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; @@ -138,7 +138,11 @@ struct Stat { } impl Stat { - fn new(path: &Path, options: &TraversalOptions) -> std::io::Result { + fn new( + path: &Path, + dir_entry: Option<&DirEntry>, + options: &TraversalOptions, + ) -> std::io::Result { // Determine whether to dereference (follow) the symbolic link let should_dereference = match &options.dereference { Deref::All => true, @@ -149,8 +153,11 @@ impl Stat { let metadata = if should_dereference { // Get metadata, following symbolic links if necessary fs::metadata(path) + } else if let Some(dir_entry) = dir_entry { + // Get metadata directly from the DirEntry, which is faster on Windows + dir_entry.metadata() } else { - // Get metadata without following symbolic links + // Get metadata from the filesystem without following symbolic links fs::symlink_metadata(path) }?; @@ -319,7 +326,7 @@ fn du( 'file_loop: for f in read { match f { Ok(entry) => { - match Stat::new(&entry.path(), options) { + match Stat::new(&entry.path(), Some(&entry), options) { Ok(this_stat) => { // We have an exclude list for pattern in &options.excludes { @@ -765,7 +772,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } // Check existence of path provided in argument - if let Ok(stat) = Stat::new(&path, &traversal_options) { + if let Ok(stat) = Stat::new(&path, None, &traversal_options) { // Kick off the computation of disk usage from the initial path let mut seen_inodes: HashSet = HashSet::new(); if let Some(inode) = stat.inode { From 28e9a880773510fd2f8790a0cbb2fba4bc1581ee Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 20 Nov 2024 09:10:39 +0100 Subject: [PATCH 028/351] du: use div_ceil() from std --- src/uu/du/src/du.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index a35e9f77e76..3681668f0c1 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -555,7 +555,7 @@ impl StatPrinter { size, uucore::format::human::SizeFormat::Binary, ), - SizeFormat::BlockSize(block_size) => div_ceil(size, block_size).to_string(), + SizeFormat::BlockSize(block_size) => size.div_ceil(block_size).to_string(), } } @@ -576,13 +576,6 @@ impl StatPrinter { } } -// This can be replaced with u64::div_ceil once it is stabilized. -// This implementation approach is optimized for when `b` is a constant, -// particularly a power of two. -pub fn div_ceil(a: u64, b: u64) -> u64 { - (a + b - 1) / b -} - // Read file paths from the specified file, separated by null characters fn read_files_from(file_name: &str) -> Result, std::io::Error> { let reader: Box = if file_name == "-" { From 7fb0f8a29dea1666537c4905d082d3a8b526125e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 20 Nov 2024 09:13:32 +0100 Subject: [PATCH 029/351] sum: use div_ceil() from std --- src/uu/sum/src/sum.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index d1f383351aa..bae288d803f 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -16,13 +16,6 @@ use uucore::{format_usage, help_about, help_usage, show}; const USAGE: &str = help_usage!("sum.md"); const ABOUT: &str = help_about!("sum.md"); -// This can be replaced with usize::div_ceil once it is stabilized. -// This implementation approach is optimized for when `b` is a constant, -// particularly a power of two. -const fn div_ceil(a: usize, b: usize) -> usize { - (a + b - 1) / b -} - fn bsd_sum(mut reader: Box) -> (usize, u16) { let mut buf = [0; 4096]; let mut bytes_read = 0; @@ -41,7 +34,7 @@ fn bsd_sum(mut reader: Box) -> (usize, u16) { } // Report blocks read in terms of 1024-byte blocks. - let blocks_read = div_ceil(bytes_read, 1024); + let blocks_read = bytes_read.div_ceil(1024); (blocks_read, checksum) } @@ -66,7 +59,7 @@ fn sysv_sum(mut reader: Box) -> (usize, u16) { ret = (ret & 0xffff) + (ret >> 16); // Report blocks read in terms of 512-byte blocks. - let blocks_read = div_ceil(bytes_read, 512); + let blocks_read = bytes_read.div_ceil(512); (blocks_read, ret as u16) } From fc2f73b16cc112f34d16f6725a5b652e611f46d1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 20 Nov 2024 09:17:14 +0100 Subject: [PATCH 030/351] cksum: use div_ceil() from std --- src/uu/cksum/src/cksum.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 2392660ee89..e96d2de6fcd 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -22,7 +22,7 @@ use uucore::{ format_usage, help_about, help_section, help_usage, line_ending::LineEnding, os_str_as_bytes, show, - sum::{div_ceil, Digest}, + sum::Digest, }; const USAGE: &str = help_usage!("cksum.md"); @@ -124,7 +124,7 @@ where format!( "{} {}{}", sum.parse::().unwrap(), - div_ceil(sz, options.output_bits), + sz.div_ceil(options.output_bits), if not_file { "" } else { " " } ), !not_file, @@ -134,7 +134,7 @@ where format!( "{:0bsd_width$} {:bsd_width$}{}", sum.parse::().unwrap(), - div_ceil(sz, options.output_bits), + sz.div_ceil(options.output_bits), if not_file { "" } else { " " } ), !not_file, From cfe2c9f6da56e9047d05a78657d7f8d0a1ad20ab Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 20 Nov 2024 09:20:41 +0100 Subject: [PATCH 031/351] uucore: remove div_ceil() from sum feature --- src/uucore/src/lib/features/sum.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs index 086c6ca9d03..1baff7f7960 100644 --- a/src/uucore/src/lib/features/sum.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -207,13 +207,6 @@ impl Digest for CRC { } } -// This can be replaced with usize::div_ceil once it is stabilized. -// This implementation approach is optimized for when `b` is a constant, -// particularly a power of two. -pub fn div_ceil(a: usize, b: usize) -> usize { - (a + b - 1) / b -} - pub struct BSD { state: u16, } From 7ffe3d49efab4e0ca9cd7fdeb49319637cd857b8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 16 Nov 2024 23:15:21 +0100 Subject: [PATCH 032/351] mv: fix the output of an error message + adjust a GNU test Should make tests/mv/dup-source.sh pass --- src/uu/mv/src/mv.rs | 2 +- tests/by-util/test_mv.rs | 20 ++++++++++++++++++++ util/gnu-patches/tests_dup_source.patch | 13 +++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 util/gnu-patches/tests_dup_source.patch diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index c57f2527e1f..9d8452b1ec7 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -488,7 +488,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options) format!( "cannot move '{}' to a subdirectory of itself, '{}/{}'", sourcepath.display(), - target_dir.display(), + uucore::fs::normalize_path(target_dir).display(), canonicalized_target_dir.components().last().map_or_else( || target_dir.display().to_string(), |dir| { PathBuf::from(dir.as_os_str()).display().to_string() } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index d8bc49e8ef9..6f2693a8676 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1732,3 +1732,23 @@ fn test_mv_error_msg_with_multiple_sources_that_does_not_exist() { .stderr_contains("mv: cannot stat 'a': No such file or directory") .stderr_contains("mv: cannot stat 'b/': No such file or directory"); } + +#[test] +fn test_mv_error_cant_move_itself() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("b"); + scene + .ucmd() + .arg("b") + .arg("b/") + .fails() + .stderr_contains("mv: cannot move 'b' to a subdirectory of itself, 'b/b'"); + scene + .ucmd() + .arg("./b") + .arg("b") + .arg("b/") + .fails() + .stderr_contains("mv: cannot move 'b' to a subdirectory of itself, 'b/b'"); +} diff --git a/util/gnu-patches/tests_dup_source.patch b/util/gnu-patches/tests_dup_source.patch new file mode 100644 index 00000000000..44e33723bc1 --- /dev/null +++ b/util/gnu-patches/tests_dup_source.patch @@ -0,0 +1,13 @@ +diff --git a/tests/mv/dup-source.sh b/tests/mv/dup-source.sh +index 7bcd82fc3..0f9005296 100755 +--- a/tests/mv/dup-source.sh ++++ b/tests/mv/dup-source.sh +@@ -83,7 +83,7 @@ $i: cannot stat 'a': No such file or directory + $i: cannot stat 'a': No such file or directory + $i: cannot stat 'b': No such file or directory + $i: cannot move './b' to a subdirectory of itself, 'b/b' +-$i: warning: source directory 'b' specified more than once ++$i: cannot move 'b' to a subdirectory of itself, 'b/b' + EOF + compare exp out || fail=1 + done From c986fb7d2e924baa1b27ec8e4c025ee104b07c85 Mon Sep 17 00:00:00 2001 From: steinwand6 <57711907+steinwand6@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:11:04 +0900 Subject: [PATCH 033/351] seq: add overflow checks when parsing exponents (#6858) * seq: remove ignore flag from test_invalid_float_point_fail_properly(#6235) * seq: prevent overflow in parse_exponent_no_decimal * seq: add tests for invalid floating point arguments * seq: add overflow checks when parsing decimal with exponent * seq: add overflow checks --- src/uu/seq/src/numberparse.rs | 29 ++++++++++++++++++++--------- tests/by-util/test_seq.rs | 14 ++++++++++++-- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 5a5c64bb991..adbaccc1104 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -106,16 +106,20 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result 0 { - 2usize + exponent as usize + (2usize) + .checked_add(exponent as usize) + .ok_or(ParseNumberError::Float)? } else { 2usize } } else { - let total = j as i64 + exponent; + let total = (j as i64) + .checked_add(exponent) + .ok_or(ParseNumberError::Float)?; let result = if total < 1 { 1 } else { - total.try_into().unwrap() + total.try_into().map_err(|_| ParseNumberError::Float)? }; if x.sign() == Sign::Minus { result + 1 @@ -207,7 +211,9 @@ fn parse_decimal_and_exponent( let integral_part: f64 = s[..j].parse().map_err(|_| ParseNumberError::Float)?; if integral_part.is_sign_negative() { if exponent > 0 { - 2usize + exponent as usize + 2usize + .checked_add(exponent as usize) + .ok_or(ParseNumberError::Float)? } else { 2usize } @@ -217,15 +223,20 @@ fn parse_decimal_and_exponent( }; // Special case: if the string is "-.1e2", we need to treat it // as if it were "-0.1e2". - let total = if s.starts_with("-.") { - i as i64 + exponent + 1 - } else { - i as i64 + exponent + let total = { + let total = (i as i64) + .checked_add(exponent) + .ok_or(ParseNumberError::Float)?; + if s.starts_with("-.") { + total.checked_add(1).ok_or(ParseNumberError::Float)? + } else { + total + } }; if total < minimum as i64 { minimum } else { - total.try_into().unwrap() + total.try_into().map_err(|_| ParseNumberError::Float)? } }; diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index a8bd1fb8359..c14d3062923 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -777,12 +777,22 @@ fn test_undefined() { } #[test] -#[ignore = "Need issue #6235 to be fixed"] fn test_invalid_float_point_fail_properly() { new_ucmd!() .args(&["66000e000000000000000000000000000000000000000000000000000009223372036854775807"]) .fails() - .stdout_only(""); // might need to be updated + .no_stdout() + .usage_error("invalid floating point argument: '66000e000000000000000000000000000000000000000000000000000009223372036854775807'"); + new_ucmd!() + .args(&["-1.1e9223372036854775807"]) + .fails() + .no_stdout() + .usage_error("invalid floating point argument: '-1.1e9223372036854775807'"); + new_ucmd!() + .args(&["-.1e9223372036854775807"]) + .fails() + .no_stdout() + .usage_error("invalid floating point argument: '-.1e9223372036854775807'"); } #[test] From 76d14ed4841d2d8342625577cf9b4a25ce691566 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 20 Nov 2024 07:46:12 -0500 Subject: [PATCH 034/351] du: fix the count with --inodes --- src/uu/du/src/du.rs | 11 +++++++++-- tests/by-util/test_du.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 49303a82c89..a2d2082e692 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -346,14 +346,21 @@ fn du( } if let Some(inode) = this_stat.inode { - if seen_inodes.contains(&inode) { - if options.count_links { + // Check if the inode has been seen before and if we should skip it + if seen_inodes.contains(&inode) + && (!options.count_links || !options.all) + { + // If `count_links` is enabled and `all` is not, increment the inode count + if options.count_links && !options.all { my_stat.inodes += 1; } + // Skip further processing for this inode continue; } + // Mark this inode as seen seen_inodes.insert(inode); } + if this_stat.is_dir { if options.one_file_system { if let (Some(this_inode), Some(my_inode)) = diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index ef6179e02b3..862d3581c95 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -546,6 +546,33 @@ fn test_du_inodes_with_count_links() { } } +#[cfg(not(target_os = "android"))] +#[test] +fn test_du_inodes_with_count_links_all() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("d"); + at.mkdir("d/d"); + at.touch("d/f"); + at.hard_link("d/f", "d/h"); + + let result = ts.ucmd().arg("--inodes").arg("-al").arg("d").succeeds(); + result.no_stderr(); + + let mut result_seq: Vec = result + .stdout_str() + .split('\n') + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort_unstable(); + #[cfg(windows)] + assert_eq!(result_seq, ["1\td\\d", "1\td\\f", "1\td\\h", "4\td"]); + #[cfg(not(windows))] + assert_eq!(result_seq, ["1\td/d", "1\td/f", "1\td/h", "4\td"]); +} + #[test] fn test_du_h_flag_empty_file() { new_ucmd!() From 1b2778b819ed68bf8a5f462cf0e50e3990f911ef Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 20 Nov 2024 07:46:53 -0500 Subject: [PATCH 035/351] du: fix the size display with --inodes --- src/uu/du/src/du.rs | 12 ++++++++---- tests/by-util/test_du.rs | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index a2d2082e692..e7b00838e30 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -557,9 +557,6 @@ impl StatPrinter { } fn convert_size(&self, size: u64) -> String { - if self.inodes { - return size.to_string(); - } match self.size_format { SizeFormat::HumanDecimal => uucore::format::human::human_readable( size, @@ -569,7 +566,14 @@ impl StatPrinter { size, uucore::format::human::SizeFormat::Binary, ), - SizeFormat::BlockSize(block_size) => size.div_ceil(block_size).to_string(), + SizeFormat::BlockSize(block_size) => { + if self.inodes { + // we ignore block size (-B) with --inodes + size.to_string() + } else { + size.div_ceil(block_size).to_string() + } + } } } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 862d3581c95..af9718a4e6b 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1198,3 +1198,25 @@ fn test_invalid_time_style() { .succeeds() .stdout_does_not_contain("du: invalid argument 'banana' for 'time style'"); } + +#[test] +fn test_human_size() { + use std::fs::File; + + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let dir = at.plus_as_string("d"); + at.mkdir(&dir); + + for i in 1..=1023 { + let file_path = format!("{dir}/file{i}"); + File::create(&file_path).expect("Failed to create file"); + } + + ts.ucmd() + .arg("--inodes") + .arg("-h") + .arg(&dir) + .succeeds() + .stdout_contains(format!("1.0K {dir}")); +} From 06e01324a185e2ed310e2f6e77dcd6c49e04e324 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 06:08:18 +0000 Subject: [PATCH 036/351] fix(deps): update rust crate proc-macro2 to v1.0.91 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54b03d6ca4f..e699d20d817 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1792,9 +1792,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" dependencies = [ "unicode-ident", ] From 9f07bf880944e777b218b43e435e9f8f03eab159 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 28 Mar 2024 16:02:54 +0100 Subject: [PATCH 037/351] mv: remove "sleep" in tests --- tests/by-util/test_mv.rs | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 6f2693a8676..6ab989ee453 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -6,8 +6,7 @@ // spell-checker:ignore mydir use crate::common::util::TestScenario; use filetime::FileTime; -use std::thread::sleep; -use std::time::Duration; +use std::io::Write; #[test] fn test_mv_invalid_arg() { @@ -974,9 +973,9 @@ fn test_mv_arg_update_older_dest_not_older() { let old_content = "file1 content\n"; let new_content = "file2 content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -1001,9 +1000,9 @@ fn test_mv_arg_update_none_then_all() { let old_content = "old content\n"; let new_content = "new content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -1029,9 +1028,9 @@ fn test_mv_arg_update_all_then_none() { let old_content = "old content\n"; let new_content = "new content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -1055,9 +1054,9 @@ fn test_mv_arg_update_older_dest_older() { let old_content = "file1 content\n"; let new_content = "file2 content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -1081,9 +1080,9 @@ fn test_mv_arg_update_short_overwrite() { let old_content = "file1 content\n"; let new_content = "file2 content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -1107,9 +1106,9 @@ fn test_mv_arg_update_short_no_overwrite() { let old_content = "file1 content\n"; let new_content = "file2 content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); From bdffbb044b706bd35f7766299439ceff373e7b06 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:24:37 +0000 Subject: [PATCH 038/351] fix(deps): update rust crate proc-macro2 to v1.0.92 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e699d20d817..9fe65293adb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1792,9 +1792,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] From 6ddd4f6285f69b43d5833472540d9da0ba4207f0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 01:17:34 +0000 Subject: [PATCH 039/351] chore(deps): update rust crate fts-sys to v0.2.13 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fe65293adb..42ccfc0f213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -961,9 +961,9 @@ dependencies = [ [[package]] name = "fts-sys" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28ab6a6dfd9184fe8a5097924dea6e7648f499121b3e933bb8486a17f817122e" +checksum = "c427b250eff90452a35afd79fdfcbcf4880e307225bc28bd36d9a2cd78bb6d90" dependencies = [ "bindgen", "libc", From cc3353ed7a454c6b95e453cf7ae6d5a49b8c2798 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 25 Nov 2024 09:03:26 +0100 Subject: [PATCH 040/351] Bump cpp & cpp_build from 0.5.9 to 0.5.10 --- Cargo.lock | 16 ++++++++-------- src/uu/stdbuf/src/libstdbuf/Cargo.toml | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42ccfc0f213..10ca01c8b08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -597,18 +597,18 @@ dependencies = [ [[package]] name = "cpp" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa65869ef853e45c60e9828aa08cdd1398cb6e13f3911d9cb2a079b144fcd64" +checksum = "f36bcac3d8234c1fb813358e83d1bb6b0290a3d2b3b5efc6b88bfeaf9d8eec17" dependencies = [ "cpp_macros", ] [[package]] name = "cpp_build" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e361fae2caf9758164b24da3eedd7f7d7451be30d90d8e7b5d2be29a2f0cf5b" +checksum = "27f8638c97fbd79cc6fc80b616e0e74b49bac21014faed590bbc89b7e2676c90" dependencies = [ "cc", "cpp_common", @@ -621,9 +621,9 @@ dependencies = [ [[package]] name = "cpp_common" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1a2532e4ed4ea13031c13bc7bc0dbca4aae32df48e9d77f0d1e743179f2ea1" +checksum = "25fcfea2ee05889597d35e986c2ad0169694320ae5cc8f6d2640a4bb8a884560" dependencies = [ "lazy_static", "proc-macro2", @@ -632,9 +632,9 @@ dependencies = [ [[package]] name = "cpp_macros" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ec9cc90633446f779ef481a9ce5a0077107dd5b87016440448d908625a83fd" +checksum = "d156158fe86e274820f5a53bc9edb0885a6e7113909497aa8d883b69dd171871" dependencies = [ "aho-corasick", "byteorder", diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index ff9de77fcc9..67a7e903e04 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -20,8 +20,8 @@ crate-type = [ ] # XXX: note: the rlib is just to prevent Cargo from spitting out a warning [dependencies] -cpp = "0.5.9" +cpp = "0.5.10" libc = { workspace = true } [build-dependencies] -cpp_build = "0.5.9" +cpp_build = "0.5.10" From 91dc89c3bab4b6694b3debf2a411aad9a8f92ebc Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 25 Nov 2024 09:28:35 +0100 Subject: [PATCH 041/351] Bump thiserror from 1.0.69 to 2.0.3 --- Cargo.lock | 44 ++++++++++++++++++++++++++++++++------------ Cargo.toml | 2 +- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42ccfc0f213..31798d689fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -912,7 +912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.69", "winapi", ] @@ -2128,7 +2128,7 @@ dependencies = [ "once_cell", "reference-counted-singleton", "selinux-sys", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2377,7 +2377,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] @@ -2391,6 +2400,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "time" version = "0.3.36" @@ -2524,7 +2544,7 @@ checksum = "e24c654e19afaa6b8f3877ece5d3bed849c2719c56f6752b18ca7da4fcc6e85a" dependencies = [ "cfg-if", "libc", - "thiserror", + "thiserror 1.0.69", "time", "utmp-classic-raw", "zerocopy", @@ -2590,7 +2610,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror", + "thiserror 2.0.3", "uucore", ] @@ -2602,7 +2622,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror", + "thiserror 2.0.3", "uucore", ] @@ -2679,7 +2699,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror", + "thiserror 2.0.3", "uucore", ] @@ -3195,7 +3215,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror", + "thiserror 2.0.3", "uucore", ] @@ -3474,7 +3494,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror", + "thiserror 2.0.3", "utmp-classic", "uucore", ] @@ -3505,7 +3525,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror", + "thiserror 2.0.3", "unicode-width 0.1.13", "uucore", ] @@ -3566,7 +3586,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror", + "thiserror 2.0.3", "time", "uucore_procs", "walkdir", @@ -4048,5 +4068,5 @@ dependencies = [ "flate2", "indexmap", "num_enum", - "thiserror", + "thiserror 1.0.69", ] diff --git a/Cargo.toml b/Cargo.toml index f7b180231bc..caa233802f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -332,7 +332,7 @@ tempfile = "3.10.1" uutils_term_grid = "0.6" terminal_size = "0.4.0" textwrap = { version = "0.16.1", features = ["terminal_size"] } -thiserror = "1.0.59" +thiserror = "2.0.3" time = { version = "0.3.36" } unicode-segmentation = "1.11.0" unicode-width = "0.1.12" From c38897b101ff405207d572cda5df9a281b2cbfd4 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 25 Nov 2024 09:32:01 +0100 Subject: [PATCH 042/351] deny.toml: add thiserror & thiserror-impl to skip list --- deny.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deny.toml b/deny.toml index 1d7524ce2f8..d64a2d33a9c 100644 --- a/deny.toml +++ b/deny.toml @@ -106,6 +106,10 @@ skip = [ { name = "unicode-width", version = "0.1.13" }, # notify { name = "mio", version = "0.8.11" }, + # various crates + { name = "thiserror", version = "1.0.69" }, + # thiserror + { name = "thiserror-impl", version = "1.0.69" }, ] # spell-checker: enable From a81bd33b6b39188deb3fe3827e1e84ae7171f754 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:58:46 +0000 Subject: [PATCH 043/351] fix(deps): update rust crate libc to v0.2.165 --- Cargo.lock | 4 ++-- fuzz/Cargo.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 604ccebd5e9..5d13b9c4ba5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,9 +1305,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.164" +version = "0.2.165" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" [[package]] name = "libloading" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4903e424d49..724e0db7ec4 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -416,9 +416,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.164" +version = "0.2.165" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" [[package]] name = "libfuzzer-sys" @@ -600,9 +600,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -759,9 +759,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.65" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -793,18 +793,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", From a3b740355057027bc556b02f9791a49317870927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 3 Nov 2024 12:29:30 +0100 Subject: [PATCH 044/351] feat(checksum): improve FileCheckError variants to be meaningful --- src/uucore/src/lib/features/checksum.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index e7a0a265377..1606440468b 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -107,14 +107,16 @@ impl From for LineCheckError { } /// Represents an error that was encountered when processing a checksum file. -#[allow(clippy::enum_variant_names)] enum FileCheckError { /// a generic UError was encountered in sub-functions UError(Box), - /// the error does not stop the processing of next files - NonCriticalError, - /// the error must stop the run of the program - CriticalError, + /// the checksum file is improperly formatted. + ImproperlyFormatted, + /// reading of the checksum file failed + CantOpenChecksumFile, + /// Algorithm detection was unsuccessful. + /// Either none is provided, or there is a conflict. + AlgoDetectionError, } impl From> for FileCheckError { @@ -735,7 +737,7 @@ fn process_checksum_file( // Could not read the file, show the error and continue to the next file show_error!("{e}"); set_exit_code(1); - return Err(FileCheckError::NonCriticalError); + return Err(FileCheckError::CantOpenChecksumFile); } } }; @@ -749,7 +751,7 @@ fn process_checksum_file( }; show_error!("{e}"); set_exit_code(1); - return Err(FileCheckError::NonCriticalError); + return Err(FileCheckError::AlgoDetectionError); }; for (i, line) in lines.iter().enumerate() { @@ -791,7 +793,7 @@ fn process_checksum_file( .into()); } set_exit_code(1); - return Err(FileCheckError::CriticalError); + return Err(FileCheckError::ImproperlyFormatted); } // if any incorrectly formatted line, show it @@ -839,8 +841,8 @@ where use FileCheckError::*; match process_checksum_file(filename_input, algo_name_input, length_input, opts) { Err(UError(e)) => return Err(e), - Err(CriticalError) => break, - Err(NonCriticalError) | Ok(_) => continue, + Err(ImproperlyFormatted) => break, + Err(CantOpenChecksumFile | AlgoDetectionError) | Ok(_) => continue, } } From 20dfe2dc10d80e2a7b3f4f6edb036b1c22c2b005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 17 Nov 2024 12:06:37 +0100 Subject: [PATCH 045/351] test(cksum): remove duplicate testcase --- tests/by-util/test_cksum.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index ee1e05292c0..86de7ea95b6 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1251,33 +1251,6 @@ fn test_several_files_error_mgmt() { .stderr_contains("incorrect: no properly "); } -#[cfg(target_os = "linux")] -#[test] -fn test_non_utf8_filename() { - use std::ffi::OsString; - use std::os::unix::ffi::OsStringExt; - - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - let filename: OsString = OsStringExt::from_vec(b"funky\xffname".to_vec()); - - at.touch(&filename); - - scene - .ucmd() - .arg(&filename) - .succeeds() - .stdout_is_bytes(b"4294967295 0 funky\xffname\n") - .no_stderr(); - scene - .ucmd() - .arg("-asha256") - .arg(filename) - .succeeds() - .stdout_is_bytes(b"SHA256 (funky\xffname) = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n") - .no_stderr(); -} - #[test] fn test_check_comment_line() { // A comment in a checksum file shall be discarded unnoticed. @@ -1458,7 +1431,6 @@ mod check_utf8 { .no_stderr(); } - #[cfg(target_os = "linux")] #[test] fn test_check_non_utf8_filename() { use std::{ffi::OsString, os::unix::ffi::OsStringExt}; From f3763ef190eb5497e10a1fa89bc16028ff35fa4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 17 Nov 2024 14:09:25 +0100 Subject: [PATCH 046/351] feat(checksum): simplify get_expected_checksum - Rename the function to emphasize its goal - Do not pass the filename anymore, as it is only used to create an error, that may be done in the scope calling the function - Change the return type to Option, as the error is made in the outer scope - Don't try to decode the base64 string as UTF8 string. This most oftenly fails and is wrong. - Get rid of the `bytes_to_hex` function, as it provides the same functionality as `hex::encode` --- src/uucore/src/lib/features/checksum.rs | 56 ++++++++----------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 1606440468b..8dce955fcdf 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -441,43 +441,15 @@ fn determine_regex(lines: &[OsString]) -> Option<(Regex, bool)> { None } -// Converts bytes to a hexadecimal string -fn bytes_to_hex(bytes: &[u8]) -> String { - use std::fmt::Write; - bytes - .iter() - .fold(String::with_capacity(bytes.len() * 2), |mut hex, byte| { - write!(hex, "{byte:02x}").unwrap(); - hex - }) -} +/// Extract the expected digest from the checksum string +fn get_expected_digest_as_hexa_string(caps: &Captures, chosen_regex: &Regex) -> Option { + // Unwraps are safe, ensured by regex. + let ck = caps.name("checksum").unwrap().as_bytes(); -fn get_expected_checksum( - filename: &[u8], - caps: &Captures, - chosen_regex: &Regex, -) -> UResult { if chosen_regex.as_str() == ALGO_BASED_REGEX_BASE64 { - // Unwrap is safe, ensured by regex - let ck = caps.name("checksum").unwrap().as_bytes(); - match BASE64.decode(ck) { - Ok(decoded_bytes) => { - match std::str::from_utf8(&decoded_bytes) { - Ok(decoded_str) => Ok(decoded_str.to_string()), - Err(_) => Ok(bytes_to_hex(&decoded_bytes)), // Handle as raw bytes if not valid UTF-8 - } - } - Err(_) => Err(Box::new( - ChecksumError::NoProperlyFormattedChecksumLinesFound { - filename: String::from_utf8_lossy(filename).to_string(), - }, - )), - } + BASE64.decode(ck).map(hex::encode).ok() } else { - // Unwraps are safe, ensured by regex. - Ok(str::from_utf8(caps.name("checksum").unwrap().as_bytes()) - .unwrap() - .to_string()) + Some(str::from_utf8(ck).unwrap().to_string()) } } @@ -631,7 +603,13 @@ fn process_checksum_line( filename_to_check = &filename_to_check[1..]; } - let expected_checksum = get_expected_checksum(filename_to_check, &caps, chosen_regex)?; + let expected_checksum = get_expected_digest_as_hexa_string(&caps, chosen_regex).ok_or( + LineCheckError::UError(Box::new( + ChecksumError::NoProperlyFormattedChecksumLinesFound { + filename: String::from_utf8_lossy(filename_to_check).to_string(), + }, + )), + )?; // If the algo_name is provided, we use it, otherwise we try to detect it let (algo_name, length) = if is_algo_based_format { @@ -1250,13 +1228,13 @@ mod tests { } #[test] - fn test_get_expected_checksum() { + fn test_get_expected_digest() { let re = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(); let caps = re .captures(b"SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") .unwrap(); - let result = get_expected_checksum(b"filename", &caps, &re); + let result = get_expected_digest_as_hexa_string(&caps, &re); assert_eq!( result.unwrap(), @@ -1271,9 +1249,9 @@ mod tests { .captures(b"SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU") .unwrap(); - let result = get_expected_checksum(b"filename", &caps, &re); + let result = get_expected_digest_as_hexa_string(&caps, &re); - assert!(result.is_err()); + assert!(result.is_none()); } #[test] From a0af49f2d8576e8e17ce9653abb5bd492c70d6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 17 Nov 2024 15:52:11 +0100 Subject: [PATCH 047/351] feat(checksum): get rid of the properly_formatted variable --- src/uucore/src/lib/features/checksum.rs | 78 ++++++++++--------------- 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 8dce955fcdf..386da1f71e8 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -174,8 +174,6 @@ pub enum ChecksumError { CombineMultipleAlgorithms, #[error("Needs an algorithm to hash with.\nUse --help for more information.")] NeedAlgorithmToHash, - #[error("{filename}: no properly formatted checksum lines found")] - NoProperlyFormattedChecksumLinesFound { filename: String }, } impl UError for ChecksumError { @@ -241,6 +239,12 @@ fn cksum_output(res: &ChecksumResult, status: bool) { } } +/// Print a "no properly formatted lines" message in stderr +#[inline] +fn log_no_properly_formatted(filename: String) { + show_error!("{filename}: no properly formatted checksum lines found"); +} + /// Represents the different outcomes that can happen to a file /// that is being checked. #[derive(Debug, Clone, Copy)] @@ -442,7 +446,7 @@ fn determine_regex(lines: &[OsString]) -> Option<(Regex, bool)> { } /// Extract the expected digest from the checksum string -fn get_expected_digest_as_hexa_string(caps: &Captures, chosen_regex: &Regex) -> Option { +fn get_expected_digest_as_hex_string(caps: &Captures, chosen_regex: &Regex) -> Option { // Unwraps are safe, ensured by regex. let ck = caps.name("checksum").unwrap().as_bytes(); @@ -528,8 +532,6 @@ fn get_input_file(filename: &OsStr) -> UResult> { fn identify_algo_name_and_length( caps: &Captures, algo_name_input: Option<&str>, - res: &mut ChecksumResult, - properly_formatted: &mut bool, ) -> Option<(String, Option)> { // When the algo-based format is matched, extract details from regex captures let algorithm = caps @@ -543,14 +545,11 @@ fn identify_algo_name_and_length( // (for example SHA1 (f) = d...) // Also handle the case cksum -s sm3 but the file contains other formats if algo_name_input.is_some() && algo_name_input != Some(&algorithm) { - res.bad_format += 1; - *properly_formatted = false; return None; } if !SUPPORTED_ALGORITHMS.contains(&algorithm.as_str()) { // Not supported algo, leave early - *properly_formatted = false; return None; } @@ -562,7 +561,6 @@ fn identify_algo_name_and_length( if bits_value % 8 == 0 { Some(Some(bits_value / 8)) } else { - *properly_formatted = false; None // Return None to signal a divisibility issue } })?; @@ -583,16 +581,12 @@ fn process_checksum_line( i: usize, chosen_regex: &Regex, is_algo_based_format: bool, - res: &mut ChecksumResult, cli_algo_name: Option<&str>, cli_algo_length: Option, - properly_formatted: &mut bool, opts: ChecksumOptions, ) -> Result<(), LineCheckError> { let line_bytes = os_str_as_bytes(line)?; if let Some(caps) = chosen_regex.captures(line_bytes) { - *properly_formatted = true; - let mut filename_to_check = caps.name("filename").unwrap().as_bytes(); if filename_to_check.starts_with(b"*") @@ -603,18 +597,13 @@ fn process_checksum_line( filename_to_check = &filename_to_check[1..]; } - let expected_checksum = get_expected_digest_as_hexa_string(&caps, chosen_regex).ok_or( - LineCheckError::UError(Box::new( - ChecksumError::NoProperlyFormattedChecksumLinesFound { - filename: String::from_utf8_lossy(filename_to_check).to_string(), - }, - )), - )?; + let expected_checksum = get_expected_digest_as_hex_string(&caps, chosen_regex) + .ok_or(LineCheckError::ImproperlyFormatted)?; // If the algo_name is provided, we use it, otherwise we try to detect it let (algo_name, length) = if is_algo_based_format { - identify_algo_name_and_length(&caps, cli_algo_name, res, properly_formatted) - .unwrap_or((String::new(), None)) + identify_algo_name_and_length(&caps, cli_algo_name) + .ok_or(LineCheckError::ImproperlyFormatted)? } else if let Some(a) = cli_algo_name { // When a specific algorithm name is input, use it and use the provided bits // except when dealing with blake2b, where we will detect the length @@ -628,16 +617,9 @@ fn process_checksum_line( } } else { // Default case if no algorithm is specified and non-algo based format is matched - (String::new(), None) + return Err(LineCheckError::ImproperlyFormatted); }; - if algo_name.is_empty() { - // we haven't been able to detect the algo name. No point to continue - *properly_formatted = false; - - // TODO: return error? - return Err(LineCheckError::ImproperlyFormatted); - } let mut algo = detect_algo(&algo_name, length)?; let (filename_to_check_unescaped, prefix) = unescape_filename(filename_to_check); @@ -689,7 +671,6 @@ fn process_checksum_line( ); } - res.bad_format += 1; Err(LineCheckError::ImproperlyFormatted) } } @@ -701,8 +682,9 @@ fn process_checksum_file( opts: ChecksumOptions, ) -> Result<(), FileCheckError> { let mut correct_format = 0; - let mut properly_formatted = false; + let mut properly_formatted_lines = 0; let mut res = ChecksumResult::default(); + let input_is_stdin = filename_input == OsStr::new("-"); let file: Box = if input_is_stdin { @@ -724,10 +706,7 @@ fn process_checksum_file( let lines = read_os_string_lines(reader).collect::>(); let Some((chosen_regex, is_algo_based_format)) = determine_regex(&lines) else { - let e = ChecksumError::NoProperlyFormattedChecksumLinesFound { - filename: get_filename_for_output(filename_input, input_is_stdin), - }; - show_error!("{e}"); + log_no_properly_formatted(get_filename_for_output(filename_input, input_is_stdin)); set_exit_code(1); return Err(FileCheckError::AlgoDetectionError); }; @@ -739,21 +718,27 @@ fn process_checksum_file( i, &chosen_regex, is_algo_based_format, - &mut res, cli_algo_name, cli_algo_length, - &mut properly_formatted, opts, ) { - Ok(()) => correct_format += 1, - Err(LineCheckError::DigestMismatch) => res.failed_cksum += 1, + Ok(()) => { + correct_format += 1; + properly_formatted_lines += 1 + } + Err(LineCheckError::DigestMismatch) => { + res.failed_cksum += 1; + properly_formatted_lines += 1 + } Err(LineCheckError::UError(e)) => return Err(e.into()), Err(LineCheckError::Skipped) => continue, - Err(LineCheckError::ImproperlyFormatted) => (), + Err(LineCheckError::ImproperlyFormatted) => res.bad_format += 1, Err(LineCheckError::CantOpenFile | LineCheckError::FileIsDirectory) => { + properly_formatted_lines += 1; res.failed_open_file += 1 } Err(LineCheckError::FileNotFound) => { + properly_formatted_lines += 1; if !opts.ignore_missing { res.failed_open_file += 1 } @@ -763,12 +748,9 @@ fn process_checksum_file( // not a single line correctly formatted found // return an error - if !properly_formatted { + if properly_formatted_lines == 0 { if !opts.status { - return Err(ChecksumError::NoProperlyFormattedChecksumLinesFound { - filename: get_filename_for_output(filename_input, input_is_stdin), - } - .into()); + log_no_properly_formatted(get_filename_for_output(filename_input, input_is_stdin)); } set_exit_code(1); return Err(FileCheckError::ImproperlyFormatted); @@ -1234,7 +1216,7 @@ mod tests { .captures(b"SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") .unwrap(); - let result = get_expected_digest_as_hexa_string(&caps, &re); + let result = get_expected_digest_as_hex_string(&caps, &re); assert_eq!( result.unwrap(), @@ -1249,7 +1231,7 @@ mod tests { .captures(b"SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU") .unwrap(); - let result = get_expected_digest_as_hexa_string(&caps, &re); + let result = get_expected_digest_as_hex_string(&caps, &re); assert!(result.is_none()); } From 7c4724edc32dd4ecfd7d16051eb074fb3bcc0ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 17 Nov 2024 16:28:53 +0100 Subject: [PATCH 048/351] feat(checksum): refactor ChecksumResult to include more counters in it - Add comments to explain what each field is counting --- src/uucore/src/lib/features/checksum.rs | 71 ++++++++++++++----------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 386da1f71e8..8c435afedf2 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -68,11 +68,27 @@ pub struct HashAlgorithm { pub bits: usize, } +/// This structure holds the count of checksum test lines' outcomes. #[derive(Default)] struct ChecksumResult { - pub bad_format: i32, - pub failed_cksum: i32, - pub failed_open_file: i32, + /// Number of lines in the file where the computed checksum MATCHES + /// the expectation. + pub correct: u32, + /// Number of lines in the file where the computed checksum DIFFERS + /// from the expectation. + pub failed_cksum: u32, + pub failed_open_file: u32, + /// Number of improperly formatted lines. + pub bad_format: u32, + /// Total number of non-empty, non-comment lines. + pub total: u32, +} + +impl ChecksumResult { + #[inline] + fn total_properly_formatted(&self) -> u32 { + self.total - self.bad_format + } } /// Represents a reason for which the processing of a checksum line @@ -681,8 +697,6 @@ fn process_checksum_file( cli_algo_length: Option, opts: ChecksumOptions, ) -> Result<(), FileCheckError> { - let mut correct_format = 0; - let mut properly_formatted_lines = 0; let mut res = ChecksumResult::default(); let input_is_stdin = filename_input == OsStr::new("-"); @@ -712,7 +726,7 @@ fn process_checksum_file( }; for (i, line) in lines.iter().enumerate() { - match process_checksum_line( + let line_result = process_checksum_line( filename_input, line, i, @@ -721,34 +735,31 @@ fn process_checksum_file( cli_algo_name, cli_algo_length, opts, - ) { - Ok(()) => { - correct_format += 1; - properly_formatted_lines += 1 - } - Err(LineCheckError::DigestMismatch) => { - res.failed_cksum += 1; - properly_formatted_lines += 1 - } - Err(LineCheckError::UError(e)) => return Err(e.into()), - Err(LineCheckError::Skipped) => continue, - Err(LineCheckError::ImproperlyFormatted) => res.bad_format += 1, - Err(LineCheckError::CantOpenFile | LineCheckError::FileIsDirectory) => { - properly_formatted_lines += 1; - res.failed_open_file += 1 - } - Err(LineCheckError::FileNotFound) => { - properly_formatted_lines += 1; - if !opts.ignore_missing { - res.failed_open_file += 1 - } - } + ); + + // Match a first time to elude critical UErrors, and increment the total + // in all cases except on skipped. + use LineCheckError::*; + match line_result { + Err(UError(e)) => return Err(e.into()), + Err(Skipped) => (), + _ => res.total += 1, + } + + // Match a second time to update the right field of `res`. + match line_result { + Ok(()) => res.correct += 1, + Err(DigestMismatch) => res.failed_cksum += 1, + Err(ImproperlyFormatted) => res.bad_format += 1, + Err(CantOpenFile | FileIsDirectory) => res.failed_open_file += 1, + Err(FileNotFound) if !opts.ignore_missing => res.failed_open_file += 1, + _ => continue, }; } // not a single line correctly formatted found // return an error - if properly_formatted_lines == 0 { + if res.total_properly_formatted() == 0 { if !opts.status { log_no_properly_formatted(get_filename_for_output(filename_input, input_is_stdin)); } @@ -759,7 +770,7 @@ fn process_checksum_file( // if any incorrectly formatted line, show it cksum_output(&res, opts.status); - if opts.ignore_missing && correct_format == 0 { + if opts.ignore_missing && res.correct == 0 { // we have only bad format // and we had ignore-missing eprintln!( From ba7c02860e30120d0a64aaf17d97bb0b3d2a8ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 17 Nov 2024 23:16:16 +0100 Subject: [PATCH 049/351] feat(checksum): odd number of hexa characters is wrong formatting --- src/uucore/src/lib/features/checksum.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 8c435afedf2..1fbf201e611 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -468,8 +468,12 @@ fn get_expected_digest_as_hex_string(caps: &Captures, chosen_regex: &Regex) -> O if chosen_regex.as_str() == ALGO_BASED_REGEX_BASE64 { BASE64.decode(ck).map(hex::encode).ok() - } else { + } else if ck.len() % 2 == 0 { Some(str::from_utf8(ck).unwrap().to_string()) + } else { + // If the length of the digest is not a multiple of 2, then it + // must be improperly formatted (1 hex digit is 2 characters) + None } } From 8c4f595f2414d9dc0aa97e75386bba6f2b76ff6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sun, 17 Nov 2024 16:58:17 +0100 Subject: [PATCH 050/351] test(cksum): rework test for improperly formatted keeps processing --- tests/by-util/test_cksum.rs | 74 ++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 86de7ea95b6..bf74de9cc59 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1403,12 +1403,13 @@ fn test_check_trailing_space_fails() { /// in checksum files. /// These tests are excluded from Windows because it does not provide any safe /// conversion between `OsString` and byte sequences for non-utf-8 strings. -#[cfg(not(windows))] mod check_utf8 { - use super::*; + // This test should pass on linux and macos. + #[cfg(not(windows))] #[test] fn test_check_non_utf8_comment() { + use super::*; let hashes = b"MD5 (empty) = 1B2M2Y8AsgTpgAmY7PhCfg==\n\ # Comment with a non utf8 char: >>\xff<<\n\ @@ -1431,8 +1432,12 @@ mod check_utf8 { .no_stderr(); } + // This test should pass on linux. Windows and macos will fail to + // create a file which name contains '\xff'. + #[cfg(target_os = "linux")] #[test] fn test_check_non_utf8_filename() { + use super::*; use std::{ffi::OsString, os::unix::ffi::OsStringExt}; let scene = TestScenario::new(util_name!()); @@ -1569,35 +1574,68 @@ fn test_check_mix_hex_base64() { .stdout_only("foo1.dat: OK\nfoo2.dat: OK\n"); } -#[ignore = "not yet implemented"] +/// This test ensures that an improperly formatted base64 checksum in a file +/// does not interrupt the processing of next lines. #[test] -fn test_check_incorrectly_formatted_checksum_does_not_stop_processing() { - // The first line contains an incorrectly formatted checksum that can't be - // correctly decoded. This must not prevent the program from looking at the - // rest of the file. - let lines = [ - "BLAKE2b-56 (foo1) = GFYEQ7HhAw=", // Should be 2 '=' at the end - "BLAKE2b-56 (foo2) = 18560443b1e103", // OK - ]; +fn test_check_incorrectly_formatted_checksum_keeps_processing_b64() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("f"); + + let good_ck = "MD5 (f) = 1B2M2Y8AsgTpgAmY7PhCfg=="; // OK + let bad_ck = "MD5 (f) = 1B2M2Y8AsgTpgAmY7PhCfg="; // Missing last '=' + + // Good then Bad + scene + .ucmd() + .arg("--check") + .pipe_in([good_ck, bad_ck].join("\n").as_bytes().to_vec()) + .succeeds() + .stdout_contains("f: OK") + .stderr_contains("cksum: WARNING: 1 line is improperly formatted"); + // Bad then Good + scene + .ucmd() + .arg("--check") + .pipe_in([bad_ck, good_ck].join("\n").as_bytes().to_vec()) + .succeeds() + .stdout_contains("f: OK") + .stderr_contains("cksum: WARNING: 1 line is improperly formatted"); +} + +/// This test ensures that an improperly formatted hexadecimal checksum in a +/// file does not interrupt the processing of next lines. +#[test] +fn test_check_incorrectly_formatted_checksum_keeps_processing_hex() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; + at.touch("f"); + + let good_ck = "MD5 (f) = d41d8cd98f00b204e9800998ecf8427e"; // OK + let bad_ck = "MD5 (f) = d41d8cd98f00b204e9800998ecf8427"; // Missing last - at.write("foo1", "foo"); - at.write("foo2", "foo"); - at.write("sum", &lines.join("\n")); + // Good then Bad + scene + .ucmd() + .arg("--check") + .pipe_in([good_ck, bad_ck].join("\n").as_bytes().to_vec()) + .succeeds() + .stdout_contains("f: OK") + .stderr_contains("cksum: WARNING: 1 line is improperly formatted"); + // Bad then Good scene .ucmd() .arg("--check") - .arg(at.subdir.join("sum")) + .pipe_in([bad_ck, good_ck].join("\n").as_bytes().to_vec()) .succeeds() - .stderr_contains("1 line is improperly formatted") - .stdout_contains("foo2: OK"); + .stdout_contains("f: OK") + .stderr_contains("cksum: WARNING: 1 line is improperly formatted"); } /// This module reimplements the cksum-base64.pl GNU test. -mod cksum_base64 { +mod gnu_cksum_base64 { use super::*; use crate::common::util::log_info; From cfc66f9f6fe8df12b3378c6d6416fe28689ff217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Tue, 26 Nov 2024 01:46:35 +0100 Subject: [PATCH 051/351] chore(checksum): fix clippy warnings in tests --- src/uucore/src/lib/features/checksum.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 1fbf201e611..f7228830b9a 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -1056,7 +1056,7 @@ mod tests { ]; for (input, expected) in test_cases { - let captures = algo_based_regex.captures(*input); + let captures = algo_based_regex.captures(input); match expected { Some((algo, bits, filename, checksum)) => { assert!(captures.is_some()); @@ -1206,7 +1206,7 @@ mod tests { // Test leading space before checksum line let lines_algo_based_leading_space = - vec![" MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"] + [" MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"] .iter() .map(|s| OsString::from(s.to_string())) .collect::>(); @@ -1216,7 +1216,7 @@ mod tests { // Test trailing space after checksum line (should fail) let lines_algo_based_leading_space = - vec!["MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e "] + ["MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e "] .iter() .map(|s| OsString::from(s.to_string())) .collect::>(); From c8b0c8b612f97f29b6d801715ed9bffd26b6e1fd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 26 Nov 2024 10:35:13 +0100 Subject: [PATCH 052/351] cp: remove some sleep() calls in tests --- tests/by-util/test_cp.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 156daec1f15..7a0889b0fa6 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -8,6 +8,7 @@ use crate::common::util::TestScenario; #[cfg(not(windows))] use std::fs::set_permissions; +use std::io::Write; #[cfg(not(windows))] use std::os::unix::fs; @@ -447,9 +448,9 @@ fn test_cp_arg_update_older_dest_older_than_src() { let old_content = "old content\n"; let new_content = "new content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -473,9 +474,9 @@ fn test_cp_arg_update_short_no_overwrite() { let old_content = "old content\n"; let new_content = "new content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -499,9 +500,9 @@ fn test_cp_arg_update_short_overwrite() { let old_content = "old content\n"; let new_content = "new content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -526,9 +527,9 @@ fn test_cp_arg_update_none_then_all() { let old_content = "old content\n"; let new_content = "new content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); @@ -554,9 +555,9 @@ fn test_cp_arg_update_all_then_none() { let old_content = "old content\n"; let new_content = "new content\n"; - at.write(old, old_content); - - sleep(Duration::from_secs(1)); + let mut f = at.make_file(old); + f.write_all(old_content.as_bytes()).unwrap(); + f.set_modified(std::time::UNIX_EPOCH).unwrap(); at.write(new, new_content); From 869253379066824ecc83fad9cde64bd1b408dc5f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 26 Nov 2024 15:32:53 +0100 Subject: [PATCH 053/351] uucore/perms: use ORs instead of match (fix todo) --- src/uucore/src/lib/features/perms.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index ebb97042e13..3623e9e6149 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -23,7 +23,7 @@ use std::fs::Metadata; use std::os::unix::fs::MetadataExt; use std::os::unix::ffi::OsStrExt; -use std::path::{Path, MAIN_SEPARATOR_STR}; +use std::path::{Path, MAIN_SEPARATOR}; /// The various level of verbosity #[derive(PartialEq, Eq, Clone, Debug)] @@ -214,23 +214,13 @@ fn is_root(path: &Path, would_traverse_symlink: bool) -> bool { // We cannot check path.is_dir() here, as this would resolve symlinks, // which we need to avoid here. // All directory-ish paths match "*/", except ".", "..", "*/.", and "*/..". - let looks_like_dir = match path.as_os_str().to_str() { - // If it contains special character, prefer to err on the side of safety, i.e. forbidding the chown operation: - None => false, - Some(".") | Some("..") => true, - Some(path_str) => { - (path_str.ends_with(MAIN_SEPARATOR_STR)) - || (path_str.ends_with(&format!("{MAIN_SEPARATOR_STR}."))) - || (path_str.ends_with(&format!("{MAIN_SEPARATOR_STR}.."))) - } - }; - // TODO: Once we reach MSRV 1.74.0, replace this abomination by something simpler, e.g. this: - // let path_bytes = path.as_os_str().as_encoded_bytes(); - // let looks_like_dir = path_bytes == [b'.'] - // || path_bytes == [b'.', b'.'] - // || path_bytes.ends_with(&[MAIN_SEPARATOR as u8]) - // || path_bytes.ends_with(&[MAIN_SEPARATOR as u8, b'.']) - // || path_bytes.ends_with(&[MAIN_SEPARATOR as u8, b'.', b'.']); + let path_bytes = path.as_os_str().as_encoded_bytes(); + let looks_like_dir = path_bytes == [b'.'] + || path_bytes == [b'.', b'.'] + || path_bytes.ends_with(&[MAIN_SEPARATOR as u8]) + || path_bytes.ends_with(&[MAIN_SEPARATOR as u8, b'.']) + || path_bytes.ends_with(&[MAIN_SEPARATOR as u8, b'.', b'.']); + if !looks_like_dir { return false; } From 3e1328c81acde33957d06b1d908ae2732d0cc053 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 02:09:06 +0000 Subject: [PATCH 054/351] fix(deps): update rust crate libc to v0.2.166 --- Cargo.lock | 4 ++-- fuzz/Cargo.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d13b9c4ba5..b6848c40944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,9 +1305,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.165" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libloading" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 724e0db7ec4..d5372d79b67 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -416,9 +416,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.165" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libfuzzer-sys" From 069ec76f5b0117cabc4d1af794caceeb3c849b7f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 04:27:39 +0000 Subject: [PATCH 055/351] chore(deps): update rust crate blake3 to v1.5.5 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d13b9c4ba5..0b4540df23d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" dependencies = [ "arrayref", "arrayvec", @@ -433,9 +433,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation-sys" From ab9e5cb8a780f75e8529fd8805a197d996361d26 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 27 Nov 2024 09:29:22 +0100 Subject: [PATCH 056/351] env: add missing cfg attributes to tests --- tests/by-util/test_env.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 208feab6db2..8c5b43b2ddc 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -10,6 +10,7 @@ use crate::common::util::TestScenario; use crate::common::util::UChild; #[cfg(unix)] use nix::sys::signal::Signal; +#[cfg(feature = "echo")] use regex::Regex; use std::env; use std::path::Path; @@ -98,6 +99,7 @@ fn test_if_windows_batch_files_can_be_executed() { assert!(result.stdout_str().contains("Hello Windows World!")); } +#[cfg(feature = "echo")] #[test] fn test_debug_1() { let ts = TestScenario::new(util_name!()); @@ -118,6 +120,7 @@ fn test_debug_1() { ); } +#[cfg(feature = "echo")] #[test] fn test_debug_2() { let ts = TestScenario::new(util_name!()); @@ -144,6 +147,7 @@ fn test_debug_2() { ); } +#[cfg(feature = "echo")] #[test] fn test_debug1_part_of_string_arg() { let ts = TestScenario::new(util_name!()); @@ -165,6 +169,7 @@ fn test_debug1_part_of_string_arg() { ); } +#[cfg(feature = "echo")] #[test] fn test_debug2_part_of_string_arg() { let ts = TestScenario::new(util_name!()); @@ -651,7 +656,7 @@ fn test_env_with_empty_executable_double_quotes() { } #[test] -#[cfg(unix)] +#[cfg(all(unix, feature = "dirname", feature = "echo"))] fn test_env_overwrite_arg0() { let ts = TestScenario::new(util_name!()); @@ -675,7 +680,7 @@ fn test_env_overwrite_arg0() { } #[test] -#[cfg(unix)] +#[cfg(all(unix, feature = "echo"))] fn test_env_arg_argv0_overwrite() { let ts = TestScenario::new(util_name!()); @@ -723,7 +728,7 @@ fn test_env_arg_argv0_overwrite() { } #[test] -#[cfg(unix)] +#[cfg(all(unix, feature = "echo"))] fn test_env_arg_argv0_overwrite_mixed_with_string_args() { let ts = TestScenario::new(util_name!()); From 75de5a0613aec8d5b5d26b80467d5d0d9cb7d99c Mon Sep 17 00:00:00 2001 From: Peng Zijun <2200012909@stu.pku.edu.cn> Date: Thu, 28 Nov 2024 16:14:16 +0800 Subject: [PATCH 057/351] tr: Add ambiguous octal escape warning (#6886) * tr: Add ambiguous octal escape warning, issue #6821 * tr: Make code cleaner --- src/uu/tr/src/operation.rs | 51 ++++++++++++++++++++++++++++++-------- tests/by-util/test_tr.rs | 4 +-- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index fc01a83608d..035f0997207 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -16,6 +16,7 @@ use nom::{ IResult, }; use std::{ + char, collections::{HashMap, HashSet}, error::Error, fmt::{Debug, Display}, @@ -23,6 +24,7 @@ use std::{ ops::Not, }; use uucore::error::UError; +use uucore::show_warning; #[derive(Debug, Clone)] pub enum BadSequence { @@ -293,7 +295,9 @@ impl Sequence { Self::parse_class, Self::parse_char_equal, // NOTE: This must be the last one - map(Self::parse_backslash_or_char, |s| Ok(Self::Char(s))), + map(Self::parse_backslash_or_char_with_warning, |s| { + Ok(Self::Char(s)) + }), )))(input) .map(|(_, r)| r) .unwrap() @@ -302,10 +306,16 @@ impl Sequence { } fn parse_octal(input: &[u8]) -> IResult<&[u8], u8> { + // For `parse_char_range`, `parse_char_star`, `parse_char_repeat`, `parse_char_equal`. + // Because in these patterns, there's no ambiguous cases. + preceded(tag("\\"), Self::parse_octal_up_to_three_digits)(input) + } + + fn parse_octal_with_warning(input: &[u8]) -> IResult<&[u8], u8> { preceded( tag("\\"), alt(( - Self::parse_octal_up_to_three_digits, + Self::parse_octal_up_to_three_digits_with_warning, // Fallback for if the three digit octal escape is greater than \377 (0xFF), and therefore can't be // parsed as as a byte // See test `test_multibyte_octal_sequence` @@ -319,16 +329,29 @@ impl Sequence { recognize(many_m_n(1, 3, one_of("01234567"))), |out: &[u8]| { let str_to_parse = std::str::from_utf8(out).unwrap(); + u8::from_str_radix(str_to_parse, 8).ok() + }, + )(input) + } - match u8::from_str_radix(str_to_parse, 8) { - Ok(ue) => Some(ue), - Err(_pa) => { - // TODO - // A warning needs to be printed here - // See https://github.com/uutils/coreutils/issues/6821 - None - } + fn parse_octal_up_to_three_digits_with_warning(input: &[u8]) -> IResult<&[u8], u8> { + map_opt( + recognize(many_m_n(1, 3, one_of("01234567"))), + |out: &[u8]| { + let str_to_parse = std::str::from_utf8(out).unwrap(); + let result = u8::from_str_radix(str_to_parse, 8).ok(); + if result.is_none() { + let origin_octal: &str = std::str::from_utf8(input).unwrap(); + let actual_octal_tail: &str = std::str::from_utf8(&input[0..2]).unwrap(); + let outstand_char: char = char::from_u32(input[2] as u32).unwrap(); + show_warning!( + "the ambiguous octal escape \\{} is being\n interpreted as the 2-byte sequence \\0{}, {}", + origin_octal, + actual_octal_tail, + outstand_char + ); } + result }, )(input) } @@ -360,6 +383,14 @@ impl Sequence { alt((Self::parse_octal, Self::parse_backslash, Self::single_char))(input) } + fn parse_backslash_or_char_with_warning(input: &[u8]) -> IResult<&[u8], u8> { + alt(( + Self::parse_octal_with_warning, + Self::parse_backslash, + Self::single_char, + ))(input) + } + fn single_char(input: &[u8]) -> IResult<&[u8], u8> { take(1usize)(input).map(|(l, a)| (l, a[0])) } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index ebd7635e432..705f40834e5 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1494,9 +1494,7 @@ fn test_multibyte_octal_sequence() { .args(&["-d", r"\501"]) .pipe_in("(1Ł)") .succeeds() - // TODO - // A warning needs to be printed here - // See https://github.com/uutils/coreutils/issues/6821 + .stderr_is("tr: warning: the ambiguous octal escape \\501 is being\n interpreted as the 2-byte sequence \\050, 1\n") .stdout_is("Ł)"); } From 29de3ee43c3277273b343119c877209c1e40383c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 18:13:49 +0100 Subject: [PATCH 058/351] run rustfmt on src/uu/shuf/src/shuf.rs --- src/uu/shuf/src/shuf.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 260b5130c53..15b158b0c8a 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -279,7 +279,10 @@ impl<'a> Shufable for Vec<&'a [u8]> { // this is safe. (**self).choose(rng).unwrap() } - type PartialShuffleIterator<'b> = std::iter::Copied> where Self: 'b; + type PartialShuffleIterator<'b> + = std::iter::Copied> + where + Self: 'b; fn partial_shuffle<'b>( &'b mut self, rng: &'b mut WrappedRng, @@ -298,7 +301,10 @@ impl Shufable for RangeInclusive { fn choose(&self, rng: &mut WrappedRng) -> usize { rng.gen_range(self.clone()) } - type PartialShuffleIterator<'b> = NonrepeatingIterator<'b> where Self: 'b; + type PartialShuffleIterator<'b> + = NonrepeatingIterator<'b> + where + Self: 'b; fn partial_shuffle<'b>( &'b mut self, rng: &'b mut WrappedRng, From cfb0b95b62c2b415a3b72640af023bb14675be23 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 19:06:10 +0100 Subject: [PATCH 059/351] clippy: fix 'empty line after doc comment' --- src/uu/dd/src/numbers.rs | 2 +- src/uu/df/src/df.rs | 2 -- src/uu/timeout/src/timeout.rs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uu/dd/src/numbers.rs b/src/uu/dd/src/numbers.rs index 8a6fa5a7a37..c29668c891a 100644 --- a/src/uu/dd/src/numbers.rs +++ b/src/uu/dd/src/numbers.rs @@ -2,8 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Functions for formatting a number as a magnitude and a unit suffix. +/// Functions for formatting a number as a magnitude and a unit suffix. /// The first ten powers of 1024. const IEC_BASES: [u128; 10] = [ 1, diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 517f8a31f1d..8ef84a46311 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -311,7 +311,6 @@ fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool { /// /// Finally, if there are duplicate entries, the one with the shorter /// path is kept. - fn filter_mount_list(vmi: Vec, opt: &Options) -> Vec { let mut result = vec![]; for mi in vmi { @@ -331,7 +330,6 @@ fn filter_mount_list(vmi: Vec, opt: &Options) -> Vec { /// /// `opt` excludes certain filesystems from consideration and allows for the synchronization of filesystems before running; see /// [`Options`] for more information. - fn get_all_filesystems(opt: &Options) -> UResult> { // Run a sync call before any operation if so instructed. if opt.sync { diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 19016900ac2..2ba93769aa1 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -288,7 +288,6 @@ fn preserve_signal_info(signal: libc::c_int) -> libc::c_int { } /// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes. - fn timeout( cmd: &[String], duration: Duration, From 4d3902426a946533fecf020572864964983544bd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 19:17:20 +0100 Subject: [PATCH 060/351] clippy: fix unneeded 'return' statement --- src/uu/env/src/string_parser.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/uu/env/src/string_parser.rs b/src/uu/env/src/string_parser.rs index 0ea4a3c0c60..5cc8d77a12f 100644 --- a/src/uu/env/src/string_parser.rs +++ b/src/uu/env/src/string_parser.rs @@ -114,10 +114,9 @@ impl<'a> StringParser<'a> { } pub fn peek_chunk(&self) -> Option> { - return self - .get_chunk_with_length_at(self.pointer) + self.get_chunk_with_length_at(self.pointer) .ok() - .map(|(chunk, _)| chunk); + .map(|(chunk, _)| chunk) } pub fn consume_chunk(&mut self) -> Result, Error> { From 41a3695b3fe5678a9be82f1585d104249d7e737d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 18:05:15 +0100 Subject: [PATCH 061/351] uucore: Fix a clippy warning The following explicit lifetimes could be elided: 'a --- src/uu/csplit/src/csplit.rs | 4 ++-- src/uu/cut/src/matcher.rs | 2 +- src/uu/cut/src/searcher.rs | 2 +- src/uu/date/src/date.rs | 4 ++-- src/uu/dd/src/dd.rs | 6 +++--- src/uu/env/src/variable_parser.rs | 2 +- src/uu/fmt/src/linebreak.rs | 2 +- src/uu/fmt/src/parasplit.rs | 12 ++++++------ src/uu/join/src/join.rs | 2 +- src/uu/od/src/inputdecoder.rs | 8 ++++---- src/uu/od/src/multifilereader.rs | 6 +++--- src/uu/shuf/src/shuf.rs | 4 ++-- src/uu/sort/src/merge.rs | 4 ++-- src/uu/split/src/filenames.rs | 2 +- src/uu/split/src/split.rs | 6 +++--- src/uu/tail/src/chunks.rs | 2 +- src/uu/wc/src/utf8/read.rs | 4 ++-- src/uucore/src/lib/features/sum.rs | 2 +- 18 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 9e132b704bf..2054e6cff4e 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -197,7 +197,7 @@ struct SplitWriter<'a> { dev_null: bool, } -impl<'a> Drop for SplitWriter<'a> { +impl Drop for SplitWriter<'_> { fn drop(&mut self) { if self.options.elide_empty_files && self.size == 0 { let file_name = self.options.split_name.get(self.counter); @@ -206,7 +206,7 @@ impl<'a> Drop for SplitWriter<'a> { } } -impl<'a> SplitWriter<'a> { +impl SplitWriter<'_> { fn new(options: &CsplitOptions) -> SplitWriter { SplitWriter { options, diff --git a/src/uu/cut/src/matcher.rs b/src/uu/cut/src/matcher.rs index 953e083b139..bb0c44d5bb4 100644 --- a/src/uu/cut/src/matcher.rs +++ b/src/uu/cut/src/matcher.rs @@ -23,7 +23,7 @@ impl<'a> ExactMatcher<'a> { } } -impl<'a> Matcher for ExactMatcher<'a> { +impl Matcher for ExactMatcher<'_> { fn next_match(&self, haystack: &[u8]) -> Option<(usize, usize)> { let mut pos = 0usize; loop { diff --git a/src/uu/cut/src/searcher.rs b/src/uu/cut/src/searcher.rs index 21424790eea..41c12cf6e2f 100644 --- a/src/uu/cut/src/searcher.rs +++ b/src/uu/cut/src/searcher.rs @@ -27,7 +27,7 @@ impl<'a, 'b, M: Matcher> Searcher<'a, 'b, M> { // Iterate over field delimiters // Returns (first, last) positions of each sequence, where `haystack[first..last]` // corresponds to the delimiter. -impl<'a, 'b, M: Matcher> Iterator for Searcher<'a, 'b, M> { +impl Iterator for Searcher<'_, '_, M> { type Item = (usize, usize); fn next(&mut self) -> Option { diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 9c7d865643d..766e79bd497 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -103,7 +103,7 @@ enum Iso8601Format { Ns, } -impl<'a> From<&'a str> for Iso8601Format { +impl From<&str> for Iso8601Format { fn from(s: &str) -> Self { match s { HOURS => Self::Hours, @@ -123,7 +123,7 @@ enum Rfc3339Format { Ns, } -impl<'a> From<&'a str> for Rfc3339Format { +impl From<&str> for Rfc3339Format { fn from(s: &str) -> Self { match s { DATE => Self::Date, diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 24fab1e2fb3..ca8c2a8b570 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -424,7 +424,7 @@ fn make_linux_iflags(iflags: &IFlags) -> Option { } } -impl<'a> Read for Input<'a> { +impl Read for Input<'_> { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut base_idx = 0; let target_len = buf.len(); @@ -447,7 +447,7 @@ impl<'a> Read for Input<'a> { } } -impl<'a> Input<'a> { +impl Input<'_> { /// Discard the system file cache for the given portion of the input. /// /// `offset` and `len` specify a contiguous portion of the input. @@ -928,7 +928,7 @@ enum BlockWriter<'a> { Unbuffered(Output<'a>), } -impl<'a> BlockWriter<'a> { +impl BlockWriter<'_> { fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { match self { Self::Unbuffered(o) => o.discard_cache(offset, len), diff --git a/src/uu/env/src/variable_parser.rs b/src/uu/env/src/variable_parser.rs index f225d494572..d08c9f0dcca 100644 --- a/src/uu/env/src/variable_parser.rs +++ b/src/uu/env/src/variable_parser.rs @@ -11,7 +11,7 @@ pub struct VariableParser<'a, 'b> { pub parser: &'b mut StringParser<'a>, } -impl<'a, 'b> VariableParser<'a, 'b> { +impl<'a> VariableParser<'a, '_> { fn get_current_char(&self) -> Option { self.parser.peek().ok() } diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index aa1477ebac0..05d01d1a3ec 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -20,7 +20,7 @@ struct BreakArgs<'a> { ostream: &'a mut BufWriter, } -impl<'a> BreakArgs<'a> { +impl BreakArgs<'_> { fn compute_width(&self, winfo: &WordInfo, posn: usize, fresh: bool) -> usize { if fresh { 0 diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index 1ae8ea34f42..8aa18c4c987 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -73,7 +73,7 @@ pub struct FileLines<'a> { lines: Lines<&'a mut FileOrStdReader>, } -impl<'a> FileLines<'a> { +impl FileLines<'_> { fn new<'b>(opts: &'b FmtOptions, lines: Lines<&'b mut FileOrStdReader>) -> FileLines<'b> { FileLines { opts, lines } } @@ -144,7 +144,7 @@ impl<'a> FileLines<'a> { } } -impl<'a> Iterator for FileLines<'a> { +impl Iterator for FileLines<'_> { type Item = Line; fn next(&mut self) -> Option { @@ -232,7 +232,7 @@ pub struct ParagraphStream<'a> { opts: &'a FmtOptions, } -impl<'a> ParagraphStream<'a> { +impl ParagraphStream<'_> { pub fn new<'b>(opts: &'b FmtOptions, reader: &'b mut FileOrStdReader) -> ParagraphStream<'b> { let lines = FileLines::new(opts, reader.lines()).peekable(); // at the beginning of the file, we might find mail headers @@ -273,7 +273,7 @@ impl<'a> ParagraphStream<'a> { } } -impl<'a> Iterator for ParagraphStream<'a> { +impl Iterator for ParagraphStream<'_> { type Item = Result; #[allow(clippy::cognitive_complexity)] @@ -491,7 +491,7 @@ struct WordSplit<'a> { prev_punct: bool, } -impl<'a> WordSplit<'a> { +impl WordSplit<'_> { fn analyze_tabs(&self, string: &str) -> (Option, usize, Option) { // given a string, determine (length before tab) and (printed length after first tab) // if there are no tabs, beforetab = -1 and aftertab is the printed length @@ -517,7 +517,7 @@ impl<'a> WordSplit<'a> { } } -impl<'a> WordSplit<'a> { +impl WordSplit<'_> { fn new<'b>(opts: &'b FmtOptions, string: &'b str) -> WordSplit<'b> { // wordsplits *must* start at a non-whitespace character let trim_string = string.trim_start(); diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index e7bc7da6963..f01f75b71d5 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -109,7 +109,7 @@ struct MultiByteSep<'a> { finder: Finder<'a>, } -impl<'a> Separator for MultiByteSep<'a> { +impl Separator for MultiByteSep<'_> { fn field_ranges(&self, haystack: &[u8], len_guess: usize) -> Vec<(usize, usize)> { let mut field_ranges = Vec::with_capacity(len_guess); let mut last_end = 0; diff --git a/src/uu/od/src/inputdecoder.rs b/src/uu/od/src/inputdecoder.rs index 62117d54608..44ad2922843 100644 --- a/src/uu/od/src/inputdecoder.rs +++ b/src/uu/od/src/inputdecoder.rs @@ -33,7 +33,7 @@ where byte_order: ByteOrder, } -impl<'a, I> InputDecoder<'a, I> { +impl InputDecoder<'_, I> { /// Creates a new `InputDecoder` with an allocated buffer of `normal_length` + `peek_length` bytes. /// `byte_order` determines how to read multibyte formats from the buffer. pub fn new( @@ -55,7 +55,7 @@ impl<'a, I> InputDecoder<'a, I> { } } -impl<'a, I> InputDecoder<'a, I> +impl InputDecoder<'_, I> where I: PeekRead, { @@ -81,7 +81,7 @@ where } } -impl<'a, I> HasError for InputDecoder<'a, I> +impl HasError for InputDecoder<'_, I> where I: HasError, { @@ -103,7 +103,7 @@ pub struct MemoryDecoder<'a> { byte_order: ByteOrder, } -impl<'a> MemoryDecoder<'a> { +impl MemoryDecoder<'_> { /// Set a part of the internal buffer to zero. /// access to the whole buffer is possible, not just to the valid data. pub fn zero_out_buffer(&mut self, start: usize, end: usize) { diff --git a/src/uu/od/src/multifilereader.rs b/src/uu/od/src/multifilereader.rs index 813ef029f37..34cd251ac78 100644 --- a/src/uu/od/src/multifilereader.rs +++ b/src/uu/od/src/multifilereader.rs @@ -28,7 +28,7 @@ pub trait HasError { fn has_error(&self) -> bool; } -impl<'b> MultifileReader<'b> { +impl MultifileReader<'_> { pub fn new(fnames: Vec) -> MultifileReader { let mut mf = MultifileReader { ni: fnames, @@ -76,7 +76,7 @@ impl<'b> MultifileReader<'b> { } } -impl<'b> io::Read for MultifileReader<'b> { +impl io::Read for MultifileReader<'_> { // Fill buf with bytes read from the list of files // Returns Ok() // Handles io errors itself, thus always returns OK @@ -113,7 +113,7 @@ impl<'b> io::Read for MultifileReader<'b> { } } -impl<'b> HasError for MultifileReader<'b> { +impl HasError for MultifileReader<'_> { fn has_error(&self) -> bool { self.any_err } diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 15b158b0c8a..2d8023448a0 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -380,7 +380,7 @@ impl<'a> NonrepeatingIterator<'a> { } } -impl<'a> Iterator for NonrepeatingIterator<'a> { +impl Iterator for NonrepeatingIterator<'_> { type Item = usize; fn next(&mut self) -> Option { @@ -407,7 +407,7 @@ trait Writable { fn write_all_to(&self, output: &mut impl Write) -> Result<(), Error>; } -impl<'a> Writable for &'a [u8] { +impl Writable for &[u8] { fn write_all_to(&self, output: &mut impl Write) -> Result<(), Error> { output.write_all(self) } diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index c0457ffa4dc..d6872ec80e3 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -267,7 +267,7 @@ pub struct FileMerger<'a> { reader_join_handle: JoinHandle>, } -impl<'a> FileMerger<'a> { +impl FileMerger<'_> { /// Write the merged contents to the output file. pub fn write_all(self, settings: &GlobalSettings, output: Output) -> UResult<()> { let mut out = output.into_write(); @@ -341,7 +341,7 @@ struct FileComparator<'a> { settings: &'a GlobalSettings, } -impl<'a> Compare for FileComparator<'a> { +impl Compare for FileComparator<'_> { fn compare(&self, a: &MergeableFile, b: &MergeableFile) -> Ordering { let mut cmp = compare_by( &a.current_chunk.lines()[a.line_idx], diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index d2ce1beb316..9e899a417a9 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -341,7 +341,7 @@ impl<'a> FilenameIterator<'a> { } } -impl<'a> Iterator for FilenameIterator<'a> { +impl Iterator for FilenameIterator<'_> { type Item = String; fn next(&mut self) -> Option { diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 11fa04184df..86fded1d5db 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -748,7 +748,7 @@ impl<'a> ByteChunkWriter<'a> { } } -impl<'a> Write for ByteChunkWriter<'a> { +impl Write for ByteChunkWriter<'_> { /// Implements `--bytes=SIZE` fn write(&mut self, mut buf: &[u8]) -> std::io::Result { // If the length of `buf` exceeds the number of bytes remaining @@ -872,7 +872,7 @@ impl<'a> LineChunkWriter<'a> { } } -impl<'a> Write for LineChunkWriter<'a> { +impl Write for LineChunkWriter<'_> { /// Implements `--lines=NUMBER` fn write(&mut self, buf: &[u8]) -> std::io::Result { // If the number of lines in `buf` exceeds the number of lines @@ -978,7 +978,7 @@ impl<'a> LineBytesChunkWriter<'a> { } } -impl<'a> Write for LineBytesChunkWriter<'a> { +impl Write for LineBytesChunkWriter<'_> { /// Write as many lines to a chunk as possible without /// exceeding the byte limit. If a single line has more bytes /// than the limit, then fill an entire single chunk with those diff --git a/src/uu/tail/src/chunks.rs b/src/uu/tail/src/chunks.rs index 636de7a90ef..2c80ac0ac01 100644 --- a/src/uu/tail/src/chunks.rs +++ b/src/uu/tail/src/chunks.rs @@ -64,7 +64,7 @@ impl<'a> ReverseChunks<'a> { } } -impl<'a> Iterator for ReverseChunks<'a> { +impl Iterator for ReverseChunks<'_> { type Item = Vec; fn next(&mut self) -> Option { diff --git a/src/uu/wc/src/utf8/read.rs b/src/uu/wc/src/utf8/read.rs index 819b0a6891c..9515cdc9fe6 100644 --- a/src/uu/wc/src/utf8/read.rs +++ b/src/uu/wc/src/utf8/read.rs @@ -27,7 +27,7 @@ pub enum BufReadDecoderError<'a> { Io(io::Error), } -impl<'a> fmt::Display for BufReadDecoderError<'a> { +impl fmt::Display for BufReadDecoderError<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { BufReadDecoderError::InvalidByteSequence(bytes) => { @@ -38,7 +38,7 @@ impl<'a> fmt::Display for BufReadDecoderError<'a> { } } -impl<'a> Error for BufReadDecoderError<'a> { +impl Error for BufReadDecoderError<'_> { fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { BufReadDecoderError::InvalidByteSequence(_) => None, diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs index 1baff7f7960..df9e1673d9d 100644 --- a/src/uucore/src/lib/features/sum.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -403,7 +403,7 @@ impl<'a> DigestWriter<'a> { } } -impl<'a> Write for DigestWriter<'a> { +impl Write for DigestWriter<'_> { #[cfg(not(windows))] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.digest.hash_update(buf); From a3a4457a4435fe559de667fd59343be1c4b2a244 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 21:27:49 +0100 Subject: [PATCH 062/351] clippy: spawned process is never 'wait()'ed on --- tests/by-util/test_dd.rs | 10 ++++++---- tests/by-util/test_env.rs | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index e1e55054a6f..64ca7603bf7 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1658,13 +1658,14 @@ fn test_reading_partial_blocks_from_fifo() { // Start different processes to write to the FIFO, with a small // pause in between. let mut writer_command = Command::new("sh"); - writer_command + let _ = writer_command .args([ "-c", &format!("(printf \"ab\"; sleep 0.1; printf \"cd\") > {fifoname}"), ]) .spawn() - .unwrap(); + .unwrap() + .wait(); let output = child.wait_with_output().unwrap(); assert_eq!(output.stdout, b"abcd"); @@ -1701,13 +1702,14 @@ fn test_reading_partial_blocks_from_fifo_unbuffered() { // Start different processes to write to the FIFO, with a small // pause in between. let mut writer_command = Command::new("sh"); - writer_command + let _ = writer_command .args([ "-c", &format!("(printf \"ab\"; sleep 0.1; printf \"cd\") > {fifoname}"), ]) .spawn() - .unwrap(); + .unwrap() + .wait(); let output = child.wait_with_output().unwrap(); assert_eq!(output.stdout, b"abcd"); diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 8c5b43b2ddc..a1b13e02037 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -36,13 +36,14 @@ impl Target { Self { child } } fn send_signal(&mut self, signal: Signal) { - Command::new("kill") + let _ = Command::new("kill") .args(&[ format!("-{}", signal as i32), format!("{}", self.child.id()), ]) .spawn() - .expect("failed to send signal"); + .expect("failed to send signal") + .wait(); self.child.delay(100); } fn is_alive(&mut self) -> bool { From c0840dd43f7764cdcdbe3c839767835cfba29fde Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 21:29:23 +0100 Subject: [PATCH 063/351] clippy: unneeded 'return' statement --- tests/by-util/test_ls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 0c0d8e3a822..3b2d46b39c2 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1329,10 +1329,10 @@ fn test_ls_long_symlink_color() { Some(captures) => { dbg!(captures.get(1).unwrap().as_str().to_string()); dbg!(captures.get(2).unwrap().as_str().to_string()); - return ( + ( captures.get(1).unwrap().as_str().to_string(), captures.get(2).unwrap().as_str().to_string(), - ); + ) } None => (String::new(), input.to_string()), } From 9d404e5ee83cd341b225baa3a860697a7ae919aa Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 21:29:57 +0100 Subject: [PATCH 064/351] clippy: it is more idiomatic to use 'Option<&T>' instead of '&Option' --- tests/common/util.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 87c937492f3..844618def47 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -75,7 +75,7 @@ pub fn is_ci() -> bool { } /// Read a test scenario fixture, returning its bytes -fn read_scenario_fixture>(tmpd: &Option>, file_rel_path: S) -> Vec { +fn read_scenario_fixture>(tmpd: Option<&Rc>, file_rel_path: S) -> Vec { let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path(); AtPath::new(tmpdir_path).read_bytes(file_rel_path.as_ref().to_str().unwrap()) } @@ -517,7 +517,7 @@ impl CmdResult { /// like `stdout_is()`, but expects the contents of the file at the provided relative path #[track_caller] pub fn stdout_is_fixture>(&self, file_rel_path: T) -> &Self { - let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + let contents = read_scenario_fixture(self.tmpd.as_ref(), file_rel_path); self.stdout_is(String::from_utf8(contents).unwrap()) } @@ -539,7 +539,7 @@ impl CmdResult { /// ``` #[track_caller] pub fn stdout_is_fixture_bytes>(&self, file_rel_path: T) -> &Self { - let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + let contents = read_scenario_fixture(self.tmpd.as_ref(), file_rel_path); self.stdout_is_bytes(contents) } @@ -552,7 +552,7 @@ impl CmdResult { template_vars: &[(&str, &str)], ) -> &Self { let mut contents = - String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap(); + String::from_utf8(read_scenario_fixture(self.tmpd.as_ref(), file_rel_path)).unwrap(); for kv in template_vars { contents = contents.replace(kv.0, kv.1); } @@ -566,7 +566,8 @@ impl CmdResult { file_rel_path: T, template_vars: &[Vec<(String, String)>], ) { - let contents = String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap(); + let contents = + String::from_utf8(read_scenario_fixture(self.tmpd.as_ref(), file_rel_path)).unwrap(); let possible_values = template_vars.iter().map(|vars| { let mut contents = contents.clone(); for kv in vars { @@ -604,7 +605,7 @@ impl CmdResult { /// Like `stdout_is_fixture`, but for stderr #[track_caller] pub fn stderr_is_fixture>(&self, file_rel_path: T) -> &Self { - let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + let contents = read_scenario_fixture(self.tmpd.as_ref(), file_rel_path); self.stderr_is(String::from_utf8(contents).unwrap()) } @@ -629,7 +630,7 @@ impl CmdResult { /// like `stdout_only()`, but expects the contents of the file at the provided relative path #[track_caller] pub fn stdout_only_fixture>(&self, file_rel_path: T) -> &Self { - let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + let contents = read_scenario_fixture(self.tmpd.as_ref(), file_rel_path); self.stdout_only_bytes(contents) } @@ -1384,7 +1385,7 @@ impl UCommand { /// like `pipe_in()`, but uses the contents of the file at the provided relative path as the piped in data pub fn pipe_in_fixture>(&mut self, file_rel_path: S) -> &mut Self { - let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + let contents = read_scenario_fixture(self.tmpd.as_ref(), file_rel_path); self.pipe_in(contents) } From 8df608cf974f2ffe9b0eca69edae81b40cb8d237 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Nov 2024 21:29:00 +0100 Subject: [PATCH 065/351] clippy: used underscore-prefixed item --- tests/by-util/test_du.rs | 102 +++++++++++++++------------------------ 1 file changed, 38 insertions(+), 64 deletions(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index af9718a4e6b..84e3b5050b4 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -7,7 +7,7 @@ #[cfg(not(windows))] use regex::Regex; -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(not(target_os = "windows"))] use crate::common::util::expected_result; use crate::common::util::TestScenario; @@ -36,11 +36,11 @@ fn test_du_basics() { return; } } - _du_basics(result.stdout_str()); + du_basics(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_basics(s: &str) { +fn du_basics(s: &str) { let answer = concat!( "4\t./subdir/deeper/deeper_dir\n", "8\t./subdir/deeper\n", @@ -52,7 +52,7 @@ fn _du_basics(s: &str) { } #[cfg(target_os = "windows")] -fn _du_basics(s: &str) { +fn du_basics(s: &str) { let answer = concat!( "0\t.\\subdir\\deeper\\deeper_dir\n", "0\t.\\subdir\\deeper\n", @@ -64,7 +64,7 @@ fn _du_basics(s: &str) { } #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"),))] -fn _du_basics(s: &str) { +fn du_basics(s: &str) { let answer = concat!( "8\t./subdir/deeper/deeper_dir\n", "16\t./subdir/deeper\n", @@ -95,19 +95,19 @@ fn test_du_basics_subdir() { return; } } - _du_basics_subdir(result.stdout_str()); + du_basics_subdir(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_basics_subdir(s: &str) { +fn du_basics_subdir(s: &str) { assert_eq!(s, "4\tsubdir/deeper/deeper_dir\n8\tsubdir/deeper\n"); } #[cfg(target_os = "windows")] -fn _du_basics_subdir(s: &str) { +fn du_basics_subdir(s: &str) { assert_eq!(s, "0\tsubdir/deeper\\deeper_dir\n0\tsubdir/deeper\n"); } #[cfg(target_os = "freebsd")] -fn _du_basics_subdir(s: &str) { +fn du_basics_subdir(s: &str) { assert_eq!(s, "8\tsubdir/deeper/deeper_dir\n16\tsubdir/deeper\n"); } #[cfg(all( @@ -115,7 +115,7 @@ fn _du_basics_subdir(s: &str) { not(target_os = "windows"), not(target_os = "freebsd") ))] -fn _du_basics_subdir(s: &str) { +fn du_basics_subdir(s: &str) { // MS-WSL linux has altered expected output if uucore::os::is_wsl_1() { assert_eq!(s, "0\tsubdir/deeper\n"); @@ -206,20 +206,20 @@ fn test_du_soft_link() { return; } } - _du_soft_link(result.stdout_str()); + du_soft_link(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_soft_link(s: &str) { +fn du_soft_link(s: &str) { // 'macos' host variants may have `du` output variation for soft links assert!((s == "12\tsubdir/links\n") || (s == "16\tsubdir/links\n")); } #[cfg(target_os = "windows")] -fn _du_soft_link(s: &str) { +fn du_soft_link(s: &str) { assert_eq!(s, "8\tsubdir/links\n"); } #[cfg(target_os = "freebsd")] -fn _du_soft_link(s: &str) { +fn du_soft_link(s: &str) { assert_eq!(s, "16\tsubdir/links\n"); } #[cfg(all( @@ -227,7 +227,7 @@ fn _du_soft_link(s: &str) { not(target_os = "windows"), not(target_os = "freebsd") ))] -fn _du_soft_link(s: &str) { +fn du_soft_link(s: &str) { // MS-WSL linux has altered expected output if uucore::os::is_wsl_1() { assert_eq!(s, "8\tsubdir/links\n"); @@ -255,19 +255,19 @@ fn test_du_hard_link() { } } // We do not double count hard links as the inodes are identical - _du_hard_link(result.stdout_str()); + du_hard_link(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_hard_link(s: &str) { +fn du_hard_link(s: &str) { assert_eq!(s, "12\tsubdir/links\n"); } #[cfg(target_os = "windows")] -fn _du_hard_link(s: &str) { +fn du_hard_link(s: &str) { assert_eq!(s, "8\tsubdir/links\n"); } #[cfg(target_os = "freebsd")] -fn _du_hard_link(s: &str) { +fn du_hard_link(s: &str) { assert_eq!(s, "16\tsubdir/links\n"); } #[cfg(all( @@ -275,7 +275,7 @@ fn _du_hard_link(s: &str) { not(target_os = "windows"), not(target_os = "freebsd") ))] -fn _du_hard_link(s: &str) { +fn du_hard_link(s: &str) { // MS-WSL linux has altered expected output if uucore::os::is_wsl_1() { assert_eq!(s, "8\tsubdir/links\n"); @@ -299,19 +299,19 @@ fn test_du_d_flag() { return; } } - _du_d_flag(result.stdout_str()); + du_d_flag(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_d_flag(s: &str) { +fn du_d_flag(s: &str) { assert_eq!(s, "20\t./subdir\n24\t.\n"); } #[cfg(target_os = "windows")] -fn _du_d_flag(s: &str) { +fn du_d_flag(s: &str) { assert_eq!(s, "8\t.\\subdir\n8\t.\n"); } #[cfg(target_os = "freebsd")] -fn _du_d_flag(s: &str) { +fn du_d_flag(s: &str) { assert_eq!(s, "36\t./subdir\n44\t.\n"); } #[cfg(all( @@ -319,7 +319,7 @@ fn _du_d_flag(s: &str) { not(target_os = "windows"), not(target_os = "freebsd") ))] -fn _du_d_flag(s: &str) { +fn du_d_flag(s: &str) { // MS-WSL linux has altered expected output if uucore::os::is_wsl_1() { assert_eq!(s, "8\t./subdir\n8\t.\n"); @@ -348,7 +348,7 @@ fn test_du_dereference() { } } - _du_dereference(result.stdout_str()); + du_dereference(result.stdout_str()); } #[cfg(not(windows))] @@ -376,15 +376,15 @@ fn test_du_dereference_args() { } #[cfg(target_vendor = "apple")] -fn _du_dereference(s: &str) { +fn du_dereference(s: &str) { assert_eq!(s, "4\tsubdir/links/deeper_dir\n16\tsubdir/links\n"); } #[cfg(target_os = "windows")] -fn _du_dereference(s: &str) { +fn du_dereference(s: &str) { assert_eq!(s, "0\tsubdir/links\\deeper_dir\n8\tsubdir/links\n"); } #[cfg(target_os = "freebsd")] -fn _du_dereference(s: &str) { +fn du_dereference(s: &str) { assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n"); } #[cfg(all( @@ -392,7 +392,7 @@ fn _du_dereference(s: &str) { not(target_os = "windows"), not(target_os = "freebsd") ))] -fn _du_dereference(s: &str) { +fn du_dereference(s: &str) { // MS-WSL linux has altered expected output if uucore::os::is_wsl_1() { assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n"); @@ -454,20 +454,15 @@ fn test_du_inodes_basic() { let ts = TestScenario::new(util_name!()); let result = ts.ucmd().arg("--inodes").succeeds(); - #[cfg(any(target_os = "linux", target_os = "android"))] + #[cfg(not(target_os = "windows"))] { let result_reference = unwrap_or_return!(expected_result(&ts, &["--inodes"])); assert_eq!(result.stdout_str(), result_reference.stdout_str()); } - #[cfg(not(any(target_os = "linux", target_os = "android")))] - _du_inodes_basic(result.stdout_str()); -} - -#[cfg(target_os = "windows")] -fn _du_inodes_basic(s: &str) { + #[cfg(target_os = "windows")] assert_eq!( - s, + result.stdout_str(), concat!( "2\t.\\subdir\\deeper\\deeper_dir\n", "4\t.\\subdir\\deeper\n", @@ -478,20 +473,6 @@ fn _du_inodes_basic(s: &str) { ); } -#[cfg(not(target_os = "windows"))] -fn _du_inodes_basic(s: &str) { - assert_eq!( - s, - concat!( - "2\t./subdir/deeper/deeper_dir\n", - "4\t./subdir/deeper\n", - "3\t./subdir/links\n", - "8\t./subdir\n", - "11\t.\n", - ) - ); -} - #[test] fn test_du_inodes() { let ts = TestScenario::new(util_name!()); @@ -706,8 +687,10 @@ fn test_du_no_permission() { return; } } - - _du_no_permission(result.stdout_str()); + #[cfg(not(target_vendor = "apple"))] + assert_eq!(result.stdout_str(), "4\tsubdir/links\n"); + #[cfg(target_vendor = "apple")] + assert_eq!(result.stdout_str(), "0\tsubdir/links\n"); } #[cfg(not(target_os = "windows"))] @@ -725,15 +708,6 @@ fn test_du_no_exec_permission() { result.stderr_contains("du: cannot access 'd/no-x/y': Permission denied"); } -#[cfg(target_vendor = "apple")] -fn _du_no_permission(s: &str) { - assert_eq!(s, "0\tsubdir/links\n"); -} -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] -fn _du_no_permission(s: &str) { - assert_eq!(s, "4\tsubdir/links\n"); -} - #[test] #[cfg(not(target_os = "openbsd"))] fn test_du_one_file_system() { @@ -749,7 +723,7 @@ fn test_du_one_file_system() { return; } } - _du_basics_subdir(result.stdout_str()); + du_basics_subdir(result.stdout_str()); } #[test] From ffbc682b92be7b53f2794fb846f6952235c5012e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 29 Nov 2024 09:19:30 +0100 Subject: [PATCH 066/351] Use the other comment syntax as it is not related Co-authored-by: Daniel Hofstetter --- src/uu/dd/src/numbers.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/dd/src/numbers.rs b/src/uu/dd/src/numbers.rs index c29668c891a..d0ee2d90b89 100644 --- a/src/uu/dd/src/numbers.rs +++ b/src/uu/dd/src/numbers.rs @@ -3,7 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Functions for formatting a number as a magnitude and a unit suffix. +//! Functions for formatting a number as a magnitude and a unit suffix. + /// The first ten powers of 1024. const IEC_BASES: [u128; 10] = [ 1, From 95bd50e09a8ced6b49eeafd66d436bbbdbf10742 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Nov 2024 13:12:55 +0100 Subject: [PATCH 067/351] du: deduplicate the input Should fix: tests/du/hard-link.sh --- src/uu/du/src/du.rs | 24 ++++++++++------- tests/by-util/test_du.rs | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e7b00838e30..2d36679f044 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -649,6 +649,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let summarize = matches.get_flag(options::SUMMARIZE); + let count_links = matches.get_flag(options::COUNT_LINKS); + let max_depth = parse_depth( matches .get_one::(options::MAX_DEPTH) @@ -669,15 +671,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } read_files_from(file_from)? - } else { - match matches.get_one::(options::FILE) { - Some(_) => matches - .get_many::(options::FILE) - .unwrap() - .map(PathBuf::from) - .collect(), - None => vec![PathBuf::from(".")], + } else if let Some(files) = matches.get_many::(options::FILE) { + let files = files.map(PathBuf::from); + if count_links { + files.collect() + } else { + // Deduplicate while preserving order + let mut seen = std::collections::HashSet::new(); + files + .filter(|path| seen.insert(path.clone())) + .collect::>() } + } else { + vec![PathBuf::from(".")] }; let time = matches.contains_id(options::TIME).then(|| { @@ -719,7 +725,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { Deref::None }, - count_links: matches.get_flag(options::COUNT_LINKS), + count_links, verbose: matches.get_flag(options::VERBOSE), excludes: build_exclude_patterns(&matches)?, }; diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 84e3b5050b4..ecbf58b117b 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1194,3 +1194,61 @@ fn test_human_size() { .succeeds() .stdout_contains(format!("1.0K {dir}")); } + +#[cfg(not(target_os = "android"))] +#[test] +fn test_du_deduplicated_input_args() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("d"); + at.mkdir("d/d"); + at.touch("d/f"); + at.hard_link("d/f", "d/h"); + + let result = ts + .ucmd() + .arg("--inodes") + .arg("d") + .arg("d") + .arg("d") + .succeeds(); + result.no_stderr(); + + let result_seq: Vec = result + .stdout_str() + .lines() + .map(|x| x.parse().unwrap()) + .collect(); + #[cfg(windows)] + assert_eq!(result_seq, ["1\td\\d", "3\td"]); + #[cfg(not(windows))] + assert_eq!(result_seq, ["1\td/d", "3\td"]); +} + +#[cfg(not(target_os = "android"))] +#[test] +fn test_du_no_deduplicated_input_args() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("d"); + at.touch("d/d"); + + let result = ts + .ucmd() + .arg("--inodes") + .arg("-l") + .arg("d") + .arg("d") + .arg("d") + .succeeds(); + result.no_stderr(); + + let result_seq: Vec = result + .stdout_str() + .lines() + .map(|x| x.parse().unwrap()) + .collect(); + assert_eq!(result_seq, ["2\td", "2\td", "2\td"]); +} From 865da0caada8208eb2c956aa9d34c0fb7c67382b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 29 Nov 2024 10:47:14 +0100 Subject: [PATCH 068/351] uudoc,chcon: fix needless_lifetimes warnings --- src/bin/uudoc.rs | 2 +- src/uu/chcon/src/chcon.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index 8ea11ed4d66..f2c325e3259 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -172,7 +172,7 @@ struct MDWriter<'a, 'b> { markdown: Option, } -impl<'a, 'b> MDWriter<'a, 'b> { +impl MDWriter<'_, '_> { /// # Errors /// Returns an error if the writer fails. fn markdown(&mut self) -> io::Result<()> { diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 1a804bd3bbf..c8d1c401762 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -777,7 +777,7 @@ enum SELinuxSecurityContext<'t> { String(Option), } -impl<'t> SELinuxSecurityContext<'t> { +impl SELinuxSecurityContext<'_> { fn to_c_string(&self) -> Result>> { match self { Self::File(context) => context From 270525a02f49bbd7050929beceef014a382f6c4d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:14:38 +0000 Subject: [PATCH 069/351] chore(deps): update dawidd6/action-download-artifact action to v7 --- .github/workflows/CICD.yml | 4 ++-- .github/workflows/GnuTests.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 6c7b50995d1..1875c512deb 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -397,14 +397,14 @@ jobs: --arg multisize "$SIZE_MULTI" \ '{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json - name: Download the previous individual size result - uses: dawidd6/action-download-artifact@v6 + uses: dawidd6/action-download-artifact@v7 with: workflow: CICD.yml name: individual-size-result repo: uutils/coreutils path: dl - name: Download the previous size result - uses: dawidd6/action-download-artifact@v6 + uses: dawidd6/action-download-artifact@v7 with: workflow: CICD.yml name: size-result diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 113cb1e9745..b47b435962f 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -86,7 +86,7 @@ jobs: working-directory: ${{ steps.vars.outputs.path_GNU }} - name: Retrieve reference artifacts - uses: dawidd6/action-download-artifact@v6 + uses: dawidd6/action-download-artifact@v7 # ref: continue-on-error: true ## don't break the build for missing reference artifacts (may be expired or just not generated yet) with: From 4f7f85da4795fbaec133189e3226057011d98f91 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:45:33 +0000 Subject: [PATCH 070/351] fix(deps): update rust crate libc to v0.2.167 --- Cargo.lock | 4 ++-- fuzz/Cargo.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca91ec30935..c5e96cef2d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,9 +1305,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.166" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libloading" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index d5372d79b67..a300d8b65a5 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -416,9 +416,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.166" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libfuzzer-sys" From 1365f6c025b6154b78ef9c8f7eef36b4d198236d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:46:48 +0000 Subject: [PATCH 071/351] chore(deps): update rust crate terminal_size to v0.4.1 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5e96cef2d1..ef1704f0142 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2351,9 +2351,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix 0.38.40", "windows-sys 0.59.0", @@ -2984,7 +2984,7 @@ dependencies = [ "number_prefix", "once_cell", "selinux", - "terminal_size 0.4.0", + "terminal_size 0.4.1", "uucore", "uutils_term_grid", ] @@ -3751,7 +3751,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] From 0cae322dfa8db31937a041efd686c56f356ef7c2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 14 Nov 2024 21:37:20 +0100 Subject: [PATCH 072/351] comm: generate an error if the input is a directory tested by tests/misc/read-errors --- src/uu/comm/src/comm.rs | 8 +++++--- tests/by-util/test_comm.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index cae405865e6..ae57b8bf8a0 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -6,9 +6,8 @@ // spell-checker:ignore (ToDO) delim mkdelim use std::cmp::Ordering; -use std::fs::File; +use std::fs::{metadata, File}; use std::io::{self, stdin, BufRead, BufReader, Stdin}; -use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; @@ -130,7 +129,10 @@ fn open_file(name: &str, line_ending: LineEnding) -> io::Result { if name == "-" { Ok(LineReader::new(Input::Stdin(stdin()), line_ending)) } else { - let f = File::open(Path::new(name))?; + if metadata(name)?.is_dir() { + return Err(io::Error::new(io::ErrorKind::Other, "Is a directory")); + } + let f = File::open(name)?; Ok(LineReader::new( Input::FileIn(BufReader::new(f)), line_ending, diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index 2dc385ef3f2..b62febf5047 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -292,3 +292,36 @@ fn test_no_such_file() { .fails() .stderr_only("comm: bogus_file_1: No such file or directory\n"); } + +#[test] +fn test_is_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + scene + .ucmd() + .args(&[".", "."]) + .fails() + .stderr_only("comm: .: Is a directory\n"); + + at.mkdir("dir"); + scene + .ucmd() + .args(&["dir", "."]) + .fails() + .stderr_only("comm: dir: Is a directory\n"); + + at.touch("file"); + scene + .ucmd() + .args(&[".", "file"]) + .fails() + .stderr_only("comm: .: Is a directory\n"); + + at.touch("file"); + scene + .ucmd() + .args(&["file", "."]) + .fails() + .stderr_only("comm: .: Is a directory\n"); +} From 2ad3c452303752e22b81d34b454515eca0b3eef7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 30 Nov 2024 10:38:46 +0100 Subject: [PATCH 073/351] tr: generate an error if the input is a directory (#6855) * tr: generate an error if the input is a directory tested by tests/misc/read-errors * tr: improve the test * tr: take the commentinto account --- src/uu/tr/src/operation.rs | 24 +++++++++++++++++------- src/uu/tr/src/tr.rs | 10 +++++----- tests/by-util/test_tr.rs | 9 +++++++++ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 035f0997207..6a1bf939126 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -23,7 +23,7 @@ use std::{ io::{BufRead, Write}, ops::Not, }; -use uucore::error::UError; +use uucore::error::{UError, UResult, USimpleError}; use uucore::show_warning; #[derive(Debug, Clone)] @@ -608,7 +608,7 @@ impl SymbolTranslator for SqueezeOperation { } } -pub fn translate_input(input: &mut R, output: &mut W, mut translator: T) +pub fn translate_input(input: &mut R, output: &mut W, mut translator: T) -> UResult<()> where T: SymbolTranslator, R: BufRead, @@ -616,15 +616,25 @@ where { let mut buf = Vec::new(); let mut output_buf = Vec::new(); + while let Ok(length) = input.read_until(b'\n', &mut buf) { if length == 0 { - break; - } else { - let filtered = buf.iter().filter_map(|c| translator.translate(*c)); - output_buf.extend(filtered); - output.write_all(&output_buf).unwrap(); + break; // EOF reached } + + let filtered = buf.iter().filter_map(|&c| translator.translate(c)); + output_buf.extend(filtered); + + if let Err(e) = output.write_all(&output_buf) { + return Err(USimpleError::new( + 1, + format!("{}: write error: {}", uucore::util_name(), e), + )); + } + buf.clear(); output_buf.clear(); } + + Ok(()) } diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 67998d26d4b..ff85002e71d 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -132,24 +132,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let delete_op = DeleteOperation::new(set1); let squeeze_op = SqueezeOperation::new(set2); let op = delete_op.chain(squeeze_op); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } else { let op = DeleteOperation::new(set1); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } } else if squeeze_flag { if sets_len < 2 { let op = SqueezeOperation::new(set1); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } else { let translate_op = TranslateOperation::new(set1, set2.clone())?; let squeeze_op = SqueezeOperation::new(set2); let op = translate_op.chain(squeeze_op); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } } else { let op = TranslateOperation::new(set1, set2)?; - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } Ok(()) } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 705f40834e5..f8fcafce3a3 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -13,6 +13,15 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_invalid_input() { + new_ucmd!() + .args(&["1", "1", "<", "."]) + .fails() + .code_is(1) + .stderr_contains("tr: extra operand '<'"); +} + #[test] fn test_to_upper() { new_ucmd!() From 6487347bc6d212edb72d332844b49701627e6641 Mon Sep 17 00:00:00 2001 From: Jesse Schalken Date: Mon, 28 Oct 2024 20:25:46 +1100 Subject: [PATCH 074/351] Reuse existing metadata instead of calling path.is_dir() again --- src/uu/du/src/du.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 2d36679f044..d4bec77ef89 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -171,7 +171,7 @@ impl Stat { Ok(Self { path: path.to_path_buf(), is_dir: metadata.is_dir(), - size: if path.is_dir() { 0 } else { metadata.len() }, + size: if metadata.is_dir() { 0 } else { metadata.len() }, blocks: metadata.blocks(), inodes: 1, inode: Some(file_info), @@ -189,7 +189,7 @@ impl Stat { Ok(Self { path: path.to_path_buf(), is_dir: metadata.is_dir(), - size: if path.is_dir() { 0 } else { metadata.len() }, + size: if metadata.is_dir() { 0 } else { metadata.len() }, blocks: size_on_disk / 1024 * 2, inodes: 1, inode: file_info, From 4f422c1a3a49f91722ee320f1c070dd3492fe593 Mon Sep 17 00:00:00 2001 From: Simone Ragusa Date: Sat, 21 Sep 2024 15:59:34 +0200 Subject: [PATCH 075/351] uucore: add alacritty to the list of terminals that support colors Any value of TERM with glob pattern `alacritty*` will be matched. Fixes #6722 --- src/uucore/src/lib/features/colors.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index f0573943133..f8cbc9ebf8b 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -13,6 +13,7 @@ /// restrict following config to systems with matching environment variables. pub static TERMS: &[&str] = &[ "Eterm", + "alacritty*", "ansi", "*color*", "con[0-9]*x[0-9]*", From f0b7d322d1e29e088708d959ee412b7781630184 Mon Sep 17 00:00:00 2001 From: Simone Ragusa Date: Sat, 21 Sep 2024 22:08:46 +0200 Subject: [PATCH 076/351] dircolors: patch test fixture to include alacritty support --- src/uu/dircolors/README.md | 8 +++++++- src/uu/dircolors/alacritty-supports-colors.patch | 12 ++++++++++++ tests/fixtures/dircolors/internal.expected | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/uu/dircolors/alacritty-supports-colors.patch diff --git a/src/uu/dircolors/README.md b/src/uu/dircolors/README.md index ce8aa965f93..f4ec5d675a3 100644 --- a/src/uu/dircolors/README.md +++ b/src/uu/dircolors/README.md @@ -9,10 +9,16 @@ dircolors -b > /PATH_TO_COREUTILS/tests/fixtures/dircolors/bash_def.expected dircolors -c > /PATH_TO_COREUTILS/tests/fixtures/dircolors/csh_def.expected ``` +Apply the patches to include more terminals that support colors: + +```shell +git apply /PATH_TO_COREUTILS/src/uu/dircolors/alacritty-supports-colors.patch +``` + Run the tests: ```shell cargo test --features "dircolors" --no-default-features ``` -Edit `/PATH_TO_COREUTILS/src/uu/dircolors/src/colors.rs` until the tests pass. +Edit `/PATH_TO_COREUTILS/src/uu/dircolors/src/dircolors.rs` until the tests pass. diff --git a/src/uu/dircolors/alacritty-supports-colors.patch b/src/uu/dircolors/alacritty-supports-colors.patch new file mode 100644 index 00000000000..c6f022423bc --- /dev/null +++ b/src/uu/dircolors/alacritty-supports-colors.patch @@ -0,0 +1,12 @@ +diff --git a/tests/fixtures/dircolors/internal.expected b/tests/fixtures/dircolors/internal.expected +index e151973f2..01dae4273 100644 +--- a/tests/fixtures/dircolors/internal.expected ++++ b/tests/fixtures/dircolors/internal.expected +@@ -7,6 +7,7 @@ + # restrict following config to systems with matching environment variables. + COLORTERM ?* + TERM Eterm ++TERM alacritty* + TERM ansi + TERM *color* + TERM con[0-9]*x[0-9]* diff --git a/tests/fixtures/dircolors/internal.expected b/tests/fixtures/dircolors/internal.expected index e151973f200..01dae42739f 100644 --- a/tests/fixtures/dircolors/internal.expected +++ b/tests/fixtures/dircolors/internal.expected @@ -7,6 +7,7 @@ # restrict following config to systems with matching environment variables. COLORTERM ?* TERM Eterm +TERM alacritty* TERM ansi TERM *color* TERM con[0-9]*x[0-9]* From 2fef5be8f757ebe6cd3e1b9d9a2b791addcbdf64 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 13 May 2024 19:39:45 +0200 Subject: [PATCH 077/351] more: reduce memory usage a bit --- src/uu/more/src/more.rs | 47 ++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 0b8c838f29d..f397d5273c5 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -447,7 +447,7 @@ struct Pager<'a> { upper_mark: usize, // The number of rows that fit on the screen content_rows: u16, - lines: Vec, + lines: Vec<&'a str>, next_file: Option<&'a str>, line_count: usize, silent: bool, @@ -456,7 +456,7 @@ struct Pager<'a> { } impl<'a> Pager<'a> { - fn new(rows: u16, lines: Vec, next_file: Option<&'a str>, options: &Options) -> Self { + fn new(rows: u16, lines: Vec<&'a str>, next_file: Option<&'a str>, options: &Options) -> Self { let line_count = lines.len(); Self { upper_mark: options.from_line, @@ -608,7 +608,7 @@ impl<'a> Pager<'a> { } } -fn search_pattern_in_file(lines: &[String], pattern: &Option) -> Option { +fn search_pattern_in_file(lines: &[&str], pattern: &Option) -> Option { let pattern = pattern.clone().unwrap_or_default(); if lines.is_empty() || pattern.is_empty() { return None; @@ -630,8 +630,10 @@ fn paging_add_back_message(options: &Options, stdout: &mut std::io::Stdout) -> U } // Break the lines on the cols of the terminal -fn break_buff(buff: &str, cols: usize) -> Vec { - let mut lines = Vec::with_capacity(buff.lines().count()); +fn break_buff(buff: &str, cols: usize) -> Vec<&str> { + // We _could_ do a precise with_capacity here, but that would require scanning the + // whole buffer. Just guess a value instead. + let mut lines = Vec::with_capacity(2048); for l in buff.lines() { lines.append(&mut break_line(l, cols)); @@ -639,11 +641,11 @@ fn break_buff(buff: &str, cols: usize) -> Vec { lines } -fn break_line(line: &str, cols: usize) -> Vec { +fn break_line(line: &str, cols: usize) -> Vec<&str> { let width = UnicodeWidthStr::width(line); let mut lines = Vec::new(); if width < cols { - lines.push(line.to_string()); + lines.push(line); return lines; } @@ -655,14 +657,14 @@ fn break_line(line: &str, cols: usize) -> Vec { total_width += width; if total_width > cols { - lines.push(line[last_index..index].to_string()); + lines.push(&line[last_index..index]); last_index = index; total_width = width; } } if last_index != line.len() { - lines.push(line[last_index..].to_string()); + lines.push(&line[last_index..]); } lines } @@ -727,29 +729,16 @@ mod tests { #[test] fn test_search_pattern_empty_pattern() { - let lines = vec![String::from("line1"), String::from("line2")]; + let lines = vec!["line1", "line2"]; let pattern = None; assert_eq!(None, search_pattern_in_file(&lines, &pattern)); } #[test] fn test_search_pattern_found_pattern() { - let lines = vec![ - String::from("line1"), - String::from("line2"), - String::from("pattern"), - ]; - let lines2 = vec![ - String::from("line1"), - String::from("line2"), - String::from("pattern"), - String::from("pattern2"), - ]; - let lines3 = vec![ - String::from("line1"), - String::from("line2"), - String::from("other_pattern"), - ]; + let lines = vec!["line1", "line2", "pattern"]; + let lines2 = vec!["line1", "line2", "pattern", "pattern2"]; + let lines3 = vec!["line1", "line2", "other_pattern"]; let pattern = Some(String::from("pattern")); assert_eq!(2, search_pattern_in_file(&lines, &pattern).unwrap()); assert_eq!(2, search_pattern_in_file(&lines2, &pattern).unwrap()); @@ -758,11 +747,7 @@ mod tests { #[test] fn test_search_pattern_not_found_pattern() { - let lines = vec![ - String::from("line1"), - String::from("line2"), - String::from("something"), - ]; + let lines = vec!["line1", "line2", "something"]; let pattern = Some(String::from("pattern")); assert_eq!(None, search_pattern_in_file(&lines, &pattern)); } From b2510feb4663a284843e6f5f794ed015b0410dd7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 14 May 2024 12:30:48 +0200 Subject: [PATCH 078/351] clean up use of u16s and patterns --- src/uu/more/src/more.rs | 53 +++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index f397d5273c5..987ed4a58d9 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -98,10 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { println!("{panic_info}"); })); - let matches = match uu_app().try_get_matches_from(args) { - Ok(m) => m, - Err(e) => return Err(e.into()), - }; + let matches = uu_app().try_get_matches_from(args)?; let mut options = Options::from(&matches); @@ -308,12 +305,12 @@ fn more( rows = number; } - let lines = break_buff(buff, usize::from(cols)); + let lines = break_buff(buff, cols as usize); let mut pager = Pager::new(rows, lines, next_file, options); - if options.pattern.is_some() { - match search_pattern_in_file(&pager.lines, &options.pattern) { + if let Some(pat) = options.pattern.as_ref() { + match search_pattern_in_file(&pager.lines, pat) { Some(number) => pager.upper_mark = number, None => { execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; @@ -446,7 +443,7 @@ struct Pager<'a> { // The current line at the top of the screen upper_mark: usize, // The number of rows that fit on the screen - content_rows: u16, + content_rows: usize, lines: Vec<&'a str>, next_file: Option<&'a str>, line_count: usize, @@ -460,7 +457,7 @@ impl<'a> Pager<'a> { let line_count = lines.len(); Self { upper_mark: options.from_line, - content_rows: rows.saturating_sub(1), + content_rows: rows.saturating_sub(1) as usize, lines, next_file, line_count, @@ -481,10 +478,10 @@ impl<'a> Pager<'a> { // the upper mark must not grow past top of the screen at the end of the open file. if self .upper_mark - .saturating_add(self.content_rows as usize * 2) - .ge(&self.line_count) + .saturating_add(self.content_rows * 2) + >= self.line_count { - self.upper_mark = self.line_count - self.content_rows as usize; + self.upper_mark = self.line_count - self.content_rows; return; } @@ -492,10 +489,9 @@ impl<'a> Pager<'a> { } fn page_up(&mut self) { - let content_row_usize: usize = self.content_rows.into(); self.upper_mark = self .upper_mark - .saturating_sub(content_row_usize.saturating_add(self.line_squeezed)); + .saturating_sub(self.content_rows.saturating_add(self.line_squeezed)); if self.squeeze { let iter = self.lines.iter().take(self.upper_mark).rev(); @@ -520,7 +516,7 @@ impl<'a> Pager<'a> { // TODO: Deal with column size changes. fn page_resize(&mut self, _: u16, row: u16, option_line: Option) { if option_line.is_none() { - self.content_rows = row.saturating_sub(1); + self.content_rows = row.saturating_sub(1) as usize; }; } @@ -541,7 +537,7 @@ impl<'a> Pager<'a> { let mut displayed_lines = Vec::new(); let mut iter = self.lines.iter().skip(self.upper_mark); - while displayed_lines.len() < self.content_rows as usize { + while displayed_lines.len() < self.content_rows { match iter.next() { Some(line) => { if self.squeeze { @@ -608,13 +604,12 @@ impl<'a> Pager<'a> { } } -fn search_pattern_in_file(lines: &[&str], pattern: &Option) -> Option { - let pattern = pattern.clone().unwrap_or_default(); +fn search_pattern_in_file(lines: &[&str], pattern: &str) -> Option { if lines.is_empty() || pattern.is_empty() { return None; } for (line_number, line) in lines.iter().enumerate() { - if line.contains(pattern.as_str()) { + if line.contains(pattern) { return Some(line_number); } } @@ -723,15 +718,15 @@ mod tests { #[test] fn test_search_pattern_empty_lines() { let lines = vec![]; - let pattern = Some(String::from("pattern")); - assert_eq!(None, search_pattern_in_file(&lines, &pattern)); + let pattern = "pattern"; + assert_eq!(None, search_pattern_in_file(&lines, pattern)); } #[test] fn test_search_pattern_empty_pattern() { let lines = vec!["line1", "line2"]; - let pattern = None; - assert_eq!(None, search_pattern_in_file(&lines, &pattern)); + let pattern = ""; + assert_eq!(None, search_pattern_in_file(&lines, pattern)); } #[test] @@ -739,16 +734,16 @@ mod tests { let lines = vec!["line1", "line2", "pattern"]; let lines2 = vec!["line1", "line2", "pattern", "pattern2"]; let lines3 = vec!["line1", "line2", "other_pattern"]; - let pattern = Some(String::from("pattern")); - assert_eq!(2, search_pattern_in_file(&lines, &pattern).unwrap()); - assert_eq!(2, search_pattern_in_file(&lines2, &pattern).unwrap()); - assert_eq!(2, search_pattern_in_file(&lines3, &pattern).unwrap()); + let pattern = "pattern"; + assert_eq!(2, search_pattern_in_file(&lines, pattern).unwrap()); + assert_eq!(2, search_pattern_in_file(&lines2, pattern).unwrap()); + assert_eq!(2, search_pattern_in_file(&lines3, pattern).unwrap()); } #[test] fn test_search_pattern_not_found_pattern() { let lines = vec!["line1", "line2", "something"]; - let pattern = Some(String::from("pattern")); - assert_eq!(None, search_pattern_in_file(&lines, &pattern)); + let pattern = "pattern"; + assert_eq!(None, search_pattern_in_file(&lines, pattern)); } } From 6b32c30d57cf60a09179bc5b294f430a864804a2 Mon Sep 17 00:00:00 2001 From: hamflx Date: Sun, 24 Mar 2024 17:15:34 +0800 Subject: [PATCH 079/351] mv: fix invalid numbered backup path --- src/uucore/src/lib/features/backup_control.rs | 58 ++++++++++++++++--- tests/by-util/test_mv.rs | 24 ++++++++ 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/src/uucore/src/lib/features/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs index 9086acb197e..4b4f7aa93c6 100644 --- a/src/uucore/src/lib/features/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -421,25 +421,29 @@ pub fn get_backup_path( } fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf { - let mut p = path.to_string_lossy().into_owned(); - p.push_str(suffix); - PathBuf::from(p) + let mut file_name = path.file_name().unwrap_or_default().to_os_string(); + file_name.push(suffix); + path.with_file_name(file_name) } fn numbered_backup_path(path: &Path) -> PathBuf { + let file_name = path.file_name().unwrap_or_default(); for i in 1_u64.. { - let path_str = &format!("{}.~{}~", path.to_string_lossy(), i); - let path = Path::new(path_str); + let mut numbered_file_name = file_name.to_os_string(); + numbered_file_name.push(format!(".~{}~", i)); + let path = path.with_file_name(numbered_file_name); if !path.exists() { - return path.to_path_buf(); + return path; } } panic!("cannot create backup") } fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { - let test_path_str = &format!("{}.~1~", path.to_string_lossy()); - let test_path = Path::new(test_path_str); + let file_name = path.file_name().unwrap_or_default(); + let mut numbered_file_name = file_name.to_os_string(); + numbered_file_name.push(".~1~"); + let test_path = path.with_file_name(numbered_file_name); if test_path.exists() { numbered_backup_path(path) } else { @@ -660,6 +664,44 @@ mod tests { let result = determine_backup_suffix(&matches); assert_eq!(result, "-v"); } + + #[test] + fn test_numbered_backup_path() { + assert_eq!(numbered_backup_path(&Path::new("")), PathBuf::from(".~1~")); + assert_eq!( + numbered_backup_path(&Path::new("/")), + PathBuf::from("/.~1~") + ); + assert_eq!( + numbered_backup_path(&Path::new("/hello/world")), + PathBuf::from("/hello/world.~1~") + ); + assert_eq!( + numbered_backup_path(&Path::new("/hello/world/")), + PathBuf::from("/hello/world.~1~") + ); + } + + #[test] + fn test_simple_backup_path() { + assert_eq!( + simple_backup_path(&Path::new(""), ".bak"), + PathBuf::from(".bak") + ); + assert_eq!( + simple_backup_path(&Path::new("/"), ".bak"), + PathBuf::from("/.bak") + ); + assert_eq!( + simple_backup_path(&Path::new("/hello/world"), ".bak"), + PathBuf::from("/hello/world.bak") + ); + assert_eq!( + simple_backup_path(&Path::new("/hello/world/"), ".bak"), + PathBuf::from("/hello/world.bak") + ); + } + #[test] fn test_source_is_target_backup() { let source = Path::new("data.txt.bak"); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 6ab989ee453..ac64fae7eb7 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -571,6 +571,30 @@ fn test_mv_simple_backup() { assert!(at.file_exists(format!("{file_b}~"))); } +#[test] +fn test_mv_simple_backup_for_directory() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir_a = "test_mv_simple_backup_dir_a"; + let dir_b = "test_mv_simple_backup_dir_b"; + + at.mkdir(dir_a); + at.mkdir(dir_b); + at.touch(format!("{dir_a}/file_a")); + at.touch(format!("{dir_b}/file_b")); + ucmd.arg("-T") + .arg("-b") + .arg(dir_a) + .arg(dir_b) + .succeeds() + .no_stderr(); + + assert!(!at.dir_exists(dir_a)); + assert!(at.dir_exists(dir_b)); + assert!(at.dir_exists(&format!("{dir_b}~"))); + assert!(at.file_exists(format!("{dir_b}/file_a"))); + assert!(at.file_exists(format!("{dir_b}~/file_b"))); +} + #[test] fn test_mv_simple_backup_with_file_extension() { let (at, mut ucmd) = at_and_ucmd!(); From 2e4b7c8cb70f81e42f95bf89a525d1c5d9c19765 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 5 Jan 2024 11:10:37 +0100 Subject: [PATCH 080/351] mv: show "same file" error for "mv d/f d" --- src/uu/mv/src/mv.rs | 8 ++++++++ tests/by-util/test_mv.rs | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 9d8452b1ec7..20a22043cbd 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -314,6 +314,7 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> ) .into()); } + if source.symlink_metadata().is_err() { return Err(if path_ends_with_terminator(source) { MvError::CannotStatNotADirectory(source.quote().to_string()).into() @@ -336,6 +337,13 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> } } + if source.parent() == Some(target) { + return Err( + // use source twice to match GNU's error message + MvError::SameFile(source.quote().to_string(), source.quote().to_string()).into(), + ); + } + let target_is_dir = target.is_dir(); let source_is_dir = source.is_dir(); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 6ab989ee453..562e24754c8 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -402,7 +402,23 @@ fn test_mv_same_file() { ucmd.arg(file_a) .arg(file_a) .fails() - .stderr_is(format!("mv: '{file_a}' and '{file_a}' are the same file\n",)); + .stderr_is(format!("mv: '{file_a}' and '{file_a}' are the same file\n")); +} + +#[test] +fn test_mv_file_to_same_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "a"; + let dir = "dir"; + let path = &format!("{dir}/{file}"); + + at.mkdir(dir); + at.touch(path); + + ucmd.arg(path) + .arg(dir) + .fails() + .stderr_is(format!("mv: '{path}' and '{path}' are the same file\n")); } #[test] From 2d81463399c3b0b7afb08f6dfa12d608437ec91a Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Mon, 2 Dec 2024 10:50:48 -0400 Subject: [PATCH 081/351] Make EscapedChar and friends pub --- src/uucore/src/lib/features/format/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 6d7a2ee3079..24dd1daaad7 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -48,7 +48,7 @@ use std::{ use crate::error::UError; -use self::{ +pub use self::{ escape::{parse_escape_code, EscapedChar}, num_format::Formatter, }; From 2799b288e3791485456d291dc80641801732e2e8 Mon Sep 17 00:00:00 2001 From: Julian <61988360+just-an-engineer@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:04:36 -0500 Subject: [PATCH 082/351] tail: fix issue #6543 (--pid when reading from stdin) (#6582) --------- Co-authored-by: just-an-engineer Co-authored-by: Sylvestre Ledru --- src/uu/tail/src/tail.rs | 12 ++++---- tests/by-util/test_tail.rs | 59 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index edac4b151cb..a48da6b315e 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -65,13 +65,15 @@ fn uu_tail(settings: &Settings) -> UResult<()> { // Add `path` and `reader` to `files` map if `--follow` is selected. for input in &settings.inputs.clone() { match input.kind() { - InputKind::File(path) if cfg!(not(unix)) || path != &PathBuf::from(text::DEV_STDIN) => { - tail_file(settings, &mut printer, input, path, &mut observer, 0)?; + InputKind::Stdin => { + tail_stdin(settings, &mut printer, input, &mut observer)?; } - // File points to /dev/stdin here - InputKind::File(_) | InputKind::Stdin => { + InputKind::File(path) if cfg!(unix) && path == &PathBuf::from(text::DEV_STDIN) => { tail_stdin(settings, &mut printer, input, &mut observer)?; } + InputKind::File(path) => { + tail_file(settings, &mut printer, input, path, &mut observer, 0)?; + } } } @@ -85,7 +87,7 @@ fn uu_tail(settings: &Settings) -> UResult<()> { the input file is not a FIFO, pipe, or regular file, it is unspecified whether or not the -f option shall be ignored. */ - if !settings.has_only_stdin() { + if !settings.has_only_stdin() || settings.pid != 0 { follow::follow(observer, settings)?; } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 4c7c52c7c18..885e50ad3c0 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -6,6 +6,7 @@ // spell-checker:ignore (ToDO) abcdefghijklmnopqrstuvwxyz efghijklmnopqrstuvwxyz vwxyz emptyfile file siette ocho nueve diez MULT // spell-checker:ignore (libs) kqueue // spell-checker:ignore (jargon) tailable untailable datasame runneradmin tmpi +// spell-checker:ignore (cmd) taskkill #![allow( clippy::unicode_not_nfc, clippy::cast_lossless, @@ -4822,3 +4823,61 @@ fn test_obsolete_encoding_windows() { .stderr_is("tail: bad argument encoding: '-�b'\n") .code_is(1); } + +#[test] +#[cfg(not(target_vendor = "apple"))] // FIXME: for currently not working platforms +fn test_following_with_pid() { + use std::process::Command; + + let ts = TestScenario::new(util_name!()); + + #[cfg(not(windows))] + let mut sleep_command = Command::new("sleep") + .arg("999d") + .spawn() + .expect("failed to start sleep command"); + #[cfg(windows)] + let mut sleep_command = Command::new("powershell") + .arg("-Command") + .arg("Start-Sleep -Seconds 999") + .spawn() + .expect("failed to start sleep command"); + + let sleep_pid = sleep_command.id(); + + let at = &ts.fixtures; + at.touch("f"); + // when -f is specified, tail should die after + // the pid from --pid also dies + let mut child = ts + .ucmd() + .args(&[ + "--pid", + &sleep_pid.to_string(), + "-f", + at.plus("f").to_str().unwrap(), + ]) + .stderr_to_stdout() + .run_no_wait(); + child.make_assertion_with_delay(2000).is_alive(); + + #[cfg(not(windows))] + Command::new("kill") + .arg("-9") + .arg(sleep_pid.to_string()) + .output() + .expect("failed to kill sleep command"); + #[cfg(windows)] + Command::new("taskkill") + .arg("/PID") + .arg(sleep_pid.to_string()) + .arg("/F") + .output() + .expect("failed to kill sleep command"); + + let _ = sleep_command.wait(); + + child.make_assertion_with_delay(2000).is_not_alive(); + + child.kill(); +} From 9877085702d359dd04cade165231fc2fce64178e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Dec 2024 20:39:39 +0100 Subject: [PATCH 083/351] Revert "mv: show "same file" error for `mv d/f d`" --- src/uu/mv/src/mv.rs | 8 -------- tests/by-util/test_mv.rs | 18 +----------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 20a22043cbd..9d8452b1ec7 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -314,7 +314,6 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> ) .into()); } - if source.symlink_metadata().is_err() { return Err(if path_ends_with_terminator(source) { MvError::CannotStatNotADirectory(source.quote().to_string()).into() @@ -337,13 +336,6 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> } } - if source.parent() == Some(target) { - return Err( - // use source twice to match GNU's error message - MvError::SameFile(source.quote().to_string(), source.quote().to_string()).into(), - ); - } - let target_is_dir = target.is_dir(); let source_is_dir = source.is_dir(); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 562e24754c8..6ab989ee453 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -402,23 +402,7 @@ fn test_mv_same_file() { ucmd.arg(file_a) .arg(file_a) .fails() - .stderr_is(format!("mv: '{file_a}' and '{file_a}' are the same file\n")); -} - -#[test] -fn test_mv_file_to_same_dir() { - let (at, mut ucmd) = at_and_ucmd!(); - let file = "a"; - let dir = "dir"; - let path = &format!("{dir}/{file}"); - - at.mkdir(dir); - at.touch(path); - - ucmd.arg(path) - .arg(dir) - .fails() - .stderr_is(format!("mv: '{path}' and '{path}' are the same file\n")); + .stderr_is(format!("mv: '{file_a}' and '{file_a}' are the same file\n",)); } #[test] From abfedc3431aae1a2037b075c0c6841089ee9695d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:09:49 +0000 Subject: [PATCH 084/351] chore(deps): update rust crate time to v0.3.37 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef1704f0142..41b1a1224b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2413,9 +2413,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -2436,9 +2436,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", From 316f5f6e846b37c7eecf160f85274d8bfb30cd51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:48:09 +0000 Subject: [PATCH 085/351] chore(deps): update rust crate thiserror to v2.0.4 --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41b1a1224b9..e58ac54d279 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2382,11 +2382,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.4", ] [[package]] @@ -2402,9 +2402,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" dependencies = [ "proc-macro2", "quote", @@ -2610,7 +2610,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.3", + "thiserror 2.0.4", "uucore", ] @@ -2622,7 +2622,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.3", + "thiserror 2.0.4", "uucore", ] @@ -2699,7 +2699,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.3", + "thiserror 2.0.4", "uucore", ] @@ -3215,7 +3215,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.3", + "thiserror 2.0.4", "uucore", ] @@ -3494,7 +3494,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.3", + "thiserror 2.0.4", "utmp-classic", "uucore", ] @@ -3525,7 +3525,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.3", + "thiserror 2.0.4", "unicode-width 0.1.13", "uucore", ] @@ -3586,7 +3586,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.4", "time", "uucore_procs", "walkdir", From 3e4221a4615e800d6010d564984b93d2d9e6abfb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 2 Dec 2024 22:11:45 +0100 Subject: [PATCH 086/351] tr: generate an error for real if the input is a directory --- src/uu/tr/Cargo.toml | 2 +- src/uu/tr/src/tr.rs | 10 +++++++--- src/uucore/src/lib/features/fs.rs | 29 +++++++++++++++++++++++++++++ tests/by-util/test_tr.rs | 8 ++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 0787e427987..6378b7766ea 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -19,7 +19,7 @@ path = "src/tr.rs" [dependencies] nom = { workspace = true } clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "tr" diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index ff85002e71d..c226d218972 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -8,17 +8,17 @@ mod operation; mod unicode_table; +use crate::operation::DeleteOperation; use clap::{crate_version, value_parser, Arg, ArgAction, Command}; use operation::{ translate_input, Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation, }; use std::ffi::OsString; use std::io::{stdin, stdout, BufWriter}; -use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show}; - -use crate::operation::DeleteOperation; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::fs::is_stdin_directory; +use uucore::{format_usage, help_about, help_section, help_usage, os_str_as_bytes, show}; const ABOUT: &str = help_about!("tr.md"); const AFTER_HELP: &str = help_section!("after help", "tr.md"); @@ -126,6 +126,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { translating, )?; + if is_stdin_directory(&stdin) { + return Err(USimpleError::new(1, "read error: Is a directory")); + } + // '*_op' are the operations that need to be applied, in order. if delete_flag { if squeeze_flag { diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index e0c8ea79d3a..beb4d77a9aa 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -20,6 +20,7 @@ use std::ffi::{OsStr, OsString}; use std::fs; use std::fs::read_dir; use std::hash::Hash; +use std::io::Stdin; use std::io::{Error, ErrorKind, Result as IOResult}; #[cfg(unix)] use std::os::unix::{fs::MetadataExt, io::AsRawFd}; @@ -721,6 +722,34 @@ pub fn path_ends_with_terminator(path: &Path) -> bool { .map_or(false, |wide| wide == b'/'.into() || wide == b'\\'.into()) } +/// Checks if the standard input (stdin) is a directory. +/// +/// # Arguments +/// +/// * `stdin` - A reference to the standard input handle. +/// +/// # Returns +/// +/// * `bool` - Returns `true` if stdin is a directory, `false` otherwise. +pub fn is_stdin_directory(stdin: &Stdin) -> bool { + #[cfg(unix)] + { + use nix::sys::stat::fstat; + let mode = fstat(stdin.as_raw_fd()).unwrap().st_mode as mode_t; + has!(mode, S_IFDIR) + } + + #[cfg(windows)] + { + use std::os::windows::io::AsRawHandle; + let handle = stdin.as_raw_handle(); + if let Ok(metadata) = fs::metadata(format!("{}", handle as usize)) { + return metadata.is_dir(); + } + false + } +} + pub mod sane_blksize { #[cfg(not(target_os = "windows"))] diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index f8fcafce3a3..cd99f1c3adf 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -20,6 +20,14 @@ fn test_invalid_input() { .fails() .code_is(1) .stderr_contains("tr: extra operand '<'"); + #[cfg(unix)] + new_ucmd!() + .args(&["1", "1"]) + // will test "tr 1 1 < ." + .set_stdin(std::process::Stdio::from(std::fs::File::open(".").unwrap())) + .fails() + .code_is(1) + .stderr_contains("tr: read error: Is a directory"); } #[test] From c1f82b158cff27e76acb152732719bdb8a009121 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Dec 2024 22:45:00 +0100 Subject: [PATCH 087/351] fix rustfmt+clippy --- src/uu/more/src/more.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 987ed4a58d9..cb74e1176f5 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -469,23 +469,19 @@ impl<'a> Pager<'a> { fn should_close(&mut self) -> bool { self.upper_mark - .saturating_add(self.content_rows.into()) + .saturating_add(self.content_rows) .ge(&self.line_count) } fn page_down(&mut self) { // If the next page down position __after redraw__ is greater than the total line count, // the upper mark must not grow past top of the screen at the end of the open file. - if self - .upper_mark - .saturating_add(self.content_rows * 2) - >= self.line_count - { + if self.upper_mark.saturating_add(self.content_rows * 2) >= self.line_count { self.upper_mark = self.line_count - self.content_rows; return; } - self.upper_mark = self.upper_mark.saturating_add(self.content_rows.into()); + self.upper_mark = self.upper_mark.saturating_add(self.content_rows); } fn page_up(&mut self) { @@ -524,7 +520,7 @@ impl<'a> Pager<'a> { self.draw_lines(stdout); let lower_mark = self .line_count - .min(self.upper_mark.saturating_add(self.content_rows.into())); + .min(self.upper_mark.saturating_add(self.content_rows)); self.draw_prompt(stdout, lower_mark, wrong_key); stdout.flush().unwrap(); } From a16630fdedc472a7625a28ec2f380737d7902f86 Mon Sep 17 00:00:00 2001 From: Anirban Halder Date: Wed, 4 Dec 2024 03:21:03 +0530 Subject: [PATCH 088/351] rm: fix the usage of '/..' '/.' with -rf options fix the test tests/rm/r-4 --------- Co-authored-by: Julian Beltz Co-authored-by: Sylvestre Ledru --- Cargo.lock | 2 +- src/uu/rm/src/rm.rs | 88 ++++++++++++++++++++++++++++++++++++++-- tests/by-util/test_rm.rs | 62 ++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41b1a1224b9..60db20ca6f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3751,7 +3751,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index a89ba6db67f..ad9c942a8ee 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.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 (path) eacces inacc +// spell-checker:ignore (path) eacces inacc rm-r4 use clap::{builder::ValueParser, crate_version, parser::ValueSource, Arg, ArgAction, Command}; use std::collections::VecDeque; @@ -11,10 +11,15 @@ use std::ffi::{OsStr, OsString}; use std::fs::{self, File, Metadata}; use std::io::ErrorKind; use std::ops::BitOr; +#[cfg(not(windows))] +use std::os::unix::ffi::OsStrExt; +use std::path::MAIN_SEPARATOR; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show_error}; +use uucore::{ + format_usage, help_about, help_section, help_usage, os_str_as_bytes, prompt_yes, show_error, +}; use walkdir::{DirEntry, WalkDir}; #[derive(Eq, PartialEq, Clone, Copy)] @@ -290,6 +295,7 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool { for filename in files { let file = Path::new(filename); + had_err = match file.symlink_metadata() { Ok(metadata) => { if metadata.is_dir() { @@ -300,6 +306,7 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool { remove_file(file, options) } } + Err(_e) => { // TODO: actually print out the specific error // TODO: When the error is not about missing files @@ -326,6 +333,15 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool { fn handle_dir(path: &Path, options: &Options) -> bool { let mut had_err = false; + let path = clean_trailing_slashes(path); + if path_is_current_or_parent_directory(path) { + show_error!( + "refusing to remove '.' or '..' directory: skipping '{}'", + path.display() + ); + return true; + } + let is_root = path.has_root() && path.parent().is_none(); if options.recursive && (!is_root || !options.preserve_root) { if options.interactive != InteractiveMode::Always && !options.verbose { @@ -396,7 +412,11 @@ fn handle_dir(path: &Path, options: &Options) -> bool { } else if options.dir && (!is_root || !options.preserve_root) { had_err = remove_dir(path, options).bitor(had_err); } else if options.recursive { - show_error!("could not remove directory {}", path.quote()); + show_error!( + "it is dangerous to operate recursively on '{}'", + MAIN_SEPARATOR + ); + show_error!("use --no-preserve-root to override this failsafe"); had_err = true; } else { show_error!( @@ -559,6 +579,20 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata true } } +/// Checks if the path is referring to current or parent directory , if it is referring to current or any parent directory in the file tree e.g '/../..' , '../..' +fn path_is_current_or_parent_directory(path: &Path) -> bool { + let path_str = os_str_as_bytes(path.as_os_str()); + let dir_separator = MAIN_SEPARATOR as u8; + if let Ok(path_bytes) = path_str { + return path_bytes == ([b'.']) + || path_bytes == ([b'.', b'.']) + || path_bytes.ends_with(&[dir_separator, b'.']) + || path_bytes.ends_with(&[dir_separator, b'.', b'.']) + || path_bytes.ends_with(&[dir_separator, b'.', dir_separator]) + || path_bytes.ends_with(&[dir_separator, b'.', b'.', dir_separator]); + } + false +} // For windows we can use windows metadata trait and file attributes to see if a directory is readonly #[cfg(windows)] @@ -586,6 +620,40 @@ fn handle_writable_directory(path: &Path, options: &Options, metadata: &Metadata } } +/// Removes trailing slashes, for example 'd/../////' yield 'd/../' required to fix rm-r4 GNU test +fn clean_trailing_slashes(path: &Path) -> &Path { + let path_str = os_str_as_bytes(path.as_os_str()); + let dir_separator = MAIN_SEPARATOR as u8; + + if let Ok(path_bytes) = path_str { + let mut idx = if path_bytes.len() > 1 { + path_bytes.len() - 1 + } else { + return path; + }; + // Checks if element at the end is a '/' + if path_bytes[idx] == dir_separator { + for i in (1..path_bytes.len()).rev() { + // Will break at the start of the continuous sequence of '/', eg: "abc//////" , will break at + // "abc/", this will clean ////// to the root '/', so we have to be careful to not + // delete the root. + if path_bytes[i - 1] != dir_separator { + idx = i; + break; + } + } + #[cfg(unix)] + return Path::new(OsStr::from_bytes(&path_bytes[0..=idx])); + + #[cfg(not(unix))] + // Unwrapping is fine here as os_str_as_bytes() would return an error on non unix + // systems with non utf-8 characters and thus bypass the if let Ok branch + return Path::new(std::str::from_utf8(&path_bytes[0..=idx]).unwrap()); + } + } + path +} + fn prompt_descend(path: &Path) -> bool { prompt_yes!("descend into directory {}?", path.quote()) } @@ -611,3 +679,17 @@ fn is_symlink_dir(metadata: &Metadata) -> bool { metadata.file_type().is_symlink() && ((metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY) != 0) } + +mod tests { + + #[test] + // Testing whether path the `/////` collapses to `/` + fn test_collapsible_slash_path() { + use std::path::Path; + + use crate::clean_trailing_slashes; + let path = Path::new("/////"); + + assert_eq!(Path::new("/"), clean_trailing_slashes(path)); + } +} diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index f997688c818..b220926fec1 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -677,6 +677,68 @@ fn test_remove_inaccessible_dir() { assert!(!at.dir_exists(dir_1)); } +#[test] +#[cfg(not(windows))] +fn test_rm_current_or_parent_dir_rm4() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("d"); + + let answers = [ + "rm: refusing to remove '.' or '..' directory: skipping 'd/.'", + "rm: refusing to remove '.' or '..' directory: skipping 'd/./'", + "rm: refusing to remove '.' or '..' directory: skipping 'd/./'", + "rm: refusing to remove '.' or '..' directory: skipping 'd/..'", + "rm: refusing to remove '.' or '..' directory: skipping 'd/../'", + ]; + let std_err_str = ts + .ucmd() + .arg("-rf") + .arg("d/.") + .arg("d/./") + .arg("d/.////") + .arg("d/..") + .arg("d/../") + .fails() + .stderr_move_str(); + + for (idx, line) in std_err_str.lines().enumerate() { + assert_eq!(line, answers[idx]); + } +} + +#[test] +#[cfg(windows)] +fn test_rm_current_or_parent_dir_rm4_windows() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("d"); + + let answers = [ + "rm: refusing to remove '.' or '..' directory: skipping 'd\\.'", + "rm: refusing to remove '.' or '..' directory: skipping 'd\\.\\'", + "rm: refusing to remove '.' or '..' directory: skipping 'd\\.\\'", + "rm: refusing to remove '.' or '..' directory: skipping 'd\\..'", + "rm: refusing to remove '.' or '..' directory: skipping 'd\\..\\'", + ]; + let std_err_str = ts + .ucmd() + .arg("-rf") + .arg("d\\.") + .arg("d\\.\\") + .arg("d\\.\\\\\\\\") + .arg("d\\..") + .arg("d\\..\\") + .fails() + .stderr_move_str(); + + for (idx, line) in std_err_str.lines().enumerate() { + assert_eq!(line, answers[idx]); + } +} + #[test] #[cfg(not(windows))] fn test_fifo_removal() { From 5b087e96246f1d10c471f9219fa932bacb91ae83 Mon Sep 17 00:00:00 2001 From: andreistan26 <48967297+andreistan26@users.noreply.github.com> Date: Wed, 4 Dec 2024 01:04:55 +0200 Subject: [PATCH 089/351] Fix `cut` when lines dont end with specified delim (#5844) Print lines without delimiters only when they end with specified line terminator('\n' by default or `\0` if `-s`) Signed-off-by: Andrei Stan Co-authored-by: Sylvestre Ledru --- src/uu/cut/src/cut.rs | 5 +---- tests/by-util/test_cut.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index cd6eb22d30a..25bb7333010 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -213,11 +213,8 @@ fn cut_fields_implicit_out_delim( let mut print_delim = false; if delim_search.peek().is_none() { - if !only_delimited { + if !only_delimited && line[line.len() - 1] == newline_char { out.write_all(line)?; - if line[line.len() - 1] != newline_char { - out.write_all(&[newline_char])?; - } } return Ok(true); diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 86d3ddf0f3d..6b376b0ca0d 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -282,6 +282,15 @@ fn test_multiple() { assert_eq!(result.stderr_str(), ""); } +#[test] +fn test_newline_delimited() { + new_ucmd!() + .args(&["-f", "1", "-d", "\n"]) + .pipe_in("a:1\nb:") + .succeeds() + .stdout_only_bytes("a:1\n"); +} + #[test] fn test_multiple_mode_args() { for args in [ From a5867bdf34cdee2b71891d5922c5ed424c4e8f33 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Sun, 28 Jul 2024 12:08:36 -0500 Subject: [PATCH 090/351] install: create destination file with safer modes before copy --- src/uu/install/src/install.rs | 56 ++++++++++++++++++++++++++--------- tests/by-util/test_install.rs | 38 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 331a50f6741..8f5d381fe87 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -13,9 +13,12 @@ use filetime::{set_file_times, FileTime}; use std::error::Error; use std::fmt::{Debug, Display}; use std::fs; +#[cfg(not(unix))] use std::fs::File; use std::os::unix::fs::MetadataExt; #[cfg(unix)] +use std::os::unix::fs::OpenOptionsExt; +#[cfg(unix)] use std::os::unix::prelude::OsStrExt; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; use std::process; @@ -750,27 +753,52 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult> { fn copy_file(from: &Path, to: &Path) -> UResult<()> { // fs::copy fails if destination is a invalid symlink. // so lets just remove all existing files at destination before copy. - if let Err(e) = fs::remove_file(to) { - if e.kind() != std::io::ErrorKind::NotFound { - show_error!( - "Failed to remove existing file {}. Error: {:?}", - to.display(), - e - ); + let remove_destination = || { + if let Err(e) = fs::remove_file(to) { + if e.kind() != std::io::ErrorKind::NotFound { + show_error!( + "Failed to remove existing file {}. Error: {:?}", + to.display(), + e + ); + } } + }; + remove_destination(); + + // create the destination file first. Using safer mode on unix to avoid + // potential unsafe mode between time-of-creation and time-of-chmod. + #[cfg(unix)] + let creation = fs::OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(to); + #[cfg(not(unix))] + let creation = File::create(to); + + if let Err(e) = creation { + show_error!( + "Failed to create destination file {}. Error: {:?}", + to.display(), + e + ); + return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), e).into()); } - if from.as_os_str() == "/dev/null" { - /* workaround a limitation of fs::copy - * https://github.com/rust-lang/rust/issues/79390 - */ - if let Err(err) = File::create(to) { + // drop the file to close the fd of creation + drop(creation); + + /* workaround a limitation of fs::copy: skip copy if source is /dev/null + * https://github.com/rust-lang/rust/issues/79390 + */ + if from.as_os_str() != "/dev/null" { + if let Err(err) = fs::copy(from, to) { + remove_destination(); return Err( InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(), ); } - } else if let Err(err) = fs::copy(from, to) { - return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into()); } Ok(()) } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index f1e3302e138..6fa9cc62f47 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1717,3 +1717,41 @@ fn test_install_root_combined() { run_and_check(&["-Cv", "c", "d"], "d", 0, 0); run_and_check(&["-Cv", "c", "d"], "d", 0, 0); } + +#[cfg(all(unix, feature = "chmod"))] +#[test] +fn test_install_copy_failures() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + + let file1 = "source_file"; + let file2 = "target_file"; + + at.touch(file1); + scene.ccmd("chmod").arg("000").arg(file1).succeeds(); + + // if source file is not readable, it will raise a permission error. + // since we create the file with mode 0600 before `fs::copy`, if the + // copy failed, the file should be removed. + scene + .ucmd() + .arg(file1) + .arg(file2) + .arg("--mode=400") + .fails() + .stderr_contains("permission denied"); + assert!(!at.file_exists(file2)); + + // if source file is good to copy, it should succeed and set the + // destination file permissions accordingly + scene.ccmd("chmod").arg("644").arg(file1).succeeds(); + scene + .ucmd() + .arg(file1) + .arg(file2) + .arg("--mode=400") + .succeeds(); + assert!(at.file_exists(file2)); + assert_eq!(0o100_400_u32, at.metadata(file2).permissions().mode()); +} From caf08dd279114ba6d7355cd90ea2fdd9050336df Mon Sep 17 00:00:00 2001 From: Andrew Liebenow Date: Fri, 11 Oct 2024 10:04:21 -0500 Subject: [PATCH 091/351] basenc: ignore Interrupted errors Mirror behavior of `std::io::Read`'s `read_to_end` function ([link][1]): continue reading when errors with kind `std::io::ErrorKind::Interrupted` are encountered. Also: clean up a few other things. [1]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_end --- src/uu/base32/src/base_common.rs | 34 ++++++++++++++++---------------- src/uu/paste/src/paste.rs | 10 +++++----- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index f6b88f55157..130fe86264b 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -160,7 +160,7 @@ pub fn get_input(config: &Config) -> UResult> { } } -pub fn handle_input(input: &mut R, format: Format, config: Config) -> UResult<()> { +pub fn handle_input(input: &mut dyn Read, format: Format, config: Config) -> UResult<()> { let supports_fast_decode_and_encode = get_supports_fast_decode_and_encode(format); let supports_fast_decode_and_encode_ref = supports_fast_decode_and_encode.as_ref(); @@ -377,13 +377,13 @@ pub mod fast_encode { } fn write_to_output( - line_wrapping_option: &mut Option, + line_wrapping: &mut Option, encoded_buffer: &mut VecDeque, output: &mut dyn Write, is_cleanup: bool, ) -> io::Result<()> { // Write all data in `encoded_buffer` to `output` - if let &mut Some(ref mut li) = line_wrapping_option { + if let &mut Some(ref mut li) = line_wrapping { write_with_line_breaks(li, encoded_buffer, output, is_cleanup)?; } else { write_without_line_breaks(encoded_buffer, output, is_cleanup)?; @@ -393,9 +393,9 @@ pub mod fast_encode { } // End of helper functions - pub fn fast_encode( - input: &mut R, - mut output: W, + pub fn fast_encode( + input: &mut dyn Read, + output: &mut dyn Write, supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode, wrap: Option, ) -> UResult<()> { @@ -475,14 +475,14 @@ pub mod fast_encode { assert!(leftover_buffer.len() < encode_in_chunks_of_size); // Write all data in `encoded_buffer` to `output` - write_to_output(&mut line_wrapping, &mut encoded_buffer, &mut output, false)?; + write_to_output(&mut line_wrapping, &mut encoded_buffer, output, false)?; } Err(er) => { let kind = er.kind(); if kind == ErrorKind::Interrupted { - // TODO - // Retry reading? + // Retry reading + continue; } return Err(USimpleError::new(1, format_read_error(kind))); @@ -499,7 +499,7 @@ pub mod fast_encode { // Write all data in `encoded_buffer` to output // `is_cleanup` triggers special cleanup-only logic - write_to_output(&mut line_wrapping, &mut encoded_buffer, &mut output, true)?; + write_to_output(&mut line_wrapping, &mut encoded_buffer, output, true)?; } Ok(()) @@ -606,9 +606,9 @@ pub mod fast_decode { } // End of helper functions - pub fn fast_decode( - input: &mut R, - mut output: &mut W, + pub fn fast_decode( + input: &mut dyn Read, + output: &mut dyn Write, supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode, ignore_garbage: bool, ) -> UResult<()> { @@ -711,14 +711,14 @@ pub mod fast_decode { assert!(leftover_buffer.len() < decode_in_chunks_of_size); // Write all data in `decoded_buffer` to `output` - write_to_output(&mut decoded_buffer, &mut output)?; + write_to_output(&mut decoded_buffer, output)?; } Err(er) => { let kind = er.kind(); if kind == ErrorKind::Interrupted { - // TODO - // Retry reading? + // Retry reading + continue; } return Err(USimpleError::new(1, format_read_error(kind))); @@ -734,7 +734,7 @@ pub mod fast_decode { .decode_into_vec(&leftover_buffer, &mut decoded_buffer)?; // Write all data in `decoded_buffer` to `output` - write_to_output(&mut decoded_buffer, &mut output)?; + write_to_output(&mut decoded_buffer, output)?; } Ok(()) diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 9d26197813b..456639ba972 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -200,7 +200,7 @@ fn parse_delimiters(delimiters: &str) -> UResult]>> { let mut add_single_char_delimiter = |vec: &mut Vec>, ch: char| { let delimiter_encoded = ch.encode_utf8(&mut buffer); - vec.push(Box::from(delimiter_encoded.as_bytes())); + vec.push(Box::<[u8]>::from(delimiter_encoded.as_bytes())); }; let mut vec = Vec::>::with_capacity(delimiters.len()); @@ -311,7 +311,7 @@ impl<'a> DelimiterState<'a> { DelimiterState::MultipleDelimiters { current_delimiter, .. } => current_delimiter.len(), - _ => { + DelimiterState::NoDelimiters => { return; } }; @@ -350,7 +350,7 @@ impl<'a> DelimiterState<'a> { *current_delimiter = bo; } - _ => {} + DelimiterState::NoDelimiters => {} } } } @@ -363,8 +363,8 @@ enum InputSource { impl InputSource { fn read_until(&mut self, byte: u8, buf: &mut Vec) -> UResult { let us = match self { - Self::File(bu) => bu.read_until(byte, buf)?, - Self::StandardInput(rc) => rc + InputSource::File(bu) => bu.read_until(byte, buf)?, + InputSource::StandardInput(rc) => rc .try_borrow() .map_err(|bo| USimpleError::new(1, format!("{bo}")))? .lock() From 0780e26914016a593f3ed6e1ff999afa7afbd508 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Dec 2024 11:08:28 +0100 Subject: [PATCH 092/351] Revert "install: create destination file with safer modes before copy" --- src/uu/install/src/install.rs | 56 +++++++++-------------------------- tests/by-util/test_install.rs | 38 ------------------------ 2 files changed, 14 insertions(+), 80 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 8f5d381fe87..331a50f6741 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -13,12 +13,9 @@ use filetime::{set_file_times, FileTime}; use std::error::Error; use std::fmt::{Debug, Display}; use std::fs; -#[cfg(not(unix))] use std::fs::File; use std::os::unix::fs::MetadataExt; #[cfg(unix)] -use std::os::unix::fs::OpenOptionsExt; -#[cfg(unix)] use std::os::unix::prelude::OsStrExt; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; use std::process; @@ -753,52 +750,27 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult> { fn copy_file(from: &Path, to: &Path) -> UResult<()> { // fs::copy fails if destination is a invalid symlink. // so lets just remove all existing files at destination before copy. - let remove_destination = || { - if let Err(e) = fs::remove_file(to) { - if e.kind() != std::io::ErrorKind::NotFound { - show_error!( - "Failed to remove existing file {}. Error: {:?}", - to.display(), - e - ); - } + if let Err(e) = fs::remove_file(to) { + if e.kind() != std::io::ErrorKind::NotFound { + show_error!( + "Failed to remove existing file {}. Error: {:?}", + to.display(), + e + ); } - }; - remove_destination(); - - // create the destination file first. Using safer mode on unix to avoid - // potential unsafe mode between time-of-creation and time-of-chmod. - #[cfg(unix)] - let creation = fs::OpenOptions::new() - .write(true) - .create_new(true) - .mode(0o600) - .open(to); - #[cfg(not(unix))] - let creation = File::create(to); - - if let Err(e) = creation { - show_error!( - "Failed to create destination file {}. Error: {:?}", - to.display(), - e - ); - return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), e).into()); } - // drop the file to close the fd of creation - drop(creation); - - /* workaround a limitation of fs::copy: skip copy if source is /dev/null - * https://github.com/rust-lang/rust/issues/79390 - */ - if from.as_os_str() != "/dev/null" { - if let Err(err) = fs::copy(from, to) { - remove_destination(); + if from.as_os_str() == "/dev/null" { + /* workaround a limitation of fs::copy + * https://github.com/rust-lang/rust/issues/79390 + */ + if let Err(err) = File::create(to) { return Err( InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(), ); } + } else if let Err(err) = fs::copy(from, to) { + return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into()); } Ok(()) } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 6fa9cc62f47..f1e3302e138 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1717,41 +1717,3 @@ fn test_install_root_combined() { run_and_check(&["-Cv", "c", "d"], "d", 0, 0); run_and_check(&["-Cv", "c", "d"], "d", 0, 0); } - -#[cfg(all(unix, feature = "chmod"))] -#[test] -fn test_install_copy_failures() { - let scene = TestScenario::new(util_name!()); - - let at = &scene.fixtures; - - let file1 = "source_file"; - let file2 = "target_file"; - - at.touch(file1); - scene.ccmd("chmod").arg("000").arg(file1).succeeds(); - - // if source file is not readable, it will raise a permission error. - // since we create the file with mode 0600 before `fs::copy`, if the - // copy failed, the file should be removed. - scene - .ucmd() - .arg(file1) - .arg(file2) - .arg("--mode=400") - .fails() - .stderr_contains("permission denied"); - assert!(!at.file_exists(file2)); - - // if source file is good to copy, it should succeed and set the - // destination file permissions accordingly - scene.ccmd("chmod").arg("644").arg(file1).succeeds(); - scene - .ucmd() - .arg(file1) - .arg(file2) - .arg("--mode=400") - .succeeds(); - assert!(at.file_exists(file2)); - assert_eq!(0o100_400_u32, at.metadata(file2).permissions().mode()); -} From de775caa9c2b7882f12401e8028b3cba7ab7a682 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Dec 2024 13:23:03 +0100 Subject: [PATCH 093/351] alacritty: remove unused patch --- src/uu/dircolors/README.md | 6 ------ src/uu/dircolors/alacritty-supports-colors.patch | 12 ------------ 2 files changed, 18 deletions(-) delete mode 100644 src/uu/dircolors/alacritty-supports-colors.patch diff --git a/src/uu/dircolors/README.md b/src/uu/dircolors/README.md index f4ec5d675a3..62944d4907b 100644 --- a/src/uu/dircolors/README.md +++ b/src/uu/dircolors/README.md @@ -9,12 +9,6 @@ dircolors -b > /PATH_TO_COREUTILS/tests/fixtures/dircolors/bash_def.expected dircolors -c > /PATH_TO_COREUTILS/tests/fixtures/dircolors/csh_def.expected ``` -Apply the patches to include more terminals that support colors: - -```shell -git apply /PATH_TO_COREUTILS/src/uu/dircolors/alacritty-supports-colors.patch -``` - Run the tests: ```shell diff --git a/src/uu/dircolors/alacritty-supports-colors.patch b/src/uu/dircolors/alacritty-supports-colors.patch deleted file mode 100644 index c6f022423bc..00000000000 --- a/src/uu/dircolors/alacritty-supports-colors.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/tests/fixtures/dircolors/internal.expected b/tests/fixtures/dircolors/internal.expected -index e151973f2..01dae4273 100644 ---- a/tests/fixtures/dircolors/internal.expected -+++ b/tests/fixtures/dircolors/internal.expected -@@ -7,6 +7,7 @@ - # restrict following config to systems with matching environment variables. - COLORTERM ?* - TERM Eterm -+TERM alacritty* - TERM ansi - TERM *color* - TERM con[0-9]*x[0-9]* From a6447241375976d9d7477de21eabe47461babbff Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Dec 2024 13:25:24 +0100 Subject: [PATCH 094/351] uucore: add foot to the list of terminals that support colors --- src/uucore/src/lib/features/colors.rs | 1 + tests/fixtures/dircolors/internal.expected | 1 + 2 files changed, 2 insertions(+) diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index f8cbc9ebf8b..885ae2fe967 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -22,6 +22,7 @@ pub static TERMS: &[&str] = &[ "cygwin", "*direct*", "dtterm", + "foot", "gnome", "hurd", "jfbterm", diff --git a/tests/fixtures/dircolors/internal.expected b/tests/fixtures/dircolors/internal.expected index 01dae42739f..feea46455f4 100644 --- a/tests/fixtures/dircolors/internal.expected +++ b/tests/fixtures/dircolors/internal.expected @@ -16,6 +16,7 @@ TERM console TERM cygwin TERM *direct* TERM dtterm +TERM foot TERM gnome TERM hurd TERM jfbterm From 93dfc933bd4d50a349913c4c1b1cf1850433630c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 4 Dec 2024 22:02:57 +0100 Subject: [PATCH 095/351] base32/base64: handle two corner cases * no padding * --wrap 0 + remove property_tests.rs, we don't need such tests as the code is already tests by test_base* (+ it is too dependant on the code structure) Should make base64.pl pass --- Cargo.lock | 83 +---- src/uu/base32/Cargo.toml | 5 - src/uu/base32/src/base32.rs | 3 +- src/uu/base32/src/base_common.rs | 131 ++++++-- src/uu/base32/tests/property_tests.rs | 430 -------------------------- tests/by-util/test_base64.rs | 33 ++ 6 files changed, 145 insertions(+), 540 deletions(-) delete mode 100644 src/uu/base32/tests/property_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 1a70d4c0281..a526c8f9887 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,21 +185,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitflags" version = "1.3.2" @@ -1557,7 +1542,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1821,32 +1805,6 @@ dependencies = [ "hex", ] -[[package]] -name = "proptest" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.6.0", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -1907,15 +1865,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - [[package]] name = "rayon" version = "1.10.0" @@ -2084,18 +2033,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error 1.2.3", - "tempfile", - "wait-timeout", -] - [[package]] name = "same-file" version = "1.0.6" @@ -2482,12 +2419,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -2574,7 +2505,6 @@ name = "uu_base32" version = "0.0.28" dependencies = [ "clap", - "proptest", "uucore", ] @@ -2686,7 +2616,7 @@ dependencies = [ "filetime", "indicatif", "libc", - "quick-error 2.0.1", + "quick-error", "selinux", "uucore", "walkdir", @@ -3134,7 +3064,7 @@ dependencies = [ "chrono", "clap", "itertools", - "quick-error 2.0.1", + "quick-error", "regex", "uucore", ] @@ -3631,15 +3561,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index ffcd4796cea..26ab2bc6fe8 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,5 +1,3 @@ -# spell-checker:ignore proptest - [package] name = "uu_base32" version = "0.0.28" @@ -22,9 +20,6 @@ path = "src/base32.rs" clap = { workspace = true } uucore = { workspace = true, features = ["encoding"] } -[dev-dependencies] -proptest = "1.5.0" - [[bin]] name = "base32" path = "src/main.rs" diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 46a0361ea4a..e14e83921e2 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -5,6 +5,7 @@ pub mod base_common; +use base_common::ReadSeek; use clap::Command; use uucore::{encoding::Format, error::UResult, help_about, help_usage}; @@ -17,7 +18,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; - let mut input = base_common::get_input(&config)?; + let mut input: Box = base_common::get_input(&config)?; base_common::handle_input(&mut input, format, config) } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 130fe86264b..84a46196339 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -3,15 +3,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore hexupper lsbf msbf unpadded +// spell-checker:ignore hexupper lsbf msbf unpadded nopad aGVsbG8sIHdvcmxkIQ use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::File; -use std::io::{self, ErrorKind, Read}; +use std::io::{self, ErrorKind, Read, Seek, SeekFrom}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::encoding::{ - for_base_common::{BASE32, BASE32HEX, BASE64, BASE64URL, HEXUPPER}, + for_base_common::{BASE32, BASE32HEX, BASE64, BASE64URL, BASE64_NOPAD, HEXUPPER}, Format, Z85Wrapper, BASE2LSBF, BASE2MSBF, }; use uucore::encoding::{EncodingWrapper, SupportsFastDecodeAndEncode}; @@ -143,25 +143,50 @@ pub fn base_app(about: &'static str, usage: &str) -> Command { ) } -pub fn get_input(config: &Config) -> UResult> { +/// A trait alias for types that implement both `Read` and `Seek`. +pub trait ReadSeek: Read + Seek {} + +/// Automatically implement the `ReadSeek` trait for any type that implements both `Read` and `Seek`. +impl ReadSeek for T {} + +pub fn get_input(config: &Config) -> UResult> { match &config.to_read { Some(path_buf) => { // Do not buffer input, because buffering is handled by `fast_decode` and `fast_encode` let file = File::open(path_buf).map_err_context(|| path_buf.maybe_quote().to_string())?; - Ok(Box::new(file)) } None => { - let stdin_lock = io::stdin().lock(); - - Ok(Box::new(stdin_lock)) + let mut buffer = Vec::new(); + io::stdin().read_to_end(&mut buffer)?; + Ok(Box::new(io::Cursor::new(buffer))) } } } -pub fn handle_input(input: &mut dyn Read, format: Format, config: Config) -> UResult<()> { - let supports_fast_decode_and_encode = get_supports_fast_decode_and_encode(format); +/// Determines if the input buffer ends with padding ('=') after trimming trailing whitespace. +fn has_padding(input: &mut R) -> UResult { + let mut buf = Vec::new(); + input + .read_to_end(&mut buf) + .map_err(|err| USimpleError::new(1, format_read_error(err.kind())))?; + + // Reverse iterator and skip trailing whitespace without extra collections + let has_padding = buf + .iter() + .rfind(|&&byte| !byte.is_ascii_whitespace()) + .is_some_and(|&byte| byte == b'='); + + input.seek(SeekFrom::Start(0))?; + Ok(has_padding) +} + +pub fn handle_input(input: &mut R, format: Format, config: Config) -> UResult<()> { + let has_padding = has_padding(input)?; + + let supports_fast_decode_and_encode = + get_supports_fast_decode_and_encode(format, config.decode, has_padding); let supports_fast_decode_and_encode_ref = supports_fast_decode_and_encode.as_ref(); @@ -184,7 +209,11 @@ pub fn handle_input(input: &mut dyn Read, format: Format, config: Config) -> URe } } -pub fn get_supports_fast_decode_and_encode(format: Format) -> Box { +pub fn get_supports_fast_decode_and_encode( + format: Format, + decode: bool, + has_padding: bool, +) -> Box { const BASE16_VALID_DECODING_MULTIPLE: usize = 2; const BASE2_VALID_DECODING_MULTIPLE: usize = 8; const BASE32_VALID_DECODING_MULTIPLE: usize = 8; @@ -231,13 +260,24 @@ pub fn get_supports_fast_decode_and_encode(format: Format) -> Box { + let alphabet: &[u8] = if has_padding { + &b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="[..] + } else { + &b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"[..] + }; + let wrapper = if decode && !has_padding { + BASE64_NOPAD + } else { + BASE64 + }; + Box::from(EncodingWrapper::new( + wrapper, + BASE64_VALID_DECODING_MULTIPLE, + BASE64_UNPADDED_MULTIPLE, + alphabet, + )) + } Format::Base64Url => Box::from(EncodingWrapper::new( BASE64URL, BASE64_VALID_DECODING_MULTIPLE, @@ -316,6 +356,7 @@ pub mod fast_encode { encoded_buffer: &mut VecDeque, output: &mut dyn Write, is_cleanup: bool, + empty_wrap: bool, ) -> io::Result<()> { // TODO // `encoded_buffer` only has to be a VecDeque if line wrapping is enabled @@ -324,7 +365,9 @@ pub mod fast_encode { output.write_all(encoded_buffer.make_contiguous())?; if is_cleanup { - output.write_all(b"\n")?; + if !empty_wrap { + output.write_all(b"\n")?; + } } else { encoded_buffer.clear(); } @@ -381,12 +424,13 @@ pub mod fast_encode { encoded_buffer: &mut VecDeque, output: &mut dyn Write, is_cleanup: bool, + empty_wrap: bool, ) -> io::Result<()> { // Write all data in `encoded_buffer` to `output` if let &mut Some(ref mut li) = line_wrapping { write_with_line_breaks(li, encoded_buffer, output, is_cleanup)?; } else { - write_without_line_breaks(encoded_buffer, output, is_cleanup)?; + write_without_line_breaks(encoded_buffer, output, is_cleanup, empty_wrap)?; } Ok(()) @@ -473,9 +517,14 @@ pub mod fast_encode { )?; assert!(leftover_buffer.len() < encode_in_chunks_of_size); - // Write all data in `encoded_buffer` to `output` - write_to_output(&mut line_wrapping, &mut encoded_buffer, output, false)?; + write_to_output( + &mut line_wrapping, + &mut encoded_buffer, + output, + false, + wrap == Some(0), + )?; } Err(er) => { let kind = er.kind(); @@ -499,7 +548,13 @@ pub mod fast_encode { // Write all data in `encoded_buffer` to output // `is_cleanup` triggers special cleanup-only logic - write_to_output(&mut line_wrapping, &mut encoded_buffer, output, true)?; + write_to_output( + &mut line_wrapping, + &mut encoded_buffer, + output, + true, + wrap == Some(0), + )?; } Ok(()) @@ -759,3 +814,33 @@ fn format_read_error(kind: ErrorKind) -> String { format!("read error: {kind_string_capitalized}") } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn test_has_padding() { + let test_cases = vec![ + ("aGVsbG8sIHdvcmxkIQ==", true), + ("aGVsbG8sIHdvcmxkIQ== ", true), + ("aGVsbG8sIHdvcmxkIQ==\n", true), + ("aGVsbG8sIHdvcmxkIQ== \n", true), + ("aGVsbG8sIHdvcmxkIQ=", true), + ("aGVsbG8sIHdvcmxkIQ= ", true), + ("aGVsbG8sIHdvcmxkIQ \n", false), + ("aGVsbG8sIHdvcmxkIQ", false), + ]; + + for (input, expected) in test_cases { + let mut cursor = Cursor::new(input.as_bytes()); + assert_eq!( + has_padding(&mut cursor).unwrap(), + expected, + "Failed for input: '{}'", + input + ); + } + } +} diff --git a/src/uu/base32/tests/property_tests.rs b/src/uu/base32/tests/property_tests.rs deleted file mode 100644 index 0f2393c42ab..00000000000 --- a/src/uu/base32/tests/property_tests.rs +++ /dev/null @@ -1,430 +0,0 @@ -// spell-checker:ignore lsbf msbf proptest - -use proptest::{prelude::TestCaseError, prop_assert, prop_assert_eq, test_runner::TestRunner}; -use std::io::Cursor; -use uu_base32::base_common::{fast_decode, fast_encode, get_supports_fast_decode_and_encode}; -use uucore::encoding::{Format, SupportsFastDecodeAndEncode}; - -const CASES: u32 = { - #[cfg(debug_assertions)] - { - 32 - } - - #[cfg(not(debug_assertions))] - { - 128 - } -}; - -const NORMAL_INPUT_SIZE_LIMIT: usize = { - #[cfg(debug_assertions)] - { - // 256 kibibytes - 256 * 1024 - } - - #[cfg(not(debug_assertions))] - { - // 4 mebibytes - 4 * 1024 * 1024 - } -}; - -const LARGE_INPUT_SIZE_LIMIT: usize = 4 * NORMAL_INPUT_SIZE_LIMIT; - -// Note that `TestRunner`s cannot be reused -fn get_test_runner() -> TestRunner { - TestRunner::new(proptest::test_runner::Config { - cases: CASES, - failure_persistence: None, - - ..proptest::test_runner::Config::default() - }) -} - -fn generic_round_trip(format: Format) { - let supports_fast_decode_and_encode = get_supports_fast_decode_and_encode(format); - - let supports_fast_decode_and_encode_ref = supports_fast_decode_and_encode.as_ref(); - - // Make sure empty inputs round trip - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - ), - |(ignore_garbage, line_wrap_zero, line_wrap)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - // Empty input - Vec::::new(), - ) - }, - ) - .unwrap(); - } - - // Unusually large line wrapping settings - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(512_usize..65_535_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } - - // Spend more time on sane line wrapping settings - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } - - // Test with garbage data - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - // Garbage data to insert - proptest::collection::vec( - ( - // Random index - proptest::num::usize::ANY, - // In all of the encodings being tested, non-ASCII bytes are garbage - 128_u8..=u8::MAX, - ), - 0..4_096, - ), - proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, garbage_data, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - garbage_data, - input, - ) - }, - ) - .unwrap(); - } - - // Test small inputs - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..1_024), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } - - // Test small inputs with garbage data - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - // Garbage data to insert - proptest::collection::vec( - ( - // Random index - proptest::num::usize::ANY, - // In all of the encodings being tested, non-ASCII bytes are garbage - 128_u8..=u8::MAX, - ), - 0..1_024, - ), - proptest::collection::vec(proptest::num::u8::ANY, 0..1_024), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, garbage_data, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - garbage_data, - input, - ) - }, - ) - .unwrap(); - } - - // Test large inputs - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..LARGE_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } -} - -fn configurable_round_trip( - format: Format, - supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode, - ignore_garbage: bool, - line_wrap_zero: bool, - line_wrap: Option, - garbage_data: Vec<(usize, u8)>, - mut input: Vec, -) -> Result<(), TestCaseError> { - // Z85 only accepts inputs with lengths divisible by 4 - if let Format::Z85 = format { - // Reduce length of "input" until it is divisible by 4 - input.truncate((input.len() / 4) * 4); - - assert!((input.len() % 4) == 0); - } - - let line_wrap_to_use = if line_wrap_zero { Some(0) } else { line_wrap }; - - let input_len = input.len(); - - let garbage_data_len = garbage_data.len(); - - let garbage_data_is_empty = garbage_data_len == 0; - - let (input, encoded) = { - let mut output = Vec::with_capacity(input_len * 8); - - let mut cursor = Cursor::new(input); - - fast_encode::fast_encode( - &mut cursor, - &mut output, - supports_fast_decode_and_encode, - line_wrap_to_use, - ) - .unwrap(); - - (cursor.into_inner(), output) - }; - - let encoded_or_encoded_with_garbage = if garbage_data_is_empty { - encoded - } else { - let encoded_len = encoded.len(); - - let encoded_highest_index = match encoded_len.checked_sub(1) { - Some(0) | None => None, - Some(x) => Some(x), - }; - - let mut garbage_data_indexed = vec![Option::::None; encoded_len]; - - let mut encoded_with_garbage = Vec::::with_capacity(encoded_len + garbage_data_len); - - for (index, garbage_byte) in garbage_data { - if let Some(x) = encoded_highest_index { - let index_to_use = index % x; - - garbage_data_indexed[index_to_use] = Some(garbage_byte); - } else { - encoded_with_garbage.push(garbage_byte); - } - } - - for (index, encoded_byte) in encoded.into_iter().enumerate() { - encoded_with_garbage.push(encoded_byte); - - if let Some(garbage_byte) = garbage_data_indexed[index] { - encoded_with_garbage.push(garbage_byte); - } - } - - encoded_with_garbage - }; - - match line_wrap_to_use { - Some(0) => { - let line_endings_count = encoded_or_encoded_with_garbage - .iter() - .filter(|byte| **byte == b'\n') - .count(); - - // If line wrapping is disabled, there should only be one '\n' character (at the very end of the output) - prop_assert_eq!(line_endings_count, 1); - } - _ => { - // TODO - // Validate other line wrapping settings - } - } - - let decoded_or_error = { - let mut output = Vec::with_capacity(input_len); - - let mut cursor = Cursor::new(encoded_or_encoded_with_garbage); - - match fast_decode::fast_decode( - &mut cursor, - &mut output, - supports_fast_decode_and_encode, - ignore_garbage, - ) { - Ok(()) => Ok(output), - Err(er) => Err(er), - } - }; - - let made_round_trip = match decoded_or_error { - Ok(ve) => input.as_slice() == ve.as_slice(), - Err(_) => false, - }; - - let result_was_correct = if garbage_data_is_empty || ignore_garbage { - // If there was no garbage data added, or if "ignore_garbage" was enabled, expect the round trip to succeed - made_round_trip - } else { - // If garbage data was added, and "ignore_garbage" was disabled, expect the round trip to fail - - !made_round_trip - }; - - if !result_was_correct { - eprintln!( - "\ -(configurable_round_trip) FAILURE -format: {format:?} -ignore_garbage: {ignore_garbage} -line_wrap_to_use: {line_wrap_to_use:?} -garbage_data_len: {garbage_data_len} -input_len: {input_len} -", - ); - } - - prop_assert!(result_was_correct); - - Ok(()) -} - -#[test] -fn base16_round_trip() { - generic_round_trip(Format::Base16); -} - -#[test] -fn base2lsbf_round_trip() { - generic_round_trip(Format::Base2Lsbf); -} - -#[test] -fn base2msbf_round_trip() { - generic_round_trip(Format::Base2Msbf); -} - -#[test] -fn base32_round_trip() { - generic_round_trip(Format::Base32); -} - -#[test] -fn base32hex_round_trip() { - generic_round_trip(Format::Base32Hex); -} - -#[test] -fn base64_round_trip() { - generic_round_trip(Format::Base64); -} - -#[test] -fn base64url_round_trip() { - generic_round_trip(Format::Base64Url); -} - -#[test] -fn z85_round_trip() { - generic_round_trip(Format::Z85); -} diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index f07da925f5b..29b9edf0251 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -40,6 +40,28 @@ fn test_encode_repeat_flags_later_wrap_15() { .stdout_only("aGVsbG8sIHdvcmx\nkIQ==\n"); // spell-checker:disable-line } +#[test] +fn test_decode_short() { + let input = "aQ"; + new_ucmd!() + .args(&["--decode"]) + .pipe_in(input) + .succeeds() + .stdout_only("i"); +} + +#[test] +fn test_multi_lines() { + let input = ["aQ\n\n\n", "a\nQ==\n\n\n"]; + for i in input { + new_ucmd!() + .args(&["--decode"]) + .pipe_in(i) + .succeeds() + .stdout_only("i"); + } +} + #[test] fn test_base64_encode_file() { new_ucmd!() @@ -105,6 +127,17 @@ fn test_wrap() { // spell-checker:disable-next-line .stdout_only("VGhlIHF1aWNrIGJyb3du\nIGZveCBqdW1wcyBvdmVy\nIHRoZSBsYXp5IGRvZy4=\n"); } + let input = "hello, world"; + new_ucmd!() + .args(&["--wrap", "0"]) + .pipe_in(input) + .succeeds() + .stdout_only("aGVsbG8sIHdvcmxk"); // spell-checker:disable-line + new_ucmd!() + .args(&["--wrap", "30"]) + .pipe_in(input) + .succeeds() + .stdout_only("aGVsbG8sIHdvcmxk\n"); // spell-checker:disable-line } #[test] From 094cab046cc66505347008dd014c7d43ee022714 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:29:52 +0000 Subject: [PATCH 096/351] chore(deps): update rust crate self_cell to v1.1.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a70d4c0281..cb09c5fd699 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2113,9 +2113,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "self_cell" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" +checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" [[package]] name = "selinux" From 76dfcd82faed3c548fc262933aac054ef3a8cb40 Mon Sep 17 00:00:00 2001 From: Arthur Pin Date: Fri, 6 Dec 2024 15:58:40 -0300 Subject: [PATCH 097/351] seq: handle scientific notation with uppercase 'E' --- src/uu/seq/src/numberparse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index adbaccc1104..79e7068e31d 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -333,7 +333,7 @@ impl FromStr for PreciseNumber { // number differently depending on its form. This is important // because the form of the input dictates how the output will be // presented. - match (s.find('.'), s.find('e')) { + match (s.find('.'), s.find(['e', 'E'])) { // For example, "123456" or "inf". (None, None) => parse_no_decimal_no_exponent(s), // For example, "123e456" or "1e-2". From 5cbe87620c380ec3d0681246b5819820f83df21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 5 Dec 2024 15:57:01 +0100 Subject: [PATCH 098/351] checksum: move regex detection to the line level --- src/uucore/src/lib/features/checksum.rs | 109 ++++++++---------------- 1 file changed, 36 insertions(+), 73 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index f7228830b9a..34dc0f87074 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -8,7 +8,7 @@ use data_encoding::BASE64; use os_display::Quotable; use regex::bytes::{Captures, Regex}; use std::{ - ffi::{OsStr, OsString}, + ffi::OsStr, fmt::Display, fs::File, io::{self, stdin, BufReader, Read, Write}, @@ -130,9 +130,6 @@ enum FileCheckError { ImproperlyFormatted, /// reading of the checksum file failed CantOpenChecksumFile, - /// Algorithm detection was unsuccessful. - /// Either none is provided, or there is a conflict. - AlgoDetectionError, } impl From> for FileCheckError { @@ -441,7 +438,7 @@ fn get_filename_for_output(filename: &OsStr, input_is_stdin: bool) -> String { } /// Determines the appropriate regular expression to use based on the provided lines. -fn determine_regex(lines: &[OsString]) -> Option<(Regex, bool)> { +fn determine_regex(line: impl AsRef) -> Option<(Regex, bool)> { let regexes = [ (Regex::new(ALGO_BASED_REGEX).unwrap(), true), (Regex::new(DOUBLE_SPACE_REGEX).unwrap(), false), @@ -449,12 +446,10 @@ fn determine_regex(lines: &[OsString]) -> Option<(Regex, bool)> { (Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(), true), ]; - for line in lines { - let line_bytes = os_str_as_bytes(line).expect("UTF-8 decoding failed"); - for (regex, is_algo_based) in ®exes { - if regex.is_match(line_bytes) { - return Some((regex.clone(), *is_algo_based)); - } + let line_bytes = os_str_as_bytes(line.as_ref()).expect("UTF-8 decoding failed"); + for (regex, is_algo_based) in ®exes { + if regex.is_match(line_bytes) { + return Some((regex.clone(), *is_algo_based)); } } @@ -599,13 +594,20 @@ fn process_checksum_line( filename_input: &OsStr, line: &OsStr, i: usize, - chosen_regex: &Regex, - is_algo_based_format: bool, cli_algo_name: Option<&str>, cli_algo_length: Option, opts: ChecksumOptions, ) -> Result<(), LineCheckError> { let line_bytes = os_str_as_bytes(line)?; + + // early return on empty or commented lines. + if line.is_empty() || line_bytes.starts_with(b"#") { + return Err(LineCheckError::Skipped); + } + + let (chosen_regex, is_algo_based_format) = + determine_regex(line).ok_or(LineCheckError::ImproperlyFormatted)?; + if let Some(caps) = chosen_regex.captures(line_bytes) { let mut filename_to_check = caps.name("filename").unwrap().as_bytes(); @@ -617,7 +619,7 @@ fn process_checksum_line( filename_to_check = &filename_to_check[1..]; } - let expected_checksum = get_expected_digest_as_hex_string(&caps, chosen_regex) + let expected_checksum = get_expected_digest_as_hex_string(&caps, &chosen_regex) .ok_or(LineCheckError::ImproperlyFormatted)?; // If the algo_name is provided, we use it, otherwise we try to detect it @@ -672,10 +674,6 @@ fn process_checksum_line( Err(LineCheckError::DigestMismatch) } } else { - if line.is_empty() || line_bytes.starts_with(b"#") { - // Don't show any warning for empty or commented lines. - return Err(LineCheckError::Skipped); - } if opts.warn { let algo = if let Some(algo_name_input) = cli_algo_name { algo_name_input.to_uppercase() @@ -723,19 +721,11 @@ fn process_checksum_file( let reader = BufReader::new(file); let lines = read_os_string_lines(reader).collect::>(); - let Some((chosen_regex, is_algo_based_format)) = determine_regex(&lines) else { - log_no_properly_formatted(get_filename_for_output(filename_input, input_is_stdin)); - set_exit_code(1); - return Err(FileCheckError::AlgoDetectionError); - }; - for (i, line) in lines.iter().enumerate() { let line_result = process_checksum_line( filename_input, line, i, - &chosen_regex, - is_algo_based_format, cli_algo_name, cli_algo_length, opts, @@ -816,8 +806,7 @@ where use FileCheckError::*; match process_checksum_file(filename_input, algo_name_input, length_input, opts) { Err(UError(e)) => return Err(e), - Err(ImproperlyFormatted) => break, - Err(CantOpenChecksumFile | AlgoDetectionError) | Ok(_) => continue, + Err(CantOpenChecksumFile | ImproperlyFormatted) | Ok(_) => continue, } } @@ -926,6 +915,7 @@ pub fn escape_filename(filename: &Path) -> (String, &'static str) { #[cfg(test)] mod tests { use super::*; + use std::ffi::OsString; #[test] fn test_unescape_filename() { @@ -1161,66 +1151,39 @@ mod tests { #[test] fn test_determine_regex() { // Test algo-based regex - let lines_algo_based = ["MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"] - .iter() - .map(|s| OsString::from(s.to_string())) - .collect::>(); - let (regex, algo_based) = determine_regex(&lines_algo_based).unwrap(); + let line_algo_based = + OsString::from("MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"); + let (regex, algo_based) = determine_regex(&line_algo_based).unwrap(); assert!(algo_based); - assert!(regex.is_match(os_str_as_bytes(&lines_algo_based[0]).unwrap())); + assert!(regex.is_match(os_str_as_bytes(&line_algo_based).unwrap())); // Test double-space regex - let lines_double_space = ["d41d8cd98f00b204e9800998ecf8427e example.txt"] - .iter() - .map(|s| OsString::from(s.to_string())) - .collect::>(); - let (regex, algo_based) = determine_regex(&lines_double_space).unwrap(); + let line_double_space = OsString::from("d41d8cd98f00b204e9800998ecf8427e example.txt"); + let (regex, algo_based) = determine_regex(&line_double_space).unwrap(); assert!(!algo_based); - assert!(regex.is_match(os_str_as_bytes(&lines_double_space[0]).unwrap())); + assert!(regex.is_match(os_str_as_bytes(&line_double_space).unwrap())); // Test single-space regex - let lines_single_space = ["d41d8cd98f00b204e9800998ecf8427e example.txt"] - .iter() - .map(|s| OsString::from(s.to_string())) - .collect::>(); - let (regex, algo_based) = determine_regex(&lines_single_space).unwrap(); - assert!(!algo_based); - assert!(regex.is_match(os_str_as_bytes(&lines_single_space[0]).unwrap())); - - // Test double-space regex start with invalid - let lines_double_space = ["ERR", "d41d8cd98f00b204e9800998ecf8427e example.txt"] - .iter() - .map(|s| OsString::from(s.to_string())) - .collect::>(); - let (regex, algo_based) = determine_regex(&lines_double_space).unwrap(); + let line_single_space = OsString::from("d41d8cd98f00b204e9800998ecf8427e example.txt"); + let (regex, algo_based) = determine_regex(&line_single_space).unwrap(); assert!(!algo_based); - assert!(!regex.is_match(os_str_as_bytes(&lines_double_space[0]).unwrap())); - assert!(regex.is_match(os_str_as_bytes(&lines_double_space[1]).unwrap())); + assert!(regex.is_match(os_str_as_bytes(&line_single_space).unwrap())); // Test invalid checksum line - let lines_invalid = ["invalid checksum line"] - .iter() - .map(|s| OsString::from(s.to_string())) - .collect::>(); - assert!(determine_regex(&lines_invalid).is_none()); + let line_invalid = OsString::from("invalid checksum line"); + assert!(determine_regex(&line_invalid).is_none()); // Test leading space before checksum line - let lines_algo_based_leading_space = - [" MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"] - .iter() - .map(|s| OsString::from(s.to_string())) - .collect::>(); - let res = determine_regex(&lines_algo_based_leading_space); + let line_algo_based_leading_space = + OsString::from(" MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"); + let res = determine_regex(&line_algo_based_leading_space); assert!(res.is_some()); assert_eq!(res.unwrap().0.as_str(), ALGO_BASED_REGEX); // Test trailing space after checksum line (should fail) - let lines_algo_based_leading_space = - ["MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e "] - .iter() - .map(|s| OsString::from(s.to_string())) - .collect::>(); - let res = determine_regex(&lines_algo_based_leading_space); + let line_algo_based_leading_space = + OsString::from("MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e "); + let res = determine_regex(&line_algo_based_leading_space); assert!(res.is_none()); } From 88e10478bc8f231c5a551e7efab30b8ba92493d7 Mon Sep 17 00:00:00 2001 From: Arthur Pin Date: Fri, 6 Dec 2024 17:47:22 -0300 Subject: [PATCH 099/351] tests/seq: test scientific notation with uppercase 'E' --- src/uu/seq/src/numberparse.rs | 1 + tests/by-util/test_seq.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 79e7068e31d..891fa2ce6d3 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -392,6 +392,7 @@ mod tests { fn test_parse_big_int() { assert_eq!(parse("0"), ExtendedBigDecimal::zero()); assert_eq!(parse("0.1e1"), ExtendedBigDecimal::one()); + assert_eq!(parse("0.1E1"), ExtendedBigDecimal::one()); assert_eq!( parse("1.0e1"), ExtendedBigDecimal::BigDecimal("10".parse::().unwrap()) diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index c14d3062923..ab0659f2102 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -333,6 +333,11 @@ fn test_width_scientific_notation() { .succeeds() .stdout_is("0999\n1000\n") .no_stderr(); + new_ucmd!() + .args(&["-w", "999", "1E3"]) + .succeeds() + .stdout_is("0999\n1000\n") + .no_stderr(); } #[test] From 4e79a01513a5c75b057c8f4af7fadfe0158a9efd Mon Sep 17 00:00:00 2001 From: Alexander Shirokov Date: Sat, 7 Dec 2024 12:23:06 +0100 Subject: [PATCH 100/351] fix(mv): don't panic if apply_xattrs fails This commit fixes issue #6727 by returning the error status instead of causing a panic. It aligns with the original GNU mv more closely. --- src/uu/mv/src/mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 9d8452b1ec7..7debf52c962 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -679,7 +679,7 @@ fn rename_with_fallback( }; #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] - fsxattr::apply_xattrs(to, xattrs).unwrap(); + fsxattr::apply_xattrs(to, xattrs)?; if let Err(err) = result { return match err.kind { From 367cc19d455a91547d14c2e06ea651fa6fb5220f Mon Sep 17 00:00:00 2001 From: aimerlief <152078880+aimerlief@users.noreply.github.com> Date: Sun, 8 Dec 2024 00:42:34 +0900 Subject: [PATCH 101/351] fix(seq): handle 0e... scientific notation without padding (#6934) * fix(seq): handle 0e... scientific notation without padding - Updated the parse_exponent_no_decimal function to treat 0e... as zero. - Added test cases to verify correct behavior for 0e15 and -w 0e15. Fix: #6926 * fix(seq): improved parse for accurate BigDecimal handling * apply missing cargo fmt formatting adjustments --- src/uu/seq/src/numberparse.rs | 22 ++++++++++++++++++++-- tests/by-util/test_seq.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 891fa2ce6d3..80587f7136c 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -102,7 +102,16 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result() + .map_err(|_| ParseNumberError::Float)?; + if parsed_decimal == BigDecimal::zero() { + BigDecimal::zero() + } else { + parsed_decimal + } + }; let num_integral_digits = if is_minus_zero_float(s, &x) { if exponent > 0 { @@ -204,7 +213,16 @@ fn parse_decimal_and_exponent( // Because of the match guard, this subtraction will not underflow. let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64; let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?; - let val: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?; + let val: BigDecimal = { + let parsed_decimal = s + .parse::() + .map_err(|_| ParseNumberError::Float)?; + if parsed_decimal == BigDecimal::zero() { + BigDecimal::zero() + } else { + parsed_decimal + } + }; let num_integral_digits = { let minimum: usize = { diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index ab0659f2102..62a0212b152 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -842,3 +842,31 @@ fn test_invalid_format() { .no_stdout() .stderr_contains("format '%g%g' has too many % directives"); } + +#[test] +fn test_parse_scientific_zero() { + new_ucmd!() + .args(&["0e15", "1"]) + .succeeds() + .stdout_only("0\n1\n"); + new_ucmd!() + .args(&["0.0e15", "1"]) + .succeeds() + .stdout_only("0\n1\n"); + new_ucmd!() + .args(&["0", "1"]) + .succeeds() + .stdout_only("0\n1\n"); + new_ucmd!() + .args(&["-w", "0e15", "1"]) + .succeeds() + .stdout_only("0000000000000000\n0000000000000001\n"); + new_ucmd!() + .args(&["-w", "0.0e15", "1"]) + .succeeds() + .stdout_only("0000000000000000\n0000000000000001\n"); + new_ucmd!() + .args(&["-w", "0", "1"]) + .succeeds() + .stdout_only("0\n1\n"); +} From e654645974f019a683f4a9e815f099cbcf7e59de Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 7 Dec 2024 16:59:13 +0100 Subject: [PATCH 102/351] tests/seq: use stdout_only() to remove no_stderr() --- tests/by-util/test_seq.rs | 162 +++++++++++++------------------------- 1 file changed, 54 insertions(+), 108 deletions(-) diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 62a0212b152..730a9571dbe 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -303,18 +303,15 @@ fn test_preserve_negative_zero_start() { new_ucmd!() .args(&["-0", "1"]) .succeeds() - .stdout_is("-0\n1\n") - .no_stderr(); + .stdout_only("-0\n1\n"); new_ucmd!() .args(&["-0", "1", "2"]) .succeeds() - .stdout_is("-0\n1\n2\n") - .no_stderr(); + .stdout_only("-0\n1\n2\n"); new_ucmd!() .args(&["-0", "1", "2.0"]) .succeeds() - .stdout_is("-0\n1\n2\n") - .no_stderr(); + .stdout_only("-0\n1\n2\n"); } #[test] @@ -322,8 +319,7 @@ fn test_drop_negative_zero_end() { new_ucmd!() .args(&["1", "-1", "-0"]) .succeeds() - .stdout_is("1\n0\n") - .no_stderr(); + .stdout_only("1\n0\n"); } #[test] @@ -331,13 +327,11 @@ fn test_width_scientific_notation() { new_ucmd!() .args(&["-w", "999", "1e3"]) .succeeds() - .stdout_is("0999\n1000\n") - .no_stderr(); + .stdout_only("0999\n1000\n"); new_ucmd!() .args(&["-w", "999", "1E3"]) .succeeds() - .stdout_is("0999\n1000\n") - .no_stderr(); + .stdout_only("0999\n1000\n"); } #[test] @@ -345,18 +339,15 @@ fn test_width_negative_zero() { new_ucmd!() .args(&["-w", "-0", "1"]) .succeeds() - .stdout_is("-0\n01\n") - .no_stderr(); + .stdout_only("-0\n01\n"); new_ucmd!() .args(&["-w", "-0", "1", "2"]) .succeeds() - .stdout_is("-0\n01\n02\n") - .no_stderr(); + .stdout_only("-0\n01\n02\n"); new_ucmd!() .args(&["-w", "-0", "1", "2.0"]) .succeeds() - .stdout_is("-0\n01\n02\n") - .no_stderr(); + .stdout_only("-0\n01\n02\n"); } #[test] @@ -364,33 +355,27 @@ fn test_width_negative_zero_decimal_notation() { new_ucmd!() .args(&["-w", "-0.0", "1"]) .succeeds() - .stdout_is("-0.0\n01.0\n") - .no_stderr(); + .stdout_only("-0.0\n01.0\n"); new_ucmd!() .args(&["-w", "-0.0", "1.0"]) .succeeds() - .stdout_is("-0.0\n01.0\n") - .no_stderr(); + .stdout_only("-0.0\n01.0\n"); new_ucmd!() .args(&["-w", "-0.0", "1", "2"]) .succeeds() - .stdout_is("-0.0\n01.0\n02.0\n") - .no_stderr(); + .stdout_only("-0.0\n01.0\n02.0\n"); new_ucmd!() .args(&["-w", "-0.0", "1", "2.0"]) .succeeds() - .stdout_is("-0.0\n01.0\n02.0\n") - .no_stderr(); + .stdout_only("-0.0\n01.0\n02.0\n"); new_ucmd!() .args(&["-w", "-0.0", "1.0", "2"]) .succeeds() - .stdout_is("-0.0\n01.0\n02.0\n") - .no_stderr(); + .stdout_only("-0.0\n01.0\n02.0\n"); new_ucmd!() .args(&["-w", "-0.0", "1.0", "2.0"]) .succeeds() - .stdout_is("-0.0\n01.0\n02.0\n") - .no_stderr(); + .stdout_only("-0.0\n01.0\n02.0\n"); } #[test] @@ -398,98 +383,80 @@ fn test_width_negative_zero_scientific_notation() { new_ucmd!() .args(&["-w", "-0e0", "1"]) .succeeds() - .stdout_is("-0\n01\n") - .no_stderr(); + .stdout_only("-0\n01\n"); new_ucmd!() .args(&["-w", "-0e0", "1", "2"]) .succeeds() - .stdout_is("-0\n01\n02\n") - .no_stderr(); + .stdout_only("-0\n01\n02\n"); new_ucmd!() .args(&["-w", "-0e0", "1", "2.0"]) .succeeds() - .stdout_is("-0\n01\n02\n") - .no_stderr(); + .stdout_only("-0\n01\n02\n"); new_ucmd!() .args(&["-w", "-0e+1", "1"]) .succeeds() - .stdout_is("-00\n001\n") - .no_stderr(); + .stdout_only("-00\n001\n"); new_ucmd!() .args(&["-w", "-0e+1", "1", "2"]) .succeeds() - .stdout_is("-00\n001\n002\n") - .no_stderr(); + .stdout_only("-00\n001\n002\n"); new_ucmd!() .args(&["-w", "-0e+1", "1", "2.0"]) .succeeds() - .stdout_is("-00\n001\n002\n") - .no_stderr(); + .stdout_only("-00\n001\n002\n"); new_ucmd!() .args(&["-w", "-0.000e0", "1"]) .succeeds() - .stdout_is("-0.000\n01.000\n") - .no_stderr(); + .stdout_only("-0.000\n01.000\n"); new_ucmd!() .args(&["-w", "-0.000e0", "1", "2"]) .succeeds() - .stdout_is("-0.000\n01.000\n02.000\n") - .no_stderr(); + .stdout_only("-0.000\n01.000\n02.000\n"); new_ucmd!() .args(&["-w", "-0.000e0", "1", "2.0"]) .succeeds() - .stdout_is("-0.000\n01.000\n02.000\n") - .no_stderr(); + .stdout_only("-0.000\n01.000\n02.000\n"); new_ucmd!() .args(&["-w", "-0.000e-2", "1"]) .succeeds() - .stdout_is("-0.00000\n01.00000\n") - .no_stderr(); + .stdout_only("-0.00000\n01.00000\n"); new_ucmd!() .args(&["-w", "-0.000e-2", "1", "2"]) .succeeds() - .stdout_is("-0.00000\n01.00000\n02.00000\n") - .no_stderr(); + .stdout_only("-0.00000\n01.00000\n02.00000\n"); new_ucmd!() .args(&["-w", "-0.000e-2", "1", "2.0"]) .succeeds() - .stdout_is("-0.00000\n01.00000\n02.00000\n") - .no_stderr(); + .stdout_only("-0.00000\n01.00000\n02.00000\n"); new_ucmd!() .args(&["-w", "-0.000e5", "1"]) .succeeds() - .stdout_is("-000000\n0000001\n") - .no_stderr(); + .stdout_only("-000000\n0000001\n"); new_ucmd!() .args(&["-w", "-0.000e5", "1", "2"]) .succeeds() - .stdout_is("-000000\n0000001\n0000002\n") - .no_stderr(); + .stdout_only("-000000\n0000001\n0000002\n"); new_ucmd!() .args(&["-w", "-0.000e5", "1", "2.0"]) .succeeds() - .stdout_is("-000000\n0000001\n0000002\n") - .no_stderr(); + .stdout_only("-000000\n0000001\n0000002\n"); new_ucmd!() .args(&["-w", "-0.000e5", "1"]) .succeeds() - .stdout_is("-000000\n0000001\n") - .no_stderr(); + .stdout_only("-000000\n0000001\n"); new_ucmd!() .args(&["-w", "-0.000e5", "1", "2"]) .succeeds() - .stdout_is("-000000\n0000001\n0000002\n") - .no_stderr(); + .stdout_only("-000000\n0000001\n0000002\n"); new_ucmd!() .args(&["-w", "-0.000e5", "1", "2.0"]) .succeeds() - .stdout_is("-000000\n0000001\n0000002\n") - .no_stderr(); + .stdout_only("-000000\n0000001\n0000002\n"); } #[test] @@ -497,14 +464,12 @@ fn test_width_decimal_scientific_notation_increment() { new_ucmd!() .args(&["-w", ".1", "1e-2", ".11"]) .succeeds() - .stdout_is("0.10\n0.11\n") - .no_stderr(); + .stdout_only("0.10\n0.11\n"); new_ucmd!() .args(&["-w", ".0", "1.500e-1", ".2"]) .succeeds() - .stdout_is("0.0000\n0.1500\n") - .no_stderr(); + .stdout_only("0.0000\n0.1500\n"); } /// Test that trailing zeros in the start argument contribute to precision. @@ -513,8 +478,7 @@ fn test_width_decimal_scientific_notation_trailing_zeros_start() { new_ucmd!() .args(&["-w", ".1000", "1e-2", ".11"]) .succeeds() - .stdout_is("0.1000\n0.1100\n") - .no_stderr(); + .stdout_only("0.1000\n0.1100\n"); } /// Test that trailing zeros in the increment argument contribute to precision. @@ -523,8 +487,7 @@ fn test_width_decimal_scientific_notation_trailing_zeros_increment() { new_ucmd!() .args(&["-w", "1e-1", "0.0100", ".11"]) .succeeds() - .stdout_is("0.1000\n0.1100\n") - .no_stderr(); + .stdout_only("0.1000\n0.1100\n"); } #[test] @@ -532,8 +495,7 @@ fn test_width_negative_decimal_notation() { new_ucmd!() .args(&["-w", "-.1", ".1", ".11"]) .succeeds() - .stdout_is("-0.1\n00.0\n00.1\n") - .no_stderr(); + .stdout_only("-0.1\n00.0\n00.1\n"); } #[test] @@ -541,22 +503,19 @@ fn test_width_negative_scientific_notation() { new_ucmd!() .args(&["-w", "-1e-3", "1"]) .succeeds() - .stdout_is("-0.001\n00.999\n") - .no_stderr(); + .stdout_only("-0.001\n00.999\n"); new_ucmd!() .args(&["-w", "-1.e-3", "1"]) .succeeds() - .stdout_is("-0.001\n00.999\n") - .no_stderr(); + .stdout_only("-0.001\n00.999\n"); new_ucmd!() .args(&["-w", "-1.0e-4", "1"]) .succeeds() - .stdout_is("-0.00010\n00.99990\n") - .no_stderr(); + .stdout_only("-0.00010\n00.99990\n"); new_ucmd!() .args(&["-w", "-.1e2", "10", "100"]) .succeeds() - .stdout_is( + .stdout_only( "-010 0000 0010 @@ -570,12 +529,11 @@ fn test_width_negative_scientific_notation() { 0090 0100 ", - ) - .no_stderr(); + ); new_ucmd!() .args(&["-w", "-0.1e2", "10", "100"]) .succeeds() - .stdout_is( + .stdout_only( "-010 0000 0010 @@ -589,8 +547,7 @@ fn test_width_negative_scientific_notation() { 0090 0100 ", - ) - .no_stderr(); + ); } /// Test that trailing zeros in the end argument do not contribute to width. @@ -599,8 +556,7 @@ fn test_width_decimal_scientific_notation_trailing_zeros_end() { new_ucmd!() .args(&["-w", "1e-1", "1e-2", ".1100"]) .succeeds() - .stdout_is("0.10\n0.11\n") - .no_stderr(); + .stdout_only("0.10\n0.11\n"); } #[test] @@ -608,8 +564,7 @@ fn test_width_floats() { new_ucmd!() .args(&["-w", "9.0", "10.0"]) .succeeds() - .stdout_is("09.0\n10.0\n") - .no_stderr(); + .stdout_only("09.0\n10.0\n"); } // TODO This is duplicated from `test_yes.rs`; refactor them. @@ -661,11 +616,7 @@ fn test_neg_inf_width() { #[test] fn test_ignore_leading_whitespace() { - new_ucmd!() - .arg(" 1") - .succeeds() - .stdout_is("1\n") - .no_stderr(); + new_ucmd!().arg(" 1").succeeds().stdout_only("1\n"); } #[test] @@ -684,8 +635,7 @@ fn test_negative_zero_int_start_float_increment() { new_ucmd!() .args(&["-0", "0.1", "0.1"]) .succeeds() - .stdout_is("-0.0\n0.1\n") - .no_stderr(); + .stdout_only("-0.0\n0.1\n"); } #[test] @@ -693,7 +643,7 @@ fn test_float_precision_increment() { new_ucmd!() .args(&["999", "0.1", "1000.1"]) .succeeds() - .stdout_is( + .stdout_only( "999.0 999.1 999.2 @@ -707,8 +657,7 @@ fn test_float_precision_increment() { 1000.0 1000.1 ", - ) - .no_stderr(); + ); } /// Test for floating point precision issues. @@ -717,8 +666,7 @@ fn test_negative_increment_decimal() { new_ucmd!() .args(&["0.1", "-0.1", "-0.2"]) .succeeds() - .stdout_is("0.1\n0.0\n-0.1\n-0.2\n") - .no_stderr(); + .stdout_only("0.1\n0.0\n-0.1\n-0.2\n"); } #[test] @@ -726,8 +674,7 @@ fn test_zero_not_first() { new_ucmd!() .args(&["-w", "-0.1", "0.1", "0.1"]) .succeeds() - .stdout_is("-0.1\n00.0\n00.1\n") - .no_stderr(); + .stdout_only("-0.1\n00.0\n00.1\n"); } #[test] @@ -735,8 +682,7 @@ fn test_rounding_end() { new_ucmd!() .args(&["1", "-1", "0.1"]) .succeeds() - .stdout_is("1\n") - .no_stderr(); + .stdout_only("1\n"); } #[test] From 1f6f7fbe8c02c4be963e18203631f827ab83446e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 7 Dec 2024 17:04:05 +0100 Subject: [PATCH 103/351] tests/seq: fix ticket references of ignored tests --- tests/by-util/test_seq.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 730a9571dbe..8f33c3aa7c8 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -710,7 +710,7 @@ fn test_format_option() { } #[test] -#[ignore = "Need issue #6233 to be fixed"] +#[ignore = "Need issue #2660 to be fixed"] fn test_auto_precision() { new_ucmd!() .args(&["1", "0x1p-1", "2"]) @@ -719,7 +719,7 @@ fn test_auto_precision() { } #[test] -#[ignore = "Need issue #6234 to be fixed"] +#[ignore = "Need issue #3318 to be fixed"] fn test_undefined() { new_ucmd!() .args(&["1e-9223372036854775808"]) From 7708d22eced7c2d5eaf539feff8c500c60522019 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 18:52:15 +0000 Subject: [PATCH 104/351] chore(deps): update rust crate thiserror to v2.0.5 --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb09c5fd699..9dce55ae0c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2382,11 +2382,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "643caef17e3128658ff44d85923ef2d28af81bb71e0d67bbfe1d76f19a73e053" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.5", ] [[package]] @@ -2402,9 +2402,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "995d0bbc9995d1f19d28b7215a9352b0fc3cd3a2d2ec95c2cadc485cdedbcdde" dependencies = [ "proc-macro2", "quote", @@ -2610,7 +2610,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.4", + "thiserror 2.0.5", "uucore", ] @@ -2622,7 +2622,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.4", + "thiserror 2.0.5", "uucore", ] @@ -2699,7 +2699,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.4", + "thiserror 2.0.5", "uucore", ] @@ -3215,7 +3215,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.4", + "thiserror 2.0.5", "uucore", ] @@ -3494,7 +3494,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.4", + "thiserror 2.0.5", "utmp-classic", "uucore", ] @@ -3525,7 +3525,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.4", + "thiserror 2.0.5", "unicode-width 0.1.13", "uucore", ] @@ -3586,7 +3586,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.4", + "thiserror 2.0.5", "time", "uucore_procs", "walkdir", From bd294ddd100080e349ad866b4bc126c33d6902a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 21:59:45 +0000 Subject: [PATCH 105/351] chore(deps): update rust crate thiserror to v2.0.6 --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dce55ae0c8..4a186d49606 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2382,11 +2382,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.5" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643caef17e3128658ff44d85923ef2d28af81bb71e0d67bbfe1d76f19a73e053" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.5", + "thiserror-impl 2.0.6", ] [[package]] @@ -2402,9 +2402,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.5" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995d0bbc9995d1f19d28b7215a9352b0fc3cd3a2d2ec95c2cadc485cdedbcdde" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", @@ -2610,7 +2610,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.5", + "thiserror 2.0.6", "uucore", ] @@ -2622,7 +2622,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.5", + "thiserror 2.0.6", "uucore", ] @@ -2699,7 +2699,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.5", + "thiserror 2.0.6", "uucore", ] @@ -3215,7 +3215,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.5", + "thiserror 2.0.6", "uucore", ] @@ -3494,7 +3494,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.5", + "thiserror 2.0.6", "utmp-classic", "uucore", ] @@ -3525,7 +3525,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.5", + "thiserror 2.0.6", "unicode-width 0.1.13", "uucore", ] @@ -3586,7 +3586,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.5", + "thiserror 2.0.6", "time", "uucore_procs", "walkdir", From 85bd072655d4c036b2385092cd4fb48ce3ce5f12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 02:20:54 +0000 Subject: [PATCH 106/351] chore(deps): update rust crate bigdecimal to v0.4.7 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dce55ae0c8..65e6a30be9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,9 +136,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bigdecimal" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" +checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" dependencies = [ "autocfg", "libm", From 46d5d638fe448b06a1b10c275ebf82889f34cd0d Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Dec 2024 15:03:28 +0100 Subject: [PATCH 107/351] Bump unicode-width from 0.1.12 to 0.2.0 --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed82e9aa7de..0a07c739828 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2743,7 +2743,7 @@ version = "0.0.28" dependencies = [ "clap", "tempfile", - "unicode-width 0.1.13", + "unicode-width 0.2.0", "uucore", ] @@ -2806,7 +2806,7 @@ name = "uu_expand" version = "0.0.28" dependencies = [ "clap", - "unicode-width 0.1.13", + "unicode-width 0.2.0", "uucore", ] @@ -2848,7 +2848,7 @@ name = "uu_fmt" version = "0.0.28" dependencies = [ "clap", - "unicode-width 0.1.13", + "unicode-width 0.2.0", "uucore", ] @@ -3033,7 +3033,7 @@ dependencies = [ "crossterm", "nix", "unicode-segmentation", - "unicode-width 0.1.13", + "unicode-width 0.2.0", "uucore", ] @@ -3276,7 +3276,7 @@ dependencies = [ "rayon", "self_cell", "tempfile", - "unicode-width 0.1.13", + "unicode-width 0.2.0", "uucore", ] @@ -3468,7 +3468,7 @@ name = "uu_unexpand" version = "0.0.28" dependencies = [ "clap", - "unicode-width 0.1.13", + "unicode-width 0.2.0", "uucore", ] @@ -3526,7 +3526,7 @@ dependencies = [ "libc", "nix", "thiserror 2.0.6", - "unicode-width 0.1.13", + "unicode-width 0.2.0", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index caa233802f5..a4f8462e4f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -335,7 +335,7 @@ textwrap = { version = "0.16.1", features = ["terminal_size"] } thiserror = "2.0.3" time = { version = "0.3.36" } unicode-segmentation = "1.11.0" -unicode-width = "0.1.12" +unicode-width = "0.2.0" utf-8 = "0.7.6" utmp-classic = "0.1.6" walkdir = "2.5" From ec67d22123bf254c138585f52856e06464487d48 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Dec 2024 16:04:24 +0100 Subject: [PATCH 108/351] more: adapt test to change in unicode-width --- src/uu/more/src/more.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index cb74e1176f5..61d9b2adbac 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -700,15 +700,15 @@ mod tests { test_string.push_str("👩🏻‍🔬"); } - let lines = break_line(&test_string, 80); + let lines = break_line(&test_string, 31); let widths: Vec = lines .iter() .map(|s| UnicodeWidthStr::width(&s[..])) .collect(); - // Each 👩🏻‍🔬 is 6 character width it break line to the closest number to 80 => 6 * 13 = 78 - assert_eq!((78, 42), (widths[0], widths[1])); + // Each 👩🏻‍🔬 is 2 character width, break line to the closest even number to 31 + assert_eq!((30, 10), (widths[0], widths[1])); } #[test] From 2a406d8cbb5ba52bc0c3f7459dddeb5b6540cc6f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 4 Dec 2024 14:56:02 +0100 Subject: [PATCH 109/351] sort: adapt fixtures to change in unicode-width --- tests/fixtures/sort/keys_closed_range.expected.debug | 4 ++-- tests/fixtures/sort/keys_multiple_ranges.expected.debug | 6 +++--- tests/fixtures/sort/keys_no_field_match.expected.debug | 4 ++-- tests/fixtures/sort/keys_open_ended.expected.debug | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/fixtures/sort/keys_closed_range.expected.debug b/tests/fixtures/sort/keys_closed_range.expected.debug index b78db4af181..e317d4079ad 100644 --- a/tests/fixtures/sort/keys_closed_range.expected.debug +++ b/tests/fixtures/sort/keys_closed_range.expected.debug @@ -11,8 +11,8 @@ ________ _ ________ 👩‍🔬 👩‍🔬 👩‍🔬 - __ -______________ + __ +________ 💣💣 💣💣 💣💣 __ ______________ diff --git a/tests/fixtures/sort/keys_multiple_ranges.expected.debug b/tests/fixtures/sort/keys_multiple_ranges.expected.debug index 830e9afd091..41b7e210df8 100644 --- a/tests/fixtures/sort/keys_multiple_ranges.expected.debug +++ b/tests/fixtures/sort/keys_multiple_ranges.expected.debug @@ -15,9 +15,9 @@ ________ ___ ________ 👩‍🔬 👩‍🔬 👩‍🔬 - _____ - _____ -______________ + ___ + ___ +________ 💣💣 💣💣 💣💣 _____ _____ diff --git a/tests/fixtures/sort/keys_no_field_match.expected.debug b/tests/fixtures/sort/keys_no_field_match.expected.debug index 60197b1deaa..0a3ea83034f 100644 --- a/tests/fixtures/sort/keys_no_field_match.expected.debug +++ b/tests/fixtures/sort/keys_no_field_match.expected.debug @@ -11,8 +11,8 @@ ________ ^ no match for key ________ 👩‍🔬 👩‍🔬 👩‍🔬 - ^ no match for key -______________ + ^ no match for key +________ 💣💣 💣💣 💣💣 ^ no match for key ______________ diff --git a/tests/fixtures/sort/keys_open_ended.expected.debug b/tests/fixtures/sort/keys_open_ended.expected.debug index d3a56ffd6f6..c8e4ad9ae83 100644 --- a/tests/fixtures/sort/keys_open_ended.expected.debug +++ b/tests/fixtures/sort/keys_open_ended.expected.debug @@ -11,8 +11,8 @@ ________ ____ ________ 👩‍🔬 👩‍🔬 👩‍🔬 - _______ -______________ + _____ +________ 💣💣 💣💣 💣💣 _______ ______________ From cf355591b94746e5f043f3f6d4c62c7aa0c8b050 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:51:39 +0000 Subject: [PATCH 110/351] chore(deps): update rust crate chrono to v0.4.39 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a07c739828..5fe9ac67c15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,9 +318,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", From 3211e43caa4c3cab6d4e80146020824ad5d95e93 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:51:47 +0000 Subject: [PATCH 111/351] fix(deps): update rust crate libc to v0.2.168 --- Cargo.lock | 4 ++-- fuzz/Cargo.lock | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a07c739828..2bd4b0f1888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1305,9 +1305,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index a300d8b65a5..f2ba3f3754d 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -416,9 +416,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libfuzzer-sys" @@ -572,7 +572,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6229bad892b46b0dcfaaeb18ad0d2e56400f5aaea05b768bde96e73676cf75" dependencies = [ - "unicode-width", + "unicode-width 0.1.12", ] [[package]] @@ -838,6 +838,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "utf8parse" version = "0.2.1" @@ -930,7 +936,7 @@ dependencies = [ "rayon", "self_cell", "tempfile", - "unicode-width", + "unicode-width 0.2.0", "uucore", ] @@ -970,7 +976,7 @@ dependencies = [ "libc", "nix 0.29.0", "thiserror", - "unicode-width", + "unicode-width 0.2.0", "uucore", ] From 2ca7c28cd97affa300f8c2a39febb21d44747e1d Mon Sep 17 00:00:00 2001 From: Christian Legnitto Date: Mon, 9 Dec 2024 22:01:36 -0400 Subject: [PATCH 112/351] Make `Spec` public --- src/uucore/src/lib/features/format/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 24dd1daaad7..25d128ed8ca 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -38,7 +38,7 @@ pub mod num_parser; mod spec; pub use argument::*; -use spec::Spec; +pub use spec::Spec; use std::{ error::Error, fmt::Display, From c60203ddd3394858ccff0819588925542ba4ff9f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 10 Dec 2024 08:48:52 +0100 Subject: [PATCH 113/351] stat: improve GNU compatibility (#6933) * stat: fix the quotes when dealing with %N and other formats should fix tests/stat/stat-fmt.sh * stats: use an enum instead of a string * stats: split the functions into smaller functions * stat: handle byte as a format for better display * stat: handle error better. should make tests/stat/stat-printf.pl pass * stat: Some escape sequences are non-standard * Fix tests * Take comments into account --- src/uu/stat/src/stat.rs | 568 +++++++++++++++++++++++-------------- tests/by-util/test_stat.rs | 100 +++++-- util/build-gnu.sh | 3 + 3 files changed, 440 insertions(+), 231 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index ee417834461..5e617e7a31a 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -9,7 +9,9 @@ use uucore::error::{UResult, USimpleError}; use clap::builder::ValueParser; use uucore::display::Quotable; use uucore::fs::display_permissions; -use uucore::fsext::{pretty_filetype, pretty_fstype, read_fs_list, statfs, BirthTime, FsMeta}; +use uucore::fsext::{ + pretty_filetype, pretty_fstype, read_fs_list, statfs, BirthTime, FsMeta, StatFs, +}; use uucore::libc::mode_t; use uucore::{ entries, format_usage, help_about, help_section, help_usage, show_error, show_warning, @@ -19,10 +21,12 @@ use chrono::{DateTime, Local}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; -use std::fs; +use std::fs::{FileType, Metadata}; +use std::io::Write; use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::os::unix::prelude::OsStrExt; use std::path::Path; +use std::{env, fs}; const ABOUT: &str = help_about!("stat.md"); const USAGE: &str = help_usage!("stat.md"); @@ -93,9 +97,33 @@ pub enum OutputType { Unknown, } +#[derive(Default)] +enum QuotingStyle { + Locale, + Shell, + #[default] + ShellEscapeAlways, + Quote, +} + +impl std::str::FromStr for QuotingStyle { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "locale" => Ok(QuotingStyle::Locale), + "shell" => Ok(QuotingStyle::Shell), + "shell-escape-always" => Ok(QuotingStyle::ShellEscapeAlways), + // The others aren't exposed to the user + _ => Err(format!("Invalid quoting style: {}", s)), + } + } +} + #[derive(Debug, PartialEq, Eq)] enum Token { Char(char), + Byte(u8), Directive { flag: Flags, width: usize, @@ -293,6 +321,93 @@ fn print_str(s: &str, flags: &Flags, width: usize, precision: Option) { pad_and_print(s, flags.left, width, Padding::Space); } +fn quote_file_name(file_name: &str, quoting_style: &QuotingStyle) -> String { + match quoting_style { + QuotingStyle::Locale | QuotingStyle::Shell => { + let escaped = file_name.replace('\'', r"\'"); + format!("'{}'", escaped) + } + QuotingStyle::ShellEscapeAlways => format!("\"{}\"", file_name), + QuotingStyle::Quote => file_name.to_string(), + } +} + +fn get_quoted_file_name( + display_name: &str, + file: &OsString, + file_type: &FileType, + from_user: bool, +) -> Result { + let quoting_style = env::var("QUOTING_STYLE") + .ok() + .and_then(|style| style.parse().ok()) + .unwrap_or_default(); + + if file_type.is_symlink() { + let quoted_display_name = quote_file_name(display_name, "ing_style); + match fs::read_link(file) { + Ok(dst) => { + let quoted_dst = quote_file_name(&dst.to_string_lossy(), "ing_style); + Ok(format!("{quoted_display_name} -> {quoted_dst}")) + } + Err(e) => { + show_error!("{e}"); + Err(1) + } + } + } else { + let style = if from_user { + quoting_style + } else { + QuotingStyle::Quote + }; + Ok(quote_file_name(display_name, &style)) + } +} + +fn process_token_filesystem(t: &Token, meta: StatFs, display_name: &str) { + match *t { + Token::Byte(byte) => write_raw_byte(byte), + Token::Char(c) => print!("{c}"), + Token::Directive { + flag, + width, + precision, + format, + } => { + let output = match format { + // free blocks available to non-superuser + 'a' => OutputType::Unsigned(meta.avail_blocks()), + // total data blocks in file system + 'b' => OutputType::Unsigned(meta.total_blocks()), + // total file nodes in file system + 'c' => OutputType::Unsigned(meta.total_file_nodes()), + // free file nodes in file system + 'd' => OutputType::Unsigned(meta.free_file_nodes()), + // free blocks in file system + 'f' => OutputType::Unsigned(meta.free_blocks()), + // file system ID in hex + 'i' => OutputType::UnsignedHex(meta.fsid()), + // maximum length of filenames + 'l' => OutputType::Unsigned(meta.namelen()), + // file name + 'n' => OutputType::Str(display_name.to_string()), + // block size (for faster transfers) + 's' => OutputType::Unsigned(meta.io_size()), + // fundamental block size (for block counts) + 'S' => OutputType::Integer(meta.block_size()), + // file system type in hex + 't' => OutputType::UnsignedHex(meta.fs_type() as u64), + // file system type in human readable form + 'T' => OutputType::Str(pretty_fstype(meta.fs_type()).into()), + _ => OutputType::Unknown, + }; + + print_it(&output, flag, width, precision); + } + } +} + /// Prints an integer value based on the provided flags, width, and precision. /// /// # Arguments @@ -403,7 +518,26 @@ fn print_unsigned_hex( pad_and_print(&s, flags.left, width, padding_char); } +fn write_raw_byte(byte: u8) { + std::io::stdout().write_all(&[byte]).unwrap(); +} + impl Stater { + fn process_flags(chars: &[char], i: &mut usize, bound: usize, flag: &mut Flags) { + while *i < bound { + match chars[*i] { + '#' => flag.alter = true, + '0' => flag.zero = true, + '-' => flag.left = true, + ' ' => flag.space = true, + '+' => flag.sign = true, + '\'' => flag.group = true, + _ => break, + } + *i += 1; + } + } + fn handle_percent_case( chars: &[char], i: &mut usize, @@ -423,20 +557,7 @@ impl Stater { let mut flag = Flags::default(); - while *i < bound { - match chars[*i] { - '#' => flag.alter = true, - '0' => flag.zero = true, - '-' => flag.left = true, - ' ' => flag.space = true, - '+' => flag.sign = true, - '\'' => flag.group = true, - 'I' => unimplemented!(), - _ => break, - } - *i += 1; - } - check_bound(format_str, bound, old, *i)?; + Self::process_flags(chars, i, bound, &mut flag); let mut width = 0; let mut precision = None; @@ -445,6 +566,15 @@ impl Stater { if let Some((field_width, offset)) = format_str[j..].scan_num::() { width = field_width; j += offset; + + // Reject directives like `%` by checking if width has been parsed. + if j >= bound || chars[j] == '%' { + let invalid_directive: String = chars[old..=j.min(bound - 1)].iter().collect(); + return Err(USimpleError::new( + 1, + format!("{}: invalid directive", invalid_directive.quote()), + )); + } } check_bound(format_str, bound, old, j)?; @@ -465,9 +595,27 @@ impl Stater { } *i = j; + + // Check for multi-character specifiers (e.g., `%Hd`, `%Lr`) + if *i + 1 < bound { + if let Some(&next_char) = chars.get(*i + 1) { + if (chars[*i] == 'H' || chars[*i] == 'L') && (next_char == 'd' || next_char == 'r') + { + let specifier = format!("{}{}", chars[*i], next_char); + *i += 1; + return Ok(Token::Directive { + flag, + width, + precision, + format: specifier.chars().next().unwrap(), + }); + } + } + } + Ok(Token::Directive { - width, flag, + width, precision, format: chars[*i], }) @@ -485,33 +633,49 @@ impl Stater { return Token::Char('\\'); } match chars[*i] { - 'x' if *i + 1 < bound => { - if let Some((c, offset)) = format_str[*i + 1..].scan_char(16) { - *i += offset; - Token::Char(c) - } else { - show_warning!("unrecognized escape '\\x'"); - Token::Char('x') + 'a' => Token::Byte(0x07), // BEL + 'b' => Token::Byte(0x08), // Backspace + 'f' => Token::Byte(0x0C), // Form feed + 'n' => Token::Byte(0x0A), // Line feed + 'r' => Token::Byte(0x0D), // Carriage return + 't' => Token::Byte(0x09), // Horizontal tab + '\\' => Token::Byte(b'\\'), // Backslash + '\'' => Token::Byte(b'\''), // Single quote + '"' => Token::Byte(b'"'), // Double quote + '0'..='7' => { + // Parse octal escape sequence (up to 3 digits) + let mut value = 0u8; + let mut count = 0; + while *i < bound && count < 3 { + if let Some(digit) = chars[*i].to_digit(8) { + value = value * 8 + digit as u8; + *i += 1; + count += 1; + } else { + break; + } } + *i -= 1; // Adjust index to account for the outer loop increment + Token::Byte(value) } - '0'..='7' => { - let (c, offset) = format_str[*i..].scan_char(8).unwrap(); - *i += offset - 1; - Token::Char(c) + 'x' => { + // Parse hexadecimal escape sequence + if *i + 1 < bound { + if let Some((c, offset)) = format_str[*i + 1..].scan_char(16) { + *i += offset; + Token::Byte(c as u8) + } else { + show_warning!("unrecognized escape '\\x'"); + Token::Byte(b'x') + } + } else { + show_warning!("incomplete hex escape '\\x'"); + Token::Byte(b'x') + } } - '"' => Token::Char('"'), - '\\' => Token::Char('\\'), - 'a' => Token::Char('\x07'), - 'b' => Token::Char('\x08'), - 'e' => Token::Char('\x1B'), - 'f' => Token::Char('\x0C'), - 'n' => Token::Char('\n'), - 'r' => Token::Char('\r'), - 't' => Token::Char('\t'), - 'v' => Token::Char('\x0B'), - c => { - show_warning!("unrecognized escape '\\{}'", c); - Token::Char(c) + other => { + show_warning!("unrecognized escape '\\{}'", other); + Token::Byte(other as u8) } } } @@ -634,7 +798,128 @@ impl Stater { ret } - #[allow(clippy::cognitive_complexity)] + fn process_token_files( + &self, + t: &Token, + meta: &Metadata, + display_name: &str, + file: &OsString, + file_type: &FileType, + from_user: bool, + ) -> Result<(), i32> { + match *t { + Token::Byte(byte) => write_raw_byte(byte), + Token::Char(c) => print!("{c}"), + + Token::Directive { + flag, + width, + precision, + format, + } => { + let output = match format { + // access rights in octal + 'a' => OutputType::UnsignedOct(0o7777 & meta.mode()), + // access rights in human readable form + 'A' => OutputType::Str(display_permissions(meta, true)), + // number of blocks allocated (see %B) + 'b' => OutputType::Unsigned(meta.blocks()), + + // the size in bytes of each block reported by %b + // FIXME: blocksize differs on various platform + // See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE // spell-checker:disable-line + 'B' => OutputType::Unsigned(512), + + // device number in decimal + 'd' => OutputType::Unsigned(meta.dev()), + // device number in hex + 'D' => OutputType::UnsignedHex(meta.dev()), + // raw mode in hex + 'f' => OutputType::UnsignedHex(meta.mode() as u64), + // file type + 'F' => OutputType::Str( + pretty_filetype(meta.mode() as mode_t, meta.len()).to_owned(), + ), + // group ID of owner + 'g' => OutputType::Unsigned(meta.gid() as u64), + // group name of owner + 'G' => { + let group_name = + entries::gid2grp(meta.gid()).unwrap_or_else(|_| "UNKNOWN".to_owned()); + OutputType::Str(group_name) + } + // number of hard links + 'h' => OutputType::Unsigned(meta.nlink()), + // inode number + 'i' => OutputType::Unsigned(meta.ino()), + // mount point + 'm' => OutputType::Str(self.find_mount_point(file).unwrap()), + // file name + 'n' => OutputType::Str(display_name.to_string()), + // quoted file name with dereference if symbolic link + 'N' => { + let file_name = + get_quoted_file_name(display_name, file, file_type, from_user)?; + OutputType::Str(file_name) + } + // optimal I/O transfer size hint + 'o' => OutputType::Unsigned(meta.blksize()), + // total size, in bytes + 's' => OutputType::Integer(meta.len() as i64), + // major device type in hex, for character/block device special + // files + 't' => OutputType::UnsignedHex(meta.rdev() >> 8), + // minor device type in hex, for character/block device special + // files + 'T' => OutputType::UnsignedHex(meta.rdev() & 0xff), + // user ID of owner + 'u' => OutputType::Unsigned(meta.uid() as u64), + // user name of owner + 'U' => { + let user_name = + entries::uid2usr(meta.uid()).unwrap_or_else(|_| "UNKNOWN".to_owned()); + OutputType::Str(user_name) + } + + // time of file birth, human-readable; - if unknown + 'w' => OutputType::Str( + meta.birth() + .map(|(sec, nsec)| pretty_time(sec as i64, nsec as i64)) + .unwrap_or(String::from("-")), + ), + + // time of file birth, seconds since Epoch; 0 if unknown + 'W' => OutputType::Unsigned(meta.birth().unwrap_or_default().0), + + // time of last access, human-readable + 'x' => OutputType::Str(pretty_time(meta.atime(), meta.atime_nsec())), + // time of last access, seconds since Epoch + 'X' => OutputType::Integer(meta.atime()), + // time of last data modification, human-readable + 'y' => OutputType::Str(pretty_time(meta.mtime(), meta.mtime_nsec())), + // time of last data modification, seconds since Epoch + 'Y' => OutputType::Integer(meta.mtime()), + // time of last status change, human-readable + 'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())), + // time of last status change, seconds since Epoch + 'Z' => OutputType::Integer(meta.ctime()), + 'R' => { + let major = meta.rdev() >> 8; + let minor = meta.rdev() & 0xff; + OutputType::Str(format!("{},{}", major, minor)) + } + 'r' => OutputType::Unsigned(meta.rdev()), + 'H' => OutputType::Unsigned(meta.rdev() >> 8), // Major in decimal + 'L' => OutputType::Unsigned(meta.rdev() & 0xff), // Minor in decimal + + _ => OutputType::Unknown, + }; + print_it(&output, flag, width, precision); + } + } + Ok(()) + } + fn do_stat(&self, file: &OsStr, stdin_is_fifo: bool) -> i32 { let display_name = file.to_string_lossy(); let file = if cfg!(unix) && display_name == "-" { @@ -659,46 +944,9 @@ impl Stater { Ok(meta) => { let tokens = &self.default_tokens; + // Usage for t in tokens { - match *t { - Token::Char(c) => print!("{c}"), - Token::Directive { - flag, - width, - precision, - format, - } => { - let output = match format { - // free blocks available to non-superuser - 'a' => OutputType::Unsigned(meta.avail_blocks()), - // total data blocks in file system - 'b' => OutputType::Unsigned(meta.total_blocks()), - // total file nodes in file system - 'c' => OutputType::Unsigned(meta.total_file_nodes()), - // free file nodes in file system - 'd' => OutputType::Unsigned(meta.free_file_nodes()), - // free blocks in file system - 'f' => OutputType::Unsigned(meta.free_blocks()), - // file system ID in hex - 'i' => OutputType::UnsignedHex(meta.fsid()), - // maximum length of filenames - 'l' => OutputType::Unsigned(meta.namelen()), - // file name - 'n' => OutputType::Str(display_name.to_string()), - // block size (for faster transfers) - 's' => OutputType::Unsigned(meta.io_size()), - // fundamental block size (for block counts) - 'S' => OutputType::Integer(meta.block_size()), - // file system type in hex - 't' => OutputType::UnsignedHex(meta.fs_type() as u64), - // file system type in human readable form - 'T' => OutputType::Str(pretty_fstype(meta.fs_type()).into()), - _ => OutputType::Unknown, - }; - - print_it(&output, flag, width, precision); - } - } + process_token_filesystem(t, meta, &display_name); } } Err(e) => { @@ -728,125 +976,15 @@ impl Stater { }; for t in tokens { - match *t { - Token::Char(c) => print!("{c}"), - Token::Directive { - flag, - width, - precision, - format, - } => { - let output = match format { - // access rights in octal - 'a' => OutputType::UnsignedOct(0o7777 & meta.mode()), - // access rights in human readable form - 'A' => OutputType::Str(display_permissions(&meta, true)), - // number of blocks allocated (see %B) - 'b' => OutputType::Unsigned(meta.blocks()), - - // the size in bytes of each block reported by %b - // FIXME: blocksize differs on various platform - // See coreutils/gnulib/lib/stat-size.h ST_NBLOCKSIZE // spell-checker:disable-line - 'B' => OutputType::Unsigned(512), - - // device number in decimal - 'd' => OutputType::Unsigned(meta.dev()), - // device number in hex - 'D' => OutputType::UnsignedHex(meta.dev()), - // raw mode in hex - 'f' => OutputType::UnsignedHex(meta.mode() as u64), - // file type - 'F' => OutputType::Str( - pretty_filetype(meta.mode() as mode_t, meta.len()) - .to_owned(), - ), - // group ID of owner - 'g' => OutputType::Unsigned(meta.gid() as u64), - // group name of owner - 'G' => { - let group_name = entries::gid2grp(meta.gid()) - .unwrap_or_else(|_| "UNKNOWN".to_owned()); - OutputType::Str(group_name) - } - // number of hard links - 'h' => OutputType::Unsigned(meta.nlink()), - // inode number - 'i' => OutputType::Unsigned(meta.ino()), - // mount point - 'm' => OutputType::Str(self.find_mount_point(&file).unwrap()), - // file name - 'n' => OutputType::Str(display_name.to_string()), - // quoted file name with dereference if symbolic link - 'N' => { - let file_name = if file_type.is_symlink() { - let dst = match fs::read_link(&file) { - Ok(path) => path, - Err(e) => { - println!("{e}"); - return 1; - } - }; - format!("{} -> {}", display_name.quote(), dst.quote()) - } else { - display_name.to_string() - }; - OutputType::Str(file_name) - } - // optimal I/O transfer size hint - 'o' => OutputType::Unsigned(meta.blksize()), - // total size, in bytes - 's' => OutputType::Integer(meta.len() as i64), - // major device type in hex, for character/block device special - // files - 't' => OutputType::UnsignedHex(meta.rdev() >> 8), - // minor device type in hex, for character/block device special - // files - 'T' => OutputType::UnsignedHex(meta.rdev() & 0xff), - // user ID of owner - 'u' => OutputType::Unsigned(meta.uid() as u64), - // user name of owner - 'U' => { - let user_name = entries::uid2usr(meta.uid()) - .unwrap_or_else(|_| "UNKNOWN".to_owned()); - OutputType::Str(user_name) - } - - // time of file birth, human-readable; - if unknown - 'w' => OutputType::Str( - meta.birth() - .map(|(sec, nsec)| pretty_time(sec as i64, nsec as i64)) - .unwrap_or(String::from("-")), - ), - - // time of file birth, seconds since Epoch; 0 if unknown - 'W' => OutputType::Unsigned(meta.birth().unwrap_or_default().0), - - // time of last access, human-readable - 'x' => OutputType::Str(pretty_time( - meta.atime(), - meta.atime_nsec(), - )), - // time of last access, seconds since Epoch - 'X' => OutputType::Integer(meta.atime()), - // time of last data modification, human-readable - 'y' => OutputType::Str(pretty_time( - meta.mtime(), - meta.mtime_nsec(), - )), - // time of last data modification, seconds since Epoch - 'Y' => OutputType::Integer(meta.mtime()), - // time of last status change, human-readable - 'z' => OutputType::Str(pretty_time( - meta.ctime(), - meta.ctime_nsec(), - )), - // time of last status change, seconds since Epoch - 'Z' => OutputType::Integer(meta.ctime()), - - _ => OutputType::Unknown, - }; - print_it(&output, flag, width, precision); - } + if let Err(code) = self.process_token_files( + t, + &meta, + &display_name, + &file, + &file_type, + self.from_user, + ) { + return code; } } } @@ -1038,7 +1176,7 @@ mod tests { #[test] fn printf_format() { - let s = r#"%-# 15a\t\r\"\\\a\b\e\f\v%+020.-23w\x12\167\132\112\n"#; + let s = r#"%-# 15a\t\r\"\\\a\b\x1B\f\x0B%+020.-23w\x12\167\132\112\n"#; let expected = vec![ Token::Directive { flag: Flags { @@ -1051,15 +1189,15 @@ mod tests { precision: None, format: 'a', }, - Token::Char('\t'), - Token::Char('\r'), - Token::Char('"'), - Token::Char('\\'), - Token::Char('\x07'), - Token::Char('\x08'), - Token::Char('\x1B'), - Token::Char('\x0C'), - Token::Char('\x0B'), + Token::Byte(b'\t'), + Token::Byte(b'\r'), + Token::Byte(b'"'), + Token::Byte(b'\\'), + Token::Byte(b'\x07'), + Token::Byte(b'\x08'), + Token::Byte(b'\x1B'), + Token::Byte(b'\x0C'), + Token::Byte(b'\x0B'), Token::Directive { flag: Flags { sign: true, @@ -1070,11 +1208,11 @@ mod tests { precision: None, format: 'w', }, - Token::Char('\x12'), - Token::Char('w'), - Token::Char('Z'), - Token::Char('J'), - Token::Char('\n'), + Token::Byte(b'\x12'), + Token::Byte(b'w'), + Token::Byte(b'Z'), + Token::Byte(b'J'), + Token::Byte(b'\n'), ]; assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap()); } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 8cb4493f0eb..cbd36832f48 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -242,7 +242,7 @@ fn test_multi_files() { #[test] fn test_printf() { let args = [ - "--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n", + "--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\x1B\\f\\x0B%+020.23m\\x12\\167\\132\\112\\n", "/", ]; let ts = TestScenario::new(util_name!()); @@ -256,11 +256,10 @@ fn test_pipe_fifo() { let (at, mut ucmd) = at_and_ucmd!(); at.mkfifo("FIFO"); ucmd.arg("FIFO") - .run() + .succeeds() .no_stderr() .stdout_contains("fifo") - .stdout_contains("File: FIFO") - .succeeded(); + .stdout_contains("File: FIFO"); } #[test] @@ -275,19 +274,17 @@ fn test_stdin_pipe_fifo1() { new_ucmd!() .arg("-") .set_stdin(std::process::Stdio::piped()) - .run() + .succeeds() .no_stderr() .stdout_contains("fifo") - .stdout_contains("File: -") - .succeeded(); + .stdout_contains("File: -"); new_ucmd!() .args(&["-L", "-"]) .set_stdin(std::process::Stdio::piped()) - .run() + .succeeds() .no_stderr() .stdout_contains("fifo") - .stdout_contains("File: -") - .succeeded(); + .stdout_contains("File: -"); } #[test] @@ -299,11 +296,10 @@ fn test_stdin_pipe_fifo2() { new_ucmd!() .arg("-") .set_stdin(std::process::Stdio::null()) - .run() + .succeeds() .no_stderr() .stdout_contains("character special file") - .stdout_contains("File: -") - .succeeded(); + .stdout_contains("File: -"); } #[test] @@ -339,11 +335,10 @@ fn test_stdin_redirect() { ts.ucmd() .arg("-") .set_stdin(std::fs::File::open(at.plus("f")).unwrap()) - .run() + .succeeds() .no_stderr() .stdout_contains("regular empty file") - .stdout_contains("File: -") - .succeeded(); + .stdout_contains("File: -"); } #[test] @@ -352,3 +347,76 @@ fn test_without_argument() { .fails() .stderr_contains("missing operand\nTry 'stat --help' for more information."); } + +#[test] +fn test_quoting_style_locale() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("'"); + ts.ucmd() + .env("QUOTING_STYLE", "locale") + .args(&["-c", "%N", "'"]) + .succeeds() + .stdout_only("'\\''\n"); + + ts.ucmd() + .args(&["-c", "%N", "'"]) + .succeeds() + .stdout_only("\"'\"\n"); +} + +#[test] +fn test_printf_octal_1() { + let ts = TestScenario::new(util_name!()); + let expected_stdout = vec![0x0A, 0xFF]; // Newline + byte 255 + ts.ucmd() + .args(&["--printf=\\012\\377", "."]) + .succeeds() + .stdout_is_bytes(expected_stdout); +} + +#[test] +fn test_printf_octal_2() { + let ts = TestScenario::new(util_name!()); + let expected_stdout = vec![b'.', 0x0A, b'a', 0xFF, b'b']; + ts.ucmd() + .args(&["--printf=.\\012a\\377b", "."]) + .succeeds() + .stdout_is_bytes(expected_stdout); +} + +#[test] +fn test_printf_incomplete_hex() { + let ts = TestScenario::new(util_name!()); + ts.ucmd() + .args(&["--printf=\\x", "."]) + .succeeds() + .stderr_contains("warning: incomplete hex escape"); +} + +#[test] +fn test_printf_bel_etc() { + let ts = TestScenario::new(util_name!()); + let expected_stdout = vec![0x07, 0x08, 0x0C, 0x0A, 0x0D, 0x09]; // BEL, BS, FF, LF, CR, TAB + ts.ucmd() + .args(&["--printf=\\a\\b\\f\\n\\r\\t", "."]) + .succeeds() + .stdout_is_bytes(expected_stdout); +} + +#[test] +fn test_printf_invalid_directive() { + let ts = TestScenario::new(util_name!()); + + ts.ucmd() + .args(&["--printf=%9", "."]) + .fails() + .code_is(1) + .stderr_contains("'%9': invalid directive"); + + ts.ucmd() + .args(&["--printf=%9%", "."]) + .fails() + .code_is(1) + .stderr_contains("'%9%': invalid directive"); +} diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 684187733de..e33e64429bf 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -204,6 +204,9 @@ sed -i "s|cp: target directory 'symlink': Permission denied|cp: 'symlink' is not # Our message is a bit better sed -i "s|cannot create regular file 'no-such/': Not a directory|'no-such/' is not a directory|" tests/mv/trailing-slash.sh +# Our message is better +sed -i "s|warning: unrecognized escape|warning: incomplete hex escape|" tests/stat/stat-printf.pl + sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh sed -i 's|paste |/usr/bin/paste |' tests/od/od-endian.sh sed -i 's|timeout |'"${SYSTEM_TIMEOUT}"' |' tests/tail/follow-stdin.sh From ed15ca1d264ce9b6faa5720d7215ed6707b77651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 5 Dec 2024 17:59:00 +0100 Subject: [PATCH 114/351] checksum: keep a cache of the first used regex for non-algo-based regexes --- src/uucore/src/lib/features/checksum.rs | 218 ++++++++++++++++-------- 1 file changed, 146 insertions(+), 72 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 34dc0f87074..d575a4b3879 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -6,8 +6,9 @@ use data_encoding::BASE64; use os_display::Quotable; -use regex::bytes::{Captures, Regex}; +use regex::bytes::{Match, Regex}; use std::{ + borrow::Cow, ffi::OsStr, fmt::Display, fs::File, @@ -427,6 +428,67 @@ const DOUBLE_SPACE_REGEX: &str = r"^(?P[a-fA-F0-9]+)\s{2}(?P // In this case, we ignore the * const SINGLE_SPACE_REGEX: &str = r"^(?P[a-fA-F0-9]+)\s(?P\*?(?-u:.*))$"; +/// Hold the data extracted from a checksum line. +struct LineInfo { + algo_name: Option, + algo_bit_len: Option, + checksum: String, + filename: Vec, + + regex: Regex, +} + +impl LineInfo { + fn parse(s: impl AsRef, cached_regex: &mut Option) -> Option { + let regexes = [ + (Regex::new(ALGO_BASED_REGEX).unwrap(), true), + (Regex::new(DOUBLE_SPACE_REGEX).unwrap(), false), + (Regex::new(SINGLE_SPACE_REGEX).unwrap(), false), + (Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(), false), + ]; + + let line_bytes = os_str_as_bytes(s.as_ref()).expect("UTF-8 decoding failed"); + + for (regex, algo_based) in ®exes { + if !regex.is_match(line_bytes) { + continue; + } + + let mut r = regex.clone(); + if !algo_based && cached_regex.is_some() { + r = cached_regex.clone().unwrap(); + } + + if let Some(caps) = r.captures(line_bytes) { + // These unwraps are safe thanks to the regex + let match_to_string = |m: Match| String::from_utf8(m.as_bytes().into()).unwrap(); + + return Some(Self { + algo_name: caps.name("algo").map(match_to_string), + algo_bit_len: caps + .name("bits") + .map(|m| match_to_string(m).parse::().unwrap()), + checksum: caps.name("checksum").map(match_to_string).unwrap(), + filename: caps.name("filename").map(|m| m.as_bytes().into()).unwrap(), + regex: r.clone(), + }); + } + } + + None + } + + #[inline] + fn is_algo_based(&self) -> bool { + self.algo_name.is_some() + } + + #[inline] + fn regex_str(&self) -> &str { + self.regex.as_str() + } +} + fn get_filename_for_output(filename: &OsStr, input_is_stdin: bool) -> String { if input_is_stdin { "standard input" @@ -437,34 +499,18 @@ fn get_filename_for_output(filename: &OsStr, input_is_stdin: bool) -> String { .to_string() } -/// Determines the appropriate regular expression to use based on the provided lines. -fn determine_regex(line: impl AsRef) -> Option<(Regex, bool)> { - let regexes = [ - (Regex::new(ALGO_BASED_REGEX).unwrap(), true), - (Regex::new(DOUBLE_SPACE_REGEX).unwrap(), false), - (Regex::new(SINGLE_SPACE_REGEX).unwrap(), false), - (Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(), true), - ]; - - let line_bytes = os_str_as_bytes(line.as_ref()).expect("UTF-8 decoding failed"); - for (regex, is_algo_based) in ®exes { - if regex.is_match(line_bytes) { - return Some((regex.clone(), *is_algo_based)); - } - } - - None -} - /// Extract the expected digest from the checksum string -fn get_expected_digest_as_hex_string(caps: &Captures, chosen_regex: &Regex) -> Option { - // Unwraps are safe, ensured by regex. - let ck = caps.name("checksum").unwrap().as_bytes(); - - if chosen_regex.as_str() == ALGO_BASED_REGEX_BASE64 { - BASE64.decode(ck).map(hex::encode).ok() +fn get_expected_digest_as_hex_string(line_info: &LineInfo) -> Option> { + let ck = &line_info.checksum; + + if line_info.regex_str() == ALGO_BASED_REGEX_BASE64 { + BASE64 + .decode(ck.as_bytes()) + .map(hex::encode) + .map(Cow::Owned) + .ok() } else if ck.len() % 2 == 0 { - Some(str::from_utf8(ck).unwrap().to_string()) + Some(Cow::Borrowed(ck)) } else { // If the length of the digest is not a multiple of 2, then it // must be improperly formatted (1 hex digit is 2 characters) @@ -545,15 +591,14 @@ fn get_input_file(filename: &OsStr) -> UResult> { /// Extracts the algorithm name and length from the regex captures if the algo-based format is matched. fn identify_algo_name_and_length( - caps: &Captures, + line_info: &LineInfo, algo_name_input: Option<&str>, ) -> Option<(String, Option)> { // When the algo-based format is matched, extract details from regex captures - let algorithm = caps - .name("algo") - .map_or(String::new(), |m| { - String::from_utf8(m.as_bytes().into()).unwrap() - }) + let algorithm = line_info + .algo_name + .clone() + .unwrap_or_default() .to_lowercase(); // check if we are called with XXXsum (example: md5sum) but we detected a different algo parsing the file @@ -568,13 +613,9 @@ fn identify_algo_name_and_length( return None; } - let bits = caps.name("bits").map_or(Some(None), |m| { - let bits_value = String::from_utf8(m.as_bytes().into()) - .unwrap() - .parse::() - .unwrap(); - if bits_value % 8 == 0 { - Some(Some(bits_value / 8)) + let bits = line_info.algo_bitlen.map_or(Some(None), |bits| { + if bits % 8 == 0 { + Some(Some(bits / 8)) } else { None // Return None to signal a divisibility issue } @@ -597,6 +638,7 @@ fn process_checksum_line( cli_algo_name: Option<&str>, cli_algo_length: Option, opts: ChecksumOptions, + cached_regex: &mut Option, ) -> Result<(), LineCheckError> { let line_bytes = os_str_as_bytes(line)?; @@ -605,26 +647,30 @@ fn process_checksum_line( return Err(LineCheckError::Skipped); } - let (chosen_regex, is_algo_based_format) = - determine_regex(line).ok_or(LineCheckError::ImproperlyFormatted)?; + if let Some(line_info) = LineInfo::parse(line, cached_regex) { + // The cached regex ensures that when processing non-algo based regexes, + // its cannot be changed (can't have single and double space regexes + // used in the same file). + if cached_regex.is_none() && !line_info.is_algo_based() { + let _ = cached_regex.insert(line_info.regex.clone()); + } - if let Some(caps) = chosen_regex.captures(line_bytes) { - let mut filename_to_check = caps.name("filename").unwrap().as_bytes(); + let mut filename_to_check = line_info.filename.as_slice(); if filename_to_check.starts_with(b"*") && i == 0 - && chosen_regex.as_str() == SINGLE_SPACE_REGEX + && line_info.regex_str() == SINGLE_SPACE_REGEX { // Remove the leading asterisk if present - only for the first line filename_to_check = &filename_to_check[1..]; } - let expected_checksum = get_expected_digest_as_hex_string(&caps, &chosen_regex) + let expected_checksum = get_expected_digest_as_hex_string(&line_info) .ok_or(LineCheckError::ImproperlyFormatted)?; // If the algo_name is provided, we use it, otherwise we try to detect it - let (algo_name, length) = if is_algo_based_format { - identify_algo_name_and_length(&caps, cli_algo_name) + let (algo_name, length) = if line_info.is_algo_based() { + identify_algo_name_and_length(&line_info, cli_algo_name) .ok_or(LineCheckError::ImproperlyFormatted)? } else if let Some(a) = cli_algo_name { // When a specific algorithm name is input, use it and use the provided bits @@ -721,6 +767,10 @@ fn process_checksum_file( let reader = BufReader::new(file); let lines = read_os_string_lines(reader).collect::>(); + // cached_regex is used to ensure that several non algo-based checksum line + // will use the same regex. + let mut cached_regex = None; + for (i, line) in lines.iter().enumerate() { let line_result = process_checksum_line( filename_input, @@ -729,6 +779,7 @@ fn process_checksum_file( cli_algo_name, cli_algo_length, opts, + &mut cached_regex, ); // Match a first time to elude critical UErrors, and increment the total @@ -1149,52 +1200,75 @@ mod tests { } #[test] - fn test_determine_regex() { + fn test_line_info() { + let mut cached_regex = None; + // Test algo-based regex let line_algo_based = OsString::from("MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"); - let (regex, algo_based) = determine_regex(&line_algo_based).unwrap(); - assert!(algo_based); - assert!(regex.is_match(os_str_as_bytes(&line_algo_based).unwrap())); + let line_info = LineInfo::parse(&line_algo_based, &mut cached_regex).unwrap(); + assert!(line_info.is_algo_based()); + assert_eq!(line_info.algo_name.as_deref(), Some("MD5")); + assert!(line_info.algo_bit_len.is_none()); + assert_eq!(line_info.filename, b"example.txt"); + assert_eq!(line_info.checksum, "d41d8cd98f00b204e9800998ecf8427e"); + assert_eq!(line_info.regex_str(), ALGO_BASED_REGEX); + assert!(cached_regex.is_none()); // Test double-space regex let line_double_space = OsString::from("d41d8cd98f00b204e9800998ecf8427e example.txt"); - let (regex, algo_based) = determine_regex(&line_double_space).unwrap(); - assert!(!algo_based); - assert!(regex.is_match(os_str_as_bytes(&line_double_space).unwrap())); + let line_info = LineInfo::parse(&line_double_space, &mut cached_regex).unwrap(); + assert!(!line_info.is_algo_based()); + assert!(line_info.algo_name.is_none()); + assert!(line_info.algo_bit_len.is_none()); + assert_eq!(line_info.filename, b"example.txt"); + assert_eq!(line_info.checksum, "d41d8cd98f00b204e9800998ecf8427e"); + assert_eq!(line_info.regex_str(), DOUBLE_SPACE_REGEX); + assert!(cached_regex.is_some()); + + cached_regex = None; // Test single-space regex let line_single_space = OsString::from("d41d8cd98f00b204e9800998ecf8427e example.txt"); - let (regex, algo_based) = determine_regex(&line_single_space).unwrap(); - assert!(!algo_based); - assert!(regex.is_match(os_str_as_bytes(&line_single_space).unwrap())); + let line_info = LineInfo::parse(&line_single_space, &mut cached_regex).unwrap(); + assert!(!line_info.is_algo_based()); + assert!(line_info.algo_name.is_none()); + assert!(line_info.algo_bit_len.is_none()); + assert_eq!(line_info.filename, b"example.txt"); + assert_eq!(line_info.checksum, "d41d8cd98f00b204e9800998ecf8427e"); + assert_eq!(line_info.regex_str(), SINGLE_SPACE_REGEX); + assert!(cached_regex.is_some()); + + cached_regex = None; // Test invalid checksum line let line_invalid = OsString::from("invalid checksum line"); - assert!(determine_regex(&line_invalid).is_none()); + assert!(LineInfo::parse(&line_invalid, &mut cached_regex).is_none()); + assert!(cached_regex.is_none()); // Test leading space before checksum line let line_algo_based_leading_space = OsString::from(" MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"); - let res = determine_regex(&line_algo_based_leading_space); + let res = LineInfo::parse(&line_algo_based_leading_space, &mut cached_regex); assert!(res.is_some()); - assert_eq!(res.unwrap().0.as_str(), ALGO_BASED_REGEX); + assert_eq!(res.unwrap().regex_str(), ALGO_BASED_REGEX); + assert!(cached_regex.is_none()); // Test trailing space after checksum line (should fail) let line_algo_based_leading_space = OsString::from("MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e "); - let res = determine_regex(&line_algo_based_leading_space); + let res = LineInfo::parse(&line_algo_based_leading_space, &mut cached_regex); assert!(res.is_none()); + assert!(cached_regex.is_none()); } #[test] fn test_get_expected_digest() { - let re = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(); - let caps = re - .captures(b"SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") - .unwrap(); + let line = OsString::from("SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="); + let mut cached_regex = None; + let line_info = LineInfo::parse(&line, &mut cached_regex).unwrap(); - let result = get_expected_digest_as_hex_string(&caps, &re); + let result = get_expected_digest_as_hex_string(&line_info); assert_eq!( result.unwrap(), @@ -1204,12 +1278,12 @@ mod tests { #[test] fn test_get_expected_checksum_invalid() { - let re = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(); - let caps = re - .captures(b"SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU") - .unwrap(); + // The line misses a '=' at the end to be valid base64 + let line = OsString::from("SHA256 (empty) = 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU"); + let mut cached_regex = None; + let line_info = LineInfo::parse(&line, &mut cached_regex).unwrap(); - let result = get_expected_digest_as_hex_string(&caps, &re); + let result = get_expected_digest_as_hex_string(&line_info); assert!(result.is_none()); } From 65ddccbeb6a4e9bc45b0fc0184209a08387da411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 5 Dec 2024 18:19:50 +0100 Subject: [PATCH 115/351] checksum: avoid to recompute Regexps --- Cargo.lock | 1 + src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features/checksum.rs | 40 ++++++++++++++++--------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb09c5fd699..611cd240cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3573,6 +3573,7 @@ dependencies = [ "glob", "hex", "itertools", + "lazy_static", "libc", "md-5", "memchr", diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index b72a8ed717c..a4529f3a551 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -25,6 +25,7 @@ dns-lookup = { workspace = true, optional = true } dunce = { version = "1.0.4", optional = true } wild = "2.2.1" glob = { workspace = true } +lazy_static = "1.4.0" # * optional itertools = { workspace = true, optional = true } thiserror = { workspace = true, optional = true } diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index d575a4b3879..dec5fcf2172 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -5,6 +5,7 @@ // spell-checker:ignore anotherfile invalidchecksum regexes JWZG FFFD xffname prefixfilename use data_encoding::BASE64; +use lazy_static::lazy_static; use os_display::Quotable; use regex::bytes::{Match, Regex}; use std::{ @@ -428,6 +429,13 @@ const DOUBLE_SPACE_REGEX: &str = r"^(?P[a-fA-F0-9]+)\s{2}(?P // In this case, we ignore the * const SINGLE_SPACE_REGEX: &str = r"^(?P[a-fA-F0-9]+)\s(?P\*?(?-u:.*))$"; +lazy_static! { + static ref R_ALGO_BASED: Regex = Regex::new(ALGO_BASED_REGEX).unwrap(); + static ref R_DOUBLE_SPACE: Regex = Regex::new(DOUBLE_SPACE_REGEX).unwrap(); + static ref R_SINGLE_SPACE: Regex = Regex::new(SINGLE_SPACE_REGEX).unwrap(); + static ref R_ALGO_BASED_BASE_64: Regex = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(); +} + /// Hold the data extracted from a checksum line. struct LineInfo { algo_name: Option, @@ -435,28 +443,32 @@ struct LineInfo { checksum: String, filename: Vec, - regex: Regex, + regex: &'static Regex, } impl LineInfo { - fn parse(s: impl AsRef, cached_regex: &mut Option) -> Option { - let regexes = [ - (Regex::new(ALGO_BASED_REGEX).unwrap(), true), - (Regex::new(DOUBLE_SPACE_REGEX).unwrap(), false), - (Regex::new(SINGLE_SPACE_REGEX).unwrap(), false), - (Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(), false), + fn parse(s: impl AsRef, cached_regex: &mut Option<&'static Regex>) -> Option { + let regexes: &[(&'static Regex, bool)] = &[ + (&R_ALGO_BASED, true), + (&R_DOUBLE_SPACE, false), + (&R_SINGLE_SPACE, false), + (&R_ALGO_BASED_BASE_64, true), ]; let line_bytes = os_str_as_bytes(s.as_ref()).expect("UTF-8 decoding failed"); - for (regex, algo_based) in ®exes { + for (regex, algo_based) in regexes { if !regex.is_match(line_bytes) { continue; } - let mut r = regex.clone(); - if !algo_based && cached_regex.is_some() { - r = cached_regex.clone().unwrap(); + let mut r = *regex; + if !algo_based { + if cached_regex.is_some() { + r = cached_regex.unwrap(); + } else { + *cached_regex = Some(r); + } } if let Some(caps) = r.captures(line_bytes) { @@ -470,7 +482,7 @@ impl LineInfo { .map(|m| match_to_string(m).parse::().unwrap()), checksum: caps.name("checksum").map(match_to_string).unwrap(), filename: caps.name("filename").map(|m| m.as_bytes().into()).unwrap(), - regex: r.clone(), + regex: r, }); } } @@ -638,7 +650,7 @@ fn process_checksum_line( cli_algo_name: Option<&str>, cli_algo_length: Option, opts: ChecksumOptions, - cached_regex: &mut Option, + cached_regex: &mut Option<&'static Regex>, ) -> Result<(), LineCheckError> { let line_bytes = os_str_as_bytes(line)?; @@ -652,7 +664,7 @@ fn process_checksum_line( // its cannot be changed (can't have single and double space regexes // used in the same file). if cached_regex.is_none() && !line_info.is_algo_based() { - let _ = cached_regex.insert(line_info.regex.clone()); + let _ = cached_regex.insert(line_info.regex); } let mut filename_to_check = line_info.filename.as_slice(); From df16c1c65560b42f0a4471876f7e3203a6c05a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 5 Dec 2024 17:59:35 +0100 Subject: [PATCH 116/351] test(cksum): Add tests --- tests/by-util/test_cksum.rs | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index bf74de9cc59..6c171811226 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1443,7 +1443,7 @@ mod check_utf8 { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let filename: OsString = OsStringExt::from_vec(b"funky\xffname".to_vec()); - at.touch(&filename); + at.touch(filename); // Checksum match at.write_bytes("check", @@ -1544,7 +1544,6 @@ fn test_check_confusing_base64() { /// This test checks that when a file contains several checksum lines /// with different encoding, the decoding still works. -#[ignore = "not yet implemented"] #[test] fn test_check_mix_hex_base64() { let b64 = "BLAKE2b-128 (foo1.dat) = BBNuJPhdRwRlw9tm5Y7VbA=="; @@ -1769,3 +1768,81 @@ mod gnu_cksum_base64 { } } } + +/// The tests in this module check the behavior of cksum when given different +/// checksum formats and algorithms in the same file, while specifying an +/// algorithm on CLI or not. +mod format_mix { + use super::*; + + // First line is algo-based, second one is not + const INPUT_ALGO_NON_ALGO: &str = "\ + BLAKE2b (bar) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n\ + 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce foo"; + + // First line is non algo-based, second one is + const INPUT_NON_ALGO_ALGO: &str = "\ + 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce foo\n\ + BLAKE2b (bar) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"; + + /// Make a simple scene with foo and bar empty files + fn make_scene() -> TestScenario { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("foo"); + at.touch("bar"); + + scene + } + + #[test] + fn test_check_cli_algo_non_algo() { + let scene = make_scene(); + scene + .ucmd() + .arg("--check") + .arg("--algo=blake2b") + .pipe_in(INPUT_ALGO_NON_ALGO) + .succeeds() + .stdout_contains("bar: OK\nfoo: OK") + .no_stderr(); + } + + #[test] + fn test_check_cli_non_algo_algo() { + let scene = make_scene(); + scene + .ucmd() + .arg("--check") + .arg("--algo=blake2b") + .pipe_in(INPUT_NON_ALGO_ALGO) + .succeeds() + .stdout_contains("foo: OK\nbar: OK") + .no_stderr(); + } + + #[test] + fn test_check_algo_non_algo() { + let scene = make_scene(); + scene + .ucmd() + .arg("--check") + .pipe_in(INPUT_ALGO_NON_ALGO) + .succeeds() + .stdout_contains("bar: OK") + .stderr_contains("cksum: WARNING: 1 line is improperly formatted"); + } + + #[test] + fn test_check_non_algo_algo() { + let scene = make_scene(); + scene + .ucmd() + .arg("--check") + .pipe_in(INPUT_NON_ALGO_ALGO) + .succeeds() + .stdout_contains("bar: OK") + .stderr_contains("cksum: WARNING: 1 line is improperly formatted"); + } +} From 10a9b0bfbf237ed37b298ffcd932c6b31e17a18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 5 Dec 2024 18:22:52 +0100 Subject: [PATCH 117/351] checksum: split treatment of algo-based and non-algo based into separate functions --- src/uucore/src/lib/features/checksum.rs | 172 ++++++++++++++---------- 1 file changed, 102 insertions(+), 70 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index dec5fcf2172..0da814a76c1 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -464,6 +464,9 @@ impl LineInfo { let mut r = *regex; if !algo_based { + // The cached regex ensures that when processing non-algo based regexes, + // its cannot be changed (can't have single and double space regexes + // used in the same file). if cached_regex.is_some() { r = cached_regex.unwrap(); } else { @@ -636,13 +639,101 @@ fn identify_algo_name_and_length( Some((algorithm, bits)) } +/// Given a filename and an algorithm, compute the digest and compare it with +/// the expected one. +fn compute_and_check_digest_from_file( + filename: &[u8], + expected_checksum: &str, + mut algo: HashAlgorithm, + opts: ChecksumOptions, +) -> Result<(), LineCheckError> { + let (filename_to_check_unescaped, prefix) = unescape_filename(filename); + let real_filename_to_check = os_str_from_bytes(&filename_to_check_unescaped)?; + + // Open the input file + let file_to_check = get_file_to_check(&real_filename_to_check, opts)?; + let mut file_reader = BufReader::new(file_to_check); + + // Read the file and calculate the checksum + let create_fn = &mut algo.create_fn; + let mut digest = create_fn(); + let (calculated_checksum, _) = + digest_reader(&mut digest, &mut file_reader, opts.binary, algo.bits).unwrap(); + + // Do the checksum validation + let checksum_correct = expected_checksum == calculated_checksum; + print_file_report( + std::io::stdout(), + filename, + FileChecksumResult::from_bool(checksum_correct), + prefix, + opts, + ); + + if checksum_correct { + Ok(()) + } else { + Err(LineCheckError::DigestMismatch) + } +} + +/// Check a digest checksum with non-algo based pre-treatment. +fn process_algo_based_line( + line_info: &LineInfo, + cli_algo_name: Option<&str>, + opts: ChecksumOptions, +) -> Result<(), LineCheckError> { + let filename_to_check = line_info.filename.as_slice(); + let expected_checksum = + get_expected_digest_as_hex_string(line_info).ok_or(LineCheckError::ImproperlyFormatted)?; + + let (algo_name, algo_bitlen) = identify_algo_name_and_length(line_info, cli_algo_name) + .ok_or(LineCheckError::ImproperlyFormatted)?; + + let algo = detect_algo(&algo_name, algo_bitlen)?; + + compute_and_check_digest_from_file(filename_to_check, &expected_checksum, algo, opts) +} + +/// Check a digest checksum with non-algo based pre-treatment. +fn process_non_algo_based_line( + i: usize, + line_info: &LineInfo, + cli_algo_name: &str, + cli_algo_length: Option, + opts: ChecksumOptions, +) -> Result<(), LineCheckError> { + let mut filename_to_check = line_info.filename.as_slice(); + if filename_to_check.starts_with(b"*") && i == 0 && line_info.regex_str() == SINGLE_SPACE_REGEX + { + // Remove the leading asterisk if present - only for the first line + filename_to_check = &filename_to_check[1..]; + } + let expected_checksum = + get_expected_digest_as_hex_string(line_info).ok_or(LineCheckError::ImproperlyFormatted)?; + + // When a specific algorithm name is input, use it and use the provided bits + // except when dealing with blake2b, where we will detect the length + let (algo_name, algo_bitlen) = if cli_algo_name == ALGORITHM_OPTIONS_BLAKE2B { + // division by 2 converts the length of the Blake2b checksum from hexadecimal + // characters to bytes, as each byte is represented by two hexadecimal characters. + let length = Some(expected_checksum.len() / 2); + (ALGORITHM_OPTIONS_BLAKE2B.to_string(), length) + } else { + (cli_algo_name.to_lowercase(), cli_algo_length) + }; + + let algo = detect_algo(&algo_name, algo_bitlen)?; + + compute_and_check_digest_from_file(filename_to_check, &expected_checksum, algo, opts) +} + /// Parses a checksum line, detect the algorithm to use, read the file and produce /// its digest, and compare it to the expected value. /// /// Returns `Ok(bool)` if the comparison happened, bool indicates if the digest /// matched the expected. /// If the comparison didn't happen, return a `LineChecksumError`. -#[allow(clippy::too_many_arguments)] fn process_checksum_line( filename_input: &OsStr, line: &OsStr, @@ -654,82 +745,23 @@ fn process_checksum_line( ) -> Result<(), LineCheckError> { let line_bytes = os_str_as_bytes(line)?; - // early return on empty or commented lines. + // Early return on empty or commented lines. if line.is_empty() || line_bytes.starts_with(b"#") { return Err(LineCheckError::Skipped); } + // Use `LineInfo` to extract the data of a line. + // Then, depending on its format, apply a different pre-treatment. if let Some(line_info) = LineInfo::parse(line, cached_regex) { - // The cached regex ensures that when processing non-algo based regexes, - // its cannot be changed (can't have single and double space regexes - // used in the same file). - if cached_regex.is_none() && !line_info.is_algo_based() { - let _ = cached_regex.insert(line_info.regex); - } - - let mut filename_to_check = line_info.filename.as_slice(); - - if filename_to_check.starts_with(b"*") - && i == 0 - && line_info.regex_str() == SINGLE_SPACE_REGEX - { - // Remove the leading asterisk if present - only for the first line - filename_to_check = &filename_to_check[1..]; - } - - let expected_checksum = get_expected_digest_as_hex_string(&line_info) - .ok_or(LineCheckError::ImproperlyFormatted)?; - - // If the algo_name is provided, we use it, otherwise we try to detect it - let (algo_name, length) = if line_info.is_algo_based() { - identify_algo_name_and_length(&line_info, cli_algo_name) - .ok_or(LineCheckError::ImproperlyFormatted)? - } else if let Some(a) = cli_algo_name { - // When a specific algorithm name is input, use it and use the provided bits - // except when dealing with blake2b, where we will detect the length - if cli_algo_name == Some(ALGORITHM_OPTIONS_BLAKE2B) { - // division by 2 converts the length of the Blake2b checksum from hexadecimal - // characters to bytes, as each byte is represented by two hexadecimal characters. - let length = Some(expected_checksum.len() / 2); - (ALGORITHM_OPTIONS_BLAKE2B.to_string(), length) - } else { - (a.to_lowercase(), cli_algo_length) - } + if line_info.is_algo_based() { + process_algo_based_line(&line_info, cli_algo_name, opts) + } else if let Some(cli_algo) = cli_algo_name { + // If we match a non-algo based regex, we expect a cli argument + // to give us the algorithm to use + process_non_algo_based_line(i, &line_info, cli_algo, cli_algo_length, opts) } else { - // Default case if no algorithm is specified and non-algo based format is matched + // We have no clue of what algorithm to use return Err(LineCheckError::ImproperlyFormatted); - }; - - let mut algo = detect_algo(&algo_name, length)?; - - let (filename_to_check_unescaped, prefix) = unescape_filename(filename_to_check); - - let real_filename_to_check = os_str_from_bytes(&filename_to_check_unescaped)?; - - // manage the input file - let file_to_check = get_file_to_check(&real_filename_to_check, opts)?; - let mut file_reader = BufReader::new(file_to_check); - - // Read the file and calculate the checksum - let create_fn = &mut algo.create_fn; - let mut digest = create_fn(); - let (calculated_checksum, _) = - digest_reader(&mut digest, &mut file_reader, opts.binary, algo.bits).unwrap(); - - // Do the checksum validation - let checksum_correct = expected_checksum == calculated_checksum; - print_file_report( - std::io::stdout(), - filename_to_check, - FileChecksumResult::from_bool(checksum_correct), - prefix, - opts, - ); - - if checksum_correct { - Ok(()) - } else { - Err(LineCheckError::DigestMismatch) } } else { if opts.warn { From cd99102c9135837f05b8221a592e3b73d5affa1c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 05:16:32 +0000 Subject: [PATCH 118/351] chore(deps): update rust crate serde to v1.0.216 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75ab4c2edc5..781850626fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2088,9 +2088,9 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -2106,9 +2106,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", From 3900fa91ba5d987f9c1bcb2f3c5da61d75d3b981 Mon Sep 17 00:00:00 2001 From: Alexander Shirokov Date: Wed, 11 Dec 2024 14:28:46 +0100 Subject: [PATCH 119/351] seq:reduce memory allocation during prefix search This improvement eliminates extra memory allocations during the search for 0x/0X prefixes in number strings. --- src/uu/seq/src/numberparse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 80587f7136c..c8dec018041 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -341,7 +341,7 @@ impl FromStr for PreciseNumber { // Check if the string seems to be in hexadecimal format. // // May be 0x123 or -0x123, so the index `i` may be either 0 or 1. - if let Some(i) = s.to_lowercase().find("0x") { + if let Some(i) = s.find("0x").or_else(|| s.find("0X")) { if i <= 1 { return parse_hexadecimal(s); } From d62e2b500d3e591cf051efd2d68e7e4324d7bf5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:00:55 +0000 Subject: [PATCH 120/351] chore(deps): update rust crate bstr to v1.11.1 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 781850626fd..04e4db58e2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "regex-automata", From f4e5dc2e0fa8ae5e2a4ad23fa4dd0f0a44e12253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sat, 7 Dec 2024 02:29:54 +0100 Subject: [PATCH 121/351] checksum: use the blake2b length as an hint to check the correctness of the expected digest --- src/uucore/src/lib/features/checksum.rs | 95 +++++++++++++++++-------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 0da814a76c1..8de983490c3 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore anotherfile invalidchecksum regexes JWZG FFFD xffname prefixfilename +// spell-checker:ignore anotherfile invalidchecksum regexes JWZG FFFD xffname prefixfilename bytelen bitlen hexdigit use data_encoding::BASE64; use lazy_static::lazy_static; @@ -515,22 +515,43 @@ fn get_filename_for_output(filename: &OsStr, input_is_stdin: bool) -> String { } /// Extract the expected digest from the checksum string -fn get_expected_digest_as_hex_string(line_info: &LineInfo) -> Option> { +fn get_expected_digest_as_hex_string( + line_info: &LineInfo, + len_hint: Option, +) -> Option> { let ck = &line_info.checksum; - if line_info.regex_str() == ALGO_BASED_REGEX_BASE64 { - BASE64 - .decode(ck.as_bytes()) - .map(hex::encode) - .map(Cow::Owned) - .ok() - } else if ck.len() % 2 == 0 { - Some(Cow::Borrowed(ck)) - } else { + // TODO MSRV 1.82, replace `is_some_and` with `is_none_or` + // to improve readability. This closure returns True if a length hint provided + // and the argument isn't the same as the hint. + let against_hint = |len| len_hint.is_some_and(|l| l != len); + + if ck.len() % 2 != 0 { // If the length of the digest is not a multiple of 2, then it // must be improperly formatted (1 hex digit is 2 characters) - None + return None; } + + // If the digest can be decoded as hexadecimal AND it length match the + // one expected (in case it's given), just go with it. + if ck.as_bytes().iter().all(u8::is_ascii_hexdigit) && !against_hint(ck.len()) { + return Some(Cow::Borrowed(ck)); + } + + // If hexadecimal digest fails for any reason, interpret the digest as base 64. + BASE64 + .decode(ck.as_bytes()) // Decode the string as encoded base64 + .map(hex::encode) // Encode it back as hexadecimal + .map(Cow::::Owned) + .ok() + .and_then(|s| { + // Check the digest length + if !against_hint(s.len()) { + Some(s) + } else { + None + } + }) } /// Returns a reader that reads from the specified file, or from stdin if `filename_to_check` is "-". @@ -604,12 +625,11 @@ fn get_input_file(filename: &OsStr) -> UResult> { } } -/// Extracts the algorithm name and length from the regex captures if the algo-based format is matched. +/// Gets the algorithm name and length from the `LineInfo` if the algo-based format is matched. fn identify_algo_name_and_length( line_info: &LineInfo, algo_name_input: Option<&str>, ) -> Option<(String, Option)> { - // When the algo-based format is matched, extract details from regex captures let algorithm = line_info .algo_name .clone() @@ -628,15 +648,20 @@ fn identify_algo_name_and_length( return None; } - let bits = line_info.algo_bitlen.map_or(Some(None), |bits| { - if bits % 8 == 0 { - Some(Some(bits / 8)) - } else { - None // Return None to signal a divisibility issue + let bytes = if let Some(bitlen) = line_info.algo_bit_len { + if bitlen % 8 != 0 { + // The given length is wrong + return None; } - })?; + Some(bitlen / 8) + } else if algorithm == ALGORITHM_OPTIONS_BLAKE2B { + // Default length with BLAKE2b, + Some(64) + } else { + None + }; - Some((algorithm, bits)) + Some((algorithm, bytes)) } /// Given a filename and an algorithm, compute the digest and compare it with @@ -684,13 +709,21 @@ fn process_algo_based_line( opts: ChecksumOptions, ) -> Result<(), LineCheckError> { let filename_to_check = line_info.filename.as_slice(); - let expected_checksum = - get_expected_digest_as_hex_string(line_info).ok_or(LineCheckError::ImproperlyFormatted)?; - let (algo_name, algo_bitlen) = identify_algo_name_and_length(line_info, cli_algo_name) + let (algo_name, algo_byte_len) = identify_algo_name_and_length(line_info, cli_algo_name) .ok_or(LineCheckError::ImproperlyFormatted)?; - let algo = detect_algo(&algo_name, algo_bitlen)?; + // If the digest bitlen is known, we can check the format of the expected + // checksum with it. + let digest_char_length_hint = match (algo_name.as_str(), algo_byte_len) { + (ALGORITHM_OPTIONS_BLAKE2B, Some(bytelen)) => Some(bytelen * 2), + _ => None, + }; + + let expected_checksum = get_expected_digest_as_hex_string(line_info, digest_char_length_hint) + .ok_or(LineCheckError::ImproperlyFormatted)?; + + let algo = detect_algo(&algo_name, algo_byte_len)?; compute_and_check_digest_from_file(filename_to_check, &expected_checksum, algo, opts) } @@ -709,12 +742,12 @@ fn process_non_algo_based_line( // Remove the leading asterisk if present - only for the first line filename_to_check = &filename_to_check[1..]; } - let expected_checksum = - get_expected_digest_as_hex_string(line_info).ok_or(LineCheckError::ImproperlyFormatted)?; + let expected_checksum = get_expected_digest_as_hex_string(line_info, None) + .ok_or(LineCheckError::ImproperlyFormatted)?; // When a specific algorithm name is input, use it and use the provided bits // except when dealing with blake2b, where we will detect the length - let (algo_name, algo_bitlen) = if cli_algo_name == ALGORITHM_OPTIONS_BLAKE2B { + let (algo_name, algo_byte_len) = if cli_algo_name == ALGORITHM_OPTIONS_BLAKE2B { // division by 2 converts the length of the Blake2b checksum from hexadecimal // characters to bytes, as each byte is represented by two hexadecimal characters. let length = Some(expected_checksum.len() / 2); @@ -723,7 +756,7 @@ fn process_non_algo_based_line( (cli_algo_name.to_lowercase(), cli_algo_length) }; - let algo = detect_algo(&algo_name, algo_bitlen)?; + let algo = detect_algo(&algo_name, algo_byte_len)?; compute_and_check_digest_from_file(filename_to_check, &expected_checksum, algo, opts) } @@ -1312,7 +1345,7 @@ mod tests { let mut cached_regex = None; let line_info = LineInfo::parse(&line, &mut cached_regex).unwrap(); - let result = get_expected_digest_as_hex_string(&line_info); + let result = get_expected_digest_as_hex_string(&line_info, None); assert_eq!( result.unwrap(), @@ -1327,7 +1360,7 @@ mod tests { let mut cached_regex = None; let line_info = LineInfo::parse(&line, &mut cached_regex).unwrap(); - let result = get_expected_digest_as_hex_string(&line_info); + let result = get_expected_digest_as_hex_string(&line_info, None); assert!(result.is_none()); } From 567bbc5f3c8a242607881554728a420b68613792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sat, 7 Dec 2024 02:38:00 +0100 Subject: [PATCH 122/351] checksum: remove ALGO_BASED_REGEX (non base64) as its not useful anymore and introduce LineFormat struct --- src/uucore/src/lib/features/checksum.rs | 85 ++++++++++++++----------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index 8de983490c3..e19845f18e9 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -421,8 +421,7 @@ pub fn detect_algo(algo: &str, length: Option) -> UResult // algo must be uppercase or b (for blake2b) // 2. [* ] // 3. [*] (only one space) -const ALGO_BASED_REGEX: &str = r"^\s*\\?(?P(?:[A-Z0-9]+|BLAKE2b))(?:-(?P\d+))?\s?\((?P(?-u:.*))\)\s*=\s*(?P[a-fA-F0-9]+)$"; -const ALGO_BASED_REGEX_BASE64: &str = r"^\s*\\?(?P(?:[A-Z0-9]+|BLAKE2b))(?:-(?P\d+))?\s?\((?P(?-u:.*))\)\s*=\s*(?P[A-Za-z0-9+/]+={0,2})$"; +const ALGO_BASED_REGEX: &str = r"^\s*\\?(?P(?:[A-Z0-9]+|BLAKE2b))(?:-(?P\d+))?\s?\((?P(?-u:.*))\)\s*=\s*(?P[A-Za-z0-9+/]+={0,2})$"; const DOUBLE_SPACE_REGEX: &str = r"^(?P[a-fA-F0-9]+)\s{2}(?P(?-u:.*))$"; @@ -433,7 +432,23 @@ lazy_static! { static ref R_ALGO_BASED: Regex = Regex::new(ALGO_BASED_REGEX).unwrap(); static ref R_DOUBLE_SPACE: Regex = Regex::new(DOUBLE_SPACE_REGEX).unwrap(); static ref R_SINGLE_SPACE: Regex = Regex::new(SINGLE_SPACE_REGEX).unwrap(); - static ref R_ALGO_BASED_BASE_64: Regex = Regex::new(ALGO_BASED_REGEX_BASE64).unwrap(); +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum LineFormat { + AlgoBased, + SingleSpace, + DoubleSpace, +} + +impl LineFormat { + fn to_regex(self) -> &'static Regex { + match self { + LineFormat::AlgoBased => &R_ALGO_BASED, + LineFormat::SingleSpace => &R_SINGLE_SPACE, + LineFormat::DoubleSpace => &R_DOUBLE_SPACE, + } + } } /// Hold the data extracted from a checksum line. @@ -443,34 +458,41 @@ struct LineInfo { checksum: String, filename: Vec, - regex: &'static Regex, + format: LineFormat, } impl LineInfo { - fn parse(s: impl AsRef, cached_regex: &mut Option<&'static Regex>) -> Option { - let regexes: &[(&'static Regex, bool)] = &[ - (&R_ALGO_BASED, true), - (&R_DOUBLE_SPACE, false), - (&R_SINGLE_SPACE, false), - (&R_ALGO_BASED_BASE_64, true), + /// Returns a `LineInfo` parsed from a checksum line. + /// The function will run 3 regexes against the line and select the first one that matches + /// to populate the fields of the struct. + /// However, there is a catch to handle regarding the handling of `cached_regex`. + /// In case of non-algo-based regex, if `cached_regex` is Some, it must take the priority + /// over the detected regex. Otherwise, we must set it the the detected regex. + /// This specific behavior is emphasized by the test + /// `test_hashsum::test_check_md5sum_only_one_space`. + fn parse(s: impl AsRef, cached_regex: &mut Option) -> Option { + let regexes: &[(&'static Regex, LineFormat)] = &[ + (&R_ALGO_BASED, LineFormat::AlgoBased), + (&R_DOUBLE_SPACE, LineFormat::DoubleSpace), + (&R_SINGLE_SPACE, LineFormat::SingleSpace), ]; let line_bytes = os_str_as_bytes(s.as_ref()).expect("UTF-8 decoding failed"); - for (regex, algo_based) in regexes { + for (regex, format) in regexes { if !regex.is_match(line_bytes) { continue; } let mut r = *regex; - if !algo_based { + if *format != LineFormat::AlgoBased { // The cached regex ensures that when processing non-algo based regexes, - // its cannot be changed (can't have single and double space regexes + // it cannot be changed (can't have single and double space regexes // used in the same file). if cached_regex.is_some() { - r = cached_regex.unwrap(); + r = cached_regex.unwrap().to_regex(); } else { - *cached_regex = Some(r); + *cached_regex = Some(*format); } } @@ -485,23 +507,13 @@ impl LineInfo { .map(|m| match_to_string(m).parse::().unwrap()), checksum: caps.name("checksum").map(match_to_string).unwrap(), filename: caps.name("filename").map(|m| m.as_bytes().into()).unwrap(), - regex: r, + format: *format, }); } } None } - - #[inline] - fn is_algo_based(&self) -> bool { - self.algo_name.is_some() - } - - #[inline] - fn regex_str(&self) -> &str { - self.regex.as_str() - } } fn get_filename_for_output(filename: &OsStr, input_is_stdin: bool) -> String { @@ -730,14 +742,16 @@ fn process_algo_based_line( /// Check a digest checksum with non-algo based pre-treatment. fn process_non_algo_based_line( - i: usize, + line_number: usize, line_info: &LineInfo, cli_algo_name: &str, cli_algo_length: Option, opts: ChecksumOptions, ) -> Result<(), LineCheckError> { let mut filename_to_check = line_info.filename.as_slice(); - if filename_to_check.starts_with(b"*") && i == 0 && line_info.regex_str() == SINGLE_SPACE_REGEX + if filename_to_check.starts_with(b"*") + && line_number == 0 + && line_info.format == LineFormat::SingleSpace { // Remove the leading asterisk if present - only for the first line filename_to_check = &filename_to_check[1..]; @@ -774,7 +788,7 @@ fn process_checksum_line( cli_algo_name: Option<&str>, cli_algo_length: Option, opts: ChecksumOptions, - cached_regex: &mut Option<&'static Regex>, + cached_regex: &mut Option, ) -> Result<(), LineCheckError> { let line_bytes = os_str_as_bytes(line)?; @@ -786,7 +800,7 @@ fn process_checksum_line( // Use `LineInfo` to extract the data of a line. // Then, depending on its format, apply a different pre-treatment. if let Some(line_info) = LineInfo::parse(line, cached_regex) { - if line_info.is_algo_based() { + if line_info.format == LineFormat::AlgoBased { process_algo_based_line(&line_info, cli_algo_name, opts) } else if let Some(cli_algo) = cli_algo_name { // If we match a non-algo based regex, we expect a cli argument @@ -1284,23 +1298,21 @@ mod tests { let line_algo_based = OsString::from("MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"); let line_info = LineInfo::parse(&line_algo_based, &mut cached_regex).unwrap(); - assert!(line_info.is_algo_based()); assert_eq!(line_info.algo_name.as_deref(), Some("MD5")); assert!(line_info.algo_bit_len.is_none()); assert_eq!(line_info.filename, b"example.txt"); assert_eq!(line_info.checksum, "d41d8cd98f00b204e9800998ecf8427e"); - assert_eq!(line_info.regex_str(), ALGO_BASED_REGEX); + assert_eq!(line_info.format, LineFormat::AlgoBased); assert!(cached_regex.is_none()); // Test double-space regex let line_double_space = OsString::from("d41d8cd98f00b204e9800998ecf8427e example.txt"); let line_info = LineInfo::parse(&line_double_space, &mut cached_regex).unwrap(); - assert!(!line_info.is_algo_based()); assert!(line_info.algo_name.is_none()); assert!(line_info.algo_bit_len.is_none()); assert_eq!(line_info.filename, b"example.txt"); assert_eq!(line_info.checksum, "d41d8cd98f00b204e9800998ecf8427e"); - assert_eq!(line_info.regex_str(), DOUBLE_SPACE_REGEX); + assert_eq!(line_info.format, LineFormat::DoubleSpace); assert!(cached_regex.is_some()); cached_regex = None; @@ -1308,12 +1320,11 @@ mod tests { // Test single-space regex let line_single_space = OsString::from("d41d8cd98f00b204e9800998ecf8427e example.txt"); let line_info = LineInfo::parse(&line_single_space, &mut cached_regex).unwrap(); - assert!(!line_info.is_algo_based()); assert!(line_info.algo_name.is_none()); assert!(line_info.algo_bit_len.is_none()); assert_eq!(line_info.filename, b"example.txt"); assert_eq!(line_info.checksum, "d41d8cd98f00b204e9800998ecf8427e"); - assert_eq!(line_info.regex_str(), SINGLE_SPACE_REGEX); + assert_eq!(line_info.format, LineFormat::SingleSpace); assert!(cached_regex.is_some()); cached_regex = None; @@ -1328,7 +1339,7 @@ mod tests { OsString::from(" MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"); let res = LineInfo::parse(&line_algo_based_leading_space, &mut cached_regex); assert!(res.is_some()); - assert_eq!(res.unwrap().regex_str(), ALGO_BASED_REGEX); + assert_eq!(line_info.format, LineFormat::AlgoBased); assert!(cached_regex.is_none()); // Test trailing space after checksum line (should fail) From 958222a07ccc25940a8aa7304bfe22cd41fd904b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sat, 7 Dec 2024 02:30:30 +0100 Subject: [PATCH 123/351] test(cksum): un-ignore tests that are now implemented --- src/uucore/src/lib/features/checksum.rs | 3 +-- tests/by-util/test_cksum.rs | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/checksum.rs b/src/uucore/src/lib/features/checksum.rs index e19845f18e9..0b3e4e24938 100644 --- a/src/uucore/src/lib/features/checksum.rs +++ b/src/uucore/src/lib/features/checksum.rs @@ -1337,8 +1337,7 @@ mod tests { // Test leading space before checksum line let line_algo_based_leading_space = OsString::from(" MD5 (example.txt) = d41d8cd98f00b204e9800998ecf8427e"); - let res = LineInfo::parse(&line_algo_based_leading_space, &mut cached_regex); - assert!(res.is_some()); + let line_info = LineInfo::parse(&line_algo_based_leading_space, &mut cached_regex).unwrap(); assert_eq!(line_info.format, LineFormat::AlgoBased); assert!(cached_regex.is_none()); diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 6c171811226..2efc78b96f3 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -1480,7 +1480,6 @@ mod check_utf8 { } } -#[ignore = "not yet implemented"] #[test] fn test_check_blake_length_guess() { let correct_lines = [ @@ -1523,7 +1522,6 @@ fn test_check_blake_length_guess() { .stderr_contains("foo.sums: no properly formatted checksum lines found"); } -#[ignore = "not yet implemented"] #[test] fn test_check_confusing_base64() { let cksum = "BLAKE2b-48 (foo.dat) = fc1f97C4"; From a5446b7bd02d0c857091b6ac653ffa774f25f2c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:18:04 +0000 Subject: [PATCH 124/351] fix(deps): update rust crate lazy_static to v1.5.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10be1cadb53..dda7b9746f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1284,9 +1284,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" From 91ea45956f9ebac6715b1644740fb14bb1af6cef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:08:04 +0000 Subject: [PATCH 125/351] chore(deps): update mozilla-actions/sccache-action action to v0.0.7 --- .github/workflows/CICD.yml | 18 +++++++++--------- .github/workflows/code-quality.yml | 2 +- .github/workflows/freebsd.yml | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 1875c512deb..56418dd6e04 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -112,7 +112,7 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Initialize workflow variables id: vars shell: bash @@ -166,7 +166,7 @@ jobs: - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Initialize workflow variables id: vars shell: bash @@ -254,7 +254,7 @@ jobs: - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: "`make build`" shell: bash run: | @@ -308,7 +308,7 @@ jobs: - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Test run: cargo nextest run --hide-progress-bar --profile ci --features ${{ matrix.job.features }} env: @@ -335,7 +335,7 @@ jobs: - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Test run: cargo nextest run --hide-progress-bar --profile ci --features ${{ matrix.job.features }} env: @@ -358,7 +358,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Install dependencies shell: bash run: | @@ -493,7 +493,7 @@ jobs: with: key: "${{ matrix.job.os }}_${{ matrix.job.target }}" - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Initialize workflow variables id: vars shell: bash @@ -782,7 +782,7 @@ jobs: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Install/setup prerequisites shell: bash run: | @@ -866,7 +866,7 @@ jobs: components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Build coreutils as multiple binaries shell: bash run: | diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index cd1334c2e35..8e7db5fc3ed 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -81,7 +81,7 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Initialize workflow variables id: vars shell: bash diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index b31ac335328..1ff0ba04780 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -37,7 +37,7 @@ jobs: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test uses: vmactions/freebsd-vm@v1.1.5 with: @@ -129,7 +129,7 @@ jobs: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.6 + uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test uses: vmactions/freebsd-vm@v1.1.5 with: From bbea4ba72adee40ced54e3035f30a340ec293bbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 23:14:28 +0000 Subject: [PATCH 126/351] chore(deps): update rust crate thiserror to v2.0.7 --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dda7b9746f5..e4ca1930984 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2319,11 +2319,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.7", ] [[package]] @@ -2339,9 +2339,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" dependencies = [ "proc-macro2", "quote", @@ -2540,7 +2540,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.6", + "thiserror 2.0.7", "uucore", ] @@ -2552,7 +2552,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.6", + "thiserror 2.0.7", "uucore", ] @@ -2629,7 +2629,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.6", + "thiserror 2.0.7", "uucore", ] @@ -3145,7 +3145,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.6", + "thiserror 2.0.7", "uucore", ] @@ -3424,7 +3424,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.6", + "thiserror 2.0.7", "utmp-classic", "uucore", ] @@ -3455,7 +3455,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.6", + "thiserror 2.0.7", "unicode-width 0.2.0", "uucore", ] @@ -3517,7 +3517,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.6", + "thiserror 2.0.7", "time", "uucore_procs", "walkdir", From 934cbb38f697eb6fd7c6565934cceee7a326deeb Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 12 Dec 2024 10:22:30 +0100 Subject: [PATCH 127/351] Bump clap from 4.4.2 to 4.5.23 --- Cargo.lock | 41 ++++++++++++++++++++++++----------------- Cargo.toml | 2 +- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4ca1930984..44534dc159e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,23 +61,24 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -99,12 +100,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -326,24 +327,24 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.2" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.2" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", - "terminal_size 0.2.6", + "terminal_size 0.4.1", ] [[package]] @@ -357,9 +358,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clap_mangen" @@ -1229,6 +1230,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.13.0" @@ -2231,9 +2238,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" @@ -3673,7 +3680,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a4f8462e4f1..79e6dff4018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -276,7 +276,7 @@ chrono = { version = "0.4.38", default-features = false, features = [ "alloc", "clock", ] } -clap = { version = "4.4", features = ["wrap_help", "cargo"] } +clap = { version = "4.5", features = ["wrap_help", "cargo"] } clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" From ee0426e3f36a420334382477ca4f205d4fb0d3c9 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 12 Dec 2024 15:37:36 +0100 Subject: [PATCH 128/351] seq: use allow_hyphen_values instead of allow_negative_numbers because clap removed support for "exotic" negative numbers like -.1 --- src/uu/seq/src/seq.rs | 31 +++++++++++++++++++++++++++++-- tests/by-util/test_seq.rs | 4 ++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 96ae83ba0a6..e14ba35a949 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -3,6 +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) extendedbigdecimal numberparse +use std::ffi::OsString; use std::io::{stdout, ErrorKind, Write}; use clap::{crate_version, Arg, ArgAction, Command}; @@ -47,9 +48,33 @@ struct SeqOptions<'a> { /// The elements are (first, increment, last). type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal); +// Turn short args with attached value, for example "-s,", into two args "-s" and "," to make +// them work with clap. +fn split_short_args_with_value(args: impl uucore::Args) -> impl uucore::Args { + let mut v: Vec = Vec::new(); + + for arg in args { + let bytes = arg.as_encoded_bytes(); + + if bytes.len() > 2 + && (bytes.starts_with(b"-f") || bytes.starts_with(b"-s") || bytes.starts_with(b"-t")) + { + let (short_arg, value) = bytes.split_at(2); + // SAFETY: + // Both `short_arg` and `value` only contain content that originated from `OsStr::as_encoded_bytes` + v.push(unsafe { OsString::from_encoded_bytes_unchecked(short_arg.to_vec()) }); + v.push(unsafe { OsString::from_encoded_bytes_unchecked(value.to_vec()) }); + } else { + v.push(arg); + } + } + + v.into_iter() +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; + let matches = uu_app().try_get_matches_from(split_short_args_with_value(args))?; let numbers_option = matches.get_many::(ARG_NUMBERS); @@ -138,7 +163,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app() -> Command { Command::new(uucore::util_name()) .trailing_var_arg(true) - .allow_negative_numbers(true) .infer_long_args(true) .version(crate_version!()) .about(ABOUT) @@ -169,7 +193,10 @@ pub fn uu_app() -> Command { .help("use printf style floating-point FORMAT"), ) .arg( + // we use allow_hyphen_values instead of allow_negative_numbers because clap removed + // the support for "exotic" negative numbers like -.1 (see https://github.com/clap-rs/clap/discussions/5837) Arg::new(ARG_NUMBERS) + .allow_hyphen_values(true) .action(ArgAction::Append) .num_args(1..=3), ) diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 8f33c3aa7c8..96460cf5fdc 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -48,12 +48,12 @@ fn test_hex_rejects_sign_after_identifier() { .args(&["-0x-123ABC"]) .fails() .no_stdout() - .stderr_contains("unexpected argument '-0' found"); + .usage_error("invalid floating point argument: '-0x-123ABC'"); new_ucmd!() .args(&["-0x+123ABC"]) .fails() .no_stdout() - .stderr_contains("unexpected argument '-0' found"); + .usage_error("invalid floating point argument: '-0x+123ABC'"); } #[test] From def66f335ca9b1fb7c866cb50b18b881a588b379 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 13 Dec 2024 11:07:48 +0100 Subject: [PATCH 129/351] build-gnu.sh: adapt "du" 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 e33e64429bf..974e188f414 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -314,7 +314,7 @@ sed -i -e "s|mv: cannot overwrite 'a/t': Directory not empty|mv: cannot move 'b/ # disable these test cases sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl -sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh +sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "s/Try 'du --help' for more information./\nFor more information, try '--help'./" tests/du/threshold.sh # Remove the extra output check sed -i -e "s|Try '\$prog --help' for more information.\\\n||" tests/du/files0-from.pl From a8ad6d92f1cb052d503bf10936fd3a54cbf5e046 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 13 Dec 2024 11:37:52 +0100 Subject: [PATCH 130/351] factor: adapt message in patch to clap change --- util/gnu-patches/tests_factor_factor.pl.patch | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/util/gnu-patches/tests_factor_factor.pl.patch b/util/gnu-patches/tests_factor_factor.pl.patch index fc8b988fea5..731abcc9117 100644 --- a/util/gnu-patches/tests_factor_factor.pl.patch +++ b/util/gnu-patches/tests_factor_factor.pl.patch @@ -1,8 +1,8 @@ diff --git a/tests/factor/factor.pl b/tests/factor/factor.pl -index 6e612e418..f19c06ca0 100755 +index b1406c266..3d97cd6a5 100755 --- a/tests/factor/factor.pl +++ b/tests/factor/factor.pl -@@ -61,12 +61,13 @@ my @Tests = +@@ -61,12 +61,14 @@ my @Tests = # Map newer glibc diagnostic to expected. # Also map OpenBSD 5.1's "unknown option" to expected "invalid option". {ERR_SUBST => q!s/'1'/1/;s/unknown/invalid/!}, @@ -10,7 +10,8 @@ index 6e612e418..f19c06ca0 100755 - . "Try '$prog --help' for more information.\n"}, + {ERR => "error: unexpected argument '-1' found\n\n" + . " tip: to pass '-1' as a value, use '-- -1'\n\n" -+ . "Usage: factor [OPTION]... [NUMBER]...\n"}, ++ . "Usage: factor [OPTION]... [NUMBER]...\n\n" ++ . "For more information, try '--help'.\n"}, {EXIT => 1}], ['cont', 'a 4', {OUT => "4: 2 2\n"}, From 5b45750115b132e5db9193b8234712f58d4d3e1f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 16:06:42 +0000 Subject: [PATCH 131/351] chore(deps): update rust crate clap_complete to v4.5.38 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44534dc159e..8397192b3f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,9 +349,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.0" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" dependencies = [ "clap", ] From 6bdcad32da40e7fe2a1b159c72a455c1452a4baa Mon Sep 17 00:00:00 2001 From: Karl McDowall Date: Wed, 11 Dec 2024 18:27:05 -0700 Subject: [PATCH 132/351] sort: Rework merge batching logic Fix bug #6944 Rework the way batching is done with sort such that it doesn't open more input files than necessary. Previously, the code would always open one extra input file which causes problems in ulimit scenarios. Add additional test case. --- src/uu/sort/src/ext_sort.rs | 4 +- src/uu/sort/src/merge.rs | 77 +++++++++++++++++++------------------ src/uu/sort/src/sort.rs | 3 +- tests/by-util/test_sort.rs | 27 ++++++++++++- 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 18309881229..57e434e99b2 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -98,12 +98,12 @@ fn reader_writer< )?; match read_result { ReadResult::WroteChunksToFile { tmp_files } => { - let merger = merge::merge_with_file_limit::<_, _, Tmp>( + merge::merge_with_file_limit::<_, _, Tmp>( tmp_files.into_iter().map(|c| c.reopen()), settings, + output, tmp_dir, )?; - merger.write_all(settings, output)?; } ReadResult::SortedSingleChunk(chunk) => { if settings.unique { diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index d6872ec80e3..300733d1e36 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -25,7 +25,6 @@ use std::{ }; use compare::Compare; -use itertools::Itertools; use uucore::error::UResult; use crate::{ @@ -67,58 +66,63 @@ fn replace_output_file_in_input_files( /// /// If `settings.merge_batch_size` is greater than the length of `files`, intermediate files will be used. /// If `settings.compress_prog` is `Some`, intermediate files will be compressed with it. -pub fn merge<'a>( +pub fn merge( files: &mut [OsString], - settings: &'a GlobalSettings, - output: Option<&str>, + settings: &GlobalSettings, + output: Output, tmp_dir: &mut TmpDirWrapper, -) -> UResult> { - replace_output_file_in_input_files(files, output, tmp_dir)?; +) -> UResult<()> { + replace_output_file_in_input_files(files, output.as_output_name(), tmp_dir)?; + let files = files + .iter() + .map(|file| open(file).map(|file| PlainMergeInput { inner: file })); if settings.compress_prog.is_none() { - merge_with_file_limit::<_, _, WriteablePlainTmpFile>( - files - .iter() - .map(|file| open(file).map(|file| PlainMergeInput { inner: file })), - settings, - tmp_dir, - ) + merge_with_file_limit::<_, _, WriteablePlainTmpFile>(files, settings, output, tmp_dir) } else { - merge_with_file_limit::<_, _, WriteableCompressedTmpFile>( - files - .iter() - .map(|file| open(file).map(|file| PlainMergeInput { inner: file })), - settings, - tmp_dir, - ) + merge_with_file_limit::<_, _, WriteableCompressedTmpFile>(files, settings, output, tmp_dir) } } // Merge already sorted `MergeInput`s. pub fn merge_with_file_limit< - 'a, M: MergeInput + 'static, F: ExactSizeIterator>, Tmp: WriteableTmpFile + 'static, >( files: F, - settings: &'a GlobalSettings, + settings: &GlobalSettings, + output: Output, tmp_dir: &mut TmpDirWrapper, -) -> UResult> { - if files.len() > settings.merge_batch_size { - let mut remaining_files = files.len(); - let batches = files.chunks(settings.merge_batch_size); - let mut batches = batches.into_iter(); +) -> UResult<()> { + if files.len() <= settings.merge_batch_size { + let merger = merge_without_limit(files, settings); + merger?.write_all(settings, output) + } else { let mut temporary_files = vec![]; - while remaining_files != 0 { - // Work around the fact that `Chunks` is not an `ExactSizeIterator`. - remaining_files = remaining_files.saturating_sub(settings.merge_batch_size); - let merger = merge_without_limit(batches.next().unwrap(), settings)?; + let mut batch = vec![]; + for file in files { + batch.push(file); + if batch.len() >= settings.merge_batch_size { + assert_eq!(batch.len(), settings.merge_batch_size); + let merger = merge_without_limit(batch.into_iter(), settings)?; + batch = vec![]; + + let mut tmp_file = + Tmp::create(tmp_dir.next_file()?, settings.compress_prog.as_deref())?; + merger.write_all_to(settings, tmp_file.as_write())?; + temporary_files.push(tmp_file.finished_writing()?); + } + } + // Merge any remaining files that didn't get merged in a full batch above. + if !batch.is_empty() { + assert!(batch.len() < settings.merge_batch_size); + let merger = merge_without_limit(batch.into_iter(), settings)?; + let mut tmp_file = Tmp::create(tmp_dir.next_file()?, settings.compress_prog.as_deref())?; merger.write_all_to(settings, tmp_file.as_write())?; temporary_files.push(tmp_file.finished_writing()?); } - assert!(batches.next().is_none()); merge_with_file_limit::<_, _, Tmp>( temporary_files .into_iter() @@ -127,10 +131,9 @@ pub fn merge_with_file_limit< dyn FnMut(Tmp::Closed) -> UResult<::Reopened>, >), settings, + output, tmp_dir, ) - } else { - merge_without_limit(files, settings) } } @@ -260,7 +263,7 @@ struct PreviousLine { } /// Merges files together. This is **not** an iterator because of lifetime problems. -pub struct FileMerger<'a> { +struct FileMerger<'a> { heap: binary_heap_plus::BinaryHeap>, request_sender: Sender<(usize, RecycledChunk)>, prev: Option, @@ -269,12 +272,12 @@ pub struct FileMerger<'a> { impl FileMerger<'_> { /// Write the merged contents to the output file. - pub fn write_all(self, settings: &GlobalSettings, output: Output) -> UResult<()> { + fn write_all(self, settings: &GlobalSettings, output: Output) -> UResult<()> { let mut out = output.into_write(); self.write_all_to(settings, &mut out) } - pub fn write_all_to(mut self, settings: &GlobalSettings, out: &mut impl Write) -> UResult<()> { + fn write_all_to(mut self, settings: &GlobalSettings, out: &mut impl Write) -> UResult<()> { while self.write_next(settings, out) {} drop(self.request_sender); self.reader_join_handle.join().unwrap() diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index c2e752bdf6a..8b6fcbb2514 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1567,8 +1567,7 @@ fn exec( tmp_dir: &mut TmpDirWrapper, ) -> UResult<()> { if settings.merge { - let file_merger = merge::merge(files, settings, output.as_output_name(), tmp_dir)?; - file_merger.write_all(settings, output) + merge::merge(files, settings, output, tmp_dir) } else if settings.check { if files.len() > 1 { Err(UUsageError::new(2, "only one file allowed with -c")) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 97bfc6a74d0..62aa07dae5d 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.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 (words) ints +// spell-checker:ignore (words) ints (linux) NOFILE #![allow(clippy::cast_possible_wrap)] use std::time::Duration; @@ -1084,6 +1084,31 @@ fn test_merge_batch_size() { .stdout_only_fixture("merge_ints_interleaved.expected"); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_merge_batch_size_with_limit() { + use rlimit::Resource; + // Currently need... + // 3 descriptors for stdin, stdout, stderr + // 2 descriptors for CTRL+C handling logic (to be reworked at some point) + // 2 descriptors for the input files (i.e. batch-size of 2). + let limit_fd = 3 + 2 + 2; + TestScenario::new(util_name!()) + .ucmd() + .limit(Resource::NOFILE, limit_fd, limit_fd) + .arg("--batch-size=2") + .arg("-m") + .arg("--unique") + .arg("merge_ints_interleaved_1.txt") + .arg("merge_ints_interleaved_2.txt") + .arg("merge_ints_interleaved_3.txt") + .arg("merge_ints_interleaved_3.txt") + .arg("merge_ints_interleaved_2.txt") + .arg("merge_ints_interleaved_1.txt") + .succeeds() + .stdout_only_fixture("merge_ints_interleaved.expected"); +} + #[test] fn test_sigpipe_panic() { let mut cmd = new_ucmd!(); From a7b9737490e782943ce65ceb6fded6555f2c0567 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 16 Dec 2024 09:36:45 +0100 Subject: [PATCH 133/351] GNUmakefile: remove "sleep" from UNIX_PROGS because it's already in PROGS --- GNUmakefile | 1 - 1 file changed, 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index 0b4f2d04ce7..af73a10f4d0 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -147,7 +147,6 @@ UNIX_PROGS := \ nohup \ pathchk \ pinky \ - sleep \ stat \ stdbuf \ timeout \ From 6755956bc4bce6c154e1b821f400d43eeb9800a7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 16 Dec 2024 15:37:29 +0100 Subject: [PATCH 134/351] cut.pl: adjust to our messages as they are better (#6921) * cut.pl: adjust to our messages as they are better but we still have some differences on this test * cut: add some missing line return when needed * cut: add failing tests covered by cut.pl * Remove dup test * cut: add spell-checker:ignore line to test --------- Co-authored-by: Daniel Hofstetter --- .../cspell.dictionaries/jargon.wordlist.txt | 2 + src/uu/cut/src/cut.rs | 9 ++- tests/by-util/test_cut.rs | 33 ++++++++- util/gnu-patches/tests_cut_error_msg.patch | 72 +++++++++++++++++++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 util/gnu-patches/tests_cut_error_msg.patch diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index c2e01f508e3..6dd5483c6c1 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -157,6 +157,8 @@ retval subdir val vals +inval +nofield # * clippy uninlined diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 25bb7333010..421b35eacea 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -131,8 +131,9 @@ fn cut_fields_explicit_out_delim( if delim_search.peek().is_none() { if !only_delimited { + // Always write the entire line, even if it doesn't end with `newline_char` out.write_all(line)?; - if line[line.len() - 1] != newline_char { + if line.is_empty() || line[line.len() - 1] != newline_char { out.write_all(&[newline_char])?; } } @@ -213,8 +214,12 @@ fn cut_fields_implicit_out_delim( let mut print_delim = false; if delim_search.peek().is_none() { - if !only_delimited && line[line.len() - 1] == newline_char { + if !only_delimited { + // Always write the entire line, even if it doesn't end with `newline_char` out.write_all(line)?; + if line.is_empty() || line[line.len() - 1] != newline_char { + out.write_all(&[newline_char])?; + } } return Ok(true); diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 6b376b0ca0d..7d6009a30eb 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -2,6 +2,9 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. + +// spell-checker:ignore defg + use crate::common::util::TestScenario; static INPUT: &str = "lists.txt"; @@ -288,7 +291,7 @@ fn test_newline_delimited() { .args(&["-f", "1", "-d", "\n"]) .pipe_in("a:1\nb:") .succeeds() - .stdout_only_bytes("a:1\n"); + .stdout_only_bytes("a:1\nb:\n"); } #[test] @@ -329,3 +332,31 @@ fn test_8bit_non_utf8_delimiter() { .succeeds() .stdout_check(|out| out == "b_c\n".as_bytes()); } + +#[test] +fn test_newline_preservation_with_f1_option() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("1", "a\nb"); + let expected = "a\nb\n"; + ucmd.args(&["-f1-", "1"]).succeeds().stdout_is(expected); +} + +#[ignore = "Not yet implemented"] +#[test] +fn test_output_delimiter_with_character_ranges() { + new_ucmd!() + .args(&["-c2-3,4-", "--output-delim=:"]) + .pipe_in("abcdefg\n") + .succeeds() + .stdout_only("bc:defg\n"); +} + +#[ignore = "Not yet implemented"] +#[test] +fn test_output_delimiter_with_adjacent_ranges() { + new_ucmd!() + .args(&["-b1-2,3-4", "--output-d=:"]) + .pipe_in("abcd\n") + .succeeds() + .stdout_only("ab:cd\n"); +} diff --git a/util/gnu-patches/tests_cut_error_msg.patch b/util/gnu-patches/tests_cut_error_msg.patch new file mode 100644 index 00000000000..3f57d204813 --- /dev/null +++ b/util/gnu-patches/tests_cut_error_msg.patch @@ -0,0 +1,72 @@ +diff --git a/tests/cut/cut.pl b/tests/cut/cut.pl +index 1670db02e..ed633792a 100755 +--- a/tests/cut/cut.pl ++++ b/tests/cut/cut.pl +@@ -29,13 +29,15 @@ my $mb_locale = $ENV{LOCALE_FR_UTF8}; + + my $prog = 'cut'; + my $try = "Try '$prog --help' for more information.\n"; +-my $from_field1 = "$prog: fields are numbered from 1\n$try"; +-my $from_pos1 = "$prog: byte/character positions are numbered from 1\n$try"; +-my $inval_fld = "$prog: invalid field range\n$try"; +-my $inval_pos = "$prog: invalid byte or character range\n$try"; +-my $no_endpoint = "$prog: invalid range with no endpoint: -\n$try"; +-my $nofield = "$prog: an input delimiter may be specified only when " . +- "operating on fields\n$try"; ++my $from_field1 = "$prog: range '' was invalid: failed to parse range\n"; ++my $from_field_0 = "$prog: range '0' was invalid: fields and positions are numbered from 1\n"; ++my $from_field_0_dash = "$prog: range '0-' was invalid: fields and positions are numbered from 1\n"; ++my $from_field_0_2 = "$prog: range '0-2' was invalid: fields and positions are numbered from 1\n"; ++my $from_pos1 = "$prog: range '' was invalid: failed to parse range\n"; ++my $inval_fld = "$prog: range '--' was invalid: failed to parse range\n"; ++my $inval_pos = "$prog: range '--' was invalid: failed to parse range\n"; ++my $no_endpoint = "$prog: range '-' was invalid: invalid range with no endpoint\n"; ++my $nofield = "$prog: invalid input: The '--delimiter' ('-d') option only usable if printing a sequence of fields\n"; + + my @Tests = + ( +@@ -44,16 +46,16 @@ my @Tests = + + # This failed (as it should) even before coreutils-6.9.90, + # but cut from 6.9.90 produces a more useful diagnostic. +- ['zero-1', '-b0', {ERR=>$from_pos1}, {EXIT => 1} ], ++ ['zero-1', '-b0', {ERR=>$from_field_0}, {EXIT => 1} ], + + # Up to coreutils-6.9, specifying a range of 0-2 was not an error. + # It was treated just like "-2". +- ['zero-2', '-f0-2', {ERR=>$from_field1}, {EXIT => 1} ], ++ ['zero-2', '-f0-2', {ERR=>$from_field_0_2}, {EXIT => 1} ], + + # Up to coreutils-8.20, specifying a range of 0- was not an error. +- ['zero-3b', '-b0-', {ERR=>$from_pos1}, {EXIT => 1} ], +- ['zero-3c', '-c0-', {ERR=>$from_pos1}, {EXIT => 1} ], +- ['zero-3f', '-f0-', {ERR=>$from_field1}, {EXIT => 1} ], ++ ['zero-3b', '-b0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], ++ ['zero-3c', '-c0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], ++ ['zero-3f', '-f0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], + + ['1', '-d:', '-f1,3-', {IN=>"a:b:c\n"}, {OUT=>"a:c\n"}], + ['2', '-d:', '-f1,3-', {IN=>"a:b:c\n"}, {OUT=>"a:c\n"}], +@@ -96,11 +98,10 @@ my @Tests = + # Errors + # -s may be used only with -f + ['y', qw(-s -b4), {IN=>":\n"}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: suppressing non-delimited lines makes sense\n" +- . "\tonly when operating on fields\n$try"}], ++ {ERR=>"$prog: invalid input: The '--only-delimited' ('-s') option only usable if printing a sequence of fields\n"}], + # You must specify bytes or fields (or chars) + ['z', '', {IN=>":\n"}, {OUT=>""}, {EXIT=>1}, +- {ERR=>"$prog: you must specify a list of bytes, characters, or fields\n$try"} ++ {ERR=>"$prog: invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)\n"} + ], + # Empty field list + ['empty-fl', qw(-f ''), {IN=>":\n"}, {OUT=>""}, {EXIT=>1}, +@@ -199,7 +200,7 @@ my @Tests = + + # None of the following invalid ranges provoked an error up to coreutils-6.9. + ['inval1', qw(-f 2-0), {IN=>''}, {OUT=>''}, {EXIT=>1}, +- {ERR=>"$prog: invalid decreasing range\n$try"}], ++ {ERR=>"$prog: range '2-0' was invalid: fields and positions are numbered from 1\n"}], + ['inval2', qw(-f -), {IN=>''}, {OUT=>''}, {EXIT=>1}, {ERR=>$no_endpoint}], + ['inval3', '-f', '4,-', {IN=>''}, {OUT=>''}, {EXIT=>1}, {ERR=>$no_endpoint}], + ['inval4', '-f', '1-2,-', {IN=>''}, {OUT=>''}, {EXIT=>1}, From d20c7fdef62d19fd071500ba7dc7b43344ee75e7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:34:19 +0000 Subject: [PATCH 135/351] fix(deps): update rust crate clap_complete to v4.5.39 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8397192b3f0..da693812e2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,9 +349,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.38" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" +checksum = "fd4db298d517d5fa00b2b84bbe044efd3fde43874a41db0d46f91994646a2da4" dependencies = [ "clap", ] From dd3d0a383f6f9a5b9dcb6affffa99579c4f37942 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 17 Dec 2024 07:40:08 +0100 Subject: [PATCH 136/351] Bump zip from 1.1.4 to 2.2.2 --- Cargo.lock | 92 +++++++++++++++++++++++++++++------------------------- Cargo.toml | 2 +- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da693812e2b..c4c28b0b404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,10 @@ version = 3 [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecount" @@ -642,9 +642,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -683,9 +683,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" @@ -803,9 +803,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", @@ -916,9 +916,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1350,11 +1350,17 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" @@ -1408,11 +1414,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -1551,27 +1557,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "num_threads" version = "0.1.6" @@ -2190,6 +2175,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.10" @@ -3986,9 +3977,9 @@ dependencies = [ [[package]] name = "zip" -version = "1.1.4" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" +checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" dependencies = [ "arbitrary", "crc32fast", @@ -3996,6 +3987,21 @@ dependencies = [ "displaydoc", "flate2", "indexmap", - "num_enum", - "thiserror 1.0.69", + "memchr", + "thiserror 2.0.7", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", ] diff --git a/Cargo.toml b/Cargo.toml index 79e6dff4018..a6b9958d4a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,7 +342,7 @@ walkdir = "2.5" winapi-util = "0.1.8" windows-sys = { version = "0.59.0", default-features = false } xattr = "1.3.1" -zip = { version = "1.1.4", default-features = false, features = ["deflate"] } +zip = { version = "2.2.2", default-features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.6" From d414dbc83beef3bfd8c8283440fadd34295ae8f4 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 17 Dec 2024 10:52:25 +0100 Subject: [PATCH 137/351] basenc: ignore case with "--base16 --decode" --- src/uu/base32/src/base_common.rs | 6 +++--- tests/by-util/test_basenc.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 84a46196339..878d07a92bb 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -11,7 +11,7 @@ use std::io::{self, ErrorKind, Read, Seek, SeekFrom}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::encoding::{ - for_base_common::{BASE32, BASE32HEX, BASE64, BASE64URL, BASE64_NOPAD, HEXUPPER}, + for_base_common::{BASE32, BASE32HEX, BASE64, BASE64URL, BASE64_NOPAD, HEXUPPER_PERMISSIVE}, Format, Z85Wrapper, BASE2LSBF, BASE2MSBF, }; use uucore::encoding::{EncodingWrapper, SupportsFastDecodeAndEncode}; @@ -226,11 +226,11 @@ pub fn get_supports_fast_decode_and_encode( match format { Format::Base16 => Box::from(EncodingWrapper::new( - HEXUPPER, + HEXUPPER_PERMISSIVE, BASE16_VALID_DECODING_MULTIPLE, BASE16_UNPADDED_MULTIPLE, // spell-checker:disable-next-line - b"0123456789ABCDEF", + b"0123456789ABCDEFabcdef", )), Format::Base2Lsbf => Box::from(EncodingWrapper::new( BASE2LSBF, diff --git a/tests/by-util/test_basenc.rs b/tests/by-util/test_basenc.rs index 85c05ad3ee0..c0f40cd1d25 100644 --- a/tests/by-util/test_basenc.rs +++ b/tests/by-util/test_basenc.rs @@ -130,6 +130,24 @@ fn test_base16_decode() { .stdout_only("Hello, World!"); } +#[test] +fn test_base16_decode_lowercase() { + new_ucmd!() + .args(&["--base16", "-d"]) + .pipe_in("48656c6c6f2c20576f726c6421") + .succeeds() + .stdout_only("Hello, World!"); +} + +#[test] +fn test_base16_decode_and_ignore_garbage_lowercase() { + new_ucmd!() + .args(&["--base16", "-d", "-i"]) + .pipe_in("48656c6c6f2c20576f726c6421") + .succeeds() + .stdout_only("Hello, World!"); +} + #[test] fn test_base2msbf() { new_ucmd!() From dd741eac1900b6731068bd72d5266e6d26a3b18b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 17 Dec 2024 14:59:40 +0100 Subject: [PATCH 138/351] build-gnu.sh: adapt basenc message to clap changes --- 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 974e188f414..16868af4dbc 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -267,7 +267,7 @@ sed -i "s/\(\(b2[ml]_[69]\|b32h_[56]\|z85_8\|z85_35\).*OUT=>\)[^}]*\(.*\)/\1\"\" sed -i "s/\$prog: invalid input/\$prog: error: invalid input/g" tests/basenc/basenc.pl # basenc: swap out error message for unexpected arg -sed -i "s/ {ERR=>\"\$prog: foobar\\\\n\" \. \$try_help }/ {ERR=>\"error: Found argument '--foobar' which wasn't expected, or isn't valid in this context\n\n If you tried to supply '--foobar' as a value rather than a flag, use '-- --foobar'\n\nUsage: basenc [OPTION]... [FILE]\n\nFor more information try '--help'\n\"}]/" tests/basenc/basenc.pl +sed -i "s/ {ERR=>\"\$prog: foobar\\\\n\" \. \$try_help }/ {ERR=>\"error: unexpected argument '--foobar' found\n\n tip: to pass '--foobar' as a value, use '-- --foobar'\n\nUsage: basenc [OPTION]... [FILE]\n\nFor more information, try '--help'.\n\"}]/" tests/basenc/basenc.pl sed -i "s/ {ERR_SUBST=>\"s\/(unrecognized|unknown) option \[-' \]\*foobar\[' \]\*\/foobar\/\"}],//" tests/basenc/basenc.pl # Remove the check whether a util was built. Otherwise tests against utils like "arch" are not run. From 8edefdc287d2cbb64228b1dceebbd65ad174e15a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:36:05 +0000 Subject: [PATCH 139/351] fix(deps): update rust crate clap_complete to v4.5.40 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4c28b0b404..7732234705e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,9 +349,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd4db298d517d5fa00b2b84bbe044efd3fde43874a41db0d46f91994646a2da4" +checksum = "ac2e663e3e3bed2d32d065a8404024dad306e699a04263ec59919529f803aee9" dependencies = [ "clap", ] From beb56b10ab0aaddc8aa6ac58b00773feb6c186e7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 3 Dec 2024 22:34:14 +0100 Subject: [PATCH 140/351] clippy: fix clippy warnings See: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or --- src/uu/chcon/src/chcon.rs | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/fold/src/fold.rs | 2 +- src/uu/tail/src/args.rs | 4 ++-- src/uu/tail/src/paths.rs | 4 ++-- src/uu/uniq/src/uniq.rs | 2 +- src/uucore/src/lib/features/fs.rs | 2 +- src/uuhelp_parser/src/lib.rs | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index c8d1c401762..b5b892f6c36 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -727,7 +727,7 @@ fn get_root_dev_ino() -> Result { } fn root_dev_ino_check(root_dev_ino: Option, dir_dev_ino: DeviceAndINode) -> bool { - root_dev_ino.map_or(false, |root_dev_ino| root_dev_ino == dir_dev_ino) + root_dev_ino == Some(dir_dev_ino) } fn root_dev_ino_warn(dir_name: &Path) { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index d4bec77ef89..2392497a935 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -533,7 +533,7 @@ impl StatPrinter { if !self .threshold - .map_or(false, |threshold| threshold.should_exclude(size)) + .is_some_and(|threshold| threshold.should_exclude(size)) && self .max_depth .map_or(true, |max_depth| stat_info.depth <= max_depth) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 0223248be27..e17ba21c324 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -99,7 +99,7 @@ pub fn uu_app() -> Command { fn handle_obsolete(args: &[String]) -> (Vec, Option) { for (i, arg) in args.iter().enumerate() { let slice = &arg; - if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_ascii_digit()) { + if slice.starts_with('-') && slice.chars().nth(1).is_some_and(|c| c.is_ascii_digit()) { let mut v = args.to_vec(); v.remove(i); return (v, Some(slice[1..].to_owned())); diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 5cadac60869..24b064d1bfd 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -336,11 +336,11 @@ impl Settings { let blocking_stdin = self.pid == 0 && self.follow == Some(FollowMode::Descriptor) && self.num_inputs() == 1 - && Handle::stdin().map_or(false, |handle| { + && Handle::stdin().is_ok_and(|handle| { handle .as_file() .metadata() - .map_or(false, |meta| !meta.is_file()) + .is_ok_and(|meta| !meta.is_file()) }); if !blocking_stdin && std::io::stdin().is_terminal() { diff --git a/src/uu/tail/src/paths.rs b/src/uu/tail/src/paths.rs index 117cab8b0a0..4a680943c11 100644 --- a/src/uu/tail/src/paths.rs +++ b/src/uu/tail/src/paths.rs @@ -93,7 +93,7 @@ impl Input { pub fn is_tailable(&self) -> bool { match &self.kind { InputKind::File(path) => path_is_tailable(path), - InputKind::Stdin => self.resolve().map_or(false, |path| path_is_tailable(&path)), + InputKind::Stdin => self.resolve().is_some_and(|path| path_is_tailable(&path)), } } } @@ -233,7 +233,7 @@ impl PathExtTail for Path { } pub fn path_is_tailable(path: &Path) -> bool { - path.is_file() || path.exists() && path.metadata().map_or(false, |meta| meta.is_tailable()) + path.is_file() || path.exists() && path.metadata().is_ok_and(|meta| meta.is_tailable()) } #[inline] diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 4084a7b3f22..b9090cd50cf 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -383,7 +383,7 @@ fn should_extract_obs_skip_chars( && posix_version().is_some_and(|v| v <= OBSOLETE) && !preceding_long_opt_req_value && !preceding_short_opt_req_value - && slice.chars().nth(1).map_or(false, |c| c.is_ascii_digit()) + && slice.chars().nth(1).is_some_and(|c| c.is_ascii_digit()) } /// Helper function to [`filter_args`] diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index beb4d77a9aa..e2958232f1f 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -710,7 +710,7 @@ pub fn path_ends_with_terminator(path: &Path) -> bool { path.as_os_str() .as_bytes() .last() - .map_or(false, |&byte| byte == b'/' || byte == b'\\') + .is_some_and(|&byte| byte == b'/' || byte == b'\\') } #[cfg(windows)] diff --git a/src/uuhelp_parser/src/lib.rs b/src/uuhelp_parser/src/lib.rs index da50c037b72..0e0907f8a54 100644 --- a/src/uuhelp_parser/src/lib.rs +++ b/src/uuhelp_parser/src/lib.rs @@ -73,7 +73,7 @@ pub fn parse_usage(content: &str) -> String { pub fn parse_section(section: &str, content: &str) -> Option { fn is_section_header(line: &str, section: &str) -> bool { line.strip_prefix("##") - .map_or(false, |l| l.trim().to_lowercase() == section) + .is_some_and(|l| l.trim().to_lowercase() == section) } let section = §ion.to_lowercase(); From 7708f6e39a50e29ba59643daed8cc84cc188a937 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 17 Dec 2024 22:47:45 +0100 Subject: [PATCH 141/351] clippy: replace .as_bytes().len() => .len() --- src/uu/split/src/split.rs | 2 +- src/uu/tac/src/tac.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 86fded1d5db..279e91daea1 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -492,7 +492,7 @@ impl Settings { } match first.as_str() { "\\0" => b'\0', - s if s.as_bytes().len() == 1 => s.as_bytes()[0], + s if s.len() == 1 => s.as_bytes()[0], s => return Err(SettingsError::MultiCharacterSeparator(s.to_string())), } } diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 3865c61ae48..d1eca4706a4 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -184,7 +184,7 @@ fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()> let mut out = BufWriter::new(out.lock()); // The number of bytes in the line separator. - let slen = separator.as_bytes().len(); + let slen = separator.len(); // The index of the start of the next line in the `data`. // From 5cd665363d9b39287f6c7bd4955e0fd7c6e816e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 04:30:52 +0000 Subject: [PATCH 142/351] chore(deps): update rust crate thiserror to v2.0.8 --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7732234705e..9f906aa6835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2317,11 +2317,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" dependencies = [ - "thiserror-impl 2.0.7", + "thiserror-impl 2.0.8", ] [[package]] @@ -2337,9 +2337,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" dependencies = [ "proc-macro2", "quote", @@ -2538,7 +2538,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.7", + "thiserror 2.0.8", "uucore", ] @@ -2550,7 +2550,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.7", + "thiserror 2.0.8", "uucore", ] @@ -2627,7 +2627,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.7", + "thiserror 2.0.8", "uucore", ] @@ -3143,7 +3143,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.7", + "thiserror 2.0.8", "uucore", ] @@ -3422,7 +3422,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.7", + "thiserror 2.0.8", "utmp-classic", "uucore", ] @@ -3453,7 +3453,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.7", + "thiserror 2.0.8", "unicode-width 0.2.0", "uucore", ] @@ -3515,7 +3515,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.7", + "thiserror 2.0.8", "time", "uucore_procs", "walkdir", @@ -3988,7 +3988,7 @@ dependencies = [ "flate2", "indexmap", "memchr", - "thiserror 2.0.7", + "thiserror 2.0.8", "zopfli", ] From 2f82853bfa3d16c1497927c26432a4853fadbfd3 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 18 Dec 2024 14:42:18 +0100 Subject: [PATCH 143/351] cut: move two tests to better places within file --- tests/by-util/test_cut.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 7d6009a30eb..fe20b5de679 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -46,6 +46,13 @@ static COMPLEX_SEQUENCE: &TestedSequence = &TestedSequence { sequence: "9-,6-7,-2,4", }; +#[test] +fn test_no_argument() { + new_ucmd!().fails().stderr_is( + "cut: invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)\n", + ); +} + #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); @@ -275,6 +282,15 @@ fn test_equal_as_delimiter3() { .stdout_only_bytes("abZcd\n"); } +#[test] +fn test_newline_delimited() { + new_ucmd!() + .args(&["-f", "1", "-d", "\n"]) + .pipe_in("a:1\nb:") + .succeeds() + .stdout_only_bytes("a:1\nb:\n"); +} + #[test] fn test_multiple() { let result = new_ucmd!() @@ -285,15 +301,6 @@ fn test_multiple() { assert_eq!(result.stderr_str(), ""); } -#[test] -fn test_newline_delimited() { - new_ucmd!() - .args(&["-f", "1", "-d", "\n"]) - .pipe_in("a:1\nb:") - .succeeds() - .stdout_only_bytes("a:1\nb:\n"); -} - #[test] fn test_multiple_mode_args() { for args in [ @@ -312,13 +319,6 @@ fn test_multiple_mode_args() { } } -#[test] -fn test_no_argument() { - new_ucmd!().fails().stderr_is( - "cut: invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)\n", - ); -} - #[test] #[cfg(unix)] fn test_8bit_non_utf8_delimiter() { From 9aca24365faef34214d78bd242493b3442a26a41 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 18 Dec 2024 14:44:12 +0100 Subject: [PATCH 144/351] cut: simplify test by removing assert_eq! calls --- tests/by-util/test_cut.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index fe20b5de679..e4c93cd75c3 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -293,12 +293,11 @@ fn test_newline_delimited() { #[test] fn test_multiple() { - let result = new_ucmd!() + new_ucmd!() .args(&["-f2", "-d:", "-d="]) .pipe_in("a=b\n") - .succeeds(); - assert_eq!(result.stdout_str(), "b\n"); - assert_eq!(result.stderr_str(), ""); + .succeeds() + .stdout_only("b\n"); } #[test] From 5ea4903632859e27e7d9947816a1e3fd30f90de8 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 18 Dec 2024 14:52:04 +0100 Subject: [PATCH 145/351] cut: rename some tests --- tests/by-util/test_cut.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index e4c93cd75c3..267eedf4541 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -47,7 +47,7 @@ static COMPLEX_SEQUENCE: &TestedSequence = &TestedSequence { }; #[test] -fn test_no_argument() { +fn test_no_args() { new_ucmd!().fails().stderr_is( "cut: invalid usage: expects one of --fields (-f), --chars (-c) or --bytes (-b)\n", ); @@ -256,7 +256,7 @@ fn test_no_such_file() { } #[test] -fn test_equal_as_delimiter1() { +fn test_equal_as_delimiter() { new_ucmd!() .args(&["-f", "2", "-d="]) .pipe_in("--dir=./out/lib") @@ -265,7 +265,7 @@ fn test_equal_as_delimiter1() { } #[test] -fn test_equal_as_delimiter2() { +fn test_empty_string_as_delimiter() { new_ucmd!() .args(&["-f2", "--delimiter="]) .pipe_in("a=b\n") @@ -274,7 +274,7 @@ fn test_equal_as_delimiter2() { } #[test] -fn test_equal_as_delimiter3() { +fn test_empty_string_as_delimiter_with_output_delimiter() { new_ucmd!() .args(&["-f", "1,2", "-d", "''", "--output-delimiter=Z"]) .pipe_in("ab\0cd\n") @@ -283,7 +283,7 @@ fn test_equal_as_delimiter3() { } #[test] -fn test_newline_delimited() { +fn test_newline_as_delimiter() { new_ucmd!() .args(&["-f", "1", "-d", "\n"]) .pipe_in("a:1\nb:") @@ -292,7 +292,7 @@ fn test_newline_delimited() { } #[test] -fn test_multiple() { +fn test_multiple_delimiters() { new_ucmd!() .args(&["-f2", "-d:", "-d="]) .pipe_in("a=b\n") From 6224c374ae490596e2d7e7429d219b82c66471bb Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 18 Dec 2024 16:15:48 +0100 Subject: [PATCH 146/351] cut: use short and long args in two tests --- tests/by-util/test_cut.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 267eedf4541..6c6914a122a 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -257,20 +257,24 @@ fn test_no_such_file() { #[test] fn test_equal_as_delimiter() { - new_ucmd!() - .args(&["-f", "2", "-d="]) - .pipe_in("--dir=./out/lib") - .succeeds() - .stdout_only("./out/lib\n"); + for arg in ["-d=", "--delimiter=="] { + new_ucmd!() + .args(&["-f2", arg]) + .pipe_in("--dir=./out/lib") + .succeeds() + .stdout_only("./out/lib\n"); + } } #[test] fn test_empty_string_as_delimiter() { - new_ucmd!() - .args(&["-f2", "--delimiter="]) - .pipe_in("a=b\n") - .succeeds() - .stdout_only("a=b\n"); + for arg in ["-d''", "--delimiter=", "--delimiter=''"] { + new_ucmd!() + .args(&["-f2", arg]) + .pipe_in("a\0b\n") + .succeeds() + .stdout_only("b\n"); + } } #[test] From cb3be5e3aa58860d93d8f4fd9043daefdc9153bc Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Thu, 21 Nov 2024 22:33:08 -0500 Subject: [PATCH 147/351] Bump MSRV to 1.79 --- .clippy.toml | 2 +- .github/workflows/CICD.yml | 2 +- Cargo.toml | 2 +- README.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index 72e8c35cfd8..6339ccf21b4 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.77.0" +msrv = "1.79.0" cognitive-complexity-threshold = 24 missing-docs-in-crate-items = true check-private-items = true diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 56418dd6e04..f1f9661b36b 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -11,7 +11,7 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.77.0" + RUST_MIN_SRV: "1.79.0" # * style job configuration STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis diff --git a/Cargo.toml b/Cargo.toml index a6b9958d4a0..1991679d8e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://github.com/uutils/coreutils" readme = "README.md" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] -rust-version = "1.77.0" +rust-version = "1.79.0" edition = "2021" build = "build.rs" diff --git a/README.md b/README.md index 9f7d1c2ae09..37c5a596b3d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) [![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) -![MSRV](https://img.shields.io/badge/MSRV-1.77.0-brightgreen) +![MSRV](https://img.shields.io/badge/MSRV-1.79.0-brightgreen) @@ -70,7 +70,7 @@ the [coreutils docs](https://github.com/uutils/uutils.github.io) repository. ### Rust Version uutils follows Rust's release channels and is tested against stable, beta and -nightly. The current Minimum Supported Rust Version (MSRV) is `1.77.0`. +nightly. The current Minimum Supported Rust Version (MSRV) is `1.79.0`. ## Building From 355103134b76923053b2fa3c5ccda53c0e1108b9 Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Thu, 21 Nov 2024 22:35:42 -0500 Subject: [PATCH 148/351] quoting_style: add support for non-unicode bytes This new functionality is implemented, but not yet exposed here. --- src/uucore/src/lib/features/quoting_style.rs | 512 +++++++++++++++---- 1 file changed, 406 insertions(+), 106 deletions(-) diff --git a/src/uucore/src/lib/features/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs index 1efa6f746b7..0544633bb5e 100644 --- a/src/uucore/src/lib/features/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -11,34 +11,38 @@ use std::fmt; // These are characters with special meaning in the shell (e.g. bash). // The first const contains characters that only have a special meaning when they appear at the beginning of a name. -const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; +const SPECIAL_SHELL_CHARS_START: &[u8] = b"~#"; // PR#6559 : Remove `]{}` from special shell chars. const SPECIAL_SHELL_CHARS: &str = "`$&*()|[;\\'\"<>?! "; /// The quoting style to use when escaping a name. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum QuotingStyle { - /// Escape the name as a literal string. + /// Escape the name as a shell string. + /// Used in, e.g., `ls --quoting-style=shell`. Shell { /// Whether to escape characters in the name. + /// True in, e.g., `ls --quoting-style=shell-escape`. escape: bool, /// Whether to always quote the name. always_quote: bool, - /// Whether to show control characters. + /// Whether to show control and non-unicode characters, or replace them with `?`. show_control: bool, }, /// Escape the name as a C string. + /// Used in, e.g., `ls --quote-name`. C { /// The type of quotes to use. quotes: Quotes, }, - /// Escape the name as a literal string. + /// Do not escape the string. + /// Used in, e.g., `ls --literal`. Literal { - /// Whether to show control characters. + /// Whether to show control and non-unicode characters, or replace them with `?`. show_control: bool, }, } @@ -72,8 +76,9 @@ enum EscapeState { Octal(EscapeOctal), } +/// Byte we need to present as escaped octal, in the form of `\nnn` struct EscapeOctal { - c: char, + c: u8, state: EscapeOctalState, idx: usize, } @@ -95,20 +100,20 @@ impl Iterator for EscapeOctal { Some('\\') } EscapeOctalState::Value => { - let octal_digit = ((self.c as u32) >> (self.idx * 3)) & 0o7; + let octal_digit = ((self.c) >> (self.idx * 3)) & 0o7; if self.idx == 0 { self.state = EscapeOctalState::Done; } else { self.idx -= 1; } - Some(from_digit(octal_digit, 8).unwrap()) + Some(from_digit(octal_digit.into(), 8).unwrap()) } } } } impl EscapeOctal { - fn from(c: char) -> Self { + fn from(c: u8) -> Self { Self { c, idx: 2, @@ -124,6 +129,12 @@ impl EscapedChar { } } + fn new_octal(b: u8) -> Self { + Self { + state: EscapeState::Octal(EscapeOctal::from(b)), + } + } + fn new_c(c: char, quotes: Quotes, dirname: bool) -> Self { use EscapeState::*; let init_state = match c { @@ -148,7 +159,7 @@ impl EscapedChar { _ => Char(' '), }, ':' if dirname => Backslash(':'), - _ if c.is_ascii_control() => Octal(EscapeOctal::from(c)), + _ if c.is_ascii_control() => Octal(EscapeOctal::from(c as u8)), _ => Char(c), }; Self { state: init_state } @@ -165,7 +176,7 @@ impl EscapedChar { '\x0B' => Backslash('v'), '\x0C' => Backslash('f'), '\r' => Backslash('r'), - '\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)), + '\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c as u8)), '\'' => match quotes { Quotes::Single => Backslash('\''), _ => Char('\''), @@ -205,102 +216,124 @@ impl Iterator for EscapedChar { } } -fn shell_without_escape(name: &str, quotes: Quotes, show_control_chars: bool) -> (String, bool) { - let mut must_quote = false; - let mut escaped_str = String::with_capacity(name.len()); +/// Check whether `bytes` starts with any byte in `pattern`. +fn bytes_start_with(bytes: &[u8], pattern: &[u8]) -> bool { + !bytes.is_empty() && pattern.contains(&bytes[0]) +} - for c in name.chars() { - let escaped = { - let ec = EscapedChar::new_shell(c, false, quotes); - if show_control_chars { - ec - } else { - ec.hide_control() - } - }; +fn shell_without_escape(name: &[u8], quotes: Quotes, show_control_chars: bool) -> (Vec, bool) { + let mut must_quote = false; + let mut escaped_str = Vec::with_capacity(name.len()); + let mut utf8_buf = vec![0; 4]; + + for s in name.utf8_chunks() { + for c in s.valid().chars() { + let escaped = { + let ec = EscapedChar::new_shell(c, false, quotes); + if show_control_chars { + ec + } else { + ec.hide_control() + } + }; - match escaped.state { - EscapeState::Backslash('\'') => escaped_str.push_str("'\\''"), - EscapeState::ForceQuote(x) => { - must_quote = true; - escaped_str.push(x); - } - _ => { - for char in escaped { - escaped_str.push(char); + match escaped.state { + EscapeState::Backslash('\'') => escaped_str.extend_from_slice(b"'\\''"), + EscapeState::ForceQuote(x) => { + must_quote = true; + escaped_str.extend_from_slice(x.encode_utf8(&mut utf8_buf).as_bytes()); + } + _ => { + for c in escaped { + escaped_str.extend_from_slice(c.encode_utf8(&mut utf8_buf).as_bytes()); + } } } } + + if show_control_chars { + escaped_str.extend_from_slice(s.invalid()); + } else { + escaped_str.resize(escaped_str.len() + s.invalid().len(), b'?'); + } } - must_quote = must_quote || name.starts_with(SPECIAL_SHELL_CHARS_START); + must_quote = must_quote || bytes_start_with(name, SPECIAL_SHELL_CHARS_START); (escaped_str, must_quote) } -fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) { +fn shell_with_escape(name: &[u8], quotes: Quotes) -> (Vec, bool) { // We need to keep track of whether we are in a dollar expression // because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n' let mut in_dollar = false; let mut must_quote = false; let mut escaped_str = String::with_capacity(name.len()); - for c in name.chars() { - let escaped = EscapedChar::new_shell(c, true, quotes); - match escaped.state { - EscapeState::Char(x) => { - if in_dollar { - escaped_str.push_str("''"); - in_dollar = false; + for s in name.utf8_chunks() { + for c in s.valid().chars() { + let escaped = EscapedChar::new_shell(c, true, quotes); + match escaped.state { + EscapeState::Char(x) => { + if in_dollar { + escaped_str.push_str("''"); + in_dollar = false; + } + escaped_str.push(x); } - escaped_str.push(x); - } - EscapeState::ForceQuote(x) => { - if in_dollar { - escaped_str.push_str("''"); - in_dollar = false; + EscapeState::ForceQuote(x) => { + if in_dollar { + escaped_str.push_str("''"); + in_dollar = false; + } + must_quote = true; + escaped_str.push(x); } - must_quote = true; - escaped_str.push(x); - } - // Single quotes are not put in dollar expressions, but are escaped - // if the string also contains double quotes. In that case, they must - // be handled separately. - EscapeState::Backslash('\'') => { - must_quote = true; - in_dollar = false; - escaped_str.push_str("'\\''"); - } - _ => { - if !in_dollar { - escaped_str.push_str("'$'"); - in_dollar = true; + // Single quotes are not put in dollar expressions, but are escaped + // if the string also contains double quotes. In that case, they must + // be handled separately. + EscapeState::Backslash('\'') => { + must_quote = true; + in_dollar = false; + escaped_str.push_str("'\\''"); } - must_quote = true; - for char in escaped { - escaped_str.push(char); + _ => { + if !in_dollar { + escaped_str.push_str("'$'"); + in_dollar = true; + } + must_quote = true; + for char in escaped { + escaped_str.push(char); + } } } } + if !s.invalid().is_empty() { + if !in_dollar { + escaped_str.push_str("'$'"); + in_dollar = true; + } + must_quote = true; + let escaped_bytes: String = s + .invalid() + .iter() + .flat_map(|b| EscapedChar::new_octal(*b)) + .collect(); + escaped_str.push_str(&escaped_bytes); + } } - must_quote = must_quote || name.starts_with(SPECIAL_SHELL_CHARS_START); - (escaped_str, must_quote) + must_quote = must_quote || bytes_start_with(name, SPECIAL_SHELL_CHARS_START); + (escaped_str.into(), must_quote) } /// Return a set of characters that implies quoting of the word in /// shell-quoting mode. -fn shell_escaped_char_set(is_dirname: bool) -> &'static [char] { - const ESCAPED_CHARS: &[char] = &[ - // the ':' colon character only induce quoting in the - // context of ls displaying a directory name before listing its content. - // (e.g. with the recursive flag -R) - ':', - // Under this line are the control characters that should be - // quoted in shell mode in all cases. - '"', '`', '$', '\\', '^', '\n', '\t', '\r', '=', - ]; - +fn shell_escaped_char_set(is_dirname: bool) -> &'static [u8] { + const ESCAPED_CHARS: &[u8] = b":\"`$\\^\n\t\r="; + // the ':' colon character only induce quoting in the + // context of ls displaying a directory name before listing its content. + // (e.g. with the recursive flag -R) let start_index = if is_dirname { 0 } else { 1 }; - &ESCAPED_CHARS[start_index..] } @@ -308,41 +341,57 @@ fn shell_escaped_char_set(is_dirname: bool) -> &'static [char] { /// /// This inner function provides an additional flag `dirname` which /// is meant for ls' directory name display. -fn escape_name_inner(name: &OsStr, style: &QuotingStyle, dirname: bool) -> String { +fn escape_name_inner(name: &[u8], style: &QuotingStyle, dirname: bool) -> Vec { match style { QuotingStyle::Literal { show_control } => { if *show_control { - name.to_string_lossy().into_owned() + name.to_owned() } else { - name.to_string_lossy() - .chars() - .flat_map(|c| EscapedChar::new_literal(c).hide_control()) - .collect() + name.utf8_chunks() + .map(|s| { + let valid: String = s + .valid() + .chars() + .flat_map(|c| EscapedChar::new_literal(c).hide_control()) + .collect(); + let invalid = "?".repeat(s.invalid().len()); + valid + &invalid + }) + .collect::() + .into() } } QuotingStyle::C { quotes } => { let escaped_str: String = name - .to_string_lossy() - .chars() - .flat_map(|c| EscapedChar::new_c(c, *quotes, dirname)) - .collect(); + .utf8_chunks() + .flat_map(|s| { + let valid = s + .valid() + .chars() + .flat_map(|c| EscapedChar::new_c(c, *quotes, dirname)); + let invalid = s.invalid().iter().flat_map(|b| EscapedChar::new_octal(*b)); + valid.chain(invalid) + }) + .collect::(); match quotes { Quotes::Single => format!("'{escaped_str}'"), Quotes::Double => format!("\"{escaped_str}\""), Quotes::None => escaped_str, } + .into() } QuotingStyle::Shell { escape, always_quote, show_control, } => { - let name = name.to_string_lossy(); - - let (quotes, must_quote) = if name.contains(shell_escaped_char_set(dirname)) { + let (quotes, must_quote) = if name + .iter() + .any(|c| shell_escaped_char_set(dirname).contains(c)) + { (Quotes::Single, true) - } else if name.contains('\'') { + } else if name.contains(&b'\'') { (Quotes::Double, true) } else if *always_quote { (Quotes::Single, true) @@ -351,15 +400,24 @@ fn escape_name_inner(name: &OsStr, style: &QuotingStyle, dirname: bool) -> Strin }; let (escaped_str, contains_quote_chars) = if *escape { - shell_with_escape(&name, quotes) + shell_with_escape(name, quotes) } else { - shell_without_escape(&name, quotes, *show_control) + shell_without_escape(name, quotes, *show_control) }; - match (must_quote | contains_quote_chars, quotes) { - (true, Quotes::Single) => format!("'{escaped_str}'"), - (true, Quotes::Double) => format!("\"{escaped_str}\""), - _ => escaped_str, + if must_quote | contains_quote_chars && quotes != Quotes::None { + let mut quoted_str = Vec::::with_capacity(escaped_str.len() + 2); + let quote = if quotes == Quotes::Single { + b'\'' + } else { + b'"' + }; + quoted_str.push(quote); + quoted_str.extend(escaped_str); + quoted_str.push(quote); + quoted_str + } else { + escaped_str } } } @@ -367,14 +425,16 @@ fn escape_name_inner(name: &OsStr, style: &QuotingStyle, dirname: bool) -> Strin /// Escape a filename with respect to the given style. pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { - escape_name_inner(name, style, false) + let name = name.to_string_lossy(); + String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false)).to_string() } /// Escape a directory name with respect to the given style. /// This is mainly meant to be used for ls' directory name printing and is not /// likely to be used elsewhere. pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> String { - escape_name_inner(dir_name, style, true) + let dir_name = dir_name.to_string_lossy(); + String::from_utf8_lossy(&escape_name_inner(dir_name.as_bytes(), style, true)).to_string() } impl fmt::Display for QuotingStyle { @@ -415,7 +475,7 @@ impl fmt::Display for Quotes { #[cfg(test)] mod tests { - use crate::quoting_style::{escape_name, Quotes, QuotingStyle}; + use crate::quoting_style::{escape_name_inner, Quotes, QuotingStyle}; // spell-checker:ignore (tests/words) one\'two one'two @@ -465,14 +525,31 @@ mod tests { } } + fn check_names_inner(name: &[u8], map: &[(T, &str)]) -> Vec> { + map.iter() + .map(|(_, style)| escape_name_inner(name, &get_style(style), false)) + .collect() + } + fn check_names(name: &str, map: &[(&str, &str)]) { assert_eq!( map.iter() - .map(|(_, style)| escape_name(name.as_ref(), &get_style(style))) - .collect::>(), + .map(|(correct, _)| *correct) + .collect::>(), + check_names_inner(name.as_bytes(), map) + .iter() + .map(|bytes| std::str::from_utf8(bytes) + .expect("valid str goes in, valid str comes out")) + .collect::>() + ); + } + + fn check_names_raw(name: &[u8], map: &[(&[u8], &str)]) { + assert_eq!( map.iter() - .map(|(correct, _)| correct.to_string()) - .collect::>() + .map(|(correct, _)| *correct) + .collect::>(), + check_names_inner(name, map) ); } @@ -732,6 +809,229 @@ mod tests { ); } + #[test] + fn test_non_unicode_bytes() { + let ascii = b'_'; + let continuation = b'\xA7'; + let first2byte = b'\xC2'; + let first3byte = b'\xE0'; + let first4byte = b'\xF0'; + let invalid = b'\xC0'; + + // a single byte value invalid outside of additional context in UTF-8 + check_names_raw( + &[continuation], + &[ + (b"?", "literal"), + (b"\xA7", "literal-show"), + (b"\\247", "escape"), + (b"\"\\247\"", "c"), + (b"?", "shell"), + (b"\xA7", "shell-show"), + (b"'?'", "shell-always"), + (b"'\xA7'", "shell-always-show"), + (b"''$'\\247'", "shell-escape"), + (b"''$'\\247'", "shell-escape-always"), + ], + ); + + // ...but the byte becomes valid with appropriate context + // (this is just the § character in UTF-8, written as bytes) + check_names_raw( + &[first2byte, continuation], + &[ + (b"\xC2\xA7", "literal"), + (b"\xC2\xA7", "literal-show"), + (b"\xC2\xA7", "escape"), + (b"\"\xC2\xA7\"", "c"), + (b"\xC2\xA7", "shell"), + (b"\xC2\xA7", "shell-show"), + (b"'\xC2\xA7'", "shell-always"), + (b"'\xC2\xA7'", "shell-always-show"), + (b"\xC2\xA7", "shell-escape"), + (b"'\xC2\xA7'", "shell-escape-always"), + ], + ); + + // mixed with valid characters + check_names_raw( + &[continuation, ascii], + &[ + (b"?_", "literal"), + (b"\xA7_", "literal-show"), + (b"\\247_", "escape"), + (b"\"\\247_\"", "c"), + (b"?_", "shell"), + (b"\xA7_", "shell-show"), + (b"'?_'", "shell-always"), + (b"'\xA7_'", "shell-always-show"), + (b"''$'\\247''_'", "shell-escape"), + (b"''$'\\247''_'", "shell-escape-always"), + ], + ); + check_names_raw( + &[ascii, continuation], + &[ + (b"_?", "literal"), + (b"_\xA7", "literal-show"), + (b"_\\247", "escape"), + (b"\"_\\247\"", "c"), + (b"_?", "shell"), + (b"_\xA7", "shell-show"), + (b"'_?'", "shell-always"), + (b"'_\xA7'", "shell-always-show"), + (b"'_'$'\\247'", "shell-escape"), + (b"'_'$'\\247'", "shell-escape-always"), + ], + ); + check_names_raw( + &[ascii, continuation, ascii], + &[ + (b"_?_", "literal"), + (b"_\xA7_", "literal-show"), + (b"_\\247_", "escape"), + (b"\"_\\247_\"", "c"), + (b"_?_", "shell"), + (b"_\xA7_", "shell-show"), + (b"'_?_'", "shell-always"), + (b"'_\xA7_'", "shell-always-show"), + (b"'_'$'\\247''_'", "shell-escape"), + (b"'_'$'\\247''_'", "shell-escape-always"), + ], + ); + check_names_raw( + &[continuation, ascii, continuation], + &[ + (b"?_?", "literal"), + (b"\xA7_\xA7", "literal-show"), + (b"\\247_\\247", "escape"), + (b"\"\\247_\\247\"", "c"), + (b"?_?", "shell"), + (b"\xA7_\xA7", "shell-show"), + (b"'?_?'", "shell-always"), + (b"'\xA7_\xA7'", "shell-always-show"), + (b"''$'\\247''_'$'\\247'", "shell-escape"), + (b"''$'\\247''_'$'\\247'", "shell-escape-always"), + ], + ); + + // contiguous invalid bytes + check_names_raw( + &[ + ascii, + invalid, + ascii, + continuation, + continuation, + ascii, + continuation, + continuation, + continuation, + ascii, + continuation, + continuation, + continuation, + continuation, + ascii, + ], + &[ + (b"_?_??_???_????_", "literal"), + ( + b"_\xC0_\xA7\xA7_\xA7\xA7\xA7_\xA7\xA7\xA7\xA7_", + "literal-show", + ), + ( + b"_\\300_\\247\\247_\\247\\247\\247_\\247\\247\\247\\247_", + "escape", + ), + ( + b"\"_\\300_\\247\\247_\\247\\247\\247_\\247\\247\\247\\247_\"", + "c", + ), + (b"_?_??_???_????_", "shell"), + ( + b"_\xC0_\xA7\xA7_\xA7\xA7\xA7_\xA7\xA7\xA7\xA7_", + "shell-show", + ), + (b"'_?_??_???_????_'", "shell-always"), + ( + b"'_\xC0_\xA7\xA7_\xA7\xA7\xA7_\xA7\xA7\xA7\xA7_'", + "shell-always-show", + ), + ( + b"'_'$'\\300''_'$'\\247\\247''_'$'\\247\\247\\247''_'$'\\247\\247\\247\\247''_'", + "shell-escape", + ), + ( + b"'_'$'\\300''_'$'\\247\\247''_'$'\\247\\247\\247''_'$'\\247\\247\\247\\247''_'", + "shell-escape-always", + ), + ], + ); + + // invalid multi-byte sequences that start valid + check_names_raw( + &[first2byte, ascii], + &[ + (b"?_", "literal"), + (b"\xC2_", "literal-show"), + (b"\\302_", "escape"), + (b"\"\\302_\"", "c"), + (b"?_", "shell"), + (b"\xC2_", "shell-show"), + (b"'?_'", "shell-always"), + (b"'\xC2_'", "shell-always-show"), + (b"''$'\\302''_'", "shell-escape"), + (b"''$'\\302''_'", "shell-escape-always"), + ], + ); + check_names_raw( + &[first2byte, first2byte, continuation], + &[ + (b"?\xC2\xA7", "literal"), + (b"\xC2\xC2\xA7", "literal-show"), + (b"\\302\xC2\xA7", "escape"), + (b"\"\\302\xC2\xA7\"", "c"), + (b"?\xC2\xA7", "shell"), + (b"\xC2\xC2\xA7", "shell-show"), + (b"'?\xC2\xA7'", "shell-always"), + (b"'\xC2\xC2\xA7'", "shell-always-show"), + (b"''$'\\302''\xC2\xA7'", "shell-escape"), + (b"''$'\\302''\xC2\xA7'", "shell-escape-always"), + ], + ); + check_names_raw( + &[first3byte, continuation, ascii], + &[ + (b"??_", "literal"), + (b"\xE0\xA7_", "literal-show"), + (b"\\340\\247_", "escape"), + (b"\"\\340\\247_\"", "c"), + (b"??_", "shell"), + (b"\xE0\xA7_", "shell-show"), + (b"'??_'", "shell-always"), + (b"'\xE0\xA7_'", "shell-always-show"), + (b"''$'\\340\\247''_'", "shell-escape"), + (b"''$'\\340\\247''_'", "shell-escape-always"), + ], + ); + check_names_raw( + &[first4byte, continuation, continuation, ascii], + &[ + (b"???_", "literal"), + (b"\xF0\xA7\xA7_", "literal-show"), + (b"\\360\\247\\247_", "escape"), + (b"\"\\360\\247\\247_\"", "c"), + (b"???_", "shell"), + (b"\xF0\xA7\xA7_", "shell-show"), + (b"'???_'", "shell-always"), + (b"'\xF0\xA7\xA7_'", "shell-always-show"), + (b"''$'\\360\\247\\247''_'", "shell-escape"), + (b"''$'\\360\\247\\247''_'", "shell-escape-always"), + ], + ); + } + #[test] fn test_question_mark() { // A question mark must force quotes in shell and shell-always, unless From 2331600f4c72444a3e846f29e51cc6841c9aa96f Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Fri, 22 Nov 2024 00:32:23 -0500 Subject: [PATCH 149/351] quoting_style: fix multi-byte control characters --- src/uucore/src/lib/features/quoting_style.rs | 127 +++++++++++++++---- 1 file changed, 99 insertions(+), 28 deletions(-) diff --git a/src/uucore/src/lib/features/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs index 0544633bb5e..8abc6bda7b0 100644 --- a/src/uucore/src/lib/features/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -76,17 +76,24 @@ enum EscapeState { Octal(EscapeOctal), } -/// Byte we need to present as escaped octal, in the form of `\nnn` +/// Bytes we need to present as escaped octal, in the form of `\nnn` per byte. +/// Only supports characters up to 2 bytes long in UTF-8. struct EscapeOctal { - c: u8, + c: [u8; 2], state: EscapeOctalState, - idx: usize, + idx: u8, } enum EscapeOctalState { Done, - Backslash, - Value, + FirstBackslash, + FirstValue, + LastBackslash, + LastValue, +} + +fn byte_to_octal_digit(byte: u8, idx: u8) -> u8 { + (byte >> (idx * 3)) & 0o7 } impl Iterator for EscapeOctal { @@ -95,12 +102,26 @@ impl Iterator for EscapeOctal { fn next(&mut self) -> Option { match self.state { EscapeOctalState::Done => None, - EscapeOctalState::Backslash => { - self.state = EscapeOctalState::Value; + EscapeOctalState::FirstBackslash => { + self.state = EscapeOctalState::FirstValue; Some('\\') } - EscapeOctalState::Value => { - let octal_digit = ((self.c) >> (self.idx * 3)) & 0o7; + EscapeOctalState::LastBackslash => { + self.state = EscapeOctalState::LastValue; + Some('\\') + } + EscapeOctalState::FirstValue => { + let octal_digit = byte_to_octal_digit(self.c[0], self.idx); + if self.idx == 0 { + self.state = EscapeOctalState::LastBackslash; + self.idx = 2; + } else { + self.idx -= 1; + } + Some(from_digit(octal_digit.into(), 8).unwrap()) + } + EscapeOctalState::LastValue => { + let octal_digit = byte_to_octal_digit(self.c[1], self.idx); if self.idx == 0 { self.state = EscapeOctalState::Done; } else { @@ -113,11 +134,25 @@ impl Iterator for EscapeOctal { } impl EscapeOctal { - fn from(c: u8) -> Self { + fn from_char(c: char) -> Self { + if c.len_utf8() == 1 { + return Self::from_byte(c as u8); + } + + let mut buf = [0; 2]; + let _s = c.encode_utf8(&mut buf); + Self { + c: buf, + idx: 2, + state: EscapeOctalState::FirstBackslash, + } + } + + fn from_byte(b: u8) -> Self { Self { - c, + c: [0, b], idx: 2, - state: EscapeOctalState::Backslash, + state: EscapeOctalState::LastBackslash, } } } @@ -131,7 +166,7 @@ impl EscapedChar { fn new_octal(b: u8) -> Self { Self { - state: EscapeState::Octal(EscapeOctal::from(b)), + state: EscapeState::Octal(EscapeOctal::from_byte(b)), } } @@ -159,7 +194,7 @@ impl EscapedChar { _ => Char(' '), }, ':' if dirname => Backslash(':'), - _ if c.is_ascii_control() => Octal(EscapeOctal::from(c as u8)), + _ if c.is_control() => Octal(EscapeOctal::from_char(c)), _ => Char(c), }; Self { state: init_state } @@ -176,11 +211,11 @@ impl EscapedChar { '\x0B' => Backslash('v'), '\x0C' => Backslash('f'), '\r' => Backslash('r'), - '\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c as u8)), '\'' => match quotes { Quotes::Single => Backslash('\''), _ => Char('\''), }, + _ if c.is_control() => Octal(EscapeOctal::from_char(c)), _ if SPECIAL_SHELL_CHARS.contains(c) => ForceQuote(c), _ => Char(c), }; @@ -564,10 +599,10 @@ mod tests { ("\"one_two\"", "c"), ("one_two", "shell"), ("one_two", "shell-show"), - ("\'one_two\'", "shell-always"), - ("\'one_two\'", "shell-always-show"), + ("'one_two'", "shell-always"), + ("'one_two'", "shell-always-show"), ("one_two", "shell-escape"), - ("\'one_two\'", "shell-escape-always"), + ("'one_two'", "shell-escape-always"), ], ); } @@ -581,12 +616,12 @@ mod tests { ("one two", "literal-show"), ("one\\ two", "escape"), ("\"one two\"", "c"), - ("\'one two\'", "shell"), - ("\'one two\'", "shell-show"), - ("\'one two\'", "shell-always"), - ("\'one two\'", "shell-always-show"), - ("\'one two\'", "shell-escape"), - ("\'one two\'", "shell-escape-always"), + ("'one two'", "shell"), + ("'one two'", "shell-show"), + ("'one two'", "shell-always"), + ("'one two'", "shell-always-show"), + ("'one two'", "shell-escape"), + ("'one two'", "shell-escape-always"), ], ); @@ -628,7 +663,7 @@ mod tests { // One single quote check_names( - "one\'two", + "one'two", &[ ("one'two", "literal"), ("one'two", "literal-show"), @@ -714,7 +749,7 @@ mod tests { ], ); - // The first 16 control characters. NUL is also included, even though it is of + // The first 16 ASCII control characters. NUL is also included, even though it is of // no importance for file names. check_names( "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", @@ -753,7 +788,7 @@ mod tests { ], ); - // The last 16 control characters. + // The last 16 ASCII control characters. check_names( "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F", &[ @@ -807,6 +842,42 @@ mod tests { ("''$'\\177'", "shell-escape-always"), ], ); + + // The first 16 Unicode control characters. + let test_str = std::str::from_utf8(b"\xC2\x80\xC2\x81\xC2\x82\xC2\x83\xC2\x84\xC2\x85\xC2\x86\xC2\x87\xC2\x88\xC2\x89\xC2\x8A\xC2\x8B\xC2\x8C\xC2\x8D\xC2\x8E\xC2\x8F").unwrap(); + check_names( + test_str, + &[ + ("????????????????", "literal"), + (test_str, "literal-show"), + ("\\302\\200\\302\\201\\302\\202\\302\\203\\302\\204\\302\\205\\302\\206\\302\\207\\302\\210\\302\\211\\302\\212\\302\\213\\302\\214\\302\\215\\302\\216\\302\\217", "escape"), + ("\"\\302\\200\\302\\201\\302\\202\\302\\203\\302\\204\\302\\205\\302\\206\\302\\207\\302\\210\\302\\211\\302\\212\\302\\213\\302\\214\\302\\215\\302\\216\\302\\217\"", "c"), + ("????????????????", "shell"), + (test_str, "shell-show"), + ("'????????????????'", "shell-always"), + (&format!("'{}'", test_str), "shell-always-show"), + ("''$'\\302\\200\\302\\201\\302\\202\\302\\203\\302\\204\\302\\205\\302\\206\\302\\207\\302\\210\\302\\211\\302\\212\\302\\213\\302\\214\\302\\215\\302\\216\\302\\217'", "shell-escape"), + ("''$'\\302\\200\\302\\201\\302\\202\\302\\203\\302\\204\\302\\205\\302\\206\\302\\207\\302\\210\\302\\211\\302\\212\\302\\213\\302\\214\\302\\215\\302\\216\\302\\217'", "shell-escape-always"), + ], + ); + + // The last 16 Unicode control characters. + let test_str = std::str::from_utf8(b"\xC2\x90\xC2\x91\xC2\x92\xC2\x93\xC2\x94\xC2\x95\xC2\x96\xC2\x97\xC2\x98\xC2\x99\xC2\x9A\xC2\x9B\xC2\x9C\xC2\x9D\xC2\x9E\xC2\x9F").unwrap(); + check_names( + test_str, + &[ + ("????????????????", "literal"), + (test_str, "literal-show"), + ("\\302\\220\\302\\221\\302\\222\\302\\223\\302\\224\\302\\225\\302\\226\\302\\227\\302\\230\\302\\231\\302\\232\\302\\233\\302\\234\\302\\235\\302\\236\\302\\237", "escape"), + ("\"\\302\\220\\302\\221\\302\\222\\302\\223\\302\\224\\302\\225\\302\\226\\302\\227\\302\\230\\302\\231\\302\\232\\302\\233\\302\\234\\302\\235\\302\\236\\302\\237\"", "c"), + ("????????????????", "shell"), + (test_str, "shell-show"), + ("'????????????????'", "shell-always"), + (&format!("'{}'", test_str), "shell-always-show"), + ("''$'\\302\\220\\302\\221\\302\\222\\302\\223\\302\\224\\302\\225\\302\\226\\302\\227\\302\\230\\302\\231\\302\\232\\302\\233\\302\\234\\302\\235\\302\\236\\302\\237'", "shell-escape"), + ("''$'\\302\\220\\302\\221\\302\\222\\302\\223\\302\\224\\302\\225\\302\\226\\302\\227\\302\\230\\302\\231\\302\\232\\302\\233\\302\\234\\302\\235\\302\\236\\302\\237'", "shell-escape-always"), + ], + ); } #[test] @@ -1065,7 +1136,7 @@ mod tests { ("one\\\\two", "escape"), ("\"one\\\\two\"", "c"), ("'one\\two'", "shell"), - ("\'one\\two\'", "shell-always"), + ("'one\\two'", "shell-always"), ("'one\\two'", "shell-escape"), ("'one\\two'", "shell-escape-always"), ], From 43229ae10465119165fb275374f9d52e149b8f3d Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Fri, 22 Nov 2024 18:13:16 -0500 Subject: [PATCH 150/351] quoting_style: use and return `OsString`s This exposes the non-UTF-8 functionality to callers. Support in `argument`, `spec`, and `wc` are implemented, as their usage is simple. A wrapper only returning valid unicode is used in `ls`, since proper handling of OsStrings there is more involved (outputs that escape non-unicode work now though). --- src/uu/ls/src/ls.rs | 18 +++++++--- src/uu/wc/src/wc.rs | 26 +++++++++----- .../src/lib/features/format/argument.rs | 3 +- src/uucore/src/lib/features/format/spec.rs | 28 +++++++-------- src/uucore/src/lib/features/quoting_style.rs | 36 +++++++++++++++---- 5 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index f4e34714704..9a22006e097 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -21,7 +21,7 @@ use std::os::windows::fs::MetadataExt; use std::{ cmp::Reverse, error::Error, - ffi::OsString, + ffi::{OsStr, OsString}, fmt::{Display, Write as FmtWrite}, fs::{self, DirEntry, FileType, Metadata, ReadDir}, io::{stdout, BufWriter, ErrorKind, Stdout, Write}, @@ -55,7 +55,7 @@ use uucore::libc::{dev_t, major, minor}; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::line_ending::LineEnding; -use uucore::quoting_style::{escape_dir_name, escape_name, QuotingStyle}; +use uucore::quoting_style::{self, QuotingStyle}; use uucore::{ display::Quotable, error::{set_exit_code, UError, UResult}, @@ -2048,7 +2048,11 @@ impl PathData { /// file11 /// ``` fn show_dir_name(path_data: &PathData, out: &mut BufWriter, config: &Config) { - let escaped_name = escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style); + // FIXME: replace this with appropriate behavior for literal unprintable bytes + let escaped_name = + quoting_style::escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style) + .to_string_lossy() + .to_string(); let name = if config.hyperlink && !config.dired { create_hyperlink(&escaped_name, path_data) @@ -3002,7 +3006,6 @@ use std::sync::Mutex; #[cfg(unix)] use uucore::entries; use uucore::fs::FileInformation; -use uucore::quoting_style; #[cfg(unix)] fn cached_uid2usr(uid: u32) -> String { @@ -3542,3 +3545,10 @@ fn calculate_padding_collection( padding_collections } + +// FIXME: replace this with appropriate behavior for literal unprintable bytes +fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { + quoting_style::escape_name(name, style) + .to_string_lossy() + .to_string() +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 33b70ee62f5..1c2d99628f7 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -13,7 +13,7 @@ mod word_count; use std::{ borrow::{Borrow, Cow}, cmp::max, - ffi::OsString, + ffi::{OsStr, OsString}, fs::{self, File}, io::{self, Write}, iter, @@ -28,7 +28,7 @@ use utf8::{BufReadDecoder, BufReadDecoderError}; use uucore::{ error::{FromIo, UError, UResult}, format_usage, help_about, help_usage, - quoting_style::{escape_name, QuotingStyle}, + quoting_style::{self, QuotingStyle}, shortcut_value_parser::ShortcutValueParser, show, }; @@ -259,7 +259,7 @@ impl<'a> Input<'a> { match self { Self::Path(path) => Some(match path.to_str() { Some(s) if !s.contains('\n') => Cow::Borrowed(s), - _ => Cow::Owned(escape_name(path.as_os_str(), QS_ESCAPE)), + _ => Cow::Owned(escape_name_wrapper(path.as_os_str())), }), Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(STDIN_REPR)), Self::Stdin(StdinKind::Implicit) => None, @@ -269,7 +269,7 @@ impl<'a> Input<'a> { /// Converts input into the form that appears in errors. fn path_display(&self) -> String { match self { - Self::Path(path) => escape_name(path.as_os_str(), QS_ESCAPE), + Self::Path(path) => escape_name_wrapper(path.as_os_str()), Self::Stdin(_) => String::from("standard input"), } } @@ -361,7 +361,7 @@ impl WcError { Some((input, idx)) => { let path = match input { Input::Stdin(_) => STDIN_REPR.into(), - Input::Path(path) => escape_name(path.as_os_str(), QS_ESCAPE).into(), + Input::Path(path) => escape_name_wrapper(path.as_os_str()).into(), }; Self::ZeroLengthFileNameCtx { path, idx } } @@ -761,7 +761,9 @@ fn files0_iter_file<'a>(path: &Path) -> UResult Err(e.map_err_context(|| { format!( "cannot open {} for reading", - escape_name(path.as_os_str(), QS_QUOTE_ESCAPE) + quoting_style::escape_name(path.as_os_str(), QS_QUOTE_ESCAPE) + .into_string() + .expect("All escaped names with the escaping option return valid strings.") ) })), } @@ -793,9 +795,9 @@ fn files0_iter<'a>( Ok(Input::Path(PathBuf::from(s).into())) } } - Err(e) => Err(e.map_err_context(|| { - format!("{}: read error", escape_name(&err_path, QS_ESCAPE)) - }) as Box), + Err(e) => Err(e + .map_err_context(|| format!("{}: read error", escape_name_wrapper(&err_path))) + as Box), }), ); // Loop until there is an error; yield that error and then nothing else. @@ -808,6 +810,12 @@ fn files0_iter<'a>( }) } +fn escape_name_wrapper(name: &OsStr) -> String { + quoting_style::escape_name(name, QS_ESCAPE) + .into_string() + .expect("All escaped names with the escaping option return valid strings.") +} + fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { let mut total_word_count = WordCount::default(); let mut num_inputs: usize = 0; diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 75851049895..5cdd0342122 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -112,7 +112,8 @@ fn extract_value(p: Result>, input: &str) -> T Default::default() } ParseError::PartialMatch(v, rest) => { - if input.starts_with('\'') { + let bytes = input.as_encoded_bytes(); + if !bytes.is_empty() && bytes[0] == b'\'' { show_warning!( "{}: character(s) following character constant have been ignored", &rest, diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 581e1fa0624..81dbc1ebc29 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -353,20 +353,20 @@ impl Spec { writer.write_all(&parsed).map_err(FormatError::IoError) } Self::QuotedString => { - let s = args.get_str(); - writer - .write_all( - escape_name( - s.as_ref(), - &QuotingStyle::Shell { - escape: true, - always_quote: false, - show_control: false, - }, - ) - .as_bytes(), - ) - .map_err(FormatError::IoError) + let s = escape_name( + args.get_str().as_ref(), + &QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }, + ); + #[cfg(unix)] + let bytes = std::os::unix::ffi::OsStringExt::into_vec(s); + #[cfg(not(unix))] + let bytes = s.to_string_lossy().as_bytes().to_owned(); + + writer.write_all(&bytes).map_err(FormatError::IoError) } Self::SignedInt { width, diff --git a/src/uucore/src/lib/features/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs index 8abc6bda7b0..2e9cd0b7ec7 100644 --- a/src/uucore/src/lib/features/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -6,8 +6,10 @@ //! Set of functions for escaping names according to different quoting styles. use std::char::from_digit; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fmt; +#[cfg(unix)] +use std::os::unix::ffi::{OsStrExt, OsStringExt}; // These are characters with special meaning in the shell (e.g. bash). // The first const contains characters that only have a special meaning when they appear at the beginning of a name. @@ -459,17 +461,37 @@ fn escape_name_inner(name: &[u8], style: &QuotingStyle, dirname: bool) -> Vec String { - let name = name.to_string_lossy(); - String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false)).to_string() +pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> OsString { + #[cfg(unix)] + { + let name = name.as_bytes(); + OsStringExt::from_vec(escape_name_inner(name, style, false)) + } + #[cfg(not(unix))] + { + let name = name.to_string_lossy(); + String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false)) + .to_string() + .into() + } } /// Escape a directory name with respect to the given style. /// This is mainly meant to be used for ls' directory name printing and is not /// likely to be used elsewhere. -pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> String { - let dir_name = dir_name.to_string_lossy(); - String::from_utf8_lossy(&escape_name_inner(dir_name.as_bytes(), style, true)).to_string() +pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> OsString { + #[cfg(unix)] + { + let name = dir_name.as_bytes(); + OsStringExt::from_vec(escape_name_inner(name, style, true)) + } + #[cfg(not(unix))] + { + let name = dir_name.to_string_lossy(); + String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, true)) + .to_string() + .into() + } } impl fmt::Display for QuotingStyle { From 67360ba8e9cae57ece1a19820e75ebb59b030ae5 Mon Sep 17 00:00:00 2001 From: David Campbell Date: Wed, 18 Dec 2024 18:42:06 -0500 Subject: [PATCH 151/351] Put DEFAULT_BRANCH into an env variable. --- .github/workflows/GnuTests.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index b47b435962f..5022ac39ccf 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -23,6 +23,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} +env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + jobs: gnu: permissions: @@ -45,9 +48,9 @@ jobs: path_reference="reference" outputs path_GNU path_GNU_tests path_reference path_UUTILS # - repo_default_branch="${{ github.event.repository.default_branch }}" + repo_default_branch="$DEFAULT_BRANCH" repo_GNU_ref="v9.5" - repo_reference_branch="${{ github.event.repository.default_branch }}" + repo_reference_branch="$DEFAULT_BRANCH" outputs repo_default_branch repo_GNU_ref repo_reference_branch # SUITE_LOG_FILE="${path_GNU_tests}/test-suite.log" From 15bea52b531651a756a089eb824c77b9af4b07b8 Mon Sep 17 00:00:00 2001 From: David Campbell Date: Wed, 18 Dec 2024 20:04:50 -0500 Subject: [PATCH 152/351] GnuComment: zizmor: ignore[dangerous-triggers] --- .github/workflows/GnuComment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GnuComment.yml b/.github/workflows/GnuComment.yml index 36c54490ce9..987343723f6 100644 --- a/.github/workflows/GnuComment.yml +++ b/.github/workflows/GnuComment.yml @@ -4,7 +4,7 @@ on: workflow_run: workflows: ["GnuTests"] types: - - completed + - completed # zizmor: ignore[dangerous-triggers] permissions: {} jobs: From 655defd15cf944371a311ac22281984f2f715b49 Mon Sep 17 00:00:00 2001 From: David Campbell Date: Wed, 18 Dec 2024 21:27:34 -0500 Subject: [PATCH 153/351] Set persist-credentials: false --- .github/workflows/CICD.yml | 26 ++++++++++++++++++++++++++ .github/workflows/CheckScripts.yml | 4 ++++ .github/workflows/FixPR.yml | 4 ++++ .github/workflows/GnuTests.yml | 2 ++ .github/workflows/android.yml | 2 ++ .github/workflows/code-quality.yml | 8 ++++++++ .github/workflows/freebsd.yml | 4 ++++ .github/workflows/fuzzing.yml | 4 ++++ 8 files changed, 54 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 56418dd6e04..f84fdf0cd36 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -37,6 +37,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: EmbarkStudios/cargo-deny-action@v2 style_deps: @@ -54,6 +56,8 @@ jobs: - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@nightly ## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option ## * ... ref: @@ -106,6 +110,8 @@ jobs: # - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -159,6 +165,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -227,6 +235,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: "`cargo update` testing" @@ -250,6 +260,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -304,6 +316,8 @@ jobs: - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -331,6 +345,8 @@ jobs: - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@nightly - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -355,6 +371,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache @@ -485,6 +503,8 @@ jobs: - { os: windows-latest , target: aarch64-pc-windows-msvc , features: feat_os_windows, use-cross: use-cross , skip-tests: true } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -780,6 +800,8 @@ jobs: ## VARs setup echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 @@ -860,6 +882,8 @@ jobs: TEST_SUMMARY_FILE="toybox-result.json" outputs TEST_SUMMARY_FILE - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -935,6 +959,8 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: build and test all programs individually diff --git a/.github/workflows/CheckScripts.yml b/.github/workflows/CheckScripts.yml index c18c4733cbe..4800cd2857d 100644 --- a/.github/workflows/CheckScripts.yml +++ b/.github/workflows/CheckScripts.yml @@ -30,6 +30,8 @@ jobs: contents: read steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Run ShellCheck uses: ludeeus/action-shellcheck@master env: @@ -46,6 +48,8 @@ jobs: contents: read steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup shfmt uses: mfinelli/setup-shfmt@v3 - name: Run shfmt diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index e837b354687..5cd7fe647f2 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -27,6 +27,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Initialize job variables id: vars shell: bash @@ -86,6 +88,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Initialize job variables id: vars shell: bash diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index b47b435962f..ddca7ab722d 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -62,6 +62,7 @@ jobs: uses: actions/checkout@v4 with: path: '${{ steps.vars.outputs.path_UUTILS }}' + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -76,6 +77,7 @@ jobs: path: '${{ steps.vars.outputs.path_GNU }}' ref: ${{ steps.vars.outputs.repo_GNU_ref }} submodules: false + persist-credentials: false - name: Override submodule URL and initialize submodules # Use github instead of upstream git server diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d920ad80187..319f7b11c17 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -79,6 +79,8 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Collect information about runner if: always() continue-on-error: true diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 8e7db5fc3ed..0e598b502a1 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -32,6 +32,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -75,6 +77,8 @@ jobs: - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -120,6 +124,8 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Initialize workflow variables id: vars shell: bash @@ -156,6 +162,8 @@ jobs: steps: - name: Clone repository uses: actions/checkout@v4 + with: + persist-credentials: false - name: Check run: npx --yes @taplo/cli fmt --check diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 1ff0ba04780..42255d8899a 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -35,6 +35,8 @@ jobs: RUSTC_WRAPPER: "sccache" steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 @@ -127,6 +129,8 @@ jobs: RUSTC_WRAPPER: "sccache" steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index df40b123679..24d0f1c436f 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -22,6 +22,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@nightly - name: Install `cargo-fuzz` run: cargo install cargo-fuzz @@ -62,6 +64,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: dtolnay/rust-toolchain@nightly - name: Install `cargo-fuzz` run: cargo install cargo-fuzz From 3295e831a10301b8f84c58f6164d49bdbf7e33a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 06:09:57 +0000 Subject: [PATCH 154/351] fix(deps): update rust crate libc to v0.2.169 --- Cargo.lock | 4 ++-- fuzz/Cargo.lock | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f906aa6835..84c28bcd97f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1297,9 +1297,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index f2ba3f3754d..ce24e582718 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -414,11 +414,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libfuzzer-sys" @@ -988,6 +994,7 @@ dependencies = [ "dunce", "glob", "itertools", + "lazy_static", "libc", "nix 0.29.0", "number_prefix", From 8ba264fa27b6f3f77c8a510640bb799b017d80f3 Mon Sep 17 00:00:00 2001 From: David Campbell Date: Wed, 18 Dec 2024 22:15:07 -0500 Subject: [PATCH 155/351] Use the env variable STYLE_FAIL_ON_FAULT. --- .github/workflows/code-quality.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 0e598b502a1..814da316a7a 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -46,7 +46,7 @@ jobs: ## VARs setup outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } # failure mode - unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + unset FAIL_ON_FAULT ; case "$STYLE_FAIL_ON_FAULT" in ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; esac; @@ -93,7 +93,7 @@ jobs: ## VARs setup outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } # failure mode - unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + unset FAIL_ON_FAULT ; case "$STYLE_FAIL_ON_FAULT" in ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; esac; @@ -133,7 +133,7 @@ jobs: ## VARs setup outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } # failure mode - unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + unset FAIL_ON_FAULT ; case "$STYLE_FAIL_ON_FAULT" in ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; esac; From 625c49d0fedd60af8814c842d2d037bc256e028f Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Thu, 19 Dec 2024 18:32:11 +0700 Subject: [PATCH 156/351] uucore: add common splice-write functionality Splice is a Linux-specific syscall that allows direct data copying from one file descriptor to another without user-space buffer. As of now, this is used by `cp`, `cat`, and `install` when compiled for Linux and Android. --- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features.rs | 4 +- src/uucore/src/lib/features/buf_copy.rs | 373 ++++++++++++++++++++++++ src/uucore/src/lib/lib.rs | 4 +- 4 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 src/uucore/src/lib/features/buf_copy.rs diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index a4529f3a551..5e1a065a610 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -87,6 +87,7 @@ lines = [] format = ["itertools", "quoting-style"] mode = ["libc"] perms = ["libc", "walkdir"] +buf-copy = [] pipes = [] process = ["libc"] proc-info = ["tty", "walkdir"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index cf24637f7bf..dfe5b773312 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -39,11 +39,13 @@ pub mod version_cmp; pub mod mode; // ** unix-only +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "buf-copy"))] +pub mod buf_copy; #[cfg(all(unix, feature = "entries"))] pub mod entries; #[cfg(all(unix, feature = "perms"))] pub mod perms; -#[cfg(all(unix, feature = "pipes"))] +#[cfg(all(unix, any(feature = "pipes", feature = "buf-copy")))] pub mod pipes; #[cfg(all(target_os = "linux", feature = "proc-info"))] pub mod proc_info; diff --git a/src/uucore/src/lib/features/buf_copy.rs b/src/uucore/src/lib/features/buf_copy.rs new file mode 100644 index 00000000000..2b46248a597 --- /dev/null +++ b/src/uucore/src/lib/features/buf_copy.rs @@ -0,0 +1,373 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! This module provides several buffer-based copy/write functions that leverage +//! the `splice` system call in Linux systems, thus increasing the I/O +//! performance of copying between two file descriptors. This module is mostly +//! used by utilities to work around the limitations of Rust's `fs::copy` which +//! does not handle copying special files (e.g pipes, character/block devices). + +use crate::error::{UError, UResult}; +use nix::unistd; +use std::fs::File; +use std::{ + io::{self, Read, Write}, + os::{ + fd::AsFd, + unix::io::{AsRawFd, RawFd}, + }, +}; + +use nix::{errno::Errno, libc::S_IFIFO, sys::stat::fstat}; + +use super::pipes::{pipe, splice, splice_exact, vmsplice}; + +type Result = std::result::Result; + +/// Error types used by buffer-copying functions from the `buf_copy` module. +#[derive(Debug)] +pub enum Error { + Io(io::Error), + WriteError(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::WriteError(msg) => write!(f, "splice() write error: {}", msg), + Error::Io(err) => write!(f, "I/O error: {}", err), + } + } +} + +impl std::error::Error for Error {} + +impl UError for Error { + fn code(&self) -> i32 { + 1 + } + + fn usage(&self) -> bool { + false + } +} + +/// Helper function to determine whether a given handle (such as a file) is a pipe or not. +/// +/// # Arguments +/// * `out` - path of handle +/// +/// # Returns +/// A `bool` indicating whether the given handle is a pipe or not. +#[inline] +#[cfg(unix)] +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) +} + +const SPLICE_SIZE: usize = 1024 * 128; +const BUF_SIZE: usize = 1024 * 16; + +/// Copy data from `Read` implementor `source` into a `Write` implementor +/// `dest`. This works by reading a chunk of data from `source` and writing the +/// data to `dest` in a loop. +/// +/// This function uses the Linux-specific `splice` call when possible which does +/// not use any intermediate user-space buffer. It falls backs to +/// `std::io::copy` under other platforms or when the call fails and is still +/// recoverable. +/// +/// # Arguments +/// * `source` - `Read` implementor to copy data from. +/// * `dest` - `Write` implementor to copy data to. +/// +/// # Returns +/// +/// Result of operation and bytes successfully written (as a `u64`) when +/// operation is successful. +pub fn copy_stream(src: &mut R, dest: &mut S) -> UResult +where + R: Read + AsFd + AsRawFd, + S: Write + AsFd + AsRawFd, +{ + #[cfg(any(target_os = "linux", target_os = "android"))] + { + // If we're on Linux or Android, try to use the splice() system call + // for faster writing. If it works, we're done. + let result = splice_write(src, &dest.as_fd())?; + if !result.1 { + return Ok(result.0); + } + } + // If we're not on Linux or Android, or the splice() call failed, + // fall back on slower writing. + let result = std::io::copy(src, dest)?; + + // If the splice() call failed and there has been some data written to + // stdout via while loop above AND there will be second splice() call + // that will succeed, data pushed through splice will be output before + // the data buffered in stdout.lock. Therefore additional explicit flush + // is required here. + dest.flush()?; + Ok(result) +} + +/// Write from source `handle` into destination `write_fd` using Linux-specific +/// `splice` system call. +/// +/// # Arguments +/// - `source` - source handle +/// - `dest` - destination handle +#[inline] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn splice_write(source: &R, dest: &S) -> UResult<(u64, bool)> +where + R: Read + AsFd + AsRawFd, + S: AsRawFd + AsFd, +{ + let (pipe_rd, pipe_wr) = pipe()?; + let mut bytes: u64 = 0; + + loop { + match splice(&source, &pipe_wr, SPLICE_SIZE) { + Ok(n) => { + if n == 0 { + return Ok((bytes, false)); + } + if splice_exact(&pipe_rd, dest, n).is_err() { + // If the first splice manages to copy to the intermediate + // pipe, but the second splice to stdout fails for some reason + // we can recover by copying the data that we have from the + // intermediate pipe to stdout using normal read/write. Then + // we tell the caller to fall back. + copy_exact(pipe_rd.as_raw_fd(), dest, n)?; + return Ok((bytes, true)); + } + + bytes += n as u64; + } + Err(_) => { + return Ok((bytes, true)); + } + } + } +} + +/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd` using the `read` +/// and `write` calls. +fn copy_exact(read_fd: RawFd, write_fd: &impl AsFd, num_bytes: usize) -> std::io::Result { + let mut left = num_bytes; + let mut buf = [0; BUF_SIZE]; + let mut written = 0; + while left > 0 { + let read = unistd::read(read_fd, &mut buf)?; + assert_ne!(read, 0, "unexpected end of pipe"); + while written < read { + let n = unistd::write(write_fd, &buf[written..read])?; + written += n; + } + left -= read; + } + Ok(written) +} + +/// 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)) +} + +/// 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. +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn splice_data_to_fd( + bytes: &[u8], + read_pipe: &File, + write_pipe: &File, + dest: &T, +) -> UResult<(u64, bool)> { + loop { + 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..]; + } + } +} + +/// Conversion from a `nix::Error` into our `Error` which implements `UError`. +#[cfg(any(target_os = "linux", target_os = "android"))] +impl From for Error { + fn from(error: nix::Error) -> Self { + Self::Io(io::Error::from_raw_os_error(error as i32)) + } +} + +/// 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. +#[cfg(any(target_os = "linux", target_os = "android"))] +fn maybe_unsupported(error: nix::Error) -> Result<(u64, bool)> { + match error { + Errno::EINVAL | Errno::ENOSYS | Errno::EBADF => Ok((0, true)), + _ => Err(error.into()), + } +} + +#[cfg(test)] +mod tests { + use tempfile::tempdir; + + use super::*; + use crate::pipes; + + fn new_temp_file() -> File { + let temp_dir = tempdir().unwrap(); + File::create(temp_dir.path().join("file.txt")).unwrap() + } + + #[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()); + } + + #[test] + fn test_valid_splice_errs() { + let err = nix::Error::from(Errno::EINVAL); + assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); + + let err = nix::Error::from(Errno::ENOSYS); + assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); + + let err = nix::Error::from(Errno::EBADF); + assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); + + let err = nix::Error::from(Errno::EPERM); + assert!(maybe_unsupported(err).is_err()); + } + + #[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()); + } + + #[test] + fn test_splice_data_to_file() { + 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(); + let mut buf = [0; 1024]; + let n = temp_file.read(&mut buf).unwrap(); + assert_eq!(&buf[..n], data); + assert_eq!(bytes as usize, data.len()); + } + + #[test] + fn test_copy_exact() { + let (mut pipe_read, mut pipe_write) = pipes::pipe().unwrap(); + let data = b"Hello, world!"; + let n = pipe_write.write(data).unwrap(); + assert_eq!(n, data.len()); + let mut buf = [0; 1024]; + let n = copy_exact(pipe_read.as_raw_fd(), &pipe_write, data.len()).unwrap(); + let n2 = pipe_read.read(&mut buf).unwrap(); + assert_eq!(n, n2); + assert_eq!(&buf[..n], data); + } + + #[test] + fn test_copy_stream() { + let (mut pipe_read, mut pipe_write) = pipes::pipe().unwrap(); + let data = b"Hello, world!"; + let n = pipe_write.write(data).unwrap(); + assert_eq!(n, data.len()); + let mut buf = [0; 1024]; + let n = copy_stream(&mut pipe_read, &mut pipe_write).unwrap(); + let n2 = pipe_read.read(&mut buf).unwrap(); + assert_eq!(n as usize, n2); + assert_eq!(&buf[..n as usize], data); + } + + #[test] + fn test_splice_write() { + let (mut pipe_read, pipe_write) = pipes::pipe().unwrap(); + let data = b"Hello, world!"; + let (bytes, _) = splice_write(&pipe_read, &pipe_write).unwrap(); + let mut buf = [0; 1024]; + let n = pipe_read.read(&mut buf).unwrap(); + assert_eq!(&buf[..n], data); + assert_eq!(bytes as usize, data.len()); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6142e688d7c..fc4709aab5b 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -70,11 +70,13 @@ pub use crate::features::version_cmp; #[cfg(all(not(windows), feature = "mode"))] pub use crate::features::mode; // ** unix-only +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "buf-copy"))] +pub use crate::features::buf_copy; #[cfg(all(unix, feature = "entries"))] pub use crate::features::entries; #[cfg(all(unix, feature = "perms"))] pub use crate::features::perms; -#[cfg(all(unix, feature = "pipes"))] +#[cfg(all(unix, any(feature = "pipes", feature = "buf-copy")))] pub use crate::features::pipes; #[cfg(all(unix, feature = "process"))] pub use crate::features::process; From 2ae914b268fcc4790db444e4765b4e6941063f12 Mon Sep 17 00:00:00 2001 From: Santeri Paavolainen Date: Thu, 19 Dec 2024 14:54:24 +0200 Subject: [PATCH 157/351] uucore: correctly truncate response if getgroups shrinks (#6978) The code above this line handles the case if `res` is larger than `ngroups`, but `res < ngroups` is also a possibility, which this line attempts to address but actually does not. The original code resizes to `ngroups` which is a no-op (given that `groups` is already `ngroups` size). The correct target for re-sizing the `groups` is the result from the last `getgroups`, i.e., `res`. --- src/uucore/src/lib/features/entries.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index d1c9f9c046b..f3d1232eb59 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -83,13 +83,14 @@ pub fn get_groups() -> IOResult> { if res == -1 { let err = IOError::last_os_error(); if err.raw_os_error() == Some(libc::EINVAL) { - // Number of groups changed, retry + // Number of groups has increased, retry continue; } else { return Err(err); } } else { - groups.truncate(ngroups.try_into().unwrap()); + // Number of groups may have decreased + groups.truncate(res.try_into().unwrap()); return Ok(groups); } } From db1ed4c094b0dc63b3567191739647d9b7c1bfef Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Thu, 19 Dec 2024 17:08:05 -0500 Subject: [PATCH 158/351] core: improve OsStr(ing) helpers This adds the `os_str_as_bytes_lossy` function, for when we want infallible conversion across platforms, and improves the doc comments of similar functions to be more accurate and better formatted. --- src/uucore/src/lib/features/quoting_style.rs | 32 +++------------ src/uucore/src/lib/lib.rs | 41 ++++++++++++++------ 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/uucore/src/lib/features/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs index 2e9cd0b7ec7..6d0265dc625 100644 --- a/src/uucore/src/lib/features/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -8,8 +8,6 @@ use std::char::from_digit; use std::ffi::{OsStr, OsString}; use std::fmt; -#[cfg(unix)] -use std::os::unix::ffi::{OsStrExt, OsStringExt}; // These are characters with special meaning in the shell (e.g. bash). // The first const contains characters that only have a special meaning when they appear at the beginning of a name. @@ -462,36 +460,18 @@ fn escape_name_inner(name: &[u8], style: &QuotingStyle, dirname: bool) -> Vec OsString { - #[cfg(unix)] - { - let name = name.as_bytes(); - OsStringExt::from_vec(escape_name_inner(name, style, false)) - } - #[cfg(not(unix))] - { - let name = name.to_string_lossy(); - String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false)) - .to_string() - .into() - } + let name = crate::os_str_as_bytes_lossy(name); + crate::os_string_from_vec(escape_name_inner(&name, style, false)) + .expect("all byte sequences should be valid for platform, or already replaced in name") } /// Escape a directory name with respect to the given style. /// This is mainly meant to be used for ls' directory name printing and is not /// likely to be used elsewhere. pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> OsString { - #[cfg(unix)] - { - let name = dir_name.as_bytes(); - OsStringExt::from_vec(escape_name_inner(name, style, true)) - } - #[cfg(not(unix))] - { - let name = dir_name.to_string_lossy(); - String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, true)) - .to_string() - .into() - } + let name = crate::os_str_as_bytes_lossy(dir_name); + crate::os_string_from_vec(escape_name_inner(&name, style, true)) + .expect("all byte sequences should be valid for platform, or already replaced in name") } impl fmt::Display for QuotingStyle { diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6142e688d7c..e98a22815d4 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -253,9 +253,10 @@ pub fn read_yes() -> bool { } } -/// Helper function for processing delimiter values (which could be non UTF-8) -/// It converts OsString to &[u8] for unix targets only -/// On non-unix (i.e. Windows) it will just return an error if delimiter value is not UTF-8 +/// Converts an `OsStr` to a UTF-8 `&[u8]`. +/// +/// This always succeeds on unix platforms, +/// and fails on other platforms if the string can't be coerced to UTF-8. pub fn os_str_as_bytes(os_string: &OsStr) -> mods::error::UResult<&[u8]> { #[cfg(unix)] let bytes = os_string.as_bytes(); @@ -271,13 +272,28 @@ pub fn os_str_as_bytes(os_string: &OsStr) -> mods::error::UResult<&[u8]> { Ok(bytes) } -/// Helper function for converting a slice of bytes into an &OsStr -/// or OsString in non-unix targets. +/// Performs a potentially lossy conversion from `OsStr` to UTF-8 bytes. +/// +/// This is always lossless on unix platforms, +/// and wraps [`OsStr::to_string_lossy`] on non-unix platforms. +pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<[u8]> { + #[cfg(unix)] + let bytes = Cow::from(os_string.as_bytes()); + + #[cfg(not(unix))] + let bytes = match os_string.to_string_lossy() { + Cow::Borrowed(slice) => Cow::from(slice.as_bytes()), + Cow::Owned(owned) => Cow::from(owned.into_bytes()), + }; + + bytes +} + +/// Converts a `&[u8]` to an `&OsStr`, +/// or parses it as UTF-8 into an [`OsString`] on non-unix platforms. /// -/// It converts `&[u8]` to `Cow` for unix targets only. -/// On non-unix (i.e. Windows), the conversion goes through the String type -/// and thus undergo UTF-8 validation, making it fail if the stream contains -/// non-UTF-8 characters. +/// This always succeeds on unix platforms, +/// and fails on other platforms if the bytes can't be parsed as UTF-8. pub fn os_str_from_bytes(bytes: &[u8]) -> mods::error::UResult> { #[cfg(unix)] let os_str = Cow::Borrowed(OsStr::from_bytes(bytes)); @@ -289,9 +305,10 @@ pub fn os_str_from_bytes(bytes: &[u8]) -> mods::error::UResult> { Ok(os_str) } -/// Helper function for making an `OsString` from a byte field -/// It converts `Vec` to `OsString` for unix targets only. -/// On non-unix (i.e. Windows) it may fail if the bytes are not valid UTF-8 +/// Converts a `Vec` into an `OsString`, parsing as UTF-8 on non-unix platforms. +/// +/// This always succeeds on unix platforms, +/// and fails on other platforms if the bytes can't be parsed as UTF-8. pub fn os_string_from_vec(vec: Vec) -> mods::error::UResult { #[cfg(unix)] let s = OsString::from_vec(vec); From 74b613d15555e3743eecde9dc98ee24360359b20 Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Thu, 19 Dec 2024 21:01:56 -0500 Subject: [PATCH 159/351] Android CICD: use posix style test --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 319f7b11c17..a7dcbdbbd45 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -178,7 +178,7 @@ jobs: util/android-commands.sh sync_host util/android-commands.sh build util/android-commands.sh tests - if [[ "${{ steps.rust-cache.outputs.cache-hit }}" != 'true' ]]; then util/android-commands.sh sync_image; fi; exit 0 + if [ "${{ steps.rust-cache.outputs.cache-hit }}" != 'true' ]; then util/android-commands.sh sync_image; fi; exit 0 - name: Collect information about runner ressources if: always() continue-on-error: true From cf8a81c6c25c36757e51790da81481591593c1ca Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 20 Dec 2024 10:02:36 +0100 Subject: [PATCH 160/351] cut: fix overriding of -d= --- src/uu/cut/src/cut.rs | 29 ++++++++++++++++------------- tests/by-util/test_cut.rs | 8 +++++++- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 421b35eacea..3dde5e66595 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -350,10 +350,7 @@ fn cut_files(mut filenames: Vec, mode: &Mode) { // Get delimiter and output delimiter from `-d`/`--delimiter` and `--output-delimiter` options respectively // Allow either delimiter to have a value that is neither UTF-8 nor ASCII to align with GNU behavior -fn get_delimiters( - matches: &ArgMatches, - delimiter_is_equal: bool, -) -> UResult<(Delimiter, Option<&[u8]>)> { +fn get_delimiters(matches: &ArgMatches) -> UResult<(Delimiter, Option<&[u8]>)> { let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED); let delim_opt = matches.get_one::(options::DELIMITER); let delim = match delim_opt { @@ -364,12 +361,7 @@ fn get_delimiters( )); } Some(os_string) => { - // GNU's `cut` supports `-d=` to set the delimiter to `=`. - // Clap parsing is limited in this situation, see: - // https://github.com/uutils/coreutils/issues/2424#issuecomment-863825242 - if delimiter_is_equal { - Delimiter::Slice(b"=") - } else if os_string == "''" || os_string.is_empty() { + if os_string == "''" || os_string.is_empty() { // treat `''` as empty delimiter Delimiter::Slice(b"\0") } else { @@ -423,15 +415,26 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect::>(); + // GNU's `cut` supports `-d=` to set the delimiter to `=`. + // Clap parsing is limited in this situation, see: + // https://github.com/uutils/coreutils/issues/2424#issuecomment-863825242 + let args: Vec = args + .into_iter() + .map(|x| { + if x == "-d=" { + "--delimiter==".into() + } else { + x + } + }) + .collect(); - let delimiter_is_equal = args.contains(&OsString::from("-d=")); // special case let matches = uu_app().try_get_matches_from(args)?; let complement = matches.get_flag(options::COMPLEMENT); let only_delimited = matches.get_flag(options::ONLY_DELIMITED); - let (delimiter, out_delimiter) = get_delimiters(&matches, delimiter_is_equal)?; + let (delimiter, out_delimiter) = get_delimiters(&matches)?; let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); // Only one, and only one of cutting mode arguments, i.e. `-b`, `-c`, `-f`, diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 6c6914a122a..c9a932a67d3 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -299,9 +299,15 @@ fn test_newline_as_delimiter() { fn test_multiple_delimiters() { new_ucmd!() .args(&["-f2", "-d:", "-d="]) - .pipe_in("a=b\n") + .pipe_in("a:=b\n") .succeeds() .stdout_only("b\n"); + + new_ucmd!() + .args(&["-f2", "-d=", "-d:"]) + .pipe_in("a:=b\n") + .succeeds() + .stdout_only("=b\n"); } #[test] From 31ffc3a0ebd444edeb72db8fb7edf36a24ad180b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 18 Dec 2024 20:42:28 +0100 Subject: [PATCH 161/351] mkfifo: better handle the mode + umask Should make tests/misc/mknod.sh pass --- src/uu/mkfifo/Cargo.toml | 2 +- src/uu/mkfifo/src/mkfifo.rs | 19 ++++++++++++--- tests/by-util/test_mkfifo.rs | 47 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 960ed601d16..68f16a1f63f 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -19,7 +19,7 @@ path = "src/mkfifo.rs" [dependencies] clap = { workspace = true } libc = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "mkfifo" diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 9320f76ed04..01fc5dc1e60 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -6,6 +6,8 @@ use clap::{crate_version, Arg, ArgAction, Command}; use libc::mkfifo; use std::ffi::CString; +use std::fs; +use std::os::unix::fs::PermissionsExt; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show}; @@ -32,11 +34,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } let mode = match matches.get_one::(options::MODE) { + // if mode is passed, ignore umask Some(m) => match usize::from_str_radix(m, 8) { Ok(m) => m, Err(e) => return Err(USimpleError::new(1, format!("invalid mode: {e}"))), }, - None => 0o666, + // Default value + umask if present + None => 0o666 & !(uucore::mode::get_umask() as usize), }; let fifos: Vec = match matches.get_many::(options::FIFO) { @@ -47,12 +51,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for f in fifos { let err = unsafe { let name = CString::new(f.as_bytes()).unwrap(); - mkfifo(name.as_ptr(), mode as libc::mode_t) + mkfifo(name.as_ptr(), 0o666) }; if err == -1 { show!(USimpleError::new( 1, - format!("cannot create fifo {}: File exists", f.quote()) + format!("cannot create fifo {}: File exists", f.quote()), + )); + } + + // Explicitly set the permissions to ignore umask + if let Err(e) = fs::set_permissions(&f, fs::Permissions::from_mode(mode as u32)) { + return Err(USimpleError::new( + 1, + format!("cannot set permissions on {}: {}", f.quote(), e), )); } } @@ -71,7 +83,6 @@ pub fn uu_app() -> Command { .short('m') .long(options::MODE) .help("file permissions for the fifo") - .default_value("0666") .value_name("MODE"), ) .arg( diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index 731b6c1d5cd..e25bbfc4494 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -52,3 +52,50 @@ fn test_create_one_fifo_already_exists() { .fails() .stderr_is("mkfifo: cannot create fifo 'abcdef': File exists\n"); } + +#[test] +fn test_create_fifo_with_mode_and_umask() { + use uucore::fs::display_permissions; + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let test_fifo_creation = |mode: &str, umask: u16, expected: &str| { + scene + .ucmd() + .arg("-m") + .arg(mode) + .arg(format!("fifo_test_{mode}")) + .umask(libc::mode_t::from(umask)) + .succeeds(); + + let metadata = std::fs::metadata(at.subdir.join(format!("fifo_test_{mode}"))).unwrap(); + let permissions = display_permissions(&metadata, true); + assert_eq!(permissions, expected.to_string()); + }; + + test_fifo_creation("734", 0o077, "prwx-wxr--"); // spell-checker:disable-line + test_fifo_creation("706", 0o777, "prwx---rw-"); // spell-checker:disable-line +} + +#[test] +fn test_create_fifo_with_umask() { + use uucore::fs::display_permissions; + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let test_fifo_creation = |umask: u16, expected: &str| { + scene + .ucmd() + .arg("fifo_test") + .umask(libc::mode_t::from(umask)) + .succeeds(); + + let metadata = std::fs::metadata(at.subdir.join("fifo_test")).unwrap(); + let permissions = display_permissions(&metadata, true); + assert_eq!(permissions, expected.to_string()); + at.remove("fifo_test"); + }; + + test_fifo_creation(0o022, "prw-r--r--"); // spell-checker:disable-line + test_fifo_creation(0o777, "p---------"); // spell-checker:disable-line +} From d762a1633f33576332cd2f860ac1298df674a065 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:31:59 +0000 Subject: [PATCH 162/351] chore(deps): update rust crate thiserror to v2.0.9 --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84c28bcd97f..972a4efcfef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2317,11 +2317,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.8" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ - "thiserror-impl 2.0.8", + "thiserror-impl 2.0.9", ] [[package]] @@ -2337,9 +2337,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.8" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", @@ -2538,7 +2538,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.8", + "thiserror 2.0.9", "uucore", ] @@ -2550,7 +2550,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.8", + "thiserror 2.0.9", "uucore", ] @@ -2627,7 +2627,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.8", + "thiserror 2.0.9", "uucore", ] @@ -3143,7 +3143,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.8", + "thiserror 2.0.9", "uucore", ] @@ -3422,7 +3422,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.8", + "thiserror 2.0.9", "utmp-classic", "uucore", ] @@ -3453,7 +3453,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.8", + "thiserror 2.0.9", "unicode-width 0.2.0", "uucore", ] @@ -3515,7 +3515,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.8", + "thiserror 2.0.9", "time", "uucore_procs", "walkdir", @@ -3988,7 +3988,7 @@ dependencies = [ "flate2", "indexmap", "memchr", - "thiserror 2.0.8", + "thiserror 2.0.9", "zopfli", ] From 1337c6f174d6364e9d642c956c93963954eb6427 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Dec 2024 12:52:05 +0100 Subject: [PATCH 163/351] Try to report no longer SKIP --- .github/workflows/GnuTests.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 04b487f5c27..bba5de96cb4 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -249,11 +249,15 @@ jobs: CURRENT_RUN_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${new_log_file}" | sort) REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${ref_log_file}"| sort) CURRENT_RUN_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${new_log_file}" | sort) - echo "Detailled information:" + REF_SKIP=$(sed -n "s/^SKIP: \([[:print:]]\+\).*/\1/p" "${ref_log_file}"| sort) + CURRENT_RUN_SKIP=$(sed -n "s/^SKIP: \([[:print:]]\+\).*/\1/p" "${new_log_file}" | sort) + + echo "Detailed information:" echo "REF_ERROR = ${REF_ERROR}" echo "CURRENT_RUN_ERROR = ${CURRENT_RUN_ERROR}" echo "REF_FAILING = ${REF_FAILING}" echo "CURRENT_RUN_FAILING = ${CURRENT_RUN_FAILING}" + echo "REF_SKIP_PASS = ${REF_SKIP}" # Compare failing and error tests for LINE in ${CURRENT_RUN_FAILING} @@ -313,6 +317,17 @@ jobs: echo $MSG >> ${COMMENT_LOG} fi done + + for LINE in ${REF_SKIP} + do + if ! grep -Fxq ${LINE}<<<"${CURRENT_RUN_SKIP}" + then + MSG="Congrats! The gnu test ${LINE} is no longer SKIP! (might be PASS, ERROR or FAIL)" + echo "::warning ::$MSG" + echo $MSG >> ${COMMENT_LOG} + fi + done + else echo "::warning ::Skipping ${test_type} test failure comparison; no prior reference test logs are available." fi From b1d4e1b8128167ba5dc78ce749609456ed38463a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Dec 2024 14:12:57 +0100 Subject: [PATCH 164/351] gnu comment: explain what might be the state --- .github/workflows/GnuTests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index bba5de96cb4..ab973defca2 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -312,7 +312,7 @@ jobs: do if ! grep -Fxq ${LINE}<<<"${CURRENT_RUN_ERROR}" then - MSG="Congrats! The gnu test ${LINE} is no longer ERROR!" + MSG="Congrats! The gnu test ${LINE} is no longer ERROR! (might be PASS or FAIL)" echo "::warning ::$MSG" echo $MSG >> ${COMMENT_LOG} fi From efe3cda6ffe900efab8b2e7d5259218f17cabcb1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 22 Dec 2024 14:26:35 +0100 Subject: [PATCH 165/351] echo: remove code made obsolete by MSRV 1.79 --- src/uu/echo/src/echo.rs | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 746cdd7c59e..2d2884b1dd7 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -208,13 +208,6 @@ fn print_escaped(input: &[u8], output: &mut StdoutLock) -> io::Result= 1.79.0 - // https://github.com/rust-lang/rust/pull/121346 - // TODO: when we have a MSRV >= 1.79.0, delete these "hold" bindings - let hold_one_byte_outside_of_match: [u8; 1_usize]; - let hold_two_bytes_outside_of_match: [u8; 2_usize]; - let unescaped: &[u8] = match *next { b'\\' => br"\", b'a' => b"\x07", @@ -230,12 +223,7 @@ fn print_escaped(input: &[u8], output: &mut StdoutLock) -> io::Result= 1.79.0 - hold_one_byte_outside_of_match = [parsed_hexadecimal_number]; - - // TODO: when we have a MSRV >= 1.79.0, return reference to a temporary array: - // &[parsed_hexadecimal_number] - &hold_one_byte_outside_of_match + &[parsed_hexadecimal_number] } else { // "\x" with any non-hexadecimal digit after means "\x" is treated literally br"\x" @@ -246,12 +234,7 @@ fn print_escaped(input: &[u8], output: &mut StdoutLock) -> io::Result= 1.79.0 - hold_one_byte_outside_of_match = [parsed_octal_number]; - - // TODO: when we have a MSRV >= 1.79.0, return reference to a temporary array: - // &[parsed_octal_number] - &hold_one_byte_outside_of_match + &[parsed_octal_number] } else { // "\0" with any non-octal digit after it means "\0" is treated as ASCII '\0' (NUL), 0x00 b"\0" @@ -259,9 +242,7 @@ fn print_escaped(input: &[u8], output: &mut StdoutLock) -> io::Result { // Backslash and the following byte are treated literally - hold_two_bytes_outside_of_match = [b'\\', other_byte]; - - &hold_two_bytes_outside_of_match + &[b'\\', other_byte] } }; From 392c48002cc966e80ab018d49b3969bcf5c5756b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 22 Dec 2024 14:53:37 +0100 Subject: [PATCH 166/351] cut: don't merge adjacent ranges --- src/uucore/src/lib/features/ranges.rs | 11 ++++------- tests/by-util/test_cut.rs | 2 -- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/uucore/src/lib/features/ranges.rs b/src/uucore/src/lib/features/ranges.rs index 222be7ca3a3..88851b9aae9 100644 --- a/src/uucore/src/lib/features/ranges.rs +++ b/src/uucore/src/lib/features/ranges.rs @@ -91,7 +91,7 @@ impl Range { Ok(Self::merge(ranges)) } - /// Merge any overlapping ranges + /// Merge any overlapping ranges. Adjacent ranges are *NOT* merged. /// /// Is guaranteed to return only disjoint ranges in a sorted order. fn merge(mut ranges: Vec) -> Vec { @@ -101,10 +101,7 @@ impl Range { for i in 0..ranges.len() { let j = i + 1; - // The +1 is a small optimization, because we can merge adjacent Ranges. - // For example (1,3) and (4,6), because in the integers, there are no - // possible values between 3 and 4, this is equivalent to (1,6). - while j < ranges.len() && ranges[j].low <= ranges[i].high + 1 { + while j < ranges.len() && ranges[j].low <= ranges[i].high { let j_high = ranges.remove(j).high; ranges[i].high = max(ranges[i].high, j_high); } @@ -216,8 +213,8 @@ mod test { &[r(10, 40), r(50, 60)], ); - // Merge adjacent ranges - m(vec![r(1, 3), r(4, 6)], &[r(1, 6)]); + // Don't merge adjacent ranges + m(vec![r(1, 3), r(4, 6)], &[r(1, 3), r(4, 6)]); } #[test] diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index c9a932a67d3..1aa3c126a23 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -350,7 +350,6 @@ fn test_newline_preservation_with_f1_option() { ucmd.args(&["-f1-", "1"]).succeeds().stdout_is(expected); } -#[ignore = "Not yet implemented"] #[test] fn test_output_delimiter_with_character_ranges() { new_ucmd!() @@ -360,7 +359,6 @@ fn test_output_delimiter_with_character_ranges() { .stdout_only("bc:defg\n"); } -#[ignore = "Not yet implemented"] #[test] fn test_output_delimiter_with_adjacent_ranges() { new_ucmd!() From d25d2df7f9dc9579eae59ae77e7882145622136e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 14:23:29 +0000 Subject: [PATCH 167/351] chore(deps): update rust crate platform-info to v2.0.5 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 972a4efcfef..435d1a39d73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1711,9 +1711,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "platform-info" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91077ffd05d058d70d79eefcd7d7f6aac34980860a7519960f7913b6563a8c3a" +checksum = "7539aeb3fdd8cb4f6a331307cf71a1039cee75e94e8a71725b9484f4a0d9451a" dependencies = [ "libc", "winapi", From 9a97c18877691f0f17b0fc1b3c0d9b21d2354b14 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 21 Dec 2024 21:18:38 +0100 Subject: [PATCH 168/351] ls: when a file has capabilities (setcap), change the color Should fix tests/ls/capability.sh --- src/uu/ls/src/colors.rs | 16 ++++++++++++++ tests/by-util/test_ls.rs | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/uu/ls/src/colors.rs b/src/uu/ls/src/colors.rs index 6c580d18a7a..0e314e5a4f4 100644 --- a/src/uu/ls/src/colors.rs +++ b/src/uu/ls/src/colors.rs @@ -156,6 +156,22 @@ pub(crate) fn color_name( target_symlink: Option<&PathData>, wrap: bool, ) -> String { + #[cfg(any(not(unix), target_os = "android", target_os = "macos"))] + let has_capabilities = false; + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + // Check if the file has capabilities + let has_capabilities = uucore::fsxattr::has_acl(path.p_buf.as_path()); + + // If the file has capabilities, use a specific style for `ca` (capabilities) + if has_capabilities { + if let Some(style) = style_manager + .colors + .style_for_indicator(Indicator::Capabilities) + { + return style_manager.apply_style(Some(style), name, wrap); + } + } + if !path.must_dereference { // If we need to dereference (follow) a symlink, we will need to get the metadata if let Some(de) = &path.de { diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 3b2d46b39c2..f65078a0d5a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs mdir COLORTERM mexe bcdef mfoo +// spell-checker:ignore (words) fakeroot setcap #![allow( clippy::similar_names, clippy::too_many_lines, @@ -5516,3 +5517,49 @@ fn test_suffix_case_sensitivity() { /* cSpell:enable */ ); } + +#[cfg(all(unix, target_os = "linux"))] +#[test] +fn test_ls_capabilities() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + // Test must be run as root (or with `sudo -E`) + // fakeroot setcap cap_net_bind_service=ep /tmp/file_name + // doesn't trigger an error and fails silently + if scene.cmd("whoami").run().stdout_str() != "root\n" { + return; + } + at.mkdir("test"); + at.mkdir("test/dir"); + at.touch("test/cap_pos"); + at.touch("test/dir/cap_neg"); + at.touch("test/dir/cap_pos"); + + let files = ["test/cap_pos", "test/dir/cap_pos"]; + for file in &files { + scene + .cmd("sudo") + .args(&[ + "-E", + "--non-interactive", + "setcap", + "cap_net_bind_service=ep", + at.plus(file).to_str().unwrap(), + ]) + .succeeds(); + } + + let ls_colors = "di=:ca=30;41"; + + scene + .ucmd() + .env("LS_COLORS", ls_colors) + .arg("--color=always") + .arg("test/cap_pos") + .arg("test/dir") + .succeeds() + .stdout_contains("\x1b[30;41mtest/cap_pos") // spell-checker:disable-line + .stdout_contains("\x1b[30;41mcap_pos") // spell-checker:disable-line + .stdout_does_not_contain("0;41mtest/dir/cap_neg"); // spell-checker:disable-line +} From ffc6eb094a646554fdd2c456a7cfd572e6e4c700 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 21 Dec 2024 22:51:29 +0100 Subject: [PATCH 169/351] ls: Don't call the capabilites features of the system when passed an empty ca= in LS_COLORS In parallel, in the GNU test, adjust the GNU tests as we don't use libcap but xattr instead. --- .../cspell.dictionaries/jargon.wordlist.txt | 3 +++ src/uu/ls/src/colors.rs | 26 +++++++++++-------- util/gnu-patches/tests_ls_no_cap.patch | 22 ++++++++++++++++ 3 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 util/gnu-patches/tests_ls_no_cap.patch diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 6dd5483c6c1..4109630e553 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -10,6 +10,7 @@ bytewise canonicalization canonicalize canonicalizing +capget codepoint codepoints codegen @@ -65,6 +66,7 @@ kibi kibibytes libacl lcase +llistxattr lossily lstat mebi @@ -108,6 +110,7 @@ seedable semver semiprime semiprimes +setcap setfacl shortcode shortcodes diff --git a/src/uu/ls/src/colors.rs b/src/uu/ls/src/colors.rs index 0e314e5a4f4..4f97e42e27d 100644 --- a/src/uu/ls/src/colors.rs +++ b/src/uu/ls/src/colors.rs @@ -156,19 +156,23 @@ pub(crate) fn color_name( target_symlink: Option<&PathData>, wrap: bool, ) -> String { - #[cfg(any(not(unix), target_os = "android", target_os = "macos"))] - let has_capabilities = false; - #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] // Check if the file has capabilities - let has_capabilities = uucore::fsxattr::has_acl(path.p_buf.as_path()); - - // If the file has capabilities, use a specific style for `ca` (capabilities) - if has_capabilities { - if let Some(style) = style_manager + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + { + // Skip checking capabilities if LS_COLORS=ca=: + let capabilities = style_manager .colors - .style_for_indicator(Indicator::Capabilities) - { - return style_manager.apply_style(Some(style), name, wrap); + .style_for_indicator(Indicator::Capabilities); + + let has_capabilities = if capabilities.is_none() { + false + } else { + uucore::fsxattr::has_acl(path.p_buf.as_path()) + }; + + // If the file has capabilities, use a specific style for `ca` (capabilities) + if has_capabilities { + return style_manager.apply_style(capabilities, name, wrap); } } diff --git a/util/gnu-patches/tests_ls_no_cap.patch b/util/gnu-patches/tests_ls_no_cap.patch new file mode 100644 index 00000000000..5944e3f5661 --- /dev/null +++ b/util/gnu-patches/tests_ls_no_cap.patch @@ -0,0 +1,22 @@ +diff --git a/tests/ls/no-cap.sh b/tests/ls/no-cap.sh +index 3d84c74ff..d1f60e70a 100755 +--- a/tests/ls/no-cap.sh ++++ b/tests/ls/no-cap.sh +@@ -21,13 +21,13 @@ print_ver_ ls + require_strace_ capget + + LS_COLORS=ca=1; export LS_COLORS +-strace -e capget ls --color=always > /dev/null 2> out || fail=1 +-$EGREP 'capget\(' out || skip_ "your ls doesn't call capget" ++strace -e llistxattr ls --color=always > /dev/null 2> out || fail=1 ++$EGREP 'llistxattr\(' out || skip_ "your ls doesn't call llistxattr" + + rm -f out + + LS_COLORS=ca=:; export LS_COLORS +-strace -e capget ls --color=always > /dev/null 2> out || fail=1 +-$EGREP 'capget\(' out && fail=1 ++strace -e llistxattr ls --color=always > /dev/null 2> out || fail=1 ++$EGREP 'llistxattr\(' out && fail=1 + + Exit $fail From cec9d9f00a767f086c4d63467eadbe957184075b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Dec 2024 16:31:56 +0100 Subject: [PATCH 170/351] GnuTests: also display CURRENT_RUN_SKIP for debug purposes --- .github/workflows/GnuTests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index ab973defca2..0b9d8ce7fe4 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -258,6 +258,7 @@ jobs: echo "REF_FAILING = ${REF_FAILING}" echo "CURRENT_RUN_FAILING = ${CURRENT_RUN_FAILING}" echo "REF_SKIP_PASS = ${REF_SKIP}" + echo "CURRENT_RUN_SKIP = ${CURRENT_RUN_SKIP}" # Compare failing and error tests for LINE in ${CURRENT_RUN_FAILING} From e4d03122654ee9946bc5b710807e8ac5cf8a9c58 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 19:40:37 +0000 Subject: [PATCH 171/351] chore(deps): update vmactions/freebsd-vm action to v1.1.6 --- .github/workflows/freebsd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 42255d8899a..27ff2afe4d4 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -41,7 +41,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.1.5 + uses: vmactions/freebsd-vm@v1.1.6 with: usesh: true sync: rsync @@ -135,7 +135,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.1.5 + uses: vmactions/freebsd-vm@v1.1.6 with: usesh: true sync: rsync From 2c2f5f14a40d762374e02f3f96985ee872c3d912 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 24 Dec 2024 16:03:00 +0100 Subject: [PATCH 172/351] echo: use succeeds() to simplify some tests --- tests/by-util/test_echo.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 136500b4894..8cb60877cac 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -219,8 +219,7 @@ fn test_hyphen_values_at_start() { .arg("-test") .arg("araba") .arg("-merci") - .run() - .success() + .succeeds() .stdout_does_not_contain("-E") .stdout_is("-test araba -merci\n"); } @@ -231,8 +230,7 @@ fn test_hyphen_values_between() { .arg("test") .arg("-E") .arg("araba") - .run() - .success() + .succeeds() .stdout_is("test -E araba\n"); new_ucmd!() @@ -240,8 +238,7 @@ fn test_hyphen_values_between() { .arg("dum dum dum") .arg("-e") .arg("dum") - .run() - .success() + .succeeds() .stdout_is("dumdum dum dum dum -e dum\n"); } From 90465357e2d313cf51ad5f470cc6bfd5c339c39c Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 24 Dec 2024 17:01:45 +0100 Subject: [PATCH 173/351] echo: handle double hyphens --- src/uu/echo/src/echo.rs | 19 ++++++++++++++++++- tests/by-util/test_echo.rs | 10 ++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 2d2884b1dd7..097e4f2e980 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -255,9 +255,26 @@ fn print_escaped(input: &[u8], output: &mut StdoutLock) -> io::Result impl uucore::Args { + let mut result = Vec::new(); + let mut is_first_double_hyphen = true; + + for arg in args { + if arg == "--" && is_first_double_hyphen { + result.push(OsString::from("--")); + is_first_double_hyphen = false; + } + result.push(arg); + } + + result.into_iter() +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from(args); + let matches = uu_app().get_matches_from(handle_double_hyphens(args)); // 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." diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 8cb60877cac..dd6b412a429 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -242,6 +242,16 @@ fn test_hyphen_values_between() { .stdout_is("dumdum dum dum dum -e dum\n"); } +#[test] +fn test_double_hyphens() { + new_ucmd!().arg("--").succeeds().stdout_only("--\n"); + new_ucmd!() + .arg("--") + .arg("--") + .succeeds() + .stdout_only("-- --\n"); +} + #[test] fn wrapping_octal() { // Some odd behavior of GNU. Values of \0400 and greater do not fit in the From e221d2a624ebb1df1b623b4dc55c5bb7cfd57774 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 25 Dec 2024 09:39:03 +0100 Subject: [PATCH 174/351] comm: adapt GNU error messages --- util/gnu-patches/tests_comm.pl.patch | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 util/gnu-patches/tests_comm.pl.patch diff --git a/util/gnu-patches/tests_comm.pl.patch b/util/gnu-patches/tests_comm.pl.patch new file mode 100644 index 00000000000..d3d5595a2c5 --- /dev/null +++ b/util/gnu-patches/tests_comm.pl.patch @@ -0,0 +1,44 @@ +diff --git a/tests/misc/comm.pl b/tests/misc/comm.pl +index 5bd5f56d7..8322d92ba 100755 +--- a/tests/misc/comm.pl ++++ b/tests/misc/comm.pl +@@ -73,18 +73,24 @@ my @Tests = + + # invalid missing command line argument (1) + ['missing-arg1', $inputs[0], {EXIT=>1}, +- {ERR => "$prog: missing operand after 'a'\n" +- . "Try '$prog --help' for more information.\n"}], ++ {ERR => "error: the following required arguments were not provided:\n" ++ . " \n\n" ++ . "Usage: $prog [OPTION]... FILE1 FILE2\n\n" ++ . "For more information, try '--help'.\n"}], + + # invalid missing command line argument (both) + ['missing-arg2', {EXIT=>1}, +- {ERR => "$prog: missing operand\n" +- . "Try '$prog --help' for more information.\n"}], ++ {ERR => "error: the following required arguments were not provided:\n" ++ . " \n" ++ . " \n\n" ++ . "Usage: $prog [OPTION]... FILE1 FILE2\n\n" ++ . "For more information, try '--help'.\n"}], + + # invalid extra command line argument + ['extra-arg', @inputs, 'no-such', {EXIT=>1}, +- {ERR => "$prog: extra operand 'no-such'\n" +- . "Try '$prog --help' for more information.\n"}], ++ {ERR => "error: unexpected argument 'no-such' found\n\n" ++ . "Usage: $prog [OPTION]... FILE1 FILE2\n\n" ++ . "For more information, try '--help'.\n"}], + + # out-of-order input + ['ooo', {IN=>{a=>"1\n3"}}, {IN=>{b=>"3\n2"}}, {EXIT=>1}, +@@ -163,7 +169,7 @@ my @Tests = + + # invalid dual delimiter + ['delim-dual', '--output-delimiter=,', '--output-delimiter=+', @inputs, +- {EXIT=>1}, {ERR => "$prog: multiple output delimiters specified\n"}], ++ {EXIT=>1}, {ERR => "$prog: multiple conflicting output delimiters specified\n"}], + + # valid dual delimiter specification + ['delim-dual2', '--output-delimiter=,', '--output-delimiter=,', @inputs, From 2bfa45652e0a651fda1b3e00a0e9d4f3674ccd1c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Dec 2024 07:24:21 +0000 Subject: [PATCH 175/351] fix(deps): update rust crate quote to v1.0.38 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 435d1a39d73..2717a64e25f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1805,9 +1805,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] From db37c316af1534bae2646a65eaab3dceabbad418 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 26 Dec 2024 09:40:55 +0100 Subject: [PATCH 176/351] csplit: add support for -q --- src/uu/csplit/src/csplit.rs | 3 ++- tests/by-util/test_csplit.rs | 27 ++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 2054e6cff4e..0602f0deec7 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -621,8 +621,9 @@ pub fn uu_app() -> Command { ) .arg( Arg::new(options::QUIET) - .short('s') + .short('q') .long(options::QUIET) + .visible_short_alias('s') .visible_alias("silent") .help("do not print counts of output file sizes") .action(ArgAction::SetTrue), diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 03b8c92fc09..10ead0b4544 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -387,18 +387,23 @@ fn test_option_keep() { #[test] fn test_option_quiet() { - let (at, mut ucmd) = at_and_ucmd!(); - ucmd.args(&["--quiet", "numbers50.txt", "13", "%25%", "/0$/"]) - .succeeds() - .no_stdout(); + for arg in ["-q", "--quiet", "-s", "--silent"] { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&[arg, "numbers50.txt", "13", "%25%", "/0$/"]) + .succeeds() + .no_stdout(); - let count = glob(&at.plus_as_string("xx*")) - .expect("there should be splits created") - .count(); - assert_eq!(count, 3); - assert_eq!(at.read("xx00"), generate(1, 13)); - assert_eq!(at.read("xx01"), generate(25, 30)); - assert_eq!(at.read("xx02"), generate(30, 51)); + let count = glob(&at.plus_as_string("xx*")) + .expect("there should be splits created") + .count(); + assert_eq!(count, 3); + assert_eq!(at.read("xx00"), generate(1, 13)); + assert_eq!(at.read("xx01"), generate(25, 30)); + assert_eq!(at.read("xx02"), generate(30, 51)); + at.remove("xx00"); + at.remove("xx01"); + at.remove("xx02"); + } } #[test] From 1180905b5e8746cc480638be4929aa524fcf725b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 26 Dec 2024 16:43:53 +0100 Subject: [PATCH 177/351] cp: use the function from uucore --- src/uu/cp/Cargo.toml | 1 + src/uu/cp/src/cp.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 6801e6a0960..3912f3308a5 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -30,6 +30,7 @@ uucore = { workspace = true, features = [ "backup-control", "entries", "fs", + "fsxattr", "perms", "mode", "update-control", diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 32168b09009..2f1f1ef1e07 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -17,6 +17,8 @@ use std::os::unix::ffi::OsStrExt; #[cfg(unix)] use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::{Path, PathBuf, StripPrefixError}; +#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +use uucore::fsxattr::copy_xattrs; use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command}; use filetime::FileTime; @@ -1603,16 +1605,11 @@ pub(crate) fn copy_attributes( })?; handle_preserve(&attributes.xattr, || -> CopyResult<()> { - #[cfg(all(unix, not(target_os = "android")))] + #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] { - let xattrs = xattr::list(source)?; - for attr in xattrs { - if let Some(attr_value) = xattr::get(source, attr.clone())? { - xattr::set(dest, attr, &attr_value[..])?; - } - } + copy_xattrs(source, dest)?; } - #[cfg(not(all(unix, not(target_os = "android"))))] + #[cfg(not(all(unix, not(any(target_os = "android", target_os = "macos")))))] { // The documentation for GNU cp states: // From 2deeb7882c58f6ed457bd40bd93f93ab70341b11 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 26 Dec 2024 17:10:01 +0100 Subject: [PATCH 178/351] xattr feature: enable it on mac too --- src/uu/cp/src/cp.rs | 6 +++--- src/uucore/src/lib/features.rs | 2 +- src/uucore/src/lib/lib.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 2f1f1ef1e07..b7469404757 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -17,7 +17,7 @@ use std::os::unix::ffi::OsStrExt; #[cfg(unix)] use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::path::{Path, PathBuf, StripPrefixError}; -#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] +#[cfg(all(unix, not(target_os = "android")))] use uucore::fsxattr::copy_xattrs; use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command}; @@ -1605,11 +1605,11 @@ pub(crate) fn copy_attributes( })?; handle_preserve(&attributes.xattr, || -> CopyResult<()> { - #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] + #[cfg(all(unix, not(target_os = "android")))] { copy_xattrs(source, dest)?; } - #[cfg(not(all(unix, not(any(target_os = "android", target_os = "macos")))))] + #[cfg(not(all(unix, not(target_os = "android"))))] { // The documentation for GNU cp states: // diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index dfe5b773312..cde1cf264a3 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -54,7 +54,7 @@ pub mod process; #[cfg(all(target_os = "linux", feature = "tty"))] pub mod tty; -#[cfg(all(unix, not(target_os = "macos"), feature = "fsxattr"))] +#[cfg(all(unix, feature = "fsxattr"))] pub mod fsxattr; #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] pub mod signals; diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 3200145bd73..3a6a537ad49 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -99,7 +99,7 @@ pub use crate::features::wide; #[cfg(feature = "fsext")] pub use crate::features::fsext; -#[cfg(all(unix, not(target_os = "macos"), feature = "fsxattr"))] +#[cfg(all(unix, feature = "fsxattr"))] pub use crate::features::fsxattr; //## core functions From 98c9be5ec4d5a61f52ea4582aab9918599d0a30a Mon Sep 17 00:00:00 2001 From: Solomon Date: Thu, 26 Dec 2024 12:48:29 -0700 Subject: [PATCH 179/351] mv: improve move-to-self error handling (#6995) - improve move-to-self detection, so this errors without data loss: ```diff mkdir mydir mv mydir mydir/subdir -mv: No such file or directory (os error 2) +mv: cannot move 'mydir' to a subdirectory of itself, 'mydir/subdir' ``` - align "cannot move source to a subdirectory of itself" and "same file" errors more closely with coreutils: ```diff mkdir mydir mv mydir/ mydir/.. -mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/../mydir/' +mv: 'mydir/' and 'mydir/../mydir' are the same file ``` Causing: https://github.com/nushell/nushell/issues/13082 --- src/uu/mv/src/error.rs | 12 +--- src/uu/mv/src/mv.rs | 137 +++++++++++++++++++++++++-------------- tests/by-util/test_mv.rs | 102 ++++++++++++++--------------- 3 files changed, 140 insertions(+), 111 deletions(-) diff --git a/src/uu/mv/src/error.rs b/src/uu/mv/src/error.rs index f989d4e1332..6daa8188ec1 100644 --- a/src/uu/mv/src/error.rs +++ b/src/uu/mv/src/error.rs @@ -12,7 +12,6 @@ pub enum MvError { NoSuchFile(String), CannotStatNotADirectory(String), SameFile(String, String), - SelfSubdirectory(String), SelfTargetSubdirectory(String, String), DirectoryToNonDirectory(String), NonDirectoryToDirectory(String, String), @@ -29,14 +28,9 @@ impl Display for MvError { Self::NoSuchFile(s) => write!(f, "cannot stat {s}: No such file or directory"), Self::CannotStatNotADirectory(s) => write!(f, "cannot stat {s}: Not a directory"), Self::SameFile(s, t) => write!(f, "{s} and {t} are the same file"), - Self::SelfSubdirectory(s) => write!( - f, - "cannot move '{s}' to a subdirectory of itself, '{s}/{s}'" - ), - Self::SelfTargetSubdirectory(s, t) => write!( - f, - "cannot move '{s}' to a subdirectory of itself, '{t}/{s}'" - ), + Self::SelfTargetSubdirectory(s, t) => { + write!(f, "cannot move {s} to a subdirectory of itself, {t}") + } Self::DirectoryToNonDirectory(t) => { write!(f, "cannot overwrite directory {t} with non-directory") } diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 7debf52c962..675982bacba 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -19,13 +19,13 @@ use std::io; use std::os::unix; #[cfg(windows)] use std::os::windows; -use std::path::{Path, PathBuf}; +use std::path::{absolute, Path, PathBuf}; use uucore::backup_control::{self, source_is_target_backup}; use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{ - are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file, - path_ends_with_terminator, + are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file, canonicalize, + path_ends_with_terminator, MissingHandling, ResolveMode, }; #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] use uucore::fsxattr; @@ -322,20 +322,6 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> }); } - if (source.eq(target) - || are_hardlinks_to_same_file(source, target) - || are_hardlinks_or_one_way_symlink_to_same_file(source, target)) - && opts.backup == BackupMode::NoBackup - { - if source.eq(Path::new(".")) || source.ends_with("/.") || source.is_file() { - return Err( - MvError::SameFile(source.quote().to_string(), target.quote().to_string()).into(), - ); - } else { - return Err(MvError::SelfSubdirectory(source.display().to_string()).into()); - } - } - let target_is_dir = target.is_dir(); let source_is_dir = source.is_dir(); @@ -347,6 +333,8 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> return Err(MvError::FailedToAccessNotADirectory(target.quote().to_string()).into()); } + assert_not_same_file(source, target, target_is_dir, opts)?; + if target_is_dir { if opts.no_target_dir { if source.is_dir() { @@ -356,14 +344,6 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> } else { Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into()) } - // Check that source & target do not contain same subdir/dir when both exist - // mkdir dir1/dir2; mv dir1 dir1/dir2 - } else if target.starts_with(source) { - Err(MvError::SelfTargetSubdirectory( - source.display().to_string(), - target.display().to_string(), - ) - .into()) } else { move_files_into_dir(&[source.to_path_buf()], target, opts) } @@ -387,6 +367,88 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> } } +fn assert_not_same_file( + source: &Path, + target: &Path, + target_is_dir: bool, + opts: &Options, +) -> UResult<()> { + // we'll compare canonicalized_source and canonicalized_target for same file detection + let canonicalized_source = match canonicalize( + absolute(source)?, + MissingHandling::Normal, + ResolveMode::Logical, + ) { + Ok(source) if source.exists() => source, + _ => absolute(source)?, // file or symlink target doesn't exist but its absolute path is still used for comparison + }; + + // special case if the target exists, is a directory, and the `-T` flag wasn't used + let target_is_dir = target_is_dir && !opts.no_target_dir; + let canonicalized_target = if target_is_dir { + // `mv source_file target_dir` => target_dir/source_file + // canonicalize the path that exists (target directory) and join the source file name + canonicalize( + absolute(target)?, + MissingHandling::Normal, + ResolveMode::Logical, + )? + .join(source.file_name().unwrap_or_default()) + } else { + // `mv source target_dir/target` => target_dir/target + // we canonicalize target_dir and join /target + match absolute(target)?.parent() { + Some(parent) if parent.to_str() != Some("") => { + canonicalize(parent, MissingHandling::Normal, ResolveMode::Logical)? + .join(target.file_name().unwrap_or_default()) + } + // path.parent() returns Some("") or None if there's no parent + _ => absolute(target)?, // absolute paths should always have a parent, but we'll fall back just in case + } + }; + + let same_file = (canonicalized_source.eq(&canonicalized_target) + || are_hardlinks_to_same_file(source, target) + || are_hardlinks_or_one_way_symlink_to_same_file(source, target)) + && opts.backup == BackupMode::NoBackup; + + // get the expected target path to show in errors + // this is based on the argument and not canonicalized + let target_display = match source.file_name() { + Some(file_name) if target_is_dir => { + // join target_dir/source_file in a platform-independent manner + let mut path = target + .display() + .to_string() + .trim_end_matches("/") + .to_owned(); + + path.push('/'); + path.push_str(&file_name.to_string_lossy()); + + path.quote().to_string() + } + _ => target.quote().to_string(), + }; + + if same_file + && (canonicalized_source.eq(&canonicalized_target) + || source.eq(Path::new(".")) + || source.ends_with("/.") + || source.is_file()) + { + return Err(MvError::SameFile(source.quote().to_string(), target_display).into()); + } else if (same_file || canonicalized_target.starts_with(canonicalized_source)) + // don't error if we're moving a symlink of a directory into itself + && !source.is_symlink() + { + return Err( + MvError::SelfTargetSubdirectory(source.quote().to_string(), target_display).into(), + ); + } + Ok(()) +} + fn handle_multiple_paths(paths: &[PathBuf], opts: &Options) -> UResult<()> { if opts.no_target_dir { return Err(UUsageError::new( @@ -425,10 +487,6 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options) return Err(MvError::NotADirectory(target_dir.quote().to_string()).into()); } - let canonicalized_target_dir = target_dir - .canonicalize() - .unwrap_or_else(|_| target_dir.to_path_buf()); - let multi_progress = options.progress_bar.then(MultiProgress::new); let count_progress = if let Some(ref multi_progress) = multi_progress { @@ -479,24 +537,9 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options) // Check if we have mv dir1 dir2 dir2 // And generate an error if this is the case - if let Ok(canonicalized_source) = sourcepath.canonicalize() { - if canonicalized_source == canonicalized_target_dir { - // User tried to move directory to itself, warning is shown - // and process of moving files is continued. - show!(USimpleError::new( - 1, - format!( - "cannot move '{}' to a subdirectory of itself, '{}/{}'", - sourcepath.display(), - uucore::fs::normalize_path(target_dir).display(), - canonicalized_target_dir.components().last().map_or_else( - || target_dir.display().to_string(), - |dir| { PathBuf::from(dir.as_os_str()).display().to_string() } - ) - ) - )); - continue; - } + if let Err(e) = assert_not_same_file(sourcepath, target_dir, true, options) { + show!(e); + continue; } match rename(sourcepath, &targetpath, options, multi_progress.as_ref()) { diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index ac64fae7eb7..1419be4e940 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -6,6 +6,7 @@ // spell-checker:ignore mydir use crate::common::util::TestScenario; use filetime::FileTime; +use rstest::rstest; use std::io::Write; #[test] @@ -467,7 +468,31 @@ fn test_mv_same_symlink() { .arg(file_c) .arg(file_a) .fails() - .stderr_is(format!("mv: '{file_c}' and '{file_a}' are the same file\n",)); + .stderr_is(format!("mv: '{file_c}' and '{file_a}' are the same file\n")); +} + +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_mv_same_broken_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.symlink_file("missing-target", "broken"); + + ucmd.arg("broken") + .arg("broken") + .fails() + .stderr_is("mv: 'broken' and 'broken' are the same file\n"); +} + +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_mv_symlink_into_target() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir"); + at.symlink_file("dir", "dir-link"); + + ucmd.arg("dir-link").arg("dir").succeeds(); } #[test] @@ -1389,24 +1414,6 @@ fn test_mv_interactive_error() { .is_empty()); } -#[test] -fn test_mv_into_self() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - let dir1 = "dir1"; - let dir2 = "dir2"; - at.mkdir(dir1); - at.mkdir(dir2); - - scene - .ucmd() - .arg(dir1) - .arg(dir2) - .arg(dir2) - .fails() - .stderr_contains("mv: cannot move 'dir2' to a subdirectory of itself, 'dir2/dir2'"); -} - #[test] fn test_mv_arg_interactive_skipped() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1456,27 +1463,32 @@ fn test_mv_into_self_data() { assert!(!at.file_exists(file1)); } -#[test] -fn test_mv_directory_into_subdirectory_of_itself_fails() { +#[rstest] +#[case(vec!["mydir"], vec!["mydir", "mydir"], "mv: cannot move 'mydir' to a subdirectory of itself, 'mydir/mydir'")] +#[case(vec!["mydir"], vec!["mydir/", "mydir/"], "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir'")] +#[case(vec!["mydir"], vec!["./mydir", "mydir", "mydir/"], "mv: cannot move './mydir' to a subdirectory of itself, 'mydir/mydir'")] +#[case(vec!["mydir"], vec!["mydir/", "mydir/mydir_2/"], "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/'")] +#[case(vec!["mydir/mydir_2"], vec!["mydir", "mydir/mydir_2"], "mv: cannot move 'mydir' to a subdirectory of itself, 'mydir/mydir_2/mydir'\n")] +#[case(vec!["mydir/mydir_2"], vec!["mydir/", "mydir/mydir_2/"], "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/mydir'\n")] +#[case(vec!["mydir", "mydir_2"], vec!["mydir/", "mydir_2/", "mydir_2/"], "mv: cannot move 'mydir_2/' to a subdirectory of itself, 'mydir_2/mydir_2'")] +#[case(vec!["mydir"], vec!["mydir/", "mydir"], "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir'")] +#[case(vec!["mydir"], vec!["-T", "mydir", "mydir"], "mv: 'mydir' and 'mydir' are the same file")] +#[case(vec!["mydir"], vec!["mydir/", "mydir/../"], "mv: 'mydir/' and 'mydir/../mydir' are the same file")] +fn test_mv_directory_self( + #[case] dirs: Vec<&str>, + #[case] args: Vec<&str>, + #[case] expected_error: &str, +) { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - let dir1 = "mydir"; - let dir2 = "mydir/mydir_2"; - at.mkdir(dir1); - at.mkdir(dir2); - scene.ucmd().arg(dir1).arg(dir2).fails().stderr_contains( - "mv: cannot move 'mydir' to a subdirectory of itself, 'mydir/mydir_2/mydir'", - ); - - // check that it also errors out with / + for dir in dirs { + at.mkdir_all(dir); + } scene .ucmd() - .arg(format!("{dir1}/")) - .arg(dir2) + .args(&args) .fails() - .stderr_contains( - "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/mydir/'", - ); + .stderr_contains(expected_error); } #[test] @@ -1755,23 +1767,3 @@ fn test_mv_error_msg_with_multiple_sources_that_does_not_exist() { .stderr_contains("mv: cannot stat 'a': No such file or directory") .stderr_contains("mv: cannot stat 'b/': No such file or directory"); } - -#[test] -fn test_mv_error_cant_move_itself() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - at.mkdir("b"); - scene - .ucmd() - .arg("b") - .arg("b/") - .fails() - .stderr_contains("mv: cannot move 'b' to a subdirectory of itself, 'b/b'"); - scene - .ucmd() - .arg("./b") - .arg("b") - .arg("b/") - .fails() - .stderr_contains("mv: cannot move 'b' to a subdirectory of itself, 'b/b'"); -} From 20dfb270577eb77ddd7fac6b9f1342c207d99458 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 27 Dec 2024 09:12:47 +0100 Subject: [PATCH 180/351] cut: fix handling of newline as delimiter --- src/uu/cut/src/cut.rs | 38 +++++++++++++++++++++++++++++++++++++- tests/by-util/test_cut.rs | 17 ++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 3dde5e66595..5e128425b63 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -9,7 +9,7 @@ use bstr::io::BufReadExt; use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command}; use std::ffi::OsString; use std::fs::File; -use std::io::{stdin, stdout, BufReader, BufWriter, IsTerminal, Read, Write}; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, IsTerminal, Read, Write}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UResult, USimpleError}; @@ -267,10 +267,46 @@ fn cut_fields_implicit_out_delim( Ok(()) } +// The input delimiter is identical to `newline_char` +fn cut_fields_newline_char_delim( + reader: R, + ranges: &[Range], + newline_char: u8, + out_delim: &[u8], +) -> UResult<()> { + let buf_in = BufReader::new(reader); + let mut out = stdout_writer(); + + let segments: Vec<_> = buf_in.split(newline_char).filter_map(|x| x.ok()).collect(); + let mut print_delim = false; + + for &Range { low, high } in ranges { + for i in low..=high { + // "- 1" is necessary because fields start from 1 whereas a Vec starts from 0 + if let Some(segment) = segments.get(i - 1) { + if print_delim { + out.write_all(out_delim)?; + } else { + print_delim = true; + } + out.write_all(segment.as_slice())?; + } else { + break; + } + } + } + out.write_all(&[newline_char])?; + Ok(()) +} + fn cut_fields(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> { let newline_char = opts.line_ending.into(); let field_opts = opts.field_opts.as_ref().unwrap(); // it is safe to unwrap() here - field_opts will always be Some() for cut_fields() call match field_opts.delimiter { + Delimiter::Slice(delim) if delim == [newline_char] => { + let out_delim = opts.out_delimiter.unwrap_or(delim); + cut_fields_newline_char_delim(reader, ranges, newline_char, out_delim) + } Delimiter::Slice(delim) => { let matcher = ExactMatcher::new(delim); match opts.out_delimiter { diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 1aa3c126a23..dbd26abb287 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -288,11 +288,22 @@ fn test_empty_string_as_delimiter_with_output_delimiter() { #[test] fn test_newline_as_delimiter() { + for (field, expected_output) in [("1", "a:1\n"), ("2", "b:\n")] { + new_ucmd!() + .args(&["-f", field, "-d", "\n"]) + .pipe_in("a:1\nb:") + .succeeds() + .stdout_only_bytes(expected_output); + } +} + +#[test] +fn test_newline_as_delimiter_with_output_delimiter() { new_ucmd!() - .args(&["-f", "1", "-d", "\n"]) - .pipe_in("a:1\nb:") + .args(&["-f1-", "-d", "\n", "--output-delimiter=:"]) + .pipe_in("a\nb\n") .succeeds() - .stdout_only_bytes("a:1\nb:\n"); + .stdout_only_bytes("a:b\n"); } #[test] From f62b8d79759650ff00bdc691249ca8eb7304e65e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 27 Dec 2024 16:08:48 +0100 Subject: [PATCH 181/351] csplit: allow offset without sign in pattern --- src/uu/csplit/src/patterns.rs | 34 +++++++++++++++++++------- tests/by-util/test_csplit.rs | 45 ++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index bd6c4fbfaef..edd632d08fc 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -106,7 +106,7 @@ pub fn get_patterns(args: &[String]) -> Result, CsplitError> { fn extract_patterns(args: &[String]) -> Result, CsplitError> { let mut patterns = Vec::with_capacity(args.len()); let to_match_reg = - Regex::new(r"^(/(?P.+)/|%(?P.+)%)(?P[\+-]\d+)?$").unwrap(); + Regex::new(r"^(/(?P.+)/|%(?P.+)%)(?P[\+-]?\d+)?$").unwrap(); let execute_ntimes_reg = Regex::new(r"^\{(?P\d+)|\*\}$").unwrap(); let mut iter = args.iter().peekable(); @@ -219,14 +219,15 @@ mod tests { "{*}", "/test3.*end$/", "{4}", - "/test4.*end$/+3", - "/test5.*end$/-3", + "/test4.*end$/3", + "/test5.*end$/+3", + "/test6.*end$/-3", ] .into_iter() .map(|v| v.to_string()) .collect(); let patterns = get_patterns(input.as_slice()).unwrap(); - assert_eq!(patterns.len(), 5); + assert_eq!(patterns.len(), 6); match patterns.first() { Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Times(1))) => { let parsed_reg = format!("{reg}"); @@ -256,12 +257,19 @@ mod tests { _ => panic!("expected UpToMatch pattern"), }; match patterns.get(4) { - Some(Pattern::UpToMatch(reg, -3, ExecutePattern::Times(1))) => { + Some(Pattern::UpToMatch(reg, 3, ExecutePattern::Times(1))) => { let parsed_reg = format!("{reg}"); assert_eq!(parsed_reg, "test5.*end$"); } _ => panic!("expected UpToMatch pattern"), }; + match patterns.get(5) { + Some(Pattern::UpToMatch(reg, -3, ExecutePattern::Times(1))) => { + let parsed_reg = format!("{reg}"); + assert_eq!(parsed_reg, "test6.*end$"); + } + _ => panic!("expected UpToMatch pattern"), + }; } #[test] @@ -273,14 +281,15 @@ mod tests { "{*}", "%test3.*end$%", "{4}", - "%test4.*end$%+3", - "%test5.*end$%-3", + "%test4.*end$%3", + "%test5.*end$%+3", + "%test6.*end$%-3", ] .into_iter() .map(|v| v.to_string()) .collect(); let patterns = get_patterns(input.as_slice()).unwrap(); - assert_eq!(patterns.len(), 5); + assert_eq!(patterns.len(), 6); match patterns.first() { Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Times(1))) => { let parsed_reg = format!("{reg}"); @@ -310,12 +319,19 @@ mod tests { _ => panic!("expected SkipToMatch pattern"), }; match patterns.get(4) { - Some(Pattern::SkipToMatch(reg, -3, ExecutePattern::Times(1))) => { + Some(Pattern::SkipToMatch(reg, 3, ExecutePattern::Times(1))) => { let parsed_reg = format!("{reg}"); assert_eq!(parsed_reg, "test5.*end$"); } _ => panic!("expected SkipToMatch pattern"), }; + match patterns.get(5) { + Some(Pattern::SkipToMatch(reg, -3, ExecutePattern::Times(1))) => { + let parsed_reg = format!("{reg}"); + assert_eq!(parsed_reg, "test6.*end$"); + } + _ => panic!("expected SkipToMatch pattern"), + }; } #[test] diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 10ead0b4544..2315715228d 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -130,17 +130,21 @@ fn test_up_to_match_sequence() { #[test] fn test_up_to_match_offset() { - let (at, mut ucmd) = at_and_ucmd!(); - ucmd.args(&["numbers50.txt", "/9$/+3"]) - .succeeds() - .stdout_only("24\n117\n"); + for offset in ["3", "+3"] { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["numbers50.txt", &format!("/9$/{offset}")]) + .succeeds() + .stdout_only("24\n117\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, 12)); - assert_eq!(at.read("xx01"), generate(12, 51)); + 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, 12)); + assert_eq!(at.read("xx01"), generate(12, 51)); + at.remove("xx00"); + at.remove("xx01"); + } } #[test] @@ -316,16 +320,19 @@ fn test_skip_to_match_sequence4() { #[test] fn test_skip_to_match_offset() { - let (at, mut ucmd) = at_and_ucmd!(); - ucmd.args(&["numbers50.txt", "%23%+3"]) - .succeeds() - .stdout_only("75\n"); + for offset in ["3", "+3"] { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["numbers50.txt", &format!("%23%{offset}")]) + .succeeds() + .stdout_only("75\n"); - let count = glob(&at.plus_as_string("xx*")) - .expect("there should be splits created") - .count(); - assert_eq!(count, 1); - assert_eq!(at.read("xx00"), generate(26, 51)); + let count = glob(&at.plus_as_string("xx*")) + .expect("there should be splits created") + .count(); + assert_eq!(count, 1); + assert_eq!(at.read("xx00"), generate(26, 51)); + at.remove("xx00"); + } } #[test] From ff8a31e835f48bef1313d71f0c63dc1b81a5103b Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Mon, 23 Dec 2024 00:14:15 -0500 Subject: [PATCH 182/351] wc: fix escaping GNU wc only escapes file names with newlines in them. --- Cargo.toml | 2 ++ build.rs | 4 +++- src/uu/wc/src/wc.rs | 33 ++++++++++++++++++++------------- tests/by-util/test_wc.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1991679d8e6..98c50d6dbb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,8 @@ windows = ["feat_os_windows"] nightly = [] test_unimplemented = [] expensive_tests = [] +# "test_risky_names" == enable tests that create problematic file names (would make a network share inaccessible to Windows, breaks SVN on Mac OS, etc.) +test_risky_names = [] # * only build `uudoc` when `--feature uudoc` is activated uudoc = ["zip", "dep:uuhelp_parser"] ## features diff --git a/build.rs b/build.rs index 91e9d0427ce..d414de09209 100644 --- a/build.rs +++ b/build.rs @@ -33,7 +33,9 @@ pub fn main() { #[allow(clippy::match_same_arms)] match krate.as_ref() { "default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names - "nightly" | "test_unimplemented" | "expensive_tests" => continue, // crate-local custom features + "nightly" | "test_unimplemented" | "expensive_tests" | "test_risky_names" => { + continue + } // crate-local custom features "uudoc" => continue, // is not a utility "test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test' s if s.starts_with(FEATURE_PREFIX) => continue, // crate feature sets diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 1c2d99628f7..6fc1efa0a00 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -255,13 +255,17 @@ impl<'a> Input<'a> { } /// Converts input to title that appears in stats. - fn to_title(&self) -> Option> { + fn to_title(&self) -> Option> { match self { - Self::Path(path) => Some(match path.to_str() { - Some(s) if !s.contains('\n') => Cow::Borrowed(s), - _ => Cow::Owned(escape_name_wrapper(path.as_os_str())), - }), - Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(STDIN_REPR)), + Self::Path(path) => { + let path = path.as_os_str(); + if path.to_string_lossy().contains('\n') { + Some(Cow::Owned(quoting_style::escape_name(path, QS_ESCAPE))) + } else { + Some(Cow::Borrowed(path)) + } + } + Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(OsStr::new(STDIN_REPR))), Self::Stdin(StdinKind::Implicit) => None, } } @@ -852,14 +856,17 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { let maybe_title = input.to_title(); let maybe_title_str = maybe_title.as_deref(); if let Err(err) = print_stats(settings, &word_count, maybe_title_str, number_width) { - let title = maybe_title_str.unwrap_or(""); - show!(err.map_err_context(|| format!("failed to print result for {title}"))); + let title = maybe_title_str.unwrap_or(OsStr::new("")); + show!(err.map_err_context(|| format!( + "failed to print result for {}", + title.to_string_lossy() + ))); } } } if settings.total_when.is_total_row_visible(num_inputs) { - let title = are_stats_visible.then_some("total"); + let title = are_stats_visible.then_some(OsStr::new("total")); if let Err(err) = print_stats(settings, &total_word_count, title, number_width) { show!(err.map_err_context(|| "failed to print total".into())); } @@ -873,7 +880,7 @@ fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { fn print_stats( settings: &Settings, result: &WordCount, - title: Option<&str>, + title: Option<&OsStr>, number_width: usize, ) -> io::Result<()> { let mut stdout = io::stdout().lock(); @@ -893,8 +900,8 @@ fn print_stats( } if let Some(title) = title { - writeln!(stdout, "{space}{title}") - } else { - writeln!(stdout) + write!(stdout, "{space}")?; + stdout.write_all(&uucore::os_str_as_bytes_lossy(title))?; } + writeln!(stdout) } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 0bdb5c843a1..e2af757b360 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -283,6 +283,32 @@ fn test_gnu_compatible_quotation() { .stdout_is("0 0 0 'some-dir1/12'$'\\n''34.txt'\n"); } +#[cfg(feature = "test_risky_names")] +#[test] +fn test_non_unicode_names() { + let scene = TestScenario::new(util_name!()); + let target1 = uucore::os_str_from_bytes(b"some-dir1/1\xC0\n.txt") + .expect("Only unix platforms can test non-unicode names"); + let target2 = uucore::os_str_from_bytes(b"some-dir1/2\xC0\t.txt") + .expect("Only unix platforms can test non-unicode names"); + let at = &scene.fixtures; + at.mkdir("some-dir1"); + at.touch(&target1); + at.touch(&target2); + scene + .ucmd() + .args(&[target1, target2]) + .run() + .stdout_is_bytes( + [ + b"0 0 0 'some-dir1/1'$'\\300\\n''.txt'\n".to_vec(), + b"0 0 0 some-dir1/2\xC0\t.txt\n".to_vec(), + b"0 0 0 total\n".to_vec(), + ] + .concat(), + ); +} + #[test] fn test_multiple_default() { new_ucmd!() From 02f1f50ccbed13c691bc741a2ea8df04b36951e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:07:48 +0000 Subject: [PATCH 183/351] chore(deps): update rust crate serde to v1.0.217 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2717a64e25f..642b3fddafb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2080,9 +2080,9 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -2098,9 +2098,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", From edf8be5e083ca2dd633d9804fe348dfefca59800 Mon Sep 17 00:00:00 2001 From: Pearl Date: Sat, 28 Dec 2024 21:30:10 +0700 Subject: [PATCH 184/351] uucore/buf_copy: Improve the uucore integration (#6983) --------- Co-authored-by: Sylvestre Ledru --- src/uucore/src/lib/features.rs | 4 +- src/uucore/src/lib/features/buf_copy.rs | 400 +++++------------- .../src/lib/features/buf_copy/common.rs | 34 ++ src/uucore/src/lib/features/buf_copy/linux.rs | 264 ++++++++++++ src/uucore/src/lib/features/buf_copy/other.rs | 30 ++ src/uucore/src/lib/lib.rs | 4 +- 6 files changed, 449 insertions(+), 287 deletions(-) create mode 100644 src/uucore/src/lib/features/buf_copy/common.rs create mode 100644 src/uucore/src/lib/features/buf_copy/linux.rs create mode 100644 src/uucore/src/lib/features/buf_copy/other.rs diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index cde1cf264a3..ef5be724d9f 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -6,6 +6,8 @@ #[cfg(feature = "backup-control")] pub mod backup_control; +#[cfg(feature = "buf-copy")] +pub mod buf_copy; #[cfg(feature = "checksum")] pub mod checksum; #[cfg(feature = "colors")] @@ -39,8 +41,6 @@ pub mod version_cmp; pub mod mode; // ** unix-only -#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "buf-copy"))] -pub mod buf_copy; #[cfg(all(unix, feature = "entries"))] pub mod entries; #[cfg(all(unix, feature = "perms"))] diff --git a/src/uucore/src/lib/features/buf_copy.rs b/src/uucore/src/lib/features/buf_copy.rs index 2b46248a597..d82f8d4d1c3 100644 --- a/src/uucore/src/lib/features/buf_copy.rs +++ b/src/uucore/src/lib/features/buf_copy.rs @@ -9,283 +9,51 @@ //! used by utilities to work around the limitations of Rust's `fs::copy` which //! does not handle copying special files (e.g pipes, character/block devices). -use crate::error::{UError, UResult}; -use nix::unistd; -use std::fs::File; -use std::{ - io::{self, Read, Write}, - os::{ - fd::AsFd, - unix::io::{AsRawFd, RawFd}, - }, -}; - -use nix::{errno::Errno, libc::S_IFIFO, sys::stat::fstat}; - -use super::pipes::{pipe, splice, splice_exact, vmsplice}; - -type Result = std::result::Result; - -/// Error types used by buffer-copying functions from the `buf_copy` module. -#[derive(Debug)] -pub enum Error { - Io(io::Error), - WriteError(String), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::WriteError(msg) => write!(f, "splice() write error: {}", msg), - Error::Io(err) => write!(f, "I/O error: {}", err), - } - } -} - -impl std::error::Error for Error {} +pub mod common; -impl UError for Error { - fn code(&self) -> i32 { - 1 - } - - fn usage(&self) -> bool { - false - } -} - -/// Helper function to determine whether a given handle (such as a file) is a pipe or not. -/// -/// # Arguments -/// * `out` - path of handle -/// -/// # Returns -/// A `bool` indicating whether the given handle is a pipe or not. -#[inline] -#[cfg(unix)] -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) -} - -const SPLICE_SIZE: usize = 1024 * 128; -const BUF_SIZE: usize = 1024 * 16; - -/// Copy data from `Read` implementor `source` into a `Write` implementor -/// `dest`. This works by reading a chunk of data from `source` and writing the -/// data to `dest` in a loop. -/// -/// This function uses the Linux-specific `splice` call when possible which does -/// not use any intermediate user-space buffer. It falls backs to -/// `std::io::copy` under other platforms or when the call fails and is still -/// recoverable. -/// -/// # Arguments -/// * `source` - `Read` implementor to copy data from. -/// * `dest` - `Write` implementor to copy data to. -/// -/// # Returns -/// -/// Result of operation and bytes successfully written (as a `u64`) when -/// operation is successful. -pub fn copy_stream(src: &mut R, dest: &mut S) -> UResult -where - R: Read + AsFd + AsRawFd, - S: Write + AsFd + AsRawFd, -{ - #[cfg(any(target_os = "linux", target_os = "android"))] - { - // If we're on Linux or Android, try to use the splice() system call - // for faster writing. If it works, we're done. - let result = splice_write(src, &dest.as_fd())?; - if !result.1 { - return Ok(result.0); - } - } - // If we're not on Linux or Android, or the splice() call failed, - // fall back on slower writing. - let result = std::io::copy(src, dest)?; - - // If the splice() call failed and there has been some data written to - // stdout via while loop above AND there will be second splice() call - // that will succeed, data pushed through splice will be output before - // the data buffered in stdout.lock. Therefore additional explicit flush - // is required here. - dest.flush()?; - Ok(result) -} - -/// Write from source `handle` into destination `write_fd` using Linux-specific -/// `splice` system call. -/// -/// # Arguments -/// - `source` - source handle -/// - `dest` - destination handle -#[inline] #[cfg(any(target_os = "linux", target_os = "android"))] -fn splice_write(source: &R, dest: &S) -> UResult<(u64, bool)> -where - R: Read + AsFd + AsRawFd, - S: AsRawFd + AsFd, -{ - let (pipe_rd, pipe_wr) = pipe()?; - let mut bytes: u64 = 0; - - loop { - match splice(&source, &pipe_wr, SPLICE_SIZE) { - Ok(n) => { - if n == 0 { - return Ok((bytes, false)); - } - if splice_exact(&pipe_rd, dest, n).is_err() { - // If the first splice manages to copy to the intermediate - // pipe, but the second splice to stdout fails for some reason - // we can recover by copying the data that we have from the - // intermediate pipe to stdout using normal read/write. Then - // we tell the caller to fall back. - copy_exact(pipe_rd.as_raw_fd(), dest, n)?; - return Ok((bytes, true)); - } - - bytes += n as u64; - } - Err(_) => { - return Ok((bytes, true)); - } - } - } -} - -/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd` using the `read` -/// and `write` calls. -fn copy_exact(read_fd: RawFd, write_fd: &impl AsFd, num_bytes: usize) -> std::io::Result { - let mut left = num_bytes; - let mut buf = [0; BUF_SIZE]; - let mut written = 0; - while left > 0 { - let read = unistd::read(read_fd, &mut buf)?; - assert_ne!(read, 0, "unexpected end of pipe"); - while written < read { - let n = unistd::write(write_fd, &buf[written..read])?; - written += n; - } - left -= read; - } - Ok(written) -} - -/// 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)) -} - -/// 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. -#[cfg(any(target_os = "linux", target_os = "android"))] -pub fn splice_data_to_fd( - bytes: &[u8], - read_pipe: &File, - write_pipe: &File, - dest: &T, -) -> UResult<(u64, bool)> { - loop { - 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..]; - } - } -} - -/// Conversion from a `nix::Error` into our `Error` which implements `UError`. +pub mod linux; #[cfg(any(target_os = "linux", target_os = "android"))] -impl From for Error { - fn from(error: nix::Error) -> Self { - Self::Io(io::Error::from_raw_os_error(error as i32)) - } -} +pub use linux::*; -/// 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. -#[cfg(any(target_os = "linux", target_os = "android"))] -fn maybe_unsupported(error: nix::Error) -> Result<(u64, bool)> { - match error { - Errno::EINVAL | Errno::ENOSYS | Errno::EBADF => Ok((0, true)), - _ => Err(error.into()), - } -} +#[cfg(not(any(target_os = "linux", target_os = "android")))] +pub mod other; +#[cfg(not(any(target_os = "linux", target_os = "android")))] +pub use other::copy_stream; #[cfg(test)] mod tests { + use super::*; + use std::fs::File; use tempfile::tempdir; - use super::*; - use crate::pipes; + #[cfg(unix)] + use { + crate::pipes, + std::fs::OpenOptions, + std::{ + io::{Seek, SeekFrom}, + thread, + }, + }; + + #[cfg(any(target_os = "linux", target_os = "android"))] + use {nix::unistd, std::os::fd::AsRawFd}; + + use std::io::{Read, Write}; + #[cfg(unix)] fn new_temp_file() -> File { let temp_dir = tempdir().unwrap(); - File::create(temp_dir.path().join("file.txt")).unwrap() + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(temp_dir.path().join("file.txt")) + .unwrap() } + #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_file_is_pipe() { let temp_file = new_temp_file(); @@ -296,21 +64,26 @@ mod tests { assert!(!is_pipe(&temp_file).unwrap()); } + #[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_valid_splice_errs() { - let err = nix::Error::from(Errno::EINVAL); + use nix::errno::Errno; + use nix::Error; + + let err = Error::from(Errno::EINVAL); assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); - let err = nix::Error::from(Errno::ENOSYS); + let err = Error::from(Errno::ENOSYS); assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); - let err = nix::Error::from(Errno::EBADF); + let err = Error::from(Errno::EBADF); assert_eq!(maybe_unsupported(err).unwrap(), (0, true)); - let err = nix::Error::from(Errno::EPERM); + 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(); @@ -322,18 +95,26 @@ mod tests { 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(); - let mut buf = [0; 1024]; - let n = temp_file.read(&mut buf).unwrap(); - assert_eq!(&buf[..n], data); 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() { let (mut pipe_read, mut pipe_write) = pipes::pipe().unwrap(); @@ -348,26 +129,79 @@ mod tests { } #[test] + #[cfg(unix)] fn test_copy_stream() { + let mut dest_file = new_temp_file(); + let (mut pipe_read, mut pipe_write) = pipes::pipe().unwrap(); let data = b"Hello, world!"; - let n = pipe_write.write(data).unwrap(); - assert_eq!(n, data.len()); - let mut buf = [0; 1024]; - let n = copy_stream(&mut pipe_read, &mut pipe_write).unwrap(); - let n2 = pipe_read.read(&mut buf).unwrap(); - assert_eq!(n as usize, n2); - assert_eq!(&buf[..n as usize], data); + let thread = thread::spawn(move || { + pipe_write.write_all(data).unwrap(); + }); + let result = copy_stream(&mut pipe_read, &mut dest_file).unwrap(); + thread.join().unwrap(); + assert!(result == 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); + } + + #[test] + #[cfg(not(unix))] + // Test for non-unix platforms. We use regular files instead. + fn test_copy_stream() { + let temp_dir = tempdir().unwrap(); + let src_path = temp_dir.path().join("src.txt"); + let dest_path = temp_dir.path().join("dest.txt"); + + let mut src_file = File::create(&src_path).unwrap(); + let mut dest_file = File::create(&dest_path).unwrap(); + + let data = b"Hello, world!"; + src_file.write_all(data).unwrap(); + src_file.sync_all().unwrap(); + + let mut src_file = File::open(&src_path).unwrap(); + let bytes_copied = copy_stream(&mut src_file, &mut dest_file).unwrap(); + + let mut dest_file = File::open(&dest_path).unwrap(); + let mut buf = Vec::new(); + dest_file.read_to_end(&mut buf).unwrap(); + + 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() { - let (mut pipe_read, pipe_write) = pipes::pipe().unwrap(); + 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 (bytes, _) = splice_write(&pipe_read, &pipe_write).unwrap(); - let mut buf = [0; 1024]; - let n = pipe_read.read(&mut buf).unwrap(); - assert_eq!(&buf[..n], data); - assert_eq!(bytes as usize, data.len()); + 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/common.rs b/src/uucore/src/lib/features/buf_copy/common.rs new file mode 100644 index 00000000000..8c74dbb8a88 --- /dev/null +++ b/src/uucore/src/lib/features/buf_copy/common.rs @@ -0,0 +1,34 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use crate::error::UError; + +/// Error types used by buffer-copying functions from the `buf_copy` module. +#[derive(Debug)] +pub enum Error { + Io(std::io::Error), + WriteError(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::WriteError(msg) => write!(f, "splice() write error: {}", msg), + Error::Io(err) => write!(f, "I/O error: {}", err), + } + } +} + +impl std::error::Error for Error {} + +impl UError for Error { + fn code(&self) -> i32 { + 1 + } + + fn usage(&self) -> bool { + false + } +} diff --git a/src/uucore/src/lib/features/buf_copy/linux.rs b/src/uucore/src/lib/features/buf_copy/linux.rs new file mode 100644 index 00000000000..77d25e44beb --- /dev/null +++ b/src/uucore/src/lib/features/buf_copy/linux.rs @@ -0,0 +1,264 @@ +// This file is part of the uutils coreutils package. +// +// 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}; + +type Result = std::result::Result; + +/// Buffer-based copying utilities for Linux and Android. +use crate::{ + error::UResult, + pipes::{pipe, splice, splice_exact, vmsplice}, +}; + +/// Buffer-based copying utilities for unix (excluding Linux). +use std::{ + fs::File, + io::{Read, Write}, + os::fd::{AsFd, AsRawFd, RawFd}, +}; + +use super::common::Error; + +/// A readable file descriptor. +pub trait FdReadable: Read + AsRawFd + AsFd {} + +impl FdReadable for T where T: Read + AsFd + AsRawFd {} + +/// A writable file descriptor. +pub trait FdWritable: Write + AsFd + AsRawFd {} + +impl FdWritable for T where T: Write + AsFd + AsRawFd {} + +const SPLICE_SIZE: usize = 1024 * 128; +const BUF_SIZE: usize = 1024 * 16; + +/// Conversion from a `nix::Error` into our `Error` which implements `UError`. +impl From for Error { + fn from(error: nix::Error) -> Self { + Self::Io(std::io::Error::from_raw_os_error(error as i32)) + } +} + +/// Copy data from `Read` implementor `source` into a `Write` implementor +/// `dest`. This works by reading a chunk of data from `source` and writing the +/// data to `dest` in a loop. +/// +/// This function uses the Linux-specific `splice` call when possible which does +/// 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. +/// +/// # Returns +/// +/// Result of operation and bytes successfully written (as a `u64`) when +/// operation is successful. +/// + +pub fn copy_stream(src: &mut R, dest: &mut S) -> UResult +where + R: Read + AsFd + AsRawFd, + S: Write + AsFd + AsRawFd, +{ + // If we're on Linux or Android, try to use the splice() system call + // for faster writing. If it works, we're done. + let result = splice_write(src, &dest.as_fd())?; + if !result.1 { + return Ok(result.0); + } + + // If the splice() call failed, fall back on slower writing. + let result = std::io::copy(src, dest)?; + + // If the splice() call failed and there has been some data written to + // stdout via while loop above AND there will be second splice() call + // that will succeed, data pushed through splice will be output before + // the data buffered in stdout.lock. Therefore additional explicit flush + // is required here. + dest.flush()?; + Ok(result) +} + +/// Write from source `handle` into destination `write_fd` using Linux-specific +/// `splice` system call. +/// +/// # Arguments +/// - `source` - source handle +/// - `dest` - destination handle +#[inline] +pub(crate) fn splice_write(source: &R, dest: &S) -> UResult<(u64, bool)> +where + R: Read + AsFd + AsRawFd, + S: AsRawFd + AsFd, +{ + let (pipe_rd, pipe_wr) = pipe()?; + let mut bytes: u64 = 0; + + loop { + match splice(&source, &pipe_wr, SPLICE_SIZE) { + Ok(n) => { + if n == 0 { + return Ok((bytes, false)); + } + if splice_exact(&pipe_rd, dest, n).is_err() { + // If the first splice manages to copy to the intermediate + // pipe, but the second splice to stdout fails for some reason + // we can recover by copying the data that we have from the + // intermediate pipe to stdout using normal read/write. Then + // we tell the caller to fall back. + copy_exact(pipe_rd.as_raw_fd(), dest, n)?; + return Ok((bytes, true)); + } + + bytes += n as u64; + } + Err(_) => { + return Ok((bytes, true)); + } + } + } +} + +/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd` using the `read` +/// and `write` calls. +#[cfg(any(target_os = "linux", target_os = "android"))] +pub(crate) fn copy_exact( + read_fd: RawFd, + write_fd: &impl AsFd, + num_bytes: usize, +) -> std::io::Result { + use nix::unistd; + + let mut left = num_bytes; + let mut buf = [0; BUF_SIZE]; + let mut written = 0; + while left > 0 { + let read = unistd::read(read_fd, &mut buf)?; + assert_ne!(read, 0, "unexpected end of pipe"); + while written < read { + let n = unistd::write(write_fd, &buf[written..read])?; + written += n; + } + left -= read; + } + 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 new file mode 100644 index 00000000000..6497c9224c6 --- /dev/null +++ b/src/uucore/src/lib/features/buf_copy/other.rs @@ -0,0 +1,30 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::io::{Read, Write}; + +use crate::error::UResult; + +/// Copy data from `Read` implementor `source` into a `Write` implementor +/// `dest`. This works by reading a chunk of data from `source` and writing the +/// data to `dest` in a loop, using std::io::copy. This is implemented for +/// non-Linux platforms. +/// +/// # Arguments +/// * `source` - `Read` implementor to copy data from. +/// * `dest` - `Write` implementor to copy data to. +/// +/// # Returns +/// +/// Result of operation and bytes successfully written (as a `u64`) when +/// operation is successful. +pub fn copy_stream(src: &mut R, dest: &mut S) -> UResult +where + R: Read, + S: Write, +{ + let result = std::io::copy(src, dest)?; + Ok(result) +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 3a6a537ad49..684de8f74e0 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -40,6 +40,8 @@ pub use crate::parser::shortcut_value_parser; // * feature-gated modules #[cfg(feature = "backup-control")] pub use crate::features::backup_control; +#[cfg(feature = "buf-copy")] +pub use crate::features::buf_copy; #[cfg(feature = "checksum")] pub use crate::features::checksum; #[cfg(feature = "colors")] @@ -70,8 +72,6 @@ pub use crate::features::version_cmp; #[cfg(all(not(windows), feature = "mode"))] pub use crate::features::mode; // ** unix-only -#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "buf-copy"))] -pub use crate::features::buf_copy; #[cfg(all(unix, feature = "entries"))] pub use crate::features::entries; #[cfg(all(unix, feature = "perms"))] From 254f762e860f1a6d3974b4ae84e336bc3eb18575 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 21:53:09 +0100 Subject: [PATCH 185/351] GNU: try to remove the fail-2eperm.sh workaround Now fails with: 2024-12-28T14:55:18.9355074Z -rm: cannot remove 'a': Operation not permitted 2024-12-28T14:55:18.9355364Z +rm: cannot remove 'a/b': Operation not permitted --- util/build-gnu.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 16868af4dbc..38df18daeb2 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -227,8 +227,6 @@ eval cat "$path_UUTILS/util/gnu-patches/*.patch" | patch -N -r - -d "$path_GNU" sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail-eacces.sh -sed -i -e "s|rm: cannot remove 'a/b'|rm: cannot remove 'a'|g" tests/rm/fail-2eperm.sh - sed -i -e "s|rm: cannot remove 'a/b/file'|rm: cannot remove 'a'|g" tests/rm/cycle.sh sed -i -e "s|rm: cannot remove directory 'b/a/p'|rm: cannot remove 'b'|g" tests/rm/rm1.sh From 19da02117d7fb2d3d8570067d4918669313345ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 28 Dec 2024 21:12:38 +0000 Subject: [PATCH 186/351] chore(deps): update vmactions/freebsd-vm action to v1.1.7 --- .github/workflows/freebsd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 27ff2afe4d4..ef1602ad7cf 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -41,7 +41,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.1.6 + uses: vmactions/freebsd-vm@v1.1.7 with: usesh: true sync: rsync @@ -135,7 +135,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.1.6 + uses: vmactions/freebsd-vm@v1.1.7 with: usesh: true sync: rsync From 7d628c65d3380dc76ee4ea433ad3ccb1682053f9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 22:27:53 +0100 Subject: [PATCH 187/351] chroot: handle the error when invalid user Currently fails with: ``` 2024-12-28T14:55:18.9330231Z thread 'main' panicked at src/uu/chroot/src/chroot.rs:284:46: 2024-12-28T14:55:18.9330718Z called `Result::unwrap()` on an `Err` value: Custom { kind: NotFound, error: "Not found: nobody:+65535" } 2024-12-28T14:55:18.9331305Z note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` --- src/uu/chroot/src/chroot.rs | 3 ++- src/uu/chroot/src/error.rs | 4 ++++ tests/by-util/test_chroot.rs | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index fb20b0ccc46..58e11e101cf 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -281,7 +281,8 @@ fn set_groups_from_str(groups: &str) -> UResult<()> { fn set_user(user: &str) -> UResult<()> { if !user.is_empty() { - let user_id = entries::usr2uid(user).unwrap(); + let user_id = + entries::usr2uid(user).map_err(|_| ChrootError::NoSuchUser(user.to_string()))?; let err = unsafe { setuid(user_id as libc::uid_t) }; if err != 0 { return Err( diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs index 526f1a75a43..1b83e76256b 100644 --- a/src/uu/chroot/src/error.rs +++ b/src/uu/chroot/src/error.rs @@ -27,6 +27,9 @@ pub enum ChrootError { /// The new root directory was not given. MissingNewRoot, + /// Failed to find the specified user. + NoSuchUser(String), + /// Failed to find the specified group. NoSuchGroup(String), @@ -71,6 +74,7 @@ impl Display for ChrootError { "Missing operand: NEWROOT\nTry '{} --help' for more information.", uucore::execution_phrase(), ), + Self::NoSuchUser(s) => write!(f, "no such user: {}", s.maybe_quote(),), Self::NoSuchGroup(s) => write!(f, "no such group: {}", s.maybe_quote(),), Self::NoSuchDirectory(s) => write!( f, diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index bf6b2ce16f1..42da75f4879 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -64,6 +64,28 @@ fn test_invalid_user_spec() { assert!(result.stderr_str().starts_with("chroot: invalid userspec")); } +#[test] +fn test_invalid_user() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let dir = "CHROOT_DIR"; + at.mkdir(dir); + if let Ok(result) = run_ucmd_as_root(&ts, &[dir, "whoami"]) { + result.success().no_stderr().stdout_is("root"); + } else { + print!("Test skipped; requires root user"); + } + + if let Ok(result) = run_ucmd_as_root(&ts, &["--user=nobody:+65535", dir, "pwd"]) { + result + .failure() + .stderr_contains("no such user: nobody:+65535"); + } else { + print!("Test skipped; requires root user"); + } +} + #[test] #[cfg(not(target_os = "android"))] fn test_preference_of_userspec() { From 200e4b10324e3e2d6c6405dac291e12ee61e8b04 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 20:03:44 +0700 Subject: [PATCH 188/351] install: implement copying from streams --- src/uu/install/Cargo.toml | 1 + src/uu/install/src/install.rs | 52 +++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 809a1dd687a..88614cf113d 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -23,6 +23,7 @@ file_diff = { workspace = true } libc = { workspace = true } uucore = { workspace = true, features = [ "backup-control", + "buf-copy", "fs", "mode", "perms", diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 331a50f6741..cf810937794 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -12,14 +12,12 @@ use file_diff::diff; use filetime::{set_file_times, FileTime}; use std::error::Error; use std::fmt::{Debug, Display}; -use std::fs; use std::fs::File; -use std::os::unix::fs::MetadataExt; -#[cfg(unix)] -use std::os::unix::prelude::OsStrExt; +use std::fs::{self, metadata}; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; use std::process; use uucore::backup_control::{self, BackupMode}; +use uucore::buf_copy::copy_stream; use uucore::display::Quotable; use uucore::entries::{grp2gid, usr2uid}; use uucore::error::{FromIo, UError, UIoError, UResult, UUsageError}; @@ -29,6 +27,11 @@ use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; use uucore::process::{getegid, geteuid}; use uucore::{format_usage, help_about, help_usage, show, show_error, show_if_err, uio_error}; +#[cfg(unix)] +use std::os::unix::fs::{FileTypeExt, MetadataExt}; +#[cfg(unix)] +use std::os::unix::prelude::OsStrExt; + const DEFAULT_MODE: u32 = 0o755; const DEFAULT_STRIP_PROGRAM: &str = "strip"; @@ -736,7 +739,24 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult> { } } -/// Copy a file from one path to another. +/// Copy a non-special file using std::fs::copy. +/// +/// # Parameters +/// * `from` - The source file path. +/// * `to` - The destination file path. +/// +/// # Returns +/// +/// Returns an empty Result or an error in case of failure. +fn copy_normal_file(from: &Path, to: &Path) -> UResult<()> { + if let Err(err) = fs::copy(from, to) { + return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into()); + } + Ok(()) +} + +/// Copy a file from one path to another. Handles the certain cases of special +/// files (e.g character specials). /// /// # Parameters /// @@ -760,18 +780,26 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { } } - if from.as_os_str() == "/dev/null" { - /* workaround a limitation of fs::copy - * https://github.com/rust-lang/rust/issues/79390 - */ - if let Err(err) = File::create(to) { + let ft = match metadata(from) { + Ok(ft) => ft.file_type(), + Err(err) => { return Err( InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(), ); } - } else if let Err(err) = fs::copy(from, to) { - return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into()); + }; + + // Stream-based copying to get around the limitations of std::fs::copy + #[cfg(unix)] + if ft.is_char_device() || ft.is_block_device() || ft.is_fifo() { + let mut handle = File::open(from)?; + let mut dest = File::create(to)?; + copy_stream(&mut handle, &mut dest)?; + return Ok(()); } + + copy_normal_file(from, to)?; + Ok(()) } From e07cc67b30699451b686e6bcc156193df41c054e Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Tue, 17 Dec 2024 20:04:14 +0700 Subject: [PATCH 189/351] tests/install: add tests to install from stdin --- tests/by-util/test_install.rs | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index f1e3302e138..9c6e48c7b9d 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1717,3 +1717,53 @@ fn test_install_root_combined() { run_and_check(&["-Cv", "c", "d"], "d", 0, 0); run_and_check(&["-Cv", "c", "d"], "d", 0, 0); } + +#[test] +#[cfg(unix)] +fn test_install_from_fifo() { + use std::fs::OpenOptions; + use std::io::Write; + use std::thread; + + let pipe_name = "pipe"; + let target_name = "target"; + let test_string = "Hello, world!\n"; + + let s = TestScenario::new(util_name!()); + s.fixtures.mkfifo(pipe_name); + assert!(s.fixtures.is_fifo(pipe_name)); + + let proc = s.ucmd().arg(pipe_name).arg(target_name).run_no_wait(); + + let pipe_path = s.fixtures.plus(pipe_name); + let thread = thread::spawn(move || { + let mut pipe = OpenOptions::new() + .write(true) + .create(false) + .open(pipe_path) + .unwrap(); + pipe.write_all(test_string.as_bytes()).unwrap(); + }); + + proc.wait().unwrap(); + thread.join().unwrap(); + + assert!(s.fixtures.file_exists(target_name)); + assert_eq!(s.fixtures.read(target_name), test_string); +} + +#[test] +#[cfg(unix)] +fn test_install_from_stdin() { + let (at, mut ucmd) = at_and_ucmd!(); + let target = "target"; + let test_string = "Hello, World!\n"; + + ucmd.arg("/dev/fd/0") + .arg(target) + .pipe_in(test_string) + .succeeds(); + + assert!(at.file_exists(target)); + assert_eq!(at.read(target), test_string); +} From fa2bf49229cc1ef43dc7dfbe1a0f442fa56bb30b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 02:17:45 +0000 Subject: [PATCH 190/351] chore(deps): update rust crate glob to v0.3.2 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 642b3fddafb..f30a0c262f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1100,9 +1100,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "half" From 13eb4792c88241d2413a72eba0d39973e1f89905 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Dec 2024 02:17:50 +0000 Subject: [PATCH 191/351] chore(deps): update davidanson/markdownlint-cli2-action action to v19 --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index e4c0fa345f7..624a7c2abd8 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -145,7 +145,7 @@ jobs: shell: bash run: | RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - - uses: DavidAnson/markdownlint-cli2-action@v18 + - uses: DavidAnson/markdownlint-cli2-action@v19 with: fix: "true" globs: | From b0837a0ca5f718ab0d1d6b6b067ff00e58cd5124 Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Fri, 27 Dec 2024 11:10:48 -0500 Subject: [PATCH 192/351] ls: add better support for non-UTF-8 bytes --- src/uu/ls/src/colors.rs | 36 ++++++----- src/uu/ls/src/dired.rs | 3 +- src/uu/ls/src/ls.rs | 130 ++++++++++++++++++++++++--------------- tests/by-util/test_ls.rs | 31 ++++++++++ 4 files changed, 131 insertions(+), 69 deletions(-) diff --git a/src/uu/ls/src/colors.rs b/src/uu/ls/src/colors.rs index 4f97e42e27d..2a1eb254e7c 100644 --- a/src/uu/ls/src/colors.rs +++ b/src/uu/ls/src/colors.rs @@ -5,6 +5,7 @@ use super::get_metadata_with_deref_opt; use super::PathData; use lscolors::{Indicator, LsColors, Style}; +use std::ffi::OsString; use std::fs::{DirEntry, Metadata}; use std::io::{BufWriter, Stdout}; @@ -31,9 +32,9 @@ impl<'a> StyleManager<'a> { pub(crate) fn apply_style( &mut self, new_style: Option<&Style>, - name: &str, + name: OsString, wrap: bool, - ) -> String { + ) -> OsString { let mut style_code = String::new(); let mut force_suffix_reset: bool = false; @@ -50,14 +51,14 @@ impl<'a> StyleManager<'a> { // normal style is to be printed we could skip printing new color // codes if !self.is_current_style(new_style) { - style_code.push_str(&self.reset(!self.initial_reset_is_done)); + style_code.push_str(self.reset(!self.initial_reset_is_done)); style_code.push_str(&self.get_style_code(new_style)); } } // if new style is None and current style is Normal we should reset it else if matches!(self.get_normal_style().copied(), Some(norm_style) if self.is_current_style(&norm_style)) { - style_code.push_str(&self.reset(false)); + style_code.push_str(self.reset(false)); // even though this is an unnecessary reset for gnu compatibility we allow it here force_suffix_reset = true; } @@ -69,16 +70,17 @@ impl<'a> StyleManager<'a> { // till the end of line let clear_to_eol = if wrap { "\x1b[K" } else { "" }; - format!( - "{style_code}{name}{}{clear_to_eol}", - self.reset(force_suffix_reset), - ) + let mut ret: OsString = style_code.into(); + ret.push(name); + ret.push(self.reset(force_suffix_reset)); + ret.push(clear_to_eol); + ret } /// Resets the current style and returns the default ANSI reset code to /// reset all text formatting attributes. If `force` is true, the reset is /// done even if the reset has been applied before. - pub(crate) fn reset(&mut self, force: bool) -> String { + pub(crate) fn reset(&mut self, force: bool) -> &str { // todo: // We need to use style from `Indicator::Reset` but as of now ls colors // uses a fallback mechanism and because of that if `Indicator::Reset` @@ -87,9 +89,9 @@ impl<'a> StyleManager<'a> { if self.current_style.is_some() || force { self.initial_reset_is_done = true; self.current_style = None; - return "\x1b[0m".to_string(); + return "\x1b[0m"; } - String::new() + "" } pub(crate) fn get_style_code(&mut self, new_style: &Style) -> String { @@ -124,9 +126,9 @@ impl<'a> StyleManager<'a> { &mut self, path: &PathData, md_option: Option<&Metadata>, - name: &str, + name: OsString, wrap: bool, - ) -> String { + ) -> OsString { let style = self .colors .style_for_path_with_metadata(&path.p_buf, md_option); @@ -136,9 +138,9 @@ impl<'a> StyleManager<'a> { pub(crate) fn apply_style_based_on_dir_entry( &mut self, dir_entry: &DirEntry, - name: &str, + name: OsString, wrap: bool, - ) -> String { + ) -> OsString { let style = self.colors.style_for(dir_entry); self.apply_style(style, name, wrap) } @@ -149,13 +151,13 @@ impl<'a> StyleManager<'a> { /// unnecessary calls to stat() /// and manages the symlink errors pub(crate) fn color_name( - name: &str, + name: OsString, path: &PathData, style_manager: &mut StyleManager, out: &mut BufWriter, target_symlink: Option<&PathData>, wrap: bool, -) -> String { +) -> OsString { // Check if the file has capabilities #[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))] { diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs index 0faec2c92f9..69122ccd08b 100644 --- a/src/uu/ls/src/dired.rs +++ b/src/uu/ls/src/dired.rs @@ -183,8 +183,7 @@ pub fn update_positions(dired: &mut DiredOutput, start: usize, end: usize) { /// we don't use clap here because we need to know if the argument is present /// as it can be overridden by --hyperlink pub fn is_dired_arg_present() -> bool { - let args: Vec = std::env::args().collect(); - args.iter().any(|x| x == "--dired" || x == "-D") + std::env::args_os().any(|x| x == "--dired" || x == "-D") } #[cfg(test)] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 9a22006e097..6317834fdad 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -55,12 +55,13 @@ use uucore::libc::{dev_t, major, minor}; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::line_ending::LineEnding; -use uucore::quoting_style::{self, QuotingStyle}; +use uucore::quoting_style::{self, escape_name, QuotingStyle}; use uucore::{ display::Quotable, error::{set_exit_code, UError, UResult}, format_usage, fs::display_permissions, + os_str_as_bytes_lossy, parse_size::parse_size_u64, shortcut_value_parser::ShortcutValueParser, version_cmp::version_cmp, @@ -2047,12 +2048,13 @@ impl PathData { /// dir1: <- This as well /// file11 /// ``` -fn show_dir_name(path_data: &PathData, out: &mut BufWriter, config: &Config) { - // FIXME: replace this with appropriate behavior for literal unprintable bytes +fn show_dir_name( + path_data: &PathData, + out: &mut BufWriter, + config: &Config, +) -> std::io::Result<()> { let escaped_name = - quoting_style::escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style) - .to_string_lossy() - .to_string(); + quoting_style::escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style); let name = if config.hyperlink && !config.dired { create_hyperlink(&escaped_name, path_data) @@ -2060,7 +2062,8 @@ fn show_dir_name(path_data: &PathData, out: &mut BufWriter, config: &Con escaped_name }; - write!(out, "{name}:").unwrap(); + write_os_str(out, &name)?; + write!(out, ":") } #[allow(clippy::cognitive_complexity)] @@ -2137,7 +2140,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { if config.dired { dired::indent(&mut out)?; } - show_dir_name(path_data, &mut out, config); + show_dir_name(path_data, &mut out, config)?; writeln!(out)?; if config.dired { // First directory displayed @@ -2149,7 +2152,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { } } else { writeln!(out)?; - show_dir_name(path_data, &mut out, config); + show_dir_name(path_data, &mut out, config)?; writeln!(out)?; } } @@ -2266,7 +2269,11 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { case_sensitive: true, }; let file_name = entry.file_name(); - // If the decoding fails, still show an incorrect rendering + // If the decoding fails, still match best we can + // FIXME: use OsStrings or Paths once we have a glob crate that supports it: + // https://github.com/rust-lang/glob/issues/23 + // https://github.com/rust-lang/glob/issues/78 + // https://github.com/BurntSushi/ripgrep/issues/1250 let file_name = match file_name.to_str() { Some(s) => s.to_string(), None => file_name.to_string_lossy().into_owned(), @@ -2378,7 +2385,7 @@ fn enter_directory( dired::add_dir_name(dired, dir_name_size); } - show_dir_name(e, out, config); + show_dir_name(e, out, config)?; writeln!(out)?; enter_directory( e, @@ -2518,7 +2525,7 @@ fn display_items( let quoted = items.iter().any(|item| { let name = escape_name(&item.display_name, &config.quoting_style); - name.starts_with('\'') + os_str_starts_with(&name, b"'") }); if config.format == Format::Long { @@ -2592,19 +2599,20 @@ fn display_items( Format::Commas => { let mut current_col = 0; if let Some(name) = names.next() { - write!(out, "{name}")?; - current_col = ansi_width(&name) as u16 + 2; + write_os_str(out, &name)?; + current_col = ansi_width(&name.to_string_lossy()) as u16 + 2; } for name in names { - let name_width = ansi_width(&name) as u16; + let name_width = ansi_width(&name.to_string_lossy()) as u16; // If the width is 0 we print one single line if config.width != 0 && current_col + name_width + 1 > config.width { current_col = name_width + 2; - write!(out, ",\n{name}")?; + writeln!(out, ",")?; } else { current_col += name_width + 2; - write!(out, ", {name}")?; + write!(out, ", ")?; } + write_os_str(out, &name)?; } // Current col is never zero again if names have been printed. // So we print a newline. @@ -2614,7 +2622,8 @@ fn display_items( } _ => { for name in names { - write!(out, "{}{}", name, config.line_ending)?; + write_os_str(out, &name)?; + write!(out, "{}", config.line_ending)?; } } }; @@ -2648,7 +2657,7 @@ fn get_block_size(md: &Metadata, config: &Config) -> u64 { } fn display_grid( - names: impl Iterator, + names: impl Iterator, width: u16, direction: Direction, out: &mut BufWriter, @@ -2662,13 +2671,13 @@ fn display_grid( write!(out, " ")?; } printed_something = true; - write!(out, "{name}")?; + write_os_str(out, &name)?; } if printed_something { writeln!(out)?; } } else { - let names: Vec = if quoted { + let names: Vec<_> = if quoted { // In case some names are quoted, GNU adds a space before each // entry that does not start with a quote to make it prettier // on multiline. @@ -2683,10 +2692,12 @@ fn display_grid( // ``` names .map(|n| { - if n.starts_with('\'') || n.starts_with('"') { + if os_str_starts_with(&n, b"'") || os_str_starts_with(&n, b"\"") { n } else { - format!(" {n}") + let mut ret: OsString = " ".into(); + ret.push(n); + ret } }) .collect() @@ -2694,6 +2705,12 @@ fn display_grid( names.collect() }; + // FIXME: the Grid crate only supports &str, so can't display raw bytes + let names: Vec<_> = names + .into_iter() + .map(|s| s.to_string_lossy().into_owned()) + .collect(); + // Determine whether to use tabs for separation based on whether any entry ends with '/'. // If any entry ends with '/', it indicates that the -F flag is likely used to classify directories. let use_tabs = names.iter().any(|name| name.ends_with('/')); @@ -2755,14 +2772,14 @@ fn display_item_long( style_manager: &mut Option, quoted: bool, ) -> UResult<()> { - let mut output_display: String = String::new(); + let mut output_display: Vec = vec![]; // apply normal color to non filename outputs if let Some(style_manager) = style_manager { write!(output_display, "{}", style_manager.apply_normal()).unwrap(); } if config.dired { - output_display += " "; + output_display.extend(b" "); } if let Some(md) = item.get_metadata(out) { #[cfg(any(not(unix), target_os = "android", target_os = "macos"))] @@ -2869,11 +2886,13 @@ fn display_item_long( String::new(), out, style_manager, - ansi_width(&output_display), + ansi_width(&String::from_utf8_lossy(&output_display)), ); - let displayed_item = if quoted && !item_name.starts_with('\'') { - format!(" {item_name}") + let displayed_item = if quoted && !os_str_starts_with(&item_name, b"'") { + let mut ret: OsString = " ".into(); + ret.push(item_name); + ret } else { item_name }; @@ -2886,7 +2905,8 @@ fn display_item_long( ); dired::update_positions(dired, start, end); } - write!(output_display, "{}{}", displayed_item, config.line_ending).unwrap(); + write_os_str(&mut output_display, &displayed_item)?; + write!(output_display, "{}", config.line_ending)?; } else { #[cfg(unix)] let leading_char = { @@ -2966,7 +2986,7 @@ fn display_item_long( String::new(), out, style_manager, - ansi_width(&output_display), + ansi_width(&String::from_utf8_lossy(&output_display)), ); let date_len = 12; @@ -2982,12 +3002,13 @@ fn display_item_long( dired::calculate_and_update_positions( dired, output_display.len(), - displayed_item.trim().len(), + displayed_item.to_string_lossy().trim().len(), ); } - write!(output_display, "{}{}", displayed_item, config.line_ending).unwrap(); + write_os_str(&mut output_display, &displayed_item)?; + write!(output_display, "{}", config.line_ending)?; } - write!(out, "{output_display}")?; + out.write_all(&output_display)?; Ok(()) } @@ -3228,7 +3249,7 @@ fn display_item_name( out: &mut BufWriter, style_manager: &mut Option, current_column: usize, -) -> String { +) -> OsString { // This is our return value. We start by `&path.display_name` and modify it along the way. let mut name = escape_name(&path.display_name, &config.quoting_style); @@ -3240,11 +3261,14 @@ fn display_item_name( } if let Some(style_manager) = style_manager { - name = color_name(&name, path, style_manager, out, None, is_wrap(name.len())); + let len = name.len(); + name = color_name(name, path, style_manager, out, None, is_wrap(len)); } if config.format != Format::Long && !more_info.is_empty() { - name = more_info + &name; + let old_name = name; + name = more_info.into(); + name.push(&old_name); } if config.indicator_style != IndicatorStyle::None { @@ -3270,7 +3294,7 @@ fn display_item_name( }; if let Some(c) = char_opt { - name.push(c); + name.push(OsStr::new(&c.to_string())); } } @@ -3281,7 +3305,7 @@ fn display_item_name( { match path.p_buf.read_link() { Ok(target) => { - name.push_str(" -> "); + name.push(" -> "); // We might as well color the symlink output after the arrow. // This makes extra system calls, but provides important information that @@ -3309,10 +3333,10 @@ fn display_item_name( ) .is_err() { - name.push_str(&path.p_buf.read_link().unwrap().to_string_lossy()); + name.push(path.p_buf.read_link().unwrap()); } else { - name.push_str(&color_name( - &escape_name(target.as_os_str(), &config.quoting_style), + name.push(color_name( + escape_name(target.as_os_str(), &config.quoting_style), path, style_manager, out, @@ -3323,7 +3347,7 @@ fn display_item_name( } else { // If no coloring is required, we just use target as is. // Apply the right quoting - name.push_str(&escape_name(target.as_os_str(), &config.quoting_style)); + name.push(escape_name(target.as_os_str(), &config.quoting_style)); } } Err(err) => { @@ -3341,14 +3365,16 @@ fn display_item_name( } else { pad_left(&path.security_context, pad_count) }; - name = format!("{security_context} {name}"); + let old_name = name; + name = format!("{security_context} ").into(); + name.push(old_name); } } name } -fn create_hyperlink(name: &str, path: &PathData) -> String { +fn create_hyperlink(name: &OsStr, path: &PathData) -> OsString { let hostname = hostname::get().unwrap_or_else(|_| OsString::from("")); let hostname = hostname.to_string_lossy(); @@ -3373,7 +3399,10 @@ fn create_hyperlink(name: &str, path: &PathData) -> String { .collect(); // \x1b = ESC, \x07 = BEL - format!("\x1b]8;;file://{hostname}{absolute_path}\x07{name}\x1b]8;;\x07") + let mut ret: OsString = format!("\x1b]8;;file://{hostname}{absolute_path}\x07").into(); + ret.push(name); + ret.push("\x1b]8;;\x07"); + ret } #[cfg(not(unix))] @@ -3546,9 +3575,10 @@ fn calculate_padding_collection( padding_collections } -// FIXME: replace this with appropriate behavior for literal unprintable bytes -fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { - quoting_style::escape_name(name, style) - .to_string_lossy() - .to_string() +fn os_str_starts_with(haystack: &OsStr, needle: &[u8]) -> bool { + os_str_as_bytes_lossy(haystack).starts_with(needle) +} + +fn write_os_str(writer: &mut W, string: &OsStr) -> std::io::Result<()> { + writer.write_all(&os_str_as_bytes_lossy(string)) } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f65078a0d5a..81a44dafa0f 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -5563,3 +5563,34 @@ fn test_ls_capabilities() { .stdout_contains("\x1b[30;41mcap_pos") // spell-checker:disable-line .stdout_does_not_contain("0;41mtest/dir/cap_neg"); // spell-checker:disable-line } + +#[cfg(feature = "test_risky_names")] +#[test] +fn test_non_unicode_names() { + // more extensive unit tests for correct escaping etc. are in the quoting_style module + let scene = TestScenario::new(util_name!()); + let target_file = uucore::os_str_from_bytes(b"some-dir1/\xC0.file") + .expect("Only unix platforms can test non-unicode names"); + let target_dir = uucore::os_str_from_bytes(b"some-dir1/\xC0.dir") + .expect("Only unix platforms can test non-unicode names"); + let at = &scene.fixtures; + at.mkdir("some-dir1"); + at.touch(target_file); + at.mkdir(target_dir); + + scene + .ucmd() + .arg("--quoting-style=shell-escape") + .arg("some-dir1") + .succeeds() + .stdout_contains("''$'\\300''.dir'") + .stdout_contains("''$'\\300''.file'"); + + scene + .ucmd() + .arg("--quoting-style=literal") + .arg("--show-control-chars") + .arg("some-dir1") + .succeeds() + .stdout_is_bytes(b"\xC0.dir\n\xC0.file\n"); +} From 3a41272174fd592461fea01bce9526f613645198 Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Sat, 28 Dec 2024 20:46:01 -0500 Subject: [PATCH 193/351] CICD: test risky names on *-unknown-linux-gnu Linux + GNU is the OS + userspace least likely to run into issues with such names, both now and in the future. --- .github/workflows/CICD.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index e4c0fa345f7..6bf18655180 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -486,13 +486,13 @@ jobs: matrix: job: # - { os , target , cargo-options , features , use-cross , toolchain, skip-tests } - - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf, features: feat_os_unix_gnueabihf, use-cross: use-cross, skip-tests: true } + - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross , skip-tests: true } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross , skip-tests: true } - { os: ubuntu-latest , target: aarch64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross , skip-tests: true } # - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross } - - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: "feat_os_unix,test_risky_names", use-cross: use-cross } - { os: ubuntu-latest , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: "feat_os_unix,test_risky_names", use-cross: use-cross } - { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - { os: ubuntu-latest , target: x86_64-unknown-redox , features: feat_os_unix_redox , use-cross: redoxer , skip-tests: true } - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos } # M1 CPU From 0a0db41a2c6695d91c4c648fcf3083c6cdb12f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Sat, 7 Dec 2024 00:18:18 +0100 Subject: [PATCH 194/351] chore(clippy): fix clippy warnings --- src/uucore/src/lib/features/backup_control.rs | 19 ++++++++----------- src/uucore/src/lib/features/proc_info.rs | 9 +++++++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/uucore/src/lib/features/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs index 4b4f7aa93c6..591f57f95f4 100644 --- a/src/uucore/src/lib/features/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -667,17 +667,14 @@ mod tests { #[test] fn test_numbered_backup_path() { - assert_eq!(numbered_backup_path(&Path::new("")), PathBuf::from(".~1~")); + assert_eq!(numbered_backup_path(Path::new("")), PathBuf::from(".~1~")); + assert_eq!(numbered_backup_path(Path::new("/")), PathBuf::from("/.~1~")); assert_eq!( - numbered_backup_path(&Path::new("/")), - PathBuf::from("/.~1~") - ); - assert_eq!( - numbered_backup_path(&Path::new("/hello/world")), + numbered_backup_path(Path::new("/hello/world")), PathBuf::from("/hello/world.~1~") ); assert_eq!( - numbered_backup_path(&Path::new("/hello/world/")), + numbered_backup_path(Path::new("/hello/world/")), PathBuf::from("/hello/world.~1~") ); } @@ -685,19 +682,19 @@ mod tests { #[test] fn test_simple_backup_path() { assert_eq!( - simple_backup_path(&Path::new(""), ".bak"), + simple_backup_path(Path::new(""), ".bak"), PathBuf::from(".bak") ); assert_eq!( - simple_backup_path(&Path::new("/"), ".bak"), + simple_backup_path(Path::new("/"), ".bak"), PathBuf::from("/.bak") ); assert_eq!( - simple_backup_path(&Path::new("/hello/world"), ".bak"), + simple_backup_path(Path::new("/hello/world"), ".bak"), PathBuf::from("/hello/world.bak") ); assert_eq!( - simple_backup_path(&Path::new("/hello/world/"), ".bak"), + simple_backup_path(Path::new("/hello/world/"), ".bak"), PathBuf::from("/hello/world.bak") ); } diff --git a/src/uucore/src/lib/features/proc_info.rs b/src/uucore/src/lib/features/proc_info.rs index 7c812ec2af9..af5d498d229 100644 --- a/src/uucore/src/lib/features/proc_info.rs +++ b/src/uucore/src/lib/features/proc_info.rs @@ -19,6 +19,15 @@ //! `snice` (TBD) //! +// This file is currently flagged as dead code, because it isn't used anywhere +// in the codebase. It may be useful in the future though, so we decide to keep +// it. +// The code was originally written in procps +// (https://github.com/uutils/procps/blob/main/src/uu/pgrep/src/process.rs) +// but was eventually moved here. +// See https://github.com/uutils/coreutils/pull/6932 for discussion. +#![allow(dead_code)] + use crate::features::tty::Teletype; use std::hash::Hash; use std::{ From 50271381c49df659881e19aca7667132eebb7aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Fri, 6 Dec 2024 21:31:24 +0100 Subject: [PATCH 195/351] fix cargo-deny-action job --- deny.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/deny.toml b/deny.toml index d64a2d33a9c..79bfe926eb2 100644 --- a/deny.toml +++ b/deny.toml @@ -22,7 +22,6 @@ allow = [ "Apache-2.0", "ISC", "BSD-2-Clause", - "BSD-2-Clause-FreeBSD", "BSD-3-Clause", "BSL-1.0", "CC0-1.0", From e8141d33607d39367c6e1830665a80ff6bb371f3 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Sun, 29 Dec 2024 04:31:02 +0100 Subject: [PATCH 196/351] Add `--tests` to CICD clippy call --- .github/workflows/code-quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 814da316a7a..c4a166493c3 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -112,7 +112,7 @@ jobs: fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets --features ${{ matrix.job.features }} -pcoreutils -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + S=$(cargo clippy --all-targets --features ${{ matrix.job.features }} --tests -pcoreutils -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi style_spellcheck: From 48eb8d870356ebb60ce1dcf32774373a5f740b59 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Dec 2024 10:20:34 +0100 Subject: [PATCH 197/351] Add procps to the spell ignore list --- src/uucore/src/lib/features/proc_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/proc_info.rs b/src/uucore/src/lib/features/proc_info.rs index af5d498d229..f40847c1d69 100644 --- a/src/uucore/src/lib/features/proc_info.rs +++ b/src/uucore/src/lib/features/proc_info.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 exitstatus cmdline kworker pgrep pwait snice +// spell-checker:ignore exitstatus cmdline kworker pgrep pwait snice procps //! Set of functions to manage IDs //! From 80ed8d514a0ea869ae904335f31e3e7713dabf2a Mon Sep 17 00:00:00 2001 From: Elden <69537751+elde-n@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:14:35 +0000 Subject: [PATCH 198/351] rm: Remove an unecessary open call (#7010) it was causing some sandbox issues and it seems that isn't necessary after all --- src/uu/rm/src/rm.rs | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index ad9c942a8ee..c52497df1c5 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -8,8 +8,7 @@ use clap::{builder::ValueParser, crate_version, parser::ValueSource, Arg, ArgAction, Command}; use std::collections::VecDeque; use std::ffi::{OsStr, OsString}; -use std::fs::{self, File, Metadata}; -use std::io::ErrorKind; +use std::fs::{self, Metadata}; use std::ops::BitOr; #[cfg(not(windows))] use std::os::unix::ffi::OsStrExt; @@ -526,26 +525,16 @@ fn prompt_file(path: &Path, options: &Options) -> bool { } } // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too - match File::options().read(true).write(true).open(path) { - Ok(file) => { - let Ok(metadata) = file.metadata() else { - return true; - }; + let Ok(metadata) = fs::metadata(path) else { + return true; + }; - if options.interactive == InteractiveMode::Always && !metadata.permissions().readonly() - { - return if metadata.len() == 0 { - prompt_yes!("remove regular empty file {}?", path.quote()) - } else { - prompt_yes!("remove file {}?", path.quote()) - }; - } - } - Err(err) => { - if err.kind() != ErrorKind::PermissionDenied { - return true; - } - } + if options.interactive == InteractiveMode::Always && !metadata.permissions().readonly() { + return if metadata.len() == 0 { + prompt_yes!("remove regular empty file {}?", path.quote()) + } else { + prompt_yes!("remove file {}?", path.quote()) + }; } prompt_file_permission_readonly(path) } From a45731eed853280f030c3dce3ac88032f2d204c5 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Sun, 29 Dec 2024 20:10:53 +0700 Subject: [PATCH 199/351] uucore/buf_copy: delete empty doc-string --- src/uucore/src/lib/features/buf_copy/linux.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uucore/src/lib/features/buf_copy/linux.rs b/src/uucore/src/lib/features/buf_copy/linux.rs index 77d25e44beb..7c41f14a58e 100644 --- a/src/uucore/src/lib/features/buf_copy/linux.rs +++ b/src/uucore/src/lib/features/buf_copy/linux.rs @@ -58,8 +58,6 @@ impl From for Error { /// /// Result of operation and bytes successfully written (as a `u64`) when /// operation is successful. -/// - pub fn copy_stream(src: &mut R, dest: &mut S) -> UResult where R: Read + AsFd + AsRawFd, From 5577165c772123e1f0591f7215106efd913bf754 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 29 Dec 2024 14:51:01 +0100 Subject: [PATCH 200/351] rm: remove outdated comment --- src/uu/rm/src/rm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index c52497df1c5..f1f45cf5261 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -524,7 +524,7 @@ fn prompt_file(path: &Path, options: &Options) -> bool { } } } - // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too + let Ok(metadata) = fs::metadata(path) else { return true; }; From 836351dd7a8920534b1ac64ee0c2ff46a391ba18 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 23:41:14 +0100 Subject: [PATCH 201/351] ch*: refactor duplicate declarations --- src/uu/chgrp/src/chgrp.rs | 30 ++++++++------------------ src/uu/chmod/src/chmod.rs | 18 +++++++++++----- src/uu/chown/src/chown.rs | 32 ++++++++-------------------- src/uucore/src/lib/features/perms.rs | 21 ++++++++++++++++++ 4 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index fba2cef1611..7f68f7b13ba 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -63,7 +63,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let mut cmd = Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -140,24 +140,12 @@ pub fn uu_app() -> Command { .long(options::RECURSIVE) .help("operate on files and directories recursively") .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE.chars().next().unwrap()) - .help("if a command line argument is a symbolic link to a directory, traverse it") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE.chars().next().unwrap()) - .help("do not traverse any symbolic links (default)") - .overrides_with_all([options::traverse::TRAVERSE, options::traverse::EVERY]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::traverse::EVERY) - .short(options::traverse::EVERY.chars().next().unwrap()) - .help("traverse every symbolic link to a directory encountered") - .action(ArgAction::SetTrue), - ) + ); + + // Add traverse-related arguments + for arg in uucore::perms::traverse_args() { + cmd = cmd.arg(arg); + } + + cmd } diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index d1325743782..07c04a98d62 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -151,7 +151,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let mut cmd = Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -206,16 +206,24 @@ pub fn uu_app() -> Command { .help("use RFILE's mode instead of MODE values"), ) .arg( - Arg::new(options::MODE).required_unless_present(options::REFERENCE), // It would be nice if clap could parse with delimiter, e.g. "g-x,u+x", - // however .multiple_occurrences(true) cannot be used here because FILE already needs that. - // Only one positional argument with .multiple_occurrences(true) set is allowed per command + Arg::new(options::MODE).required_unless_present(options::REFERENCE), + // It would be nice if clap could parse with delimiter, e.g. "g-x,u+x", + // however .multiple_occurrences(true) cannot be used here because FILE already needs that. + // Only one positional argument with .multiple_occurrences(true) set is allowed per command ) .arg( Arg::new(options::FILE) .required_unless_present(options::MODE) .action(ArgAction::Append) .value_hint(clap::ValueHint::AnyPath), - ) + ); + + // Add traverse-related arguments + for arg in uucore::perms::traverse_args() { + cmd = cmd.arg(arg); + } + + cmd } struct Chmoder { diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 0e9b8b2423c..61fe24eabe5 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -77,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let mut cmd = Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -165,34 +165,20 @@ pub fn uu_app() -> Command { .long(options::verbosity::SILENT) .action(ArgAction::SetTrue), ) - .arg( - Arg::new(options::traverse::TRAVERSE) - .short(options::traverse::TRAVERSE.chars().next().unwrap()) - .help("if a command line argument is a symbolic link to a directory, traverse it") - .overrides_with_all([options::traverse::EVERY, options::traverse::NO_TRAVERSE]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::traverse::EVERY) - .short(options::traverse::EVERY.chars().next().unwrap()) - .help("traverse every symbolic link to a directory encountered") - .overrides_with_all([options::traverse::TRAVERSE, options::traverse::NO_TRAVERSE]) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::traverse::NO_TRAVERSE) - .short(options::traverse::NO_TRAVERSE.chars().next().unwrap()) - .help("do not traverse any symbolic links (default)") - .overrides_with_all([options::traverse::TRAVERSE, options::traverse::EVERY]) - .action(ArgAction::SetTrue), - ) .arg( Arg::new(options::verbosity::VERBOSE) .long(options::verbosity::VERBOSE) .short('v') .help("output a diagnostic for every file processed") .action(ArgAction::SetTrue), - ) + ); + + // Add traverse-related arguments + for arg in uucore::perms::traverse_args() { + cmd = cmd.arg(arg); + } + + cmd } /// Parses the user string to extract the UID. diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 3623e9e6149..9ee64e21287 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -13,6 +13,7 @@ pub use crate::features::entries; use crate::show_error; use clap::{Arg, ArgMatches, Command}; use libc::{gid_t, uid_t}; +use options::traverse; use walkdir::WalkDir; use std::io::Error as IOError; @@ -634,6 +635,26 @@ pub fn chown_base( executor.exec() } +pub fn traverse_args() -> Vec { + vec![ + Arg::new(traverse::TRAVERSE) + .short(traverse::TRAVERSE.chars().next().unwrap()) + .help("if a command line argument is a symbolic link to a directory, traverse it") + .overrides_with_all([traverse::EVERY, traverse::NO_TRAVERSE]) + .action(clap::ArgAction::SetTrue), + Arg::new(traverse::EVERY) + .short(traverse::EVERY.chars().next().unwrap()) + .help("traverse every symbolic link to a directory encountered") + .overrides_with_all([traverse::TRAVERSE, traverse::NO_TRAVERSE]) + .action(clap::ArgAction::SetTrue), + Arg::new(traverse::NO_TRAVERSE) + .short(traverse::NO_TRAVERSE.chars().next().unwrap()) + .help("do not traverse any symbolic links (default)") + .overrides_with_all([traverse::TRAVERSE, traverse::EVERY]) + .action(clap::ArgAction::SetTrue), + ] +} + #[cfg(test)] mod tests { // Note this useful idiom: importing names from outer (for mod tests) scope. From 6de5aa7ac7b33fb32d530908a7eb16591341ae17 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 23:41:58 +0100 Subject: [PATCH 202/351] ch*: improve the presentation --- src/uucore/src/lib/features/perms.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 9ee64e21287..9b59f725949 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -34,6 +34,7 @@ pub enum VerbosityLevel { Verbose, Normal, } + #[derive(PartialEq, Eq, Clone, Debug)] pub struct Verbosity { pub groups_only: bool, From e45c56b926cdd8d5c346513fd06c3569085588ef Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Dec 2024 00:17:22 +0100 Subject: [PATCH 203/351] ch*: also remove duplications for deref & no deref --- src/uu/chgrp/src/chgrp.rs | 20 +++----------------- src/uu/chmod/src/chmod.rs | 12 ++++++++++-- src/uu/chown/src/chown.rs | 23 ++--------------------- src/uucore/src/lib/features/perms.rs | 17 ++++++++++++++++- 4 files changed, 31 insertions(+), 41 deletions(-) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 7f68f7b13ba..16f67144ad9 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -73,7 +73,7 @@ pub fn uu_app() -> Command { Arg::new(options::HELP) .long(options::HELP) .help("Print help information.") - .action(ArgAction::Help) + .action(ArgAction::Help), ) .arg( Arg::new(options::verbosity::CHANGES) @@ -101,20 +101,6 @@ pub fn uu_app() -> Command { .help("output a diagnostic for every file processed") .action(ArgAction::SetTrue), ) - .arg( - Arg::new(options::dereference::DEREFERENCE) - .long(options::dereference::DEREFERENCE) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::dereference::NO_DEREFERENCE) - .short('h') - .long(options::dereference::NO_DEREFERENCE) - .help( - "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", - ) - .action(ArgAction::SetTrue), - ) .arg( Arg::new(options::preserve_root::PRESERVE) .long(options::preserve_root::PRESERVE) @@ -142,8 +128,8 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ); - // Add traverse-related arguments - for arg in uucore::perms::traverse_args() { + // Add common arguments with chgrp, chown & chmod + for arg in uucore::perms::common_args() { cmd = cmd.arg(arg); } diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 07c04a98d62..f62f665033a 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -23,6 +23,7 @@ const USAGE: &str = help_usage!("chmod.md"); const LONG_USAGE: &str = help_section!("after help", "chmod.md"); mod options { + pub const HELP: &str = "help"; pub const CHANGES: &str = "changes"; pub const QUIET: &str = "quiet"; // visible_alias("silent") pub const VERBOSE: &str = "verbose"; @@ -158,6 +159,13 @@ pub fn uu_app() -> Command { .args_override_self(true) .infer_long_args(true) .no_binary_name(true) + .disable_help_flag(true) + .arg( + Arg::new(options::HELP) + .long(options::HELP) + .help("Print help information.") + .action(ArgAction::Help), + ) .arg( Arg::new(options::CHANGES) .long(options::CHANGES) @@ -218,8 +226,8 @@ pub fn uu_app() -> Command { .value_hint(clap::ValueHint::AnyPath), ); - // Add traverse-related arguments - for arg in uucore::perms::traverse_args() { + // Add common arguments with chgrp, chown & chmod + for arg in uucore::perms::common_args() { cmd = cmd.arg(arg); } diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 61fe24eabe5..d8cb14fa311 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -96,25 +96,6 @@ pub fn uu_app() -> Command { .help("like verbose but report only when a change is made") .action(ArgAction::SetTrue), ) - .arg( - Arg::new(options::dereference::DEREFERENCE) - .long(options::dereference::DEREFERENCE) - .help( - "affect the referent of each symbolic link (this is the default), \ - rather than the symbolic link itself", - ) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::dereference::NO_DEREFERENCE) - .short('h') - .long(options::dereference::NO_DEREFERENCE) - .help( - "affect symbolic links instead of any referenced file \ - (useful only on systems that can change the ownership of a symlink)", - ) - .action(ArgAction::SetTrue), - ) .arg( Arg::new(options::FROM) .long(options::FROM) @@ -173,8 +154,8 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ); - // Add traverse-related arguments - for arg in uucore::perms::traverse_args() { + // Add common arguments with chgrp, chown & chmod + for arg in uucore::perms::common_args() { cmd = cmd.arg(arg); } diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 9b59f725949..73b84be721f 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -636,7 +636,7 @@ pub fn chown_base( executor.exec() } -pub fn traverse_args() -> Vec { +pub fn common_args() -> Vec { vec![ Arg::new(traverse::TRAVERSE) .short(traverse::TRAVERSE.chars().next().unwrap()) @@ -653,6 +653,21 @@ pub fn traverse_args() -> Vec { .help("do not traverse any symbolic links (default)") .overrides_with_all([traverse::TRAVERSE, traverse::EVERY]) .action(clap::ArgAction::SetTrue), + Arg::new(options::dereference::DEREFERENCE) + .long(options::dereference::DEREFERENCE) + .help( + "affect the referent of each symbolic link (this is the default), \ + rather than the symbolic link itself", + ) + .action(clap::ArgAction::SetTrue), + Arg::new(options::dereference::NO_DEREFERENCE) + .short('h') + .long(options::dereference::NO_DEREFERENCE) + .help( + "affect symbolic links instead of any referenced file \ + (useful only on systems that can change the ownership of a symlink)", + ) + .action(clap::ArgAction::SetTrue), ] } From 29694212dd9423ce3a221bc32cdf9636d941bde8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Dec 2024 16:36:14 +0100 Subject: [PATCH 204/351] refactor: simplify the code by using the clap function instead --- src/uu/chgrp/src/chgrp.rs | 13 ++++--------- src/uu/chmod/src/chmod.rs | 13 ++++--------- src/uu/chown/src/chown.rs | 13 ++++--------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 16f67144ad9..fe5aee872e6 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -63,7 +63,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - let mut cmd = Command::new(uucore::util_name()) + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -126,12 +126,7 @@ pub fn uu_app() -> Command { .long(options::RECURSIVE) .help("operate on files and directories recursively") .action(ArgAction::SetTrue), - ); - - // Add common arguments with chgrp, chown & chmod - for arg in uucore::perms::common_args() { - cmd = cmd.arg(arg); - } - - cmd + ) + // Add common arguments with chgrp, chown & chmod + .args(uucore::perms::common_args()) } diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index f62f665033a..c05288e213f 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - let mut cmd = Command::new(uucore::util_name()) + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -224,14 +224,9 @@ pub fn uu_app() -> Command { .required_unless_present(options::MODE) .action(ArgAction::Append) .value_hint(clap::ValueHint::AnyPath), - ); - - // Add common arguments with chgrp, chown & chmod - for arg in uucore::perms::common_args() { - cmd = cmd.arg(arg); - } - - cmd + ) + // Add common arguments with chgrp, chown & chmod + .args(uucore::perms::common_args()) } struct Chmoder { diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index d8cb14fa311..20bc87c341d 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -77,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - let mut cmd = Command::new(uucore::util_name()) + Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -152,14 +152,9 @@ pub fn uu_app() -> Command { .short('v') .help("output a diagnostic for every file processed") .action(ArgAction::SetTrue), - ); - - // Add common arguments with chgrp, chown & chmod - for arg in uucore::perms::common_args() { - cmd = cmd.arg(arg); - } - - cmd + ) + // Add common arguments with chgrp, chown & chmod + .args(uucore::perms::common_args()) } /// Parses the user string to extract the UID. From 89d79753b12cdda47cc85006c1f7396a397e3296 Mon Sep 17 00:00:00 2001 From: jovielarue <44442031+jovielarue@users.noreply.github.com> Date: Sun, 29 Dec 2024 13:02:35 -0700 Subject: [PATCH 205/351] Update uudoc to include development and code of conduct in SUMMARY and fix links in book --- docs/src/CODE_OF_CONDUCT.md | 1 + docs/src/{contributing.md => CONTRIBUTING.md} | 0 docs/src/DEVELOPMENT.md | 1 + src/bin/uudoc.rs | 4 +++- 4 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 docs/src/CODE_OF_CONDUCT.md rename docs/src/{contributing.md => CONTRIBUTING.md} (100%) create mode 100644 docs/src/DEVELOPMENT.md diff --git a/docs/src/CODE_OF_CONDUCT.md b/docs/src/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..b9520d32ef8 --- /dev/null +++ b/docs/src/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +{{ #include ../../CODE_OF_CONDUCT.md }} diff --git a/docs/src/contributing.md b/docs/src/CONTRIBUTING.md similarity index 100% rename from docs/src/contributing.md rename to docs/src/CONTRIBUTING.md diff --git a/docs/src/DEVELOPMENT.md b/docs/src/DEVELOPMENT.md new file mode 100644 index 00000000000..20270b710fd --- /dev/null +++ b/docs/src/DEVELOPMENT.md @@ -0,0 +1 @@ +{{ #include ../../DEVELOPMENT.md }} diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index f2c325e3259..111e7a77fce 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -46,7 +46,9 @@ fn main() -> io::Result<()> { * [Installation](installation.md)\n\ * [Build from source](build.md)\n\ * [Platform support](platforms.md)\n\ - * [Contributing](contributing.md)\n\ + * [Contributing](CONTRIBUTING.md)\n\ + \t* [Development](DEVELOPMENT.md)\n\ + \t* [Code of Conduct](CODE_OF_CONDUCT.md)\n\ * [GNU test coverage](test_coverage.md)\n\ * [Extensions](extensions.md)\n\ \n\ From cd8bebe703a1eec308d0e84c504ff7e34ee5aad4 Mon Sep 17 00:00:00 2001 From: jovielarue <44442031+jovielarue@users.noreply.github.com> Date: Mon, 30 Dec 2024 08:44:49 -0700 Subject: [PATCH 206/351] Disable top-level heading warning MD041 for new files --- docs/src/CODE_OF_CONDUCT.md | 2 ++ docs/src/DEVELOPMENT.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/src/CODE_OF_CONDUCT.md b/docs/src/CODE_OF_CONDUCT.md index b9520d32ef8..ce326a1ee03 100644 --- a/docs/src/CODE_OF_CONDUCT.md +++ b/docs/src/CODE_OF_CONDUCT.md @@ -1 +1,3 @@ + + {{ #include ../../CODE_OF_CONDUCT.md }} diff --git a/docs/src/DEVELOPMENT.md b/docs/src/DEVELOPMENT.md index 20270b710fd..580cecf0855 100644 --- a/docs/src/DEVELOPMENT.md +++ b/docs/src/DEVELOPMENT.md @@ -1 +1,3 @@ + + {{ #include ../../DEVELOPMENT.md }} From cccab35337f91b5aba450d409fc854423fc6fb4e Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 30 Dec 2024 12:23:36 -0500 Subject: [PATCH 207/351] seq: improve error handling for invalid -f values Improve the error message produced by `seq` when given invalid format specifiers for the `-f` option. Before this commit: $ seq -f "%" 1 seq: %: invalid conversion specification $ seq -f "%g%" 1 seq: %: invalid conversion specification After this commit: $ seq -f "%" 1 seq: format '%' ends in % $ seq -f "%g%" 1 seq: format '%g%' has too many % directives This matches the behavior of GNU `seq`. --- src/uucore/src/lib/features/format/mod.rs | 19 +++++++++++++++---- tests/by-util/test_seq.rs | 10 ++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 25d128ed8ca..6a09b32e2a9 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -22,7 +22,7 @@ //! 3. Parse both `printf` specifiers and escape sequences (for e.g. `printf`) //! //! This module aims to combine all three use cases. An iterator parsing each -//! of these cases is provided by [`parse_escape_only`], [`parse_spec_only`] +//! of these cases is provided by [`parse_spec_only`], [`parse_escape_only`] //! and [`parse_spec_and_escape`], respectively. //! //! There is a special [`Format`] type, which can be used to parse a format @@ -46,6 +46,8 @@ use std::{ ops::ControlFlow, }; +use os_display::Quotable; + use crate::error::UError; pub use self::{ @@ -63,6 +65,8 @@ pub enum FormatError { NeedAtLeastOneSpec(Vec), WrongSpecType, InvalidPrecision(String), + /// The format specifier ends with a %, as in `%f%`. + EndsWithPercent(Vec), } impl Error for FormatError {} @@ -92,6 +96,9 @@ impl Display for FormatError { "format '{}' has no % directive", String::from_utf8_lossy(s) ), + Self::EndsWithPercent(s) => { + write!(f, "format {} ends in %", String::from_utf8_lossy(s).quote()) + } Self::InvalidPrecision(precision) => write!(f, "invalid precision: '{precision}'"), // TODO: Error message below needs some work Self::WrongSpecType => write!(f, "wrong % directive type was given"), @@ -190,6 +197,7 @@ pub fn parse_spec_only( let mut current = fmt; std::iter::from_fn(move || match current { [] => None, + [b'%'] => Some(Err(FormatError::EndsWithPercent(fmt.to_vec()))), [b'%', b'%', rest @ ..] => { current = rest; Some(Ok(FormatItem::Char(b'%'))) @@ -323,11 +331,14 @@ impl Format { let mut suffix = Vec::new(); for item in &mut iter { - match item? { - FormatItem::Spec(_) => { + match item { + // If the `format_string` is of the form `%f%f` or + // `%f%`, then return an error. + Ok(FormatItem::Spec(_)) | Err(FormatError::EndsWithPercent(_)) => { return Err(FormatError::TooManySpecs(format_string.as_ref().to_vec())); } - FormatItem::Char(c) => suffix.push(c), + Ok(FormatItem::Char(c)) => suffix.push(c), + Err(e) => return Err(e), } } diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 96460cf5fdc..3ff9227345c 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -787,6 +787,16 @@ fn test_invalid_format() { .fails() .no_stdout() .stderr_contains("format '%g%g' has too many % directives"); + new_ucmd!() + .args(&["-f", "%g%", "1"]) + .fails() + .no_stdout() + .stderr_contains("format '%g%' has too many % directives"); + new_ucmd!() + .args(&["-f", "%", "1"]) + .fails() + .no_stdout() + .stderr_contains("format '%' ends in %"); } #[test] From c9acf5ddd4b3bd50ad433c5f7ffe9189cb630c5a Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 30 Dec 2024 21:07:45 -0500 Subject: [PATCH 208/351] head: fix subtraction underflow with --bytes=-N Fix a subtraction underflow that occurred on `head --bytes=-N` when the input had fewer than `N` bytes. Before this commit, printf "a" | head -c -2 would panic. After this commit, it terminates successfully with no output as expected. --- src/uu/head/src/head.rs | 3 ++- tests/by-util/test_head.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index a6bb7b53fc0..04751fa71e2 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -256,6 +256,7 @@ fn catch_too_large_numbers_in_backwards_bytes_or_lines(n: u64) -> Option } } +/// Print to stdout all but the last `n` bytes from the given reader. fn read_but_last_n_bytes(input: &mut impl std::io::BufRead, n: u64) -> std::io::Result<()> { if n == 0 { //prints everything @@ -285,7 +286,7 @@ fn read_but_last_n_bytes(input: &mut impl std::io::BufRead, n: u64) -> std::io:: if total_read <= n { // Fill the ring buffer without exceeding n bytes - let overflow = total_read - n; + let overflow = n - total_read; ring_buffer.extend_from_slice(&buffer[..read - overflow]); } else { // Write the ring buffer and the part of the buffer that exceeds n diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 296d7dab7ca..1d4b1596337 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -157,6 +157,23 @@ fn test_negative_byte_syntax() { .stdout_is(""); } +#[test] +fn test_negative_bytes_greater_than_input_size_stdin() { + new_ucmd!() + .args(&["-c", "-2"]) + .pipe_in("a") + .succeeds() + .no_output(); +} + +#[test] +fn test_negative_bytes_greater_than_input_size_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.write_bytes("f", b"a"); + ts.ucmd().args(&["-c", "-2", "f"]).succeeds().no_output(); +} + #[test] fn test_negative_zero_lines() { new_ucmd!() From 646fc394f664b8b1abfd4735968511548336377f Mon Sep 17 00:00:00 2001 From: "jovie :)" <44442031+jovielarue@users.noreply.github.com> Date: Tue, 31 Dec 2024 01:16:14 -0700 Subject: [PATCH 209/351] env: don't allow '=' with -u (#7008) * Added error handling to ensure '=' after -u returns 125 as '=' is not allowed * Added tests for disallow = on short options * Added error handling to ensure '=' after -u returns 125 as '=' is not allowed * Added tests for disallow = on short options * Disallow only short unset option from having '=' as its starting character * Remove needless tests and update function name for clarity --- src/uu/env/src/env.rs | 13 +++++++++++++ tests/by-util/test_env.rs | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 9e2e56d06c0..ea31c01079e 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -370,6 +370,19 @@ impl EnvAppData { self.had_string_argument = true; } _ => { + let arg_str = arg.to_string_lossy(); + + // Short unset option (-u) is not allowed to contain '=' + if arg_str.contains('=') + && arg_str.starts_with("-u") + && !arg_str.starts_with("--") + { + return Err(USimpleError::new( + 125, + format!("cannot unset '{}': Invalid argument", &arg_str[2..]), + )); + } + all_args.push(arg.clone()); } } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index a1b13e02037..2b33f725dbe 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -882,6 +882,29 @@ fn test_env_arg_ignore_signal_empty() { .no_stderr() .stdout_contains("hello"); } + +#[test] +fn disallow_equals_sign_on_short_unset_option() { + let ts = TestScenario::new(util_name!()); + + ts.ucmd() + .arg("-u=") + .fails() + .code_is(125) + .stderr_contains("env: cannot unset '=': Invalid argument"); + ts.ucmd() + .arg("-u=A1B2C3") + .fails() + .code_is(125) + .stderr_contains("env: cannot unset '=A1B2C3': Invalid argument"); + ts.ucmd().arg("--split-string=A1B=2C3=").succeeds(); + ts.ucmd() + .arg("--unset=") + .fails() + .code_is(125) + .stderr_contains("env: cannot unset '': Invalid argument"); +} + #[cfg(test)] mod tests_split_iterator { From 4b68bce9a7718f260d38005498f5a85f43a5b9ea Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 31 Dec 2024 10:11:44 +0100 Subject: [PATCH 210/351] chmod: add uucore's "perms" feature to Cargo.toml --- src/uu/chmod/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 9d62348792f..acfeb6c0409 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -19,7 +19,7 @@ path = "src/chmod.rs" [dependencies] clap = { workspace = true } libc = { workspace = true } -uucore = { workspace = true, features = ["fs", "mode"] } +uucore = { workspace = true, features = ["fs", "mode", "perms"] } [[bin]] name = "chmod" From d88dc3435be025489d57acd5a40c1d91c688cc26 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 10:53:49 +0100 Subject: [PATCH 211/351] CI: test all features individually --- .github/workflows/CICD.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 624a7c2abd8..1f12f36300d 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -971,3 +971,26 @@ jobs: echo "Building and testing $f" cargo test -p "uu_$f" || exit 1 done + + test_all_features: + name: Test all features separately + needs: [ min_version, deps ] + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: build and test all features individually + shell: bash + run: | + for f in $(util/show-utils.sh) + do + echo "Running tests with --features=$f and --no-default-features" + cargo test --features=$f --no-default-features + done From 4e2246f4b8434cc3e8efb50a95393bb80acc3609 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 21:38:49 +0100 Subject: [PATCH 212/351] why-skip.txt: remove a test that now passes --- util/why-skip.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/util/why-skip.txt b/util/why-skip.txt index c790311c13d..40bb2a0093e 100644 --- a/util/why-skip.txt +++ b/util/why-skip.txt @@ -63,10 +63,6 @@ tests/misc/csplit-heap.sh = multicall binary is disabled = tests/misc/coreutils.sh -= your ls doesn't call capget = -tests/ls/no-cap.sh - - = not running on GNU/Hurd = tests/id/gnu-zero-uids.sh From 3760163b3f53393537c19ef68701007e073753bd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 21:30:04 +0100 Subject: [PATCH 213/351] document the remaining failures/errors --- util/why-error.txt | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 util/why-error.txt diff --git a/util/why-error.txt b/util/why-error.txt new file mode 100644 index 00000000000..0b09c08565d --- /dev/null +++ b/util/why-error.txt @@ -0,0 +1,88 @@ +This file documents why some tests are failing: + +gnu/tests/chgrp/from.sh +gnu/tests/chmod/symlinks.sh +gnu/tests/chroot/chroot-credentials.sh +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 +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/fail-2eperm.sh +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/seq/seq.pl +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 44073e7b238f118379ff27548d111586af126982 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 11:49:30 +0100 Subject: [PATCH 214/351] doc: chmod/symlinks.sh is being worked on --- util/why-error.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/why-error.txt b/util/why-error.txt index 0b09c08565d..981d9cfee86 100644 --- a/util/why-error.txt +++ b/util/why-error.txt @@ -1,7 +1,7 @@ This file documents why some tests are failing: gnu/tests/chgrp/from.sh -gnu/tests/chmod/symlinks.sh +gnu/tests/chmod/symlinks.sh - https://github.com/uutils/coreutils/pull/7025 gnu/tests/chroot/chroot-credentials.sh gnu/tests/cp/cp-i.sh gnu/tests/cp/preserve-gid.sh From 4677dd5b3a1a9343e9bacfafbf40abed18283bff Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 11:49:49 +0100 Subject: [PATCH 215/351] doc: seq/seq has been fixed https://github.com/uutils/coreutils/pull/7032 --- util/why-error.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.txt b/util/why-error.txt index 981d9cfee86..e13ad97d550 100644 --- a/util/why-error.txt +++ b/util/why-error.txt @@ -63,7 +63,6 @@ gnu/tests/rm/one-file-system.sh - https://github.com/uutils/coreutils/issues/701 gnu/tests/rm/rm1.sh gnu/tests/rm/rm2.sh gnu/tests/seq/seq-precision.sh -gnu/tests/seq/seq.pl gnu/tests/shred/shred-passes.sh gnu/tests/sort/sort-continue.sh gnu/tests/sort/sort-debug-keys.sh From f14dff0dc92ae1f54f892081d2ed73da0a234ec3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 11:51:21 +0100 Subject: [PATCH 216/351] doc: rm/fail-2eperm.sh has been fixed --- util/why-error.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.txt b/util/why-error.txt index e13ad97d550..40d4dcdcf8e 100644 --- a/util/why-error.txt +++ b/util/why-error.txt @@ -57,7 +57,6 @@ 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/fail-2eperm.sh 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 From 049a1fbce8bf8f2f0d4cdaacbf7515d2beb8f03f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 11:53:30 +0100 Subject: [PATCH 217/351] doc: https://github.com/uutils/coreutils/pull/7009 is being worked on --- util/why-error.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/why-error.txt b/util/why-error.txt index 40d4dcdcf8e..ceb7a956542 100644 --- a/util/why-error.txt +++ b/util/why-error.txt @@ -42,7 +42,7 @@ 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 +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 From e3de43a942e362633cd70f49c563d2dd42342b83 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 12:26:05 +0100 Subject: [PATCH 218/351] doc: extend the spell list --- .vscode/cspell.dictionaries/jargon.wordlist.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 4109630e553..edb9cd8f371 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -88,6 +88,7 @@ nolinks nonblock nonportable nonprinting +nonseekable notrunc noxfer ofile @@ -145,6 +146,8 @@ xattrs consts deps dev +fdlimit +inacc maint proc procs From 4c330d43ba9a24ee31d0867b2dacaa0cb4c76a8b Mon Sep 17 00:00:00 2001 From: Alexander Shirokov Date: Mon, 30 Dec 2024 22:33:40 +0100 Subject: [PATCH 219/351] uucore:format:fix floating-point rounding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change resolves issues with exponent calculation and usage, ensuring more accurate formatting: - Exponent for negative values can differ from 0 - Switching to decimal mode now follows the P > X ≥ −4 rule --- .../src/lib/features/format/num_format.rs | 36 +++++++++++++- tests/by-util/test_printf.rs | 47 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 546171b96fe..caee8e30374 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -404,8 +404,10 @@ fn format_float_shortest( }; } - let mut exponent = f.log10().floor() as i32; - if f != 0.0 && exponent <= -4 || exponent > precision as i32 { + // Retrieve the exponent. Note that log10 is undefined for negative numbers. + // To avoid NaN or zero (due to i32 conversion), use the absolute value of f. + let mut exponent = f.abs().log10().floor() as i32; + if f != 0.0 && exponent < -4 || exponent > precision as i32 { // Scientific-ish notation (with a few differences) let mut normalized = f / 10.0_f64.powi(exponent); @@ -665,4 +667,34 @@ mod test { assert_eq!(&f("1000.02030"), "1000.0203"); assert_eq!(&f("1000.00000"), "1000"); } + + #[test] + fn shortest_float_abs_value_less_than_one() { + use super::format_float_shortest; + let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.1171875), "0.117188"); + assert_eq!(f(0.01171875), "0.0117188"); + assert_eq!(f(0.001171875), "0.00117187"); + assert_eq!(f(0.0001171875), "0.000117187"); + assert_eq!(f(0.001171875001), "0.00117188"); + assert_eq!(f(-0.1171875), "-0.117188"); + assert_eq!(f(-0.01171875), "-0.0117188"); + assert_eq!(f(-0.001171875), "-0.00117187"); + assert_eq!(f(-0.0001171875), "-0.000117187"); + assert_eq!(f(-0.001171875001), "-0.00117188"); + } + + #[test] + fn shortest_float_switch_decimal_scientific() { + use super::format_float_shortest; + let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.001), "0.001"); + assert_eq!(f(0.0001), "0.0001"); + assert_eq!(f(0.00001), "1e-05"); + assert_eq!(f(0.000001), "1e-06"); + assert_eq!(f(-0.001), "-0.001"); + assert_eq!(f(-0.0001), "-0.0001"); + assert_eq!(f(-0.00001), "-1e-05"); + assert_eq!(f(-0.000001), "-1e-06"); + } } diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 29a8cc9140f..9b29c404ca8 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -916,3 +916,50 @@ fn float_flag_position_space_padding() { .succeeds() .stdout_only(" +1.0"); } + +#[test] +fn float_abs_value_less_than_one() { + new_ucmd!() + .args(&["%g", "0.1171875"]) + .succeeds() + .stdout_only("0.117188"); + + // The original value from #7031 issue + new_ucmd!() + .args(&["%g", "-0.1171875"]) + .succeeds() + .stdout_only("-0.117188"); + + new_ucmd!() + .args(&["%g", "0.01171875"]) + .succeeds() + .stdout_only("0.0117188"); + + new_ucmd!() + .args(&["%g", "-0.01171875"]) + .succeeds() + .stdout_only("-0.0117188"); + + new_ucmd!() + .args(&["%g", "0.001171875001"]) + .succeeds() + .stdout_only("0.00117188"); + + new_ucmd!() + .args(&["%g", "-0.001171875001"]) + .succeeds() + .stdout_only("-0.00117188"); +} + +#[test] +fn float_switch_switch_decimal_scientific() { + new_ucmd!() + .args(&["%g", "0.0001"]) + .succeeds() + .stdout_only("0.0001"); + + new_ucmd!() + .args(&["%g", "0.00001"]) + .succeeds() + .stdout_only("1e-05"); +} From 24f4b9ca4fcfa75be442d4f0abc54af34de6a037 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 14:41:20 +0100 Subject: [PATCH 220/351] document two more failures --- util/why-error.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/why-error.txt b/util/why-error.txt index ceb7a956542..bbfb0508c0f 100644 --- a/util/why-error.txt +++ b/util/why-error.txt @@ -1,8 +1,8 @@ This file documents why some tests are failing: -gnu/tests/chgrp/from.sh +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 +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 From 958ac72113c5e86763a8bffd82cd84bc5acac20e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 22:50:49 +0100 Subject: [PATCH 221/351] ls: finish the plug of mtime Will help with tests/ls/ls-time --- src/uu/ls/src/ls.rs | 3 +++ tests/by-util/test_ls.rs | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 9a22006e097..b9da2c97720 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -536,6 +536,7 @@ fn extract_time(options: &clap::ArgMatches) -> Time { match field.as_str() { "ctime" | "status" => Time::Change, "access" | "atime" | "use" => Time::Access, + "mtime" | "modification" => Time::Modification, "birth" | "creation" => Time::Birth, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --time"), @@ -1442,12 +1443,14 @@ pub fn uu_app() -> Command { "Show time in :\n\ \taccess time (-u): atime, access, use;\n\ \tchange time (-t): ctime, status.\n\ + \tmodification time: mtime, modification.\n\ \tbirth time: birth, creation;", ) .value_name("field") .value_parser(ShortcutValueParser::new([ PossibleValue::new("atime").alias("access").alias("use"), PossibleValue::new("ctime").alias("status"), + PossibleValue::new("mtime").alias("modification"), PossibleValue::new("birth").alias("creation"), ])) .hide_possible_values(true) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f65078a0d5a..1a5c11194f0 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -2099,6 +2099,30 @@ fn test_ls_order_time() { } } +#[test] +fn test_ls_order_mtime() { + use std::time::SystemTime; + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let f3 = at.make_file("test-3"); + f3.set_modified(SystemTime::now()).unwrap(); + let f4 = at.make_file("test-4"); + f4.set_modified(SystemTime::now()).unwrap(); + let f1 = at.make_file("test-1"); + f1.set_modified(SystemTime::now()).unwrap(); + let f2 = at.make_file("test-2"); + f2.set_modified(SystemTime::now()).unwrap(); + + let result = scene.ucmd().arg("-t").arg("--time=mtime").succeeds(); + result.stdout_only("test-2\ntest-1\ntest-4\ntest-3\n"); + f3.set_modified(SystemTime::now()).unwrap(); + + f4.set_modified(SystemTime::now()).unwrap(); + let result = scene.ucmd().arg("-t").arg("--time=mtime").succeeds(); + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); +} + #[test] fn test_ls_non_existing() { new_ucmd!().arg("doesntexist").fails(); From 1d2740c12af93f7ecf11566dc4ff7cef4aa08162 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 1 Jan 2025 09:25:14 +0100 Subject: [PATCH 222/351] Bump itertools from 0.13.0 to 0.14.0 --- Cargo.lock | 19 ++++++++++++++----- Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f30a0c262f0..2efdbb0740a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,7 +175,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -1245,6 +1245,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.4" @@ -3061,7 +3070,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "itertools", + "itertools 0.14.0", "quick-error", "regex", "uucore", @@ -3197,7 +3206,7 @@ dependencies = [ "compare", "ctrlc", "fnv", - "itertools", + "itertools 0.14.0", "memchr", "nix", "rand", @@ -3481,7 +3490,7 @@ name = "uu_yes" version = "0.0.28" dependencies = [ "clap", - "itertools", + "itertools 0.14.0", "nix", "uucore", ] @@ -3500,7 +3509,7 @@ dependencies = [ "dunce", "glob", "hex", - "itertools", + "itertools 0.14.0", "lazy_static", "libc", "md-5", diff --git a/Cargo.toml b/Cargo.toml index 98c50d6dbb6..2e208c5562f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -298,7 +298,7 @@ glob = "0.3.1" half = "2.4.1" hostname = "0.4" indicatif = "0.17.8" -itertools = "0.13.0" +itertools = "0.14.0" libc = "0.2.153" lscolors = { version = "0.20.0", default-features = false, features = [ "gnu_legacy", From ac542966e7c7b99dff50b597837ed436fb7eaa7e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 1 Jan 2025 09:27:22 +0100 Subject: [PATCH 223/351] deny.toml: add itertools to skip list --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index 79bfe926eb2..4208b9245ce 100644 --- a/deny.toml +++ b/deny.toml @@ -109,6 +109,8 @@ skip = [ { name = "thiserror", version = "1.0.69" }, # thiserror { name = "thiserror-impl", version = "1.0.69" }, + # bindgen + { name = "itertools", version = "0.13.0" }, ] # spell-checker: enable From 1e23a3fa8d1cf751bead646666f03a3f6406d514 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 1 Jan 2025 13:04:04 +0100 Subject: [PATCH 224/351] uniq: fix multibyte input Should fix tests/uniq/uniq.pl --- src/uu/uniq/src/uniq.rs | 61 +++++++++++++++++++------------------- tests/by-util/test_uniq.rs | 10 +++++++ 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index b9090cd50cf..4995f8c198e 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -154,43 +154,44 @@ impl Uniq { fn cmp_key(&self, line: &[u8], mut closure: F) -> bool where - F: FnMut(&mut dyn Iterator) -> bool, + F: FnMut(&mut dyn Iterator) -> bool, { let fields_to_check = self.skip_fields(line); - let len = fields_to_check.len(); - let slice_start = self.slice_start.unwrap_or(0); - let slice_stop = self.slice_stop.unwrap_or(len); - if len > 0 { - // fast path: avoid doing any work if there is no need to skip or map to lower-case - if !self.ignore_case && slice_start == 0 && slice_stop == len { - return closure(&mut fields_to_check.iter().copied()); - } - // fast path: avoid skipping - if self.ignore_case && slice_start == 0 && slice_stop == len { - return closure(&mut fields_to_check.iter().map(|u| u.to_ascii_lowercase())); - } + // Skip self.slice_start bytes (if -s was used). + // self.slice_start is how many characters to skip, but historically + // uniq’s `-s N` means “skip N *bytes*,” so do that literally: + let skip_bytes = self.slice_start.unwrap_or(0); + let fields_to_check = if skip_bytes < fields_to_check.len() { + &fields_to_check[skip_bytes..] + } else { + // If skipping beyond end-of-line, leftover is empty => effectively "" + &[] + }; - // fast path: we can avoid mapping chars to lower-case, if we don't want to ignore the case - if !self.ignore_case { - return closure( - &mut fields_to_check - .iter() - .skip(slice_start) - .take(slice_stop) - .copied(), - ); + // Convert the leftover bytes to UTF-8 for character-based -w + // If invalid UTF-8, just compare them as individual bytes (fallback). + let string_after_skip = match std::str::from_utf8(fields_to_check) { + Ok(s) => s, + Err(_) => { + // Fallback: if invalid UTF-8, treat them as single-byte “chars” + return closure(&mut fields_to_check.iter().map(|&b| b as char)); } + }; - closure( - &mut fields_to_check - .iter() - .skip(slice_start) - .take(slice_stop) - .map(|u| u.to_ascii_lowercase()), - ) + let total_chars = string_after_skip.chars().count(); + + // `-w N` => Compare no more than N characters + let slice_stop = self.slice_stop.unwrap_or(total_chars); + let slice_start = slice_stop.min(total_chars); + + let mut iter = string_after_skip.chars().take(slice_start); + + if self.ignore_case { + // We can do ASCII-lowercase or full Unicode-lowercase. For minimal changes, do ASCII: + closure(&mut iter.map(|c| c.to_ascii_lowercase())) } else { - closure(&mut fields_to_check.iter().copied()) + closure(&mut iter) } } diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index 30cf73b8436..18f226f07dd 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -1172,3 +1172,13 @@ fn gnu_tests() { } } } + +#[test] +fn test_stdin_w1_multibyte() { + let input = "à\ná\n"; + new_ucmd!() + .args(&["-w1"]) + .pipe_in(input) + .run() + .stdout_is("à\ná\n"); +} From 97a442ff6c34d3a5991a4137630350951dc49ec8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 1 Jan 2025 13:24:33 +0100 Subject: [PATCH 225/351] CI: disable the windows job for indiv tests Closes: #7044 --- .github/workflows/CICD.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 1f12f36300d..a406b094298 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -979,7 +979,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest] + # windows-latest - https://github.com/uutils/coreutils/issues/7044 steps: - uses: actions/checkout@v4 with: From 974b18e85c806ba0d8027d28b3a4c56a2dc6b053 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 20:00:24 +0000 Subject: [PATCH 226/351] chore(deps): update rust crate rstest to 0.24.0 --- Cargo.lock | 62 +++++------------------------------------------------- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2efdbb0740a..8d19eca2d31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -976,54 +976,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-macro" version = "0.3.31" @@ -1035,12 +993,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - [[package]] name = "futures-task" version = "0.3.31" @@ -1059,13 +1011,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1953,21 +1901,21 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rstest" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89" dependencies = [ - "futures", "futures-timer", + "futures-util", "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b" dependencies = [ "cfg-if", "glob", diff --git a/Cargo.toml b/Cargo.toml index 2e208c5562f..4ae18be7452 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -323,7 +323,7 @@ rand = { version = "0.8.5", features = ["small_rng"] } rand_core = "0.6.4" rayon = "1.10" regex = "1.10.4" -rstest = "0.23.0" +rstest = "0.24.0" rust-ini = "0.21.0" same-file = "1.0.6" self_cell = "1.0.4" From 5bd5cdb7c1f114ca3829cdab37545139de8e4c4b Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 31 Dec 2024 12:05:25 -0500 Subject: [PATCH 227/351] chroot: fix parsing of --userspec argument Fix the parsing of the `--userspec=USER:GROUP` argument so that the both the user and the group are optional, and update the error message to match that of GNU `chroot`. This commit also removes the incorrect `clap` arguments for `--user` and `--group`. In `chroot --user=USER`, the `--user` is an abbreviation of `--userspec`, and in `chroot --group=GROUP`, the `--group` is an abbreviation of `--groups`. Closes #7040. --- src/uu/chroot/src/chroot.rs | 167 ++++++++++++++++++++--------------- src/uu/chroot/src/error.rs | 8 +- tests/by-util/test_chroot.rs | 37 ++++++-- 3 files changed, 129 insertions(+), 83 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 58e11e101cf..04db1380f48 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -11,7 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::ffi::CString; use std::io::Error; use std::os::unix::prelude::OsStrExt; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process; use uucore::error::{set_exit_code, UClapError, UResult, UUsageError}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; @@ -23,14 +23,79 @@ static USAGE: &str = help_usage!("chroot.md"); mod options { pub const NEWROOT: &str = "newroot"; - pub const USER: &str = "user"; - pub const GROUP: &str = "group"; pub const GROUPS: &str = "groups"; pub const USERSPEC: &str = "userspec"; pub const COMMAND: &str = "command"; pub const SKIP_CHDIR: &str = "skip-chdir"; } +/// A user and group specification, where each is optional. +enum UserSpec { + NeitherGroupNorUser, + UserOnly(String), + GroupOnly(String), + UserAndGroup(String, String), +} + +struct Options { + /// Path to the new root directory. + newroot: PathBuf, + /// Whether to change to the new root directory. + skip_chdir: bool, + /// List of groups under which the command will be run. + groups: Vec, + /// The user and group (each optional) under which the command will be run. + userspec: Option, +} + +/// Parse a user and group from the argument to `--userspec`. +/// +/// The `spec` must be of the form `[USER][:[GROUP]]`, otherwise an +/// error is returned. +fn parse_userspec(spec: &str) -> UResult { + match &spec.splitn(2, ':').collect::>()[..] { + // "" + [""] => Ok(UserSpec::NeitherGroupNorUser), + // "usr" + [usr] => Ok(UserSpec::UserOnly(usr.to_string())), + // ":" + ["", ""] => Ok(UserSpec::NeitherGroupNorUser), + // ":grp" + ["", grp] => Ok(UserSpec::GroupOnly(grp.to_string())), + // "usr:" + [usr, ""] => Ok(UserSpec::UserOnly(usr.to_string())), + // "usr:grp" + [usr, grp] => Ok(UserSpec::UserAndGroup(usr.to_string(), grp.to_string())), + // everything else + _ => Err(ChrootError::InvalidUserspec(spec.to_string()).into()), + } +} + +impl Options { + /// Parse parameters from the command-line arguments. + fn from(matches: &clap::ArgMatches) -> UResult { + let newroot = match matches.get_one::(options::NEWROOT) { + Some(v) => Path::new(v).to_path_buf(), + None => return Err(ChrootError::MissingNewRoot.into()), + }; + let groups = match matches.get_one::(options::GROUPS) { + None => vec![], + Some(s) => s.split(",").map(str::to_string).collect(), + }; + let skip_chdir = matches.get_flag(options::SKIP_CHDIR); + let userspec = match matches.get_one::(options::USERSPEC) { + None => None, + Some(s) => Some(parse_userspec(s)?), + }; + Ok(Self { + newroot, + skip_chdir, + groups, + userspec, + }) + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?; @@ -39,17 +104,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let default_option: &'static str = "-i"; let user_shell = std::env::var("SHELL"); - let newroot: &Path = match matches.get_one::(options::NEWROOT) { - Some(v) => Path::new(v), - None => return Err(ChrootError::MissingNewRoot.into()), - }; + let options = Options::from(&matches)?; - let skip_chdir = matches.get_flag(options::SKIP_CHDIR); // We are resolving the path in case it is a symlink or /. or /../ - if skip_chdir - && canonicalize(newroot, MissingHandling::Normal, ResolveMode::Logical) - .unwrap() - .to_str() + if options.skip_chdir + && canonicalize( + &options.newroot, + MissingHandling::Normal, + ResolveMode::Logical, + ) + .unwrap() + .to_str() != Some("/") { return Err(UUsageError::new( @@ -58,8 +123,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )); } - if !newroot.is_dir() { - return Err(ChrootError::NoSuchDirectory(format!("{}", newroot.display())).into()); + if !options.newroot.is_dir() { + return Err(ChrootError::NoSuchDirectory(format!("{}", options.newroot.display())).into()); } let commands = match matches.get_many::(options::COMMAND) { @@ -85,7 +150,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let chroot_args = &command[1..]; // NOTE: Tests can only trigger code beyond this point if they're invoked with root permissions - set_context(newroot, &matches)?; + set_context(&options)?; let pstatus = match process::Command::new(chroot_command) .args(chroot_args) @@ -125,20 +190,6 @@ pub fn uu_app() -> Command { .required(true) .index(1), ) - .arg( - Arg::new(options::USER) - .short('u') - .long(options::USER) - .help("User (ID or name) to switch before running the program") - .value_name("USER"), - ) - .arg( - Arg::new(options::GROUP) - .short('g') - .long(options::GROUP) - .help("Group (ID or name) to switch to") - .value_name("GROUP"), - ) .arg( Arg::new(options::GROUPS) .short('G') @@ -175,43 +226,18 @@ pub fn uu_app() -> Command { ) } -fn set_context(root: &Path, options: &clap::ArgMatches) -> UResult<()> { - let userspec_str = options.get_one::(options::USERSPEC); - let user_str = options - .get_one::(options::USER) - .map(|s| s.as_str()) - .unwrap_or_default(); - let group_str = options - .get_one::(options::GROUP) - .map(|s| s.as_str()) - .unwrap_or_default(); - let groups_str = options - .get_one::(options::GROUPS) - .map(|s| s.as_str()) - .unwrap_or_default(); - let skip_chdir = options.contains_id(options::SKIP_CHDIR); - let userspec = match userspec_str { - Some(u) => { - let s: Vec<&str> = u.split(':').collect(); - if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { - return Err(ChrootError::InvalidUserspec(u.to_string()).into()); - }; - s +fn set_context(options: &Options) -> UResult<()> { + enter_chroot(&options.newroot, options.skip_chdir)?; + set_groups_from_str(&options.groups)?; + match &options.userspec { + None | Some(UserSpec::NeitherGroupNorUser) => {} + Some(UserSpec::UserOnly(user)) => set_user(user)?, + Some(UserSpec::GroupOnly(group)) => set_main_group(group)?, + Some(UserSpec::UserAndGroup(user, group)) => { + set_main_group(group)?; + set_user(user)?; } - None => Vec::new(), - }; - - let (user, group) = if userspec.is_empty() { - (user_str, group_str) - } else { - (userspec[0], userspec[1]) - }; - - enter_chroot(root, skip_chdir)?; - - set_groups_from_str(groups_str)?; - set_main_group(group)?; - set_user(user)?; + } Ok(()) } @@ -239,7 +265,7 @@ fn set_main_group(group: &str) -> UResult<()> { if !group.is_empty() { let group_id = match entries::grp2gid(group) { Ok(g) => g, - _ => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), + _ => return Err(ChrootError::NoSuchGroup.into()), }; let err = unsafe { setgid(group_id) }; if err != 0 { @@ -261,13 +287,13 @@ fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } } -fn set_groups_from_str(groups: &str) -> UResult<()> { +fn set_groups_from_str(groups: &[String]) -> UResult<()> { if !groups.is_empty() { let mut groups_vec = vec![]; - for group in groups.split(',') { + for group in groups { let gid = match entries::grp2gid(group) { Ok(g) => g, - Err(_) => return Err(ChrootError::NoSuchGroup(group.to_string()).into()), + Err(_) => return Err(ChrootError::NoSuchGroup.into()), }; groups_vec.push(gid); } @@ -281,8 +307,7 @@ fn set_groups_from_str(groups: &str) -> UResult<()> { fn set_user(user: &str) -> UResult<()> { if !user.is_empty() { - let user_id = - entries::usr2uid(user).map_err(|_| ChrootError::NoSuchUser(user.to_string()))?; + let user_id = entries::usr2uid(user).map_err(|_| ChrootError::NoSuchUser)?; let err = unsafe { setuid(user_id as libc::uid_t) }; if err != 0 { return Err( diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs index 1b83e76256b..cc1acbee86e 100644 --- a/src/uu/chroot/src/error.rs +++ b/src/uu/chroot/src/error.rs @@ -28,10 +28,10 @@ pub enum ChrootError { MissingNewRoot, /// Failed to find the specified user. - NoSuchUser(String), + NoSuchUser, /// Failed to find the specified group. - NoSuchGroup(String), + NoSuchGroup, /// The given directory does not exist. NoSuchDirectory(String), @@ -74,8 +74,8 @@ impl Display for ChrootError { "Missing operand: NEWROOT\nTry '{} --help' for more information.", uucore::execution_phrase(), ), - Self::NoSuchUser(s) => write!(f, "no such user: {}", s.maybe_quote(),), - Self::NoSuchGroup(s) => write!(f, "no such group: {}", s.maybe_quote(),), + Self::NoSuchUser => write!(f, "invalid user"), + Self::NoSuchGroup => write!(f, "invalid group"), Self::NoSuchDirectory(s) => write!( f, "cannot change root directory to {}: no such directory", diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 42da75f4879..31facdd551e 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -55,13 +55,34 @@ fn test_no_such_directory() { #[test] fn test_invalid_user_spec() { - let (at, mut ucmd) = at_and_ucmd!(); + let ts = TestScenario::new(util_name!()); - at.mkdir("a"); + if let Ok(result) = run_ucmd_as_root(&ts, &["--userspec=ARABA:", "/"]) { + result + .failure() + .code_is(125) + .stderr_is("chroot: invalid user"); + } else { + print!("Test skipped; requires root user"); + } - let result = ucmd.arg("a").arg("--userspec=ARABA:").fails(); - result.code_is(125); - assert!(result.stderr_str().starts_with("chroot: invalid userspec")); + if let Ok(result) = run_ucmd_as_root(&ts, &["--userspec=ARABA:ARABA", "/"]) { + result + .failure() + .code_is(125) + .stderr_is("chroot: invalid user"); + } else { + print!("Test skipped; requires root user"); + } + + if let Ok(result) = run_ucmd_as_root(&ts, &["--userspec=:ARABA", "/"]) { + result + .failure() + .code_is(125) + .stderr_is("chroot: invalid group"); + } else { + print!("Test skipped; requires root user"); + } } #[test] @@ -77,10 +98,9 @@ fn test_invalid_user() { print!("Test skipped; requires root user"); } + // `--user` is an abbreviation of `--userspec`. if let Ok(result) = run_ucmd_as_root(&ts, &["--user=nobody:+65535", dir, "pwd"]) { - result - .failure() - .stderr_contains("no such user: nobody:+65535"); + result.failure().stderr_is("chroot: invalid user"); } else { print!("Test skipped; requires root user"); } @@ -116,6 +136,7 @@ fn test_preference_of_userspec() { at.mkdir("a"); + // `--user` is an abbreviation of `--userspec`. let result = ucmd .arg("a") .arg("--user") From e5e589afd5a9d03a51ec4a557cf46b33069cf0ba Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 2 Jan 2025 13:35:19 +0100 Subject: [PATCH 228/351] chroot: remove short option "-G" --- src/uu/chroot/src/chroot.rs | 1 - tests/by-util/test_chroot.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 04db1380f48..cc67704d867 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -192,7 +192,6 @@ pub fn uu_app() -> Command { ) .arg( Arg::new(options::GROUPS) - .short('G') .long(options::GROUPS) .help("Comma-separated list of groups to switch to") .value_name("GROUP1,GROUP2..."), diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 31facdd551e..711dd09435d 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -141,7 +141,7 @@ fn test_preference_of_userspec() { .arg("a") .arg("--user") .arg("fake") - .arg("-G") + .arg("--groups") .arg("ABC,DEF") .arg(format!("--userspec={username}:{group_name}")) .fails(); From e22bcbb74dd9782beaeddeefd4699456506ecc6a Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 2 Jan 2025 13:38:39 +0100 Subject: [PATCH 229/351] chroot: remove outdated info from help --- src/uu/chroot/src/chroot.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index cc67704d867..81c530dd8d6 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -199,11 +199,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::USERSPEC) .long(options::USERSPEC) - .help( - "Colon-separated user and group to switch to. \ - Same as -u USER -g GROUP. \ - Userspec has higher preference than -u and/or -g", - ) + .help("Colon-separated user and group to switch to.") .value_name("USER:GROUP"), ) .arg( From 541c345a4db333e75af8e27dbf56b2bb344a5e04 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 22:53:57 +0000 Subject: [PATCH 230/351] chore(deps): update rust crate bstr to v1.11.3 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d19eca2d31..bfca23683ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata", From 52c05e2fd678b286afa4ed4683dbeb03d8b41a8d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 2 Jan 2025 19:06:19 -0500 Subject: [PATCH 231/351] chroot: improve parsing of --groups parameter Improve the parsing of the `--groups` parameter to `chroot` so that it handles more error cases and better matches the behavior of GNU `chroot`. For example, multiple adjacent commas are allowed: --groups=a,,,b but spaces between commas are not allowed: --groups="a, , ,b" --- src/uu/chroot/src/chroot.rs | 64 +++++++++++++++++++++++++++++++++++-- src/uu/chroot/src/error.rs | 9 ++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 04db1380f48..cad30552e1a 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -16,7 +16,7 @@ use std::process; use uucore::error::{set_exit_code, UClapError, UResult, UUsageError}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; -use uucore::{entries, format_usage, help_about, help_usage}; +use uucore::{entries, format_usage, help_about, help_usage, show}; static ABOUT: &str = help_about!("chroot.md"); static USAGE: &str = help_usage!("chroot.md"); @@ -71,6 +71,60 @@ fn parse_userspec(spec: &str) -> UResult { } } +// Pre-condition: `list_str` is non-empty. +fn parse_group_list(list_str: &str) -> Result, ChrootError> { + let split: Vec<&str> = list_str.split(",").collect(); + if split.len() == 1 { + let name = split[0].trim(); + if name.is_empty() { + // --groups=" " + // chroot: invalid group ‘ ’ + Err(ChrootError::InvalidGroup(name.to_string())) + } else { + // --groups="blah" + Ok(vec![name.to_string()]) + } + } else if split.iter().all(|s| s.is_empty()) { + // --groups="," + // chroot: invalid group list ‘,’ + Err(ChrootError::InvalidGroupList(list_str.to_string())) + } else { + let mut result = vec![]; + let mut err = false; + for name in split { + let trimmed_name = name.trim(); + if trimmed_name.is_empty() { + if name.is_empty() { + // --groups="," + continue; + } else { + // --groups=", " + // chroot: invalid group ‘ ’ + show!(ChrootError::InvalidGroup(name.to_string())); + err = true; + } + } else { + // TODO Figure out a better condition here. + if trimmed_name.starts_with(char::is_numeric) + && trimmed_name.ends_with(|c: char| !c.is_numeric()) + { + // --groups="0trail" + // chroot: invalid group ‘0trail’ + show!(ChrootError::InvalidGroup(name.to_string())); + err = true; + } else { + result.push(trimmed_name.to_string()); + } + } + } + if err { + Err(ChrootError::GroupsParsingFailed) + } else { + Ok(result) + } + } +} + impl Options { /// Parse parameters from the command-line arguments. fn from(matches: &clap::ArgMatches) -> UResult { @@ -80,7 +134,13 @@ impl Options { }; let groups = match matches.get_one::(options::GROUPS) { None => vec![], - Some(s) => s.split(",").map(str::to_string).collect(), + Some(s) => { + if s.is_empty() { + vec![] + } else { + parse_group_list(s)? + } + } }; let skip_chdir = matches.get_flag(options::SKIP_CHDIR); let userspec = match matches.get_one::(options::USERSPEC) { diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs index cc1acbee86e..94a938e8bf0 100644 --- a/src/uu/chroot/src/error.rs +++ b/src/uu/chroot/src/error.rs @@ -21,6 +21,12 @@ pub enum ChrootError { /// Failed to find the specified command. CommandNotFound(String, Error), + GroupsParsingFailed, + + InvalidGroup(String), + + InvalidGroupList(String), + /// The given user and group specification was invalid. InvalidUserspec(String), @@ -68,6 +74,9 @@ impl Display for ChrootError { Self::CommandFailed(s, e) | Self::CommandNotFound(s, e) => { write!(f, "failed to run command {}: {}", s.to_string().quote(), e,) } + Self::GroupsParsingFailed => write!(f, "--groups parsing failed"), + Self::InvalidGroup(s) => write!(f, "invalid group: {}", s.quote()), + Self::InvalidGroupList(s) => write!(f, "invalid group list: {}", s.quote()), Self::InvalidUserspec(s) => write!(f, "invalid userspec: {}", s.quote(),), Self::MissingNewRoot => write!( f, From b59da37365460f720d66ea1c02d746a2a2e0f336 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 2 Jan 2025 19:06:49 -0500 Subject: [PATCH 232/351] chroot: make Options.groups into an Option type Change the type of `Options.groups` to be `Option>` so that the absence of the `--groups` option is meaningfully distinguished from an empty list of groups. This is important because `chroot --groups=''` is used specifically to disable supplementary group lookup. --- src/uu/chroot/src/chroot.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index cad30552e1a..3da4e52d1a3 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -43,7 +43,7 @@ struct Options { /// Whether to change to the new root directory. skip_chdir: bool, /// List of groups under which the command will be run. - groups: Vec, + groups: Option>, /// The user and group (each optional) under which the command will be run. userspec: Option, } @@ -133,12 +133,12 @@ impl Options { None => return Err(ChrootError::MissingNewRoot.into()), }; let groups = match matches.get_one::(options::GROUPS) { - None => vec![], + None => None, Some(s) => { if s.is_empty() { - vec![] + Some(vec![]) } else { - parse_group_list(s)? + Some(parse_group_list(s)?) } } }; @@ -288,7 +288,9 @@ pub fn uu_app() -> Command { fn set_context(options: &Options) -> UResult<()> { enter_chroot(&options.newroot, options.skip_chdir)?; - set_groups_from_str(&options.groups)?; + if let Some(groups) = &options.groups { + set_groups_from_str(groups)?; + } match &options.userspec { None | Some(UserSpec::NeitherGroupNorUser) => {} Some(UserSpec::UserOnly(user)) => set_user(user)?, From b29d8f10084e073c19d9d00380ca5bae51488d87 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 2 Jan 2025 19:20:21 -0500 Subject: [PATCH 233/351] chroot: fix several group setting issues Fix several issues in the behavior of `chroot`, mostly around setting group IDs. This commit ensures that supplemental groups provided by `--groups` are properly handled with respect to the groups implied by `--userspec`. This commit also ensures that we fall back to numeric parsing when attempting to lookup user and group IDs from their names. --- src/uu/chroot/src/chroot.rs | 214 +++++++++++++++++++++++++----------- src/uu/chroot/src/error.rs | 4 + 2 files changed, 152 insertions(+), 66 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 3da4e52d1a3..6174ae9abb1 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -13,10 +13,11 @@ use std::io::Error; use std::os::unix::prelude::OsStrExt; use std::path::{Path, PathBuf}; use std::process; +use uucore::entries::{grp2gid, usr2uid, Locate, Passwd}; use uucore::error::{set_exit_code, UClapError, UResult, UUsageError}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; -use uucore::{entries, format_usage, help_about, help_usage, show}; +use uucore::{format_usage, help_about, help_usage, show}; static ABOUT: &str = help_about!("chroot.md"); static USAGE: &str = help_usage!("chroot.md"); @@ -286,18 +287,156 @@ pub fn uu_app() -> Command { ) } +/// Get the UID for the given username, falling back to numeric parsing. +/// +/// According to the documentation of GNU `chroot`, "POSIX requires that +/// these commands first attempt to resolve the specified string as a +/// name, and only once that fails, then try to interpret it as an ID." +fn name_to_uid(name: &str) -> Result { + match usr2uid(name) { + Ok(uid) => Ok(uid), + Err(_) => name + .parse::() + .map_err(|_| ChrootError::NoSuchUser), + } +} + +/// Get the GID for the given group name, falling back to numeric parsing. +/// +/// According to the documentation of GNU `chroot`, "POSIX requires that +/// these commands first attempt to resolve the specified string as a +/// name, and only once that fails, then try to interpret it as an ID." +fn name_to_gid(name: &str) -> Result { + match grp2gid(name) { + Ok(gid) => Ok(gid), + Err(_) => name + .parse::() + .map_err(|_| ChrootError::NoSuchGroup), + } +} + +/// Get the list of group IDs for the given user. +/// +/// According to the GNU documentation, "the supplementary groups are +/// set according to the system defined list for that user". This +/// function gets that list. +fn supplemental_gids(uid: libc::uid_t) -> Vec { + match Passwd::locate(uid) { + Err(_) => vec![], + Ok(passwd) => passwd.belongs_to(), + } +} + +/// Set the supplemental group IDs for this process. +fn set_supplemental_gids(gids: &[libc::gid_t]) -> std::io::Result<()> { + #[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "openbsd"))] + let n = gids.len() as libc::c_int; + #[cfg(any(target_os = "linux", target_os = "android"))] + let n = gids.len() as libc::size_t; + let err = unsafe { setgroups(n, gids.as_ptr()) }; + if err == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the group ID of this process. +fn set_gid(gid: libc::gid_t) -> std::io::Result<()> { + let err = unsafe { setgid(gid) }; + if err == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the user ID of this process. +fn set_uid(uid: libc::uid_t) -> std::io::Result<()> { + let err = unsafe { setuid(uid) }; + if err == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// What to do when the `--groups` argument is missing. +enum Strategy { + /// Do nothing. + Nothing, + /// Use the list of supplemental groups for the given user. + /// + /// If the `bool` parameter is `false` and the list of groups for + /// the given user is empty, then this will result in an error. + FromUID(libc::uid_t, bool), +} + +/// Set supplemental groups when the `--groups` argument is not specified. +fn handle_missing_groups(strategy: Strategy) -> Result<(), ChrootError> { + match strategy { + Strategy::Nothing => Ok(()), + Strategy::FromUID(uid, false) => { + let gids = supplemental_gids(uid); + if gids.is_empty() { + Err(ChrootError::NoGroupSpecified(uid)) + } else { + set_supplemental_gids(&gids).map_err(ChrootError::SetGroupsFailed) + } + } + Strategy::FromUID(uid, true) => { + let gids = supplemental_gids(uid); + set_supplemental_gids(&gids).map_err(ChrootError::SetGroupsFailed) + } + } +} + +/// Set supplemental groups for this process. +fn set_supplemental_gids_with_strategy( + strategy: Strategy, + groups: &Option>, +) -> Result<(), ChrootError> { + match groups { + None => handle_missing_groups(strategy), + Some(groups) => { + let mut gids = vec![]; + for group in groups { + gids.push(name_to_gid(group)?); + } + set_supplemental_gids(&gids).map_err(ChrootError::SetGroupsFailed) + } + } +} + +/// Change the root, set the user ID, and set the group IDs for this process. fn set_context(options: &Options) -> UResult<()> { enter_chroot(&options.newroot, options.skip_chdir)?; - if let Some(groups) = &options.groups { - set_groups_from_str(groups)?; - } match &options.userspec { - None | Some(UserSpec::NeitherGroupNorUser) => {} - Some(UserSpec::UserOnly(user)) => set_user(user)?, - Some(UserSpec::GroupOnly(group)) => set_main_group(group)?, + None | Some(UserSpec::NeitherGroupNorUser) => { + let strategy = Strategy::Nothing; + set_supplemental_gids_with_strategy(strategy, &options.groups)?; + } + Some(UserSpec::UserOnly(user)) => { + let uid = name_to_uid(user)?; + let gid = uid as libc::gid_t; + let strategy = Strategy::FromUID(uid, false); + set_supplemental_gids_with_strategy(strategy, &options.groups)?; + set_gid(gid).map_err(|e| ChrootError::SetGidFailed(user.to_string(), e))?; + set_uid(uid).map_err(|e| ChrootError::SetUserFailed(user.to_string(), e))?; + } + Some(UserSpec::GroupOnly(group)) => { + let gid = name_to_gid(group)?; + let strategy = Strategy::Nothing; + set_supplemental_gids_with_strategy(strategy, &options.groups)?; + set_gid(gid).map_err(|e| ChrootError::SetGidFailed(group.to_string(), e))?; + } Some(UserSpec::UserAndGroup(user, group)) => { - set_main_group(group)?; - set_user(user)?; + let uid = name_to_uid(user)?; + let gid = name_to_gid(group)?; + let strategy = Strategy::FromUID(uid, true); + set_supplemental_gids_with_strategy(strategy, &options.groups)?; + set_gid(gid).map_err(|e| ChrootError::SetGidFailed(group.to_string(), e))?; + set_uid(uid).map_err(|e| ChrootError::SetUserFailed(user.to_string(), e))?; } } Ok(()) @@ -322,60 +461,3 @@ fn enter_chroot(root: &Path, skip_chdir: bool) -> UResult<()> { Err(ChrootError::CannotEnter(format!("{}", root.display()), Error::last_os_error()).into()) } } - -fn set_main_group(group: &str) -> UResult<()> { - if !group.is_empty() { - let group_id = match entries::grp2gid(group) { - Ok(g) => g, - _ => return Err(ChrootError::NoSuchGroup.into()), - }; - let err = unsafe { setgid(group_id) }; - if err != 0 { - return Err( - ChrootError::SetGidFailed(group_id.to_string(), Error::last_os_error()).into(), - ); - } - } - Ok(()) -} - -#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "openbsd"))] -fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { - unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) } -} - -#[cfg(any(target_os = "linux", target_os = "android"))] -fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { - unsafe { setgroups(groups.len() as libc::size_t, groups.as_ptr()) } -} - -fn set_groups_from_str(groups: &[String]) -> UResult<()> { - if !groups.is_empty() { - let mut groups_vec = vec![]; - for group in groups { - let gid = match entries::grp2gid(group) { - Ok(g) => g, - Err(_) => return Err(ChrootError::NoSuchGroup.into()), - }; - groups_vec.push(gid); - } - let err = set_groups(&groups_vec); - if err != 0 { - return Err(ChrootError::SetGroupsFailed(Error::last_os_error()).into()); - } - } - Ok(()) -} - -fn set_user(user: &str) -> UResult<()> { - if !user.is_empty() { - let user_id = entries::usr2uid(user).map_err(|_| ChrootError::NoSuchUser)?; - let err = unsafe { setuid(user_id as libc::uid_t) }; - if err != 0 { - return Err( - ChrootError::SetUserFailed(user.to_string(), Error::last_os_error()).into(), - ); - } - } - Ok(()) -} diff --git a/src/uu/chroot/src/error.rs b/src/uu/chroot/src/error.rs index 94a938e8bf0..b8109d41910 100644 --- a/src/uu/chroot/src/error.rs +++ b/src/uu/chroot/src/error.rs @@ -8,6 +8,7 @@ use std::fmt::Display; use std::io::Error; use uucore::display::Quotable; use uucore::error::UError; +use uucore::libc; /// Errors that can happen while executing chroot. #[derive(Debug)] @@ -33,6 +34,8 @@ pub enum ChrootError { /// The new root directory was not given. MissingNewRoot, + NoGroupSpecified(libc::uid_t), + /// Failed to find the specified user. NoSuchUser, @@ -83,6 +86,7 @@ impl Display for ChrootError { "Missing operand: NEWROOT\nTry '{} --help' for more information.", uucore::execution_phrase(), ), + Self::NoGroupSpecified(uid) => write!(f, "no group specified for unknown uid: {}", uid), Self::NoSuchUser => write!(f, "invalid user"), Self::NoSuchGroup => write!(f, "invalid group"), Self::NoSuchDirectory(s) => write!( From 35a044b532a94c5305b68bf3c037d45ea2d24a6d Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Fri, 3 Jan 2025 17:42:28 +0700 Subject: [PATCH 234/351] 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 d82f8d4d1c3..16138e67fa2 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 7c41f14a58e..7ae5b2bd023 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 6497c9224c6..61dd13e6cea 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 2ab6dac8f09f4bcea530e3d8ca46ec858ac176cf Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 3 Jan 2025 14:40:37 +0100 Subject: [PATCH 235/351] Bump tempfile from 3.14.0 to 3.15.0 --- Cargo.lock | 9 +++++---- Cargo.toml | 2 +- fuzz/Cargo.lock | 28 ++++++++++++++++++++-------- fuzz/Cargo.toml | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bfca23683ad..5c7258f7aec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1037,9 +1037,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -2220,12 +2220,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix 0.38.40", "windows-sys 0.59.0", diff --git a/Cargo.toml b/Cargo.toml index 4ae18be7452..ea51f6f99e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -330,7 +330,7 @@ self_cell = "1.0.4" selinux = "0.4.4" signal-hook = "0.3.17" smallvec = { version = "1.13.2", features = ["union"] } -tempfile = "3.10.1" +tempfile = "3.15.0" uutils_term_grid = "0.6" terminal_size = "0.4.0" textwrap = { version = "0.16.1", features = ["terminal_size"] } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index a2bae6dd306..e07a770f40a 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -511,9 +511,9 @@ checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -889,7 +889,7 @@ checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.89", ] [[package]] @@ -951,6 +951,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.89" @@ -964,12 +975,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1002,7 +1014,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.89", ] [[package]] @@ -1296,7 +1308,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -1318,7 +1330,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.65", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index cc2df2d4215..190c57a51a6 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4.7" libc = "0.2.153" -tempfile = "3.11.0" +tempfile = "3.15.0" rand = { version = "0.8.5", features = ["small_rng"] } similar = "2.5.0" From c6d3ec688c12e24959dc9ecb02738a5806567fe5 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 4 Jan 2025 10:35:47 +0100 Subject: [PATCH 236/351] kill: add `-n` hidden option for compatibility with bash GNU coreutils also implements it this way. --- src/uu/kill/src/kill.rs | 1 + tests/by-util/test_kill.rs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index b5bcc0d7c0c..4067f51aacb 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -122,6 +122,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::SIGNAL) .short('s') + .short_alias('n') // For bash compatibility, like in GNU coreutils .long(options::SIGNAL) .value_name("signal") .help("Sends given signal instead of SIGTERM"), diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index e07def51379..6aa148241c2 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -296,3 +296,14 @@ fn test_kill_with_signal_exit_new_form() { .arg(format!("{}", target.pid())) .succeeds(); } + +#[test] +fn test_kill_with_signal_number_hidden_compatibility_option() { + let mut target = Target::new(); + new_ucmd!() + .arg("-n") + .arg("9") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(9)); +} From b5fc19e60e694032c45a09b163aff92d0aeff46f Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Sat, 4 Jan 2025 11:40:21 +0100 Subject: [PATCH 237/351] kill: make `-s` conflict with `-l` and `-t` --- src/uu/kill/src/kill.rs | 3 ++- tests/by-util/test_kill.rs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 4067f51aacb..1dc3526538d 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -125,7 +125,8 @@ pub fn uu_app() -> Command { .short_alias('n') // For bash compatibility, like in GNU coreutils .long(options::SIGNAL) .value_name("signal") - .help("Sends given signal instead of SIGTERM"), + .help("Sends given signal instead of SIGTERM") + .conflicts_with_all([options::LIST, options::TABLE]), ) .arg( Arg::new(options::PIDS_OR_SIGNALS) diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 6aa148241c2..ba2b963518d 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -307,3 +307,25 @@ fn test_kill_with_signal_number_hidden_compatibility_option() { .succeeds(); assert_eq!(target.wait_for_signal(), Some(9)); } + +#[test] +fn test_kill_with_signal_and_list() { + let target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("EXIT") + .arg(format!("{}", target.pid())) + .arg("-l") + .fails(); +} + +#[test] +fn test_kill_with_signal_and_table() { + let target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("EXIT") + .arg(format!("{}", target.pid())) + .arg("-t") + .fails(); +} From da2a4fedf5a79ab665e028d6b505f7e57225ef86 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Dec 2024 10:08:30 +0100 Subject: [PATCH 238/351] cp: when copying a read only file, make sure that the xattrs can be set properly on the destination Should fix tests/misc/xattr.sh --- .../cspell.dictionaries/jargon.wordlist.txt | 1 + src/uu/cp/src/cp.rs | 38 ++++++++- tests/by-util/test_cp.rs | 85 ++++++++++++++++++- 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index edb9cd8f371..dc9e372d8c4 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -113,6 +113,7 @@ semiprime semiprimes setcap setfacl +setfattr shortcode shortcodes siginfo diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b7469404757..41686f5bf4f 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1518,6 +1518,42 @@ fn handle_preserve CopyResult<()>>(p: &Preserve, f: F) -> CopyResult< Ok(()) } +/// Copies extended attributes (xattrs) from `source` to `dest`, ensuring that `dest` is temporarily +/// user-writable if needed and restoring its original permissions afterward. This avoids “Operation +/// not permitted” errors on read-only files. Returns an error if permission or metadata operations fail, +/// or if xattr copying fails. +#[cfg(all(unix, not(target_os = "android")))] +fn copy_extended_attrs(source: &Path, dest: &Path) -> CopyResult<()> { + let metadata = fs::symlink_metadata(dest)?; + + // Check if the destination file is currently read-only for the user. + let mut perms = metadata.permissions(); + let was_readonly = perms.readonly(); + + // Temporarily grant user write if it was read-only. + if was_readonly { + #[allow(clippy::permissions_set_readonly_false)] + perms.set_readonly(false); + fs::set_permissions(dest, perms)?; + } + + // Perform the xattr copy and capture any potential error, + // so we can restore permissions before returning. + let copy_xattrs_result = copy_xattrs(source, dest); + + // Restore read-only if we changed it. + if was_readonly { + let mut revert_perms = fs::symlink_metadata(dest)?.permissions(); + revert_perms.set_readonly(true); + fs::set_permissions(dest, revert_perms)?; + } + + // If copying xattrs failed, propagate that error now. + copy_xattrs_result?; + + Ok(()) +} + /// Copy the specified attributes from one path to another. pub(crate) fn copy_attributes( source: &Path, @@ -1607,7 +1643,7 @@ pub(crate) fn copy_attributes( handle_preserve(&attributes.xattr, || -> CopyResult<()> { #[cfg(all(unix, not(target_os = "android")))] { - copy_xattrs(source, dest)?; + copy_extended_attrs(source, dest)?; } #[cfg(not(all(unix, not(target_os = "android"))))] { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 7a0889b0fa6..19fbc5ca717 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.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 (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs -// spell-checker:ignore bdfl hlsl IRWXO IRWXG +// spell-checker:ignore bdfl hlsl IRWXO IRWXG getfattr use crate::common::util::TestScenario; #[cfg(not(windows))] use std::fs::set_permissions; @@ -5920,3 +5920,86 @@ fn test_cp_no_file() { .code_is(1) .stderr_contains("error: the following required arguments were not provided:"); } + +#[test] +#[cfg(all( + unix, + not(any(target_os = "android", target_os = "macos", target_os = "openbsd")) +))] +fn test_cp_preserve_xattr_readonly_source() { + use crate::common::util::compare_xattrs; + use std::process::Command; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let source_file = "a"; + let dest_file = "e"; + + at.touch(source_file); + + let xattr_key = "user.test"; + match Command::new("setfattr") + .args([ + "-n", + xattr_key, + "-v", + "value", + &at.plus_as_string(source_file), + ]) + .status() + .map(|status| status.code()) + { + Ok(Some(0)) => {} + Ok(_) => { + println!("test skipped: setfattr failed"); + return; + } + Err(e) => { + println!("test skipped: setfattr failed with {e}"); + return; + } + } + + let getfattr_output = Command::new("getfattr") + .args([&at.plus_as_string(source_file)]) + .output() + .expect("Failed to run `getfattr` on the destination file"); + + assert!( + getfattr_output.status.success(), + "getfattr did not run successfully: {}", + String::from_utf8_lossy(&getfattr_output.stderr) + ); + + let stdout = String::from_utf8_lossy(&getfattr_output.stdout); + assert!( + stdout.contains(xattr_key), + "Expected '{}' not found in getfattr output:\n{}", + xattr_key, + stdout + ); + + at.set_readonly(source_file); + assert!(scene + .fixtures + .metadata(source_file) + .permissions() + .readonly()); + + scene + .ucmd() + .args(&[ + "--preserve=xattr", + &at.plus_as_string(source_file), + &at.plus_as_string(dest_file), + ]) + .succeeds() + .no_output(); + + assert!(scene.fixtures.metadata(dest_file).permissions().readonly()); + assert!( + compare_xattrs(&at.plus(source_file), &at.plus(dest_file)), + "Extended attributes were not preserved" + ); +} From 5a3afb28cab79de91f682856c9be08c2ccd69791 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 14:53:21 +0100 Subject: [PATCH 239/351] CI: also install attr in one of the CI job to test a cp feature --- .github/workflows/CICD.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index a406b094298..5440ad5d4e3 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -199,6 +199,11 @@ jobs: # * ensure '.clippy.toml' MSRV configuration setting is equal to ${{ env.RUST_MIN_SRV }} CLIPPY_MSRV=$(grep -P "(?i)^\s*msrv\s*=\s*" .clippy.toml | grep -oP "\d+([.]\d+)+") if [ "${CLIPPY_MSRV}" != "${{ env.RUST_MIN_SRV }}" ]; then { echo "::error file=.clippy.toml::Incorrect MSRV configuration for clippy (found '${CLIPPY_MSRV}'; should be '${{ env.RUST_MIN_SRV }}'); update '.clippy.toml' with 'msrv = \"${{ env.RUST_MIN_SRV }}\"'" ; exit 1 ; } ; fi + - name: Install/setup prerequisites + shell: bash + run: | + # Install a package for one of the tests + sudo apt-get -y update ; sudo apt-get -y install attr - name: Info shell: bash run: | From b15bc8440cab81c0f4574488c3e2c12e5450b1ac Mon Sep 17 00:00:00 2001 From: Tommaso Fellegara <96147629+Felle33@users.noreply.github.com> Date: Sat, 4 Jan 2025 21:53:10 +0100 Subject: [PATCH 240/351] cksum: fix error message when the flags length and an algorithm different from blake2b are present (#7071) test length and blake2b algorithm before testing supported algorithms --- src/uu/cksum/src/cksum.rs | 8 ++++---- tests/by-util/test_cksum.rs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index e96d2de6fcd..64bbf65a497 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -276,10 +276,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }; - if ["bsd", "crc", "sysv"].contains(&algo_name) && check { - return Err(ChecksumError::AlgorithmNotSupportedWithCheck.into()); - } - let input_length = matches.get_one::(options::LENGTH); let length = match input_length { @@ -293,6 +289,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { None => None, }; + if ["bsd", "crc", "sysv"].contains(&algo_name) && check { + return Err(ChecksumError::AlgorithmNotSupportedWithCheck.into()); + } + if check { let text_flag = matches.get_flag(options::TEXT); let binary_flag = matches.get_flag(options::BINARY); diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 2efc78b96f3..c213c4a82d5 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -352,6 +352,18 @@ fn test_length_not_supported() { .no_stdout() .stderr_contains("--length is only supported with --algorithm=blake2b") .code_is(1); + + new_ucmd!() + .arg("-l") + .arg("158") + .arg("-c") + .arg("-a") + .arg("crc") + .arg("/tmp/xxx") + .fails() + .no_stdout() + .stderr_contains("--length is only supported with --algorithm=blake2b") + .code_is(1); } #[test] From 4f43e7f2ddf3b21e7a9e1c263a10c5b805deca20 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 4 Jan 2025 17:58:29 -0500 Subject: [PATCH 241/351] mkfifo: add missing "mode" feature for uucore dep --- src/uu/mkfifo/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 68f16a1f63f..93ebed94ada 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -19,7 +19,7 @@ path = "src/mkfifo.rs" [dependencies] clap = { workspace = true } libc = { workspace = true } -uucore = { workspace = true, features = ["fs"] } +uucore = { workspace = true, features = ["fs", "mode"] } [[bin]] name = "mkfifo" From 5fe6706c510945524942813159478527f2323568 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Sat, 4 Jan 2025 11:30:57 +0700 Subject: [PATCH 242/351] 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 bfca23683ad..e40171f46ff 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 5e2b977b8a3..1ccf3b9851b 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 04751fa71e2..70f9653f2fd 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 243/351] 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 1d4b1596337..6d7ecffb2df 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 c9ae4819f50cf46d557ee4b69df48fdf4ca17981 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 4 Jan 2025 22:49:22 -0500 Subject: [PATCH 244/351] tsort: use TsortError for errors in tsort --- src/uu/tsort/src/tsort.rs | 68 ++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 4a61f271033..4b7e6b13037 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -8,10 +8,11 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt::Write; use std::fs::File; use std::io::{stdin, BufReader, Read}; +use std::fmt::Display; use std::path::Path; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::error::{UError, UResult}; +use uucore::{format_usage, help_about, help_usage, show}; const ABOUT: &str = help_about!("tsort.md"); const USAGE: &str = help_usage!("tsort.md"); @@ -20,6 +21,43 @@ mod options { pub const FILE: &str = "file"; } +#[derive(Debug)] +enum TsortError { + /// The input file is actually a directory. + IsDir(String), + + /// The number of tokens in the input data is odd. + /// + /// The list of edges must be even because each edge has two + /// components: a source node and a target node. + NumTokensOdd(String), + + /// The graph contains a cycle. + Loop(String), + + /// A particular node in a cycle. (This is mainly used for printing.) + LoopNode(String), +} + +impl std::error::Error for TsortError {} + +impl UError for TsortError {} + +impl Display for TsortError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::IsDir(d) => write!(f, "{d}: read error: Is a directory"), + Self::NumTokensOdd(i) => write!( + f, + "{}: input contains an odd number of tokens", + i.maybe_quote() + ), + Self::Loop(i) => write!(f, "{i}: input contains a loop:"), + Self::LoopNode(v) => write!(f, "{v}"), + } + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; @@ -36,10 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { let path = Path::new(&input); if path.is_dir() { - return Err(USimpleError::new( - 1, - format!("{input}: read error: Is a directory"), - )); + return Err(TsortError::IsDir(input.to_string()).into()); } file_buf = File::open(path).map_err_context(|| input.to_string())?; &mut file_buf as &mut dyn Read @@ -57,32 +92,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for ab in tokens.chunks(2) { match ab.len() { 2 => g.add_edge(ab[0], ab[1]), - _ => { - return Err(USimpleError::new( - 1, - format!( - "{}: input contains an odd number of tokens", - input.maybe_quote() - ), - )) - } + _ => return Err(TsortError::NumTokensOdd(input.to_string()).into()), } } } match g.run_tsort() { Err(cycle) => { - let mut error_message = format!( - "{}: {}: input contains a loop:\n", - uucore::util_name(), - input - ); + show!(TsortError::Loop(input.to_string())); for node in &cycle { - writeln!(error_message, "{}: {}", uucore::util_name(), node).unwrap(); + show!(TsortError::LoopNode(node.to_string())); } - eprint!("{}", error_message); println!("{}", cycle.join("\n")); - Err(USimpleError::new(1, "")) + Ok(()) } Ok(ordering) => { println!("{}", ordering.join("\n")); From 5cf5acb4a28e6b4c68426cefdbf34da74f31b7bf Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 4 Jan 2025 22:51:08 -0500 Subject: [PATCH 245/351] tsort: use std::io::read_to_string to load data --- src/uu/tsort/src/tsort.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 4b7e6b13037..98d3c6f7876 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -5,9 +5,6 @@ //spell-checker:ignore TAOCP use clap::{crate_version, Arg, Command}; use std::collections::{HashMap, HashSet, VecDeque}; -use std::fmt::Write; -use std::fs::File; -use std::io::{stdin, BufReader, Read}; use std::fmt::Display; use std::path::Path; use uucore::display::Quotable; @@ -66,25 +63,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .get_one::(options::FILE) .expect("Value is required by clap"); - let mut stdin_buf; - let mut file_buf; - let mut reader = BufReader::new(if input == "-" { - stdin_buf = stdin(); - &mut stdin_buf as &mut dyn Read + let data = if input == "-" { + let stdin = std::io::stdin(); + std::io::read_to_string(stdin)? } else { let path = Path::new(&input); if path.is_dir() { return Err(TsortError::IsDir(input.to_string()).into()); } - file_buf = File::open(path).map_err_context(|| input.to_string())?; - &mut file_buf as &mut dyn Read - }); + std::fs::read_to_string(path)? + }; - let mut input_buffer = String::new(); - reader.read_to_string(&mut input_buffer)?; let mut g = Graph::default(); - for line in input_buffer.lines() { + for line in data.lines() { let tokens: Vec<_> = line.split_whitespace().collect(); if tokens.is_empty() { break; From f703e88e9f82dc5d5ecfca89eb2dc4cc66bdd9e0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 4 Jan 2025 22:51:36 -0500 Subject: [PATCH 246/351] tsort: split edge data on any whitespace chars Make `tsort` split on any whitespace character instead of just whitespace within a single line. This allows edge information to be split with the source node on one line and the target node on another. For example, after this commit $ printf "a\nb\n" | tsort a b whereas before this would print an error message. Closes #7077 --- src/uu/tsort/src/tsort.rs | 16 +++++----------- tests/by-util/test_tsort.rs | 8 ++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 98d3c6f7876..ea5084a34d2 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -74,18 +74,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { std::fs::read_to_string(path)? }; + // Create the directed graph from pairs of tokens in the input data. let mut g = Graph::default(); - - for line in data.lines() { - let tokens: Vec<_> = line.split_whitespace().collect(); - if tokens.is_empty() { - break; - } - for ab in tokens.chunks(2) { - match ab.len() { - 2 => g.add_edge(ab[0], ab[1]), - _ => return Err(TsortError::NumTokensOdd(input.to_string()).into()), - } + for ab in data.split_whitespace().collect::>().chunks(2) { + match ab { + [a, b] => g.add_edge(a, b), + _ => return Err(TsortError::NumTokensOdd(input.to_string()).into()), } } diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index 49809e0dfc7..f86add2947f 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -75,3 +75,11 @@ fn test_error_on_dir() { .fails() .stderr_contains("tsort: tsort_test_dir: read error: Is a directory"); } + +#[test] +fn test_split_on_any_whitespace() { + new_ucmd!() + .pipe_in("a\nb\n") + .succeeds() + .stdout_only("a\nb\n"); +} From dd17f9a61cdd3507c50e765f89b83fb90cc9cc57 Mon Sep 17 00:00:00 2001 From: tommi Date: Sun, 5 Jan 2025 10:28:08 +0100 Subject: [PATCH 247/351] cksum: check ourself the flags --check and --tag and output the correct error --- src/uu/cksum/src/cksum.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 64bbf65a497..e384e51c140 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -301,8 +301,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let warn = matches.get_flag(options::WARN); let ignore_missing = matches.get_flag(options::IGNORE_MISSING); let quiet = matches.get_flag(options::QUIET); + let tag = matches.get_flag(options::TAG); - if binary_flag || text_flag { + if tag || binary_flag || text_flag { return Err(ChecksumError::BinaryTextConflict.into()); } // Determine the appropriate algorithm option to pass @@ -426,8 +427,7 @@ pub fn uu_app() -> Command { .short('c') .long(options::CHECK) .help("read hashsums from the FILEs and check them") - .action(ArgAction::SetTrue) - .conflicts_with("tag"), + .action(ArgAction::SetTrue), ) .arg( Arg::new(options::BASE64) From 61290fd6e878f97257cdc2c2ff217284acc1b087 Mon Sep 17 00:00:00 2001 From: tommi Date: Sun, 5 Jan 2025 10:32:08 +0100 Subject: [PATCH 248/351] tests/cksum: test added to check that the --check and --tag flags are meaningless --- tests/by-util/test_cksum.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index c213c4a82d5..b7c11320e11 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -748,6 +748,19 @@ fn test_conflicting_options() { "cksum: the --binary and --text options are meaningless when verifying checksums", ) .code_is(1); + + scene + .ucmd() + .arg("--tag") + .arg("-c") + .arg("-a") + .arg("md5") + .fails() + .no_stdout() + .stderr_contains( + "cksum: the --binary and --text options are meaningless when verifying checksums", + ) + .code_is(1); } #[test] From 5dfeb809a4e5e4f4f9be63f0ca643c7dcdf95a2b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 5 Jan 2025 14:50:14 +0100 Subject: [PATCH 249/351] cksum: remove some unnecessary type info --- src/uu/cksum/src/cksum.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index e384e51c140..b9f74133814 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -244,16 +244,16 @@ fn had_reset(args: &[OsString]) -> bool { * and "easier" to understand */ fn handle_tag_text_binary_flags(matches: &clap::ArgMatches) -> UResult<(bool, bool)> { - let untagged: bool = matches.get_flag(options::UNTAGGED); - let tag: bool = matches.get_flag(options::TAG); - let tag: bool = tag || !untagged; + let untagged = matches.get_flag(options::UNTAGGED); + let tag = matches.get_flag(options::TAG); + let tag = tag || !untagged; - let binary_flag: bool = matches.get_flag(options::BINARY); + let binary_flag = matches.get_flag(options::BINARY); let args: Vec = std::env::args_os().collect(); let had_reset = had_reset(&args); - let asterisk: bool = prompt_asterisk(tag, binary_flag, had_reset); + let asterisk = prompt_asterisk(tag, binary_flag, had_reset); Ok((tag, asterisk)) } From de66422d402b121f3e4a9bce2f97397fa325437e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 5 Jan 2025 18:52:49 +0000 Subject: [PATCH 250/351] chore(deps): update rust crate xattr to v1.4.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c7258f7aec..f9d537f2a3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3891,9 +3891,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", "linux-raw-sys 0.4.14", From 49ae68d443f300d44b7fb83af7373c08b5fa3c8f Mon Sep 17 00:00:00 2001 From: Mark <96270320+rm-dr@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:12:12 -0800 Subject: [PATCH 251/351] cp: copy attributes after making subdir (#6884) --- src/uu/cp/src/copydir.rs | 136 ++++++++++++++++++++++++++++++++++----- src/uu/cp/src/cp.rs | 2 +- tests/by-util/test_cp.rs | 25 ++++++- 3 files changed, 146 insertions(+), 17 deletions(-) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index 3bfc5524913..bd81a39f5da 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -79,6 +79,12 @@ fn get_local_to_root_parent( } } +/// Given an iterator, return all its items except the last. +fn skip_last(mut iter: impl Iterator) -> impl Iterator { + let last = iter.next(); + iter.scan(last, |state, item| std::mem::replace(state, Some(item))) +} + /// Paths that are invariant throughout the traversal when copying a directory. struct Context<'a> { /// The current working directory at the time of starting the traversal. @@ -162,17 +168,18 @@ struct Entry { } impl Entry { - fn new( + fn new>( context: &Context, - direntry: &DirEntry, + source: A, no_target_dir: bool, ) -> Result { - let source_relative = direntry.path().to_path_buf(); + let source = source.as_ref(); + let source_relative = source.to_path_buf(); let source_absolute = context.current_dir.join(&source_relative); let mut descendant = get_local_to_root_parent(&source_absolute, context.root_parent.as_deref())?; if no_target_dir { - let source_is_dir = direntry.path().is_dir(); + let source_is_dir = source.is_dir(); if path_ends_with_terminator(context.target) && source_is_dir { if let Err(e) = std::fs::create_dir_all(context.target) { eprintln!("Failed to create directory: {e}"); @@ -213,6 +220,7 @@ where // `path.ends_with(".")` does not seem to work path.as_ref().display().to_string().ends_with("/.") } + #[allow(clippy::too_many_arguments)] /// Copy a single entry during a directory traversal. fn copy_direntry( @@ -246,7 +254,7 @@ fn copy_direntry( if target_is_file { return Err("cannot overwrite non-directory with directory".into()); } else { - build_dir(options, &local_to_target, false)?; + build_dir(&local_to_target, false, options, Some(&source_absolute))?; if options.verbose { println!("{}", context_for(&source_relative, &local_to_target)); } @@ -371,7 +379,7 @@ pub(crate) fn copy_directory( let tmp = if options.parents { if let Some(parent) = root.parent() { let new_target = target.join(parent); - build_dir(options, &new_target, true)?; + build_dir(&new_target, true, options, None)?; if options.verbose { // For example, if copying file `a/b/c` and its parents // to directory `d/`, then print @@ -403,6 +411,9 @@ pub(crate) fn copy_directory( Err(e) => return Err(format!("failed to get current directory {e}").into()), }; + // The directory we were in during the previous iteration + let mut last_iter: Option = None; + // Traverse the contents of the directory, copying each one. for direntry_result in WalkDir::new(root) .same_file_system(options.one_file_system) @@ -410,7 +421,8 @@ pub(crate) fn copy_directory( { match direntry_result { Ok(direntry) => { - let entry = Entry::new(&context, &direntry, options.no_target_dir)?; + let entry = Entry::new(&context, direntry.path(), options.no_target_dir)?; + copy_direntry( progress_bar, entry, @@ -420,23 +432,93 @@ pub(crate) fn copy_directory( copied_destinations, copied_files, )?; + + // We omit certain permissions when creating directories + // to prevent other users from accessing them before they're done. + // We thus need to fix the permissions of each directory we copy + // once it's contents are ready. + // This "fixup" is implemented here in a memory-efficient manner. + // + // We detect iterations where we "walk up" the directory tree, + // and fix permissions on all the directories we exited. + // (Note that there can be more than one! We might step out of + // `./a/b/c` into `./a/`, in which case we'll need to fix the + // permissions of both `./a/b/c` and `./a/b`, in that order.) + if direntry.file_type().is_dir() { + // If true, last_iter is not a parent of this iter. + // The means we just exited a directory. + let went_up = if let Some(last_iter) = &last_iter { + last_iter.path().strip_prefix(direntry.path()).is_ok() + } else { + false + }; + + if went_up { + // Compute the "difference" between `last_iter` and `direntry`. + // For example, if... + // - last_iter = `a/b/c/d` + // - direntry = `a/b` + // then diff = `c/d` + // + // All the unwraps() here are unreachable. + let last_iter = last_iter.as_ref().unwrap(); + let diff = last_iter.path().strip_prefix(direntry.path()).unwrap(); + + // Fix permissions for every entry in `diff`, inside-out. + // We skip the last directory (which will be `.`) because + // its permissions will be fixed when we walk _out_ of it. + // (at this point, we might not be done copying `.`!) + for p in skip_last(diff.ancestors()) { + let src = direntry.path().join(p); + let entry = Entry::new(&context, &src, options.no_target_dir)?; + + copy_attributes( + &entry.source_absolute, + &entry.local_to_target, + &options.attributes, + )?; + } + } + + last_iter = Some(direntry); + } } + // Print an error message, but continue traversing the directory. Err(e) => show_error!("{}", e), } } - // Copy the attributes from the root directory to the target directory. + // Handle final directory permission fixes. + // This is almost the same as the permission-fixing code above, + // with minor differences (commented) + if let Some(last_iter) = last_iter { + let diff = last_iter.path().strip_prefix(root).unwrap(); + + // Do _not_ skip `.` this time, since we know we're done. + // This is where we fix the permissions of the top-level + // directory we just copied. + for p in diff.ancestors() { + let src = root.join(p); + let entry = Entry::new(&context, &src, options.no_target_dir)?; + + copy_attributes( + &entry.source_absolute, + &entry.local_to_target, + &options.attributes, + )?; + } + } + + // Also fix permissions for parent directories, + // if we were asked to create them. if options.parents { let dest = target.join(root.file_name().unwrap()); - copy_attributes(root, dest.as_path(), &options.attributes)?; for (x, y) in aligned_ancestors(root, dest.as_path()) { if let Ok(src) = canonicalize(x, MissingHandling::Normal, ResolveMode::Physical) { copy_attributes(&src, y, &options.attributes)?; } } - } else { - copy_attributes(root, target, &options.attributes)?; } Ok(()) @@ -469,20 +551,32 @@ pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result { /// Builds a directory at the specified path with the given options. /// /// # Notes -/// - It excludes certain permissions if ownership or special mode bits could -/// potentially change. +/// - If `copy_attributes_from` is `Some`, the new directory's attributes will be +/// copied from the provided file. Otherwise, the new directory will have the default +/// attributes for the current user. +/// - This method excludes certain permissions if ownership or special mode bits could +/// potentially change. (See `test_dir_perm_race_with_preserve_mode_and_ownership``) /// - The `recursive` flag determines whether parent directories should be created /// if they do not already exist. // we need to allow unused_variable since `options` might be unused in non unix systems #[allow(unused_variables)] -fn build_dir(options: &Options, path: &PathBuf, recursive: bool) -> CopyResult<()> { +fn build_dir( + path: &PathBuf, + recursive: bool, + options: &Options, + copy_attributes_from: Option<&Path>, +) -> CopyResult<()> { let mut builder = fs::DirBuilder::new(); builder.recursive(recursive); + // To prevent unauthorized access before the folder is ready, // exclude certain permissions if ownership or special mode bits // could potentially change. #[cfg(unix)] { + use crate::Preserve; + use std::os::unix::fs::PermissionsExt; + // we need to allow trivial casts here because some systems like linux have u32 constants in // in libc while others don't. #[allow(clippy::unnecessary_cast)] @@ -494,10 +588,22 @@ fn build_dir(options: &Options, path: &PathBuf, recursive: bool) -> CopyResult<( } else { 0 } as u32; - excluded_perms |= uucore::mode::get_umask(); + + let umask = if copy_attributes_from.is_some() + && matches!(options.attributes.mode, Preserve::Yes { .. }) + { + !fs::symlink_metadata(copy_attributes_from.unwrap())? + .permissions() + .mode() + } else { + uucore::mode::get_umask() + }; + + excluded_perms |= umask; let mode = !excluded_perms & 0o777; //use only the last three octet bits std::os::unix::fs::DirBuilderExt::mode(&mut builder, mode); } + builder.create(path)?; Ok(()) } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 41686f5bf4f..c1ebc20bf80 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -174,7 +174,7 @@ pub enum CopyMode { /// For full compatibility with GNU, these options should also combine. We /// currently only do a best effort imitation of that behavior, because it is /// difficult to achieve in clap, especially with `--no-preserve`. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub struct Attributes { #[cfg(unix)] pub ownership: Preserve, diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 19fbc5ca717..fc56dc3160e 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3365,6 +3365,29 @@ fn test_copy_dir_preserve_permissions() { assert_metadata_eq!(metadata1, metadata2); } +/// cp should preserve attributes of subdirectories when copying recursively. +#[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] +#[test] +fn test_copy_dir_preserve_subdir_permissions() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("a1"); + at.mkdir("a1/a2"); + // Use different permissions for a better test + at.set_mode("a1/a2", 0o0555); + at.set_mode("a1", 0o0777); + + ucmd.args(&["-p", "-r", "a1", "b1"]) + .succeeds() + .no_stderr() + .no_stdout(); + + // Make sure everything is preserved + assert!(at.dir_exists("b1")); + assert!(at.dir_exists("b1/a2")); + assert_metadata_eq!(at.metadata("a1"), at.metadata("b1")); + assert_metadata_eq!(at.metadata("a1/a2"), at.metadata("b1/a2")); +} + /// Test for preserving permissions when copying a directory, even in /// the face of an inaccessible file in that directory. #[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))] @@ -5616,7 +5639,7 @@ mod link_deref { // which could be problematic if we aim to preserve ownership or mode. For example, when // copying a directory, the destination directory could temporarily be setgid on some filesystems. // This temporary setgid status could grant access to other users who share the same group -// ownership as the newly created directory.To mitigate this issue, when creating a directory we +// ownership as the newly created directory. To mitigate this issue, when creating a directory we // disable these excessive permissions. #[test] #[cfg(unix)] From 509b755b3b19e753cc7bf1d577dec4c7033c8b06 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Sat, 4 Jan 2025 11:33:30 +0700 Subject: [PATCH 252/351] 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 70f9653f2fd..52d52f13bba 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 253/351] 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 c1ebc20bf80..b87898c783e 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 fc56dc3160e..2132b93636f 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 254/351] 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 097e4f2e980..228b5a0c123 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 dd6b412a429..d4430d05655 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 77fadc6897e66f7f8722e3b0684c39ae94957b8c Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Fri, 3 Jan 2025 19:04:06 +0700 Subject: [PATCH 255/351] cp: implement copying content from streams Previously, the only stream that cp supports copying are FIFOs. Any other files will be handled using std::io::copy which does not handle special files like block devices or character specials. This commit fixes the previously missing support by using std::io::copy or splice syscall for Linux to handle those cases. --- src/uu/cp/Cargo.toml | 1 + src/uu/cp/src/cp.rs | 60 +++++++++++++++++++-------- src/uu/cp/src/platform/linux.rs | 44 +++++++++++--------- src/uu/cp/src/platform/macos.rs | 26 +++++++++--- src/uu/cp/src/platform/mod.rs | 22 +++++++++- src/uu/cp/src/platform/other_unix.rs | 62 ++++++++++++++++++++++++++++ 6 files changed, 172 insertions(+), 43 deletions(-) create mode 100644 src/uu/cp/src/platform/other_unix.rs diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 3912f3308a5..c3089a339be 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -28,6 +28,7 @@ quick-error = { workspace = true } selinux = { workspace = true, optional = true } uucore = { workspace = true, features = [ "backup-control", + "buf-copy", "entries", "fs", "fsxattr", diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b87898c783e..68e289bb746 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -10,7 +10,7 @@ use std::collections::{HashMap, HashSet}; #[cfg(not(windows))] use std::ffi::CString; use std::ffi::OsString; -use std::fs::{self, File, Metadata, OpenOptions, Permissions}; +use std::fs::{self, Metadata, OpenOptions, Permissions}; use std::io; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; @@ -1963,6 +1963,7 @@ fn print_paths(parents: bool, source: &Path, dest: &Path) { /// /// * `Ok(())` - The file was copied successfully. /// * `Err(CopyError)` - An error occurred while copying the file. +#[allow(clippy::too_many_arguments)] fn handle_copy_mode( source: &Path, dest: &Path, @@ -1971,15 +1972,10 @@ fn handle_copy_mode( source_metadata: &Metadata, symlinked_files: &mut HashSet, source_in_command_line: bool, + source_is_fifo: bool, + #[cfg(unix)] source_is_stream: bool, ) -> CopyResult<()> { - let source_file_type = source_metadata.file_type(); - - let source_is_symlink = source_file_type.is_symlink(); - - #[cfg(unix)] - let source_is_fifo = source_file_type.is_fifo(); - #[cfg(not(unix))] - let source_is_fifo = false; + let source_is_symlink = source_metadata.is_symlink(); match options.copy_mode { CopyMode::Link => { @@ -2016,6 +2012,8 @@ fn handle_copy_mode( source_is_symlink, source_is_fifo, symlinked_files, + #[cfg(unix)] + source_is_stream, )?; } CopyMode::SymLink => { @@ -2036,6 +2034,8 @@ fn handle_copy_mode( source_is_symlink, source_is_fifo, symlinked_files, + #[cfg(unix)] + source_is_stream, )?; } update_control::UpdateMode::ReplaceNone => { @@ -2066,6 +2066,8 @@ fn handle_copy_mode( source_is_symlink, source_is_fifo, symlinked_files, + #[cfg(unix)] + source_is_stream, )?; } } @@ -2079,6 +2081,8 @@ fn handle_copy_mode( source_is_symlink, source_is_fifo, symlinked_files, + #[cfg(unix)] + source_is_stream, )?; } } @@ -2305,6 +2309,18 @@ fn copy_file( let dest_permissions = calculate_dest_permissions(dest, &source_metadata, options, context)?; + #[cfg(unix)] + let source_is_fifo = source_metadata.file_type().is_fifo(); + #[cfg(not(unix))] + let source_is_fifo = false; + + #[cfg(unix)] + let source_is_stream = source_is_fifo + || source_metadata.file_type().is_char_device() + || source_metadata.file_type().is_block_device(); + #[cfg(not(unix))] + let source_is_stream = false; + handle_copy_mode( source, dest, @@ -2313,6 +2329,9 @@ fn copy_file( &source_metadata, symlinked_files, source_in_command_line, + source_is_fifo, + #[cfg(unix)] + source_is_stream, )?; // TODO: implement something similar to gnu's lchown @@ -2328,8 +2347,16 @@ fn copy_file( if options.dereference(source_in_command_line) { if let Ok(src) = canonicalize(source, MissingHandling::Normal, ResolveMode::Physical) { - copy_attributes(&src, dest, &options.attributes)?; + if src.exists() { + copy_attributes(&src, dest, &options.attributes)?; + } } + } else if source_is_stream && source.exists() { + // Some stream files may not exist after we have copied it, + // like anonymous pipes. Thus, we can't really copy its + // attributes. However, this is already handled in the stream + // copy function (see `copy_stream` under platform/linux.rs). + copy_attributes(source, dest, &options.attributes)?; } else { copy_attributes(source, dest, &options.attributes)?; } @@ -2393,6 +2420,7 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. +#[allow(clippy::too_many_arguments)] fn copy_helper( source: &Path, dest: &Path, @@ -2401,6 +2429,7 @@ fn copy_helper( source_is_symlink: bool, source_is_fifo: bool, symlinked_files: &mut HashSet, + #[cfg(unix)] source_is_stream: bool, ) -> CopyResult<()> { if options.parents { let parent = dest.parent().unwrap_or(dest); @@ -2411,12 +2440,7 @@ fn copy_helper( return Err(Error::NotADirectory(dest.to_path_buf())); } - if source.as_os_str() == "/dev/null" { - /* workaround a limitation of fs::copy - * https://github.com/rust-lang/rust/issues/79390 - */ - File::create(dest).context(dest.display().to_string())?; - } else if source_is_fifo && options.recursive && !options.copy_contents { + if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] copy_fifo(dest, options.overwrite, options.debug)?; } else if source_is_symlink { @@ -2428,8 +2452,10 @@ fn copy_helper( options.reflink_mode, options.sparse_mode, context, - #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] + #[cfg(unix)] source_is_fifo, + #[cfg(unix)] + source_is_stream, )?; if !options.attributes_only && options.debug { diff --git a/src/uu/cp/src/platform/linux.rs b/src/uu/cp/src/platform/linux.rs index 949bd5e03c7..0ca39a75ef2 100644 --- a/src/uu/cp/src/platform/linux.rs +++ b/src/uu/cp/src/platform/linux.rs @@ -12,6 +12,7 @@ use std::os::unix::fs::MetadataExt; use std::os::unix::fs::{FileTypeExt, OpenOptionsExt}; use std::os::unix::io::AsRawFd; use std::path::Path; +use uucore::buf_copy; use quick_error::ResultExt; @@ -220,8 +221,9 @@ fn check_dest_is_fifo(dest: &Path) -> bool { } } -/// Copy the contents of the given source FIFO to the given file. -fn copy_fifo_contents

(source: P, dest: P) -> std::io::Result +/// Copy the contents of a stream from `source` to `dest`. The `if_fifo` argument is used to +/// determine if we need to modify the file's attributes before and after copying. +fn copy_stream

(source: P, dest: P, is_fifo: bool) -> std::io::Result where P: AsRef, { @@ -250,8 +252,14 @@ where .write(true) .mode(mode) .open(&dest)?; - let num_bytes_copied = std::io::copy(&mut src_file, &mut dst_file)?; - dst_file.set_permissions(src_file.metadata()?.permissions())?; + + let num_bytes_copied = buf_copy::copy_stream(&mut src_file, &mut dst_file) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other))?; + + if is_fifo { + dst_file.set_permissions(src_file.metadata()?.permissions())?; + } + Ok(num_bytes_copied) } @@ -268,6 +276,7 @@ pub(crate) fn copy_on_write( sparse_mode: SparseMode, context: &str, source_is_fifo: bool, + source_is_stream: bool, ) -> CopyResult { let mut copy_debug = CopyDebug { offload: OffloadReflinkDebug::Unknown, @@ -279,10 +288,9 @@ pub(crate) fn copy_on_write( copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for SparseMode::Always copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream(source, dest, source_is_fifo).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_never_sparse_always(source, dest); @@ -300,10 +308,9 @@ pub(crate) fn copy_on_write( (ReflinkMode::Never, SparseMode::Never) => { copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream(source, dest, source_is_fifo).map(|_| ()) } else { let result = handle_reflink_never_sparse_never(source); if let Ok(debug) = result { @@ -315,9 +322,9 @@ pub(crate) fn copy_on_write( (ReflinkMode::Never, SparseMode::Auto) => { copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream(source, dest, source_is_fifo).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_never_sparse_auto(source, dest); @@ -335,10 +342,9 @@ pub(crate) fn copy_on_write( (ReflinkMode::Auto, SparseMode::Always) => { copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for // SparseMode::Always - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream(source, dest, source_is_fifo).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_auto_sparse_always(source, dest); @@ -356,9 +362,9 @@ pub(crate) fn copy_on_write( (ReflinkMode::Auto, SparseMode::Never) => { copy_debug.reflink = OffloadReflinkDebug::No; - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Avoided; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream(source, dest, source_is_fifo).map(|_| ()) } else { let result = handle_reflink_auto_sparse_never(source); if let Ok(debug) = result { @@ -369,9 +375,9 @@ pub(crate) fn copy_on_write( } } (ReflinkMode::Auto, SparseMode::Auto) => { - if source_is_fifo { + if source_is_stream { copy_debug.offload = OffloadReflinkDebug::Unsupported; - copy_fifo_contents(source, dest).map(|_| ()) + copy_stream(source, dest, source_is_fifo).map(|_| ()) } else { let mut copy_method = CopyMethod::Default; let result = handle_reflink_auto_sparse_auto(source, dest); diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index 77bdbbbdb83..c4cf95a81ef 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -4,12 +4,14 @@ // file that was distributed with this source code. // spell-checker:ignore reflink use std::ffi::CString; -use std::fs::{self, File}; -use std::io; +use std::fs::{self, File, OpenOptions}; use std::os::unix::ffi::OsStrExt; +use std::os::unix::fs::OpenOptionsExt; use std::path::Path; use quick_error::ResultExt; +use uucore::buf_copy; +use uucore::mode::get_umask; use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; @@ -24,6 +26,7 @@ pub(crate) fn copy_on_write( sparse_mode: SparseMode, context: &str, source_is_fifo: bool, + source_is_stream: bool, ) -> CopyResult { if sparse_mode != SparseMode::Auto { return Err("--sparse is only supported on linux".to_string().into()); @@ -85,10 +88,23 @@ pub(crate) fn copy_on_write( } _ => { copy_debug.reflink = OffloadReflinkDebug::Yes; - if source_is_fifo { + if source_is_stream { let mut src_file = File::open(source)?; - let mut dst_file = File::create(dest)?; - io::copy(&mut src_file, &mut dst_file).context(context)? + let mode = 0o622 & !get_umask(); + let mut dst_file = OpenOptions::new() + .create(true) + .write(true) + .mode(mode) + .open(dest)?; + + let context = buf_copy::copy_stream(&mut src_file, &mut dst_file) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other)) + .context(context)?; + + if source_is_fifo { + dst_file.set_permissions(src_file.metadata()?.permissions())?; + } + context } else { fs::copy(source, dest).context(context)? } diff --git a/src/uu/cp/src/platform/mod.rs b/src/uu/cp/src/platform/mod.rs index c7942706868..2071e928f41 100644 --- a/src/uu/cp/src/platform/mod.rs +++ b/src/uu/cp/src/platform/mod.rs @@ -2,6 +2,18 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. + +#[cfg(all( + unix, + not(any(target_os = "macos", target_os = "linux", target_os = "android")) +))] +mod other_unix; +#[cfg(all( + unix, + not(any(target_os = "macos", target_os = "linux", target_os = "android")) +))] +pub(crate) use self::other_unix::copy_on_write; + #[cfg(target_os = "macos")] mod macos; #[cfg(target_os = "macos")] @@ -12,7 +24,13 @@ mod linux; #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) use self::linux::copy_on_write; -#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))] +#[cfg(not(any( + unix, + any(target_os = "macos", target_os = "linux", target_os = "android") +)))] mod other; -#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))] +#[cfg(not(any( + unix, + any(target_os = "macos", target_os = "linux", target_os = "android") +)))] pub(crate) use self::other::copy_on_write; diff --git a/src/uu/cp/src/platform/other_unix.rs b/src/uu/cp/src/platform/other_unix.rs new file mode 100644 index 00000000000..aa8fed3fab1 --- /dev/null +++ b/src/uu/cp/src/platform/other_unix.rs @@ -0,0 +1,62 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore reflink +use std::fs::{self, File, OpenOptions}; +use std::os::unix::fs::OpenOptionsExt; +use std::path::Path; + +use quick_error::ResultExt; +use uucore::buf_copy; +use uucore::mode::get_umask; + +use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode}; + +/// Copies `source` to `dest` for systems without copy-on-write +pub(crate) fn copy_on_write( + source: &Path, + dest: &Path, + reflink_mode: ReflinkMode, + sparse_mode: SparseMode, + context: &str, + source_is_fifo: bool, + source_is_stream: bool, +) -> CopyResult { + if reflink_mode != ReflinkMode::Never { + return Err("--reflink is only supported on linux and macOS" + .to_string() + .into()); + } + if sparse_mode != SparseMode::Auto { + return Err("--sparse is only supported on linux".to_string().into()); + } + let copy_debug = CopyDebug { + offload: OffloadReflinkDebug::Unsupported, + reflink: OffloadReflinkDebug::Unsupported, + sparse_detection: SparseDebug::Unsupported, + }; + + if source_is_stream { + let mut src_file = File::open(source)?; + let mode = 0o622 & !get_umask(); + let mut dst_file = OpenOptions::new() + .create(true) + .write(true) + .mode(mode) + .open(dest)?; + + buf_copy::copy_stream(&mut src_file, &mut dst_file) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other)) + .context(context)?; + + if source_is_fifo { + dst_file.set_permissions(src_file.metadata()?.permissions())?; + } + return Ok(copy_debug); + } + + fs::copy(source, dest).context(context)?; + + Ok(copy_debug) +} From b75d0f944617a021565faa2ef8b81dda7960fad7 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Fri, 3 Jan 2025 19:08:14 +0700 Subject: [PATCH 256/351] tests/cp: remove FreeBSD guard in FIFO copy test Copying FIFOs now work under FreeBSD and other UNIX platforms. --- tests/by-util/test_cp.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 2132b93636f..9c85f89a3c6 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3471,15 +3471,9 @@ fn test_same_file_force_backup() { } /// Test for copying the contents of a FIFO as opposed to the FIFO object itself. -#[cfg(all(unix, not(target_os = "freebsd"), not(target_os = "openbsd")))] +#[cfg(unix)] #[test] fn test_copy_contents_fifo() { - // TODO this test should work on FreeBSD, but the command was - // causing an error: - // - // cp: 'fifo' -> 'outfile': the source path is neither a regular file nor a symlink to a regular file - // - // the underlying `std::fs:copy` doesn't support copying fifo on freeBSD let scenario = TestScenario::new(util_name!()); let at = &scenario.fixtures; From c6d192370173ec6b6e42daf98a79cd0d53990d57 Mon Sep 17 00:00:00 2001 From: Daringcuteseal Date: Mon, 6 Jan 2025 20:20:40 +0700 Subject: [PATCH 257/351] tests/cp: add test to copy from stdin --- tests/by-util/test_cp.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 9c85f89a3c6..7c7230c9ee4 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -6031,3 +6031,19 @@ fn test_cp_preserve_xattr_readonly_source() { "Extended attributes were not preserved" ); } + +#[test] +#[cfg(unix)] +fn test_cp_from_stdin() { + let (at, mut ucmd) = at_and_ucmd!(); + let target = "target"; + let test_string = "Hello, World!\n"; + + ucmd.arg("/dev/fd/0") + .arg(target) + .pipe_in(test_string) + .succeeds(); + + assert!(at.file_exists(target)); + assert_eq!(at.read(target), test_string); +} 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 258/351] 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 443d56d15bb..32a73172a68 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 259/351] 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 443d56d15bb..1f2b2ac98bb 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 260/351] 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 38df18daeb2..0131bae910d 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 261/351] 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 0602f0deec7..329b06d3681 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 f01f75b71d5..01e1b40fc4a 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 7336376eb6c..1e8cede37dd 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 c161db39fc7..e248332c9d1 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 684de8f74e0..9516b5e1bf6 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 6fe60053886..7d428f4e73f 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 262/351] 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 ea31c01079e..b000857a882 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 2b33f725dbe..79ca0d2f45c 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 263/351] 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 bc7b2394911..e566439f0f9 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 a86f893e084..c2d7f9121d9 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 264/351] 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 e566439f0f9..8c20cc39a2f 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 c2d7f9121d9..4bee30fab14 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 265/351] 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 8c20cc39a2f..4540c60d89f 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 266/351] 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 267/351] 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 bbfb0508c0f..bceedc89630 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 268/351] 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 bceedc89630..0ab38db28a2 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 1bb33e04465b82d7ea6f8bac8ad11db5fc9a58e9 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 7 Jan 2025 19:41:16 -0500 Subject: [PATCH 269/351] tsort: derive Default trait for Node struct Replace custom `Node::new` function with derived `Default` implementation, which does the same thing but more concisely. --- src/uu/tsort/src/tsort.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index ea5084a34d2..9b60e482860 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -114,20 +114,13 @@ pub fn uu_app() -> Command { // We use String as a representation of node here // but using integer may improve performance. - +#[derive(Default)] struct Node<'input> { successor_names: Vec<&'input str>, predecessor_count: usize, } impl<'input> Node<'input> { - fn new() -> Self { - Node { - successor_names: Vec::new(), - predecessor_count: 0, - } - } - fn add_successor(&mut self, successor_name: &'input str) { self.successor_names.push(successor_name); } @@ -139,7 +132,7 @@ struct Graph<'input> { impl<'input> Graph<'input> { fn add_node(&mut self, name: &'input str) { - self.nodes.entry(name).or_insert_with(Node::new); + self.nodes.entry(name).or_default(); } fn add_edge(&mut self, from: &'input str, to: &'input str) { From 0eb6f9bddbabf927c96784221edf9511a5db4cb0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 7 Jan 2025 21:34:56 -0500 Subject: [PATCH 270/351] 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 0131bae910d..d29283a69b1 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 af99952de6747367c1f4e42f9cf13eb5832966f0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 7 Jan 2025 19:42:45 -0500 Subject: [PATCH 271/351] tsort: print nodes and cycles as they are visited Update `tsort` so that * nodes are printed as they are visited, * cycles are printed as they are discovered, * finding a cycle doesn't terminate the traversal, * multiple cycles can be found and displayed. Fixes #7074 --- src/uu/tsort/src/tsort.rs | 124 +++++++++++++++++++++++++++--------- tests/by-util/test_tsort.rs | 28 ++++++++ 2 files changed, 122 insertions(+), 30 deletions(-) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 9b60e482860..2f0b4c9b836 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -//spell-checker:ignore TAOCP +//spell-checker:ignore TAOCP indegree use clap::{crate_version, Arg, Command}; use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt::Display; @@ -75,7 +75,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; // Create the directed graph from pairs of tokens in the input data. - let mut g = Graph::default(); + let mut g = Graph::new(input.clone()); for ab in data.split_whitespace().collect::>().chunks(2) { match ab { [a, b] => g.add_edge(a, b), @@ -83,20 +83,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - match g.run_tsort() { - Err(cycle) => { - show!(TsortError::Loop(input.to_string())); - for node in &cycle { - show!(TsortError::LoopNode(node.to_string())); - } - println!("{}", cycle.join("\n")); - Ok(()) - } - Ok(ordering) => { - println!("{}", ordering.join("\n")); - Ok(()) - } - } + g.run_tsort(); + Ok(()) } pub fn uu_app() -> Command { Command::new(uucore::util_name()) @@ -112,6 +100,20 @@ pub fn uu_app() -> Command { ) } +/// Find the element `x` in `vec` and remove it, returning its index. +fn remove(vec: &mut Vec, x: T) -> Option +where + T: PartialEq, +{ + for i in 0..vec.len() { + if vec[i] == x { + vec.remove(i); + return Some(i); + } + } + None +} + // We use String as a representation of node here // but using integer may improve performance. #[derive(Default)] @@ -125,12 +127,20 @@ impl<'input> Node<'input> { self.successor_names.push(successor_name); } } -#[derive(Default)] + struct Graph<'input> { + name: String, nodes: HashMap<&'input str, Node<'input>>, } impl<'input> Graph<'input> { + fn new(name: String) -> Graph<'input> { + Self { + name, + nodes: HashMap::default(), + } + } + fn add_node(&mut self, name: &'input str) { self.nodes.entry(name).or_default(); } @@ -147,9 +157,14 @@ impl<'input> Graph<'input> { to_node.predecessor_count += 1; } } + + fn remove_edge(&mut self, u: &'input str, v: &'input str) { + remove(&mut self.nodes.get_mut(u).unwrap().successor_names, v); + self.nodes.get_mut(v).unwrap().predecessor_count -= 1; + } + /// Implementation of algorithm T from TAOCP (Don. Knuth), vol. 1. - fn run_tsort(&mut self) -> Result, Vec<&'input str>> { - let mut result = Vec::with_capacity(self.nodes.len()); + fn run_tsort(&mut self) { // First, we find a node that have no prerequisites (independent nodes) // If no such node exists, then there is a cycle. let mut independent_nodes_queue: VecDeque<&'input str> = self @@ -166,10 +181,18 @@ impl<'input> Graph<'input> { independent_nodes_queue.make_contiguous().sort_unstable(); // to make sure the resulting ordering is deterministic we need to order independent nodes // FIXME: this doesn't comply entirely with the GNU coreutils implementation. - // we remove each independent node, from the graph, updating each successor predecessor_count variable as we do. - while let Some(name_of_next_node_to_process) = independent_nodes_queue.pop_front() { - result.push(name_of_next_node_to_process); - if let Some(node_to_process) = self.nodes.remove(name_of_next_node_to_process) { + // To make sure the resulting ordering is deterministic we + // need to order independent nodes. + // + // FIXME: this doesn't comply entirely with the GNU coreutils + // implementation. + independent_nodes_queue.make_contiguous().sort_unstable(); + + while !self.nodes.is_empty() { + // Get the next node (breaking any cycles necessary to do so). + let v = self.find_next_node(&mut independent_nodes_queue); + println!("{v}"); + if let Some(node_to_process) = self.nodes.remove(v) { for successor_name in node_to_process.successor_names { let successor_node = self.nodes.get_mut(successor_name).unwrap(); successor_node.predecessor_count -= 1; @@ -180,20 +203,61 @@ impl<'input> Graph<'input> { } } } + } - // if the graph has no cycle (it's a dependency tree), the graph should be empty now, as all nodes have been deleted. - if self.nodes.is_empty() { - Ok(result) - } else { - // otherwise, we detect and show a cycle to the user (as the GNU coreutils implementation does) - Err(self.detect_cycle()) + /// Get the in-degree of the node with the given name. + fn indegree(&self, name: &str) -> Option { + self.nodes.get(name).map(|data| data.predecessor_count) + } + + // Pre-condition: self.nodes is non-empty. + fn find_next_node(&mut self, frontier: &mut VecDeque<&'input str>) -> &'input str { + // If there are no nodes of in-degree zero but there are still + // un-visited nodes in the graph, then there must be a cycle. + // We need to find the cycle, display it, and then break the + // cycle. + // + // A cycle is guaranteed to be of length at least two. We break + // the cycle by deleting an arbitrary edge (the first). That is + // not necessarily the optimal thing, but it should be enough to + // continue making progress in the graph traversal. + // + // It is possible that deleting the edge does not actually + // result in the target node having in-degree zero, so we repeat + // the process until such a node appears. + loop { + match frontier.pop_front() { + None => self.find_and_break_cycle(frontier), + Some(v) => return v, + } + } + } + + fn find_and_break_cycle(&mut self, frontier: &mut VecDeque<&'input str>) { + let cycle = self.detect_cycle(); + show!(TsortError::Loop(self.name.clone())); + for node in &cycle { + show!(TsortError::LoopNode(node.to_string())); + } + let u = cycle[0]; + let v = cycle[1]; + self.remove_edge(u, v); + if self.indegree(v).unwrap() == 0 { + frontier.push_back(v); } } fn detect_cycle(&self) -> Vec<&'input str> { + // Sort the nodes just to make this function deterministic. + let mut nodes = Vec::new(); + for node in self.nodes.keys() { + nodes.push(node); + } + nodes.sort_unstable(); + let mut visited = HashSet::new(); let mut stack = Vec::with_capacity(self.nodes.len()); - for &node in self.nodes.keys() { + for node in nodes { if !visited.contains(node) && self.dfs(node, &mut visited, &mut stack) { return stack; } diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index f86add2947f..299a8f0bb50 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -83,3 +83,31 @@ fn test_split_on_any_whitespace() { .succeeds() .stdout_only("a\nb\n"); } + +#[test] +fn test_cycle() { + // The graph looks like: a --> b <==> c --> d + new_ucmd!() + .pipe_in("a b b c c d c b") + .fails() + .code_is(1) + .stdout_is("a\nc\nd\nb\n") + .stderr_is("tsort: -: input contains a loop:\ntsort: b\ntsort: c\n"); +} + +#[test] +fn test_two_cycles() { + // The graph looks like: + // + // a + // | + // V + // c <==> b <==> d + // + new_ucmd!() + .pipe_in("a b b c c b b d d b") + .fails() + .code_is(1) + .stdout_is("a\nc\nd\nb\n") + .stderr_is("tsort: -: input contains a loop:\ntsort: b\ntsort: c\ntsort: -: input contains a loop:\ntsort: b\ntsort: d\n"); +} 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 272/351] 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 fa1c35a6dee..e1db70e8b61 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 273/351] 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 fa1c35a6dee..0c84f935064 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 274/351] 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 0ab38db28a2..f0e021f41da 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 275/351] 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 556bd9603c6..e7be3548df5 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 97821a763aba837016520c2a1cc4df289f5252ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:53:06 +0000 Subject: [PATCH 276/351] chore(deps): update rust crate thiserror to v2.0.10 --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7be3548df5..2c5423b848e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2275,11 +2275,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "a3ac7f54ca534db81081ef1c1e7f6ea8a3ef428d2fc069097c079443d24124d3" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.10", ] [[package]] @@ -2295,9 +2295,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "9e9465d30713b56a37ede7185763c3492a91be2f5fa68d958c44e41ab9248beb" dependencies = [ "proc-macro2", "quote", @@ -2496,7 +2496,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.9", + "thiserror 2.0.10", "uucore", ] @@ -2508,7 +2508,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.9", + "thiserror 2.0.10", "uucore", ] @@ -2585,7 +2585,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.9", + "thiserror 2.0.10", "uucore", ] @@ -2771,7 +2771,7 @@ version = "0.0.28" dependencies = [ "clap", "memchr", - "thiserror 2.0.9", + "thiserror 2.0.10", "uucore", ] @@ -3102,7 +3102,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.9", + "thiserror 2.0.10", "uucore", ] @@ -3381,7 +3381,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.9", + "thiserror 2.0.10", "utmp-classic", "uucore", ] @@ -3412,7 +3412,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.9", + "thiserror 2.0.10", "unicode-width 0.2.0", "uucore", ] @@ -3474,7 +3474,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.10", "time", "uucore_procs", "walkdir", @@ -3947,7 +3947,7 @@ dependencies = [ "flate2", "indexmap", "memchr", - "thiserror 2.0.9", + "thiserror 2.0.10", "zopfli", ] From 9280a46cc868e3d039b0d185cfd325691aad6a5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:53:11 +0000 Subject: [PATCH 277/351] chore(deps): update vmactions/freebsd-vm action to v1.1.8 --- .github/workflows/freebsd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index ef1602ad7cf..4c43b77d7f3 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -41,7 +41,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.1.7 + uses: vmactions/freebsd-vm@v1.1.8 with: usesh: true sync: rsync @@ -135,7 +135,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.1.7 + uses: vmactions/freebsd-vm@v1.1.8 with: usesh: true sync: rsync From 33ac58383c499e19e92c3effefa1c4641861851a Mon Sep 17 00:00:00 2001 From: Tommaso Fellegara <96147629+Felle33@users.noreply.github.com> Date: Thu, 9 Jan 2025 09:20:48 +0100 Subject: [PATCH 278/351] csplit: fix bug when --suppress-matched flag is active and positive/negative offset is present (#7088) * 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 * csplit: managed the positive and negative offset when the --suppressed-matched flag is active * 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 * csplit: managed the positive and negative offset when the --suppressed-matched flag is active * csplit: swapped if and else blocks for better readability --- src/uu/csplit/src/csplit.rs | 19 ++++++++++++++++++- tests/by-util/test_csplit.rs | 10 +++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 329b06d3681..d654c9271fe 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)?; @@ -420,7 +431,12 @@ impl SplitWriter<'_> { for line in input_iter.shrink_buffer_to_size() { self.writeln(&line)?; } - if !self.options.suppress_matched { + if self.options.suppress_matched { + // 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); + } else { // add 1 to the buffer size to make place for the matched line input_iter.set_size_of_buffer(offset_usize + 1); assert!( @@ -428,6 +444,7 @@ impl SplitWriter<'_> { "should be big enough to hold every lines" ); } + self.finish_split(); if input_iter.buffer_len() < offset_usize { return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string())); diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 2315715228d..9323b985189 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 1dc463fd26c578290c2c6e88045516f511e96c82 Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Thu, 12 Dec 2024 11:02:50 +0700 Subject: [PATCH 279/351] tests/csplit: add named pipe input file test. --- tests/by-util/test_csplit.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 9323b985189..991cd36ef0e 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1417,3 +1417,34 @@ fn repeat_everything() { assert_eq!(at.read("xxz_004"), generate(37, 44 + 1)); assert_eq!(at.read("xxz_005"), generate(46, 50 + 1)); } + +#[cfg(unix)] +#[test] +fn test_named_pipe_input_file() { + let (at, mut ucmd) = at_and_ucmd!(); + + let mut fifo_writer = + create_named_pipe_with_writer(&at.plus_as_string("fifo"), &generate(1, 51)); + + let result = ucmd.args(&["fifo", "10"]).succeeds(); + fifo_writer.kill().unwrap(); + fifo_writer.wait().unwrap(); + result.stdout_only("18\n123\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)); + assert_eq!(at.read("xx01"), generate(10, 51)); +} + +#[cfg(unix)] +fn create_named_pipe_with_writer(path: &str, data: &str) -> std::process::Child { + nix::unistd::mkfifo(path, nix::sys::stat::Mode::S_IRWXU).unwrap(); + std::process::Command::new("sh") + .arg("-c") + .arg(format!("echo -n '{}' > {path}", data)) + .spawn() + .unwrap() +} From 757c0b260e1ef7b886dfadd91530623054d2ddf3 Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Thu, 12 Dec 2024 11:05:39 +0700 Subject: [PATCH 280/351] csplit: defer IO read error handling to iterator. --- src/uu/csplit/src/csplit.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index d654c9271fe..a0bfc61cc84 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -582,12 +582,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { let file = File::open(file_name) .map_err_context(|| format!("cannot access {}", file_name.quote()))?; - let file_metadata = file - .metadata() - .map_err_context(|| format!("cannot access {}", file_name.quote()))?; - if !file_metadata.is_file() { - return Err(CsplitError::NotRegularFile(file_name.to_string()).into()); - } Ok(csplit(&options, &patterns, BufReader::new(file))?) } } From 19f990f29ad1434b79a6704db21926f279ba436d Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Thu, 12 Dec 2024 11:16:57 +0700 Subject: [PATCH 281/351] tests/csplit: add directory input file test. --- tests/by-util/test_csplit.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 991cd36ef0e..697a8f47a79 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1448,3 +1448,14 @@ fn create_named_pipe_with_writer(path: &str, data: &str) -> std::process::Child .spawn() .unwrap() } + +#[test] +fn test_directory_input_file() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("test_directory"); + + ucmd.args(&["test_directory", "1"]) + .fails() + .code_is(1) + .stderr_only("csplit: read error: Is a directory\n"); +} From 51dce9c5f8df412e94740411581d20101303645b Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Thu, 12 Dec 2024 11:45:58 +0700 Subject: [PATCH 282/351] csplit: return UResult instead of io::Result from iterator to handle error message more uniformly. --- src/uu/csplit/src/csplit.rs | 18 +++++++++++------- src/uu/csplit/src/csplit_error.rs | 13 ++++++++++++- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index a0bfc61cc84..678d170941f 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -87,7 +87,11 @@ pub fn csplit(options: &CsplitOptions, patterns: &[String], input: T) -> Resu where T: BufRead, { - let mut input_iter = InputSplitter::new(input.lines().enumerate()); + let enumerated_input_lines = input + .lines() + .map(|line| line.map_err_context(|| "read error".to_string())) + .enumerate(); + let mut input_iter = InputSplitter::new(enumerated_input_lines); let mut split_writer = SplitWriter::new(options); let patterns: Vec = patterns::get_patterns(patterns)?; let ret = do_csplit(&mut split_writer, patterns, &mut input_iter); @@ -117,7 +121,7 @@ fn do_csplit( input_iter: &mut InputSplitter, ) -> Result<(), CsplitError> where - I: Iterator)>, + I: Iterator)>, { // split the file based on patterns for pattern in patterns { @@ -305,7 +309,7 @@ impl SplitWriter<'_> { input_iter: &mut InputSplitter, ) -> Result<(), CsplitError> where - I: Iterator)>, + I: Iterator)>, { input_iter.rewind_buffer(); input_iter.set_size_of_buffer(1); @@ -358,7 +362,7 @@ impl SplitWriter<'_> { input_iter: &mut InputSplitter, ) -> Result<(), CsplitError> where - I: Iterator)>, + I: Iterator)>, { if offset >= 0 { // The offset is zero or positive, no need for a buffer on the lines read. @@ -470,7 +474,7 @@ impl SplitWriter<'_> { /// This is used to pass matching lines to the next split and to support patterns with a negative offset. struct InputSplitter where - I: Iterator)>, + I: Iterator)>, { iter: I, buffer: Vec<::Item>, @@ -483,7 +487,7 @@ where impl InputSplitter where - I: Iterator)>, + I: Iterator)>, { fn new(iter: I) -> Self { Self { @@ -547,7 +551,7 @@ where impl Iterator for InputSplitter where - I: Iterator)>, + I: Iterator)>, { type Item = ::Item; diff --git a/src/uu/csplit/src/csplit_error.rs b/src/uu/csplit/src/csplit_error.rs index 4a83b637b07..ac1c8d01c48 100644 --- a/src/uu/csplit/src/csplit_error.rs +++ b/src/uu/csplit/src/csplit_error.rs @@ -35,6 +35,8 @@ pub enum CsplitError { SuffixFormatTooManyPercents, #[error("{} is not a regular file", ._0.quote())] NotRegularFile(String), + #[error("{}", _0)] + UError(Box), } impl From for CsplitError { @@ -43,8 +45,17 @@ impl From for CsplitError { } } +impl From> for CsplitError { + fn from(error: Box) -> Self { + Self::UError(error) + } +} + impl UError for CsplitError { fn code(&self) -> i32 { - 1 + match self { + Self::UError(e) => e.code(), + _ => 1, + } } } From c8bc5d24550b3b7f7f19a0b66ec3e97356b62a85 Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Tue, 7 Jan 2025 21:35:30 +0700 Subject: [PATCH 283/351] tests/csplit: handle directory input file test for Windows separately --- tests/by-util/test_csplit.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 697a8f47a79..7dcd79e4e56 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1454,8 +1454,14 @@ fn test_directory_input_file() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("test_directory"); + #[cfg(unix)] ucmd.args(&["test_directory", "1"]) .fails() .code_is(1) .stderr_only("csplit: read error: Is a directory\n"); + #[cfg(windows)] + ucmd.args(&["test_directory", "1"]) + .fails() + .code_is(1) + .stderr_only("csplit: cannot open 'test_directory' for reading: Permission denied\n"); } From 96929734ea95d99c3ff9678a3cebaf667179921f Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Tue, 7 Jan 2025 21:44:09 +0700 Subject: [PATCH 284/351] tests/csplit: modify no_such_file test expected error to conform better with original csplit error --- tests/by-util/test_csplit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 7dcd79e4e56..bb714509eac 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1379,7 +1379,7 @@ fn no_such_file() { let (_, mut ucmd) = at_and_ucmd!(); ucmd.args(&["in", "0"]) .fails() - .stderr_contains("cannot access 'in': No such file or directory"); + .stderr_contains("cannot open 'in' for reading: No such file or directory"); } #[test] From 694298f0d1b1ee30deda46913820de73c311e2af Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Tue, 7 Jan 2025 21:45:52 +0700 Subject: [PATCH 285/351] csplit: modify failure in opening file error message --- src/uu/csplit/src/csplit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 678d170941f..501f97582ec 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -585,7 +585,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(csplit(&options, &patterns, stdin.lock())?) } else { let file = File::open(file_name) - .map_err_context(|| format!("cannot access {}", file_name.quote()))?; + .map_err_context(|| format!("cannot open {} for reading", file_name.quote()))?; Ok(csplit(&options, &patterns, BufReader::new(file))?) } } From 981019138da55cf10cc73ac9f18d09adb1d77116 Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Tue, 7 Jan 2025 15:31:49 +0700 Subject: [PATCH 286/351] tests/csplit: ignore IRWXU from cspell check --- tests/by-util/test_csplit.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index bb714509eac..0b2353a5071 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1441,6 +1441,7 @@ fn test_named_pipe_input_file() { #[cfg(unix)] fn create_named_pipe_with_writer(path: &str, data: &str) -> std::process::Child { + // cSpell:ignore IRWXU nix::unistd::mkfifo(path, nix::sys::stat::Mode::S_IRWXU).unwrap(); std::process::Command::new("sh") .arg("-c") From a4fdecbf48e33116fe91d993a43e47a074d18102 Mon Sep 17 00:00:00 2001 From: Fuad Ismail Date: Tue, 7 Jan 2025 23:13:47 +0700 Subject: [PATCH 287/351] tests/csplit: use printf instead of echo -n for maximum portability with all UNIX systems --- tests/by-util/test_csplit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 0b2353a5071..38f5c97bfaf 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1445,7 +1445,7 @@ fn create_named_pipe_with_writer(path: &str, data: &str) -> std::process::Child nix::unistd::mkfifo(path, nix::sys::stat::Mode::S_IRWXU).unwrap(); std::process::Command::new("sh") .arg("-c") - .arg(format!("echo -n '{}' > {path}", data)) + .arg(format!("printf '{}' > {path}", data)) .spawn() .unwrap() } From 6096767442f14f5f5e83a296087d06b04a0b81d2 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 9 Jan 2025 15:20:22 +0100 Subject: [PATCH 288/351] csplit: simplify test --- tests/by-util/test_csplit.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 38f5c97bfaf..e062b6d551f 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1376,8 +1376,8 @@ fn zero_error() { #[test] fn no_such_file() { - let (_, mut ucmd) = at_and_ucmd!(); - ucmd.args(&["in", "0"]) + new_ucmd!() + .args(&["in", "0"]) .fails() .stderr_contains("cannot open 'in' for reading: No such file or directory"); } From c872cfa5d1b0e5f729d26e30b6ffe55997706c1c Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 10 Jan 2025 07:28:37 +0100 Subject: [PATCH 289/351] cp: use is_some_and instead of map_or --- src/uu/cp/src/cp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b87898c783e..f3bded69ef1 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -935,7 +935,7 @@ impl Options { if backup_mode != BackupMode::NoBackup && matches .get_one::(update_control::arguments::OPT_UPDATE) - .map_or(false, |v| v == "none" || v == "none-fail") + .is_some_and(|v| v == "none" || v == "none-fail") { return Err(Error::InvalidArgument( "--backup is mutually exclusive with -n or --update=none-fail".to_string(), From e777db93046e90fb37053e138b67c094b96b9d14 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 10 Jan 2025 07:42:13 +0100 Subject: [PATCH 290/351] ls: compile regexes outside of loop in tests --- tests/by-util/test_ls.rs | 52 ++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 88ca08320c4..6ef7ac93a2e 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1096,13 +1096,16 @@ fn test_ls_long() { let at = &scene.fixtures; at.touch(at.plus_as_string("test-long")); + #[cfg(not(windows))] + let regex = r"[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3}.*"; + #[cfg(windows)] + let regex = r"[-dl](r[w-]x){3}.*"; + + let re = &Regex::new(regex).unwrap(); + for arg in LONG_ARGS { let result = scene.ucmd().arg(arg).arg("test-long").succeeds(); - #[cfg(not(windows))] - result.stdout_matches(&Regex::new(r"[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3}.*").unwrap()); - - #[cfg(windows)] - result.stdout_matches(&Regex::new(r"[-dl](r[w-]x){3}.*").unwrap()); + result.stdout_matches(re); } } @@ -1115,23 +1118,30 @@ fn test_ls_long_format() { at.touch(at.plus_as_string("test-long-dir/test-long-file")); at.mkdir(at.plus_as_string("test-long-dir/test-long-dir")); - for arg in LONG_ARGS { - // Assuming sane username do not have spaces within them. - // A line of the output should be: - // One of the characters -bcCdDlMnpPsStTx? - // rwx, with - for missing permissions, thrice. - // Zero or one "." for indicating a file with security context - // A number, preceded by column whitespace, and followed by a single space. - // A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd). - // A number, followed by a single space. - // A month, followed by a single space. - // A day, preceded by column whitespace, and followed by a single space. - // Either a year or a time, currently [0-9:]+, preceded by column whitespace, - // and followed by a single space. - // Whatever comes after is irrelevant to this specific test. - scene.ucmd().arg(arg).arg("test-long-dir").succeeds().stdout_matches(&Regex::new( + // Assuming sane username do not have spaces within them. + // A line of the output should be: + // One of the characters -bcCdDlMnpPsStTx? + // rwx, with - for missing permissions, thrice. + // Zero or one "." for indicating a file with security context + // A number, preceded by column whitespace, and followed by a single space. + // A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd). + // A number, followed by a single space. + // A month, followed by a single space. + // A day, preceded by column whitespace, and followed by a single space. + // Either a year or a time, currently [0-9:]+, preceded by column whitespace, + // and followed by a single space. + // Whatever comes after is irrelevant to this specific test. + let re = &Regex::new( r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3}\.? +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ " - ).unwrap()); + ).unwrap(); + + for arg in LONG_ARGS { + scene + .ucmd() + .arg(arg) + .arg("test-long-dir") + .succeeds() + .stdout_matches(re); } // This checks for the line with the .. entry. The uname and group should be digits. From e75abb2dbd65ec8a96e274611bd92e1e31c45654 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 10 Jan 2025 08:33:28 +0100 Subject: [PATCH 291/351] cp: fix error from large_stack_arrays lint in test --- tests/by-util/test_cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 2132b93636f..babd4885529 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2524,7 +2524,7 @@ fn test_cp_sparse_always_non_empty() { const BUFFER_SIZE: usize = 4096 * 16 + 3; let (at, mut ucmd) = at_and_ucmd!(); - let mut buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + let mut buf = vec![0; BUFFER_SIZE].into_boxed_slice(); let blocks_to_touch = [buf.len() / 3, 2 * (buf.len() / 3)]; for i in blocks_to_touch { @@ -2540,7 +2540,7 @@ fn test_cp_sparse_always_non_empty() { let touched_block_count = blocks_to_touch.len() as u64 * at.metadata("dst_file_sparse").blksize() / 512; - assert_eq!(at.read_bytes("dst_file_sparse"), buf); + assert_eq!(at.read_bytes("dst_file_sparse").into_boxed_slice(), buf); assert_eq!(at.metadata("dst_file_sparse").blocks(), touched_block_count); } From 9fe4c9facf56b829283a8bd33f8d12053afe3fe3 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 10 Jan 2025 08:34:24 +0100 Subject: [PATCH 292/351] Cargo.toml: allow large_stack_arrays lint due to https://github.com/rust-lang/rust-clippy/issues/13774 --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ea51f6f99e2..2b62476f518 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -564,6 +564,8 @@ multiple_crate_versions = "allow" cargo_common_metadata = "allow" uninlined_format_args = "allow" missing_panics_doc = "allow" +# TODO remove when https://github.com/rust-lang/rust-clippy/issues/13774 is fixed +large_stack_arrays = "allow" use_self = "warn" needless_pass_by_value = "warn" From 55367205a936e6368c2918d6e4629a618fd0db92 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 10 Jan 2025 08:53:03 +0100 Subject: [PATCH 293/351] cp: use is_ok_and instead of map_or --- src/uu/cp/src/platform/macos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index 77bdbbbdb83..bd47c44ae96 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -64,7 +64,7 @@ pub(crate) fn copy_on_write( // clonefile(2) fails if the destination exists. Remove it and try again. Do not // bother to check if removal worked because we're going to try to clone again. // first lets make sure the dest file is not read only - if fs::metadata(dest).map_or(false, |md| !md.permissions().readonly()) { + if fs::metadata(dest).is_ok_and(|md| !md.permissions().readonly()) { // remove and copy again // TODO: rewrite this to better match linux behavior // linux first opens the source file and destination file then uses the file From 51f4bfa1a99a14893faee6aec8b5315e5b123086 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 10 Jan 2025 08:55:45 +0100 Subject: [PATCH 294/351] uucore: use is_some_and instead of map_or --- src/uucore/src/lib/features/fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index e2958232f1f..d0875f78a91 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -719,7 +719,7 @@ pub fn path_ends_with_terminator(path: &Path) -> bool { path.as_os_str() .encode_wide() .last() - .map_or(false, |wide| wide == b'/'.into() || wide == b'\\'.into()) + .is_some_and(|wide| wide == b'/'.into() || wide == b'\\'.into()) } /// Checks if the standard input (stdin) is a directory. From 62ecd00c8c13fc0e0fef6fa16552ac538480e2d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:26:22 +0000 Subject: [PATCH 295/351] chore(deps): update rust crate clap to v4.5.26 --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c5423b848e..194c23b009d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,18 +327,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.24" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.24" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", From a098eb4d8436cb521949288504b5d41a04061e9d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:26:28 +0000 Subject: [PATCH 296/351] chore(deps): update rust crate clap_complete to v4.5.42 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c5423b848e..4c7781ce49d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,9 +349,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.41" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942dc5991a34d8cf58937ec33201856feba9cbceeeab5adf04116ec7c763bff1" +checksum = "33a7e468e750fa4b6be660e8b5651ad47372e8fb114030b594c2d75d48c5ffd0" dependencies = [ "clap", ] From 43c7dfdd91b6a246cc7f231cb5c3fc41968d65f4 Mon Sep 17 00:00:00 2001 From: jfinkels Date: Thu, 9 Jan 2025 19:17:54 -0500 Subject: [PATCH 297/351] uucore: remove mention of crash in docs The `crash!` macro has been removed in https://github.com/uutils/coreutils/pull/7084 --- src/uucore/src/lib/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 7d428f4e73f..3ef16ab4d5a 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -20,7 +20,7 @@ //! fully qualified name like this: //! //! ```no_run -//! use uucore::{show, crash}; +//! use uucore::show; //! ``` //! //! Here's an overview of the macros sorted by purpose From f2dbd200aa39612f25eb58239f116823524c0de4 Mon Sep 17 00:00:00 2001 From: jfinkels Date: Thu, 9 Jan 2025 19:19:33 -0500 Subject: [PATCH 298/351] printf: remove allow(dead_code) directive Remove an `allow(dead_code)` directive that seems unnecessary. --- src/uu/printf/src/printf.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index f86b7bd9fa2..f278affaede 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -2,9 +2,6 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. - -#![allow(dead_code)] - use clap::{crate_version, Arg, ArgAction, Command}; use std::io::stdout; use std::ops::ControlFlow; From 16c7bb77846a1cf6df44029afc4bcca58d01de84 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:20:57 +0000 Subject: [PATCH 299/351] chore(deps): update rust crate clap_mangen to v0.2.26 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47a81c08006..175f1a3437b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,9 +364,9 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clap_mangen" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbfe6ac42a2438d0968beba18e3c35cacf16b0c25310bc22b1f5f3cffff09f4" +checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a" dependencies = [ "clap", "roff", From ab6e1b21037582b0a8234c0c30dc16682d470172 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 23:09:37 +0000 Subject: [PATCH 300/351] chore(deps): update rust crate thiserror to v2.0.11 --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47a81c08006..d2c42cced0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2275,11 +2275,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ac7f54ca534db81081ef1c1e7f6ea8a3ef428d2fc069097c079443d24124d3" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.10", + "thiserror-impl 2.0.11", ] [[package]] @@ -2295,9 +2295,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9465d30713b56a37ede7185763c3492a91be2f5fa68d958c44e41ab9248beb" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -2496,7 +2496,7 @@ version = "0.0.28" dependencies = [ "clap", "nix", - "thiserror 2.0.10", + "thiserror 2.0.11", "uucore", ] @@ -2508,7 +2508,7 @@ dependencies = [ "fts-sys", "libc", "selinux", - "thiserror 2.0.10", + "thiserror 2.0.11", "uucore", ] @@ -2585,7 +2585,7 @@ version = "0.0.28" dependencies = [ "clap", "regex", - "thiserror 2.0.10", + "thiserror 2.0.11", "uucore", ] @@ -2771,7 +2771,7 @@ version = "0.0.28" dependencies = [ "clap", "memchr", - "thiserror 2.0.10", + "thiserror 2.0.11", "uucore", ] @@ -3102,7 +3102,7 @@ dependencies = [ "clap", "libc", "selinux", - "thiserror 2.0.10", + "thiserror 2.0.11", "uucore", ] @@ -3381,7 +3381,7 @@ version = "0.0.28" dependencies = [ "chrono", "clap", - "thiserror 2.0.10", + "thiserror 2.0.11", "utmp-classic", "uucore", ] @@ -3412,7 +3412,7 @@ dependencies = [ "clap", "libc", "nix", - "thiserror 2.0.10", + "thiserror 2.0.11", "unicode-width 0.2.0", "uucore", ] @@ -3474,7 +3474,7 @@ dependencies = [ "sha3", "sm3", "tempfile", - "thiserror 2.0.10", + "thiserror 2.0.11", "time", "uucore_procs", "walkdir", @@ -3947,7 +3947,7 @@ dependencies = [ "flate2", "indexmap", "memchr", - "thiserror 2.0.10", + "thiserror 2.0.11", "zopfli", ] From 1d0dcb596208449bea1f9147e21e85cd95658731 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 10 Jan 2025 21:31:01 -0500 Subject: [PATCH 301/351] stat: fix precision when rendering mtime (%Y) Support precision when rendering the time of last data modification with `stat`. For example, after this commit $ stat --printf='%.1Y\n' f 1668645806.7 Previously, the precision in the format specification was ignored. This is implemented with a custom renderer because GNU `stat` seems to truncate the number as opposed to rounding the number as would happen when using `format!` with a specified number of digits of precision. Fixes #3233 --- src/uu/stat/src/stat.rs | 79 +++++++++++++++++++++++++++++++++++++- tests/by-util/test_stat.rs | 11 ++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 5e617e7a31a..a950e98ea67 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -94,6 +94,7 @@ pub enum OutputType { Unsigned(u64), UnsignedHex(u64), UnsignedOct(u32), + Float(f64), Unknown, } @@ -283,6 +284,9 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option { print_unsigned_hex(*num, &flags, width, precision, padding_char); } + OutputType::Float(num) => { + print_float(*num, &flags, width, precision, padding_char); + } OutputType::Unknown => print!("?"), } } @@ -444,6 +448,58 @@ fn print_integer( pad_and_print(&extended, flags.left, width, padding_char); } +/// Truncate a float to the given number of digits after the decimal point. +fn precision_trunc(num: f64, precision: usize) -> String { + // GNU `stat` doesn't round, it just seems to truncate to the + // given precision: + // + // $ stat -c "%.5Y" /dev/pts/ptmx + // 1736344012.76399 + // $ stat -c "%.4Y" /dev/pts/ptmx + // 1736344012.7639 + // $ stat -c "%.3Y" /dev/pts/ptmx + // 1736344012.763 + // + // Contrast this with `printf`, which seems to round the + // numbers: + // + // $ printf "%.5f\n" 1736344012.76399 + // 1736344012.76399 + // $ printf "%.4f\n" 1736344012.76399 + // 1736344012.7640 + // $ printf "%.3f\n" 1736344012.76399 + // 1736344012.764 + // + let num_str = num.to_string(); + let n = num_str.len(); + match (num_str.find('.'), precision) { + (None, 0) => num_str, + (None, p) => format!("{num_str}.{zeros}", zeros = "0".repeat(p)), + (Some(i), 0) => num_str[..i].to_string(), + (Some(i), p) if p < n - i => num_str[..i + 1 + p].to_string(), + (Some(i), p) => format!("{num_str}{zeros}", zeros = "0".repeat(p - (n - i - 1))), + } +} + +fn print_float( + num: f64, + flags: &Flags, + width: usize, + precision: Option, + padding_char: Padding, +) { + let prefix = if flags.sign { + "+" + } else if flags.space { + " " + } else { + "" + }; + let num_str = precision_trunc(num, precision.unwrap_or(0)); + let extended = format!("{prefix}{num_str}"); + pad_and_print(&extended, flags.left, width, padding_char) +} + /// Prints an unsigned integer value based on the provided flags, width, and precision. /// /// # Arguments @@ -898,7 +954,16 @@ impl Stater { // time of last data modification, human-readable 'y' => OutputType::Str(pretty_time(meta.mtime(), meta.mtime_nsec())), // time of last data modification, seconds since Epoch - 'Y' => OutputType::Integer(meta.mtime()), + 'Y' => { + let sec = meta.mtime(); + let nsec = meta.mtime_nsec(); + let tm = + chrono::DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default(); + let tm: DateTime = tm.into(); + let micros = tm.timestamp_micros(); + let secs = micros as f64 / 1_000_000.0; + OutputType::Float(secs) + } // time of last status change, human-readable 'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())), // time of last status change, seconds since Epoch @@ -1107,7 +1172,7 @@ fn pretty_time(sec: i64, nsec: i64) -> String { #[cfg(test)] mod tests { - use super::{group_num, Flags, ScanUtil, Stater, Token}; + use super::{group_num, precision_trunc, Flags, ScanUtil, Stater, Token}; #[test] fn test_scanners() { @@ -1216,4 +1281,14 @@ mod tests { ]; assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap()); } + + #[test] + fn test_precision_trunc() { + assert_eq!(precision_trunc(123.456, 0), "123"); + assert_eq!(precision_trunc(123.456, 1), "123.4"); + assert_eq!(precision_trunc(123.456, 2), "123.45"); + assert_eq!(precision_trunc(123.456, 3), "123.456"); + assert_eq!(precision_trunc(123.456, 4), "123.4560"); + assert_eq!(precision_trunc(123.456, 5), "123.45600"); + } } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index cbd36832f48..a48462c3eae 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -184,6 +184,17 @@ fn test_char() { ]; let ts = TestScenario::new(util_name!()); let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + eprintln!("{expected_stdout}"); + ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); +} + +#[cfg(target_os = "linux")] +#[test] +fn test_printf_mtime_precision() { + let args = ["-c", "%.0Y %.1Y %.2Y %.3Y %.4Y", "/dev/pts/ptmx"]; + let ts = TestScenario::new(util_name!()); + let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); + eprintln!("{expected_stdout}"); ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } From 1c76b4fd2436d74444c25ceb177b4814116080b9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Jan 2025 05:28:18 +0000 Subject: [PATCH 302/351] fix(deps): update rust crate proc-macro2 to v1.0.93 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47a81c08006..cb8a00b4bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1725,9 +1725,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] From 57704144242f11c0f63c9d6b3fafd4d8259c21b5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 11 Jan 2025 06:59:02 +0000 Subject: [PATCH 303/351] chore(deps): update rust crate notify to v8 --- Cargo.lock | 149 ++++++++++++----------------------------------------- Cargo.toml | 2 +- 2 files changed, 35 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1235384ce0a..fe6cfc59499 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,7 +172,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -194,9 +194,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" [[package]] name = "bitvec" @@ -649,16 +649,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.4" @@ -693,10 +683,10 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "crossterm_winapi", "filedescriptor", - "mio 1.0.2", + "mio", "parking_lot", "rustix 0.38.40", "signal-hook", @@ -873,7 +863,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22be12de19decddab85d09f251ec8363f060ccb22ec9c81bc157c0c8433946d8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "log", "scopeguard", "uuid", @@ -1149,11 +1139,11 @@ dependencies = [ [[package]] name = "inotify" -version = "0.9.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.7.0", "inotify-sys", "libc", ] @@ -1228,9 +1218,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" dependencies = [ "kqueue-sys", "libc", @@ -1238,9 +1228,9 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", @@ -1280,7 +1270,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "libc", "redox_syscall", ] @@ -1378,18 +1368,6 @@ dependencies = [ "adler2", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", -] - [[package]] name = "mio" version = "1.0.2" @@ -1409,7 +1387,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "cfg-if", "cfg_aliases", "libc", @@ -1427,22 +1405,29 @@ dependencies = [ [[package]] name = "notify" -version = "6.0.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" +checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" dependencies = [ - "bitflags 1.3.2", - "crossbeam-channel", + "bitflags 2.7.0", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", - "mio 0.8.11", + "log", + "mio", + "notify-types", "walkdir", - "windows-sys 0.45.0", + "windows-sys 0.59.0", ] +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + [[package]] name = "nu-ansi-term" version = "0.50.0" @@ -1738,7 +1723,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "hex", "procfs-core", "rustix 0.38.40", @@ -1750,7 +1735,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "hex", ] @@ -1840,7 +1825,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", ] [[package]] @@ -1975,7 +1960,7 @@ version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "errno", "libc", "linux-raw-sys 0.4.14", @@ -2009,7 +1994,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0139b2436c81305eb6bda33af151851f75bd62783817b25f44daa371119c30b5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.7.0", "libc", "once_cell", "reference-counted-singleton", @@ -2119,7 +2104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 1.0.2", + "mio", "signal-hook", ] @@ -3658,15 +3643,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3694,21 +3670,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -3740,12 +3701,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3758,12 +3713,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3776,12 +3725,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3800,12 +3743,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3818,12 +3755,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3836,12 +3767,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3854,12 +3779,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 2b62476f518..6d2430a4028 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -307,7 +307,7 @@ memchr = "2.7.2" memmap2 = "0.9.4" nix = { version = "0.29", default-features = false } nom = "7.1.3" -notify = { version = "=6.0.1", features = ["macos_kqueue"] } +notify = { version = "=8.0.0", features = ["macos_kqueue"] } num-bigint = "0.4.4" num-prime = "0.4.4" num-traits = "0.2.19" From 7781f2539500267e7f3f5e419276d5425a46973a Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 11 Jan 2025 08:49:47 +0100 Subject: [PATCH 304/351] deny.toml: remove entries from skip list --- deny.toml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/deny.toml b/deny.toml index 4208b9245ce..952094a5fe7 100644 --- a/deny.toml +++ b/deny.toml @@ -57,31 +57,13 @@ skip = [ { name = "linux-raw-sys", version = "0.3.8" }, # terminal_size { name = "rustix", version = "0.37.26" }, - # notify - { name = "windows-sys", version = "0.45.0" }, # various crates { name = "windows-sys", version = "0.48.0" }, # various crates { name = "windows-sys", version = "0.52.0" }, # windows-sys - { name = "windows-targets", version = "0.42.2" }, - # windows-sys { name = "windows-targets", version = "0.48.0" }, # windows-targets - { name = "windows_aarch64_gnullvm", version = "0.42.2" }, - # windows-targets - { name = "windows_aarch64_msvc", version = "0.42.2" }, - # windows-targets - { name = "windows_i686_gnu", version = "0.42.2" }, - # windows-targets - { name = "windows_i686_msvc", version = "0.42.2" }, - # windows-targets - { name = "windows_x86_64_gnu", version = "0.42.2" }, - # windows-targets - { name = "windows_x86_64_gnullvm", version = "0.42.2" }, - # windows-targets - { name = "windows_x86_64_msvc", version = "0.42.2" }, - # windows-targets { name = "windows_aarch64_gnullvm", version = "0.48.0" }, # windows-targets { name = "windows_aarch64_msvc", version = "0.48.0" }, @@ -103,8 +85,6 @@ skip = [ { name = "terminal_size", version = "0.2.6" }, # ansi-width, console, os_display { name = "unicode-width", version = "0.1.13" }, - # notify - { name = "mio", version = "0.8.11" }, # various crates { name = "thiserror", version = "1.0.69" }, # thiserror From c47ead363945d89e96b9266827887f402283513d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Dec 2024 14:20:21 +0100 Subject: [PATCH 305/351] chmod: split a test into two as they are testing different things --- tests/by-util/test_chmod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 167e79cd070..26ad104e9be 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -229,7 +229,10 @@ fn test_chmod_ugoa() { }, ]; run_tests(tests); +} +#[test] +fn test_chmod_error_permissions() { // check that we print an error if umask prevents us from removing a permission let (at, mut ucmd) = at_and_ucmd!(); at.touch("file"); From b0126fdc687104c034a29542e709ccda4d0e93f6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Dec 2024 17:05:49 +0100 Subject: [PATCH 306/351] chmod: Implement --dereference, -L, -H, etc Should fix tests/chmod/symlinks.sh --- src/uu/chmod/src/chmod.rs | 81 +++++++++- tests/by-util/test_chmod.rs | 290 +++++++++++++++++++++++++++++++++++- 2 files changed, 362 insertions(+), 9 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index c05288e213f..c51de50f9bc 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -16,6 +16,7 @@ use uucore::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; +use uucore::perms::TraverseSymlinks; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error}; const ABOUT: &str = help_about!("chmod.md"); @@ -33,6 +34,11 @@ mod options { pub const RECURSIVE: &str = "recursive"; pub const MODE: &str = "MODE"; pub const FILE: &str = "FILE"; + // TODO remove duplication with perms.rs + pub mod dereference { + pub const DEREFERENCE: &str = "dereference"; + pub const NO_DEREFERENCE: &str = "no-dereference"; + } } /// Extract negative modes (starting with '-') from the rest of the arguments. @@ -99,7 +105,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let quiet = matches.get_flag(options::QUIET); let verbose = matches.get_flag(options::VERBOSE); let preserve_root = matches.get_flag(options::PRESERVE_ROOT); - let recursive = matches.get_flag(options::RECURSIVE); let fmode = match matches.get_one::(options::REFERENCE) { Some(fref) => match fs::metadata(fref) { Ok(meta) => Some(meta.mode() & 0o7777), @@ -138,6 +143,34 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(UUsageError::new(1, "missing operand".to_string())); } + let mut dereference = if matches.get_flag(options::dereference::DEREFERENCE) { + Some(true) // Follow symlinks + } else if matches.get_flag(options::dereference::NO_DEREFERENCE) { + Some(false) // Do not follow symlinks + } else { + None // Default behavior + }; + + let mut traverse_symlinks = if matches.get_flag("L") { + TraverseSymlinks::All + } else if matches.get_flag("H") { + TraverseSymlinks::First + } else { + TraverseSymlinks::None + }; + + let recursive = matches.get_flag(options::RECURSIVE); + if recursive { + if traverse_symlinks == TraverseSymlinks::None { + if dereference == Some(true) { + return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); + } + dereference = Some(false); + } + } else { + traverse_symlinks = TraverseSymlinks::None; + } + let chmoder = Chmoder { changes, quiet, @@ -146,6 +179,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { recursive, fmode, cmode, + traverse_symlinks, + dereference: dereference.unwrap_or(true), }; chmoder.chmod(&files) @@ -237,6 +272,8 @@ struct Chmoder { recursive: bool, fmode: Option, cmode: Option, + traverse_symlinks: TraverseSymlinks, + dereference: bool, } impl Chmoder { @@ -248,12 +285,19 @@ impl Chmoder { let file = Path::new(filename); if !file.exists() { if file.is_symlink() { + if !self.dereference && !self.recursive { + // The file is a symlink and we should not follow it + // Don't try to change the mode of the symlink itself + continue; + } if !self.quiet { show!(USimpleError::new( 1, format!("cannot operate on dangling symlink {}", filename.quote()), )); + set_exit_code(1); } + if self.verbose { println!( "failed to change mode of {} from 0000 (---------) to 1500 (r-x-----T)", @@ -273,6 +317,11 @@ impl Chmoder { // So we set the exit code, because it hasn't been set yet if `self.quiet` is true. set_exit_code(1); continue; + } else if !self.dereference && file.is_symlink() { + // The file is a symlink and we should not follow it + // chmod 755 --no-dereference a/link + // should not change the permissions in this case + continue; } if self.recursive && self.preserve_root && filename == "/" { return Err(USimpleError::new( @@ -294,11 +343,23 @@ impl Chmoder { fn walk_dir(&self, file_path: &Path) -> UResult<()> { let mut r = self.chmod_file(file_path); - if !file_path.is_symlink() && file_path.is_dir() { + // Determine whether to traverse symlinks based on `self.traverse_symlinks` + let should_follow_symlink = match self.traverse_symlinks { + TraverseSymlinks::All => true, + TraverseSymlinks::First => { + file_path == file_path.canonicalize().unwrap_or(file_path.to_path_buf()) + } + TraverseSymlinks::None => false, + }; + + // If the path is a directory (or we should follow symlinks), recurse into it + if (!file_path.is_symlink() || should_follow_symlink) && file_path.is_dir() { for dir_entry in file_path.read_dir()? { let path = dir_entry?.path(); if !path.is_symlink() { r = self.walk_dir(path.as_path()); + } else if should_follow_symlink { + r = self.chmod_file(path.as_path()).and(r); } } } @@ -316,17 +377,25 @@ impl Chmoder { fn chmod_file(&self, file: &Path) -> UResult<()> { use uucore::mode::get_umask; - let fperm = match fs::metadata(file) { + // Determine metadata based on dereference flag + let metadata = if self.dereference { + file.metadata() // Follow symlinks + } else { + file.symlink_metadata() // Act on the symlink itself + }; + + let fperm = match metadata { Ok(meta) => meta.mode() & 0o7777, Err(err) => { - if file.is_symlink() { + // Handle dangling symlinks or other errors + if file.is_symlink() && !self.dereference { if self.verbose { println!( "neither symbolic link {} nor referent has been changed", file.quote() ); } - return Ok(()); + return Ok(()); // Skip dangling symlinks } else if err.kind() == std::io::ErrorKind::PermissionDenied { // These two filenames would normally be conditionally // quoted, but GNU's tests expect them to always be quoted @@ -339,6 +408,8 @@ impl Chmoder { } } }; + + // Determine the new permissions to apply match self.fmode { Some(mode) => self.change_file(fperm, mode, file)?, None => { diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 26ad104e9be..be4ce6761a3 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -231,20 +231,50 @@ fn test_chmod_ugoa() { run_tests(tests); } +#[test] +#[cfg(any(target_os = "linux", target_os = "macos"))] +// TODO fix android, it has 0777 +// We should force for the umask on startup +fn test_chmod_umask_expected() { + let current_umask = uucore::mode::get_umask(); + assert_eq!( + current_umask, + 0o022, + "Unexpected umask value: expected 022 (octal), but got {:03o}. Please adjust the test environment.", + current_umask + ); +} + +fn get_expected_symlink_permissions() -> u32 { + #[cfg(any(target_os = "linux", target_os = "android"))] + { + 0o120_777 + } + #[cfg(not(any(target_os = "linux", target_os = "android")))] + { + 0o120_755 + } +} + #[test] fn test_chmod_error_permissions() { // check that we print an error if umask prevents us from removing a permission let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file"); set_permissions(at.plus("file"), Permissions::from_mode(0o777)).unwrap(); + ucmd.args(&["-w", "file"]) + .umask(0o022) .fails() .code_is(1) - // spell-checker:disable-next-line - .stderr_is("chmod: file: new permissions are r-xrwxrwx, not r-xr-xr-x\n"); + .stderr_is( + // spell-checker:disable-next-line + "chmod: file: new permissions are r-xrwxrwx, not r-xr-xr-x\n", + ); assert_eq!( metadata(at.plus("file")).unwrap().permissions().mode(), - 0o100577 + 0o100_577 ); } @@ -645,7 +675,10 @@ fn test_chmod_file_symlink_after_non_existing_file() { .stderr_contains(expected_stderr); assert_eq!( at.metadata(test_existing_symlink).permissions().mode(), - 0o100_764 + 0o100_764, + "Expected mode: {:o}, but got: {:o}", + 0o100_764, + at.metadata(test_existing_symlink).permissions().mode() ); } @@ -749,3 +782,252 @@ fn test_gnu_special_options() { scene.ucmd().arg("--").arg("--").arg("file").succeeds(); scene.ucmd().arg("--").arg("--").fails(); } + +#[test] +fn test_chmod_dereference_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let target = "file"; + let symlink = "symlink"; + + at.touch(target); + set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); + at.symlink_file(target, symlink); + + // Use --dereference: should modify the target file's permissions + scene + .ucmd() + .arg("--dereference") + .arg("u+x") + .arg(symlink) + .succeeds() + .no_stderr(); + assert_eq!(at.metadata(target).permissions().mode(), 0o100_764); + assert_eq!( + at.symlink_metadata(symlink).permissions().mode(), + get_expected_symlink_permissions() + ); +} + +#[test] +fn test_chmod_no_dereference_symlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let target = "file"; + let symlink = "symlink"; + + at.touch(target); + set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); + at.symlink_file(target, symlink); + + // Use --no-dereference: should modify the symlink itself + scene + .ucmd() + .arg("--no-dereference") + .arg("u+x") + .arg(symlink) + .succeeds() + .no_stderr(); + assert_eq!(at.metadata(target).permissions().mode(), 0o100_664); + assert_eq!( + at.symlink_metadata(symlink).permissions().mode(), + get_expected_symlink_permissions() + ); +} + +#[test] +fn test_chmod_traverse_symlink_h() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let target = "file"; + let symlink = "symlink"; + let directory = "dir"; + + at.mkdir(directory); + at.touch(target); + at.symlink_file(target, &format!("{directory}/{symlink}")); + + set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); + + // Use -H: should only follow symlinks specified as arguments + scene + .ucmd() + .arg("-R") + .arg("-H") + .arg("u+x") + .arg(directory) + .succeeds() + .no_stderr(); + + let target_permissions = at.metadata(target).permissions().mode(); + assert_eq!( + target_permissions, 0o100_664, + "Expected target permissions to be 0o100_664, but got {:o}", + target_permissions + ); + + let symlink_path = format!("{directory}/{symlink}"); + let symlink_permissions = at.symlink_metadata(&symlink_path).permissions().mode(); + assert_eq!( + symlink_permissions, + get_expected_symlink_permissions(), + "Expected symlink permissions to be 0o120_777, but got {:o} for symlink at {}", + symlink_permissions, + symlink_path + ); +} + +#[test] +fn test_chmod_traverse_symlink_l() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let target = "file"; + let symlink = "symlink"; + let directory = "dir"; + + at.mkdir(directory); + at.touch(target); + at.symlink_file(target, &format!("{directory}/{symlink}")); + + set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); + + // Use -L: should follow all symlinks + scene + .ucmd() + .arg("-R") + .arg("-L") + .arg("u+x") + .umask(0o022) + .arg(directory) + .succeeds() + .no_stderr(); + + let target_permissions = at.metadata(target).permissions().mode(); + assert_eq!( + target_permissions, 0o100_764, + "Expected target permissions to be 0o100_764, but got {:o}", + target_permissions + ); + + let symlink_path = format!("{directory}/{symlink}"); + let symlink_permissions = at.symlink_metadata(&symlink_path).permissions().mode(); + assert_eq!( + symlink_permissions, + get_expected_symlink_permissions(), + "Expected symlink permissions to be 0o120_777, but got {:o} for symlink at {}", + symlink_permissions, + symlink_path + ); +} + +#[test] +fn test_chmod_traverse_symlink_p() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let target = "file"; + let symlink = "symlink"; + let directory = "dir"; + + at.mkdir(directory); + at.touch(target); + at.symlink_file(target, &format!("{directory}/{symlink}")); + + set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); + + // Use -P: should not follow any symlinks + scene + .ucmd() + .arg("-R") + .arg("-P") + .arg("u+x") + .umask(0o022) + .arg(directory) + .succeeds() + .no_stderr(); + assert_eq!(at.metadata(target).permissions().mode(), 0o100_664); + assert_eq!( + at.symlink_metadata(&format!("{directory}/{symlink}")) + .permissions() + .mode(), + get_expected_symlink_permissions() + ); +} + +#[test] +fn test_chmod_symlink_to_dangling_target_dereference() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let dangling_target = "nonexistent_file"; + let symlink = "symlink"; + + at.symlink_file(dangling_target, symlink); + + // Use --dereference: should fail due to dangling symlink + scene + .ucmd() + .arg("--dereference") + .arg("u+x") + .arg(symlink) + .fails() + .stderr_contains(format!("cannot operate on dangling symlink '{}'", symlink)); +} + +#[test] +fn test_chmod_symlink_target_no_dereference() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "a"; + let symlink = "symlink"; + at.touch(file); + at.symlink_file(file, symlink); + set_permissions(at.plus(file), Permissions::from_mode(0o644)).unwrap(); + + scene + .ucmd() + .arg("--no-dereference") + .arg("755") + .arg(symlink) + .succeeds() + .no_stderr(); + assert_eq!( + at.symlink_metadata(file).permissions().mode(), + 0o100_644, + "Expected symlink permissions: {:o}, but got: {:o}", + 0o100_644, + at.symlink_metadata(file).permissions().mode() + ); +} + +#[test] +fn test_chmod_symlink_to_dangling_recursive() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let dangling_target = "nonexistent_file"; + let symlink = "symlink"; + + at.symlink_file(dangling_target, symlink); + + // Use --no-dereference: should succeed and modify the symlink itself + scene + .ucmd() + .arg("755") + .arg("-R") + .arg(symlink) + .fails() + .stderr_is("chmod: cannot operate on dangling symlink 'symlink'\n"); + assert_eq!( + at.symlink_metadata(symlink).permissions().mode(), + get_expected_symlink_permissions(), + "Expected symlink permissions: {:o}, but got: {:o}", + get_expected_symlink_permissions(), + at.symlink_metadata(symlink).permissions().mode() + ); +} From 1a0f41e4ccbe59f7ddfbd003dffbf1d3e28a2cda Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 30 Dec 2024 14:35:36 +0100 Subject: [PATCH 307/351] chmod/chown/chgrp: deduplicate some arg management --- src/uu/chmod/src/chmod.rs | 35 +------------- src/uucore/src/lib/features/perms.rs | 68 ++++++++++++++++------------ 2 files changed, 42 insertions(+), 61 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index c51de50f9bc..8da715e399f 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -16,7 +16,7 @@ use uucore::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; -use uucore::perms::TraverseSymlinks; +use uucore::perms::{configure_symlink_and_recursion, TraverseSymlinks}; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error}; const ABOUT: &str = help_about!("chmod.md"); @@ -34,11 +34,6 @@ mod options { pub const RECURSIVE: &str = "recursive"; pub const MODE: &str = "MODE"; pub const FILE: &str = "FILE"; - // TODO remove duplication with perms.rs - pub mod dereference { - pub const DEREFERENCE: &str = "dereference"; - pub const NO_DEREFERENCE: &str = "no-dereference"; - } } /// Extract negative modes (starting with '-') from the rest of the arguments. @@ -143,33 +138,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(UUsageError::new(1, "missing operand".to_string())); } - let mut dereference = if matches.get_flag(options::dereference::DEREFERENCE) { - Some(true) // Follow symlinks - } else if matches.get_flag(options::dereference::NO_DEREFERENCE) { - Some(false) // Do not follow symlinks - } else { - None // Default behavior - }; - - let mut traverse_symlinks = if matches.get_flag("L") { - TraverseSymlinks::All - } else if matches.get_flag("H") { - TraverseSymlinks::First - } else { - TraverseSymlinks::None - }; - - let recursive = matches.get_flag(options::RECURSIVE); - if recursive { - if traverse_symlinks == TraverseSymlinks::None { - if dereference == Some(true) { - return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); - } - dereference = Some(false); - } - } else { - traverse_symlinks = TraverseSymlinks::None; - } + let (recursive, dereference, traverse_symlinks) = configure_symlink_and_recursion(&matches)?; let chmoder = Chmoder { changes, diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 73b84be721f..dcb797341de 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -516,6 +516,45 @@ pub struct GidUidOwnerFilter { } type GidUidFilterOwnerParser = fn(&ArgMatches) -> UResult; +/// Determines symbolic link traversal and recursion settings based on flags. +/// Returns the updated `dereference` and `traverse_symlinks` values. +pub fn configure_symlink_and_recursion( + matches: &ArgMatches, +) -> Result<(bool, Option, TraverseSymlinks), Box> { + let mut dereference = if matches.get_flag(options::dereference::DEREFERENCE) { + Some(true) // Follow symlinks + } else if matches.get_flag(options::dereference::NO_DEREFERENCE) { + Some(false) // Do not follow symlinks + } else { + None // Default behavior + }; + + let mut traverse_symlinks = if matches.get_flag("L") { + TraverseSymlinks::All + } else if matches.get_flag("H") { + TraverseSymlinks::First + } else { + TraverseSymlinks::None + }; + + let recursive = matches.get_flag(options::RECURSIVE); + if recursive { + if traverse_symlinks == TraverseSymlinks::None { + if dereference == Some(true) { + return Err(USimpleError::new( + 1, + "-R --dereference requires -H or -L".to_string(), + )); + } + dereference = Some(false); + } + } else { + traverse_symlinks = TraverseSymlinks::None; + } + + Ok((recursive, dereference, traverse_symlinks)) +} + /// Base implementation for `chgrp` and `chown`. /// /// An argument called `add_arg_if_not_reference` will be added to `command` if @@ -571,34 +610,7 @@ pub fn chown_base( .unwrap_or_default(); let preserve_root = matches.get_flag(options::preserve_root::PRESERVE); - - let mut dereference = if matches.get_flag(options::dereference::DEREFERENCE) { - Some(true) - } else if matches.get_flag(options::dereference::NO_DEREFERENCE) { - Some(false) - } else { - None - }; - - let mut traverse_symlinks = if matches.get_flag(options::traverse::TRAVERSE) { - TraverseSymlinks::First - } else if matches.get_flag(options::traverse::EVERY) { - TraverseSymlinks::All - } else { - TraverseSymlinks::None - }; - - let recursive = matches.get_flag(options::RECURSIVE); - if recursive { - if traverse_symlinks == TraverseSymlinks::None { - if dereference == Some(true) { - return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); - } - dereference = Some(false); - } - } else { - traverse_symlinks = TraverseSymlinks::None; - } + let (recursive, dereference, traverse_symlinks) = configure_symlink_and_recursion(&matches)?; let verbosity_level = if matches.get_flag(options::verbosity::CHANGES) { VerbosityLevel::Changes From bb3741067ad0662cf00d7e9155a7767276c14bdd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 30 Dec 2024 14:42:27 +0100 Subject: [PATCH 308/351] chmod/chown/chgrp: deduplicate get metadata --- src/uu/chmod/src/chmod.rs | 9 ++------- src/uucore/src/lib/features/perms.rs | 16 +++++++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 8da715e399f..bde2ae77339 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -344,14 +344,9 @@ impl Chmoder { } #[cfg(unix)] fn chmod_file(&self, file: &Path) -> UResult<()> { - use uucore::mode::get_umask; + use uucore::{mode::get_umask, perms::get_metadata}; - // Determine metadata based on dereference flag - let metadata = if self.dereference { - file.metadata() // Follow symlinks - } else { - file.symlink_metadata() // Act on the symlink itself - }; + let metadata = get_metadata(file, self.dereference); let fperm = match metadata { Ok(meta) => meta.mode() & 0o7777, diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index dcb797341de..2143358200a 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -250,6 +250,14 @@ fn is_root(path: &Path, would_traverse_symlink: bool) -> bool { false } +pub fn get_metadata(file: &Path, follow: bool) -> Result { + if follow { + file.metadata() + } else { + file.symlink_metadata() + } +} + impl ChownExecutor { pub fn exec(&self) -> UResult<()> { let mut ret = 0; @@ -417,11 +425,9 @@ impl ChownExecutor { fn obtain_meta>(&self, path: P, follow: bool) -> Option { let path = path.as_ref(); - let meta = if follow { - path.metadata() - } else { - path.symlink_metadata() - }; + + let meta = get_metadata(path, follow); + match meta { Err(e) => { match self.verbosity.level { From 78b09f400b6b5cd5acc09b5fb7c51899e59ad40e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Dec 2024 09:24:03 +0100 Subject: [PATCH 309/351] chmod: merge some tests into one --- tests/by-util/test_chmod.rs | 184 ++++++++++++------------------------ 1 file changed, 63 insertions(+), 121 deletions(-) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index be4ce6761a3..c2f8197bb7e 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -837,127 +837,6 @@ fn test_chmod_no_dereference_symlink() { ); } -#[test] -fn test_chmod_traverse_symlink_h() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let target = "file"; - let symlink = "symlink"; - let directory = "dir"; - - at.mkdir(directory); - at.touch(target); - at.symlink_file(target, &format!("{directory}/{symlink}")); - - set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); - - // Use -H: should only follow symlinks specified as arguments - scene - .ucmd() - .arg("-R") - .arg("-H") - .arg("u+x") - .arg(directory) - .succeeds() - .no_stderr(); - - let target_permissions = at.metadata(target).permissions().mode(); - assert_eq!( - target_permissions, 0o100_664, - "Expected target permissions to be 0o100_664, but got {:o}", - target_permissions - ); - - let symlink_path = format!("{directory}/{symlink}"); - let symlink_permissions = at.symlink_metadata(&symlink_path).permissions().mode(); - assert_eq!( - symlink_permissions, - get_expected_symlink_permissions(), - "Expected symlink permissions to be 0o120_777, but got {:o} for symlink at {}", - symlink_permissions, - symlink_path - ); -} - -#[test] -fn test_chmod_traverse_symlink_l() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let target = "file"; - let symlink = "symlink"; - let directory = "dir"; - - at.mkdir(directory); - at.touch(target); - at.symlink_file(target, &format!("{directory}/{symlink}")); - - set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); - - // Use -L: should follow all symlinks - scene - .ucmd() - .arg("-R") - .arg("-L") - .arg("u+x") - .umask(0o022) - .arg(directory) - .succeeds() - .no_stderr(); - - let target_permissions = at.metadata(target).permissions().mode(); - assert_eq!( - target_permissions, 0o100_764, - "Expected target permissions to be 0o100_764, but got {:o}", - target_permissions - ); - - let symlink_path = format!("{directory}/{symlink}"); - let symlink_permissions = at.symlink_metadata(&symlink_path).permissions().mode(); - assert_eq!( - symlink_permissions, - get_expected_symlink_permissions(), - "Expected symlink permissions to be 0o120_777, but got {:o} for symlink at {}", - symlink_permissions, - symlink_path - ); -} - -#[test] -fn test_chmod_traverse_symlink_p() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - let target = "file"; - let symlink = "symlink"; - let directory = "dir"; - - at.mkdir(directory); - at.touch(target); - at.symlink_file(target, &format!("{directory}/{symlink}")); - - set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); - - // Use -P: should not follow any symlinks - scene - .ucmd() - .arg("-R") - .arg("-P") - .arg("u+x") - .umask(0o022) - .arg(directory) - .succeeds() - .no_stderr(); - assert_eq!(at.metadata(target).permissions().mode(), 0o100_664); - assert_eq!( - at.symlink_metadata(&format!("{directory}/{symlink}")) - .permissions() - .mode(), - get_expected_symlink_permissions() - ); -} - #[test] fn test_chmod_symlink_to_dangling_target_dereference() { let scene = TestScenario::new(util_name!()); @@ -1031,3 +910,66 @@ fn test_chmod_symlink_to_dangling_recursive() { at.symlink_metadata(symlink).permissions().mode() ); } + +#[test] +fn test_chmod_traverse_symlink_combo() { + let scenarios = [ + ( + vec!["-R", "-H"], + 0o100_664, + get_expected_symlink_permissions(), + ), + ( + vec!["-R", "-L"], + 0o100_764, + get_expected_symlink_permissions(), + ), + ( + vec!["-R", "-P"], + 0o100_664, + get_expected_symlink_permissions(), + ), + ]; + + for (flags, expected_target_perms, expected_symlink_perms) in scenarios { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let directory = "dir"; + let target = "file"; + let symlink = "symlink"; + + at.mkdir(directory); + at.touch(target); + at.symlink_file(target, &format!("{directory}/{symlink}")); + + set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); + + let mut ucmd = scene.ucmd(); + for f in &flags { + ucmd.arg(f); + } + ucmd.arg("u+x") + .umask(0o022) + .arg(directory) + .succeeds() + .no_stderr(); + + let actual_target = at.metadata(target).permissions().mode(); + assert_eq!( + actual_target, expected_target_perms, + "For flags {:?}, expected target perms = {:o}, got = {:o}", + flags, expected_target_perms, actual_target + ); + + let actual_symlink = at + .symlink_metadata(&format!("{directory}/{symlink}")) + .permissions() + .mode(); + assert_eq!( + actual_symlink, expected_symlink_perms, + "For flags {:?}, expected symlink perms = {:o}, got = {:o}", + flags, expected_symlink_perms, actual_symlink + ); + } +} From 22ade13d3f9a0ed93272bf493c9caf0e973015e0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 Jan 2025 13:44:50 +0100 Subject: [PATCH 310/351] Refresh cargo.lock Done with: $ cargo +1.79.0 update --- Cargo.lock | 419 +++++++++++++++++++++++++++-------------------------- 1 file changed, 210 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe6cfc59499..2e5a31fcf97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,18 +8,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -31,9 +19,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -56,7 +44,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219e3ce6f2611d83b51ec2098a12702112c29e57203a6b0a0929b2cddb486608" dependencies = [ - "unicode-width 0.1.13", + "unicode-width 0.1.14", ] [[package]] @@ -82,20 +70,20 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -110,30 +98,30 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bigdecimal" @@ -183,7 +171,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -236,9 +224,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -274,9 +262,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.1.13" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "ad0cf6e91fde44c773c6ee7ec6bba798504641a8bc2eb7e37a04ffbf4dfaa55a" dependencies = [ "shlex", ] @@ -316,9 +304,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -374,9 +362,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "compare" @@ -386,22 +374,22 @@ checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3" [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width 0.1.13", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] name = "const-random" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11df32a13d7892ec42d51d3d175faba5211ffe13ed25d4fb348ac9e9ce835593" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", ] @@ -425,9 +413,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "coreutils" @@ -601,7 +589,7 @@ dependencies = [ "lazy_static", "proc-macro2", "regex", - "syn 2.0.87", + "syn 2.0.96", "unicode-xid", ] @@ -613,7 +601,7 @@ checksum = "25fcfea2ee05889597d35e986c2ad0169694320ae5cc8f6d2640a4bb8a884560" dependencies = [ "lazy_static", "proc-macro2", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -628,14 +616,14 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -651,31 +639,28 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.17" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" @@ -688,7 +673,7 @@ dependencies = [ "filedescriptor", "mio", "parking_lot", - "rustix 0.38.40", + "rustix 0.38.43", "signal-hook", "signal-hook-mio", "winapi", @@ -766,13 +751,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -799,14 +784,14 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "dlv-list" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d529fd73d344663edfd598ccb3f344e46034db51ebd103518eae34338248ad73" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" dependencies = [ "const-random", ] @@ -831,15 +816,15 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.8.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" @@ -849,12 +834,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -871,9 +856,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "file_diff" @@ -920,6 +905,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "fs_extra" version = "1.3.0" @@ -980,7 +971,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -1017,9 +1008,9 @@ checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1054,12 +1045,19 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", ] [[package]] @@ -1093,16 +1091,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows-core", ] [[package]] @@ -1116,12 +1114,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -1194,24 +1192,25 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -1250,19 +1249,19 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "winapi", + "windows-targets 0.52.6", ] [[package]] name = "libm" -version = "0.2.7" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -1283,15 +1282,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1305,17 +1304,17 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.22" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "3d6ea2a48c204030ee31a7d7fc72c93294c92fe87ecb1789881c9543516e1a0d" [[package]] name = "lru" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -1361,20 +1360,19 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "log", "wasi", @@ -1430,11 +1428,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" [[package]] name = "nu-ansi-term" -version = "0.50.0" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1501,9 +1499,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] @@ -1549,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1558,14 +1556,14 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6229bad892b46b0dcfaaeb18ad0d2e56400f5aaea05b768bde96e73676cf75" dependencies = [ - "unicode-width 0.1.13", + "unicode-width 0.1.14", ] [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1616,9 +1614,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -1635,9 +1633,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1647,9 +1645,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "platform-info" @@ -1663,9 +1661,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" @@ -1675,9 +1673,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "pretty_assertions" @@ -1691,12 +1692,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.19" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" +checksum = "483f8c21f64f3ea09fe0f30f5d48c3e8eefe5dac9129f0075f76593b4c1da705" dependencies = [ "proc-macro2", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -1726,7 +1727,7 @@ dependencies = [ "bitflags 2.7.0", "hex", "procfs-core", - "rustix 0.38.40", + "rustix 0.38.43", ] [[package]] @@ -1821,18 +1822,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.7.0", ] [[package]] name = "reference-counted-singleton" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" +checksum = "5daffa8f5ca827e146485577fa9dba9bd9c6921e06e954ab8f6408c10f753086" [[package]] name = "regex" @@ -1848,9 +1849,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1880,9 +1881,9 @@ dependencies = [ [[package]] name = "roff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" +checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "rstest" @@ -1910,7 +1911,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.87", + "syn 2.0.96", "unicode-ident", ] @@ -1942,9 +1943,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.26" +version = "0.37.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" dependencies = [ "bitflags 1.3.2", "errno", @@ -1956,15 +1957,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ "bitflags 2.7.0", "errno", "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] @@ -2004,9 +2005,9 @@ dependencies = [ [[package]] name = "selinux-sys" -version = "0.6.12" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d557667087c5b4791e180b80979cd1a92fdb9bfd92cfd4b9ab199c4d7402423" +checksum = "e5e6e2b8e07a8ff45c90f8e3611bf10c4da7a28d73a26f9ede04f927da234f52" dependencies = [ "bindgen", "cc", @@ -2016,9 +2017,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" @@ -2046,7 +2047,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -2110,9 +2111,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2131,9 +2132,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] @@ -2155,18 +2156,18 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2188,9 +2189,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -2213,7 +2214,7 @@ dependencies = [ "fastrand", "getrandom", "once_cell", - "rustix 0.38.40", + "rustix 0.38.43", "windows-sys 0.59.0", ] @@ -2223,7 +2224,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ - "rustix 0.37.26", + "rustix 0.37.28", "windows-sys 0.48.0", ] @@ -2233,7 +2234,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ - "rustix 0.38.40", + "rustix 0.38.43", "windows-sys 0.59.0", ] @@ -2246,7 +2247,7 @@ dependencies = [ "smawk", "terminal_size 0.2.6", "unicode-linebreak", - "unicode-width 0.1.13", + "unicode-width 0.1.14", ] [[package]] @@ -2275,7 +2276,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -2286,7 +2287,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -2356,15 +2357,15 @@ checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -2380,9 +2381,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" @@ -2392,9 +2393,9 @@ checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unindent" @@ -2404,9 +2405,9 @@ checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utmp-classic" @@ -3485,9 +3486,9 @@ version = "0.0.28" [[package]] name = "uuid" -version = "1.7.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4" [[package]] name = "uutils_term_grid" @@ -3500,9 +3501,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -3522,34 +3523,34 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3557,22 +3558,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-time" @@ -3793,9 +3794,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] @@ -3816,8 +3817,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", - "linux-raw-sys 0.4.14", - "rustix 0.38.40", + "linux-raw-sys 0.4.15", + "rustix 0.38.43", ] [[package]] @@ -3834,9 +3835,9 @@ checksum = "2a599daf1b507819c1121f0bf87fa37eb19daac6aff3aefefd4e6e2e0f2020fc" [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", @@ -3844,13 +3845,13 @@ dependencies = [ [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] From 55ec19ae562ca100ba17eeeefaa8b3e785c5b534 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 Jan 2025 13:33:00 +0100 Subject: [PATCH 311/351] deny: ignore hashbrown duplication --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index 952094a5fe7..ae4147a086d 100644 --- a/deny.toml +++ b/deny.toml @@ -91,6 +91,8 @@ skip = [ { name = "thiserror-impl", version = "1.0.69" }, # bindgen { name = "itertools", version = "0.13.0" }, + # indexmap + { name = "hashbrown", version = "0.14.5" }, ] # spell-checker: enable From 354c1d27ecc16dec45110b3a290092be53895b57 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 Jan 2025 13:48:34 +0100 Subject: [PATCH 312/351] deny: add Unicode 3.0 and zlib as allowed license --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index ae4147a086d..26937bc653a 100644 --- a/deny.toml +++ b/deny.toml @@ -26,6 +26,8 @@ allow = [ "BSL-1.0", "CC0-1.0", "Unicode-DFS-2016", + "Unicode-3.0", + "Zlib", ] confidence-threshold = 0.8 From aef4234fb5e49dc0d71977eb4a57b7618d5d70c2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 Jan 2025 14:01:27 +0100 Subject: [PATCH 313/351] New release --- Cargo.lock | 214 ++++++++++++------------- Cargo.toml | 214 ++++++++++++------------- src/uu/arch/Cargo.toml | 2 +- src/uu/base32/Cargo.toml | 2 +- src/uu/base64/Cargo.toml | 2 +- src/uu/basename/Cargo.toml | 2 +- src/uu/basenc/Cargo.toml | 2 +- src/uu/cat/Cargo.toml | 2 +- src/uu/chcon/Cargo.toml | 2 +- src/uu/chgrp/Cargo.toml | 2 +- src/uu/chmod/Cargo.toml | 2 +- src/uu/chown/Cargo.toml | 2 +- src/uu/chroot/Cargo.toml | 2 +- src/uu/cksum/Cargo.toml | 2 +- src/uu/comm/Cargo.toml | 2 +- src/uu/cp/Cargo.toml | 2 +- src/uu/csplit/Cargo.toml | 2 +- src/uu/cut/Cargo.toml | 2 +- src/uu/date/Cargo.toml | 2 +- src/uu/dd/Cargo.toml | 2 +- src/uu/df/Cargo.toml | 2 +- src/uu/dir/Cargo.toml | 2 +- src/uu/dircolors/Cargo.toml | 2 +- src/uu/dirname/Cargo.toml | 2 +- src/uu/du/Cargo.toml | 2 +- src/uu/echo/Cargo.toml | 2 +- src/uu/env/Cargo.toml | 2 +- src/uu/expand/Cargo.toml | 2 +- src/uu/expr/Cargo.toml | 2 +- src/uu/factor/Cargo.toml | 2 +- src/uu/false/Cargo.toml | 2 +- src/uu/fmt/Cargo.toml | 2 +- src/uu/fold/Cargo.toml | 2 +- src/uu/groups/Cargo.toml | 2 +- src/uu/hashsum/Cargo.toml | 2 +- src/uu/head/Cargo.toml | 2 +- src/uu/hostid/Cargo.toml | 2 +- src/uu/hostname/Cargo.toml | 2 +- src/uu/id/Cargo.toml | 2 +- src/uu/install/Cargo.toml | 2 +- src/uu/join/Cargo.toml | 2 +- src/uu/kill/Cargo.toml | 2 +- src/uu/link/Cargo.toml | 2 +- src/uu/ln/Cargo.toml | 2 +- src/uu/logname/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 2 +- src/uu/mkdir/Cargo.toml | 2 +- src/uu/mkfifo/Cargo.toml | 2 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mktemp/Cargo.toml | 2 +- src/uu/more/Cargo.toml | 2 +- src/uu/mv/Cargo.toml | 2 +- src/uu/nice/Cargo.toml | 2 +- src/uu/nl/Cargo.toml | 2 +- src/uu/nohup/Cargo.toml | 2 +- src/uu/nproc/Cargo.toml | 2 +- src/uu/numfmt/Cargo.toml | 2 +- src/uu/od/Cargo.toml | 2 +- src/uu/paste/Cargo.toml | 2 +- src/uu/pathchk/Cargo.toml | 2 +- src/uu/pinky/Cargo.toml | 2 +- src/uu/pr/Cargo.toml | 2 +- src/uu/printenv/Cargo.toml | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/ptx/Cargo.toml | 2 +- src/uu/pwd/Cargo.toml | 2 +- src/uu/readlink/Cargo.toml | 2 +- src/uu/realpath/Cargo.toml | 2 +- src/uu/rm/Cargo.toml | 2 +- src/uu/rmdir/Cargo.toml | 2 +- src/uu/runcon/Cargo.toml | 2 +- src/uu/seq/Cargo.toml | 2 +- src/uu/shred/Cargo.toml | 2 +- src/uu/shuf/Cargo.toml | 2 +- src/uu/sleep/Cargo.toml | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uu/split/Cargo.toml | 2 +- src/uu/stat/Cargo.toml | 2 +- src/uu/stdbuf/Cargo.toml | 4 +- src/uu/stdbuf/src/libstdbuf/Cargo.toml | 2 +- src/uu/stty/Cargo.toml | 2 +- src/uu/sum/Cargo.toml | 2 +- src/uu/sync/Cargo.toml | 2 +- src/uu/tac/Cargo.toml | 2 +- src/uu/tail/Cargo.toml | 2 +- src/uu/tee/Cargo.toml | 2 +- src/uu/test/Cargo.toml | 2 +- src/uu/timeout/Cargo.toml | 2 +- src/uu/touch/Cargo.toml | 2 +- src/uu/tr/Cargo.toml | 2 +- src/uu/true/Cargo.toml | 2 +- src/uu/truncate/Cargo.toml | 2 +- src/uu/tsort/Cargo.toml | 2 +- src/uu/tty/Cargo.toml | 2 +- src/uu/uname/Cargo.toml | 2 +- src/uu/unexpand/Cargo.toml | 2 +- src/uu/uniq/Cargo.toml | 2 +- src/uu/unlink/Cargo.toml | 2 +- src/uu/uptime/Cargo.toml | 2 +- src/uu/users/Cargo.toml | 2 +- src/uu/vdir/Cargo.toml | 2 +- src/uu/wc/Cargo.toml | 2 +- src/uu/who/Cargo.toml | 2 +- src/uu/whoami/Cargo.toml | 2 +- src/uu/yes/Cargo.toml | 2 +- src/uucore/Cargo.toml | 2 +- src/uucore_procs/Cargo.toml | 4 +- src/uuhelp_parser/Cargo.toml | 2 +- util/update-version.sh | 4 +- 109 files changed, 324 insertions(+), 324 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe6cfc59499..c1aec752173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,7 +431,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.28" +version = "0.0.29" dependencies = [ "bincode", "chrono", @@ -2434,7 +2434,7 @@ dependencies = [ [[package]] name = "uu_arch" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "platform-info", @@ -2443,7 +2443,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2451,7 +2451,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uu_base32", @@ -2460,7 +2460,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2468,7 +2468,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uu_base32", @@ -2477,7 +2477,7 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nix", @@ -2487,7 +2487,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "fts-sys", @@ -2499,7 +2499,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2507,7 +2507,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2516,7 +2516,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2524,7 +2524,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2532,7 +2532,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "hex", @@ -2542,7 +2542,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2550,7 +2550,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "exacl", @@ -2566,7 +2566,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "regex", @@ -2576,7 +2576,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.28" +version = "0.0.29" dependencies = [ "bstr", "clap", @@ -2586,7 +2586,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.28" +version = "0.0.29" dependencies = [ "chrono", "clap", @@ -2598,7 +2598,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "gcd", @@ -2610,7 +2610,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "tempfile", @@ -2620,7 +2620,7 @@ dependencies = [ [[package]] name = "uu_dir" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uu_ls", @@ -2629,7 +2629,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2637,7 +2637,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.28" +version = "0.0.29" dependencies = [ "chrono", "clap", @@ -2656,7 +2656,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2664,7 +2664,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nix", @@ -2674,7 +2674,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "unicode-width 0.2.0", @@ -2683,7 +2683,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "num-bigint", @@ -2694,7 +2694,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "coz", @@ -2708,7 +2708,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2716,7 +2716,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "unicode-width 0.2.0", @@ -2725,7 +2725,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2733,7 +2733,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2741,7 +2741,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "hex", @@ -2752,7 +2752,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "memchr", @@ -2762,7 +2762,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2771,7 +2771,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "dns-lookup", @@ -2782,7 +2782,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "selinux", @@ -2791,7 +2791,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "file_diff", @@ -2802,7 +2802,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "memchr", @@ -2811,7 +2811,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nix", @@ -2820,7 +2820,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2828,7 +2828,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2836,7 +2836,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2845,7 +2845,7 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.28" +version = "0.0.29" dependencies = [ "ansi-width", "chrono", @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2871,7 +2871,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2880,7 +2880,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2889,7 +2889,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "rand", @@ -2899,7 +2899,7 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "crossterm", @@ -2911,7 +2911,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "fs_extra", @@ -2921,7 +2921,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2931,7 +2931,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "regex", @@ -2940,7 +2940,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2949,7 +2949,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2958,7 +2958,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2966,7 +2966,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.28" +version = "0.0.29" dependencies = [ "byteorder", "clap", @@ -2976,7 +2976,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -2984,7 +2984,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -2993,7 +2993,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3001,7 +3001,7 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.28" +version = "0.0.29" dependencies = [ "chrono", "clap", @@ -3013,7 +3013,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3021,7 +3021,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3029,7 +3029,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "regex", @@ -3038,7 +3038,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3046,7 +3046,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3054,7 +3054,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3062,7 +3062,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3073,7 +3073,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3082,7 +3082,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3093,7 +3093,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.28" +version = "0.0.29" dependencies = [ "bigdecimal", "clap", @@ -3104,7 +3104,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3114,7 +3114,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "memchr", @@ -3125,7 +3125,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "fundu", @@ -3134,7 +3134,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.28" +version = "0.0.29" dependencies = [ "binary-heap-plus", "clap", @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "memchr", @@ -3163,7 +3163,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.28" +version = "0.0.29" dependencies = [ "chrono", "clap", @@ -3172,7 +3172,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "tempfile", @@ -3182,7 +3182,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.28" +version = "0.0.29" dependencies = [ "cpp", "cpp_build", @@ -3191,7 +3191,7 @@ dependencies = [ [[package]] name = "uu_stty" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nix", @@ -3200,7 +3200,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3208,7 +3208,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3219,7 +3219,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "memchr", @@ -3230,7 +3230,7 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "fundu", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3255,7 +3255,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3264,7 +3264,7 @@ dependencies = [ [[package]] name = "uu_timeout" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3274,7 +3274,7 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.28" +version = "0.0.29" dependencies = [ "chrono", "clap", @@ -3286,7 +3286,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nom", @@ -3295,7 +3295,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3303,7 +3303,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3311,7 +3311,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3319,7 +3319,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nix", @@ -3328,7 +3328,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "platform-info", @@ -3337,7 +3337,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "unicode-width 0.2.0", @@ -3346,7 +3346,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3354,7 +3354,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3362,7 +3362,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.28" +version = "0.0.29" dependencies = [ "chrono", "clap", @@ -3373,7 +3373,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "utmp-classic", @@ -3382,7 +3382,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uu_ls", @@ -3391,7 +3391,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.28" +version = "0.0.29" dependencies = [ "bytecount", "clap", @@ -3404,7 +3404,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -3412,7 +3412,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -3422,7 +3422,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "itertools 0.14.0", @@ -3432,7 +3432,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.28" +version = "0.0.29" dependencies = [ "blake2b_simd", "blake3", @@ -3472,7 +3472,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.28" +version = "0.0.29" dependencies = [ "proc-macro2", "quote", @@ -3481,7 +3481,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.28" +version = "0.0.29" [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index 6d2430a4028..8fdb15d5d6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -356,10 +356,10 @@ blake3 = "1.5.1" sm3 = "0.4.2" digest = "0.10.7" -uucore = { version = "0.0.28", package = "uucore", path = "src/uucore" } -uucore_procs = { version = "0.0.28", package = "uucore_procs", path = "src/uucore_procs" } -uu_ls = { version = "0.0.28", path = "src/uu/ls" } -uu_base32 = { version = "0.0.28", path = "src/uu/base32" } +uucore = { version = "0.0.29", package = "uucore", path = "src/uucore" } +uucore_procs = { version = "0.0.29", package = "uucore_procs", path = "src/uucore_procs" } +uu_ls = { version = "0.0.29", path = "src/uu/ls" } +uu_base32 = { version = "0.0.29", path = "src/uu/base32" } [dependencies] clap = { workspace = true } @@ -375,109 +375,109 @@ zip = { workspace = true, optional = true } uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" } # * uutils -uu_test = { optional = true, version = "0.0.28", package = "uu_test", path = "src/uu/test" } +uu_test = { optional = true, version = "0.0.29", package = "uu_test", path = "src/uu/test" } # -arch = { optional = true, version = "0.0.28", package = "uu_arch", path = "src/uu/arch" } -base32 = { optional = true, version = "0.0.28", package = "uu_base32", path = "src/uu/base32" } -base64 = { optional = true, version = "0.0.28", package = "uu_base64", path = "src/uu/base64" } -basename = { optional = true, version = "0.0.28", package = "uu_basename", path = "src/uu/basename" } -basenc = { optional = true, version = "0.0.28", package = "uu_basenc", path = "src/uu/basenc" } -cat = { optional = true, version = "0.0.28", package = "uu_cat", path = "src/uu/cat" } -chcon = { optional = true, version = "0.0.28", package = "uu_chcon", path = "src/uu/chcon" } -chgrp = { optional = true, version = "0.0.28", package = "uu_chgrp", path = "src/uu/chgrp" } -chmod = { optional = true, version = "0.0.28", package = "uu_chmod", path = "src/uu/chmod" } -chown = { optional = true, version = "0.0.28", package = "uu_chown", path = "src/uu/chown" } -chroot = { optional = true, version = "0.0.28", package = "uu_chroot", path = "src/uu/chroot" } -cksum = { optional = true, version = "0.0.28", package = "uu_cksum", path = "src/uu/cksum" } -comm = { optional = true, version = "0.0.28", package = "uu_comm", path = "src/uu/comm" } -cp = { optional = true, version = "0.0.28", package = "uu_cp", path = "src/uu/cp" } -csplit = { optional = true, version = "0.0.28", package = "uu_csplit", path = "src/uu/csplit" } -cut = { optional = true, version = "0.0.28", package = "uu_cut", path = "src/uu/cut" } -date = { optional = true, version = "0.0.28", package = "uu_date", path = "src/uu/date" } -dd = { optional = true, version = "0.0.28", package = "uu_dd", path = "src/uu/dd" } -df = { optional = true, version = "0.0.28", package = "uu_df", path = "src/uu/df" } -dir = { optional = true, version = "0.0.28", package = "uu_dir", path = "src/uu/dir" } -dircolors = { optional = true, version = "0.0.28", package = "uu_dircolors", path = "src/uu/dircolors" } -dirname = { optional = true, version = "0.0.28", package = "uu_dirname", path = "src/uu/dirname" } -du = { optional = true, version = "0.0.28", package = "uu_du", path = "src/uu/du" } -echo = { optional = true, version = "0.0.28", package = "uu_echo", path = "src/uu/echo" } -env = { optional = true, version = "0.0.28", package = "uu_env", path = "src/uu/env" } -expand = { optional = true, version = "0.0.28", package = "uu_expand", path = "src/uu/expand" } -expr = { optional = true, version = "0.0.28", package = "uu_expr", path = "src/uu/expr" } -factor = { optional = true, version = "0.0.28", package = "uu_factor", path = "src/uu/factor" } -false = { optional = true, version = "0.0.28", package = "uu_false", path = "src/uu/false" } -fmt = { optional = true, version = "0.0.28", package = "uu_fmt", path = "src/uu/fmt" } -fold = { optional = true, version = "0.0.28", package = "uu_fold", path = "src/uu/fold" } -groups = { optional = true, version = "0.0.28", package = "uu_groups", path = "src/uu/groups" } -hashsum = { optional = true, version = "0.0.28", package = "uu_hashsum", path = "src/uu/hashsum" } -head = { optional = true, version = "0.0.28", package = "uu_head", path = "src/uu/head" } -hostid = { optional = true, version = "0.0.28", package = "uu_hostid", path = "src/uu/hostid" } -hostname = { optional = true, version = "0.0.28", package = "uu_hostname", path = "src/uu/hostname" } -id = { optional = true, version = "0.0.28", package = "uu_id", path = "src/uu/id" } -install = { optional = true, version = "0.0.28", package = "uu_install", path = "src/uu/install" } -join = { optional = true, version = "0.0.28", package = "uu_join", path = "src/uu/join" } -kill = { optional = true, version = "0.0.28", package = "uu_kill", path = "src/uu/kill" } -link = { optional = true, version = "0.0.28", package = "uu_link", path = "src/uu/link" } -ln = { optional = true, version = "0.0.28", package = "uu_ln", path = "src/uu/ln" } -ls = { optional = true, version = "0.0.28", package = "uu_ls", path = "src/uu/ls" } -logname = { optional = true, version = "0.0.28", package = "uu_logname", path = "src/uu/logname" } -mkdir = { optional = true, version = "0.0.28", package = "uu_mkdir", path = "src/uu/mkdir" } -mkfifo = { optional = true, version = "0.0.28", package = "uu_mkfifo", path = "src/uu/mkfifo" } -mknod = { optional = true, version = "0.0.28", package = "uu_mknod", path = "src/uu/mknod" } -mktemp = { optional = true, version = "0.0.28", package = "uu_mktemp", path = "src/uu/mktemp" } -more = { optional = true, version = "0.0.28", package = "uu_more", path = "src/uu/more" } -mv = { optional = true, version = "0.0.28", package = "uu_mv", path = "src/uu/mv" } -nice = { optional = true, version = "0.0.28", package = "uu_nice", path = "src/uu/nice" } -nl = { optional = true, version = "0.0.28", package = "uu_nl", path = "src/uu/nl" } -nohup = { optional = true, version = "0.0.28", package = "uu_nohup", path = "src/uu/nohup" } -nproc = { optional = true, version = "0.0.28", package = "uu_nproc", path = "src/uu/nproc" } -numfmt = { optional = true, version = "0.0.28", package = "uu_numfmt", path = "src/uu/numfmt" } -od = { optional = true, version = "0.0.28", package = "uu_od", path = "src/uu/od" } -paste = { optional = true, version = "0.0.28", package = "uu_paste", path = "src/uu/paste" } -pathchk = { optional = true, version = "0.0.28", package = "uu_pathchk", path = "src/uu/pathchk" } -pinky = { optional = true, version = "0.0.28", package = "uu_pinky", path = "src/uu/pinky" } -pr = { optional = true, version = "0.0.28", package = "uu_pr", path = "src/uu/pr" } -printenv = { optional = true, version = "0.0.28", package = "uu_printenv", path = "src/uu/printenv" } -printf = { optional = true, version = "0.0.28", package = "uu_printf", path = "src/uu/printf" } -ptx = { optional = true, version = "0.0.28", package = "uu_ptx", path = "src/uu/ptx" } -pwd = { optional = true, version = "0.0.28", package = "uu_pwd", path = "src/uu/pwd" } -readlink = { optional = true, version = "0.0.28", package = "uu_readlink", path = "src/uu/readlink" } -realpath = { optional = true, version = "0.0.28", package = "uu_realpath", path = "src/uu/realpath" } -rm = { optional = true, version = "0.0.28", package = "uu_rm", path = "src/uu/rm" } -rmdir = { optional = true, version = "0.0.28", package = "uu_rmdir", path = "src/uu/rmdir" } -runcon = { optional = true, version = "0.0.28", package = "uu_runcon", path = "src/uu/runcon" } -seq = { optional = true, version = "0.0.28", package = "uu_seq", path = "src/uu/seq" } -shred = { optional = true, version = "0.0.28", package = "uu_shred", path = "src/uu/shred" } -shuf = { optional = true, version = "0.0.28", package = "uu_shuf", path = "src/uu/shuf" } -sleep = { optional = true, version = "0.0.28", package = "uu_sleep", path = "src/uu/sleep" } -sort = { optional = true, version = "0.0.28", package = "uu_sort", path = "src/uu/sort" } -split = { optional = true, version = "0.0.28", package = "uu_split", path = "src/uu/split" } -stat = { optional = true, version = "0.0.28", package = "uu_stat", path = "src/uu/stat" } -stdbuf = { optional = true, version = "0.0.28", package = "uu_stdbuf", path = "src/uu/stdbuf" } -stty = { optional = true, version = "0.0.28", package = "uu_stty", path = "src/uu/stty" } -sum = { optional = true, version = "0.0.28", package = "uu_sum", path = "src/uu/sum" } -sync = { optional = true, version = "0.0.28", package = "uu_sync", path = "src/uu/sync" } -tac = { optional = true, version = "0.0.28", package = "uu_tac", path = "src/uu/tac" } -tail = { optional = true, version = "0.0.28", package = "uu_tail", path = "src/uu/tail" } -tee = { optional = true, version = "0.0.28", package = "uu_tee", path = "src/uu/tee" } -timeout = { optional = true, version = "0.0.28", package = "uu_timeout", path = "src/uu/timeout" } -touch = { optional = true, version = "0.0.28", package = "uu_touch", path = "src/uu/touch" } -tr = { optional = true, version = "0.0.28", package = "uu_tr", path = "src/uu/tr" } -true = { optional = true, version = "0.0.28", package = "uu_true", path = "src/uu/true" } -truncate = { optional = true, version = "0.0.28", package = "uu_truncate", path = "src/uu/truncate" } -tsort = { optional = true, version = "0.0.28", package = "uu_tsort", path = "src/uu/tsort" } -tty = { optional = true, version = "0.0.28", package = "uu_tty", path = "src/uu/tty" } -uname = { optional = true, version = "0.0.28", package = "uu_uname", path = "src/uu/uname" } -unexpand = { optional = true, version = "0.0.28", package = "uu_unexpand", path = "src/uu/unexpand" } -uniq = { optional = true, version = "0.0.28", package = "uu_uniq", path = "src/uu/uniq" } -unlink = { optional = true, version = "0.0.28", package = "uu_unlink", path = "src/uu/unlink" } -uptime = { optional = true, version = "0.0.28", package = "uu_uptime", path = "src/uu/uptime" } -users = { optional = true, version = "0.0.28", package = "uu_users", path = "src/uu/users" } -vdir = { optional = true, version = "0.0.28", package = "uu_vdir", path = "src/uu/vdir" } -wc = { optional = true, version = "0.0.28", package = "uu_wc", path = "src/uu/wc" } -who = { optional = true, version = "0.0.28", package = "uu_who", path = "src/uu/who" } -whoami = { optional = true, version = "0.0.28", package = "uu_whoami", path = "src/uu/whoami" } -yes = { optional = true, version = "0.0.28", package = "uu_yes", path = "src/uu/yes" } +arch = { optional = true, version = "0.0.29", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.29", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.29", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.29", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.29", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.29", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.29", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.29", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.29", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.29", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.29", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.29", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.29", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.29", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.29", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.29", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.29", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.29", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.29", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.29", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.29", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.29", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.29", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.29", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.29", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.29", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.29", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.29", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.29", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.29", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.29", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.29", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.29", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.29", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.29", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.29", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.29", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.29", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.29", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.29", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.29", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.29", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.29", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.29", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.29", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.29", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.29", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.29", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.29", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.29", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.29", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.29", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.29", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.29", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.29", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.29", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.29", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.29", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.29", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.29", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.29", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.29", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.29", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.29", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.29", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.29", package = "uu_realpath", path = "src/uu/realpath" } +rm = { optional = true, version = "0.0.29", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.29", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.29", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.29", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.29", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.29", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.29", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.29", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.29", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.29", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.29", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.29", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.29", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.29", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.29", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.29", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.29", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.29", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.29", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.29", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.29", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.29", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.29", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.29", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.29", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.29", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.29", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.29", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.29", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.29", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.29", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.29", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.29", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.29", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.29", package = "uu_yes", path = "src/uu/yes" } # this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)" # factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index ccf2136f720..d1b8baea7d2 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index 26ab2bc6fe8..b75a4cdc05b 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index 7110b6395b0..4ed327ddc69 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 18f937b091f..31c962019d6 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index d7a2849ca2b..a3bccb72c48 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index f2df1c343ab..7a571c2cc95 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index a4b914ad72a..897fafbe00f 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 778576b887d..ec5e77ea569 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index acfeb6c0409..073abc76eaf 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 0312d84b2a1..be05584b38e 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index 797b6246026..9a9a8290fdf 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 18c1b2899f1..c8693190be7 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index 26d82dbecc6..dd691971555 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 3912f3308a5..43113b4610c 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.28" +version = "0.0.29" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index b2771d8fe52..ec726e9d2b2 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 33046e77321..4a41b4fac4f 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index a99f284713a..75766507655 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_date" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 5bc117ea7cd..ceb85dcc881 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 3eaff87f38e..7de8028108b 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index 73dea05f15a..cfcfc8e9c87 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -C -b" diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 4fd6f95371c..085b6a75f9b 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index ac160ffe975..a53930d7e40 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 5b9a707da50..27ec1700a5f 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index faabc121df1..f3a7a6400b7 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 51a6e817949..9b21ed45ac9 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index fe7a5ba03a9..db3fad7329e 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index b8596976aec..1abf853d760 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 8b0d35bcb0f..08ff64f57b6 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index f8c6d84573a..5c817c754b7 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index c71d9a5df32..6522c909f80 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index 5bfe63ec9c8..cf6d59ad66e 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 14a3cb924f1..11055f529a3 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 42484fabf8f..9ab253bd7b0 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 1ccf3b9851b..3590e11465b 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index e667e76ca5a..4e853ac8edd 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index 716d76f6902..0fa58481717 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index 8be08ee8060..575808042c7 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index 88614cf113d..26507023806 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.28" +version = "0.0.29" authors = ["Ben Eills ", "uutils developers"] license = "MIT" description = "install ~ (uutils) copy files from SOURCE to DESTINATION (with specified attributes)" diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index 322fcbf6bd1..a19b6818f04 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 22b05268ec1..aa7cb4749d3 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 4baf6adcfcd..b8c5df3618e 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index 488262a417c..5b82e211e0a 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index deb1d1229a1..d0bf9a8d6ab 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index de50b4c924b..17cef9b8aa4 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index 6fbce879a8b..80871b11544 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 93ebed94ada..e2605ae1de9 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 9177fe4f466..02e3f4cab8a 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index 0bcd6ac5187..60d0d28997b 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 5af95afe65e..ef0bb8de1e7 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 7e8ccc16c32..4982edd8050 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 3d231aff6c0..afcce849e37 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index 7f3091d942c..d82793761ca 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 55660a99240..df324856107 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index ebcf8b6d2a2..5b65d445f70 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index 1ae42b5d5cf..1313a234e32 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 2ca1678abf4..c713f121f3e 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index f8ea87965a7..dc18d3124b1 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index 24005d30a49..25904bfdbf4 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index cb64288c664..4af298339d4 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 04aec10307f..2e245569e8f 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "pr ~ (uutils) convert text files for printing" diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 65abcdd3381..e5e07ced3c2 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index c37bcffc91e..cad30bd32b4 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.28" +version = "0.0.29" authors = ["Nathan Ross", "uutils developers"] license = "MIT" description = "printf ~ (uutils) FORMAT and display ARGUMENTS" diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index de97790abcb..4d50a7cd419 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index e734c181907..c9290f16b70 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index 4a226cad062..3792bb3de8d 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index dea52663e98..bd0154e2162 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 49e95bb06ce..c45dfe33d8a 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 3637b9130f7..286795e02c4 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index 8bb10acbbbe..cda5fc2f776 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "runcon ~ (uutils) run command with specified security context" diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 99de71c1276..a063061f8fc 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore bigdecimal cfgs [package] name = "uu_seq" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 0f5f1d6aab8..10394565a37 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 8b7ae28f8ea..f8b887b7e87 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 8e72dd82a21..bb6e2e13103 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 799b4831d9d..99c1254c0cf 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index cc69a6bc4cb..8e09eb76d6b 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 191183cfa41..c503426d142 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 0e17c3441e3..75af9db3960 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -22,7 +22,7 @@ tempfile = { workspace = true } uucore = { workspace = true } [build-dependencies] -libstdbuf = { version = "0.0.28", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } +libstdbuf = { version = "0.0.29", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } [[bin]] name = "stdbuf" diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 67a7e903e04..a49832b3434 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf_libstdbuf" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" diff --git a/src/uu/stty/Cargo.toml b/src/uu/stty/Cargo.toml index c38df5819d9..7d34d13f1d4 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stty" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "stty ~ (uutils) print or change terminal characteristics" diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 548bc44e072..1995f11df85 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 5c02cada17e..8ce6cb73adb 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 60d4f3b3e7e..2d9aedb8e40 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index d687a6f1d31..011ee31ceb4 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore (libs) kqueue fundu [package] name = "uu_tail" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index bee28ffb159..282ae46731e 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 6994efeaa5a..16e6376ed69 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 8c5f5dcbe8e..eddcf8222f6 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 1aa788c8d9f..b076ddfd882 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_touch" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 6378b7766ea..a9a0e2089b6 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index 6b1657276d7..e9d85a6c941 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index 9c19a8d8ad4..6845ce27d32 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index 4e179c47f4a..ba53e0d7be8 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 9f81a69cc3c..dac2464d4b7 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index 87446827314..5545445a1a0 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index f6725762d29..b0ed1fa845d 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 9d23e06b0bd..ace29f4701f 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index 380bddcbe73..3152ccd4597 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index bae87dfd3c8..38126a3e956 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index 1f1f5773d28..fa9f4c8271b 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" diff --git a/src/uu/vdir/Cargo.toml b/src/uu/vdir/Cargo.toml index f76f4c96b0f..09fc48c05a8 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_vdir" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -l -b" diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 193e1667915..b3e06e29681 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index f5aab2ecd24..15bed98b70e 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 4f3f7659905..7b24429b0d4 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index 21b5ff7ac33..af1b937b79f 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 5e1a065a610..ee461e048ce 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uucore" -version = "0.0.28" +version = "0.0.29" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 40ad6cd5b42..196544091d3 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uucore_procs" -version = "0.0.28" +version = "0.0.29" authors = ["Roy Ivy III "] license = "MIT" description = "uutils ~ 'uucore' proc-macros" @@ -19,4 +19,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.81" quote = "1.0.36" -uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.28" } +uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.29" } diff --git a/src/uuhelp_parser/Cargo.toml b/src/uuhelp_parser/Cargo.toml index 1df8ae889c6..af46f719595 100644 --- a/src/uuhelp_parser/Cargo.toml +++ b/src/uuhelp_parser/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uuhelp_parser" -version = "0.0.28" +version = "0.0.29" edition = "2021" license = "MIT" description = "A collection of functions to parse the markdown code of help files" diff --git a/util/update-version.sh b/util/update-version.sh index 47e1695ae29..237beb1008d 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -17,8 +17,8 @@ # 10) Create the release on github https://github.com/uutils/coreutils/releases/new # 11) Make sure we have good release notes -FROM="0.0.27" -TO="0.0.28" +FROM="0.0.28" +TO="0.0.29" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml src/uucore/Cargo.toml Cargo.toml) From 6457836f330bc8edb2264b37244234d5e0d1674d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 Jan 2025 16:03:12 +0100 Subject: [PATCH 314/351] chmod: Remove misleading comments --- tests/by-util/test_chmod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index c2f8197bb7e..6f508afd6ce 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -822,7 +822,6 @@ fn test_chmod_no_dereference_symlink() { set_permissions(at.plus(target), Permissions::from_mode(0o664)).unwrap(); at.symlink_file(target, symlink); - // Use --no-dereference: should modify the symlink itself scene .ucmd() .arg("--no-dereference") @@ -894,7 +893,6 @@ fn test_chmod_symlink_to_dangling_recursive() { at.symlink_file(dangling_target, symlink); - // Use --no-dereference: should succeed and modify the symlink itself scene .ucmd() .arg("755") From f042a9a2227128224d42ad5b3d11b3f65ad3ff77 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 11 Jan 2025 16:46:37 +0100 Subject: [PATCH 315/351] chmod: remove the option from configure_symlink_and_recursion --- src/uu/chmod/src/chmod.rs | 2 +- src/uucore/src/lib/features/perms.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index bde2ae77339..d2eb22ce6a6 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -149,7 +149,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { fmode, cmode, traverse_symlinks, - dereference: dereference.unwrap_or(true), + dereference, }; chmoder.chmod(&files) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 2143358200a..62e7d56ed2f 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -526,7 +526,7 @@ type GidUidFilterOwnerParser = fn(&ArgMatches) -> UResult; /// Returns the updated `dereference` and `traverse_symlinks` values. pub fn configure_symlink_and_recursion( matches: &ArgMatches, -) -> Result<(bool, Option, TraverseSymlinks), Box> { +) -> Result<(bool, bool, TraverseSymlinks), Box> { let mut dereference = if matches.get_flag(options::dereference::DEREFERENCE) { Some(true) // Follow symlinks } else if matches.get_flag(options::dereference::NO_DEREFERENCE) { @@ -558,7 +558,7 @@ pub fn configure_symlink_and_recursion( traverse_symlinks = TraverseSymlinks::None; } - Ok((recursive, dereference, traverse_symlinks)) + Ok((recursive, dereference.unwrap_or(true), traverse_symlinks)) } /// Base implementation for `chgrp` and `chown`. @@ -646,7 +646,7 @@ pub fn chown_base( level: verbosity_level, }, recursive, - dereference: dereference.unwrap_or(true), + dereference, preserve_root, files, filter, From f59c7899c3c1f71969e2d61ab5bb5313d73387b9 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 11 Jan 2025 11:25:59 -0500 Subject: [PATCH 316/351] Add more test cases and improve precision --- src/uu/stat/src/stat.rs | 151 ++++++++++++++++++++++--------------- tests/by-util/test_stat.rs | 56 +++++++++++++- 2 files changed, 144 insertions(+), 63 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index a950e98ea67..a6220267314 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -121,6 +121,13 @@ impl std::str::FromStr for QuotingStyle { } } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum Precision { + NotSpecified, + NoNumber, + Number(usize), +} + #[derive(Debug, PartialEq, Eq)] enum Token { Char(char), @@ -128,7 +135,7 @@ enum Token { Directive { flag: Flags, width: usize, - precision: Option, + precision: Precision, format: char, }, } @@ -239,10 +246,10 @@ struct Stater { /// * `output` - A reference to the OutputType enum containing the value to be printed. /// * `flags` - A Flags struct containing formatting flags. /// * `width` - The width of the field for the printed output. -/// * `precision` - An Option containing the precision value. +/// * `precision` - How many digits of precision, if any. /// /// This function delegates the printing process to more specialized functions depending on the output type. -fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option) { +fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Precision) { // If the precision is given as just '.', the precision is taken to be zero. // A negative precision is taken as if the precision were omitted. // This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions, @@ -272,7 +279,7 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option print_str(s, &flags, width, precision), @@ -296,13 +303,12 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Option) -> Padding { - if flags.zero && !flags.left && precision.is_none() { +fn determine_padding_char(flags: &Flags) -> Padding { + if flags.zero && !flags.left { Padding::Zero } else { Padding::Space @@ -316,10 +322,10 @@ fn determine_padding_char(flags: &Flags, precision: &Option) -> Padding { /// * `s` - The string to be printed. /// * `flags` - A reference to the Flags struct containing formatting flags. /// * `width` - The width of the field for the printed string. -/// * `precision` - An Option containing the precision value. -fn print_str(s: &str, flags: &Flags, width: usize, precision: Option) { +/// * `precision` - How many digits of precision, if any. +fn print_str(s: &str, flags: &Flags, width: usize, precision: Precision) { let s = match precision { - Some(p) if p < s.len() => &s[..p], + Precision::Number(p) if p < s.len() => &s[..p], _ => s, }; pad_and_print(s, flags.left, width, Padding::Space); @@ -419,13 +425,13 @@ fn process_token_filesystem(t: &Token, meta: StatFs, display_name: &str) { /// * `num` - The integer value to be printed. /// * `flags` - A reference to the Flags struct containing formatting flags. /// * `width` - The width of the field for the printed integer. -/// * `precision` - An Option containing the precision value. +/// * `precision` - How many digits of precision, if any. /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_integer( num: i64, flags: &Flags, width: usize, - precision: Option, + precision: Precision, padding_char: Padding, ) { let num = num.to_string(); @@ -441,15 +447,16 @@ fn print_integer( } else { "" }; - let extended = format!( - "{prefix}{arg:0>precision$}", - precision = precision.unwrap_or(0) - ); + let extended = match precision { + Precision::NotSpecified => format!("{prefix}{arg}"), + Precision::NoNumber => format!("{prefix}{arg}"), + Precision::Number(p) => format!("{prefix}{arg:0>precision$}", precision = p), + }; pad_and_print(&extended, flags.left, width, padding_char); } /// Truncate a float to the given number of digits after the decimal point. -fn precision_trunc(num: f64, precision: usize) -> String { +fn precision_trunc(num: f64, precision: Precision) -> String { // GNU `stat` doesn't round, it just seems to truncate to the // given precision: // @@ -473,21 +480,21 @@ fn precision_trunc(num: f64, precision: usize) -> String { let num_str = num.to_string(); let n = num_str.len(); match (num_str.find('.'), precision) { - (None, 0) => num_str, - (None, p) => format!("{num_str}.{zeros}", zeros = "0".repeat(p)), - (Some(i), 0) => num_str[..i].to_string(), - (Some(i), p) if p < n - i => num_str[..i + 1 + p].to_string(), - (Some(i), p) => format!("{num_str}{zeros}", zeros = "0".repeat(p - (n - i - 1))), + (None, Precision::NotSpecified) => num_str, + (None, Precision::NoNumber) => num_str, + (None, Precision::Number(0)) => num_str, + (None, Precision::Number(p)) => format!("{num_str}.{zeros}", zeros = "0".repeat(p)), + (Some(i), Precision::NotSpecified) => num_str[..i].to_string(), + (Some(_), Precision::NoNumber) => num_str, + (Some(i), Precision::Number(0)) => num_str[..i].to_string(), + (Some(i), Precision::Number(p)) if p < n - i => num_str[..i + 1 + p].to_string(), + (Some(i), Precision::Number(p)) => { + format!("{num_str}{zeros}", zeros = "0".repeat(p - (n - i - 1))) + } } } -fn print_float( - num: f64, - flags: &Flags, - width: usize, - precision: Option, - padding_char: Padding, -) { +fn print_float(num: f64, flags: &Flags, width: usize, precision: Precision, padding_char: Padding) { let prefix = if flags.sign { "+" } else if flags.space { @@ -495,7 +502,7 @@ fn print_float( } else { "" }; - let num_str = precision_trunc(num, precision.unwrap_or(0)); + let num_str = precision_trunc(num, precision); let extended = format!("{prefix}{num_str}"); pad_and_print(&extended, flags.left, width, padding_char) } @@ -507,13 +514,13 @@ fn print_float( /// * `num` - The unsigned integer value to be printed. /// * `flags` - A reference to the Flags struct containing formatting flags. /// * `width` - The width of the field for the printed unsigned integer. -/// * `precision` - An Option containing the precision value. +/// * `precision` - How many digits of precision, if any. /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_unsigned( num: u64, flags: &Flags, width: usize, - precision: Option, + precision: Precision, padding_char: Padding, ) { let num = num.to_string(); @@ -522,7 +529,11 @@ fn print_unsigned( } else { Cow::Borrowed(num.as_str()) }; - let s = format!("{s:0>precision$}", precision = precision.unwrap_or(0)); + let s = match precision { + Precision::NotSpecified => s, + Precision::NoNumber => s, + Precision::Number(p) => format!("{s:0>precision$}", precision = p).into(), + }; pad_and_print(&s, flags.left, width, padding_char); } @@ -533,20 +544,21 @@ fn print_unsigned( /// * `num` - The unsigned octal integer value to be printed. /// * `flags` - A reference to the Flags struct containing formatting flags. /// * `width` - The width of the field for the printed unsigned octal integer. -/// * `precision` - An Option containing the precision value. +/// * `precision` - How many digits of precision, if any. /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_unsigned_oct( num: u32, flags: &Flags, width: usize, - precision: Option, + precision: Precision, padding_char: Padding, ) { let prefix = if flags.alter { "0" } else { "" }; - let s = format!( - "{prefix}{num:0>precision$o}", - precision = precision.unwrap_or(0) - ); + let s = match precision { + Precision::NotSpecified => format!("{prefix}{num:o}"), + Precision::NoNumber => format!("{prefix}{num:o}"), + Precision::Number(p) => format!("{prefix}{num:0>precision$o}", precision = p), + }; pad_and_print(&s, flags.left, width, padding_char); } @@ -557,20 +569,21 @@ fn print_unsigned_oct( /// * `num` - The unsigned hexadecimal integer value to be printed. /// * `flags` - A reference to the Flags struct containing formatting flags. /// * `width` - The width of the field for the printed unsigned hexadecimal integer. -/// * `precision` - An Option containing the precision value. +/// * `precision` - How many digits of precision, if any. /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_unsigned_hex( num: u64, flags: &Flags, width: usize, - precision: Option, + precision: Precision, padding_char: Padding, ) { let prefix = if flags.alter { "0x" } else { "" }; - let s = format!( - "{prefix}{num:0>precision$x}", - precision = precision.unwrap_or(0) - ); + let s = match precision { + Precision::NotSpecified => format!("{prefix}{num:x}"), + Precision::NoNumber => format!("{prefix}{num:x}"), + Precision::Number(p) => format!("{prefix}{num:0>precision$x}", precision = p), + }; pad_and_print(&s, flags.left, width, padding_char); } @@ -586,6 +599,10 @@ impl Stater { '0' => flag.zero = true, '-' => flag.left = true, ' ' => flag.space = true, + // This is not documented but the behavior seems to be + // the same as a space. For example `stat -c "%I5s" f` + // prints " 0". + 'I' => flag.space = true, '+' => flag.sign = true, '\'' => flag.group = true, _ => break, @@ -616,7 +633,7 @@ impl Stater { Self::process_flags(chars, i, bound, &mut flag); let mut width = 0; - let mut precision = None; + let mut precision = Precision::NotSpecified; let mut j = *i; if let Some((field_width, offset)) = format_str[j..].scan_num::() { @@ -641,11 +658,11 @@ impl Stater { match format_str[j..].scan_num::() { Some((value, offset)) => { if value >= 0 { - precision = Some(value as usize); + precision = Precision::Number(value as usize); } j += offset; } - None => precision = Some(0), + None => precision = Precision::NoNumber, } check_bound(format_str, bound, old, j)?; } @@ -960,9 +977,17 @@ impl Stater { let tm = chrono::DateTime::from_timestamp(sec, nsec as u32).unwrap_or_default(); let tm: DateTime = tm.into(); - let micros = tm.timestamp_micros(); - let secs = micros as f64 / 1_000_000.0; - OutputType::Float(secs) + match tm.timestamp_nanos_opt() { + None => { + let micros = tm.timestamp_micros(); + let secs = micros as f64 / 1_000_000.0; + OutputType::Float(secs) + } + Some(ns) => { + let secs = ns as f64 / 1_000_000_000.0; + OutputType::Float(secs) + } + } } // time of last status change, human-readable 'z' => OutputType::Str(pretty_time(meta.ctime(), meta.ctime_nsec())), @@ -1172,7 +1197,7 @@ fn pretty_time(sec: i64, nsec: i64) -> String { #[cfg(test)] mod tests { - use super::{group_num, precision_trunc, Flags, ScanUtil, Stater, Token}; + use super::{group_num, precision_trunc, Flags, Precision, ScanUtil, Stater, Token}; #[test] fn test_scanners() { @@ -1220,7 +1245,7 @@ mod tests { ..Default::default() }, width: 10, - precision: Some(2), + precision: Precision::Number(2), format: 'a', }, Token::Char('c'), @@ -1231,7 +1256,7 @@ mod tests { ..Default::default() }, width: 5, - precision: Some(0), + precision: Precision::NoNumber, format: 'w', }, Token::Char('\n'), @@ -1251,7 +1276,7 @@ mod tests { ..Default::default() }, width: 15, - precision: None, + precision: Precision::NotSpecified, format: 'a', }, Token::Byte(b'\t'), @@ -1270,7 +1295,7 @@ mod tests { ..Default::default() }, width: 20, - precision: None, + precision: Precision::NotSpecified, format: 'w', }, Token::Byte(b'\x12'), @@ -1284,11 +1309,13 @@ mod tests { #[test] fn test_precision_trunc() { - assert_eq!(precision_trunc(123.456, 0), "123"); - assert_eq!(precision_trunc(123.456, 1), "123.4"); - assert_eq!(precision_trunc(123.456, 2), "123.45"); - assert_eq!(precision_trunc(123.456, 3), "123.456"); - assert_eq!(precision_trunc(123.456, 4), "123.4560"); - assert_eq!(precision_trunc(123.456, 5), "123.45600"); + assert_eq!(precision_trunc(123.456, Precision::NotSpecified), "123"); + assert_eq!(precision_trunc(123.456, Precision::NoNumber), "123.456"); + assert_eq!(precision_trunc(123.456, Precision::Number(0)), "123"); + assert_eq!(precision_trunc(123.456, Precision::Number(1)), "123.4"); + assert_eq!(precision_trunc(123.456, Precision::Number(2)), "123.45"); + assert_eq!(precision_trunc(123.456, Precision::Number(3)), "123.456"); + assert_eq!(precision_trunc(123.456, Precision::Number(4)), "123.4560"); + assert_eq!(precision_trunc(123.456, Precision::Number(5)), "123.45600"); } } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index a48462c3eae..cd74767283a 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -191,13 +191,67 @@ fn test_char() { #[cfg(target_os = "linux")] #[test] fn test_printf_mtime_precision() { - let args = ["-c", "%.0Y %.1Y %.2Y %.3Y %.4Y", "/dev/pts/ptmx"]; + // TODO Higher precision numbers (`%.3Y`, `%.4Y`, etc.) are + // formatted correctly, but we are not precise enough when we do + // some `mtime` computations, so we get `.7640` instead of + // `.7639`. This can be fixed by being more careful when + // transforming the number from `Metadata::mtime_nsec()` to the form + // used in rendering. + let args = ["-c", "%.0Y %.1Y %.2Y", "/dev/pts/ptmx"]; let ts = TestScenario::new(util_name!()); let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str(); eprintln!("{expected_stdout}"); ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout); } +#[cfg(feature = "touch")] +#[test] +fn test_timestamp_format() { + let ts = TestScenario::new(util_name!()); + + // Create a file with a specific timestamp for testing + ts.ccmd("touch") + .args(&["-d", "1970-01-01 18:43:33.023456789", "k"]) + .succeeds() + .no_stderr(); + + let test_cases = vec![ + // Basic timestamp formats + ("%Y", "67413"), + ("%.Y", "67413.023456789"), + ("%.1Y", "67413.0"), + ("%.3Y", "67413.023"), + ("%.6Y", "67413.023456"), + ("%.9Y", "67413.023456789"), + // Width and padding tests + ("%13.6Y", " 67413.023456"), + ("%013.6Y", "067413.023456"), + ("%-13.6Y", "67413.023456 "), + // Longer width/precision combinations + ("%18.10Y", " 67413.0234567890"), + ("%I18.10Y", " 67413.0234567890"), + ("%018.10Y", "0067413.0234567890"), + ("%-18.10Y", "67413.0234567890 "), + ]; + + for (format_str, expected) in test_cases { + let result = ts + .ucmd() + .args(&["-c", format_str, "k"]) + .succeeds() + .stdout_move_str(); + + assert_eq!( + result, + format!("{expected}\n"), + "Format '{}' failed.\nExpected: '{}'\nGot: '{}'", + format_str, + expected, + result, + ); + } +} + #[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))] #[test] fn test_date() { From 700a5f007a3d7732c1b44eb850ef37c5c1b49c1e Mon Sep 17 00:00:00 2001 From: jfinkels Date: Sat, 11 Jan 2025 11:47:23 -0500 Subject: [PATCH 317/351] tsort: use iterators to remove from vec Co-authored-by: Sylvestre Ledru --- src/uu/tsort/src/tsort.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 2f0b4c9b836..91036fe275a 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -105,13 +105,10 @@ fn remove(vec: &mut Vec, x: T) -> Option where T: PartialEq, { - for i in 0..vec.len() { - if vec[i] == x { - vec.remove(i); - return Some(i); - } - } - None + vec.iter().position(|item| *item == x).map(|i| { + vec.remove(i); + i + }) } // We use String as a representation of node here From 62879244cbfa7599a95dc95f485de1323a8229fe Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 11 Jan 2025 11:58:51 -0500 Subject: [PATCH 318/351] tsort: use Option::inspect over map --- src/uu/tsort/src/tsort.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 91036fe275a..aac0a055fea 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -105,9 +105,8 @@ fn remove(vec: &mut Vec, x: T) -> Option where T: PartialEq, { - vec.iter().position(|item| *item == x).map(|i| { - vec.remove(i); - i + vec.iter().position(|item| *item == x).inspect(|i| { + vec.remove(*i); }) } From 02e49a40c03f2fb08e0d778cdb9b157443b90709 Mon Sep 17 00:00:00 2001 From: ctsk <9384305+ctsk@users.noreply.github.com> Date: Wed, 1 Feb 2023 13:24:28 +0100 Subject: [PATCH 319/351] df: error on over-mounted device Make `df` error when two devices are mounted at the same directory and the earlier one is given as a positional argument to `df`. This is called "over-mounting". After this commit, an error message is printed like this: df: cannot access '/dev/loop1': over-mounted by another device Fixes #3970 Co-authored-by: Jeffrey Finkelstein --- src/uu/df/src/df.rs | 55 +++++++++---- src/uu/df/src/filesystem.rs | 154 ++++++++++++++++++++++++++++++++---- 2 files changed, 176 insertions(+), 33 deletions(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 8ef84a46311..092c8381290 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -27,6 +27,7 @@ use std::path::Path; use crate::blocks::{read_block_size, BlockSize}; use crate::columns::{Column, ColumnError}; use crate::filesystem::Filesystem; +use crate::filesystem::FsError; use crate::table::Table; const ABOUT: &str = help_about!("df.md"); @@ -350,11 +351,25 @@ fn get_all_filesystems(opt: &Options) -> UResult> { // Convert each `MountInfo` into a `Filesystem`, which contains // both the mount information and usage information. - Ok(mounts - .into_iter() - .filter_map(|m| Filesystem::new(m, None)) - .filter(|fs| opt.show_all_fs || fs.usage.blocks > 0) - .collect()) + #[cfg(not(windows))] + { + let maybe_mount = |m| Filesystem::from_mount(&mounts, &m, None).ok(); + Ok(mounts + .clone() + .into_iter() + .filter_map(maybe_mount) + .filter(|fs| opt.show_all_fs || fs.usage.blocks > 0) + .collect()) + } + #[cfg(windows)] + { + let maybe_mount = |m| Filesystem::from_mount(&m, None).ok(); + Ok(mounts + .into_iter() + .filter_map(maybe_mount) + .filter(|fs| opt.show_all_fs || fs.usage.blocks > 0) + .collect()) + } } /// For each path, get the filesystem that contains that path. @@ -385,17 +400,25 @@ where // both the mount information and usage information. for path in paths { match Filesystem::from_path(&mounts, path) { - Some(fs) => result.push(fs), - None => { - // this happens if specified file system type != file system type of the file - if path.as_ref().exists() { - show!(USimpleError::new(1, "no file systems processed")); - } else { - show!(USimpleError::new( - 1, - format!("{}: No such file or directory", path.as_ref().display()) - )); - } + Ok(fs) => result.push(fs), + Err(FsError::InvalidPath) => { + show!(USimpleError::new( + 1, + format!("{}: No such file or directory", path.as_ref().display()) + )); + } + Err(FsError::MountMissing) => { + show!(USimpleError::new(1, "no file systems processed")); + } + #[cfg(not(windows))] + Err(FsError::OverMounted) => { + show!(USimpleError::new( + 1, + format!( + "cannot access {}: over-mounted by another device", + path.as_ref().quote() + ) + )); } } } diff --git a/src/uu/df/src/filesystem.rs b/src/uu/df/src/filesystem.rs index 5e86cf31781..6f59e2c1027 100644 --- a/src/uu/df/src/filesystem.rs +++ b/src/uu/df/src/filesystem.rs @@ -37,6 +37,33 @@ pub(crate) struct Filesystem { pub usage: FsUsage, } +#[derive(Debug, PartialEq)] +pub(crate) enum FsError { + #[cfg(not(windows))] + OverMounted, + InvalidPath, + MountMissing, +} + +/// Check whether `mount` has been over-mounted. +/// +/// `mount` is considered over-mounted if it there is an element in +/// `mounts` after mount that has the same mount_dir. +#[cfg(not(windows))] +fn is_over_mounted(mounts: &[MountInfo], mount: &MountInfo) -> bool { + let last_mount_for_dir = mounts + .iter() + .filter(|m| m.mount_dir == mount.mount_dir) + .last(); + + if let Some(lmi) = last_mount_for_dir { + lmi.dev_name != mount.dev_name + } else { + // Should be unreachable if `mount` is in `mounts` + false + } +} + /// Find the mount info that best matches a given filesystem path. /// /// This function returns the element of `mounts` on which `path` is @@ -56,14 +83,16 @@ fn mount_info_from_path

( path: P, // This is really only used for testing purposes. canonicalize: bool, -) -> Option<&MountInfo> +) -> Result<&MountInfo, FsError> where P: AsRef, { // TODO Refactor this function with `Stater::find_mount_point()` // in the `stat` crate. let path = if canonicalize { - path.as_ref().canonicalize().ok()? + path.as_ref() + .canonicalize() + .map_err(|_| FsError::InvalidPath)? } else { path.as_ref().to_path_buf() }; @@ -82,12 +111,14 @@ where .find(|m| m.1.eq(&path)) .map(|m| m.0); - maybe_mount_point.or_else(|| { - mounts - .iter() - .filter(|mi| path.starts_with(&mi.mount_dir)) - .max_by_key(|mi| mi.mount_dir.len()) - }) + maybe_mount_point + .or_else(|| { + mounts + .iter() + .filter(|mi| path.starts_with(&mi.mount_dir)) + .max_by_key(|mi| mi.mount_dir.len()) + }) + .ok_or(FsError::MountMissing) } impl Filesystem { @@ -117,6 +148,27 @@ impl Filesystem { }) } + /// Find and create the filesystem from the given mount + /// after checking that the it hasn't been over-mounted + #[cfg(not(windows))] + pub(crate) fn from_mount( + mounts: &[MountInfo], + mount: &MountInfo, + file: Option, + ) -> Result { + if is_over_mounted(mounts, mount) { + Err(FsError::OverMounted) + } else { + Self::new(mount.clone(), file).ok_or(FsError::MountMissing) + } + } + + /// Find and create the filesystem from the given mount. + #[cfg(windows)] + pub(crate) fn from_mount(mount: &MountInfo, file: Option) -> Result { + Self::new(mount.clone(), file).ok_or(FsError::MountMissing) + } + /// Find and create the filesystem that best matches a given path. /// /// This function returns a new `Filesystem` derived from the @@ -133,16 +185,18 @@ impl Filesystem { /// * [`Path::canonicalize`] /// * [`MountInfo::mount_dir`] /// - pub(crate) fn from_path

(mounts: &[MountInfo], path: P) -> Option + pub(crate) fn from_path

(mounts: &[MountInfo], path: P) -> Result where P: AsRef, { let file = path.as_ref().display().to_string(); let canonicalize = true; - let mount_info = mount_info_from_path(mounts, path, canonicalize)?; - // TODO Make it so that we do not need to clone the `mount_info`. - let mount_info = (*mount_info).clone(); - Self::new(mount_info, Some(file)) + + let result = mount_info_from_path(mounts, path, canonicalize); + #[cfg(windows)] + return result.and_then(|mount_info| Self::from_mount(mount_info, Some(file))); + #[cfg(not(windows))] + return result.and_then(|mount_info| Self::from_mount(mounts, mount_info, Some(file))); } } @@ -153,7 +207,7 @@ mod tests { use uucore::fsext::MountInfo; - use crate::filesystem::mount_info_from_path; + use crate::filesystem::{mount_info_from_path, FsError}; // Create a fake `MountInfo` with the given directory name. fn mount_info(mount_dir: &str) -> MountInfo { @@ -183,7 +237,19 @@ mod tests { #[test] fn test_empty_mounts() { - assert!(mount_info_from_path(&[], "/", false).is_none()); + assert_eq!( + mount_info_from_path(&[], "/", false).unwrap_err(), + FsError::MountMissing + ); + } + + #[test] + fn test_bad_path() { + assert_eq!( + // This path better not exist.... + mount_info_from_path(&[], "/non-existent-path", true).unwrap_err(), + FsError::InvalidPath + ); } #[test] @@ -210,13 +276,19 @@ mod tests { #[test] fn test_no_match() { let mounts = [mount_info("/foo")]; - assert!(mount_info_from_path(&mounts, "/bar", false).is_none()); + assert_eq!( + mount_info_from_path(&mounts, "/bar", false).unwrap_err(), + FsError::MountMissing + ); } #[test] fn test_partial_match() { let mounts = [mount_info("/foo/bar")]; - assert!(mount_info_from_path(&mounts, "/foo/baz", false).is_none()); + assert_eq!( + mount_info_from_path(&mounts, "/foo/baz", false).unwrap_err(), + FsError::MountMissing + ); } #[test] @@ -237,4 +309,52 @@ mod tests { assert!(mount_info_eq(actual, &mounts[0])); } } + + #[cfg(not(windows))] + mod over_mount { + use crate::filesystem::{is_over_mounted, Filesystem, FsError}; + use uucore::fsext::MountInfo; + + fn mount_info_with_dev_name(mount_dir: &str, dev_name: Option<&str>) -> MountInfo { + MountInfo { + dev_id: Default::default(), + dev_name: dev_name.map(String::from).unwrap_or_default(), + fs_type: Default::default(), + mount_dir: String::from(mount_dir), + mount_option: Default::default(), + mount_root: Default::default(), + remote: Default::default(), + dummy: Default::default(), + } + } + + #[test] + fn test_over_mount() { + let mount_info1 = mount_info_with_dev_name("/foo", Some("dev_name_1")); + let mount_info2 = mount_info_with_dev_name("/foo", Some("dev_name_2")); + let mounts = [mount_info1, mount_info2]; + assert!(is_over_mounted(&mounts, &mounts[0])); + } + + #[test] + fn test_over_mount_not_over_mounted() { + let mount_info1 = mount_info_with_dev_name("/foo", Some("dev_name_1")); + let mount_info2 = mount_info_with_dev_name("/foo", Some("dev_name_2")); + let mounts = [mount_info1, mount_info2]; + assert!(!is_over_mounted(&mounts, &mounts[1])); + } + + #[test] + fn test_from_mount_over_mounted() { + let mount_info1 = mount_info_with_dev_name("/foo", Some("dev_name_1")); + let mount_info2 = mount_info_with_dev_name("/foo", Some("dev_name_2")); + + let mounts = [mount_info1, mount_info2]; + + assert_eq!( + Filesystem::from_mount(&mounts, &mounts[0], None).unwrap_err(), + FsError::OverMounted + ); + } + } } From 924d40867c0839b6bf6b4c5da7511db4b1f4e520 Mon Sep 17 00:00:00 2001 From: Anirban Halder Date: Sun, 12 Jan 2025 01:08:38 +0530 Subject: [PATCH 320/351] chroot: make group option self overwriting --- src/uu/chroot/src/chroot.rs | 1 + tests/by-util/test_chroot.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index f348d0c554b..4ea5db65348 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -254,6 +254,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::GROUPS) .long(options::GROUPS) + .overrides_with(options::GROUPS) .help("Comma-separated list of groups to switch to") .value_name("GROUP1,GROUP2..."), ) diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 711dd09435d..022822c6b36 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -53,6 +53,22 @@ fn test_no_such_directory() { .code_is(125); } +#[test] +fn test_multiple_group_args() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.mkdir("id"); + + if let Ok(result) = run_ucmd_as_root( + &ts, + &["--groups='invalid ignored'", "--groups=''", "/", "id", "-G"], + ) { + result.success().stdout_is("0"); + } else { + print!("Test skipped; requires root user"); + } +} + #[test] fn test_invalid_user_spec() { let ts = TestScenario::new(util_name!()); From cc6c4a7bd6e646fbf1847172e04258feb9757250 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Jan 2025 01:43:55 +0100 Subject: [PATCH 321/351] tests/chroot/chroot-credentials has been fxied https://github.com/uutils/coreutils/pull/7123 --- util/why-error.md | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.md b/util/why-error.md index f0e021f41da..5897f873200 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -2,7 +2,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/preserve-gid.sh * gnu/tests/csplit/csplit-suppress-matched.pl * gnu/tests/date/date-debug.sh From c643ba1874518892e9868bb5659d9da6032c5e3a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Jan 2025 02:14:58 +0100 Subject: [PATCH 322/351] remove test t tests/chmod/symlinks from the list (#7125) https://github.com/uutils/coreutils/pull/7025 --- util/why-error.md | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.md b/util/why-error.md index 5897f873200..8f791f9864d 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -1,7 +1,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/cp/preserve-gid.sh * gnu/tests/csplit/csplit-suppress-matched.pl * gnu/tests/date/date-debug.sh From 98699b61ab1573e47332d6c4191991724a7f2d21 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Jan 2025 21:29:53 +0100 Subject: [PATCH 323/351] df test now passes https://github.com/uutils/coreutils/pull/7116 --- util/why-error.md | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.md b/util/why-error.md index 8f791f9864d..77390d24e70 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -12,7 +12,6 @@ This file documents why some tests are failing: * 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 From 6e1504ff68aa60a14b3bb57c7bdf6c930e6e7754 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 13 Jan 2025 08:20:30 +0100 Subject: [PATCH 324/351] Cargo.lock: downgrade log from 0.4.24 to 0.4.22 because the newer versions have been yanked --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 452439adc80..c3f84e90be4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1304,9 +1304,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.24" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6ea2a48c204030ee31a7d7fc72c93294c92fe87ecb1789881c9543516e1a0d" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" From a850fc059f7b23ac3dfb6398610c673d48bd02cd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 13 Jan 2025 09:11:25 +0100 Subject: [PATCH 325/351] ci: install libselinux1-dev --- .github/workflows/CICD.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index ae89a36ee8a..c4bcf51115d 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -119,6 +119,10 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.7 + - name: Install/setup prerequisites + shell: bash + run: | + sudo apt-get -y update ; sudo apt-get -y install libselinux1-dev - name: Initialize workflow variables id: vars shell: bash From d9b3b3ef675d44ee5a6497679bb627bc58e3ba6a Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 10 Jan 2025 23:28:02 -0500 Subject: [PATCH 326/351] dd: error if iflag=directory and input is stdin Make `dd` error if `iflag=directory` and the input file is not a directory, as in : | dd iflag=directory Fixes #5900 --- src/uu/dd/src/dd.rs | 12 ++++++------ tests/by-util/test_dd.rs | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index ca8c2a8b570..aaa4684617a 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -54,7 +54,7 @@ use nix::{ }; use uucore::display::Quotable; #[cfg(unix)] -use uucore::error::set_exit_code; +use uucore::error::{set_exit_code, USimpleError}; use uucore::error::{FromIo, UResult}; #[cfg(target_os = "linux")] use uucore::show_if_err; @@ -338,11 +338,11 @@ impl<'a> Input<'a> { let mut src = Source::stdin_as_file(); #[cfg(unix)] if let Source::StdinFile(f) = &src { - // GNU compatibility: - // this will check whether stdin points to a folder or not - if f.metadata()?.is_file() && settings.iflags.directory { - show_error!("standard input: not a directory"); - return Err(1.into()); + if settings.iflags.directory && !f.metadata()?.is_dir() { + return Err(USimpleError::new( + 1, + "setting flags for 'standard input': Not a directory", + )); } }; if settings.skip > 0 { diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 64ca7603bf7..57a2933201e 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1728,7 +1728,26 @@ fn test_iflag_directory_fails_when_file_is_passed_via_std_in() { .args(&["iflag=directory", "count=0"]) .set_stdin(std::process::Stdio::from(File::open(filename).unwrap())) .fails() - .stderr_contains("standard input: not a directory"); + .stderr_only("dd: setting flags for 'standard input': Not a directory\n"); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_iflag_directory_passes_when_dir_is_redirected() { + new_ucmd!() + .args(&["iflag=directory", "count=0"]) + .set_stdin(std::process::Stdio::from(File::open(".").unwrap())) + .succeeds(); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_iflag_directory_fails_when_file_is_piped_via_std_in() { + new_ucmd!() + .arg("iflag=directory") + .pipe_in("") + .fails() + .stderr_only("dd: setting flags for 'standard input': Not a directory\n"); } #[test] From 071e72ffc8949750b726302b8a56710a39fdaf9b Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 12 Jan 2025 11:06:56 -0500 Subject: [PATCH 327/351] split: fix bug with large arguments to -C Fix the behavior of `split -C` when given large arguments. Before this commit, bytes were being greedily assigned to a chunk too aggressively, leading to a situation where a split happened in the middle of a line even though the entire line could have fit within the next chunk. This was appearing for large arguments to `-C` and long lines that extended beyond the size of the read buffer. This commit fixes the behavior. Fixes #7026 --- src/uu/split/src/split.rs | 325 ++++++++++++------------------------ tests/by-util/test_split.rs | 17 ++ 2 files changed, 126 insertions(+), 216 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 279e91daea1..053d86e8c28 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -919,204 +919,6 @@ impl Write for LineChunkWriter<'_> { } } -/// Write lines to each sequential output files, limited by bytes. -/// -/// This struct maintains an underlying writer representing the -/// current chunk of the output. On each call to [`write`], it writes -/// as many lines as possible to the current chunk without exceeding -/// the specified byte limit. If a single line has more bytes than the -/// limit, then fill an entire single chunk with those bytes and -/// handle the remainder of the line as if it were its own distinct -/// line. As many new underlying writers are created as needed to -/// write all the data in the input buffer. -struct LineBytesChunkWriter<'a> { - /// Parameters for creating the underlying writer for each new chunk. - settings: &'a Settings, - - /// The maximum number of bytes allowed for a single chunk of output. - chunk_size: u64, - - /// Running total of number of chunks that have been completed. - num_chunks_written: usize, - - /// Remaining capacity in number of bytes in the current chunk. - /// - /// This number starts at `chunk_size` and decreases as lines are - /// written. Once it reaches zero, a writer for a new chunk is - /// initialized and this number gets reset to `chunk_size`. - num_bytes_remaining_in_current_chunk: usize, - - /// The underlying writer for the current chunk. - /// - /// Once the number of bytes written to this writer exceeds - /// `chunk_size`, a new writer is initialized and assigned to this - /// field. - inner: BufWriter>, - - /// Iterator that yields filenames for each chunk. - filename_iterator: FilenameIterator<'a>, -} - -impl<'a> LineBytesChunkWriter<'a> { - fn new(chunk_size: u64, settings: &'a Settings) -> UResult { - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - if settings.verbose { - println!("creating file {}", filename.quote()); - } - let inner = settings.instantiate_current_writer(&filename, true)?; - Ok(LineBytesChunkWriter { - settings, - chunk_size, - num_bytes_remaining_in_current_chunk: usize::try_from(chunk_size).unwrap(), - num_chunks_written: 0, - inner, - filename_iterator, - }) - } -} - -impl Write for LineBytesChunkWriter<'_> { - /// Write as many lines to a chunk as possible without - /// exceeding the byte limit. If a single line has more bytes - /// than the limit, then fill an entire single chunk with those - /// bytes and handle the remainder of the line as if it were - /// its own distinct line. - /// - /// For example: if the `chunk_size` is 8 and the input is: - /// - /// ```text - /// aaaaaaaaa\nbbbb\ncccc\ndd\nee\n - /// ``` - /// - /// then the output gets broken into chunks like this: - /// - /// ```text - /// chunk 0 chunk 1 chunk 2 chunk 3 - /// - /// 0 1 2 - /// 01234567 89 01234 56789 012 345 6 - /// |------| |-------| |--------| |---| - /// aaaaaaaa a\nbbbb\n cccc\ndd\n ee\n - /// ``` - /// - /// Implements `--line-bytes=SIZE` - fn write(&mut self, mut buf: &[u8]) -> std::io::Result { - // The total number of bytes written during the loop below. - // - // It is necessary to keep this running total because we may - // be making multiple calls to `write()` on multiple different - // underlying writers and we want the final reported number of - // bytes written to reflect the total number of bytes written - // to all of the underlying writers. - let mut total_bytes_written = 0; - - // Loop until we have written all bytes in the input buffer - // (or an IO error occurs). - loop { - // If the buffer is empty, then we are done writing. - if buf.is_empty() { - return Ok(total_bytes_written); - } - - // If we have filled the current chunk with bytes, then - // start a new chunk and initialize its corresponding - // writer. - if self.num_bytes_remaining_in_current_chunk == 0 { - self.num_chunks_written += 1; - let filename = self.filename_iterator.next().ok_or_else(|| { - std::io::Error::new(ErrorKind::Other, "output file suffixes exhausted") - })?; - if self.settings.verbose { - println!("creating file {}", filename.quote()); - } - self.inner = self.settings.instantiate_current_writer(&filename, true)?; - self.num_bytes_remaining_in_current_chunk = self.chunk_size.try_into().unwrap(); - } - - // Find the first separator (default - newline character) in the buffer. - let sep = self.settings.separator; - match memchr::memchr(sep, buf) { - // If there is no separator character and the buffer is - // not empty, then write as many bytes as we can and - // then move on to the next chunk if necessary. - None => { - let end = self.num_bytes_remaining_in_current_chunk; - - // This is ugly but here to match GNU behavior. If the input - // doesn't end with a separator, pretend that it does for handling - // the second to last segment chunk. See `line-bytes.sh`. - if end == buf.len() - && self.num_bytes_remaining_in_current_chunk - < self.chunk_size.try_into().unwrap() - && buf[buf.len() - 1] != sep - { - self.num_bytes_remaining_in_current_chunk = 0; - } else { - let num_bytes_written = custom_write( - &buf[..end.min(buf.len())], - &mut self.inner, - self.settings, - )?; - self.num_bytes_remaining_in_current_chunk -= num_bytes_written; - total_bytes_written += num_bytes_written; - buf = &buf[num_bytes_written..]; - } - } - - // If there is a separator character and the line - // (including the separator character) will fit in the - // current chunk, then write the entire line and - // continue to the next iteration. (See chunk 1 in the - // example comment above.) - Some(i) if i < self.num_bytes_remaining_in_current_chunk => { - let num_bytes_written = - custom_write(&buf[..=i], &mut self.inner, self.settings)?; - self.num_bytes_remaining_in_current_chunk -= num_bytes_written; - total_bytes_written += num_bytes_written; - buf = &buf[num_bytes_written..]; - } - - // If there is a separator character, the line - // (including the separator character) will not fit in - // the current chunk, *and* no other lines have been - // written to the current chunk, then write as many - // bytes as we can and continue to the next - // iteration. (See chunk 0 in the example comment - // above.) - Some(_) - if self.num_bytes_remaining_in_current_chunk - == self.chunk_size.try_into().unwrap() => - { - let end = self.num_bytes_remaining_in_current_chunk; - let num_bytes_written = - custom_write(&buf[..end], &mut self.inner, self.settings)?; - self.num_bytes_remaining_in_current_chunk -= num_bytes_written; - total_bytes_written += num_bytes_written; - buf = &buf[num_bytes_written..]; - } - - // If there is a separator character, the line - // (including the separator character) will not fit in - // the current chunk, and at least one other line has - // been written to the current chunk, then signal to - // the next iteration that a new chunk needs to be - // created and continue to the next iteration of the - // loop to try writing the line there. - Some(_) => { - self.num_bytes_remaining_in_current_chunk = 0; - } - } - } - } - - fn flush(&mut self) -> std::io::Result<()> { - self.inner.flush() - } -} - /// Output file parameters struct OutFile { filename: String, @@ -1629,6 +1431,114 @@ where Ok(()) } +/// Like `std::io::Lines`, but includes the line ending character. +/// +/// This struct is generally created by calling `lines_with_sep` on a +/// reader. +pub struct LinesWithSep { + inner: R, + separator: u8, +} + +impl Iterator for LinesWithSep +where + R: BufRead, +{ + type Item = std::io::Result>; + + /// Read bytes from a buffer up to the requested number of lines. + fn next(&mut self) -> Option { + let mut buf = vec![]; + match self.inner.read_until(self.separator, &mut buf) { + Ok(0) => None, + Ok(_) => Some(Ok(buf)), + Err(e) => Some(Err(e)), + } + } +} + +/// Like `std::str::lines` but includes the line ending character. +/// +/// The `separator` defines the character to interpret as the line +/// ending. For the usual notion of "line", set this to `b'\n'`. +pub fn lines_with_sep(reader: R, separator: u8) -> LinesWithSep +where + R: BufRead, +{ + LinesWithSep { + inner: reader, + separator, + } +} + +fn line_bytes(settings: &Settings, reader: &mut R, chunk_size: usize) -> UResult<()> +where + R: BufRead, +{ + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; + + // Initialize the writer just to satisfy the compiler. It is going + // to be overwritten for sure at the beginning of the loop below + // because we start with `remaining == 0`, indicating that a new + // chunk should start. + let mut writer: BufWriter> = + BufWriter::new(Box::new(std::io::Cursor::new(vec![]))); + + let mut remaining = 0; + for line in lines_with_sep(reader, settings.separator) { + let line = line?; + let mut line = &line[..]; + loop { + if remaining == 0 { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + if settings.verbose { + println!("creating file {}", filename.quote()); + } + writer = settings.instantiate_current_writer(&filename, true)?; + remaining = chunk_size; + } + + // Special case: if this is the last line and it doesn't end + // with a newline character, then count its length as though + // it did end with a newline. If that puts it over the edge + // of this chunk, continue to the next chunk. + if line.len() == remaining + && remaining < chunk_size + && line[line.len() - 1] != settings.separator + { + remaining = 0; + continue; + } + + // If the entire line fits in this chunk, write it and + // continue to the next line. + if line.len() <= remaining { + custom_write_all(line, &mut writer, settings)?; + remaining -= line.len(); + break; + } + + // If the line is too large to fit in *any* chunk and we are + // at the start of a new chunk, write as much as we can of + // it and pass the remainder along to the next chunk. + if line.len() > chunk_size && remaining == chunk_size { + custom_write_all(&line[..chunk_size], &mut writer, settings)?; + line = &line[chunk_size..]; + remaining = 0; + continue; + } + + // If the line is too large to fit in *this* chunk, but + // might otherwise fit in the next chunk, then just continue + // to the next chunk and let it be handled there. + remaining = 0; + } + } + Ok(()) +} + #[allow(clippy::cognitive_complexity)] fn split(settings: &Settings) -> UResult<()> { let r_box = if settings.input == "-" { @@ -1701,23 +1611,6 @@ fn split(settings: &Settings) -> UResult<()> { }, } } - Strategy::LineBytes(chunk_size) => { - let mut writer = LineBytesChunkWriter::new(chunk_size, settings)?; - match std::io::copy(&mut reader, &mut writer) { - Ok(_) => Ok(()), - Err(e) => match e.kind() { - // TODO Since the writer object controls the creation of - // new files, we need to rely on the `std::io::Result` - // returned by its `write()` method to communicate any - // errors to this calling scope. If a new file cannot be - // created because we have exceeded the number of - // allowable filenames, we use `ErrorKind::Other` to - // indicate that. A special error message needs to be - // printed in that case. - ErrorKind::Other => Err(USimpleError::new(1, format!("{e}"))), - _ => Err(uio_error!(e, "input/output error")), - }, - } - } + Strategy::LineBytes(chunk_size) => line_bytes(settings, &mut reader, chunk_size as usize), } } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index e2390dba0ab..e6e91ccccc1 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1973,3 +1973,20 @@ fn test_split_separator_same_multiple() { .args(&["-t:", "-t:", "-t,", "fivelines.txt"]) .fails(); } + +#[test] +fn test_long_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + let line1 = format!("{:131070}\n", ""); + let line2 = format!("{:1}\n", ""); + let line3 = format!("{:131071}\n", ""); + let infile = [line1, line2, line3].concat(); + ucmd.args(&["-C", "131072"]) + .pipe_in(infile) + .succeeds() + .no_output(); + assert_eq!(at.read("xaa").len(), 131_071); + assert_eq!(at.read("xab").len(), 2); + assert_eq!(at.read("xac").len(), 131_072); + assert!(!at.plus("xad").exists()); +} From 2594404cb4bc56acaf9c7640618186abde3033ee Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Jan 2025 11:51:03 +0100 Subject: [PATCH 328/351] gnu/tests/split/line-bytes.sh fixed https://github.com/uutils/coreutils/pull/7128 --- util/why-error.md | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.md b/util/why-error.md index 77390d24e70..1c4015a9eb5 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -65,7 +65,6 @@ This file documents why some tests are failing: * 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 From de18b765554f88017c4d0997d3ee2b7c683894a1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 14 Jan 2025 16:27:10 +0100 Subject: [PATCH 329/351] numfmt: use succeeds/fails instead of run in tests --- tests/by-util/test_numfmt.rs | 71 +++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 1ef588f26a9..0a7cdda0135 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -32,7 +32,7 @@ fn test_from_si() { new_ucmd!() .args(&["--from=si"]) .pipe_in("1000\n1.1M\n0.1G") - .run() + .succeeds() .stdout_is("1000\n1100000\n100000000\n"); } @@ -41,7 +41,7 @@ fn test_from_iec() { new_ucmd!() .args(&["--from=iec"]) .pipe_in("1024\n1.1M\n0.1G") - .run() + .succeeds() .stdout_is("1024\n1153434\n107374183\n"); } @@ -50,7 +50,7 @@ fn test_from_iec_i() { new_ucmd!() .args(&["--from=iec-i"]) .pipe_in("1.1Mi\n0.1Gi") - .run() + .succeeds() .stdout_is("1153434\n107374183\n"); } @@ -74,7 +74,7 @@ fn test_from_auto() { new_ucmd!() .args(&["--from=auto"]) .pipe_in("1K\n1Ki") - .run() + .succeeds() .stdout_is("1000\n1024\n"); } @@ -83,7 +83,7 @@ fn test_to_si() { new_ucmd!() .args(&["--to=si"]) .pipe_in("1000\n1100000\n100000000") - .run() + .succeeds() .stdout_is("1.0K\n1.1M\n100M\n"); } @@ -92,7 +92,7 @@ fn test_to_iec() { new_ucmd!() .args(&["--to=iec"]) .pipe_in("1024\n1153434\n107374182") - .run() + .succeeds() .stdout_is("1.0K\n1.2M\n103M\n"); } @@ -101,7 +101,7 @@ fn test_to_iec_i() { new_ucmd!() .args(&["--to=iec-i"]) .pipe_in("1024\n1153434\n107374182") - .run() + .succeeds() .stdout_is("1.0Ki\n1.2Mi\n103Mi\n"); } @@ -109,7 +109,7 @@ fn test_to_iec_i() { fn test_input_from_free_arguments() { new_ucmd!() .args(&["--from=si", "1K", "1.1M", "0.1G"]) - .run() + .succeeds() .stdout_is("1000\n1100000\n100000000\n"); } @@ -118,7 +118,7 @@ fn test_padding() { new_ucmd!() .args(&["--from=si", "--padding=8"]) .pipe_in("1K\n1.1M\n0.1G") - .run() + .succeeds() .stdout_is(" 1000\n 1100000\n100000000\n"); } @@ -127,7 +127,7 @@ fn test_negative_padding() { new_ucmd!() .args(&["--from=si", "--padding=-8"]) .pipe_in("1K\n1.1M\n0.1G") - .run() + .succeeds() .stdout_is("1000 \n1100000 \n100000000\n"); } @@ -136,7 +136,7 @@ fn test_header() { new_ucmd!() .args(&["--from=si", "--header=2"]) .pipe_in("header\nheader2\n1K\n1.1M\n0.1G") - .run() + .succeeds() .stdout_is("header\nheader2\n1000\n1100000\n100000000\n"); } @@ -145,7 +145,7 @@ fn test_header_default() { new_ucmd!() .args(&["--from=si", "--header"]) .pipe_in("header\n1K\n1.1M\n0.1G") - .run() + .succeeds() .stdout_is("header\n1000\n1100000\n100000000\n"); } @@ -153,7 +153,7 @@ fn test_header_default() { fn test_header_error_if_non_numeric() { new_ucmd!() .args(&["--header=two"]) - .run() + .fails() .stderr_is("numfmt: invalid header value 'two'\n"); } @@ -161,7 +161,7 @@ fn test_header_error_if_non_numeric() { fn test_header_error_if_0() { new_ucmd!() .args(&["--header=0"]) - .run() + .fails() .stderr_is("numfmt: invalid header value '0'\n"); } @@ -169,7 +169,7 @@ fn test_header_error_if_0() { fn test_header_error_if_negative() { new_ucmd!() .args(&["--header=-3"]) - .run() + .fails() .stderr_is("numfmt: invalid header value '-3'\n"); } @@ -178,25 +178,28 @@ fn test_negative() { new_ucmd!() .args(&["--from=si"]) .pipe_in("-1000\n-1.1M\n-0.1G") - .run() + .succeeds() .stdout_is("-1000\n-1100000\n-100000000\n"); new_ucmd!() .args(&["--to=iec-i"]) .pipe_in("-1024\n-1153434\n-107374182") - .run() + .succeeds() .stdout_is("-1.0Ki\n-1.2Mi\n-103Mi\n"); } #[test] fn test_negative_zero() { - new_ucmd!().pipe_in("-0\n-0.0").run().stdout_is("0\n0.0\n"); + new_ucmd!() + .pipe_in("-0\n-0.0") + .succeeds() + .stdout_is("0\n0.0\n"); } #[test] fn test_no_op() { new_ucmd!() .pipe_in("1024\n1234567") - .run() + .succeeds() .stdout_is("1024\n1234567\n"); } @@ -205,7 +208,7 @@ fn test_normalize() { new_ucmd!() .args(&["--from=si", "--to=si"]) .pipe_in("10000000K\n0.001K") - .run() + .succeeds() .stdout_is("10G\n1\n"); } @@ -213,7 +216,7 @@ fn test_normalize() { fn test_si_to_iec() { new_ucmd!() .args(&["--from=si", "--to=iec", "15334263563K"]) - .run() + .succeeds() .stdout_is("14T\n"); } @@ -222,7 +225,7 @@ fn test_should_report_invalid_empty_number_on_empty_stdin() { new_ucmd!() .args(&["--from=auto"]) .pipe_in("\n") - .run() + .fails() .stderr_is("numfmt: invalid number: ''\n"); } @@ -231,7 +234,7 @@ fn test_should_report_invalid_empty_number_on_blank_stdin() { new_ucmd!() .args(&["--from=auto"]) .pipe_in(" \t \n") - .run() + .fails() .stderr_is("numfmt: invalid number: ''\n"); } @@ -241,7 +244,7 @@ fn test_should_report_invalid_suffix_on_stdin() { new_ucmd!() .args(&["--from=auto"]) .pipe_in(format!("1{}", c as char)) - .run() + .fails() .stderr_is(format!( "numfmt: invalid suffix in input: '1{}'\n", c as char @@ -252,7 +255,7 @@ fn test_should_report_invalid_suffix_on_stdin() { new_ucmd!() .args(&["--from=auto"]) .pipe_in("NaN") - .run() + .fails() .stderr_is("numfmt: invalid suffix in input: 'NaN'\n"); } @@ -262,7 +265,7 @@ fn test_should_report_invalid_number_with_interior_junk() { new_ucmd!() .args(&["--from=auto"]) .pipe_in("1x0K") - .run() + .fails() .stderr_is("numfmt: invalid number: '1x0K'\n"); } @@ -271,14 +274,14 @@ fn test_should_skip_leading_space_from_stdin() { new_ucmd!() .args(&["--from=auto"]) .pipe_in(" 2Ki") - .run() + .succeeds() .stdout_is("2048\n"); // multi-line new_ucmd!() .args(&["--from=auto"]) .pipe_in("\t1Ki\n 2K") - .run() + .succeeds() .stdout_is("1024\n2000\n"); } @@ -287,7 +290,7 @@ fn test_should_convert_only_first_number_in_line() { new_ucmd!() .args(&["--from=auto"]) .pipe_in("1Ki 2M 3G") - .run() + .succeeds() .stdout_is("1024 2M 3G\n"); } @@ -296,13 +299,13 @@ fn test_leading_whitespace_should_imply_padding() { new_ucmd!() .args(&["--from=auto"]) .pipe_in(" 1K") - .run() + .succeeds() .stdout_is(" 1000\n"); new_ucmd!() .args(&["--from=auto"]) .pipe_in(" 202Ki") - .run() + .succeeds() .stdout_is(" 206848\n"); } @@ -311,7 +314,7 @@ fn test_should_calculate_implicit_padding_per_line() { new_ucmd!() .args(&["--from=auto"]) .pipe_in(" 1Ki\n 2K") - .run() + .succeeds() .stdout_is(" 1024\n 2000\n"); } @@ -319,7 +322,7 @@ fn test_should_calculate_implicit_padding_per_line() { fn test_leading_whitespace_in_free_argument_should_imply_padding() { new_ucmd!() .args(&["--from=auto", " 1Ki"]) - .run() + .succeeds() .stdout_is(" 1024\n"); } @@ -327,7 +330,7 @@ fn test_leading_whitespace_in_free_argument_should_imply_padding() { fn test_should_calculate_implicit_padding_per_free_argument() { new_ucmd!() .args(&["--from=auto", " 1Ki", " 2K"]) - .run() + .succeeds() .stdout_is(" 1024\n 2000\n"); } From 1db2e2356a6745a06f53c3ed4d3867900ae9a6c6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 7 Jan 2025 22:33:57 +0100 Subject: [PATCH 330/351] dircolors: fix empty COLORTERM matching with ?* pattern should fix tests/misc/dircolors --- src/uu/dircolors/src/dircolors.rs | 15 ++++++++++++++- tests/by-util/test_dircolors.rs | 11 +++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index faef0683e71..9b57050f9e2 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -374,6 +374,7 @@ where let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned()); let term = term.as_str(); + let colorterm = env::var("COLORTERM").unwrap_or_default(); let mut state = ParseState::Global; @@ -396,8 +397,20 @@ where )); } let lower = key.to_lowercase(); + if lower == "term" || lower == "colorterm" { - if term.fnmatch(val) { + let should_match = if lower == "colorterm" { + // For COLORTERM ?*, only match if COLORTERM is non-empty + if val == "?*" { + !colorterm.is_empty() + } else { + colorterm.fnmatch(val) + } + } else { + term.fnmatch(val) + }; + + if should_match { state = ParseState::Matched; } else if state != ParseState::Matched { state = ParseState::Pass; diff --git a/tests/by-util/test_dircolors.rs b/tests/by-util/test_dircolors.rs index 06d490c4a02..909d904c4a7 100644 --- a/tests/by-util/test_dircolors.rs +++ b/tests/by-util/test_dircolors.rs @@ -253,3 +253,14 @@ fn test_repeated() { new_ucmd!().arg(arg).arg(arg).succeeds().no_stderr(); } } + +#[test] +fn test_colorterm_empty_with_wildcard() { + new_ucmd!() + .env("COLORTERM", "") + .pipe_in("COLORTERM ?*\nowt 40;33\n") + .args(&["-b", "-"]) + .succeeds() + .stdout_is("LS_COLORS='';\nexport LS_COLORS\n") + .no_stderr(); +} From 591cdc1d31447fa1fb9983410e6c20a2d74524e6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 7 Jan 2025 22:49:28 +0100 Subject: [PATCH 331/351] dircolors: split the code to remove the clippy warning --- src/uu/dircolors/src/dircolors.rs | 122 ++++++++++++++++-------------- 1 file changed, 67 insertions(+), 55 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 9b57050f9e2..06e109e2bf3 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -8,7 +8,6 @@ use std::borrow::Borrow; use std::env; use std::fs::File; -//use std::io::IsTerminal; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -360,8 +359,6 @@ enum ParseState { } use uucore::{format_usage, parse_glob}; - -#[allow(clippy::cognitive_complexity)] fn parse(user_input: T, fmt: &OutputFmt, fp: &str) -> Result where T: IntoIterator, @@ -372,11 +369,12 @@ where result.push_str(&prefix); + // Get environment variables once at the start let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned()); - let term = term.as_str(); let colorterm = env::var("COLORTERM").unwrap_or_default(); let mut state = ParseState::Global; + let mut saw_colorterm_match = false; for (num, line) in user_input.into_iter().enumerate() { let num = num + 1; @@ -396,64 +394,38 @@ where num )); } - let lower = key.to_lowercase(); - if lower == "term" || lower == "colorterm" { - let should_match = if lower == "colorterm" { + let lower = key.to_lowercase(); + match lower.as_str() { + "term" => { + if term.fnmatch(val) { + state = ParseState::Matched; + } else if state == ParseState::Global { + state = ParseState::Pass; + } + } + "colorterm" => { // For COLORTERM ?*, only match if COLORTERM is non-empty - if val == "?*" { + let matches = if val == "?*" { !colorterm.is_empty() } else { colorterm.fnmatch(val) + }; + if matches { + state = ParseState::Matched; + saw_colorterm_match = true; + } else if !saw_colorterm_match && state == ParseState::Global { + state = ParseState::Pass; } - } else { - term.fnmatch(val) - }; - - if should_match { - state = ParseState::Matched; - } else if state != ParseState::Matched { - state = ParseState::Pass; - } - } else { - if state == ParseState::Matched { - // prevent subsequent mismatched TERM from - // cancelling the input - state = ParseState::Continue; } - if state != ParseState::Pass { - let search_key = lower.as_str(); - - if key.starts_with('.') { - if *fmt == OutputFmt::Display { - result.push_str(format!("\x1b[{val}m*{key}\t{val}\x1b[0m\n").as_str()); - } else { - result.push_str(format!("*{key}={val}:").as_str()); - } - } else if key.starts_with('*') { - if *fmt == OutputFmt::Display { - result.push_str(format!("\x1b[{val}m{key}\t{val}\x1b[0m\n").as_str()); - } else { - result.push_str(format!("{key}={val}:").as_str()); - } - } else if lower == "options" || lower == "color" || lower == "eightbit" { - // Slackware only. Ignore - } else if let Some((_, s)) = FILE_ATTRIBUTE_CODES - .iter() - .find(|&&(key, _)| key == search_key) - { - if *fmt == OutputFmt::Display { - result.push_str(format!("\x1b[{val}m{s}\t{val}\x1b[0m\n").as_str()); - } else { - result.push_str(format!("{s}={val}:").as_str()); - } - } else { - return Err(format!( - "{}:{}: unrecognized keyword {}", - fp.maybe_quote(), - num, - key - )); + _ => { + if state == ParseState::Matched { + // prevent subsequent mismatched TERM from + // cancelling the input + state = ParseState::Continue; + } + if state != ParseState::Pass { + append_entry(&mut result, fmt, key, &lower, val)?; } } } @@ -468,6 +440,46 @@ where Ok(result) } +fn append_entry( + result: &mut String, + fmt: &OutputFmt, + key: &str, + lower: &str, + val: &str, +) -> Result<(), String> { + if key.starts_with(['.', '*']) { + let entry = if key.starts_with('.') { + format!("*{key}") + } else { + key.to_string() + }; + let disp = if *fmt == OutputFmt::Display { + format!("\x1b[{val}m{entry}\t{val}\x1b[0m\n") + } else { + format!("{entry}={val}:") + }; + result.push_str(&disp); + return Ok(()); + } + + match lower { + "options" | "color" | "eightbit" => Ok(()), // Slackware only, ignore + _ => { + if let Some((_, s)) = FILE_ATTRIBUTE_CODES.iter().find(|&&(key, _)| key == lower) { + let disp = if *fmt == OutputFmt::Display { + format!("\x1b[{val}m{s}\t{val}\x1b[0m\n") + } else { + format!("{s}={val}:") + }; + result.push_str(&disp); + Ok(()) + } else { + Err(format!("unrecognized keyword {key}")) + } + } + } +} + /// Escape single quotes because they are not allowed between single quotes in shell code, and code /// enclosed by single quotes is what is returned by `parse()`. /// From 106873171d4f4a195d09da653ec637f7c16cbb99 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 Jan 2025 23:24:38 +0100 Subject: [PATCH 332/351] dircolors: ignore spell issues --- src/uu/dircolors/src/dircolors.rs | 2 +- tests/by-util/test_dircolors.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 06e109e2bf3..c5b2e49f982 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.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) clrtoeol dircolors eightbit endcode fnmatch leftcode multihardlink rightcode setenv sgid suid colorterm +// spell-checker:ignore (ToDO) clrtoeol dircolors eightbit endcode fnmatch leftcode multihardlink rightcode setenv sgid suid colorterm disp use std::borrow::Borrow; use std::env; diff --git a/tests/by-util/test_dircolors.rs b/tests/by-util/test_dircolors.rs index 909d904c4a7..ffabe2923df 100644 --- a/tests/by-util/test_dircolors.rs +++ b/tests/by-util/test_dircolors.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore overridable +// spell-checker:ignore overridable colorterm use crate::common::util::TestScenario; use dircolors::{guess_syntax, OutputFmt, StrUtils}; From d445edfd196e79e40b07bfc093d05bc79f5f4645 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Jan 2025 10:15:33 +0100 Subject: [PATCH 333/351] GNU build: disable an old check --- util/build-gnu.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index d29283a69b1..b1daee7e6bc 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -122,6 +122,8 @@ if test -f gnu-built; then echo "'rm -f $(pwd)/gnu-built' to force the build" echo "Note: the customization of the tests will still happen" else + # Disable useless checks + sed -i 's|check-texinfo: $(syntax_checks)|check-texinfo:|' doc/local.mk ./bootstrap --skip-po ./configure --quiet --disable-gcc-warnings --disable-nls --disable-dependency-tracking --disable-bold-man-page-references #Add timeout to to protect against hangs From 7f0d77954d17827dccdfee25a4364c3851066e03 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Jan 2025 10:15:50 +0100 Subject: [PATCH 334/351] GNU build: perf the change only once --- util/build-gnu.sh | 82 +++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index b1daee7e6bc..a8b85f29661 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -134,51 +134,51 @@ else # Use a better diff sed -i 's|diff -c|diff -u|g' tests/Coreutils.pm "${MAKE}" -j "$("${NPROC}")" - touch gnu-built -fi -# Handle generated factor tests -t_first=00 -t_max=36 -# t_max_release=20 -# if test "${UU_MAKE_PROFILE}" != "debug"; then -# # Generate the factor tests, so they can be fixed -# # * reduced to 20 to decrease log size (down from 36 expected by GNU) -# # * only for 'release', skipped for 'debug' as redundant and too time consuming (causing timeout errors) -# seq=$( -# i=${t_first} -# while test "${i}" -le "${t_max_release}"; do -# printf '%02d ' $i -# i=$((i + 1)) -# done -# ) -# for i in ${seq}; do -# "${MAKE}" "tests/factor/t${i}.sh" -# done -# cat -# sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*.sh -# t_first=$((t_max_release + 1)) -# fi -# strip all (debug) or just the longer (release) factor tests from Makefile -seq=$( - i=${t_first} - while test "${i}" -le "${t_max}"; do - printf '%02d ' ${i} - i=$((i + 1)) + # Handle generated factor tests + t_first=00 + t_max=36 + # t_max_release=20 + # if test "${UU_MAKE_PROFILE}" != "debug"; then + # # Generate the factor tests, so they can be fixed + # # * reduced to 20 to decrease log size (down from 36 expected by GNU) + # # * only for 'release', skipped for 'debug' as redundant and too time consuming (causing timeout errors) + # seq=$( + # i=${t_first} + # while test "${i}" -le "${t_max_release}"; do + # printf '%02d ' $i + # i=$((i + 1)) + # done + # ) + # for i in ${seq}; do + # "${MAKE}" "tests/factor/t${i}.sh" + # done + # cat + # sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*.sh + # t_first=$((t_max_release + 1)) + # fi + # strip all (debug) or just the longer (release) factor tests from Makefile + seq=$( + i=${t_first} + while test "${i}" -le "${t_max}"; do + printf '%02d ' ${i} + i=$((i + 1)) + done + ) + for i in ${seq}; do + echo "strip t${i}.sh from Makefile" + sed -i -e "s/\$(tf)\/t${i}.sh//g" Makefile done -) -for i in ${seq}; do - echo "strip t${i}.sh from Makefile" - sed -i -e "s/\$(tf)\/t${i}.sh//g" Makefile -done -grep -rl 'path_prepend_' tests/* | xargs sed -i 's| path_prepend_ ./src||' + # Remove tests checking for --version & --help + # Not really interesting for us and logs are too big + sed -i -e '/tests\/help\/help-version.sh/ D' \ + -e '/tests\/help\/help-version-getopt.sh/ D' \ + Makefile + touch gnu-built +fi -# Remove tests checking for --version & --help -# Not really interesting for us and logs are too big -sed -i -e '/tests\/help\/help-version.sh/ D' \ - -e '/tests\/help\/help-version-getopt.sh/ D' \ - Makefile +grep -rl 'path_prepend_' tests/* | xargs sed -i 's| path_prepend_ ./src||' # 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 dac35129d2f600c30372360695bece6b7fe013b5 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 12 Jan 2025 20:02:01 -0500 Subject: [PATCH 335/351] date: add dependencies for managing time zones Add dependencies on third-party packages `chrono-tz` and `iana-time-zone` to our `date` package. Together, these two packages allow us to produce time zone abbreviations (like `UTC`) from numeric timezone offsets. --- Cargo.lock | 33 +++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/uu/date/Cargo.toml | 2 ++ 3 files changed, 37 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c3f84e90be4..b564af6c407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,6 +302,28 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "chrono-tz" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -1582,6 +1604,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "parse_datetime" version = "0.6.0" @@ -2590,7 +2621,9 @@ name = "uu_date" version = "0.0.29" dependencies = [ "chrono", + "chrono-tz", "clap", + "iana-time-zone", "libc", "parse_datetime", "uucore", diff --git a/Cargo.toml b/Cargo.toml index 8fdb15d5d6f..2fb991d5705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -280,6 +280,8 @@ chrono = { version = "0.4.38", default-features = false, features = [ ] } clap = { version = "4.5", features = ["wrap_help", "cargo"] } clap_complete = "4.4" +chrono-tz = "0.8.3" +iana-time-zone = "0.1.57" clap_mangen = "0.2" compare = "0.1.0" coz = { version = "0.1.3" } diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 75766507655..87e8d383a75 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -22,6 +22,8 @@ chrono = { workspace = true } clap = { workspace = true } uucore = { workspace = true } parse_datetime = { workspace = true } +chrono-tz = { workspace = true } +iana-time-zone = { workspace = true } [target.'cfg(unix)'.dependencies] libc = { workspace = true } From d82d038b5475a14d2210925bac9d949774a4531c Mon Sep 17 00:00:00 2001 From: Krishna Nagam <40730166+KrishnaNagam@users.noreply.github.com> Date: Sun, 12 Jan 2025 20:02:31 -0500 Subject: [PATCH 336/351] date: display %Z alphabetic time zone abbreviation Improve the display of dates formatted with the `%Z` specifier so that the timezone abbreviation is displayed, not just its numeric offset. Fixes #3756 Co-authored-by: Jeffrey Finkelstein --- src/uu/date/src/date.rs | 21 ++++++++++++++++++--- tests/by-util/test_date.rs | 16 ++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 766e79bd497..d91f8f82c71 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -6,10 +6,12 @@ // spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes use chrono::format::{Item, StrftimeItems}; -use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, Utc}; +use chrono::{DateTime, FixedOffset, Local, Offset, TimeDelta, TimeZone, Utc}; #[cfg(windows)] use chrono::{Datelike, Timelike}; +use chrono_tz::{OffsetName, Tz}; use clap::{crate_version, Arg, ArgAction, Command}; +use iana_time_zone::get_timezone; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))] use libc::{clock_settime, timespec, CLOCK_REALTIME}; use std::fs::File; @@ -272,8 +274,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for date in dates { match date { Ok(date) => { + // TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970 + let tz = match std::env::var("TZ") { + // TODO Support other time zones... + Ok(s) if s == "UTC0" => Tz::Etc__UTC, + _ => match get_timezone() { + Ok(tz_str) => tz_str.parse().unwrap(), + Err(_) => Tz::Etc__UTC, + }, + }; + let offset = tz.offset_from_utc_date(&Utc::now().date_naive()); + let tz_abbreviation = offset.abbreviation(); // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f` - let format_string = &format_string.replace("%N", "%f"); + let format_string = &format_string + .replace("%N", "%f") + .replace("%Z", tz_abbreviation); // Refuse to pass this string to chrono as it is crashing in this crate if format_string.contains("%#z") { return Err(USimpleError::new( @@ -403,7 +418,7 @@ fn make_format_string(settings: &Settings) -> &str { Rfc3339Format::Ns => "%F %T.%f%:z", }, Format::Custom(ref fmt) => fmt, - Format::Default => "%c", + Format::Default => "%a %b %e %X %Z %Y", } } diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 553414af853..d0a9c09a201 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -144,11 +144,12 @@ fn test_date_utc() { #[test] fn test_date_utc_issue_6495() { new_ucmd!() + .env("TZ", "UTC0") .arg("-u") .arg("-d") .arg("@0") .succeeds() - .stdout_is("Thu Jan 1 00:00:00 1970\n"); + .stdout_is("Thu Jan 1 00:00:00 UTC 1970\n"); } #[test] @@ -423,16 +424,18 @@ fn test_invalid_date_string() { #[test] fn test_date_one_digit_date() { new_ucmd!() + .env("TZ", "UTC0") .arg("-d") .arg("2000-1-1") .succeeds() - .stdout_contains("Sat Jan 1 00:00:00 2000"); + .stdout_only("Sat Jan 1 00:00:00 UTC 2000\n"); new_ucmd!() + .env("TZ", "UTC0") .arg("-d") .arg("2000-1-4") .succeeds() - .stdout_contains("Tue Jan 4 00:00:00 2000"); + .stdout_only("Tue Jan 4 00:00:00 UTC 2000\n"); } #[test] @@ -464,6 +467,7 @@ fn test_date_parse_from_format() { #[test] fn test_date_from_stdin() { new_ucmd!() + .env("TZ", "UTC0") .arg("-f") .arg("-") .pipe_in( @@ -473,8 +477,8 @@ fn test_date_from_stdin() { ) .succeeds() .stdout_is( - "Mon Mar 27 08:30:00 2023\n\ - Sat Apr 1 12:00:00 2023\n\ - Sat Apr 15 18:30:00 2023\n", + "Mon Mar 27 08:30:00 UTC 2023\n\ + Sat Apr 1 12:00:00 UTC 2023\n\ + Sat Apr 15 18:30:00 UTC 2023\n", ); } From 0d4fa8a3ff404ddd89d4b3842ddf4f42781f3e8c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:58:58 +0000 Subject: [PATCH 337/351] fix(deps): update rust crate data-encoding-macro to v0.1.16 --- Cargo.lock | 55 ++++++++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3f84e90be4..622c7053dcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -171,7 +171,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.96", + "syn", ] [[package]] @@ -589,7 +589,7 @@ dependencies = [ "lazy_static", "proc-macro2", "regex", - "syn 2.0.96", + "syn", "unicode-xid", ] @@ -601,7 +601,7 @@ checksum = "25fcfea2ee05889597d35e986c2ad0169694320ae5cc8f6d2640a4bb8a884560" dependencies = [ "lazy_static", "proc-macro2", - "syn 2.0.96", + "syn", ] [[package]] @@ -616,7 +616,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -716,15 +716,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "data-encoding-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +checksum = "5b16d9d0d88a5273d830dac8b78ceb217ffc9b1d5404e5597a3542515329405b" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -732,12 +732,12 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +checksum = "1145d32e826a7748b69ee8fc62d3e6355ff7f1051df53141e7048162fc90481b" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn", ] [[package]] @@ -757,7 +757,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -784,7 +784,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -971,7 +971,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -1697,7 +1697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "483f8c21f64f3ea09fe0f30f5d48c3e8eefe5dac9129f0075f76593b4c1da705" dependencies = [ "proc-macro2", - "syn 2.0.96", + "syn", ] [[package]] @@ -1911,7 +1911,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.96", + "syn", "unicode-ident", ] @@ -2047,7 +2047,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2176,17 +2176,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.96" @@ -2276,7 +2265,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -2287,7 +2276,7 @@ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] @@ -3542,7 +3531,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.96", + "syn", "wasm-bindgen-shared", ] @@ -3564,7 +3553,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3851,7 +3840,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn", ] [[package]] From 09ee3dc8ac872c200654c4e58b876e85ecbfc272 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 15 Jan 2025 09:43:37 +0100 Subject: [PATCH 338/351] GNU build: add word to spell-checker:ignore --- 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 a8b85f29661..782e21a1a30 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -2,7 +2,7 @@ # `build-gnu.bash` ~ builds GNU coreutils (from supplied sources) # -# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules xstrtol ; (vars/env) SRCDIR vdir rcexp xpart dired OSTYPE ; (utils) gnproc greadlink gsed multihardlink +# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules xstrtol ; (vars/env) SRCDIR vdir rcexp xpart dired OSTYPE ; (utils) gnproc greadlink gsed multihardlink texinfo set -e From c07e0e897388b68bf9e8a546b9c86575a43c2c04 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 15 Jan 2025 10:42:14 +0100 Subject: [PATCH 339/351] dircolors is fixed by https://github.com/uutils/coreutils/pull/7089 --- util/why-error.md | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.md b/util/why-error.md index 1c4015a9eb5..39ca5c1781e 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -27,7 +27,6 @@ This file documents why some tests are failing: * 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/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 From 05ada0d204998df50748e87192d6cdf919f8a17a Mon Sep 17 00:00:00 2001 From: Alexander <50661022+alexs-sh@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:53:18 +0100 Subject: [PATCH 340/351] seq:add floating point support (#6959) * seq:enable parsing of hexadecimal floats Turn on the float parser. Now it's possible to use hexadecimal floats as parameters. For example, cargo run -- 0x1p-1 3 0.5 1.5 2.5 Issue #6935 --------- Co-authored-by: Daniel Hofstetter Co-authored-by: Sylvestre Ledru --- src/uu/seq/src/hexadecimalfloat.rs | 404 +++++++++++++++++++++++++++++ src/uu/seq/src/number.rs | 2 + src/uu/seq/src/numberparse.rs | 11 +- src/uu/seq/src/seq.rs | 83 ++++-- tests/by-util/test_seq.rs | 81 +++++- 5 files changed, 555 insertions(+), 26 deletions(-) create mode 100644 src/uu/seq/src/hexadecimalfloat.rs diff --git a/src/uu/seq/src/hexadecimalfloat.rs b/src/uu/seq/src/hexadecimalfloat.rs new file mode 100644 index 00000000000..e98074dd928 --- /dev/null +++ b/src/uu/seq/src/hexadecimalfloat.rs @@ -0,0 +1,404 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore extendedbigdecimal bigdecimal hexdigit numberparse +use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::number::PreciseNumber; +use crate::numberparse::ParseNumberError; +use bigdecimal::BigDecimal; +use num_traits::FromPrimitive; + +/// The base of the hex number system +const HEX_RADIX: u32 = 16; + +/// Parse a number from a floating-point hexadecimal exponent notation. +/// +/// # Errors +/// Returns [`Err`] if: +/// - the input string is not a valid hexadecimal string +/// - the input data can't be interpreted as ['f64'] or ['BigDecimal'] +/// +/// # Examples +/// +/// ```rust,ignore +/// let input = "0x1.4p-2"; +/// let expected = 0.3125; +/// match input.parse_number::().unwrap().number { +/// ExtendedBigDecimal::BigDecimal(bd) => assert_eq!(bd.to_f64().unwrap(),expected), +/// _ => unreachable!() +/// }; +/// ``` +pub fn parse_number(s: &str) -> Result { + // Parse floating point parts + let (sign, remain) = parse_sign_multiplier(s.trim())?; + let remain = parse_hex_prefix(remain)?; + let (integral_part, remain) = parse_integral_part(remain)?; + let (fractional_part, remain) = parse_fractional_part(remain)?; + let (exponent_part, remain) = parse_exponent_part(remain)?; + + // Check parts. Rise error if: + // - The input string is not fully consumed + // - Only integral part is presented + // - Only exponent part is presented + // - All 3 parts are empty + match ( + integral_part, + fractional_part, + exponent_part, + remain.is_empty(), + ) { + (_, _, _, false) + | (Some(_), None, None, _) + | (None, None, Some(_), _) + | (None, None, None, _) => return Err(ParseNumberError::Float), + _ => (), + }; + + // Build a number from parts + let integral_value = integral_part.unwrap_or(0.0); + let fractional_value = fractional_part.unwrap_or(0.0); + let exponent_value = (2.0_f64).powi(exponent_part.unwrap_or(0)); + let value = sign * (integral_value + fractional_value) * exponent_value; + + // Build a PreciseNumber + let number = BigDecimal::from_f64(value).ok_or(ParseNumberError::Float)?; + let num_fractional_digits = number.fractional_digit_count().max(0) as u64; + let num_integral_digits = if value.abs() < 1.0 { + 0 + } else { + number.digits() - num_fractional_digits + }; + let num_integral_digits = num_integral_digits + if sign < 0.0 { 1 } else { 0 }; + + Ok(PreciseNumber::new( + ExtendedBigDecimal::BigDecimal(number), + num_integral_digits as usize, + num_fractional_digits as usize, + )) +} + +// Detect number precision similar to GNU coreutils. Refer to scan_arg in seq.c. There are still +// some differences from the GNU version, but this should be sufficient to test the idea. +pub fn parse_precision(s: &str) -> Option { + let hex_index = s.find(['x', 'X']); + let point_index = s.find('.'); + + if hex_index.is_some() { + // Hex value. Returns: + // - 0 for a hexadecimal integer (filled above) + // - None for a hexadecimal floating-point number (the default value of precision) + let power_index = s.find(['p', 'P']); + if point_index.is_none() && power_index.is_none() { + // No decimal point and no 'p' (power) => integer => precision = 0 + return Some(0); + } else { + return None; + } + } + + // This is a decimal floating point. The precision depends on two parameters: + // - the number of fractional digits + // - the exponent + // Let's detect the number of fractional digits + let fractional_length = if let Some(point_index) = point_index { + s[point_index + 1..] + .chars() + .take_while(|c| c.is_ascii_digit()) + .count() + } else { + 0 + }; + + let mut precision = Some(fractional_length); + + // Let's update the precision if exponent is present + if let Some(exponent_index) = s.find(['e', 'E']) { + let exponent_value: i32 = s[exponent_index + 1..].parse().unwrap_or(0); + if exponent_value < 0 { + precision = precision.map(|p| p + exponent_value.unsigned_abs() as usize); + } else { + precision = precision.map(|p| p - p.min(exponent_value as usize)); + } + } + precision +} + +/// Parse the sign multiplier. +/// +/// If a sign is present, the function reads and converts it into a multiplier. +/// If no sign is present, a multiplier of 1.0 is used. +/// +/// # Errors +/// +/// Returns [`Err`] if the input string does not start with a recognized sign or '0' symbol. +fn parse_sign_multiplier(s: &str) -> Result<(f64, &str), ParseNumberError> { + if let Some(remain) = s.strip_prefix('-') { + Ok((-1.0, remain)) + } else if let Some(remain) = s.strip_prefix('+') { + Ok((1.0, remain)) + } else if s.starts_with('0') { + Ok((1.0, s)) + } else { + Err(ParseNumberError::Float) + } +} + +/// Parses the `0x` prefix in a case-insensitive manner. +/// +/// # Errors +/// +/// Returns [`Err`] if the input string does not contain the required prefix. +fn parse_hex_prefix(s: &str) -> Result<&str, ParseNumberError> { + if !(s.starts_with("0x") || s.starts_with("0X")) { + return Err(ParseNumberError::Float); + } + Ok(&s[2..]) +} + +/// Parse the integral part in hexadecimal notation. +/// +/// The integral part is hexadecimal number located after the '0x' prefix and before '.' or 'p' +/// symbols. For example, the number 0x1.234p2 has an integral part 1. +/// +/// This part is optional. +/// +/// # Errors +/// +/// Returns [`Err`] if the integral part is present but a hexadecimal number cannot be parsed from the input string. +fn parse_integral_part(s: &str) -> Result<(Option, &str), ParseNumberError> { + // This part is optional. Skip parsing if symbol is not a hex digit. + let length = s.chars().take_while(|c| c.is_ascii_hexdigit()).count(); + if length > 0 { + let integer = + u64::from_str_radix(&s[..length], HEX_RADIX).map_err(|_| ParseNumberError::Float)?; + Ok((Some(integer as f64), &s[length..])) + } else { + Ok((None, s)) + } +} + +/// Parse the fractional part in hexadecimal notation. +/// +/// The function calculates the sum of the digits after the '.' (dot) sign. Each Nth digit is +/// interpreted as digit / 16^n, where n represents the position after the dot starting from 1. +/// +/// For example, the number 0x1.234p2 has a fractional part 234, which can be interpreted as +/// 2/16^1 + 3/16^2 + 4/16^3, where 16 is the radix of the hexadecimal number system. This equals +/// 0.125 + 0.01171875 + 0.0009765625 = 0.1376953125 in decimal. And this is exactly what the +/// function does. +/// +/// This part is optional. +/// +/// # Errors +/// +/// Returns [`Err`] if the fractional part is present but a hexadecimal number cannot be parsed from the input string. +fn parse_fractional_part(s: &str) -> Result<(Option, &str), ParseNumberError> { + // This part is optional and follows after the '.' symbol. Skip parsing if the dot is not present. + if !s.starts_with('.') { + return Ok((None, s)); + } + + let s = &s[1..]; + let mut multiplier = 1.0 / HEX_RADIX as f64; + let mut total = 0.0; + let mut length = 0; + + for c in s.chars().take_while(|c| c.is_ascii_hexdigit()) { + let digit = c + .to_digit(HEX_RADIX) + .map(|x| x as u8) + .ok_or(ParseNumberError::Float)?; + total += (digit as f64) * multiplier; + multiplier /= HEX_RADIX as f64; + length += 1; + } + + if length == 0 { + return Err(ParseNumberError::Float); + } + Ok((Some(total), &s[length..])) +} + +/// Parse the exponent part in hexadecimal notation. +/// +/// The exponent part is a decimal number located after the 'p' symbol. +/// For example, the number 0x1.234p2 has an exponent part 2. +/// +/// This part is optional. +/// +/// # Errors +/// +/// Returns [`Err`] if the exponent part is presented but a decimal number cannot be parsed from +/// the input string. +fn parse_exponent_part(s: &str) -> Result<(Option, &str), ParseNumberError> { + // This part is optional and follows after 'p' or 'P' symbols. Skip parsing if the symbols are not present + if !(s.starts_with('p') || s.starts_with('P')) { + return Ok((None, s)); + } + + let s = &s[1..]; + let length = s + .chars() + .take_while(|c| c.is_ascii_digit() || *c == '-' || *c == '+') + .count(); + + if length == 0 { + return Err(ParseNumberError::Float); + } + + let value = s[..length].parse().map_err(|_| ParseNumberError::Float)?; + Ok((Some(value), &s[length..])) +} + +#[cfg(test)] +mod tests { + + use super::{parse_number, parse_precision}; + use crate::{numberparse::ParseNumberError, ExtendedBigDecimal}; + use bigdecimal::BigDecimal; + use num_traits::ToPrimitive; + + fn parse_big_decimal(s: &str) -> Result { + match parse_number(s)?.number { + ExtendedBigDecimal::BigDecimal(bd) => Ok(bd), + _ => Err(ParseNumberError::Float), + } + } + + fn parse_f64(s: &str) -> Result { + parse_big_decimal(s)? + .to_f64() + .ok_or(ParseNumberError::Float) + } + + #[test] + fn test_parse_precise_number_case_insensitive() { + assert_eq!(parse_f64("0x1P1").unwrap(), 2.0); + assert_eq!(parse_f64("0x1p1").unwrap(), 2.0); + } + + #[test] + fn test_parse_precise_number_plus_minus_prefixes() { + assert_eq!(parse_f64("+0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("-0x1p1").unwrap(), -2.0); + } + + #[test] + fn test_parse_precise_number_power_signs() { + assert_eq!(parse_f64("0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("0x1p+1").unwrap(), 2.0); + assert_eq!(parse_f64("0x1p-1").unwrap(), 0.5); + } + + #[test] + fn test_parse_precise_number_hex() { + assert_eq!(parse_f64("0xd.dp-1").unwrap(), 6.90625); + } + + #[test] + fn test_parse_precise_number_no_power() { + assert_eq!(parse_f64("0x123.a").unwrap(), 291.625); + } + + #[test] + fn test_parse_precise_number_no_fractional() { + assert_eq!(parse_f64("0x333p-4").unwrap(), 51.1875); + } + + #[test] + fn test_parse_precise_number_no_integral() { + assert_eq!(parse_f64("0x.9").unwrap(), 0.5625); + assert_eq!(parse_f64("0x.9p2").unwrap(), 2.25); + } + + #[test] + fn test_parse_precise_number_from_valid_values() { + assert_eq!(parse_f64("0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("+0x1p1").unwrap(), 2.0); + assert_eq!(parse_f64("-0x1p1").unwrap(), -2.0); + assert_eq!(parse_f64("0x1p-1").unwrap(), 0.5); + assert_eq!(parse_f64("0x1.8").unwrap(), 1.5); + assert_eq!(parse_f64("-0x1.8").unwrap(), -1.5); + assert_eq!(parse_f64("0x1.8p2").unwrap(), 6.0); + assert_eq!(parse_f64("0x1.8p+2").unwrap(), 6.0); + assert_eq!(parse_f64("0x1.8p-2").unwrap(), 0.375); + assert_eq!(parse_f64("0x.8").unwrap(), 0.5); + assert_eq!(parse_f64("0x10p0").unwrap(), 16.0); + assert_eq!(parse_f64("0x0.0").unwrap(), 0.0); + assert_eq!(parse_f64("0x0p0").unwrap(), 0.0); + assert_eq!(parse_f64("0x0.0p0").unwrap(), 0.0); + assert_eq!(parse_f64("-0x.1p-3").unwrap(), -0.0078125); + assert_eq!(parse_f64("-0x.ep-3").unwrap(), -0.109375); + } + + #[test] + fn test_parse_float_from_invalid_values() { + let expected_error = ParseNumberError::Float; + assert_eq!(parse_f64("").unwrap_err(), expected_error); + assert_eq!(parse_f64("1").unwrap_err(), expected_error); + assert_eq!(parse_f64("1p").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x").unwrap_err(), expected_error); + assert_eq!(parse_f64("0xG").unwrap_err(), expected_error); + assert_eq!(parse_f64("0xp").unwrap_err(), expected_error); + assert_eq!(parse_f64("0xp3").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1p").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1p+").unwrap_err(), expected_error); + assert_eq!(parse_f64("-0xx1p1").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.k").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1").unwrap_err(), expected_error); + assert_eq!(parse_f64("-0x1pa").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.1pk").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1.8p2z").unwrap_err(), expected_error); + assert_eq!(parse_f64("0x1p3.2").unwrap_err(), expected_error); + assert_eq!(parse_f64("-0x.ep-3z").unwrap_err(), expected_error); + } + + #[test] + fn test_parse_precise_number_count_digits() { + let precise_num = parse_number("0x1.2").unwrap(); // 1.125 decimal + assert_eq!(precise_num.num_integral_digits, 1); + assert_eq!(precise_num.num_fractional_digits, 3); + + let precise_num = parse_number("-0x1.2").unwrap(); // -1.125 decimal + assert_eq!(precise_num.num_integral_digits, 2); + assert_eq!(precise_num.num_fractional_digits, 3); + + let precise_num = parse_number("0x123.8").unwrap(); // 291.5 decimal + assert_eq!(precise_num.num_integral_digits, 3); + assert_eq!(precise_num.num_fractional_digits, 1); + + let precise_num = parse_number("-0x123.8").unwrap(); // -291.5 decimal + assert_eq!(precise_num.num_integral_digits, 4); + assert_eq!(precise_num.num_fractional_digits, 1); + } + + #[test] + fn test_parse_precision_valid_values() { + assert_eq!(parse_precision("1"), Some(0)); + assert_eq!(parse_precision("0x1"), Some(0)); + assert_eq!(parse_precision("0x1.1"), None); + assert_eq!(parse_precision("0x1.1p2"), None); + assert_eq!(parse_precision("0x1.1p-2"), None); + assert_eq!(parse_precision(".1"), Some(1)); + assert_eq!(parse_precision("1.1"), Some(1)); + assert_eq!(parse_precision("1.12"), Some(2)); + assert_eq!(parse_precision("1.12345678"), Some(8)); + assert_eq!(parse_precision("1.12345678e-3"), Some(11)); + assert_eq!(parse_precision("1.1e-1"), Some(2)); + assert_eq!(parse_precision("1.1e-3"), Some(4)); + } + + #[test] + fn test_parse_precision_invalid_values() { + // Just to make sure it doesn't crash on incomplete values/bad format + // Good enough for now. + assert_eq!(parse_precision("1."), Some(0)); + assert_eq!(parse_precision("1e"), Some(0)); + assert_eq!(parse_precision("1e-"), Some(0)); + assert_eq!(parse_precision("1e+"), Some(0)); + assert_eq!(parse_precision("1em"), Some(0)); + } +} diff --git a/src/uu/seq/src/number.rs b/src/uu/seq/src/number.rs index 314c842ba15..ec6ac0f1687 100644 --- a/src/uu/seq/src/number.rs +++ b/src/uu/seq/src/number.rs @@ -19,6 +19,8 @@ use crate::extendedbigdecimal::ExtendedBigDecimal; pub struct PreciseNumber { pub number: ExtendedBigDecimal, pub num_integral_digits: usize, + + #[allow(dead_code)] pub num_fractional_digits: usize, } diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index c8dec018041..478622515cc 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore extendedbigdecimal bigdecimal numberparse +// spell-checker:ignore extendedbigdecimal bigdecimal numberparse hexadecimalfloat //! Parsing numbers for use in `seq`. //! //! This module provides an implementation of [`FromStr`] for the @@ -16,6 +16,7 @@ use num_traits::Num; use num_traits::Zero; use crate::extendedbigdecimal::ExtendedBigDecimal; +use crate::hexadecimalfloat; use crate::number::PreciseNumber; /// An error returned when parsing a number fails. @@ -296,6 +297,14 @@ fn parse_decimal_and_exponent( /// assert_eq!(actual, expected); /// ``` fn parse_hexadecimal(s: &str) -> Result { + if s.find(['.', 'p', 'P']).is_some() { + hexadecimalfloat::parse_number(s) + } else { + parse_hexadecimal_integer(s) + } +} + +fn parse_hexadecimal_integer(s: &str) -> Result { let (is_neg, s) = if s.starts_with('-') { (true, &s[3..]) } else { diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index e14ba35a949..0ee5101d7ef 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) extendedbigdecimal numberparse +// spell-checker:ignore (ToDO) bigdecimal extendedbigdecimal numberparse hexadecimalfloat use std::ffi::OsString; use std::io::{stdout, ErrorKind, Write}; @@ -10,11 +10,13 @@ use clap::{crate_version, Arg, ArgAction, Command}; use num_traits::{ToPrimitive, Zero}; use uucore::error::{FromIo, UResult}; -use uucore::format::{num_format, Format}; +use uucore::format::{num_format, sprintf, Format, FormatArgument}; use uucore::{format_usage, help_about, help_usage}; mod error; mod extendedbigdecimal; +mod hexadecimalfloat; + // public to allow fuzzing #[cfg(fuzzing)] pub mod number; @@ -72,6 +74,18 @@ fn split_short_args_with_value(args: impl uucore::Args) -> impl uucore::Args { v.into_iter() } +fn select_precision( + first: Option, + increment: Option, + last: Option, +) -> Option { + match (first, increment, last) { + (Some(0), Some(0), Some(0)) => Some(0), + (Some(f), Some(i), Some(_)) => Some(f.max(i)), + _ => None, + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(split_short_args_with_value(args))?; @@ -99,32 +113,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { format: matches.get_one::(OPT_FORMAT).map(|s| s.as_str()), }; - let first = if numbers.len() > 1 { + let (first, first_precision) = if numbers.len() > 1 { match numbers[0].parse() { - Ok(num) => num, + Ok(num) => (num, hexadecimalfloat::parse_precision(numbers[0])), Err(e) => return Err(SeqError::ParseError(numbers[0].to_string(), e).into()), } } else { - PreciseNumber::one() + (PreciseNumber::one(), Some(0)) }; - let increment = if numbers.len() > 2 { + let (increment, increment_precision) = if numbers.len() > 2 { match numbers[1].parse() { - Ok(num) => num, + Ok(num) => (num, hexadecimalfloat::parse_precision(numbers[1])), Err(e) => return Err(SeqError::ParseError(numbers[1].to_string(), e).into()), } } else { - PreciseNumber::one() + (PreciseNumber::one(), Some(0)) }; if increment.is_zero() { return Err(SeqError::ZeroIncrement(numbers[1].to_string()).into()); } - let last: PreciseNumber = { + let (last, last_precision): (PreciseNumber, Option) = { // We are guaranteed that `numbers.len()` is greater than zero // and at most three because of the argument specification in // `uu_app()`. let n: usize = numbers.len(); match numbers[n - 1].parse() { - Ok(num) => num, + Ok(num) => (num, hexadecimalfloat::parse_precision(numbers[n - 1])), Err(e) => return Err(SeqError::ParseError(numbers[n - 1].to_string(), e).into()), } }; @@ -133,9 +147,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .num_integral_digits .max(increment.num_integral_digits) .max(last.num_integral_digits); - let largest_dec = first - .num_fractional_digits - .max(increment.num_fractional_digits); + + let precision = select_precision(first_precision, increment_precision, last_precision); let format = match options.format { Some(f) => { @@ -146,7 +159,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let result = print_seq( (first.number, increment.number, last.number), - largest_dec, + precision, &options.separator, &options.terminator, options.equal_width, @@ -210,26 +223,42 @@ fn done_printing(next: &T, increment: &T, last: &T) -> boo } } +fn format_bigdecimal(value: &bigdecimal::BigDecimal) -> Option { + let format_arguments = &[FormatArgument::Float(value.to_f64()?)]; + let value_as_bytes = sprintf("%g", format_arguments).ok()?; + String::from_utf8(value_as_bytes).ok() +} + /// Write a big decimal formatted according to the given parameters. fn write_value_float( writer: &mut impl Write, value: &ExtendedBigDecimal, width: usize, - precision: usize, + precision: Option, ) -> std::io::Result<()> { - let value_as_str = - if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity { - format!("{value:>width$.precision$}") - } else { - format!("{value:>0width$.precision$}") - }; + let value_as_str = match precision { + // format with precision: decimal floats and integers + Some(precision) => match value { + ExtendedBigDecimal::Infinity | ExtendedBigDecimal::MinusInfinity => { + format!("{value:>width$.precision$}") + } + _ => format!("{value:>0width$.precision$}"), + }, + // format without precision: hexadecimal floats + None => match value { + ExtendedBigDecimal::BigDecimal(bd) => { + format_bigdecimal(bd).unwrap_or_else(|| "{value}".to_owned()) + } + _ => format!("{value:>0width$}"), + }, + }; write!(writer, "{value_as_str}") } /// Floating point based code path fn print_seq( range: RangeFloat, - largest_dec: usize, + precision: Option, separator: &str, terminator: &str, pad: bool, @@ -241,7 +270,13 @@ fn print_seq( let (first, increment, last) = range; let mut value = first; let padding = if pad { - padding + if largest_dec > 0 { largest_dec + 1 } else { 0 } + let precision_value = precision.unwrap_or(0); + padding + + if precision_value > 0 { + precision_value + 1 + } else { + 0 + } } else { 0 }; @@ -273,7 +308,7 @@ fn print_seq( }; f.fmt(&mut stdout, float)?; } - None => write_value_float(&mut stdout, &value, padding, largest_dec)?, + None => write_value_float(&mut stdout, &value, padding, precision)?, } // TODO Implement augmenting addition. value = value + increment.clone(); diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 3ff9227345c..78fcb0068c7 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -698,7 +698,7 @@ fn test_parse_error_hex() { new_ucmd!() .arg("0xlmnop") .fails() - .usage_error("invalid hexadecimal argument: '0xlmnop'"); + .usage_error("invalid floating point argument: '0xlmnop'"); } #[test] @@ -826,3 +826,82 @@ fn test_parse_scientific_zero() { .succeeds() .stdout_only("0\n1\n"); } + +#[test] +fn test_parse_valid_hexadecimal_float_two_args() { + let test_cases = [ + (["0x1p-1", "2"], "0.5\n1.5\n"), + (["0x.8p16", "32768"], "32768\n"), + (["0xffff.4p-4", "4096"], "4095.95\n"), + (["0xA.A9p-1", "6"], "5.33008\n"), + (["0xa.a9p-1", "6"], "5.33008\n"), + (["0xffffffffffp-30", "1024"], "1024\n"), // spell-checker:disable-line + ]; + + for (input_arguments, expected_output) in &test_cases { + new_ucmd!() + .args(input_arguments) + .succeeds() + .stdout_only(expected_output); + } +} + +#[test] +fn test_parse_valid_hexadecimal_float_three_args() { + let test_cases = [ + (["0x3.4p-1", "0x4p-1", "4"], "1.625\n3.625\n"), + ( + ["-0x.ep-3", "-0x.1p-3", "-0x.fp-3"], + "-0.109375\n-0.117188\n", + ), + ]; + + for (input_arguments, expected_output) in &test_cases { + new_ucmd!() + .args(input_arguments) + .succeeds() + .stdout_only(expected_output); + } +} + +#[test] +fn test_parse_float_gnu_coreutils() { + // some values from GNU coreutils tests + new_ucmd!() + .args(&[".89999", "1e-7", ".8999901"]) + .succeeds() + .stdout_only("0.8999900\n0.8999901\n"); + + new_ucmd!() + .args(&["0", "0.000001", "0.000003"]) + .succeeds() + .stdout_only("0.000000\n0.000001\n0.000002\n0.000003\n"); +} + +#[ignore] +#[test] +fn test_parse_valid_hexadecimal_float_format_issues() { + // These tests detect differences in the representation of floating-point values with GNU seq. + // There are two key areas to investigate: + // + // 1. GNU seq uses long double (80-bit) types for values, while the current implementation + // relies on f64 (64-bit). This can lead to differences due to varying precision. However, it's + // likely not the primary cause, as even double (64-bit) values can differ when compared to + // f64. + // + // 2. GNU seq uses the %Lg format specifier for printing (see the "get_default_format" function + // ). It appears that Rust lacks a direct equivalent for this format. Additionally, %Lg + // can use %f (floating) or %e (scientific) depending on the precision. There also seem to be + // some differences in the behavior of C and Rust when displaying floating-point or scientific + // notation, at least without additional configuration. + // + // It makes sense to begin by experimenting with formats and attempting to replicate + // the printf("%Lg",...) behavior. Another area worth investigating is glibc, as reviewing its + // code may help uncover additional corner cases or test data that could reveal more issues. + + //Test output: 0.00000000992804416455328464508056640625 + new_ucmd!() + .args(&["0xa.a9p-30", "1"]) + .succeeds() + .stdout_only("9.92804e-09\n1\n"); +} From 1b5e321fc26f6bfc3b017aff7dc5f39b165744cb Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 15 Jan 2025 14:19:05 +0100 Subject: [PATCH 341/351] dircolors: move use declaration to top --- src/uu/dircolors/src/dircolors.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index c5b2e49f982..180be5e255f 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -15,7 +15,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES, TERMS}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::{help_about, help_section, help_usage}; +use uucore::{format_usage, help_about, help_section, help_usage, parse_glob}; mod options { pub const BOURNE_SHELL: &str = "bourne-shell"; @@ -358,7 +358,6 @@ enum ParseState { Pass, } -use uucore::{format_usage, parse_glob}; fn parse(user_input: T, fmt: &OutputFmt, fp: &str) -> Result where T: IntoIterator, From 4e45ca673c6ec7920a90274ad6127e1dbc41411e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 Jan 2025 23:20:50 +0100 Subject: [PATCH 342/351] comm: don't use files for test --- tests/by-util/test_comm.rs | 229 ++++++++++++++---- tests/fixtures/comm/a | 2 - tests/fixtures/comm/a_nul | Bin 3 -> 0 bytes tests/fixtures/comm/ab.expected | 3 - tests/fixtures/comm/ab1.expected | 2 - tests/fixtures/comm/ab2.expected | 2 - tests/fixtures/comm/ab3.expected | 2 - .../comm/ab_delimiter_hyphen_help.expected | 3 - .../comm/ab_delimiter_hyphen_one.expected | 3 - tests/fixtures/comm/ab_delimiter_nul.expected | Bin 9 -> 0 bytes .../fixtures/comm/ab_delimiter_word.expected | 3 - tests/fixtures/comm/ab_nul.expected | Bin 9 -> 0 bytes tests/fixtures/comm/ab_nul_total.expected | Bin 21 -> 0 bytes tests/fixtures/comm/ab_total.expected | 4 - .../comm/ab_total_delimiter_word.expected | 4 - ...b_total_suppressed_regular_output.expected | 1 - tests/fixtures/comm/aempty.expected | 2 - tests/fixtures/comm/b | 2 - tests/fixtures/comm/b_nul | Bin 3 -> 0 bytes .../bad_order11.defaultcheck_order.expected | 4 - .../comm/bad_order12.check_order.expected | 1 - .../comm/bad_order12.nocheck_order.expected | 7 - tests/fixtures/comm/bad_order_1 | 4 - tests/fixtures/comm/bad_order_2 | 4 - .../comm/defaultcheck_unintuitive.expected | 6 - .../fixtures/comm/defaultcheck_unintuitive_1 | 6 - .../fixtures/comm/defaultcheck_unintuitive_2 | 5 - tests/fixtures/comm/empty | 0 tests/fixtures/comm/emptyempty.expected | 0 tests/fixtures/comm/lowercase_uppercase | 2 - .../comm/lowercase_uppercase.c.expected | 1 - .../comm/lowercase_uppercase.en_us.expected | 2 - 32 files changed, 181 insertions(+), 123 deletions(-) delete mode 100644 tests/fixtures/comm/a delete mode 100644 tests/fixtures/comm/a_nul delete mode 100644 tests/fixtures/comm/ab.expected delete mode 100644 tests/fixtures/comm/ab1.expected delete mode 100644 tests/fixtures/comm/ab2.expected delete mode 100644 tests/fixtures/comm/ab3.expected delete mode 100644 tests/fixtures/comm/ab_delimiter_hyphen_help.expected delete mode 100644 tests/fixtures/comm/ab_delimiter_hyphen_one.expected delete mode 100644 tests/fixtures/comm/ab_delimiter_nul.expected delete mode 100644 tests/fixtures/comm/ab_delimiter_word.expected delete mode 100644 tests/fixtures/comm/ab_nul.expected delete mode 100644 tests/fixtures/comm/ab_nul_total.expected delete mode 100644 tests/fixtures/comm/ab_total.expected delete mode 100644 tests/fixtures/comm/ab_total_delimiter_word.expected delete mode 100644 tests/fixtures/comm/ab_total_suppressed_regular_output.expected delete mode 100644 tests/fixtures/comm/aempty.expected delete mode 100644 tests/fixtures/comm/b delete mode 100644 tests/fixtures/comm/b_nul delete mode 100644 tests/fixtures/comm/bad_order11.defaultcheck_order.expected delete mode 100644 tests/fixtures/comm/bad_order12.check_order.expected delete mode 100644 tests/fixtures/comm/bad_order12.nocheck_order.expected delete mode 100644 tests/fixtures/comm/bad_order_1 delete mode 100644 tests/fixtures/comm/bad_order_2 delete mode 100644 tests/fixtures/comm/defaultcheck_unintuitive.expected delete mode 100644 tests/fixtures/comm/defaultcheck_unintuitive_1 delete mode 100644 tests/fixtures/comm/defaultcheck_unintuitive_2 delete mode 100644 tests/fixtures/comm/empty delete mode 100644 tests/fixtures/comm/emptyempty.expected delete mode 100644 tests/fixtures/comm/lowercase_uppercase delete mode 100644 tests/fixtures/comm/lowercase_uppercase.c.expected delete mode 100644 tests/fixtures/comm/lowercase_uppercase.en_us.expected diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index b62febf5047..ac0d11c0908 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) defaultcheck nocheck +// spell-checker:ignore (words) defaultcheck nocheck helpb helpz nwordb nwordwordz wordtotal use crate::common::util::TestScenario; @@ -13,111 +13,184 @@ fn test_invalid_arg() { #[test] fn ab_no_args() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + + scene + .ucmd() .args(&["a", "b"]) .succeeds() - .stdout_only_fixture("ab.expected"); + .stdout_is("a\n\tb\n\t\tz\n"); } #[test] fn ab_dash_one() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + + scene + .ucmd() .args(&["a", "b", "-1"]) .succeeds() - .stdout_only_fixture("ab1.expected"); + .stdout_is("b\n\tz\n"); } #[test] fn ab_dash_two() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + + scene + .ucmd() .args(&["a", "b", "-2"]) .succeeds() - .stdout_only_fixture("ab2.expected"); + .stdout_is("a\n\tz\n"); } #[test] fn ab_dash_three() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + + scene + .ucmd() .args(&["a", "b", "-3"]) .succeeds() - .stdout_only_fixture("ab3.expected"); + .stdout_is("a\n\tb\n"); } #[test] fn a_empty() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.touch("empty"); + scene + .ucmd() .args(&["a", "empty"]) .succeeds() - .stdout_only_fixture("aempty.expected"); // spell-checker:disable-line + .stdout_is("a\nz\n"); } #[test] fn empty_empty() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("empty"); + scene + .ucmd() .args(&["empty", "empty"]) .succeeds() - .stdout_only_fixture("emptyempty.expected"); // spell-checker:disable-line + .no_output(); } #[test] fn total() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--total", "a", "b"]) .succeeds() - .stdout_is_fixture("ab_total.expected"); + .stdout_is("a\n\tb\n\t\tz\n1\t1\t1\ttotal\n"); } #[test] fn total_with_suppressed_regular_output() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--total", "-123", "a", "b"]) .succeeds() - .stdout_is_fixture("ab_total_suppressed_regular_output.expected"); + .stdout_is("1\t1\t1\ttotal\n"); } #[test] fn repeated_flags() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--total", "-123123", "--total", "a", "b"]) .succeeds() - .stdout_is_fixture("ab_total_suppressed_regular_output.expected"); + .stdout_is("1\t1\t1\ttotal\n"); } #[test] fn total_with_output_delimiter() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--total", "--output-delimiter=word", "a", "b"]) .succeeds() - .stdout_is_fixture("ab_total_delimiter_word.expected"); + .stdout_is("a\nwordb\nwordwordz\n1word1word1wordtotal\n"); } #[test] fn output_delimiter() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--output-delimiter=word", "a", "b"]) .succeeds() - .stdout_only_fixture("ab_delimiter_word.expected"); + .stdout_is("a\nwordb\nwordwordz\n"); } #[test] fn output_delimiter_hyphen_one() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--output-delimiter", "-1", "a", "b"]) .succeeds() - .stdout_only_fixture("ab_delimiter_hyphen_one.expected"); + .stdout_is("a\n-1b\n-1-1z\n"); } #[test] fn output_delimiter_hyphen_help() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--output-delimiter", "--help", "a", "b"]) .succeeds() - .stdout_only_fixture("ab_delimiter_hyphen_help.expected"); + .stdout_is("a\n--helpb\n--help--helpz\n"); } #[test] fn output_delimiter_multiple_identical() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&[ "--output-delimiter=word", "--output-delimiter=word", @@ -125,12 +198,17 @@ fn output_delimiter_multiple_identical() { "b", ]) .succeeds() - .stdout_only_fixture("ab_delimiter_word.expected"); + .stdout_is("a\nwordb\nwordwordz\n"); } #[test] fn output_delimiter_multiple_different() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&[ "--output-delimiter=word", "--output-delimiter=other", @@ -147,7 +225,12 @@ fn output_delimiter_multiple_different() { #[test] #[ignore = "This is too weird; deviate intentionally."] fn output_delimiter_multiple_different_prevents_help() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&[ "--output-delimiter=word", "--output-delimiter=other", @@ -164,59 +247,92 @@ fn output_delimiter_multiple_different_prevents_help() { #[test] fn output_delimiter_nul() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("a", "a\nz\n"); + at.write("b", "b\nz\n"); + scene + .ucmd() .args(&["--output-delimiter=", "a", "b"]) .succeeds() - .stdout_only_fixture("ab_delimiter_nul.expected"); + .stdout_is("a\n\0b\n\0\0z\n"); } #[test] fn zero_terminated() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("a_nul", "a\0z\0"); + at.write("b_nul", "b\0z\0"); for param in ["-z", "--zero-terminated"] { - new_ucmd!() + scene + .ucmd() .args(&[param, "a_nul", "b_nul"]) .succeeds() - .stdout_only_fixture("ab_nul.expected"); + .stdout_is("a\0\tb\0\t\tz\0"); } } #[test] fn zero_terminated_provided_multiple_times() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("a_nul", "a\0z\0"); + at.write("b_nul", "b\0z\0"); for param in ["-z", "--zero-terminated"] { - new_ucmd!() + scene + .ucmd() .args(&[param, param, param, "a_nul", "b_nul"]) .succeeds() - .stdout_only_fixture("ab_nul.expected"); + .stdout_is("a\0\tb\0\t\tz\0"); } } #[test] fn zero_terminated_with_total() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("a_nul", "a\0z\0"); + at.write("b_nul", "b\0z\0"); + for param in ["-z", "--zero-terminated"] { - new_ucmd!() + scene + .ucmd() .args(&[param, "--total", "a_nul", "b_nul"]) .succeeds() - .stdout_only_fixture("ab_nul_total.expected"); + .stdout_is("a\0\tb\0\t\tz\x001\t1\t1\ttotal\0"); } } #[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn check_order() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("bad_order_1", "e\nd\nb\na\n"); + at.write("bad_order_2", "e\nc\nb\na\n"); + scene + .ucmd() .args(&["--check-order", "bad_order_1", "bad_order_2"]) .fails() - .stdout_is_fixture("bad_order12.check_order.expected") + .stdout_is("\t\te") .stderr_is("error to be defined"); } #[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn nocheck_order() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("bad_order_1", "e\nd\nb\na\n"); + at.write("bad_order_2", "e\nc\nb\na\n"); new_ucmd!() .args(&["--nocheck-order", "bad_order_1", "bad_order_2"]) .succeeds() - .stdout_only_fixture("bad_order12.nocheck_order.expected"); + .stdout_is("\t\te\n\tc\n\tb\n\ta\nd\nb\na\n"); } // when neither --check-order nor --no-check-order is provided, @@ -225,6 +341,9 @@ fn nocheck_order() { #[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn defaultcheck_order() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("bad_order_1", "e\nd\nb\na\n"); new_ucmd!() .args(&["a", "bad_order_1"]) .fails() @@ -240,19 +359,28 @@ fn defaultcheck_order() { #[test] fn defaultcheck_order_identical_bad_order_files() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("bad_order_1", "e\nd\nb\na\n"); + scene + .ucmd() .args(&["bad_order_1", "bad_order_1"]) .succeeds() - .stdout_only_fixture("bad_order11.defaultcheck_order.expected"); + .stdout_is("\t\te\n\t\td\n\t\tb\n\t\ta\n"); } #[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn defaultcheck_order_two_different_bad_order_files() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("bad_order_1", "e\nd\nb\na\n"); + at.write("bad_order_2", "e\nc\nb\na\n"); + scene + .ucmd() .args(&["bad_order_1", "bad_order_2"]) .fails() - .stdout_is_fixture("bad_order12.nocheck_order.expected") + .stdout_is("\t\te\n\tc\n\tb\n\ta\nd\nb\na\n") .stderr_is("error to be defined"); } @@ -269,10 +397,15 @@ fn defaultcheck_order_two_different_bad_order_files() { #[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn unintuitive_default_behavior_1() { - new_ucmd!() + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("defaultcheck_unintuitive_1", "m\nh\nn\no\nc\np\n"); + at.write("defaultcheck_unintuitive_2", "m\nh\nn\no\np\n"); + scene + .ucmd() .args(&["defaultcheck_unintuitive_1", "defaultcheck_unintuitive_2"]) .succeeds() - .stdout_only_fixture("defaultcheck_unintuitive.expected"); + .stdout_is("\t\tm\n\t\th\n\t\tn\n\t\to\nc\n\t\tp\n"); } #[test] diff --git a/tests/fixtures/comm/a b/tests/fixtures/comm/a deleted file mode 100644 index 58f424cdb4f..00000000000 --- a/tests/fixtures/comm/a +++ /dev/null @@ -1,2 +0,0 @@ -a -z diff --git a/tests/fixtures/comm/a_nul b/tests/fixtures/comm/a_nul deleted file mode 100644 index 3142c801ccd54e1a9bc9d587e5ab16369ead768d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3 KcmYdfr~&{1pa9$e diff --git a/tests/fixtures/comm/ab.expected b/tests/fixtures/comm/ab.expected deleted file mode 100644 index 0efac0666b0..00000000000 --- a/tests/fixtures/comm/ab.expected +++ /dev/null @@ -1,3 +0,0 @@ -a - b - z diff --git a/tests/fixtures/comm/ab1.expected b/tests/fixtures/comm/ab1.expected deleted file mode 100644 index 7c81c8ba7cb..00000000000 --- a/tests/fixtures/comm/ab1.expected +++ /dev/null @@ -1,2 +0,0 @@ -b - z diff --git a/tests/fixtures/comm/ab2.expected b/tests/fixtures/comm/ab2.expected deleted file mode 100644 index c98cd0da66d..00000000000 --- a/tests/fixtures/comm/ab2.expected +++ /dev/null @@ -1,2 +0,0 @@ -a - z diff --git a/tests/fixtures/comm/ab3.expected b/tests/fixtures/comm/ab3.expected deleted file mode 100644 index 1898bc804f2..00000000000 --- a/tests/fixtures/comm/ab3.expected +++ /dev/null @@ -1,2 +0,0 @@ -a - b diff --git a/tests/fixtures/comm/ab_delimiter_hyphen_help.expected b/tests/fixtures/comm/ab_delimiter_hyphen_help.expected deleted file mode 100644 index c245aa27f13..00000000000 --- a/tests/fixtures/comm/ab_delimiter_hyphen_help.expected +++ /dev/null @@ -1,3 +0,0 @@ -a ---helpb ---help--helpz diff --git a/tests/fixtures/comm/ab_delimiter_hyphen_one.expected b/tests/fixtures/comm/ab_delimiter_hyphen_one.expected deleted file mode 100644 index 458f98dd42f..00000000000 --- a/tests/fixtures/comm/ab_delimiter_hyphen_one.expected +++ /dev/null @@ -1,3 +0,0 @@ -a --1b --1-1z diff --git a/tests/fixtures/comm/ab_delimiter_nul.expected b/tests/fixtures/comm/ab_delimiter_nul.expected deleted file mode 100644 index e56ec5cd7895d1dfb5d3015f08c64ee28621b4b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9 QcmYe~Vo2g*V5s5(00%w+TmS$7 diff --git a/tests/fixtures/comm/ab_delimiter_word.expected b/tests/fixtures/comm/ab_delimiter_word.expected deleted file mode 100644 index a0c510e2071..00000000000 --- a/tests/fixtures/comm/ab_delimiter_word.expected +++ /dev/null @@ -1,3 +0,0 @@ -a -wordb -wordwordz diff --git a/tests/fixtures/comm/ab_nul.expected b/tests/fixtures/comm/ab_nul.expected deleted file mode 100644 index f826106bb1926f3ce2c6553b72bb0a007c432f4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9 QcmYdf;7nrR Date: Wed, 15 Jan 2025 20:54:52 +0100 Subject: [PATCH 343/351] seq:add bounds for exponents Add bounds for exponents to avoid overflow issues for inputs like 'seq 1e-9223372036854775808' --- src/uu/seq/src/numberparse.rs | 35 ++++++++++++++++++++++++++--------- tests/by-util/test_seq.rs | 10 ++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 478622515cc..d00db16fa13 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -103,15 +103,18 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result() - .map_err(|_| ParseNumberError::Float)?; - if parsed_decimal == BigDecimal::zero() { - BigDecimal::zero() - } else { - parsed_decimal - } + + // In ['BigDecimal'], a positive scale represents a negative power of 10. + // This means the exponent value from the number must be inverted. However, + // since the |i64::MIN| > |i64::MAX| (i.e. |−2^63| > |2^63−1|) inverting a + // valid negative value could result in an overflow. To prevent this, we + // limit the minimal value with i64::MIN + 1. + let exponent = exponent.max(i64::MIN + 1); + let base: BigInt = s[..j].parse().map_err(|_| ParseNumberError::Float)?; + let x = if base.is_zero() { + BigDecimal::zero() + } else { + BigDecimal::from_bigint(base, -exponent) }; let num_integral_digits = if is_minus_zero_float(s, &x) { @@ -599,4 +602,18 @@ mod tests { assert_eq!(num_fractional_digits("-0e-1"), 1); assert_eq!(num_fractional_digits("-0.0e-1"), 2); } + + #[test] + fn test_parse_min_exponents() { + // Make sure exponents <= i64::MIN do not cause errors + assert!("1e-9223372036854775807".parse::().is_ok()); + assert!("1e-9223372036854775808".parse::().is_ok()); + } + + #[test] + fn test_parse_max_exponents() { + // Make sure exponents >= i64::MAX cause errors + assert!("1e9223372036854775807".parse::().is_err()); + assert!("1e9223372036854775808".parse::().is_err()); + } } diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 78fcb0068c7..d8ab71842a4 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -878,6 +878,16 @@ fn test_parse_float_gnu_coreutils() { .stdout_only("0.000000\n0.000001\n0.000002\n0.000003\n"); } +#[test] +fn test_parse_out_of_bounds_exponents() { + // The value 1e-9223372036854775808 is used in GNU Coreutils and BigDecimal tests to verify + // overflows and undefined behavior. Let's test the value too. + new_ucmd!() + .args(&["1e-9223372036854775808"]) + .succeeds() + .stdout_only(""); +} + #[ignore] #[test] fn test_parse_valid_hexadecimal_float_format_issues() { From 8b93c066f4d1672bfd57693d03dc240e08ed5b37 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 16 Jan 2025 10:29:47 +0100 Subject: [PATCH 344/351] why-error: fixed tests/seq/seq-precision by https://github.com/uutils/coreutils/pull/7145 --- util/why-error.md | 1 - 1 file changed, 1 deletion(-) diff --git a/util/why-error.md b/util/why-error.md index 39ca5c1781e..44c4a9e9728 100644 --- a/util/why-error.md +++ b/util/why-error.md @@ -53,7 +53,6 @@ This file documents why some tests are failing: * 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 From 2e4d4b4599894b53a8c127dd9aa0478af205c140 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 16 Jan 2025 10:55:07 +0100 Subject: [PATCH 345/351] fuzz: bump bigdecimal from 0.4.3 to 0.4.7 --- fuzz/Cargo.lock | 113 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index e07a770f40a..12873851297 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -101,9 +101,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bigdecimal" -version = "0.4.3" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9324c8014cd04590682b34f1e9448d38f0674d0f7b2dc553331016ef0e4e9ebc" +checksum = "7f31f3af01c5c65a07985c804d3366560e6fa7883d640a122819b14ec327482c" dependencies = [ "autocfg", "libm", @@ -230,6 +230,28 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "chrono-tz" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "4.5.4" @@ -722,6 +744,15 @@ dependencies = [ "unicode-width 0.1.12", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "parse_datetime" version = "0.6.0" @@ -733,6 +764,44 @@ dependencies = [ "regex", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -936,6 +1005,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "sm3" version = "0.4.2" @@ -1064,7 +1139,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_cksum" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "hex", @@ -1074,7 +1149,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.28" +version = "0.0.29" dependencies = [ "bstr", "clap", @@ -1084,10 +1159,12 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.28" +version = "0.0.29" dependencies = [ "chrono", + "chrono-tz", "clap", + "iana-time-zone", "libc", "parse_datetime", "uucore", @@ -1096,7 +1173,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -1104,7 +1181,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nix 0.29.0", @@ -1114,7 +1191,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "num-bigint", @@ -1125,7 +1202,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "uucore", @@ -1133,7 +1210,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.28" +version = "0.0.29" dependencies = [ "bigdecimal", "clap", @@ -1144,7 +1221,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.28" +version = "0.0.29" dependencies = [ "binary-heap-plus", "clap", @@ -1164,7 +1241,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "memchr", @@ -1173,7 +1250,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "libc", @@ -1182,7 +1259,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.28" +version = "0.0.29" dependencies = [ "clap", "nom", @@ -1191,7 +1268,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.28" +version = "0.0.29" dependencies = [ "bytecount", "clap", @@ -1204,7 +1281,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.28" +version = "0.0.29" dependencies = [ "blake2b_simd", "blake3", @@ -1264,7 +1341,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.28" +version = "0.0.29" dependencies = [ "proc-macro2", "quote", @@ -1273,7 +1350,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.28" +version = "0.0.29" [[package]] name = "version_check" From 6bedec53add8571f7757f89195004abe46dd14ab Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 16 Jan 2025 09:05:27 +0100 Subject: [PATCH 346/351] Bump chrono-tz from 0.8.6 to 0.10.0 --- Cargo.lock | 9 ++++----- Cargo.toml | 4 ++-- fuzz/Cargo.lock | 9 ++++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a17afd7cbb..ed2da1859c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.8.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" dependencies = [ "chrono", "chrono-tz-build", @@ -315,12 +315,11 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" dependencies = [ "parse-zoneinfo", - "phf", "phf_codegen", ] diff --git a/Cargo.toml b/Cargo.toml index 2fb991d5705..ea87ccea79b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -278,10 +278,9 @@ chrono = { version = "0.4.38", default-features = false, features = [ "alloc", "clock", ] } +chrono-tz = "0.10.0" clap = { version = "4.5", features = ["wrap_help", "cargo"] } clap_complete = "4.4" -chrono-tz = "0.8.3" -iana-time-zone = "0.1.57" clap_mangen = "0.2" compare = "0.1.0" coz = { version = "0.1.3" } @@ -299,6 +298,7 @@ gcd = "2.3" glob = "0.3.1" half = "2.4.1" hostname = "0.4" +iana-time-zone = "0.1.57" indicatif = "0.17.8" itertools = "0.14.0" libc = "0.2.153" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 12873851297..db31d38847d 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -232,9 +232,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.8.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" dependencies = [ "chrono", "chrono-tz-build", @@ -243,12 +243,11 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" dependencies = [ "parse-zoneinfo", - "phf", "phf_codegen", ] From ada18863a7fcaf2534f23499615b03b7dee1fe07 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 16 Jan 2025 09:37:51 +0100 Subject: [PATCH 347/351] date: adapt to API change in chrono-tz --- src/uu/date/src/date.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index d91f8f82c71..24aaf6967ff 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -288,7 +288,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f` let format_string = &format_string .replace("%N", "%f") - .replace("%Z", tz_abbreviation); + .replace("%Z", tz_abbreviation.unwrap_or("UTC")); // Refuse to pass this string to chrono as it is crashing in this crate if format_string.contains("%#z") { return Err(USimpleError::new( From 7edd045206b89f0a22b88ed0ae9336e361947eb0 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 16 Jan 2025 09:41:04 +0100 Subject: [PATCH 348/351] date: use UTC if TZ is empty --- src/uu/date/src/date.rs | 2 +- tests/by-util/test_date.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 24aaf6967ff..f4d420c3fd2 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -277,7 +277,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // TODO - Revisit when chrono 0.5 is released. https://github.com/chronotope/chrono/issues/970 let tz = match std::env::var("TZ") { // TODO Support other time zones... - Ok(s) if s == "UTC0" => Tz::Etc__UTC, + Ok(s) if s == "UTC0" || s.is_empty() => Tz::Etc__UTC, _ => match get_timezone() { Ok(tz_str) => tz_str.parse().unwrap(), Err(_) => Tz::Etc__UTC, diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index d0a9c09a201..ac16fe83145 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -482,3 +482,12 @@ fn test_date_from_stdin() { Sat Apr 15 18:30:00 UTC 2023\n", ); } + +#[test] +fn test_date_empty_tz() { + new_ucmd!() + .env("TZ", "") + .arg("+%Z") + .succeeds() + .stdout_only("UTC\n"); +} From 498f63ca12025fd20898bc9eb617abb8f9fbadb4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:38:10 +0000 Subject: [PATCH 349/351] chore(deps): update rust crate chrono-tz to v0.10.1 --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed2da1859c4..1b2a67c13aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" +checksum = "9c6ac4f2c0bf0f44e9161aec9675e1050aa4a530663c4a9e37e108fa948bca9f" dependencies = [ "chrono", "chrono-tz-build", @@ -1275,7 +1275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] From 96c17312eadf4a04091d224b22e5aa8378976ce1 Mon Sep 17 00:00:00 2001 From: Yykz Date: Tue, 19 Dec 2023 17:34:56 +0100 Subject: [PATCH 350/351] df: fix display of special characters Replace the display of certain special characters in `df`: ASCII space for plain space character, ASCII horizontal tab for plain tab character, and ASCII backslash for plain backslash character. Co-authored-by: Jeffrey Finkelstein --- src/uucore/src/lib/features/fsext.rs | 41 ++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index e248332c9d1..fa961388b62 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -137,6 +137,19 @@ pub struct MountInfo { pub dummy: bool, } +#[cfg(any(target_os = "linux", target_os = "android"))] +fn replace_special_chars(s: String) -> String { + // Replace + // + // * ASCII space with a regular space character, + // * \011 ASCII horizontal tab with a tab character, + // * ASCII backslash with an actual backslash character. + // + s.replace(r#"\040"#, " ") + .replace(r#"\011"#, " ") + .replace(r#"\134"#, r#"\"#) +} + impl MountInfo { #[cfg(any(target_os = "linux", target_os = "android"))] fn new(file_name: &str, raw: &[&str]) -> Option { @@ -158,14 +171,14 @@ impl MountInfo { dev_name = raw[after_fields + 1].to_string(); fs_type = raw[after_fields].to_string(); mount_root = raw[3].to_string(); - mount_dir = raw[4].to_string(); + mount_dir = replace_special_chars(raw[4].to_string()); mount_option = raw[5].to_string(); } LINUX_MTAB => { dev_name = raw[0].to_string(); fs_type = raw[2].to_string(); mount_root = String::new(); - mount_dir = raw[1].to_string(); + mount_dir = replace_special_chars(raw[1].to_string()); mount_option = raw[3].to_string(); } _ => return None, @@ -1081,4 +1094,28 @@ mod tests { assert_eq!(info.fs_type, "xfs"); assert_eq!(info.dev_name, "/dev/fs0"); } + + #[test] + #[cfg(any(target_os = "linux", target_os = "android"))] + fn test_mountinfo_dir_special_chars() { + let info = MountInfo::new( + LINUX_MOUNTINFO, + &r#"317 61 7:0 / /mnt/f\134\040\011oo rw,relatime shared:641 - ext4 /dev/loop0 rw"# + .split_ascii_whitespace() + .collect::>(), + ) + .unwrap(); + + assert_eq!(info.mount_dir, r#"/mnt/f\ oo"#); + + let info = MountInfo::new( + LINUX_MTAB, + &r#"/dev/loop0 /mnt/f\134\040\011oo ext4 rw,relatime 0 0"# + .split_ascii_whitespace() + .collect::>(), + ) + .unwrap(); + + assert_eq!(info.mount_dir, r#"/mnt/f\ oo"#); + } } From b50a012d1302afb40dd49d1d5796d5fd74754d04 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 15 Jan 2025 21:18:04 +0100 Subject: [PATCH 351/351] comm: implement the ordering check A few comments: * skip if the two args are pointing to the same file * skip if the same content in the two files * implement --check-order * implement --nocheck-order * output the right things on stderr Should pass: tests/misc/comm --- src/uu/comm/Cargo.toml | 2 +- src/uu/comm/src/comm.rs | 160 +++++++++++++++++++++++++++++++++++-- tests/by-util/test_comm.rs | 153 ++++++++++++++++++++++++++++++----- 3 files changed, 291 insertions(+), 24 deletions(-) diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index dd691971555..ce250c554c3 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -18,7 +18,7 @@ path = "src/comm.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["fs"] } [[bin]] name = "comm" diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index ae57b8bf8a0..e075830cb85 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -3,12 +3,13 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) delim mkdelim +// spell-checker:ignore (ToDO) delim mkdelim pairable use std::cmp::Ordering; use std::fs::{metadata, File}; -use std::io::{self, stdin, BufRead, BufReader, Stdin}; +use std::io::{self, stdin, BufRead, BufReader, Read, Stdin}; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::fs::paths_refer_to_same_file; use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; @@ -27,6 +28,30 @@ mod options { pub const FILE_2: &str = "FILE2"; pub const TOTAL: &str = "total"; pub const ZERO_TERMINATED: &str = "zero-terminated"; + pub const CHECK_ORDER: &str = "check-order"; + pub const NO_CHECK_ORDER: &str = "nocheck-order"; +} + +#[derive(Debug, Clone, Copy)] +enum FileNumber { + One, + Two, +} + +impl FileNumber { + fn as_str(&self) -> &'static str { + match self { + FileNumber::One => "1", + FileNumber::Two => "2", + } + } +} + +struct OrderChecker { + last_line: Vec, + file_num: FileNumber, + check_order: bool, + has_error: bool, } enum Input { @@ -60,7 +85,74 @@ impl LineReader { } } -fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) { +impl OrderChecker { + fn new(file_num: FileNumber, check_order: bool) -> Self { + Self { + last_line: Vec::new(), + file_num, + check_order, + has_error: false, + } + } + + fn verify_order(&mut self, current_line: &[u8]) -> bool { + if self.last_line.is_empty() { + self.last_line = current_line.to_vec(); + return true; + } + + let is_ordered = current_line >= &self.last_line; + if !is_ordered && !self.has_error { + eprintln!( + "comm: file {} is not in sorted order", + self.file_num.as_str() + ); + self.has_error = true; + } + + self.last_line = current_line.to_vec(); + is_ordered || !self.check_order + } +} + +// Check if two files are identical by comparing their contents +pub fn are_files_identical(path1: &str, path2: &str) -> io::Result { + // First compare file sizes + let metadata1 = std::fs::metadata(path1)?; + let metadata2 = std::fs::metadata(path2)?; + + if metadata1.len() != metadata2.len() { + return Ok(false); + } + + let file1 = File::open(path1)?; + let file2 = File::open(path2)?; + + let mut reader1 = BufReader::new(file1); + let mut reader2 = BufReader::new(file2); + + let mut buffer1 = [0; 8192]; + let mut buffer2 = [0; 8192]; + + loop { + let bytes1 = reader1.read(&mut buffer1)?; + let bytes2 = reader2.read(&mut buffer2)?; + + if bytes1 != bytes2 { + return Ok(false); + } + + if bytes1 == 0 { + return Ok(true); + } + + if buffer1[..bytes1] != buffer2[..bytes2] { + return Ok(false); + } + } +} + +fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) -> UResult<()> { let width_col_1 = usize::from(!opts.get_flag(options::COLUMN_1)); let width_col_2 = usize::from(!opts.get_flag(options::COLUMN_2)); @@ -76,6 +168,26 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) let mut total_col_2 = 0; let mut total_col_3 = 0; + let check_order = opts.get_flag(options::CHECK_ORDER); + let no_check_order = opts.get_flag(options::NO_CHECK_ORDER); + + // Determine if we should perform order checking + let should_check_order = !no_check_order + && (check_order + || if let (Some(file1), Some(file2)) = ( + opts.get_one::(options::FILE_1), + opts.get_one::(options::FILE_2), + ) { + !(paths_refer_to_same_file(file1, file2, true) + || are_files_identical(file1, file2).unwrap_or(false)) + } else { + true + }); + + let mut checker1 = OrderChecker::new(FileNumber::One, check_order); + let mut checker2 = OrderChecker::new(FileNumber::Two, check_order); + let mut input_error = false; + while na.is_ok() || nb.is_ok() { let ord = match (na.is_ok(), nb.is_ok()) { (false, true) => Ordering::Greater, @@ -91,6 +203,9 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) match ord { Ordering::Less => { + if should_check_order && !checker1.verify_order(ra) { + break; + } if !opts.get_flag(options::COLUMN_1) { print!("{}", String::from_utf8_lossy(ra)); } @@ -99,6 +214,9 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) total_col_1 += 1; } Ordering::Greater => { + if should_check_order && !checker2.verify_order(rb) { + break; + } if !opts.get_flag(options::COLUMN_2) { print!("{delim_col_2}{}", String::from_utf8_lossy(rb)); } @@ -107,6 +225,10 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) total_col_2 += 1; } Ordering::Equal => { + if should_check_order && (!checker1.verify_order(ra) || !checker2.verify_order(rb)) + { + break; + } if !opts.get_flag(options::COLUMN_3) { print!("{delim_col_3}{}", String::from_utf8_lossy(ra)); } @@ -117,12 +239,27 @@ fn comm(a: &mut LineReader, b: &mut LineReader, delim: &str, opts: &ArgMatches) total_col_3 += 1; } } + + // Track if we've seen any order errors + if (checker1.has_error || checker2.has_error) && !input_error && !check_order { + input_error = true; + } } if opts.get_flag(options::TOTAL) { let line_ending = LineEnding::from_zero_flag(opts.get_flag(options::ZERO_TERMINATED)); print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}"); } + + if should_check_order && (checker1.has_error || checker2.has_error) { + // Print the input error message once at the end + if input_error { + eprintln!("comm: input is not in sorted order"); + } + Err(USimpleError::new(1, "")) + } else { + Ok(()) + } } fn open_file(name: &str, line_ending: LineEnding) -> io::Result { @@ -170,8 +307,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { "" => "\0", delim => delim, }; - comm(&mut f1, &mut f2, delim, &matches); - Ok(()) + + comm(&mut f1, &mut f2, delim, &matches) } pub fn uu_app() -> Command { @@ -233,4 +370,17 @@ pub fn uu_app() -> Command { .help("output a summary") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::CHECK_ORDER) + .long(options::CHECK_ORDER) + .help("check that the input is correctly sorted, even if all input lines are pairable") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::NO_CHECK_ORDER) + .long(options::NO_CHECK_ORDER) + .help("do not check that the input is correctly sorted") + .action(ArgAction::SetTrue) + .conflicts_with(options::CHECK_ORDER), + ) } diff --git a/tests/by-util/test_comm.rs b/tests/by-util/test_comm.rs index ac0d11c0908..bad00b1290e 100644 --- a/tests/by-util/test_comm.rs +++ b/tests/by-util/test_comm.rs @@ -352,11 +352,9 @@ fn defaultcheck_order() { // * the first: if both files are not in order, the default behavior is the only // behavior that will provide an error message - // * the second: if two rows are paired but are out of order, // it won't matter if all rows in the two files are exactly the same. // This is specified in the documentation - #[test] fn defaultcheck_order_identical_bad_order_files() { let scene = TestScenario::new(util_name!()); @@ -367,21 +365,13 @@ fn defaultcheck_order_identical_bad_order_files() { .args(&["bad_order_1", "bad_order_1"]) .succeeds() .stdout_is("\t\te\n\t\td\n\t\tb\n\t\ta\n"); -} - -#[cfg_attr(not(feature = "test_unimplemented"), ignore)] -#[test] -fn defaultcheck_order_two_different_bad_order_files() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - at.write("bad_order_1", "e\nd\nb\na\n"); - at.write("bad_order_2", "e\nc\nb\na\n"); scene .ucmd() - .args(&["bad_order_1", "bad_order_2"]) + .arg("--check-order") + .args(&["bad_order_1", "bad_order_1"]) .fails() - .stdout_is("\t\te\n\tc\n\tb\n\ta\nd\nb\na\n") - .stderr_is("error to be defined"); + .stdout_is("\t\te\n") + .stderr_is("comm: file 1 is not in sorted order\n"); } // * the third: (it is not know whether this is a bug or not) @@ -389,22 +379,20 @@ fn defaultcheck_order_two_different_bad_order_files() { // where both lines are different and one or both file lines being // compared are out of order from the preceding line, // it is ignored and no errors occur. - // * the fourth: (it is not known whether this is a bug or not) // there are additional, not-yet-understood circumstances where an out-of-order // pair is ignored and is not counted against the 1 maximum out-of-order line. - -#[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn unintuitive_default_behavior_1() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.write("defaultcheck_unintuitive_1", "m\nh\nn\no\nc\np\n"); at.write("defaultcheck_unintuitive_2", "m\nh\nn\no\np\n"); + // Here, GNU does not fail, but uutils does scene .ucmd() .args(&["defaultcheck_unintuitive_1", "defaultcheck_unintuitive_2"]) - .succeeds() + .fails() .stdout_is("\t\tm\n\t\th\n\t\tn\n\t\to\nc\n\t\tp\n"); } @@ -458,3 +446,132 @@ fn test_is_dir() { .fails() .stderr_only("comm: .: Is a directory\n"); } + +#[test] +fn test_sorted() { + let expected_stderr = + "comm: file 2 is not in sorted order\ncomm: input is not in sorted order\n"; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("comm1", "1\n3"); + at.write("comm2", "3\n2"); + scene + .ucmd() + .args(&["comm1", "comm2"]) + .fails() + .code_is(1) + .stdout_is("1\n\t\t3\n\t2\n") + .stderr_is(expected_stderr); +} + +#[test] +fn test_sorted_check_order() { + let expected_stderr = "comm: file 2 is not in sorted order\n"; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("comm1", "1\n3"); + at.write("comm2", "3\n2"); + scene + .ucmd() + .arg("--check-order") + .args(&["comm1", "comm2"]) + .fails() + .code_is(1) + .stdout_is("1\n\t\t3\n") + .stderr_is(expected_stderr); +} + +#[test] +fn test_both_inputs_out_of_order() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("file_a", "3\n1\n0\n"); + at.write("file_b", "3\n2\n0\n"); + + scene + .ucmd() + .args(&["file_a", "file_b"]) + .fails() + .code_is(1) + .stdout_is("\t\t3\n1\n0\n\t2\n\t0\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); +} + +#[test] +fn test_both_inputs_out_of_order_last_pair() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("file_a", "3\n1\n"); + at.write("file_b", "3\n2\n"); + + scene + .ucmd() + .args(&["file_a", "file_b"]) + .fails() + .code_is(1) + .stdout_is("\t\t3\n1\n\t2\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: file 2 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); +} + +#[test] +fn test_first_input_out_of_order_extended() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.write("file_a", "0\n3\n1\n"); + at.write("file_b", "2\n3\n"); + + scene + .ucmd() + .args(&["file_a", "file_b"]) + .fails() + .code_is(1) + .stdout_is("0\n\t2\n\t\t3\n1\n") + .stderr_is( + "comm: file 1 is not in sorted order\n\ + comm: input is not in sorted order\n", + ); +} + +#[test] +fn test_out_of_order_input_nocheck() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + // Create input files + at.write("file_a", "1\n3\n"); + at.write("file_b", "3\n2\n"); + + scene + .ucmd() + .arg("--nocheck-order") + .args(&["file_a", "file_b"]) + .succeeds() + .stdout_is("1\n\t\t3\n\t2\n") + .no_stderr(); +} + +#[test] +fn test_both_inputs_out_of_order_but_identical() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("file_a", "2\n1\n0\n"); + at.write("file_b", "2\n1\n0\n"); + + scene + .ucmd() + .args(&["file_a", "file_b"]) + .succeeds() + .stdout_is("\t\t2\n\t\t1\n\t\t0\n") + .no_stderr(); +}