#ifndef _WIN32 #define _POSIX_C_SOURCE 200809L #endif #include "ts7-process.h" #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #include #endif static const char *executable; static const char *callbacks = "readFile,fileExists"; static uint32_t start(const char *mode, int timing) { return scriptc_ts7_open((const uint8_t *)executable, strlen(executable), (const uint8_t *)mode, strlen(mode), (const uint8_t *)callbacks, strlen(callbacks), (uint8_t)timing); } static void fail_message(void) { uint8_t message[1024]; int32_t size = scriptc_ts7_error(message, sizeof(message)); assert(size > 0); assert(scriptc_ts7_error(message, 3) == 3); assert(scriptc_ts7_error(NULL, 0) == 0); } static void read_exact(uint32_t id, uint8_t *bytes, size_t size) { size_t at = 0; while (at < size) { int32_t count = scriptc_ts7_read(id, bytes, size, (uint32_t)at, (uint32_t)(size - at)); assert(count > 0 && (size_t)count <= size - at); at += (size_t)count; } } static void write_exact(uint32_t id, const uint8_t *bytes, size_t size, uint32_t offset, uint32_t length) { uint32_t at = 0; while (at < length) { int32_t count = scriptc_ts7_write(id, bytes, size, offset + at, length - at); assert(count > 0 && (uint32_t)count <= length - at); at += (uint32_t)count; } } static void assert_reaped(uint32_t pid) { #ifdef _WIN32 HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, pid); if (process != NULL) { assert(WaitForSingleObject(process, 0) == WAIT_OBJECT_0); CloseHandle(process); } else assert(GetLastError() == ERROR_INVALID_PARAMETER); #else int status; assert(waitpid((pid_t)pid, &status, WNOHANG) == -1 && errno == ECHILD); #endif } static unsigned resource_count(void) { #ifdef _WIN32 DWORD count; assert(GetProcessHandleCount(GetCurrentProcess(), &count)); return (unsigned)count; #else unsigned count = 0; for (int fd = 0; fd < 1024; fd++) if (fcntl(fd, F_GETFD) != -1) count++; return count; #endif } static void argument_cases(void) { const char *values[] = { "", "a b", "double\"quote", "end\\", "many\\\\\"quotes\\\\", "a;&$()`'b", "h\xc3\xa9llo \xf0\x9f\x8c\x8d", }; for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) { for (int timing = 0; timing <= 1; timing++) { char mode[256], expected[512]; snprintf(mode, sizeof(mode), "argv:%s", values[i]); snprintf(expected, sizeof(expected), "%s\n--callbacks=%s\n%s\n", values[i], callbacks, timing ? "timing" : "plain"); uint32_t id = start(mode, timing); assert(id != 0); uint8_t actual[512]; read_exact(id, actual, strlen(expected)); assert(memcmp(actual, expected, strlen(expected)) == 0); assert(scriptc_ts7_read(id, actual, sizeof(actual), 0, sizeof(actual)) == 0); uint32_t pid = scriptc_ts7_pid(id); scriptc_ts7_close(id); assert_reaped(pid); } } } static void exchange_cases(void) { uint32_t ids[3]; for (int i = 0; i < 3; i++) { ids[i] = start("echo", 0); assert(ids[i] != 0); } assert(ids[0] != ids[1] && ids[1] != ids[2]); uint8_t bytes[1028], result[1028]; for (unsigned round = 0; round < 200; round++) { for (unsigned i = 0; i < sizeof(bytes); i++) bytes[i] = (uint8_t)(i * 31 + round); memset(result, 0xcc, sizeof(result)); uint32_t id = ids[round % 3]; write_exact(id, bytes, sizeof(bytes), 2, 1024); size_t at = 2; while (at < 1026) { int32_t count = scriptc_ts7_read(id, result, sizeof(result), (uint32_t)at, (uint32_t)(1026 - at)); assert(count > 0); at += (size_t)count; } assert(memcmp(bytes + 2, result + 2, 1024) == 0); assert(result[0] == 0xcc && result[1] == 0xcc && result[1026] == 0xcc && result[1027] == 0xcc); } for (int i = 0; i < 3; i++) { uint32_t id = ids[i], pid = scriptc_ts7_pid(id); assert(scriptc_ts7_read(id, NULL, 0, 0, 0) == 0); assert(scriptc_ts7_write(id, NULL, 0, 0, 0) == 0); assert(scriptc_ts7_read(id, result, sizeof(result), sizeof(result) + 1, 0) == -1); assert(scriptc_ts7_read(id, result, sizeof(result), 1, sizeof(result)) == -1); assert(scriptc_ts7_write(id, NULL, 1, 0, 1) == -1); assert(scriptc_ts7_write(id, bytes, sizeof(bytes), UINT32_MAX, 2) == -1); fail_message(); scriptc_ts7_close(id); scriptc_ts7_close(id); assert_reaped(pid); assert(scriptc_ts7_read(id, result, sizeof(result), 0, 1) == -1); assert(scriptc_ts7_write(id, bytes, sizeof(bytes), 0, 1) == -1); } uint32_t newer = start("echo", 0); assert(newer > ids[2]); scriptc_ts7_close(ids[0]); assert(scriptc_ts7_pid(newer) != 0); scriptc_ts7_close(newer); } static void failure_cases(void) { uint8_t bytes[1] = {42}; const char *saved = executable; executable = "/scriptc/does-not-exist/tsgo"; for (int i = 0; i < 30; i++) { assert(start("echo", 0) == 0); fail_message(); } executable = saved; assert(scriptc_ts7_open(NULL, 0, bytes, 1, bytes, 1, 0) == 0); assert(scriptc_ts7_open((const uint8_t *)"abc\0def", 7, bytes, 1, bytes, 1, 0) == 0); assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), NULL, 0, bytes, 1, 0) == 0); assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), bytes, 1, NULL, 0, 0) == 0); for (int i = 0; i < 30; i++) { uint32_t id = start("exit", 0); assert(id != 0); assert(scriptc_ts7_read(id, bytes, 1, 0, 1) == 0); // The peer has closed: writes must report an error, never SIGPIPE. assert(scriptc_ts7_write(id, bytes, 1, 0, 1) == -1); uint32_t pid = scriptc_ts7_pid(id); scriptc_ts7_close(id); assert_reaped(pid); } for (int i = 0; i < 3; i++) { uint32_t id = start("stall", 0); assert(id != 0); read_exact(id, bytes, 1); assert(bytes[0] == '!'); } scriptc_ts7_close_all(); scriptc_ts7_close_all(); scriptc_ts7_close(0); scriptc_ts7_close(UINT32_MAX); } static int process_main(int argc, char **argv) { assert(argc >= 2); executable = argv[1]; #ifndef _WIN32 assert(signal(SIGPIPE, SIG_DFL) != SIG_ERR); int saved_input = -1, saved_output = -1; if (argc > 2 && strcmp(argv[2], "closed-standard") == 0) { saved_input = dup(STDIN_FILENO); saved_output = dup(STDOUT_FILENO); close(STDIN_FILENO); close(STDOUT_FILENO); } #endif // Windows initializes process creation, Unicode conversion and error // formatting facilities lazily. Measure repeated use after exercising // those paths; their one-time handles are not owned by this transport. #ifdef _WIN32 argument_cases(); exchange_cases(); failure_cases(); #endif unsigned before = resource_count(); argument_cases(); exchange_cases(); failure_cases(); assert(resource_count() == before); #ifndef _WIN32 if (saved_input >= 0) { assert(dup2(saved_input, STDIN_FILENO) >= 0); close(saved_input); } if (saved_output >= 0) { assert(dup2(saved_output, STDOUT_FILENO) >= 0); close(saved_output); } #endif puts("native process contracts passed"); return 0; } #ifdef _WIN32 int wmain(int argc, wchar_t **wide) { char **argv = calloc((size_t)argc, sizeof(*argv)); assert(argv != NULL); for (int i = 0; i < argc; i++) { int size = WideCharToMultiByte(CP_UTF8, 0, wide[i], -1, NULL, 0, NULL, NULL); assert(size > 0); argv[i] = malloc((size_t)size); assert(argv[i] != NULL); assert(WideCharToMultiByte(CP_UTF8, 0, wide[i], -1, argv[i], size, NULL, NULL) == size); } int result = process_main(argc, argv); for (int i = 0; i < argc; i++) free(argv[i]); free(argv); return result; } #else int main(int argc, char **argv) { return process_main(argc, argv); } #endif