Skip to content

Commit dfabb4b

Browse files
committed
fix: detect closed stdin before Rust sanitizes it to /dev/null
1 parent c085cd1 commit dfabb4b

File tree

11 files changed

+175
-37
lines changed

11 files changed

+175
-37
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/dd/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ uucore = { workspace = true, features = [
2626
"parser-size",
2727
"quoting-style",
2828
"fs",
29+
"signals",
2930
] }
3031
thiserror = { workspace = true }
3132
fluent = { workspace = true }

src/uu/dd/src/dd.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
// 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
77

8+
#[cfg(unix)]
9+
uucore::init_stdio_state_capture!();
10+
811
mod blocks;
912
mod bufferedoutput;
1013
mod conversion_tables;
@@ -1485,6 +1488,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
14851488
.unwrap_or_default(),
14861489
)?;
14871490

1491+
#[cfg(unix)]
1492+
if uucore::signals::stderr_was_closed() && settings.status != Some(StatusLevel::None) {
1493+
return Err(USimpleError::new(1, "write error"));
1494+
}
1495+
14881496
let i = match settings.infile {
14891497
#[cfg(unix)]
14901498
Some(ref infile) if is_fifo(infile) => Input::new_fifo(Path::new(&infile), &settings)?,

src/uu/dd/src/progress.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use std::time::Duration;
1818
#[cfg(target_os = "linux")]
1919
use signal_hook::iterator::Handle;
2020
use uucore::{
21-
error::UResult,
21+
error::{UResult, set_exit_code},
2222
format::num_format::{FloatVariant, Formatter},
2323
locale::setup_localization,
2424
translate,
@@ -231,7 +231,9 @@ impl ProgUpdate {
231231
/// See [`ProgUpdate::write_io_lines`] for more information.
232232
pub(crate) fn print_io_lines(&self) {
233233
let mut stderr = std::io::stderr();
234-
self.write_io_lines(&mut stderr).unwrap();
234+
if self.write_io_lines(&mut stderr).is_err() {
235+
set_exit_code(1);
236+
}
235237
}
236238

237239
/// Re-print the number of bytes written, duration, and throughput.
@@ -240,15 +242,19 @@ impl ProgUpdate {
240242
pub(crate) fn reprint_prog_line(&self) {
241243
let mut stderr = std::io::stderr();
242244
let rewrite = true;
243-
self.write_prog_line(&mut stderr, rewrite).unwrap();
245+
if self.write_prog_line(&mut stderr, rewrite).is_err() {
246+
set_exit_code(1);
247+
}
244248
}
245249

246250
/// Write all summary statistics.
247251
///
248252
/// See [`ProgUpdate::write_transfer_stats`] for more information.
249253
pub(crate) fn print_transfer_stats(&self, new_line: bool) {
250254
let mut stderr = std::io::stderr();
251-
self.write_transfer_stats(&mut stderr, new_line).unwrap();
255+
if self.write_transfer_stats(&mut stderr, new_line).is_err() {
256+
set_exit_code(1);
257+
}
252258
}
253259

254260
/// Write all the final statistics.

src/uu/tac/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ memchr = { workspace = true }
2424
memmap2 = { workspace = true }
2525
regex = { workspace = true }
2626
clap = { workspace = true }
27-
uucore = { workspace = true }
27+
libc = { workspace = true }
28+
uucore = { workspace = true, features = ["signals"] }
2829
thiserror = { workspace = true }
2930
fluent = { workspace = true }
3031

src/uu/tac/src/tac.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
// file that was distributed with this source code.
55

66
// spell-checker:ignore (ToDO) sbytes slen dlen memmem memmap Mmap mmap SIGBUS
7+
#[cfg(unix)]
8+
uucore::init_stdio_state_capture!();
9+
710
mod error;
811

912
use clap::{Arg, ArgAction, Command};
@@ -15,8 +18,9 @@ use std::{
1518
fs::{File, read},
1619
path::Path,
1720
};
18-
use uucore::error::UError;
19-
use uucore::error::UResult;
21+
use uucore::error::{UError, UResult};
22+
#[cfg(unix)]
23+
use uucore::error::set_exit_code;
2024
use uucore::{format_usage, show};
2125

2226
use crate::error::TacError;
@@ -237,6 +241,17 @@ fn tac(filenames: &[OsString], before: bool, regex: bool, separator: &str) -> UR
237241
let buf;
238242

239243
let data: &[u8] = if filename == "-" {
244+
#[cfg(unix)]
245+
if uucore::signals::stdin_was_closed() {
246+
let e: Box<dyn UError> = TacError::ReadError(
247+
OsString::from("-"),
248+
std::io::Error::from_raw_os_error(libc::EBADF),
249+
)
250+
.into();
251+
show!(e);
252+
set_exit_code(1);
253+
continue;
254+
}
240255
if let Some(mmap1) = try_mmap_stdin() {
241256
mmap = mmap1;
242257
&mmap

src/uu/tail/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ clap = { workspace = true }
2323
libc = { workspace = true }
2424
memchr = { workspace = true }
2525
notify = { workspace = true }
26-
uucore = { workspace = true, features = ["fs", "parser-size"] }
26+
uucore = { workspace = true, features = ["fs", "parser-size", "signals"] }
2727
same-file = { workspace = true }
2828
fluent = { workspace = true }
2929

src/uu/tail/src/paths.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,13 @@ pub fn path_is_tailable(path: &Path) -> bool {
229229
}
230230

231231
#[inline]
232+
#[cfg(unix)]
233+
pub fn stdin_is_bad_fd() -> bool {
234+
uucore::signals::stdin_was_closed()
235+
}
236+
237+
#[inline]
238+
#[cfg(not(unix))]
232239
pub fn stdin_is_bad_fd() -> bool {
233-
// FIXME : Rust's stdlib is reopening fds as /dev/null
234-
// see also: https://github.com/uutils/coreutils/issues/2873
235-
// (gnu/tests/tail-2/follow-stdin.sh fails because of this)
236-
//#[cfg(unix)]
237-
{
238-
//platform::stdin_is_bad_fd()
239-
}
240-
//#[cfg(not(unix))]
241240
false
242241
}

src/uu/tail/src/tail.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,14 @@ use std::fs::File;
3333
use std::io::{self, BufReader, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write, stdin, stdout};
3434
use std::path::{Path, PathBuf};
3535
use uucore::display::Quotable;
36-
use uucore::error::{FromIo, UResult, USimpleError, get_exit_code, set_exit_code};
36+
use uucore::error::{FromIo, UResult, USimpleError, set_exit_code};
3737
use uucore::translate;
3838

3939
use uucore::{show, show_error};
4040

41+
#[cfg(unix)]
42+
uucore::init_stdio_state_capture!();
43+
4144
#[uucore::main]
4245
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4346
// When we receive a SIGPIPE signal, we want to terminate the process so
@@ -105,10 +108,6 @@ fn uu_tail(settings: &Settings) -> UResult<()> {
105108
}
106109
}
107110

108-
if get_exit_code() > 0 && paths::stdin_is_bad_fd() {
109-
show_error!("{}: {}", text::DASH, translate!("tail-bad-fd"));
110-
}
111-
112111
Ok(())
113112
}
114113

@@ -227,6 +226,17 @@ fn tail_stdin(
227226
}
228227
}
229228

229+
// Check if stdin was closed before Rust reopened it as /dev/null
230+
if paths::stdin_is_bad_fd() {
231+
set_exit_code(1);
232+
show_error!(
233+
"{}",
234+
translate!("tail-error-cannot-fstat", "file" => translate!("tail-stdin-header").quote(), "error" => translate!("tail-bad-fd"))
235+
);
236+
show_error!("{}", translate!("tail-no-files-remaining"));
237+
return Ok(());
238+
}
239+
230240
match input.resolve() {
231241
// fifo
232242
Some(path) => {

src/uu/timeout/src/timeout.rs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
// file that was distributed with this source code.
55

66
// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld getpid
7+
#[cfg(unix)]
8+
uucore::init_stdio_state_capture!();
9+
710
mod status;
811

912
use crate::status::ExitStatus;
1013
use clap::{Arg, ArgAction, Command};
1114
use std::io::ErrorKind;
12-
use std::os::unix::process::ExitStatusExt;
15+
use std::os::unix::process::{CommandExt, ExitStatusExt};
1316
use std::process::{self, Child, Stdio};
1417
use std::sync::atomic::{self, AtomicBool};
1518
use std::time::Duration;
@@ -320,23 +323,34 @@ fn timeout(
320323
#[cfg(unix)]
321324
enable_pipe_errors()?;
322325

323-
let process = &mut process::Command::new(&cmd[0])
326+
let mut command = process::Command::new(&cmd[0]);
327+
command
324328
.args(&cmd[1..])
325329
.stdin(Stdio::inherit())
326330
.stdout(Stdio::inherit())
327-
.stderr(Stdio::inherit())
328-
.spawn()
329-
.map_err(|err| {
330-
let status_code = match err.kind() {
331-
ErrorKind::NotFound => ExitStatus::CommandNotFound.into(),
332-
ErrorKind::PermissionDenied => ExitStatus::CannotInvoke.into(),
333-
_ => ExitStatus::CannotInvoke.into(),
334-
};
335-
USimpleError::new(
336-
status_code,
337-
translate!("timeout-error-failed-to-execute-process", "error" => err),
338-
)
339-
})?;
331+
.stderr(Stdio::inherit());
332+
333+
// If stdin was closed before Rust reopened it as /dev/null, close it in child
334+
if uucore::signals::stdin_was_closed() {
335+
unsafe {
336+
command.pre_exec(|| {
337+
libc::close(libc::STDIN_FILENO);
338+
Ok(())
339+
});
340+
}
341+
}
342+
343+
let process = &mut command.spawn().map_err(|err| {
344+
let status_code = match err.kind() {
345+
ErrorKind::NotFound => ExitStatus::CommandNotFound.into(),
346+
ErrorKind::PermissionDenied => ExitStatus::CannotInvoke.into(),
347+
_ => ExitStatus::CannotInvoke.into(),
348+
};
349+
USimpleError::new(
350+
status_code,
351+
translate!("timeout-error-failed-to-execute-process", "error" => err),
352+
)
353+
})?;
340354
unblock_sigchld();
341355
catch_sigterm();
342356
// Wait for the child process for the specified time period.

0 commit comments

Comments
 (0)