diff --git a/Cargo.lock b/Cargo.lock index 51424332d..f411c5920 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2570,6 +2570,7 @@ version = "0.0.6" dependencies = [ "clap", "libc", + "nix 0.20.0", "redox_syscall 0.1.57", "uucore", "uucore_procs", diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 273c67bb3..47db8dc6b 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -24,6 +24,10 @@ winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", [target.'cfg(target_os = "redox")'.dependencies] redox_syscall = "0.1" +[target.'cfg(unix)'.dependencies] +nix = "0.20" +libc = "0.2" + [[bin]] name = "tail" path = "src/main.rs" diff --git a/src/uu/tail/README.md b/src/uu/tail/README.md index b7f92f8e4..94b6816af 100644 --- a/src/uu/tail/README.md +++ b/src/uu/tail/README.md @@ -11,7 +11,7 @@ ### Others -- [ ] The current implementation does not handle `-` as an alias for stdin. +- [ ] The current implementation doesn't follow stdin in non-unix platforms ## Possible optimizations diff --git a/src/uu/tail/src/platform/mod.rs b/src/uu/tail/src/platform/mod.rs index 010c5c4ac..4a8982713 100644 --- a/src/uu/tail/src/platform/mod.rs +++ b/src/uu/tail/src/platform/mod.rs @@ -2,13 +2,14 @@ * This file is part of the uutils coreutils package. * * (c) Alexander Batischev + * (c) Thomas Queiroz * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #[cfg(unix)] -pub use self::unix::{supports_pid_checks, Pid, ProcessChecker}; +pub use self::unix::{stdin_is_pipe_or_fifo, supports_pid_checks, Pid, ProcessChecker}; #[cfg(windows)] pub use self::windows::{supports_pid_checks, Pid, ProcessChecker}; diff --git a/src/uu/tail/src/platform/unix.rs b/src/uu/tail/src/platform/unix.rs index 167f693e6..580a40135 100644 --- a/src/uu/tail/src/platform/unix.rs +++ b/src/uu/tail/src/platform/unix.rs @@ -2,6 +2,7 @@ * This file is part of the uutils coreutils package. * * (c) Alexander Batischev + * (c) Thomas Queiroz * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -9,7 +10,13 @@ // spell-checker:ignore (ToDO) errno EPERM ENOSYS -use std::io::Error; +use std::io::{stdin, Error}; + +use std::os::unix::prelude::AsRawFd; + +use nix::sys::stat::fstat; + +use libc::{S_IFIFO, S_IFSOCK}; pub type Pid = libc::pid_t; @@ -40,3 +47,16 @@ pub fn supports_pid_checks(pid: self::Pid) -> bool { fn get_errno() -> i32 { Error::last_os_error().raw_os_error().unwrap() } + +pub fn stdin_is_pipe_or_fifo() -> bool { + let fd = stdin().lock().as_raw_fd(); + fd >= 0 // GNU tail checks fd >= 0 + && match fstat(fd) { + Ok(stat) => { + let mode = stat.st_mode; + // NOTE: This is probably not the most correct way to check this + (mode & S_IFIFO != 0) || (mode & S_IFSOCK != 0) + } + Err(err) => panic!("{}", err), + } +} diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 4970cdcc2..471c1a404 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -2,6 +2,7 @@ // * // * (c) Morten Olsen Lysgaard // * (c) Alexander Batischev +// * (c) Thomas Queiroz // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. @@ -29,6 +30,9 @@ use std::time::Duration; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::ringbuffer::RingBuffer; +#[cfg(unix)] +use crate::platform::stdin_is_pipe_or_fifo; + pub mod options { pub mod verbosity { pub static QUIET: &str = "quiet"; @@ -130,25 +134,56 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let files: Vec = matches .values_of(options::ARG_FILES) .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); + .unwrap_or_else(|| vec![String::from("-")]); - if files.is_empty() { - let mut buffer = BufReader::new(stdin()); - unbounded_tail(&mut buffer, &settings); - } else { - let multiple = files.len() > 1; - let mut first_header = true; - let mut readers = Vec::new(); + let multiple = files.len() > 1; + let mut first_header = true; + let mut readers: Vec<(Box, &String)> = Vec::new(); - for filename in &files { - if (multiple || verbose) && !quiet { - if !first_header { - println!(); - } + #[cfg(unix)] + let stdin_string = String::from("standard input"); + + for filename in &files { + let use_stdin = filename.as_str() == "-"; + if (multiple || verbose) && !quiet { + if !first_header { + println!(); + } + if use_stdin { + println!("==> standard input <=="); + } else { println!("==> {} <==", filename); } - first_header = false; + } + first_header = false; + if use_stdin { + let mut reader = BufReader::new(stdin()); + unbounded_tail(&mut reader, &settings); + + // Don't follow stdin since there are no checks for pipes/FIFOs + // + // FIXME windows has GetFileType which can determine if the file is a pipe/FIFO + // so this check can also be performed + + #[cfg(unix)] + { + /* + POSIX specification regarding tail -f + + If the input file is a regular file or if the file operand specifies a FIFO, do not + terminate after the last line of the input file has been copied, but read and copy + further bytes from the input file when they become available. If no file operand is + specified and standard input is a pipe or FIFO, the -f option shall be ignored. If + the input file is not a FIFO, pipe, or regular file, it is unspecified whether or + not the -f option shall be ignored. + */ + + if settings.follow && !stdin_is_pipe_or_fifo() { + readers.push((Box::new(reader), &stdin_string)); + } + } + } else { let path = Path::new(filename); if path.is_dir() { continue; @@ -158,20 +193,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { bounded_tail(&mut file, &settings); if settings.follow { let reader = BufReader::new(file); - readers.push(reader); + readers.push((Box::new(reader), filename)); } } else { let mut reader = BufReader::new(file); unbounded_tail(&mut reader, &settings); if settings.follow { - readers.push(reader); + readers.push((Box::new(reader), filename)); } } } + } - if settings.follow { - follow(&mut readers[..], &files[..], &settings); - } + if settings.follow { + follow(&mut readers[..], &settings); } 0 @@ -248,8 +283,12 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn follow(readers: &mut [BufReader], filenames: &[String], settings: &Settings) { +fn follow(readers: &mut [(T, &String)], settings: &Settings) { assert!(settings.follow); + if readers.is_empty() { + return; + } + let mut last = readers.len() - 1; let mut read_some = false; let mut process = platform::ProcessChecker::new(settings.pid); @@ -260,7 +299,7 @@ fn follow(readers: &mut [BufReader], filenames: &[String], settings: let pid_is_dead = !read_some && settings.pid != 0 && process.is_dead(); read_some = false; - for (i, reader) in readers.iter_mut().enumerate() { + for (i, (reader, filename)) in readers.iter_mut().enumerate() { // Print all new content since the last pass loop { let mut datum = String::new(); @@ -269,7 +308,7 @@ fn follow(readers: &mut [BufReader], filenames: &[String], settings: Ok(_) => { read_some = true; if i != last { - println!("\n==> {} <==", filenames[i]); + println!("\n==> {} <==", filename); last = i; } print!("{}", datum);