mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 21:33:09 +00:00
Adopt Rust postfork code
This adopts the Rust postfork code, bridging it from C++ exec module. We use direct function calls for the bridge, rather than cxx/autocxx, so that we can be sure that no memory allocations or other shenanigans are happening.
This commit is contained in:
parent
c862a06874
commit
555171cb55
4 changed files with 102 additions and 59 deletions
|
@ -120,7 +120,7 @@ set(FISH_SRCS
|
||||||
src/io.cpp
|
src/io.cpp
|
||||||
src/null_terminated_array.cpp src/operation_context.cpp src/output.cpp
|
src/null_terminated_array.cpp src/operation_context.cpp src/output.cpp
|
||||||
src/pager.cpp src/parse_execution.cpp src/parse_util.cpp
|
src/pager.cpp src/parse_execution.cpp src/parse_util.cpp
|
||||||
src/parser.cpp src/parser_keywords.cpp src/path.cpp src/postfork.cpp
|
src/parser.cpp src/parser_keywords.cpp src/path.cpp
|
||||||
src/proc.cpp src/reader.cpp src/screen.cpp
|
src/proc.cpp src/reader.cpp src/screen.cpp
|
||||||
src/signals.cpp src/utf8.cpp
|
src/signals.cpp src/utf8.cpp
|
||||||
src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp
|
src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp
|
||||||
|
|
|
@ -545,3 +545,60 @@ fn get_interpreter<'a>(command: &CStr, buffer: &'a mut [u8]) -> Option<&'a CStr>
|
||||||
};
|
};
|
||||||
Some(CStr::from_bytes_with_nul(&buffer[offset..idx.max(offset)]).unwrap())
|
Some(CStr::from_bytes_with_nul(&buffer[offset..idx.max(offset)]).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set up redirections and signal handling in the child process.
|
||||||
|
mod ffi {
|
||||||
|
use super::*;
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn child_setup_process(
|
||||||
|
claim_tty_from: pid_t,
|
||||||
|
sigmask: *const libc::sigset_t,
|
||||||
|
is_forked: bool,
|
||||||
|
dup2s: *const Dup2List,
|
||||||
|
) -> i32 {
|
||||||
|
let sigmask = unsafe { sigmask.as_ref() };
|
||||||
|
let dup2s = unsafe { &*dup2s };
|
||||||
|
super::child_setup_process(claim_tty_from, sigmask, is_forked, dup2s)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn safe_report_exec_error(
|
||||||
|
err: i32,
|
||||||
|
actual_cmd: *const c_char,
|
||||||
|
argvv: *const *const c_char,
|
||||||
|
envv: *const *const c_char,
|
||||||
|
) {
|
||||||
|
super::safe_report_exec_error(err, actual_cmd, argvv, envv)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn execute_fork() -> pid_t {
|
||||||
|
super::execute_fork()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn execute_setpgid(pid: pid_t, pgroup: pid_t, is_parent: bool) -> i32 {
|
||||||
|
super::execute_setpgid(pid, pgroup, is_parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn report_setpgid_error(
|
||||||
|
err: i32,
|
||||||
|
is_parent: bool,
|
||||||
|
pid: pid_t,
|
||||||
|
desired_pgid: pid_t,
|
||||||
|
job_id: c_int,
|
||||||
|
command_str: *const c_char,
|
||||||
|
argv0_str: *const c_char,
|
||||||
|
) {
|
||||||
|
super::report_setpgid_error(
|
||||||
|
err,
|
||||||
|
is_parent,
|
||||||
|
pid,
|
||||||
|
desired_pgid,
|
||||||
|
job_id,
|
||||||
|
command_str,
|
||||||
|
argv0_str,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
50
src/exec.cpp
50
src/exec.cpp
|
@ -49,7 +49,6 @@
|
||||||
#include "null_terminated_array.h"
|
#include "null_terminated_array.h"
|
||||||
#include "parse_tree.h"
|
#include "parse_tree.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include "postfork.h"
|
|
||||||
#include "proc.h"
|
#include "proc.h"
|
||||||
#include "reader.h"
|
#include "reader.h"
|
||||||
#include "redirection.h"
|
#include "redirection.h"
|
||||||
|
@ -145,6 +144,22 @@ bool is_thompson_shell_script(const char *path) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implemented in postfork.rs. We don't use the FFI bridge to avoid the risk of allocations.
|
||||||
|
extern "C" {
|
||||||
|
void safe_report_exec_error(int err, const char *actual_cmd, const char *const *argv,
|
||||||
|
const char *const *envv);
|
||||||
|
|
||||||
|
int child_setup_process(pid_t claim_tty_from, const sigset_t *sigmask, bool is_forked,
|
||||||
|
const dup2_list_t *dup2s);
|
||||||
|
|
||||||
|
pid_t execute_fork();
|
||||||
|
|
||||||
|
int execute_setpgid(pid_t pid, pid_t pgroup, bool is_parent);
|
||||||
|
|
||||||
|
void report_setpgid_error(int err, bool is_parent, pid_t pid, pid_t desired_pgid, job_id_t job_id,
|
||||||
|
const char *cmd, const char *argv0);
|
||||||
|
}
|
||||||
|
|
||||||
/// This function is executed by the child process created by a call to fork(). It should be called
|
/// This function is executed by the child process created by a call to fork(). It should be called
|
||||||
/// after \c child_setup_process. It calls execve to replace the fish process image with the command
|
/// after \c child_setup_process. It calls execve to replace the fish process image with the command
|
||||||
/// specified in \c p. It never returns. Called in a forked child! Do not allocate memory, etc.
|
/// specified in \c p. It never returns. Called in a forked child! Do not allocate memory, etc.
|
||||||
|
@ -244,10 +259,17 @@ static void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &block_i
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sigset_t blocked_signals_storage;
|
||||||
|
sigemptyset(&blocked_signals_storage);
|
||||||
|
const sigset_t *blocked_signals = nullptr;
|
||||||
|
if (blocked_signals_for_job(*j, &blocked_signals_storage)) {
|
||||||
|
blocked_signals = &blocked_signals_storage;
|
||||||
|
}
|
||||||
|
|
||||||
// child_setup_process makes sure signals are properly set up.
|
// child_setup_process makes sure signals are properly set up.
|
||||||
dup2_list_t redirs = dup2_list_resolve_chain_shim(all_ios);
|
dup2_list_t redirs = dup2_list_resolve_chain_shim(all_ios);
|
||||||
if (child_setup_process(false /* not claim_tty */, *j, false /* not is_forked */, redirs) ==
|
if (child_setup_process(false /* not claim_tty */, blocked_signals, false /* not is_forked */,
|
||||||
0) {
|
&redirs) == 0) {
|
||||||
// Decrement SHLVL as we're removing ourselves from the shell "stack".
|
// Decrement SHLVL as we're removing ourselves from the shell "stack".
|
||||||
if (is_interactive_session()) {
|
if (is_interactive_session()) {
|
||||||
auto shlvl_var = vars.get(L"SHLVL", ENV_GLOBAL | ENV_EXPORT);
|
auto shlvl_var = vars.get(L"SHLVL", ENV_GLOBAL | ENV_EXPORT);
|
||||||
|
@ -408,6 +430,20 @@ static launch_result_t fork_child_for_process(const std::shared_ptr<job_t> &job,
|
||||||
pid_t claim_tty_from =
|
pid_t claim_tty_from =
|
||||||
(p->leads_pgrp && job->group->wants_terminal()) ? getpgrp() : INVALID_PID;
|
(p->leads_pgrp && job->group->wants_terminal()) ? getpgrp() : INVALID_PID;
|
||||||
|
|
||||||
|
// Decide if the job wants to set a custom sigmask.
|
||||||
|
sigset_t blocked_signals_storage;
|
||||||
|
sigemptyset(&blocked_signals_storage);
|
||||||
|
const sigset_t *blocked_signals = nullptr;
|
||||||
|
if (blocked_signals_for_job(*job, &blocked_signals_storage)) {
|
||||||
|
blocked_signals = &blocked_signals_storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Narrow the command name for error reporting before fork,
|
||||||
|
// to avoid allocations in the forked child.
|
||||||
|
std::string narrow_cmd = wcs2string(job->command_wcstr());
|
||||||
|
std::string narrow_argv0 = wcs2string(p->argv0());
|
||||||
|
job_id_t job_id = job->job_id();
|
||||||
|
|
||||||
pid_t pid = execute_fork();
|
pid_t pid = execute_fork();
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
return launch_result_t::failed;
|
return launch_result_t::failed;
|
||||||
|
@ -420,18 +456,20 @@ static launch_result_t fork_child_for_process(const std::shared_ptr<job_t> &job,
|
||||||
if (p->leads_pgrp) {
|
if (p->leads_pgrp) {
|
||||||
job->group->set_pgid(p->pid);
|
job->group->set_pgid(p->pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto pgid = job->group->get_pgid();
|
auto pgid = job->group->get_pgid();
|
||||||
if (pgid) {
|
if (pgid) {
|
||||||
if (int err = execute_setpgid(p->pid, pgid->value, is_parent)) {
|
if (int err = execute_setpgid(p->pid, pgid->value, is_parent)) {
|
||||||
report_setpgid_error(err, is_parent, pgid->value, job.get(), p);
|
report_setpgid_error(err, is_parent, p->pid, pgid->value, job_id,
|
||||||
|
narrow_cmd.c_str(), narrow_argv0.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_parent) {
|
if (!is_parent) {
|
||||||
// Child process.
|
// Child process.
|
||||||
child_setup_process(claim_tty_from, *job, true, dup2s);
|
child_setup_process(claim_tty_from, blocked_signals, true, &dup2s);
|
||||||
child_action();
|
child_action();
|
||||||
DIE("Child process returned control to fork_child lambda!");
|
DIE("Child process returned control to fork_child lambda!");
|
||||||
}
|
}
|
||||||
|
@ -533,7 +571,7 @@ static launch_result_t exec_external_command(parser_t &parser, const std::shared
|
||||||
const char *actual_cmd = actual_cmd_str.c_str();
|
const char *actual_cmd = actual_cmd_str.c_str();
|
||||||
filename_ref_t file = parser.libdata().current_filename;
|
filename_ref_t file = parser.libdata().current_filename;
|
||||||
|
|
||||||
#if FISH_USE_POSIX_SPAWN
|
#if HAVE_SPAWN_H
|
||||||
// Prefer to use posix_spawn, since it's faster on some systems like OS X.
|
// Prefer to use posix_spawn, since it's faster on some systems like OS X.
|
||||||
if (can_use_posix_spawn_for_job(j, dup2s)) {
|
if (can_use_posix_spawn_for_job(j, dup2s)) {
|
||||||
++s_fork_count; // spawn counts as a fork+exec
|
++s_fork_count; // spawn counts as a fork+exec
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
// Functions that we may safely call after fork(), of which there are very few. In particular we
|
|
||||||
// cannot allocate memory, since we're insane enough to call fork from a multithreaded process.
|
|
||||||
#ifndef FISH_POSTFORK_H
|
|
||||||
#define FISH_POSTFORK_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#ifdef HAVE_SPAWN_H
|
|
||||||
#include <spawn.h>
|
|
||||||
#endif
|
|
||||||
#ifndef FISH_USE_POSIX_SPAWN
|
|
||||||
#define FISH_USE_POSIX_SPAWN HAVE_SPAWN_H
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
#include "maybe.h"
|
|
||||||
|
|
||||||
struct Dup2List;
|
|
||||||
using dup2_list_t = Dup2List;
|
|
||||||
class job_t;
|
|
||||||
class process_t;
|
|
||||||
|
|
||||||
/// Tell the proc \p pid to join process group \p pgroup.
|
|
||||||
/// If \p is_child is true, we are the child process; otherwise we are fish.
|
|
||||||
/// Called by both parent and child; this is an unavoidable race inherent to Unix.
|
|
||||||
/// If is_parent is set, then we are the parent process and should swallow EACCESS.
|
|
||||||
/// \return 0 on success, an errno error code on failure.
|
|
||||||
int execute_setpgid(pid_t pid, pid_t pgroup, bool is_parent);
|
|
||||||
|
|
||||||
/// Report the error code \p err for a failed setpgid call.
|
|
||||||
/// Note not all errors should be reported; in particular EACCESS is expected and benign in the
|
|
||||||
/// parent only.
|
|
||||||
void report_setpgid_error(int err, bool is_parent, pid_t desired_pgid, const job_t *j,
|
|
||||||
const process_t *p);
|
|
||||||
|
|
||||||
/// Initialize a new child process. This should be called right away after forking in the child
|
|
||||||
/// process. This resets signal handlers and applies IO redirections.
|
|
||||||
///
|
|
||||||
/// If \p claim_tty_from is >= 0 and owns the tty, use tcsetpgrp() to claim it.
|
|
||||||
///
|
|
||||||
/// \return 0 on success, -1 on failure, in which case an error will be printed.
|
|
||||||
int child_setup_process(pid_t claim_tty_from, const job_t &job, bool is_forked,
|
|
||||||
const dup2_list_t &dup2s);
|
|
||||||
|
|
||||||
/// Call fork(), retrying on failure a few times.
|
|
||||||
pid_t execute_fork();
|
|
||||||
|
|
||||||
/// Report an error from failing to exec or posix_spawn a command.
|
|
||||||
void safe_report_exec_error(int err, const char *actual_cmd, const char *const *argv,
|
|
||||||
const char *const *envv);
|
|
||||||
|
|
||||||
#endif
|
|
Loading…
Reference in a new issue