Merge pull request #402 from Arcterus/tail-busybox

Modify tail to pass some Busybox tests
This commit is contained in:
Heather 2014-09-26 12:52:32 +04:00
commit c453d493fb
2 changed files with 117 additions and 35 deletions

View file

@ -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

View file

@ -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<String>) -> 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<String>) -> 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<String>) -> 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 }
@ -75,19 +85,44 @@ pub fn uumain(args: Vec<String>) -> 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 => { }
}
};
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<String>) -> 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);
}
}
@ -148,24 +183,11 @@ fn obsolete (options: &[String]) -> (Vec<String>, Option<uint>) {
(options, None)
}
fn tail<T: Reader> (reader: &mut BufferedReader<T>, 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<String> = 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<T: Reader>(reader: &mut BufferedReader<T>, 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<T: Reader> (reader: &mut BufferedReader<T>, line_count:uint, follow:bool
}
}
fn version () {
println!("tail version 0.0.1");
#[inline]
fn tail_lines<T: Reader>(reader: &mut BufferedReader<T>, 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<String> = 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<T: Reader>(reader: &mut BufferedReader<T>, mut byte_count: uint, beginning: bool) {
let mut ringbuf: RingBuf<u8> = 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);
}