#include // IWYU pragma: keep #include #include #include #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #include #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #else #include #include #include #include #include #include #include #include #endif #include "ant.h" #include "errors.h" #include "output.h" #include "utils.h" #include "tty_ctrl.h" #include "internal.h" #include "descriptors.h" #include "runtime.h" #include "silver/engine.h" #include "gc/modules.h" #include "modules/process.h" #include "modules/tty.h" #include "modules/symbol.h" #include "modules/buffer.h" #include "modules/napi.h" #include "modules/timer.h" #include "modules/string_decoder.h" #ifndef _WIN32 extern char **environ; #else #define environ _environ #endif #define DEFAULT_MAX_LISTENERS 10 #define INITIAL_LISTENER_CAPACITY 4 typedef struct { ant_value_t listener; bool once; } ProcessEventListener; typedef struct { char *event_type; ProcessEventListener *listeners; int listener_count; int listener_capacity; int emitting; bool free_deferred; UT_hash_handle hh; } ProcessEventType; static int max_listeners = DEFAULT_MAX_LISTENERS; static ProcessEventType *process_events = NULL; typedef struct { uv_signal_t handle; int signum; UT_hash_handle hh; } SignalHandle; static SignalHandle *signal_handles = NULL; static ProcessEventType *stdin_events = NULL; static ProcessEventType *stdout_events = NULL; static ProcessEventType *stderr_events = NULL; typedef struct { uv_tty_t tty; bool tty_initialized; bool reading; bool keypress_enabled; ant_value_t decoder; int escape_state; int escape_len; char escape_buf[16]; } stdin_state_t; static stdin_state_t stdin_state = {0}; static uint64_t process_start_time = 0; #ifndef _WIN32 static uv_signal_t sigwinch_handle; static bool sigwinch_initialized = false; #endif typedef struct { const char *name; int signum; UT_hash_handle hh_name; UT_hash_handle hh_num; } SignalEntry; static SignalEntry *signals_by_name = NULL; static SignalEntry *signals_by_num = NULL; static void init_signal_map(void) { static bool initialized = false; if (initialized) return; #define S(sig) { #sig, sig, {0}, {0} } static SignalEntry entries[] = { S(SIGINT), S(SIGILL), S(SIGABRT), S(SIGFPE), S(SIGSEGV), S(SIGTERM), #ifdef SIGHUP S(SIGHUP), #endif #ifdef SIGQUIT S(SIGQUIT), #endif #ifdef SIGTRAP S(SIGTRAP), #endif #ifdef SIGKILL S(SIGKILL), #endif #ifdef SIGBUS S(SIGBUS), #endif #ifdef SIGSYS S(SIGSYS), #endif #ifdef SIGPIPE S(SIGPIPE), #endif #ifdef SIGALRM S(SIGALRM), #endif #ifdef SIGURG S(SIGURG), #endif #ifdef SIGSTOP S(SIGSTOP), #endif #ifdef SIGTSTP S(SIGTSTP), #endif #ifdef SIGCONT S(SIGCONT), #endif #ifdef SIGCHLD S(SIGCHLD), #endif #ifdef SIGTTIN S(SIGTTIN), #endif #ifdef SIGTTOU S(SIGTTOU), #endif #ifdef SIGXCPU S(SIGXCPU), #endif #ifdef SIGXFSZ S(SIGXFSZ), #endif #ifdef SIGVTALRM S(SIGVTALRM), #endif #ifdef SIGPROF S(SIGPROF), #endif #ifdef SIGUSR1 S(SIGUSR1), #endif #ifdef SIGUSR2 S(SIGUSR2), #endif #ifdef SIGEMT S(SIGEMT), #endif #ifdef SIGIO S(SIGIO), #endif #ifdef SIGWINCH S(SIGWINCH), #endif #ifdef SIGINFO S(SIGINFO), #endif #ifdef SIGPWR S(SIGPWR), #endif #ifdef SIGSTKFLT S(SIGSTKFLT), #endif #ifdef SIGPOLL S(SIGPOLL), #endif }; #undef S size_t count = sizeof(entries) / sizeof(entries[0]); for (size_t i = 0; i < count; i++) { HASH_ADD_KEYPTR(hh_name, signals_by_name, entries[i].name, strlen(entries[i].name), &entries[i]); HASH_ADD(hh_num, signals_by_num, signum, sizeof(int), &entries[i]); } initialized = true; } static int get_signal_number(const char *name) { init_signal_map(); SignalEntry *entry = NULL; HASH_FIND(hh_name, signals_by_name, name, strlen(name), entry); return entry ? entry->signum : -1; } static const char *get_signal_name(int signum) { init_signal_map(); SignalEntry *entry = NULL; HASH_FIND(hh_num, signals_by_num, &signum, sizeof(int), entry); return entry ? entry->name : NULL; } static ProcessEventType *find_or_create_event(ProcessEventType **table, const char *event_type) { ProcessEventType *evt = NULL; HASH_FIND_STR(*table, event_type, evt); if (evt == NULL) { evt = malloc(sizeof(ProcessEventType)); *evt = (ProcessEventType){ .event_type = strdup(event_type), .emitting = 0, .listener_count = 0, .free_deferred = false, .listener_capacity = INITIAL_LISTENER_CAPACITY, .listeners = malloc(sizeof(ProcessEventListener) * INITIAL_LISTENER_CAPACITY), }; HASH_ADD_KEYPTR(hh, *table, evt->event_type, strlen(evt->event_type), evt); } return evt; } static bool ensure_listener_capacity(ProcessEventType *evt) { if (evt->listener_count >= evt->listener_capacity) { int new_capacity = evt->listener_capacity * 2; ProcessEventListener *new_listeners = realloc(evt->listeners, sizeof(ProcessEventListener) * new_capacity); if (!new_listeners) return false; evt->listeners = new_listeners; evt->listener_capacity = new_capacity; } return true; } static void free_event_type(ProcessEventType **events, ProcessEventType *evt) { if (!evt) return; if (evt->emitting > 0) { evt->free_deferred = true; return; } HASH_DEL(*events, evt); free(evt->listeners); free(evt->event_type); free(evt); } static void check_listener_warning(const char *event) { ProcessEventType *evt = NULL; HASH_FIND_STR(process_events, event, evt); if (evt && evt->listener_count == max_listeners) fprintf(stderr, "Warning: Possible EventEmitter memory leak detected. " "%d '%s' listeners added. Use process.setMaxListeners() to increase limit.\n", evt->listener_count, event ); } static void uv_signal_handler(uv_signal_t *handle, int signum) { const char *name = get_signal_name(signum); if (name) { ant_value_t sig_arg = js_mkstr(rt->js, name, strlen(name)); emit_process_event(name, &sig_arg, 1); } } static void start_signal_watch(int signum) { SignalHandle *sh = NULL; HASH_FIND_INT(signal_handles, &signum, sh); if (sh) return; sh = malloc(sizeof(SignalHandle)); sh->signum = signum; uv_signal_init(uv_default_loop(), &sh->handle); uv_signal_start(&sh->handle, uv_signal_handler, signum); uv_unref((uv_handle_t *)&sh->handle); HASH_ADD_INT(signal_handles, signum, sh); } static void on_signal_handle_close(uv_handle_t *handle) { SignalHandle *sh = (SignalHandle *)handle; free(sh); } static void stop_signal_watch(int signum) { SignalHandle *sh = NULL; HASH_FIND_INT(signal_handles, &signum, sh); if (!sh) return; HASH_DEL(signal_handles, sh); uv_signal_stop(&sh->handle); uv_close((uv_handle_t *)&sh->handle, on_signal_handle_close); } void emit_process_event(const char *event_type, ant_value_t *args, int nargs) { if (!rt->js) return; ProcessEventType *evt = NULL; HASH_FIND_STR(process_events, event_type, evt); if (evt == NULL || evt->listener_count == 0) return; evt->emitting++; int i = 0; while (i < evt->listener_count) { ProcessEventListener *listener = &evt->listeners[i]; sv_vm_call(rt->js->vm, rt->js, listener->listener, js_mkundef(), args, nargs, NULL, false); if (listener->once) { for (int j = i; j < evt->listener_count - 1; j++) { evt->listeners[j] = evt->listeners[j + 1]; } evt->listener_count--; } else i++; } evt->emitting--; if (evt->listener_count == 0 || evt->free_deferred) { int signum = get_signal_number(event_type); if (signum > 0) stop_signal_watch(signum); if (evt->emitting == 0) free_event_type(&process_events, evt); } } bool process_has_event_listeners(const char *event_type) { ProcessEventType *evt = NULL; if (!event_type) return false; HASH_FIND_STR(process_events, event_type, evt); return evt && evt->listener_count > 0; } static void emit_stdio_event(ProcessEventType **events, const char *event_type, ant_value_t *args, int nargs) { if (!rt->js) return; ProcessEventType *evt = NULL; HASH_FIND_STR(*events, event_type, evt); if (evt == NULL || evt->listener_count == 0) return; evt->emitting++; int i = 0; while (i < evt->listener_count) { ProcessEventListener *listener = &evt->listeners[i]; sv_vm_call(rt->js->vm, rt->js, listener->listener, js_mkundef(), args, nargs, NULL, false); if (listener->once) { for (int j = i; j < evt->listener_count - 1; j++) evt->listeners[j] = evt->listeners[j + 1]; evt->listener_count--; } else i++; } evt->emitting--; if ((evt->listener_count == 0 || evt->free_deferred) && evt->emitting == 0) free_event_type(events, evt); } static const char *stdin_escape_name(const char *seq, int len) { if (len < 2) return NULL; if (seq[0] == '[') { if (seq[1] >= '0' && seq[1] <= '9') { int num = 0; int idx = 1; while (idx < len && seq[idx] >= '0' && seq[idx] <= '9') { num = num * 10 + (seq[idx] - '0'); idx++; } if (idx < len && seq[idx] == '~') { typedef struct { int code; const char *name; } esc_num_map_t; static const esc_num_map_t esc_num_map[] = { { 1, "home" }, { 2, "insert" }, { 3, "delete" }, { 4, "end" }, { 5, "pageup" }, { 6, "pagedown" }, { 7, "home" }, { 8, "end" }, { 15, "f5" }, { 17, "f6" }, { 18, "f7" }, { 19, "f8" }, { 20, "f9" }, { 21, "f10" }, { 23, "f11" }, { 24, "f12" }, }; for (size_t i = 0; i < sizeof(esc_num_map) / sizeof(esc_num_map[0]); i++) { if (esc_num_map[i].code == num) return esc_num_map[i].name; } } return NULL; } typedef struct { char code; bool needs_tilde; const char *name; } esc_map_t; static const esc_map_t esc_map[] = { { 'A', false, "up" }, { 'B', false, "down" }, { 'C', false, "right" }, { 'D', false, "left" }, { 'H', false, "home" }, { 'F', false, "end" }, { 'Z', false, "tab" }, { '2', true, "insert" }, { '3', true, "delete" }, { '5', true, "pageup" }, { '6', true, "pagedown" }, }; for (size_t i = 0; i < sizeof(esc_map) / sizeof(esc_map[0]); i++) { if (seq[1] != esc_map[i].code) continue; if (esc_map[i].needs_tilde) { return (len >= 3 && seq[2] == '~') ? esc_map[i].name : NULL; } return esc_map[i].name; } return NULL; } if (seq[0] == 'O') { switch (seq[1]) { case 'P': return "f1"; case 'Q': return "f2"; case 'R': return "f3"; case 'S': return "f4"; default: return NULL; } } return NULL; } static void emit_keypress_event( ant_t *js, const char *str, size_t str_len, const char *name, bool ctrl, bool meta, bool shift, const char *sequence, size_t sequence_len ) { ant_value_t str_val = js_mkstr(js, str ? str : "", str ? str_len : 0); ant_value_t key_obj = js_mkobj(js); if (name) { js_set(js, key_obj, "name", js_mkstr(js, name, strlen(name))); } else { js_set(js, key_obj, "name", js_mkundef()); } js_set(js, key_obj, "ctrl", js_bool(ctrl)); js_set(js, key_obj, "meta", js_bool(meta)); js_set(js, key_obj, "shift", js_bool(shift)); if (sequence) { js_set(js, key_obj, "sequence", js_mkstr(js, sequence, sequence_len)); } ant_value_t args[2] = { str_val, key_obj }; emit_stdio_event(&stdin_events, "keypress", args, 2); } static void process_keypress_data(ant_t *js, const char *data, size_t len) { for (size_t i = 0; i < len; i++) { unsigned char c = (unsigned char)data[i]; if (stdin_state.escape_state == 1) { stdin_state.escape_buf[stdin_state.escape_len++] = (char)c; if (c == '[' || c == 'O') { stdin_state.escape_state = 2; continue; } emit_keypress_event(js, "\x1b", 1, "escape", false, false, false, "\x1b", 1); stdin_state.escape_state = 0; stdin_state.escape_len = 0; } if (stdin_state.escape_state == 2) { stdin_state.escape_buf[stdin_state.escape_len++] = (char)c; if ((c >= 'A' && c <= 'Z') || c == '~' || stdin_state.escape_len >= 15) { char sequence[18]; size_t seq_len = 0; sequence[seq_len++] = '\x1b'; memcpy(sequence + seq_len, stdin_state.escape_buf, (size_t)stdin_state.escape_len); seq_len += (size_t)stdin_state.escape_len; const char *name = stdin_escape_name(stdin_state.escape_buf, stdin_state.escape_len); if (!name) name = "escape"; emit_keypress_event(js, "", 0, name, false, false, false, sequence, seq_len); stdin_state.escape_state = 0; stdin_state.escape_len = 0; } continue; } if (c == 27) { stdin_state.escape_state = 1; stdin_state.escape_len = 0; continue; } if (c == '\r' || c == '\n') { emit_keypress_event(js, "\r", 1, "return", false, false, false, "\r", 1); continue; } if (c == 127 || c == 8) { emit_keypress_event(js, "", 0, "backspace", false, false, false, NULL, 0); continue; } if (c == '\t') { emit_keypress_event(js, "\t", 1, "tab", false, false, false, "\t", 1); continue; } if (c < 32) { char name_buf[2] = { (char)('a' + c - 1), '\0' }; char seq = (char)c; emit_keypress_event(js, &seq, 1, name_buf, true, false, false, &seq, 1); continue; } char ch = (char)c; char name_buf[2] = { ch, '\0' }; emit_keypress_event(js, &ch, 1, name_buf, false, false, false, &ch, 1); } if (stdin_state.escape_state == 1) { emit_keypress_event(js, "\x1b", 1, "escape", false, false, false, "\x1b", 1); stdin_state.escape_state = 0; stdin_state.escape_len = 0; } } static bool remove_listener_from_events(ProcessEventType **events, const char *event, ant_value_t listener) { ProcessEventType *evt = NULL; HASH_FIND_STR(*events, event, evt); if (!evt) return false; for (int i = 0; i < evt->listener_count; i++) { if (evt->listeners[i].listener != listener) continue; memmove( &evt->listeners[i], &evt->listeners[i + 1], (size_t)(evt->listener_count - i - 1) * sizeof(ProcessEventListener) ); if (--evt->listener_count == 0) { free_event_type(events, evt); return true; } return false; } return false; } static bool stdin_is_tty(void) { return uv_guess_handle(STDIN_FILENO) == UV_TTY; } static bool stdout_is_tty(void) { return uv_guess_handle(STDOUT_FILENO) == UV_TTY; } static bool stderr_is_tty(void) { return uv_guess_handle(STDERR_FILENO) == UV_TTY; } static void get_tty_size(int fd, int *rows, int *cols) { int out_rows = 24, out_cols = 80; #ifndef _WIN32 struct winsize ws; if (ioctl(fd, TIOCGWINSZ, &ws) == 0) { if (ws.ws_row > 0) out_rows = ws.ws_row; if (ws.ws_col > 0) out_cols = ws.ws_col; } #else CONSOLE_SCREEN_BUFFER_INFO csbi; if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { int width = csbi.srWindow.Right - csbi.srWindow.Left + 1; int height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; if (height > 0) out_rows = height; if (width > 0) out_cols = width; } #endif if (rows) *rows = out_rows; if (cols) *cols = out_cols; } static bool stdin_set_raw_mode(bool enable) { if (!stdin_is_tty()) return false; return tty_set_raw_mode(STDIN_FILENO, enable); } static void stdin_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 = (ULONG)suggested_size; #else buf->len = suggested_size; #endif } static void on_stdin_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { (void)stream; if (nread > 0 && rt->js) { ArrayBufferData *ab = create_array_buffer_data((size_t)nread); if (ab) memcpy(ab->data, buf->base, (size_t)nread); ant_value_t raw_val = ab ? create_typed_array(rt->js, TYPED_ARRAY_UINT8, ab, 0, (size_t)nread, "Buffer") : js_mkstr(rt->js, buf->base, (size_t)nread); ant_value_t data_val = is_object_type(stdin_state.decoder) ? string_decoder_decode_value(rt->js, stdin_state.decoder, raw_val, false) : raw_val; if (is_err(data_val)) data_val = raw_val; emit_stdio_event(&stdin_events, "data", &data_val, 1); if (stdin_state.keypress_enabled) process_keypress_data(rt->js, buf->base, (size_t)nread); } if (buf->base) free(buf->base); } static void stdin_start_reading(void) { if (stdin_state.reading) return; if (!stdin_state.tty_initialized) { uv_loop_t *loop = uv_default_loop(); if (uv_tty_init(loop, &stdin_state.tty, STDIN_FILENO, 1) != 0) return; stdin_state.tty.data = NULL; stdin_state.tty_initialized = true; uv_unref((uv_handle_t *)&stdin_state.tty); } uv_ref((uv_handle_t *)&stdin_state.tty); stdin_state.reading = true; uv_read_start((uv_stream_t *)&stdin_state.tty, stdin_alloc_buffer, on_stdin_read); } static void stdin_stop_reading(void) { if (!stdin_state.reading) return; uv_read_stop((uv_stream_t *)&stdin_state.tty); stdin_state.reading = false; uv_unref((uv_handle_t *)&stdin_state.tty); } #ifndef _WIN32 static void on_sigwinch(uv_signal_t *handle, int signum) { if (!rt->js) return; ant_value_t process_obj = js_get(rt->js, js_glob(rt->js), "process"); if (!is_special_object(process_obj)) return; ant_value_t stdout_obj = js_get(rt->js, process_obj, "stdout"); if (!is_special_object(stdout_obj)) return; emit_stdio_event(&stdout_events, "resize", NULL, 0); } #endif static void start_sigwinch_handler(void) { #ifndef _WIN32 if (sigwinch_initialized) return; uv_loop_t *loop = uv_default_loop(); if (uv_signal_init(loop, &sigwinch_handle) != 0) return; if (uv_signal_start(&sigwinch_handle, on_sigwinch, SIGWINCH) != 0) { uv_close((uv_handle_t *)&sigwinch_handle, NULL); return; } uv_unref((uv_handle_t *)&sigwinch_handle); sigwinch_initialized = true; #endif } static ant_value_t js_stdin_set_raw_mode(ant_t *js, ant_value_t *args, int nargs) { bool enable = nargs > 0 ? js_truthy(js, args[0]) : true; return js_bool(stdin_set_raw_mode(enable)); } static ant_value_t js_stdin_set_encoding(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t encoding = nargs > 0 && !is_undefined(args[0]) ? args[0] : js_mkstr(js, "utf8", 4); ant_value_t decoder = string_decoder_create(js, encoding); ant_value_t encoding_str = 0; if (is_err(decoder)) return decoder; encoding_str = js_tostring_val(js, encoding); if (is_err(encoding_str)) return encoding_str; stdin_state.decoder = decoder; js_set(js, this_obj, "encoding", encoding_str); return this_obj; } static ant_value_t js_stdin_resume(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; stdin_start_reading(); return js_getthis(js); } static ant_value_t js_stdin_pause(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; stdin_stop_reading(); return js_getthis(js); } static ant_value_t js_stdin_on(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event || vtype(args[1]) != T_FUNC) return this_obj; ProcessEventType *evt = find_or_create_event(&stdin_events, event); if (!ensure_listener_capacity(evt)) return this_obj; evt->listeners[evt->listener_count].listener = args[1]; evt->listeners[evt->listener_count].once = false; evt->listener_count++; if (strcmp(event, "data") == 0) stdin_start_reading(); return this_obj; } static ant_value_t js_stdin_remove_all_listeners(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 1) { ProcessEventType *evt, *tmp; HASH_ITER(hh, stdin_events, evt, tmp) { free_event_type(&stdin_events, evt); } stdin_stop_reading(); return this_obj; } char *event = js_getstr(js, args[0], NULL); if (!event) return this_obj; ProcessEventType *evt = NULL; HASH_FIND_STR(stdin_events, event, evt); if (evt) free_event_type(&stdin_events, evt); if (strcmp(event, "data") == 0) stdin_stop_reading(); return this_obj; } static ant_value_t js_stdin_remove_listener(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event) return this_obj; bool now_empty = remove_listener_from_events(&stdin_events, event, args[1]); if (now_empty && strcmp(event, "data") == 0) stdin_stop_reading(); return this_obj; } static ant_value_t process_write_stream(ant_t *js, ant_value_t *args, int nargs, FILE *stream, int fd) { if (nargs < 1) return js_false; size_t len = 0; char *data = js_getstr(js, args[0], &len); ant_output_stream_t *out = NULL; if (!data) return js_false; if (uv_guess_handle(fd) == UV_TTY) { return js_bool(tty_ctrl_write_fd(fd, data, len)); } out = ant_output_stream(stream); ant_output_stream_begin(out); if (!ant_output_stream_append(out, data, len)) return js_false; return ant_output_stream_flush(out) ? js_true : js_false; } static ant_value_t js_stdout_write(ant_t *js, ant_value_t *args, int nargs) { return process_write_stream(js, args, nargs, stdout, STDOUT_FILENO); } static ant_value_t js_stdout_on(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event || vtype(args[1]) != T_FUNC) return this_obj; ProcessEventType *evt = find_or_create_event(&stdout_events, event); if (!ensure_listener_capacity(evt)) return this_obj; evt->listeners[evt->listener_count].listener = args[1]; evt->listeners[evt->listener_count].once = false; evt->listener_count++; if (strcmp(event, "resize") == 0) start_sigwinch_handler(); return this_obj; } static ant_value_t js_stdout_once(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event || vtype(args[1]) != T_FUNC) return this_obj; ProcessEventType *evt = find_or_create_event(&stdout_events, event); if (!ensure_listener_capacity(evt)) return this_obj; evt->listeners[evt->listener_count].listener = args[1]; evt->listeners[evt->listener_count].once = true; evt->listener_count++; if (strcmp(event, "resize") == 0) start_sigwinch_handler(); return this_obj; } static ant_value_t js_stdout_remove_all_listeners(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 1) { ProcessEventType *evt, *tmp; HASH_ITER(hh, stdout_events, evt, tmp) { free_event_type(&stdout_events, evt); } return this_obj; } char *event = js_getstr(js, args[0], NULL); if (!event) return this_obj; ProcessEventType *evt = NULL; HASH_FIND_STR(stdout_events, event, evt); if (evt) free_event_type(&stdout_events, evt); return this_obj; } static ant_value_t js_stdout_remove_listener(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event) return this_obj; remove_listener_from_events(&stdout_events, event, args[1]); return this_obj; } static ant_value_t js_stdout_get_window_size(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; int rows = 0, cols = 0; get_tty_size(STDOUT_FILENO, &rows, &cols); ant_value_t arr = js_mkarr(js); js_arr_push(js, arr, js_mknum(cols)); js_arr_push(js, arr, js_mknum(rows)); return arr; } static ant_value_t js_stdout_rows_getter(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; int rows = 0, cols = 0; get_tty_size(STDOUT_FILENO, &rows, &cols); return js_mknum(rows); } static ant_value_t js_stdout_columns_getter(ant_t *js, ant_value_t *args, int nargs) { int rows = 0, cols = 0; get_tty_size(STDOUT_FILENO, &rows, &cols); return js_mknum(cols); } static ant_value_t js_stderr_write(ant_t *js, ant_value_t *args, int nargs) { return process_write_stream(js, args, nargs, stderr, STDERR_FILENO); } static ant_value_t js_stderr_on(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event || vtype(args[1]) != T_FUNC) return this_obj; ProcessEventType *evt = find_or_create_event(&stderr_events, event); if (!ensure_listener_capacity(evt)) return this_obj; evt->listeners[evt->listener_count].listener = args[1]; evt->listeners[evt->listener_count].once = false; evt->listener_count++; return this_obj; } static ant_value_t js_stderr_once(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event || vtype(args[1]) != T_FUNC) return this_obj; ProcessEventType *evt = find_or_create_event(&stderr_events, event); if (!ensure_listener_capacity(evt)) return this_obj; evt->listeners[evt->listener_count].listener = args[1]; evt->listeners[evt->listener_count].once = true; evt->listener_count++; return this_obj; } static ant_value_t js_stderr_remove_all_listeners(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 1) { ProcessEventType *evt, *tmp; HASH_ITER(hh, stderr_events, evt, tmp) { free_event_type(&stderr_events, evt); } return this_obj; } char *event = js_getstr(js, args[0], NULL); if (!event) return this_obj; ProcessEventType *evt = NULL; HASH_FIND_STR(stderr_events, event, evt); if (evt) free_event_type(&stderr_events, evt); return this_obj; } static ant_value_t js_stderr_remove_listener(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); if (nargs < 2) return this_obj; char *event = js_getstr(js, args[0], NULL); if (!event) return this_obj; remove_listener_from_events(&stderr_events, event, args[1]); return this_obj; } static ant_value_t process_uptime(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; uint64_t now = uv_hrtime(); double seconds = (double)(now - process_start_time) / 1e9; return js_mknum(seconds); } static ant_value_t process_hrtime(ant_t *js, ant_value_t *args, int nargs) { uint64_t now = uv_hrtime(); if (nargs > 0 && vtype(args[0]) == T_ARR) { ant_value_t prev_sec = js_get(js, args[0], "0"); ant_value_t prev_nsec = js_get(js, args[0], "1"); if (vtype(prev_sec) == T_NUM && vtype(prev_nsec) == T_NUM) { uint64_t prev = (uint64_t)js_getnum(prev_sec) * 1000000000ULL + (uint64_t)js_getnum(prev_nsec); now = now - prev; } } ant_value_t arr = js_mkarr(js); uint64_t secs = now / 1000000000ULL; uint64_t nsecs = now % 1000000000ULL; js_arr_push(js, arr, js_mknum((double)secs)); js_arr_push(js, arr, js_mknum((double)nsecs)); return arr; } static ant_value_t process_hrtime_bigint(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; uint64_t now = uv_hrtime(); char buf[32]; snprintf(buf, sizeof(buf), "%llu", (unsigned long long)now); return js_mkbigint(js, buf, strlen(buf), false); } static ant_value_t process_memory_usage(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; ant_value_t obj = js_mkobj(js); size_t rss = 0; uv_resident_set_memory(&rss); js_set(js, obj, "rss", js_mknum((double)rss)); #ifdef _WIN32 PROCESS_MEMORY_COUNTERS_EX pmc; if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) { js_set(js, obj, "heapTotal", js_mknum((double)pmc.WorkingSetSize)); js_set(js, obj, "heapUsed", js_mknum((double)pmc.PrivateUsage)); } else { js_set(js, obj, "heapTotal", js_mknum(0)); js_set(js, obj, "heapUsed", js_mknum(0)); } #else struct rusage usage; if (getrusage(RUSAGE_SELF, &usage) == 0) { js_set(js, obj, "heapTotal", js_mknum((double)rss)); js_set(js, obj, "heapUsed", js_mknum((double)rss)); } else { js_set(js, obj, "heapTotal", js_mknum(0)); js_set(js, obj, "heapUsed", js_mknum(0)); } #endif js_set(js, obj, "external", js_mknum(0)); js_set(js, obj, "arrayBuffers", js_mknum(0)); return obj; } static ant_value_t process_memory_usage_rss(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; size_t rss = 0; uv_resident_set_memory(&rss); return js_mknum((double)rss); } static ant_value_t process_cpu_usage(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_mkobj(js); uv_rusage_t rusage; if (uv_getrusage(&rusage) == 0) { int64_t user_usec = rusage.ru_utime.tv_sec * 1000000LL + rusage.ru_utime.tv_usec; int64_t sys_usec = rusage.ru_stime.tv_sec * 1000000LL + rusage.ru_stime.tv_usec; if (nargs > 0 && is_special_object(args[0])) { ant_value_t prev_user = js_get(js, args[0], "user"); ant_value_t prev_system = js_get(js, args[0], "system"); if (vtype(prev_user) == T_NUM) user_usec -= (int64_t)js_getnum(prev_user); if (vtype(prev_system) == T_NUM) sys_usec -= (int64_t)js_getnum(prev_system); } js_set(js, obj, "user", js_mknum((double)user_usec)); js_set(js, obj, "system", js_mknum((double)sys_usec)); } else { js_set(js, obj, "user", js_mknum(0)); js_set(js, obj, "system", js_mknum(0)); } return obj; } static ant_value_t process_kill(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "process.kill requires at least 1 argument"); if (vtype(args[0]) != T_NUM) return js_mkerr(js, "pid must be a number"); int pid = (int)js_getnum(args[0]); int sig = SIGTERM; if (nargs > 1) { if (vtype(args[1]) == T_NUM) { sig = (int)js_getnum(args[1]); } else if (vtype(args[1]) == T_STR) { char *sig_name = js_getstr(js, args[1], NULL); if (sig_name) { int signum = get_signal_number(sig_name); if (signum > 0) sig = signum; else return js_mkerr(js, "Unknown signal"); } } } int result = uv_kill(pid, sig); if (result != 0) return js_mkerr(js, "Failed to send signal"); return js_true; } static ant_value_t process_abort(ant_params_t) { abort(); return js_mkundef(); } static ant_value_t process_chdir(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "process.chdir requires 1 argument"); char *dir = js_getstr(js, args[0], NULL); if (!dir) return js_mkerr(js, "directory must be a string"); int result = uv_chdir(dir); if (result != 0) return js_mkerr(js, "ENOENT: no such file or directory, chdir"); return js_mkundef(); } static ant_value_t process_umask(ant_t *js, ant_value_t *args, int nargs) { #ifdef _WIN32 (void)args; (void)nargs; return js_mknum(0); #else if (nargs > 0 && vtype(args[0]) == T_NUM) { int new_mask = (int)js_getnum(args[0]); int old_mask = umask((mode_t)new_mask); return js_mknum(old_mask); } int cur = umask(0); umask((mode_t)cur); return js_mknum(cur); #endif } #ifndef _WIN32 static ant_value_t process_getuid(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; return js_mknum((double)getuid()); } static ant_value_t process_geteuid(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; return js_mknum((double)geteuid()); } static ant_value_t process_getgid(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; return js_mknum((double)getgid()); } static ant_value_t process_getegid(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; return js_mknum((double)getegid()); } static ant_value_t process_getgroups(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; int ngroups = getgroups(0, NULL); if (ngroups < 0) return js_mkarr(js); gid_t *groups = malloc(sizeof(gid_t) * (size_t)ngroups); if (!groups) return js_mkarr(js); ngroups = getgroups(ngroups, groups); ant_value_t arr = js_mkarr(js); for (int i = 0; i < ngroups; i++) { js_arr_push(js, arr, js_mknum((double)groups[i])); } free(groups); return arr; } static ant_value_t process_setuid(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "process.setuid requires 1 argument"); uid_t uid; if (vtype(args[0]) == T_NUM) { uid = (uid_t)js_getnum(args[0]); } else if (vtype(args[0]) == T_STR) { char *name = js_getstr(js, args[0], NULL); struct passwd *pwd = getpwnam(name); if (!pwd) return js_mkerr(js, "setuid user not found"); uid = pwd->pw_uid; } else { return js_mkerr(js, "uid must be a number or string"); } if (setuid(uid) != 0) return js_mkerr(js, "setuid failed"); return js_mkundef(); } static ant_value_t process_setgid(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "process.setgid requires 1 argument"); gid_t gid; if (vtype(args[0]) == T_NUM) { gid = (gid_t)js_getnum(args[0]); } else if (vtype(args[0]) == T_STR) { char *name = js_getstr(js, args[0], NULL); struct group *grp = getgrnam(name); if (!grp) return js_mkerr(js, "setgid group not found"); gid = grp->gr_gid; } else { return js_mkerr(js, "gid must be a number or string"); } if (setgid(gid) != 0) return js_mkerr(js, "setgid failed"); return js_mkundef(); } static ant_value_t process_seteuid(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "process.seteuid requires 1 argument"); uid_t uid; if (vtype(args[0]) == T_NUM) { uid = (uid_t)js_getnum(args[0]); } else if (vtype(args[0]) == T_STR) { char *name = js_getstr(js, args[0], NULL); struct passwd *pwd = getpwnam(name); if (!pwd) return js_mkerr(js, "seteuid user not found"); uid = pwd->pw_uid; } else { return js_mkerr(js, "uid must be a number or string"); } if (seteuid(uid) != 0) return js_mkerr(js, "seteuid failed"); return js_mkundef(); } static ant_value_t process_setegid(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "process.setegid requires 1 argument"); gid_t gid; if (vtype(args[0]) == T_NUM) { gid = (gid_t)js_getnum(args[0]); } else if (vtype(args[0]) == T_STR) { char *name = js_getstr(js, args[0], NULL); struct group *grp = getgrnam(name); if (!grp) return js_mkerr(js, "setegid group not found"); gid = grp->gr_gid; } else { return js_mkerr(js, "gid must be a number or string"); } if (setegid(gid) != 0) return js_mkerr(js, "setegid failed"); return js_mkundef(); } static ant_value_t process_setgroups(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1 || vtype(args[0]) != T_ARR) { return js_mkerr(js, "process.setgroups requires an array"); } ant_value_t len_val = js_get(js, args[0], "length"); int len = (int)js_getnum(len_val); gid_t *groups = malloc(sizeof(gid_t) * (size_t)len); if (!groups) return js_mkerr(js, "allocation failed"); for (int i = 0; i < len; i++) { char idx[16]; snprintf(idx, sizeof(idx), "%d", i); ant_value_t val = js_get(js, args[0], idx); if (vtype(val) == T_NUM) { groups[i] = (gid_t)js_getnum(val); } else if (vtype(val) == T_STR) { char *name = js_getstr(js, val, NULL); struct group *grp = getgrnam(name); if (!grp) { free(groups); return js_mkerr(js, "group not found"); } groups[i] = grp->gr_gid; } else { free(groups); return js_mkerr(js, "group id must be number or string"); } } if (setgroups(len, groups) != 0) { free(groups); return js_mkerr(js, "setgroups failed"); } free(groups); return js_mkundef(); } static ant_value_t process_initgroups(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 2) return js_mkerr(js, "process.initgroups requires 2 arguments"); char *user = js_getstr(js, args[0], NULL); if (!user) return js_mkerr(js, "user must be a string"); gid_t gid; if (vtype(args[1]) == T_NUM) { gid = (gid_t)js_getnum(args[1]); } else if (vtype(args[1]) == T_STR) { char *name = js_getstr(js, args[1], NULL); struct group *grp = getgrnam(name); if (!grp) return js_mkerr(js, "group not found"); gid = grp->gr_gid; } else { return js_mkerr(js, "gid must be a number or string"); } if (initgroups(user, gid) != 0) return js_mkerr(js, "initgroups failed"); return js_mkundef(); } #endif static ant_value_t env_getter(ant_t *js, ant_value_t obj, const char *key, size_t key_len) { CSTR_BUF(buf, 256); char *key_str = CSTR_INIT(buf, key, key_len); if (!key_str) return js_mkundef(); char *value = getenv(key_str); cstr_free(&buf); if (value == NULL) return js_mkundef(); return js_mkstr(js, value, strlen(value)); } static bool env_setter(ant_t *js, ant_value_t obj, const char *key, size_t key_len, ant_value_t value) { ant_value_t str_val = coerce_to_str(js, value); if (is_err(str_val)) return false; setprop_cstr(js, obj, key, key_len, str_val); CSTR_BUF(buf, 256); char *key_str = CSTR_INIT(buf, key, key_len); if (key_str) { size_t val_len; char *val_str = js_getstr(js, str_val, &val_len); if (val_str) setenv(key_str, val_str, 1); cstr_free(&buf); } return true; } static bool env_deleter(ant_t *js, ant_value_t obj, const char *key, size_t key_len) { CSTR_BUF(buf, 256); char *key_str = CSTR_INIT(buf, key, key_len); if (key_str) { unsetenv(key_str); cstr_free(&buf); } return true; } static void load_dotenv_file(ant_t *js, ant_value_t env_obj) { FILE *fp = fopen(".env", "r"); if (fp == NULL) return; char line[1024]; while (fgets(line, sizeof(line), fp) != NULL) { size_t len = strlen(line); if (len > 0 && line[len - 1] == '\n') { line[len - 1] = '\0'; len--; } if (len > 0 && line[len - 1] == '\r') { line[len - 1] = '\0'; len--; } if (len == 0 || line[0] == '#') continue; char *equals = strchr(line, '='); if (equals == NULL) continue; *equals = '\0'; char *key = line; char *value = equals + 1; while (*key == ' ' || *key == '\t') key++; char *key_end = key + strlen(key) - 1; while (key_end > key && (*key_end == ' ' || *key_end == '\t')) { *key_end = '\0'; key_end--; } while (*value == ' ' || *value == '\t') value++; char *value_end = value + strlen(value) - 1; while (value_end > value && (*value_end == ' ' || *value_end == '\t')) { *value_end = '\0'; value_end--; } if (strlen(value) >= 2 && ((value[0] == '"' && value[strlen(value) - 1] == '"') || (value[0] == '\'' && value[strlen(value) - 1] == '\''))) { value[strlen(value) - 1] = '\0'; value++; } js_set(js, env_obj, key, js_mkstr(js, value, strlen(value))); } fclose(fp); } static ant_value_t process_exit(ant_t *js, ant_value_t *args, int nargs) { int code = 0; if (nargs > 0 && vtype(args[0]) == T_NUM) { code = (int)js_getnum(args[0]); } exit(code); return js_mkundef(); } typedef struct { char *buf; size_t pos; size_t cap; } env_str_ctx; typedef void (*env_iter_cb)( ant_t *js, const char *key, size_t key_len, const char *value, size_t val_len, void *ctx ); static void env_foreach(ant_t *js, ant_value_t env_obj, env_iter_cb cb, void *ctx) { for (char **env = environ; *env != NULL; env++) { char *entry = *env; char *equals = strchr(entry, '='); if (equals == NULL) continue; size_t key_len = (size_t)(equals - entry); char *value = equals + 1; cb(js, entry, key_len, value, strlen(value), ctx); } ant_iter_t iter = js_prop_iter_begin(js, env_obj); const char *key; size_t key_len; ant_value_t value; while (js_prop_iter_next(&iter, &key, &key_len, &value)) { if (vtype(value) != T_STR) continue; CSTR_BUF(buf, 256); char *key_str = CSTR_INIT(buf, key, key_len); if (!key_str) continue; if (getenv(key_str)) { cstr_free(&buf); continue; } cstr_free(&buf); size_t val_len; char *val_str = js_getstr(js, value, &val_len); cb(js, key, key_len, val_str ? val_str : "", val_str ? val_len : 0, ctx); } js_prop_iter_end(&iter); } static void env_to_object_cb(ant_t *js, const char *key, size_t key_len, const char *value, size_t val_len, void *ctx) { ant_value_t obj = *(ant_value_t *)ctx; CSTR_BUF(buf, 256); char *key_str = CSTR_INIT(buf, key, key_len); if (!key_str) return; js_set(js, obj, key_str, js_mkstr(js, value, val_len)); cstr_free(&buf); } static ant_value_t env_to_object(ant_t *js, ant_value_t *args, int nargs) { ant_value_t obj = js_mkobj(js); env_foreach(js, js->this_val, env_to_object_cb, &obj); return obj; } static void env_tostring_cb(ant_t *js, const char *key, size_t key_len, const char *value, size_t val_len, void *ctx) { env_str_ctx *c = ctx; size_t entry_len = key_len + 1 + val_len; if (c->pos + entry_len + 2 >= c->cap) { size_t new_cap = c->cap * 2 + entry_len; char *new_buf = realloc(c->buf, new_cap); if (!new_buf) return; c->buf = new_buf; c->cap = new_cap; } if (c->pos > 0) c->buf[c->pos++] = '\n'; memcpy(c->buf + c->pos, key, key_len); c->pos += key_len; c->buf[c->pos++] = '='; memcpy(c->buf + c->pos, value, val_len); c->pos += val_len; } static ant_value_t env_toString(ant_t *js, ant_value_t *args, int nargs) { env_str_ctx ctx = { .buf = malloc(4096), .pos = 0, .cap = 4096 }; if (!ctx.buf) return js_mkstr(js, "", 0); env_foreach(js, js->this_val, env_tostring_cb, &ctx); ctx.buf[ctx.pos] = '\0'; ant_value_t ret = js_mkstr(js, ctx.buf, ctx.pos); free(ctx.buf); return ret; } static void env_keys_cb(ant_t *js, const char *key, size_t key_len, const char *value, size_t val_len, void *ctx) { ant_value_t arr = *(ant_value_t *)ctx; js_arr_push(js, arr, js_mkstr(js, key, key_len)); } static ant_value_t env_keys(ant_t *js, ant_value_t obj) { ant_value_t arr = js_mkarr(js); env_foreach(js, obj, env_keys_cb, &arr); return arr; } static ant_value_t process_cwd(ant_t *js, ant_value_t *args, int nargs) { char cwd[4096]; if (getcwd(cwd, sizeof(cwd)) != NULL) { return js_mkstr(js, cwd, strlen(cwd)); } return js_mkundef(); } static ant_value_t process_add(ant_t *js, ant_value_t *args, int nargs, bool once, bool prepend) { if (nargs < 2) { return js_mkerr(js, once ? "process.once requires 2 arguments" : "process.on requires 2 arguments"); } char *event = js_getstr(js, args[0], NULL); if (!event) return js_mkerr(js, "event must be a string"); uint8_t listener_type = vtype(args[1]); if (listener_type != T_FUNC && listener_type != T_CFUNC) { return js_mkerr(js, "listener must be a function"); } int signum = get_signal_number(event); if (signum > 0) start_signal_watch(signum); ProcessEventType *evt = find_or_create_event(&process_events, event); if (!ensure_listener_capacity(evt)) return js_mkerr(js, "failed to allocate listener"); if (prepend && evt->listener_count > 0) { memmove( &evt->listeners[1], &evt->listeners[0], (size_t)evt->listener_count * sizeof(ProcessEventListener) ); evt->listeners[0].listener = args[1]; evt->listeners[0].once = once; } else { evt->listeners[evt->listener_count].listener = args[1]; evt->listeners[evt->listener_count].once = once; } evt->listener_count++; check_listener_warning(event); return js_get(js, js_glob(js), "process"); } static ant_value_t process_on(ant_t *js, ant_value_t *args, int nargs) { return process_add(js, args, nargs, false, false); } static ant_value_t process_once(ant_t *js, ant_value_t *args, int nargs) { return process_add(js, args, nargs, true, false); } static ant_value_t process_prepend_listener(ant_t *js, ant_value_t *args, int nargs) { return process_add(js, args, nargs, false, true); } static ant_value_t process_prepend_once_listener(ant_t *js, ant_value_t *args, int nargs) { return process_add(js, args, nargs, true, true); } static ant_value_t process_off(ant_t *js, ant_value_t *args, int nargs) { ant_value_t process_obj = js_get(js, js_glob(js), "process"); if (nargs < 2) return process_obj; char *event = js_getstr(js, args[0], NULL); if (!event) return process_obj; ProcessEventType *evt = NULL; HASH_FIND_STR(process_events, event, evt); if (!evt) return process_obj; for (int i = 0; i < evt->listener_count; i++) { if (evt->listeners[i].listener == args[1]) { for (int j = i; j < evt->listener_count - 1; j++) { evt->listeners[j] = evt->listeners[j + 1]; } evt->listener_count--; break; } } if (evt->listener_count == 0) { int signum = get_signal_number(event); if (signum > 0) stop_signal_watch(signum); } return process_obj; } static ant_value_t process_remove_all_listeners(ant_t *js, ant_value_t *args, int nargs) { ant_value_t process_obj = js_get(js, js_glob(js), "process"); if (nargs > 0 && vtype(args[0]) == T_STR) { char *event = js_getstr(js, args[0], NULL); if (event) { ProcessEventType *evt = NULL; HASH_FIND_STR(process_events, event, evt); if (evt) { int signum = get_signal_number(event); if (signum > 0) stop_signal_watch(signum); free_event_type(&process_events, evt); } } } else { ProcessEventType *evt, *tmp; HASH_ITER(hh, process_events, evt, tmp) { int signum = get_signal_number(evt->event_type); if (signum > 0) stop_signal_watch(signum); free_event_type(&process_events, evt); } } return process_obj; } static ant_value_t process_emit(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_false; char *event = js_getstr(js, args[0], NULL); if (!event) return js_false; emit_process_event(event, nargs > 1 ? &args[1] : NULL, nargs - 1); return js_true; } static bool process_is_error_object(ant_value_t value) { if (is_err(value)) return true; return is_object_type(value) && js_get_slot(value, SLOT_ERROR_BRAND) == js_true; } static void process_get_warning_options( ant_t *js, ant_value_t options, const char **type, const char **code, const char **detail, ant_offset_t *detail_len ) { if (!is_object_type(options)) return; ant_value_t type_val = js_get(js, options, "type"); ant_value_t code_val = js_get(js, options, "code"); ant_value_t detail_val = js_get(js, options, "detail"); if (vtype(type_val) == T_STR) *type = js_getstr(js, type_val, NULL); if (vtype(code_val) == T_STR) *code = js_getstr(js, code_val, NULL); if (vtype(detail_val) == T_STR) *detail = (const char *)(uintptr_t)vstr(js, detail_val, detail_len); } static ant_value_t process_make_warning_object( ant_t *js, const char *type, js_cstr_t msg, const char *code, const char *detail, ant_offset_t detail_len ) { ant_value_t warning_obj = js_mkobj(js); js_set_proto_init(warning_obj, js_get_ctor_proto(js, "Error", 5)); js_set_slot(warning_obj, SLOT_ERROR_BRAND, js_true); js_set(js, warning_obj, "name", js_mkstr(js, type, strlen(type))); js_set(js, warning_obj, "message", js_mkstr(js, msg.ptr, msg.len)); if (code) js_set(js, warning_obj, "code", js_mkstr(js, code, strlen(code))); if (detail) js_set(js, warning_obj, "detail", js_mkstr(js, detail, detail_len)); js_capture_stack(js, warning_obj); return warning_obj; } static ant_value_t process_emit_warning(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); ant_value_t warning = args[0]; const char *type = "Warning"; const char *code = NULL; const char *detail = NULL; ant_offset_t detail_len = 0; if (nargs >= 2) { if (vtype(args[1]) == T_STR) type = js_getstr(js, args[1], NULL); else process_get_warning_options(js, args[1], &type, &code, &detail, &detail_len); } if (nargs >= 3 && vtype(args[2]) == T_STR) code = js_getstr(js, args[2], NULL); char msg_buf[512]; js_cstr_t msg = {0}; ant_value_t warning_event_arg = warning; bool is_error = process_is_error_object(warning); if (is_error) { ant_value_t warning_obj = js_as_obj(warning); ant_offset_t prop_len = 0; const char *name_prop = get_str_prop(js, warning_obj, "name", 4, &prop_len); if (name_prop) type = name_prop; const char *message_prop = get_str_prop(js, warning_obj, "message", 7, &prop_len); msg.ptr = message_prop ? message_prop : ""; msg.len = message_prop ? prop_len : 0; msg.needs_free = false; code = get_str_prop(js, warning_obj, "code", 4, NULL); detail = get_str_prop(js, warning_obj, "detail", 6, &detail_len); warning_event_arg = warning_obj; } else { msg = js_to_cstr(js, warning, msg_buf, sizeof(msg_buf)); warning_event_arg = process_make_warning_object(js, type, msg, code, detail, detail_len); } fprintf(stderr, "(%s:%d) ", "ant", (int)getpid()); if (code) fprintf(stderr, "[%s] ", code); fprintf(stderr, "%s: %.*s\n", type ? type : "Warning", (int)msg.len, msg.ptr); if (detail) fprintf(stderr, "%.*s\n", (int)detail_len, detail); emit_process_event("warning", &warning_event_arg, 1); if (msg.needs_free) free((void *)msg.ptr); return js_mkundef(); } static ant_value_t process_listener_count(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mknum(0); char *event = js_getstr(js, args[0], NULL); if (!event) return js_mknum(0); ProcessEventType *evt = NULL; HASH_FIND_STR(process_events, event, evt); return js_mknum(evt ? evt->listener_count : 0); } static ant_value_t process_listeners_impl(ant_t *js, ant_value_t *args, int nargs, bool raw) { (void)raw; ant_value_t result = js_mkarr(js); if (nargs < 1) return result; char *event = js_getstr(js, args[0], NULL); if (!event) return result; ProcessEventType *evt = NULL; HASH_FIND_STR(process_events, event, evt); if (!evt) return result; for (int i = 0; i < evt->listener_count; i++) { js_arr_push(js, result, evt->listeners[i].listener); } return result; } static ant_value_t process_listeners(ant_t *js, ant_value_t *args, int nargs) { return process_listeners_impl(js, args, nargs, false); } static ant_value_t process_raw_listeners(ant_t *js, ant_value_t *args, int nargs) { return process_listeners_impl(js, args, nargs, true); } static ant_value_t process_event_names(ant_t *js, ant_value_t *args, int nargs) { (void)args; (void)nargs; ant_value_t result = js_mkarr(js); ProcessEventType *evt = NULL; ProcessEventType *tmp = NULL; HASH_ITER(hh, process_events, evt, tmp) { if (evt->listener_count > 0) { js_arr_push(js, result, js_mkstr(js, evt->event_type, strlen(evt->event_type))); } } return result; } static ant_value_t process_set_max_listeners(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr(js, "setMaxListeners requires 1 argument"); if (vtype(args[0]) != T_NUM) return js_mkerr(js, "n must be a number"); int n = (int)js_getnum(args[0]); if (n < 0) return js_mkerr(js, "n must be non-negative"); max_listeners = n; return js_get(js, js_glob(js), "process"); } static ant_value_t process_get_max_listeners(ant_t *js, ant_value_t *args, int nargs) { return js_mknum(max_listeners); } static ant_value_t process_next_tick(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "process.nextTick requires a callback"); ant_value_t cb = args[0]; if (vtype(cb) != T_FUNC && vtype(cb) != T_CFUNC) return js_mkerr_typed(js, JS_ERR_TYPE, "process.nextTick callback is not a function"); if (nargs <= 1) queue_next_tick(js, cb); else queue_next_tick_with_args(js, cb, args + 1, nargs - 1); return js_mkundef(); } static void process_set_methods(ant_t *js, ant_value_t obj, bool include_event_methods) { js_set(js, obj, "exit", js_mkfun(process_exit)); js_set(js, obj, "cwd", js_mkfun(process_cwd)); js_set(js, obj, "chdir", js_mkfun(process_chdir)); js_set(js, obj, "uptime", js_mkfun(process_uptime)); js_set(js, obj, "cpuUsage", js_mkfun(process_cpu_usage)); js_set(js, obj, "kill", js_mkfun(process_kill)); js_set(js, obj, "abort", js_mkfun(process_abort)); js_set(js, obj, "umask", js_mkfun(process_umask)); js_set(js, obj, "nextTick", js_mkfun(process_next_tick)); js_set(js, obj, "emitWarning", js_mkfun(process_emit_warning)); js_set(js, obj, "dlopen", js_mkfun(napi_process_dlopen_js)); ant_value_t mem_usage_fn = js_heavy_mkfun(js, process_memory_usage, js_mkundef()); js_set(js, mem_usage_fn, "rss", js_mkfun(process_memory_usage_rss)); js_set(js, obj, "memoryUsage", mem_usage_fn); ant_value_t hrtime_fn = js_heavy_mkfun(js, process_hrtime, js_mkundef()); js_set(js, hrtime_fn, "bigint", js_mkfun(process_hrtime_bigint)); js_set(js, obj, "hrtime", hrtime_fn); if (include_event_methods) { js_set(js, obj, "on", js_mkfun(process_on)); js_set_exact(js, obj, "addListener", js_get(js, obj, "on")); js_set(js, obj, "once", js_mkfun(process_once)); js_set(js, obj, "prependListener", js_mkfun(process_prepend_listener)); js_set(js, obj, "prependOnceListener", js_mkfun(process_prepend_once_listener)); js_set(js, obj, "off", js_mkfun(process_off)); js_set_exact(js, obj, "removeListener", js_get(js, obj, "off")); js_set(js, obj, "removeAllListeners", js_mkfun(process_remove_all_listeners)); js_set(js, obj, "emit", js_mkfun(process_emit)); js_set(js, obj, "listenerCount", js_mkfun(process_listener_count)); js_set(js, obj, "listeners", js_mkfun(process_listeners)); js_set(js, obj, "rawListeners", js_mkfun(process_raw_listeners)); js_set(js, obj, "eventNames", js_mkfun(process_event_names)); js_set(js, obj, "setMaxListeners", js_mkfun(process_set_max_listeners)); js_set(js, obj, "getMaxListeners", js_mkfun(process_get_max_listeners)); } #ifndef _WIN32 js_set(js, obj, "getuid", js_mkfun(process_getuid)); js_set(js, obj, "geteuid", js_mkfun(process_geteuid)); js_set(js, obj, "getgid", js_mkfun(process_getgid)); js_set(js, obj, "getegid", js_mkfun(process_getegid)); js_set(js, obj, "getgroups", js_mkfun(process_getgroups)); js_set(js, obj, "setuid", js_mkfun(process_setuid)); js_set(js, obj, "setgid", js_mkfun(process_setgid)); js_set(js, obj, "seteuid", js_mkfun(process_seteuid)); js_set(js, obj, "setegid", js_mkfun(process_setegid)); js_set(js, obj, "setgroups", js_mkfun(process_setgroups)); js_set(js, obj, "initgroups", js_mkfun(process_initgroups)); #endif } ant_value_t process_library(ant_t *js) { ant_value_t process_obj = js_get(js, js_glob(js), "process"); js_set(js, process_obj, "default", process_obj); js_set_slot_wb(js, process_obj, SLOT_DEFAULT, process_obj); return process_obj; } void init_process_module() { ant_t *js = rt->js; ant_value_t global = js_glob(js); stdin_state.decoder = js_mkundef(); process_start_time = uv_hrtime(); ant_value_t process_proto = js_mkobj(js); process_set_methods(js, process_proto, true); js_set_sym(js, process_proto, get_toStringTag_sym(), js_mkstr(js, "process", 7)); ant_value_t process_obj = js_mkobj(js); ant_value_t env_obj = js_mkobj(js); js_set_proto_init(process_obj, process_proto); process_set_methods(js, process_obj, false); load_dotenv_file(js, env_obj); js_set_keys(env_obj, env_keys); js_set_getter(env_obj, env_getter); js_set_setter(env_obj, env_setter); js_set_deleter(env_obj, env_deleter); js_set(js, env_obj, "toObject", js_mkfun(env_to_object)); js_set(js, env_obj, "toString", js_mkfun(env_toString)); js_set(js, process_obj, "env", env_obj); ant_value_t argv_arr = js_mkarr(js); for (int i = 0; i < rt->argc; i++) { js_arr_push(js, argv_arr, js_mkstr(js, rt->argv[i], strlen(rt->argv[i]))); } js_set(js, process_obj, "argv", argv_arr); js_set(js, process_obj, "execArgv", js_mkarr(js)); js_set(js, process_obj, "argv0", rt->argc > 0 ? js_mkstr(js, rt->argv[0], strlen(rt->argv[0])) : js_mkstr(js, "ant", 3)); js_set(js, process_obj, "execPath", rt->argc > 0 ? js_mkstr(js, rt->argv[0], strlen(rt->argv[0])) : js_mkundef()); js_set(js, process_obj, "pid", js_mknum((double)getpid())); js_set(js, process_obj, "ppid", js_mknum((double)getppid())); ant_value_t versions_obj = js_mkobj(js); js_set(js, versions_obj, "ant", js_mkstr(js, ANT_VERSION, strlen(ANT_VERSION))); char uv_ver[32]; snprintf(uv_ver, sizeof(uv_ver), "%d.%d.%d", UV_VERSION_MAJOR, UV_VERSION_MINOR, UV_VERSION_PATCH); js_set(js, versions_obj, "uv", js_mkstr(js, uv_ver, strlen(uv_ver))); js_set(js, process_obj, "versions", versions_obj); ant_value_t release_obj = js_mkobj(js); js_set(js, release_obj, "name", js_mkstr(js, "ant", 3)); js_set(js, process_obj, "release", release_obj); // process.platform #if defined(__APPLE__) js_set(js, process_obj, "platform", js_mkstr(js, "darwin", 6)); #elif defined(__linux__) js_set(js, process_obj, "platform", js_mkstr(js, "linux", 5)); #elif defined(_WIN32) || defined(_WIN64) js_set(js, process_obj, "platform", js_mkstr(js, "win32", 5)); #elif defined(__FreeBSD__) js_set(js, process_obj, "platform", js_mkstr(js, "freebsd", 7)); #else js_set(js, process_obj, "platform", js_mkstr(js, "unknown", 7)); #endif // process.arch #if defined(__x86_64__) || defined(_M_X64) js_set(js, process_obj, "arch", js_mkstr(js, "x64", 3)); #elif defined(__i386__) || defined(_M_IX86) js_set(js, process_obj, "arch", js_mkstr(js, "ia32", 4)); #elif defined(__aarch64__) || defined(_M_ARM64) js_set(js, process_obj, "arch", js_mkstr(js, "arm64", 5)); #elif defined(__arm__) || defined(_M_ARM) js_set(js, process_obj, "arch", js_mkstr(js, "arm", 3)); #else js_set(js, process_obj, "arch", js_mkstr(js, "unknown", 7)); #endif ant_value_t stdin_proto = js_mkobj(js); js_set(js, stdin_proto, "setRawMode", js_mkfun(js_stdin_set_raw_mode)); js_set(js, stdin_proto, "setEncoding", js_mkfun(js_stdin_set_encoding)); js_set(js, stdin_proto, "resume", js_mkfun(js_stdin_resume)); js_set(js, stdin_proto, "pause", js_mkfun(js_stdin_pause)); js_set(js, stdin_proto, "on", js_mkfun(js_stdin_on)); js_set(js, stdin_proto, "off", js_mkfun(js_stdin_remove_listener)); js_set_exact(js, stdin_proto, "removeListener", js_get(js, stdin_proto, "off")); js_set(js, stdin_proto, "removeAllListeners", js_mkfun(js_stdin_remove_all_listeners)); js_set_sym(js, stdin_proto, get_toStringTag_sym(), js_mkstr(js, "ReadStream", 10)); ant_value_t stdin_obj = js_mkobj(js); js_set_proto_init(stdin_obj, stdin_proto); js_set(js, stdin_obj, "isTTY", js_bool(stdin_is_tty())); js_set(js, stdin_obj, "encoding", js_mkundef()); js_set(js, process_obj, "stdin", stdin_obj); ant_value_t stdout_proto = js_mkobj(js); js_set(js, stdout_proto, "write", js_mkfun(js_stdout_write)); js_set(js, stdout_proto, "on", js_mkfun(js_stdout_on)); js_set(js, stdout_proto, "once", js_mkfun(js_stdout_once)); js_set(js, stdout_proto, "off", js_mkfun(js_stdout_remove_listener)); js_set_exact(js, stdout_proto, "removeListener", js_get(js, stdout_proto, "off")); js_set(js, stdout_proto, "removeAllListeners", js_mkfun(js_stdout_remove_all_listeners)); js_set(js, stdout_proto, "getWindowSize", js_mkfun(js_stdout_get_window_size)); js_set_sym(js, stdout_proto, get_toStringTag_sym(), js_mkstr(js, "WriteStream", 11)); ant_value_t stdout_obj = js_mkobj(js); js_set_proto_init(stdout_obj, stdout_proto); js_set(js, stdout_obj, "isTTY", js_bool(stdout_is_tty())); js_set_getter_desc(js, stdout_obj, "rows", 4, js_mkfun(js_stdout_rows_getter), JS_DESC_E | JS_DESC_C); js_set_getter_desc(js, stdout_obj, "columns", 7, js_mkfun(js_stdout_columns_getter), JS_DESC_E | JS_DESC_C); js_set(js, process_obj, "stdout", stdout_obj); ant_value_t stderr_proto = js_mkobj(js); js_set(js, stderr_proto, "write", js_mkfun(js_stderr_write)); js_set(js, stderr_proto, "on", js_mkfun(js_stderr_on)); js_set(js, stderr_proto, "once", js_mkfun(js_stderr_once)); js_set(js, stderr_proto, "off", js_mkfun(js_stderr_remove_listener)); js_set_exact(js, stderr_proto, "removeListener", js_get(js, stderr_proto, "off")); js_set(js, stderr_proto, "removeAllListeners", js_mkfun(js_stderr_remove_all_listeners)); js_set_sym(js, stderr_proto, get_toStringTag_sym(), js_mkstr(js, "WriteStream", 11)); ant_value_t stderr_obj = js_mkobj(js); js_set_proto_init(stderr_obj, stderr_proto); js_set(js, stderr_obj, "isTTY", js_bool(stderr_is_tty())); js_set(js, process_obj, "stderr", stderr_obj); js_set(js, global, "process", process_obj); } bool has_active_stdin(void) { return stdin_state.reading; } void gc_mark_process(ant_t *js, gc_mark_fn mark) { ProcessEventType *tables[] = {process_events, stdin_events, stdout_events, stderr_events}; for (int t = 0; t < 4; t++) { ProcessEventType *evt, *tmp; HASH_ITER(hh, tables[t], evt, tmp) for (int i = 0; i < evt->listener_count; i++) mark(js, evt->listeners[i].listener); } if (is_object_type(stdin_state.decoder)) mark(js, stdin_state.decoder); } void process_enable_keypress_events(void) { stdin_state.keypress_enabled = true; stdin_state.escape_state = 0; stdin_state.escape_len = 0; }