mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 04:27:45 +00:00
Merge branch 'main' into hotfix-mktemp
This commit is contained in:
commit
be2b7032f6
42 changed files with 1199 additions and 635 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -1470,9 +1470,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.16.0"
|
version = "1.17.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onig"
|
name = "onig"
|
||||||
|
@ -1982,9 +1982,9 @@ checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selinux"
|
name = "selinux"
|
||||||
version = "0.3.1"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "966a861c0b329c3078d82b404f7086009487123fd0cc905a9caac55d8b13bee1"
|
checksum = "a00576725d21b588213fbd4af84cd7e4cc4304e8e9bd6c0f5a1498a3e2ca6a51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2101,6 +2101,15 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sm3"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f943a7c5e3089f2bd046221d1e9f4fa59396bf0fe966360983649683086215da"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
@ -2439,6 +2448,7 @@ name = "uu_cksum"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"hex",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2643,17 +2653,10 @@ dependencies = [
|
||||||
name = "uu_hashsum"
|
name = "uu_hashsum"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake2b_simd",
|
|
||||||
"blake3",
|
|
||||||
"clap",
|
"clap",
|
||||||
"digest",
|
|
||||||
"hex",
|
"hex",
|
||||||
"md-5",
|
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex",
|
"regex",
|
||||||
"sha1",
|
|
||||||
"sha2",
|
|
||||||
"sha3",
|
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3347,17 +3350,27 @@ dependencies = [
|
||||||
name = "uucore"
|
name = "uucore"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"blake2b_simd",
|
||||||
|
"blake3",
|
||||||
"clap",
|
"clap",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"data-encoding-macro",
|
"data-encoding-macro",
|
||||||
|
"digest",
|
||||||
"dns-lookup",
|
"dns-lookup",
|
||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
|
"hex",
|
||||||
"itertools",
|
"itertools",
|
||||||
"libc",
|
"libc",
|
||||||
|
"md-5",
|
||||||
|
"memchr",
|
||||||
"nix",
|
"nix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"os_display",
|
"os_display",
|
||||||
|
"sha1",
|
||||||
|
"sha2",
|
||||||
|
"sha3",
|
||||||
|
"sm3",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -312,7 +312,7 @@ redox_syscall = "0.2"
|
||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
||||||
rust-ini = "0.18.0"
|
rust-ini = "0.18.0"
|
||||||
same-file = "1.0.6"
|
same-file = "1.0.6"
|
||||||
selinux = "0.3"
|
selinux = "0.4"
|
||||||
signal-hook = "0.3.14"
|
signal-hook = "0.3.14"
|
||||||
smallvec = { version = "1.10", features = ["union"] }
|
smallvec = { version = "1.10", features = ["union"] }
|
||||||
strum = "0.24.1"
|
strum = "0.24.1"
|
||||||
|
@ -332,6 +332,16 @@ windows-sys = { version="0.42.0", default-features=false }
|
||||||
xattr = "0.2.3"
|
xattr = "0.2.3"
|
||||||
zip = { version = "0.6.3", default_features=false, features=["deflate"] }
|
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 = { version=">=0.0.17", package="uucore", path="src/uucore" }
|
||||||
uucore_procs = { version=">=0.0.17", package="uucore_procs", path="src/uucore_procs" }
|
uucore_procs = { version=">=0.0.17", package="uucore_procs", path="src/uucore_procs" }
|
||||||
uu_ls = { version=">=0.0.17", path="src/uu/ls" }
|
uu_ls = { version=">=0.0.17", path="src/uu/ls" }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<!-- spell-checker:ignore pacman pamac nixpkgs -->
|
<!-- spell-checker:ignore pacman pamac nixpkgs openmandriva -->
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
@ -55,10 +55,10 @@ export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
||||||
|
|
||||||
### Gentoo
|
### Gentoo
|
||||||
|
|
||||||
[](https://packages.gentoo.org/packages/sys-apps/uutils)
|
[](https://packages.gentoo.org/packages/sys-apps/uutils-coreutils)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
emerge -pv sys-apps/uutils
|
emerge -pv sys-apps/uutils-coreutils
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manjaro
|
### Manjaro
|
||||||
|
@ -79,6 +79,13 @@ pamac install uutils-coreutils
|
||||||
nix-env -iA nixos.uutils-coreutils
|
nix-env -iA nixos.uutils-coreutils
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### OpenMandriva Lx
|
||||||
|
[](https://repology.org/project/uutils-coreutils/versions)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dnf install uutils-coreutils
|
||||||
|
```
|
||||||
|
|
||||||
### Ubuntu
|
### Ubuntu
|
||||||
|
|
||||||
[](https://packages.ubuntu.com/source/lunar/rust-coreutils)
|
[](https://packages.ubuntu.com/source/lunar/rust-coreutils)
|
||||||
|
|
|
@ -110,6 +110,7 @@ pub fn uu_app() -> Command {
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
|
.args_override_self(true)
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::CHANGES)
|
Arg::new(options::CHANGES)
|
||||||
|
|
|
@ -16,7 +16,8 @@ path = "src/cksum.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
uucore = { workspace=true }
|
uucore = { workspace=true, features=["sum"] }
|
||||||
|
hex = { workspace=true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cksum"
|
name = "cksum"
|
||||||
|
|
|
@ -5,134 +5,244 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// 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 clap::{crate_version, Arg, Command};
|
||||||
|
use hex::encode;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, stdin, BufReader, Read};
|
use std::io::{self, stdin, BufReader, Read};
|
||||||
|
use std::iter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::display::Quotable;
|
use uucore::{
|
||||||
use uucore::error::{FromIo, UResult};
|
error::{FromIo, UResult},
|
||||||
use uucore::{format_usage, show};
|
format_usage,
|
||||||
|
sum::{
|
||||||
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
|
div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3,
|
||||||
const CRC_TABLE_LEN: usize = 256;
|
BSD, CRC, SYSV,
|
||||||
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const USAGE: &str = "{} [OPTIONS] [FILE]...";
|
const USAGE: &str = "{} [OPTIONS] [FILE]...";
|
||||||
const ABOUT: &str = "Print CRC and size for each file";
|
const ABOUT: &str = "Print CRC and size for each file";
|
||||||
|
|
||||||
const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
|
const ALGORITHM_OPTIONS_SYSV: &str = "sysv";
|
||||||
let mut table = [0; CRC_TABLE_LEN];
|
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;
|
fn detect_algo(program: &str) -> (&'static str, Box<dyn Digest + 'static>, usize) {
|
||||||
while i < CRC_TABLE_LEN {
|
match program {
|
||||||
table[i] = crc_entry(i as u8);
|
ALGORITHM_OPTIONS_SYSV => (
|
||||||
|
ALGORITHM_OPTIONS_SYSV,
|
||||||
|
Box::new(SYSV::new()) as Box<dyn Digest>,
|
||||||
|
512,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_BSD => (
|
||||||
|
ALGORITHM_OPTIONS_BSD,
|
||||||
|
Box::new(BSD::new()) as Box<dyn Digest>,
|
||||||
|
1024,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_CRC => (
|
||||||
|
ALGORITHM_OPTIONS_CRC,
|
||||||
|
Box::new(CRC::new()) as Box<dyn Digest>,
|
||||||
|
256,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_MD5 => (
|
||||||
|
ALGORITHM_OPTIONS_MD5,
|
||||||
|
Box::new(Md5::new()) as Box<dyn Digest>,
|
||||||
|
128,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_SHA1 => (
|
||||||
|
ALGORITHM_OPTIONS_SHA1,
|
||||||
|
Box::new(Sha1::new()) as Box<dyn Digest>,
|
||||||
|
160,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_SHA224 => (
|
||||||
|
ALGORITHM_OPTIONS_SHA224,
|
||||||
|
Box::new(Sha224::new()) as Box<dyn Digest>,
|
||||||
|
224,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_SHA256 => (
|
||||||
|
ALGORITHM_OPTIONS_SHA256,
|
||||||
|
Box::new(Sha256::new()) as Box<dyn Digest>,
|
||||||
|
256,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_SHA384 => (
|
||||||
|
ALGORITHM_OPTIONS_SHA384,
|
||||||
|
Box::new(Sha384::new()) as Box<dyn Digest>,
|
||||||
|
384,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_SHA512 => (
|
||||||
|
ALGORITHM_OPTIONS_SHA512,
|
||||||
|
Box::new(Sha512::new()) as Box<dyn Digest>,
|
||||||
|
512,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_BLAKE2B => (
|
||||||
|
ALGORITHM_OPTIONS_BLAKE2B,
|
||||||
|
Box::new(Blake2b::new()) as Box<dyn Digest>,
|
||||||
|
512,
|
||||||
|
),
|
||||||
|
ALGORITHM_OPTIONS_SM3 => (
|
||||||
|
ALGORITHM_OPTIONS_SM3,
|
||||||
|
Box::new(Sm3::new()) as Box<dyn Digest>,
|
||||||
|
512,
|
||||||
|
),
|
||||||
|
_ => unreachable!("unknown algorithm: clap should have prevented this case"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
i += 1;
|
struct Options {
|
||||||
|
algo_name: &'static str,
|
||||||
|
digest: Box<dyn Digest + 'static>,
|
||||||
|
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<Item = &'a OsStr>,
|
||||||
|
{
|
||||||
|
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<dyn Read>
|
||||||
|
} else if filename.is_dir() {
|
||||||
|
Box::new(BufReader::new(io::empty())) as Box<dyn Read>
|
||||||
|
} else {
|
||||||
|
file_buf =
|
||||||
|
File::open(filename).map_err_context(|| filename.to_str().unwrap().to_string())?;
|
||||||
|
Box::new(file_buf) as Box<dyn Read>
|
||||||
|
});
|
||||||
|
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::<u16>().unwrap(),
|
||||||
|
div_ceil(sz, options.output_bits)
|
||||||
|
),
|
||||||
|
(ALGORITHM_OPTIONS_SYSV, false) => println!(
|
||||||
|
"{} {} {}",
|
||||||
|
sum.parse::<u16>().unwrap(),
|
||||||
|
div_ceil(sz, options.output_bits),
|
||||||
|
filename.display()
|
||||||
|
),
|
||||||
|
(ALGORITHM_OPTIONS_BSD, true) => println!(
|
||||||
|
"{:0bsd_width$} {:bsd_width$}",
|
||||||
|
sum.parse::<u16>().unwrap(),
|
||||||
|
div_ceil(sz, options.output_bits)
|
||||||
|
),
|
||||||
|
(ALGORITHM_OPTIONS_BSD, false) => println!(
|
||||||
|
"{:0bsd_width$} {:bsd_width$} {}",
|
||||||
|
sum.parse::<u16>().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 {
|
fn digest_read<T: Read>(
|
||||||
let mut crc = (input as u32) << 24;
|
digest: &mut Box<dyn Digest>,
|
||||||
|
reader: &mut BufReader<T>,
|
||||||
|
output_bits: usize,
|
||||||
|
) -> io::Result<(String, usize)> {
|
||||||
|
digest.reset();
|
||||||
|
|
||||||
let mut i = 0;
|
// Read bytes from `reader` and write those bytes to `digest`.
|
||||||
while i < 8 {
|
//
|
||||||
let if_condition = crc & 0x8000_0000;
|
// If `binary` is `false` and the operating system is Windows, then
|
||||||
let if_body = (crc << 1) ^ 0x04c1_1db7;
|
// `DigestWriter` replaces "\r\n" with "\n" before it writes the
|
||||||
let else_body = crc << 1;
|
// 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
|
if digest.output_bits() > 0 {
|
||||||
// ops
|
Ok((digest.result_str(), output_size))
|
||||||
let condition_table = [else_body, if_body];
|
} else {
|
||||||
|
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
|
||||||
crc = condition_table[(if_condition != 0) as usize];
|
let mut bytes = Vec::new();
|
||||||
i += 1;
|
bytes.resize((output_bits + 7) / 8, 0);
|
||||||
}
|
digest.hash_finalize(&mut bytes);
|
||||||
|
Ok((encode(bytes), output_size))
|
||||||
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<u8> {
|
|
||||||
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<dyn Read> = 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<dyn Read>
|
|
||||||
} else {
|
|
||||||
Box::new(BufReader::new(File::open(p)?)) as Box<dyn Read>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub static FILE: &str = "file";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args.collect_ignore();
|
let args = args.collect_ignore();
|
||||||
|
|
||||||
let matches = uu_app().try_get_matches_from(args)?;
|
let matches = uu_app().try_get_matches_from(args)?;
|
||||||
|
|
||||||
let files: Vec<String> = match matches.get_many::<String>(options::FILE) {
|
let algo_name: &str = match matches.get_one::<String>(options::ALGORITHM) {
|
||||||
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
|
Some(v) => v,
|
||||||
None => vec![],
|
None => ALGORITHM_OPTIONS_CRC,
|
||||||
};
|
};
|
||||||
|
|
||||||
if files.is_empty() {
|
let (name, algo, bits) = detect_algo(algo_name);
|
||||||
let (crc, size) = cksum("-")?;
|
let opts = Options {
|
||||||
println!("{crc} {size}");
|
algo_name: name,
|
||||||
return Ok(());
|
digest: algo,
|
||||||
}
|
output_bits: bits,
|
||||||
|
};
|
||||||
|
|
||||||
|
match matches.get_many::<String>(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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,4 +258,25 @@ pub fn uu_app() -> Command {
|
||||||
.action(clap::ArgAction::Append)
|
.action(clap::ArgAction::Append)
|
||||||
.value_hint(clap::ValueHint::FilePath),
|
.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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ impl Read for Source {
|
||||||
///
|
///
|
||||||
/// Use the [`Input::new_stdin`] or [`Input::new_file`] functions to
|
/// Use the [`Input::new_stdin`] or [`Input::new_file`] functions to
|
||||||
/// construct a new instance of this struct. Then pass the instance 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`.
|
/// for `dd`.
|
||||||
struct Input<'a> {
|
struct Input<'a> {
|
||||||
/// The source from which bytes will be read.
|
/// 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
|
/// Use the [`Output::new_stdout`] or [`Output::new_file`] functions
|
||||||
/// to construct a new instance of this struct. Then use the
|
/// 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`.
|
/// `dd`.
|
||||||
struct Output<'a> {
|
struct Output<'a> {
|
||||||
/// The destination to which bytes will be written.
|
/// The destination to which bytes will be written.
|
||||||
|
@ -579,136 +579,135 @@ impl<'a> Output<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Copy the given input data to this output, consuming both.
|
/// Copy the given input data to this output, consuming both.
|
||||||
///
|
///
|
||||||
/// This method contains the main loop for the `dd` program. Bytes
|
/// This method contains the main loop for the `dd` program. Bytes
|
||||||
/// are read in blocks from `i` and written in blocks to this
|
/// are read in blocks from `i` and written in blocks to this
|
||||||
/// output. Read/write statistics are reported to stderr as
|
/// output. Read/write statistics are reported to stderr as
|
||||||
/// configured by the `status` command-line argument.
|
/// configured by the `status` command-line argument.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If there is a problem reading from the input or writing to
|
/// If there is a problem reading from the input or writing to
|
||||||
/// this output.
|
/// this output.
|
||||||
fn dd_out(mut self, mut i: Input) -> std::io::Result<()> {
|
fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
|
||||||
// The read and write statistics.
|
// 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
|
// As an optimization, make an educated guess about the
|
||||||
// iteration of the main loop, each will be incremented by the
|
// best buffer size for reading based on the number of
|
||||||
// number of blocks read and written, respectively.
|
// blocks already read and the number of blocks remaining.
|
||||||
let mut rstat = ReadStat::default();
|
let loop_bsize = calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize);
|
||||||
let mut wstat = WriteStat::default();
|
let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?;
|
||||||
|
if rstat_update.is_empty() {
|
||||||
// The time at which the main loop starts executing.
|
break;
|
||||||
//
|
|
||||||
// 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(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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<T>(
|
||||||
|
output: &mut Output,
|
||||||
|
rstat: ReadStat,
|
||||||
|
wstat: WriteStat,
|
||||||
|
start: time::Instant,
|
||||||
|
prog_tx: &mpsc::Sender<ProgUpdate>,
|
||||||
|
output_thread: thread::JoinHandle<T>,
|
||||||
|
) -> 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.
|
// Print the final read/write statistics.
|
||||||
fn finalize<T>(
|
let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true);
|
||||||
&mut self,
|
prog_tx.send(prog_update).unwrap_or(());
|
||||||
rstat: ReadStat,
|
// Wait for the output thread to finish
|
||||||
wstat: WriteStat,
|
output_thread
|
||||||
start: time::Instant,
|
.join()
|
||||||
prog_tx: &mpsc::Sender<ProgUpdate>,
|
.expect("Failed to join with the output thread.");
|
||||||
output_thread: thread::JoinHandle<T>,
|
Ok(())
|
||||||
) -> 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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[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)?,
|
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 {
|
pub fn uu_app() -> Command {
|
||||||
|
|
18
src/uu/df/df.md
Normal file
18
src/uu/df/df.md
Normal file
|
@ -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).
|
|
@ -19,7 +19,7 @@ use uucore::error::FromIo;
|
||||||
use uucore::error::{UError, UResult, USimpleError};
|
use uucore::error::{UError, UResult, USimpleError};
|
||||||
use uucore::fsext::{read_fs_list, MountInfo};
|
use uucore::fsext::{read_fs_list, MountInfo};
|
||||||
use uucore::parse_size::ParseSizeError;
|
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};
|
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::filesystem::Filesystem;
|
||||||
use crate::table::Table;
|
use crate::table::Table;
|
||||||
|
|
||||||
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
|
const ABOUT: &str = help_about!("df.md");
|
||||||
or all file systems by default.";
|
const USAGE: &str = help_usage!("df.md");
|
||||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
const AFTER_HELP: &str = help_section!("after help", "df.md");
|
||||||
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).";
|
|
||||||
|
|
||||||
static OPT_HELP: &str = "help";
|
static OPT_HELP: &str = "help";
|
||||||
static OPT_ALL: &str = "all";
|
static OPT_ALL: &str = "all";
|
||||||
|
@ -487,7 +480,7 @@ pub fn uu_app() -> Command {
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.after_help(LONG_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.disable_help_flag(true)
|
.disable_help_flag(true)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
13
src/uu/dircolors/dircolors.md
Normal file
13
src/uu/dircolors/dircolors.md
Normal file
|
@ -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'
|
|
@ -16,6 +16,7 @@ use std::io::{BufRead, BufReader};
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||||
|
use uucore::{help_about, help_section, help_usage};
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const BOURNE_SHELL: &str = "bourne-shell";
|
pub const BOURNE_SHELL: &str = "bourne-shell";
|
||||||
|
@ -25,13 +26,9 @@ mod options {
|
||||||
pub const FILE: &str = "FILE";
|
pub const FILE: &str = "FILE";
|
||||||
}
|
}
|
||||||
|
|
||||||
static USAGE: &str = "{} [OPTION]... [FILE]";
|
const USAGE: &str = help_usage!("dircolors.md");
|
||||||
static ABOUT: &str = "Output commands to set the LS_COLORS environment variable.";
|
const ABOUT: &str = help_about!("dircolors.md");
|
||||||
static LONG_HELP: &str = "
|
const AFTER_HELP: &str = help_section!("after help", "dircolors.md");
|
||||||
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'
|
|
||||||
";
|
|
||||||
|
|
||||||
mod colors;
|
mod colors;
|
||||||
use self::colors::INTERNAL_DB;
|
use self::colors::INTERNAL_DB;
|
||||||
|
@ -170,7 +167,7 @@ pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.after_help(LONG_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
7
src/uu/fmt/fmt.md
Normal file
7
src/uu/fmt/fmt.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# fmt
|
||||||
|
|
||||||
|
```
|
||||||
|
fmt [OPTION]... [FILE]...
|
||||||
|
```
|
||||||
|
|
||||||
|
Reformat paragraphs from input files (or stdin) to stdout.
|
|
@ -14,7 +14,7 @@ use std::io::{stdin, stdout, Write};
|
||||||
use std::io::{BufReader, BufWriter, Read};
|
use std::io::{BufReader, BufWriter, Read};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult, USimpleError};
|
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::linebreak::break_lines;
|
||||||
use self::parasplit::ParagraphStream;
|
use self::parasplit::ParagraphStream;
|
||||||
|
@ -22,8 +22,8 @@ use self::parasplit::ParagraphStream;
|
||||||
mod linebreak;
|
mod linebreak;
|
||||||
mod parasplit;
|
mod parasplit;
|
||||||
|
|
||||||
static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout.";
|
static ABOUT: &str = help_about!("fmt.md");
|
||||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
const USAGE: &str = help_usage!("fmt.md");
|
||||||
static MAX_WIDTH: usize = 2500;
|
static MAX_WIDTH: usize = 2500;
|
||||||
|
|
||||||
static OPT_CROWN_MARGIN: &str = "crown-margin";
|
static OPT_CROWN_MARGIN: &str = "crown-margin";
|
||||||
|
|
|
@ -15,18 +15,11 @@ edition = "2021"
|
||||||
path = "src/hashsum.rs"
|
path = "src/hashsum.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
digest = "0.10.6"
|
|
||||||
clap = { workspace=true }
|
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 }
|
uucore = { workspace=true }
|
||||||
|
memchr = { workspace=true }
|
||||||
|
regex = { workspace=true }
|
||||||
|
hex = { workspace=true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "hashsum"
|
name = "hashsum"
|
||||||
|
|
|
@ -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<u8> = 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<dyn Digest>,
|
|
||||||
|
|
||||||
/// 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<dyn Digest>, 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<usize> {
|
|
||||||
self.digest.input(buf);
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
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<dyn Digest>;
|
|
||||||
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<dyn Digest>;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,21 +9,12 @@
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) algo, algoname, regexes, nread, nonames
|
// 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::builder::ValueParser;
|
||||||
use clap::crate_version;
|
use clap::crate_version;
|
||||||
use clap::ArgAction;
|
use clap::ArgAction;
|
||||||
use clap::{Arg, ArgMatches, Command};
|
use clap::{Arg, ArgMatches, Command};
|
||||||
use hex::encode;
|
use hex::encode;
|
||||||
use md5::Md5;
|
|
||||||
use regex::Regex;
|
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::cmp::Ordering;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
|
@ -32,10 +23,12 @@ use std::io::{self, stdin, BufRead, BufReader, Read};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::crash;
|
|
||||||
use uucore::display::Quotable;
|
|
||||||
use uucore::error::{FromIo, UError, UResult};
|
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";
|
const NAME: &str = "hashsum";
|
||||||
|
|
||||||
|
@ -68,16 +61,8 @@ fn detect_algo(
|
||||||
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
|
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
|
||||||
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
|
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
|
||||||
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
|
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
|
||||||
"b2sum" => (
|
"b2sum" => ("BLAKE2", Box::new(Blake2b::new()) as Box<dyn Digest>, 512),
|
||||||
"BLAKE2",
|
"b3sum" => ("BLAKE3", Box::new(Blake3::new()) as Box<dyn Digest>, 256),
|
||||||
Box::new(blake2b_simd::State::new()) as Box<dyn Digest>,
|
|
||||||
512,
|
|
||||||
),
|
|
||||||
"b3sum" => (
|
|
||||||
"BLAKE3",
|
|
||||||
Box::new(blake3::Hasher::new()) as Box<dyn Digest>,
|
|
||||||
256,
|
|
||||||
),
|
|
||||||
"sha3sum" => match matches.get_one::<usize>("bits") {
|
"sha3sum" => match matches.get_one::<usize>("bits") {
|
||||||
Some(224) => (
|
Some(224) => (
|
||||||
"SHA3-224",
|
"SHA3-224",
|
||||||
|
@ -170,10 +155,10 @@ fn detect_algo(
|
||||||
set_or_crash("SHA512", Box::new(Sha512::new()), 512);
|
set_or_crash("SHA512", Box::new(Sha512::new()), 512);
|
||||||
}
|
}
|
||||||
if matches.get_flag("b2sum") {
|
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") {
|
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") {
|
if matches.get_flag("sha3") {
|
||||||
match matches.get_one::<usize>("bits") {
|
match matches.get_one::<usize>("bits") {
|
||||||
|
@ -680,7 +665,7 @@ fn digest_reader<T: Read>(
|
||||||
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
|
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
bytes.resize((output_bits + 7) / 8, 0);
|
bytes.resize((output_bits + 7) / 8, 0);
|
||||||
digest.result(&mut bytes);
|
digest.hash_finalize(&mut bytes);
|
||||||
Ok(encode(bytes))
|
Ok(encode(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
src/uu/link/link.md
Normal file
7
src/uu/link/link.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# link
|
||||||
|
|
||||||
|
```
|
||||||
|
link FILE1 FILE2
|
||||||
|
```
|
||||||
|
|
||||||
|
Call the link function to create a link named FILE2 to an existing FILE1.
|
|
@ -11,10 +11,10 @@ use std::fs::hard_link;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult};
|
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.";
|
static ABOUT: &str = help_about!("link.md");
|
||||||
const USAGE: &str = "{} FILE1 FILE2";
|
const USAGE: &str = help_usage!("link.md");
|
||||||
|
|
||||||
pub mod options {
|
pub mod options {
|
||||||
pub static FILES: &str = "FILES";
|
pub static FILES: &str = "FILES";
|
||||||
|
|
21
src/uu/ln/ln.md
Normal file
21
src/uu/ln/ln.md
Normal file
|
@ -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.
|
|
@ -11,7 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UError, UResult};
|
use uucore::error::{FromIo, UError, UResult};
|
||||||
use uucore::fs::{make_path_relative_to, paths_refer_to_same_file};
|
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::borrow::Cow;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
@ -85,21 +85,9 @@ impl UError for LnError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ABOUT: &str = "Change file owner and group";
|
const ABOUT: &str = help_about!("ln.md");
|
||||||
const USAGE: &str = "\
|
const USAGE: &str = help_usage!("ln.md");
|
||||||
{} [OPTION]... [-T] TARGET LINK_NAME
|
const AFTER_HELP: &str = help_section!("after help", "ln.md");
|
||||||
{} [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.";
|
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const FORCE: &str = "force";
|
pub const FORCE: &str = "force";
|
||||||
|
@ -119,13 +107,13 @@ static ARG_FILES: &str = "files";
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let long_usage = format!(
|
let after_help = format!(
|
||||||
"{}\n\n{}",
|
"{}\n\n{}",
|
||||||
LONG_USAGE,
|
AFTER_HELP,
|
||||||
backup_control::BACKUP_CONTROL_LONG_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 */
|
/* the list of files */
|
||||||
|
|
||||||
|
|
7
src/uu/realpath/realpath.md
Normal file
7
src/uu/realpath/realpath.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# realpath
|
||||||
|
|
||||||
|
```
|
||||||
|
realpath [OPTION]... FILE...
|
||||||
|
```
|
||||||
|
|
||||||
|
Print the resolved path
|
|
@ -20,11 +20,12 @@ use uucore::{
|
||||||
error::{FromIo, UResult},
|
error::{FromIo, UResult},
|
||||||
format_usage,
|
format_usage,
|
||||||
fs::{canonicalize, MissingHandling, ResolveMode},
|
fs::{canonicalize, MissingHandling, ResolveMode},
|
||||||
|
help_about, help_usage,
|
||||||
};
|
};
|
||||||
use uucore::{error::UClapError, show, show_if_err};
|
use uucore::{error::UClapError, show, show_if_err};
|
||||||
|
|
||||||
static ABOUT: &str = "Print the resolved path";
|
static ABOUT: &str = help_about!("realpath.md");
|
||||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
const USAGE: &str = help_usage!("realpath.md");
|
||||||
|
|
||||||
static OPT_QUIET: &str = "quiet";
|
static OPT_QUIET: &str = "quiet";
|
||||||
static OPT_STRIP: &str = "strip";
|
static OPT_STRIP: &str = "strip";
|
||||||
|
|
11
src/uu/shuf/shuf.md
Normal file
11
src/uu/shuf/shuf.md
Normal file
|
@ -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.
|
|
@ -15,7 +15,7 @@ use std::fs::File;
|
||||||
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
|
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult, USimpleError};
|
use uucore::error::{FromIo, UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
mod rand_read_adapter;
|
mod rand_read_adapter;
|
||||||
|
|
||||||
|
@ -25,14 +25,8 @@ enum Mode {
|
||||||
InputRange((usize, usize)),
|
InputRange((usize, usize)),
|
||||||
}
|
}
|
||||||
|
|
||||||
static USAGE: &str = "\
|
static USAGE: &str = help_usage!("shuf.md");
|
||||||
{} [OPTION]... [FILE]
|
static ABOUT: &str = help_about!("shuf.md");
|
||||||
{} -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.";
|
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
head_count: usize,
|
head_count: usize,
|
||||||
|
|
|
@ -3,19 +3,20 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE file
|
// * For the full copyright and license information, please view the LICENSE file
|
||||||
// * that was distributed with this source code.
|
// * 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;
|
mod flags;
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
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::{
|
use nix::sys::termios::{
|
||||||
cfgetospeed, tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios,
|
cfgetospeed, tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios,
|
||||||
};
|
};
|
||||||
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
|
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
|
||||||
use std::io::{self, stdout};
|
use std::io::{self, stdout};
|
||||||
use std::ops::ControlFlow;
|
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::error::{UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
use uucore::format_usage;
|
||||||
|
|
||||||
|
@ -102,7 +103,20 @@ impl<'a> Options<'a> {
|
||||||
all: matches.get_flag(options::ALL),
|
all: matches.get_flag(options::ALL),
|
||||||
save: matches.get_flag(options::SAVE),
|
save: matches.get_flag(options::SAVE),
|
||||||
file: match matches.get_one::<String>(options::FILE) {
|
file: match matches.get_one::<String>(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(),
|
None => stdout().as_raw_fd(),
|
||||||
},
|
},
|
||||||
settings: matches
|
settings: matches
|
||||||
|
|
|
@ -17,16 +17,10 @@ use std::ffi::OsString;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
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 = "\
|
const ABOUT: &str = help_about!("tail.md");
|
||||||
Print the last 10 lines of each FILE to standard output.\n\
|
const USAGE: &str = help_usage!("tail.md");
|
||||||
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]...";
|
|
||||||
|
|
||||||
pub mod options {
|
pub mod options {
|
||||||
pub mod verbosity {
|
pub mod verbosity {
|
||||||
|
|
11
src/uu/tail/tail.md
Normal file
11
src/uu/tail/tail.md
Normal file
|
@ -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.
|
|
@ -11,15 +11,16 @@ use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::UResult;
|
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
|
// spell-checker:ignore nopipe
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use uucore::libc;
|
use uucore::libc;
|
||||||
|
|
||||||
static ABOUT: &str = "Copy standard input to each FILE, and also to standard output.";
|
const ABOUT: &str = help_about!("tee.md");
|
||||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
const USAGE: &str = help_usage!("tee.md");
|
||||||
|
const AFTER_HELP: &str = help_section!("after help", "tee.md");
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const APPEND: &str = "append";
|
pub const APPEND: &str = "append";
|
||||||
|
@ -88,7 +89,7 @@ pub fn uu_app() -> Command {
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.override_usage(format_usage(USAGE))
|
.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)
|
.infer_long_args(true)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::APPEND)
|
Arg::new(options::APPEND)
|
||||||
|
|
11
src/uu/tee/tee.md
Normal file
11
src/uu/tee/tee.md
Normal file
|
@ -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 - .
|
|
@ -36,6 +36,17 @@ libc = { version="0.2.137", optional=true }
|
||||||
once_cell = { workspace=true }
|
once_cell = { workspace=true }
|
||||||
os_display = "0.1.3"
|
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]
|
[target.'cfg(unix)'.dependencies]
|
||||||
walkdir = { workspace=true, optional=true }
|
walkdir = { workspace=true, optional=true }
|
||||||
nix = { workspace=true, features = ["fs", "uio", "zerocopy"] }
|
nix = { workspace=true, features = ["fs", "uio", "zerocopy"] }
|
||||||
|
@ -66,3 +77,4 @@ utf8 = []
|
||||||
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
|
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
|
||||||
wide = []
|
wide = []
|
||||||
pipes = []
|
pipes = []
|
||||||
|
sum = []
|
||||||
|
|
|
@ -12,6 +12,8 @@ pub mod lines;
|
||||||
pub mod memo;
|
pub mod memo;
|
||||||
#[cfg(feature = "ringbuffer")]
|
#[cfg(feature = "ringbuffer")]
|
||||||
pub mod ringbuffer;
|
pub mod ringbuffer;
|
||||||
|
#[cfg(feature = "sum")]
|
||||||
|
pub mod sum;
|
||||||
#[cfg(feature = "memo")]
|
#[cfg(feature = "memo")]
|
||||||
mod tokenize;
|
mod tokenize;
|
||||||
|
|
||||||
|
|
494
src/uucore/src/lib/features/sum.rs
Normal file
494
src/uucore/src/lib/features/sum.rs
Normal file
|
@ -0,0 +1,494 @@
|
||||||
|
// This file is part of the uutils coreutils package.
|
||||||
|
//
|
||||||
|
// (c) Yuan YangHao <yuanyanghau@gmail.com>
|
||||||
|
//
|
||||||
|
// 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<u8> = 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(<sm3::Sm3 as sm3::Digest>::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_update(&mut self, input: &[u8]) {
|
||||||
|
<sm3::Sm3 as sm3::Digest>::update(&mut self.0, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_finalize(&mut self, out: &mut [u8]) {
|
||||||
|
out.copy_from_slice(&<sm3::Sm3 as sm3::Digest>::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<u8> = 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<u8> = 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<u8> = 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<dyn Digest>,
|
||||||
|
|
||||||
|
/// 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<dyn Digest>, 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<usize> {
|
||||||
|
self.digest.hash_update(buf);
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
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<dyn Digest>;
|
||||||
|
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<dyn Digest>;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,8 @@ pub use crate::features::lines;
|
||||||
pub use crate::features::memo;
|
pub use crate::features::memo;
|
||||||
#[cfg(feature = "ringbuffer")]
|
#[cfg(feature = "ringbuffer")]
|
||||||
pub use crate::features::ringbuffer;
|
pub use crate::features::ringbuffer;
|
||||||
|
#[cfg(feature = "sum")]
|
||||||
|
pub use crate::features::sum;
|
||||||
|
|
||||||
// * (platform-specific) feature-gated modules
|
// * (platform-specific) feature-gated modules
|
||||||
// ** non-windows (i.e. Unix + Fuchsia)
|
// ** non-windows (i.e. Unix + Fuchsia)
|
||||||
|
|
|
@ -650,3 +650,24 @@ fn test_chmod_file_symlink_after_non_existing_file() {
|
||||||
0o100764
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -114,3 +114,79 @@ fn test_stdin_larger_than_128_bytes() {
|
||||||
assert_eq!(cksum, 945_881_979);
|
assert_eq!(cksum, 945_881_979);
|
||||||
assert_eq!(bytes_cnt, 2058);
|
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");
|
||||||
|
}
|
||||||
|
|
|
@ -121,6 +121,14 @@ fn test_invalid_signal() {
|
||||||
.usage_error("'invalid': 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 that the long form of the `--kill-after` argument is recognized.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_kill_after_long() {
|
fn test_kill_after_long() {
|
||||||
|
|
2
tests/fixtures/cksum/bsd_multiple_files.expected
vendored
Normal file
2
tests/fixtures/cksum/bsd_multiple_files.expected
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
08109 1 lorem_ipsum.txt
|
||||||
|
01814 1 alice_in_wonderland.txt
|
1
tests/fixtures/cksum/bsd_single_file.expected
vendored
Normal file
1
tests/fixtures/cksum/bsd_single_file.expected
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
08109 1 lorem_ipsum.txt
|
1
tests/fixtures/cksum/bsd_stdin.expected
vendored
Normal file
1
tests/fixtures/cksum/bsd_stdin.expected
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
08109 1
|
2
tests/fixtures/cksum/sysv_multiple_files.expected
vendored
Normal file
2
tests/fixtures/cksum/sysv_multiple_files.expected
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
6985 2 lorem_ipsum.txt
|
||||||
|
27441 1 alice_in_wonderland.txt
|
1
tests/fixtures/cksum/sysv_single_file.expected
vendored
Normal file
1
tests/fixtures/cksum/sysv_single_file.expected
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
6985 2 lorem_ipsum.txt
|
1
tests/fixtures/cksum/sysv_stdin.expected
vendored
Normal file
1
tests/fixtures/cksum/sysv_stdin.expected
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
6985 2
|
Loading…
Add table
Add a link
Reference in a new issue