1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #2468 from thomasqueirozb/tail_stdin

tail: handle `-` as an alias for stdin
This commit is contained in:
Sylvestre Ledru 2021-08-02 00:24:28 +02:00 committed by GitHub
commit 24b1822cba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 102 additions and 25 deletions

View file

@ -151,6 +151,9 @@ Sylvestre Ledru
T Jameson Little T Jameson Little
Jameson Jameson
Little Little
Thomas Queiroz
Thomas
Queiroz
Tobias Bohumir Schottdorf Tobias Bohumir Schottdorf
Tobias Tobias
Bohumir Bohumir

1
Cargo.lock generated
View file

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

View file

@ -24,6 +24,10 @@ winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi",
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1" redox_syscall = "0.1"
[target.'cfg(unix)'.dependencies]
nix = "0.20"
libc = "0.2"
[[bin]] [[bin]]
name = "tail" name = "tail"
path = "src/main.rs" path = "src/main.rs"

View file

@ -11,7 +11,7 @@
### Others ### 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 ## Possible optimizations

View file

@ -2,13 +2,14 @@
* This file is part of the uutils coreutils package. * This file is part of the uutils coreutils package.
* *
* (c) Alexander Batischev <eual.jp@gmail.com> * (c) Alexander Batischev <eual.jp@gmail.com>
* (c) Thomas Queiroz <thomasqueirozb@gmail.com>
* *
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
#[cfg(unix)] #[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)] #[cfg(windows)]
pub use self::windows::{supports_pid_checks, Pid, ProcessChecker}; pub use self::windows::{supports_pid_checks, Pid, ProcessChecker};

View file

@ -2,6 +2,7 @@
* This file is part of the uutils coreutils package. * This file is part of the uutils coreutils package.
* *
* (c) Alexander Batischev <eual.jp@gmail.com> * (c) Alexander Batischev <eual.jp@gmail.com>
* (c) Thomas Queiroz <thomasqueirozb@gmail.com>
* *
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
@ -9,7 +10,13 @@
// spell-checker:ignore (ToDO) errno EPERM ENOSYS // 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; pub type Pid = libc::pid_t;
@ -40,3 +47,16 @@ pub fn supports_pid_checks(pid: self::Pid) -> bool {
fn get_errno() -> i32 { fn get_errno() -> i32 {
Error::last_os_error().raw_os_error().unwrap() 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) Morten Olsen Lysgaard <morten@lysgaard.no>
// * (c) Alexander Batischev <eual.jp@gmail.com> // * (c) Alexander Batischev <eual.jp@gmail.com>
// * (c) Thomas Queiroz <thomasqueirozb@gmail.com>
// * // *
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * 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::parse_size::{parse_size, ParseSizeError};
use uucore::ringbuffer::RingBuffer; use uucore::ringbuffer::RingBuffer;
#[cfg(unix)]
use crate::platform::stdin_is_pipe_or_fifo;
pub mod options { pub mod options {
pub mod verbosity { pub mod verbosity {
pub static QUIET: &str = "quiet"; pub static QUIET: &str = "quiet";
@ -130,25 +134,56 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let files: Vec<String> = matches let files: Vec<String> = matches
.values_of(options::ARG_FILES) .values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_else(|| vec![String::from("-")]);
if files.is_empty() { let multiple = files.len() > 1;
let mut buffer = BufReader::new(stdin()); let mut first_header = true;
unbounded_tail(&mut buffer, &settings); let mut readers: Vec<(Box<dyn BufRead>, &String)> = Vec::new();
} else {
let multiple = files.len() > 1;
let mut first_header = true;
let mut readers = Vec::new();
for filename in &files { #[cfg(unix)]
if (multiple || verbose) && !quiet { let stdin_string = String::from("standard input");
if !first_header {
println!(); 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); 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); let path = Path::new(filename);
if path.is_dir() { if path.is_dir() {
continue; continue;
@ -158,20 +193,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
bounded_tail(&mut file, &settings); bounded_tail(&mut file, &settings);
if settings.follow { if settings.follow {
let reader = BufReader::new(file); let reader = BufReader::new(file);
readers.push(reader); readers.push((Box::new(reader), filename));
} }
} else { } else {
let mut reader = BufReader::new(file); let mut reader = BufReader::new(file);
unbounded_tail(&mut reader, &settings); unbounded_tail(&mut reader, &settings);
if settings.follow { if settings.follow {
readers.push(reader); readers.push((Box::new(reader), filename));
} }
} }
} }
}
if settings.follow { if settings.follow {
follow(&mut readers[..], &files[..], &settings); follow(&mut readers[..], &settings);
}
} }
0 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); assert!(settings.follow);
if readers.is_empty() {
return;
}
let mut last = readers.len() - 1; let mut last = readers.len() - 1;
let mut read_some = false; let mut read_some = false;
let mut process = platform::ProcessChecker::new(settings.pid); 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(); let pid_is_dead = !read_some && settings.pid != 0 && process.is_dead();
read_some = false; 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 // Print all new content since the last pass
loop { loop {
let mut datum = String::new(); let mut datum = String::new();
@ -269,7 +308,7 @@ fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings:
Ok(_) => { Ok(_) => {
read_some = true; read_some = true;
if i != last { if i != last {
println!("\n==> {} <==", filenames[i]); println!("\n==> {} <==", filename);
last = i; last = i;
} }
print!("{}", datum); print!("{}", datum);

View file

@ -23,6 +23,15 @@ fn test_stdin_default() {
.stdout_is_fixture("foobar_stdin_default.expected"); .stdout_is_fixture("foobar_stdin_default.expected");
} }
#[test]
fn test_stdin_explicit() {
new_ucmd!()
.pipe_in_fixture(FOOBAR_TXT)
.arg("-")
.run()
.stdout_is_fixture("foobar_stdin_default.expected");
}
#[test] #[test]
fn test_single_default() { fn test_single_default() {
new_ucmd!() new_ucmd!()