tail: handle - as an alias for stdin

This commit is contained in:
Thomas Queiroz 2021-06-29 19:20:01 -03:00
parent 2428a1ccfb
commit 940559f0e1
6 changed files with 90 additions and 25 deletions

1
Cargo.lock generated
View file

@ -2570,6 +2570,7 @@ version = "0.0.6"
dependencies = [
"clap",
"libc",
"nix 0.20.0",
"redox_syscall 0.1.57",
"uucore",
"uucore_procs",

View file

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

View file

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

View file

@ -2,13 +2,14 @@
* This file is part of the uutils coreutils package.
*
* (c) Alexander Batischev <eual.jp@gmail.com>
* (c) Thomas Queiroz <thomasqueirozb@gmail.com>
*
* 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};

View file

@ -2,6 +2,7 @@
* This file is part of the uutils coreutils package.
*
* (c) Alexander Batischev <eual.jp@gmail.com>
* (c) Thomas Queiroz <thomasqueirozb@gmail.com>
*
* 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),
}
}

View file

@ -2,6 +2,7 @@
// *
// * (c) Morten Olsen Lysgaard <morten@lysgaard.no>
// * (c) Alexander Batischev <eual.jp@gmail.com>
// * (c) Thomas Queiroz <thomasqueirozb@gmail.com>
// *
// * 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<String> = 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<dyn BufRead>, &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<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings: &Settings) {
fn follow<T: BufRead>(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<T: Read>(readers: &mut [BufReader<T>], 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<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings:
Ok(_) => {
read_some = true;
if i != last {
println!("\n==> {} <==", filenames[i]);
println!("\n==> {} <==", filename);
last = i;
}
print!("{}", datum);