diff --git a/c/builtins.c b/c/builtins.c index 8176c1c..28f7778 100644 --- a/c/builtins.c +++ b/c/builtins.c @@ -1223,6 +1223,86 @@ static Value bi_tcp_close(Value *a, int n, Env *e) { return VAL_VOID; } +#include +/* spawn-process-stdio: fork+exec a child with its stdin & stdout piped + * back to us; return (stdin-port . stdout-port). Lets gpu-worker.lsp + * (and any other lumbda code) hold a long-lived subprocess across many + * request cycles without spawning per request. + * + * (spawn-process-stdio "path/to/binary" '("arg1" "arg2")) + * ⇒ (# . #) + */ +static Value bi_spawn_process_stdio(Value *a, int n, Env *e) { + (void)e; CHECK_ARITY("spawn-process-stdio", 2); check_string(a[0]); + const char *path = AS_STRING(a[0])->data; + + /* count args */ + int argc = 1; + Value lst = a[1]; + while (IS_PAIR(lst)) { argc++; lst = CDR(lst); } + char **argv = (char**)ul_malloc(sizeof(char*) * (argc + 1)); + argv[0] = (char*)path; + int i = 1; lst = a[1]; + while (IS_PAIR(lst)) { + Value v = CAR(lst); + if (IS_STRING(v)) { + argv[i++] = AS_STRING(v)->data; + } else if (IS_SYM(v)) { + argv[i++] = (char*)sym_name(v); + } else { + ul_free(argv); + lisp_error("spawn-process-stdio: arg must be string or symbol"); + } + lst = CDR(lst); + } + argv[argc] = NULL; + + int in_pipe[2]; /* parent writes → child reads (child's stdin) */ + int out_pipe[2]; /* child writes → parent reads (child's stdout)*/ + if (pipe(in_pipe) < 0 || pipe(out_pipe) < 0) { + ul_free(argv); return VAL_FALSE; + } + pid_t pid = fork(); + if (pid < 0) { + close(in_pipe[0]); close(in_pipe[1]); + close(out_pipe[0]); close(out_pipe[1]); + ul_free(argv); return VAL_FALSE; + } + if (pid == 0) { + /* child */ + dup2(in_pipe[0], STDIN_FILENO); + dup2(out_pipe[1], STDOUT_FILENO); + close(in_pipe[0]); close(in_pipe[1]); + close(out_pipe[0]); close(out_pipe[1]); + execvp(path, argv); + _exit(127); + } + /* parent */ + close(in_pipe[0]); + close(out_pipe[1]); + ul_free(argv); + + /* line-buffer the parent's write end so daemon sees newlines promptly */ + FILE *win = fdopen(in_pipe[1], "w"); + FILE *rout = fdopen(out_pipe[0], "r"); + if (!win || !rout) { + if (win) fclose(win); else close(in_pipe[1]); + if (rout) fclose(rout); else close(out_pipe[0]); + return VAL_FALSE; + } + setvbuf(win, NULL, _IOLBF, 0); + Value pin = make_file_port(win, PORT_OUTPUT); + Value pout = make_file_port(rout, PORT_INPUT); + return cons(pin, pout); +} + +static Value bi_flush_port(Value *a, int n, Env *e) { + (void)e; CHECK_ARITY("flush-port", 1); + ULPort *p = AS_PORT(a[0]); + if (p->fp) fflush(p->fp); + return VAL_VOID; +} + /* heap-snapshot / heap-restore: asm-only arena primitives. The asm impl * has no GC; these let a server rewind its bump allocator between * requests. Python + C have real GCs — no-ops here so portable .lsp @@ -1906,6 +1986,8 @@ Env *make_global_env(void) { DEF("tcp-recv", bi_tcp_recv); DEF("tcp-send", bi_tcp_send); DEF("tcp-close", bi_tcp_close); + DEF("spawn-process-stdio", bi_spawn_process_stdio); + DEF("flush-port", bi_flush_port); DEF("heap-snapshot", bi_heap_snapshot); DEF("heap-restore", bi_heap_restore); DEF("current-time-ms", bi_current_time_ms);