Page MenuHomePhorge

child_process.c
No OneTemporary

Size
59 KB
Referenced Files
None
Subscribers
None

child_process.c

#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 _WIN32
static 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;
}
#else
static 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);
}}

File Metadata

Mime Type
text/x-c
Expires
Fri, May 1, 6:49 PM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
541868
Default Alt Text
child_process.c (59 KB)

Event Timeline