Page MenuHomePhorge

child_process.c
No OneTemporary

Size
47 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 "modules/child_process.h"
#include "modules/buffer.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 {
jsval_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;
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;
jsval_t child_obj;
jsval_t stdin_obj;
jsval_t stdout_obj;
jsval_t stderr_obj;
child_stream_ctx_t *stdin_ctx;
child_stream_ctx_t *stdout_ctx;
child_stream_ctx_t *stderr_ctx;
jsval_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;
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 void fprint_js_str_raw(FILE *out, ant_t *js, jsval_t s) {
if (vtype(s) != T_STR) {
fprintf(out, "%s\n", js_str(js, s));
return;
}
jsoff_t len = 0;
jsoff_t off = vstr(js, s, &len);
if (off > 0 && len > 0) fwrite(&js->mem[off], 1, (size_t)len, out);
if (len == 0 || js->mem[off + len - 1] != '\n') fputc('\n', out);
}
static void log_listener_error(ant_t *js, const char *event_name, jsval_t err) {
jsval_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;
}
jsval_t thrown_value = js->thrown_value;
jsval_t src = (vtype(thrown_value) != T_UNDEF) ? thrown_value : err;
jsval_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;
}
jsval_t name = js_get(js, src, "name");
jsval_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, jsval_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];
jsval_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,
jsval_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 jsval_t make_uint8array_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, "Uint8Array");
}
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 (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->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;
jsval_t stdout_val = js_mkstr(cp->js, cp->stdout_buf ? cp->stdout_buf : "", cp->stdout_len);
jsval_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());
jsval_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) {
jsval_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);
jsval_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 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);
}
jsval_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);
cp->pending_closes++;
uv_close((uv_handle_t *)proc, on_handle_close);
if (!cp->stdin_closed && !uv_is_closing((uv_handle_t *)&cp->stdin_pipe)) {
cp->pending_closes++;
uv_close((uv_handle_t *)&cp->stdin_pipe, on_handle_close);
cp->stdin_closed = true;
}
if (!cp->stdout_closed && !uv_is_closing((uv_handle_t *)&cp->stdout_pipe)) {
uv_read_stop((uv_stream_t *)&cp->stdout_pipe);
cp->pending_closes++;
uv_close((uv_handle_t *)&cp->stdout_pipe, on_handle_close);
cp->stdout_closed = true;
}
if (!cp->stderr_closed && !uv_is_closing((uv_handle_t *)&cp->stderr_pipe)) {
uv_read_stop((uv_stream_t *)&cp->stderr_pipe);
cp->pending_closes++;
uv_close((uv_handle_t *)&cp->stderr_pipe, on_handle_close);
cp->stderr_closed = 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);
buf->len = buf->base ? suggested_size : 0;
}
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;
}
jsval_t text_data = js_mkstr(cp->js, buf->base, nread);
jsval_t text_args[1] = { text_data };
emit_event(cp, "stdout", text_args, 1);
jsval_t byte_data = make_uint8array_chunk(cp->js, buf->base, (size_t)nread);
jsval_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) {
jsval_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);
cp->pending_closes++;
uv_close((uv_handle_t *)stream, on_handle_close);
cp->stdout_closed = 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;
}
jsval_t text_data = js_mkstr(cp->js, buf->base, nread);
jsval_t text_args[1] = { text_data };
emit_event(cp, "stderr", text_args, 1);
jsval_t byte_data = make_uint8array_chunk(cp->js, buf->base, (size_t)nread);
jsval_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) {
jsval_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);
cp->pending_closes++;
uv_close((uv_handle_t *)stream, on_handle_close);
cp->stderr_closed = true;
check_completion(cp);
}
}
static jsval_t child_on(ant_t *js, jsval_t *args, int nargs) {
jsval_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");
jsval_t cp_ptr = js_get_slot(js, 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 jsval_t child_once(ant_t *js, jsval_t *args, int nargs) {
jsval_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");
jsval_t cp_ptr = js_get_slot(js, 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 jsval_t child_kill(ant_t *js, jsval_t *args, int nargs) {
jsval_t this_obj = js_getthis(js);
jsval_t cp_ptr = js_get_slot(js, 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 jsval_t child_write_impl(ant_t *js, child_process_t *cp, jsval_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 {
jsval_t ta_data_val = js_get_slot(js, data_arg, SLOT_BUFFER);
TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
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) {
jsval_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 jsval_t child_end_impl(child_process_t *cp) {
if (!cp->stdin_closed) {
uv_close((uv_handle_t *)&cp->stdin_pipe, NULL);
cp->stdin_closed = true;
}
return js_mkundef();
}
static jsval_t child_write(ant_t *js, jsval_t *args, int nargs) {
jsval_t this_obj = js_getthis(js);
if (nargs < 1) return js_mkerr(js, "write() requires data argument");
jsval_t cp_ptr = js_get_slot(js, 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 jsval_t child_ref(ant_t *js, jsval_t *args, int nargs) {
jsval_t this_obj = js_getthis(js);
jsval_t cp_ptr = js_get_slot(js, 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);
if (!uv_is_closing((uv_handle_t *)&cp->stdin_pipe)) uv_ref((uv_handle_t *)&cp->stdin_pipe);
if (!uv_is_closing((uv_handle_t *)&cp->stdout_pipe)) uv_ref((uv_handle_t *)&cp->stdout_pipe);
if (!uv_is_closing((uv_handle_t *)&cp->stderr_pipe)) uv_ref((uv_handle_t *)&cp->stderr_pipe);
return this_obj;
}
static jsval_t child_unref(ant_t *js, jsval_t *args, int nargs) {
jsval_t this_obj = js_getthis(js);
jsval_t cp_ptr = js_get_slot(js, 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);
if (!uv_is_closing((uv_handle_t *)&cp->stdin_pipe)) uv_unref((uv_handle_t *)&cp->stdin_pipe);
if (!uv_is_closing((uv_handle_t *)&cp->stdout_pipe)) uv_unref((uv_handle_t *)&cp->stdout_pipe);
if (!uv_is_closing((uv_handle_t *)&cp->stderr_pipe)) uv_unref((uv_handle_t *)&cp->stderr_pipe);
return this_obj;
}
static jsval_t child_end(ant_t *js, jsval_t *args, int nargs) {
jsval_t this_obj = js_getthis(js);
jsval_t cp_ptr = js_get_slot(js, 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 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 jsval_t child_stream_write(ant_t *js, jsval_t *args, int nargs) {
jsval_t this_obj = js_getthis(js);
if (nargs < 1) return js_mkerr(js, "write() requires data argument");
jsval_t ctx_ptr = js_get_slot(js, 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 jsval_t child_stream_end(ant_t *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t this_obj = js_getthis(js);
jsval_t ctx_ptr = js_get_slot(js, 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 jsval_t child_stream_ref(ant_t *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t this_obj = js_getthis(js);
jsval_t ctx_ptr = js_get_slot(js, 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 jsval_t child_stream_unref(ant_t *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t this_obj = js_getthis(js);
jsval_t ctx_ptr = js_get_slot(js, 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 jsval_t child_stream_destroy(ant_t *js, jsval_t *args, int nargs) {
(void)args; (void)nargs;
jsval_t this_obj = js_getthis(js);
jsval_t ctx_ptr = js_get_slot(js, 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) {
uv_read_stop((uv_stream_t *)&cp->stdout_pipe);
cp->stdout_closed = true;
} else if (ctx->kind == CHILD_STREAM_STDERR) {
uv_read_stop((uv_stream_t *)&cp->stderr_pipe);
cp->stderr_closed = true;
}
cp->pending_closes++;
uv_close(h, on_handle_close);
check_completion(cp);
}
return this_obj;
}
static jsval_t child_stream_on(ant_t *js, jsval_t *args, int nargs) {
jsval_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");
jsval_t ctx_ptr = js_get_slot(js, 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 jsval_t child_stream_once(ant_t *js, jsval_t *args, int nargs) {
jsval_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");
jsval_t ctx_ptr = js_get_slot(js, 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 jsval_t create_child_stream_object(ant_t *js, child_process_t *cp, child_stream_kind_t kind) {
jsval_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(js, 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 jsval_t create_child_object(ant_t *js, child_process_t *cp) {
jsval_t obj = js_mkobj(js);
js_set_slot(js, 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);
cp->stdin_obj = create_child_stream_object(js, cp, CHILD_STREAM_STDIN);
cp->stdout_obj = create_child_stream_object(js, cp, CHILD_STREAM_STDOUT);
cp->stderr_obj = create_child_stream_object(js, cp, CHILD_STREAM_STDERR);
js_set(js, obj, "stdin", cp->stdin_obj);
js_set(js, obj, "stdout", cp->stdout_obj);
js_set(js, obj, "stderr", cp->stderr_obj);
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, jsval_t arr, int *count) {
jsval_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);
jsval_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 jsval_t builtin_spawn(ant_t *js, jsval_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])) {
jsval_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])) {
jsval_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);
}
jsval_t shell_val = js_get(js, args[2], "shell");
use_shell = js_truthy(js, shell_val);
jsval_t detached_val = js_get(js, args[2], "detached");
detached = js_truthy(js, detached_val);
}
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;
uv_pipe_init(uv_default_loop(), &cp->stdin_pipe, 0);
uv_pipe_init(uv_default_loop(), &cp->stdout_pipe, 0);
uv_pipe_init(uv_default_loop(), &cp->stderr_pipe, 0);
cp->stdin_pipe.data = cp;
cp->stdout_pipe.data = cp;
cp->stderr_pipe.data = cp;
cp->process.data = cp;
uv_stdio_container_t stdio[3];
stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
stdio[0].data.stream = (uv_stream_t *)&cp->stdin_pipe;
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 **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));
}
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->child_obj;
}
static jsval_t builtin_exec(ant_t *js, jsval_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])) {
jsval_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;
uv_pipe_init(uv_default_loop(), &cp->stdin_pipe, 0);
uv_pipe_init(uv_default_loop(), &cp->stdout_pipe, 0);
uv_pipe_init(uv_default_loop(), &cp->stderr_pipe, 0);
cp->stdin_pipe.data = cp;
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) {
jsval_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 jsval_t builtin_execSync(ant_t *js, jsval_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);
}
jsval_t result = js_mkstr(js, output, output_len);
free(output);
return result;
}
#ifdef _WIN32
static jsval_t builtin_spawnSync(ant_t *js, jsval_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])) {
jsval_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])) {
jsval_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);
jsval_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 jsval_t builtin_spawnSync(ant_t *js, jsval_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])) {
jsval_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])) {
jsval_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;
jsval_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 jsval_t builtin_fork(ant_t *js, jsval_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
jsval_t spawn_args[3];
spawn_args[0] = js_mkstr(js, exe_path, strlen(exe_path));
jsval_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])) {
jsval_t exec_args = js_get(js, args[1], "execArgv");
if (is_special_object(exec_args)) {
jsval_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);
jsval_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);
}
jsval_t child_process_library(ant_t *js) {
jsval_t lib = js_mkobj(js);
js_set(js, lib, "spawn", js_mkfun(builtin_spawn));
js_set(js, lib, "exec", js_mkfun(builtin_exec));
js_set(js, lib, "execSync", js_mkfun(builtin_execSync));
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 child_process_gc_update_roots(GC_OP_VAL_ARGS) {
for (child_process_t *cp = pending_children_head; cp; cp = cp->next) {
op_val(ctx, &cp->child_obj);
op_val(ctx, &cp->stdin_obj);
op_val(ctx, &cp->stdout_obj);
op_val(ctx, &cp->stderr_obj);
op_val(ctx, &cp->promise);
child_event_t *evt, *tmp;
HASH_ITER(hh, cp->events, evt, tmp)
for (int i = 0; i < evt->count; i++) op_val(ctx, &evt->listeners[i].callback);
}
}

File Metadata

Mime Type
text/x-c
Expires
Thu, Mar 26, 4:46 PM (2 d)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
511741
Default Alt Text
child_process.c (47 KB)

Event Timeline