From 21328899404366331d8c47bc7fc1bfcb325125d1 Mon Sep 17 00:00:00 2001 From: Mariano Casco Date: Mon, 30 May 2016 13:28:50 -0300 Subject: [PATCH 1/6] tail: don't follow() as part of bounded_tail To get the -f option to follow multiple files, bounded_tail should just tail a single file and return, instead of blocking processing of other files by calling follow() (which loops forever). --- src/tail/tail.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/tail/tail.rs b/src/tail/tail.rs index c22177498..41c4cac72 100755 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -357,14 +357,6 @@ fn backwards_thru_file(file: &mut File, size: u64, buf: &mut Vec, delimit /// being a nice performance win for very large files. fn bounded_tail(mut file: File, settings: &Settings) { let size = file.seek(SeekFrom::End(0)).unwrap(); - if size == 0 { - if settings.follow { - let reader = BufReader::new(file); - follow(reader, settings); - } - return; - } - let mut buf = vec![0; BLOCK_SIZE as usize]; // Find the position in the file to start printing from. @@ -397,12 +389,6 @@ fn bounded_tail(mut file: File, settings: &Settings) { break; } } - - // Continue following changes, if requested. - if settings.follow { - let reader = BufReader::new(file); - follow(reader, settings); - } } fn unbounded_tail(mut reader: BufReader, settings: &Settings) { From 966bfde70f322f2f8c4f728d5b8a2ad91ead5099 Mon Sep 17 00:00:00 2001 From: Mariano Casco Date: Mon, 30 May 2016 15:40:17 -0300 Subject: [PATCH 2/6] tail: follow multiple files If multiple files are passed as arguments with the -f option, a vector of BufReaders is built as the files are first tailed, so that follow() can take control for the rest of the time the program is running. follow() loops over each reader and prints all new available content on each file before moving on to the next. --- src/tail/tail.rs | 54 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/tail/tail.rs b/src/tail/tail.rs index 41c4cac72..a9f66f3a5 100755 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -147,22 +147,29 @@ pub fn uumain(args: Vec) -> i32 { unbounded_tail(buffer, &settings); } else { let mut multiple = false; - let mut firstime = true; + let mut readers = Vec::new(); if files.len() > 1 { multiple = true; } - for file in &files { + for filename in &files { if multiple { - if !firstime { println!(""); } - println!("==> {} <==", file); + println!("==> {} <==", filename); } - firstime = false; - let path = Path::new(file); - let reader = File::open(&path).unwrap(); - bounded_tail(reader, &settings); + let path = Path::new(filename); + let file = File::open(&path).unwrap(); + bounded_tail(&file, &settings); + + if settings.follow { + let reader = BufReader::new(file); + readers.push(reader); + } + } + + if settings.follow { + follow(readers, &settings); } } @@ -294,16 +301,25 @@ fn obsolete(options: &[String]) -> (Vec, Option) { /// block read at a time. const BLOCK_SIZE: u64 = 1 << 16; -fn follow(mut reader: BufReader, settings: &Settings) { +fn follow(mut readers: Vec>, settings: &Settings) { assert!(settings.follow); + let mut last = readers.len(); + loop { sleep(Duration::new(0, settings.sleep_msec*1000)); - loop { - let mut datum = String::new(); - match reader.read_line(&mut datum) { - Ok(0) => break, - Ok(_) => print!("{}", datum), - Err(err) => panic!(err) + + for reader in &mut readers { + // Print all new content since the last pass + loop { + let mut datum = String::new(); + match reader.read_line(&mut datum) { + Ok(0) => break, + Ok(_) => { + // TODO: Print headers if i != last + print!("{}", datum); + }, + Err(err) => panic!(err) + } } } } @@ -312,7 +328,7 @@ fn follow(mut reader: BufReader, settings: &Settings) { /// Iterate over bytes in the file, in reverse, until `should_stop` returns /// true. The `file` is left seek'd to the position just after the byte that /// `should_stop` returned true for. -fn backwards_thru_file(file: &mut File, size: u64, buf: &mut Vec, delimiter: u8, should_stop: &mut F) +fn backwards_thru_file(mut file: &File, size: u64, buf: &mut Vec, delimiter: u8, should_stop: &mut F) where F: FnMut(u8) -> bool { assert!(buf.len() >= BLOCK_SIZE as usize); @@ -355,7 +371,7 @@ fn backwards_thru_file(file: &mut File, size: u64, buf: &mut Vec, delimit /// end of the file, and then read the file "backwards" in blocks of size /// `BLOCK_SIZE` until we find the location of the first line/byte. This ends up /// being a nice performance win for very large files. -fn bounded_tail(mut file: File, settings: &Settings) { +fn bounded_tail(mut file: &File, settings: &Settings) { let size = file.seek(SeekFrom::End(0)).unwrap(); let mut buf = vec![0; BLOCK_SIZE as usize]; @@ -460,8 +476,10 @@ fn unbounded_tail(mut reader: BufReader, settings: &Settings) { } } + // TODO: make following stdin work with the new follow() signature + // maybe wrap stdin in a 1-element vec? if settings.follow { - follow(reader, settings); + //follow(reader, settings); } } From 8866e05e984a53180cc9223ceaa93dedce6dbdf3 Mon Sep 17 00:00:00 2001 From: Mariano Casco Date: Mon, 30 May 2016 15:54:31 -0300 Subject: [PATCH 3/6] tail: print headers when following multiple files Before each line of content is printed, check if it's from a different file than the last one we printed for. If so, print a '==> file <==' header to separate the output in the way tail does. --- src/tail/tail.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/tail/tail.rs b/src/tail/tail.rs index a9f66f3a5..70e8ab5ea 100755 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -169,7 +169,7 @@ pub fn uumain(args: Vec) -> i32 { } if settings.follow { - follow(readers, &settings); + follow(readers, &files, &settings); } } @@ -301,21 +301,25 @@ fn obsolete(options: &[String]) -> (Vec, Option) { /// block read at a time. const BLOCK_SIZE: u64 = 1 << 16; -fn follow(mut readers: Vec>, settings: &Settings) { +fn follow(mut readers: Vec>, filenames: &Vec, settings: &Settings) { assert!(settings.follow); let mut last = readers.len(); loop { sleep(Duration::new(0, settings.sleep_msec*1000)); - for reader in &mut readers { + for (i, reader) in readers.iter_mut().enumerate() { // Print all new content since the last pass loop { let mut datum = String::new(); match reader.read_line(&mut datum) { Ok(0) => break, Ok(_) => { - // TODO: Print headers if i != last + if i != last { + println!(""); + println!("==> {} <==", filenames[i]); + last = i; + } print!("{}", datum); }, Err(err) => panic!(err) From 440fb867bc35cbef4880bd5111d84fd026db4d52 Mon Sep 17 00:00:00 2001 From: Mariano Casco Date: Mon, 30 May 2016 16:43:14 -0300 Subject: [PATCH 4/6] tail: no headers when following a single file Headers should only be printed when following more than one file. This commit makes the test_follow() test pass again. --- src/tail/tail.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tail/tail.rs b/src/tail/tail.rs index 70e8ab5ea..ab526ec97 100755 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -303,7 +303,7 @@ const BLOCK_SIZE: u64 = 1 << 16; fn follow(mut readers: Vec>, filenames: &Vec, settings: &Settings) { assert!(settings.follow); - let mut last = readers.len(); + let mut last = readers.len() - 1; loop { sleep(Duration::new(0, settings.sleep_msec*1000)); From f9627e02d0987551f80d68447b65bc05ba7d19aa Mon Sep 17 00:00:00 2001 From: Mariano Casco Date: Mon, 30 May 2016 17:32:57 -0300 Subject: [PATCH 5/6] tail: print empty line between headers --- src/tail/tail.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tail/tail.rs b/src/tail/tail.rs index ab526ec97..a806af722 100755 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -147,6 +147,7 @@ pub fn uumain(args: Vec) -> i32 { unbounded_tail(buffer, &settings); } else { let mut multiple = false; + let mut first_header = true; let mut readers = Vec::new(); if files.len() > 1 { @@ -155,8 +156,10 @@ pub fn uumain(args: Vec) -> i32 { for filename in &files { if multiple { + if !first_header { println!(""); } println!("==> {} <==", filename); } + first_header = false; let path = Path::new(filename); let file = File::open(&path).unwrap(); From 9c584bab9d8f454778a584648abe7682ea405982 Mon Sep 17 00:00:00 2001 From: Mariano Casco Date: Mon, 30 May 2016 17:34:53 -0300 Subject: [PATCH 6/6] tail: test following multiple files The test_follow_multiple() test verifies that input is read upon append on both files and that headers are printed when expected. --- tests/fixtures/tail/foobar2.txt | 2 ++ .../tail/foobar_follow_multiple.expected | 15 +++++++++++++ .../foobar_follow_multiple_appended.expected | 4 ++++ tests/test_tail.rs | 21 +++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 tests/fixtures/tail/foobar2.txt create mode 100644 tests/fixtures/tail/foobar_follow_multiple.expected create mode 100644 tests/fixtures/tail/foobar_follow_multiple_appended.expected diff --git a/tests/fixtures/tail/foobar2.txt b/tests/fixtures/tail/foobar2.txt new file mode 100644 index 000000000..25973d326 --- /dev/null +++ b/tests/fixtures/tail/foobar2.txt @@ -0,0 +1,2 @@ +un +deux diff --git a/tests/fixtures/tail/foobar_follow_multiple.expected b/tests/fixtures/tail/foobar_follow_multiple.expected new file mode 100644 index 000000000..140a9088d --- /dev/null +++ b/tests/fixtures/tail/foobar_follow_multiple.expected @@ -0,0 +1,15 @@ +==> foobar.txt <== +dos +tres +quattro +cinco +seis +siette +ocho +nueve +diez +once + +==> foobar2.txt <== +un +deux diff --git a/tests/fixtures/tail/foobar_follow_multiple_appended.expected b/tests/fixtures/tail/foobar_follow_multiple_appended.expected new file mode 100644 index 000000000..0896d3743 --- /dev/null +++ b/tests/fixtures/tail/foobar_follow_multiple_appended.expected @@ -0,0 +1,4 @@ + +==> foobar.txt <== +doce +trece diff --git a/tests/test_tail.rs b/tests/test_tail.rs index fbd527b39..c842354d1 100644 --- a/tests/test_tail.rs +++ b/tests/test_tail.rs @@ -8,6 +8,7 @@ use uu_tail::parse_size; static UTIL_NAME: &'static str = "tail"; static FOOBAR_TXT: &'static str = "foobar.txt"; +static FOOBAR_2_TXT: &'static str = "foobar2.txt"; static FOOBAR_WITH_NULL_TXT: &'static str = "foobar_with_null.txt"; #[test] @@ -56,6 +57,26 @@ fn test_follow() { child.kill().unwrap(); } +#[test] +fn test_follow_multiple() { + let (at, mut ucmd) = testing(UTIL_NAME); + let mut child = ucmd.arg("-f").arg(FOOBAR_TXT).arg(FOOBAR_2_TXT).run_no_wait(); + + let expected = at.read("foobar_follow_multiple.expected"); + assert_eq!(read_size(&mut child, expected.len()), expected); + + let first_append = "trois\n"; + at.append(FOOBAR_2_TXT, first_append); + assert_eq!(read_size(&mut child, first_append.len()), first_append); + + let second_append = "doce\ntrece\n"; + let expected = at.read("foobar_follow_multiple_appended.expected"); + at.append(FOOBAR_TXT, second_append); + assert_eq!(read_size(&mut child, expected.len()), expected); + + child.kill().unwrap(); +} + #[test] fn test_single_big_args() { const FILE: &'static str = "single_big_args.txt";