mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
fix: detect closed stdin before Rust sanitizes it to /dev/null
This commit is contained in:
parent
c085cd1c21
commit
2c10c5fdbd
11 changed files with 175 additions and 37 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3925,6 +3925,7 @@ version = "0.5.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"fluent",
|
||||
"libc",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"regex",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ uucore = { workspace = true, features = [
|
|||
"parser-size",
|
||||
"quoting-style",
|
||||
"fs",
|
||||
"signals",
|
||||
] }
|
||||
thiserror = { workspace = true }
|
||||
fluent = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
|
||||
// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE bufferedoutput, SETFL
|
||||
|
||||
#[cfg(unix)]
|
||||
uucore::init_stdio_state_capture!();
|
||||
|
||||
mod blocks;
|
||||
mod bufferedoutput;
|
||||
mod conversion_tables;
|
||||
|
|
@ -1485,6 +1488,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.unwrap_or_default(),
|
||||
)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
if uucore::signals::stderr_was_closed() && settings.status != Some(StatusLevel::None) {
|
||||
return Err(USimpleError::new(1, "write error"));
|
||||
}
|
||||
|
||||
let i = match settings.infile {
|
||||
#[cfg(unix)]
|
||||
Some(ref infile) if is_fifo(infile) => Input::new_fifo(Path::new(&infile), &settings)?,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use std::time::Duration;
|
|||
#[cfg(target_os = "linux")]
|
||||
use signal_hook::iterator::Handle;
|
||||
use uucore::{
|
||||
error::UResult,
|
||||
error::{UResult, set_exit_code},
|
||||
format::num_format::{FloatVariant, Formatter},
|
||||
locale::setup_localization,
|
||||
translate,
|
||||
|
|
@ -231,7 +231,9 @@ impl ProgUpdate {
|
|||
/// See [`ProgUpdate::write_io_lines`] for more information.
|
||||
pub(crate) fn print_io_lines(&self) {
|
||||
let mut stderr = std::io::stderr();
|
||||
self.write_io_lines(&mut stderr).unwrap();
|
||||
if self.write_io_lines(&mut stderr).is_err() {
|
||||
set_exit_code(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-print the number of bytes written, duration, and throughput.
|
||||
|
|
@ -240,7 +242,9 @@ impl ProgUpdate {
|
|||
pub(crate) fn reprint_prog_line(&self) {
|
||||
let mut stderr = std::io::stderr();
|
||||
let rewrite = true;
|
||||
self.write_prog_line(&mut stderr, rewrite).unwrap();
|
||||
if self.write_prog_line(&mut stderr, rewrite).is_err() {
|
||||
set_exit_code(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write all summary statistics.
|
||||
|
|
@ -248,7 +252,9 @@ impl ProgUpdate {
|
|||
/// See [`ProgUpdate::write_transfer_stats`] for more information.
|
||||
pub(crate) fn print_transfer_stats(&self, new_line: bool) {
|
||||
let mut stderr = std::io::stderr();
|
||||
self.write_transfer_stats(&mut stderr, new_line).unwrap();
|
||||
if self.write_transfer_stats(&mut stderr, new_line).is_err() {
|
||||
set_exit_code(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write all the final statistics.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
@ -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;
|
||||
|
|
@ -237,6 +241,17 @@ 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
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ clap = { workspace = true }
|
|||
libc = { workspace = true }
|
||||
memchr = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
uucore = { workspace = true, features = ["fs", "parser-size"] }
|
||||
uucore = { workspace = true, features = ["fs", "parser-size", "signals"] }
|
||||
same-file = { workspace = true }
|
||||
fluent = { workspace = true }
|
||||
|
||||
|
|
|
|||
|
|
@ -229,14 +229,13 @@ pub fn path_is_tailable(path: &Path) -> bool {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(unix)]
|
||||
pub fn stdin_is_bad_fd() -> bool {
|
||||
uucore::signals::stdin_was_closed()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(unix))]
|
||||
pub fn stdin_is_bad_fd() -> bool {
|
||||
// FIXME : Rust's stdlib is reopening fds as /dev/null
|
||||
// see also: https://github.com/uutils/coreutils/issues/2873
|
||||
// (gnu/tests/tail-2/follow-stdin.sh fails because of this)
|
||||
//#[cfg(unix)]
|
||||
{
|
||||
//platform::stdin_is_bad_fd()
|
||||
}
|
||||
//#[cfg(not(unix))]
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,14 @@ use std::fs::File;
|
|||
use std::io::{self, BufReader, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write, stdin, stdout};
|
||||
use std::path::{Path, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult, USimpleError, get_exit_code, set_exit_code};
|
||||
use uucore::error::{FromIo, UResult, USimpleError, set_exit_code};
|
||||
use uucore::translate;
|
||||
|
||||
use uucore::{show, show_error};
|
||||
|
||||
#[cfg(unix)]
|
||||
uucore::init_stdio_state_capture!();
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
// When we receive a SIGPIPE signal, we want to terminate the process so
|
||||
|
|
@ -105,10 +108,6 @@ fn uu_tail(settings: &Settings) -> UResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if get_exit_code() > 0 && paths::stdin_is_bad_fd() {
|
||||
show_error!("{}: {}", text::DASH, translate!("tail-bad-fd"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -227,6 +226,17 @@ fn tail_stdin(
|
|||
}
|
||||
}
|
||||
|
||||
// Check if stdin was closed before Rust reopened it as /dev/null
|
||||
if paths::stdin_is_bad_fd() {
|
||||
set_exit_code(1);
|
||||
show_error!(
|
||||
"{}",
|
||||
translate!("tail-error-cannot-fstat", "file" => translate!("tail-stdin-header").quote(), "error" => translate!("tail-bad-fd"))
|
||||
);
|
||||
show_error!("{}", translate!("tail-no-files-remaining"));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match input.resolve() {
|
||||
// fifo
|
||||
Some(path) => {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@
|
|||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld getpid
|
||||
#[cfg(unix)]
|
||||
uucore::init_stdio_state_capture!();
|
||||
|
||||
mod status;
|
||||
|
||||
use crate::status::ExitStatus;
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::io::ErrorKind;
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
||||
use std::process::{self, Child, Stdio};
|
||||
use std::sync::atomic::{self, AtomicBool};
|
||||
use std::time::Duration;
|
||||
|
|
@ -320,23 +323,34 @@ fn timeout(
|
|||
#[cfg(unix)]
|
||||
enable_pipe_errors()?;
|
||||
|
||||
let process = &mut process::Command::new(&cmd[0])
|
||||
let mut command = process::Command::new(&cmd[0]);
|
||||
command
|
||||
.args(&cmd[1..])
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.map_err(|err| {
|
||||
let status_code = match err.kind() {
|
||||
ErrorKind::NotFound => ExitStatus::CommandNotFound.into(),
|
||||
ErrorKind::PermissionDenied => ExitStatus::CannotInvoke.into(),
|
||||
_ => ExitStatus::CannotInvoke.into(),
|
||||
};
|
||||
USimpleError::new(
|
||||
status_code,
|
||||
translate!("timeout-error-failed-to-execute-process", "error" => err),
|
||||
)
|
||||
})?;
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
// If stdin was closed before Rust reopened it as /dev/null, close it in child
|
||||
if uucore::signals::stdin_was_closed() {
|
||||
unsafe {
|
||||
command.pre_exec(|| {
|
||||
libc::close(libc::STDIN_FILENO);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let process = &mut command.spawn().map_err(|err| {
|
||||
let status_code = match err.kind() {
|
||||
ErrorKind::NotFound => ExitStatus::CommandNotFound.into(),
|
||||
ErrorKind::PermissionDenied => ExitStatus::CannotInvoke.into(),
|
||||
_ => ExitStatus::CannotInvoke.into(),
|
||||
};
|
||||
USimpleError::new(
|
||||
status_code,
|
||||
translate!("timeout-error-failed-to-execute-process", "error" => err),
|
||||
)
|
||||
})?;
|
||||
unblock_sigchld();
|
||||
catch_sigterm();
|
||||
// Wait for the child process for the specified time period.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable sysconf pgrp
|
||||
// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable sysconf pgrp GETFD
|
||||
// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGDANGER SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGMIGRATE SIGMSG SIGPIPE SIGPRE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTALRM SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVIRT SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VIRT VTALRM XCPU XFSZ SIGCLD SIGPOLL SIGWAITING SIGAIOCANCEL SIGLWP SIGFREEZE SIGTHAW SIGCANCEL SIGLOST SIGXRES SIGJVM SIGRTMIN SIGRT SIGRTMAX TALRM AIOCANCEL XRES RTMIN RTMAX LTOSTOP
|
||||
|
||||
//! This module provides a way to handle signals in a platform-independent way.
|
||||
|
|
@ -426,6 +426,89 @@ pub fn ignore_interrupts() -> Result<(), Errno> {
|
|||
unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
|
||||
}
|
||||
|
||||
// Detect closed stdin/stdout 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,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
STDOUT_WAS_CLOSED.store(
|
||||
libc::fcntl(libc::STDOUT_FILENO, libc::F_GETFD) == -1,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
STDERR_WAS_CLOSED.store(
|
||||
libc::fcntl(libc::STDERR_FILENO, libc::F_GETFD) == -1,
|
||||
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));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue