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.

at mir/inline-method 1885 lines 61 kB view raw
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}}