mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #1015 from Minoru/feature/846-tail-pid
tail: implement --pid
This commit is contained in:
commit
65b46314a2
8 changed files with 205 additions and 2 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -942,8 +942,10 @@ name = "tail"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uucore 0.0.1",
|
"uucore 0.0.1",
|
||||||
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -9,7 +9,9 @@ path = "tail.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "*"
|
getopts = "*"
|
||||||
|
kernel32-sys = "*"
|
||||||
libc = "*"
|
libc = "*"
|
||||||
|
winapi = "*"
|
||||||
uucore = { path="../uucore" }
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -4,7 +4,6 @@ Rudimentary tail implementation.
|
||||||
|
|
||||||
### Flags with features
|
### Flags with features
|
||||||
* `--max-unchanged-stats` : with `--follow=name`, reopen a FILE which has not changed size after N (default 5) iterations to see if it has been unlinked or renamed (this is the usual case of rotated log files). With inotify, this option is rarely useful.
|
* `--max-unchanged-stats` : with `--follow=name`, reopen a FILE which has not changed size after N (default 5) iterations to see if it has been unlinked or renamed (this is the usual case of rotated log files). With inotify, this option is rarely useful.
|
||||||
* `--pid` : with `-f`, terminate after process ID, PID dies
|
|
||||||
* `--quiet` : never output headers giving file names
|
* `--quiet` : never output headers giving file names
|
||||||
* `--retry` : keep trying to open a file even when it is or becomes inaccessible; useful when follow‐ing by name, i.e., with `--follow=name`
|
* `--retry` : keep trying to open a file even when it is or becomes inaccessible; useful when follow‐ing by name, i.e., with `--follow=name`
|
||||||
|
|
||||||
|
|
20
src/tail/platform/mod.rs
Normal file
20
src/tail/platform/mod.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the uutils coreutils package.
|
||||||
|
*
|
||||||
|
* (c) Alexander Batischev <eual.jp@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::{Pid, supports_pid_checks, ProcessChecker};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub use self::windows::{Pid, supports_pid_checks, ProcessChecker};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
mod unix;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows;
|
45
src/tail/platform/unix.rs
Normal file
45
src/tail/platform/unix.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the uutils coreutils package.
|
||||||
|
*
|
||||||
|
* (c) Alexander Batischev <eual.jp@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
use std::io::Error;
|
||||||
|
|
||||||
|
pub type Pid = libc::pid_t;
|
||||||
|
|
||||||
|
pub struct ProcessChecker { pid: self::Pid }
|
||||||
|
|
||||||
|
impl ProcessChecker {
|
||||||
|
pub fn new(process_id: self::Pid) -> ProcessChecker {
|
||||||
|
ProcessChecker { pid: process_id }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrowing mutably to be aligned with Windows implementation
|
||||||
|
pub fn is_dead(&mut self) -> bool {
|
||||||
|
unsafe {
|
||||||
|
libc::kill(self.pid, 0) != 0 && get_errno() != libc::EPERM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ProcessChecker {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn supports_pid_checks(pid: self::Pid) -> bool {
|
||||||
|
unsafe {
|
||||||
|
!(libc::kill(pid, 0) != 0 && get_errno() == libc::ENOSYS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_errno() -> i32 {
|
||||||
|
Error::last_os_error().raw_os_error().unwrap()
|
||||||
|
}
|
57
src/tail/platform/windows.rs
Normal file
57
src/tail/platform/windows.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the uutils coreutils package.
|
||||||
|
*
|
||||||
|
* (c) Alexander Batischev <eual.jp@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern crate winapi;
|
||||||
|
extern crate kernel32;
|
||||||
|
|
||||||
|
use self::kernel32::{OpenProcess, CloseHandle, WaitForSingleObject};
|
||||||
|
use self::winapi::minwindef::DWORD;
|
||||||
|
use self::winapi::winbase::{WAIT_OBJECT_0, WAIT_FAILED};
|
||||||
|
use self::winapi::winnt::{HANDLE, SYNCHRONIZE};
|
||||||
|
|
||||||
|
pub type Pid = DWORD;
|
||||||
|
|
||||||
|
pub struct ProcessChecker {
|
||||||
|
dead: bool,
|
||||||
|
handle: HANDLE
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessChecker {
|
||||||
|
pub fn new(process_id: self::Pid) -> ProcessChecker {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let FALSE = 0i32;
|
||||||
|
let h = unsafe {
|
||||||
|
OpenProcess(SYNCHRONIZE, FALSE, process_id as DWORD)
|
||||||
|
};
|
||||||
|
ProcessChecker { dead: h.is_null(), handle: h }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_dead(&mut self) -> bool {
|
||||||
|
if !self.dead {
|
||||||
|
self.dead = unsafe {
|
||||||
|
let status = WaitForSingleObject(self.handle, 0);
|
||||||
|
status == WAIT_OBJECT_0 || status == WAIT_FAILED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ProcessChecker {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
CloseHandle(self.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn supports_pid_checks(_pid: self::Pid) -> bool {
|
||||||
|
true
|
||||||
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
* This file is part of the uutils coreutils package.
|
* This file is part of the uutils coreutils package.
|
||||||
*
|
*
|
||||||
* (c) Morten Olsen Lysgaard <morten@lysgaard.no>
|
* (c) Morten Olsen Lysgaard <morten@lysgaard.no>
|
||||||
|
* (c) Alexander Batischev <eual.jp@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.
|
||||||
|
@ -11,10 +12,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern crate getopts;
|
extern crate getopts;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
|
mod platform;
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -38,6 +42,7 @@ struct Settings {
|
||||||
sleep_msec: u32,
|
sleep_msec: u32,
|
||||||
beginning: bool,
|
beginning: bool,
|
||||||
follow: bool,
|
follow: bool,
|
||||||
|
pid: platform::Pid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
|
@ -47,6 +52,7 @@ impl Default for Settings {
|
||||||
sleep_msec: 1000,
|
sleep_msec: 1000,
|
||||||
beginning: false,
|
beginning: false,
|
||||||
follow: false,
|
follow: false,
|
||||||
|
pid: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +74,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
opts.optopt("n", "lines", "Number of lines to print", "k");
|
opts.optopt("n", "lines", "Number of lines to print", "k");
|
||||||
opts.optflag("f", "follow", "Print the file as it grows");
|
opts.optflag("f", "follow", "Print the file as it grows");
|
||||||
opts.optopt("s", "sleep-interval", "Number or seconds to sleep between polling the file when running with -f", "n");
|
opts.optopt("s", "sleep-interval", "Number or seconds to sleep between polling the file when running with -f", "n");
|
||||||
|
opts.optopt("", "pid", "with -f, terminate after process ID, PID dies", "PID");
|
||||||
opts.optflag("z", "zero-terminated", "Line delimiter is NUL, not newline");
|
opts.optflag("z", "zero-terminated", "Line delimiter is NUL, not newline");
|
||||||
opts.optflag("h", "help", "help");
|
opts.optflag("h", "help", "help");
|
||||||
opts.optflag("V", "version", "version");
|
opts.optflag("V", "version", "version");
|
||||||
|
@ -101,6 +108,22 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(pid_str) = given_options.opt_str("pid") {
|
||||||
|
if let Ok(pid) = pid_str.parse() {
|
||||||
|
settings.pid = pid;
|
||||||
|
if pid != 0 {
|
||||||
|
if !settings.follow {
|
||||||
|
show_warning!("PID ignored; --pid=PID is useful only when following");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !platform::supports_pid_checks(pid) {
|
||||||
|
show_warning!("--pid=PID is not supported on this system");
|
||||||
|
settings.pid = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match given_options.opt_str("n") {
|
match given_options.opt_str("n") {
|
||||||
Some(n) => {
|
Some(n) => {
|
||||||
let mut slice: &str = n.as_ref();
|
let mut slice: &str = n.as_ref();
|
||||||
|
@ -320,10 +343,15 @@ const BLOCK_SIZE: u64 = 1 << 16;
|
||||||
fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings: &Settings) {
|
fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings: &Settings) {
|
||||||
assert!(settings.follow);
|
assert!(settings.follow);
|
||||||
let mut last = readers.len() - 1;
|
let mut last = readers.len() - 1;
|
||||||
|
let mut read_some = false;
|
||||||
|
let mut process = platform::ProcessChecker::new(settings.pid);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
sleep(Duration::new(0, settings.sleep_msec*1000));
|
sleep(Duration::new(0, settings.sleep_msec*1000));
|
||||||
|
|
||||||
|
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) in readers.iter_mut().enumerate() {
|
||||||
// Print all new content since the last pass
|
// Print all new content since the last pass
|
||||||
loop {
|
loop {
|
||||||
|
@ -331,6 +359,7 @@ fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings:
|
||||||
match reader.read_line(&mut datum) {
|
match reader.read_line(&mut datum) {
|
||||||
Ok(0) => break,
|
Ok(0) => break,
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
read_some = true;
|
||||||
if i != last {
|
if i != last {
|
||||||
println!("\n==> {} <==", filenames[i]);
|
println!("\n==> {} <==", filenames[i]);
|
||||||
last = i;
|
last = i;
|
||||||
|
@ -341,6 +370,10 @@ fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pid_is_dead {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,11 @@ extern crate uu_tail;
|
||||||
|
|
||||||
use common::util::*;
|
use common::util::*;
|
||||||
use std::char::from_digit;
|
use std::char::from_digit;
|
||||||
use std::io::Write;
|
|
||||||
use self::uu_tail::parse_size;
|
use self::uu_tail::parse_size;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
||||||
static FOOBAR_TXT: &'static str = "foobar.txt";
|
static FOOBAR_TXT: &'static str = "foobar.txt";
|
||||||
|
@ -74,6 +77,48 @@ fn test_follow_stdin() {
|
||||||
new_ucmd!().arg("-f").pipe_in_fixture(FOOBAR_TXT).run().stdout_is_fixture("follow_stdin.expected");
|
new_ucmd!().arg("-f").pipe_in_fixture(FOOBAR_TXT).run().stdout_is_fixture("follow_stdin.expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_follow_with_pid() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let dummy_cmd = "sh";
|
||||||
|
#[cfg(windows)]
|
||||||
|
let dummy_cmd = "cmd";
|
||||||
|
|
||||||
|
let mut dummy = Command::new(dummy_cmd).stdout(Stdio::null()).spawn().unwrap();
|
||||||
|
let pid = dummy.id();
|
||||||
|
|
||||||
|
let mut child = ucmd.arg("-f").arg(format!("--pid={}", pid)).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);
|
||||||
|
|
||||||
|
// kill the dummy process and give tail time to notice this
|
||||||
|
dummy.kill().unwrap();
|
||||||
|
let _ = dummy.wait();
|
||||||
|
sleep(Duration::from_secs(1));
|
||||||
|
|
||||||
|
let third_append = "should\nbe\nignored\n";
|
||||||
|
at.append(FOOBAR_TXT, third_append);
|
||||||
|
assert_eq!(read_size(&mut child, 1), "\u{0}");
|
||||||
|
|
||||||
|
// On Unix, trying to kill a process that's already dead is fine; on Windows it's an error.
|
||||||
|
#[cfg(unix)]
|
||||||
|
child.kill().unwrap();
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!(child.kill().is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_big_args() {
|
fn test_single_big_args() {
|
||||||
const FILE: &'static str = "single_big_args.txt";
|
const FILE: &'static str = "single_big_args.txt";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue