diff --git a/Makefile b/Makefile index 4cc0e53e2..088b03d7d 100644 --- a/Makefile +++ b/Makefile @@ -167,6 +167,7 @@ TEST_PROGS := \ factor \ false \ fold \ + hashsum \ mkdir \ mv \ nl \ diff --git a/src/hashsum/hashsum.rs b/src/hashsum/hashsum.rs index 0fcf2ded4..9759c384f 100644 --- a/src/hashsum/hashsum.rs +++ b/src/hashsum/hashsum.rs @@ -1,5 +1,5 @@ #![crate_name = "hashsum"] -#![feature(collections, core, old_io, old_path, rustc_private)] +#![feature(rustc_private)] /* * This file is part of the uutils coreutils package. @@ -17,17 +17,15 @@ extern crate regex; extern crate crypto; extern crate getopts; -use std::ascii::AsciiExt; -use std::old_io::fs::File; -use std::old_io::stdio::stdin_raw; -use std::old_io::BufferedReader; -use std::old_io::IoError; -use std::old_io::EndOfFile; -use regex::Regex; use crypto::digest::Digest; use crypto::md5::Md5; use crypto::sha1::Sha1; use crypto::sha2::{Sha224, Sha256, Sha384, Sha512}; +use regex::Regex; +use std::ascii::AsciiExt; +use std::fs::File; +use std::io::{self, BufRead, BufReader, Read, stdin, Write}; +use std::path::Path; #[path = "../common/util.rs"] #[macro_use] @@ -91,18 +89,17 @@ fn detect_algo(program: &str, matches: &getopts::Matches) -> (&'static str, Box< } pub fn uumain(args: Vec) -> i32 { - let program = args[0].clone(); - let binary = Path::new(program.as_slice()); - let binary_name = binary.filename_str().unwrap(); + let program = &args[0]; + let binary_name = Path::new(program).file_name().unwrap().to_str().unwrap(); // Default binary in Windows, text mode otherwise let binary_flag_default = cfg!(windows); let mut opts: Vec = vec!( - getopts::optflag("b", "binary", format!("read in binary mode{}", if binary_flag_default { " (default)" } else { "" }).as_slice()), + getopts::optflag("b", "binary", &format!("read in binary mode{}", if binary_flag_default { " (default)" } else { "" })), getopts::optflag("c", "check", "read hashsums from the FILEs and check them"), getopts::optflag("", "tag", "create a BSD-style checksum"), - getopts::optflag("t", "text", format!("read in text mode{}", if binary_flag_default { "" } else { " (default)" }).as_slice()), + getopts::optflag("t", "text", &format!("read in text mode{}", if binary_flag_default { "" } else { " (default)" })), getopts::optflag("q", "quiet", "don't print OK for each successfully verified file"), getopts::optflag("s", "status", "don't output anything, status code shows success"), getopts::optflag("", "strict", "exit non-zero for improperly formatted checksum lines"), @@ -111,19 +108,19 @@ pub fn uumain(args: Vec) -> i32 { getopts::optflag("V", "version", "output version information and exit"), ); - opts.extend(get_algo_opts(binary_name.as_slice()).into_iter()); + opts.extend(get_algo_opts(binary_name).into_iter()); - let matches = match getopts::getopts(args.tail(), opts.as_slice()) { + let matches = match getopts::getopts(&args[1..], &opts) { Ok(m) => m, Err(f) => crash!(1, "{}", f) }; if matches.opt_present("help") { - usage(program.as_slice(), binary_name.as_slice(), opts.as_slice()); + usage(program, binary_name, &opts); } else if matches.opt_present("version") { version(); } else { - let (name, algo) = detect_algo(binary_name.as_slice(), &matches); + let (name, algo) = detect_algo(binary_name, &matches); let binary_flag = matches.opt_present("binary"); let text_flag = matches.opt_present("text"); @@ -168,7 +165,7 @@ fn usage(program: &str, binary_name: &str, opts: &[getopts::OptGroup]) { pipe_print!("{}", getopts::usage("Compute and check message digests.", opts)); } -fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: bool, check: bool, tag: bool, status: bool, quiet: bool, strict: bool, warn: bool) -> Result<(), i32> { +fn hashsum<'a>(algoname: &str, mut digest: Box, files: Vec, binary: bool, check: bool, tag: bool, status: bool, quiet: bool, strict: bool, warn: bool) -> Result<(), i32> { let mut bad_format = 0; let mut failed = 0; let binary_marker = if binary { @@ -177,16 +174,16 @@ fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: " " }; for filename in files.iter() { - let filename: &str = filename.as_slice(); + let filename: &str = filename; let mut stdin_buf; let mut file_buf; - let mut file = BufferedReader::new( + let mut file = BufReader::new( if filename == "-" { - stdin_buf = stdin_raw(); - &mut stdin_buf as &mut Reader + stdin_buf = stdin(); + Box::new(stdin_buf) as Box } else { - file_buf = safe_unwrap!(File::open(&Path::new(filename))); - &mut file_buf as &mut Reader + file_buf = safe_unwrap!(File::open(filename)); + Box::new(file_buf) as Box } ); if check { @@ -194,30 +191,30 @@ fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: let bytes = digest.output_bits() / 4; let gnu_re = safe_unwrap!( Regex::new( - format!( + &format!( r"^(?P[a-fA-F0-9]{{{}}}) (?P[ \*])(?P.*)", bytes - ).as_slice() + ) ) ); let bsd_re = safe_unwrap!( Regex::new( - format!( + &format!( r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{{{digest_size}}})", algorithm = algoname, digest_size = bytes - ).as_slice() + ) ) ); - let mut buffer = file; + let buffer = file; for (i, line) in buffer.lines().enumerate() { let line = safe_unwrap!(line); - let (ck_filename, sum, binary_check) = match gnu_re.captures(line.as_slice()) { + let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) { Some(caps) => (caps.name("fileName").unwrap(), caps.name("digest").unwrap().to_ascii_lowercase(), caps.name("binary").unwrap() == "*"), - None => match bsd_re.captures(line.as_slice()) { + None => match bsd_re.captures(&line) { Some(caps) => (caps.name("fileName").unwrap(), caps.name("digest").unwrap().to_ascii_lowercase(), true), @@ -233,10 +230,11 @@ fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: } } }; - let mut ckf = safe_unwrap!(File::open(&Path::new(ck_filename))); + let f = safe_unwrap!(File::open(ck_filename)); + let mut ckf = BufReader::new(Box::new(f) as Box); let real_sum = safe_unwrap!(digest_reader(&mut digest, &mut ckf, binary_check)) - .as_slice().to_ascii_lowercase(); - if sum.as_slice() == real_sum.as_slice() { + .to_ascii_lowercase(); + if sum == real_sum { if !quiet { pipe_println!("{}: OK", ck_filename); } @@ -270,21 +268,21 @@ fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: Ok(()) } -fn digest_reader(digest: &mut Box, reader: &mut Reader, binary: bool) -> Result { +fn digest_reader<'a, T: Read>(digest: &mut Box, reader: &mut BufReader, binary: bool) -> io::Result { digest.reset(); // Digest file, do not hold too much in memory at any given moment let windows = cfg!(windows); - let mut buffer = [0; 524288]; + let mut buffer = Vec::with_capacity(524288); let mut vec = Vec::with_capacity(524288); let mut looking_for_newline = false; loop { - match reader.read(&mut buffer) { - Ok(0) => {}, + match reader.read_to_end(&mut buffer) { + Ok(0) => { break; }, Ok(nread) => { if windows && !binary { // Windows text mode returns '\n' when reading '\r\n' - for i in range(0, nread) { + for i in 0 .. nread { if looking_for_newline { if buffer[i] != ('\n' as u8) { vec.push('\r' as u8); @@ -299,25 +297,18 @@ fn digest_reader(digest: &mut Box, reader: &mut Reader, binary: bool) -> looking_for_newline = true; } } - digest.input(vec.as_slice()); + digest.input(&vec); vec.clear(); } else { digest.input(&buffer[..nread]); } }, - Err(e) => match e.kind { - EndOfFile => { - break; - }, - _ => { - return Err(e); - } - } + Err(e) => return Err(e) } } if windows && looking_for_newline { vec.push('\r' as u8); - digest.input(vec.as_slice()); + digest.input(&vec); } Ok(digest.result_str()) diff --git a/src/uutils/uutils.rs b/src/uutils/uutils.rs index 59586b95e..07ad9cbb5 100644 --- a/src/uutils/uutils.rs +++ b/src/uutils/uutils.rs @@ -21,13 +21,15 @@ use std::path::Path; static NAME: &'static str = "uutils"; static VERSION: &'static str = "1.0.0"; -fn util_map() -> HashMap<&'static str, fn(Vec) -> i32> { - let mut map = HashMap::new(); +type UtilityMap = HashMap<&'static str, fn(Vec) -> i32>; + +fn util_map() -> UtilityMap { + let mut map: UtilityMap = HashMap::new(); @UTIL_MAP@ map } -fn usage(cmap: &HashMap<&'static str, fn(Vec) -> i32>) { +fn usage(cmap: &UtilityMap) { println!("{} {}", NAME, VERSION); println!(""); println!("Usage:"); diff --git a/test/fixtures/hashsum/input.txt b/test/fixtures/hashsum/input.txt new file mode 100644 index 000000000..8c01d89ae --- /dev/null +++ b/test/fixtures/hashsum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/test/fixtures/hashsum/md5.expected b/test/fixtures/hashsum/md5.expected new file mode 100644 index 000000000..9a78d4ab7 --- /dev/null +++ b/test/fixtures/hashsum/md5.expected @@ -0,0 +1 @@ +e4d7f1b4ed2e42d15898f4b27b019da4 \ No newline at end of file diff --git a/test/fixtures/hashsum/sha1.expected b/test/fixtures/hashsum/sha1.expected new file mode 100644 index 000000000..8a50a732e --- /dev/null +++ b/test/fixtures/hashsum/sha1.expected @@ -0,0 +1 @@ +b7e23ec29af22b0b4e41da31e868d57226121c84 \ No newline at end of file diff --git a/test/fixtures/hashsum/sha224.expected b/test/fixtures/hashsum/sha224.expected new file mode 100644 index 000000000..456eca808 --- /dev/null +++ b/test/fixtures/hashsum/sha224.expected @@ -0,0 +1 @@ +6e1a93e32fb44081a401f3db3ef2e6e108b7bbeeb5705afdaf01fb27 \ No newline at end of file diff --git a/test/fixtures/hashsum/sha256.expected b/test/fixtures/hashsum/sha256.expected new file mode 100644 index 000000000..8def59791 --- /dev/null +++ b/test/fixtures/hashsum/sha256.expected @@ -0,0 +1 @@ +09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b \ No newline at end of file diff --git a/test/fixtures/hashsum/sha384.expected b/test/fixtures/hashsum/sha384.expected new file mode 100644 index 000000000..489dbcef7 --- /dev/null +++ b/test/fixtures/hashsum/sha384.expected @@ -0,0 +1 @@ +1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e \ No newline at end of file diff --git a/test/fixtures/hashsum/sha512.expected b/test/fixtures/hashsum/sha512.expected new file mode 100644 index 000000000..fd8173686 --- /dev/null +++ b/test/fixtures/hashsum/sha512.expected @@ -0,0 +1 @@ +8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9 \ No newline at end of file diff --git a/test/hashsum.rs b/test/hashsum.rs new file mode 100644 index 000000000..c467530da --- /dev/null +++ b/test/hashsum.rs @@ -0,0 +1,99 @@ +use std::fs::File; +use std::io::{Read, Write}; +use std::process::{Command, Stdio}; +use std::str::from_utf8; + +static PROGNAME: &'static str = "./hashsum"; + +struct CmdResult { + success: bool, + stdout: String, + stderr: String, +} + +fn run(cmd: &mut Command) -> CmdResult { + let prog = cmd.output().unwrap(); + CmdResult { + success: prog.status.success(), + stdout: from_utf8(&prog.stdout).unwrap().to_string(), + stderr: from_utf8(&prog.stderr).unwrap().to_string(), + } +} + +fn run_piped_stdin(cmd: &mut Command, input: &[u8])-> CmdResult { + let mut command = cmd + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + command.stdin + .take() + .unwrap_or_else(|| panic!("Could not take child process stdin")) + .write_all(input) + .unwrap_or_else(|e| panic!("{}", e)); + + let prog = command.wait_with_output().unwrap(); + CmdResult { + success: prog.status.success(), + stdout: from_utf8(&prog.stdout).unwrap().to_string(), + stderr: from_utf8(&prog.stderr).unwrap().to_string(), + } +} + +fn get_file_contents(name: &str) -> String { + let mut f = File::open(name).unwrap(); + let mut contents = String::new(); + let _ = f.read_to_string(&mut contents); + contents +} + +macro_rules! assert_empty_stderr( + ($cond:expr) => ( + if $cond.stderr.len() > 0 { + panic!(format!("stderr: {}", $cond.stderr)) + } + ); +); + +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($($t:ident)*) => ($( + + mod $t { + use std::process::Command; + + static DIGEST_ARG: &'static str = concat!("--", stringify!($t)); + static EXPECTED_FILE: &'static str = concat!(stringify!($t), ".expected"); + + #[test] + fn test_single_file() { + let mut cmd = Command::new(::PROGNAME); + let result = ::run(&mut cmd.arg(DIGEST_ARG).arg("input.txt")); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(get_hash!(result.stdout), ::get_file_contents(EXPECTED_FILE)); + } + + #[test] + fn test_stdin() { + let input = ::get_file_contents("input.txt"); + let mut cmd = Command::new(::PROGNAME); + let result = ::run_piped_stdin(&mut cmd.arg(DIGEST_ARG), input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(get_hash!(result.stdout), ::get_file_contents(EXPECTED_FILE)); + } + } + )*) +} + +test_digest! { md5 sha1 sha224 sha256 sha384 sha512 }