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:
commit
24b1822cba
8 changed files with 102 additions and 25 deletions
|
@ -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
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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!()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue