Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/cspell.dictionaries/jargon.wordlist.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ACCMODE
AFAICT
alloc
arity
Expand Down Expand Up @@ -119,6 +120,8 @@ primality
pseudoprime
pseudoprimes
quantiles
RDONLY
RDWR
readonly
reparse
rposition
Expand All @@ -143,6 +146,7 @@ symlinks
syscall
syscalls
sysconf
sysfs
tokenize
toolchain
totalram
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/uu/tac/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ memchr = { workspace = true }
memmap2 = { workspace = true }
regex = { workspace = true }
clap = { workspace = true }
uucore = { workspace = true }
libc = { workspace = true }
uucore = { workspace = true, features = ["signals"] }
thiserror = { workspace = true }
fluent = { workspace = true }

Expand Down
48 changes: 37 additions & 11 deletions src/uu/tac/src/tac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
// file that was distributed with this source code.

// spell-checker:ignore (ToDO) sbytes slen dlen memmem memmap Mmap mmap SIGBUS
#[cfg(unix)]
uucore::init_stdio_state_capture!();

mod error;

use clap::{Arg, ArgAction, Command};
Expand All @@ -15,8 +18,9 @@ use std::{
fs::{File, read},
path::Path,
};
use uucore::error::UError;
use uucore::error::UResult;
#[cfg(unix)]
use uucore::error::set_exit_code;
use uucore::error::{UError, UResult};
use uucore::{format_usage, show};

use crate::error::TacError;
Expand Down Expand Up @@ -237,13 +241,24 @@ fn tac(filenames: &[OsString], before: bool, regex: bool, separator: &str) -> UR
let buf;

let data: &[u8] = if filename == "-" {
#[cfg(unix)]
if uucore::signals::stdin_was_closed() {
let e: Box<dyn UError> = TacError::ReadError(
OsString::from("-"),
std::io::Error::from_raw_os_error(libc::EBADF),
)
.into();
show!(e);
set_exit_code(1);
continue;
}
if let Some(mmap1) = try_mmap_stdin() {
mmap = mmap1;
&mmap
} else {
let mut buf1 = Vec::new();
if let Err(e) = stdin().read_to_end(&mut buf1) {
let e: Box<dyn UError> = TacError::ReadError(OsString::from("stdin"), e).into();
let e: Box<dyn UError> = TacError::ReadError(OsString::from("-"), e).into();
show!(e);
continue;
}
Expand All @@ -252,21 +267,32 @@ fn tac(filenames: &[OsString], before: bool, regex: bool, separator: &str) -> UR
}
} else {
let path = Path::new(filename);
if path.is_dir() {
let e: Box<dyn UError> = TacError::InvalidArgument(filename.clone()).into();
let Ok(metadata) = path.metadata() else {
let e: Box<dyn UError> = TacError::FileNotFound(filename.clone()).into();
show!(e);
continue;
}
};

if path.metadata().is_err() {
let e: Box<dyn UError> = TacError::FileNotFound(filename.clone()).into();
if metadata.is_dir() {
let e: Box<dyn UError> = TacError::InvalidArgument(filename.clone()).into();
show!(e);
continue;
}

if let Some(mmap1) = try_mmap_path(path) {
mmap = mmap1;
&mmap
let mut maybe_data: Option<&[u8]> = None;
// Avoid mmap when the reported size is zero; procfs/sysfs files often
// claim length 0 while still producing data, and mapping them would
// yield an empty buffer (GNU tac-2-nonseekable).
let should_mmap = metadata.is_file() && metadata.len() > 0;
if should_mmap {
if let Some(mmap1) = try_mmap_path(path) {
mmap = mmap1;
maybe_data = Some(&mmap[..]);
}
}

if let Some(data_slice) = maybe_data {
data_slice
} else {
match read(path) {
Ok(buf1) => {
Expand Down
84 changes: 84 additions & 0 deletions src/uucore/src/lib/features/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,90 @@
unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
}

// Detect closed stdin/stdout/stderr before Rust reopens them as /dev/null (see issue #2873)
#[cfg(unix)]
use std::sync::atomic::{AtomicBool, Ordering};

#[cfg(unix)]
static STDIN_WAS_CLOSED: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
static STDOUT_WAS_CLOSED: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
static STDERR_WAS_CLOSED: AtomicBool = AtomicBool::new(false);

#[cfg(unix)]
#[allow(clippy::missing_safety_doc)]
pub unsafe extern "C" fn capture_stdio_state() {
use nix::libc;

unsafe {
STDIN_WAS_CLOSED.store(
libc::fcntl(libc::STDIN_FILENO, libc::F_GETFD) == -1,

Check failure on line 447 in src/uucore/src/lib/features/signals.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'GETFD' (file:'src/uucore/src/lib/features/signals.rs', line:447)
Ordering::Relaxed,
);
STDOUT_WAS_CLOSED.store(
libc::fcntl(libc::STDOUT_FILENO, libc::F_GETFD) == -1,

Check failure on line 451 in src/uucore/src/lib/features/signals.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'GETFD' (file:'src/uucore/src/lib/features/signals.rs', line:451)
Ordering::Relaxed,
);
STDERR_WAS_CLOSED.store(
libc::fcntl(libc::STDERR_FILENO, libc::F_GETFD) == -1,

Check failure on line 455 in src/uucore/src/lib/features/signals.rs

View workflow job for this annotation

GitHub Actions / Style/spelling (ubuntu-latest, feat_os_unix)

ERROR: `cspell`: Unknown word 'GETFD' (file:'src/uucore/src/lib/features/signals.rs', line:455)
Ordering::Relaxed,
);
}
}

#[macro_export]
#[cfg(unix)]
macro_rules! init_stdio_state_capture {
() => {
#[cfg(not(target_os = "macos"))]
#[used]
#[unsafe(link_section = ".init_array")]
static CAPTURE_STDIO_STATE: unsafe extern "C" fn() = $crate::signals::capture_stdio_state;

#[cfg(target_os = "macos")]
#[used]
#[unsafe(link_section = "__DATA,__mod_init_func")]
static CAPTURE_STDIO_STATE: unsafe extern "C" fn() = $crate::signals::capture_stdio_state;
};
}

#[macro_export]
#[cfg(not(unix))]
macro_rules! init_stdio_state_capture {
() => {};
}

#[cfg(unix)]
pub fn stdin_was_closed() -> bool {
STDIN_WAS_CLOSED.load(Ordering::Relaxed)
}

#[cfg(not(unix))]
pub const fn stdin_was_closed() -> bool {
false
}

#[cfg(unix)]
pub fn stdout_was_closed() -> bool {
STDOUT_WAS_CLOSED.load(Ordering::Relaxed)
}

#[cfg(not(unix))]
pub const fn stdout_was_closed() -> bool {
false
}

#[cfg(unix)]
pub fn stderr_was_closed() -> bool {
STDERR_WAS_CLOSED.load(Ordering::Relaxed)
}

#[cfg(not(unix))]
pub const fn stderr_was_closed() -> bool {
false
}

#[test]
fn signal_by_value() {
assert_eq!(signal_by_name_or_value("0"), Some(0));
Expand Down
62 changes: 62 additions & 0 deletions tests/by-util/test_tac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,65 @@ fn test_failed_write_is_reported() {
.fails()
.stderr_is("tac: failed to write to stdout: No space left on device (os error 28)\n");
}

#[test]
#[cfg(unix)]
fn test_fifo_argument() {
use std::fs::OpenOptions;
use std::io::Write;
use std::thread;

let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkfifo("fifo_input");
let fifo_path = at.plus("fifo_input");

let child = scene.ucmd().arg("fifo_input").run_no_wait();

let writer = thread::spawn(move || {
let mut pipe = OpenOptions::new().write(true).open(fifo_path).unwrap();
pipe.write_all(b"line1\nline2\n").unwrap();
});

child.wait().unwrap().success().stdout_is("line2\nline1\n");

writer.join().unwrap();
}

#[test]
#[cfg(unix)]
fn test_stdin_dev_null_rdwr_is_supported() {
use std::fs::OpenOptions;

let dev_null = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/null")
.unwrap();

new_ucmd!()
.set_stdin(dev_null)
.succeeds()
.no_stderr()
.stdout_is("");
}

#[test]
#[cfg(unix)]
fn test_stdin_closed_with_dash_args_fails() {
use std::os::unix::process::CommandExt;

let ts = TestScenario::new(util_name!());
let mut cmd = std::process::Command::new(&ts.bin_path);
cmd.arg("tac").args(["-", "-"]);

unsafe {
cmd.pre_exec(|| {
libc::close(libc::STDIN_FILENO);
Ok(())
});
}

let output = cmd.output().unwrap();
assert_eq!(output.status.code(), Some(1));
}
Loading