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:
parent
2660bb4fc3
commit
f2166fed0a
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"
|
||||
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]]
|
||||
|
|
|
@ -9,7 +9,9 @@ path = "tail.rs"
|
|||
|
||||
[dependencies]
|
||||
getopts = "*"
|
||||
kernel32-sys = "*"
|
||||
libc = "*"
|
||||
winapi = "*"
|
||||
uucore = { path="../uucore" }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -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 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.
|
||||
*
|
||||
* (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue