mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 03:57:44 +00:00
Merge pull request #3845 from jhscheer/fix_3842
tail: fix stdin redirect (#3842)
This commit is contained in:
commit
987479d601
5 changed files with 101 additions and 15 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2816,6 +2816,7 @@ dependencies = [
|
|||
"libc",
|
||||
"nix",
|
||||
"notify",
|
||||
"same-file",
|
||||
"uucore",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
|
|
|
@ -20,6 +20,7 @@ clap = { version = "3.2", features = ["wrap_help", "cargo"] }
|
|||
libc = "0.2.132"
|
||||
notify = { version = "=5.0.0-pre.16", features=["macos_kqueue"]}
|
||||
uucore = { version=">=0.0.15", package="uucore", path="../../uucore", features=["ringbuffer", "lines"] }
|
||||
same-file = "1.0.6"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }
|
||||
|
|
|
@ -30,7 +30,12 @@ pub struct ReverseChunks<'a> {
|
|||
|
||||
impl<'a> ReverseChunks<'a> {
|
||||
pub fn new(file: &'a mut File) -> ReverseChunks<'a> {
|
||||
let size = file.seek(SeekFrom::End(0)).unwrap();
|
||||
let current = if cfg!(unix) {
|
||||
file.seek(SeekFrom::Current(0)).unwrap()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let size = file.seek(SeekFrom::End(0)).unwrap() - current;
|
||||
let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize;
|
||||
let block_idx = 0;
|
||||
ReverseChunks {
|
||||
|
|
|
@ -128,6 +128,8 @@ pub struct Settings {
|
|||
use_polling: bool,
|
||||
verbose: bool,
|
||||
stdin_is_pipe_or_fifo: bool,
|
||||
stdin_offset: u64,
|
||||
stdin_redirect: PathBuf,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
|
@ -316,6 +318,18 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
|||
));
|
||||
}
|
||||
|
||||
settings.stdin_redirect = dash.handle_redirect();
|
||||
if cfg!(unix) && settings.stdin_is_pipe_or_fifo {
|
||||
// Save the current seek position/offset of a stdin redirected file.
|
||||
// This is needed to pass "gnu/tests/tail-2/start-middle.sh"
|
||||
use same_file::Handle;
|
||||
if let Ok(mut stdin_handle) = Handle::stdin() {
|
||||
if let Ok(offset) = stdin_handle.as_file_mut().seek(SeekFrom::Current(0)) {
|
||||
settings.stdin_offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add '-' to paths
|
||||
if !settings.paths.contains(&dash) && settings.stdin_is_pipe_or_fifo
|
||||
|| settings.paths.is_empty() && !settings.stdin_is_pipe_or_fifo
|
||||
|
@ -341,7 +355,11 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
|||
// Iterate user provided `paths` and add them to Watcher.
|
||||
if let Some((ref mut watcher, _)) = watcher_rx {
|
||||
for path in &settings.paths {
|
||||
let mut path = path.handle_redirect();
|
||||
let mut path = if path.is_stdin() {
|
||||
settings.stdin_redirect.to_owned()
|
||||
} else {
|
||||
path.to_owned()
|
||||
};
|
||||
if path.is_stdin() {
|
||||
continue;
|
||||
}
|
||||
|
@ -376,7 +394,11 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
|||
} else {
|
||||
path.to_owned()
|
||||
};
|
||||
let mut path = path.handle_redirect();
|
||||
let path = if path.is_stdin() {
|
||||
settings.stdin_redirect.to_owned()
|
||||
} else {
|
||||
path.to_owned()
|
||||
};
|
||||
let path_is_tailable = path.is_tailable();
|
||||
|
||||
if !path.is_stdin() && !path_is_tailable {
|
||||
|
@ -436,9 +458,9 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
|||
|
||||
let metadata = path.metadata().ok();
|
||||
|
||||
if display_name.is_stdin() && path_is_tailable {
|
||||
if display_name.is_stdin() && !path.is_file() {
|
||||
if settings.verbose {
|
||||
files.print_header(Path::new(text::STDIN_HEADER), !first_header);
|
||||
files.print_header(&display_name, !first_header);
|
||||
first_header = false;
|
||||
}
|
||||
|
||||
|
@ -452,7 +474,7 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
|||
PathData {
|
||||
reader: Some(Box::new(reader)),
|
||||
metadata: None,
|
||||
display_name: PathBuf::from(text::STDIN_HEADER),
|
||||
display_name,
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
@ -476,12 +498,17 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
|||
match File::open(&path) {
|
||||
Ok(mut file) => {
|
||||
if settings.verbose {
|
||||
files.print_header(&path, !first_header);
|
||||
files.print_header(&display_name, !first_header);
|
||||
first_header = false;
|
||||
}
|
||||
let mut reader;
|
||||
|
||||
if file.is_seekable() && metadata.as_ref().unwrap().get_block_size() > 0 {
|
||||
let mut reader;
|
||||
if file.is_seekable(if display_name.is_stdin() {
|
||||
settings.stdin_offset
|
||||
} else {
|
||||
0
|
||||
}) && metadata.as_ref().unwrap().get_block_size() > 0
|
||||
{
|
||||
bounded_tail(&mut file, &settings);
|
||||
reader = BufReader::new(file);
|
||||
} else {
|
||||
|
@ -513,9 +540,11 @@ fn uu_tail(mut settings: Settings) -> UResult<()> {
|
|||
}
|
||||
}
|
||||
} else if settings.retry && settings.follow.is_some() {
|
||||
if path.is_relative() {
|
||||
path = std::env::current_dir()?.join(&path);
|
||||
}
|
||||
let path = if path.is_relative() {
|
||||
std::env::current_dir()?.join(&path)
|
||||
} else {
|
||||
path.to_owned()
|
||||
};
|
||||
// Insert non-is_tailable() paths into `files.map`
|
||||
files.insert(
|
||||
&path,
|
||||
|
@ -1544,14 +1573,16 @@ pub fn stdin_is_bad_fd() -> bool {
|
|||
|
||||
trait FileExtTail {
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_seekable(&mut self) -> bool;
|
||||
fn is_seekable(&mut self, current_offset: u64) -> bool;
|
||||
}
|
||||
|
||||
impl FileExtTail for File {
|
||||
fn is_seekable(&mut self) -> bool {
|
||||
/// Test if File is seekable.
|
||||
/// Set the current position offset to `current_offset`.
|
||||
fn is_seekable(&mut self, current_offset: u64) -> bool {
|
||||
self.seek(SeekFrom::Current(0)).is_ok()
|
||||
&& self.seek(SeekFrom::End(0)).is_ok()
|
||||
&& self.seek(SeekFrom::Start(0)).is_ok()
|
||||
&& self.seek(SeekFrom::Start(current_offset)).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,6 +76,7 @@ fn test_stdin_redirect_file() {
|
|||
.set_stdin(std::fs::File::open(at.plus("f")).unwrap())
|
||||
.arg("-v")
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_is("==> standard input <==\nfoo")
|
||||
.succeeded();
|
||||
|
||||
|
@ -93,6 +94,53 @@ fn test_stdin_redirect_file() {
|
|||
assert!(buf_stderr.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||
fn test_stdin_redirect_offset() {
|
||||
// inspired by: "gnu/tests/tail-2/start-middle.sh"
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
|
||||
at.write("k", "1\n2\n");
|
||||
let mut fh = std::fs::File::open(at.plus("k")).unwrap();
|
||||
fh.seek(SeekFrom::Start(2)).unwrap();
|
||||
|
||||
ts.ucmd()
|
||||
.set_stdin(fh)
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_is("2\n")
|
||||
.succeeded();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_vendor = "apple"))))] // FIXME: make this work not just on Linux
|
||||
fn test_stdin_redirect_offset2() {
|
||||
// like test_stdin_redirect_offset but with multiple files
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
|
||||
at.write("k", "1\n2\n");
|
||||
at.write("l", "3\n4\n");
|
||||
at.write("m", "5\n6\n");
|
||||
let mut fh = std::fs::File::open(at.plus("k")).unwrap();
|
||||
fh.seek(SeekFrom::Start(2)).unwrap();
|
||||
|
||||
ts.ucmd()
|
||||
.set_stdin(fh)
|
||||
.args(&["k", "-", "l", "m"])
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_is(
|
||||
"==> k <==\n1\n2\n\n==> standard input <==\n2\n\n==> l <==\n3\n4\n\n==> m <==\n5\n6\n",
|
||||
)
|
||||
.succeeded();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nc_0_wo_follow() {
|
||||
// verify that -[nc]0 without -f, exit without reading
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue