1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

tail: implement --pid. Closes #846.

Kudos to zHz for helping out with Windows API part.
This commit is contained in:
Alexander Batischev 2016-12-20 04:47:40 +03:00
parent 2660bb4fc3
commit f2166fed0a
8 changed files with 205 additions and 2 deletions

2
Cargo.lock generated
View file

@ -942,8 +942,10 @@ name = "tail"
version = "0.0.1"
dependencies = [
"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)",
"uucore 0.0.1",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]

View file

@ -9,7 +9,9 @@ path = "tail.rs"
[dependencies]
getopts = "*"
kernel32-sys = "*"
libc = "*"
winapi = "*"
uucore = { path="../uucore" }
[[bin]]

View file

@ -4,7 +4,6 @@ Rudimentary tail implementation.
### 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.
* `--pid` : with `-f`, terminate after process ID, PID dies
* `--quiet` : never output headers giving file names
* `--retry` : keep trying to open a file even when it is or becomes inaccessible; useful when following by name, i.e., with `--follow=name`

20
src/tail/platform/mod.rs Normal file
View 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
View 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()
}

View 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
}

View file

@ -4,6 +4,7 @@
* This file is part of the uutils coreutils package.
*
* (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
* file that was distributed with this source code.
@ -11,10 +12,13 @@
*/
extern crate getopts;
extern crate libc;
#[macro_use]
extern crate uucore;
mod platform;
use std::collections::VecDeque;
use std::error::Error;
use std::fmt;
@ -38,6 +42,7 @@ struct Settings {
sleep_msec: u32,
beginning: bool,
follow: bool,
pid: platform::Pid,
}
impl Default for Settings {
@ -47,6 +52,7 @@ impl Default for Settings {
sleep_msec: 1000,
beginning: 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.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("", "pid", "with -f, terminate after process ID, PID dies", "PID");
opts.optflag("z", "zero-terminated", "Line delimiter is NUL, not newline");
opts.optflag("h", "help", "help");
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") {
Some(n) => {
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) {
assert!(settings.follow);
let mut last = readers.len() - 1;
let mut read_some = false;
let mut process = platform::ProcessChecker::new(settings.pid);
loop {
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() {
// Print all new content since the last pass
loop {
@ -331,6 +359,7 @@ fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings:
match reader.read_line(&mut datum) {
Ok(0) => break,
Ok(_) => {
read_some = true;
if i != last {
println!("\n==> {} <==", filenames[i]);
last = i;
@ -341,6 +370,10 @@ fn follow<T: Read>(readers: &mut [BufReader<T>], filenames: &[String], settings:
}
}
}
if pid_is_dead {
break;
}
}
}

View file

@ -2,8 +2,11 @@ extern crate uu_tail;
use common::util::*;
use std::char::from_digit;
use std::io::Write;
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";
@ -74,6 +77,48 @@ fn test_follow_stdin() {
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]
fn test_single_big_args() {
const FILE: &'static str = "single_big_args.txt";