Something went wrong. Try again.
MIRROR: javascript for 🐜's, a tiny runtime with big ambitions ant.themackabu.dev
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886#include <compat.h> // IWYU pragma: keep
#include <uv.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <uthash.h>#include <utarray.h>
#ifdef _WIN32#define WIN32_LEAN_AND_MEAN#include <windows.h>#include <io.h>#include <process.h>#else#include <sys/wait.h>#include <sys/select.h>#include <signal.h>#include <fcntl.h>#if defined(__APPLE__)#include <mach-o/dyld.h>#endif#endif
#include "ant.h"#include "errors.h"#include "internal.h"#include "silver/engine.h"
#include "gc/modules.h"#include "modules/child_process.h"#include "modules/buffer.h"#include "modules/events.h"#include "modules/symbol.h"
#define MAX_CHILD_LISTENERS 16#define PIPE_READ_BUF_SIZE 65536
typedef struct child_process_s child_process_t;
typedef enum { CHILD_STREAM_STDIN = 0, CHILD_STREAM_STDOUT = 1, CHILD_STREAM_STDERR = 2,} child_stream_kind_t;
typedef struct { child_process_t *cp; child_stream_kind_t kind;} child_stream_ctx_t;
typedef struct { ant_value_t callback; bool once;} child_listener_t;
typedef struct { char *event_name; child_listener_t listeners[MAX_CHILD_LISTENERS]; int count; UT_hash_handle hh;} child_event_t;
typedef enum { STDIO_PIPE = 0, STDIO_INHERIT, STDIO_IGNORE,} stdio_mode_t;
struct child_process_s { ant_t *js; uv_process_t process; uv_pipe_t stdin_pipe; uv_pipe_t stdout_pipe; uv_pipe_t stderr_pipe; ant_value_t child_obj; ant_value_t stdin_obj; ant_value_t stdout_obj; ant_value_t stderr_obj; child_stream_ctx_t *stdin_ctx; child_stream_ctx_t *stdout_ctx; child_stream_ctx_t *stderr_ctx; ant_value_t promise; child_event_t *events; char *stdout_buf; size_t stdout_len; size_t stdout_cap; char *stderr_buf; size_t stderr_len; size_t stderr_cap; int64_t exit_code; int term_signal; bool exited; bool stdout_closed; bool stderr_closed; bool stdin_closed; bool use_shell; bool detached; bool close_emitted; bool keep_alive; int pending_closes; char *cwd; stdio_mode_t stdio_modes[3]; struct child_process_s *next; struct child_process_s *prev;};
static child_process_t *pending_children_head = NULL;static child_process_t *pending_children_tail = NULL;
static ant_value_t g_child_process_proto = 0;static ant_value_t g_child_process_ctor = 0;
static void fprint_js_str_raw(FILE *out, ant_t *js, ant_value_t s) { if (vtype(s) != T_STR) { fprintf(out, "%s\n", js_str(js, s)); return; }
ant_offset_t len = 0; ant_offset_t off = vstr(js, s, &len); const char *ptr = (const char *)(uintptr_t)off; if (ptr && len > 0) fwrite(ptr, 1, (size_t)len, out); if (len == 0 || ptr[len - 1] != '\n') fputc('\n', out);}
static void log_listener_error(ant_t *js, const char *event_name, ant_value_t err) { ant_value_t thrown_stack = js->thrown_stack; if (vtype(thrown_stack) == T_STR) { fprintf(stderr, "Error in child_process '%s' listener:\n", event_name); fprint_js_str_raw(stderr, js, thrown_stack); return; }
ant_value_t thrown_value = js->thrown_value; ant_value_t src = (vtype(thrown_value) != T_UNDEF) ? thrown_value : err; ant_value_t stack = js_get(js, src, "stack"); if (vtype(stack) == T_STR) { fprintf(stderr, "Error in child_process '%s' listener:\n", event_name); fprint_js_str_raw(stderr, js, stack); return; }
ant_value_t name = js_get(js, src, "name"); ant_value_t message = js_get(js, src, "message");
const char *detail = NULL; if (vtype(name) == T_STR && vtype(message) == T_STR) { const char *name_s = js_str(js, name); const char *msg_s = js_str(js, message); if (msg_s && msg_s[0]) fprintf(stderr, "Error in child_process '%s' listener: %s: %s\n", event_name, name_s, msg_s); else detail = name_s; } else if (vtype(message) == T_STR) detail = js_str(js, message); else detail = js_str(js, src); if (detail) fprintf(stderr, "Error in child_process '%s' listener: %s\n", event_name, detail); js_print_stack_trace_vm(js, stderr);}
static void emit_event(child_process_t *cp, const char *name, ant_value_t *args, int nargs) { child_event_t *evt = NULL; HASH_FIND_STR(cp->events, name, evt); if (!evt || evt->count == 0) return; int i = 0; while (i < evt->count) { child_listener_t *l = &evt->listeners[i]; ant_value_t result = sv_vm_call(cp->js->vm, cp->js, l->callback, js_mkundef(), args, nargs, NULL, false); if (vtype(result) == T_ERR) log_listener_error(cp->js, name, result); if (l->once) { for (int j = i; j < evt->count - 1; j++) evt->listeners[j] = evt->listeners[j + 1]; evt->count--; } else i++; }}
static const char *stream_kind_name(child_stream_kind_t kind) {switch (kind) { case CHILD_STREAM_STDIN: return "stdin"; case CHILD_STREAM_STDOUT: return "stdout"; case CHILD_STREAM_STDERR: return "stderr"; default: return "unknown";}}
static void emit_stream_event( child_process_t *cp, child_stream_kind_t kind, const char *event, ant_value_t *args, int nargs) { char full_name[64]; snprintf(full_name, sizeof(full_name), "%s:%s", stream_kind_name(kind), event); emit_event(cp, full_name, args, nargs);}
static ant_value_t make_buffer_chunk(ant_t *js, const char *data, size_t len) { ArrayBufferData *ab = create_array_buffer_data(len); if (!ab) return js_mkerr(js, "Out of memory"); if (len > 0 && data) memcpy(ab->data, data, len); return create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer");}
static uv_pipe_t *child_pipe(child_process_t *cp, child_stream_kind_t kind) {switch (kind) { case CHILD_STREAM_STDIN: return &cp->stdin_pipe; case CHILD_STREAM_STDOUT: return &cp->stdout_pipe; case CHILD_STREAM_STDERR: return &cp->stderr_pipe; default: return NULL;}}
static bool child_stdio_is_pipe(child_process_t *cp, child_stream_kind_t kind) { return cp->stdio_modes[kind] == STDIO_PIPE;}
static bool *child_closed_flag(child_process_t *cp, child_stream_kind_t kind) { switch (kind) { case CHILD_STREAM_STDIN: return &cp->stdin_closed; case CHILD_STREAM_STDOUT: return &cp->stdout_closed; case CHILD_STREAM_STDERR: return &cp->stderr_closed; default: return NULL; }}
static void add_pending_child(child_process_t *cp) { cp->next = NULL; cp->prev = pending_children_tail; if (pending_children_tail) { pending_children_tail->next = cp; } else pending_children_head = cp; pending_children_tail = cp;}
static void remove_pending_child(child_process_t *cp) { if (cp->prev) cp->prev->next = cp->next; else pending_children_head = cp->next; if (cp->next) cp->next->prev = cp->prev; else pending_children_tail = cp->prev; cp->next = NULL; cp->prev = NULL;}
static void free_child_process(child_process_t *cp) { if (!cp) return;
if (vtype(cp->child_obj) == T_OBJ) js_set_slot(cp->child_obj, SLOT_DATA, js_mkundef()); if (vtype(cp->stdin_obj) == T_OBJ) js_set_slot(cp->stdin_obj, SLOT_DATA, js_mkundef()); if (vtype(cp->stdout_obj) == T_OBJ) js_set_slot(cp->stdout_obj, SLOT_DATA, js_mkundef()); if (vtype(cp->stderr_obj) == T_OBJ) js_set_slot(cp->stderr_obj, SLOT_DATA, js_mkundef()); if (cp->stdout_buf) free(cp->stdout_buf); if (cp->stderr_buf) free(cp->stderr_buf); if (cp->cwd) free(cp->cwd); if (cp->stdin_ctx) free(cp->stdin_ctx); if (cp->stdout_ctx) free(cp->stdout_ctx); if (cp->stderr_ctx) free(cp->stderr_ctx); child_event_t *evt, *tmp; HASH_ITER(hh, cp->events, evt, tmp) { HASH_DEL(cp->events, evt); free(evt->event_name); free(evt); } free(cp);}
static child_event_t *find_or_create_event(child_process_t *cp, const char *name) { child_event_t *evt = NULL; HASH_FIND_STR(cp->events, name, evt); if (!evt) { evt = calloc(1, sizeof(child_event_t)); evt->event_name = strdup(name); evt->count = 0; HASH_ADD_KEYPTR(hh, cp->events, evt->event_name, strlen(evt->event_name), evt); } return evt;}
static void try_free_child(child_process_t *cp) { if (!cp) return; if (cp->exited && cp->stdout_closed && cp->stderr_closed && cp->pending_closes == 0) { remove_pending_child(cp); free_child_process(cp); }}
static void check_completion(child_process_t *cp) { if (cp->exited && cp->stdout_closed && cp->stderr_closed && !cp->close_emitted) { cp->close_emitted = true; ant_value_t stdout_val = js_mkstr(cp->js, cp->stdout_buf ? cp->stdout_buf : "", cp->stdout_len); ant_value_t stderr_val = js_mkstr(cp->js, cp->stderr_buf ? cp->stderr_buf : "", cp->stderr_len);
if (vtype(cp->stdout_obj) == T_OBJ) { js_set(cp->js, cp->stdout_obj, "text", stdout_val); js_set(cp->js, cp->stdout_obj, "length", js_mknum((double)cp->stdout_len)); } if (vtype(cp->stderr_obj) == T_OBJ) { js_set(cp->js, cp->stderr_obj, "text", stderr_val); js_set(cp->js, cp->stderr_obj, "length", js_mknum((double)cp->stderr_len)); }
js_set(cp->js, cp->child_obj, "stdoutText", stdout_val); js_set(cp->js, cp->child_obj, "stderrText", stderr_val); js_set(cp->js, cp->child_obj, "exitCode", js_mknum((double)cp->exit_code)); js_set(cp->js, cp->child_obj, "signalCode", cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull()); ant_value_t close_args[2] = { js_mknum((double)cp->exit_code), cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull() }; emit_event(cp, "close", close_args, 2); if (vtype(cp->promise) != T_UNDEF) { ant_value_t result = js_mkobj(cp->js); js_set(cp->js, result, "stdout", stdout_val); js_set(cp->js, result, "stderr", stderr_val); js_set(cp->js, result, "exitCode", js_mknum((double)cp->exit_code)); js_set(cp->js, result, "signalCode", cp->term_signal ? js_mknum((double)cp->term_signal) : js_mknull()); if (cp->exit_code != 0) { char err_msg[256]; snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %lld", (long long)cp->exit_code); ant_value_t err = js_mkstr(cp->js, err_msg, strlen(err_msg)); js_reject_promise(cp->js, cp->promise, err); } else js_resolve_promise(cp->js, cp->promise, result); } try_free_child(cp); }}
static void on_handle_close(uv_handle_t *handle) { child_process_t *cp = (child_process_t *)handle->data; if (cp) { cp->pending_closes--; try_free_child(cp); }}
static void close_child_handle(child_process_t *cp, uv_handle_t *handle) { if (!cp || !handle || uv_is_closing(handle)) return; cp->pending_closes++; uv_close(handle, on_handle_close);}
static void close_child_pipe(child_process_t *cp, child_stream_kind_t kind, bool stop_read) { bool *closed = NULL; uv_pipe_t *pipe = NULL;
if (!cp || !child_stdio_is_pipe(cp, kind)) return;
closed = child_closed_flag(cp, kind); pipe = child_pipe(cp, kind); if (!closed || !pipe || *closed || uv_is_closing((uv_handle_t *)pipe)) return;
if (stop_read && kind != CHILD_STREAM_STDIN) { uv_read_stop((uv_stream_t *)pipe); }
*closed = true; close_child_handle(cp, (uv_handle_t *)pipe);}
static void on_process_exit(uv_process_t *proc, int64_t exit_status, int term_signal) { child_process_t *cp = (child_process_t *)proc->data; cp->exit_code = exit_status; cp->term_signal = term_signal; cp->exited = true; js_set(cp->js, cp->child_obj, "exitCode", js_mknum((double)exit_status)); if (term_signal) { js_set(cp->js, cp->child_obj, "signalCode", js_mknum((double)term_signal)); js_set(cp->js, cp->child_obj, "killed", js_true); } ant_value_t exit_args[2] = { js_mknum((double)exit_status), term_signal ? js_mknum((double)term_signal) : js_mknull() }; emit_event(cp, "exit", exit_args, 2); close_child_handle(cp, (uv_handle_t *)proc); close_child_pipe(cp, CHILD_STREAM_STDIN, false); close_child_pipe(cp, CHILD_STREAM_STDOUT, true); close_child_pipe(cp, CHILD_STREAM_STDERR, true); check_completion(cp);}
static void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { (void)handle; buf->base = malloc(suggested_size);#ifdef _WIN32 buf->len = buf->base ? (ULONG)(suggested_size > (size_t)ULONG_MAX ? ULONG_MAX : suggested_size) : 0;#else buf->len = buf->base ? suggested_size : 0;#endif}
static void on_stdout_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { child_process_t *cp = (child_process_t *)stream->data; if (nread > 0) { if (cp->stdout_len + nread > cp->stdout_cap) { size_t new_cap = cp->stdout_cap == 0 ? 4096 : cp->stdout_cap * 2; while (new_cap < cp->stdout_len + nread) new_cap *= 2; char *new_buf = realloc(cp->stdout_buf, new_cap); if (new_buf) { cp->stdout_buf = new_buf; cp->stdout_cap = new_cap; } } if (cp->stdout_buf) { memcpy(cp->stdout_buf + cp->stdout_len, buf->base, nread); cp->stdout_len += nread; } ant_value_t text_data = js_mkstr(cp->js, buf->base, nread); ant_value_t text_args[1] = { text_data }; emit_event(cp, "stdout", text_args, 1);
ant_value_t byte_data = make_buffer_chunk(cp->js, buf->base, (size_t)nread); ant_value_t byte_args[1] = { byte_data }; emit_stream_event(cp, CHILD_STREAM_STDOUT, "data", byte_args, 1); if (vtype(cp->stdout_obj) == T_OBJ) js_set( cp->js, cp->stdout_obj, "length", js_mknum((double)cp->stdout_len) ); } if (buf->base) free(buf->base); if (nread < 0) { if (nread != UV_EOF) { ant_value_t err_args[1] = { js_mkstr(cp->js, uv_strerror((int)nread), (int)strlen(uv_strerror((int)nread))) }; emit_event(cp, "error", err_args, 1); emit_stream_event(cp, CHILD_STREAM_STDOUT, "error", err_args, 1); } else emit_stream_event(cp, CHILD_STREAM_STDOUT, "end", NULL, 0); if (nread != UV_EOF) emit_stream_event(cp, CHILD_STREAM_STDOUT, "end", NULL, 0); close_child_pipe(cp, CHILD_STREAM_STDOUT, true); check_completion(cp); }}
static void on_stderr_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { child_process_t *cp = (child_process_t *)stream->data; if (nread > 0) { if (cp->stderr_len + nread > cp->stderr_cap) { size_t new_cap = cp->stderr_cap == 0 ? 4096 : cp->stderr_cap * 2; while (new_cap < cp->stderr_len + nread) new_cap *= 2; char *new_buf = realloc(cp->stderr_buf, new_cap); if (new_buf) { cp->stderr_buf = new_buf; cp->stderr_cap = new_cap; } } if (cp->stderr_buf) { memcpy(cp->stderr_buf + cp->stderr_len, buf->base, nread); cp->stderr_len += nread; } ant_value_t text_data = js_mkstr(cp->js, buf->base, nread); ant_value_t text_args[1] = { text_data }; emit_event(cp, "stderr", text_args, 1);
ant_value_t byte_data = make_buffer_chunk(cp->js, buf->base, (size_t)nread); ant_value_t byte_args[1] = { byte_data }; emit_stream_event(cp, CHILD_STREAM_STDERR, "data", byte_args, 1); if (vtype(cp->stderr_obj) == T_OBJ) js_set( cp->js, cp->stderr_obj, "length", js_mknum((double)cp->stderr_len) ); } if (buf->base) free(buf->base); if (nread < 0) { if (nread != UV_EOF) { ant_value_t err_args[1] = { js_mkstr(cp->js, uv_strerror((int)nread), (int)strlen(uv_strerror((int)nread))) }; emit_stream_event(cp, CHILD_STREAM_STDERR, "error", err_args, 1); } emit_stream_event(cp, CHILD_STREAM_STDERR, "end", NULL, 0); close_child_pipe(cp, CHILD_STREAM_STDERR, true); check_completion(cp); }}
static ant_value_t child_on(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return js_mkerr(js, "on() requires event name and callback"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string"); if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function"); ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(cp_ptr) == T_UNDEF) return js_mkerr(js, "Invalid child process object"); child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); size_t name_len; char *name = js_getstr(js, args[0], &name_len); char *name_cstr = strndup(name, name_len); child_event_t *evt = find_or_create_event(cp, name_cstr); free(name_cstr); if (evt->count >= MAX_CHILD_LISTENERS) { return js_mkerr(js, "Maximum listeners reached for event"); } evt->listeners[evt->count].callback = args[1]; evt->listeners[evt->count].once = false; evt->count++; return this_obj;}
static ant_value_t child_once(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return js_mkerr(js, "once() requires event name and callback"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string"); if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function"); ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(cp_ptr) == T_UNDEF) return js_mkerr(js, "Invalid child process object"); child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); size_t name_len; char *name = js_getstr(js, args[0], &name_len); char *name_cstr = strndup(name, name_len); child_event_t *evt = find_or_create_event(cp, name_cstr); free(name_cstr); if (evt->count >= MAX_CHILD_LISTENERS) { return js_mkerr(js, "Maximum listeners reached for event"); } evt->listeners[evt->count].callback = args[1]; evt->listeners[evt->count].once = true; evt->count++; return this_obj;}
static ant_value_t child_kill(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(cp_ptr) == T_UNDEF) return js_false; child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); if (cp->exited) return js_false; int sig = SIGTERM; if (nargs > 0) { if (vtype(args[0]) == T_NUM) { sig = (int)js_getnum(args[0]); } else if (vtype(args[0]) == T_STR) { size_t sig_len; char *sig_str = js_getstr(js, args[0], &sig_len); if (sig_len == 7 && strncmp(sig_str, "SIGTERM", 7) == 0) sig = SIGTERM; else if (sig_len == 7 && strncmp(sig_str, "SIGKILL", 7) == 0) sig = SIGKILL; else if (sig_len == 6 && strncmp(sig_str, "SIGINT", 6) == 0) sig = SIGINT; else if (sig_len == 6 && strncmp(sig_str, "SIGHUP", 6) == 0) sig = SIGHUP; else if (sig_len == 7 && strncmp(sig_str, "SIGQUIT", 7) == 0) sig = SIGQUIT; } } int result = uv_process_kill(&cp->process, sig); return js_bool(result == 0);}
static ant_value_t child_write_impl(ant_t *js, child_process_t *cp, ant_value_t data_arg) { if (cp->stdin_closed) return js_false; const char *data = NULL; size_t data_len = 0; if (vtype(data_arg) == T_STR) { data = js_getstr(js, data_arg, &data_len); if (!data) return js_mkerr(js, "Data must be a string or Buffer"); } else { TypedArrayData *ta_data = buffer_get_typedarray_data(data_arg); if (!ta_data || !ta_data->buffer || !ta_data->buffer->data) { return js_mkerr(js, "Data must be a string or Buffer"); } data = (const char *)(ta_data->buffer->data + ta_data->byte_offset); data_len = ta_data->byte_length; }
uv_write_t *write_req = malloc(sizeof(uv_write_t)); char *buf_data = malloc(data_len); memcpy(buf_data, data, data_len); uv_buf_t buf = uv_buf_init(buf_data, (unsigned int)data_len); write_req->data = buf_data; int result = uv_write(write_req, (uv_stream_t *)&cp->stdin_pipe, &buf, 1, NULL); if (result < 0) { ant_value_t err_args[1] = { js_mkstr(js, uv_strerror(result), strlen(uv_strerror(result))) }; emit_stream_event(cp, CHILD_STREAM_STDIN, "error", err_args, 1); free(buf_data); free(write_req); return js_false; } return js_true;}
static ant_value_t child_end_impl(child_process_t *cp) { close_child_pipe(cp, CHILD_STREAM_STDIN, false); return js_mkundef();}
static ant_value_t child_write(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 1) return js_mkerr(js, "write() requires data argument"); ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(cp_ptr) == T_UNDEF) return js_mkerr(js, "Invalid child process object"); child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); return child_write_impl(js, cp, args[0]);}
static ant_value_t child_ref(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(cp_ptr) == T_UNDEF) return this_obj; child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); if (!cp) return this_obj; cp->keep_alive = true; if (!uv_is_closing((uv_handle_t *)&cp->process)) uv_ref((uv_handle_t *)&cp->process); for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) { uv_handle_t *h = (uv_handle_t *)child_pipe(cp, i); if (child_stdio_is_pipe(cp, i) && !uv_is_closing(h)) uv_ref(h); } return this_obj;}
static ant_value_t child_unref(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(cp_ptr) == T_UNDEF) return this_obj; child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); if (!cp) return this_obj; cp->keep_alive = false; if (!uv_is_closing((uv_handle_t *)&cp->process)) uv_unref((uv_handle_t *)&cp->process); for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) { uv_handle_t *h = (uv_handle_t *)child_pipe(cp, i); if (child_stdio_is_pipe(cp, i) && !uv_is_closing(h)) uv_unref(h); } return this_obj;}
static ant_value_t child_end(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t cp_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(cp_ptr) == T_UNDEF) return js_mkundef(); child_process_t *cp = (child_process_t *)(uintptr_t)js_getnum(cp_ptr); return child_end_impl(cp);}
static ant_value_t child_process_ctor(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_mkobj(js); if (is_object_type(g_child_process_proto)) js_set_proto_init(obj, g_child_process_proto);
js_set(js, obj, "pid", js_mkundef()); js_set(js, obj, "exitCode", js_mknull()); js_set(js, obj, "signalCode", js_mknull()); js_set(js, obj, "killed", js_false); js_set(js, obj, "connected", js_false); js_set(js, obj, "stdin", js_mknull()); js_set(js, obj, "stdout", js_mknull()); js_set(js, obj, "stderr", js_mknull());
return obj;}
static void child_process_init_constructor(ant_t *js) { if (g_child_process_ctor && g_child_process_proto) return; ant_value_t ee_proto = eventemitter_prototype(js);
g_child_process_proto = js_mkobj(js); if (is_object_type(ee_proto)) js_set_proto_init(g_child_process_proto, ee_proto); js_set(js, g_child_process_proto, "kill", js_mkfun(child_kill)); js_set(js, g_child_process_proto, "ref", js_mkfun(child_ref)); js_set(js, g_child_process_proto, "unref", js_mkfun(child_unref));
g_child_process_ctor = js_make_ctor( js, child_process_ctor, g_child_process_proto, "ChildProcess", 12 );}
static uv_handle_t *child_stream_handle(child_process_t *cp, child_stream_kind_t kind) { if (!cp) return NULL; switch (kind) { case CHILD_STREAM_STDIN: return (uv_handle_t *)&cp->stdin_pipe; case CHILD_STREAM_STDOUT: return (uv_handle_t *)&cp->stdout_pipe; case CHILD_STREAM_STDERR: return (uv_handle_t *)&cp->stderr_pipe; default: return NULL; }}
static ant_value_t child_stream_write(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 1) return js_mkerr(js, "write() requires data argument"); ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(ctx_ptr) == T_UNDEF) return js_mkerr(js, "Invalid stream object"); child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr); if (!ctx || !ctx->cp) return js_mkerr(js, "Invalid stream context"); return child_write_impl(js, ctx->cp, args[0]);}
static ant_value_t child_stream_end(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; ant_value_t this_obj = js_getthis(js); ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(ctx_ptr) == T_UNDEF) return js_mkundef(); child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr); if (!ctx || !ctx->cp) return js_mkundef(); return child_end_impl(ctx->cp);}
static ant_value_t child_stream_ref(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; ant_value_t this_obj = js_getthis(js); ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(ctx_ptr) == T_UNDEF) return this_obj; child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr); if (!ctx || !ctx->cp) return this_obj;
uv_handle_t *h = child_stream_handle(ctx->cp, ctx->kind); if (h && !uv_is_closing(h)) uv_ref(h); return this_obj;}
static ant_value_t child_stream_unref(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; ant_value_t this_obj = js_getthis(js); ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(ctx_ptr) == T_UNDEF) return this_obj; child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr); if (!ctx || !ctx->cp) return this_obj;
uv_handle_t *h = child_stream_handle(ctx->cp, ctx->kind); if (h && !uv_is_closing(h)) uv_unref(h); return this_obj;}
static ant_value_t child_stream_destroy(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; ant_value_t this_obj = js_getthis(js); ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(ctx_ptr) == T_UNDEF) return this_obj; child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr); if (!ctx || !ctx->cp) return this_obj;
child_process_t *cp = ctx->cp; if (ctx->kind == CHILD_STREAM_STDIN) { (void)child_end_impl(cp); return this_obj; }
uv_handle_t *h = child_stream_handle(cp, ctx->kind); if (h && !uv_is_closing(h)) { if (ctx->kind == CHILD_STREAM_STDOUT) close_child_pipe(cp, CHILD_STREAM_STDOUT, true); else if (ctx->kind == CHILD_STREAM_STDERR) close_child_pipe(cp, CHILD_STREAM_STDERR, true); check_completion(cp); }
return this_obj;}
static ant_value_t child_stream_on(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return js_mkerr(js, "on() requires event name and callback"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string"); if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function");
ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(ctx_ptr) == T_UNDEF) return js_mkerr(js, "Invalid stream object"); child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr); if (!ctx || !ctx->cp) return js_mkerr(js, "Invalid stream context"); child_process_t *cp = ctx->cp; child_stream_kind_t kind = ctx->kind; size_t name_len = 0; char *name = js_getstr(js, args[0], &name_len); if (!name) return js_mkerr(js, "Event name must be a string");
char full_name[64]; snprintf( full_name, sizeof(full_name), "%s:%.*s", stream_kind_name(kind), (int)name_len, name );
child_event_t *evt = find_or_create_event(cp, full_name); if (evt->count >= MAX_CHILD_LISTENERS) { return js_mkerr(js, "Maximum listeners reached for event"); }
evt->listeners[evt->count].callback = args[1]; evt->listeners[evt->count].once = false; evt->count++; return this_obj;}
static ant_value_t child_stream_once(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return js_mkerr(js, "once() requires event name and callback"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Event name must be a string"); if (vtype(args[1]) != T_FUNC) return js_mkerr(js, "Callback must be a function");
ant_value_t ctx_ptr = js_get_slot(this_obj, SLOT_DATA); if (vtype(ctx_ptr) == T_UNDEF) return js_mkerr(js, "Invalid stream object"); child_stream_ctx_t *ctx = (child_stream_ctx_t *)(uintptr_t)js_getnum(ctx_ptr); if (!ctx || !ctx->cp) return js_mkerr(js, "Invalid stream context"); child_process_t *cp = ctx->cp; child_stream_kind_t kind = ctx->kind; size_t name_len = 0; char *name = js_getstr(js, args[0], &name_len); if (!name) return js_mkerr(js, "Event name must be a string");
char full_name[64]; snprintf( full_name, sizeof(full_name), "%s:%.*s", stream_kind_name(kind), (int)name_len, name );
child_event_t *evt = find_or_create_event(cp, full_name); if (evt->count >= MAX_CHILD_LISTENERS) { return js_mkerr(js, "Maximum listeners reached for event"); }
evt->listeners[evt->count].callback = args[1]; evt->listeners[evt->count].once = true; evt->count++; return this_obj;}
static ant_value_t create_child_stream_object(ant_t *js, child_process_t *cp, child_stream_kind_t kind) { ant_value_t obj = js_mkobj(js); child_stream_ctx_t *ctx = calloc(1, sizeof(child_stream_ctx_t)); if (!ctx) return js_mkerr(js, "Out of memory"); ctx->cp = cp; ctx->kind = kind; if (kind == CHILD_STREAM_STDIN) cp->stdin_ctx = ctx; else if (kind == CHILD_STREAM_STDOUT) cp->stdout_ctx = ctx; else cp->stderr_ctx = ctx;
js_set_slot(obj, SLOT_DATA, ANT_PTR(ctx)); js_set(js, obj, "on", js_mkfun(child_stream_on)); js_set(js, obj, "once", js_mkfun(child_stream_once)); js_set(js, obj, "ref", js_mkfun(child_stream_ref)); js_set(js, obj, "unref", js_mkfun(child_stream_unref)); js_set(js, obj, "destroy", js_mkfun(child_stream_destroy)); js_set(js, obj, "length", js_mknum(0));
if (kind == CHILD_STREAM_STDIN) { js_set(js, obj, "write", js_mkfun(child_stream_write)); js_set(js, obj, "end", js_mkfun(child_stream_end)); }
return obj;}
static ant_value_t create_child_object(ant_t *js, child_process_t *cp) { ant_value_t obj = js_mkobj(js); if (is_object_type(g_child_process_proto)) js_set_proto_init(obj, g_child_process_proto); js_set_slot(obj, SLOT_DATA, ANT_PTR(cp)); js_set(js, obj, "pid", js_mknum((double)cp->process.pid)); js_set(js, obj, "exitCode", js_mknull()); js_set(js, obj, "signalCode", js_mknull()); js_set(js, obj, "killed", js_false); js_set(js, obj, "connected", js_true);
static const struct { child_stream_kind_t kind; const char *name; } streams[] = { { CHILD_STREAM_STDIN, "stdin" }, { CHILD_STREAM_STDOUT, "stdout" }, { CHILD_STREAM_STDERR, "stderr" }, }; ant_value_t *stream_objs[] = { &cp->stdin_obj, &cp->stdout_obj, &cp->stderr_obj }; for (int i = 0; i < 3; i++) { if (child_stdio_is_pipe(cp, streams[i].kind)) { *stream_objs[i] = create_child_stream_object(js, cp, streams[i].kind); } else *stream_objs[i] = js_mknull(); js_set(js, obj, streams[i].name, *stream_objs[i]); } js_set(js, obj, "on", js_mkfun(child_on)); js_set(js, obj, "once", js_mkfun(child_once)); js_set(js, obj, "ref", js_mkfun(child_ref)); js_set(js, obj, "unref", js_mkfun(child_unref)); js_set(js, obj, "kill", js_mkfun(child_kill)); js_set(js, obj, "write", js_mkfun(child_write)); js_set(js, obj, "end", js_mkfun(child_end)); js_set_sym(js, obj, get_toStringTag_sym(), js_mkstr(js, "ChildProcess", 12)); return obj;}
static char **parse_args_array(ant_t *js, ant_value_t arr, int *count) { ant_value_t len_val = js_get(js, arr, "length"); int len = (int)js_getnum(len_val); char **args = calloc(len + 1, sizeof(char *)); if (!args) { *count = 0; return NULL; } for (int i = 0; i < len; i++) { char idx[16]; snprintf(idx, sizeof(idx), "%d", i); ant_value_t val = js_get(js, arr, idx); if (vtype(val) == T_STR) { size_t arg_len; char *arg = js_getstr(js, val, &arg_len); args[i] = strndup(arg, arg_len); } else args[i] = strdup(""); } args[len] = NULL; *count = len; return args;}
static void free_args_array(char **args, int count) { if (!args) return; for (int i = 0; i < count; i++) { if (args[i]) free(args[i]); } free(args);}
static stdio_mode_t parse_stdio_mode(ant_t *js, ant_value_t val) { if (vtype(val) != T_STR) return STDIO_PIPE; char *s = js_getstr(js, val, NULL); if (strcmp(s, "inherit") == 0) return STDIO_INHERIT; if (strcmp(s, "ignore") == 0) return STDIO_IGNORE; return STDIO_PIPE;}
static void parse_stdio_option(ant_t *js, ant_value_t stdio_val, stdio_mode_t *modes) {if (vtype(stdio_val) == T_STR) { stdio_mode_t mode = parse_stdio_mode(js, stdio_val); for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) modes[i] = mode;} else if (is_special_object(stdio_val)) { ant_offset_t len = js_arr_len(js, stdio_val); if (len > 3) len = 3; for (ant_offset_t i = 0; i < len; i++) modes[i] = parse_stdio_mode(js, js_arr_get(js, stdio_val, i));}}
static ant_value_t builtin_spawn(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "spawn() requires a command"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string"); size_t cmd_len; char *cmd = js_getstr(js, args[0], &cmd_len); char *cmd_str = strndup(cmd, cmd_len); char **spawn_args = NULL; int spawn_argc = 0; char *cwd = NULL; bool use_shell = false; bool detached = false; if (nargs >= 2 && is_special_object(args[1])) { ant_value_t len_val = js_get(js, args[1], "length"); if (vtype(len_val) == T_NUM) { spawn_args = parse_args_array(js, args[1], &spawn_argc); } } stdio_mode_t stdio_modes[3] = { STDIO_PIPE, STDIO_PIPE, STDIO_PIPE }; if (nargs >= 3 && is_special_object(args[2])) { ant_value_t cwd_val = js_get(js, args[2], "cwd"); if (vtype(cwd_val) == T_STR) { size_t cwd_len; char *cwd_str = js_getstr(js, cwd_val, &cwd_len); cwd = strndup(cwd_str, cwd_len); } ant_value_t shell_val = js_get(js, args[2], "shell"); use_shell = js_truthy(js, shell_val); ant_value_t detached_val = js_get(js, args[2], "detached"); detached = js_truthy(js, detached_val); ant_value_t stdio_val = js_get(js, args[2], "stdio"); parse_stdio_option(js, stdio_val, stdio_modes); } child_process_t *cp = calloc(1, sizeof(child_process_t)); if (!cp) { free(cmd_str); free_args_array(spawn_args, spawn_argc); if (cwd) free(cwd); return js_mkerr(js, "Out of memory"); } cp->js = js; cp->use_shell = use_shell; cp->detached = detached; cp->cwd = cwd; cp->promise = js_mkundef(); cp->keep_alive = true; memcpy(cp->stdio_modes, stdio_modes, sizeof(stdio_modes)); cp->process.data = cp; for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) { if (stdio_modes[i] == STDIO_PIPE) { uv_pipe_t *p = child_pipe(cp, i); uv_pipe_init(uv_default_loop(), p, 0); p->data = cp; }} uv_stdio_container_t stdio[3]; for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) { if (stdio_modes[i] == STDIO_INHERIT) { stdio[i].flags = UV_INHERIT_FD; stdio[i].data.fd = i; } else if (stdio_modes[i] == STDIO_IGNORE) stdio[i].flags = UV_IGNORE; else { stdio[i].flags = UV_CREATE_PIPE | (i == CHILD_STREAM_STDIN ? UV_READABLE_PIPE : UV_WRITABLE_PIPE); stdio[i].data.stream = (uv_stream_t *)child_pipe(cp, i); }} char **final_args; int final_argc; char *shell_cmd = NULL; if (use_shell) { final_args = calloc(4, sizeof(char *)); final_args[0] = strdup("/bin/sh"); final_args[1] = strdup("-c"); size_t total_len = cmd_len + 1; for (int i = 0; i < spawn_argc; i++) { total_len += strlen(spawn_args[i]) + 3; } shell_cmd = malloc(total_len); strcpy(shell_cmd, cmd_str); for (int i = 0; i < spawn_argc; i++) { strcat(shell_cmd, " "); strcat(shell_cmd, spawn_args[i]); } final_args[2] = shell_cmd; final_args[3] = NULL; final_argc = 3; free(cmd_str); cmd_str = strdup("/bin/sh"); } else { final_argc = spawn_argc + 1; final_args = calloc(final_argc + 1, sizeof(char *)); final_args[0] = cmd_str; for (int i = 0; i < spawn_argc; i++) { final_args[i + 1] = spawn_args ? spawn_args[i] : NULL; } final_args[final_argc] = NULL; if (spawn_args) free(spawn_args); spawn_args = NULL; } uv_process_options_t options = {0}; options.exit_cb = on_process_exit; options.file = final_args[0]; options.args = final_args; options.stdio_count = 3; options.stdio = stdio; if (cwd) options.cwd = cwd; if (detached) options.flags = UV_PROCESS_DETACHED; int r = uv_spawn(uv_default_loop(), &cp->process, &options); if (use_shell) { free(final_args[0]); free(final_args[1]); free(shell_cmd); free(final_args); } else { for (int i = 0; i < final_argc; i++) { if (final_args[i]) free(final_args[i]); } free(final_args); } free_args_array(spawn_args, spawn_argc); if (r < 0) { free_child_process(cp); return js_mkerr(js, "Failed to spawn process: %s", uv_strerror(r)); } static const uv_read_cb read_cbs[] = { NULL, on_stdout_read, on_stderr_read }; bool *closed[] = { &cp->stdin_closed, &cp->stdout_closed, &cp->stderr_closed }; for (int i = CHILD_STREAM_STDIN; i <= CHILD_STREAM_STDERR; i++) { if (stdio_modes[i] == STDIO_PIPE && read_cbs[i]) { uv_read_start((uv_stream_t *)child_pipe(cp, i), alloc_buffer, read_cbs[i]); } else if (stdio_modes[i] != STDIO_PIPE) *closed[i] = true; } add_pending_child(cp); cp->child_obj = create_child_object(js, cp); return cp->child_obj;}
static ant_value_t builtin_exec(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "exec() requires a command"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string"); size_t cmd_len; char *cmd = js_getstr(js, args[0], &cmd_len); char *cmd_str = strndup(cmd, cmd_len); char *cwd = NULL; if (nargs >= 2 && is_special_object(args[1])) { ant_value_t cwd_val = js_get(js, args[1], "cwd"); if (vtype(cwd_val) == T_STR) { size_t cwd_len; char *cwd_s = js_getstr(js, cwd_val, &cwd_len); cwd = strndup(cwd_s, cwd_len); } } child_process_t *cp = calloc(1, sizeof(child_process_t)); if (!cp) { free(cmd_str); if (cwd) free(cwd); return js_mkerr(js, "Out of memory"); } cp->js = js; cp->use_shell = true; cp->cwd = cwd; cp->promise = js_mkpromise(js); cp->keep_alive = true; cp->stdio_modes[CHILD_STREAM_STDIN] = STDIO_IGNORE; cp->stdio_modes[CHILD_STREAM_STDOUT] = STDIO_PIPE; cp->stdio_modes[CHILD_STREAM_STDERR] = STDIO_PIPE; uv_pipe_init(uv_default_loop(), &cp->stdout_pipe, 0); uv_pipe_init(uv_default_loop(), &cp->stderr_pipe, 0); cp->stdout_pipe.data = cp; cp->stderr_pipe.data = cp; cp->process.data = cp; cp->stdin_closed = true; uv_stdio_container_t stdio[3]; stdio[0].flags = UV_IGNORE; stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; stdio[1].data.stream = (uv_stream_t *)&cp->stdout_pipe; stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; stdio[2].data.stream = (uv_stream_t *)&cp->stderr_pipe; char *shell_args[4]; shell_args[0] = "/bin/sh"; shell_args[1] = "-c"; shell_args[2] = cmd_str; shell_args[3] = NULL; uv_process_options_t options = {0}; options.exit_cb = on_process_exit; options.file = "/bin/sh"; options.args = shell_args; options.stdio_count = 3; options.stdio = stdio; if (cwd) options.cwd = cwd; int r = uv_spawn(uv_default_loop(), &cp->process, &options); free(cmd_str); if (r < 0) { ant_value_t promise = cp->promise; free_child_process(cp); js_reject_promise(js, promise, js_mkstr(js, uv_strerror(r), strlen(uv_strerror(r)))); return promise; } uv_read_start((uv_stream_t *)&cp->stdout_pipe, alloc_buffer, on_stdout_read); uv_read_start((uv_stream_t *)&cp->stderr_pipe, alloc_buffer, on_stderr_read); add_pending_child(cp); cp->child_obj = create_child_object(js, cp); return cp->promise;}
static ant_value_t exec_file_close_callback(ant_t *js, ant_value_t *args, int nargs) { ant_value_t fn = js_getcurrentfunc(js); ant_value_t ctx = js_get_slot(fn, SLOT_DATA); ant_value_t callback = js_get(js, ctx, "callback"); ant_value_t child = js_get(js, ctx, "child"); ant_value_t stdout_val = js_get(js, child, "stdoutText"); ant_value_t stderr_val = js_get(js, child, "stderrText"); ant_value_t exit_code_val = js_get(js, child, "exitCode"); ant_value_t cb_args[3];
if (!is_callable(callback)) return js_mkundef();
if (vtype(exit_code_val) == T_NUM && (int)js_getnum(exit_code_val) != 0) { char err_msg[256]; snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %d", (int)js_getnum(exit_code_val)); cb_args[0] = js_make_error_silent(js, JS_ERR_GENERIC, err_msg); } else cb_args[0] = js_mknull();
cb_args[1] = stdout_val; cb_args[2] = stderr_val;
ant_value_t result = sv_vm_call(js->vm, js, callback, js_mkundef(), cb_args, 3, NULL, false); if (vtype(result) == T_ERR) log_listener_error(js, "execFile", result); return js_mkundef();}
static ant_value_t exec_file_promisify_callback(ant_t *js, ant_value_t *args, int nargs) { ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); if (!is_object_type(state)) return js_mkundef();
ant_value_t settled = js_get_slot(state, SLOT_SETTLED); if (vtype(settled) == T_BOOL && settled == js_true) return js_mkundef(); js_set_slot(state, SLOT_SETTLED, js_true);
ant_value_t promise = js_get_slot(state, SLOT_DATA); if (vtype(promise) != T_PROMISE) return js_mkundef();
ant_value_t stdout_val = nargs > 1 ? args[1] : js_mkstr(js, "", 0); ant_value_t stderr_val = nargs > 2 ? args[2] : js_mkstr(js, "", 0);
if (nargs > 0 && !is_null(args[0]) && !is_undefined(args[0])) { if (is_object_type(args[0])) { js_set(js, args[0], "stdout", stdout_val); js_set(js, args[0], "stderr", stderr_val); } js_reject_promise(js, promise, args[0]); return js_mkundef(); }
ant_value_t result = js_mkobj(js); js_set(js, result, "stdout", stdout_val); js_set(js, result, "stderr", stderr_val); js_resolve_promise(js, promise, result); return js_mkundef();}
static ant_value_t exec_file_promisified_call(ant_t *js, ant_value_t *args, int nargs) { ant_value_t original = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); if (!is_callable(original)) return js_mkerr(js, "execFile promisify target is not callable");
ant_value_t promise = js_mkpromise(js); ant_value_t state = js_mkobj(js); js_set_slot(state, SLOT_DATA, promise); js_set_slot(state, SLOT_SETTLED, js_false); ant_value_t callback = js_heavy_mkfun(js, exec_file_promisify_callback, state); ant_value_t *call_args = malloc((size_t)(nargs + 1) * sizeof(ant_value_t)); if (!call_args) { js_reject_promise(js, promise, js_mkerr(js, "Out of memory")); return promise; }
for (int i = 0; i < nargs; i++) call_args[i] = args[i]; call_args[nargs] = callback;
ant_value_t call_result = sv_vm_call( js->vm, js, original, js_getthis(js), call_args, nargs + 1, NULL, false ); free(call_args);
ant_value_t settled = js_get_slot(state, SLOT_SETTLED); bool is_settled = (vtype(settled) == T_BOOL && settled == js_true); if (!is_settled && (is_err(call_result) || js->thrown_exists)) { ant_value_t ex = js->thrown_exists ? js->thrown_value : call_result; js->thrown_exists = false; js->thrown_value = js_mkundef(); js->thrown_stack = js_mkundef(); js_set_slot(state, SLOT_SETTLED, js_true); js_reject_promise(js, promise, ex); }
return promise;}
static ant_value_t exec_promisified_call(ant_t *js, ant_value_t *args, int nargs) { ant_value_t original = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); if (!is_callable(original)) return js_mkerr(js, "exec promisify target is not callable");
ant_value_t call_result = sv_vm_call( js->vm, js, original, js_getthis(js), args, nargs, NULL, false );
if (vtype(call_result) == T_PROMISE) return call_result; if (is_err(call_result) || js->thrown_exists) { ant_value_t promise = js_mkpromise(js); ant_value_t ex = js->thrown_exists ? js->thrown_value : call_result; js->thrown_exists = false; js->thrown_value = js_mkundef(); js->thrown_stack = js_mkundef(); js_reject_promise(js, promise, ex); return promise; }
ant_value_t promise = js_mkpromise(js); js_resolve_promise(js, promise, call_result); return promise;}
static ant_value_t builtin_execFile(ant_t *js, ant_value_t *args, int nargs) { ant_value_t argv = js_mkundef(); ant_value_t options = js_mkundef(); ant_value_t callback = js_mkundef(); ant_value_t spawn_args[3]; ant_value_t child;
if (nargs < 1) return js_mkerr(js, "execFile() requires a file"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "File must be a string");
if (nargs >= 2 && is_callable(args[nargs - 1])) { callback = args[nargs - 1]; nargs--; }
if (nargs >= 2) { if (vtype(args[1]) == T_ARR) { argv = args[1]; if (nargs >= 3 && is_special_object(args[2])) options = args[2]; } else if (is_special_object(args[1])) options = args[1]; }
spawn_args[0] = args[0]; spawn_args[1] = argv; spawn_args[2] = options;
child = builtin_spawn(js, spawn_args, 3); if (vtype(child) != T_OBJ || !is_callable(callback)) return child;
ant_value_t ctx = js_mkobj(js); js_set(js, ctx, "callback", callback); js_set(js, ctx, "child", child);
// TODO: reduce duck-typing ant_value_t close_listener = js_heavy_mkfun(js, exec_file_close_callback, ctx); ant_value_t once_fn = js_get(js, child, "once"); ant_value_t once_args[2] = { js_mkstr(js, "close", 5), close_listener }; ant_value_t once_result = sv_vm_call(js->vm, js, once_fn, child, once_args, 2, NULL, false); if (vtype(once_result) == T_ERR) return once_result;
return child;}
static ant_value_t builtin_execSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "execSync() requires a command"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string"); size_t cmd_len; char *cmd = js_getstr(js, args[0], &cmd_len); char *cmd_str = strndup(cmd, cmd_len); FILE *fp = popen(cmd_str, "r"); free(cmd_str); if (!fp) { return js_mkerr(js, "Failed to execute command"); } char *output = NULL; size_t output_len = 0; size_t output_cap = 4096; output = malloc(output_cap); if (!output) { pclose(fp); return js_mkerr(js, "Out of memory"); } char buffer[4096]; while (fgets(buffer, sizeof(buffer), fp) != NULL) { size_t len = strlen(buffer); if (output_len + len >= output_cap) { output_cap *= 2; char *new_output = realloc(output, output_cap); if (!new_output) { free(output); pclose(fp); return js_mkerr(js, "Out of memory"); } output = new_output; } memcpy(output + output_len, buffer, len); output_len += len; } int status = pclose(fp);#ifdef _WIN32 int exit_code = status;#else int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;#endif if (output_len > 0 && output[output_len - 1] == '\n') { output_len--; } if (exit_code != 0) { char err_msg[256]; snprintf(err_msg, sizeof(err_msg), "Command failed with exit code %d", exit_code); free(output); return js_mkerr(js, "%s", err_msg); } ant_value_t result = js_mkstr(js, output, output_len); free(output); return result;}
#ifdef _WIN32static ant_value_t builtin_spawnSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "spawnSync() requires a command"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string"); size_t cmd_len; char *cmd = js_getstr(js, args[0], &cmd_len); char *cmd_str = strndup(cmd, cmd_len); char **spawn_args = NULL; int spawn_argc = 0; char *input = NULL; size_t input_len = 0; if (nargs >= 2 && is_special_object(args[1])) { ant_value_t len_val = js_get(js, args[1], "length"); if (vtype(len_val) == T_NUM) { spawn_args = parse_args_array(js, args[1], &spawn_argc); } } if (nargs >= 3 && is_special_object(args[2])) { ant_value_t input_val = js_get(js, args[2], "input"); if (vtype(input_val) == T_STR) { input = js_getstr(js, input_val, &input_len); } } size_t cmdline_len = cmd_len + 3; for (int i = 0; i < spawn_argc; i++) { cmdline_len += strlen(spawn_args[i]) + 3; } char *cmdline = malloc(cmdline_len); if (!cmdline) { free(cmd_str); free_args_array(spawn_args, spawn_argc); return js_mkerr(js, "Out of memory"); } char *p = cmdline; p += sprintf(p, "%s", cmd_str); for (int i = 0; i < spawn_argc; i++) { p += sprintf(p, " \"%s\"", spawn_args[i]); } SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; HANDLE stdin_read = NULL, stdin_write = NULL; HANDLE stdout_read = NULL, stdout_write = NULL; HANDLE stderr_read = NULL, stderr_write = NULL; if (!CreatePipe(&stdin_read, &stdin_write, &sa, 0) || !CreatePipe(&stdout_read, &stdout_write, &sa, 0) || !CreatePipe(&stderr_read, &stderr_write, &sa, 0)) { free(cmdline); free(cmd_str); free_args_array(spawn_args, spawn_argc); return js_mkerr(js, "Failed to create pipes"); } SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0); SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0); SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0); STARTUPINFOA si = {0}; si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = stdin_read; si.hStdOutput = stdout_write; si.hStdError = stderr_write; PROCESS_INFORMATION pi = {0}; BOOL success = CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); free(cmdline); free(cmd_str); free_args_array(spawn_args, spawn_argc); CloseHandle(stdin_read); CloseHandle(stdout_write); CloseHandle(stderr_write); if (!success) { CloseHandle(stdin_write); CloseHandle(stdout_read); CloseHandle(stderr_read); return js_mkerr(js, "Failed to create process"); } if (input && input_len > 0) { DWORD written; WriteFile(stdin_write, input, (DWORD)input_len, &written, NULL); } CloseHandle(stdin_write); char *stdout_buf = malloc(4096); size_t stdout_len = 0, stdout_cap = 4096; char *stderr_buf = malloc(4096); size_t stderr_len = 0, stderr_cap = 4096; char buffer[4096]; DWORD n; while (ReadFile(stdout_read, buffer, sizeof(buffer), &n, NULL) && n > 0) { if (stdout_len + n >= stdout_cap) { stdout_cap *= 2; stdout_buf = realloc(stdout_buf, stdout_cap); } memcpy(stdout_buf + stdout_len, buffer, n); stdout_len += n; } CloseHandle(stdout_read); while (ReadFile(stderr_read, buffer, sizeof(buffer), &n, NULL) && n > 0) { if (stderr_len + n >= stderr_cap) { stderr_cap *= 2; stderr_buf = realloc(stderr_buf, stderr_cap); } memcpy(stderr_buf + stderr_len, buffer, n); stderr_len += n; } CloseHandle(stderr_read); WaitForSingleObject(pi.hProcess, INFINITE); DWORD exit_code = 0; GetExitCodeProcess(pi.hProcess, &exit_code); DWORD pid = pi.dwProcessId; CloseHandle(pi.hProcess); CloseHandle(pi.hThread); ant_value_t result = js_mkobj(js); js_set(js, result, "stdout", js_mkstr(js, stdout_buf ? stdout_buf : "", stdout_len)); js_set(js, result, "stderr", js_mkstr(js, stderr_buf ? stderr_buf : "", stderr_len)); js_set(js, result, "status", js_mknum((double)exit_code)); js_set(js, result, "signal", js_mknull()); js_set(js, result, "pid", js_mknum((double)pid)); if (stdout_buf) free(stdout_buf); if (stderr_buf) free(stderr_buf); return result;}#elsestatic ant_value_t builtin_spawnSync(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "spawnSync() requires a command"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Command must be a string"); size_t cmd_len; char *cmd = js_getstr(js, args[0], &cmd_len); char *cmd_str = strndup(cmd, cmd_len); char **spawn_args = NULL; int spawn_argc = 0; char *input = NULL; size_t input_len = 0; if (nargs >= 2 && is_special_object(args[1])) { ant_value_t len_val = js_get(js, args[1], "length"); if (vtype(len_val) == T_NUM) { spawn_args = parse_args_array(js, args[1], &spawn_argc); } } if (nargs >= 3 && is_special_object(args[2])) { ant_value_t input_val = js_get(js, args[2], "input"); if (vtype(input_val) == T_STR) { input = js_getstr(js, input_val, &input_len); } } char **exec_args = calloc(spawn_argc + 2, sizeof(char *)); if (!exec_args) { free(cmd_str); free_args_array(spawn_args, spawn_argc); return js_mkerr(js, "Out of memory"); } exec_args[0] = cmd_str; for (int i = 0; i < spawn_argc; i++) { exec_args[i + 1] = spawn_args[i]; } exec_args[spawn_argc + 1] = NULL; int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2]; if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0 || pipe(stderr_pipe) < 0) { free(exec_args); free(cmd_str); free_args_array(spawn_args, spawn_argc); return js_mkerr(js, "Failed to create pipes"); } pid_t pid = fork(); if (pid < 0) { free(exec_args); free(cmd_str); free_args_array(spawn_args, spawn_argc); return js_mkerr(js, "Fork failed"); } if (pid == 0) { close(stdin_pipe[1]); close(stdout_pipe[0]); close(stderr_pipe[0]); dup2(stdin_pipe[0], STDIN_FILENO); dup2(stdout_pipe[1], STDOUT_FILENO); dup2(stderr_pipe[1], STDERR_FILENO); close(stdin_pipe[0]); close(stdout_pipe[1]); close(stderr_pipe[1]); execvp(exec_args[0], exec_args); _exit(127); } free(exec_args); free(cmd_str); free_args_array(spawn_args, spawn_argc); close(stdin_pipe[0]); close(stdout_pipe[1]); close(stderr_pipe[1]); if (input && input_len > 0) { write(stdin_pipe[1], input, input_len); } close(stdin_pipe[1]); char *stdout_buf = NULL; size_t stdout_len = 0; size_t stdout_cap = 4096; stdout_buf = malloc(stdout_cap); char *stderr_buf = NULL; size_t stderr_len = 0; size_t stderr_cap = 4096; stderr_buf = malloc(stderr_cap); char buffer[4096]; ssize_t n; int status = 0; while ((n = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) { if (stdout_len + n >= stdout_cap) { stdout_cap *= 2; stdout_buf = realloc(stdout_buf, stdout_cap); } memcpy(stdout_buf + stdout_len, buffer, n); stdout_len += n; } close(stdout_pipe[0]); while ((n = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) { if (stderr_len + n >= stderr_cap) { stderr_cap *= 2; stderr_buf = realloc(stderr_buf, stderr_cap); } memcpy(stderr_buf + stderr_len, buffer, n); stderr_len += n; } close(stderr_pipe[0]); waitpid(pid, &status, 0); int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1; int signal_code = WIFSIGNALED(status) ? WTERMSIG(status) : 0; ant_value_t result = js_mkobj(js); js_set(js, result, "stdout", js_mkstr(js, stdout_buf ? stdout_buf : "", stdout_len)); js_set(js, result, "stderr", js_mkstr(js, stderr_buf ? stderr_buf : "", stderr_len)); js_set(js, result, "status", js_mknum((double)exit_code)); js_set(js, result, "signal", signal_code ? js_mknum((double)signal_code) : js_mknull()); js_set(js, result, "pid", js_mknum((double)pid)); if (stdout_buf) free(stdout_buf); if (stderr_buf) free(stderr_buf); return result;}#endif
static ant_value_t builtin_execFileSync(ant_t *js, ant_value_t *args, int nargs) { ant_value_t argv = js_mkundef(); ant_value_t options = js_mkundef(); ant_value_t spawn_args[3]; ant_value_t result; ant_value_t status;
if (nargs < 1) return js_mkerr(js, "execFileSync() requires a file"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "File must be a string");
if (nargs >= 2) { if (vtype(args[1]) == T_ARR) { argv = args[1]; if (nargs >= 3 && is_special_object(args[2])) options = args[2]; } else if (is_special_object(args[1])) options = args[1]; }
spawn_args[0] = args[0]; spawn_args[1] = argv; spawn_args[2] = options;
result = builtin_spawnSync(js, spawn_args, 3); if (vtype(result) != T_OBJ) return result;
status = js_get(js, result, "status"); if (vtype(status) == T_NUM && (int)js_getnum(status) != 0) return js_mkerr(js, "Command failed with exit code %d", (int)js_getnum(status));
return js_get(js, result, "stdout");}
static ant_value_t builtin_fork(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "fork() requires a module path"); if (vtype(args[0]) != T_STR) return js_mkerr(js, "Module path must be a string"); size_t path_len; char *path = js_getstr(js, args[0], &path_len); char *path_str = strndup(path, path_len); char exe_path[1024]; #if defined(__APPLE__) uint32_t size = sizeof(exe_path); if (_NSGetExecutablePath(exe_path, &size) != 0) { free(path_str); return js_mkerr(js, "Failed to get executable path"); }#elif defined(__linux__) ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); if (len == -1) { free(path_str); return js_mkerr(js, "Failed to get executable path"); } exe_path[len] = '\0';#else strncpy(exe_path, "ant", sizeof(exe_path));#endif ant_value_t spawn_args[3]; spawn_args[0] = js_mkstr(js, exe_path, strlen(exe_path)); ant_value_t args_arr = js_mkarr(js); js_arr_push(js, args_arr, js_mkstr(js, path_str, path_len)); if (nargs >= 2 && is_special_object(args[1])) { ant_value_t exec_args = js_get(js, args[1], "execArgv"); if (is_special_object(exec_args)) { ant_value_t len_val = js_get(js, exec_args, "length"); int arr_len = (int)js_getnum(len_val); for (int i = 0; i < arr_len; i++) { char idx[16]; snprintf(idx, sizeof(idx), "%d", i); ant_value_t arg = js_get(js, exec_args, idx); js_arr_push(js, args_arr, arg); } } } spawn_args[1] = args_arr; spawn_args[2] = js_mkobj(js); free(path_str); return builtin_spawn(js, spawn_args, 3);}
ant_value_t child_process_library(ant_t *js) { ant_value_t lib = js_mkobj(js); ant_value_t exec_fn = js_heavy_mkfun(js, builtin_exec, js_mkundef()); ant_value_t exec_file_fn = js_heavy_mkfun(js, builtin_execFile, js_mkundef()); child_process_init_constructor(js); js_set_symbol(js, exec_fn, "nodejs.util.promisify.custom", js_heavy_mkfun(js, exec_promisified_call, exec_fn) );
js_set_symbol(js, exec_file_fn, "nodejs.util.promisify.custom", js_heavy_mkfun(js, exec_file_promisified_call, exec_file_fn) ); js_set(js, lib, "ChildProcess", g_child_process_ctor); js_set(js, lib, "spawn", js_mkfun(builtin_spawn)); js_set(js, lib, "exec", exec_fn); js_set(js, lib, "execFile", exec_file_fn); js_set(js, lib, "execSync", js_mkfun(builtin_execSync)); js_set(js, lib, "execFileSync", js_mkfun(builtin_execFileSync)); js_set(js, lib, "spawnSync", js_mkfun(builtin_spawnSync)); js_set(js, lib, "fork", js_mkfun(builtin_fork)); js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "child_process", 13)); return lib;}
int has_pending_child_processes(void) { for (child_process_t *cp = pending_children_head; cp; cp = cp->next) if (cp->keep_alive) return 1; return 0;}
void gc_mark_child_process(ant_t *js, gc_mark_fn mark) {for (child_process_t *cp = pending_children_head; cp; cp = cp->next) { mark(js, cp->child_obj); mark(js, cp->stdin_obj); mark(js, cp->stdout_obj); mark(js, cp->stderr_obj); mark(js, cp->promise);
child_event_t *evt, *tmp; HASH_ITER(hh, cp->events, evt, tmp) for (int i = 0; i < evt->count; i++) mark(js, evt->listeners[i].callback);}}