mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
tail: fix handling of -f
with non regular files
This makes uu_tail pass the "gnu/tests/tail-2/inotify-only-regular" test again by adding support for charater devices. test_tail: * add test_follow_inotify_only_regular * add clippy fixes for windows
This commit is contained in:
parent
7228902e55
commit
4a56d2916d
2 changed files with 49 additions and 17 deletions
|
@ -33,9 +33,11 @@ use std::collections::HashMap;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fs::metadata;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
|
use std::os::unix::prelude::FileTypeExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::mpsc::{self, channel};
|
use std::sync::mpsc::{self, channel};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -219,15 +221,22 @@ impl Settings {
|
||||||
.map(|v| v.map(PathBuf::from).collect())
|
.map(|v| v.map(PathBuf::from).collect())
|
||||||
.unwrap_or_else(|| vec![PathBuf::from("-")]);
|
.unwrap_or_else(|| vec![PathBuf::from("-")]);
|
||||||
|
|
||||||
// Filter out paths depending on `FollowMode`.
|
// Filter out non tailable paths depending on `FollowMode`.
|
||||||
paths.retain(|path| {
|
paths.retain(|path| {
|
||||||
if !path.is_stdin() {
|
if !path.is_stdin() {
|
||||||
if !path.is_file() {
|
if !(path.is_tailable()) {
|
||||||
settings.return_code = 1;
|
|
||||||
if settings.follow == Some(FollowMode::Descriptor) && settings.retry {
|
if settings.follow == Some(FollowMode::Descriptor) && settings.retry {
|
||||||
show_warning!("--retry only effective for the initial open");
|
show_warning!("--retry only effective for the initial open");
|
||||||
}
|
}
|
||||||
if path.is_dir() {
|
if !path.exists() {
|
||||||
|
settings.return_code = 1;
|
||||||
|
show_error!(
|
||||||
|
"cannot open {} for reading: {}",
|
||||||
|
path.quote(),
|
||||||
|
text::NO_SUCH_FILE
|
||||||
|
);
|
||||||
|
} else if path.is_dir() {
|
||||||
|
settings.return_code = 1;
|
||||||
show_error!("error reading {}: Is a directory", path.quote());
|
show_error!("error reading {}: Is a directory", path.quote());
|
||||||
if settings.follow.is_some() {
|
if settings.follow.is_some() {
|
||||||
let msg = if !settings.retry {
|
let msg = if !settings.retry {
|
||||||
|
@ -242,11 +251,8 @@ impl Settings {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
show_error!(
|
// TODO: [2021-10; jhscheer] how to handle block device, socket, fifo?
|
||||||
"cannot open {} for reading: {}",
|
todo!();
|
||||||
path.quote(),
|
|
||||||
text::NO_SUCH_FILE
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if settings.follow == Some(FollowMode::Name) {
|
} else if settings.follow == Some(FollowMode::Name) {
|
||||||
|
@ -330,7 +336,7 @@ fn uu_tail(settings: &Settings) -> UResult<()> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if path.is_file() {
|
} else if path.is_tailable() {
|
||||||
if settings.verbose {
|
if settings.verbose {
|
||||||
if !first_header {
|
if !first_header {
|
||||||
println!();
|
println!();
|
||||||
|
@ -361,7 +367,7 @@ fn uu_tail(settings: &Settings) -> UResult<()> {
|
||||||
files.last = Some(path.canonicalize().unwrap());
|
files.last = Some(path.canonicalize().unwrap());
|
||||||
}
|
}
|
||||||
} else if settings.retry && settings.follow.is_some() {
|
} else if settings.retry && settings.follow.is_some() {
|
||||||
// Insert non-is_file() paths into `files.map`.
|
// Insert non-is_tailable() paths into `files.map`.
|
||||||
let key = if path.is_relative() {
|
let key = if path.is_relative() {
|
||||||
std::env::current_dir().unwrap().join(path)
|
std::env::current_dir().unwrap().join(path)
|
||||||
} else {
|
} else {
|
||||||
|
@ -569,10 +575,10 @@ fn follow(files: &mut FileHandling, settings: &Settings) -> UResult<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate user provided `paths`.
|
// Iterate user provided `paths`.
|
||||||
// Add existing files to `Watcher` (InotifyWatcher).
|
// Add existing regular files to `Watcher` (InotifyWatcher).
|
||||||
// If `path` is not an existing file, add its parent to `Watcher`.
|
// If `path` is not an existing file, add its parent to `Watcher`.
|
||||||
// If there is no parent, add `path` to `orphans`.
|
// If there is no parent, add `path` to `orphans`.
|
||||||
let mut orphans = Vec::with_capacity(files.map.len());
|
let mut orphans = Vec::new();
|
||||||
for path in files.map.keys() {
|
for path in files.map.keys() {
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let path = get_path(path, settings);
|
let path = get_path(path, settings);
|
||||||
|
@ -590,7 +596,7 @@ fn follow(files: &mut FileHandling, settings: &Settings) -> UResult<()> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unreachable!();
|
// TODO: [2021-10; jhscheer] does this case need handling?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,7 +952,7 @@ struct FileHandling {
|
||||||
impl FileHandling {
|
impl FileHandling {
|
||||||
fn files_remaining(&self) -> bool {
|
fn files_remaining(&self) -> bool {
|
||||||
for path in self.map.keys() {
|
for path in self.map.keys() {
|
||||||
if path.is_file() {
|
if path.is_tailable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1282,6 +1288,7 @@ trait PathExt {
|
||||||
fn is_stdin(&self) -> bool;
|
fn is_stdin(&self) -> bool;
|
||||||
fn print_header(&self);
|
fn print_header(&self);
|
||||||
fn is_orphan(&self) -> bool;
|
fn is_orphan(&self) -> bool;
|
||||||
|
fn is_tailable(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathExt for Path {
|
impl PathExt for Path {
|
||||||
|
@ -1294,6 +1301,10 @@ impl PathExt for Path {
|
||||||
fn is_orphan(&self) -> bool {
|
fn is_orphan(&self) -> bool {
|
||||||
!matches!(self.parent(), Some(parent) if parent.is_dir())
|
!matches!(self.parent(), Some(parent) if parent.is_dir())
|
||||||
}
|
}
|
||||||
|
fn is_tailable(&self) -> bool {
|
||||||
|
// TODO: [2021-10; jhscheer] what about fifos?
|
||||||
|
self.is_file() || (self.exists() && metadata(self).unwrap().file_type().is_char_device())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -12,20 +12,22 @@ extern crate tail;
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
use std::char::from_digit;
|
use std::char::from_digit;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
#[cfg(unix)]
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
|
#[cfg(unix)]
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub static BACKEND: &str = "inotify";
|
pub static BACKEND: &str = "inotify";
|
||||||
#[cfg(all(unix, not(target_os = "linux")))]
|
#[cfg(all(unix, not(target_os = "linux")))]
|
||||||
pub static BACKEND: &str = "kqueue";
|
pub static BACKEND: &str = "kqueue";
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub static BACKEND: &str = "ReadDirectoryChanges";
|
|
||||||
|
|
||||||
static FOOBAR_TXT: &str = "foobar.txt";
|
static FOOBAR_TXT: &str = "foobar.txt";
|
||||||
static FOOBAR_2_TXT: &str = "foobar2.txt";
|
static FOOBAR_2_TXT: &str = "foobar2.txt";
|
||||||
static FOOBAR_WITH_NULL_TXT: &str = "foobar_with_null.txt";
|
static FOOBAR_WITH_NULL_TXT: &str = "foobar_with_null.txt";
|
||||||
|
#[cfg(unix)]
|
||||||
static FOLLOW_NAME_TXT: &str = "follow_name.txt";
|
static FOLLOW_NAME_TXT: &str = "follow_name.txt";
|
||||||
|
#[cfg(unix)]
|
||||||
static FOLLOW_NAME_SHORT_EXP: &str = "follow_name_short.expected";
|
static FOLLOW_NAME_SHORT_EXP: &str = "follow_name_short.expected";
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
static FOLLOW_NAME_EXP: &str = "follow_name.expected";
|
static FOLLOW_NAME_EXP: &str = "follow_name.expected";
|
||||||
|
@ -1407,6 +1409,25 @@ fn test_follow_name_move() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_follow_inotify_only_regular() {
|
||||||
|
// The GNU test inotify-only-regular.sh uses strace to ensure that `tail -f`
|
||||||
|
// doesn't make inotify syscalls and only uses inotify for regular files or fifos.
|
||||||
|
// We just check if tailing a character device has the same behaviour than GNU's tail.
|
||||||
|
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
let mut p = ts.ucmd().arg("-f").arg("/dev/null").run_no_wait();
|
||||||
|
sleep(Duration::from_millis(200));
|
||||||
|
|
||||||
|
p.kill().unwrap();
|
||||||
|
|
||||||
|
let (buf_stdout, buf_stderr) = take_stdout_stderr(&mut p);
|
||||||
|
assert_eq!(buf_stdout, "".to_string());
|
||||||
|
assert_eq!(buf_stderr, "".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
fn take_stdout_stderr(p: &mut std::process::Child) -> (String, String) {
|
fn take_stdout_stderr(p: &mut std::process::Child) -> (String, String) {
|
||||||
let mut buf_stdout = String::new();
|
let mut buf_stdout = String::new();
|
||||||
let mut p_stdout = p.stdout.take().unwrap();
|
let mut p_stdout = p.stdout.take().unwrap();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue