diff --git a/.busybox-config b/.busybox-config new file mode 100644 index 000000000..d1fb62794 --- /dev/null +++ b/.busybox-config @@ -0,0 +1,2 @@ +CONFIG_FEATURE_FANCY_HEAD=y +CONFIG_UNICODE_SUPPORT=y diff --git a/Makefile b/Makefile index 88baaf4ca..1ef4a9bee 100644 --- a/Makefile +++ b/Makefile @@ -268,9 +268,8 @@ $(BUILDDIR)/busybox: $(BUILDDIR)/uutils ln -s $(BUILDDIR)/uutils $(BUILDDIR)/busybox # This is a busybox-specific config file their test suite wants to parse. -# For now it's blank. -$(BUILDDIR)/.config: $(BUILDDIR)/uutils - touch $@ +$(BUILDDIR)/.config: $(BASEDIR)/.busybox-config $(BUILDDIR)/uutils + cp $< $@ ifeq ($(BUSYBOX_SRC),) busytest: diff --git a/src/common/util.rs b/src/common/util.rs index 95f86a0fe..513de0847 100644 --- a/src/common/util.rs +++ b/src/common/util.rs @@ -14,42 +14,41 @@ extern crate libc; #[macro_export] macro_rules! show_error( ($($args:expr),+) => ({ - safe_write!(&mut ::std::io::stderr(), "{}: error: ", ::NAME); - safe_writeln!(&mut ::std::io::stderr(), $($args),+); + pipe_write!(&mut ::std::io::stderr(), "{}: error: ", ::NAME); + pipe_writeln!(&mut ::std::io::stderr(), $($args),+); }) ) #[macro_export] macro_rules! show_warning( ($($args:expr),+) => ({ - safe_write!(&mut ::std::io::stderr(), "{}: warning: ", ::NAME); - safe_writeln!(&mut ::std::io::stderr(), $($args),+); + pipe_write!(&mut ::std::io::stderr(), "{}: warning: ", ::NAME); + pipe_writeln!(&mut ::std::io::stderr(), $($args),+); }) ) #[macro_export] macro_rules! show_info( ($($args:expr),+) => ({ - safe_write!(&mut ::std::io::stderr(), "{}: ", ::NAME); - safe_writeln!(&mut ::std::io::stderr(), $($args),+); + pipe_write!(&mut ::std::io::stderr(), "{}: ", ::NAME); + pipe_writeln!(&mut ::std::io::stderr(), $($args),+); }) ) #[macro_export] macro_rules! eprint( - ($($args:expr),+) => (safe_write!(&mut ::std::io::stderr(), $($args),+)) + ($($args:expr),+) => (pipe_write!(&mut ::std::io::stderr(), $($args),+)) ) #[macro_export] macro_rules! eprintln( - ($($args:expr),+) => (safe_writeln!(&mut ::std::io::stderr(), $($args),+)) + ($($args:expr),+) => (pipe_writeln!(&mut ::std::io::stderr(), $($args),+)) ) #[macro_export] macro_rules! crash( ($exitcode:expr, $($args:expr),+) => ({ - safe_write!(&mut ::std::io::stderr(), "{}: error: ", ::NAME); - safe_writeln!(&mut ::std::io::stderr(), $($args),+); + show_error!($($args),+); unsafe { ::util::libc::exit($exitcode as ::util::libc::c_int); } }) ) @@ -84,12 +83,78 @@ macro_rules! return_if_err( ) ) +// XXX: should the pipe_* macros return an Err just to show the write failed? + +#[macro_export] +macro_rules! pipe_print( + ($($args:expr),+) => ( + match write!(&mut ::std::io::stdout() as &mut Writer, $($args),+) { + Ok(_) => true, + Err(f) => { + if f.kind == ::std::io::BrokenPipe { + false + } else { + fail!("{}", f) + } + } + } + ) +) + +#[macro_export] +macro_rules! pipe_println( + ($($args:expr),+) => ( + match writeln!(&mut ::std::io::stdout() as &mut Writer, $($args),+) { + Ok(_) => true, + Err(f) => { + if f.kind == ::std::io::BrokenPipe { + false + } else { + fail!("{}", f) + } + } + } + ) +) + +#[macro_export] +macro_rules! pipe_write( + ($fd:expr, $($args:expr),+) => ( + match write!($fd, $($args),+) { + Ok(_) => true, + Err(f) => { + if f.kind == ::std::io::BrokenPipe { + false + } else { + fail!("{}", f) + } + } + } + ) +) + +#[macro_export] +macro_rules! pipe_writeln( + ($fd:expr, $($args:expr),+) => ( + match write!($fd, $($args),+) { + Ok(_) => true, + Err(f) => { + if f.kind == ::std::io::BrokenPipe { + false + } else { + fail!("{}", f) + } + } + } + ) +) + #[macro_export] macro_rules! safe_write( ($fd:expr, $($args:expr),+) => ( match write!($fd, $($args),+) { Ok(_) => {} - Err(f) => { fail!(f.to_string()); } + Err(f) => fail!(f.to_string()) } ) ) @@ -99,7 +164,7 @@ macro_rules! safe_writeln( ($fd:expr, $($args:expr),+) => ( match writeln!($fd, $($args),+) { Ok(_) => {} - Err(f) => { fail!(f.to_string()); } + Err(f) => fail!(f.to_string()) } ) ) diff --git a/src/fold/fold.rs b/src/fold/fold.rs index 6b8b872d3..48e488cee 100644 --- a/src/fold/fold.rs +++ b/src/fold/fold.rs @@ -26,7 +26,6 @@ static NAME: &'static str = "fold"; static VERSION: &'static str = "1.0.0"; pub fn uumain(args: Vec) -> int { - let (args, obs_width) = handle_obsolete(args.as_slice()); let program = args[0].clone(); @@ -98,11 +97,15 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { fn fold(filenames: Vec, bytes: bool, spaces: bool, width: uint) { for filename in filenames.iter() { let filename: &str = filename.as_slice(); + let mut stdin_buf; + let mut file_buf; let buffer = BufferedReader::new( if filename == "-" { - box io::stdio::stdin_raw() as Box + stdin_buf = io::stdio::stdin_raw(); + &mut stdin_buf as &mut Reader } else { - box safe_unwrap!(File::open(&Path::new(filename))) as Box + file_buf = safe_unwrap!(File::open(&Path::new(filename))); + &mut file_buf as &mut Reader } ); fold_file(buffer, bytes, spaces, width); @@ -112,19 +115,24 @@ fn fold(filenames: Vec, bytes: bool, spaces: bool, width: uint) { fn fold_file(file: BufferedReader, bytes: bool, spaces: bool, width: uint) { let mut file = file; for line in file.lines() { - let line = safe_unwrap!(line); - if line.len() == 1 { - println!(""); - continue; + let line_string = safe_unwrap!(line); + let mut line = line_string.as_slice(); + let len = line.len(); + if line.char_at(len - 1) == '\n' { + if len == 1 { + println!(""); + continue; + } else { + line = line.slice_to(len - 1); + } } - let line = line.as_slice().slice_to(line.len() - 1); if bytes { let mut i = 0; while i < line.len() { let width = if line.len() - i >= width { width } else { line.len() - i }; let slice = { let slice = line.slice(i, i + width); - if spaces && i + width != line.len() { + if spaces && i + width < line.len() { match slice.rfind(|ch: char| ch.is_whitespace()) { Some(m) => slice.slice_to(m + 1), None => slice @@ -140,11 +148,40 @@ fn fold_file(file: BufferedReader, bytes: bool, spaces: bool, let mut output = String::new(); let mut count = 0; for (i, ch) in line.chars().enumerate() { + if count >= width { + let (val, ncount) = { + let slice = output.as_slice(); + let (out, val, ncount) = + if spaces && i + 1 < line.len() { + match slice.rfind(|ch: char| ch.is_whitespace()) { + Some(m) => { + let routput = slice.slice_from(m + 1).to_string(); + let ncount = routput.as_slice().chars().fold(0u, |out, ch: char| { + out + match ch { + '\t' => 8, + '\x08' => if out > 0 { -1 } else { 0 }, + '\r' => return 0, + _ => 1 + } + }); + (slice.slice_to(m + 1), routput, ncount) + }, + None => (slice, "".to_string(), 0) + } + } else { + (slice, "".to_string(), 0) + }; + println!("{}", out); + (val, ncount) + }; + output = val.into_string(); + count = ncount; + } match ch { '\t' => { count += 8; if count > width { - println!("{}", output.as_slice()); + println!("{}", output); output.truncate(0); count = 8; } @@ -165,31 +202,9 @@ fn fold_file(file: BufferedReader, bytes: bool, spaces: bool, _ => count += 1 }; output.push_char(ch); - if count == width { - let (val, ncount) = { - let slice = output.as_slice(); - let (out, val, ncount) = - if spaces && i + 1 != line.len() { - match slice.rfind(|ch: char| ch.is_whitespace()) { - Some(m) => { - let routput = slice.slice_from(m + 1).to_string(); - let ncount = routput.as_slice().chars().fold(0, |out, ch: char| out + if ch == '\t' { 8 } else { 1 }); - (slice.slice_to(m + 1), routput, ncount) - }, - None => (slice, "".to_string(), 0) - } - } else { - (slice, "".to_string(), 0) - }; - println!("{}", out); - (val, ncount) - }; - output = val.into_string(); - count = ncount; - } } if count > 0 { - println!("{}", output); + print!("{}", output); } } } diff --git a/src/hashsum/hashsum.rs b/src/hashsum/hashsum.rs index d89540891..7557eb54f 100644 --- a/src/hashsum/hashsum.rs +++ b/src/hashsum/hashsum.rs @@ -151,20 +151,20 @@ pub fn uumain(args: Vec) -> int { } fn version() { - println!("{} v{}", NAME, VERSION); + pipe_println!("{} v{}", NAME, VERSION); } fn usage(program: &str, binary_name: &str, opts: &[getopts::OptGroup]) { version(); - println!(""); - println!("Usage:"); + pipe_println!(""); + pipe_println!("Usage:"); if is_custom_binary(binary_name) { - println!(" {} [OPTION]... [FILE]...", program); + pipe_println!(" {} [OPTION]... [FILE]...", program); } else { - println!(" {} {{--md5|--sha1|--sha224|--sha256|--sha384|--sha512}} [OPTION]... [FILE]...", program); + pipe_println!(" {} {{--md5|--sha1|--sha224|--sha256|--sha384|--sha512}} [OPTION]... [FILE]...", program); } - println!(""); - print!("{}", getopts::usage("Compute and check message digests.", opts)); + pipe_println!(""); + 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<(), int> { @@ -177,15 +177,18 @@ fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: }; for filename in files.iter() { let filename: &str = filename.as_slice(); + let mut stdin_buf; + let mut file_buf; let mut file = BufferedReader::new( if filename == "-" { - box stdin_raw() as Box + stdin_buf = stdin_raw(); + &mut stdin_buf as &mut Reader } else { - box safe_unwrap!(File::open(&Path::new(filename))) as Box + file_buf = safe_unwrap!(File::open(&Path::new(filename))); + &mut file_buf as &mut Reader } ); if check { - // Set up Regexes for line validation and parsing let bytes = digest.output_bits() / 4; let gnu_re = safe_unwrap!( @@ -229,11 +232,11 @@ fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: .as_slice().to_ascii().to_lower(); if sum.as_slice() == real_sum.as_slice() { if !quiet { - println!("{}: OK", ck_filename); + pipe_println!("{}: OK", ck_filename); } } else { if !status { - println!("{}: FAILED", ck_filename); + pipe_println!("{}: FAILED", ck_filename); } failed += 1; } @@ -241,9 +244,9 @@ fn hashsum(algoname: &str, mut digest: Box, files: Vec, binary: } else { let sum = safe_unwrap!(digest_reader(&mut digest, &mut file, binary)); if tag { - println!("{} ({}) = {}", algoname, filename, sum); + pipe_println!("{} ({}) = {}", algoname, filename, sum); } else { - println!("{} {}{}", sum, binary_marker, filename); + pipe_println!("{} {}{}", sum, binary_marker, filename); } } } diff --git a/src/head/head.rs b/src/head/head.rs index d3890def1..4cf95035c 100644 --- a/src/head/head.rs +++ b/src/head/head.rs @@ -10,6 +10,8 @@ * Synced with: https://raw.github.com/avsm/src/master/usr.bin/head/head.c */ +#![feature(macro_rules)] + extern crate getopts; use std::char; @@ -20,10 +22,14 @@ use std::path::Path; use std::str::from_utf8; use getopts::{optopt, optflag, getopts, usage}; -static PROGRAM: &'static str = "head"; +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "head"; pub fn uumain(args: Vec) -> int { let mut line_count = 10u; + let mut byte_count = 0u; // handle obsolete -number syntax let options = match obsolete(args.tail()) { @@ -34,7 +40,8 @@ pub fn uumain(args: Vec) -> int { let args = options; let possible_options = [ - optopt("n", "number", "Number of lines to print", "n"), + optopt("c", "bytes", "Print the first K bytes. With the leading '-', print all but the last K bytes", "[-]K"), + optopt("n", "lines", "Print the first K lines. With the leading '-', print all but the last K lines", "[-]K"), optflag("h", "help", "help"), optflag("V", "version", "version") ]; @@ -42,32 +49,58 @@ pub fn uumain(args: Vec) -> int { let given_options = match getopts(args.as_slice(), possible_options) { Ok (m) => { m } Err(_) => { - println!("{:s}", usage(PROGRAM, possible_options)); + println!("{:s}", usage(NAME, possible_options)); return 1; } }; if given_options.opt_present("h") { - println!("{:s}", usage(PROGRAM, possible_options)); + println!("{:s}", usage(NAME, possible_options)); return 0; } if given_options.opt_present("V") { version(); return 0 } + let use_bytes = given_options.opt_present("c"); + + // TODO: suffixes (e.g. b, kB, etc.) match given_options.opt_str("n") { Some(n) => { + if use_bytes { + show_error!("cannot specify both --bytes and --lines."); + return 1; + } match from_str(n.as_slice()) { Some(m) => { line_count = m } - None => {} + None => { + show_error!("invalid line count '{}'", n); + return 1; + } } } - None => {} + None => match given_options.opt_str("c") { + Some(count) => match from_str(count.as_slice()) { + Some(m) => byte_count = m, + None => { + show_error!("invalid byte count '{}'", count); + return 1; + } + }, + None => {} + } }; let files = given_options.free; + let count = + if use_bytes { + byte_count + } else { + line_count + }; + if files.is_empty() { let mut buffer = BufferedReader::new(stdin()); - head(&mut buffer, line_count); + head(&mut buffer, count, use_bytes); } else { let mut multiple = false; let mut firstime = true; @@ -76,18 +109,19 @@ pub fn uumain(args: Vec) -> int { multiple = true; } - for file in files.iter() { if multiple { - if !firstime { println!(""); } - println!("==> {:s} <==", file.as_slice()); + if !firstime { pipe_println!(""); } + pipe_println!("==> {:s} <==", file.as_slice()); } firstime = false; let path = Path::new(file.as_slice()); let reader = File::open(&path).unwrap(); let mut buffer = BufferedReader::new(reader); - head(&mut buffer, line_count); + if !head(&mut buffer, count, use_bytes) { + break; + } } } @@ -98,7 +132,7 @@ pub fn uumain(args: Vec) -> int { // // In case is found, the options vector will get rid of that object so that // getopts works correctly. -fn obsolete (options: &[String]) -> (Vec, Option) { +fn obsolete(options: &[String]) -> (Vec, Option) { let mut options: Vec = Vec::from_slice(options); let mut a = 0; let b = options.len(); @@ -116,7 +150,7 @@ fn obsolete (options: &[String]) -> (Vec, Option) { // If this is the last number if pos == len - 1 { options.remove(a); - let number : Option = from_str(from_utf8(current.slice(1,len)).unwrap()); + let number: Option = from_str(from_utf8(current.slice(1,len)).unwrap()); return (options, Some(number.unwrap())); } } @@ -128,10 +162,24 @@ fn obsolete (options: &[String]) -> (Vec, Option) { (options, None) } -fn head (reader: &mut BufferedReader, line_count:uint) { - for line in reader.lines().take(line_count) { print!("{}", line.unwrap()); } +// TODO: handle errors on read +fn head(reader: &mut BufferedReader, count: uint, use_bytes: bool) -> bool { + if use_bytes { + for byte in reader.bytes().take(count) { + if !pipe_print!("{}", byte.unwrap() as char) { + return false; + } + } + } else { + for line in reader.lines().take(count) { + if !pipe_print!("{}", line.unwrap()) { + return false; + } + } + } + true } -fn version () { +fn version() { println!("head version 1.0.0"); } diff --git a/src/hostname/hostname.rs b/src/hostname/hostname.rs index 8581b220a..56dc60f3e 100644 --- a/src/hostname/hostname.rs +++ b/src/hostname/hostname.rs @@ -12,12 +12,21 @@ * https://www.opensource.apple.com/source/shell_cmds/shell_cmds-170/hostname/hostname.c?txt */ +#![feature(macro_rules)] + extern crate getopts; extern crate libc; +use std::collections::hashmap::HashSet; +use std::io::net::addrinfo; use std::str; use getopts::{optflag, getopts, usage}; +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "hostname"; + extern { fn gethostname(name: *mut libc::c_char, namelen: libc::size_t) -> libc::c_int; } @@ -36,8 +45,10 @@ pub fn uumain(args: Vec) -> int { let program = &args[0]; let options = [ - optflag("f", "full", "Default option to show full name"), - optflag("s", "slice subdomain", "Cuts the subdomain off if any"), + optflag("d", "domain", "Display the name of the DNS domain if possible"), + optflag("i", "ip-address", "Display the network address(es) of the host"), + optflag("f", "fqdn", "Display the FQDN (Fully Qualified Domain Name) (default)"), // TODO: support --long + optflag("s", "short", "Display the short hostname (the portion before the first dot) if possible"), optflag("h", "help", "Show help"), optflag("V", "version", "Show program's version") ]; @@ -57,18 +68,49 @@ pub fn uumain(args: Vec) -> int { 0 => { let hostname = xgethostname(); - if matches.opt_present("s") { - let pos = hostname.as_slice().find_str("."); - if pos.is_some() { - println!("{:s}", hostname.as_slice().slice_to(pos.unwrap())); - return 0; + if matches.opt_present("i") { + match addrinfo::get_host_addresses(hostname.as_slice()) { + Ok(addresses) => { + let mut hashset = HashSet::new(); + let mut output = String::new(); + for addr in addresses.iter() { + // XXX: not sure why this is necessary... + if !hashset.contains(addr) { + output.push_str(addr.to_string().as_slice()); + output.push_str(" "); + hashset.insert(addr.clone()); + } + } + let len = output.len(); + if len > 0 { + println!("{}", output.as_slice().slice_to(len - 1)); + } + } + Err(f) => { + show_error!("{}", f); + return 1; + } + } + } else { + if matches.opt_present("s") { + let pos = hostname.as_slice().find_str("."); + if pos.is_some() { + println!("{:s}", hostname.as_slice().slice_to(pos.unwrap())); + return 0; + } + } else if matches.opt_present("d") { + let pos = hostname.as_slice().find_str("."); + if pos.is_some() { + println!("{}", hostname.as_slice().slice_from(pos.unwrap() + 1)); + return 0; + } } - } - println!("{:s}", hostname.as_slice()); + println!("{:s}", hostname); + } } - 1 => { xsethostname( matches.free.last().unwrap().as_slice() ) } - _ => { help_menu(program.as_slice(), options); } + 1 => xsethostname(matches.free.last().unwrap().as_slice()), + _ => help_menu(program.as_slice(), options) }; 0 diff --git a/src/seq/seq.rs b/src/seq/seq.rs index 44496f705..0f948f21b 100644 --- a/src/seq/seq.rs +++ b/src/seq/seq.rs @@ -9,27 +9,12 @@ extern crate getopts; extern crate libc; use std::cmp; -use std::io; #[path = "../common/util.rs"] mod util; static NAME: &'static str = "seq"; -macro_rules! pipe_write( - ($($args:expr),+) => ( - match write!(&mut io::stdout() as &mut Writer, $($args),+) { - Ok(_) => {} - Err(f) => - if f.kind == io::BrokenPipe { - return - } else { - fail!("{}", f.to_string()) - } - } - ) -) - #[deriving(Clone)] struct SeqOptions { separator: String, @@ -237,27 +222,35 @@ fn print_seq(first: f64, step: f64, last: f64, largest_dec: uint, separator: Str let before_dec = istr.as_slice().find('.').unwrap_or(ilen); if pad && before_dec < padding { for _ in range(0, padding - before_dec) { - pipe_write!("0"); + if !pipe_print!("0") { + return; + } } } - pipe_write!("{}", istr); + pipe_print!("{}", istr); let mut idec = ilen - before_dec; if idec < largest_dec { if idec == 0 { - pipe_write!("."); + if !pipe_print!(".") { + return; + } idec += 1; } for _ in range(idec, largest_dec) { - pipe_write!("0") + if !pipe_print!("0") { + return; + } } } i += 1; value = first + i as f64 * step; if !done_printing(value, step, last) { - pipe_write!("{:s}", separator); + if !pipe_print!("{:s}", separator) { + return; + } } } if (first >= last && step < 0f64) || (first <= last && step > 0f64) { - pipe_write!("{:s}", terminator); + pipe_print!("{:s}", terminator); } } diff --git a/src/uutils/uutils.rs b/src/uutils/uutils.rs index b59957535..23b7e8f34 100644 --- a/src/uutils/uutils.rs +++ b/src/uutils/uutils.rs @@ -54,8 +54,8 @@ fn main() { None => (), } - if binary_as_util.ends_with("uutils") - || binary_as_util.ends_with("busybox") { + if binary_as_util.ends_with("uutils") || binary_as_util.starts_with("uutils") || + binary_as_util.ends_with("busybox") || binary_as_util.starts_with("busybox") { // uutils can be called as either "uutils", "busybox" // "uutils-suffix" or "busybox-suffix". Not sure // what busybox uses the -suffix pattern for. diff --git a/src/yes/yes.rs b/src/yes/yes.rs index 73bd2c5df..cd51e745c 100644 --- a/src/yes/yes.rs +++ b/src/yes/yes.rs @@ -16,7 +16,7 @@ extern crate getopts; extern crate libc; -use std::io::{print, println}; +use std::io::print; #[path = "../common/util.rs"] mod util; @@ -60,6 +60,8 @@ pub fn uumain(args: Vec) -> int { pub fn exec(string: &str) { loop { - println(string); + if !pipe_println!("{}", string) { + break; + } } }