/* SPDX-License-Identifier: GPL-2.0 */ /** \file exec.c Helper functions for spawning new processes. */ #include "para.h" #include "error.h" #include "fd.h" #include "string.h" static void xdup2(int oldfd, int newfd) { while (dup2(oldfd, newfd) == -1) { if (errno == EINTR) continue; PARA_EMERG_LOG("dup2 failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); } } /* Spawn a new process, redirect fds, have the child call execvp(2). */ static int fork_and_exec(pid_t *pid, const char *file, char *const *const args, int *fds) { int ret, in[2] = {-1, -1}, out[2] = {-1, -1}, err[2] = {-1, -1}, null = -1, control[2]; int exec_errno; *pid = 0; ret = -E_DUP_PIPE; if (fds[0] > 0 && pipe(in) < 0) goto err_out; if (fds[1] > 0 && pipe(out) < 0) goto err_out; if (fds[2] > 0 && pipe(err) < 0) goto err_out; if (pipe2(control, O_CLOEXEC) < 0) goto err_out; if (!fds[0] || !fds[1] || !fds[2]) { ret = para_open("/dev/null", O_RDWR, 42); if (ret < 0) goto err_out; null = ret; } ret = fork(); if (ret < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err_out; } *pid = ret; if (!(*pid)) { /* child */ close(control[0]); if (fds[0] == 0) xdup2(null, STDIN_FILENO); else if (fds[0] > 0) { close(in[1]); if (in[0] != STDIN_FILENO) xdup2(in[0], STDIN_FILENO); } if (fds[1] == 0) xdup2(null, STDOUT_FILENO); else if (fds[1] > 0) { close(out[0]); if (out[1] != STDOUT_FILENO) xdup2(out[1], STDOUT_FILENO); } if (fds[2] == 0) xdup2(null, STDERR_FILENO); else if (fds[2] > 0) { close(err[0]); if (err[1] != STDERR_FILENO) xdup2(err[1], STDERR_FILENO); } if (null >= 0) close(null); /* * If stdin is redirected, the foreground process group can no * longer be signalled with CTRL+C. Set the process group ID of * the child to its PID so that the parent can send a signal to * the process group to kill the child and all its subprocesses. */ if (fds[0] >= 0) setpgid(0, 0); execvp(file, args); /* closes control[1] */ /* exec failed, notify parent */ xwrite(control[1], &errno, sizeof(int)); _exit(EXIT_FAILURE); } close(control[1]); do ret = read(control[0], &exec_errno, sizeof(int)); while (ret < 0 && errno == EINTR); close(control[0]); if (ret > 0) { /* exec failed */ assert(ret == sizeof(int)); ret = -ERRNO_TO_PARA_ERROR(exec_errno); goto err_out; } if (fds[0] > 0) { close(in[0]); *fds = in[1]; } if (fds[1] > 0) { close(out[1]); *(fds + 1) = out[0]; } if (fds[2] > 0) { close(err[1]); *(fds + 2) = err[0]; } if (null >= 0) close(null); return 1; err_out: if (err[0] >= 0) close(err[0]); if (err[1] >= 0) close(err[1]); if (out[0] >= 0) close(out[0]); if (out[1] >= 0) close(out[1]); if (in[0] >= 0) close(in[0]); if (in[1] >= 0) close(in[1]); if (null >= 0) close(null); return ret; } /** * Execute a file as a background process, honoring $PATH. * * \param pid Contains the PID of the child process on return. * \param cmdline Path to the file to execute, followed by arguments. * \param fds A pointer to a value-result array. * * Create a new process and return without waiting for the child * to terminate. The child replaces itself with the process image that * corresponds to the first word of the given command line. * * The fd pointer must point to an array of three integers. Initially, the * integers specify how to deal with fd 0, 1, 2 in the child: * * < 0: Leave fd alone. * == 0: Dup fd to /dev/null. * > 0: Create a pipe and dup to one end of that pipe. * * In the third case, the function sets the corresponding integer of the * fd array to one end of the newly created pipe. * * \return Standard. Regardless of the return value, if pid points to a * non-zero value, a child process was created. It is the responsibility of * the caller to reap the child. * * \sa \ref create_argv(), null(4), pipe(2), dup2(2), fork(2), exec(3). */ int xexec(pid_t *pid, const char *cmdline, int *fds) { int ret; char **argv; ret = create_argv(cmdline, " \t", &argv); if (ret < 0) return ret; PARA_INFO_LOG("cmdline: %s\n", cmdline); ret = fork_and_exec(pid, argv[0], argv, fds); free_argv(argv); return ret; }