diff --git a/Cargo.lock b/Cargo.lock index bbaf1bf..8b94988 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.3" @@ -30,15 +39,21 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.1.1" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ "bitflags", "clap_lex", @@ -49,18 +64,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.1.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8955d4e8cd4f28f9a01c93a050194c4d131e73ca02f6636bcddbed867014d7" +checksum = "3d6540eedc41f8a5a76cf3d8d458057dcdf817be4158a55b5f861f7a5483de75" dependencies = [ "clap", ] [[package]] name = "clap_complete_nushell" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ceeff3734be0ba9e15998f1f1a487cd2928b31444209e8fcd92b30efac13a" +checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c" dependencies = [ "clap", "clap_complete", @@ -85,6 +100,15 @@ dependencies = [ "roff", ] +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -109,6 +133,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -153,18 +178,15 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01" [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ "libc", "windows-sys", @@ -172,11 +194,11 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.0", "io-lifetimes", "rustix", "windows-sys", @@ -217,9 +239,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rustix" -version = "0.36.6" +version = "0.36.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags", "errno", @@ -244,19 +266,45 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "010e18bd3bfd1d45a7e666b236c78720df0d9a7698ebaa9c1c559961eb60a38b" +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shitbox" version = "0.1.0" dependencies = [ "atty", + "blake2", "clap", "clap_complete", "clap_complete_nushell", "clap_mangen", "data-encoding", + "digest", "libc", "md-5", "sc", + "sha1", + "sha2", "termcolor", "textwrap", "walkdir", @@ -274,6 +322,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "termcolor" version = "1.2.0" @@ -348,9 +402,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", diff --git a/Cargo.toml b/Cargo.toml index 68492eb..a12454e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,21 @@ edition = "2021" [dependencies] atty = "0.2" +blake2 = "0.10" clap = "4.1" -clap_complete = "4.0" +clap_complete = "4.1" clap_complete_nushell = "0.1" clap_mangen = "0.2" data-encoding = "2.3" +digest = "0.10.6" libc = "0.2" -md-5 = "0.10.5" +md-5 = "0.10" sc = "0.2" +sha1 = "0.10" +sha2 = "0.10" termcolor = "1.1" textwrap = { version = "0.16", default-features = false, features = ["smawk"] } -walkdir = "2.3.2" +walkdir = "2.3" [profile.release] codegen-units = 1 diff --git a/README.md b/README.md index 3dc16b6..97b0640 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ code between applets, making for an overall smaller binary. - md5sum - mkfifo - mknod +- mktemp - mountpoint - nologin - nproc diff --git a/src/args/mod.rs b/src/args/mod.rs index 338905a..3ff43b0 100644 --- a/src/args/mod.rs +++ b/src/args/mod.rs @@ -44,3 +44,18 @@ pub fn color() -> Arg { .long("color") .value_parser(["always", "ansi", "auto", "never"]) } + +pub fn check() -> Arg { + Arg::new("check") + .help("read checksums from the FILEs and check them") + .short('c') + .long("check") + .action(ArgAction::SetTrue) +} + +pub fn file() -> Arg { + Arg::new("file") + .value_name("FILE") + .num_args(1..) + .default_value("-") +} diff --git a/src/cmd/md5sum/mod.rs b/src/cmd/md5sum/mod.rs index 5e6e706..7175933 100644 --- a/src/cmd/md5sum/mod.rs +++ b/src/cmd/md5sum/mod.rs @@ -1,12 +1,11 @@ use super::Cmd; -use clap::{Arg, ArgAction, Command}; -use md5::{Digest, Md5}; -use std::{ - fmt::Write, - fs::File, - io::{self, BufRead, BufReader, Read}, - process, error::Error, +use crate::{ + args, + hash::{self, HashType}, }; +use clap::Command; +use md5::{Digest, Md5}; +use std::{io, process}; #[derive(Debug, Default)] pub struct Md5sum; @@ -17,17 +16,7 @@ impl Cmd for Md5sum { .about("compute and check MD5 message digest") .author("Nathan Fisher") .version(env!("CARGO_PKG_VERSION")) - .args([ - Arg::new("check") - .help("read checksums from the FILEs and check them") - .short('c') - .long("check") - .action(ArgAction::SetTrue), - Arg::new("file") - .value_name("FILE") - .num_args(1..) - .default_value("-"), - ]) + .args([args::check(), args::file()]) } fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { @@ -43,49 +32,11 @@ impl Cmd for Md5sum { io::Error::new(io::ErrorKind::Other, "no file specified").into() ); } - let fd = File::open(f)?; - let reader = BufReader::new(fd); - for line in reader.lines() { - let line = line?; - let mut split = line.split_whitespace(); - let sum = split.next().ok_or::>( - io::Error::new(io::ErrorKind::Other, "invalid checksum file").into(), - )?; - let file = split.next().ok_or::>( - io::Error::new(io::ErrorKind::Other, "invalid checksum file").into(), - )?; - let mut hasher = Md5::new(); - let mut buf = vec![]; - let mut fd = File::open(file)?; - let _s = fd.read_to_end(&mut buf)?; - hasher.update(&buf); - let res = hasher.finalize(); - let mut s = String::new(); - for c in res { - write!(s, "{c:x}")?; - } - if s.as_str() == sum { - println!("{file}: OK"); - } else { - println!("{file}: FAILED"); - erred += 1; - } - } + hash::check_sums(f, HashType::Md5, &mut erred)?; } else { - let mut hasher = Md5::new(); - let mut buf = vec![]; - if f == "-" { - let _s = io::stdin().read_to_end(&mut buf)?; - } else { - let mut fd = File::open(f)?; - let _s = fd.read_to_end(&mut buf)?; - } - hasher.update(&buf); - let res = hasher.finalize(); - for c in res { - print!("{c:x}"); - } - println!(" {f}"); + let hasher = Md5::new(); + let s = hash::compute_hash(f, hasher)?; + println!("{s} {f}"); } } if erred > 0 { diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index d386533..252900e 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -44,6 +44,9 @@ mod realpath; mod rev; mod rm; mod rmdir; +mod sha1sum; +mod sha224sum; +mod sha256sum; mod shitbox; mod sleep; mod sync; @@ -108,6 +111,9 @@ pub fn get(name: &str) -> Option> { "rev" => Some(Box::new(rev::Rev::default())), "rm" => Some(Box::new(rm::Rm::default())), "rmdir" => Some(Box::new(rmdir::Rmdir::default())), + "sha1sum" => Some(Box::new(sha1sum::Sha1sum::default())), + "sha224sum" => Some(Box::new(sha224sum::Sha224sum::default())), + "sha256sum" => Some(Box::new(sha256sum::Sha256sum::default())), "shitbox" => Some(Box::new(shitbox::Shitbox::default())), "sleep" => Some(Box::new(sleep::Sleep::default())), "sync" => Some(Box::new(sync::Sync::default())), @@ -121,7 +127,7 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&str; 44] = [ +pub static COMMANDS: [&str; 47] = [ "base32", "base64", "basename", @@ -157,8 +163,11 @@ pub static COMMANDS: [&str; 44] = [ "rev", "rm", "rmdir", - "sleep", + "sha1sum", + "sha224sum", + "sha256sum", "shitbox", + "sleep", "sync", "true", "unlink", diff --git a/src/cmd/sha1sum/mod.rs b/src/cmd/sha1sum/mod.rs new file mode 100644 index 0000000..060bfa1 --- /dev/null +++ b/src/cmd/sha1sum/mod.rs @@ -0,0 +1,53 @@ +use super::Cmd; +use crate::{ + args, + hash::{self, HashType}, +}; +use clap::Command; +use sha1::{Digest, Sha1}; +use std::{io, process}; + +#[derive(Debug, Default)] +pub struct Sha1sum; + +impl Cmd for Sha1sum { + fn cli(&self) -> clap::Command { + Command::new("sha1sum") + .about("compute and check SHA1 message digest") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([args::check(), args::file()]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(io::Error::new(io::ErrorKind::Other, "no input").into()); + }; + if let Some(files) = matches.get_many::("file") { + let mut erred = 0; + for f in files { + if matches.get_flag("check") { + if f == "-" { + return Err( + io::Error::new(io::ErrorKind::Other, "no file specified").into() + ); + } + hash::check_sums(f, HashType::Sha1, &mut erred)?; + } else { + let hasher = Sha1::new(); + let s = hash::compute_hash(f, hasher)?; + println!("{s} {f}"); + } + } + if erred > 0 { + println!("sha1sum: WARNING: {erred} computed checksum did NOT match"); + process::exit(1); + } + } + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} diff --git a/src/cmd/sha224sum/mod.rs b/src/cmd/sha224sum/mod.rs new file mode 100644 index 0000000..69aaefa --- /dev/null +++ b/src/cmd/sha224sum/mod.rs @@ -0,0 +1,53 @@ +use super::Cmd; +use crate::{ + args, + hash::{self, HashType}, +}; +use clap::Command; +use sha2::{Digest, Sha224}; +use std::{io, process}; + +#[derive(Debug, Default)] +pub struct Sha224sum; + +impl Cmd for Sha224sum { + fn cli(&self) -> clap::Command { + Command::new("sha224sum") + .about("compute and check SHA1 message digest") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([args::check(), args::file()]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(io::Error::new(io::ErrorKind::Other, "no input").into()); + }; + if let Some(files) = matches.get_many::("file") { + let mut erred = 0; + for f in files { + if matches.get_flag("check") { + if f == "-" { + return Err( + io::Error::new(io::ErrorKind::Other, "no file specified").into() + ); + } + hash::check_sums(f, HashType::Sha224, &mut erred)?; + } else { + let hasher = Sha224::new(); + let s = hash::compute_hash(f, hasher)?; + println!("{s} {f}"); + } + } + if erred > 0 { + println!("sha224sum: WARNING: {erred} computed checksum did NOT match"); + process::exit(1); + } + } + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} diff --git a/src/cmd/sha256sum/mod.rs b/src/cmd/sha256sum/mod.rs new file mode 100644 index 0000000..cdbee95 --- /dev/null +++ b/src/cmd/sha256sum/mod.rs @@ -0,0 +1,53 @@ +use super::Cmd; +use crate::{ + args, + hash::{self, HashType}, +}; +use clap::Command; +use sha2::{Digest, Sha256}; +use std::{io, process}; + +#[derive(Debug, Default)] +pub struct Sha224sum; + +impl Cmd for Sha224sum { + fn cli(&self) -> clap::Command { + Command::new("sha256sum") + .about("compute and check SHA1 message digest") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([args::check(), args::file()]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(io::Error::new(io::ErrorKind::Other, "no input").into()); + }; + if let Some(files) = matches.get_many::("file") { + let mut erred = 0; + for f in files { + if matches.get_flag("check") { + if f == "-" { + return Err( + io::Error::new(io::ErrorKind::Other, "no file specified").into() + ); + } + hash::check_sums(f, HashType::Sha256, &mut erred)?; + } else { + let hasher = Sha256::new(); + let s = hash::compute_hash(f, hasher)?; + println!("{s} {f}"); + } + } + if erred > 0 { + println!("sha256sum: WARNING: {erred} computed checksum did NOT match"); + process::exit(1); + } + } + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::UsrBin) + } +} diff --git a/src/hash/mod.rs b/src/hash/mod.rs new file mode 100644 index 0000000..61fad88 --- /dev/null +++ b/src/hash/mod.rs @@ -0,0 +1,73 @@ +use { + digest::{Digest, FixedOutput, HashMarker}, + md5::Md5, + sha1::Sha1, + sha2::{Sha224, Sha256, Sha384, Sha512}, + std::{ + error::Error, + fmt::Write, + fs::File, + io::{self, BufRead, BufReader, Read}, + }, +}; + +pub enum HashType { + Blake2b, + Md5, + Sha1, + Sha224, + Sha256, + Sha384, + Sha512, +} + +pub fn compute_hash(file: &str, mut hasher: T) -> Result> +where + T: Default + FixedOutput + HashMarker, +{ + let mut buf = vec![]; + if file == "-" { + let _s = io::stdin().read_to_end(&mut buf)?; + } else { + let mut fd = File::open(file)?; + let _s = fd.read_to_end(&mut buf)?; + } + let mut s = String::new(); + hasher.update(&buf); + let res = hasher.finalize(); + for c in res { + write!(s, "{c:02x}")?; + } + Ok(s) +} + +pub fn check_sums(file: &str, hashtype: HashType, erred: &mut usize) -> Result<(), Box> { + let fd = File::open(file)?; + let reader = BufReader::new(fd); + for line in reader.lines() { + let line = line?; + let mut split = line.split_whitespace(); + let sum = split.next().ok_or::( + io::Error::new(io::ErrorKind::Other, "invalid checksum file").into(), + )?; + let file = split.next().ok_or::( + io::Error::new(io::ErrorKind::Other, "invalid checksum file").into(), + )?; + let s = match hashtype { + HashType::Blake2b => unimplemented!(), + HashType::Md5 => compute_hash(file, Md5::new())?, + HashType::Sha1 => compute_hash(file, Sha1::new())?, + HashType::Sha224 => compute_hash(file, Sha224::new())?, + HashType::Sha256 => compute_hash(file, Sha256::new())?, + HashType::Sha384 => compute_hash(file, Sha384::new())?, + HashType::Sha512 => compute_hash(file, Sha512::new())?, + }; + if s.as_str() == sum { + println!("{file}: OK"); + } else { + println!("{file}: FAILED"); + *erred += 1; + } + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 86d94e6..c4d7a20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod args; pub mod bitflags; mod cmd; pub mod fs; +pub mod hash; pub mod math; pub mod mode; pub mod pw;