556 lines
22 KiB
C
556 lines
22 KiB
C
// User-mode smoke test for server-owned POSIX spawn and child lifecycle semantics.
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <spawn.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <ntlite/posix_extensions.h>
|
|
|
|
#define CONTROL_READ_FD 40
|
|
#define ACTION_OPEN_FD 41
|
|
#define ACTION_DUP_FD 42
|
|
#define CONTROL_WRITE_FD 43
|
|
#define CONTROL_WRITE_A_FD 44
|
|
#define EXEC_PARENT_READ_FD 45
|
|
#define EXEC_CHILD_WRITE_FD 46
|
|
#define EXEC_CHILD_READ_FD 47
|
|
#define EXEC_PARENT_WRITE_FD 48
|
|
#define EXEC_RETAIN_FD 49
|
|
#define EXEC_CLOEXEC_FD 50
|
|
|
|
static const char* gExecFile = "posix_process_exec.tmp";
|
|
static volatile uint32_t gVforkChildTouched;
|
|
|
|
static void print(const char* text) {
|
|
(void)write(1, text, strlen(text));
|
|
}
|
|
|
|
// Terminates the smoke test with one diagnostic.
|
|
static void fail(const char* message) {
|
|
print("posix_process_smoke: ");
|
|
print(message);
|
|
print("\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Returns whether the command-line compatibility view contains one child marker.
|
|
static bool child_mode(const char* marker) {
|
|
const char* command = posix_get_command_line();
|
|
return command && strstr(command, marker) != (char*)0;
|
|
}
|
|
|
|
// Moves one descriptor to an exact number and closes the source alias.
|
|
static void move_fd(int source, int target) {
|
|
if (source < 0 || dup2(source, target) != target)
|
|
fail("control dup2 failed");
|
|
if (source != target && close(source) != 0)
|
|
fail("control source close failed");
|
|
}
|
|
|
|
// Builds one inherited control pipe with a close-on-spawn parent write endpoint.
|
|
static void make_control_pipe(int writeFd) {
|
|
int pipefd[2];
|
|
if (pipe(pipefd) != 0)
|
|
fail("control pipe failed");
|
|
move_fd(pipefd[0], CONTROL_READ_FD);
|
|
move_fd(pipefd[1], writeFd);
|
|
if (fcntl(writeFd, F_SETFD, FD_CLOEXEC) != 0)
|
|
fail("control CLOEXEC failed");
|
|
}
|
|
|
|
// Resolves the current executable path from the compatibility command line.
|
|
static void get_self_path(char* path, size_t capacity) {
|
|
const char* command = posix_get_command_line();
|
|
size_t length = 0;
|
|
while (command && command[length] && command[length] != ' ' && command[length] != '\t' &&
|
|
length + 1u < capacity) {
|
|
path[length] = command[length];
|
|
++length;
|
|
}
|
|
path[length] = '\0';
|
|
if (length == 0 || (command[length] && command[length] != ' ' && command[length] != '\t'))
|
|
fail("could not resolve self path");
|
|
}
|
|
|
|
// Converts one positive 32-bit value to decimal without depending on stdio.
|
|
static void format_u32(uint32_t value, char* text, size_t capacity) {
|
|
char reverse[16];
|
|
size_t count = 0;
|
|
if (!text || capacity == 0) return;
|
|
do {
|
|
reverse[count++] = (char)('0' + value % 10u);
|
|
value /= 10u;
|
|
} while (value && count < sizeof(reverse));
|
|
if (count + 1u > capacity) fail("numeric format overflow");
|
|
for (size_t i = 0; i < count; ++i) text[i] = reverse[count - i - 1u];
|
|
text[count] = '\0';
|
|
}
|
|
|
|
// Parses one nonzero decimal 32-bit value used by exec smoke stages.
|
|
static uint32_t parse_u32(const char* text) {
|
|
if (!text || !text[0]) return 0;
|
|
uint32_t value = 0;
|
|
for (size_t i = 0; text[i]; ++i) {
|
|
if (text[i] < '0' || text[i] > '9') return 0;
|
|
uint32_t digit = (uint32_t)(text[i] - '0');
|
|
if (value > (UINT32_MAX - digit) / 10u) return 0;
|
|
value = value * 10u + digit;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// Reports one exec-stage failure before preserving its distinct exit code.
|
|
static void exec_stage_fail(uint32_t code, const char* stage) {
|
|
char codeText[16];
|
|
format_u32(code, codeText, sizeof(codeText));
|
|
print("posix_process_smoke: exec stage failed: ");
|
|
print(stage);
|
|
print(" exit=");
|
|
print(codeText);
|
|
print("\n");
|
|
_exit((int)code);
|
|
}
|
|
|
|
// Exists only so exec can prove caught dispositions reset to default.
|
|
static void exec_caught_handler(int signalNumber) {
|
|
(void)signalNumber;
|
|
}
|
|
|
|
// First child stage captures state and replaces its native generation.
|
|
static void run_exec_stage1(const char* self) {
|
|
pid_t stablePid = getpid();
|
|
uint32_t nativePid = SysGetCurrentPid();
|
|
if (stablePid <= 0 || nativePid == 0) _exit(120);
|
|
|
|
int retained = open(gExecFile, O_CREAT | O_TRUNC | O_RDWR, 0666);
|
|
if (retained < 0) _exit(121);
|
|
move_fd(retained, EXEC_RETAIN_FD);
|
|
int cloexec = dup(EXEC_RETAIN_FD);
|
|
if (cloexec < 0) _exit(122);
|
|
move_fd(cloexec, EXEC_CLOEXEC_FD);
|
|
if (fcntl(EXEC_CLOEXEC_FD, F_SETFD, FD_CLOEXEC) != 0) _exit(123);
|
|
|
|
struct sigaction ignored;
|
|
memset(&ignored, 0, sizeof(ignored));
|
|
ignored.sa_handler = SIG_IGN;
|
|
if (sigaction(SIGUSR1, &ignored, (struct sigaction*)0) != 0) _exit(124);
|
|
struct sigaction caught;
|
|
memset(&caught, 0, sizeof(caught));
|
|
caught.sa_handler = exec_caught_handler;
|
|
if (sigaction(SIGUSR2, &caught, (struct sigaction*)0) != 0) _exit(125);
|
|
sigset_t blocked;
|
|
if (sigemptyset(&blocked) != 0 || sigaddset(&blocked, SIGTERM) != 0 ||
|
|
sigprocmask(SIG_BLOCK, &blocked, (sigset_t*)0) != 0)
|
|
_exit(126);
|
|
|
|
char stableText[16];
|
|
char nativeText[16];
|
|
format_u32((uint32_t)stablePid, stableText, sizeof(stableText));
|
|
format_u32(nativePid, nativeText, sizeof(nativeText));
|
|
char* nextArgv[] = { (char*)self, "--p7-exec2", stableText, nativeText, (char*)0 };
|
|
char* nextEnv[] = { "P7_ENV=replaced", (char*)0 };
|
|
(void)execve(self, nextArgv, nextEnv);
|
|
{
|
|
char errnoText[16];
|
|
format_u32((uint32_t)errno, errnoText, sizeof(errnoText));
|
|
print("posix_process_smoke: execve stage1 returned errno=");
|
|
print(errnoText);
|
|
print("\n");
|
|
_exit(127);
|
|
}
|
|
}
|
|
|
|
// Second child stage validates one committed replacement and performs a repeated exec.
|
|
static void run_exec_stage2(int argc, char** argv) {
|
|
if (argc != 4) exec_stage_fail(128, "argv");
|
|
uint32_t stablePid = parse_u32(argv[2]);
|
|
uint32_t oldNativePid = parse_u32(argv[3]);
|
|
uint32_t nativePid = SysGetCurrentPid();
|
|
if (stablePid == 0 || oldNativePid == 0 || (uint32_t)getpid() != stablePid ||
|
|
nativePid == 0 || nativePid == oldNativePid)
|
|
exec_stage_fail(129, "stable/native pid");
|
|
const char* marker = getenv("P7_ENV");
|
|
if (!marker || strcmp(marker, "replaced") != 0) exec_stage_fail(130, "environment");
|
|
if (fcntl(EXEC_RETAIN_FD, F_GETFD) < 0) exec_stage_fail(131, "retained fd");
|
|
errno = 0;
|
|
if (fcntl(EXEC_CLOEXEC_FD, F_GETFD) != -1 || errno != EBADF)
|
|
exec_stage_fail(132, "cloexec fd");
|
|
|
|
struct sigaction action;
|
|
memset(&action, 0, sizeof(action));
|
|
if (sigaction(SIGUSR1, (const struct sigaction*)0, &action) != 0 ||
|
|
action.sa_handler != SIG_IGN)
|
|
exec_stage_fail(133, "ignored disposition");
|
|
if (sigaction(SIGUSR2, (const struct sigaction*)0, &action) != 0 ||
|
|
action.sa_handler != SIG_DFL)
|
|
exec_stage_fail(134, "caught disposition reset");
|
|
sigset_t blocked = 0;
|
|
if (sigprocmask(SIG_BLOCK, (const sigset_t*)0, &blocked) != 0 ||
|
|
sigismember(&blocked, SIGTERM) != 1)
|
|
exec_stage_fail(135, "blocked mask");
|
|
|
|
if (write(EXEC_CHILD_WRITE_FD, "E", 1) != 1) exec_stage_fail(136, "marker write");
|
|
char release = 0;
|
|
if (read(EXEC_CHILD_READ_FD, &release, 1) != 1 || release != 'R') _exit(137);
|
|
|
|
char stableText[16];
|
|
char nativeText[16];
|
|
format_u32(stablePid, stableText, sizeof(stableText));
|
|
format_u32(nativePid, nativeText, sizeof(nativeText));
|
|
char self[512];
|
|
get_self_path(self, sizeof(self));
|
|
char* nextArgv[] = { self, "--p7-exec3", stableText, nativeText, (char*)0 };
|
|
char* nextEnv[] = { "P7_ENV=second", (char*)0 };
|
|
(void)execve(self, nextArgv, nextEnv);
|
|
_exit(138);
|
|
}
|
|
|
|
// Final child stage proves repeated native generations retain the same POSIX PID.
|
|
static void run_exec_stage3(int argc, char** argv) {
|
|
if (argc != 4) _exit(139);
|
|
uint32_t stablePid = parse_u32(argv[2]);
|
|
uint32_t oldNativePid = parse_u32(argv[3]);
|
|
uint32_t nativePid = SysGetCurrentPid();
|
|
if (stablePid == 0 || oldNativePid == 0 || (uint32_t)getpid() != stablePid ||
|
|
nativePid == 0 || nativePid == oldNativePid)
|
|
_exit(140);
|
|
const char* marker = getenv("P7_ENV");
|
|
if (!marker || strcmp(marker, "second") != 0 || fcntl(EXEC_RETAIN_FD, F_GETFD) < 0)
|
|
_exit(141);
|
|
if (write(EXEC_CHILD_WRITE_FD, "D", 1) != 1) _exit(142);
|
|
(void)close(EXEC_RETAIN_FD);
|
|
(void)close(EXEC_CHILD_WRITE_FD);
|
|
(void)close(EXEC_CHILD_READ_FD);
|
|
(void)unlink(gExecFile);
|
|
_exit(47);
|
|
}
|
|
|
|
// Executes with Linux-style null argv/envp. The replacement retains the spawned child parent association.
|
|
static void run_null_exec_stage(const char* self) {
|
|
(void)execve(self, (char* const*)0, (char* const*)0);
|
|
_exit(143);
|
|
}
|
|
|
|
// Exercises replacement, rollback, wait isolation, repeated generations, and null vectors.
|
|
static void run_exec_parent(const char* self) {
|
|
pid_t beforePid = getpid();
|
|
uint32_t beforeNativePid = SysGetCurrentPid();
|
|
char* missingArgv[] = { "/system/p7_missing_image.exe", (char*)0 };
|
|
errno = 0;
|
|
if (execve(missingArgv[0], missingArgv, (char* const*)0) != -1 ||
|
|
getpid() != beforePid || SysGetCurrentPid() != beforeNativePid)
|
|
fail("failed exec did not preserve old image");
|
|
|
|
int childToParent[2];
|
|
int parentToChild[2];
|
|
if (pipe(childToParent) != 0 || pipe(parentToChild) != 0)
|
|
fail("exec control pipe failed");
|
|
move_fd(childToParent[0], EXEC_PARENT_READ_FD);
|
|
move_fd(childToParent[1], EXEC_CHILD_WRITE_FD);
|
|
move_fd(parentToChild[0], EXEC_CHILD_READ_FD);
|
|
move_fd(parentToChild[1], EXEC_PARENT_WRITE_FD);
|
|
|
|
posix_spawn_file_actions_t actions;
|
|
if (posix_spawn_file_actions_init(&actions) != 0 ||
|
|
posix_spawn_file_actions_addclose(&actions, EXEC_PARENT_READ_FD) != 0 ||
|
|
posix_spawn_file_actions_addclose(&actions, EXEC_PARENT_WRITE_FD) != 0)
|
|
fail("exec spawn actions failed");
|
|
char* childArgv[] = { (char*)self, "--p7-exec1", (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
int error = posix_spawn(&child, self, &actions, (const posix_spawnattr_t*)0,
|
|
childArgv, (char* const*)0);
|
|
if (error != 0 || child <= 0) fail("exec child spawn failed");
|
|
if (posix_spawn_file_actions_destroy(&actions) != 0) fail("exec actions destroy failed");
|
|
if (close(EXEC_CHILD_WRITE_FD) != 0 || close(EXEC_CHILD_READ_FD) != 0)
|
|
fail("exec parent child-end close failed");
|
|
|
|
char marker = 0;
|
|
if (read(EXEC_PARENT_READ_FD, &marker, 1) != 1 || marker != 'E') {
|
|
int earlyStatus = -1;
|
|
pid_t early = waitpid(child, &earlyStatus, WNOHANG);
|
|
if (early == child && WIFEXITED(earlyStatus)) {
|
|
char exitText[16];
|
|
format_u32((uint32_t)WEXITSTATUS(earlyStatus), exitText, sizeof(exitText));
|
|
print("posix_process_smoke: exec child exited before marker exit=");
|
|
print(exitText);
|
|
print("\n");
|
|
} else if (early == 0) {
|
|
print("posix_process_smoke: exec child still running after marker channel closed\n");
|
|
} else {
|
|
print("posix_process_smoke: exec child wait diagnostic unavailable\n");
|
|
}
|
|
fail("exec replacement marker missing");
|
|
}
|
|
int childStatus = -1;
|
|
if (waitpid(child, &childStatus, WNOHANG) != 0)
|
|
fail("exec retirement became waitable");
|
|
if (write(EXEC_PARENT_WRITE_FD, "R", 1) != 1)
|
|
fail("exec replacement release failed");
|
|
marker = 0;
|
|
if (read(EXEC_PARENT_READ_FD, &marker, 1) != 1 || marker != 'D')
|
|
fail("repeated exec marker missing");
|
|
if (close(EXEC_PARENT_READ_FD) != 0 || close(EXEC_PARENT_WRITE_FD) != 0)
|
|
fail("exec parent pipe close failed");
|
|
if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus) ||
|
|
WEXITSTATUS(childStatus) != 47)
|
|
fail("repeated exec child wait failed");
|
|
|
|
char* nullArgv[] = { (char*)self, "--p7-null", (char*)0 };
|
|
child = (pid_t)-1;
|
|
error = posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0,
|
|
(const posix_spawnattr_t*)0, nullArgv, (char* const*)0);
|
|
if (error != 0 || child <= 0) fail("null-vector child spawn failed");
|
|
childStatus = -1;
|
|
if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus) ||
|
|
WEXITSTATUS(childStatus) != 48)
|
|
fail("null argv/env exec failed");
|
|
}
|
|
|
|
// Validates the independent native generation created by vfork()+execve().
|
|
static void run_vfork_exec_stage(int argc, char** argv) {
|
|
if (argc != 4) _exit(150);
|
|
uint32_t stablePid = parse_u32(argv[2]);
|
|
uint32_t parentNativePid = parse_u32(argv[3]);
|
|
if (stablePid == 0 || parentNativePid == 0 || (uint32_t)getpid() != stablePid ||
|
|
SysGetCurrentPid() == 0 || SysGetCurrentPid() == parentNativePid)
|
|
_exit(151);
|
|
_exit(52);
|
|
}
|
|
|
|
// Exercises both terminal vfork paths and the child-zero/parent-PID continuation split.
|
|
static void run_vfork_parent(const char* self) {
|
|
gVforkChildTouched = 0;
|
|
pid_t child = vfork();
|
|
if (child < 0) fail("vfork _exit creation failed");
|
|
if (child == 0) {
|
|
gVforkChildTouched = 1u;
|
|
_exit(51);
|
|
}
|
|
if (!gVforkChildTouched) fail("vfork parent resumed before child terminal path");
|
|
int status = -1;
|
|
if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status) != 51)
|
|
fail("vfork _exit wait failed");
|
|
|
|
uint32_t parentNativePid = SysGetCurrentPid();
|
|
child = vfork();
|
|
if (child < 0) fail("vfork exec creation failed");
|
|
if (child == 0) {
|
|
char stableText[16];
|
|
char parentNativeText[16];
|
|
format_u32((uint32_t)getpid(), stableText, sizeof(stableText));
|
|
format_u32(parentNativePid, parentNativeText, sizeof(parentNativeText));
|
|
char* nextArgv[] = { (char*)self, "--p7-vfork-exec", stableText,
|
|
parentNativeText, (char*)0 };
|
|
(void)execve(self, nextArgv, (char* const*)0);
|
|
_exit(152);
|
|
}
|
|
status = -1;
|
|
if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status) != 52)
|
|
fail("vfork exec wait failed");
|
|
}
|
|
|
|
// Validates server-owned bootstrap state, then blocks until the parent releases the child.
|
|
static void run_full_child(void) {
|
|
pid_t pid = getpid();
|
|
pid_t parent = getppid();
|
|
if (pid <= 0 || parent <= 0 || parent == pid)
|
|
_exit(101);
|
|
if (getpgrp() != pid)
|
|
_exit(102);
|
|
|
|
const char* marker = getenv("P5_ENV");
|
|
if (!marker || strcmp(marker, "server-owned") != 0)
|
|
_exit(103);
|
|
const char* expectedCwd = getenv("P5_CWD");
|
|
char cwd[512];
|
|
if (!expectedCwd || !getcwd(cwd, sizeof(cwd)) || strcmp(cwd, expectedCwd) != 0)
|
|
_exit(104);
|
|
|
|
errno = 0;
|
|
if (fcntl(ACTION_OPEN_FD, F_GETFD) != -1 || errno != EBADF)
|
|
_exit(105);
|
|
if (write(ACTION_DUP_FD, "ok", 2) != 2)
|
|
_exit(106);
|
|
if (close(ACTION_DUP_FD) != 0)
|
|
_exit(107);
|
|
errno = 0;
|
|
if (fcntl(CONTROL_WRITE_FD, F_GETFD) != -1 || errno != EBADF)
|
|
_exit(108);
|
|
|
|
char release = 0;
|
|
if (read(CONTROL_READ_FD, &release, 1) != 1 || release != 'R')
|
|
_exit(109);
|
|
(void)close(CONTROL_READ_FD);
|
|
_exit(23);
|
|
}
|
|
|
|
// Blocks one child until the parent supplies the exit code byte.
|
|
static void run_wait_child(void) {
|
|
if (getppid() <= 0 || getppid() == getpid())
|
|
_exit(110);
|
|
char exitCode = 0;
|
|
if (read(CONTROL_READ_FD, &exitCode, 1) != 1)
|
|
_exit(111);
|
|
(void)close(CONTROL_READ_FD);
|
|
_exit((unsigned char)exitCode);
|
|
}
|
|
|
|
// Spawns one child that waits on CONTROL_READ_FD.
|
|
static pid_t spawn_wait_child(const char* self, int writeFd) {
|
|
make_control_pipe(writeFd);
|
|
char* argv[] = { (char*)self, "--p5-wait", (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
int error = posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0,
|
|
(const posix_spawnattr_t*)0, argv, (char* const*)0);
|
|
if (error != 0 || child <= 0)
|
|
fail("wait child spawn failed");
|
|
if (close(CONTROL_READ_FD) != 0)
|
|
fail("parent control read close failed");
|
|
return child;
|
|
}
|
|
|
|
// Exercises the P5 server-owned process construction and wait contract.
|
|
int main(int argc, char** argv) {
|
|
if (argc == 0) {
|
|
pid_t parent = getppid();
|
|
if (parent < 0) _exit(145);
|
|
if (parent == 0) {
|
|
char self[512];
|
|
get_self_path(self, sizeof(self));
|
|
char* rootArgv[] = { self, "--p5-root", (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
int error = posix_spawn(&child, self, (const posix_spawn_file_actions_t*)0,
|
|
(const posix_spawnattr_t*)0, rootArgv, environ);
|
|
if (error != 0 || child <= 0) _exit(146);
|
|
int childStatus = -1;
|
|
if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus)) _exit(147);
|
|
_exit(WEXITSTATUS(childStatus));
|
|
}
|
|
if (!environ || environ[0] != (char*)0) _exit(144);
|
|
_exit(48);
|
|
}
|
|
if (argc >= 2 && strcmp(argv[1], "--p7-exec1") == 0) run_exec_stage1(argv[0]);
|
|
if (argc >= 2 && strcmp(argv[1], "--p7-exec2") == 0) run_exec_stage2(argc, argv);
|
|
if (argc >= 2 && strcmp(argv[1], "--p7-exec3") == 0) run_exec_stage3(argc, argv);
|
|
if (argc >= 2 && strcmp(argv[1], "--p7-null") == 0) run_null_exec_stage(argv[0]);
|
|
if (argc >= 2 && strcmp(argv[1], "--p7-vfork-exec") == 0) run_vfork_exec_stage(argc, argv);
|
|
if (child_mode("--p5-full")) run_full_child();
|
|
if (child_mode("--p5-wait")) run_wait_child();
|
|
|
|
char self[512];
|
|
get_self_path(self, sizeof(self));
|
|
|
|
char cwd[512];
|
|
if (!getcwd(cwd, sizeof(cwd)))
|
|
fail("parent getcwd failed");
|
|
const char cwdPrefix[] = "P5_CWD=";
|
|
char cwdEnvironment[sizeof(cwdPrefix) + sizeof(cwd)];
|
|
size_t prefixLength = sizeof(cwdPrefix) - 1u;
|
|
size_t cwdLength = strlen(cwd);
|
|
if (prefixLength + cwdLength + 1u > sizeof(cwdEnvironment))
|
|
fail("cwd environment overflow");
|
|
memcpy(cwdEnvironment, cwdPrefix, prefixLength);
|
|
memcpy(cwdEnvironment + prefixLength, cwd, cwdLength + 1u);
|
|
|
|
const char* actionPath = "posix_process_spawn.tmp";
|
|
const char* collisionPath = "posix_process_collision.tmp";
|
|
(void)unlink(actionPath);
|
|
(void)unlink(collisionPath);
|
|
make_control_pipe(CONTROL_WRITE_FD);
|
|
|
|
// Seed the dup2 target with an unrelated inherited description so the child
|
|
// action must replace an already-occupied descriptor number.
|
|
int collision = open(collisionPath, O_CREAT | O_TRUNC | O_RDWR, 0666);
|
|
if (collision < 0)
|
|
fail("spawn collision source open failed");
|
|
move_fd(collision, ACTION_DUP_FD);
|
|
|
|
posix_spawn_file_actions_t actions;
|
|
if (posix_spawn_file_actions_init(&actions) != 0 ||
|
|
posix_spawn_file_actions_addopen(&actions, ACTION_OPEN_FD, actionPath,
|
|
O_CREAT | O_TRUNC | O_RDWR, 0666) != 0 ||
|
|
posix_spawn_file_actions_adddup2(&actions, ACTION_OPEN_FD, ACTION_DUP_FD) != 0 ||
|
|
posix_spawn_file_actions_addclose(&actions, ACTION_OPEN_FD) != 0)
|
|
fail("spawn file actions setup failed");
|
|
|
|
posix_spawnattr_t attributes;
|
|
if (posix_spawnattr_init(&attributes) != 0 ||
|
|
posix_spawnattr_setpgroup(&attributes, 0) != 0 ||
|
|
posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETPGROUP) != 0)
|
|
fail("spawn attributes setup failed");
|
|
|
|
char* childArgv[] = { self, "--p5-full", (char*)0 };
|
|
char* childEnv[] = { "P5_ENV=server-owned", cwdEnvironment, (char*)0 };
|
|
pid_t child = (pid_t)-1;
|
|
int spawnError = posix_spawn(&child, self, &actions, &attributes, childArgv, childEnv);
|
|
if (spawnError != 0 || child <= 0)
|
|
fail("full child spawn failed");
|
|
if (posix_spawn_file_actions_destroy(&actions) != 0 ||
|
|
posix_spawnattr_destroy(&attributes) != 0)
|
|
fail("spawn setup destroy failed");
|
|
if (close(ACTION_DUP_FD) != 0)
|
|
fail("parent collision descriptor close failed");
|
|
if (unlink(collisionPath) != 0)
|
|
fail("parent collision source cleanup failed");
|
|
if (close(CONTROL_READ_FD) != 0)
|
|
fail("full parent control read close failed");
|
|
|
|
int childStatus = -1;
|
|
if (waitpid(child, &childStatus, WNOHANG) != 0)
|
|
fail("WNOHANG did not report running child");
|
|
if (write(CONTROL_WRITE_FD, "R", 1) != 1 || close(CONTROL_WRITE_FD) != 0)
|
|
fail("full child release failed");
|
|
if (waitpid(child, &childStatus, 0) != child || !WIFEXITED(childStatus) ||
|
|
WEXITSTATUS(childStatus) != 23)
|
|
fail("exact child wait failed");
|
|
|
|
int result = open(actionPath, O_RDONLY);
|
|
char actionBytes[3] = {0, 0, 0};
|
|
if (result < 0 || read(result, actionBytes, 2) != 2 || strcmp(actionBytes, "ok") != 0)
|
|
fail("spawn file action result was wrong");
|
|
if (close(result) != 0 || unlink(actionPath) != 0)
|
|
fail("spawn action cleanup failed");
|
|
|
|
errno = 0;
|
|
if (waitpid(child, &childStatus, WNOHANG) != (pid_t)-1 || errno != ECHILD)
|
|
fail("collected child remained waitable");
|
|
|
|
pid_t failedPid = 123;
|
|
if (posix_spawn(&failedPid, "/system/p5_missing_image.exe",
|
|
(const posix_spawn_file_actions_t*)0, (const posix_spawnattr_t*)0,
|
|
(char* const*)0, (char* const*)0) == 0 || failedPid != (pid_t)-1)
|
|
fail("failed construction returned a child");
|
|
|
|
pid_t first = spawn_wait_child(self, CONTROL_WRITE_A_FD);
|
|
pid_t second = spawn_wait_child(self, CONTROL_WRITE_FD);
|
|
char secondCode = 32;
|
|
if (write(CONTROL_WRITE_FD, &secondCode, 1) != 1 || close(CONTROL_WRITE_FD) != 0)
|
|
fail("second child release failed");
|
|
childStatus = -1;
|
|
if (wait(&childStatus) != second || WEXITSTATUS(childStatus) != 32)
|
|
fail("wait did not collect second child first");
|
|
|
|
char firstCode = 31;
|
|
if (write(CONTROL_WRITE_A_FD, &firstCode, 1) != 1 || close(CONTROL_WRITE_A_FD) != 0)
|
|
fail("first child release failed");
|
|
childStatus = -1;
|
|
if (wait(&childStatus) != first || WEXITSTATUS(childStatus) != 31)
|
|
fail("wait did not collect first child second");
|
|
|
|
errno = 0;
|
|
if (wait((int*)0) != (pid_t)-1 || errno != ECHILD)
|
|
fail("wait without children did not return ECHILD");
|
|
|
|
run_exec_parent(self);
|
|
run_vfork_parent(self);
|
|
|
|
print("posix_process_smoke: PASS\n");
|
|
return 0;
|
|
}
|