diff options
50 files changed, 1757 insertions, 405 deletions
diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 75b867b71..a32bf74ee 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -477,8 +477,8 @@ jobs: - { 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-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-14 , target: aarch64-apple-darwin , features: feat_os_macos } # M1 CPU - - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } + - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos } # M1 CPU + - { os: macos-13 , target: x86_64-apple-darwin , features: feat_os_macos } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 528c2ad49..d4aff652a 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -112,7 +112,7 @@ jobs: ~/.android/avd/*/*.lock - name: Create and cache emulator image if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2.30.1 + uses: reactivecircus/android-emulator-runner@v2.31.0 with: api-level: ${{ matrix.api-level }} target: ${{ matrix.target }} @@ -157,7 +157,7 @@ jobs: free -mh df -Th - name: Build and Test - uses: reactivecircus/android-emulator-runner@v2.30.1 + uses: reactivecircus/android-emulator-runner@v2.31.0 with: api-level: ${{ matrix.api-level }} target: ${{ matrix.target }} diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index c3c854a4c..ce4822f1e 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -166,6 +166,7 @@ RTLD_NEXT RTLD SIGINT SIGKILL +SIGSTOP SIGTERM SYS_fdatasync SYS_syncfs diff --git a/Cargo.lock b/Cargo.lock index 0d229aed4..1dfc796d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1303,9 +1303,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -1619,11 +1619,12 @@ dependencies = [ [[package]] name = "parse_datetime" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bbf4e25b13841080e018a1e666358adfe5e39b6d353f986ca5091c210b586a1" +checksum = "a8720474e3dd4af20cea8716703498b9f3b690f318fa9d9d9e2e38eaf44b96d0" dependencies = [ "chrono", + "nom", "regex", ] @@ -1733,9 +1734,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -2277,18 +2278,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -2494,6 +2495,7 @@ version = "0.0.26" dependencies = [ "clap", "hex", + "regex", "uucore", ] @@ -3847,9 +3849,9 @@ dependencies = [ [[package]] name = "zip" -version = "1.2.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700ea425e148de30c29c580c1f9508b93ca57ad31c9f4e96b83c194c37a7a8f" +checksum = "f1f4a27345eb6f7aa7bd015ba7eb4175fa4e1b462a29874b779e0bbcf96c6ac7" dependencies = [ "arbitrary", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml index 229d6b9f5..af58f45a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -289,7 +289,7 @@ half = "2.4.1" hostname = "0.4" indicatif = "0.17.8" itertools = "0.12.1" -libc = "0.2.154" +libc = "0.2.153" lscolors = { version = "0.16.0", default-features = false, features = [ "gnu_legacy", ] } @@ -304,7 +304,7 @@ num-traits = "0.2.19" number_prefix = "0.4" once_cell = "1.19.0" onig = { version = "~6.4", default-features = false } -parse_datetime = "0.5.0" +parse_datetime = "0.6.0" phf = "0.11.2" phf_codegen = "0.11.2" platform-info = "2.0.3" @@ -490,7 +490,12 @@ sha1 = { version = "0.10.6", features = ["std"] } tempfile = { workspace = true } time = { workspace = true, features = ["local-offset"] } unindent = "0.2.3" -uucore = { workspace = true, features = ["entries", "process", "signals"] } +uucore = { workspace = true, features = [ + "entries", + "mode", + "process", + "signals", +] } walkdir = { workspace = true } hex-literal = "0.4.1" rstest = { workspace = true } @@ -24,6 +24,7 @@ allow = [ "BSD-2-Clause", "BSD-2-Clause-FreeBSD", "BSD-3-Clause", + "BSL-1.0", "CC0-1.0", "Unicode-DFS-2016", ] diff --git a/docs/src/extensions.md b/docs/src/extensions.md index 61c25ba48..fb91f7d54 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -85,7 +85,11 @@ also provides a `-v`/`--verbose` flag. ## `id` -`id` has three additional flags: +`id` has three additional flags: * `-P` displays the id as a password file entry * `-p` makes the output human-readable * `-A` displays the process audit user ID + +## `uptime` + +Similar to the proc-ps implementation and unlike GNU/Coreutils, `uptime` provides `-s`/`--since` to show since when the system is up. diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index f2848c071..45f3f22ec 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -9,7 +9,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4.7" -libc = "0.2.154" +libc = "0.2.153" tempfile = "3.10.1" rand = { version = "0.8.5", features = ["small_rng"] } similar = "2.5.0" diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index f6a5a138a..208c01901 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -16,8 +16,9 @@ path = "src/cksum.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["encoding", "sum"] } +uucore = { workspace = true, features = ["checksum", "encoding", "sum"] } hex = { workspace = true } +regex = { workspace = true } [[bin]] name = "cksum" diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index a50abada5..9268a40bb 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -5,15 +5,18 @@ // spell-checker:ignore (ToDO) fname, algo use clap::{crate_version, value_parser, Arg, ArgAction, Command}; -use hex::decode; -use hex::encode; +use regex::Regex; use std::error::Error; use std::ffi::OsStr; use std::fmt::Display; use std::fs::File; +use std::io::BufRead; use std::io::{self, stdin, stdout, BufReader, Read, Write}; use std::iter; use std::path::Path; +use uucore::checksum::cksum_output; +use uucore::display::Quotable; +use uucore::error::set_exit_code; use uucore::{ encoding, error::{FromIo, UError, UResult, USimpleError}, @@ -40,6 +43,20 @@ const ALGORITHM_OPTIONS_SHA512: &str = "sha512"; const ALGORITHM_OPTIONS_BLAKE2B: &str = "blake2b"; const ALGORITHM_OPTIONS_SM3: &str = "sm3"; +const SUPPORTED_ALGO: [&str; 11] = [ + ALGORITHM_OPTIONS_SYSV, + ALGORITHM_OPTIONS_BSD, + ALGORITHM_OPTIONS_CRC, + ALGORITHM_OPTIONS_MD5, + ALGORITHM_OPTIONS_SHA1, + ALGORITHM_OPTIONS_SHA224, + ALGORITHM_OPTIONS_SHA256, + ALGORITHM_OPTIONS_SHA384, + ALGORITHM_OPTIONS_SHA512, + ALGORITHM_OPTIONS_BLAKE2B, + ALGORITHM_OPTIONS_SM3, +]; + #[derive(Debug)] enum CkSumError { RawMultipleFiles, @@ -73,10 +90,10 @@ impl Display for CkSumError { } fn detect_algo( - program: &str, + algo: &str, length: Option<usize>, ) -> (&'static str, Box<dyn Digest + 'static>, usize) { - match program { + match algo { ALGORITHM_OPTIONS_SYSV => ( ALGORITHM_OPTIONS_SYSV, Box::new(SYSV::new()) as Box<dyn Digest>, @@ -205,7 +222,7 @@ where ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => { sum_hex.parse::<u16>().unwrap().to_be_bytes().to_vec() } - _ => decode(sum_hex).unwrap(), + _ => hex::decode(sum_hex).unwrap(), }; // Cannot handle multiple files anyway, output immediately. stdout().write_all(&bytes)?; @@ -214,7 +231,8 @@ where OutputFormat::Hexadecimal => sum_hex, OutputFormat::Base64 => match options.algo_name { ALGORITHM_OPTIONS_CRC | ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => sum_hex, - _ => encoding::encode(encoding::Format::Base64, &decode(sum_hex).unwrap()).unwrap(), + _ => encoding::encode(encoding::Format::Base64, &hex::decode(sum_hex).unwrap()) + .unwrap(), }, }; // The BSD checksum output is 5 digit integer @@ -299,7 +317,7 @@ fn digest_read<T: Read>( // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) let mut bytes = vec![0; (output_bits + 7) / 8]; digest.hash_finalize(&mut bytes); - Ok((encode(bytes), output_size)) + Ok((hex::encode(bytes), output_size)) } } @@ -312,6 +330,7 @@ mod options { pub const RAW: &str = "raw"; pub const BASE64: &str = "base64"; pub const CHECK: &str = "check"; + pub const STRICT: &str = "strict"; pub const TEXT: &str = "text"; pub const BINARY: &str = "binary"; } @@ -353,17 +372,45 @@ fn had_reset(args: &[String]) -> bool { } } +/// Calculates the length of the digest for the given algorithm. +fn calculate_blake2b_length(length: usize) -> UResult<Option<usize>> { + match length { + 0 => Ok(None), + n if n % 8 != 0 => { + uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); + Err(io::Error::new(io::ErrorKind::InvalidInput, "length is not a multiple of 8").into()) + } + n if n > 512 => { + uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); + Err(io::Error::new( + io::ErrorKind::InvalidInput, + "maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits", + ) + .into()) + } + n => { + // Divide by 8, as our blake2b implementation expects bytes instead of bits. + if n == 512 { + // When length is 512, it is blake2b's default. + // So, don't show it + Ok(None) + } else { + Ok(Some(n / 8)) + } + } + } +} + /*** * cksum has a bunch of legacy behavior. * We handle this in this function to make sure they are self contained * and "easier" to understand */ -fn handle_tag_text_binary_flags(matches: &clap::ArgMatches, check: bool) -> UResult<(bool, bool)> { +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 text_flag: bool = matches.get_flag(options::TEXT); let binary_flag: bool = matches.get_flag(options::BINARY); let args: Vec<String> = std::env::args().collect(); @@ -371,78 +418,262 @@ fn handle_tag_text_binary_flags(matches: &clap::ArgMatches, check: bool) -> URes let asterisk: bool = prompt_asterisk(tag, binary_flag, had_reset); - if (binary_flag || text_flag) && check { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "the --binary and --text options are meaningless when verifying checksums", - ) - .into()); - } Ok((tag, asterisk)) } +/*** + * Do the checksum validation (can be strict or not) +*/ +fn perform_checksum_validation<'a, I>( + files: I, + strict: bool, + algo_name_input: Option<&str>, +) -> UResult<()> +where + I: Iterator<Item = &'a OsStr>, +{ + // Regexp to handle the two input formats: + // 1. <algo>[-<bits>] (<filename>) = <checksum> + // algo must be uppercase or b (for blake2b) + // 2. <checksum> [* ]<filename> + let regex_pattern = r"^\s*\\?(?P<algo>(?:[A-Z0-9]+|BLAKE2b))(?:-(?P<bits>\d+))?\s?\((?P<filename1>.*)\) = (?P<checksum1>[a-fA-F0-9]+)$|^(?P<checksum2>[a-fA-F0-9]+)\s[* ](?P<filename2>.*)"; + let re = Regex::new(regex_pattern).unwrap(); + + // if cksum has several input files, it will print the result for each file + for filename_input in files { + let mut bad_format = 0; + let mut failed_cksum = 0; + let mut failed_open_file = 0; + let mut properly_formatted = false; + let input_is_stdin = filename_input == OsStr::new("-"); + + let file: Box<dyn Read> = if input_is_stdin { + Box::new(stdin()) // Use stdin if "-" is specified + } else { + match File::open(filename_input) { + Ok(f) => Box::new(f), + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::Other, + format!( + "{}: No such file or directory", + filename_input.to_string_lossy() + ), + ) + .into()); + } + } + }; + let reader = BufReader::new(file); + + // for each line in the input, check if it is a valid checksum line + for line in reader.lines() { + let line = line.unwrap_or_else(|_| String::new()); + if let Some(caps) = re.captures(&line) { + properly_formatted = true; + + // Determine what kind of file input we had + // we need it for case "--check -a sm3 <file>" when <file> is + // <algo>[-<bits>] (<filename>) = <checksum> + let algo_based_format = + caps.name("filename1").is_some() && caps.name("checksum1").is_some(); + + let filename_to_check = caps + .name("filename1") + .or(caps.name("filename2")) + .unwrap() + .as_str(); + let expected_checksum = caps + .name("checksum1") + .or(caps.name("checksum2")) + .unwrap() + .as_str(); + + // If the algo_name is provided, we use it, otherwise we try to detect it + let (algo_name, length) = if algo_based_format { + // When the algo-based format is matched, extract details from regex captures + let algorithm = caps.name("algo").map_or("", |m| m.as_str()).to_lowercase(); + if !SUPPORTED_ALGO.contains(&algorithm.as_str()) { + // Not supported algo, leave early + properly_formatted = false; + continue; + } + + let bits = caps.name("bits").map_or(Some(None), |m| { + let bits_value = m.as_str().parse::<usize>().unwrap(); + if bits_value % 8 == 0 { + Some(Some(bits_value / 8)) + } else { + properly_formatted = false; + None // Return None to signal a parsing or divisibility issue + } + }); + + if bits.is_none() { + // If bits is None, we have a parsing or divisibility issue + // Exit the loop outside of the closure + continue; + } + + (algorithm, bits.unwrap()) + } else if let Some(a) = algo_name_input { + // When a specific algorithm name is input, use it and default bits to None + (a.to_lowercase(), None) + } else { + // Default case if no algorithm is specified and non-algo based format is matched + (String::new(), None) + }; + + if algo_based_format && algo_name_input.map_or(false, |input| algo_name != input) { + bad_format += 1; + continue; + } + + 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, bits) = detect_algo(&algo_name, length); + + // manage the input file + let file_to_check: Box<dyn Read> = if filename_to_check == "-" { + Box::new(stdin()) // Use stdin if "-" is specified in the checksum file + } else { + match File::open(filename_to_check) { + Ok(f) => Box::new(f), + Err(err) => { + // yes, we have both stderr and stdout here + show!(err.map_err_context(|| filename_to_check.to_string())); + println!("{}: FAILED open or read", filename_to_check); + failed_open_file += 1; + // we could not open the file but we want to continue + continue; + } + } + }; + let mut file_reader = BufReader::new(file_to_check); + // Read the file and calculate the checksum + let (calculated_checksum, _) = + digest_read(&mut algo, &mut file_reader, bits).unwrap(); + + // Do the checksum validation + if expected_checksum == calculated_checksum { + println!("{}: OK", filename_to_check); + } else { + println!("{}: FAILED", filename_to_check); + failed_cksum += 1; + } + } else { + if line.is_empty() { + continue; + } + bad_format += 1; + } + } + + // not a single line correctly formatted found + // return an error + if !properly_formatted { + let filename = filename_input.to_string_lossy(); + uucore::show_error!( + "{}: no properly formatted checksum lines found", + if input_is_stdin { + "standard input" + } else { + &filename + } + .maybe_quote() + ); + set_exit_code(1); + } + // strict means that we should have an exit code. + if strict && bad_format > 0 { + set_exit_code(1); + } + + // if we have any failed checksum verification, we set an exit code + if failed_cksum > 0 || failed_open_file > 0 { + set_exit_code(1); + } + + // if any incorrectly formatted line, show it + cksum_output(bad_format, failed_cksum, failed_open_file); + } + Ok(()) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; + let check = matches.get_flag(options::CHECK); + let algo_name: &str = match matches.get_one::<String>(options::ALGORITHM) { Some(v) => v, - None => ALGORITHM_OPTIONS_CRC, + None => { + if check { + // if we are doing a --check, we should not default to crc + "" + } else { + ALGORITHM_OPTIONS_CRC + } + } }; - let input_length = matches.get_one::<usize>(options::LENGTH); - let check = matches.get_flag(options::CHECK); + if ["bsd", "crc", "sysv"].contains(&algo_name) && check { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "--check is not supported with --algorithm={bsd,sysv,crc}", + ) + .into()); + } - let length = if let Some(length) = input_length { - match length.to_owned() { - 0 => None, - n if n % 8 != 0 => { - // GNU's implementation seem to use these quotation marks - // in their error messages, so we do the same. - uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "length is not a multiple of 8", - ) - .into()); - } - n if n > 512 => { - uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); + let input_length = matches.get_one::<usize>(options::LENGTH); + let length = match input_length { + Some(length) => { + if algo_name == ALGORITHM_OPTIONS_BLAKE2B { + calculate_blake2b_length(*length)? + } else { return Err(io::Error::new( io::ErrorKind::InvalidInput, - "maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits", + "--lengt |