#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, ¤t) != 0) return -1; return sigismember(¤t, 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. Load more
#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, ¤t) != 0) return -1; return sigismember(¤t, 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;