From 4fab9e525af6e97755558515f1d91459bf0b8398 Mon Sep 17 00:00:00 2001 From: David Adam Date: Mon, 25 Sep 2023 21:47:19 +0800 Subject: [PATCH] drop now-unused postfork C++ module --- src/postfork.cpp | 476 ----------------------------------------------- 1 file changed, 476 deletions(-) delete mode 100644 src/postfork.cpp diff --git a/src/postfork.cpp b/src/postfork.cpp deleted file mode 100644 index ecbfac160..000000000 --- a/src/postfork.cpp +++ /dev/null @@ -1,476 +0,0 @@ -// Functions that we may safely call after fork(). -#include "config.h" // IWYU pragma: keep - -#include -#include -#include -#include -#include -#include - -#include -#ifdef HAVE_SPAWN_H -#include -#endif -#include -#include - -#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(arg_max)) { - char sz2[128]; - format_size_safe(sz2, static_cast(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(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; -};