text: #define _POSIX_C_SOURCE 200809L
  
  #include "process.h"
  
  #include <errno.h>
  #include <fcntl.h>
  #include <limits.h>
  #include <signal.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/resource.h>
  #include <sys/wait.h>
  #include <unistd.h>
  
  #include "error.h"
  #include "macro.h"
  #include "pipe.h"
  #include "strv.h"
  
  const pid_t PROCESS_INVALID = -1;
  
  static int signal_mask(int how, const sigset_t *newmask, sigset_t *oldmask)
  {
    int r = -1;
  
  #if defined(REPROC_MULTITHREADED)
    // `pthread_sigmask` returns positive errno values so we negate them.
    r = -pthread_sigmask(how, newmask, oldmask);
  #else
    r = sigprocmask(how, newmask, oldmask);
    r = r < 0 ? -errno : 0;
  #endif
  
    return r;
  }
  
  // Returns true if the NUL-terminated string indicated by `path` is a relative
  // path. A path is relative if any character except the first is a forward slash
  // ('/').
  static bool path_is_relative(const char *path)
  {
    return strlen(path) > 0 && path[0] != '/' && strchr(path + 1, '/') != NULL;
  }
  
  // Prepends the NUL-terminated string indicated by `path` with the current
  // working directory. The caller is responsible for freeing the result of this
  // function. If an error occurs, `NULL` is returned and `errno` is set to
  // indicate the error.
  static char *path_prepend_cwd(const char *path)
  {
    ASSERT(path);
  
    size_t path_size = strlen(path);
    size_t cwd_size = PATH_MAX;
  
    // We always allocate sufficient space for `path` but do not include this
    // space in `cwd_size` so we can be sure that when `getcwd` succeeds there is
    // sufficient space left in `cwd` to append `path`.
  
    // +2 reserves space to add a NUL terminator and potentially a missing '/'
    // after the current working directory.
    char *cwd = calloc(cwd_size + path_size + 2, sizeof(char));
    if (cwd == NULL) {
      return cwd;
    }
  
    while (getcwd(cwd, cwd_size) == NULL) {
      if (errno != ERANGE) {
        free(cwd);
        return NULL;
      }
  
      cwd_size += PATH_MAX;
  
      char *result = realloc(cwd, cwd_size + path_size + 1);
      if (result == NULL) {
        free(cwd);
        return result;
      }
  
      cwd = result;
    }
  
    cwd_size = strlen(cwd);
  
    // Add a forward slash after `cwd` if there is none.
    if (cwd[cwd_size - 1] != '/') {
      cwd[cwd_size] = '/';
      cwd[cwd_size + 1] = '\0';
      cwd_size++;
    }
  
    // We've made sure there's sufficient space left in `cwd` to add `path` and a
    // NUL terminator.
    memcpy(cwd + cwd_size, path, path_size);
    cwd[cwd_size + path_size] = '\0';
  
    return cwd;
  }
  
  static const int MAX_FD_LIMIT = 1024 * 1024;
  
  static int get_max_fd(void)
  {
    struct rlimit limit = { 0 };
  
    int r = getrlimit(RLIMIT_NOFILE, &limit);
    if (r < 0) {
      return -errno;
    }
  
    rlim_t soft = limit.rlim_cur;
  
    if (soft == RLIM_INFINITY || soft > INT_MAX) {
      return INT_MAX;
    }
  
    return (int) (soft - 1);
  }
  
  static bool fd_in_set(int fd, const int *fd_set, size_t size)
  {
    for (size_t i = 0; i < size; i++) {
      if (fd == fd_set[i]) {
        return true;
      }
    }
  
    return false;
  }
  
  static pid_t process_fork(const int *except, size_t num_except)
  {
    struct {
      sigset_t old;
      sigset_t new;
    } mask;
  
    int r = -1;
  
    // We don't want signal handlers of the parent to run in the child process so
    // we block all signals before forking.
  
    r = sigfillset(&mask.new);
    if (r < 0) {
      return -errno;
    }
  
    r = signal_mask(SIG_SETMASK, &mask.new, &mask.old);
    if (r < 0) {
      return r;
    }
  
    struct {
      int read;
      int write;
    } pipe = { PIPE_INVALID, PIPE_INVALID };
  
    r = pipe_init(&pipe.read, &pipe.write);
    if (r < 0) {
      return r;
    }
  
    r = fork();
    if (r < 0) {
      // `fork` error.
  
      r = -errno; // Save `errno`.
  
      int q = signal_mask(SIG_SETMASK, &mask.new, &mask.old);
      ASSERT_UNUSED(q == 0);
  
      pipe_destroy(pipe.read);
      pipe_destroy(pipe.write);
  
      return r;
    }
  
    if (r > 0) {
      // Parent process
  
      pid_t child = r;
  
      // From now on, the child process might have started so we don't report
      // errors from `signal_mask` and `read`. This puts the responsibility
      // for cleaning up the process in the hands of the caller.
  
      int q = signal_mask(SIG_SETMASK, &mask.old, &mask.old);
      ASSERT_UNUSED(q == 0);
  
      // Close the error pipe write end on the parent's side so `read` will return
      // when it is closed on the child side as well.
      pipe_destroy(pipe.write);
  
      int child_errno = 0;
      q = (int) read(pipe.read, &child_errno, sizeof(child_errno));
      ASSERT_UNUSED(q >= 0);
  
      if (child_errno > 0) {
        // If the child writes to the error pipe and exits, we're certain the
        // child process exited on its own and we can report errors as usual.
        r = waitpid(child, NULL, 0);
        ASSERT(r < 0 || r == child);
  
        r = r < 0 ? -errno : -child_errno;
      }
  
      pipe_destroy(pipe.read);
  
      return r < 0 ? r : child;
    }
  
    // Child process
  
    // Reset all signal handlers so they don't run in the child process. By
    // default, a child process inherits the parent's signal handlers but we
    // override this as most signal handlers won't be written in a way that they
    // can deal with being run in a child process.
  
    struct sigaction action = { .sa_handler = SIG_DFL };
  
    r = sigemptyset(&action.sa_mask);
    if (r < 0) {
      r = -errno;
      goto finish;
    }
  
    // NSIG is not standardized so we use a fixed limit instead.
    for (int signal = 0; signal < 32; signal++) {
      r = sigaction(signal, &action, NULL);
      if (r < 0 && errno != EINVAL) {
        r = -errno;
        goto finish;
      }
    }
  
    // Reset the child's signal mask to the default signal mask. By default, a
    // child process inherits the parent's signal mask (even over an `exec` call)
    // but we override this as most processes won't be written in a way that they
    // can deal with starting with a custom signal mask.
  
    r = sigemptyset(&mask.new);
    if (r < 0) {
      r = -errno;
      goto finish;
    }
  
    r = signal_mask(SIG_SETMASK, &mask.new, NULL);
    if (r < 0) {
      goto finish;
    }
  
    // Not all file descriptors might have been created with the `FD_CLOEXEC`
    // flag so we manually close all file descriptors to prevent file descriptors
    // leaking into the child process.
  
    r = get_max_fd();
    if (r < 0) {
      goto finish;
    }
  
    int max_fd = r;
  
    if (max_fd > MAX_FD_LIMIT) {
      // Refuse to try to close too many file descriptors.
      r = -EMFILE;
      goto finish;
    }
  
    for (int i = 0; i < max_fd; i++) {
      // Make sure we don't close the error pipe file descriptors twice.
      if (i == pipe.read || i == pipe.write) {
        continue;
      }
  
      if (fd_in_set(i, except, num_except)) {
        continue;
      }
  
      // Check if `i` is a valid file descriptor before trying to close it.
      r = fcntl(i, F_GETFD);
      if (r >= 0) {
        handle_destroy(i);
      }
    }
  
    r = 0;
  
  finish:
    if (r < 0) {
      (void) !write(pipe.write, &errno, sizeof(errno));
      _exit(EXIT_FAILURE);
    }
  
    pipe_destroy(pipe.write);
    pipe_destroy(pipe.read);
  
    return 0;
  }
  
  int process_start(pid_t *process,
                    const char *const *argv,
                    struct process_options options)
  {
    ASSERT(process);
  
    if (argv != NULL) {
      ASSERT(argv[0] != NULL);
    }
  
    struct {
      int read;
      int write;
    } pipe = { PIPE_INVALID, PIPE_INVALID };
    char *program = NULL;
    char **env = NULL;
    int r = -1;
  
    // We create an error pipe to receive errors from the child process.
    r = pipe_init(&pipe.read, &pipe.write);
    if (r < 0) {
      goto finish;
    }
  
    if (argv != NULL) {
      // We prepend the parent working directory to `program` if it is a
      // relative path so that it will always be searched for relative to the
      // parent working directory even after executing `chdir`.
      program = options.working_directory && path_is_relative(argv[0])
                    ? path_prepend_cwd(argv[0])
                    : strdup(argv[0]);
      if (program == NULL) {
        r = -errno;
        goto finish;
      }
    }
  
    extern char **environ; // NOLINT
    char *const *parent = options.env.behavior == REPROC_ENV_EMPTY ? NULL
                                                                   : environ;
    env = strv_concat(parent, options.env.extra);
    if (env == NULL) {
      goto finish;
    }
  
    int except[] = { options.handle.in, options.handle.out, options.handle.err,
                     pipe.read,         pipe.write,         options.handle.exit };
  
    r = process_fork(except, ARRAY_SIZE(except));
    if (r < 0) {
      goto finish;
    }
  
    if (r == 0) {
      // Redirect stdin, stdout and stderr.
  
      int redirect[] = { options.handle.in, options.handle.out,
                         options.handle.err };
  
      for (int i = 0; i < (int) ARRAY_SIZE(redirect); i++) {
        // `i` corresponds to the standard stream we need to redirect.
        r = dup2(redirect[i], i);
        if (r < 0) {
          r = -errno;
          goto child;
        }
  
        // Make sure we don't accidentally cloexec the standard streams of the
        // child process when we're inheriting the parent standard streams. If we
        // don't call `exec`, the caller is responsible for closing the redirect
        // and exit handles.
        if (redirect[i] != i) {
          // Make sure the pipe is closed when we call exec.
          r = handle_cloexec(redirect[i], true);
          if (r < 0) {
            goto child;
          }
        }
      }
  
      // Make sure the `exit` file descriptor is inherited.
  
      r = handle_cloexec(options.handle.exit, false);
      if (r < 0) {
        goto child;
      }
  
      if (options.working_directory != NULL) {
        r = chdir(options.working_directory);
        if (r < 0) {
          r = -errno;
          goto child;
        }
      }
  
      // `environ` is carried over calls to `exec`.
      environ = env;
  
      if (argv != NULL) {
        ASSERT(program);
  
        r = execvp(program, (char *const *) argv);
        if (r < 0) {
          r = -errno;
          goto child;
        }
      }
  
      env = NULL;
  
    child:
      if (r < 0) {
        (void) !write(pipe.write, &errno, sizeof(errno));
        _exit(EXIT_FAILURE);
      }
  
      pipe_destroy(pipe.read);
      pipe_destroy(pipe.write);
      free(program);
      strv_free(env);
  
      return 0;
    }
  
    pid_t child = r;
  
    // Close the error pipe write end on the parent's side so `read` will return
    // when it is closed on the child side as well.
    pipe.write = pipe_destroy(pipe.write);
  
    int child_errno = 0;
    r = (int) read(pipe.read, &child_errno, sizeof(child_errno));
    ASSERT_UNUSED(r >= 0);
  
    if (child_errno > 0) {
      r = waitpid(child, NULL, 0);
      r = r < 0 ? -errno : -child_errno;
      goto finish;
    }
  
    *process = child;
    r = 0;
  
  finish:
    pipe_destroy(pipe.read);
    pipe_destroy(pipe.write);
    free(program);
    strv_free(env);
  
    return r < 0 ? r : 1;
  }
  
  static int parse_status(int status)
  {
    return WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status) + 128;
  }
  
  int process_pid(process_type process)
  {
    return process;
  }
  
  int process_wait(pid_t process)
  {
    ASSERT(process != PROCESS_INVALID);
  
    int status = 0;
    int r = waitpid(process, &status, 0);
    if (r < 0) {
      return -errno;
    }
  
    ASSERT(r == process);
  
    return parse_status(status);
  }
  
  int process_terminate(pid_t process)
  {
    ASSERT(process != PROCESS_INVALID);
  
    int r = kill(process, SIGTERM);
    return r < 0 ? -errno : 0;
  }
  
  int process_kill(pid_t process)
  {
    ASSERT(process != PROCESS_INVALID);
  
    int r = kill(process, SIGKILL);
    return r < 0 ? -errno : 0;
  }
  
  pid_t process_destroy(pid_t process)
  {
    // `waitpid` already cleans up the process for us.
    (void) process;
    return PROCESS_INVALID;
  }
  