Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

tac, tail, dd: detect closed stdin before Rust sanitizes it to /dev/null#9664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
ChrisDryden wants to merge1 commit intouutils:main
base:main
Choose a base branch
Loading
fromChrisDryden:fix-stdin-closed-detection
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
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
1 change: 1 addition & 0 deletionsCargo.lock
View file
Open in desktop

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

1 change: 1 addition & 0 deletionssrc/uu/dd/Cargo.toml
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ uucore = { workspace = true, features = [
"parser-size",
"quoting-style",
"fs",
"signals",
] }
thiserror = { workspace = true }
fluent = { workspace = true }
Expand Down
8 changes: 8 additions & 0 deletionssrc/uu/dd/src/dd.rs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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)?,
Expand Down
14 changes: 10 additions & 4 deletionssrc/uu/dd/src/progress.rs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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,
Expand DownExpand Up@@ -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.
Expand All@@ -240,15 +242,19 @@ 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.
///
/// 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.
Expand Down
3 changes: 2 additions & 1 deletionsrc/uu/tac/Cargo.toml
View file
Open in desktop
Original file line numberDiff line numberDiff 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
19 changes: 17 additions & 2 deletionssrc/uu/tac/src/tac.rs
View file
Open in desktop
Original file line numberDiff line numberDiff 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 DownExpand Up@@ -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
Expand Down
2 changes: 1 addition & 1 deletionsrc/uu/tail/Cargo.toml
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 }

Expand Down
15 changes: 7 additions & 8 deletionssrc/uu/tail/src/paths.rs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
}
20 changes: 15 additions & 5 deletionssrc/uu/tail/src/tail.rs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Expand DownExpand Up@@ -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(())
}

Expand DownExpand Up@@ -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) => {
Expand Down
44 changes: 29 additions & 15 deletionssrc/uu/timeout/src/timeout.rs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand DownExpand Up@@ -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.
Expand Down
85 changes: 84 additions & 1 deletionsrc/uucore/src/lib/features/signals.rs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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.
Expand DownExpand Up@@ -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,
);
}
}
Comment on lines +440 to +458
Copy link
Contributor

@anastygnomeanastygnomeDec 16, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

For efficiency we should do something similar to the fastpath shown inthis function. It is the function that reopens the FDs in the standard library.

how about this

Suggested change
#[cfg(unix)]
#[allow(clippy::missing_safety_doc)]
pubunsafeextern"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,
);
}
}
/// # Safety
///
/// Must be called very early in process startup, before the runtime may reopen
/// closed standard file descriptors. Uses low-level libc APIs.
/// This code is just checking the state of the three standard fds and has no side-effects.
#[cfg(unix)]
pubunsafeextern"C"fn capture_stdio_state(){
use nix::libc;
// Prefer poll() where it reliably reports POLLNVAL
#[cfg(not(any(
miri,
target_os ="emscripten",
target_os ="fuchsia",
target_os ="vxworks",
target_os ="macos",
target_os ="ios",
target_os ="watchos",
target_os ="redox",
target_os ="l4re",
target_os ="horizon",
)))]
{
unsafe{
letmut pfds =[
libc::pollfd{
fd: libc::STDIN_FILENO,
events:0,
revents:0,
},
libc::pollfd{
fd: libc::STDOUT_FILENO,
events:0,
revents:0,
},
libc::pollfd{
fd: libc::STDERR_FILENO,
events:0,
revents:0,
},
];
let ok =loop{
match libc::poll(pfds.as_mut_ptr(), pfds.len()as_,0){
-1 =>match*libc::__errno_location(){
libc::EINTR =>continue,
libc::EINVAL | libc::ENOMEM | libc::EAGAIN =>breakfalse,
_ => libc::abort(),
},
_ =>breaktrue,
}
};
if ok{
STDIN_WAS_CLOSED.store(pfds[0].revents& libc::POLLNVAL !=0,Ordering::Relaxed);
STDOUT_WAS_CLOSED.store(pfds[1].revents& libc::POLLNVAL !=0,Ordering::Relaxed);
STDERR_WAS_CLOSED.store(pfds[2].revents& libc::POLLNVAL !=0,Ordering::Relaxed);
return;
}
}
}
// Fallback: fcntl()
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));
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp