// SPDX-FileCopyrightText: 2018 Frank Hunleth
//
// SPDX-License-Identifier: Apache-2.0

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <grp.h>
#include <poll.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#ifdef DEBUG
static FILE *debug_fp = NULL;
#define INFO(MSG, ...) do { fprintf(debug_fp, "%d:" MSG "\n", microsecs(), ## __VA_ARGS__); fflush(debug_fp); } while (0)
#else
#define INFO(MSG, ...) ;
#endif

// asprintf can fail, but it's so rare that it's annoying to see the checks in the code.
#define checked_asprintf(MSG, ...) do { if (asprintf(MSG, ## __VA_ARGS__) < 0) err(EXIT_FAILURE, "asprintf"); } while (0)

static struct option long_options[] = {
    {"arg0", required_argument, 0, '0'},
    {"controller", required_argument, 0, 'c'},
    {"help",     no_argument,       0, 'h'},
    {"delay-to-sigkill", required_argument, 0, 'k'},
    {"group", required_argument, 0, 'g'},
    {"set", required_argument, 0, 's'},
    {"uid", required_argument, 0, 'u'},
    {"gid", required_argument, 0, 'a'},
    {"stdio-window", required_argument, 0, 'l'},
    {"capture-output", no_argument, 0, 'o'},
    {"capture-stderr", no_argument, 0, 'e'},
    {0,          0,                 0, 0 }
};

#define CGROUP_MOUNT_PATH "/sys/fs/cgroup"

struct controller_var {
    struct controller_var *next;
    const char *key;
    const char *value;
};

struct controller_info {
    const char *name;
    char *group_path;
    char *procfile;

    struct controller_var *vars;
    struct controller_info *next;
};

static struct controller_info *controllers = NULL;
static const char *cgroup_path = NULL;
static int brutal_kill_wait_ms = 500;
static uid_t run_as_uid = 0; // 0 means don't set, since we don't support privilege escalation
static gid_t run_as_gid = 0; // 0 means don't set, since we don't support privilege escalation

static int signal_pipe[2] = { -1, -1};
static int stdout_pipe[2] = { -1, -1};
static int stderr_pipe[2] = { -1, -1};

#define DEFAULT_STDIO_WINDOW 10240 // Allow up to 10 KB out to Elixir at a time
static int stdio_bytes_max = DEFAULT_STDIO_WINDOW;
static int stdio_bytes_avail = DEFAULT_STDIO_WINDOW;
static int capture_output = 0; // Don't capture output by default
static int capture_stderr = 0; // If capturing output, don't capture stderr by default

#define FOREACH_CONTROLLER for (struct controller_info *controller = controllers; controller != NULL; controller = controller->next)

static void move_pid_to_cgroups(pid_t pid);

static void usage()
{
    printf("Usage: muontrap [OPTION] -- <program> <args>\n");
    printf("\n");
    printf("Options:\n");

    printf("--arg0,-0 <arg0>\n");
    printf("--controller,-c <cgroup controller> (may be specified multiple times)\n");
    printf("--group,-g <cgroup path>\n");
    printf("--set,-s <cgroup variable>=<value>\n (may be specified multiple times)\n");
    printf("--delay-to-sigkill,-k <milliseconds>\n");
    printf("--stdio-window <bytes>\n");
    printf("--capture-output\n");
    printf("--capture-stderr\n");
    printf("--uid <uid/user> drop privilege to this uid or user\n");
    printf("--gid <gid/group> drop privilege to this gid or group\n");
    printf("-- the program to run and its arguments come after this\n");
}

static int microsecs()
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
}

void sigchild_handler(int signum)
{
    if (signal_pipe[1] >= 0 &&
            write(signal_pipe[1], &signum, sizeof(signum)) < 0)
        warn("write(signal_pipe)");
}

void enable_signal_handlers()
{
    struct sigaction sa;
    sa.sa_handler = sigchild_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGCHLD, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGQUIT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
}

void disable_signal_handlers()
{
    sigaction(SIGCHLD, NULL, NULL);
    sigaction(SIGINT, NULL, NULL);
    sigaction(SIGQUIT, NULL, NULL);
    sigaction(SIGTERM, NULL, NULL);
}

static int fork_exec(const char *path, char *const *argv)
{
    INFO("Running %s", path);
    for (char *const *arg = argv; *arg != NULL; arg++) {
        INFO("  arg: %s", *arg);
    }

    pid_t pid = fork();
    if (pid == 0) {
        // child

        // Move to the container
        move_pid_to_cgroups(getpid());

        if (capture_output) {
            // Replace stdout a with flow controlled versions
            if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0)
                err(EXIT_FAILURE, "dup2 STDOUT_FILENO");

            // If capturing stderr too, do the same thing.
            if (capture_stderr) {
                if (dup2(stderr_pipe[1], STDERR_FILENO) < 0)
                    err(EXIT_FAILURE, "dup2 STDERR_FILENO");
            }
        } else {
            // Not capturing stdout, so send it to /dev/null to get it dropped with as little processing as possible
            int dev_null_fd = open("/dev/null", O_WRONLY);
            if (dev_null_fd < 0)
                err(EXIT_FAILURE, "Can't open /dev/null");

            if (dup2(dev_null_fd, STDOUT_FILENO) < 0)
                err(EXIT_FAILURE, "dup2 STDOUT_FILENO");
            close(dev_null_fd);
        }

        // Drop/change privilege if requested
        // See https://wiki.sei.cmu.edu/confluence/display/c/POS36-C.+Observe+correct+revocation+order+while+relinquishing+privileges
        if (run_as_gid > 0 && setgid(run_as_gid) < 0)
            err(EXIT_FAILURE, "setgid(%d)", run_as_gid);

        if (run_as_uid > 0 && setuid(run_as_uid) < 0)
            err(EXIT_FAILURE, "setuid(%d)", run_as_uid);

        execvp(path, argv);

        // Not supposed to reach here.
        exit(EXIT_FAILURE);
    } else {

        return pid;
    }
}

static int mkdir_p(const char *abspath, int start_index)
{
    int rc = 0;
    int last_errno = 0;
    char *group_path = strdup(abspath);
    for (int i = start_index; ; i++) {
        if (group_path[i] == '/' || group_path[i] == 0) {
            char save = group_path[i];
            group_path[i] = 0;
            rc = mkdir(group_path, 0755);
            if (rc < 0)
                last_errno = errno;

            group_path[i] = save;
            if (save == 0)
                break;
        }
    }
    free(group_path);

    // Return the last call to mkdir since that's the one that matters
    // and earlier directories are likely already created.
    errno = last_errno;
    return rc;
}

static void create_cgroups()
{
    FOREACH_CONTROLLER {
        int start_index = strlen(CGROUP_MOUNT_PATH) + 1 + strlen(controller->name) + 1;
        INFO("Create cgroup: mkdir -p %s", controller->group_path);
        if (mkdir_p(controller->group_path, start_index) < 0) {
            if (errno == EEXIST)
                errx(EXIT_FAILURE, "'%s' already exists. Please specify a deeper group_path or clean up the cgroup",
                     controller->group_path);
            else
                err(EXIT_FAILURE, "Couldn't create '%s'. Check permissions.", controller->group_path);
        }
    }
}

static int write_file(const char *group_path, const char *value)
{
   FILE *fp = fopen(group_path, "w");
   if (!fp)
       return -1;

   int rc = fwrite(value, 1, strlen(value), fp);
   fclose(fp);
   return rc;
}

static void update_cgroup_settings()
{
    FOREACH_CONTROLLER {
        for (struct controller_var *var = controller->vars;
             var != NULL;
             var = var->next) {
            char *setting_file;
            checked_asprintf(&setting_file, "%s/%s", controller->group_path, var->key);
            if (write_file(setting_file, var->value) < 0)
                err(EXIT_FAILURE, "Error writing '%s' to '%s'", var->value, setting_file);
            free(setting_file);
        }
    }
}

static void move_pid_to_cgroups(pid_t pid)
{
    FOREACH_CONTROLLER {
        FILE *fp = fopen(controller->procfile, "w");
        if (fp == NULL ||
            fprintf(fp, "%d", pid) < 0)
            err(EXIT_FAILURE, "Can't add pid to %s", controller->procfile);
        fclose(fp);
    }
}

static void destroy_cgroups()
{
    FOREACH_CONTROLLER {
        // Only remove the final directory, since we don't keep track of
        // what we actually create.
        INFO("rmdir %s", controller->group_path);
        if (rmdir(controller->group_path) < 0) {
            INFO("Error removing %s (%s)", controller->group_path, strerror(errno));
            warn("Error removing %s", controller->group_path);
        }
    }
}

static int procfile_killall(const char *group_path, int sig)
{
    int children_killed = 0;

    FILE *fp = fopen(group_path, "r");
    if (!fp)
        return children_killed;

    int pid;
    while (fscanf(fp, "%d", &pid) == 1) {
        INFO("  kill -%d %d", sig, pid);
        kill(pid, sig);
        children_killed++;
    }
    fclose(fp);
    return children_killed;
}

static int kill_children(int sig)
{
    int children_killed = 0;
    FOREACH_CONTROLLER {
        INFO("killall -%d from %s", sig, controller->procfile);
        children_killed += procfile_killall(controller->procfile, sig);
    }
    return children_killed;
}

#ifdef DEBUG
static void read_proc_cmdline(int pid, char *cmdline)
{
    char *cmdline_filename;

    checked_asprintf(&cmdline_filename, "/proc/%d/cmdline", pid);
    FILE *fp = fopen(cmdline_filename, "r");
    if (fp) {
        size_t len = fread(cmdline, 1, 128, fp);
        if (len > 0)
            cmdline[len] = 0;
        else
            strcpy(cmdline, "<NULL>");
        fclose(fp);
    } else {
        sprintf(cmdline, "Error reading %s", cmdline_filename);
    }

    free(cmdline_filename);
}

static void procfile_dump_children(const char *group_path)
{
    INFO("---Begin child list for %s", group_path);
    FILE *fp = fopen(group_path, "r");
    if (!fp) {
        INFO("Error reading child list!");
        return;
    }

    int pid;
    while (fscanf(fp, "%d", &pid) == 1) {
        char cmdline[129];
        read_proc_cmdline(pid, cmdline);
        INFO("  %d: %s", pid, cmdline);
    }
    fclose(fp);
    INFO("---End child list for %s", group_path);
}

static void dump_all_children_from_cgroups()
{
    FOREACH_CONTROLLER {
        procfile_dump_children(controller->procfile);
    }
}
#endif

static void finish_controller_init()
{
    FOREACH_CONTROLLER {
        checked_asprintf(&controller->group_path, "%s/%s/%s", CGROUP_MOUNT_PATH, controller->name, cgroup_path);
        checked_asprintf(&controller->procfile, "%s/cgroup.procs", controller->group_path);
    }
}

static int wait_for_sigchld(pid_t pid_to_match, int timeout_ms)
{
    struct pollfd fds[1];
    fds[0].fd = signal_pipe[0];
    fds[0].events = POLLIN;

    int end_timeout_us = microsecs() + (1000 * timeout_ms);
    int next_time_to_wait_ms = timeout_ms;
    do {
        INFO("poll - %d ms", next_time_to_wait_ms);
        if (poll(fds, 1, next_time_to_wait_ms) < 0) {
            if (errno == EINTR)
                continue;

            warn("poll");
            return -1;
        }

        if (fds[0].revents) {
            int signal;
            ssize_t amt = read(signal_pipe[0], &signal, sizeof(signal));
            if (amt < 0) {
                warn("read signal_pipe");
                return -1;
            }

            INFO("signal_pipe - SIGNAL %d", signal);
            switch (signal) {
            case SIGCHLD: {
                int status;
                pid_t pid = wait(&status);
                if (pid_to_match == pid) {
                    INFO("cleaned up matching pid %d.", pid);
                    return 0;
                }
                INFO("cleaned up pid %d.", pid);
                break;
            }

            case SIGTERM:
            case SIGQUIT:
            case SIGINT:
                return -1;

            default:
                warnx("unexpected signal: %d", signal);
                return -1;
            }
        }

        next_time_to_wait_ms = (end_timeout_us - microsecs()) / 1000;
    } while (next_time_to_wait_ms > 0);

    INFO("timed out waiting for pid %d", pid_to_match);
    return -1;
}

static void cleanup_all_children()
{
    // In order to cleanup the cgroup, all processes need to exit.
    // The immediate child of muontrap will have either exited
    // at this point, so any other processes are orphaned descendents.
    // I.e., Their parent is now PID 1 and we won't get a SIGCHLD when
    // they die. We only know who they are since they're in the cgroup.

    // Send every child a SIGKILL
    int children_left = kill_children(SIGKILL);
    if (children_left > 0) {
        INFO("Found %d pids and sent them a SIGKILL", children_left);
        // poll to see if the cleanup is done every 1 ms
        int poll_intervals = brutal_kill_wait_ms / 1;
        do {
            usleep(1000);

            // Check for children and send SIGKILLs again. This
            // handles the race where we a new process was spawned
            // when we iterated through the pids the previous time.
            children_left = kill_children(SIGKILL);
            INFO("%d pids are still around", children_left);
            poll_intervals--;
        } while (poll_intervals && children_left);

        if (children_left > 0) {
            warnx("Failed to kill %d pids!", children_left);
#ifdef DEBUG
            dump_all_children_from_cgroups();
#endif
        }
    }
}

static void kill_child_nicely(pid_t child)
{
    // Start with SIGTERM
    int rc = kill(child, SIGTERM);
    INFO("kill -%d %d -> %d (%s)", SIGTERM, child, rc, rc < 0 ? strerror(errno) : "success");
    if (rc < 0)
        return;

    // Wait a little for the child to exit
    if (wait_for_sigchld(child, brutal_kill_wait_ms) < 0) {
        // Child didn't exit, so SIGKILL it.
        rc = kill(child, SIGKILL);
        INFO("kill -%d %d -> %d (%s)", SIGKILL, child, rc, rc < 0 ? strerror(errno) : "success");
        if (rc < 0)
            return;

        if (wait_for_sigchld(child, brutal_kill_wait_ms) < 0)
            warnx("SIGKILL didn't work on %d", child);
    }
}

static struct controller_info *add_controller(const char *name)
{
    // If the controller exists, don't add it twice.
    for (struct controller_info *c = controllers; c != NULL; c = c->next) {
        if (strcmp(name, c->name) == 0)
            return c;
    }

    struct controller_info *new_controller = malloc(sizeof(struct controller_info));
    new_controller->name = name;
    new_controller->group_path = NULL;
    new_controller->vars = NULL;
    new_controller->next = controllers;
    controllers = new_controller;

    return new_controller;
}

static void add_controller_setting(struct controller_info *controller, const char *key, const char *value)
{
    struct controller_var *new_var = malloc(sizeof(struct controller_var));
    new_var->key = key;
    new_var->value = value;
    new_var->next = controller->vars;
    controller->vars = new_var;
}

#if defined(__linux__)
static void process_stdio(int from_fd) {
    if (stdio_bytes_avail <= 0)
        return;

retry:
    ssize_t written = splice(from_fd, NULL, STDOUT_FILENO, NULL, stdio_bytes_avail, SPLICE_F_MOVE);
    if (written < 0) {
        if (errno == EINTR)
            goto retry;

        err(EXIT_FAILURE, "failed to splice stdio (%d bytes)", stdio_bytes_avail);
    }
    stdio_bytes_avail -= written;
}
#else
static void process_stdio(int from_fd) {
    if (stdio_bytes_avail <= 0)
        return;

    size_t max_to_read = stdio_bytes_avail > 4096 ? 4096 : stdio_bytes_avail;
    char buff[max_to_read];
    ssize_t got = read(from_fd, buff, max_to_read);

    if (got > 0) {
        for (ssize_t i = 0; i < got;) {
            ssize_t written = write(STDOUT_FILENO, &buff[i], got - i);

            if (written <= 0) {
                if (errno == EINTR)
                    continue;

                err(EXIT_FAILURE, "failed to copy stdio");
            }
            stdio_bytes_avail -= written;
            i += written;
        }
    }
}
#endif

static int child_wait_loop(pid_t child_pid, int *still_running)
{
    struct pollfd fds[4];
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN | POLLHUP; // POLLERR is implicit
    fds[1].fd = signal_pipe[0];
    fds[1].events = POLLIN;
    fds[2].fd = stdout_pipe[0];
    fds[2].events = POLLIN;
    fds[3].fd = stderr_pipe[0];
    fds[3].events = POLLIN;
    int poll_num = 2;

    for (;;) {
        poll_num = 2;
        // Also poll stdout and optionally stderr when capturing output and accepting stdio data
        if (capture_output && stdio_bytes_avail > 0) {
            poll_num++;

            if (capture_stderr)
                poll_num++;
        }

        if (poll(fds, poll_num, -1) < 0) {
            if (errno == EINTR)
                continue;

            warn("poll");
            return EXIT_FAILURE;
        }

        if (fds[0].revents & POLLHUP) {
            // Erlang signals that it's done by closing stdin. Exit immediately.
            INFO("stdin closed. Exiting...");
            return EXIT_FAILURE;
        }

        if (fds[0].revents & POLLIN) {
            uint8_t acknowledgments[32];
            ssize_t amt = read(STDIN_FILENO, acknowledgments, sizeof(acknowledgments));
            if (amt < 0) {
                INFO("read STDIN_FILENO");
                return EXIT_FAILURE;
            }

            // More than one acknowledgment may have come in, so process them all.
            // NOTE: each ack is 1+its_value
            int total_acks = amt;
            for (ssize_t i = 0; i < amt; i++)
                total_acks += acknowledgments[i];

            stdio_bytes_avail += total_acks;
            if (stdio_bytes_avail > stdio_bytes_max)
                errx(EXIT_FAILURE, "Too many acks %d/%d, got %d", (int) stdio_bytes_avail, (int) stdio_bytes_max, total_acks);
        }

        if (poll_num > 2 && fds[2].revents)
            process_stdio(fds[2].fd);

        if (poll_num > 3 && fds[3].revents)
            process_stdio(fds[3].fd);

        if (fds[1].revents) {
            int signal;
            ssize_t amt = read(signal_pipe[0], &signal, sizeof(signal));
            if (amt < 0) {
                warn("read signal_pipe");
                return EXIT_FAILURE;
            }

            switch (signal) {
            case SIGCHLD: {
                int status;
                pid_t dying_pid = wait(&status);
                if (dying_pid == child_pid) {
                    // Let the caller know that the child isn't running and has been cleaned up
                    *still_running = 0;

                    int exit_status;
                    if (WIFSIGNALED(status)) {
                        // Crash on signal, return the signal in the exit status. See POSIX:
                        // http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02
                        exit_status = 128 + WTERMSIG(status);
                        INFO("child terminated via signal %d. our exit status: %d", status, exit_status);
                    } else if (WIFEXITED(status)) {
                        exit_status = WEXITSTATUS(status);
                        INFO("child exited with exit status: %d", exit_status);
                    } else {
                        INFO("child terminated with unexpected status: %d", status);
                        exit_status = EXIT_FAILURE;
                    }
                    return exit_status;
                } else {
                    INFO("something else caused sigchild: pid=%d, status=%d. our child=%d", dying_pid, status, child_pid);
                }
                break;
            }

            case SIGTERM:
            case SIGQUIT:
            case SIGINT:
                return EXIT_FAILURE;

            default:
                warnx("unexpected signal: %d", signal);
                return EXIT_FAILURE;
            }
        }
    }
}

int main(int argc, char *argv[])
{
#ifdef DEBUG
    char filename[64];
    sprintf(filename, "muontrap-%d.log", getpid());
    debug_fp = fopen(filename, "w");
    if (!debug_fp)
        debug_fp = stderr;
#endif
    INFO("muontrap argc=%d", argc);
    if (argc == 1) {
        usage();
        exit(EXIT_FAILURE);
    }

    int opt;
    char *argv0 = NULL;
    struct controller_info *current_controller = NULL;
    while ((opt = getopt_long(argc, argv, "a:c:g:hk:s:0:", long_options, NULL)) != -1) {
        switch (opt) {
        case 'a': // --gid
        {
            char *endptr;
            run_as_gid = strtoul(optarg, &endptr, 0);
            if (*endptr != '\0') {
                struct group *group = getgrnam(optarg);
                if (!group)
                    errx(EXIT_FAILURE, "Unknown group '%s'", optarg);
                run_as_gid = group->gr_gid;
            }
            if (run_as_gid == 0)
                errx(EXIT_FAILURE, "Setting the group to root or gid 0 is not allowed");
            break;
        }

        case 'c':
            current_controller = add_controller(optarg);
            break;

        case 'g':
            if (cgroup_path)
                errx(EXIT_FAILURE, "Only one cgroup group_path supported.");
            cgroup_path = optarg;
            break;

        case 'h':
            usage();
            exit(EXIT_SUCCESS);

        case 'k': // --delay-to-sigkill
            brutal_kill_wait_ms = strtoul(optarg, NULL, 0);
            break;

        case 'l': // --stdio-window
            stdio_bytes_max = strtol(optarg, NULL, 0);
            if (stdio_bytes_max < 16)
                stdio_bytes_max = 16;

            stdio_bytes_avail = stdio_bytes_max;
            break;

        case 'o': // --capture-output
            capture_output = 1;
            break;

        case 'e': // --capture-stderr
            capture_stderr = 1;
            break;

        case 's':
        {
            if (!current_controller)
                errx(EXIT_FAILURE, "Specify a cgroup controller (-c) before setting a variable");

            char *equalsign = strchr(optarg, '=');
            if (!equalsign)
                errx(EXIT_FAILURE, "No '=' found when setting a variable: '%s'", optarg);

            // NULL terminate the key. We can do this since we're already modifying
            // the arguments by using getopt.
            *equalsign = '\0';
            add_controller_setting(current_controller, optarg, equalsign + 1);
            break;
        }

        case 'u': // --uid
        {
            char *endptr;
            run_as_uid = strtoul(optarg, &endptr, 0);
            if (*endptr != '\0') {
                struct passwd *passwd = getpwnam(optarg);
                if (!passwd)
                    errx(EXIT_FAILURE, "Unknown user '%s'", optarg);
                run_as_uid = passwd->pw_uid;
            }
            if (run_as_uid == 0)
                errx(EXIT_FAILURE, "Setting the user to root or uid 0 is not allowed");
            break;
        }

        case '0': // --argv0
            argv0 = optarg;
            break;

        default:
            usage();
            exit(EXIT_FAILURE);
        }
    }

    if (argc == optind)
        errx(EXIT_FAILURE, "Specify a program to run");

    if (cgroup_path == NULL && controllers)
        errx(EXIT_FAILURE, "Specify a cgroup group_path (-g)");

    if (cgroup_path && !controllers)
        errx(EXIT_FAILURE, "Specify a cgroup controller (-c) if you specify a group_path");

    finish_controller_init();

    // Finished processing commandline. Initialize and run child.

    if (pipe(signal_pipe) < 0)
        err(EXIT_FAILURE, "pipe");
    if (fcntl(signal_pipe[0], F_SETFD, FD_CLOEXEC) < 0 ||
        fcntl(signal_pipe[1], F_SETFD, FD_CLOEXEC) < 0)
        warn("fcntl(FD_CLOEXEC)");

    if (capture_output) {
        if (pipe(stdout_pipe) < 0)
            err(EXIT_FAILURE, "pipe");
        if (fcntl(stdout_pipe[0], F_SETFD, FD_CLOEXEC) < 0 ||
            fcntl(stdout_pipe[1], F_SETFD, FD_CLOEXEC) < 0)
            warn("fcntl(FD_CLOEXEC)");

        if (capture_stderr) {
            if (pipe(stderr_pipe) < 0)
                err(EXIT_FAILURE, "pipe");
            if (fcntl(stderr_pipe[0], F_SETFD, FD_CLOEXEC) < 0 ||
                fcntl(stderr_pipe[1], F_SETFD, FD_CLOEXEC) < 0)
                warn("fcntl(FD_CLOEXEC)");
        }
    }

    enable_signal_handlers();

    create_cgroups();

    update_cgroup_settings();

    const char *program_name = argv[optind];
    if (argv0)
        argv[optind] = argv0;
    pid_t pid = fork_exec(program_name, &argv[optind]);

    int still_running = 1;
    int exit_status = child_wait_loop(pid, &still_running);

    if (still_running) {
        // Kill our immediate child if it's still running
        kill_child_nicely(pid);
    }

    // Cleanup all descendents if using cgroups
    cleanup_all_children();

    destroy_cgroups();
    disable_signal_handlers();

    exit(exit_status);
}
