Skip to content

Commit 6d29b7b

Browse files
authored
Tail macos stdin ran from script fix (#7844)
* fixes #7763 - introduce macOS-specific config guard - added test for testing tail stdin when redirected (`>`) from file and when through a pipe (`|`) * created test to mock behavior in #7763, with comments added drop line * re-enabled test_stdin_redirect_dir_when_target_os_is_macos, and added a check to handle error message * added location of current directory so test env can find script * adjusting to try to have FreeBSD find the file in CI test * putting in /env instead of assuming bash * removed ignore macro * added comments explaining the need for specific macOS cases, including reference to rust-lang issue: rust-lang/rust#95239
1 parent fd29eb5 commit 6d29b7b

File tree

3 files changed

+82
-6
lines changed

3 files changed

+82
-6
lines changed

src/uu/tail/src/paths.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,18 @@ impl Input {
7878
path.canonicalize().ok()
7979
}
8080
InputKind::File(_) | InputKind::Stdin => {
81-
if cfg!(unix) {
82-
match PathBuf::from(text::DEV_STDIN).canonicalize().ok() {
83-
Some(path) if path != PathBuf::from(text::FD0) => Some(path),
84-
Some(_) | None => None,
85-
}
86-
} else {
81+
// on macOS, /dev/fd isn't backed by /proc and canonicalize()
82+
// on dev/fd/0 (or /dev/stdin) will fail (NotFound),
83+
// so we treat stdin as a pipe here
84+
// https://github.com/rust-lang/rust/issues/95239
85+
#[cfg(target_os = "macos")]
86+
{
8787
None
8888
}
89+
#[cfg(not(target_os = "macos"))]
90+
{
91+
PathBuf::from(text::FD0).canonicalize().ok()
92+
}
8993
}
9094
}
9195
}

src/uu/tail/src/tail.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,29 @@ fn tail_stdin(
189189
input: &Input,
190190
observer: &mut Observer,
191191
) -> UResult<()> {
192+
// on macOS, resolve() will always return None for stdin,
193+
// we need to detect if stdin is a directory ourselves.
194+
// fstat-ing certain descriptors under /dev/fd fails with
195+
// bad file descriptor or might not catch directory cases
196+
// e.g. see the differences between running ls -l /dev/stdin /dev/fd/0
197+
// on macOS and Linux.
198+
#[cfg(target_os = "macos")]
199+
{
200+
if let Ok(mut stdin_handle) = Handle::stdin() {
201+
if let Ok(meta) = stdin_handle.as_file_mut().metadata() {
202+
if meta.file_type().is_dir() {
203+
set_exit_code(1);
204+
show_error!(
205+
"cannot open '{}' for reading: {}",
206+
input.display_name,
207+
text::NO_SUCH_FILE
208+
);
209+
return Ok(());
210+
}
211+
}
212+
}
213+
}
214+
192215
match input.resolve() {
193216
// fifo
194217
Some(path) => {

tests/by-util/test_tail.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,55 @@ fn test_stdin_redirect_dir_when_target_os_is_macos() {
346346
.stderr_is("tail: cannot open 'standard input' for reading: No such file or directory\n");
347347
}
348348

349+
#[test]
350+
#[cfg(unix)]
351+
fn test_stdin_via_script_redirection_and_pipe() {
352+
// $ touch file.txt
353+
// $ echo line1 > file.txt
354+
// $ echo line2 >> file.txt
355+
// $ chmod +x test.sh
356+
// $ ./test.sh < file.txt
357+
// line1
358+
// line2
359+
// $ cat file.txt | ./test.sh
360+
// line1
361+
// line2
362+
use std::os::unix::fs::PermissionsExt;
363+
364+
let scene = TestScenario::new(util_name!());
365+
let at = &scene.fixtures;
366+
let data = "line1\nline2\n";
367+
368+
at.write("file.txt", data);
369+
370+
let mut script = at.make_file("test.sh");
371+
writeln!(script, "#!/usr/bin/env sh").unwrap();
372+
writeln!(script, "tail").unwrap();
373+
script
374+
.set_permissions(PermissionsExt::from_mode(0o755))
375+
.unwrap();
376+
377+
drop(script); // close the file handle to ensure file is not busy
378+
379+
// test with redirection
380+
scene
381+
.cmd("sh")
382+
.current_dir(at.plus(""))
383+
.arg("-c")
384+
.arg("./test.sh < file.txt")
385+
.succeeds()
386+
.stdout_only(data);
387+
388+
// test with pipe
389+
scene
390+
.cmd("sh")
391+
.current_dir(at.plus(""))
392+
.arg("-c")
393+
.arg("cat file.txt | ./test.sh")
394+
.succeeds()
395+
.stdout_only(data);
396+
}
397+
349398
#[test]
350399
fn test_follow_stdin_descriptor() {
351400
let ts = TestScenario::new(util_name!());

0 commit comments

Comments
 (0)