From d22c7e14ece8791dec463e32d39d333e8d882a3d Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 9 Jan 2026 15:43:12 +0100 Subject: [PATCH] b2sum: introduce standalone binary --- Cargo.lock | 13 + Cargo.toml | 2 + GNUmakefile | 1 - build.rs | 1 - src/common/validation.rs | 2 +- src/uu/b2sum/Cargo.toml | 38 +++ src/uu/b2sum/LICENSE | 1 + src/uu/b2sum/locales/en-US.ftl | 2 + src/uu/b2sum/locales/fr-FR.ftl | 2 + src/uu/b2sum/src/b2sum.rs | 29 ++ src/uu/b2sum/src/main.rs | 1 + src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/locale.rs | 2 +- tests/by-util/test_b2sum.rs | 299 ++++++++++++++++++ tests/by-util/test_hashsum.rs | 11 +- .../{hashsum => b2sum}/b2sum.checkfile | 0 .../{hashsum => b2sum}/b2sum.expected | 0 tests/fixtures/b2sum/input.txt | 1 + tests/tests.rs | 4 + 19 files changed, 405 insertions(+), 6 deletions(-) create mode 100644 src/uu/b2sum/Cargo.toml create mode 120000 src/uu/b2sum/LICENSE create mode 100644 src/uu/b2sum/locales/en-US.ftl create mode 100644 src/uu/b2sum/locales/fr-FR.ftl create mode 100644 src/uu/b2sum/src/b2sum.rs create mode 100644 src/uu/b2sum/src/main.rs create mode 100644 tests/by-util/test_b2sum.rs rename tests/fixtures/{hashsum => b2sum}/b2sum.checkfile (100%) rename tests/fixtures/{hashsum => b2sum}/b2sum.expected (100%) create mode 100644 tests/fixtures/b2sum/input.txt diff --git a/Cargo.lock b/Cargo.lock index 49d209ab0..a1b35d585 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -556,6 +556,7 @@ dependencies = [ "unicode-width 0.2.2", "unindent", "uu_arch", + "uu_b2sum", "uu_base32", "uu_base64", "uu_basename", @@ -3172,6 +3173,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_b2sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_base32" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index f6354a644..429959b28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ feat_common_core = [ "basenc", "cat", "cksum", + "b2sum", "md5sum", "comm", "cp", @@ -438,6 +439,7 @@ chmod = { optional = true, version = "0.6.0", package = "uu_chmod", path = "src/ chown = { optional = true, version = "0.6.0", package = "uu_chown", path = "src/uu/chown" } chroot = { optional = true, version = "0.6.0", package = "uu_chroot", path = "src/uu/chroot" } cksum = { optional = true, version = "0.6.0", package = "uu_cksum", path = "src/uu/cksum" } +b2sum = { optional = true, version = "0.6.0", package = "uu_b2sum", path = "src/uu/b2sum" } md5sum = { optional = true, version = "0.6.0", package = "uu_md5sum", path = "src/uu/md5sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } diff --git a/GNUmakefile b/GNUmakefile index 243e038c4..b28ef8903 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -96,7 +96,6 @@ SELINUX_PROGS := \ runcon HASHSUM_PROGS := \ - b2sum \ sha1sum \ sha224sum \ sha256sum \ diff --git a/build.rs b/build.rs index 989f7630d..39a26a807 100644 --- a/build.rs +++ b/build.rs @@ -96,7 +96,6 @@ pub fn main() { phf_map.entry("sha256sum", map_value.clone()); phf_map.entry("sha384sum", map_value.clone()); phf_map.entry("sha512sum", map_value.clone()); - phf_map.entry("b2sum", map_value.clone()); } _ => { phf_map.entry(krate, map_value.clone()); diff --git a/src/common/validation.rs b/src/common/validation.rs index d332d304f..773505bed 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -51,7 +51,7 @@ fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => "hashsum", + "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uu/b2sum/Cargo.toml b/src/uu/b2sum/Cargo.toml new file mode 100644 index 000000000..61b0b702a --- /dev/null +++ b/src/uu/b2sum/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "uu_b2sum" +description = "b2sum ~ (uutils) Print or check the BLAKE2b checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/b2sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/b2sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "b2sum" +path = "src/main.rs" diff --git a/src/uu/b2sum/LICENSE b/src/uu/b2sum/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/b2sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/b2sum/locales/en-US.ftl b/src/uu/b2sum/locales/en-US.ftl new file mode 100644 index 000000000..a5ab9ea7e --- /dev/null +++ b/src/uu/b2sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +b2sum-about = Print or check the BLAKE2b checksums +b2sum-usage = b2sum [OPTIONS] [FILE]... diff --git a/src/uu/b2sum/locales/fr-FR.ftl b/src/uu/b2sum/locales/fr-FR.ftl new file mode 100644 index 000000000..7cb93e5d8 --- /dev/null +++ b/src/uu/b2sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +b2sum-about = Afficher le BLAKE2b et la taille de chaque fichier +b2sum-usage = b2sum [OPTION]... [FICHIER]... diff --git a/src/uu/b2sum/src/b2sum.rs b/src/uu/b2sum/src/b2sum.rs new file mode 100644 index 000000000..502bd8b53 --- /dev/null +++ b/src/uu/b2sum/src/b2sum.rs @@ -0,0 +1,29 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +// spell-checker:ignore (ToDO) algo + +use clap::Command; + +use uu_checksum_common::{standalone_checksum_app_with_length, standalone_with_length_main}; + +use uucore::checksum::{AlgoKind, calculate_blake2b_length_str}; +use uucore::error::UResult; +use uucore::translate; + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + standalone_with_length_main( + AlgoKind::Blake2b, + uu_app(), + args, + calculate_blake2b_length_str, + ) +} + +#[inline] +pub fn uu_app() -> Command { + standalone_checksum_app_with_length(translate!("b2sum-about"), translate!("b2sum-usage")) +} diff --git a/src/uu/b2sum/src/main.rs b/src/uu/b2sum/src/main.rs new file mode 100644 index 000000000..422fa2fe7 --- /dev/null +++ b/src/uu/b2sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_b2sum); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index effefbc06..d793867c5 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -173,7 +173,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => "hashsum", + "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 19b9be9f1..6036501f4 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -157,7 +157,7 @@ fn create_bundle( try_add_resource_from(get_locales_dir(util_name).ok()); // checksum binaries also require fluent files from the checksum_common crate - if ["cksum", "md5sum"].contains(&util_name) { + if ["cksum", "b2sum", "md5sum"].contains(&util_name) { try_add_resource_from(get_locales_dir("checksum_common").ok()); } diff --git a/tests/by-util/test_b2sum.rs b/tests/by-util/test_b2sum.rs new file mode 100644 index 000000000..30e2c46ca --- /dev/null +++ b/tests/by-util/test_b2sum.rs @@ -0,0 +1,299 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use rstest::rstest; + +use uutests::new_ucmd; +use uutests::util::TestScenario; +use uutests::util_name; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest_with_len { + ($id:ident, $size:expr) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static LENGTH_ARG: &'static str = concat!("--length=", stringify!($size)); + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(LENGTH_ARG) + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(LENGTH_ARG) + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&[LENGTH_ARG, "--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(LENGTH_ARG) + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&[LENGTH_ARG, "a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} + +test_digest_with_len! {b2sum, 512} + +#[test] +fn test_check_b2sum_length_option_0() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write("testf.b2sum", "9e2bf63e933e610efee4a8d6cd4a9387e80860edee97e27db3b37a828d226ab1eb92a9cdd8ca9ca67a753edaf8bd89a0558496f67a30af6f766943839acf0110 testf\n"); + + scene + .ccmd("b2sum") + .arg("--length=0") + .arg("-c") + .arg(at.subdir.join("testf.b2sum")) + .succeeds() + .stdout_only("testf: OK\n"); +} + +#[test] +fn test_check_b2sum_length_duplicate() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + + scene + .ccmd("b2sum") + .arg("--length=123") + .arg("--length=128") + .arg("testf") + .succeeds() + .stdout_contains("d6d45901dec53e65d2b55fb6e2ab67b0"); +} + +#[test] +fn test_check_b2sum_length_option_8() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write("testf.b2sum", "6a testf\n"); + + scene + .ccmd("b2sum") + .arg("--length=8") + .arg("-c") + .arg(at.subdir.join("testf.b2sum")) + .succeeds() + .stdout_only("testf: OK\n"); +} + +#[test] +fn test_invalid_b2sum_length_option_not_multiple_of_8() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + + scene + .ccmd("b2sum") + .arg("--length=9") + .arg(at.subdir.join("testf")) + .fails_with_code(1) + .stderr_contains("b2sum: invalid length: '9'") + .stderr_contains("b2sum: length is not a multiple of 8"); +} + +#[rstest] +#[case("513")] +#[case("1024")] +#[case("18446744073709552000")] +fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + + scene + .ccmd("b2sum") + .arg("--length") + .arg(len) + .arg(at.subdir.join("testf")) + .fails_with_code(1) + .no_stdout() + .stderr_contains(format!("b2sum: invalid length: '{len}'")) + .stderr_contains("b2sum: maximum digest length for 'BLAKE2b' is 512 bits"); +} + +#[test] +fn test_check_b2sum_tag_output() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + + scene + .ccmd("b2sum") + .arg("--length=0") + .arg("--tag") + .arg("f") + .succeeds() + .stdout_only("BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n"); + + scene + .ccmd("b2sum") + .arg("--length=128") + .arg("--tag") + .arg("f") + .succeeds() + .stdout_only("BLAKE2b-128 (f) = cae66941d9efbd404e4d88758ea67670\n"); +} + +#[test] +fn test_check_b2sum_verify() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("a", "a\n"); + + scene + .ccmd("b2sum") + .arg("--tag") + .arg("a") + .succeeds() + .stdout_only("BLAKE2b (a) = bedfbb90d858c2d67b7ee8f7523be3d3b54004ef9e4f02f2ad79a1d05bfdfe49b81e3c92ebf99b504102b6bf003fa342587f5b3124c205f55204e8c4b4ce7d7c\n"); + + scene + .ccmd("b2sum") + .arg("--tag") + .arg("-l") + .arg("128") + .arg("a") + .succeeds() + .stdout_only("BLAKE2b-128 (a) = b93e0fc7bb21633c08bba07c5e71dc00\n"); +} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_check_b2sum_strict_check() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("f"); + + let checksums = [ + "2e f\n", + "e4a6a0577479b2b4 f\n", + "cae66941d9efbd404e4d88758ea67670 f\n", + "246c0442cd564aced8145b8b60f1370aa7 f\n", + "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8 f\n", + "4ded8c5fc8b12f3273f877ca585a44ad6503249a2b345d6d9c0e67d85bcb700db4178c0303e93b8f4ad758b8e2c9fd8b3d0c28e585f1928334bb77d36782e8 f\n", + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce f\n", + ]; + + at.write("ck", &checksums.join("")); + + let output = "f: OK\n".to_string().repeat(checksums.len()); + + scene + .ccmd("b2sum") + .arg("-c") + .arg(at.subdir.join("ck")) + .succeeds() + .stdout_only(&output); + + scene + .ccmd("b2sum") + .arg("--strict") + .arg("-c") + .arg(at.subdir.join("ck")) + .succeeds() + .stdout_only(&output); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test that help output shows the actual utility name instead of "hashsum" + let scene = TestScenario::new(util_name!()); + + // Test b2sum + scene + .ccmd("b2sum") + .arg("--help") + .succeeds() + .stdout_contains("Usage: b2sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 2022f81b8..26125dc5e 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -214,7 +214,6 @@ test_digest_with_len! {sha3_224, sha3, 224} test_digest_with_len! {sha3_256, sha3, 256} test_digest_with_len! {sha3_384, sha3, 384} test_digest_with_len! {sha3_512, sha3, 512} -test_digest_with_len! {b2sum, b2sum, 512} #[test] fn test_check_sha1() { @@ -271,6 +270,7 @@ fn test_check_md5_ignore_missing() { .stderr_contains("the --ignore-missing option is meaningful only when verifying checksums"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_length_option_0() { let scene = TestScenario::new(util_name!()); @@ -288,6 +288,7 @@ fn test_check_b2sum_length_option_0() { .stdout_only("testf: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_length_duplicate() { let scene = TestScenario::new(util_name!()); @@ -304,6 +305,7 @@ fn test_check_b2sum_length_duplicate() { .stdout_contains("d6d45901dec53e65d2b55fb6e2ab67b0"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_length_option_8() { let scene = TestScenario::new(util_name!()); @@ -321,6 +323,7 @@ fn test_check_b2sum_length_option_8() { .stdout_only("testf: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_invalid_b2sum_length_option_not_multiple_of_8() { let scene = TestScenario::new(util_name!()); @@ -338,8 +341,11 @@ fn test_invalid_b2sum_length_option_not_multiple_of_8() { } #[rstest] +#[ignore = "moved to standalone"] #[case("513")] +#[ignore = "moved to standalone"] #[case("1024")] +#[ignore = "moved to standalone"] #[case("18446744073709552000")] fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) { let scene = TestScenario::new(util_name!()); @@ -358,6 +364,7 @@ fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) { .stderr_contains("b2sum: maximum digest length for 'BLAKE2b' is 512 bits"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_tag_output() { let scene = TestScenario::new(util_name!()); @@ -382,6 +389,7 @@ fn test_check_b2sum_tag_output() { .stdout_only("BLAKE2b-128 (f) = cae66941d9efbd404e4d88758ea67670\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_verify() { let scene = TestScenario::new(util_name!()); @@ -1068,6 +1076,7 @@ fn test_star_to_start() { .stdout_only("f: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_strict_check() { let scene = TestScenario::new(util_name!()); diff --git a/tests/fixtures/hashsum/b2sum.checkfile b/tests/fixtures/b2sum/b2sum.checkfile similarity index 100% rename from tests/fixtures/hashsum/b2sum.checkfile rename to tests/fixtures/b2sum/b2sum.checkfile diff --git a/tests/fixtures/hashsum/b2sum.expected b/tests/fixtures/b2sum/b2sum.expected similarity index 100% rename from tests/fixtures/hashsum/b2sum.expected rename to tests/fixtures/b2sum/b2sum.expected diff --git a/tests/fixtures/b2sum/input.txt b/tests/fixtures/b2sum/input.txt new file mode 100644 index 000000000..8c01d89ae --- /dev/null +++ b/tests/fixtures/b2sum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/tests/tests.rs b/tests/tests.rs index 1b2a2131b..78225ac97 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -68,6 +68,10 @@ mod test_cksum; #[path = "by-util/test_comm.rs"] mod test_comm; +#[cfg(feature = "b2sum")] +#[path = "by-util/test_b2sum.rs"] +mod test_b2sum; + #[cfg(feature = "md5sum")] #[path = "by-util/test_md5sum.rs"] mod test_md5sum;