diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index b2ae59bdf..bbbf60838 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -20,6 +20,13 @@ env: on: [push, pull_request] jobs: + cargo-deny: + name: Style/cargo-deny + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: EmbarkStudios/cargo-deny-action@v1 + style_deps: ## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard name: Style/deps diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 3b332a51c..1f24f3045 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -137,6 +137,8 @@ jobs: echo "::error ::Failed to find summary of test results (missing '${SUITE_LOG_FILE}'); failing early" exit 1 fi + # Compress logs before upload (fails otherwise) + gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }} - name: Reserve SHA1/ID of 'test-summary' uses: actions/upload-artifact@v2 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac2e0811e..15adfe488 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,6 +94,16 @@ uutils: add new utility gitignore: add temporary files ``` +## cargo-deny + +This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to +detect duplicate dependencies, checks licenses, etc. To run it locally, first +install it and then run with: + +``` +cargo deny --all-features check all +``` + ## Licensing uutils is distributed under the terms of the MIT License; see the `LICENSE` file diff --git a/Cargo.lock b/Cargo.lock index 8ac24c4b5..fcc0b4ca4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -877,7 +877,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37978dab2ca789938a83b2f8bc1ef32db6633af9051a6cd409eff72cbaaa79a" dependencies = [ - "paste 1.0.6", + "paste", ] [[package]] @@ -1431,31 +1431,12 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "paste" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" -dependencies = [ - "paste-impl", - "proc-macro-hack", -] - [[package]] name = "paste" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" -[[package]] -name = "paste-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" -dependencies = [ - "proc-macro-hack", -] - [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1574,12 +1555,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - [[package]] name = "proc-macro2" version = "1.0.36" @@ -2568,7 +2543,7 @@ dependencies = [ "clap 3.1.6", "coz", "num-traits", - "paste 0.1.18", + "paste", "quickcheck", "rand", "smallvec", diff --git a/deny.toml b/deny.toml new file mode 100644 index 000000000..dea3503de --- /dev/null +++ b/deny.toml @@ -0,0 +1,95 @@ +# spell-checker:ignore SSLeay RUSTSEC + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +db-path = "~/.cargo/advisory-db" +db-urls = ["https://github.com/rustsec/advisory-db"] +vulnerability = "warn" +unmaintained = "warn" +yanked = "warn" +notice = "warn" +ignore = [ + #"RUSTSEC-0000-0000", +] + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +unlicensed = "deny" +allow = [ + "MIT", + "Apache-2.0", + "ISC", + "BSD-2-Clause", + "BSD-2-Clause-FreeBSD", + "BSD-3-Clause", + "CC0-1.0", + "MPL-2.0", # XXX considered copyleft? +] +copyleft = "deny" +allow-osi-fsf-free = "neither" +default = "deny" +confidence-threshold = 0.8 +exceptions = [ + { allow = ["OpenSSL"], name = "ring" }, +] + +[[licenses.clarify]] +name = "ring" +# SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses +# https://spdx.org/licenses/OpenSSL.html +# ISC - Both BoringSSL and ring use this for their new files +# MIT - "Files in third_party/ have their own licenses, as described therein. The MIT +# license, for third_party/fiat, which, unlike other third_party directories, is +# compiled into non-test libraries, is included below." +# OpenSSL - Obviously +expression = "ISC AND MIT AND OpenSSL" +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +multiple-versions = "deny" +wildcards = "allow" +highlight = "all" + +# For each duplicate dependency, indicate the name of the dependency which +# introduces it. +# spell-checker: disable +skip = [ + # blake2d_simd + { name = "arrayvec", version = "=0.7.2" }, + # flimit/unix_socket + { name = "cfg-if", version = "=0.1.10" }, + # ordered-multimap + { name = "hashbrown", version = "=0.9.1" }, + # kernel32-sys + { name = "winapi", version = "=0.2.8" }, + # bindgen 0.59.2 + { name = "clap", version = "=2.34.0" }, + { name = "strsim", version = "=0.8.0" }, + { name = "textwrap", version = "=0.11.0" }, + { name = "cpp_common", version = "=0.4.0" }, + # quickcheck + { name = "env_logger", version = "=0.8.4" }, + # cpp_* + { name = "memchr", version = "=1.0.2" }, + { name = "quote", version = "=0.3.15" }, + { name = "unicode-xid", version = "=0.0.4" }, + # exacl + { name = "nix", version = "=0.21.0" }, +] +# spell-checker: enable + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +unknown-registry = "warn" +unknown-git = "warn" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 592867ddf..8c6587617 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -23,7 +23,7 @@ smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or late uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } [dev-dependencies] -paste = "0.1.18" +paste = "1.0.6" quickcheck = "1.0.3" [[bin]] diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 2ac6275a6..385f2017c 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -63,7 +63,7 @@ fn sleep(args: &[&str]) -> UResult<()> { args.iter().try_fold( Duration::new(0, 0), |result, arg| match uucore::parse_time::from_str(&arg[..]) { - Ok(m) => Ok(m + result), + Ok(m) => Ok(m.saturating_add(result)), Err(f) => Err(USimpleError::new(1, f)), }, )?; diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 6cb7c629a..b44b984b6 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1162,46 +1162,6 @@ where Ok(()) } -fn split_into_n_chunks_by_line_round_robin( - settings: &Settings, - reader: &mut R, - num_chunks: u64, -) -> UResult<()> -where - R: BufRead, -{ - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - ); - - // Create one writer for each chunk. This will create each - // of the underlying files (if not in `--filter` mode). - let mut writers = vec![]; - for _ in 0..num_chunks { - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - let writer = platform::instantiate_current_writer(&settings.filter, filename.as_str()); - writers.push(writer); - } - - let num_chunks: usize = num_chunks.try_into().unwrap(); - for (i, line_result) in reader.lines().enumerate() { - let line = line_result.unwrap(); - let maybe_writer = writers.get_mut(i % num_chunks); - let writer = maybe_writer.unwrap(); - let bytes = line.as_bytes(); - writer.write_all(bytes)?; - writer.write_all(b"\n")?; - } - - Ok(()) -} - fn split(settings: &Settings) -> UResult<()> { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box @@ -1228,9 +1188,6 @@ fn split(settings: &Settings) -> UResult<()> { let chunk_number = chunk_number - 1; kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks) } - Strategy::Number(NumberType::RoundRobin(num_chunks)) => { - split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) - } Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")), Strategy::Lines(chunk_size) => { let mut writer = LineChunkWriter::new(chunk_size, settings) diff --git a/src/uucore/src/lib/parser/parse_time.rs b/src/uucore/src/lib/parser/parse_time.rs index 68f0ca8d0..366eebdea 100644 --- a/src/uucore/src/lib/parser/parse_time.rs +++ b/src/uucore/src/lib/parser/parse_time.rs @@ -6,11 +6,39 @@ // file that was distributed with this source code. // spell-checker:ignore (vars) NANOS numstr +//! Parsing a duration from a string. +//! +//! Use the [`from_str`] function to parse a [`Duration`] from a string. use std::time::Duration; use crate::display::Quotable; +/// Parse a duration from a string. +/// +/// The string may contain only a number, like "123" or "4.5", or it +/// may contain a number with a unit specifier, like "123s" meaning +/// one hundred twenty three seconds or "4.5d" meaning four and a half +/// days. If no unit is specified, the unit is assumed to be seconds. +/// +/// This function uses [`Duration::saturating_mul`] to compute the +/// number of seconds, so it does not overflow. If overflow would have +/// occurred, [`Duration::MAX`] is returned instead. +/// +/// # Errors +/// +/// This function returns an error if the input string is empty, the +/// input is not a valid number, or the unit specifier is invalid or +/// unknown. +/// +/// # Examples +/// +/// ```rust +/// use std::time::Duration; +/// use uucore::parse_time::from_str; +/// assert_eq!(from_str("123"), Ok(Duration::from_secs(123))); +/// assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2))); +/// ``` pub fn from_str(string: &str) -> Result { let len = string.len(); if len == 0 { @@ -39,5 +67,42 @@ pub fn from_str(string: &str) -> Result { let whole_secs = num.trunc(); let nanos = (num.fract() * (NANOS_PER_SEC as f64)).trunc(); let duration = Duration::new(whole_secs as u64, nanos as u32); - Ok(duration * times) + Ok(duration.saturating_mul(times)) +} + +#[cfg(test)] +mod tests { + + use crate::parse_time::from_str; + use std::time::Duration; + + #[test] + fn test_no_units() { + assert_eq!(from_str("123"), Ok(Duration::from_secs(123))); + } + + #[test] + fn test_units() { + assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2))); + } + + #[test] + fn test_saturating_mul() { + assert_eq!(from_str("9223372036854775808d"), Ok(Duration::MAX)); + } + + #[test] + fn test_error_empty() { + assert!(from_str("").is_err()); + } + + #[test] + fn test_error_invalid_unit() { + assert!(from_str("123X").is_err()); + } + + #[test] + fn test_error_invalid_magnitude() { + assert!(from_str("12abc3s").is_err()); + } } diff --git a/tests/by-util/test_sleep.rs b/tests/by-util/test_sleep.rs index 94a0a6896..d33143ae0 100644 --- a/tests/by-util/test_sleep.rs +++ b/tests/by-util/test_sleep.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore dont use crate::common::util::*; use std::time::{Duration, Instant}; @@ -115,3 +116,28 @@ fn test_sleep_sum_duration_many() { fn test_sleep_wrong_time() { new_ucmd!().args(&["0.1s", "abc"]).fails(); } + +// TODO These tests would obviously block for a very long time. We +// only want to verify that there is no error here, so we could just +// figure out a way to terminate the child process after a short +// period of time. + +// #[test] +#[allow(dead_code)] +fn test_dont_overflow() { + new_ucmd!() + .arg("9223372036854775808d") + .succeeds() + .no_stderr() + .no_stdout(); +} + +// #[test] +#[allow(dead_code)] +fn test_sum_overflow() { + new_ucmd!() + .args(&["100000000000000d", "100000000000000d", "100000000000000d"]) + .succeeds() + .no_stderr() + .no_stdout(); +} diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 814074f3c..642cb7c68 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -605,19 +605,3 @@ fn test_line_bytes() { assert_eq!(at.read("xac"), "cccc\ndd\n"); assert_eq!(at.read("xad"), "ee\n"); } - -#[test] -fn test_round_robin() { - let (at, mut ucmd) = at_and_ucmd!(); - - let file_read = |f| { - let mut s = String::new(); - at.open(f).read_to_string(&mut s).unwrap(); - s - }; - - ucmd.args(&["-n", "r/2", "fivelines.txt"]).succeeds(); - - assert_eq!(file_read("xaa"), "1\n3\n5\n"); - assert_eq!(file_read("xab"), "2\n4\n"); -} diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index b9c8a59ed..96a5b6a05 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore dont use crate::common::util::*; // FIXME: this depends on the system having true and false in PATH @@ -64,3 +65,19 @@ fn test_preserve_status() { .no_stderr() .no_stdout(); } + +#[test] +fn test_dont_overflow() { + new_ucmd!() + .args(&["9223372036854775808d", "sleep", "0"]) + .succeeds() + .code_is(0) + .no_stderr() + .no_stdout(); + new_ucmd!() + .args(&["-k", "9223372036854775808d", "10", "sleep", "0"]) + .succeeds() + .code_is(0) + .no_stderr() + .no_stdout(); +}