diff --git a/Cargo.lock b/Cargo.lock index 0fdd4a65d..89d67bf81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1470,9 +1470,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "onig" @@ -1982,9 +1982,9 @@ checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] name = "selinux" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966a861c0b329c3078d82b404f7086009487123fd0cc905a9caac55d8b13bee1" +checksum = "a00576725d21b588213fbd4af84cd7e4cc4304e8e9bd6c0f5a1498a3e2ca6a51" dependencies = [ "bitflags", "libc", @@ -2101,6 +2101,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "sm3" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f943a7c5e3089f2bd046221d1e9f4fa59396bf0fe966360983649683086215da" +dependencies = [ + "digest", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -2439,6 +2448,7 @@ name = "uu_cksum" version = "0.0.17" dependencies = [ "clap", + "hex", "uucore", ] @@ -2643,17 +2653,10 @@ dependencies = [ name = "uu_hashsum" version = "0.0.17" dependencies = [ - "blake2b_simd", - "blake3", "clap", - "digest", "hex", - "md-5", "memchr", "regex", - "sha1", - "sha2", - "sha3", "uucore", ] @@ -3347,17 +3350,27 @@ dependencies = [ name = "uucore" version = "0.0.17" dependencies = [ + "blake2b_simd", + "blake3", "clap", "data-encoding", "data-encoding-macro", + "digest", "dns-lookup", "dunce", "glob", + "hex", "itertools", "libc", + "md-5", + "memchr", "nix", "once_cell", "os_display", + "sha1", + "sha2", + "sha3", + "sm3", "thiserror", "time", "uucore_procs", diff --git a/Cargo.toml b/Cargo.toml index f9cbf1d05..61a461deb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -312,7 +312,7 @@ redox_syscall = "0.2" regex = "1.7.1" rust-ini = "0.18.0" same-file = "1.0.6" -selinux = "0.3" +selinux = "0.4" signal-hook = "0.3.14" smallvec = { version = "1.10", features = ["union"] } strum = "0.24.1" @@ -332,6 +332,16 @@ windows-sys = { version="0.42.0", default-features=false } xattr = "0.2.3" zip = { version = "0.6.3", default_features=false, features=["deflate"] } +hex = "0.4.3" +md-5 = "0.10.5" +sha1 = "0.10.1" +sha2 = "0.10.2" +sha3 = "0.10.6" +blake2b_simd = "1.0.1" +blake3 = "1.3.2" +sm3 = "0.4.1" +digest = "0.10.6" + uucore = { version=">=0.0.17", package="uucore", path="src/uucore" } uucore_procs = { version=">=0.0.17", package="uucore_procs", path="src/uucore_procs" } uu_ls = { version=">=0.0.17", path="src/uu/ls" } diff --git a/docs/src/installation.md b/docs/src/installation.md index 063935ed6..6c5667cf6 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -1,4 +1,4 @@ - + # Installation @@ -55,10 +55,10 @@ export PATH=/usr/lib/cargo/bin/coreutils:$PATH ### Gentoo -[![Gentoo package](https://repology.org/badge/version-for-repo/gentoo/uutils-coreutils.svg)](https://packages.gentoo.org/packages/sys-apps/uutils) +[![Gentoo package](https://repology.org/badge/version-for-repo/gentoo/uutils-coreutils.svg)](https://packages.gentoo.org/packages/sys-apps/uutils-coreutils) ```bash -emerge -pv sys-apps/uutils +emerge -pv sys-apps/uutils-coreutils ``` ### Manjaro @@ -79,6 +79,13 @@ pamac install uutils-coreutils nix-env -iA nixos.uutils-coreutils ``` +### OpenMandriva Lx +[![openmandriva cooker package](https://repology.org/badge/version-for-repo/openmandriva_cooker/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions) + +```bash +dnf install uutils-coreutils +``` + ### Ubuntu [![Ubuntu package](https://repology.org/badge/version-for-repo/ubuntu_23_04/uutils-coreutils.svg)](https://packages.ubuntu.com/source/lunar/rust-coreutils) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index eb8db104f..dee26ab69 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -110,6 +110,7 @@ pub fn uu_app() -> Command { .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) + .args_override_self(true) .infer_long_args(true) .arg( Arg::new(options::CHANGES) diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 4719af074..9b7f70f8f 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -16,7 +16,8 @@ path = "src/cksum.rs" [dependencies] clap = { workspace=true } -uucore = { workspace=true } +uucore = { workspace=true, features=["sum"] } +hex = { workspace=true } [[bin]] name = "cksum" diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index f567d8bf8..e66d5f029 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -5,134 +5,244 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) fname +// spell-checker:ignore (ToDO) fname, algo use clap::{crate_version, Arg, Command}; +use hex::encode; +use std::ffi::OsStr; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; +use std::iter; use std::path::Path; -use uucore::display::Quotable; -use uucore::error::{FromIo, UResult}; -use uucore::{format_usage, show}; - -// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 -const CRC_TABLE_LEN: usize = 256; -const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table(); +use uucore::{ + error::{FromIo, UResult}, + format_usage, + sum::{ + div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3, + BSD, CRC, SYSV, + }, +}; const USAGE: &str = "{} [OPTIONS] [FILE]..."; const ABOUT: &str = "Print CRC and size for each file"; -const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { - let mut table = [0; CRC_TABLE_LEN]; +const ALGORITHM_OPTIONS_SYSV: &str = "sysv"; +const ALGORITHM_OPTIONS_BSD: &str = "bsd"; +const ALGORITHM_OPTIONS_CRC: &str = "crc"; +const ALGORITHM_OPTIONS_MD5: &str = "md5"; +const ALGORITHM_OPTIONS_SHA1: &str = "sha1"; +const ALGORITHM_OPTIONS_SHA224: &str = "sha224"; +const ALGORITHM_OPTIONS_SHA256: &str = "sha256"; +const ALGORITHM_OPTIONS_SHA384: &str = "sha384"; +const ALGORITHM_OPTIONS_SHA512: &str = "sha512"; +const ALGORITHM_OPTIONS_BLAKE2B: &str = "blake2b"; +const ALGORITHM_OPTIONS_SM3: &str = "sm3"; - let mut i = 0; - while i < CRC_TABLE_LEN { - table[i] = crc_entry(i as u8); +fn detect_algo(program: &str) -> (&'static str, Box, usize) { + match program { + ALGORITHM_OPTIONS_SYSV => ( + ALGORITHM_OPTIONS_SYSV, + Box::new(SYSV::new()) as Box, + 512, + ), + ALGORITHM_OPTIONS_BSD => ( + ALGORITHM_OPTIONS_BSD, + Box::new(BSD::new()) as Box, + 1024, + ), + ALGORITHM_OPTIONS_CRC => ( + ALGORITHM_OPTIONS_CRC, + Box::new(CRC::new()) as Box, + 256, + ), + ALGORITHM_OPTIONS_MD5 => ( + ALGORITHM_OPTIONS_MD5, + Box::new(Md5::new()) as Box, + 128, + ), + ALGORITHM_OPTIONS_SHA1 => ( + ALGORITHM_OPTIONS_SHA1, + Box::new(Sha1::new()) as Box, + 160, + ), + ALGORITHM_OPTIONS_SHA224 => ( + ALGORITHM_OPTIONS_SHA224, + Box::new(Sha224::new()) as Box, + 224, + ), + ALGORITHM_OPTIONS_SHA256 => ( + ALGORITHM_OPTIONS_SHA256, + Box::new(Sha256::new()) as Box, + 256, + ), + ALGORITHM_OPTIONS_SHA384 => ( + ALGORITHM_OPTIONS_SHA384, + Box::new(Sha384::new()) as Box, + 384, + ), + ALGORITHM_OPTIONS_SHA512 => ( + ALGORITHM_OPTIONS_SHA512, + Box::new(Sha512::new()) as Box, + 512, + ), + ALGORITHM_OPTIONS_BLAKE2B => ( + ALGORITHM_OPTIONS_BLAKE2B, + Box::new(Blake2b::new()) as Box, + 512, + ), + ALGORITHM_OPTIONS_SM3 => ( + ALGORITHM_OPTIONS_SM3, + Box::new(Sm3::new()) as Box, + 512, + ), + _ => unreachable!("unknown algorithm: clap should have prevented this case"), + } +} - i += 1; +struct Options { + algo_name: &'static str, + digest: Box, + output_bits: usize, +} + +/// Calculate checksum +/// +/// # Arguments +/// +/// * `options` - CLI options for the assigning checksum algorithm +/// * `files` - A iterator of OsStr which is a bunch of files that are using for calculating checksum +#[allow(clippy::cognitive_complexity)] +fn cksum<'a, I>(mut options: Options, files: I) -> UResult<()> +where + I: Iterator, +{ + for filename in files { + let filename = Path::new(filename); + let stdin_buf; + let file_buf; + let not_file = filename == OsStr::new("-"); + let mut file = BufReader::new(if not_file { + stdin_buf = stdin(); + Box::new(stdin_buf) as Box + } else if filename.is_dir() { + Box::new(BufReader::new(io::empty())) as Box + } else { + file_buf = + File::open(filename).map_err_context(|| filename.to_str().unwrap().to_string())?; + Box::new(file_buf) as Box + }); + let (sum, sz) = digest_read(&mut options.digest, &mut file, options.output_bits) + .map_err_context(|| "failed to read input".to_string())?; + + // The BSD checksum output is 5 digit integer + let bsd_width = 5; + match (options.algo_name, not_file) { + (ALGORITHM_OPTIONS_SYSV, true) => println!( + "{} {}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits) + ), + (ALGORITHM_OPTIONS_SYSV, false) => println!( + "{} {} {}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits), + filename.display() + ), + (ALGORITHM_OPTIONS_BSD, true) => println!( + "{:0bsd_width$} {:bsd_width$}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits) + ), + (ALGORITHM_OPTIONS_BSD, false) => println!( + "{:0bsd_width$} {:bsd_width$} {}", + sum.parse::().unwrap(), + div_ceil(sz, options.output_bits), + filename.display() + ), + (_, true) => println!("{sum} {sz}"), + (_, false) => println!("{sum} {sz} {}", filename.display()), + } } - table + Ok(()) } -const fn crc_entry(input: u8) -> u32 { - let mut crc = (input as u32) << 24; +fn digest_read( + digest: &mut Box, + reader: &mut BufReader, + output_bits: usize, +) -> io::Result<(String, usize)> { + digest.reset(); - let mut i = 0; - while i < 8 { - let if_condition = crc & 0x8000_0000; - let if_body = (crc << 1) ^ 0x04c1_1db7; - let else_body = crc << 1; + // Read bytes from `reader` and write those bytes to `digest`. + // + // If `binary` is `false` and the operating system is Windows, then + // `DigestWriter` replaces "\r\n" with "\n" before it writes the + // bytes into `digest`. Otherwise, it just inserts the bytes as-is. + // + // In order to support replacing "\r\n", we must call `finalize()` + // in order to support the possibility that the last character read + // from the reader was "\r". (This character gets buffered by + // `DigestWriter` and only written if the following character is + // "\n". But when "\r" is the last character read, we need to force + // it to be written.) + let mut digest_writer = DigestWriter::new(digest, true); + let output_size = std::io::copy(reader, &mut digest_writer)? as usize; + digest_writer.finalize(); - // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise - // ops - let condition_table = [else_body, if_body]; - - crc = condition_table[(if_condition != 0) as usize]; - i += 1; - } - - crc -} - -#[inline] -fn crc_update(crc: u32, input: u8) -> u32 { - (crc << 8) ^ CRC_TABLE[((crc >> 24) as usize ^ input as usize) & 0xFF] -} - -#[inline] -fn crc_final(mut crc: u32, mut length: usize) -> u32 { - while length != 0 { - crc = crc_update(crc, length as u8); - length >>= 8; - } - - !crc -} - -fn init_byte_array() -> Vec { - vec![0; 1024 * 1024] -} - -#[inline] -fn cksum(fname: &str) -> io::Result<(u32, usize)> { - let mut crc = 0u32; - let mut size = 0usize; - - let mut rd: Box = match fname { - "-" => Box::new(stdin()), - _ => { - let p = Path::new(fname); - - // Directories should not give an error, but should be interpreted - // as empty files to match GNU semantics. - if p.is_dir() { - Box::new(BufReader::new(io::empty())) as Box - } else { - Box::new(BufReader::new(File::open(p)?)) as Box - } - } - }; - - let mut bytes = init_byte_array(); - loop { - let num_bytes = rd.read(&mut bytes)?; - if num_bytes == 0 { - return Ok((crc_final(crc, size), size)); - } - for &b in bytes[..num_bytes].iter() { - crc = crc_update(crc, b); - } - size += num_bytes; + if digest.output_bits() > 0 { + Ok((digest.result_str(), output_size)) + } else { + // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) + let mut bytes = Vec::new(); + bytes.resize((output_bits + 7) / 8, 0); + digest.hash_finalize(&mut bytes); + Ok((encode(bytes), output_size)) } } mod options { pub static FILE: &str = "file"; + pub static ALGORITHM: &str = "algorithm"; } +const ALGORITHM_HELP_DESC: &str = + "DIGEST determines the digest algorithm and default output format:\n\ +\n\ +-a=sysv: (equivalent to sum -s)\n\ +-a=bsd: (equivalent to sum -r)\n\ +-a=crc: (equivalent to cksum)\n\ +-a=md5: (equivalent to md5sum)\n\ +-a=sha1: (equivalent to sha1sum)\n\ +-a=sha224: (equivalent to sha224sum)\n\ +-a=sha256: (equivalent to sha256sum)\n\ +-a=sha384: (equivalent to sha384sum)\n\ +-a=sha512: (equivalent to sha512sum)\n\ +-a=blake2b: (equivalent to b2sum)\n\ +-a=sm3: (only available through cksum)\n"; + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_ignore(); let matches = uu_app().try_get_matches_from(args)?; - let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), - None => vec![], + let algo_name: &str = match matches.get_one::(options::ALGORITHM) { + Some(v) => v, + None => ALGORITHM_OPTIONS_CRC, }; - if files.is_empty() { - let (crc, size) = cksum("-")?; - println!("{crc} {size}"); - return Ok(()); - } + let (name, algo, bits) = detect_algo(algo_name); + let opts = Options { + algo_name: name, + digest: algo, + output_bits: bits, + }; + + match matches.get_many::(options::FILE) { + Some(files) => cksum(opts, files.map(OsStr::new))?, + None => cksum(opts, iter::once(OsStr::new("-")))?, + }; - for fname in &files { - match cksum(fname.as_ref()).map_err_context(|| format!("{}", fname.maybe_quote())) { - Ok((crc, size)) => println!("{crc} {size} {fname}"), - Err(err) => show!(err), - }; - } Ok(()) } @@ -148,4 +258,25 @@ pub fn uu_app() -> Command { .action(clap::ArgAction::Append) .value_hint(clap::ValueHint::FilePath), ) + .arg( + Arg::new(options::ALGORITHM) + .long(options::ALGORITHM) + .short('a') + .help("select the digest type to use. See DIGEST below") + .value_name("ALGORITHM") + .value_parser([ + 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, + ]), + ) + .after_help(ALGORITHM_HELP_DESC) } diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 2325e462c..eaf89ca55 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -138,7 +138,7 @@ impl Read for Source { /// /// Use the [`Input::new_stdin`] or [`Input::new_file`] functions to /// construct a new instance of this struct. Then pass the instance to -/// the [`Output::dd_out`] function to execute the main copy operation +/// the [`dd_copy`] function to execute the main copy operation /// for `dd`. struct Input<'a> { /// The source from which bytes will be read. @@ -449,7 +449,7 @@ impl Write for Dest { /// /// Use the [`Output::new_stdout`] or [`Output::new_file`] functions /// to construct a new instance of this struct. Then use the -/// [`Output::dd_out`] function to execute the main copy operation for +/// [`dd_copy`] function to execute the main copy operation for /// `dd`. struct Output<'a> { /// The destination to which bytes will be written. @@ -579,136 +579,135 @@ impl<'a> Output<'a> { Ok(()) } } +} - /// Copy the given input data to this output, consuming both. - /// - /// This method contains the main loop for the `dd` program. Bytes - /// are read in blocks from `i` and written in blocks to this - /// output. Read/write statistics are reported to stderr as - /// configured by the `status` command-line argument. - /// - /// # Errors - /// - /// If there is a problem reading from the input or writing to - /// this output. - fn dd_out(mut self, mut i: Input) -> std::io::Result<()> { - // The read and write statistics. +/// Copy the given input data to this output, consuming both. +/// +/// This method contains the main loop for the `dd` program. Bytes +/// are read in blocks from `i` and written in blocks to this +/// output. Read/write statistics are reported to stderr as +/// configured by the `status` command-line argument. +/// +/// # Errors +/// +/// If there is a problem reading from the input or writing to +/// this output. +fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { + // The read and write statistics. + // + // These objects are counters, initialized to zero. After each + // iteration of the main loop, each will be incremented by the + // number of blocks read and written, respectively. + let mut rstat = ReadStat::default(); + let mut wstat = WriteStat::default(); + + // The time at which the main loop starts executing. + // + // When `status=progress` is given on the command-line, the + // `dd` program reports its progress every second or so. Part + // of its report includes the throughput in bytes per second, + // which requires knowing how long the process has been + // running. + let start = time::Instant::now(); + + // A good buffer size for reading. + // + // This is an educated guess about a good buffer size based on + // the input and output block sizes. + let bsize = calc_bsize(i.settings.ibs, o.settings.obs); + + // Start a thread that reports transfer progress. + // + // The `dd` program reports its progress after every block is written, + // at most every 1 second, and only if `status=progress` is given on + // the command-line or a SIGUSR1 signal is received. We + // perform this reporting in a new thread so as not to take + // any CPU time away from the actual reading and writing of + // data. We send a `ProgUpdate` from the transmitter `prog_tx` + // to the receives `rx`, and the receiver prints the transfer + // information. + let (prog_tx, rx) = mpsc::channel(); + let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status)); + let mut progress_as_secs = 0; + + // Optimization: if no blocks are to be written, then don't + // bother allocating any buffers. + if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count { + return finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread); + }; + + // Create a common buffer with a capacity of the block size. + // This is the max size needed. + let mut buf = vec![BUF_INIT_BYTE; bsize]; + + // The main read/write loop. + // + // Each iteration reads blocks from the input and writes + // blocks to this output. Read/write statistics are updated on + // each iteration and cumulative statistics are reported to + // the progress reporting thread. + while below_count_limit(&i.settings.count, &rstat, &wstat) { + // Read a block from the input then write the block to the output. // - // These objects are counters, initialized to zero. After each - // iteration of the main loop, each will be incremented by the - // number of blocks read and written, respectively. - let mut rstat = ReadStat::default(); - let mut wstat = WriteStat::default(); - - // The time at which the main loop starts executing. - // - // When `status=progress` is given on the command-line, the - // `dd` program reports its progress every second or so. Part - // of its report includes the throughput in bytes per second, - // which requires knowing how long the process has been - // running. - let start = time::Instant::now(); - - // A good buffer size for reading. - // - // This is an educated guess about a good buffer size based on - // the input and output block sizes. - let bsize = calc_bsize(i.settings.ibs, self.settings.obs); - - // Start a thread that reports transfer progress. - // - // The `dd` program reports its progress after every block is written, - // at most every 1 second, and only if `status=progress` is given on - // the command-line or a SIGUSR1 signal is received. We - // perform this reporting in a new thread so as not to take - // any CPU time away from the actual reading and writing of - // data. We send a `ProgUpdate` from the transmitter `prog_tx` - // to the receives `rx`, and the receiver prints the transfer - // information. - let (prog_tx, rx) = mpsc::channel(); - let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status)); - let mut progress_as_secs = 0; - - // Optimization: if no blocks are to be written, then don't - // bother allocating any buffers. - if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count { - return self.finalize(rstat, wstat, start, &prog_tx, output_thread); - }; - - // Create a common buffer with a capacity of the block size. - // This is the max size needed. - let mut buf = vec![BUF_INIT_BYTE; bsize]; - - // The main read/write loop. - // - // Each iteration reads blocks from the input and writes - // blocks to this output. Read/write statistics are updated on - // each iteration and cumulative statistics are reported to - // the progress reporting thread. - while below_count_limit(&i.settings.count, &rstat, &wstat) { - // Read a block from the input then write the block to the output. - // - // As an optimization, make an educated guess about the - // best buffer size for reading based on the number of - // blocks already read and the number of blocks remaining. - let loop_bsize = - calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize); - let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?; - if rstat_update.is_empty() { - break; - } - let wstat_update = self.write_blocks(&buf)?; - - // Update the read/write stats and inform the progress thread once per second. - // - // If the receiver is disconnected, `send()` returns an - // error. Since it is just reporting progress and is not - // crucial to the operation of `dd`, let's just ignore the - // error. - rstat += rstat_update; - wstat += wstat_update; - let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false); - if prog_update.duration.as_secs() >= progress_as_secs { - progress_as_secs = prog_update.duration.as_secs() + 1; - prog_tx.send(prog_update).unwrap_or(()); - } + // As an optimization, make an educated guess about the + // best buffer size for reading based on the number of + // blocks already read and the number of blocks remaining. + let loop_bsize = calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize); + let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?; + if rstat_update.is_empty() { + break; } - self.finalize(rstat, wstat, start, &prog_tx, output_thread) + let wstat_update = o.write_blocks(&buf)?; + + // Update the read/write stats and inform the progress thread once per second. + // + // If the receiver is disconnected, `send()` returns an + // error. Since it is just reporting progress and is not + // crucial to the operation of `dd`, let's just ignore the + // error. + rstat += rstat_update; + wstat += wstat_update; + let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), false); + if prog_update.duration.as_secs() >= progress_as_secs { + progress_as_secs = prog_update.duration.as_secs() + 1; + prog_tx.send(prog_update).unwrap_or(()); + } + } + finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread) +} + +/// Flush output, print final stats, and join with the progress thread. +fn finalize( + output: &mut Output, + rstat: ReadStat, + wstat: WriteStat, + start: time::Instant, + prog_tx: &mpsc::Sender, + output_thread: thread::JoinHandle, +) -> std::io::Result<()> { + // Flush the output, if configured to do so. + output.sync()?; + + // Truncate the file to the final cursor location. + // + // Calling `set_len()` may result in an error (for example, + // when calling it on `/dev/null`), but we don't want to + // terminate the process when that happens. Instead, we + // suppress the error by calling `Result::ok()`. This matches + // the behavior of GNU `dd` when given the command-line + // argument `of=/dev/null`. + if !output.settings.oconv.notrunc { + output.dst.truncate().ok(); } - /// Flush output, print final stats, and join with the progress thread. - fn finalize( - &mut self, - rstat: ReadStat, - wstat: WriteStat, - start: time::Instant, - prog_tx: &mpsc::Sender, - output_thread: thread::JoinHandle, - ) -> std::io::Result<()> { - // Flush the output, if configured to do so. - self.sync()?; - - // Truncate the file to the final cursor location. - // - // Calling `set_len()` may result in an error (for example, - // when calling it on `/dev/null`), but we don't want to - // terminate the process when that happens. Instead, we - // suppress the error by calling `Result::ok()`. This matches - // the behavior of GNU `dd` when given the command-line - // argument `of=/dev/null`. - if !self.settings.oconv.notrunc { - self.dst.truncate().ok(); - } - - // Print the final read/write statistics. - let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true); - prog_tx.send(prog_update).unwrap_or(()); - // Wait for the output thread to finish - output_thread - .join() - .expect("Failed to join with the output thread."); - Ok(()) - } + // Print the final read/write statistics. + let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true); + prog_tx.send(prog_update).unwrap_or(()); + // Wait for the output thread to finish + output_thread + .join() + .expect("Failed to join with the output thread."); + Ok(()) } #[cfg(any(target_os = "linux", target_os = "android"))] @@ -925,7 +924,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } None => Output::new_stdout(&settings)?, }; - o.dd_out(i).map_err_context(|| "IO error".to_string()) + dd_copy(i, o).map_err_context(|| "IO error".to_string()) } pub fn uu_app() -> Command { diff --git a/src/uu/df/df.md b/src/uu/df/df.md new file mode 100644 index 000000000..1a192f8fd --- /dev/null +++ b/src/uu/df/df.md @@ -0,0 +1,18 @@ +# df + +``` +df [OPTION]... [FILE]... +``` + +Show information about the file system on which each FILE resides, +or all file systems by default. + +## After Help + +Display values are in units of the first available SIZE from --block-size, +and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. +Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). + +SIZE is an integer and optional unit (example: 10M is 10*1024*1024). +Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers +of 1000). diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index c501b0bab..685ebe58a 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -19,7 +19,7 @@ use uucore::error::FromIo; use uucore::error::{UError, UResult, USimpleError}; use uucore::fsext::{read_fs_list, MountInfo}; use uucore::parse_size::ParseSizeError; -use uucore::{format_usage, show}; +use uucore::{format_usage, help_about, help_section, help_usage, show}; use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command}; @@ -33,16 +33,9 @@ use crate::columns::{Column, ColumnError}; use crate::filesystem::Filesystem; use crate::table::Table; -static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ - or all file systems by default."; -const USAGE: &str = "{} [OPTION]... [FILE]..."; -const LONG_HELP: &str = "Display values are in units of the first available SIZE from --block-size, -and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables. -Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set). - -SIZE is an integer and optional unit (example: 10M is 10*1024*1024). -Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers -of 1000)."; +const ABOUT: &str = help_about!("df.md"); +const USAGE: &str = help_usage!("df.md"); +const AFTER_HELP: &str = help_section!("after help", "df.md"); static OPT_HELP: &str = "help"; static OPT_ALL: &str = "all"; @@ -487,7 +480,7 @@ pub fn uu_app() -> Command { .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) - .after_help(LONG_HELP) + .after_help(AFTER_HELP) .infer_long_args(true) .disable_help_flag(true) .arg( diff --git a/src/uu/dircolors/dircolors.md b/src/uu/dircolors/dircolors.md new file mode 100644 index 000000000..959b8fd19 --- /dev/null +++ b/src/uu/dircolors/dircolors.md @@ -0,0 +1,13 @@ +# dircolors + +``` +dircolors [OPTION]... [FILE] +``` + +Output commands to set the LS_COLORS environment variable. + +## After Help + +If FILE is specified, read it to determine which colors to use for which +file types and extensions. Otherwise, a precompiled database is used. +For details on the format of these files, run 'dircolors --print-database' diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 852d422a0..abab966af 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -16,6 +16,7 @@ use std::io::{BufRead, BufReader}; use clap::{crate_version, Arg, ArgAction, Command}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::{help_about, help_section, help_usage}; mod options { pub const BOURNE_SHELL: &str = "bourne-shell"; @@ -25,13 +26,9 @@ mod options { pub const FILE: &str = "FILE"; } -static USAGE: &str = "{} [OPTION]... [FILE]"; -static ABOUT: &str = "Output commands to set the LS_COLORS environment variable."; -static LONG_HELP: &str = " - If FILE is specified, read it to determine which colors to use for which - file types and extensions. Otherwise, a precompiled database is used. - For details on the format of these files, run 'dircolors --print-database' -"; +const USAGE: &str = help_usage!("dircolors.md"); +const ABOUT: &str = help_about!("dircolors.md"); +const AFTER_HELP: &str = help_section!("after help", "dircolors.md"); mod colors; use self::colors::INTERNAL_DB; @@ -170,7 +167,7 @@ pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .after_help(LONG_HELP) + .after_help(AFTER_HELP) .override_usage(format_usage(USAGE)) .infer_long_args(true) .arg( diff --git a/src/uu/fmt/fmt.md b/src/uu/fmt/fmt.md new file mode 100644 index 000000000..5cf4fa6e6 --- /dev/null +++ b/src/uu/fmt/fmt.md @@ -0,0 +1,7 @@ +# fmt + +``` +fmt [OPTION]... [FILE]... +``` + +Reformat paragraphs from input files (or stdin) to stdout. diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index a97f64945..bd03b5497 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -14,7 +14,7 @@ use std::io::{stdin, stdout, Write}; use std::io::{BufReader, BufWriter, Read}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, show_warning}; +use uucore::{format_usage, help_about, help_usage, show_warning}; use self::linebreak::break_lines; use self::parasplit::ParagraphStream; @@ -22,8 +22,8 @@ use self::parasplit::ParagraphStream; mod linebreak; mod parasplit; -static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout."; -const USAGE: &str = "{} [OPTION]... [FILE]..."; +static ABOUT: &str = help_about!("fmt.md"); +const USAGE: &str = help_usage!("fmt.md"); static MAX_WIDTH: usize = 2500; static OPT_CROWN_MARGIN: &str = "crown-margin"; diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index b037cbcb8..f335211b8 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -15,18 +15,11 @@ edition = "2021" path = "src/hashsum.rs" [dependencies] -digest = "0.10.6" clap = { workspace=true } -hex = "0.4.3" -memchr = { workspace=true } -md-5 = "0.10.5" -regex = { workspace=true } -sha1 = "0.10.1" -sha2 = "0.10.2" -sha3 = "0.10.6" -blake2b_simd = "1.0.1" -blake3 = "1.3.2" uucore = { workspace=true } +memchr = { workspace=true } +regex = { workspace=true } +hex = { workspace=true } [[bin]] name = "hashsum" diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs deleted file mode 100644 index df3bfc166..000000000 --- a/src/uu/hashsum/src/digest.rs +++ /dev/null @@ -1,287 +0,0 @@ -// spell-checker:ignore memmem -//! Implementations of digest functions, like md5 and sha1. -//! -//! The [`Digest`] trait represents the interface for providing inputs -//! to these digest functions and accessing the resulting hash. The -//! [`DigestWriter`] struct provides a wrapper around [`Digest`] that -//! implements the [`Write`] trait, for use in situations where calling -//! [`write`] would be useful. -use std::io::Write; - -use hex::encode; -#[cfg(windows)] -use memchr::memmem; - -pub trait Digest { - fn new() -> Self - where - Self: Sized; - fn input(&mut self, input: &[u8]); - fn result(&mut self, out: &mut [u8]); - fn reset(&mut self); - fn output_bits(&self) -> usize; - fn output_bytes(&self) -> usize { - (self.output_bits() + 7) / 8 - } - fn result_str(&mut self) -> String { - let mut buf: Vec = vec![0; self.output_bytes()]; - self.result(&mut buf); - encode(buf) - } -} - -impl Digest for blake2b_simd::State { - fn new() -> Self { - Self::new() - } - - fn input(&mut self, input: &[u8]) { - self.update(input); - } - - fn result(&mut self, out: &mut [u8]) { - let hash_result = &self.finalize(); - out.copy_from_slice(hash_result.as_bytes()); - } - - fn reset(&mut self) { - *self = Self::new(); - } - - fn output_bits(&self) -> usize { - 512 - } -} - -impl Digest for blake3::Hasher { - fn new() -> Self { - Self::new() - } - - fn input(&mut self, input: &[u8]) { - self.update(input); - } - - fn result(&mut self, out: &mut [u8]) { - let hash_result = &self.finalize(); - out.copy_from_slice(hash_result.as_bytes()); - } - - fn reset(&mut self) { - *self = Self::new(); - } - - fn output_bits(&self) -> usize { - 256 - } -} - -// Implements the Digest trait for sha2 / sha3 algorithms with fixed output -macro_rules! impl_digest_common { - ($type: ty, $size: expr) => { - impl Digest for $type { - fn new() -> Self { - Self::default() - } - - fn input(&mut self, input: &[u8]) { - digest::Digest::update(self, input); - } - - fn result(&mut self, out: &mut [u8]) { - digest::Digest::finalize_into_reset(self, out.into()); - } - - fn reset(&mut self) { - *self = Self::new(); - } - - fn output_bits(&self) -> usize { - $size - } - } - }; -} - -// Implements the Digest trait for sha2 / sha3 algorithms with variable output -macro_rules! impl_digest_shake { - ($type: ty) => { - impl Digest for $type { - fn new() -> Self { - Self::default() - } - - fn input(&mut self, input: &[u8]) { - digest::Update::update(self, input); - } - - fn result(&mut self, out: &mut [u8]) { - digest::ExtendableOutputReset::finalize_xof_reset_into(self, out); - } - - fn reset(&mut self) { - *self = Self::new(); - } - - fn output_bits(&self) -> usize { - 0 - } - } - }; -} - -impl_digest_common!(md5::Md5, 128); -impl_digest_common!(sha1::Sha1, 160); -impl_digest_common!(sha2::Sha224, 224); -impl_digest_common!(sha2::Sha256, 256); -impl_digest_common!(sha2::Sha384, 384); -impl_digest_common!(sha2::Sha512, 512); - -impl_digest_common!(sha3::Sha3_224, 224); -impl_digest_common!(sha3::Sha3_256, 256); -impl_digest_common!(sha3::Sha3_384, 384); -impl_digest_common!(sha3::Sha3_512, 512); -impl_digest_shake!(sha3::Shake128); -impl_digest_shake!(sha3::Shake256); - -/// A struct that writes to a digest. -/// -/// This struct wraps a [`Digest`] and provides a [`Write`] -/// implementation that passes input bytes directly to the -/// [`Digest::input`]. -/// -/// On Windows, if `binary` is `false`, then the [`write`] -/// implementation replaces instances of "\r\n" with "\n" before passing -/// the input bytes to the [`digest`]. -pub struct DigestWriter<'a> { - digest: &'a mut Box, - - /// Whether to write to the digest in binary mode or text mode on Windows. - /// - /// If this is `false`, then instances of "\r\n" are replaced with - /// "\n" before passing input bytes to the [`digest`]. - #[allow(dead_code)] - binary: bool, - - /// Whether the previous - #[allow(dead_code)] - was_last_character_carriage_return: bool, - // TODO These are dead code only on non-Windows operating systems. - // It might be better to use a `#[cfg(windows)]` guard here. -} - -impl<'a> DigestWriter<'a> { - pub fn new(digest: &'a mut Box, binary: bool) -> DigestWriter { - let was_last_character_carriage_return = false; - DigestWriter { - digest, - binary, - was_last_character_carriage_return, - } - } - - pub fn finalize(&mut self) -> bool { - if self.was_last_character_carriage_return { - self.digest.input(&[b'\r']); - true - } else { - false - } - } -} - -impl<'a> Write for DigestWriter<'a> { - #[cfg(not(windows))] - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.digest.input(buf); - Ok(buf.len()) - } - - #[cfg(windows)] - fn write(&mut self, buf: &[u8]) -> std::io::Result { - if self.binary { - self.digest.input(buf); - return Ok(buf.len()); - } - - // The remaining code handles Windows text mode, where we must - // replace each occurrence of "\r\n" with "\n". - // - // First, if the last character written was "\r" and the first - // character in the current buffer to write is not "\n", then we - // need to write the "\r" that we buffered from the previous - // call to `write()`. - let n = buf.len(); - if self.was_last_character_carriage_return && n > 0 && buf[0] != b'\n' { - self.digest.input(&[b'\r']); - } - - // Next, find all occurrences of "\r\n", inputting the slice - // just before the "\n" in the previous instance of "\r\n" and - // the beginning of this "\r\n". - let mut i_prev = 0; - for i in memmem::find_iter(buf, b"\r\n") { - self.digest.input(&buf[i_prev..i]); - i_prev = i + 1; - } - - // Finally, check whether the last character is "\r". If so, - // buffer it until we know that the next character is not "\n", - // which can only be known on the next call to `write()`. - // - // This all assumes that `write()` will be called on adjacent - // blocks of the input. - if n > 0 && buf[n - 1] == b'\r' { - self.was_last_character_carriage_return = true; - self.digest.input(&buf[i_prev..n - 1]); - } else { - self.was_last_character_carriage_return = false; - self.digest.input(&buf[i_prev..n]); - } - - // Even though we dropped a "\r" for each "\r\n" we found, we - // still report the number of bytes written as `n`. This is - // because the meaning of the returned number is supposed to be - // the number of bytes consumed by the writer, so that if the - // calling code were calling `write()` in a loop, it would know - // where the next contiguous slice of the buffer starts. - Ok(n) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -#[cfg(test)] -mod tests { - - /// Test for replacing a "\r\n" sequence with "\n" when the "\r" is - /// at the end of one block and the "\n" is at the beginning of the - /// next block, when reading in blocks. - #[cfg(windows)] - #[test] - fn test_crlf_across_blocks() { - use std::io::Write; - - use crate::digest::Digest; - use crate::digest::DigestWriter; - - // Writing "\r" in one call to `write()`, and then "\n" in another. - let mut digest = Box::new(md5::Md5::new()) as Box; - let mut writer_crlf = DigestWriter::new(&mut digest, false); - writer_crlf.write_all(&[b'\r']).unwrap(); - writer_crlf.write_all(&[b'\n']).unwrap(); - writer_crlf.finalize(); - let result_crlf = digest.result_str(); - - // We expect "\r\n" to be replaced with "\n" in text mode on Windows. - let mut digest = Box::new(md5::Md5::new()) as Box; - let mut writer_lf = DigestWriter::new(&mut digest, false); - writer_lf.write_all(&[b'\n']).unwrap(); - writer_lf.finalize(); - let result_lf = digest.result_str(); - - assert_eq!(result_crlf, result_lf); - } -} diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 3271425bc..c49c530e4 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -9,21 +9,12 @@ // spell-checker:ignore (ToDO) algo, algoname, regexes, nread, nonames -mod digest; - -use self::digest::Digest; -use self::digest::DigestWriter; - use clap::builder::ValueParser; use clap::crate_version; use clap::ArgAction; use clap::{Arg, ArgMatches, Command}; use hex::encode; -use md5::Md5; use regex::Regex; -use sha1::Sha1; -use sha2::{Sha224, Sha256, Sha384, Sha512}; -use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256}; use std::cmp::Ordering; use std::error::Error; use std::ffi::{OsStr, OsString}; @@ -32,10 +23,12 @@ use std::io::{self, stdin, BufRead, BufReader, Read}; use std::iter; use std::num::ParseIntError; use std::path::Path; -use uucore::crash; -use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; -use uucore::show_warning; +use uucore::sum::{ + Blake2b, Blake3, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha3_224, Sha3_256, + Sha3_384, Sha3_512, Sha512, Shake128, Shake256, +}; +use uucore::{crash, display::Quotable, show_warning}; const NAME: &str = "hashsum"; @@ -68,16 +61,8 @@ fn detect_algo( "sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box, 256), "sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box, 384), "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box, 512), - "b2sum" => ( - "BLAKE2", - Box::new(blake2b_simd::State::new()) as Box, - 512, - ), - "b3sum" => ( - "BLAKE3", - Box::new(blake3::Hasher::new()) as Box, - 256, - ), + "b2sum" => ("BLAKE2", Box::new(Blake2b::new()) as Box, 512), + "b3sum" => ("BLAKE3", Box::new(Blake3::new()) as Box, 256), "sha3sum" => match matches.get_one::("bits") { Some(224) => ( "SHA3-224", @@ -170,10 +155,10 @@ fn detect_algo( set_or_crash("SHA512", Box::new(Sha512::new()), 512); } if matches.get_flag("b2sum") { - set_or_crash("BLAKE2", Box::new(blake2b_simd::State::new()), 512); + set_or_crash("BLAKE2", Box::new(Blake2b::new()), 512); } if matches.get_flag("b3sum") { - set_or_crash("BLAKE3", Box::new(blake3::Hasher::new()), 256); + set_or_crash("BLAKE3", Box::new(Blake3::new()), 256); } if matches.get_flag("sha3") { match matches.get_one::("bits") { @@ -680,7 +665,7 @@ fn digest_reader( // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) let mut bytes = Vec::new(); bytes.resize((output_bits + 7) / 8, 0); - digest.result(&mut bytes); + digest.hash_finalize(&mut bytes); Ok(encode(bytes)) } } diff --git a/src/uu/link/link.md b/src/uu/link/link.md new file mode 100644 index 000000000..ea6a531b9 --- /dev/null +++ b/src/uu/link/link.md @@ -0,0 +1,7 @@ +# link + +``` +link FILE1 FILE2 +``` + +Call the link function to create a link named FILE2 to an existing FILE1. diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 80b2b1f9b..6688003a9 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -11,10 +11,10 @@ use std::fs::hard_link; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::format_usage; +use uucore::{format_usage, help_about, help_usage}; -static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1."; -const USAGE: &str = "{} FILE1 FILE2"; +static ABOUT: &str = help_about!("link.md"); +const USAGE: &str = help_usage!("link.md"); pub mod options { pub static FILES: &str = "FILES"; diff --git a/src/uu/ln/ln.md b/src/uu/ln/ln.md new file mode 100644 index 000000000..b2320d6c4 --- /dev/null +++ b/src/uu/ln/ln.md @@ -0,0 +1,21 @@ +# ln + +``` +ln [OPTION]... [-T] TARGET LINK_NAME +ln [OPTION]... TARGET +ln [OPTION]... TARGET... DIRECTORY +ln [OPTION]... -t DIRECTORY TARGET... +``` + +Change file owner and group + +## After Help + +In the 1st form, create a link to TARGET with the name LINK_NAME. +In the 2nd form, create a link to TARGET in the current directory. +In the 3rd and 4th forms, create links to each TARGET in DIRECTORY. +Create hard links by default, symbolic links with --symbolic. +By default, each destination (name of new link) should not already exist. +When creating hard links, each TARGET must exist. Symbolic links +can hold arbitrary text; if later resolved, a relative link is +interpreted in relation to its parent directory. diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index dac7fd556..c583aac1e 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -11,7 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; use uucore::fs::{make_path_relative_to, paths_refer_to_same_file}; -use uucore::{format_usage, prompt_yes, show_error}; +use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show_error}; use std::borrow::Cow; use std::error::Error; @@ -85,21 +85,9 @@ impl UError for LnError { } } -const ABOUT: &str = "Change file owner and group"; -const USAGE: &str = "\ - {} [OPTION]... [-T] TARGET LINK_NAME - {} [OPTION]... TARGET - {} [OPTION]... TARGET... DIRECTORY - {} [OPTION]... -t DIRECTORY TARGET..."; -const LONG_USAGE: &str = "\ - In the 1st form, create a link to TARGET with the name LINK_NAME.\n\ - In the 2nd form, create a link to TARGET in the current directory.\n\ - In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.\n\ - Create hard links by default, symbolic links with --symbolic.\n\ - By default, each destination (name of new link) should not already exist.\n\ - When creating hard links, each TARGET must exist. Symbolic links\n\ - can hold arbitrary text; if later resolved, a relative link is\n\ - interpreted in relation to its parent directory."; +const ABOUT: &str = help_about!("ln.md"); +const USAGE: &str = help_usage!("ln.md"); +const AFTER_HELP: &str = help_section!("after help", "ln.md"); mod options { pub const FORCE: &str = "force"; @@ -119,13 +107,13 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let long_usage = format!( + let after_help = format!( "{}\n\n{}", - LONG_USAGE, + AFTER_HELP, backup_control::BACKUP_CONTROL_LONG_HELP ); - let matches = uu_app().after_help(long_usage).try_get_matches_from(args)?; + let matches = uu_app().after_help(after_help).try_get_matches_from(args)?; /* the list of files */ diff --git a/src/uu/realpath/realpath.md b/src/uu/realpath/realpath.md new file mode 100644 index 000000000..69fb39407 --- /dev/null +++ b/src/uu/realpath/realpath.md @@ -0,0 +1,7 @@ +# realpath + +``` +realpath [OPTION]... FILE... +``` + +Print the resolved path \ No newline at end of file diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 1a6d42790..cb7a09a41 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -20,11 +20,12 @@ use uucore::{ error::{FromIo, UResult}, format_usage, fs::{canonicalize, MissingHandling, ResolveMode}, + help_about, help_usage, }; use uucore::{error::UClapError, show, show_if_err}; -static ABOUT: &str = "Print the resolved path"; -const USAGE: &str = "{} [OPTION]... FILE..."; +static ABOUT: &str = help_about!("realpath.md"); +const USAGE: &str = help_usage!("realpath.md"); static OPT_QUIET: &str = "quiet"; static OPT_STRIP: &str = "strip"; diff --git a/src/uu/shuf/shuf.md b/src/uu/shuf/shuf.md new file mode 100644 index 000000000..41a275035 --- /dev/null +++ b/src/uu/shuf/shuf.md @@ -0,0 +1,11 @@ +# shuf + +``` +shuf [OPTION]... [FILE] +shuf -e [OPTION]... [ARG]... +shuf -i LO-HI [OPTION]...; +``` + +Shuffle the input by outputting a random permutation of input lines. +Each output permutation is equally likely. +With no FILE, or when FILE is -, read standard input. \ No newline at end of file diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index d0022f5f5..2481baf3d 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -15,7 +15,7 @@ use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::format_usage; +use uucore::{format_usage, help_about, help_usage}; mod rand_read_adapter; @@ -25,14 +25,8 @@ enum Mode { InputRange((usize, usize)), } -static USAGE: &str = "\ - {} [OPTION]... [FILE] - {} -e [OPTION]... [ARG]... - {} -i LO-HI [OPTION]..."; -static ABOUT: &str = "\ - Shuffle the input by outputting a random permutation of input lines. \ - Each output permutation is equally likely. \ - With no FILE, or when FILE is -, read standard input."; +static USAGE: &str = help_usage!("shuf.md"); +static ABOUT: &str = help_about!("shuf.md"); struct Options { head_count: usize, diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 6bd3c2f65..928c3f177 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -3,19 +3,20 @@ // * For the full copyright and license information, please view the LICENSE file // * that was distributed with this source code. -// spell-checker:ignore tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed ushort +// spell-checker:ignore clocal tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed ushort mod flags; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; -use nix::libc::{c_ushort, TIOCGWINSZ, TIOCSWINSZ}; +use nix::libc::{c_ushort, O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ}; use nix::sys::termios::{ cfgetospeed, tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios, }; use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; use std::io::{self, stdout}; use std::ops::ControlFlow; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; use uucore::error::{UResult, USimpleError}; use uucore::format_usage; @@ -102,7 +103,20 @@ impl<'a> Options<'a> { all: matches.get_flag(options::ALL), save: matches.get_flag(options::SAVE), file: match matches.get_one::(options::FILE) { - Some(_f) => todo!(), + // Two notes here: + // 1. O_NONBLOCK is needed because according to GNU docs, a + // POSIX tty can block waiting for carrier-detect if the + // "clocal" flag is not set. If your TTY is not connected + // to a modem, it is probably not relevant though. + // 2. We never close the FD that we open here, but the OS + // will clean up the FD for us on exit, so it doesn't + // matter. The alternative would be to have an enum of + // BorrowedFd/OwnedFd to handle both cases. + Some(f) => std::fs::OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK) + .open(f)? + .into_raw_fd(), None => stdout().as_raw_fd(), }, settings: matches diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 0807aa912..6cb77757c 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -17,16 +17,10 @@ use std::ffi::OsString; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size, ParseSizeError}; -use uucore::{format_usage, show_warning}; +use uucore::{format_usage, help_about, help_usage, show_warning}; -const ABOUT: &str = "\ - Print the last 10 lines of each FILE to standard output.\n\ - With more than one FILE, precede each with a header giving the file name.\n\ - With no FILE, or when FILE is -, read standard input.\n\ - \n\ - Mandatory arguments to long flags are mandatory for short flags too.\ - "; -const USAGE: &str = "{} [FLAG]... [FILE]..."; +const ABOUT: &str = help_about!("tail.md"); +const USAGE: &str = help_usage!("tail.md"); pub mod options { pub mod verbosity { diff --git a/src/uu/tail/tail.md b/src/uu/tail/tail.md new file mode 100644 index 000000000..fefe7f6ee --- /dev/null +++ b/src/uu/tail/tail.md @@ -0,0 +1,11 @@ +# tail + +``` +tail [FLAG]... [FILE]... +``` + +Print the last 10 lines of each FILE to standard output. +With more than one FILE, precede each with a header giving the file name. +With no FILE, or when FILE is -, read standard input. + +Mandatory arguments to long flags are mandatory for short flags too. diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 71b5edc5c..96c318afd 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -11,15 +11,16 @@ use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::PathBuf; use uucore::display::Quotable; use uucore::error::UResult; -use uucore::{format_usage, show_error}; +use uucore::{format_usage, help_about, help_section, help_usage, show_error}; // spell-checker:ignore nopipe #[cfg(unix)] use uucore::libc; -static ABOUT: &str = "Copy standard input to each FILE, and also to standard output."; -const USAGE: &str = "{} [OPTION]... [FILE]..."; +const ABOUT: &str = help_about!("tee.md"); +const USAGE: &str = help_usage!("tee.md"); +const AFTER_HELP: &str = help_section!("after help", "tee.md"); mod options { pub const APPEND: &str = "append"; @@ -88,7 +89,7 @@ pub fn uu_app() -> Command { .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) - .after_help("If a FILE is -, it refers to a file named - .") + .after_help(AFTER_HELP) .infer_long_args(true) .arg( Arg::new(options::APPEND) diff --git a/src/uu/tee/tee.md b/src/uu/tee/tee.md new file mode 100644 index 000000000..8bf097cec --- /dev/null +++ b/src/uu/tee/tee.md @@ -0,0 +1,11 @@ +# tee + +``` +tee [OPTION]... [FILE]... +``` + +Copy standard input to each FILE, and also to standard output. + +## After Help + +If a FILE is -, it refers to a file named - . diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index d4d8b8cf2..f5bec9053 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -36,6 +36,17 @@ libc = { version="0.2.137", optional=true } once_cell = { workspace=true } os_display = "0.1.3" +digest = { workspace=true } +hex = { workspace=true } +memchr = { workspace=true } +md-5 = { workspace=true } +sha1 = { workspace=true } +sha2 = { workspace=true } +sha3 = { workspace=true } +blake2b_simd = { workspace=true } +blake3 = { workspace=true } +sm3 = { workspace=true } + [target.'cfg(unix)'.dependencies] walkdir = { workspace=true, optional=true } nix = { workspace=true, features = ["fs", "uio", "zerocopy"] } @@ -66,3 +77,4 @@ utf8 = [] utmpx = ["time", "time/macros", "libc", "dns-lookup"] wide = [] pipes = [] +sum = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 2e5aea1e2..f8a8d2d10 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -12,6 +12,8 @@ pub mod lines; pub mod memo; #[cfg(feature = "ringbuffer")] pub mod ringbuffer; +#[cfg(feature = "sum")] +pub mod sum; #[cfg(feature = "memo")] mod tokenize; diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs new file mode 100644 index 000000000..339414630 --- /dev/null +++ b/src/uucore/src/lib/features/sum.rs @@ -0,0 +1,494 @@ +// This file is part of the uutils coreutils package. +// +// (c) Yuan YangHao +// +// For the full copyright and license information, please view the LICENSE file +// that was distributed with this source code. + +// spell-checker:ignore memmem algo + +//! Implementations of digest functions, like md5 and sha1. +//! +//! The [`Digest`] trait represents the interface for providing inputs +//! to these digest functions and accessing the resulting hash. The +//! [`DigestWriter`] struct provides a wrapper around [`Digest`] that +//! implements the [`Write`] trait, for use in situations where calling +//! [`write`] would be useful. +use std::io::Write; + +use hex::encode; +#[cfg(windows)] +use memchr::memmem; + +pub trait Digest { + fn new() -> Self + where + Self: Sized; + fn hash_update(&mut self, input: &[u8]); + fn hash_finalize(&mut self, out: &mut [u8]); + fn reset(&mut self); + fn output_bits(&self) -> usize; + fn output_bytes(&self) -> usize { + (self.output_bits() + 7) / 8 + } + fn result_str(&mut self) -> String { + let mut buf: Vec = vec![0; self.output_bytes()]; + self.hash_finalize(&mut buf); + encode(buf) + } +} + +pub struct Blake2b(blake2b_simd::State); +impl Digest for Blake2b { + fn new() -> Self { + Self(blake2b_simd::State::new()) + } + + fn hash_update(&mut self, input: &[u8]) { + self.0.update(input); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + let hash_result = &self.0.finalize(); + out.copy_from_slice(hash_result.as_bytes()); + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 512 + } +} + +pub struct Blake3(blake3::Hasher); +impl Digest for Blake3 { + fn new() -> Self { + Self(blake3::Hasher::new()) + } + + fn hash_update(&mut self, input: &[u8]) { + self.0.update(input); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + let hash_result = &self.0.finalize(); + out.copy_from_slice(hash_result.as_bytes()); + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 256 + } +} + +pub struct Sm3(sm3::Sm3); +impl Digest for Sm3 { + fn new() -> Self { + Self(::new()) + } + + fn hash_update(&mut self, input: &[u8]) { + ::update(&mut self.0, input); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + out.copy_from_slice(&::finalize(self.0.clone())); + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 256 + } +} + +// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 +const CRC_TABLE_LEN: usize = 256; + +pub struct CRC { + state: u32, + size: usize, + crc_table: [u32; CRC_TABLE_LEN], +} +impl CRC { + fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { + let mut table = [0; CRC_TABLE_LEN]; + + for (i, elt) in table.iter_mut().enumerate().take(CRC_TABLE_LEN) { + *elt = Self::crc_entry(i as u8); + } + + table + } + fn crc_entry(input: u8) -> u32 { + let mut crc = (input as u32) << 24; + + let mut i = 0; + while i < 8 { + let if_condition = crc & 0x8000_0000; + let if_body = (crc << 1) ^ 0x04c1_1db7; + let else_body = crc << 1; + + // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise + // ops + let condition_table = [else_body, if_body]; + + crc = condition_table[(if_condition != 0) as usize]; + i += 1; + } + + crc + } + + fn update(&mut self, input: u8) { + self.state = (self.state << 8) + ^ self.crc_table[((self.state >> 24) as usize ^ input as usize) & 0xFF]; + } +} + +impl Digest for CRC { + fn new() -> Self { + Self { + state: 0, + size: 0, + crc_table: Self::generate_crc_table(), + } + } + + fn hash_update(&mut self, input: &[u8]) { + for &elt in input.iter() { + self.update(elt); + } + self.size += input.len(); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + let mut sz = self.size; + while sz != 0 { + self.update(sz as u8); + sz >>= 8; + } + self.state = !self.state; + out.copy_from_slice(&self.state.to_ne_bytes()); + } + + fn result_str(&mut self) -> String { + let mut _out: Vec = vec![0; 4]; + self.hash_finalize(&mut _out); + format!("{}", self.state) + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 256 + } +} + +// 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, +} +impl Digest for BSD { + fn new() -> Self { + Self { state: 0 } + } + + fn hash_update(&mut self, input: &[u8]) { + for &byte in input.iter() { + self.state = (self.state >> 1) + ((self.state & 1) << 15); + self.state = self.state.wrapping_add(u16::from(byte)); + } + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + out.copy_from_slice(&self.state.to_ne_bytes()); + } + + fn result_str(&mut self) -> String { + let mut _out: Vec = vec![0; 2]; + self.hash_finalize(&mut _out); + format!("{}", self.state) + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 128 + } +} + +pub struct SYSV { + state: u32, +} +impl Digest for SYSV { + fn new() -> Self { + Self { state: 0 } + } + + fn hash_update(&mut self, input: &[u8]) { + for &byte in input.iter() { + self.state = self.state.wrapping_add(u32::from(byte)); + } + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + self.state = (self.state & 0xffff) + (self.state >> 16); + self.state = (self.state & 0xffff) + (self.state >> 16); + out.copy_from_slice(&(self.state as u16).to_ne_bytes()); + } + + fn result_str(&mut self) -> String { + let mut _out: Vec = vec![0; 2]; + self.hash_finalize(&mut _out); + format!("{}", self.state) + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 512 + } +} + +// Implements the Digest trait for sha2 / sha3 algorithms with fixed output +macro_rules! impl_digest_common { + ($algo_type: ty, $size: expr) => { + impl Digest for $algo_type { + fn new() -> Self { + Self(Default::default()) + } + + fn hash_update(&mut self, input: &[u8]) { + digest::Digest::update(&mut self.0, input); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + digest::Digest::finalize_into_reset(&mut self.0, out.into()); + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + $size + } + } + }; +} + +// Implements the Digest trait for sha2 / sha3 algorithms with variable output +macro_rules! impl_digest_shake { + ($algo_type: ty) => { + impl Digest for $algo_type { + fn new() -> Self { + Self(Default::default()) + } + + fn hash_update(&mut self, input: &[u8]) { + digest::Update::update(&mut self.0, input); + } + + fn hash_finalize(&mut self, out: &mut [u8]) { + digest::ExtendableOutputReset::finalize_xof_reset_into(&mut self.0, out); + } + + fn reset(&mut self) { + *self = Self::new(); + } + + fn output_bits(&self) -> usize { + 0 + } + } + }; +} + +pub struct Md5(md5::Md5); +pub struct Sha1(sha1::Sha1); +pub struct Sha224(sha2::Sha224); +pub struct Sha256(sha2::Sha256); +pub struct Sha384(sha2::Sha384); +pub struct Sha512(sha2::Sha512); +impl_digest_common!(Md5, 128); +impl_digest_common!(Sha1, 160); +impl_digest_common!(Sha224, 224); +impl_digest_common!(Sha256, 256); +impl_digest_common!(Sha384, 384); +impl_digest_common!(Sha512, 512); + +pub struct Sha3_224(sha3::Sha3_224); +pub struct Sha3_256(sha3::Sha3_256); +pub struct Sha3_384(sha3::Sha3_384); +pub struct Sha3_512(sha3::Sha3_512); +impl_digest_common!(Sha3_224, 224); +impl_digest_common!(Sha3_256, 256); +impl_digest_common!(Sha3_384, 384); +impl_digest_common!(Sha3_512, 512); + +pub struct Shake128(sha3::Shake128); +pub struct Shake256(sha3::Shake256); +impl_digest_shake!(Shake128); +impl_digest_shake!(Shake256); + +/// A struct that writes to a digest. +/// +/// This struct wraps a [`Digest`] and provides a [`Write`] +/// implementation that passes input bytes directly to the +/// [`Digest::hash_update`]. +/// +/// On Windows, if `binary` is `false`, then the [`write`] +/// implementation replaces instances of "\r\n" with "\n" before passing +/// the input bytes to the [`digest`]. +pub struct DigestWriter<'a> { + digest: &'a mut Box, + + /// Whether to write to the digest in binary mode or text mode on Windows. + /// + /// If this is `false`, then instances of "\r\n" are replaced with + /// "\n" before passing input bytes to the [`digest`]. + #[allow(dead_code)] + binary: bool, + + /// Whether the previous + #[allow(dead_code)] + was_last_character_carriage_return: bool, + // TODO These are dead code only on non-Windows operating systems. + // It might be better to use a `#[cfg(windows)]` guard here. +} + +impl<'a> DigestWriter<'a> { + pub fn new(digest: &'a mut Box, binary: bool) -> DigestWriter { + let was_last_character_carriage_return = false; + DigestWriter { + digest, + binary, + was_last_character_carriage_return, + } + } + + pub fn finalize(&mut self) -> bool { + if self.was_last_character_carriage_return { + self.digest.hash_update(&[b'\r']); + true + } else { + false + } + } +} + +impl<'a> Write for DigestWriter<'a> { + #[cfg(not(windows))] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.digest.hash_update(buf); + Ok(buf.len()) + } + + #[cfg(windows)] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + if self.binary { + self.digest.hash_update(buf); + return Ok(buf.len()); + } + + // The remaining code handles Windows text mode, where we must + // replace each occurrence of "\r\n" with "\n". + // + // First, if the last character written was "\r" and the first + // character in the current buffer to write is not "\n", then we + // need to write the "\r" that we buffered from the previous + // call to `write()`. + let n = buf.len(); + if self.was_last_character_carriage_return && n > 0 && buf[0] != b'\n' { + self.digest.hash_update(&[b'\r']); + } + + // Next, find all occurrences of "\r\n", inputting the slice + // just before the "\n" in the previous instance of "\r\n" and + // the beginning of this "\r\n". + let mut i_prev = 0; + for i in memmem::find_iter(buf, b"\r\n") { + self.digest.hash_update(&buf[i_prev..i]); + i_prev = i + 1; + } + + // Finally, check whether the last character is "\r". If so, + // buffer it until we know that the next character is not "\n", + // which can only be known on the next call to `write()`. + // + // This all assumes that `write()` will be called on adjacent + // blocks of the input. + if n > 0 && buf[n - 1] == b'\r' { + self.was_last_character_carriage_return = true; + self.digest.hash_update(&buf[i_prev..n - 1]); + } else { + self.was_last_character_carriage_return = false; + self.digest.hash_update(&buf[i_prev..n]); + } + + // Even though we dropped a "\r" for each "\r\n" we found, we + // still report the number of bytes written as `n`. This is + // because the meaning of the returned number is supposed to be + // the number of bytes consumed by the writer, so that if the + // calling code were calling `write()` in a loop, it would know + // where the next contiguous slice of the buffer starts. + Ok(n) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + + /// Test for replacing a "\r\n" sequence with "\n" when the "\r" is + /// at the end of one block and the "\n" is at the beginning of the + /// next block, when reading in blocks. + #[cfg(windows)] + #[test] + fn test_crlf_across_blocks() { + use std::io::Write; + + use crate::digest::Digest; + use crate::digest::DigestWriter; + + // Writing "\r" in one call to `write()`, and then "\n" in another. + let mut digest = Box::new(md5::Md5::new()) as Box; + let mut writer_crlf = DigestWriter::new(&mut digest, false); + writer_crlf.write_all(&[b'\r']).unwrap(); + writer_crlf.write_all(&[b'\n']).unwrap(); + writer_crlf.hash_finalize(); + let result_crlf = digest.result_str(); + + // We expect "\r\n" to be replaced with "\n" in text mode on Windows. + let mut digest = Box::new(md5::Md5::new()) as Box; + let mut writer_lf = DigestWriter::new(&mut digest, false); + writer_lf.write_all(&[b'\n']).unwrap(); + writer_lf.hash_finalize(); + let result_lf = digest.result_str(); + + assert_eq!(result_crlf, result_lf); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 66f8881d6..5cbf58faa 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -46,6 +46,8 @@ pub use crate::features::lines; pub use crate::features::memo; #[cfg(feature = "ringbuffer")] pub use crate::features::ringbuffer; +#[cfg(feature = "sum")] +pub use crate::features::sum; // * (platform-specific) feature-gated modules // ** non-windows (i.e. Unix + Fuchsia) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index a34a3b3c0..b5f8ca5c5 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -650,3 +650,24 @@ fn test_chmod_file_symlink_after_non_existing_file() { 0o100764 ); } + +#[test] +fn test_quiet_n_verbose_used_multiple_times() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + scene + .ucmd() + .arg("u+x") + .arg("--verbose") + .arg("--verbose") + .arg("file") + .succeeds(); + scene + .ucmd() + .arg("u+x") + .arg("--quiet") + .arg("--quiet") + .arg("file") + .succeeds(); +} diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 523715126..361a9c472 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -114,3 +114,79 @@ fn test_stdin_larger_than_128_bytes() { assert_eq!(cksum, 945_881_979); assert_eq!(bytes_cnt, 2058); } + +#[test] +fn test_sha1_single_file() { + new_ucmd!() + .arg("-a=sha1") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is("ab1dd0bae1d8883a3d18a66de6afbd28252cfbef 772 lorem_ipsum.txt\n"); +} + +#[test] +fn test_sm3_single_file() { + new_ucmd!() + .arg("-a=sm3") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is( + "6d296b805d060bfed22808df308dbb9b4317794dd4ed6740a10770a782699bc2 772 lorem_ipsum.txt\n", + ); +} + +#[test] +fn test_bsd_single_file() { + new_ucmd!() + .arg("-a=bsd") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("bsd_single_file.expected"); +} + +#[test] +fn test_bsd_multiple_files() { + new_ucmd!() + .arg("-a=bsd") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_only_fixture("bsd_multiple_files.expected"); +} + +#[test] +fn test_bsd_stdin() { + new_ucmd!() + .arg("-a=bsd") + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("bsd_stdin.expected"); +} + +#[test] +fn test_sysv_single_file() { + new_ucmd!() + .arg("-a=sysv") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("sysv_single_file.expected"); +} + +#[test] +fn test_sysv_multiple_files() { + new_ucmd!() + .arg("-a=sysv") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_only_fixture("sysv_multiple_files.expected"); +} + +#[test] +fn test_sysv_stdin() { + new_ucmd!() + .arg("-a=sysv") + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_only_fixture("sysv_stdin.expected"); +} diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 79d128d51..deedea6c7 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -121,6 +121,14 @@ fn test_invalid_signal() { .usage_error("'invalid': invalid signal"); } +#[test] +fn test_invalid_multi_byte_characters() { + new_ucmd!() + .args(&["10€", "sleep", "0"]) + .fails() + .usage_error("invalid time interval '10€'"); +} + /// Test that the long form of the `--kill-after` argument is recognized. #[test] fn test_kill_after_long() { diff --git a/tests/fixtures/cksum/bsd_multiple_files.expected b/tests/fixtures/cksum/bsd_multiple_files.expected new file mode 100644 index 000000000..941a2a512 --- /dev/null +++ b/tests/fixtures/cksum/bsd_multiple_files.expected @@ -0,0 +1,2 @@ +08109 1 lorem_ipsum.txt +01814 1 alice_in_wonderland.txt diff --git a/tests/fixtures/cksum/bsd_single_file.expected b/tests/fixtures/cksum/bsd_single_file.expected new file mode 100644 index 000000000..293ada3bd --- /dev/null +++ b/tests/fixtures/cksum/bsd_single_file.expected @@ -0,0 +1 @@ +08109 1 lorem_ipsum.txt diff --git a/tests/fixtures/cksum/bsd_stdin.expected b/tests/fixtures/cksum/bsd_stdin.expected new file mode 100644 index 000000000..4843ba082 --- /dev/null +++ b/tests/fixtures/cksum/bsd_stdin.expected @@ -0,0 +1 @@ +08109 1 diff --git a/tests/fixtures/cksum/sysv_multiple_files.expected b/tests/fixtures/cksum/sysv_multiple_files.expected new file mode 100644 index 000000000..83a6d6d83 --- /dev/null +++ b/tests/fixtures/cksum/sysv_multiple_files.expected @@ -0,0 +1,2 @@ +6985 2 lorem_ipsum.txt +27441 1 alice_in_wonderland.txt diff --git a/tests/fixtures/cksum/sysv_single_file.expected b/tests/fixtures/cksum/sysv_single_file.expected new file mode 100644 index 000000000..e0f7252cb --- /dev/null +++ b/tests/fixtures/cksum/sysv_single_file.expected @@ -0,0 +1 @@ +6985 2 lorem_ipsum.txt diff --git a/tests/fixtures/cksum/sysv_stdin.expected b/tests/fixtures/cksum/sysv_stdin.expected new file mode 100644 index 000000000..f0fba8c81 --- /dev/null +++ b/tests/fixtures/cksum/sysv_stdin.expected @@ -0,0 +1 @@ +6985 2