1331 lines · 53.1 KB · C
RawDownload
#define _GNU_SOURCE #include <errno.h>#include <fcntl.h>#include <poll.h>#include <sched.h>#include <signal.h>#include <spawn.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <sys/wait.h>#include <unistd.h>#ifdef __wasi__#include <wasi/api.h>#endif extern char **environ; static volatile sig_atomic_t usr1_calls;static volatile sig_atomic_t masked_usr1_calls;static volatile sig_atomic_t masked_usr1_depth;static volatile sig_atomic_t masked_usr1_max_depth;static volatile sig_atomic_t usr2_calls;static volatile sig_atomic_t usr2_during_usr1; static void usr1_handler(int signal) {    (void)signal;    usr1_calls++;} static void usr2_handler(int signal) {    (void)signal;    usr2_calls++;    if (masked_usr1_depth != 0)        usr2_during_usr1 = 1;} static void masked_usr1_handler(int signal) {    (void)signal;    masked_usr1_depth++;    if (masked_usr1_depth > masked_usr1_max_depth)        masked_usr1_max_depth = masked_usr1_depth;    masked_usr1_calls++;    if (masked_usr1_calls == 1) {        kill(getpid(), SIGUSR2);        kill(getpid(), SIGUSR1);    }    masked_usr1_depth--;} static void reset_usr1_handler(int signal) {    (void)signal;} static int wait_exit(pid_t child, int expected) {    int status = 0;    return waitpid(child, &status, 0) == child &&        WIFEXITED(status) && WEXITSTATUS(status) == expected;} static int spawn_self(const char *self, posix_spawn_file_actions_t *actions,    posix_spawnattr_t *attributes, char *const child_argv[], int *status_out) {    pid_t child = -1;    int error = posix_spawnp(&child, self, actions, attributes,        child_argv, environ);    if (error != 0)        return error;    int status = 0;    if (waitpid(child, &status, 0) != child)        return errno;    *status_out = status;    return 0;} static int query_mask_member(int signal) {    sigset_t current;    if (sigprocmask(SIG_SETMASK, NULL, &current) != 0)        return -1;    return sigismember(&current, signal);} static int child_actions(int argc, char **argv) {    if (argc != 7)        return 20;    int dir_fd = atoi(argv[3]);    int closed_fd = atoi(argv[4]);    int readable_fd = atoi(argv[5]);    int inherited_fd = atoi(argv[6]);    char cwd[512];    char payload[8] = {0};    struct stat payload_stat;    errno = 0;    int dir_closed = fcntl(dir_fd, F_GETFD) == -1 && errno == EBADF;    errno = 0;    int source_closed = fcntl(closed_fd, F_GETFD) == -1 && errno == EBADF;    errno = 0;    int inherited_closed =        fcntl(inherited_fd, F_GETFD) == -1 && errno == EBADF;    int cwd_ok = getcwd(cwd, sizeof(cwd)) != NULL && strcmp(cwd, argv[2]) == 0;    int readable = read(readable_fd, payload, sizeof(payload)) == 6 &&        memcmp(payload, "action", 6) == 0;    int cloexec_cleared = (fcntl(readable_fd, F_GETFD) & FD_CLOEXEC) == 0;    int public_preopen_hidden;#ifdef __wasi__    __wasi_prestat_t prestat;    public_preopen_hidden =        __wasi_fd_prestat_get(3, &prestat) == __WASI_ERRNO_BADF;#else    errno = 0;    public_preopen_hidden = fcntl(3, F_GETFD) == -1 && errno == EBADF;#endif    int path_resolution_preserved = stat("payload", &payload_stat) == 0 &&        S_ISREG(payload_stat.st_mode);    int ok = dir_closed && source_closed && inherited_closed && cwd_ok &&        readable && cloexec_cleared && public_preopen_hidden &&        path_resolution_preserved;    if (!ok)        fprintf(stderr,            "spawn_actions detail: dir_closed=%d source_closed=%d inherited_closed=%d cwd_ok=%d readable=%d cloexec_cleared=%d public_preopen_hidden=%d path_resolution_preserved=%d errno=%d cwd=%s expected=%s payload=%.*s\n",            dir_closed, source_closed, inherited_closed, cwd_ok, readable,            cloexec_cleared, public_preopen_hidden, path_resolution_preserved,            errno, cwd, argv[2], (int)sizeof(payload), payload);    printf("spawn_closefrom_public_preopen_hidden=%s\n",        public_preopen_hidden && path_resolution_preserved ? "yes" : "no");    printf("spawn_actions_child=%s\n", ok ? "ok" : "FAIL");    return ok ? 0 : 21;} static int child_inherit(int argc, char **argv) {    if (argc != 4)        return 30;    int shared_fd = atoi(argv[2]);    int cloexec_fd = atoi(argv[3]);    char byte = 0;    int shared = read(shared_fd, &byte, 1) == 1 && byte == 'b';    errno = 0;    int cloexec_closed = fcntl(cloexec_fd, F_GETFD) == -1 && errno == EBADF;    int ok = shared && cloexec_closed;    if (!ok)        fprintf(stderr,            "spawn_inherit detail: shared=%d byte=%d cloexec_closed=%d errno=%d shared_fd=%d cloexec_fd=%d\n",            shared, (int)(unsigned char)byte, cloexec_closed, errno,            shared_fd, cloexec_fd);    printf("spawn_inherit_child=%s\n", ok ? "ok" : "FAIL");    return ok ? 0 : 31;} static int child_same_fd_dup2(int argc, char **argv) {    if (argc != 3)        return 32;    int fd = atoi(argv[2]);    int flags = fcntl(fd, F_GETFD);    int ok = flags >= 0 && (flags & FD_CLOEXEC) == 0;    if (!ok)        fprintf(stderr,            "spawn same-fd dup2: fd=%d flags=%d errno=%d\n",            fd, flags, errno);    return ok ? 0 : 33;} static int child_live_cwd(int argc, char **argv) {    char cwd[512] = {0};    int ok = argc == 3 && getcwd(cwd, sizeof(cwd)) != NULL &&        strcmp(cwd, argv[2]) == 0;    if (!ok)        fprintf(stderr, "spawn live cwd: cwd=%s expected=%s errno=%d\n",            cwd, argc == 3 ? argv[2] : "<missing>", errno);    printf("spawn_live_cwd_child=%s\n", ok ? "yes" : "no");    return ok ? 0 : 34;} static int child_mask_query(int argc, char **argv) {    if (argc != 3)        return 40;    int expected = strcmp(argv[2], "blocked") == 0;    int observed = query_mask_member(SIGUSR1);    int ok = observed == expected;    printf("spawn_mask_child=%s\n", ok ? "ok" : "FAIL");    return ok ? 0 : 41;} static int child_mask_delivery(void) {    struct sigaction action;    sigset_t usr1;    memset(&action, 0, sizeof(action));    action.sa_handler = usr1_handler;    sigemptyset(&action.sa_mask);    if (sigaction(SIGUSR1, &action, NULL) != 0 ||        sigemptyset(&usr1) != 0 || sigaddset(&usr1, SIGUSR1) != 0)        return 50;    int initially_blocked = query_mask_member(SIGUSR1) == 1;    if (kill(getpid(), SIGUSR1) != 0 || kill(getpid(), SIGUSR1) != 0)        return 51;    usleep(1000);    int delayed = usr1_calls == 0;    if (sigprocmask(SIG_UNBLOCK, &usr1, NULL) != 0)        return 52;    usleep(1000);    int coalesced = usr1_calls == 1;    int ok = initially_blocked && delayed && coalesced;    printf("spawn_mask_delivery=%s\n", ok ? "ok" : "FAIL");    return ok ? 0 : 53;} static int child_sigpipe(void) {    if (kill(getpid(), SIGPIPE) != 0)        return 60;    usleep(1000);    printf("spawn_sigpipe_ignored=yes\n");    return 0;} static int child_resethand(void) {    struct sigaction action;    memset(&action, 0, sizeof(action));    action.sa_handler = reset_usr1_handler;    action.sa_flags = SA_RESETHAND;    sigemptyset(&action.sa_mask);    if (sigaction(SIGUSR1, &action, NULL) != 0 ||        kill(getpid(), SIGUSR1) != 0)        return 61;    usleep(1000);    if (kill(getpid(), SIGUSR1) != 0)        return 62;    usleep(1000);    return 63;} static int child_pgroup(int argc, char **argv) {    pid_t expected = argc == 3 ? (pid_t)atoi(argv[2]) : getpid();    pid_t observed_pgrp = getpgrp();    pid_t observed_pgid = getpgid(0);    int ok = observed_pgrp == expected && observed_pgid == expected;    if (!ok)        fprintf(stderr, "spawn pgroup: expected=%d getpgrp=%d getpgid=%d errno=%d\n",            (int)expected, (int)observed_pgrp, (int)observed_pgid, errno);    printf("spawn_pgroup_child=%s\n", ok ? "ok" : "FAIL");    return ok ? 0 : 70;} static ssize_t read_line_fd(int fd, char *buffer, size_t capacity) {    size_t used = 0;    while (used + 1 < capacity) {        ssize_t result = read(fd, buffer + used, 1);        if (result <= 0)            return used == 0 ? result : (ssize_t)used;        if (buffer[used++] == '\n')            break;    }    buffer[used] = '\0';    return (ssize_t)used;} static int child_bidirectional(void) {    struct stat stdin_stat;    struct stat stdout_stat;    if (fstat(STDIN_FILENO, &stdin_stat) != 0 ||        fstat(STDOUT_FILENO, &stdout_stat) != 0 ||        !S_ISFIFO(stdin_stat.st_mode) || !S_ISFIFO(stdout_stat.st_mode)) {        fprintf(stderr,            "spawn bidirectional fstat: stdin=%#o stdout=%#o errno=%d\n",            (unsigned)stdin_stat.st_mode, (unsigned)stdout_stat.st_mode, errno);        return 85;    }    char request[32];    const char *expected[] = {"request-one\n", "request-two\n"};    const char *responses[] = {"response-one\n", "response-two\n"};    for (size_t index = 0; index < 2; index++) {        ssize_t length = read_line_fd(STDIN_FILENO, request, sizeof(request));        if (length != (ssize_t)strlen(expected[index]) ||            memcmp(request, expected[index],                length > 0 ? (size_t)length : 0) != 0) {            fprintf(stderr,                "spawn bidirectional child read: index=%zu length=%zd expected=%zu errno=%d\n",                index, length, strlen(expected[index]), errno);            return 80 + (int)index;        }        if (write(STDOUT_FILENO, responses[index], strlen(responses[index])) !=            (ssize_t)strlen(responses[index]))            return 82 + (int)index;    }    char eof_probe;    errno = 0;    ssize_t eof_read = read(STDIN_FILENO, &eof_probe, 1);    if (eof_read != 0) {        fprintf(stderr,            "spawn bidirectional child EOF: length=%zd errno=%d\n",            eof_read, errno);        return 84;    }    return 0;} static int child_waitpid_producer(void) {    const char payload[] = "sibling-ready\n";    return write(STDOUT_FILENO, payload, sizeof(payload) - 1) ==        (ssize_t)(sizeof(payload) - 1) ? 0 : 87;} static int child_waitpid_consumer(void) {    char payload[32];    ssize_t length = read_line_fd(STDIN_FILENO, payload, sizeof(payload));    return length == 14 && strcmp(payload, "sibling-ready\n") == 0 ? 0 : 88;} static int child_hold_stdin_until_eof(void) {    char buffer[32];    while (read(STDIN_FILENO, buffer, sizeof(buffer)) > 0) {    }    return 0;} static int test_spawn_pipe_writer_lifetime(const char *self,        int close_parent_writer_before_holder, int holder_uses_closefrom) {    int output[2] = {-1, -1};    int control[2] = {-1, -1};    if (pipe(output) != 0 || pipe(control) != 0)        return 0;     posix_spawn_file_actions_t actions;    posix_spawn_file_actions_init(&actions);    int producer_setup =        posix_spawn_file_actions_adddup2(&actions, output[1], STDOUT_FILENO) == 0 &&        posix_spawn_file_actions_addclose(&actions, output[0]) == 0 &&        posix_spawn_file_actions_addclose(&actions, output[1]) == 0 &&        posix_spawn_file_actions_addclose(&actions, control[0]) == 0 &&        posix_spawn_file_actions_addclose(&actions, control[1]) == 0;    char *producer_argv[] = {(char *)self, "--waitpid-producer", NULL};    pid_t producer = -1;    int producer_error = producer_setup        ? posix_spawnp(&producer, self, &actions, NULL, producer_argv, environ)        : EINVAL;    posix_spawn_file_actions_destroy(&actions);     if (close_parent_writer_before_holder) {        if (close(output[1]) != 0)            return 0;        output[1] = -1;    }     posix_spawn_file_actions_init(&actions);    int holder_setup =        posix_spawn_file_actions_adddup2(&actions, control[0], STDIN_FILENO) == 0 &&        posix_spawn_file_actions_addclose(&actions, control[0]) == 0 &&        posix_spawn_file_actions_addclose(&actions, control[1]) == 0;    if (holder_setup && holder_uses_closefrom)        holder_setup = posix_spawn_file_actions_addclosefrom_np(&actions,            STDERR_FILENO + 1) == 0;    char *holder_argv[] = {(char *)self, "--hold-stdin", NULL};    pid_t holder = -1;    int holder_error = producer_error == 0 && holder_setup        ? posix_spawnp(&holder, self, &actions, NULL, holder_argv, environ)        : EINVAL;    posix_spawn_file_actions_destroy(&actions);     if (output[1] >= 0)        close(output[1]);    close(control[0]);    char payload[32] = {0};    ssize_t payload_length = read_line_fd(output[0], payload, sizeof(payload));    int producer_status = 0;    pid_t producer_wait = producer_error == 0        ? waitpid(producer, &producer_status, 0) : -1;    struct pollfd eof_poll = {        .fd = output[0],        .events = POLLIN | POLLHUP,        .revents = 0,    };    int poll_result = poll(&eof_poll, 1, 1000);    char eof_byte = 0;    ssize_t eof_read = poll_result > 0 ? read(output[0], &eof_byte, 1) : -1;     close(control[1]);    int holder_status = 0;    pid_t holder_wait = holder_error == 0        ? waitpid(holder, &holder_status, 0) : -1;    close(output[0]);     int ok = payload_length == 14 && strcmp(payload, "sibling-ready\n") == 0 &&        producer_wait == producer && WIFEXITED(producer_status) &&        WEXITSTATUS(producer_status) == 0 && poll_result > 0 && eof_read == 0 &&        holder_wait == holder && WIFEXITED(holder_status) &&        WEXITSTATUS(holder_status) == 0;    if (!ok)        fprintf(stderr,            "spawn pipe writer lifetime: parent_closed_first=%d closefrom=%d producer_error=%d holder_error=%d payload_length=%zd producer_wait=%d producer_status=%d poll=%d revents=%#x eof_read=%zd holder_wait=%d holder_status=%d errno=%d\n",            close_parent_writer_before_holder, holder_uses_closefrom,            producer_error, holder_error, payload_length, (int)producer_wait,            producer_status, poll_result, eof_poll.revents, eof_read,            (int)holder_wait, holder_status, errno);    printf("%s=%s\n",        holder_uses_closefrom ? "spawn_closefrom_pipe_writer_eof" :            "spawn_retained_writer_not_inherited",        ok ? "yes" : "no");    return ok;} static int test_spawn_closefrom_closes_unrelated_pipe_writer(const char *self) {    return test_spawn_pipe_writer_lifetime(self, 0, 1);} static int test_spawn_retained_writer_is_not_inherited(const char *self) {    return test_spawn_pipe_writer_lifetime(self, 1, 0);} static int test_waitpid_schedules_sibling(const char *self) {    int transport[2] = {-1, -1};    if (pipe(transport) != 0)        return 0;     posix_spawn_file_actions_t actions;    posix_spawn_file_actions_init(&actions);    int consumer_setup =        posix_spawn_file_actions_adddup2(&actions, transport[0], STDIN_FILENO) == 0 &&        posix_spawn_file_actions_addclose(&actions, transport[0]) == 0 &&        posix_spawn_file_actions_addclose(&actions, transport[1]) == 0;    char *consumer_argv[] = {(char *)self, "--waitpid-consumer", NULL};    pid_t consumer = -1;    int consumer_error = consumer_setup        ? posix_spawnp(&consumer, self, &actions, NULL, consumer_argv, environ)        : EINVAL;    posix_spawn_file_actions_destroy(&actions);     posix_spawn_file_actions_init(&actions);    int producer_setup =        posix_spawn_file_actions_adddup2(&actions, transport[1], STDOUT_FILENO) == 0 &&        posix_spawn_file_actions_addclose(&actions, transport[0]) == 0 &&        posix_spawn_file_actions_addclose(&actions, transport[1]) == 0;    char *producer_argv[] = {(char *)self, "--waitpid-producer", NULL};    pid_t producer = -1;    int producer_error = consumer_error == 0 && producer_setup        ? posix_spawnp(&producer, self, &actions, NULL, producer_argv, environ)        : EINVAL;    posix_spawn_file_actions_destroy(&actions);    close(transport[0]);    close(transport[1]);     int consumer_status = 0;    int producer_status = 0;    pid_t consumer_wait = consumer_error == 0        ? waitpid(consumer, &consumer_status, 0) : -1;    pid_t producer_wait = producer_error == 0        ? waitpid(producer, &producer_status, 0) : -1;    int ok = consumer_wait == consumer && WIFEXITED(consumer_status) &&        WEXITSTATUS(consumer_status) == 0 && producer_wait == producer &&        WIFEXITED(producer_status) && WEXITSTATUS(producer_status) == 0;    if (!ok)        fprintf(stderr,            "waitpid sibling scheduling: consumer_error=%d producer_error=%d consumer_wait=%d consumer_status=%d producer_wait=%d producer_status=%d errno=%d\n",            consumer_error, producer_error, (int)consumer_wait, consumer_status,            (int)producer_wait, producer_status, errno);    printf("waitpid_schedules_sibling=%s\n", ok ? "yes" : "no");    return ok;} static int child_stdout_closed(void) {    struct stat stdout_stat;    errno = 0;    int fstat_closed = fstat(STDOUT_FILENO, &stdout_stat) == -1 &&        errno == EBADF;    errno = 0;    int write_closed = write(STDOUT_FILENO, "x", 1) == -1 &&        errno == EBADF;    errno = 0;    int close_closed = close(STDOUT_FILENO) == -1 && errno == EBADF;    int ok = fstat_closed && write_closed && close_closed;    if (!ok)        fprintf(stderr,            "spawn closed stdout child: fstat=%d write=%d close=%d errno=%d\n",            fstat_closed, write_closed, close_closed, errno);    return ok ? 0 : 86;} static int test_bidirectional_spawn_pipes(const char *self) {    int requests[2];    int responses[2];    if (pipe(requests) != 0 || pipe(responses) != 0)        return 0;     posix_spawn_file_actions_t actions;    posix_spawn_file_actions_init(&actions);    posix_spawn_file_actions_adddup2(&actions, requests[0], STDIN_FILENO);    posix_spawn_file_actions_adddup2(&actions, responses[1], STDOUT_FILENO);    posix_spawn_file_actions_addclose(&actions, requests[0]);    posix_spawn_file_actions_addclose(&actions, requests[1]);    posix_spawn_file_actions_addclose(&actions, responses[0]);    posix_spawn_file_actions_addclose(&actions, responses[1]);    char *child_argv[] = {(char *)self, "--bidirectional", NULL};    pid_t child = -1;    int spawn_error = posix_spawnp(&child, self, &actions, NULL,        child_argv, environ);    posix_spawn_file_actions_destroy(&actions);    close(requests[0]);    close(responses[1]);    if (spawn_error != 0) {        close(requests[1]);        close(responses[0]);        return 0;

Lines 1–500 of 1331.