MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add child process management module

+1157 -13
+92
examples/spec/child_process.js
··· 1 + import { test, testThrows, summary } from './helpers.js'; 2 + import { spawn, exec, execSync, spawnSync } from 'child_process'; 3 + 4 + console.log('Child Process Tests\n'); 5 + 6 + test('execSync returns stdout', execSync('echo hello').trim(), 'hello'); 7 + test('execSync with spaces', execSync('echo "hello world"').trim(), 'hello world'); 8 + test('execSync exit code 0', typeof execSync('true'), 'string'); 9 + 10 + testThrows('execSync throws on non-zero exit', () => { 11 + execSync('exit 1'); 12 + }); 13 + 14 + const syncResult = spawnSync('echo', ['hello', 'world']); 15 + test('spawnSync stdout', syncResult.stdout.trim(), 'hello world'); 16 + test('spawnSync status 0', syncResult.status, 0); 17 + test('spawnSync signal null', syncResult.signal, null); 18 + test('spawnSync has pid', syncResult.pid > 0, true); 19 + 20 + const failSync = spawnSync('sh', ['-c', 'exit 42']); 21 + test('spawnSync non-zero status', failSync.status, 42); 22 + 23 + const stderrSync = spawnSync('sh', ['-c', 'echo error >&2']); 24 + test('spawnSync captures stderr', stderrSync.stderr.trim(), 'error'); 25 + 26 + const inputSync = spawnSync('cat', [], { input: 'test input' }); 27 + test('spawnSync with input', inputSync.stdout, 'test input'); 28 + 29 + let execDone = false; 30 + exec('echo async').then(result => { 31 + test('exec resolves with stdout', result.stdout.trim(), 'async'); 32 + test('exec exitCode', result.exitCode, 0); 33 + execDone = true; 34 + }); 35 + 36 + let execFailDone = false; 37 + exec('exit 1').catch(err => { 38 + test('exec rejects on non-zero exit', typeof err, 'string'); 39 + execFailDone = true; 40 + }); 41 + 42 + let spawnStdout = ''; 43 + let spawnExitCode = null; 44 + let spawnClosed = false; 45 + 46 + const child = spawn('sh', ['-c', 'echo line1; echo line2']); 47 + 48 + test('spawn returns object', typeof child, 'object'); 49 + test('spawn has pid', child.pid > 0, true); 50 + test('spawn has on method', typeof child.on, 'function'); 51 + test('spawn has once method', typeof child.once, 'function'); 52 + test('spawn has kill method', typeof child.kill, 'function'); 53 + 54 + child.on('stdout', data => { 55 + spawnStdout += data; 56 + }); 57 + 58 + child.on('exit', code => { 59 + spawnExitCode = code; 60 + }); 61 + 62 + child.on('close', () => { 63 + spawnClosed = true; 64 + test('spawn collects stdout', spawnStdout.trim(), 'line1\nline2'); 65 + test('spawn exit code', spawnExitCode, 0); 66 + test('spawn close event fired', spawnClosed, true); 67 + 68 + const shellChild = spawn('echo $HOME', [], { shell: true }); 69 + shellChild.on('close', () => { 70 + test('spawn with shell option', shellChild.stdout.length > 0, true); 71 + 72 + setTimeout(() => { 73 + test('exec async completed', execDone, true); 74 + test('exec fail async completed', execFailDone, true); 75 + summary(); 76 + }, 100); 77 + }); 78 + }); 79 + 80 + const stderrChild = spawn('sh', ['-c', 'echo err >&2']); 81 + let stderrData = ''; 82 + stderrChild.on('stderr', data => { 83 + stderrData += data; 84 + }); 85 + stderrChild.on('close', () => { 86 + test('spawn captures stderr', stderrData.trim(), 'err'); 87 + }); 88 + 89 + const longChild = spawn('sleep', ['10']); 90 + longChild.on('close', () => {}); 91 + const killResult = longChild.kill('SIGTERM'); 92 + test('spawn kill returns true', killResult, true);
+11
include/modules/child_process.h
··· 1 + #ifndef ANT_CHILD_PROCESS_MODULE_H 2 + #define ANT_CHILD_PROCESS_MODULE_H 3 + 4 + #include "ant.h" 5 + 6 + jsval_t child_process_library(struct js *js); 7 + void child_process_poll_events(void); 8 + int has_pending_child_processes(void); 9 + void child_process_gc_update_roots(GC_FWD_ARGS); 10 + 11 + #endif
+1 -1
meson.build
··· 96 96 build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() 97 97 98 98 version_conf = configuration_data() 99 - version_conf.set('ANT_VERSION', '0.3.2.38') 99 + version_conf.set('ANT_VERSION', '0.3.2.39') 100 100 version_conf.set('ANT_GIT_HASH', git_hash) 101 101 version_conf.set('ANT_BUILD_DATE', build_date) 102 102
+38 -10
src/ant.c
··· 36 36 #include "modules/fetch.h" 37 37 #include "modules/symbol.h" 38 38 #include "modules/ffi.h" 39 + #include "modules/child_process.h" 39 40 40 41 #define CORO_MALLOC(size) calloc(1, size) 41 42 #define CORO_FREE(ptr) free(ptr) ··· 926 927 void js_poll_events(struct js *js) { 927 928 fetch_poll_events(); 928 929 fs_poll_events(); 930 + child_process_poll_events(); 929 931 930 932 int has_timers = has_pending_timers(); 931 933 if (has_timers) { ··· 943 945 944 946 if (temp->is_ready && temp->mco && mco_status(temp->mco) == MCO_SUSPENDED) { 945 947 remove_coroutine(temp); 946 - 947 948 mco_result res = mco_resume(temp->mco); 948 949 949 950 if (res == MCO_SUCCESS && mco_status(temp->mco) == MCO_DEAD) { ··· 951 952 } else if (res == MCO_SUCCESS) { 952 953 temp->is_ready = false; 953 954 enqueue_coroutine(temp); 954 - } else { 955 - free_coroutine(temp); 956 - } 955 + } else free_coroutine(temp); 957 956 } 958 957 959 958 temp = next; 960 959 } 961 960 } 962 961 962 + typedef enum { 963 + WORK_MICROTASKS = 1 << 0, 964 + WORK_TIMERS = 1 << 1, 965 + WORK_IMMEDIATES = 1 << 2, 966 + WORK_COROUTINES = 1 << 3, 967 + WORK_COROUTINES_READY = 1 << 4, 968 + WORK_FETCHES = 1 << 5, 969 + WORK_FS_OPS = 1 << 6, 970 + WORK_CHILD_PROCS = 1 << 7, 971 + } work_flags_t; 972 + 973 + static inline work_flags_t get_pending_work(void) { 974 + work_flags_t flags = 0; 975 + if (has_pending_microtasks()) flags |= WORK_MICROTASKS; 976 + if (has_pending_timers()) flags |= WORK_TIMERS; 977 + if (has_pending_immediates()) flags |= WORK_IMMEDIATES; 978 + if (js_has_pending_coroutines()) flags |= WORK_COROUTINES; 979 + if (has_ready_coroutines()) flags |= WORK_COROUTINES_READY; 980 + if (has_pending_fetches()) flags |= WORK_FETCHES; 981 + if (has_pending_fs_ops()) flags |= WORK_FS_OPS; 982 + if (has_pending_child_processes()) flags |= WORK_CHILD_PROCS; 983 + return flags; 984 + } 985 + 986 + #define WORK_PENDING (WORK_MICROTASKS | WORK_TIMERS | WORK_IMMEDIATES | WORK_COROUTINES | WORK_FETCHES | WORK_FS_OPS | WORK_CHILD_PROCS) 987 + #define WORK_BLOCKING (WORK_MICROTASKS | WORK_IMMEDIATES | WORK_COROUTINES_READY) 988 + 963 989 void js_run_event_loop(struct js *js) { 964 - while (has_pending_microtasks() || has_pending_timers() || has_pending_immediates() || js_has_pending_coroutines() || has_pending_fetches() || has_pending_fs_ops()) { 990 + work_flags_t work; 991 + 992 + while ((work = get_pending_work()) & WORK_PENDING) { 965 993 js_poll_events(js); 994 + work = get_pending_work(); 966 995 967 - if (!has_pending_microtasks() && !has_pending_immediates() && has_pending_timers() && !has_ready_coroutines()) { 968 - int64_t next_timeout_ms = get_next_timer_timeout(); 969 - if (next_timeout_ms > 0) usleep(next_timeout_ms > 1000000 ? 1000000 : next_timeout_ms * 1000); 996 + if (!(work & WORK_BLOCKING) && (work & WORK_TIMERS)) { 997 + int64_t ms = get_next_timer_timeout(); 998 + if (ms > 0) usleep(ms > 1000 ? 1000000 : (useconds_t)(ms * 1000)); 970 999 } 971 - 972 - if (!has_pending_microtasks() && !has_pending_timers() && !has_pending_immediates() && !js_has_pending_coroutines() && !has_pending_fetches() && !has_pending_fs_ops()) break; 973 1000 } 974 1001 975 1002 js_poll_events(js); ··· 21390 21417 ffi_gc_update_roots(fwd_val, ctx); 21391 21418 fetch_gc_update_roots(fwd_val, ctx); 21392 21419 fs_gc_update_roots(fwd_val, ctx); 21420 + child_process_gc_update_roots(fwd_val, ctx); 21393 21421 21394 21422 map_registry_entry_t *map_reg, *map_reg_tmp; 21395 21423 HASH_ITER(hh, map_registry, map_reg, map_reg_tmp) {
+4 -2
src/main.c
··· 18 18 #include "modules/builtin.h" 19 19 #include "modules/buffer.h" 20 20 #include "modules/atomics.h" 21 + #include "modules/os.h" 21 22 #include "modules/io.h" 22 23 #include "modules/fs.h" 23 24 #include "modules/crypto.h" ··· 39 40 #include "modules/sessionstorage.h" 40 41 #include "modules/localstorage.h" 41 42 #include "modules/navigator.h" 42 - #include "modules/os.h" 43 + #include "modules/child_process.h" 43 44 44 45 int js_result = EXIT_SUCCESS; 45 46 ··· 219 220 220 221 ant_standard_library("path", path_library); 221 222 ant_standard_library("fs", fs_library); 223 + ant_standard_library("os", os_library); 222 224 ant_standard_library("crypto", crypto_library); 223 225 ant_standard_library("events", events_library); 224 - ant_standard_library("os", os_library); 226 + ant_standard_library("child_process", child_process_library); 225 227 226 228 #ifndef ANT_SNAPSHOT_GENERATOR 227 229 jsval_t snapshot_result = ant_load_snapshot(js);
+1011
src/modules/child_process.c
··· 1 + #include <uv.h> 2 + #include <stdio.h> 3 + #include <stdlib.h> 4 + #include <string.h> 5 + #include <unistd.h> 6 + #include <sys/wait.h> 7 + #include <sys/select.h> 8 + #include <signal.h> 9 + #include <errno.h> 10 + #include <fcntl.h> 11 + #include <uthash.h> 12 + #include <utarray.h> 13 + 14 + #if defined(__APPLE__) 15 + #include <mach-o/dyld.h> 16 + #endif 17 + 18 + #include "ant.h" 19 + #include "modules/child_process.h" 20 + #include "modules/symbol.h" 21 + 22 + #define MAX_CHILD_LISTENERS 16 23 + #define PIPE_READ_BUF_SIZE 65536 24 + 25 + typedef struct { 26 + jsval_t callback; 27 + bool once; 28 + } child_listener_t; 29 + 30 + typedef struct { 31 + char *event_name; 32 + child_listener_t listeners[MAX_CHILD_LISTENERS]; 33 + int count; 34 + UT_hash_handle hh; 35 + } child_event_t; 36 + 37 + typedef struct child_process_s { 38 + struct js *js; 39 + uv_process_t process; 40 + uv_pipe_t stdin_pipe; 41 + uv_pipe_t stdout_pipe; 42 + uv_pipe_t stderr_pipe; 43 + jsval_t child_obj; 44 + jsval_t promise; 45 + child_event_t *events; 46 + char *stdout_buf; 47 + size_t stdout_len; 48 + size_t stdout_cap; 49 + char *stderr_buf; 50 + size_t stderr_len; 51 + size_t stderr_cap; 52 + int64_t exit_code; 53 + int term_signal; 54 + bool exited; 55 + bool stdout_closed; 56 + bool stderr_closed; 57 + bool stdin_closed; 58 + bool use_shell; 59 + bool detached; 60 + bool close_emitted; 61 + int pending_closes; 62 + char *cwd; 63 + struct child_process_s *next; 64 + struct child_process_s *prev; 65 + } child_process_t; 66 + 67 + static child_process_t *pending_children_head = NULL; 68 + static child_process_t *pending_children_tail = NULL; 69 + static uv_loop_t *cp_loop = NULL; 70 + 71 + static void ensure_cp_loop(void) { 72 + if (cp_loop == NULL) cp_loop = uv_default_loop(); 73 + } 74 + 75 + static void add_pending_child(child_process_t *cp) { 76 + cp->next = NULL; 77 + cp->prev = pending_children_tail; 78 + if (pending_children_tail) { 79 + pending_children_tail->next = cp; 80 + } else pending_children_head = cp; 81 + pending_children_tail = cp; 82 + } 83 + 84 + static void remove_pending_child(child_process_t *cp) { 85 + if (cp->prev) cp->prev->next = cp->next; 86 + else pending_children_head = cp->next; 87 + if (cp->next) cp->next->prev = cp->prev; 88 + else pending_children_tail = cp->prev; 89 + cp->next = NULL; 90 + cp->prev = NULL; 91 + } 92 + 93 + static void free_child_process(child_process_t *cp) { 94 + if (!cp) return; 95 + if (cp->stdout_buf) free(cp->stdout_buf); 96 + if (cp->stderr_buf) free(cp->stderr_buf); 97 + if (cp->cwd) free(cp->cwd); 98 + 99 + child_event_t *evt, *tmp; 100 + HASH_ITER(hh, cp->events, evt, tmp) { 101 + HASH_DEL(cp->events, evt); 102 + free(evt->event_name); 103 + free(evt); 104 + } 105 + 106 + free(cp); 107 + } 108 + 109 + static child_event_t *find_or_create_event(child_process_t *cp, const char *name) { 110 + child_event_t *evt = NULL; 111 + HASH_FIND_STR(cp->events, name, evt); 112 + if (!evt) { 113 + evt = calloc(1, sizeof(child_event_t)); 114 + evt->event_name = strdup(name); 115 + evt->count = 0; 116 + HASH_ADD_KEYPTR(hh, cp->events, evt->event_name, strlen(evt->event_name), evt); 117 + } 118 + return evt; 119 + } 120 + 121 + static void emit_event(child_process_t *cp, const char *name, jsval_t *args, int nargs) { 122 + child_event_t *evt = NULL; 123 + HASH_FIND_STR(cp->events, name, evt); 124 + if (!evt || evt->count == 0) return; 125 + 126 + int i = 0; 127 + while (i < evt->count) { 128 + child_listener_t *l = &evt->listeners[i]; 129 + jsval_t result = js_call(cp->js, l->callback, args, nargs); 130 + if (js_type(result) == JS_ERR) { 131 + fprintf(stderr, "Error in child_process '%s' listener: %s\n", name, js_str(cp->js, result)); 132 + } 133 + if (l->once) { 134 + for (int j = i; j < evt->count - 1; j++) { 135 + evt->listeners[j] = evt->listeners[j + 1]; 136 + } 137 + evt->count--; 138 + } else i++; 139 + } 140 + } 141 + 142 + static void try_free_child(child_process_t *cp) { 143 + if (cp->exited && cp->stdout_closed && cp->stderr_closed && cp->pending_closes == 0) { 144 + remove_pending_child(cp); 145 + free_child_process(cp); 146 + } 147 + } 148 + 149 + static void check_completion(child_process_t *cp) { 150 + if (cp->exited && cp->stdout_closed && cp->stderr_closed && !cp->close_emitted) { 151 + cp->close_emitted = true; 152 + 153 + jsval_t stdout_val = js_mkstr(cp->js, cp->stdout_buf ? cp->stdout_buf : "", cp->stdout_len); 154 + jsval_t stderr_val = js_mkstr(cp->js, cp->stderr_buf ? cp->stderr_buf : "", cp->stderr_len); 155 + 156 + js_set(cp->js, cp->child_obj, "stdout", stdout_val); 157 + js_set(cp->js, cp->child_obj, "stderr", stderr_val); 158 + js_set(cp->js, cp->child_obj, "exitCode", js_mknum((double)cp->exit_code)); 159 + js_set(cp->js, cp->child_obj, "signalCode", cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull()); 160 + 161 + jsval_t close_args[2] = { js_mknum((double)cp->exit_code), cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull() }; 162 + emit_event(cp, "close", close_args, 2); 163 + 164 + if (js_type(cp->promise) != JS_UNDEF) { 165 + jsval_t result = js_mkobj(cp->js); 166 + js_set(cp->js, result, "stdout", stdout_val); 167 + js_set(cp->js, result, "stderr", stderr_val); 168 + js_set(cp->js, result, "exitCode", js_mknum((double)cp->exit_code)); 169 + js_set(cp->js, result, "signalCode", cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull()); 170 + 171 + if (cp->exit_code != 0) { 172 + char err_msg[256]; 173 + snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %lld", (long long)cp->exit_code); 174 + jsval_t err = js_mkstr(cp->js, err_msg, strlen(err_msg)); 175 + js_reject_promise(cp->js, cp->promise, err); 176 + } else { 177 + js_resolve_promise(cp->js, cp->promise, result); 178 + } 179 + } 180 + 181 + try_free_child(cp); 182 + } 183 + } 184 + 185 + static void on_handle_close(uv_handle_t *handle) { 186 + child_process_t *cp = (child_process_t *)handle->data; 187 + if (cp) { 188 + cp->pending_closes--; 189 + try_free_child(cp); 190 + } 191 + } 192 + 193 + static void on_exit(uv_process_t *proc, int64_t exit_status, int term_signal) { 194 + child_process_t *cp = (child_process_t *)proc->data; 195 + cp->exit_code = exit_status; 196 + cp->term_signal = term_signal; 197 + cp->exited = true; 198 + 199 + js_set(cp->js, cp->child_obj, "exitCode", js_mknum((double)exit_status)); 200 + if (term_signal) { 201 + js_set(cp->js, cp->child_obj, "signalCode", js_mknum((double)term_signal)); 202 + js_set(cp->js, cp->child_obj, "killed", js_mktrue()); 203 + } 204 + 205 + jsval_t exit_args[2] = { js_mknum((double)exit_status), term_signal ? js_mknum((double)term_signal) : js_mknull() }; 206 + emit_event(cp, "exit", exit_args, 2); 207 + 208 + cp->pending_closes++; 209 + uv_close((uv_handle_t *)proc, on_handle_close); 210 + 211 + if (!cp->stdin_closed && !uv_is_closing((uv_handle_t *)&cp->stdin_pipe)) { 212 + cp->pending_closes++; 213 + uv_close((uv_handle_t *)&cp->stdin_pipe, on_handle_close); 214 + cp->stdin_closed = true; 215 + } 216 + if (!cp->stdout_closed && !uv_is_closing((uv_handle_t *)&cp->stdout_pipe)) { 217 + uv_read_stop((uv_stream_t *)&cp->stdout_pipe); 218 + cp->pending_closes++; 219 + uv_close((uv_handle_t *)&cp->stdout_pipe, on_handle_close); 220 + cp->stdout_closed = true; 221 + } 222 + if (!cp->stderr_closed && !uv_is_closing((uv_handle_t *)&cp->stderr_pipe)) { 223 + uv_read_stop((uv_stream_t *)&cp->stderr_pipe); 224 + cp->pending_closes++; 225 + uv_close((uv_handle_t *)&cp->stderr_pipe, on_handle_close); 226 + cp->stderr_closed = true; 227 + } 228 + 229 + check_completion(cp); 230 + } 231 + 232 + static void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 233 + (void)handle; 234 + buf->base = malloc(suggested_size); 235 + buf->len = buf->base ? suggested_size : 0; 236 + } 237 + 238 + static void on_stdout_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { 239 + child_process_t *cp = (child_process_t *)stream->data; 240 + 241 + if (nread > 0) { 242 + if (cp->stdout_len + nread > cp->stdout_cap) { 243 + size_t new_cap = cp->stdout_cap == 0 ? 4096 : cp->stdout_cap * 2; 244 + while (new_cap < cp->stdout_len + nread) new_cap *= 2; 245 + char *new_buf = realloc(cp->stdout_buf, new_cap); 246 + if (new_buf) { 247 + cp->stdout_buf = new_buf; 248 + cp->stdout_cap = new_cap; 249 + } 250 + } 251 + if (cp->stdout_buf) { 252 + memcpy(cp->stdout_buf + cp->stdout_len, buf->base, nread); 253 + cp->stdout_len += nread; 254 + } 255 + 256 + jsval_t data = js_mkstr(cp->js, buf->base, nread); 257 + jsval_t data_args[1] = { data }; 258 + emit_event(cp, "stdout", data_args, 1); 259 + } 260 + 261 + if (buf->base) free(buf->base); 262 + 263 + if (nread < 0) { 264 + if (nread != UV_EOF) { 265 + jsval_t err_args[1] = { js_mkstr(cp->js, uv_strerror(nread), strlen(uv_strerror(nread))) }; 266 + emit_event(cp, "error", err_args, 1); 267 + } 268 + cp->pending_closes++; 269 + uv_close((uv_handle_t *)stream, on_handle_close); 270 + cp->stdout_closed = true; 271 + check_completion(cp); 272 + } 273 + } 274 + 275 + static void on_stderr_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { 276 + child_process_t *cp = (child_process_t *)stream->data; 277 + 278 + if (nread > 0) { 279 + if (cp->stderr_len + nread > cp->stderr_cap) { 280 + size_t new_cap = cp->stderr_cap == 0 ? 4096 : cp->stderr_cap * 2; 281 + while (new_cap < cp->stderr_len + nread) new_cap *= 2; 282 + char *new_buf = realloc(cp->stderr_buf, new_cap); 283 + if (new_buf) { 284 + cp->stderr_buf = new_buf; 285 + cp->stderr_cap = new_cap; 286 + } 287 + } 288 + if (cp->stderr_buf) { 289 + memcpy(cp->stderr_buf + cp->stderr_len, buf->base, nread); 290 + cp->stderr_len += nread; 291 + } 292 + 293 + jsval_t data = js_mkstr(cp->js, buf->base, nread); 294 + jsval_t data_args[1] = { data }; 295 + emit_event(cp, "stderr", data_args, 1); 296 + } 297 + 298 + if (buf->base) free(buf->base); 299 + 300 + if (nread < 0) { 301 + cp->pending_closes++; 302 + uv_close((uv_handle_t *)stream, on_handle_close); 303 + cp->stderr_closed = true; 304 + check_completion(cp); 305 + } 306 + } 307 + 308 + static jsval_t child_on(struct js *js, jsval_t *args, int nargs) { 309 + jsval_t this_obj = js_getthis(js); 310 + if (nargs < 2) return js_mkerr(js, "on() requires event name and callback"); 311 + if (js_type(args[0]) != JS_STR) return js_mkerr(js, "Event name must be a string"); 312 + if (js_type(args[1]) != JS_FUNC) return js_mkerr(js, "Callback must be a function"); 313 + 314 + jsval_t cp_ptr = js_get(js, this_obj, "_cp_ptr"); 315 + if (js_type(cp_ptr) == JS_UNDEF) return js_mkerr(js, "Invalid child process object"); 316 + 317 + child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); 318 + 319 + size_t name_len; 320 + char *name = js_getstr(js, args[0], &name_len); 321 + char *name_cstr = strndup(name, name_len); 322 + 323 + child_event_t *evt = find_or_create_event(cp, name_cstr); 324 + free(name_cstr); 325 + 326 + if (evt->count >= MAX_CHILD_LISTENERS) { 327 + return js_mkerr(js, "Maximum listeners reached for event"); 328 + } 329 + 330 + evt->listeners[evt->count].callback = args[1]; 331 + evt->listeners[evt->count].once = false; 332 + evt->count++; 333 + 334 + return this_obj; 335 + } 336 + 337 + static jsval_t child_once(struct js *js, jsval_t *args, int nargs) { 338 + jsval_t this_obj = js_getthis(js); 339 + if (nargs < 2) return js_mkerr(js, "once() requires event name and callback"); 340 + if (js_type(args[0]) != JS_STR) return js_mkerr(js, "Event name must be a string"); 341 + if (js_type(args[1]) != JS_FUNC) return js_mkerr(js, "Callback must be a function"); 342 + 343 + jsval_t cp_ptr = js_get(js, this_obj, "_cp_ptr"); 344 + if (js_type(cp_ptr) == JS_UNDEF) return js_mkerr(js, "Invalid child process object"); 345 + 346 + child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); 347 + 348 + size_t name_len; 349 + char *name = js_getstr(js, args[0], &name_len); 350 + char *name_cstr = strndup(name, name_len); 351 + 352 + child_event_t *evt = find_or_create_event(cp, name_cstr); 353 + free(name_cstr); 354 + 355 + if (evt->count >= MAX_CHILD_LISTENERS) { 356 + return js_mkerr(js, "Maximum listeners reached for event"); 357 + } 358 + 359 + evt->listeners[evt->count].callback = args[1]; 360 + evt->listeners[evt->count].once = true; 361 + evt->count++; 362 + 363 + return this_obj; 364 + } 365 + 366 + static jsval_t child_kill(struct js *js, jsval_t *args, int nargs) { 367 + jsval_t this_obj = js_getthis(js); 368 + 369 + jsval_t cp_ptr = js_get(js, this_obj, "_cp_ptr"); 370 + if (js_type(cp_ptr) == JS_UNDEF) return js_mkfalse(); 371 + 372 + child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); 373 + if (cp->exited) return js_mkfalse(); 374 + 375 + int sig = SIGTERM; 376 + if (nargs > 0) { 377 + if (js_type(args[0]) == JS_NUM) { 378 + sig = (int)js_getnum(args[0]); 379 + } else if (js_type(args[0]) == JS_STR) { 380 + size_t sig_len; 381 + char *sig_str = js_getstr(js, args[0], &sig_len); 382 + if (strncmp(sig_str, "SIGTERM", sig_len) == 0) sig = SIGTERM; 383 + else if (strncmp(sig_str, "SIGKILL", sig_len) == 0) sig = SIGKILL; 384 + else if (strncmp(sig_str, "SIGINT", sig_len) == 0) sig = SIGINT; 385 + else if (strncmp(sig_str, "SIGHUP", sig_len) == 0) sig = SIGHUP; 386 + else if (strncmp(sig_str, "SIGQUIT", sig_len) == 0) sig = SIGQUIT; 387 + } 388 + } 389 + 390 + int result = uv_process_kill(&cp->process, sig); 391 + return result == 0 ? js_mktrue() : js_mkfalse(); 392 + } 393 + 394 + static jsval_t child_write(struct js *js, jsval_t *args, int nargs) { 395 + jsval_t this_obj = js_getthis(js); 396 + if (nargs < 1) return js_mkerr(js, "write() requires data argument"); 397 + 398 + jsval_t cp_ptr = js_get(js, this_obj, "_cp_ptr"); 399 + if (js_type(cp_ptr) == JS_UNDEF) return js_mkerr(js, "Invalid child process object"); 400 + 401 + child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); 402 + if (cp->stdin_closed) return js_mkfalse(); 403 + 404 + size_t data_len; 405 + char *data = js_getstr(js, args[0], &data_len); 406 + if (!data) return js_mkerr(js, "Data must be a string"); 407 + 408 + uv_write_t *write_req = malloc(sizeof(uv_write_t)); 409 + char *buf_data = malloc(data_len); 410 + memcpy(buf_data, data, data_len); 411 + 412 + uv_buf_t buf = uv_buf_init(buf_data, data_len); 413 + write_req->data = buf_data; 414 + 415 + int result = uv_write(write_req, (uv_stream_t *)&cp->stdin_pipe, &buf, 1, NULL); 416 + if (result < 0) { 417 + free(buf_data); 418 + free(write_req); 419 + return js_mkfalse(); 420 + } 421 + 422 + return js_mktrue(); 423 + } 424 + 425 + static jsval_t child_end(struct js *js, jsval_t *args, int nargs) { 426 + (void)args; (void)nargs; 427 + jsval_t this_obj = js_getthis(js); 428 + 429 + jsval_t cp_ptr = js_get(js, this_obj, "_cp_ptr"); 430 + if (js_type(cp_ptr) == JS_UNDEF) return js_mkundef(); 431 + 432 + child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); 433 + if (!cp->stdin_closed) { 434 + uv_close((uv_handle_t *)&cp->stdin_pipe, NULL); 435 + cp->stdin_closed = true; 436 + } 437 + 438 + return js_mkundef(); 439 + } 440 + 441 + static jsval_t create_child_object(struct js *js, child_process_t *cp) { 442 + jsval_t obj = js_mkobj(js); 443 + 444 + js_set(js, obj, "_cp_ptr", js_mknum((double)(uintptr_t)cp)); 445 + js_set(js, obj, "pid", js_mknum((double)cp->process.pid)); 446 + js_set(js, obj, "exitCode", js_mknull()); 447 + js_set(js, obj, "signalCode", js_mknull()); 448 + js_set(js, obj, "killed", js_mkfalse()); 449 + js_set(js, obj, "connected", js_mktrue()); 450 + 451 + js_set(js, obj, "on", js_mkfun(child_on)); 452 + js_set(js, obj, "once", js_mkfun(child_once)); 453 + js_set(js, obj, "kill", js_mkfun(child_kill)); 454 + js_set(js, obj, "write", js_mkfun(child_write)); 455 + js_set(js, obj, "end", js_mkfun(child_end)); 456 + 457 + js_set(js, obj, get_toStringTag_sym_key(), js_mkstr(js, "ChildProcess", 12)); 458 + 459 + return obj; 460 + } 461 + 462 + static char **parse_args_array(struct js *js, jsval_t arr, int *count) { 463 + jsval_t len_val = js_get(js, arr, "length"); 464 + int len = (int)js_getnum(len_val); 465 + 466 + char **args = calloc(len + 1, sizeof(char *)); 467 + if (!args) { 468 + *count = 0; 469 + return NULL; 470 + } 471 + 472 + for (int i = 0; i < len; i++) { 473 + char idx[16]; 474 + snprintf(idx, sizeof(idx), "%d", i); 475 + jsval_t val = js_get(js, arr, idx); 476 + if (js_type(val) == JS_STR) { 477 + size_t arg_len; 478 + char *arg = js_getstr(js, val, &arg_len); 479 + args[i] = strndup(arg, arg_len); 480 + } else args[i] = strdup(""); 481 + } 482 + 483 + args[len] = NULL; 484 + *count = len; 485 + return args; 486 + } 487 + 488 + static void free_args_array(char **args, int count) { 489 + if (!args) return; 490 + for (int i = 0; i < count; i++) { 491 + if (args[i]) free(args[i]); 492 + } 493 + free(args); 494 + } 495 + 496 + static jsval_t builtin_spawn(struct js *js, jsval_t *args, int nargs) { 497 + if (nargs < 1) return js_mkerr(js, "spawn() requires a command"); 498 + if (js_type(args[0]) != JS_STR) return js_mkerr(js, "Command must be a string"); 499 + 500 + ensure_cp_loop(); 501 + 502 + size_t cmd_len; 503 + char *cmd = js_getstr(js, args[0], &cmd_len); 504 + char *cmd_str = strndup(cmd, cmd_len); 505 + 506 + char **spawn_args = NULL; 507 + int spawn_argc = 0; 508 + char *cwd = NULL; 509 + bool use_shell = false; 510 + bool detached = false; 511 + 512 + if (nargs >= 2 && js_type(args[1]) == JS_OBJ) { 513 + jsval_t len_val = js_get(js, args[1], "length"); 514 + if (js_type(len_val) == JS_NUM) { 515 + spawn_args = parse_args_array(js, args[1], &spawn_argc); 516 + } 517 + } 518 + 519 + if (nargs >= 3 && js_type(args[2]) == JS_OBJ) { 520 + jsval_t cwd_val = js_get(js, args[2], "cwd"); 521 + if (js_type(cwd_val) == JS_STR) { 522 + size_t cwd_len; 523 + char *cwd_str = js_getstr(js, cwd_val, &cwd_len); 524 + cwd = strndup(cwd_str, cwd_len); 525 + } 526 + 527 + jsval_t shell_val = js_get(js, args[2], "shell"); 528 + use_shell = js_truthy(js, shell_val); 529 + 530 + jsval_t detached_val = js_get(js, args[2], "detached"); 531 + detached = js_truthy(js, detached_val); 532 + } 533 + 534 + child_process_t *cp = calloc(1, sizeof(child_process_t)); 535 + if (!cp) { 536 + free(cmd_str); 537 + free_args_array(spawn_args, spawn_argc); 538 + if (cwd) free(cwd); 539 + return js_mkerr(js, "Out of memory"); 540 + } 541 + 542 + cp->js = js; 543 + cp->use_shell = use_shell; 544 + cp->detached = detached; 545 + cp->cwd = cwd; 546 + cp->promise = js_mkundef(); 547 + 548 + uv_pipe_init(cp_loop, &cp->stdin_pipe, 0); 549 + uv_pipe_init(cp_loop, &cp->stdout_pipe, 0); 550 + uv_pipe_init(cp_loop, &cp->stderr_pipe, 0); 551 + 552 + cp->stdin_pipe.data = cp; 553 + cp->stdout_pipe.data = cp; 554 + cp->stderr_pipe.data = cp; 555 + cp->process.data = cp; 556 + 557 + uv_stdio_container_t stdio[3]; 558 + stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; 559 + stdio[0].data.stream = (uv_stream_t *)&cp->stdin_pipe; 560 + stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; 561 + stdio[1].data.stream = (uv_stream_t *)&cp->stdout_pipe; 562 + stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; 563 + stdio[2].data.stream = (uv_stream_t *)&cp->stderr_pipe; 564 + 565 + char **final_args; 566 + int final_argc; 567 + char *shell_cmd = NULL; 568 + 569 + if (use_shell) { 570 + final_args = calloc(4, sizeof(char *)); 571 + final_args[0] = strdup("/bin/sh"); 572 + final_args[1] = strdup("-c"); 573 + 574 + size_t total_len = cmd_len + 1; 575 + for (int i = 0; i < spawn_argc; i++) { 576 + total_len += strlen(spawn_args[i]) + 3; 577 + } 578 + shell_cmd = malloc(total_len); 579 + strcpy(shell_cmd, cmd_str); 580 + for (int i = 0; i < spawn_argc; i++) { 581 + strcat(shell_cmd, " "); 582 + strcat(shell_cmd, spawn_args[i]); 583 + } 584 + final_args[2] = shell_cmd; 585 + final_args[3] = NULL; 586 + final_argc = 3; 587 + 588 + free(cmd_str); 589 + cmd_str = strdup("/bin/sh"); 590 + } else { 591 + final_argc = spawn_argc + 1; 592 + final_args = calloc(final_argc + 1, sizeof(char *)); 593 + final_args[0] = cmd_str; 594 + for (int i = 0; i < spawn_argc; i++) { 595 + final_args[i + 1] = spawn_args ? spawn_args[i] : NULL; 596 + } 597 + final_args[final_argc] = NULL; 598 + if (spawn_args) free(spawn_args); 599 + spawn_args = NULL; 600 + } 601 + 602 + uv_process_options_t options = {0}; 603 + options.exit_cb = on_exit; 604 + options.file = final_args[0]; 605 + options.args = final_args; 606 + options.stdio_count = 3; 607 + options.stdio = stdio; 608 + if (cwd) options.cwd = cwd; 609 + if (detached) options.flags = UV_PROCESS_DETACHED; 610 + 611 + int r = uv_spawn(cp_loop, &cp->process, &options); 612 + 613 + if (use_shell) { 614 + free(final_args[0]); 615 + free(final_args[1]); 616 + free(shell_cmd); 617 + free(final_args); 618 + } else { 619 + for (int i = 0; i < final_argc; i++) { 620 + if (final_args[i]) free(final_args[i]); 621 + } 622 + free(final_args); 623 + } 624 + 625 + free_args_array(spawn_args, spawn_argc); 626 + 627 + if (r < 0) { 628 + free_child_process(cp); 629 + return js_mkerr(js, "Failed to spawn process: %s", uv_strerror(r)); 630 + } 631 + 632 + uv_read_start((uv_stream_t *)&cp->stdout_pipe, alloc_buffer, on_stdout_read); 633 + uv_read_start((uv_stream_t *)&cp->stderr_pipe, alloc_buffer, on_stderr_read); 634 + 635 + add_pending_child(cp); 636 + 637 + cp->child_obj = create_child_object(js, cp); 638 + return cp->child_obj; 639 + } 640 + 641 + static jsval_t builtin_exec(struct js *js, jsval_t *args, int nargs) { 642 + if (nargs < 1) return js_mkerr(js, "exec() requires a command"); 643 + if (js_type(args[0]) != JS_STR) return js_mkerr(js, "Command must be a string"); 644 + 645 + ensure_cp_loop(); 646 + 647 + size_t cmd_len; 648 + char *cmd = js_getstr(js, args[0], &cmd_len); 649 + char *cmd_str = strndup(cmd, cmd_len); 650 + 651 + char *cwd = NULL; 652 + if (nargs >= 2 && js_type(args[1]) == JS_OBJ) { 653 + jsval_t cwd_val = js_get(js, args[1], "cwd"); 654 + if (js_type(cwd_val) == JS_STR) { 655 + size_t cwd_len; 656 + char *cwd_s = js_getstr(js, cwd_val, &cwd_len); 657 + cwd = strndup(cwd_s, cwd_len); 658 + } 659 + } 660 + 661 + child_process_t *cp = calloc(1, sizeof(child_process_t)); 662 + if (!cp) { 663 + free(cmd_str); 664 + if (cwd) free(cwd); 665 + return js_mkerr(js, "Out of memory"); 666 + } 667 + 668 + cp->js = js; 669 + cp->use_shell = true; 670 + cp->cwd = cwd; 671 + cp->promise = js_mkpromise(js); 672 + 673 + uv_pipe_init(cp_loop, &cp->stdin_pipe, 0); 674 + uv_pipe_init(cp_loop, &cp->stdout_pipe, 0); 675 + uv_pipe_init(cp_loop, &cp->stderr_pipe, 0); 676 + 677 + cp->stdin_pipe.data = cp; 678 + cp->stdout_pipe.data = cp; 679 + cp->stderr_pipe.data = cp; 680 + cp->process.data = cp; 681 + cp->stdin_closed = true; 682 + 683 + uv_stdio_container_t stdio[3]; 684 + stdio[0].flags = UV_IGNORE; 685 + stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; 686 + stdio[1].data.stream = (uv_stream_t *)&cp->stdout_pipe; 687 + stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; 688 + stdio[2].data.stream = (uv_stream_t *)&cp->stderr_pipe; 689 + 690 + char *shell_args[4]; 691 + shell_args[0] = "/bin/sh"; 692 + shell_args[1] = "-c"; 693 + shell_args[2] = cmd_str; 694 + shell_args[3] = NULL; 695 + 696 + uv_process_options_t options = {0}; 697 + options.exit_cb = on_exit; 698 + options.file = "/bin/sh"; 699 + options.args = shell_args; 700 + options.stdio_count = 3; 701 + options.stdio = stdio; 702 + if (cwd) options.cwd = cwd; 703 + 704 + int r = uv_spawn(cp_loop, &cp->process, &options); 705 + free(cmd_str); 706 + 707 + if (r < 0) { 708 + jsval_t promise = cp->promise; 709 + free_child_process(cp); 710 + js_reject_promise(js, promise, js_mkstr(js, uv_strerror(r), strlen(uv_strerror(r)))); 711 + return promise; 712 + } 713 + 714 + uv_read_start((uv_stream_t *)&cp->stdout_pipe, alloc_buffer, on_stdout_read); 715 + uv_read_start((uv_stream_t *)&cp->stderr_pipe, alloc_buffer, on_stderr_read); 716 + 717 + add_pending_child(cp); 718 + 719 + cp->child_obj = create_child_object(js, cp); 720 + return cp->promise; 721 + } 722 + 723 + static jsval_t builtin_execSync(struct js *js, jsval_t *args, int nargs) { 724 + if (nargs < 1) return js_mkerr(js, "execSync() requires a command"); 725 + if (js_type(args[0]) != JS_STR) return js_mkerr(js, "Command must be a string"); 726 + 727 + size_t cmd_len; 728 + char *cmd = js_getstr(js, args[0], &cmd_len); 729 + char *cmd_str = strndup(cmd, cmd_len); 730 + 731 + FILE *fp = popen(cmd_str, "r"); 732 + free(cmd_str); 733 + 734 + if (!fp) { 735 + return js_mkerr(js, "Failed to execute command"); 736 + } 737 + 738 + char *output = NULL; 739 + size_t output_len = 0; 740 + size_t output_cap = 4096; 741 + output = malloc(output_cap); 742 + 743 + if (!output) { 744 + pclose(fp); 745 + return js_mkerr(js, "Out of memory"); 746 + } 747 + 748 + char buffer[4096]; 749 + while (fgets(buffer, sizeof(buffer), fp) != NULL) { 750 + size_t len = strlen(buffer); 751 + if (output_len + len >= output_cap) { 752 + output_cap *= 2; 753 + char *new_output = realloc(output, output_cap); 754 + if (!new_output) { 755 + free(output); 756 + pclose(fp); 757 + return js_mkerr(js, "Out of memory"); 758 + } 759 + output = new_output; 760 + } 761 + memcpy(output + output_len, buffer, len); 762 + output_len += len; 763 + } 764 + 765 + int status = pclose(fp); 766 + int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1; 767 + 768 + if (output_len > 0 && output[output_len - 1] == '\n') { 769 + output_len--; 770 + } 771 + 772 + if (exit_code != 0) { 773 + char err_msg[256]; 774 + snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %d", exit_code); 775 + free(output); 776 + return js_mkerr(js, err_msg); 777 + } 778 + 779 + jsval_t result = js_mkstr(js, output, output_len); 780 + free(output); 781 + return result; 782 + } 783 + 784 + static jsval_t builtin_spawnSync(struct js *js, jsval_t *args, int nargs) { 785 + if (nargs < 1) return js_mkerr(js, "spawnSync() requires a command"); 786 + if (js_type(args[0]) != JS_STR) return js_mkerr(js, "Command must be a string"); 787 + 788 + size_t cmd_len; 789 + char *cmd = js_getstr(js, args[0], &cmd_len); 790 + char *cmd_str = strndup(cmd, cmd_len); 791 + 792 + char **spawn_args = NULL; 793 + int spawn_argc = 0; 794 + char *input = NULL; 795 + size_t input_len = 0; 796 + 797 + if (nargs >= 2 && js_type(args[1]) == JS_OBJ) { 798 + jsval_t len_val = js_get(js, args[1], "length"); 799 + if (js_type(len_val) == JS_NUM) { 800 + spawn_args = parse_args_array(js, args[1], &spawn_argc); 801 + } 802 + } 803 + 804 + if (nargs >= 3 && js_type(args[2]) == JS_OBJ) { 805 + jsval_t input_val = js_get(js, args[2], "input"); 806 + if (js_type(input_val) == JS_STR) { 807 + input = js_getstr(js, input_val, &input_len); 808 + } 809 + } 810 + 811 + char **exec_args = calloc(spawn_argc + 2, sizeof(char *)); 812 + if (!exec_args) { 813 + free(cmd_str); 814 + free_args_array(spawn_args, spawn_argc); 815 + return js_mkerr(js, "Out of memory"); 816 + } 817 + 818 + exec_args[0] = cmd_str; 819 + for (int i = 0; i < spawn_argc; i++) { 820 + exec_args[i + 1] = spawn_args[i]; 821 + } 822 + exec_args[spawn_argc + 1] = NULL; 823 + 824 + int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2]; 825 + if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0 || pipe(stderr_pipe) < 0) { 826 + free(exec_args); 827 + free(cmd_str); 828 + free_args_array(spawn_args, spawn_argc); 829 + return js_mkerr(js, "Failed to create pipes"); 830 + } 831 + 832 + pid_t pid = fork(); 833 + 834 + if (pid < 0) { 835 + free(exec_args); 836 + free(cmd_str); 837 + free_args_array(spawn_args, spawn_argc); 838 + return js_mkerr(js, "Fork failed"); 839 + } 840 + 841 + if (pid == 0) { 842 + close(stdin_pipe[1]); 843 + close(stdout_pipe[0]); 844 + close(stderr_pipe[0]); 845 + 846 + dup2(stdin_pipe[0], STDIN_FILENO); 847 + dup2(stdout_pipe[1], STDOUT_FILENO); 848 + dup2(stderr_pipe[1], STDERR_FILENO); 849 + 850 + close(stdin_pipe[0]); 851 + close(stdout_pipe[1]); 852 + close(stderr_pipe[1]); 853 + 854 + execvp(exec_args[0], exec_args); 855 + _exit(127); 856 + } 857 + 858 + free(exec_args); 859 + free(cmd_str); 860 + free_args_array(spawn_args, spawn_argc); 861 + 862 + close(stdin_pipe[0]); 863 + close(stdout_pipe[1]); 864 + close(stderr_pipe[1]); 865 + 866 + if (input && input_len > 0) { 867 + write(stdin_pipe[1], input, input_len); 868 + } 869 + close(stdin_pipe[1]); 870 + 871 + char *stdout_buf = NULL; 872 + size_t stdout_len = 0; 873 + size_t stdout_cap = 4096; 874 + stdout_buf = malloc(stdout_cap); 875 + 876 + char *stderr_buf = NULL; 877 + size_t stderr_len = 0; 878 + size_t stderr_cap = 4096; 879 + stderr_buf = malloc(stderr_cap); 880 + 881 + char buffer[4096]; 882 + ssize_t n; 883 + int status = 0; 884 + 885 + while ((n = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) { 886 + if (stdout_len + n >= stdout_cap) { 887 + stdout_cap *= 2; 888 + stdout_buf = realloc(stdout_buf, stdout_cap); 889 + } 890 + memcpy(stdout_buf + stdout_len, buffer, n); 891 + stdout_len += n; 892 + } 893 + close(stdout_pipe[0]); 894 + 895 + while ((n = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) { 896 + if (stderr_len + n >= stderr_cap) { 897 + stderr_cap *= 2; 898 + stderr_buf = realloc(stderr_buf, stderr_cap); 899 + } 900 + memcpy(stderr_buf + stderr_len, buffer, n); 901 + stderr_len += n; 902 + } 903 + close(stderr_pipe[0]); 904 + 905 + waitpid(pid, &status, 0); 906 + 907 + int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1; 908 + int signal_code = WIFSIGNALED(status) ? WTERMSIG(status) : 0; 909 + 910 + jsval_t result = js_mkobj(js); 911 + js_set(js, result, "stdout", js_mkstr(js, stdout_buf ? stdout_buf : "", stdout_len)); 912 + js_set(js, result, "stderr", js_mkstr(js, stderr_buf ? stderr_buf : "", stderr_len)); 913 + js_set(js, result, "status", js_mknum((double)exit_code)); 914 + js_set(js, result, "signal", signal_code ? js_mknum((double)signal_code) : js_mknull()); 915 + js_set(js, result, "pid", js_mknum((double)pid)); 916 + 917 + if (stdout_buf) free(stdout_buf); 918 + if (stderr_buf) free(stderr_buf); 919 + 920 + return result; 921 + } 922 + 923 + static jsval_t builtin_fork(struct js *js, jsval_t *args, int nargs) { 924 + if (nargs < 1) return js_mkerr(js, "fork() requires a module path"); 925 + if (js_type(args[0]) != JS_STR) return js_mkerr(js, "Module path must be a string"); 926 + 927 + size_t path_len; 928 + char *path = js_getstr(js, args[0], &path_len); 929 + char *path_str = strndup(path, path_len); 930 + 931 + char exe_path[1024]; 932 + uint32_t size = sizeof(exe_path); 933 + 934 + #if defined(__APPLE__) 935 + if (_NSGetExecutablePath(exe_path, &size) != 0) { 936 + free(path_str); 937 + return js_mkerr(js, "Failed to get executable path"); 938 + } 939 + #elif defined(__linux__) 940 + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); 941 + if (len == -1) { 942 + free(path_str); 943 + return js_mkerr(js, "Failed to get executable path"); 944 + } 945 + exe_path[len] = '\0'; 946 + #else 947 + strncpy(exe_path, "ant", sizeof(exe_path)); 948 + #endif 949 + 950 + jsval_t spawn_args[3]; 951 + spawn_args[0] = js_mkstr(js, exe_path, strlen(exe_path)); 952 + 953 + jsval_t args_arr = js_mkarr(js); 954 + js_arr_push(js, args_arr, js_mkstr(js, path_str, path_len)); 955 + 956 + if (nargs >= 2 && js_type(args[1]) == JS_OBJ) { 957 + jsval_t exec_args = js_get(js, args[1], "execArgv"); 958 + if (js_type(exec_args) == JS_OBJ) { 959 + jsval_t len_val = js_get(js, exec_args, "length"); 960 + int len = (int)js_getnum(len_val); 961 + for (int i = 0; i < len; i++) { 962 + char idx[16]; 963 + snprintf(idx, sizeof(idx), "%d", i); 964 + jsval_t arg = js_get(js, exec_args, idx); 965 + js_arr_push(js, args_arr, arg); 966 + } 967 + } 968 + } 969 + 970 + spawn_args[1] = args_arr; 971 + spawn_args[2] = js_mkobj(js); 972 + 973 + free(path_str); 974 + 975 + return builtin_spawn(js, spawn_args, 3); 976 + } 977 + 978 + jsval_t child_process_library(struct js *js) { 979 + jsval_t lib = js_mkobj(js); 980 + 981 + js_set(js, lib, "spawn", js_mkfun(builtin_spawn)); 982 + js_set(js, lib, "exec", js_mkfun(builtin_exec)); 983 + js_set(js, lib, "execSync", js_mkfun(builtin_execSync)); 984 + js_set(js, lib, "spawnSync", js_mkfun(builtin_spawnSync)); 985 + js_set(js, lib, "fork", js_mkfun(builtin_fork)); 986 + js_set(js, lib, get_toStringTag_sym_key(), js_mkstr(js, "child_process", 13)); 987 + 988 + return lib; 989 + } 990 + 991 + int has_pending_child_processes(void) { 992 + return pending_children_head != NULL; 993 + } 994 + 995 + void child_process_poll_events(void) { 996 + if (cp_loop && uv_loop_alive(cp_loop)) { 997 + uv_run(cp_loop, UV_RUN_NOWAIT); 998 + } 999 + } 1000 + 1001 + void child_process_gc_update_roots(GC_FWD_ARGS) { 1002 + for (child_process_t *cp = pending_children_head; cp; cp = cp->next) { 1003 + cp->child_obj = fwd_val(ctx, cp->child_obj); 1004 + cp->promise = fwd_val(ctx, cp->promise); 1005 + 1006 + child_event_t *evt, *tmp; 1007 + HASH_ITER(hh, cp->events, evt, tmp) { 1008 + for (int i = 0; i < evt->count; i++) evt->listeners[i].callback = fwd_val(ctx, evt->listeners[i].callback); 1009 + } 1010 + } 1011 + }