diff --git a/src/tail/README.md b/src/tail/README.md index 2536e1639..49a7b4011 100644 --- a/src/tail/README.md +++ b/src/tail/README.md @@ -3,7 +3,8 @@ Rudimentary tail implementation. ##Missing features: ### Flags with features -* `--bytes` : output the last K bytes; alternatively, use `-c` +K to output bytes starting with the Kth of each file +* `--bytes` : does not handle size suffixes +* `--lines` : does not handle size suffixes * `--max-unchanged-stats` : with `--follow=name`, reopen a FILE which has not changed size after N (default 5) iterations to see if it has been unlinked or renamed (this is the usual case of rotated log files). With inotify, this option is rarely useful. * `--pid` : with `-f`, terminate after process ID, PID dies * `--quiet` : never output headers giving file names diff --git a/src/tail/tail.rs b/src/tail/tail.rs index 712da2a04..ec1a6c164 100644 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -9,6 +9,8 @@ * */ +#![feature(macro_rules)] + extern crate getopts; use std::char; @@ -23,9 +25,16 @@ use std::collections::ringbuf::RingBuf; use std::io::timer::sleep; use std::time::duration::Duration; -static PROGRAM: &'static str = "tail"; +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "tail"; +static VERSION: &'static str = "0.0.1"; pub fn uumain(args: Vec) -> int { + let mut beginning = false; + let mut lines = true; + let mut byte_count = 0u; let mut line_count = 10u; let mut sleep_msec = 1000u64; @@ -38,7 +47,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", "Number of bytes to print", "k"), + optopt("n", "lines", "Number of lines to print", "k"), optflag("f", "follow", "Print the file as it grows"), optopt("s", "sleep-interval", "Number or seconds to sleep between polling the file when running with -f", "n"), optflag("h", "help", "help"), @@ -48,13 +58,13 @@ 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 } @@ -63,9 +73,9 @@ pub fn uumain(args: Vec) -> int { if follow { match given_options.opt_str("s") { Some(n) => { - let parsed : Option = from_str(n.as_slice()); + let parsed: Option = from_str(n.as_slice()); match parsed { - Some(m) => { sleep_msec = m*1000 } + Some(m) => { sleep_msec = m * 1000 } None => {} } } @@ -75,19 +85,44 @@ pub fn uumain(args: Vec) -> int { match given_options.opt_str("n") { Some(n) => { - match from_str(n.as_slice()) { - Some(m) => { line_count = m } - None => {} + let mut slice = n.as_slice(); + if slice.len() > 0 && slice.char_at(0) == '+' { + beginning = true; + slice = slice.slice_from(1); } + line_count = match from_str(slice) { + Some(m) => m, + None => { + show_error!("invalid number of lines ({})", slice); + return 1; + } + }; + } + None => match given_options.opt_str("c") { + Some(n) => { + let mut slice = n.as_slice(); + if slice.len() > 0 && slice.char_at(0) == '+' { + beginning = true; + slice = slice.slice_from(1); + } + byte_count = match from_str(slice) { + Some(m) => m, + None => { + show_error!("invalid number of bytes ({})", slice); + return 1; + } + }; + lines = false; + } + None => { } } - None => {} }; let files = given_options.free; if files.is_empty() { let mut buffer = BufferedReader::new(stdin()); - tail(&mut buffer, line_count, follow, sleep_msec); + tail(&mut buffer, line_count, byte_count, beginning, lines, follow, sleep_msec); } else { let mut multiple = false; let mut firstime = true; @@ -107,7 +142,7 @@ pub fn uumain(args: Vec) -> int { let path = Path::new(file.as_slice()); let reader = File::open(&path).unwrap(); let mut buffer = BufferedReader::new(reader); - tail(&mut buffer, line_count, follow, sleep_msec); + tail(&mut buffer, line_count, byte_count, beginning, lines, follow, sleep_msec); } } @@ -118,7 +153,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 = options.to_vec(); let mut a = 0; let b = options.len(); @@ -136,7 +171,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())); } } @@ -148,24 +183,11 @@ fn obsolete (options: &[String]) -> (Vec, Option) { (options, None) } -fn tail (reader: &mut BufferedReader, line_count:uint, follow:bool, sleep_msec:u64) { - // read through each line and store them in a ringbuffer that always contains - // line_count lines. When reaching the end of file, output the lines in the - // ringbuf. - let mut ringbuf : RingBuf = RingBuf::new(); - for io_line in reader.lines(){ - match io_line { - Ok(line) => { - if line_count<=ringbuf.len(){ - ringbuf.pop_front(); - } - ringbuf.push(line); - } - Err(err) => fail!(err) - } - } - for line in ringbuf.iter() { - print!("{}", line); +fn tail(reader: &mut BufferedReader, line_count: uint, byte_count: uint, beginning: bool, lines: bool, follow: bool, sleep_msec: u64) { + if lines { + tail_lines(reader, line_count, beginning); + } else { + tail_bytes(reader, byte_count, beginning); } // if we follow the file, sleep a bit and print the rest if the file has grown. @@ -180,6 +202,65 @@ fn tail (reader: &mut BufferedReader, line_count:uint, follow:bool } } -fn version () { - println!("tail version 0.0.1"); +#[inline] +fn tail_lines(reader: &mut BufferedReader, mut line_count: uint, beginning: bool) { + // read through each line and store them in a ringbuffer that always contains + // line_count lines. When reaching the end of file, output the lines in the + // ringbuf. + let mut ringbuf: RingBuf = RingBuf::new(); + let mut lines = reader.lines().skip( + if beginning { + let temp = line_count; + line_count = ::std::uint::MAX; + temp - 1 + } else { + 0 + } + ); + for io_line in lines { + match io_line { + Ok(line) => { + if line_count <= ringbuf.len() { + ringbuf.pop_front(); + } + ringbuf.push(line); + } + Err(err) => fail!(err) + } + } + for line in ringbuf.iter() { + print!("{}", line); + } +} + +#[inline] +fn tail_bytes(reader: &mut BufferedReader, mut byte_count: uint, beginning: bool) { + let mut ringbuf: RingBuf = RingBuf::new(); + let mut bytes = reader.bytes().skip( + if beginning { + let temp = byte_count; + byte_count = ::std::uint::MAX; + temp - 1 + } else { + 0 + } + ); + for io_byte in bytes { + match io_byte { + Ok(byte) => { + if byte_count <= ringbuf.len() { + ringbuf.pop_front(); + } + ringbuf.push(byte); + } + Err(err) => fail!(err) + } + } + for byte in ringbuf.iter() { + print!("{}", byte); + } +} + +fn version () { + println!("{} v{}", NAME, VERSION); }