mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-02-01 03:32:02 +08:00
drop now-unused postfork C++ module
This commit is contained in:
parent
555171cb55
commit
4fab9e525a
476
src/postfork.cpp
476
src/postfork.cpp
|
@ -1,476 +0,0 @@
|
|||
// Functions that we may safely call after fork().
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <paths.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
#ifdef HAVE_SPAWN_H
|
||||
#include <spawn.h>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common.h"
|
||||
#include "exec.h"
|
||||
#include "fds.h"
|
||||
#include "flog.h"
|
||||
#include "iothread.h"
|
||||
#include "job_group.rs.h"
|
||||
#include "postfork.h"
|
||||
#include "proc.h"
|
||||
#include "redirection.h"
|
||||
#include "signals.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
#ifndef JOIN_THREADS_BEFORE_FORK
|
||||
#define JOIN_THREADS_BEFORE_FORK 0
|
||||
#endif
|
||||
|
||||
/// The number of times to try to call fork() before giving up.
|
||||
#define FORK_LAPS 5
|
||||
|
||||
/// The number of nanoseconds to sleep between attempts to call fork().
|
||||
#define FORK_SLEEP_TIME 1000000
|
||||
|
||||
extern bool is_thompson_shell_script(const char *path);
|
||||
static char *get_interpreter(const char *command, char *buffer, size_t buff_size);
|
||||
|
||||
/// Report the error code \p err for a failed setpgid call.
|
||||
void report_setpgid_error(int err, bool is_parent, pid_t desired_pgid, const job_t *j,
|
||||
const process_t *p) {
|
||||
char pid_buff[128];
|
||||
char job_id_buff[128];
|
||||
char getpgid_buff[128];
|
||||
char job_pgid_buff[128];
|
||||
char argv0[64];
|
||||
char command[64];
|
||||
|
||||
format_long_safe(pid_buff, p->pid);
|
||||
format_long_safe(job_id_buff, j->job_id());
|
||||
format_long_safe(getpgid_buff, getpgid(p->pid));
|
||||
format_long_safe(job_pgid_buff, desired_pgid);
|
||||
narrow_string_safe(argv0, p->argv0());
|
||||
narrow_string_safe(command, j->command_wcstr());
|
||||
|
||||
FLOGF_SAFE(warning, "Could not send %s %s, '%s' in job %s, '%s' from group %s to group %s",
|
||||
is_parent ? "child" : "self", pid_buff, argv0, job_id_buff, command, getpgid_buff,
|
||||
job_pgid_buff);
|
||||
|
||||
if (is_windows_subsystem_for_linux() && errno == EPERM) {
|
||||
FLOGF_SAFE(warning,
|
||||
"Please update to Windows 10 1809/17763 or higher to address known issues "
|
||||
"with process groups and zombie processes.");
|
||||
}
|
||||
|
||||
errno = err;
|
||||
switch (errno) {
|
||||
case EACCES: {
|
||||
FLOGF_SAFE(error, "setpgid: Process %s has already exec'd", pid_buff);
|
||||
break;
|
||||
}
|
||||
case EINVAL: {
|
||||
FLOGF_SAFE(error, "setpgid: pgid %s unsupported", getpgid_buff);
|
||||
break;
|
||||
}
|
||||
case EPERM: {
|
||||
FLOGF_SAFE(error, "setpgid: Process %s is a session leader or pgid %s does not match",
|
||||
pid_buff, getpgid_buff);
|
||||
break;
|
||||
}
|
||||
case ESRCH: {
|
||||
FLOGF_SAFE(error, "setpgid: Process ID %s does not match", pid_buff);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
char errno_buff[64];
|
||||
format_long_safe(errno_buff, errno);
|
||||
FLOGF_SAFE(error, "setpgid: Unknown error number %s", errno_buff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int execute_setpgid(pid_t pid, pid_t pgroup, bool is_parent) {
|
||||
// Historically we have looped here to support WSL.
|
||||
unsigned eperm_count = 0;
|
||||
for (;;) {
|
||||
if (setpgid(pid, pgroup) == 0) {
|
||||
return 0;
|
||||
}
|
||||
int err = errno;
|
||||
if (err == EACCES && is_parent) {
|
||||
// We are the parent process and our child has called exec().
|
||||
// This is an unavoidable benign race.
|
||||
return 0;
|
||||
} else if (err == EINTR) {
|
||||
// Paranoia.
|
||||
continue;
|
||||
} else if (err == EPERM && eperm_count++ < 100) {
|
||||
// The setpgid(2) man page says that EPERM is returned only if attempts are made
|
||||
// to move processes into groups across session boundaries (which can never be
|
||||
// the case in fish, anywhere) or to change the process group ID of a session
|
||||
// leader (again, can never be the case). I'm pretty sure this is a WSL bug, as
|
||||
// we see the same with tcsetpgrp(2) in other places and it disappears on retry.
|
||||
FLOGF_SAFE(proc_pgroup, "setpgid(2) returned EPERM. Retrying");
|
||||
continue;
|
||||
}
|
||||
#if defined(__BSD__) || defined(__APPLE__)
|
||||
// POSIX.1 doesn't specify that zombie processes are required to be considered extant and/or
|
||||
// children of the parent for purposes of setpgid(2). In particular, FreeBSD (at least up to
|
||||
// 12.2) does not consider a child that has already forked, exec'd, and exited to "exist"
|
||||
// and returns ESRCH (process not found) instead of EACCES (child has called exec).
|
||||
// See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=251227
|
||||
else if (err == ESRCH && is_parent) {
|
||||
// Handle this just like we would EACCES above, as we're virtually certain that
|
||||
// setpgid(2) was called against a process that was at least at one point in time a
|
||||
// valid child.
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
int child_setup_process(pid_t claim_tty_from, const job_t &job, bool is_forked,
|
||||
const dup2_list_t &dup2s) {
|
||||
// Note we are called in a forked child.
|
||||
for (const auto &act : dup2s.get_actions()) {
|
||||
int err;
|
||||
if (act.target < 0) {
|
||||
err = close(act.src);
|
||||
} else if (act.target != act.src) {
|
||||
// Normal redirection.
|
||||
err = dup2(act.src, act.target);
|
||||
} else {
|
||||
// This is a weird case like /bin/cmd 6< file.txt
|
||||
// The opened file (which is CLO_EXEC) wants to be dup2'd to its own fd.
|
||||
// We need to unset the CLO_EXEC flag.
|
||||
err = set_cloexec(act.src, false);
|
||||
}
|
||||
if (err < 0) {
|
||||
if (is_forked) {
|
||||
FLOGF_SAFE(warning, "failed to set up file descriptors in child_setup_process");
|
||||
exit_without_destructors(1);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
if (claim_tty_from >= 0 && tcgetpgrp(STDIN_FILENO) == claim_tty_from) {
|
||||
// Assign the terminal within the child to avoid the well-known race between tcsetgrp() in
|
||||
// the parent and the child executing. We are not interested in error handling here, except
|
||||
// we try to avoid this for non-terminals; in particular pipelines often make non-terminal
|
||||
// stdin.
|
||||
// Only do this if the tty currently belongs to fish's pgrp. Don't try to steal it away from
|
||||
// another process which may happen if we are run in the background with job control
|
||||
// enabled. Note if stdin is not a tty, then tcgetpgrp() will return -1 and we will not
|
||||
// enter this.
|
||||
// Ensure this doesn't send us to the background (see #5963)
|
||||
signal(SIGTTIN, SIG_IGN);
|
||||
signal(SIGTTOU, SIG_IGN);
|
||||
(void)tcsetpgrp(STDIN_FILENO, getpid());
|
||||
}
|
||||
sigset_t sigmask;
|
||||
sigemptyset(&sigmask);
|
||||
if (blocked_signals_for_job(job, &sigmask)) {
|
||||
sigprocmask(SIG_SETMASK, &sigmask, nullptr);
|
||||
}
|
||||
// Set the handling for job control signals back to the default.
|
||||
// Do this after any tcsetpgrp call so that we swallow SIGTTIN.
|
||||
signal_reset_handlers();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// This function is a wrapper around fork. If the fork calls fails with EAGAIN, it is retried
|
||||
/// FORK_LAPS times, with a very slight delay between each lap. If fork fails even then, the process
|
||||
/// will exit with an error message.
|
||||
pid_t execute_fork() {
|
||||
if (JOIN_THREADS_BEFORE_FORK) {
|
||||
// Make sure we have no outstanding threads before we fork. This is a pretty sketchy thing
|
||||
// to do here, both because exec.cpp shouldn't have to know about iothreads, and because the
|
||||
// completion handlers may do unexpected things.
|
||||
FLOGF_SAFE(iothread, "waiting for threads to drain.");
|
||||
iothread_drain_all();
|
||||
}
|
||||
|
||||
pid_t pid;
|
||||
struct timespec pollint;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FORK_LAPS; i++) {
|
||||
pid = fork();
|
||||
if (pid >= 0) {
|
||||
return pid;
|
||||
}
|
||||
|
||||
if (errno != EAGAIN) {
|
||||
break;
|
||||
}
|
||||
|
||||
pollint.tv_sec = 0;
|
||||
pollint.tv_nsec = FORK_SLEEP_TIME;
|
||||
|
||||
// Don't sleep on the final lap - sleeping might change the value of errno, which will break
|
||||
// the error reporting below.
|
||||
if (i != FORK_LAPS - 1) {
|
||||
nanosleep(&pollint, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// These are all the errno numbers for fork() I can find.
|
||||
// Also ENOSYS, but I doubt anyone is running
|
||||
// fish on a platform without an MMU.
|
||||
switch (errno) {
|
||||
case EAGAIN: {
|
||||
// We should have retried these already?
|
||||
FLOGF_SAFE(error, "fork: Out of resources. Check RLIMIT_NPROC and pid_max.");
|
||||
break;
|
||||
}
|
||||
case ENOMEM: {
|
||||
FLOGF_SAFE(error, "fork: Out of memory.");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
char errno_buff[64];
|
||||
format_long_safe(errno_buff, errno);
|
||||
FLOGF_SAFE(error, "fork: Unknown error number %s", errno_buff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
FATAL_EXIT();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void safe_report_exec_error(int err, const char *actual_cmd, const char *const *argv,
|
||||
const char *const *envv) {
|
||||
switch (err) {
|
||||
case E2BIG: {
|
||||
char sz1[128];
|
||||
|
||||
long arg_max = -1;
|
||||
|
||||
size_t sz = 0;
|
||||
size_t szenv = 0;
|
||||
const char *const *p;
|
||||
for (p = argv; *p; p++) {
|
||||
sz += std::strlen(*p) + 1;
|
||||
}
|
||||
|
||||
for (p = envv; *p; p++) {
|
||||
szenv += std::strlen(*p) + 1;
|
||||
}
|
||||
sz += szenv;
|
||||
|
||||
format_size_safe(sz1, sz);
|
||||
arg_max = sysconf(_SC_ARG_MAX);
|
||||
|
||||
if (arg_max > 0) {
|
||||
if (sz >= static_cast<unsigned long long>(arg_max)) {
|
||||
char sz2[128];
|
||||
format_size_safe(sz2, static_cast<unsigned long long>(arg_max));
|
||||
FLOGF_SAFE(
|
||||
exec,
|
||||
"Failed to execute process '%s': the total size of the argument list and "
|
||||
"exported variables (%s) exceeds the OS limit of %s.",
|
||||
actual_cmd, sz1, sz2);
|
||||
} else {
|
||||
// MAX_ARG_STRLEN, a linux thing that limits the size of one argument. It's
|
||||
// defined in binfmts.h, but we don't want to include that just to be able to
|
||||
// print the real limit.
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process '%s': An argument or exported variable "
|
||||
"exceeds the OS "
|
||||
"argument length limit.",
|
||||
actual_cmd);
|
||||
}
|
||||
|
||||
if (szenv >= static_cast<unsigned long long>(arg_max) / 2) {
|
||||
FLOGF_SAFE(exec,
|
||||
"Hint: Your exported variables take up over half the limit. Try "
|
||||
"erasing or unexporting variables.");
|
||||
}
|
||||
} else {
|
||||
FLOGF_SAFE(
|
||||
exec,
|
||||
"Failed to execute process '%s': the total size of the argument list and "
|
||||
"exported variables (%s) exceeds the "
|
||||
"operating system limit.",
|
||||
actual_cmd, sz1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ENOEXEC: {
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process: '%s' the file could not be run by the "
|
||||
"operating system.",
|
||||
actual_cmd);
|
||||
char interpreter_buff[128] = {};
|
||||
const char *interpreter =
|
||||
get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff);
|
||||
if (!interpreter) {
|
||||
// Paths ending in ".fish" need to start with a shebang
|
||||
if (const char *lastdot = strrchr(actual_cmd, '.')) {
|
||||
if (0 == strcmp(lastdot, ".fish")) {
|
||||
FLOGF_SAFE(exec,
|
||||
"fish scripts require an interpreter directive (must start with "
|
||||
"'#!/path/to/fish').");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the shebang line exists, we would get an ENOENT or similar instead,
|
||||
// so I don't know how to reach this.
|
||||
FLOGF_SAFE(exec, "Maybe the interpreter directive (#! line) is broken?",
|
||||
actual_cmd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EACCES:
|
||||
case ENOENT: {
|
||||
// ENOENT is returned by exec() when the path fails, but also returned by posix_spawn if
|
||||
// an open file action fails. These cases appear to be impossible to distinguish. We
|
||||
// address this by not using posix_spawn for file redirections, so all the ENOENTs we
|
||||
// find must be errors from exec().
|
||||
char interpreter_buff[128] = {};
|
||||
const char *interpreter =
|
||||
get_interpreter(actual_cmd, interpreter_buff, sizeof interpreter_buff);
|
||||
struct stat buf;
|
||||
auto statret = stat(interpreter, &buf);
|
||||
if (interpreter && (0 != statret || access(interpreter, X_OK))) {
|
||||
// Detect Windows line endings and complain specifically about them.
|
||||
auto len = strlen(interpreter);
|
||||
if (len && interpreter[len - 1] == '\r') {
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process '%s': The file uses Windows line "
|
||||
"endings (\\r\\n). Run dos2unix or similar to fix it.",
|
||||
actual_cmd);
|
||||
} else {
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process '%s': The file specified the interpreter "
|
||||
"'%s', which is not an "
|
||||
"executable command.",
|
||||
actual_cmd, interpreter);
|
||||
}
|
||||
} else if (interpreter && S_ISDIR(buf.st_mode)) {
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process '%s': The file specified the interpreter "
|
||||
"'%s', which is a directory.",
|
||||
actual_cmd, interpreter);
|
||||
} else if (access(actual_cmd, X_OK) == 0) {
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process '%s': The file exists and is executable. "
|
||||
"Check the interpreter or linker?",
|
||||
actual_cmd);
|
||||
} else if (err == ENOENT) {
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process '%s': The file does not exist or could not "
|
||||
"be executed.",
|
||||
actual_cmd);
|
||||
} else {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': The file could not be accessed.",
|
||||
actual_cmd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ENOMEM: {
|
||||
FLOGF_SAFE(exec, "Out of memory");
|
||||
break;
|
||||
}
|
||||
case ETXTBSY: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': File is currently open for writing.",
|
||||
actual_cmd);
|
||||
break;
|
||||
}
|
||||
case ELOOP: {
|
||||
FLOGF_SAFE(
|
||||
exec,
|
||||
"Failed to execute process '%s': Too many layers of symbolic links. Maybe a loop?",
|
||||
actual_cmd);
|
||||
break;
|
||||
}
|
||||
case EINVAL: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': Unsupported format.", actual_cmd);
|
||||
break;
|
||||
}
|
||||
case EISDIR: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': File is a directory.", actual_cmd);
|
||||
break;
|
||||
}
|
||||
case ENOTDIR: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': A path component is not a directory.",
|
||||
actual_cmd);
|
||||
break;
|
||||
}
|
||||
|
||||
case EMFILE: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': Too many open files in this process.",
|
||||
actual_cmd);
|
||||
break;
|
||||
}
|
||||
case ENFILE: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': Too many open files on the system.",
|
||||
actual_cmd);
|
||||
break;
|
||||
}
|
||||
case ENAMETOOLONG: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': Name is too long.", actual_cmd);
|
||||
break;
|
||||
}
|
||||
case EPERM: {
|
||||
FLOGF_SAFE(exec,
|
||||
"Failed to execute process '%s': No permission. Either suid/sgid is "
|
||||
"forbidden or you lack capabilities.",
|
||||
actual_cmd);
|
||||
break;
|
||||
}
|
||||
#ifdef EBADARCH
|
||||
case EBADARCH: {
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s': Bad CPU type in executable.",
|
||||
actual_cmd);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default: {
|
||||
char errnum_buff[64];
|
||||
format_long_safe(errnum_buff, err);
|
||||
FLOGF_SAFE(exec, "Failed to execute process '%s', unknown error number %s", actual_cmd,
|
||||
errnum_buff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the interpreter for the specified script. Returns NULL if file is not a script with a
|
||||
/// shebang.
|
||||
static char *get_interpreter(const char *command, char *buffer, size_t buff_size) {
|
||||
// OK to not use CLO_EXEC here because this is only called after fork.
|
||||
int fd = open(command, O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
size_t idx = 0;
|
||||
while (idx + 1 < buff_size) {
|
||||
char ch;
|
||||
ssize_t amt = read(fd, &ch, sizeof ch);
|
||||
if (amt <= 0) break;
|
||||
if (ch == '\n') break;
|
||||
buffer[idx++] = ch;
|
||||
}
|
||||
buffer[idx++] = '\0';
|
||||
close(fd);
|
||||
}
|
||||
|
||||
if (std::strncmp(buffer, "#! /", const_strlen("#! /")) == 0) {
|
||||
return buffer + 3;
|
||||
} else if (std::strncmp(buffer, "#!/", const_strlen("#!/")) == 0) {
|
||||
return buffer + 2;
|
||||
} else if (std::strncmp(buffer, "#!", const_strlen("#!")) == 0) {
|
||||
// Relative path, basically always an issue.
|
||||
return buffer + 2;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
Loading…
Reference in New Issue
Block a user