MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <uv.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <errno.h>
8#include <uthash.h>
9#include <utarray.h>
10
11#ifdef _WIN32
12#define WIN32_LEAN_AND_MEAN
13#include <windows.h>
14#include <io.h>
15#include <process.h>
16#else
17#include <sys/wait.h>
18#include <sys/select.h>
19#include <signal.h>
20#include <fcntl.h>
21#if defined(__APPLE__)
22#include <mach-o/dyld.h>
23#endif
24#endif
25
26#include "ant.h"
27#include "errors.h"
28#include "internal.h"
29#include "silver/engine.h"
30
31#include "gc/modules.h"
32#include "modules/child_process.h"
33#include "modules/buffer.h"
34#include "modules/events.h"
35#include "modules/symbol.h"
36
37#define MAX_CHILD_LISTENERS 16
38#define PIPE_READ_BUF_SIZE 65536
39
40typedef struct
41 child_process_s child_process_t;
42
43typedef enum {
44 CHILD_STREAM_STDIN = 0,
45 CHILD_STREAM_STDOUT = 1,
46 CHILD_STREAM_STDERR = 2,
47} child_stream_kind_t;
48
49typedef struct {
50 child_process_t *cp;
51 child_stream_kind_t kind;
52} child_stream_ctx_t;
53
54typedef struct {
55 ant_value_t callback;
56 bool once;
57} child_listener_t;
58
59typedef struct {
60 char *event_name;
61 child_listener_t listeners[MAX_CHILD_LISTENERS];
62 int count;
63 UT_hash_handle hh;
64} child_event_t;
65
66typedef enum {
67 STDIO_PIPE = 0,
68 STDIO_INHERIT,
69 STDIO_IGNORE,
70} stdio_mode_t;
71
72struct child_process_s {
73 ant_t *js;
74 uv_process_t process;
75 uv_pipe_t stdin_pipe;
76 uv_pipe_t stdout_pipe;
77 uv_pipe_t stderr_pipe;
78 ant_value_t child_obj;
79 ant_value_t stdin_obj;
80 ant_value_t stdout_obj;
81 ant_value_t stderr_obj;
82 child_stream_ctx_t *stdin_ctx;
83 child_stream_ctx_t *stdout_ctx;
84 child_stream_ctx_t *stderr_ctx;
85 ant_value_t promise;
86 child_event_t *events;
87 char *stdout_buf;
88 size_t stdout_len;
89 size_t stdout_cap;
90 char *stderr_buf;
91 size_t stderr_len;
92 size_t stderr_cap;
93 int64_t exit_code;
94 int term_signal;
95 bool exited;
96 bool stdout_closed;
97 bool stderr_closed;
98 bool stdin_closed;
99 bool use_shell;
100 bool detached;
101 bool close_emitted;
102 bool keep_alive;
103 int pending_closes;
104 char *cwd;
105 stdio_mode_t stdio_modes[3];
106 struct child_process_s *next;
107 struct child_process_s *prev;
108};
109
110static child_process_t *pending_children_head = NULL;
111static child_process_t *pending_children_tail = NULL;
112
113static ant_value_t g_child_process_proto = 0;
114static ant_value_t g_child_process_ctor = 0;
115
116static void fprint_js_str_raw(FILE *out, ant_t *js, ant_value_t s) {
117 if (vtype(s) != T_STR) {
118 fprintf(out, "%s\n", js_str(js, s));
119 return;
120 }
121
122 ant_offset_t len = 0;
123 ant_offset_t off = vstr(js, s, &len);
124 const char *ptr = (const char *)(uintptr_t)off;
125 if (ptr && len > 0) fwrite(ptr, 1, (size_t)len, out);
126 if (len == 0 || ptr[len - 1] != '\n') fputc('\n', out);
127}
128
129static void log_listener_error(ant_t *js, const char *event_name, ant_value_t err) {
130 ant_value_t thrown_stack = js->thrown_stack;
131 if (vtype(thrown_stack) == T_STR) {
132 fprintf(stderr, "Error in child_process '%s' listener:\n", event_name);
133 fprint_js_str_raw(stderr, js, thrown_stack);
134 return;
135 }
136
137 ant_value_t thrown_value = js->thrown_value;
138 ant_value_t src = (vtype(thrown_value) != T_UNDEF) ? thrown_value : err;
139
140 ant_value_t stack = js_get(js, src, "stack");
141 if (vtype(stack) == T_STR) {
142 fprintf(stderr, "Error in child_process '%s' listener:\n", event_name);
143 fprint_js_str_raw(stderr, js, stack);
144 return;
145 }
146
147 ant_value_t name = js_get(js, src, "name");
148 ant_value_t message = js_get(js, src, "message");
149
150 const char *detail = NULL;
151 if (vtype(name) == T_STR && vtype(message) == T_STR) {
152 const char *name_s = js_str(js, name);
153 const char *msg_s = js_str(js, message);
154 if (msg_s && msg_s[0]) fprintf(stderr, "Error in child_process '%s' listener: %s: %s\n", event_name, name_s, msg_s);
155 else detail = name_s;
156 }
157 else if (vtype(message) == T_STR) detail = js_str(js, message);
158 else detail = js_str(js, src);
159
160 if (detail) fprintf(stderr, "Error in child_process '%s' listener: %s\n", event_name, detail);
161 js_print_stack_trace_vm(js, stderr);
162}
163
164static void emit_event(child_process_t *cp, const char *name, ant_value_t *args, int nargs) {
165 child_event_t *evt = NULL;
166 HASH_FIND_STR(cp->events, name, evt);
167 if (!evt || evt->count == 0) return;
168
169 int i = 0;
170 while (i < evt->count) {
171 child_listener_t *l = &evt->listeners[i];
172 ant_value_t result = sv_vm_call(cp->js->vm, cp->js, l->callback, js_mkundef(), args, nargs, NULL, false);
173 if (vtype(result) == T_ERR) log_listener_error(cp->js, name, result);
174 if (l->once) {
175 for (int j = i; j < evt->count - 1; j++)
176 evt->listeners[j] = evt->listeners[j + 1];
177 evt->count--;
178 } else i++;
179 }
180}
181
182static const char *stream_kind_name(child_stream_kind_t kind) {
183switch (kind) {
184 case CHILD_STREAM_STDIN: return "stdin";
185 case CHILD_STREAM_STDOUT: return "stdout";
186 case CHILD_STREAM_STDERR: return "stderr";
187 default: return "unknown";
188}}
189
190static void emit_stream_event(
191 child_process_t *cp,
192 child_stream_kind_t kind,
193 const char *event,
194 ant_value_t *args,
195 int nargs
196) {
197 char full_name[64];
198 snprintf(full_name, sizeof(full_name), "%s:%s", stream_kind_name(kind), event);
199 emit_event(cp, full_name, args, nargs);
200}
201
202static ant_value_t make_buffer_chunk(ant_t *js, const char *data, size_t len) {
203 ArrayBufferData *ab = create_array_buffer_data(len);
204 if (!ab) return js_mkerr(js, "Out of memory");
205 if (len > 0 && data) memcpy(ab->data, data, len);
206 return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer");
207}
208
209static uv_pipe_t *child_pipe(child_process_t *cp, child_stream_kind_t kind) {
210switch (kind) {
211 case CHILD_STREAM_STDIN: return &cp->stdin_pipe;
212 case CHILD_STREAM_STDOUT: return &cp->stdout_pipe;
213 case CHILD_STREAM_STDERR: return &cp->stderr_pipe;
214 default: return NULL;
215}}
216
217static bool child_stdio_is_pipe(child_process_t *cp, child_stream_kind_t kind) {
218 return cp->stdio_modes[kind] == STDIO_PIPE;
219}
220
221static bool *child_closed_flag(child_process_t *cp, child_stream_kind_t kind) {
222 switch (kind) {
223 case CHILD_STREAM_STDIN: return &cp->stdin_closed;
224 case CHILD_STREAM_STDOUT: return &cp->stdout_closed;
225 case CHILD_STREAM_STDERR: return &cp->stderr_closed;
226 default: return NULL;
227 }
228}
229
230static void add_pending_child(child_process_t *cp) {
231 cp->next = NULL;
232 cp->prev = pending_children_tail;
233 if (pending_children_tail) {
234 pending_children_tail->next = cp;
235 } else pending_children_head = cp;
236 pending_children_tail = cp;
237}
238
239static void remove_pending_child(child_process_t *cp) {
240 if (cp->prev) cp->prev->next = cp->next;
241 else pending_children_head = cp->next;
242 if (cp->next) cp->next->prev = cp->prev;
243 else pending_children_tail = cp->prev;
244 cp->next = NULL;
245 cp->prev = NULL;
246}
247
248static void free_child_process(child_process_t *cp) {
249 if (!cp) return;
250
251 if (vtype(cp->child_obj) == T_OBJ) js_set_slot(cp->child_obj, SLOT_DATA, js_mkundef());
252 if (vtype(cp->stdin_obj) == T_OBJ) js_set_slot(cp->stdin_obj, SLOT_DATA, js_mkundef());
253 if (vtype(cp->stdout_obj) == T_OBJ) js_set_slot(cp->stdout_obj, SLOT_DATA, js_mkundef());
254 if (vtype(cp->stderr_obj) == T_OBJ) js_set_slot(cp->stderr_obj, SLOT_DATA, js_mkundef());
255
256 if (cp->stdout_buf) free(cp->stdout_buf);
257 if (cp->stderr_buf) free(cp->stderr_buf);
258 if (cp->cwd) free(cp->cwd);
259 if (cp->stdin_ctx) free(cp->stdin_ctx);
260 if (cp->stdout_ctx) free(cp->stdout_ctx);
261 if (cp->stderr_ctx) free(cp->stderr_ctx);
262
263 child_event_t *evt, *tmp;
264 HASH_ITER(hh, cp->events, evt, tmp) {
265 HASH_DEL(cp->events, evt);
266 free(evt->event_name);
267 free(evt);
268 }
269
270 free(cp);
271}
272
273static child_event_t *find_or_create_event(child_process_t *cp, const char *name) {
274 child_event_t *evt = NULL;
275 HASH_FIND_STR(cp->events, name, evt);
276 if (!evt) {
277 evt = calloc(1, sizeof(child_event_t));
278 evt->event_name = strdup(name);
279 evt->count = 0;
280 HASH_ADD_KEYPTR(hh, cp->events, evt->event_name, strlen(evt->event_name), evt);
281 }
282 return evt;
283}
284
285static void try_free_child(child_process_t *cp) {
286 if (!cp) return;
287 if (cp->exited && cp->stdout_closed && cp->stderr_closed && cp->pending_closes == 0) {
288 remove_pending_child(cp);
289 free_child_process(cp);
290 }
291}
292
293static void check_completion(child_process_t *cp) {
294 if (cp->exited && cp->stdout_closed && cp->stderr_closed && !cp->close_emitted) {
295 cp->close_emitted = true;
296
297 ant_value_t stdout_val = js_mkstr(cp->js, cp->stdout_buf ? cp->stdout_buf : "", cp->stdout_len);
298 ant_value_t stderr_val = js_mkstr(cp->js, cp->stderr_buf ? cp->stderr_buf : "", cp->stderr_len);
299
300 if (vtype(cp->stdout_obj) == T_OBJ) {
301 js_set(cp->js, cp->stdout_obj, "text", stdout_val);
302 js_set(cp->js, cp->stdout_obj, "length", js_mknum((double)cp->stdout_len));
303 }
304
305 if (vtype(cp->stderr_obj) == T_OBJ) {
306 js_set(cp->js, cp->stderr_obj, "text", stderr_val);
307 js_set(cp->js, cp->stderr_obj, "length", js_mknum((double)cp->stderr_len));
308 }
309
310 js_set(cp->js, cp->child_obj, "stdoutText", stdout_val);
311 js_set(cp->js, cp->child_obj, "stderrText", stderr_val);
312 js_set(cp->js, cp->child_obj, "exitCode", js_mknum((double)cp->exit_code));
313 js_set(cp->js, cp->child_obj, "signalCode", cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull());
314
315 ant_value_t close_args[2] = { js_mknum((double)cp->exit_code), cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull() };
316 emit_event(cp, "close", close_args, 2);
317
318 if (vtype(cp->promise) != T_UNDEF) {
319 ant_value_t result = js_mkobj(cp->js);
320 js_set(cp->js, result, "stdout", stdout_val);
321 js_set(cp->js, result, "stderr", stderr_val);
322 js_set(cp->js, result, "exitCode", js_mknum((double)cp->exit_code));
323 js_set(cp->js, result, "signalCode", cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull());
324
325 if (cp->exit_code != 0) {
326 char err_msg[256];
327 snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %lld", (long long)cp->exit_code);
328 ant_value_t err = js_mkstr(cp->js, err_msg, strlen(err_msg));
329 js_reject_promise(cp->js, cp->promise, err);
330 } else js_resolve_promise(cp->js, cp->promise, result);
331 }
332
333 try_free_child(cp);
334 }
335}
336
337static void on_handle_close(uv_handle_t *handle) {
338 child_process_t *cp = (child_process_t *)handle->data;
339 if (cp) {
340 cp->pending_closes--;
341 try_free_child(cp);
342 }
343}
344
345static void close_child_handle(child_process_t *cp, uv_handle_t *handle) {
346 if (!cp || !handle || uv_is_closing(handle)) return;
347 cp->pending_closes++;
348 uv_close(handle, on_handle_close);
349}
350
351static void close_child_pipe(child_process_t *cp, child_stream_kind_t kind, bool stop_read) {
352 bool *closed = NULL;
353 uv_pipe_t *pipe = NULL;
354
355 if (!cp || !child_stdio_is_pipe(cp, kind)) return;
356
357 closed = child_closed_flag(cp, kind);
358 pipe = child_pipe(cp, kind);
359 if (!closed || !pipe || *closed || uv_is_closing((uv_handle_t *)pipe)) return;
360
361 if (stop_read && kind != CHILD_STREAM_STDIN) {
362 uv_read_stop((uv_stream_t *)pipe);
363 }
364
365 *closed = true;
366 close_child_handle(cp, (uv_handle_t *)pipe);
367}
368
369static void on_process_exit(uv_process_t *proc, int64_t exit_status, int term_signal) {
370 child_process_t *cp = (child_process_t *)proc->data;
371 cp->exit_code = exit_status;
372 cp->term_signal = term_signal;
373 cp->exited = true;
374
375 js_set(cp->js, cp->child_obj, "exitCode", js_mknum((double)exit_status));
376 if (term_signal) {
377 js_set(cp->js, cp->child_obj, "signalCode", js_mknum((double)term_signal));
378 js_set(cp->js, cp->child_obj, "killed", js_true);
379 }
380
381 ant_value_t exit_args[2] = { js_mknum((double)exit_status), term_signal ? js_mknum((double)term_signal) : js_mknull() };
382 emit_event(cp, "exit", exit_args, 2);
383
384 close_child_handle(cp, (uv_handle_t *)proc);
385 close_child_pipe(cp, CHILD_STREAM_STDIN, false);
386 close_child_pipe(cp, CHILD_STREAM_STDOUT, true);
387 close_child_pipe(cp, CHILD_STREAM_STDERR, true);
388
389 check_completion(cp);
390}
391
392static void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
393 (void)handle;
394 buf->base = malloc(suggested_size);
395#ifdef _WIN32
396 buf->len = buf->base ? (ULONG)(suggested_size > (size_t)ULONG_MAX ? ULONG_MAX : suggested_size) : 0;
397#else
398 buf->len = buf->base ? suggested_size : 0;
399#endif
400}
401
402static void on_stdout_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
403 child_process_t *cp = (child_process_t *)stream->data;
404
405 if (nread > 0) {
406 if (cp->stdout_len + nread > cp->stdout_cap) {
407 size_t new_cap = cp->stdout_cap == 0 ? 4096 : cp->stdout_cap * 2;
408 while (new_cap < cp->stdout_len + nread) new_cap *= 2;
409 char *new_buf = realloc(cp->stdout_buf, new_cap);
410 if (new_buf) {
411 cp->stdout_buf = new_buf;
412 cp->stdout_cap = new_cap;
413 }
414 }
415 if (cp->stdout_buf) {
416 memcpy(cp->stdout_buf + cp->stdout_len, buf->base, nread);
417 cp->stdout_len += nread;
418 }
419
420 ant_value_t text_data = js_mkstr(cp->js, buf->base, nread);
421 ant_value_t text_args[1] = { text_data };
422 emit_event(cp, "stdout", text_args, 1);
423
424 ant_value_t byte_data = make_buffer_chunk(cp->js, buf->base, (size_t)nread);
425 ant_value_t byte_args[1] = { byte_data };
426 emit_stream_event(cp, CHILD_STREAM_STDOUT, "data", byte_args, 1);
427
428 if (vtype(cp->stdout_obj) == T_OBJ) js_set(
429 cp->js, cp->stdout_obj, "length",
430 js_mknum((double)cp->stdout_len)
431 );
432 }
433
434 if (buf->base) free(buf->base);
435
436 if (nread < 0) {
437 if (nread != UV_EOF) {
438 ant_value_t err_args[1] = { js_mkstr(cp->js, uv_strerror((int)nread), (int)strlen(uv_strerror((int)nread))) };
439 emit_event(cp, "error", err_args, 1);
440 emit_stream_event(cp, CHILD_STREAM_STDOUT, "error", err_args, 1);
441 } else emit_stream_event(cp, CHILD_STREAM_STDOUT, "end", NULL, 0);
442
443 if (nread != UV_EOF)
444 emit_stream_event(cp, CHILD_STREAM_STDOUT, "end", NULL, 0);
445
446 close_child_pipe(cp, CHILD_STREAM_STDOUT, true);
447 check_completion(cp);
448 }
449}
450
451static void on_stderr_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
452 child_process_t *cp = (child_process_t *)stream->data;
453
454 if (nread > 0) {
455 if (cp->stderr_len + nread > cp->stderr_cap) {
456 size_t new_cap = cp->stderr_cap == 0 ? 4096 : cp->stderr_cap * 2;
457 while (new_cap < cp->stderr_len + nread) new_cap *= 2;
458 char *new_buf = realloc(cp->stderr_buf, new_cap);
459 if (new_buf) {
460 cp->stderr_buf = new_buf;
461 cp->stderr_cap = new_cap;
462 }
463 }
464 if (cp->stderr_buf) {
465 memcpy(cp->stderr_buf + cp->stderr_len, buf->base, nread);
466 cp->stderr_len += nread;
467 }
468
469 ant_value_t text_data = js_mkstr(cp->js, buf->base, nread);
470 ant_value_t text_args[1] = { text_data };
471 emit_event(cp, "stderr", text_args, 1);
472
473 ant_value_t byte_data = make_buffer_chunk(cp->js, buf->base, (size_t)nread);
474 ant_value_t byte_args[1] = { byte_data };
475 emit_stream_event(cp, CHILD_STREAM_STDERR, "data", byte_args, 1);
476
477 if (vtype(cp->stderr_obj) == T_OBJ) js_set(
478 cp->js, cp->stderr_obj, "length",
479 js_mknum((double)cp->stderr_len)
480 );
481 }
482
483 if (buf->base) free(buf->base);
484
485 if (nread < 0) {
486 if (nread != UV_EOF) {
487 ant_value_t err_args[1] = {
488 js_mkstr(cp->js, uv_strerror((int)nread),
489 (int)strlen(uv_strerror((int)nread)))
490 };
491 emit_stream_event(cp, CHILD_STREAM_STDERR, "error", err_args, 1);
492 }
493
494 emit_stream_event(cp, CHILD_STREAM_STDERR, "end", NULL, 0);
495 close_child_pipe(cp, CHILD_STREAM_STDERR, true);
496 check_completion(cp);
497 }
498}
499
500static ant_value_t child_on(ant_t *js, ant_value_t *args, int nargs) {
501 ant_value_t this_obj = js_getthis(js);
502 if (nargs < 2) return js_mkerr(js, "on() requires event name and callback");
503 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string");
504 if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function");
505
506 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA);
507 if (vtype(cp_ptr) == T_UNDEF) return js_mkerr(js, "Invalid child process object");
508 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr);
509
510 size_t name_len;
511 char *name = js_getstr(js, args[0], &name_len);
512 char *name_cstr = strndup(name, name_len);
513
514 child_event_t *evt = find_or_create_event(cp, name_cstr);
515 free(name_cstr);
516
517 if (evt->count >= MAX_CHILD_LISTENERS) {
518 return js_mkerr(js, "Maximum listeners reached for event");
519 }
520
521 evt->listeners[evt->count].callback = args[1];
522 evt->listeners[evt->count].once = false;
523 evt->count++;
524
525 return this_obj;
526}
527
528static ant_value_t child_once(ant_t *js, ant_value_t *args, int nargs) {
529 ant_value_t this_obj = js_getthis(js);
530 if (nargs < 2) return js_mkerr(js, "once() requires event name and callback");
531 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string");
532 if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function");
533
534 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA);
535 if (vtype(cp_ptr) == T_UNDEF) return js_mkerr(js, "Invalid child process object");
536
537 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr);
538
539 size_t name_len;
540 char *name = js_getstr(js, args[0], &name_len);
541 char *name_cstr = strndup(name, name_len);
542
543 child_event_t *evt = find_or_create_event(cp, name_cstr);
544 free(name_cstr);
545
546 if (evt->count >= MAX_CHILD_LISTENERS) {
547 return js_mkerr(js, "Maximum listeners reached for event");
548 }
549
550 evt->listeners[evt->count].callback = args[1];
551 evt->listeners[evt->count].once = true;
552 evt->count++;
553
554 return this_obj;
555}
556
557static ant_value_t child_kill(ant_t *js, ant_value_t *args, int nargs) {
558 ant_value_t this_obj = js_getthis(js);
559
560 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA);
561 if (vtype(cp_ptr) == T_UNDEF) return js_false;
562
563 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr);
564 if (cp->exited) return js_false;
565
566 int sig = SIGTERM;
567 if (nargs > 0) {
568 if (vtype(args[0]) == T_NUM) {
569 sig = (int)js_getnum(args[0]);
570 } else if (vtype(args[0]) == T_STR) {
571 size_t sig_len;
572 char *sig_str = js_getstr(js, args[0], &sig_len);
573 if (sig_len == 7 && strncmp(sig_str, "SIGTERM", 7) == 0) sig = SIGTERM;
574 else if (sig_len == 7 && strncmp(sig_str, "SIGKILL", 7) == 0) sig = SIGKILL;
575 else if (sig_len == 6 && strncmp(sig_str, "SIGINT", 6) == 0) sig = SIGINT;
576 else if (sig_len == 6 && strncmp(sig_str, "SIGHUP", 6) == 0) sig = SIGHUP;
577 else if (sig_len == 7 && strncmp(sig_str, "SIGQUIT", 7) == 0) sig = SIGQUIT;
578 }
579 }
580
581 int result = uv_process_kill(&cp->process, sig);
582 return js_bool(result == 0);
583}
584
585static ant_value_t child_write_impl(ant_t *js, child_process_t *cp, ant_value_t data_arg) {
586 if (cp->stdin_closed) return js_false;
587
588 const char *data = NULL;
589 size_t data_len = 0;
590
591 if (vtype(data_arg) == T_STR) {
592 data = js_getstr(js, data_arg, &data_len);
593 if (!data) return js_mkerr(js, "Data must be a string or Buffer");
594 } else {
595 TypedArrayData *ta_data = buffer_get_typedarray_data(data_arg);
596 if (!ta_data || !ta_data->buffer || !ta_data->buffer->data) {
597 return js_mkerr(js, "Data must be a string or Buffer");
598 }
599 data = (const char *)(ta_data->buffer->data + ta_data->byte_offset);
600 data_len = ta_data->byte_length;
601 }
602
603 uv_write_t *write_req = malloc(sizeof(uv_write_t));
604 char *buf_data = malloc(data_len);
605 memcpy(buf_data, data, data_len);
606
607 uv_buf_t buf = uv_buf_init(buf_data, (unsigned int)data_len);
608 write_req->data = buf_data;
609
610 int result = uv_write(write_req, (uv_stream_t *)&cp->stdin_pipe, &buf, 1, NULL);
611 if (result < 0) {
612 ant_value_t err_args[1] = { js_mkstr(js, uv_strerror(result), strlen(uv_strerror(result))) };
613 emit_stream_event(cp, CHILD_STREAM_STDIN, "error", err_args, 1);
614 free(buf_data); free(write_req);
615 return js_false;
616 }
617
618 return js_true;
619}
620
621static ant_value_t child_end_impl(child_process_t *cp) {
622 close_child_pipe(cp, CHILD_STREAM_STDIN, false);
623 return js_mkundef();
624}
625
626static ant_value_t child_write(ant_t *js, ant_value_t *args, int nargs) {
627 ant_value_t this_obj = js_getthis(js);
628 if (nargs < 1) return js_mkerr(js, "write() requires data argument");
629
630 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA);
631 if (vtype(cp_ptr) == T_UNDEF) return js_mkerr(js, "Invalid child process object");
632
633 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr);
634 return child_write_impl(js, cp, args[0]);
635}
636
637static ant_value_t child_ref(ant_t *js, ant_value_t *args, int nargs) {
638 ant_value_t this_obj = js_getthis(js);
639 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA);
640
641 if (vtype(cp_ptr) == T_UNDEF) return this_obj;
642 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr);
643
644 if (!cp) return this_obj;
645 cp->keep_alive = true;
646
647 if (!uv_is_closing((uv_handle_t *)&cp->process)) uv_ref((uv_handle_t *)&cp->process);
648 for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) {
649 uv_handle_t *h = (uv_handle_t *)child_pipe(cp, i);
650 if (child_stdio_is_pipe(cp, i) && !uv_is_closing(h)) uv_ref(h);
651 }
652
653 return this_obj;
654}
655
656static ant_value_t child_unref(ant_t *js, ant_value_t *args, int nargs) {
657 ant_value_t this_obj = js_getthis(js);
658 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA);
659
660 if (vtype(cp_ptr) == T_UNDEF) return this_obj;
661 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr);
662
663 if (!cp) return this_obj;
664 cp->keep_alive = false;
665
666 if (!uv_is_closing((uv_handle_t *)&cp->process)) uv_unref((uv_handle_t *)&cp->process);
667 for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) {
668 uv_handle_t *h = (uv_handle_t *)child_pipe(cp, i);
669 if (child_stdio_is_pipe(cp, i) && !uv_is_closing(h)) uv_unref(h);
670 }
671
672 return this_obj;
673}
674
675static ant_value_t child_end(ant_t *js, ant_value_t *args, int nargs) {
676 ant_value_t this_obj = js_getthis(js);
677
678 ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA);
679 if (vtype(cp_ptr) == T_UNDEF) return js_mkundef();
680
681 child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr);
682 return child_end_impl(cp);
683}
684
685static ant_value_t child_process_ctor(ant_t *js, ant_value_t *args, int nargs) {
686 ant_value_t obj = js_mkobj(js);
687 if (is_object_type(g_child_process_proto)) js_set_proto_init(obj, g_child_process_proto);
688
689 js_set(js, obj, "pid", js_mkundef());
690 js_set(js, obj, "exitCode", js_mknull());
691 js_set(js, obj, "signalCode", js_mknull());
692 js_set(js, obj, "killed", js_false);
693 js_set(js, obj, "connected", js_false);
694 js_set(js, obj, "stdin", js_mknull());
695 js_set(js, obj, "stdout", js_mknull());
696 js_set(js, obj, "stderr", js_mknull());
697
698 return obj;
699}
700
701static void child_process_init_constructor(ant_t *js) {
702 if (g_child_process_ctor && g_child_process_proto) return;
703 ant_value_t ee_proto = eventemitter_prototype(js);
704
705 g_child_process_proto = js_mkobj(js);
706 if (is_object_type(ee_proto)) js_set_proto_init(g_child_process_proto, ee_proto);
707 js_set(js, g_child_process_proto, "kill", js_mkfun(child_kill));
708 js_set(js, g_child_process_proto, "ref", js_mkfun(child_ref));
709 js_set(js, g_child_process_proto, "unref", js_mkfun(child_unref));
710
711 g_child_process_ctor = js_make_ctor(
712 js, child_process_ctor,
713 g_child_process_proto, "ChildProcess", 12
714 );
715}
716
717static uv_handle_t *child_stream_handle(child_process_t *cp, child_stream_kind_t kind) {
718 if (!cp) return NULL;
719 switch (kind) {
720 case CHILD_STREAM_STDIN: return (uv_handle_t *)&cp->stdin_pipe;
721 case CHILD_STREAM_STDOUT: return (uv_handle_t *)&cp->stdout_pipe;
722 case CHILD_STREAM_STDERR: return (uv_handle_t *)&cp->stderr_pipe;
723 default: return NULL;
724 }
725}
726
727static ant_value_t child_stream_write(ant_t *js, ant_value_t *args, int nargs) {
728 ant_value_t this_obj = js_getthis(js);
729 if (nargs < 1) return js_mkerr(js, "write() requires data argument");
730 ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA);
731 if (vtype(ctx_ptr) == T_UNDEF) return js_mkerr(js, "Invalid stream object");
732 child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr);
733 if (!ctx || !ctx->cp) return js_mkerr(js, "Invalid stream context");
734 return child_write_impl(js, ctx->cp, args[0]);
735}
736
737static ant_value_t child_stream_end(ant_t *js, ant_value_t *args, int nargs) {
738 (void)args; (void)nargs;
739 ant_value_t this_obj = js_getthis(js);
740 ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA);
741 if (vtype(ctx_ptr) == T_UNDEF) return js_mkundef();
742 child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr);
743 if (!ctx || !ctx->cp) return js_mkundef();
744 return child_end_impl(ctx->cp);
745}
746
747static ant_value_t child_stream_ref(ant_t *js, ant_value_t *args, int nargs) {
748 (void)args; (void)nargs;
749 ant_value_t this_obj = js_getthis(js);
750 ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA);
751 if (vtype(ctx_ptr) == T_UNDEF) return this_obj;
752 child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr);
753 if (!ctx || !ctx->cp) return this_obj;
754
755 uv_handle_t *h = child_stream_handle(ctx->cp, ctx->kind);
756 if (h && !uv_is_closing(h)) uv_ref(h);
757 return this_obj;
758}
759
760static ant_value_t child_stream_unref(ant_t *js, ant_value_t *args, int nargs) {
761 (void)args; (void)nargs;
762 ant_value_t this_obj = js_getthis(js);
763 ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA);
764 if (vtype(ctx_ptr) == T_UNDEF) return this_obj;
765 child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr);
766 if (!ctx || !ctx->cp) return this_obj;
767
768 uv_handle_t *h = child_stream_handle(ctx->cp, ctx->kind);
769 if (h && !uv_is_closing(h)) uv_unref(h);
770 return this_obj;
771}
772
773static ant_value_t child_stream_destroy(ant_t *js, ant_value_t *args, int nargs) {
774 (void)args; (void)nargs;
775 ant_value_t this_obj = js_getthis(js);
776 ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA);
777 if (vtype(ctx_ptr) == T_UNDEF) return this_obj;
778 child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr);
779 if (!ctx || !ctx->cp) return this_obj;
780
781 child_process_t *cp = ctx->cp;
782 if (ctx->kind == CHILD_STREAM_STDIN) {
783 (void)child_end_impl(cp);
784 return this_obj;
785 }
786
787 uv_handle_t *h = child_stream_handle(cp, ctx->kind);
788 if (h && !uv_is_closing(h)) {
789 if (ctx->kind == CHILD_STREAM_STDOUT) close_child_pipe(cp, CHILD_STREAM_STDOUT, true);
790 else if (ctx->kind == CHILD_STREAM_STDERR) close_child_pipe(cp, CHILD_STREAM_STDERR, true);
791 check_completion(cp);
792 }
793
794 return this_obj;
795}
796
797static ant_value_t child_stream_on(ant_t *js, ant_value_t *args, int nargs) {
798 ant_value_t this_obj = js_getthis(js);
799 if (nargs < 2) return js_mkerr(js, "on() requires event name and callback");
800 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string");
801 if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function");
802
803 ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA);
804 if (vtype(ctx_ptr) == T_UNDEF) return js_mkerr(js, "Invalid stream object");
805
806 child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr);
807 if (!ctx || !ctx->cp) return js_mkerr(js, "Invalid stream context");
808
809 child_process_t *cp = ctx->cp;
810 child_stream_kind_t kind = ctx->kind;
811
812 size_t name_len = 0;
813 char *name = js_getstr(js, args[0], &name_len);
814 if (!name) return js_mkerr(js, "Event name must be a string");
815
816 char full_name[64];
817 snprintf(
818 full_name, sizeof(full_name),
819 "%s:%.*s", stream_kind_name(kind),
820 (int)name_len, name
821 );
822
823 child_event_t *evt = find_or_create_event(cp, full_name);
824 if (evt->count >= MAX_CHILD_LISTENERS) {
825 return js_mkerr(js, "Maximum listeners reached for event");
826 }
827
828 evt->listeners[evt->count].callback = args[1];
829 evt->listeners[evt->count].once = false;
830 evt->count++;
831 return this_obj;
832}
833
834static ant_value_t child_stream_once(ant_t *js, ant_value_t *args, int nargs) {
835 ant_value_t this_obj = js_getthis(js);
836 if (nargs < 2) return js_mkerr(js, "once() requires event name and callback");
837 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string");
838 if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function");
839
840 ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA);
841 if (vtype(ctx_ptr) == T_UNDEF) return js_mkerr(js, "Invalid stream object");
842
843 child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr);
844 if (!ctx || !ctx->cp) return js_mkerr(js, "Invalid stream context");
845
846 child_process_t *cp = ctx->cp;
847 child_stream_kind_t kind = ctx->kind;
848
849 size_t name_len = 0;
850 char *name = js_getstr(js, args[0], &name_len);
851 if (!name) return js_mkerr(js, "Event name must be a string");
852
853 char full_name[64];
854 snprintf(
855 full_name, sizeof(full_name),
856 "%s:%.*s", stream_kind_name(kind),
857 (int)name_len, name
858 );
859
860 child_event_t *evt = find_or_create_event(cp, full_name);
861 if (evt->count >= MAX_CHILD_LISTENERS) {
862 return js_mkerr(js, "Maximum listeners reached for event");
863 }
864
865 evt->listeners[evt->count].callback = args[1];
866 evt->listeners[evt->count].once = true;
867 evt->count++;
868 return this_obj;
869}
870
871static ant_value_t create_child_stream_object(ant_t *js, child_process_t *cp, child_stream_kind_t kind) {
872 ant_value_t obj = js_mkobj(js);
873 child_stream_ctx_t *ctx = calloc(1, sizeof(child_stream_ctx_t));
874 if (!ctx) return js_mkerr(js, "Out of memory");
875
876 ctx->cp = cp;
877 ctx->kind = kind;
878
879 if (kind == CHILD_STREAM_STDIN) cp->stdin_ctx = ctx;
880 else if (kind == CHILD_STREAM_STDOUT) cp->stdout_ctx = ctx;
881 else cp->stderr_ctx = ctx;
882
883 js_set_slot(obj, SLOT_DATA, ANT_PTR(ctx));
884 js_set(js, obj, "on", js_mkfun(child_stream_on));
885 js_set(js, obj, "once", js_mkfun(child_stream_once));
886 js_set(js, obj, "ref", js_mkfun(child_stream_ref));
887 js_set(js, obj, "unref", js_mkfun(child_stream_unref));
888 js_set(js, obj, "destroy", js_mkfun(child_stream_destroy));
889 js_set(js, obj, "length", js_mknum(0));
890
891 if (kind == CHILD_STREAM_STDIN) {
892 js_set(js, obj, "write", js_mkfun(child_stream_write));
893 js_set(js, obj, "end", js_mkfun(child_stream_end));
894 }
895
896 return obj;
897}
898
899static ant_value_t create_child_object(ant_t *js, child_process_t *cp) {
900 ant_value_t obj = js_mkobj(js);
901 if (is_object_type(g_child_process_proto)) js_set_proto_init(obj, g_child_process_proto);
902
903 js_set_slot(obj, SLOT_DATA, ANT_PTR(cp));
904 js_set(js, obj, "pid", js_mknum((double)cp->process.pid));
905 js_set(js, obj, "exitCode", js_mknull());
906 js_set(js, obj, "signalCode", js_mknull());
907 js_set(js, obj, "killed", js_false);
908 js_set(js, obj, "connected", js_true);
909
910 static const struct { child_stream_kind_t kind; const char *name; } streams[] = {
911 { CHILD_STREAM_STDIN, "stdin" },
912 { CHILD_STREAM_STDOUT, "stdout" },
913 { CHILD_STREAM_STDERR, "stderr" },
914 };
915
916 ant_value_t *stream_objs[] = { &cp->stdin_obj, &cp->stdout_obj, &cp->stderr_obj };
917 for (int i = 0; i < 3; i++) {
918 if (child_stdio_is_pipe(cp, streams[i].kind)) {
919 *stream_objs[i] = create_child_stream_object(js, cp, streams[i].kind);
920 } else *stream_objs[i] = js_mknull();
921 js_set(js, obj, streams[i].name, *stream_objs[i]);
922 }
923
924 js_set(js, obj, "on", js_mkfun(child_on));
925 js_set(js, obj, "once", js_mkfun(child_once));
926 js_set(js, obj, "ref", js_mkfun(child_ref));
927 js_set(js, obj, "unref", js_mkfun(child_unref));
928 js_set(js, obj, "kill", js_mkfun(child_kill));
929 js_set(js, obj, "write", js_mkfun(child_write));
930 js_set(js, obj, "end", js_mkfun(child_end));
931
932 js_set_sym(js, obj, get_toStringTag_sym(), js_mkstr(js, "ChildProcess", 12));
933
934 return obj;
935}
936
937static char **parse_args_array(ant_t *js, ant_value_t arr, int *count) {
938 ant_value_t len_val = js_get(js, arr, "length");
939 int len = (int)js_getnum(len_val);
940
941 char **args = calloc(len + 1, sizeof(char *));
942 if (!args) {
943 *count = 0;
944 return NULL;
945 }
946
947 for (int i = 0; i < len; i++) {
948 char idx[16];
949 snprintf(idx, sizeof(idx), "%d", i);
950 ant_value_t val = js_get(js, arr, idx);
951 if (vtype(val) == T_STR) {
952 size_t arg_len;
953 char *arg = js_getstr(js, val, &arg_len);
954 args[i] = strndup(arg, arg_len);
955 } else args[i] = strdup("");
956 }
957
958 args[len] = NULL;
959 *count = len;
960 return args;
961}
962
963static void free_args_array(char **args, int count) {
964 if (!args) return;
965 for (int i = 0; i < count; i++) {
966 if (args[i]) free(args[i]);
967 }
968 free(args);
969}
970
971static stdio_mode_t parse_stdio_mode(ant_t *js, ant_value_t val) {
972 if (vtype(val) != T_STR) return STDIO_PIPE;
973 char *s = js_getstr(js, val, NULL);
974 if (strcmp(s, "inherit") == 0) return STDIO_INHERIT;
975 if (strcmp(s, "ignore") == 0) return STDIO_IGNORE;
976 return STDIO_PIPE;
977}
978
979static void parse_stdio_option(ant_t *js, ant_value_t stdio_val, stdio_mode_t *modes) {
980if (vtype(stdio_val) == T_STR) {
981 stdio_mode_t mode = parse_stdio_mode(js, stdio_val);
982 for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) modes[i] = mode;
983} else if (is_special_object(stdio_val)) {
984 ant_offset_t len = js_arr_len(js, stdio_val);
985 if (len > 3) len = 3;
986 for (ant_offset_t i = 0; i < len; i++)
987 modes[i] = parse_stdio_mode(js, js_arr_get(js, stdio_val, i));
988}}
989
990static ant_value_t builtin_spawn(ant_t *js, ant_value_t *args, int nargs) {
991 if (nargs < 1) return js_mkerr(js, "spawn() requires a command");
992 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string");
993
994 size_t cmd_len;
995 char *cmd = js_getstr(js, args[0], &cmd_len);
996 char *cmd_str = strndup(cmd, cmd_len);
997
998 char **spawn_args = NULL;
999 int spawn_argc = 0;
1000 char *cwd = NULL;
1001 bool use_shell = false;
1002 bool detached = false;
1003
1004 if (nargs >= 2 && is_special_object(args[1])) {
1005 ant_value_t len_val = js_get(js, args[1], "length");
1006 if (vtype(len_val) == T_NUM) {
1007 spawn_args = parse_args_array(js, args[1], &spawn_argc);
1008 }
1009 }
1010
1011 stdio_mode_t stdio_modes[3] = {
1012 STDIO_PIPE, STDIO_PIPE, STDIO_PIPE
1013 };
1014
1015 if (nargs >= 3 && is_special_object(args[2])) {
1016 ant_value_t cwd_val = js_get(js, args[2], "cwd");
1017 if (vtype(cwd_val) == T_STR) {
1018 size_t cwd_len;
1019 char *cwd_str = js_getstr(js, cwd_val, &cwd_len);
1020 cwd = strndup(cwd_str, cwd_len);
1021 }
1022
1023 ant_value_t shell_val = js_get(js, args[2], "shell");
1024 use_shell = js_truthy(js, shell_val);
1025
1026 ant_value_t detached_val = js_get(js, args[2], "detached");
1027 detached = js_truthy(js, detached_val);
1028
1029 ant_value_t stdio_val = js_get(js, args[2], "stdio");
1030 parse_stdio_option(js, stdio_val, stdio_modes);
1031 }
1032
1033 child_process_t *cp = calloc(1, sizeof(child_process_t));
1034 if (!cp) {
1035 free(cmd_str);
1036 free_args_array(spawn_args, spawn_argc);
1037 if (cwd) free(cwd);
1038 return js_mkerr(js, "Out of memory");
1039 }
1040
1041 cp->js = js;
1042 cp->use_shell = use_shell;
1043 cp->detached = detached;
1044 cp->cwd = cwd;
1045 cp->promise = js_mkundef();
1046 cp->keep_alive = true;
1047 memcpy(cp->stdio_modes, stdio_modes, sizeof(stdio_modes));
1048 cp->process.data = cp;
1049
1050 for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) {
1051 if (stdio_modes[i] == STDIO_PIPE) {
1052 uv_pipe_t *p = child_pipe(cp, i);
1053 uv_pipe_init(uv_default_loop(), p, 0);
1054 p->data = cp;
1055 }}
1056
1057 uv_stdio_container_t stdio[3];
1058 for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) {
1059 if (stdio_modes[i] == STDIO_INHERIT) {
1060 stdio[i].flags = UV_INHERIT_FD;
1061 stdio[i].data.fd = i;
1062 } else if (stdio_modes[i] == STDIO_IGNORE) stdio[i].flags = UV_IGNORE; else {
1063 stdio[i].flags = UV_CREATE_PIPE | (i == CHILD_STREAM_STDIN ? UV_READABLE_PIPE : UV_WRITABLE_PIPE);
1064 stdio[i].data.stream = (uv_stream_t *)child_pipe(cp, i);
1065 }}
1066
1067 char **final_args;
1068 int final_argc;
1069 char *shell_cmd = NULL;
1070
1071 if (use_shell) {
1072 final_args = calloc(4, sizeof(char *));
1073 final_args[0] = strdup("/bin/sh");
1074 final_args[1] = strdup("-c");
1075
1076 size_t total_len = cmd_len + 1;
1077 for (int i = 0; i < spawn_argc; i++) {
1078 total_len += strlen(spawn_args[i]) + 3;
1079 }
1080 shell_cmd = malloc(total_len);
1081 strcpy(shell_cmd, cmd_str);
1082 for (int i = 0; i < spawn_argc; i++) {
1083 strcat(shell_cmd, " ");
1084 strcat(shell_cmd, spawn_args[i]);
1085 }
1086 final_args[2] = shell_cmd;
1087 final_args[3] = NULL;
1088 final_argc = 3;
1089
1090 free(cmd_str);
1091 cmd_str = strdup("/bin/sh");
1092 } else {
1093 final_argc = spawn_argc + 1;
1094 final_args = calloc(final_argc + 1, sizeof(char *));
1095 final_args[0] = cmd_str;
1096 for (int i = 0; i < spawn_argc; i++) {
1097 final_args[i + 1] = spawn_args ? spawn_args[i] : NULL;
1098 }
1099 final_args[final_argc] = NULL;
1100 if (spawn_args) free(spawn_args);
1101 spawn_args = NULL;
1102 }
1103
1104 uv_process_options_t options = {0};
1105 options.exit_cb = on_process_exit;
1106 options.file = final_args[0];
1107 options.args = final_args;
1108 options.stdio_count = 3;
1109 options.stdio = stdio;
1110
1111 if (cwd) options.cwd = cwd;
1112 if (detached) options.flags = UV_PROCESS_DETACHED;
1113 int r = uv_spawn(uv_default_loop(), &cp->process, &options);
1114
1115 if (use_shell) {
1116 free(final_args[0]);
1117 free(final_args[1]);
1118 free(shell_cmd);
1119 free(final_args);
1120 } else {
1121 for (int i = 0; i < final_argc; i++) {
1122 if (final_args[i]) free(final_args[i]);
1123 } free(final_args);
1124 }
1125
1126 free_args_array(spawn_args, spawn_argc);
1127
1128 if (r < 0) {
1129 free_child_process(cp);
1130 return js_mkerr(js, "Failed to spawn process: %s", uv_strerror(r));
1131 }
1132
1133 static const uv_read_cb read_cbs[] = { NULL, on_stdout_read, on_stderr_read };
1134 bool *closed[] = { &cp->stdin_closed, &cp->stdout_closed, &cp->stderr_closed };
1135
1136 for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) {
1137 if (stdio_modes[i] == STDIO_PIPE && read_cbs[i]) {
1138 uv_read_start((uv_stream_t *)child_pipe(cp, i), alloc_buffer, read_cbs[i]);
1139 } else if (stdio_modes[i] != STDIO_PIPE) *closed[i] = true;
1140 }
1141
1142 add_pending_child(cp);
1143 cp->child_obj = create_child_object(js, cp);
1144
1145 return cp->child_obj;
1146}
1147
1148static ant_value_t builtin_exec(ant_t *js, ant_value_t *args, int nargs) {
1149 if (nargs < 1) return js_mkerr(js, "exec() requires a command");
1150 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string");
1151
1152 size_t cmd_len;
1153 char *cmd = js_getstr(js, args[0], &cmd_len);
1154 char *cmd_str = strndup(cmd, cmd_len);
1155
1156 char *cwd = NULL;
1157 if (nargs >= 2 && is_special_object(args[1])) {
1158 ant_value_t cwd_val = js_get(js, args[1], "cwd");
1159 if (vtype(cwd_val) == T_STR) {
1160 size_t cwd_len;
1161 char *cwd_s = js_getstr(js, cwd_val, &cwd_len);
1162 cwd = strndup(cwd_s, cwd_len);
1163 }
1164 }
1165
1166 child_process_t *cp = calloc(1, sizeof(child_process_t));
1167 if (!cp) {
1168 free(cmd_str);
1169 if (cwd) free(cwd);
1170 return js_mkerr(js, "Out of memory");
1171 }
1172
1173 cp->js = js;
1174 cp->use_shell = true;
1175 cp->cwd = cwd;
1176 cp->promise = js_mkpromise(js);
1177 cp->keep_alive = true;
1178
1179 cp->stdio_modes[CHILD_STREAM_STDIN] = STDIO_IGNORE;
1180 cp->stdio_modes[CHILD_STREAM_STDOUT] = STDIO_PIPE;
1181 cp->stdio_modes[CHILD_STREAM_STDERR] = STDIO_PIPE;
1182
1183 uv_pipe_init(uv_default_loop(), &cp->stdout_pipe, 0);
1184 uv_pipe_init(uv_default_loop(), &cp->stderr_pipe, 0);
1185
1186 cp->stdout_pipe.data = cp;
1187 cp->stderr_pipe.data = cp;
1188 cp->process.data = cp;
1189 cp->stdin_closed = true;
1190
1191 uv_stdio_container_t stdio[3];
1192 stdio[0].flags = UV_IGNORE;
1193 stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
1194 stdio[1].data.stream = (uv_stream_t *)&cp->stdout_pipe;
1195 stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
1196 stdio[2].data.stream = (uv_stream_t *)&cp->stderr_pipe;
1197
1198 char *shell_args[4];
1199 shell_args[0] = "/bin/sh";
1200 shell_args[1] = "-c";
1201 shell_args[2] = cmd_str;
1202 shell_args[3] = NULL;
1203
1204 uv_process_options_t options = {0};
1205 options.exit_cb = on_process_exit;
1206 options.file = "/bin/sh";
1207 options.args = shell_args;
1208 options.stdio_count = 3;
1209 options.stdio = stdio;
1210
1211 if (cwd) options.cwd = cwd;
1212 int r = uv_spawn(uv_default_loop(), &cp->process, &options);
1213 free(cmd_str);
1214
1215 if (r < 0) {
1216 ant_value_t promise = cp->promise;
1217 free_child_process(cp);
1218 js_reject_promise(js, promise, js_mkstr(js, uv_strerror(r), strlen(uv_strerror(r))));
1219 return promise;
1220 }
1221
1222 uv_read_start((uv_stream_t *)&cp->stdout_pipe, alloc_buffer, on_stdout_read);
1223 uv_read_start((uv_stream_t *)&cp->stderr_pipe, alloc_buffer, on_stderr_read);
1224
1225 add_pending_child(cp);
1226
1227 cp->child_obj = create_child_object(js, cp);
1228 return cp->promise;
1229}
1230
1231static ant_value_t exec_file_close_callback(ant_t *js, ant_value_t *args, int nargs) {
1232 ant_value_t fn = js_getcurrentfunc(js);
1233 ant_value_t ctx = js_get_slot(fn, SLOT_DATA);
1234
1235 ant_value_t callback = js_get(js, ctx, "callback");
1236 ant_value_t child = js_get(js, ctx, "child");
1237
1238 ant_value_t stdout_val = js_get(js, child, "stdoutText");
1239 ant_value_t stderr_val = js_get(js, child, "stderrText");
1240 ant_value_t exit_code_val = js_get(js, child, "exitCode");
1241 ant_value_t cb_args[3];
1242
1243 if (!is_callable(callback)) return js_mkundef();
1244
1245 if (vtype(exit_code_val) == T_NUM && (int)js_getnum(exit_code_val) != 0) {
1246 char err_msg[256];
1247 snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %d", (int)js_getnum(exit_code_val));
1248 cb_args[0] = js_make_error_silent(js, JS_ERR_GENERIC, err_msg);
1249 } else cb_args[0] = js_mknull();
1250
1251 cb_args[1] = stdout_val;
1252 cb_args[2] = stderr_val;
1253
1254 ant_value_t result = sv_vm_call(js->vm, js, callback, js_mkundef(), cb_args, 3, NULL, false);
1255 if (vtype(result) == T_ERR) log_listener_error(js, "execFile", result);
1256 return js_mkundef();
1257}
1258
1259static ant_value_t exec_file_promisify_callback(ant_t *js, ant_value_t *args, int nargs) {
1260 ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA);
1261 if (!is_object_type(state)) return js_mkundef();
1262
1263 ant_value_t settled = js_get_slot(state, SLOT_SETTLED);
1264 if (vtype(settled) == T_BOOL && settled == js_true) return js_mkundef();
1265 js_set_slot(state, SLOT_SETTLED, js_true);
1266
1267 ant_value_t promise = js_get_slot(state, SLOT_DATA);
1268 if (vtype(promise) != T_PROMISE) return js_mkundef();
1269
1270 ant_value_t stdout_val = nargs > 1 ? args[1] : js_mkstr(js, "", 0);
1271 ant_value_t stderr_val = nargs > 2 ? args[2] : js_mkstr(js, "", 0);
1272
1273 if (nargs > 0 && !is_null(args[0]) && !is_undefined(args[0])) {
1274 if (is_object_type(args[0])) {
1275 js_set(js, args[0], "stdout", stdout_val);
1276 js_set(js, args[0], "stderr", stderr_val);
1277 }
1278 js_reject_promise(js, promise, args[0]);
1279 return js_mkundef();
1280 }
1281
1282 ant_value_t result = js_mkobj(js);
1283 js_set(js, result, "stdout", stdout_val);
1284 js_set(js, result, "stderr", stderr_val);
1285 js_resolve_promise(js, promise, result);
1286
1287 return js_mkundef();
1288}
1289
1290static ant_value_t exec_file_promisified_call(ant_t *js, ant_value_t *args, int nargs) {
1291 ant_value_t original = js_get_slot(js_getcurrentfunc(js), SLOT_DATA);
1292 if (!is_callable(original)) return js_mkerr(js, "execFile promisify target is not callable");
1293
1294 ant_value_t promise = js_mkpromise(js);
1295 ant_value_t state = js_mkobj(js);
1296
1297 js_set_slot(state, SLOT_DATA, promise);
1298 js_set_slot(state, SLOT_SETTLED, js_false);
1299
1300 ant_value_t callback = js_heavy_mkfun(js, exec_file_promisify_callback, state);
1301 ant_value_t *call_args = malloc((size_t)(nargs + 1) * sizeof(ant_value_t));
1302
1303 if (!call_args) {
1304 js_reject_promise(js, promise, js_mkerr(js, "Out of memory"));
1305 return promise;
1306 }
1307
1308 for (int i = 0; i < nargs; i++) call_args[i] = args[i];
1309 call_args[nargs] = callback;
1310
1311 ant_value_t call_result = sv_vm_call(
1312 js->vm, js, original, js_getthis(js),
1313 call_args, nargs + 1, NULL, false
1314 ); free(call_args);
1315
1316 ant_value_t settled = js_get_slot(state, SLOT_SETTLED);
1317 bool is_settled = (vtype(settled) == T_BOOL && settled == js_true);
1318
1319 if (!is_settled && (is_err(call_result) || js->thrown_exists)) {
1320 ant_value_t ex = js->thrown_exists ? js->thrown_value : call_result;
1321 js->thrown_exists = false;
1322 js->thrown_value = js_mkundef();
1323 js->thrown_stack = js_mkundef();
1324 js_set_slot(state, SLOT_SETTLED, js_true);
1325 js_reject_promise(js, promise, ex);
1326 }
1327
1328 return promise;
1329}
1330
1331static ant_value_t exec_promisified_call(ant_t *js, ant_value_t *args, int nargs) {
1332 ant_value_t original = js_get_slot(js_getcurrentfunc(js), SLOT_DATA);
1333 if (!is_callable(original)) return js_mkerr(js, "exec promisify target is not callable");
1334
1335 ant_value_t call_result = sv_vm_call(
1336 js->vm, js, original, js_getthis(js),
1337 args, nargs, NULL, false
1338 );
1339
1340 if (vtype(call_result) == T_PROMISE) return call_result;
1341 if (is_err(call_result) || js->thrown_exists) {
1342 ant_value_t promise = js_mkpromise(js);
1343 ant_value_t ex = js->thrown_exists ? js->thrown_value : call_result;
1344 js->thrown_exists = false;
1345 js->thrown_value = js_mkundef();
1346 js->thrown_stack = js_mkundef();
1347 js_reject_promise(js, promise, ex);
1348 return promise;
1349 }
1350
1351 ant_value_t promise = js_mkpromise(js);
1352 js_resolve_promise(js, promise, call_result);
1353 return promise;
1354}
1355
1356static ant_value_t builtin_execFile(ant_t *js, ant_value_t *args, int nargs) {
1357 ant_value_t argv = js_mkundef();
1358 ant_value_t options = js_mkundef();
1359 ant_value_t callback = js_mkundef();
1360
1361 ant_value_t spawn_args[3];
1362 ant_value_t child;
1363
1364 if (nargs < 1) return js_mkerr(js, "execFile() requires a file");
1365 if (vtype(args[0]) != T_STR) return js_mkerr(js, "File must be a string");
1366
1367 if (nargs >= 2 && is_callable(args[nargs - 1])) {
1368 callback = args[nargs - 1];
1369 nargs--;
1370 }
1371
1372 if (nargs >= 2) {
1373 if (vtype(args[1]) == T_ARR) {
1374 argv = args[1];
1375 if (nargs >= 3 && is_special_object(args[2])) options = args[2];
1376 } else if (is_special_object(args[1])) options = args[1];
1377 }
1378
1379 spawn_args[0] = args[0];
1380 spawn_args[1] = argv;
1381 spawn_args[2] = options;
1382
1383 child = builtin_spawn(js, spawn_args, 3);
1384 if (vtype(child) != T_OBJ || !is_callable(callback)) return child;
1385
1386 ant_value_t ctx = js_mkobj(js);
1387 js_set(js, ctx, "callback", callback);
1388 js_set(js, ctx, "child", child);
1389
1390 // TODO: reduce duck-typing
1391 ant_value_t close_listener = js_heavy_mkfun(js, exec_file_close_callback, ctx);
1392 ant_value_t once_fn = js_get(js, child, "once");
1393 ant_value_t once_args[2] = { js_mkstr(js, "close", 5), close_listener };
1394
1395 ant_value_t once_result = sv_vm_call(js->vm, js, once_fn, child, once_args, 2, NULL, false);
1396 if (vtype(once_result) == T_ERR) return once_result;
1397
1398 return child;
1399}
1400
1401static ant_value_t builtin_execSync(ant_t *js, ant_value_t *args, int nargs) {
1402 if (nargs < 1) return js_mkerr(js, "execSync() requires a command");
1403 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string");
1404
1405 size_t cmd_len;
1406 char *cmd = js_getstr(js, args[0], &cmd_len);
1407 char *cmd_str = strndup(cmd, cmd_len);
1408
1409 FILE *fp = popen(cmd_str, "r");
1410 free(cmd_str);
1411
1412 if (!fp) {
1413 return js_mkerr(js, "Failed to execute command");
1414 }
1415
1416 char *output = NULL;
1417 size_t output_len = 0;
1418 size_t output_cap = 4096;
1419 output = malloc(output_cap);
1420
1421 if (!output) {
1422 pclose(fp);
1423 return js_mkerr(js, "Out of memory");
1424 }
1425
1426 char buffer[4096];
1427 while (fgets(buffer, sizeof(buffer), fp) != NULL) {
1428 size_t len = strlen(buffer);
1429 if (output_len + len >= output_cap) {
1430 output_cap *= 2;
1431 char *new_output = realloc(output, output_cap);
1432 if (!new_output) {
1433 free(output);
1434 pclose(fp);
1435 return js_mkerr(js, "Out of memory");
1436 }
1437 output = new_output;
1438 }
1439 memcpy(output + output_len, buffer, len);
1440 output_len += len;
1441 }
1442
1443 int status = pclose(fp);
1444#ifdef _WIN32
1445 int exit_code = status;
1446#else
1447 int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
1448#endif
1449
1450 if (output_len > 0 && output[output_len - 1] == '\n') {
1451 output_len--;
1452 }
1453
1454 if (exit_code != 0) {
1455 char err_msg[256];
1456 snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %d", exit_code);
1457 free(output); return js_mkerr(js, "%s", err_msg);
1458 }
1459
1460 ant_value_t result = js_mkstr(js, output, output_len);
1461 free(output);
1462 return result;
1463}
1464
1465#ifdef _WIN32
1466static ant_value_t builtin_spawnSync(ant_t *js, ant_value_t *args, int nargs) {
1467 if (nargs < 1) return js_mkerr(js, "spawnSync() requires a command");
1468 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string");
1469
1470 size_t cmd_len;
1471 char *cmd = js_getstr(js, args[0], &cmd_len);
1472 char *cmd_str = strndup(cmd, cmd_len);
1473
1474 char **spawn_args = NULL;
1475 int spawn_argc = 0;
1476 char *input = NULL;
1477 size_t input_len = 0;
1478
1479 if (nargs >= 2 && is_special_object(args[1])) {
1480 ant_value_t len_val = js_get(js, args[1], "length");
1481 if (vtype(len_val) == T_NUM) {
1482 spawn_args = parse_args_array(js, args[1], &spawn_argc);
1483 }
1484 }
1485
1486 if (nargs >= 3 && is_special_object(args[2])) {
1487 ant_value_t input_val = js_get(js, args[2], "input");
1488 if (vtype(input_val) == T_STR) {
1489 input = js_getstr(js, input_val, &input_len);
1490 }
1491 }
1492
1493 size_t cmdline_len = cmd_len + 3;
1494 for (int i = 0; i < spawn_argc; i++) {
1495 cmdline_len += strlen(spawn_args[i]) + 3;
1496 }
1497
1498 char *cmdline = malloc(cmdline_len);
1499 if (!cmdline) {
1500 free(cmd_str);
1501 free_args_array(spawn_args, spawn_argc);
1502 return js_mkerr(js, "Out of memory");
1503 }
1504
1505 char *p = cmdline;
1506 p += sprintf(p, "%s", cmd_str);
1507 for (int i = 0; i < spawn_argc; i++) {
1508 p += sprintf(p, " \"%s\"", spawn_args[i]);
1509 }
1510
1511 SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
1512 HANDLE stdin_read = NULL, stdin_write = NULL;
1513 HANDLE stdout_read = NULL, stdout_write = NULL;
1514 HANDLE stderr_read = NULL, stderr_write = NULL;
1515
1516 if (!CreatePipe(&stdin_read, &stdin_write, &sa, 0) ||
1517 !CreatePipe(&stdout_read, &stdout_write, &sa, 0) ||
1518 !CreatePipe(&stderr_read, &stderr_write, &sa, 0)) {
1519 free(cmdline);
1520 free(cmd_str);
1521 free_args_array(spawn_args, spawn_argc);
1522 return js_mkerr(js, "Failed to create pipes");
1523 }
1524
1525 SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0);
1526 SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0);
1527 SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0);
1528
1529 STARTUPINFOA si = {0};
1530 si.cb = sizeof(si);
1531 si.dwFlags = STARTF_USESTDHANDLES;
1532 si.hStdInput = stdin_read;
1533 si.hStdOutput = stdout_write;
1534 si.hStdError = stderr_write;
1535
1536 PROCESS_INFORMATION pi = {0};
1537
1538 BOOL success = CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
1539
1540 free(cmdline);
1541 free(cmd_str);
1542 free_args_array(spawn_args, spawn_argc);
1543
1544 CloseHandle(stdin_read);
1545 CloseHandle(stdout_write);
1546 CloseHandle(stderr_write);
1547
1548 if (!success) {
1549 CloseHandle(stdin_write);
1550 CloseHandle(stdout_read);
1551 CloseHandle(stderr_read);
1552 return js_mkerr(js, "Failed to create process");
1553 }
1554
1555 if (input && input_len > 0) {
1556 DWORD written;
1557 WriteFile(stdin_write, input, (DWORD)input_len, &written, NULL);
1558 }
1559 CloseHandle(stdin_write);
1560
1561 char *stdout_buf = malloc(4096);
1562 size_t stdout_len = 0, stdout_cap = 4096;
1563 char *stderr_buf = malloc(4096);
1564 size_t stderr_len = 0, stderr_cap = 4096;
1565
1566 char buffer[4096];
1567 DWORD n;
1568
1569 while (ReadFile(stdout_read, buffer, sizeof(buffer), &n, NULL) && n > 0) {
1570 if (stdout_len + n >= stdout_cap) {
1571 stdout_cap *= 2;
1572 stdout_buf = realloc(stdout_buf, stdout_cap);
1573 }
1574 memcpy(stdout_buf + stdout_len, buffer, n);
1575 stdout_len += n;
1576 }
1577 CloseHandle(stdout_read);
1578
1579 while (ReadFile(stderr_read, buffer, sizeof(buffer), &n, NULL) && n > 0) {
1580 if (stderr_len + n >= stderr_cap) {
1581 stderr_cap *= 2;
1582 stderr_buf = realloc(stderr_buf, stderr_cap);
1583 }
1584 memcpy(stderr_buf + stderr_len, buffer, n);
1585 stderr_len += n;
1586 }
1587 CloseHandle(stderr_read);
1588
1589 WaitForSingleObject(pi.hProcess, INFINITE);
1590
1591 DWORD exit_code = 0;
1592 GetExitCodeProcess(pi.hProcess, &exit_code);
1593 DWORD pid = pi.dwProcessId;
1594
1595 CloseHandle(pi.hProcess);
1596 CloseHandle(pi.hThread);
1597
1598 ant_value_t result = js_mkobj(js);
1599 js_set(js, result, "stdout", js_mkstr(js, stdout_buf ? stdout_buf : "", stdout_len));
1600 js_set(js, result, "stderr", js_mkstr(js, stderr_buf ? stderr_buf : "", stderr_len));
1601 js_set(js, result, "status", js_mknum((double)exit_code));
1602 js_set(js, result, "signal", js_mknull());
1603 js_set(js, result, "pid", js_mknum((double)pid));
1604
1605 if (stdout_buf) free(stdout_buf);
1606 if (stderr_buf) free(stderr_buf);
1607
1608 return result;
1609}
1610#else
1611static ant_value_t builtin_spawnSync(ant_t *js, ant_value_t *args, int nargs) {
1612 if (nargs < 1) return js_mkerr(js, "spawnSync() requires a command");
1613 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string");
1614
1615 size_t cmd_len;
1616 char *cmd = js_getstr(js, args[0], &cmd_len);
1617 char *cmd_str = strndup(cmd, cmd_len);
1618
1619 char **spawn_args = NULL;
1620 int spawn_argc = 0;
1621 char *input = NULL;
1622 size_t input_len = 0;
1623
1624 if (nargs >= 2 && is_special_object(args[1])) {
1625 ant_value_t len_val = js_get(js, args[1], "length");
1626 if (vtype(len_val) == T_NUM) {
1627 spawn_args = parse_args_array(js, args[1], &spawn_argc);
1628 }
1629 }
1630
1631 if (nargs >= 3 && is_special_object(args[2])) {
1632 ant_value_t input_val = js_get(js, args[2], "input");
1633 if (vtype(input_val) == T_STR) {
1634 input = js_getstr(js, input_val, &input_len);
1635 }
1636 }
1637
1638 char **exec_args = calloc(spawn_argc + 2, sizeof(char *));
1639 if (!exec_args) {
1640 free(cmd_str);
1641 free_args_array(spawn_args, spawn_argc);
1642 return js_mkerr(js, "Out of memory");
1643 }
1644
1645 exec_args[0] = cmd_str;
1646 for (int i = 0; i < spawn_argc; i++) {
1647 exec_args[i + 1] = spawn_args[i];
1648 }
1649 exec_args[spawn_argc + 1] = NULL;
1650
1651 int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
1652 if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0 || pipe(stderr_pipe) < 0) {
1653 free(exec_args);
1654 free(cmd_str);
1655 free_args_array(spawn_args, spawn_argc);
1656 return js_mkerr(js, "Failed to create pipes");
1657 }
1658
1659 pid_t pid = fork();
1660
1661 if (pid < 0) {
1662 free(exec_args);
1663 free(cmd_str);
1664 free_args_array(spawn_args, spawn_argc);
1665 return js_mkerr(js, "Fork failed");
1666 }
1667
1668 if (pid == 0) {
1669 close(stdin_pipe[1]);
1670 close(stdout_pipe[0]);
1671 close(stderr_pipe[0]);
1672
1673 dup2(stdin_pipe[0], STDIN_FILENO);
1674 dup2(stdout_pipe[1], STDOUT_FILENO);
1675 dup2(stderr_pipe[1], STDERR_FILENO);
1676
1677 close(stdin_pipe[0]);
1678 close(stdout_pipe[1]);
1679 close(stderr_pipe[1]);
1680
1681 execvp(exec_args[0], exec_args);
1682 _exit(127);
1683 }
1684
1685 free(exec_args);
1686 free(cmd_str);
1687 free_args_array(spawn_args, spawn_argc);
1688
1689 close(stdin_pipe[0]);
1690 close(stdout_pipe[1]);
1691 close(stderr_pipe[1]);
1692
1693 if (input && input_len > 0) {
1694 write(stdin_pipe[1], input, input_len);
1695 }
1696 close(stdin_pipe[1]);
1697
1698 char *stdout_buf = NULL;
1699 size_t stdout_len = 0;
1700 size_t stdout_cap = 4096;
1701 stdout_buf = malloc(stdout_cap);
1702
1703 char *stderr_buf = NULL;
1704 size_t stderr_len = 0;
1705 size_t stderr_cap = 4096;
1706 stderr_buf = malloc(stderr_cap);
1707
1708 char buffer[4096];
1709 ssize_t n;
1710 int status = 0;
1711
1712 while ((n = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) {
1713 if (stdout_len + n >= stdout_cap) {
1714 stdout_cap *= 2;
1715 stdout_buf = realloc(stdout_buf, stdout_cap);
1716 }
1717 memcpy(stdout_buf + stdout_len, buffer, n);
1718 stdout_len += n;
1719 }
1720 close(stdout_pipe[0]);
1721
1722 while ((n = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) {
1723 if (stderr_len + n >= stderr_cap) {
1724 stderr_cap *= 2;
1725 stderr_buf = realloc(stderr_buf, stderr_cap);
1726 }
1727 memcpy(stderr_buf + stderr_len, buffer, n);
1728 stderr_len += n;
1729 }
1730 close(stderr_pipe[0]);
1731
1732 waitpid(pid, &status, 0);
1733
1734 int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
1735 int signal_code = WIFSIGNALED(status) ? WTERMSIG(status) : 0;
1736
1737 ant_value_t result = js_mkobj(js);
1738 js_set(js, result, "stdout", js_mkstr(js, stdout_buf ? stdout_buf : "", stdout_len));
1739 js_set(js, result, "stderr", js_mkstr(js, stderr_buf ? stderr_buf : "", stderr_len));
1740 js_set(js, result, "status", js_mknum((double)exit_code));
1741 js_set(js, result, "signal", signal_code ? js_mknum((double)signal_code) : js_mknull());
1742 js_set(js, result, "pid", js_mknum((double)pid));
1743
1744 if (stdout_buf) free(stdout_buf);
1745 if (stderr_buf) free(stderr_buf);
1746
1747 return result;
1748}
1749#endif
1750
1751static ant_value_t builtin_execFileSync(ant_t *js, ant_value_t *args, int nargs) {
1752 ant_value_t argv = js_mkundef();
1753 ant_value_t options = js_mkundef();
1754
1755 ant_value_t spawn_args[3];
1756 ant_value_t result;
1757 ant_value_t status;
1758
1759 if (nargs < 1) return js_mkerr(js, "execFileSync() requires a file");
1760 if (vtype(args[0]) != T_STR) return js_mkerr(js, "File must be a string");
1761
1762 if (nargs >= 2) {
1763 if (vtype(args[1]) == T_ARR) {
1764 argv = args[1];
1765 if (nargs >= 3 && is_special_object(args[2])) options = args[2];
1766 } else if (is_special_object(args[1])) options = args[1];
1767 }
1768
1769 spawn_args[0] = args[0];
1770 spawn_args[1] = argv;
1771 spawn_args[2] = options;
1772
1773 result = builtin_spawnSync(js, spawn_args, 3);
1774 if (vtype(result) != T_OBJ) return result;
1775
1776 status = js_get(js, result, "status");
1777 if (vtype(status) == T_NUM && (int)js_getnum(status) != 0)
1778 return js_mkerr(js, "Command failed with exit code %d", (int)js_getnum(status));
1779
1780 return js_get(js, result, "stdout");
1781}
1782
1783static ant_value_t builtin_fork(ant_t *js, ant_value_t *args, int nargs) {
1784 if (nargs < 1) return js_mkerr(js, "fork() requires a module path");
1785 if (vtype(args[0]) != T_STR) return js_mkerr(js, "Module path must be a string");
1786 size_t path_len;
1787
1788 char *path = js_getstr(js, args[0], &path_len);
1789 char *path_str = strndup(path, path_len);
1790 char exe_path[1024];
1791
1792#if defined(__APPLE__)
1793 uint32_t size = sizeof(exe_path);
1794 if (_NSGetExecutablePath(exe_path, &size) != 0) {
1795 free(path_str);
1796 return js_mkerr(js, "Failed to get executable path");
1797 }
1798#elif defined(__linux__)
1799 ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
1800 if (len == -1) {
1801 free(path_str);
1802 return js_mkerr(js, "Failed to get executable path");
1803 }
1804 exe_path[len] = '\0';
1805#else
1806 strncpy(exe_path, "ant", sizeof(exe_path));
1807#endif
1808
1809 ant_value_t spawn_args[3];
1810 spawn_args[0] = js_mkstr(js, exe_path, strlen(exe_path));
1811
1812 ant_value_t args_arr = js_mkarr(js);
1813 js_arr_push(js, args_arr, js_mkstr(js, path_str, path_len));
1814
1815 if (nargs >= 2 && is_special_object(args[1])) {
1816 ant_value_t exec_args = js_get(js, args[1], "execArgv");
1817 if (is_special_object(exec_args)) {
1818 ant_value_t len_val = js_get(js, exec_args, "length");
1819 int arr_len = (int)js_getnum(len_val);
1820 for (int i = 0; i < arr_len; i++) {
1821 char idx[16];
1822 snprintf(idx, sizeof(idx), "%d", i);
1823 ant_value_t arg = js_get(js, exec_args, idx);
1824 js_arr_push(js, args_arr, arg);
1825 }
1826 }
1827 }
1828
1829 spawn_args[1] = args_arr;
1830 spawn_args[2] = js_mkobj(js);
1831
1832 free(path_str);
1833
1834 return builtin_spawn(js, spawn_args, 3);
1835}
1836
1837ant_value_t child_process_library(ant_t *js) {
1838 ant_value_t lib = js_mkobj(js);
1839
1840 ant_value_t exec_fn = js_heavy_mkfun(js, builtin_exec, js_mkundef());
1841 ant_value_t exec_file_fn = js_heavy_mkfun(js, builtin_execFile, js_mkundef());
1842
1843 child_process_init_constructor(js);
1844
1845 js_set_symbol(js, exec_fn,
1846 "nodejs.util.promisify.custom",
1847 js_heavy_mkfun(js, exec_promisified_call, exec_fn)
1848 );
1849
1850 js_set_symbol(js, exec_file_fn,
1851 "nodejs.util.promisify.custom",
1852 js_heavy_mkfun(js, exec_file_promisified_call, exec_file_fn)
1853 );
1854
1855 js_set(js, lib, "ChildProcess", g_child_process_ctor);
1856 js_set(js, lib, "spawn", js_mkfun(builtin_spawn));
1857 js_set(js, lib, "exec", exec_fn);
1858 js_set(js, lib, "execFile", exec_file_fn);
1859 js_set(js, lib, "execSync", js_mkfun(builtin_execSync));
1860 js_set(js, lib, "execFileSync", js_mkfun(builtin_execFileSync));
1861 js_set(js, lib, "spawnSync", js_mkfun(builtin_spawnSync));
1862 js_set(js, lib, "fork", js_mkfun(builtin_fork));
1863 js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "child_process", 13));
1864
1865 return lib;
1866}
1867
1868int has_pending_child_processes(void) {
1869 for (child_process_t *cp = pending_children_head; cp; cp = cp->next)
1870 if (cp->keep_alive) return 1;
1871 return 0;
1872}
1873
1874void gc_mark_child_process(ant_t *js, gc_mark_fn mark) {
1875for (child_process_t *cp = pending_children_head; cp; cp = cp->next) {
1876 mark(js, cp->child_obj);
1877 mark(js, cp->stdin_obj);
1878 mark(js, cp->stdout_obj);
1879 mark(js, cp->stderr_obj);
1880 mark(js, cp->promise);
1881
1882 child_event_t *evt, *tmp;
1883 HASH_ITER(hh, cp->events, evt, tmp)
1884 for (int i = 0; i < evt->count; i++) mark(js, evt->listeners[i].callback);
1885}}