mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-28 04:51:15 -03:00
Adopt dup2_list_t in fork execution path
This switches IO redirections after fork() to use the dup2_list_t, instead of io_chain_t. This results in simpler code with much simpler error handling.
This commit is contained in:
147
src/postfork.cpp
147
src/postfork.cpp
@@ -19,6 +19,7 @@
|
||||
#include "iothread.h"
|
||||
#include "postfork.h"
|
||||
#include "proc.h"
|
||||
#include "redirection.h"
|
||||
#include "signal.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
@@ -38,27 +39,6 @@
|
||||
/// Fork error message.
|
||||
#define FORK_ERROR "Could not create child process - exiting"
|
||||
|
||||
/// File redirection clobbering error message.
|
||||
#define NOCLOB_ERROR "The file '%s' already exists"
|
||||
|
||||
/// File redirection error message.
|
||||
#define FILE_ERROR "An error occurred while redirecting file '%s'"
|
||||
|
||||
/// File descriptor redirection error message.
|
||||
#define FD_ERROR "An error occurred while redirecting file descriptor %s"
|
||||
|
||||
/// Pipe error message.
|
||||
#define LOCAL_PIPE_ERROR "An error occurred while setting up pipe"
|
||||
|
||||
static bool log_redirections = false;
|
||||
|
||||
/// Cover for debug_safe that can take an int. The format string should expect a %s.
|
||||
static void debug_safe_int(int level, const char *format, int val) {
|
||||
char buff[128];
|
||||
format_long_safe(buff, val);
|
||||
debug_safe(level, format, buff);
|
||||
}
|
||||
|
||||
/// Called only by the child to set its own process group (possibly creating a new group in the
|
||||
/// process if it is the first in a JOB_CONTROL job.
|
||||
/// Returns true on sucess, false on failiure.
|
||||
@@ -175,126 +155,23 @@ bool maybe_assign_terminal(const job_t *j) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Set up a childs io redirections. Should only be called by setup_child_process(). Does the
|
||||
/// following: First it closes any open file descriptors not related to the child by calling
|
||||
/// close_unused_internal_pipes() and closing the universal variable server file descriptor. It then
|
||||
/// goes on to perform all the redirections described by \c io.
|
||||
///
|
||||
/// \param io_chain the list of IO redirections for the child
|
||||
///
|
||||
/// \return 0 on sucess, -1 on failure
|
||||
static int handle_child_io(const io_chain_t &io_chain) {
|
||||
for (size_t idx = 0; idx < io_chain.size(); idx++) {
|
||||
const io_data_t *io = io_chain.at(idx).get();
|
||||
|
||||
if (io->io_mode == io_mode_t::fd && io->fd == static_cast<const io_fd_t *>(io)->old_fd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (io->io_mode) {
|
||||
case io_mode_t::close: {
|
||||
if (log_redirections) fwprintf(stderr, L"%d: close %d\n", getpid(), io->fd);
|
||||
if (close(io->fd)) {
|
||||
debug_safe_int(0, "Failed to close file descriptor %s", io->fd);
|
||||
safe_perror("close");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case io_mode_t::file: {
|
||||
// Here we definitely do not want to set CLO_EXEC because our child needs access.
|
||||
const io_file_t *io_file = static_cast<const io_file_t *>(io);
|
||||
int tmp = open(io_file->filename_cstr, io_file->flags, OPEN_MASK);
|
||||
if (tmp < 0) {
|
||||
if ((io_file->flags & O_EXCL) && (errno == EEXIST)) {
|
||||
debug_safe(1, NOCLOB_ERROR, io_file->filename_cstr);
|
||||
} else {
|
||||
debug_safe(1, FILE_ERROR, io_file->filename_cstr);
|
||||
safe_perror("open");
|
||||
}
|
||||
|
||||
return -1;
|
||||
} else if (tmp != io->fd) {
|
||||
// This call will sometimes fail, but that is ok, this is just a precausion.
|
||||
close(io->fd);
|
||||
|
||||
if (dup2(tmp, io->fd) == -1) {
|
||||
debug_safe_int(1, FD_ERROR, io->fd);
|
||||
safe_perror("dup2");
|
||||
exec_close(tmp);
|
||||
return -1;
|
||||
}
|
||||
exec_close(tmp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case io_mode_t::fd: {
|
||||
int old_fd = static_cast<const io_fd_t *>(io)->old_fd;
|
||||
if (log_redirections)
|
||||
fwprintf(stderr, L"%d: fd dup %d to %d\n", getpid(), old_fd, io->fd);
|
||||
|
||||
// This call will sometimes fail, but that is ok, this is just a precausion.
|
||||
close(io->fd);
|
||||
|
||||
if (dup2(old_fd, io->fd) == -1) {
|
||||
debug_safe_int(1, FD_ERROR, io->fd);
|
||||
safe_perror("dup2");
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case io_mode_t::buffer:
|
||||
case io_mode_t::pipe: {
|
||||
const io_pipe_t *io_pipe = static_cast<const io_pipe_t *>(io);
|
||||
// If write_pipe_idx is 0, it means we're connecting to the read end (first pipe
|
||||
// fd). If it's 1, we're connecting to the write end (second pipe fd).
|
||||
unsigned int write_pipe_idx = (io_pipe->is_input ? 0 : 1);
|
||||
#if 0
|
||||
debug(0, L"%ls %ls on fd %d (%d %d)", write_pipe?L"write":L"read",
|
||||
(io->io_mode == io_mode_t::buffer)?L"buffer":L"pipe", io->fd, io->pipe_fd[0],
|
||||
io->pipe_fd[1]);
|
||||
#endif
|
||||
if (log_redirections)
|
||||
fwprintf(stderr, L"%d: %s dup %d to %d\n", getpid(),
|
||||
io->io_mode == io_mode_t::buffer ? "buffer" : "pipe",
|
||||
io_pipe->pipe_fd[write_pipe_idx], io->fd);
|
||||
if (dup2(io_pipe->pipe_fd[write_pipe_idx], io->fd) != io->fd) {
|
||||
debug_safe(1, LOCAL_PIPE_ERROR);
|
||||
safe_perror("dup2");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (io_pipe->pipe_fd[0] >= 0) exec_close(io_pipe->pipe_fd[0]);
|
||||
if (io_pipe->pipe_fd[1] >= 0) exec_close(io_pipe->pipe_fd[1]);
|
||||
break;
|
||||
int setup_child_process(process_t *p, const dup2_list_t &dup2s) {
|
||||
for (const auto &act : dup2s.get_actions()) {
|
||||
int err = act.target < 0 ? close(act.src) : dup2(act.src, act.target);
|
||||
if (err < 0) {
|
||||
// We have a null p if this is for the exec (non-fork) path.
|
||||
if (p != nullptr) {
|
||||
debug_safe(4, "redirect_in_child_after_fork failed in setup_child_process");
|
||||
exit_without_destructors(1);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the handling for job control signals back to the default.
|
||||
signal_reset_handlers();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setup_child_process(process_t *p, const io_chain_t &io_chain) {
|
||||
bool ok = true;
|
||||
|
||||
if (ok) {
|
||||
// In the case of io_mode_t::file, this can hang until data is available to read/write!
|
||||
ok = (0 == handle_child_io(io_chain));
|
||||
if (p != 0 && !ok) {
|
||||
debug_safe(4, "handle_child_io failed in setup_child_process");
|
||||
exit_without_destructors(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
// Set the handling for job control signals back to the default.
|
||||
signal_reset_handlers();
|
||||
}
|
||||
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
int g_fork_count = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user