Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ clap = { version = "4.0", features = ["cargo", "derive"] }
serde = { version = "1.0" }
serde_json = { version = "1.0" }
tabled = { version = "0.20" }
stdlib = { path = "./stdlib" }
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This began as, and continues to be, a learning exercise to better understand the
| util | status |
| ---- | ------ |
| arch | :white_check_mark: |
| b2sum | :white_large_square: |
| b2sum | :white_check_mark: |
| base32 | :white_large_square: |
| base64 | :white_large_square: |
| basename | :white_large_square: |
Expand Down
2 changes: 1 addition & 1 deletion arch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ platform-info = "1.0.1"
clap = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
stdlib = { path = "../stdlib" }
stdlib = { workspace = true }
tabled = { workspace = true }
7 changes: 5 additions & 2 deletions arch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ use platform_info::*;
use serde_json::json;
use tabled::{builder::Builder, settings::Style};

use stdlib::clap_base_command;
use stdlib::{clap_args, clap_base_command};

clap_args!(Args {});

fn main() {
let matches = clap_base_command().get_matches();
let args = Args::from_matches(&matches);

let arch = run();

if let Some(output) = matches.get_one::<String>("output") {
if let Some(output) = args.output {
match output.as_str() {
"table" => {
let mut builder = Builder::new();
Expand Down
6 changes: 5 additions & 1 deletion b2sum/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
[package]
name = "b2sum"
version = "0.1.0"
edition = "2021"
edition = "2024"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
blake2 = "0.10.4"
clap = { workspace = true }
shellexpand = "2.1.2"
stdlib = { workspace = true }
tabled = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
225 changes: 127 additions & 98 deletions b2sum/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,118 +1,117 @@
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::io::ErrorKind;
use std::io::Write;
use std::io::prelude::*;
use std::process;

use blake2::{Blake2b512, Digest};
use clap::Parser;
// use exitcode;

/// Print or check BLAKE2 (512-bit) checksums.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
// // There's no difference between --binary and --text on GNU systems, so I'm not
// // sure how to implement and test this.
// /// read in binary mode
// #[arg(short, long)]
// binary: bool,
/// read BLAKE2 sums from the FILEs and check them
#[arg(short, long)]
check: bool,

files: Vec<String>,

/// don't fail or report status for missing files
#[arg(long)]
ignore_missing: bool,

/// digest length in bits; must not exceed the maximum for the blake2 algorithm and must be a multiple of 8
#[arg(short, long, default_value_t = 128)]
length: i32,

/// don't print OK for each successfully verified file
#[arg(long)]
quiet: bool,

/// don't output anything, status code shows success
#[arg(long)]
status: bool,

/// exit non-zero for improperly formatted checksum lines
#[arg(long)]
strict: bool,

/// create a BSD-style checksum
#[arg(long)]
tag: bool,

// /// read in text mode (default)
// #[arg(short, long, default_value_t = true)]
// text: bool,
/// warn about improperly formatted files
#[arg(short, long)]
warn: bool,

/// end each output line with NUL, not newline, and disable file name escaping
#[arg(short, long)]
zero: bool,
}
use clap::{Arg, ArgAction, arg};
// use serde_json::{Map, Value, json};
use tabled::{builder::Builder, settings::Style};

use stdlib::{clap_args, clap_base_command};

clap_args!(Args {
flag check: bool,
flag ignore_missing: bool,
value(128) length: i32,
flag quiet: bool,
flag status: bool,
flag strict: bool,
flag tag: bool,
flag warn: bool,
flag zero: bool,
multi files: Vec<String>,
});

struct B2Hash {
filename: String,
hash: String,
}

fn main() {
let matches = clap_base_command()
.arg(arg!(-c --check "read BLAKE2 sums from the FILEs and check them"))
.arg(Arg::new("files").action(ArgAction::Append))
.arg(Arg::new("ignore_missing").long("ignore-missing").action(ArgAction::SetTrue).help("ignore missing files"))
.arg(arg!(-l --length <LENGTH> "output LENGTH characters of each checksum"))
.arg(arg!(--quiet "quiet mode, don't print OK for each successfully verified file"))
.arg(arg!(--status "output status only, don't print OK for each successfully verified file"))
.arg(arg!(--strict "strict mode, fail if any file fails to verify"))
.arg(arg!(--tag "output a BSD-style checksum"))
.arg(arg!(-w --warn "warn about improperly formatted files"))
.arg(arg!(-z --zero "end each output line with NUL, not newline, and disable file name escaping"))
.get_matches();

let mut retcode = 0;
let args = Args::parse();
let args = Args::from_matches(&matches);

if args.check {
retcode = check(&args);
} else {
let checksums = run(&args);

// Collapse the checksums down into a single HashMap
let mut map = HashMap::new();

for checksum in checksums {
if args.length == 0 {
output_hash(&args, checksum.hash, checksum.filename);
map.insert(checksum.filename, checksum.hash);
} else if args.length % 8 == 0 {
// length must be a multiple of 8
if checksum.hash.is_empty() {
continue;
}
let slice = &checksum.hash[..args.length as usize];

output_hash(&args, slice.to_string(), checksum.filename);
map.insert(checksum.filename, slice.to_string());
} else {
output(
&args,
map.insert(
checksum.filename,
format!("length ({}) is not a multiple of 8", args.length),
);
}
}
}
process::exit(retcode);
}

/// Print the output of a successful hash
fn output_hash(args: &Args, hash: String, filename: String) {
if args.tag {
output(
args,
format!("BLAKE2b-{} ({}) = {}", args.length, filename, hash),
);
} else {
output(args, format!("{} {}", hash, filename));
}
}
// Output the hashes
if let Some(output) = &args.output {
match output.as_str() {
"table" => {
let mut builder = Builder::new();
builder.push_column(["Hash"]);
builder.push_column(["File"]);

/// Output the line with either a newline or NUL
fn output(args: &Args, line: String) {
if args.zero {
print!("{}\0", line);
io::stdout().flush().unwrap();
} else {
println!("{}", line);
for file in map {
builder.push_record([file.1, file.0]);
}
let mut table = builder.build();
println!("{}", table.with(Style::rounded()));
}
"json" => {
println!("{}", serde_json::to_string(&map).unwrap());
}
"yaml" => {
println!("files:");
for file in map {
println!(" - file: \"{}\"\n hash: \"{}\"", file.0, file.1);
}
}
_ => {
for file in map {
if args.zero {
print!("{} {}\0", file.1, file.0);
io::stdout().flush().unwrap();
} else {
println!("{} {}", file.1, file.0);
}
}
}
}
}
}
process::exit(retcode);
}

/// Perform the checksum validation
Expand All @@ -129,6 +128,8 @@ fn check(args: &Args) -> i32 {
let mut retval = 0;
let mut failed = 0;

let mut map = HashMap::new();

for filename in &args.files {
let file = match File::open(filename) {
Err(why) => panic!("couldn't open: {}", why),
Expand Down Expand Up @@ -166,7 +167,8 @@ fn check(args: &Args) -> i32 {
buf.clear();
continue;
}
panic!("Invalid file format.");
println!("Invalid file format.");
return retval;
}

let hash2 = match b2sum_file(fname.to_string()) {
Expand All @@ -176,39 +178,65 @@ fn check(args: &Args) -> i32 {
buf.clear();
continue;
} else {
output(args, format!("b2sum: {}: {}", fname, why));
map.insert(fname.to_string(), why.to_string());
}
"".to_string()
}
Ok(hash) => hash,
};

// TODO: return this information to the caller, so main() can
// process it and handle returning the right error code.
if hash == hash2 {
if !args.quiet && !args.status {
output(args, format!("{}: OK", fname));
}
map.insert(fname.to_string(), "OK".to_string());
} else {
if !args.quiet && !args.status {
output(args, format!("{}: FAILED", fname));
}

map.insert(fname.to_string(), "FAILED".to_string());
failed += 1;
}

// clear the buffer for the next read
buf.clear();
}
}

if !args.quiet
&& !args.status
&& let Some(output) = &args.output
{
match output.as_str() {
"table" => {
let mut builder = Builder::new();
builder.push_column(["Status"]);
builder.push_column(["File"]);

for file in map {
builder.push_record([file.1, file.0]);
}
let mut table = builder.build();
println!("{}", table.with(Style::rounded()));
}
"json" => {
println!("{}", serde_json::to_string(&map).unwrap());
}
"yaml" => {
println!("files:");
for file in map {
println!(" - file: \"{}\"\n status: \"{}\"", file.0, file.1);
}
}
_ => {
for file in map {
if args.zero {
print!("{} {}\0", file.1, file.0);
io::stdout().flush().unwrap();
} else {
println!("{} {}", file.1, file.0);
}
}
}
}
}

if failed > 0 {
retval = 1;
if !args.status {
output(
args,
format!("b2sum: WARNING: {} computed checksum did NOT match", failed),
);
}
}
retval
}
Expand All @@ -225,7 +253,8 @@ fn run(args: &Args) -> Vec<B2Hash> {
// skip this file
continue;
} else {
output(args, format!("b2sum: {}: {}", filename, why));
// TODO: figure out a better way to surface this via the output format?
println!("b2sum: {}: {}", filename, why);
}
"".to_string()
}
Expand Down
3 changes: 3 additions & 0 deletions data/test.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
91beee108359196458cf821584c1259100e6b01e0c5b1099db8a733ff01c238cbe2d038d3a088058123bc012fbce9feb395241ddab5b907b192e005d2048f2ac data/test.data
9c4fb6a525a103b341d73e707090e6bedcbc611b064b7327941b84ea80ab3ab80fde7a55b4d0255e615756326695d3020460d78518f4c2f4192739a464bf5336 data/test1.data
2475acb0eb9b5c8fc6d51f4134aafea2c20f46abcd970e222a5952c7061bd6bee0b74d9e10fde8dc87cd6839bea49569ece307d786c7d16c5cc91ddec632a313 data/test2.data
3 changes: 3 additions & 0 deletions data/test.check-bad
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a1beee108359196458cf821584c1259100e6b01e0c5b1099db8a733ff01c238cbe2d038d3a088058123bc012fbce9feb395241ddab5b907b192e005d2048f2ac data/test.data
9c4fb6a525a103b341d73e707090e6bedcbc611b064b7327941b84ea80ab3ab80fde7a55b4d0255e615756326695d3020460d78518f4c2f4192739a464bf5335 data/test1.data
2475acb0eb9b5c8fc6d51f4134aafea2c20f46abcd970e222a5952c7061bd6bee0b74d9e10fde8dc87cd6839bea49569ece307d786c7d16c5cc91ddec632a313 data/test2.data
3 changes: 3 additions & 0 deletions data/test.check-invalid
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
91beee108359196458cf821584c1259100e6b01e0c5b1099db8a733ff01c238cbe2d038d3a088058123bc012fbce9feb395241ddab5b907b192e005d2048f2ac data/test.data
9c4fb6a525a103b341d73e707090e6bedcbc611b064b7327941b84ea80ab3ab80fde7a55b4d0255e615756326695d3020460d78518f4c2f4192739a464bf5336 bad data/test1.data
2475acb0eb9b5c8fc6d51f4134aafea2c20f46abcd970e222a5952c7061bd6bee0b74d9e10fde8dc87cd6839bea49569ece307d786c7d16c5cc91ddec632a313 data/test2.data
1 change: 1 addition & 0 deletions data/test.data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello, world
1 change: 1 addition & 0 deletions data/test1.data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Another random string
1 change: 1 addition & 0 deletions data/test2.data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Blah blah blah
Loading
Loading