mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-19 05:29:46 +08:00
- Launch and manage TypeScript server processes through a portable native FFI boundary. - Share the production session API between Node and statically compiled clients. - Preserve Unicode command-line arguments and release server resources on every exit path.
227 lines
7.7 KiB
C
227 lines
7.7 KiB
C
#ifndef _WIN32
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#endif
|
|
#include "ts7-process.h"
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#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
|