Retry open_cloexec for signals other than SIGINT

Fixes #10250
This commit is contained in:
Fabian Boehm 2024-01-23 16:38:03 +01:00
parent ea980c19db
commit ac9c5ed1b2
3 changed files with 33 additions and 3 deletions

View File

@ -1,9 +1,11 @@
use crate::common::wcs2zstring;
use crate::flog::FLOG;
use crate::signal::signal_check_cancel;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wutil::perror;
use errno::{errno, set_errno};
use libc::{
c_int, EINTR, FD_CLOEXEC, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC,
O_NONBLOCK,
@ -228,7 +230,22 @@ pub fn wopen_cloexec(pathname: &wstr, flags: i32, mode: libc::c_int) -> RawFd {
pub fn open_cloexec(path: &CStr, flags: i32, mode: libc::c_int) -> RawFd {
// Port note: the C++ version of this function had a fallback for platforms where
// O_CLOEXEC is not supported, using fcntl. In 2023, this is no longer needed.
unsafe { libc::open(path.as_ptr(), flags | O_CLOEXEC, mode) }
let saved_errno = errno();
errno::set_errno(errno::Errno(0));
// We retry this in case of signals,
// if we get EINTR and it's not a SIGINIT, we continue.
// If it is that's our cancel signal, so we abort.
loop {
let ret = unsafe { libc::open(path.as_ptr(), flags | O_CLOEXEC, mode) };
if ret >= 0 {
set_errno(saved_errno);
return ret;
}
if errno::errno().0 != EINTR || signal_check_cancel() != 0 {
return ret;
}
}
}
/// Close a file descriptor \p fd, retrying on EINTR.

View File

@ -663,12 +663,12 @@ impl IoChain {
FLOGF!(warning, NOCLOB_ERROR, spec.target);
} else {
if should_flog!(warning) {
FLOGF!(warning, FILE_ERROR, spec.target);
let err = errno::errno().0;
// If the error is that the file doesn't exist
// or there's a non-directory component,
// find the first problematic component for a better message.
if [ENOENT, ENOTDIR].contains(&err) {
FLOGF!(warning, FILE_ERROR, spec.target);
let mut dname: &wstr = &spec.target;
while !dname.is_empty() {
let next: &wstr = wdirname(dname);
@ -686,7 +686,11 @@ impl IoChain {
}
dname = next;
}
} else {
} else if err != EINTR {
// If we get EINTR we had a cancel signal.
// That's expected (ctrl-c on the commandline),
// so no warning.
FLOGF!(warning, FILE_ERROR, spec.target);
perror("open");
}
}

View File

@ -63,6 +63,15 @@ subprocess.call(["pkill", "-TERM", "-P", str(sp.spawn.pid), "sleep"])
expect_str("fish_kill_signal 15")
expect_prompt()
# See that open() is only interruptible by SIGINT.
sendline("mkfifo fifoo")
expect_prompt()
sendline("cat >fifoo")
subprocess.call(["kill", "-WINCH", str(sp.spawn.pid)])
expect_re("open: ", shouldfail=True, timeout=10)
subprocess.call(["kill", "-INT", str(sp.spawn.pid)])
expect_prompt()
# Verify that sending SIGHUP to the shell, such as will happen when the tty is
# closed by the terminal, terminates the shell and the foreground command and
# any background commands run from that shell.