From 1fbda9663d47fddb175323e40ffefe0365456617 Mon Sep 17 00:00:00 2001 From: modelorganism Date: Mon, 25 Apr 2016 21:47:24 -0500 Subject: [PATCH 1/5] Add extra spaces to output to match formating of origial `od` --- src/od/od.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/od/od.rs b/src/od/od.rs index 969ee55ea..3b65a17c0 100644 --- a/src/od/od.rs +++ b/src/od/od.rs @@ -61,6 +61,9 @@ pub fn uumain(args: Vec) -> i32 { 0 } +const LINEBYTES:usize = 16; +const WORDBYTES:usize = 2; + fn odfunc(input_offset_base: &Radix, fname: &str) { let mut f = match File::open(Path::new(fname)) { Ok(f) => f, @@ -68,29 +71,37 @@ fn odfunc(input_offset_base: &Radix, fname: &str) { }; let mut addr = 0; - let bytes = &mut [b'\x00'; 16]; - loop { + let bytes = &mut [b'\x00'; LINEBYTES]; + loop { // print each line + print_with_radix(input_offset_base, addr); // print offset match f.read(bytes) { Ok(0) => { - print_with_radix(input_offset_base, addr); print!("\n"); break; } Ok(n) => { - print_with_radix(input_offset_base, addr); + print!(" "); // 4 spaces after offset - we print 2 more before each word + for b in 0 .. n / mem::size_of::() { let bs = &bytes[(2 * b) .. (2 * b + 2)]; let p: u16 = (bs[1] as u16) << 8 | bs[0] as u16; - print!(" {:06o}", p); + print!(" {:06o}", p); } if n % mem::size_of::() == 1 { - print!(" {:06o}", bytes[n - 1]); + print!(" {:06o}", bytes[n - 1]); } + + // Add extra spaces to pad out the short, presumably last, line. + if nwidth$}", "", width=(words_short)*(6+2)); + } + print!("\n"); addr += n; }, Err(_) => { - print_with_radix(input_offset_base, addr); break; } }; @@ -118,7 +129,7 @@ fn parse_radix(radix_str: Option) -> Result { } } } - + fn print_with_radix(r: &Radix, x: usize) { // TODO(keunwoo): field widths should be based on sizeof(x), or chosen dynamically based on the // expected range of address values. Binary in particular is not great here. @@ -128,4 +139,4 @@ fn print_with_radix(r: &Radix, x: usize) { Radix::Octal => print!("{:07o}", x), Radix::Binary => print!("{:07b}", x) } -} +} \ No newline at end of file From e6cf167d1d1378891aeef9eec078bcc564a87b58 Mon Sep 17 00:00:00 2001 From: modelorganism Date: Mon, 25 Apr 2016 21:55:34 -0500 Subject: [PATCH 2/5] od: Accept multiple files names as input --- src/od/od.rs | 168 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 49 deletions(-) diff --git a/src/od/od.rs b/src/od/od.rs index 3b65a17c0..87c3abed3 100644 --- a/src/od/od.rs +++ b/src/od/od.rs @@ -14,8 +14,10 @@ extern crate getopts; use std::fs::File; use std::io::Read; use std::mem; -use std::path::Path; - +use std::io::BufReader; +use std::io::Write; +use std::io; + #[derive(Debug)] enum Radix { Decimal, Hexadecimal, Octal, Binary } @@ -50,62 +52,130 @@ pub fn uumain(args: Vec) -> i32 { Ok(r) => r, Err(f) => { panic!("Invalid -A/--address-radix\n{}", f) } }; - - let fname = match args.last() { - Some(n) => n, - None => { panic!("Need fname for now") ; } + + // Gather up file names - args wich don't start with '-' + let fnames = args[1..] + .iter() + .filter(|w| !w.starts_with('-')) + .map(|x| x.clone()) + .collect::>(); + + // With no filenames, od would use stdin as input, which is currently not supported. + if fnames.len() == 0 { + panic!("Need fname for now") ; }; - - odfunc(&input_offset_base, &fname); - - 0 + odfunc(&input_offset_base, fnames) } const LINEBYTES:usize = 16; const WORDBYTES:usize = 2; + +fn odfunc(input_offset_base: &Radix, fnames: Vec) -> i32 { -fn odfunc(input_offset_base: &Radix, fname: &str) { - let mut f = match File::open(Path::new(fname)) { - Ok(f) => f, - Err(e) => panic!("file error: {}", e) - }; - - let mut addr = 0; - let bytes = &mut [b'\x00'; LINEBYTES]; - loop { // print each line - print_with_radix(input_offset_base, addr); // print offset - match f.read(bytes) { - Ok(0) => { - print!("\n"); - break; - } - Ok(n) => { - print!(" "); // 4 spaces after offset - we print 2 more before each word - - for b in 0 .. n / mem::size_of::() { - let bs = &bytes[(2 * b) .. (2 * b + 2)]; - let p: u16 = (bs[1] as u16) << 8 | bs[0] as u16; - print!(" {:06o}", p); + let mut status = 0; + let mut ni = fnames.iter(); + { + // Open and return the next file to process as a BufReader + // Returns None when no more files. + let mut next_file = || -> Option> { + // loop retries with subsequent files if err - normally 'loops' once + loop { + let fname = match ni.next() { + None => return None, + Some(s) => s, + }; + match File::open(fname) { + Ok(f) => return Some(BufReader::new(f)), + Err(e) => { + // If any file can't be opened, + // print an error at the time that the file is needed, + // then move on the the next file. + // This matches the behavior of the original `od` + let _ = writeln!(&mut std::io::stderr(), "od: '{}': {}", fname, e); + if status == 0 {status = 1} + } } - if n % mem::size_of::() == 1 { - print!(" {:06o}", bytes[n - 1]); - } - - // Add extra spaces to pad out the short, presumably last, line. - if nwidth$}", "", width=(words_short)*(6+2)); - } - - print!("\n"); - addr += n; - }, - Err(_) => { - break; } }; + + let mut curr_file: BufReader = match next_file() { + Some(f) => f, + None => { + return 1; + } + }; + + let mut exhausted = false; // There is no more input, gone to the end of the last file. + + // Fill buf with bytes read from the list of files + // Returns Ok() + // Handles io errors itself, thus always returns OK + // Fills the provided buffer completely, unless it has run out of input. + // If any call returns short (< buf.len()), all subsequent calls will return Ok<0> + let mut f_read = |buf: &mut [u8]| -> io::Result { + if exhausted { + Ok(0) + } else { + let mut xfrd = 0; + while xfrd < buf.len() { + xfrd += match curr_file.read(&mut buf[xfrd..]) { + Ok(n) => n, + Err(e) => panic!("file error: {}", e), + }; + if xfrd == buf.len() { + // transferred all that was asked for. + break; + } + curr_file = match next_file() { + Some(f) => f, + None => { + exhausted = true; + break; + } + }; + } + Ok(xfrd) + } + }; + + let mut addr = 0; + let bytes = &mut [b'\x00'; LINEBYTES]; + loop { // print each line + print_with_radix(input_offset_base, addr); // print offset + match f_read(bytes) { + Ok(0) => { + print!("\n"); + break; + } + Ok(n) => { + print!(" "); // 4 spaces after offset - we print 2 more before each word + + for b in 0 .. n / mem::size_of::() { + let bs = &bytes[(2 * b) .. (2 * b + 2)]; + let p: u16 = (bs[1] as u16) << 8 | bs[0] as u16; + print!(" {:06o}", p); + } + if n % mem::size_of::() == 1 { + print!(" {:06o}", bytes[n - 1]); + } + + // Add extra spaces to pad out the short, presumably last, line. + if nwidth$}", "", width=(words_short)*(6+2)); + } + + print!("\n"); + addr += n; + }, + Err(_) => { + break; + } + }; + }; }; + status } fn parse_radix(radix_str: Option) -> Result { @@ -129,7 +199,7 @@ fn parse_radix(radix_str: Option) -> Result { } } } - + fn print_with_radix(r: &Radix, x: usize) { // TODO(keunwoo): field widths should be based on sizeof(x), or chosen dynamically based on the // expected range of address values. Binary in particular is not great here. From 365b3427920c250a9614208b02f5586fff644df5 Mon Sep 17 00:00:00 2001 From: modelorganism Date: Mon, 25 Apr 2016 22:06:38 -0500 Subject: [PATCH 3/5] od: create first tests for od --- Makefile | 1 + tests/od.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 tests/od.rs diff --git a/Makefile b/Makefile index 19bc50449..09190f2eb 100644 --- a/Makefile +++ b/Makefile @@ -149,6 +149,7 @@ TEST_PROGS := \ mktemp \ mv \ nl \ + od \ paste \ printf \ ptx \ diff --git a/tests/od.rs b/tests/od.rs new file mode 100644 index 000000000..86211093f --- /dev/null +++ b/tests/od.rs @@ -0,0 +1,87 @@ +#[macro_use] +mod common; + +use common::util::*; +use std::path::Path; +use std::env; +use std::io::Write; +use std::fs::File; +use std::fs::remove_file; + +static UTIL_NAME: &'static str = "od"; + +// octal dump of 'abcdefghijklmnopqrstuvwxyz\n' +static ALPHA_OUT: &'static str = "0000000 061141 062143 063145 064147 065151 066153 067155 070157\n0000020 071161 072163 073165 074167 075171 000012 \n0000033\n"; + +// XXX We could do a better job of ensuring that we have a fresh temp dir to ourself, +// not a general one ful of other proc's leftovers. + +// Test that od can read one file and dump with default format +#[test] +fn test_file() { + let (_, mut ucmd) = testing(UTIL_NAME); + let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + let tmpdir = Path::new(&temp); + let file = tmpdir.join("test"); + + { + let mut f = File::create(&file).unwrap(); + match f.write_all(b"abcdefghijklmnopqrstuvwxyz\n") { + Err(_) => panic!("Test setup failed - could not write file"), + _ => {} + } + } + + let result = ucmd.arg(file.as_os_str()).run(); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, ALPHA_OUT); + + let _ = remove_file(file); +} + +// Test that od can read 2 files and concatenate the contents +#[test] +fn test_2files() { + let (_, mut ucmd) = testing(UTIL_NAME); + let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + let tmpdir = Path::new(&temp); + let file1 = tmpdir.join("test1"); + let file2 = tmpdir.join("test2"); + + for &(n,a) in [(1,"a"), (2,"b")].iter() { + println!("number: {} letter:{}", n, a); + } + + for &(path,data)in &[(&file1, "abcdefghijklmnop"),(&file2, "qrstuvwxyz\n")] { + let mut f = File::create(&path).unwrap(); + match f.write_all(data.as_bytes()) { + Err(_) => panic!("Test setup failed - could not write file"), + _ => {} + } + } + + let result = ucmd.arg(file1.as_os_str()).arg(file2.as_os_str()).run(); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, ALPHA_OUT); + + let _ = remove_file(file1); + let _ = remove_file(file2); +} + +// Test that od gives non-0 exit val for filename that dosen't exist. +#[test] +fn test_no_file() { + let (_, mut ucmd) = testing(UTIL_NAME); + let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + let tmpdir = Path::new(&temp); + let file = tmpdir.join("}surely'none'would'thus'a'file'name"); + + let result = ucmd.arg(file.as_os_str()).run(); + + assert!(!result.success); + +} \ No newline at end of file From 3f356a4190abb119949c66a2efa526bacdcfaca2 Mon Sep 17 00:00:00 2001 From: modelorganism Date: Mon, 25 Apr 2016 21:05:16 -0500 Subject: [PATCH 4/5] od: take input from stdin, as well as files. --- src/od/od.rs | 84 ++++++++++++++++++++++++++++++---------------------- tests/od.rs | 44 +++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 37 deletions(-) diff --git a/src/od/od.rs b/src/od/od.rs index 87c3abed3..7b52011e6 100644 --- a/src/od/od.rs +++ b/src/od/od.rs @@ -20,7 +20,13 @@ use std::io; #[derive(Debug)] enum Radix { Decimal, Hexadecimal, Octal, Binary } - + +#[derive(Debug)] +enum InputSource<'a> { + FileName(&'a str ), + Stdin +} + pub fn uumain(args: Vec) -> i32 { let mut opts = getopts::Options::new(); @@ -53,58 +59,62 @@ pub fn uumain(args: Vec) -> i32 { Err(f) => { panic!("Invalid -A/--address-radix\n{}", f) } }; - // Gather up file names - args wich don't start with '-' + // Gather up file names - args which don't start with '-' let fnames = args[1..] .iter() - .filter(|w| !w.starts_with('-')) - .map(|x| x.clone()) + .filter(|w| !w.starts_with('-') || w == &"--" ) // "--" starts with '-', but it denotes stdin, not a flag + .map(|x| match x.as_str() { "--" => InputSource::Stdin, x => InputSource::FileName(x)}) .collect::>(); - // With no filenames, od would use stdin as input, which is currently not supported. + // With no filenames, od uses stdin as input. if fnames.len() == 0 { - panic!("Need fname for now") ; - }; - odfunc(&input_offset_base, fnames) + odfunc(&input_offset_base, &[InputSource::Stdin]) + } + else { + odfunc(&input_offset_base, &fnames) + } } const LINEBYTES:usize = 16; const WORDBYTES:usize = 2; -fn odfunc(input_offset_base: &Radix, fnames: Vec) -> i32 { - +fn odfunc(input_offset_base: &Radix, fnames: &[InputSource]) -> i32 { + let mut status = 0; let mut ni = fnames.iter(); { // Open and return the next file to process as a BufReader // Returns None when no more files. - let mut next_file = || -> Option> { + let mut next_file = || -> Option> { // loop retries with subsequent files if err - normally 'loops' once loop { - let fname = match ni.next() { + match ni.next() { None => return None, - Some(s) => s, - }; - match File::open(fname) { - Ok(f) => return Some(BufReader::new(f)), - Err(e) => { - // If any file can't be opened, - // print an error at the time that the file is needed, - // then move on the the next file. - // This matches the behavior of the original `od` - let _ = writeln!(&mut std::io::stderr(), "od: '{}': {}", fname, e); - if status == 0 {status = 1} + Some(input) => match *input { + InputSource::Stdin => return Some(Box::new(BufReader::new(std::io::stdin()))), + InputSource::FileName(fname) => match File::open(fname) { + Ok(f) => return Some(Box::new(BufReader::new(f))), + Err(e) => { + // If any file can't be opened, + // print an error at the time that the file is needed, + // then move on the the next file. + // This matches the behavior of the original `od` + let _ = writeln!(&mut std::io::stderr(), "od: '{}': {}", fname, e); + if status == 0 {status = 1} + } + } } } } }; - - let mut curr_file: BufReader = match next_file() { + + let mut curr_file: Box = match next_file() { Some(f) => f, None => { return 1; } }; - + let mut exhausted = false; // There is no more input, gone to the end of the last file. // Fill buf with bytes read from the list of files @@ -117,14 +127,18 @@ fn odfunc(input_offset_base: &Radix, fnames: Vec) -> i32 { Ok(0) } else { let mut xfrd = 0; - while xfrd < buf.len() { - xfrd += match curr_file.read(&mut buf[xfrd..]) { - Ok(n) => n, - Err(e) => panic!("file error: {}", e), - }; - if xfrd == buf.len() { - // transferred all that was asked for. - break; + // while buffer we are filling is not full.. May go thru several files. + 'fillloop: while xfrd < buf.len() { + loop { // stdin may return on 'return' (enter), even though the buffer isn't full. + xfrd += match curr_file.read(&mut buf[xfrd..]) { + Ok(0) => break, + Ok(n) => n, + Err(e) => panic!("file error: {}", e), + }; + if xfrd == buf.len() { + // transferred all that was asked for. + break 'fillloop; + } } curr_file = match next_file() { Some(f) => f, @@ -209,4 +223,4 @@ fn print_with_radix(r: &Radix, x: usize) { Radix::Octal => print!("{:07o}", x), Radix::Binary => print!("{:07b}", x) } -} \ No newline at end of file +} diff --git a/tests/od.rs b/tests/od.rs index 86211093f..8e641f907 100644 --- a/tests/od.rs +++ b/tests/od.rs @@ -83,5 +83,45 @@ fn test_no_file() { let result = ucmd.arg(file.as_os_str()).run(); assert!(!result.success); - -} \ No newline at end of file +} + +// Test that od reads from stdin instead of a file +#[test] +fn test_from_stdin() { + let (_, mut ucmd) = testing(UTIL_NAME); + + let input = "abcdefghijklmnopqrstuvwxyz\n"; + let result = ucmd.run_piped_stdin(input.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, ALPHA_OUT); + +} + +// Test that od reads from stdin and also from files +#[test] +fn test_from_mixed() { + let (_, mut ucmd) = testing(UTIL_NAME); + + let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + let tmpdir = Path::new(&temp); + let file1 = tmpdir.join("test-1"); + let file3 = tmpdir.join("test-3"); + + let (data1, data2, data3) = ("abcdefg","hijklmnop","qrstuvwxyz\n"); + for &(path,data)in &[(&file1, data1),(&file3, data3)] { + let mut f = File::create(&path).unwrap(); + match f.write_all(data.as_bytes()) { + Err(_) => panic!("Test setup failed - could not write file"), + _ => {} + } + } + + let result = ucmd.arg(file1.as_os_str()).arg("--").arg(file3.as_os_str()).run_piped_stdin(data2.as_bytes()); + + assert_empty_stderr!(result); + assert!(result.success); + assert_eq!(result.stdout, ALPHA_OUT); + +} From caebd834c6c49bea060d2b701521ba66c593215d Mon Sep 17 00:00:00 2001 From: modelorganism Date: Fri, 29 Apr 2016 20:17:51 -0500 Subject: [PATCH 5/5] od: fix failing test unable to find temp dir on linux --- tests/od.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/od.rs b/tests/od.rs index 8e641f907..d177d344c 100644 --- a/tests/od.rs +++ b/tests/od.rs @@ -20,7 +20,8 @@ static ALPHA_OUT: &'static str = "0000000 061141 062143 063145 064147 065 #[test] fn test_file() { let (_, mut ucmd) = testing(UTIL_NAME); - let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + use std::env; + let temp = env::temp_dir(); let tmpdir = Path::new(&temp); let file = tmpdir.join("test"); @@ -45,7 +46,7 @@ fn test_file() { #[test] fn test_2files() { let (_, mut ucmd) = testing(UTIL_NAME); - let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + let temp = env::temp_dir(); let tmpdir = Path::new(&temp); let file1 = tmpdir.join("test1"); let file2 = tmpdir.join("test2"); @@ -76,7 +77,7 @@ fn test_2files() { #[test] fn test_no_file() { let (_, mut ucmd) = testing(UTIL_NAME); - let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + let temp = env::temp_dir(); let tmpdir = Path::new(&temp); let file = tmpdir.join("}surely'none'would'thus'a'file'name"); @@ -104,7 +105,7 @@ fn test_from_stdin() { fn test_from_mixed() { let (_, mut ucmd) = testing(UTIL_NAME); - let temp = env::var("TMPDIR").unwrap_or_else(|_| env::var("TEMP").unwrap()); + let temp = env::temp_dir(); let tmpdir = Path::new(&temp); let file1 = tmpdir.join("test-1"); let file3 = tmpdir.join("test-3");