| /* |
| * QuickJS C library |
| * |
| * Copyright (c) 2017-2020 Fabrice Bellard |
| * Copyright (c) 2017-2020 Charlie Gordon |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <inttypes.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <signal.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <dirent.h> |
| #if defined(_WIN32) |
| #include <windows.h> |
| #include <conio.h> |
| #include <utime.h> |
| #else |
| #include <dlfcn.h> |
| #include <termios.h> |
| #include <sys/ioctl.h> |
| #include <sys/wait.h> |
| |
| #if defined(__APPLE__) |
| typedef sig_t sighandler_t; |
| #if !defined(environ) |
| #include <crt_externs.h> |
| #define environ (*_NSGetEnviron()) |
| #endif |
| #endif /* __APPLE__ */ |
| |
| #endif |
| |
| #if !defined(_WIN32) |
| /* enable the os.Worker API. IT relies on POSIX threads */ |
| #define USE_WORKER |
| #endif |
| |
| #ifdef USE_WORKER |
| #include <pthread.h> |
| #include <stdatomic.h> |
| #endif |
| |
| #if defined(__Fuchsia__) |
| #include <poll.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/port.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/unsafe.h> |
| #endif |
| |
| #include "cutils.h" |
| #include "list.h" |
| #include "quickjs-libc.h" |
| |
| /* TODO: |
| - add socket calls |
| */ |
| |
| typedef struct { |
| struct list_head link; |
| int fd; |
| JSValue rw_func[2]; |
| } JSOSRWHandler; |
| |
| typedef struct { |
| struct list_head link; |
| int sig_num; |
| JSValue func; |
| } JSOSSignalHandler; |
| |
| typedef struct { |
| struct list_head link; |
| BOOL has_object; |
| int64_t timeout; |
| JSValue func; |
| } JSOSTimer; |
| |
| #ifdef __Fuchsia__ |
| typedef struct { |
| struct list_head link; |
| zx_handle_t handle; |
| zx_signals_t signals; |
| JSValue func; |
| } JSOSHandleHandler; |
| #endif |
| |
| typedef struct { |
| struct list_head link; |
| uint8_t *data; |
| size_t data_len; |
| /* list of SharedArrayBuffers, necessary to free the message */ |
| uint8_t **sab_tab; |
| size_t sab_tab_len; |
| } JSWorkerMessage; |
| |
| typedef struct { |
| int ref_count; |
| #ifdef USE_WORKER |
| pthread_mutex_t mutex; |
| #endif |
| struct list_head msg_queue; /* list of JSWorkerMessage.link */ |
| int read_fd; |
| int write_fd; |
| } JSWorkerMessagePipe; |
| |
| typedef struct { |
| struct list_head link; |
| JSWorkerMessagePipe *recv_pipe; |
| JSValue on_message_func; |
| } JSWorkerMessageHandler; |
| |
| typedef struct JSThreadState { |
| struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ |
| struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ |
| struct list_head os_timers; /* list of JSOSTimer.link */ |
| #ifdef __Fuchsia__ |
| struct list_head os_handle_handlers; |
| #endif |
| struct list_head port_list; /* list of JSWorkerMessageHandler.link */ |
| int eval_script_recurse; /* only used in the main thread */ |
| /* not used in the main thread */ |
| JSWorkerMessagePipe *recv_pipe, *send_pipe; |
| } JSThreadState; |
| |
| static uint64_t os_pending_signals; |
| static int (*os_poll_func)(JSContext *ctx); |
| |
| static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) |
| { |
| dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); |
| } |
| |
| static BOOL my_isdigit(int c) |
| { |
| return (c >= '0' && c <= '9'); |
| } |
| |
| static JSValue js_printf_internal(JSContext *ctx, |
| int argc, JSValueConst *argv, FILE *fp) |
| { |
| char fmtbuf[32]; |
| uint8_t cbuf[UTF8_CHAR_LEN_MAX+1]; |
| JSValue res; |
| DynBuf dbuf; |
| const char *fmt_str; |
| const uint8_t *fmt, *fmt_end; |
| const uint8_t *p; |
| char *q; |
| int i, c, len, mod; |
| size_t fmt_len; |
| int32_t int32_arg; |
| int64_t int64_arg; |
| double double_arg; |
| const char *string_arg; |
| /* Use indirect call to dbuf_printf to prevent gcc warning */ |
| int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf; |
| |
| js_std_dbuf_init(ctx, &dbuf); |
| |
| if (argc > 0) { |
| fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]); |
| if (!fmt_str) |
| goto fail; |
| |
| i = 1; |
| fmt = (const uint8_t *)fmt_str; |
| fmt_end = fmt + fmt_len; |
| while (fmt < fmt_end) { |
| for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) |
| continue; |
| dbuf_put(&dbuf, p, fmt - p); |
| if (fmt >= fmt_end) |
| break; |
| q = fmtbuf; |
| *q++ = *fmt++; /* copy '%' */ |
| |
| /* flags */ |
| for(;;) { |
| c = *fmt; |
| if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || |
| c == '\'') { |
| if (q >= fmtbuf + sizeof(fmtbuf) - 1) |
| goto invalid; |
| *q++ = c; |
| fmt++; |
| } else { |
| break; |
| } |
| } |
| /* width */ |
| if (*fmt == '*') { |
| if (i >= argc) |
| goto missing; |
| if (JS_ToInt32(ctx, &int32_arg, argv[i++])) |
| goto fail; |
| q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); |
| fmt++; |
| } else { |
| while (my_isdigit(*fmt)) { |
| if (q >= fmtbuf + sizeof(fmtbuf) - 1) |
| goto invalid; |
| *q++ = *fmt++; |
| } |
| } |
| if (*fmt == '.') { |
| if (q >= fmtbuf + sizeof(fmtbuf) - 1) |
| goto invalid; |
| *q++ = *fmt++; |
| if (*fmt == '*') { |
| if (i >= argc) |
| goto missing; |
| if (JS_ToInt32(ctx, &int32_arg, argv[i++])) |
| goto fail; |
| q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); |
| fmt++; |
| } else { |
| while (my_isdigit(*fmt)) { |
| if (q >= fmtbuf + sizeof(fmtbuf) - 1) |
| goto invalid; |
| *q++ = *fmt++; |
| } |
| } |
| } |
| |
| /* we only support the "l" modifier for 64 bit numbers */ |
| mod = ' '; |
| if (*fmt == 'l') { |
| mod = *fmt++; |
| } |
| |
| /* type */ |
| c = *fmt++; |
| if (q >= fmtbuf + sizeof(fmtbuf) - 1) |
| goto invalid; |
| *q++ = c; |
| *q = '\0'; |
| |
| switch (c) { |
| case 'c': |
| if (i >= argc) |
| goto missing; |
| if (JS_IsString(argv[i])) { |
| string_arg = JS_ToCString(ctx, argv[i++]); |
| if (!string_arg) |
| goto fail; |
| int32_arg = unicode_from_utf8((uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); |
| JS_FreeCString(ctx, string_arg); |
| } else { |
| if (JS_ToInt32(ctx, &int32_arg, argv[i++])) |
| goto fail; |
| } |
| /* handle utf-8 encoding explicitly */ |
| if ((unsigned)int32_arg > 0x10FFFF) |
| int32_arg = 0xFFFD; |
| /* ignore conversion flags, width and precision */ |
| len = unicode_to_utf8(cbuf, int32_arg); |
| dbuf_put(&dbuf, cbuf, len); |
| break; |
| |
| case 'd': |
| case 'i': |
| case 'o': |
| case 'u': |
| case 'x': |
| case 'X': |
| if (i >= argc) |
| goto missing; |
| if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) |
| goto fail; |
| if (mod == 'l') { |
| /* 64 bit number */ |
| #if defined(_WIN32) |
| if (q >= fmtbuf + sizeof(fmtbuf) - 3) |
| goto invalid; |
| q[2] = q[-1]; |
| q[-1] = 'I'; |
| q[0] = '6'; |
| q[1] = '4'; |
| q[3] = '\0'; |
| dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg); |
| #else |
| if (q >= fmtbuf + sizeof(fmtbuf) - 2) |
| goto invalid; |
| q[1] = q[-1]; |
| q[-1] = q[0] = 'l'; |
| q[2] = '\0'; |
| dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); |
| #endif |
| } else { |
| dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); |
| } |
| break; |
| |
| case 's': |
| if (i >= argc) |
| goto missing; |
| /* XXX: handle strings containing null characters */ |
| string_arg = JS_ToCString(ctx, argv[i++]); |
| if (!string_arg) |
| goto fail; |
| dbuf_printf_fun(&dbuf, fmtbuf, string_arg); |
| JS_FreeCString(ctx, string_arg); |
| break; |
| |
| case 'e': |
| case 'f': |
| case 'g': |
| case 'a': |
| case 'E': |
| case 'F': |
| case 'G': |
| case 'A': |
| if (i >= argc) |
| goto missing; |
| if (JS_ToFloat64(ctx, &double_arg, argv[i++])) |
| goto fail; |
| dbuf_printf_fun(&dbuf, fmtbuf, double_arg); |
| break; |
| |
| case '%': |
| dbuf_putc(&dbuf, '%'); |
| break; |
| |
| default: |
| /* XXX: should support an extension mechanism */ |
| invalid: |
| JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); |
| goto fail; |
| missing: |
| JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); |
| goto fail; |
| } |
| } |
| JS_FreeCString(ctx, fmt_str); |
| } |
| if (dbuf.error) { |
| res = JS_ThrowOutOfMemory(ctx); |
| } else { |
| if (fp) { |
| len = fwrite(dbuf.buf, 1, dbuf.size, fp); |
| res = JS_NewInt32(ctx, len); |
| } else { |
| res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); |
| } |
| } |
| dbuf_free(&dbuf); |
| return res; |
| |
| fail: |
| dbuf_free(&dbuf); |
| return JS_EXCEPTION; |
| } |
| |
| uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) |
| { |
| FILE *f; |
| uint8_t *buf; |
| size_t buf_len; |
| long lret; |
| |
| f = fopen(filename, "rb"); |
| if (!f) |
| return NULL; |
| if (fseek(f, 0, SEEK_END) < 0) |
| goto fail; |
| lret = ftell(f); |
| if (lret < 0) |
| goto fail; |
| /* XXX: on Linux, ftell() return LONG_MAX for directories */ |
| if (lret == LONG_MAX) { |
| errno = EISDIR; |
| goto fail; |
| } |
| buf_len = lret; |
| if (fseek(f, 0, SEEK_SET) < 0) |
| goto fail; |
| if (ctx) |
| buf = js_malloc(ctx, buf_len + 1); |
| else |
| buf = malloc(buf_len + 1); |
| if (!buf) |
| goto fail; |
| if (fread(buf, 1, buf_len, f) != buf_len) { |
| errno = EIO; |
| if (ctx) |
| js_free(ctx, buf); |
| else |
| free(buf); |
| fail: |
| fclose(f); |
| return NULL; |
| } |
| buf[buf_len] = '\0'; |
| fclose(f); |
| *pbuf_len = buf_len; |
| return buf; |
| } |
| |
| /* load and evaluate a file */ |
| static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| uint8_t *buf; |
| const char *filename; |
| JSValue ret; |
| size_t buf_len; |
| |
| filename = JS_ToCString(ctx, argv[0]); |
| if (!filename) |
| return JS_EXCEPTION; |
| buf = js_load_file(ctx, &buf_len, filename); |
| if (!buf) { |
| JS_ThrowReferenceError(ctx, "could not load '%s'", filename); |
| JS_FreeCString(ctx, filename); |
| return JS_EXCEPTION; |
| } |
| ret = JS_Eval(ctx, (char *)buf, buf_len, filename, |
| JS_EVAL_TYPE_GLOBAL); |
| js_free(ctx, buf); |
| JS_FreeCString(ctx, filename); |
| return ret; |
| } |
| |
| /* load a file as a UTF-8 encoded string */ |
| static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| uint8_t *buf; |
| const char *filename; |
| JSValue ret; |
| size_t buf_len; |
| |
| filename = JS_ToCString(ctx, argv[0]); |
| if (!filename) |
| return JS_EXCEPTION; |
| buf = js_load_file(ctx, &buf_len, filename); |
| JS_FreeCString(ctx, filename); |
| if (!buf) |
| return JS_NULL; |
| ret = JS_NewStringLen(ctx, (char *)buf, buf_len); |
| js_free(ctx, buf); |
| return ret; |
| } |
| |
| typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, |
| const char *module_name); |
| |
| |
| #if defined(_WIN32) |
| static JSModuleDef *js_module_loader_so(JSContext *ctx, |
| const char *module_name) |
| { |
| JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); |
| return NULL; |
| } |
| #else |
| static JSModuleDef *js_module_loader_so(JSContext *ctx, |
| const char *module_name) |
| { |
| JSModuleDef *m; |
| void *hd; |
| JSInitModuleFunc *init; |
| char *filename; |
| |
| if (!strchr(module_name, '/')) { |
| /* must add a '/' so that the DLL is not searched in the |
| system library paths */ |
| filename = js_malloc(ctx, strlen(module_name) + 2 + 1); |
| if (!filename) |
| return NULL; |
| strcpy(filename, "./"); |
| strcpy(filename + 2, module_name); |
| } else { |
| filename = (char *)module_name; |
| } |
| |
| /* C module */ |
| hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); |
| if (filename != module_name) |
| js_free(ctx, filename); |
| if (!hd) { |
| JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library", |
| module_name); |
| goto fail; |
| } |
| |
| init = dlsym(hd, "js_init_module"); |
| if (!init) { |
| JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", |
| module_name); |
| goto fail; |
| } |
| |
| m = init(ctx, module_name); |
| if (!m) { |
| JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", |
| module_name); |
| fail: |
| if (hd) |
| dlclose(hd); |
| return NULL; |
| } |
| return m; |
| } |
| #endif /* !_WIN32 */ |
| |
| int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, |
| JS_BOOL use_realpath, JS_BOOL is_main) |
| { |
| JSModuleDef *m; |
| char buf[PATH_MAX + 16]; |
| JSValue meta_obj; |
| JSAtom module_name_atom; |
| const char *module_name; |
| |
| assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE); |
| m = JS_VALUE_GET_PTR(func_val); |
| |
| module_name_atom = JS_GetModuleName(ctx, m); |
| module_name = JS_AtomToCString(ctx, module_name_atom); |
| JS_FreeAtom(ctx, module_name_atom); |
| if (!module_name) |
| return -1; |
| if (!strchr(module_name, ':')) { |
| strcpy(buf, "file://"); |
| #if !defined(_WIN32) |
| /* realpath() cannot be used with modules compiled with qjsc |
| because the corresponding module source code is not |
| necessarily present */ |
| if (use_realpath) { |
| char *res = realpath(module_name, buf + strlen(buf)); |
| if (!res) { |
| JS_ThrowTypeError(ctx, "realpath failure"); |
| JS_FreeCString(ctx, module_name); |
| return -1; |
| } |
| } else |
| #endif |
| { |
| pstrcat(buf, sizeof(buf), module_name); |
| } |
| } else { |
| pstrcpy(buf, sizeof(buf), module_name); |
| } |
| JS_FreeCString(ctx, module_name); |
| |
| meta_obj = JS_GetImportMeta(ctx, m); |
| if (JS_IsException(meta_obj)) |
| return -1; |
| JS_DefinePropertyValueStr(ctx, meta_obj, "url", |
| JS_NewString(ctx, buf), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, meta_obj, "main", |
| JS_NewBool(ctx, is_main), |
| JS_PROP_C_W_E); |
| JS_FreeValue(ctx, meta_obj); |
| return 0; |
| } |
| |
| JSModuleDef *js_module_loader(JSContext *ctx, |
| const char *module_name, void *opaque) |
| { |
| JSModuleDef *m; |
| |
| if (has_suffix(module_name, ".so")) { |
| m = js_module_loader_so(ctx, module_name); |
| } else { |
| size_t buf_len; |
| uint8_t *buf; |
| JSValue func_val; |
| |
| buf = js_load_file(ctx, &buf_len, module_name); |
| if (!buf) { |
| JS_ThrowReferenceError(ctx, "could not load module filename '%s'", |
| module_name); |
| return NULL; |
| } |
| |
| /* compile the module */ |
| func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, |
| JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); |
| js_free(ctx, buf); |
| if (JS_IsException(func_val)) |
| return NULL; |
| /* XXX: could propagate the exception */ |
| js_module_set_import_meta(ctx, func_val, TRUE, FALSE); |
| /* the module is already referenced, so we must free it */ |
| m = JS_VALUE_GET_PTR(func_val); |
| JS_FreeValue(ctx, func_val); |
| } |
| return m; |
| } |
| |
| static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int status; |
| if (JS_ToInt32(ctx, &status, argv[0])) |
| status = -1; |
| exit(status); |
| return JS_UNDEFINED; |
| } |
| |
| static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *name, *str; |
| name = JS_ToCString(ctx, argv[0]); |
| if (!name) |
| return JS_EXCEPTION; |
| str = getenv(name); |
| JS_FreeCString(ctx, name); |
| if (!str) |
| return JS_UNDEFINED; |
| else |
| return JS_NewString(ctx, str); |
| } |
| |
| static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JS_RunGC(JS_GetRuntime(ctx)); |
| return JS_UNDEFINED; |
| } |
| |
| static int interrupt_handler(JSRuntime *rt, void *opaque) |
| { |
| return (os_pending_signals >> SIGINT) & 1; |
| } |
| |
| static int get_bool_option(JSContext *ctx, BOOL *pbool, |
| JSValueConst obj, |
| const char *option) |
| { |
| JSValue val; |
| val = JS_GetPropertyStr(ctx, obj, option); |
| if (JS_IsException(val)) |
| return -1; |
| if (!JS_IsUndefined(val)) { |
| *pbool = JS_ToBool(ctx, val); |
| } |
| JS_FreeValue(ctx, val); |
| return 0; |
| } |
| |
| static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| const char *str; |
| size_t len; |
| JSValue ret; |
| JSValueConst options_obj; |
| BOOL backtrace_barrier = FALSE; |
| int flags; |
| |
| if (argc >= 2) { |
| options_obj = argv[1]; |
| if (get_bool_option(ctx, &backtrace_barrier, options_obj, |
| "backtrace_barrier")) |
| return JS_EXCEPTION; |
| } |
| |
| str = JS_ToCStringLen(ctx, &len, argv[0]); |
| if (!str) |
| return JS_EXCEPTION; |
| if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { |
| /* install the interrupt handler */ |
| JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); |
| } |
| flags = JS_EVAL_TYPE_GLOBAL; |
| if (backtrace_barrier) |
| flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; |
| ret = JS_Eval(ctx, str, len, "<evalScript>", flags); |
| JS_FreeCString(ctx, str); |
| if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { |
| /* remove the interrupt handler */ |
| JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); |
| os_pending_signals &= ~((uint64_t)1 << SIGINT); |
| /* convert the uncatchable "interrupted" error into a normal error |
| so that it can be caught by the REPL */ |
| if (JS_IsException(ret)) |
| JS_ResetUncatchableError(ctx); |
| } |
| return ret; |
| } |
| |
| static JSClassID js_std_file_class_id; |
| |
| typedef struct { |
| FILE *f; |
| BOOL close_in_finalizer; |
| BOOL is_popen; |
| } JSSTDFile; |
| |
| static void js_std_file_finalizer(JSRuntime *rt, JSValue val) |
| { |
| JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id); |
| if (s) { |
| if (s->f && s->close_in_finalizer) { |
| if (s->is_popen) |
| pclose(s->f); |
| else |
| fclose(s->f); |
| } |
| js_free_rt(rt, s); |
| } |
| } |
| |
| static ssize_t js_get_errno(ssize_t ret) |
| { |
| if (ret == -1) |
| ret = -errno; |
| return ret; |
| } |
| |
| static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int err; |
| if (JS_ToInt32(ctx, &err, argv[0])) |
| return JS_EXCEPTION; |
| return JS_NewString(ctx, strerror(err)); |
| } |
| |
| static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSValue obj; |
| const char *str; |
| size_t len; |
| |
| str = JS_ToCStringLen(ctx, &len, argv[0]); |
| if (!str) |
| return JS_EXCEPTION; |
| obj = JS_ParseJSON2(ctx, str, len, "<input>", JS_PARSE_JSON_EXT); |
| JS_FreeCString(ctx, str); |
| return obj; |
| } |
| |
| static JSValue js_new_std_file(JSContext *ctx, FILE *f, |
| BOOL close_in_finalizer, |
| BOOL is_popen) |
| { |
| JSSTDFile *s; |
| JSValue obj; |
| obj = JS_NewObjectClass(ctx, js_std_file_class_id); |
| if (JS_IsException(obj)) |
| return obj; |
| s = js_mallocz(ctx, sizeof(*s)); |
| if (!s) { |
| JS_FreeValue(ctx, obj); |
| return JS_EXCEPTION; |
| } |
| s->close_in_finalizer = close_in_finalizer; |
| s->is_popen = is_popen; |
| s->f = f; |
| JS_SetOpaque(obj, s); |
| return obj; |
| } |
| |
| static void js_set_error_object(JSContext *ctx, JSValue obj, int err) |
| { |
| if (!JS_IsUndefined(obj)) { |
| JS_SetPropertyStr(ctx, obj, "errno", JS_NewInt32(ctx, err)); |
| } |
| } |
| |
| static JSValue js_std_open(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *filename, *mode = NULL; |
| FILE *f; |
| int err; |
| |
| filename = JS_ToCString(ctx, argv[0]); |
| if (!filename) |
| goto fail; |
| mode = JS_ToCString(ctx, argv[1]); |
| if (!mode) |
| goto fail; |
| if (mode[strspn(mode, "rwa+b")] != '\0') { |
| JS_ThrowTypeError(ctx, "invalid file mode"); |
| goto fail; |
| } |
| |
| f = fopen(filename, mode); |
| if (!f) |
| err = errno; |
| else |
| err = 0; |
| if (argc >= 3) |
| js_set_error_object(ctx, argv[2], err); |
| JS_FreeCString(ctx, filename); |
| JS_FreeCString(ctx, mode); |
| if (!f) |
| return JS_NULL; |
| return js_new_std_file(ctx, f, TRUE, FALSE); |
| fail: |
| JS_FreeCString(ctx, filename); |
| JS_FreeCString(ctx, mode); |
| return JS_EXCEPTION; |
| } |
| |
| static JSValue js_std_popen(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *filename, *mode = NULL; |
| FILE *f; |
| int err; |
| |
| filename = JS_ToCString(ctx, argv[0]); |
| if (!filename) |
| goto fail; |
| mode = JS_ToCString(ctx, argv[1]); |
| if (!mode) |
| goto fail; |
| if (mode[strspn(mode, "rw")] != '\0') { |
| JS_ThrowTypeError(ctx, "invalid file mode"); |
| goto fail; |
| } |
| |
| f = popen(filename, mode); |
| if (!f) |
| err = errno; |
| else |
| err = 0; |
| if (argc >= 3) |
| js_set_error_object(ctx, argv[2], err); |
| JS_FreeCString(ctx, filename); |
| JS_FreeCString(ctx, mode); |
| if (!f) |
| return JS_NULL; |
| return js_new_std_file(ctx, f, TRUE, TRUE); |
| fail: |
| JS_FreeCString(ctx, filename); |
| JS_FreeCString(ctx, mode); |
| return JS_EXCEPTION; |
| } |
| |
| static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *mode; |
| FILE *f; |
| int fd, err; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| mode = JS_ToCString(ctx, argv[1]); |
| if (!mode) |
| goto fail; |
| if (mode[strspn(mode, "rwa+")] != '\0') { |
| JS_ThrowTypeError(ctx, "invalid file mode"); |
| goto fail; |
| } |
| |
| f = fdopen(fd, mode); |
| if (!f) |
| err = errno; |
| else |
| err = 0; |
| if (argc >= 3) |
| js_set_error_object(ctx, argv[2], err); |
| JS_FreeCString(ctx, mode); |
| if (!f) |
| return JS_NULL; |
| return js_new_std_file(ctx, f, TRUE, FALSE); |
| fail: |
| JS_FreeCString(ctx, mode); |
| return JS_EXCEPTION; |
| } |
| |
| static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f; |
| f = tmpfile(); |
| if (argc >= 1) |
| js_set_error_object(ctx, argv[0], f ? 0 : errno); |
| if (!f) |
| return JS_NULL; |
| return js_new_std_file(ctx, f, TRUE, FALSE); |
| } |
| |
| static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| return js_printf_internal(ctx, argc, argv, NULL); |
| } |
| |
| static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| return js_printf_internal(ctx, argc, argv, stdout); |
| } |
| |
| static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj) |
| { |
| JSSTDFile *s = JS_GetOpaque2(ctx, obj, js_std_file_class_id); |
| if (!s) |
| return NULL; |
| if (!s->f) { |
| JS_ThrowTypeError(ctx, "invalid file handle"); |
| return NULL; |
| } |
| return s->f; |
| } |
| |
| static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv, int magic) |
| { |
| FILE *f; |
| int i; |
| const char *str; |
| size_t len; |
| |
| if (magic == 0) { |
| f = stdout; |
| } else { |
| f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| } |
| |
| for(i = 0; i < argc; i++) { |
| str = JS_ToCStringLen(ctx, &len, argv[i]); |
| if (!str) |
| return JS_EXCEPTION; |
| fwrite(str, 1, len, f); |
| JS_FreeCString(ctx, str); |
| } |
| return JS_UNDEFINED; |
| } |
| |
| static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSSTDFile *s = JS_GetOpaque2(ctx, this_val, js_std_file_class_id); |
| int err; |
| if (!s) |
| return JS_EXCEPTION; |
| if (!s->f) |
| return JS_ThrowTypeError(ctx, "invalid file handle"); |
| if (s->is_popen) |
| err = js_get_errno(pclose(s->f)); |
| else |
| err = js_get_errno(fclose(s->f)); |
| s->f = NULL; |
| return JS_NewInt32(ctx, err); |
| } |
| |
| static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| return js_printf_internal(ctx, argc, argv, f); |
| } |
| |
| static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| fflush(f); |
| return JS_UNDEFINED; |
| } |
| |
| static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv, int is_bigint) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| int64_t pos; |
| if (!f) |
| return JS_EXCEPTION; |
| #if defined(__linux__) |
| pos = ftello(f); |
| #else |
| pos = ftell(f); |
| #endif |
| if (is_bigint) |
| return JS_NewBigInt64(ctx, pos); |
| else |
| return JS_NewInt64(ctx, pos); |
| } |
| |
| static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| int64_t pos; |
| int whence, ret; |
| if (!f) |
| return JS_EXCEPTION; |
| if (JS_ToInt64Ext(ctx, &pos, argv[0])) |
| return JS_EXCEPTION; |
| if (JS_ToInt32(ctx, &whence, argv[1])) |
| return JS_EXCEPTION; |
| #if defined(__linux__) |
| ret = fseeko(f, pos, whence); |
| #else |
| ret = fseek(f, pos, whence); |
| #endif |
| if (ret < 0) |
| ret = -errno; |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| return JS_NewBool(ctx, feof(f)); |
| } |
| |
| static JSValue js_std_file_error(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| return JS_NewBool(ctx, ferror(f)); |
| } |
| |
| static JSValue js_std_file_clearerr(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| clearerr(f); |
| return JS_UNDEFINED; |
| } |
| |
| static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| return JS_NewInt32(ctx, fileno(f)); |
| } |
| |
| static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv, int magic) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| uint64_t pos, len; |
| size_t size, ret; |
| uint8_t *buf; |
| |
| if (!f) |
| return JS_EXCEPTION; |
| if (JS_ToIndex(ctx, &pos, argv[1])) |
| return JS_EXCEPTION; |
| if (JS_ToIndex(ctx, &len, argv[2])) |
| return JS_EXCEPTION; |
| buf = JS_GetArrayBuffer(ctx, &size, argv[0]); |
| if (!buf) |
| return JS_EXCEPTION; |
| if (pos + len > size) |
| return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); |
| if (magic) |
| ret = fwrite(buf + pos, 1, len, f); |
| else |
| ret = fread(buf + pos, 1, len, f); |
| return JS_NewInt64(ctx, ret); |
| } |
| |
| /* XXX: could use less memory and go faster */ |
| static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| int c; |
| DynBuf dbuf; |
| JSValue obj; |
| |
| if (!f) |
| return JS_EXCEPTION; |
| |
| js_std_dbuf_init(ctx, &dbuf); |
| for(;;) { |
| c = fgetc(f); |
| if (c == EOF) { |
| if (dbuf.size == 0) { |
| /* EOF */ |
| dbuf_free(&dbuf); |
| return JS_NULL; |
| } else { |
| break; |
| } |
| } |
| if (c == '\n') |
| break; |
| if (dbuf_putc(&dbuf, c)) { |
| dbuf_free(&dbuf); |
| return JS_ThrowOutOfMemory(ctx); |
| } |
| } |
| obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); |
| dbuf_free(&dbuf); |
| return obj; |
| } |
| |
| /* XXX: could use less memory and go faster */ |
| static JSValue js_std_file_readAsString(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| int c; |
| DynBuf dbuf; |
| JSValue obj; |
| uint64_t max_size64; |
| size_t max_size; |
| JSValueConst max_size_val; |
| |
| if (!f) |
| return JS_EXCEPTION; |
| |
| if (argc >= 1) |
| max_size_val = argv[0]; |
| else |
| max_size_val = JS_UNDEFINED; |
| max_size = (size_t)-1; |
| if (!JS_IsUndefined(max_size_val)) { |
| if (JS_ToIndex(ctx, &max_size64, max_size_val)) |
| return JS_EXCEPTION; |
| if (max_size64 < max_size) |
| max_size = max_size64; |
| } |
| |
| js_std_dbuf_init(ctx, &dbuf); |
| while (max_size != 0) { |
| c = fgetc(f); |
| if (c == EOF) |
| break; |
| if (dbuf_putc(&dbuf, c)) { |
| dbuf_free(&dbuf); |
| return JS_EXCEPTION; |
| } |
| max_size--; |
| } |
| obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); |
| dbuf_free(&dbuf); |
| return obj; |
| } |
| |
| static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| if (!f) |
| return JS_EXCEPTION; |
| return JS_NewInt32(ctx, fgetc(f)); |
| } |
| |
| static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| FILE *f = js_std_file_get(ctx, this_val); |
| int c; |
| if (!f) |
| return JS_EXCEPTION; |
| if (JS_ToInt32(ctx, &c, argv[0])) |
| return JS_EXCEPTION; |
| c = fputc(c, f); |
| return JS_NewInt32(ctx, c); |
| } |
| |
| /* urlGet */ |
| |
| #define URL_GET_PROGRAM "curl -s -i" |
| #define URL_GET_BUF_SIZE 4096 |
| |
| static int http_get_header_line(FILE *f, char *buf, size_t buf_size, |
| DynBuf *dbuf) |
| { |
| int c; |
| char *p; |
| |
| p = buf; |
| for(;;) { |
| c = fgetc(f); |
| if (c < 0) |
| return -1; |
| if ((p - buf) < buf_size - 1) |
| *p++ = c; |
| if (dbuf) |
| dbuf_putc(dbuf, c); |
| if (c == '\n') |
| break; |
| } |
| *p = '\0'; |
| return 0; |
| } |
| |
| static int http_get_status(const char *buf) |
| { |
| const char *p = buf; |
| while (*p != ' ' && *p != '\0') |
| p++; |
| if (*p != ' ') |
| return 0; |
| while (*p == ' ') |
| p++; |
| return atoi(p); |
| } |
| |
| static JSValue js_std_urlGet(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *url; |
| DynBuf cmd_buf; |
| DynBuf data_buf_s, *data_buf = &data_buf_s; |
| DynBuf header_buf_s, *header_buf = &header_buf_s; |
| char *buf; |
| size_t i, len; |
| int c, status; |
| JSValue response = JS_UNDEFINED, ret_obj; |
| JSValueConst options_obj; |
| FILE *f; |
| BOOL binary_flag, full_flag; |
| |
| url = JS_ToCString(ctx, argv[0]); |
| if (!url) |
| return JS_EXCEPTION; |
| |
| binary_flag = FALSE; |
| full_flag = FALSE; |
| |
| if (argc >= 2) { |
| options_obj = argv[1]; |
| |
| if (get_bool_option(ctx, &binary_flag, options_obj, "binary")) |
| goto fail_obj; |
| |
| if (get_bool_option(ctx, &full_flag, options_obj, "full")) { |
| fail_obj: |
| JS_FreeCString(ctx, url); |
| return JS_EXCEPTION; |
| } |
| } |
| |
| js_std_dbuf_init(ctx, &cmd_buf); |
| dbuf_printf(&cmd_buf, "%s ''", URL_GET_PROGRAM); |
| len = strlen(url); |
| for(i = 0; i < len; i++) { |
| c = url[i]; |
| if (c == '\'' || c == '\\') |
| dbuf_putc(&cmd_buf, '\\'); |
| dbuf_putc(&cmd_buf, c); |
| } |
| JS_FreeCString(ctx, url); |
| dbuf_putstr(&cmd_buf, "''"); |
| dbuf_putc(&cmd_buf, '\0'); |
| if (dbuf_error(&cmd_buf)) { |
| dbuf_free(&cmd_buf); |
| return JS_EXCEPTION; |
| } |
| // printf("%s\n", (char *)cmd_buf.buf); |
| f = popen((char *)cmd_buf.buf, "r"); |
| dbuf_free(&cmd_buf); |
| if (!f) { |
| return JS_ThrowTypeError(ctx, "could not start curl"); |
| } |
| |
| js_std_dbuf_init(ctx, data_buf); |
| js_std_dbuf_init(ctx, header_buf); |
| |
| buf = js_malloc(ctx, URL_GET_BUF_SIZE); |
| if (!buf) |
| goto fail; |
| |
| /* get the HTTP status */ |
| if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0) { |
| status = 0; |
| goto bad_header; |
| } |
| status = http_get_status(buf); |
| if (!full_flag && !(status >= 200 && status <= 299)) { |
| goto bad_header; |
| } |
| |
| /* wait until there is an empty line */ |
| for(;;) { |
| if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) { |
| bad_header: |
| response = JS_NULL; |
| goto done; |
| } |
| if (!strcmp(buf, "\r\n")) |
| break; |
| } |
| if (dbuf_error(header_buf)) |
| goto fail; |
| header_buf->size -= 2; /* remove the trailing CRLF */ |
| |
| /* download the data */ |
| for(;;) { |
| len = fread(buf, 1, URL_GET_BUF_SIZE, f); |
| if (len == 0) |
| break; |
| dbuf_put(data_buf, (uint8_t *)buf, len); |
| } |
| if (dbuf_error(data_buf)) |
| goto fail; |
| if (binary_flag) { |
| response = JS_NewArrayBufferCopy(ctx, |
| data_buf->buf, data_buf->size); |
| } else { |
| response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size); |
| } |
| if (JS_IsException(response)) |
| goto fail; |
| done: |
| js_free(ctx, buf); |
| buf = NULL; |
| pclose(f); |
| f = NULL; |
| dbuf_free(data_buf); |
| data_buf = NULL; |
| |
| if (full_flag) { |
| ret_obj = JS_NewObject(ctx); |
| if (JS_IsException(ret_obj)) |
| goto fail; |
| JS_DefinePropertyValueStr(ctx, ret_obj, "response", |
| response, |
| JS_PROP_C_W_E); |
| if (!JS_IsNull(response)) { |
| JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders", |
| JS_NewStringLen(ctx, (char *)header_buf->buf, |
| header_buf->size), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, ret_obj, "status", |
| JS_NewInt32(ctx, status), |
| JS_PROP_C_W_E); |
| } |
| } else { |
| ret_obj = response; |
| } |
| dbuf_free(header_buf); |
| return ret_obj; |
| fail: |
| if (f) |
| pclose(f); |
| js_free(ctx, buf); |
| if (data_buf) |
| dbuf_free(data_buf); |
| if (header_buf) |
| dbuf_free(header_buf); |
| JS_FreeValue(ctx, response); |
| return JS_EXCEPTION; |
| } |
| |
| static JSClassDef js_std_file_class = { |
| "FILE", |
| .finalizer = js_std_file_finalizer, |
| }; |
| |
| static const JSCFunctionListEntry js_std_error_props[] = { |
| /* various errno values */ |
| #define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) |
| DEF(EINVAL), |
| DEF(EIO), |
| DEF(EACCES), |
| DEF(EEXIST), |
| DEF(ENOSPC), |
| DEF(ENOSYS), |
| DEF(EBUSY), |
| DEF(ENOENT), |
| DEF(EPERM), |
| DEF(EPIPE), |
| DEF(EBADF), |
| #undef DEF |
| }; |
| |
| static const JSCFunctionListEntry js_std_funcs[] = { |
| JS_CFUNC_DEF("exit", 1, js_std_exit ), |
| JS_CFUNC_DEF("gc", 0, js_std_gc ), |
| JS_CFUNC_DEF("evalScript", 1, js_evalScript ), |
| JS_CFUNC_DEF("loadScript", 1, js_loadScript ), |
| JS_CFUNC_DEF("getenv", 1, js_std_getenv ), |
| JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), |
| JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), |
| JS_CFUNC_DEF("strerror", 1, js_std_strerror ), |
| JS_CFUNC_DEF("parseExtJSON", 1, js_std_parseExtJSON ), |
| |
| /* FILE I/O */ |
| JS_CFUNC_DEF("open", 2, js_std_open ), |
| JS_CFUNC_DEF("popen", 2, js_std_popen ), |
| JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ), |
| JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ), |
| JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ), |
| JS_CFUNC_DEF("printf", 1, js_std_printf ), |
| JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ), |
| JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ), |
| JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), |
| JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), |
| JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), |
| /* setenv, ... */ |
| }; |
| |
| static const JSCFunctionListEntry js_std_file_proto_funcs[] = { |
| JS_CFUNC_DEF("close", 0, js_std_file_close ), |
| JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ), |
| JS_CFUNC_DEF("printf", 1, js_std_file_printf ), |
| JS_CFUNC_DEF("flush", 0, js_std_file_flush ), |
| JS_CFUNC_MAGIC_DEF("tell", 0, js_std_file_tell, 0 ), |
| JS_CFUNC_MAGIC_DEF("tello", 0, js_std_file_tell, 1 ), |
| JS_CFUNC_DEF("seek", 2, js_std_file_seek ), |
| JS_CFUNC_DEF("eof", 0, js_std_file_eof ), |
| JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ), |
| JS_CFUNC_DEF("error", 0, js_std_file_error ), |
| JS_CFUNC_DEF("clearerr", 0, js_std_file_clearerr ), |
| JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ), |
| JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ), |
| JS_CFUNC_DEF("getline", 0, js_std_file_getline ), |
| JS_CFUNC_DEF("readAsString", 0, js_std_file_readAsString ), |
| JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ), |
| JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ), |
| /* setvbuf, ... */ |
| }; |
| |
| static int js_std_init(JSContext *ctx, JSModuleDef *m) |
| { |
| JSValue proto; |
| |
| /* FILE class */ |
| /* the class ID is created once */ |
| JS_NewClassID(&js_std_file_class_id); |
| /* the class is created once per runtime */ |
| JS_NewClass(JS_GetRuntime(ctx), js_std_file_class_id, &js_std_file_class); |
| proto = JS_NewObject(ctx); |
| JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs, |
| countof(js_std_file_proto_funcs)); |
| JS_SetClassProto(ctx, js_std_file_class_id, proto); |
| |
| JS_SetModuleExportList(ctx, m, js_std_funcs, |
| countof(js_std_funcs)); |
| JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE, FALSE)); |
| JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE, FALSE)); |
| JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE, FALSE)); |
| return 0; |
| } |
| |
| JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name) |
| { |
| JSModuleDef *m; |
| m = JS_NewCModule(ctx, module_name, js_std_init); |
| if (!m) |
| return NULL; |
| JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs)); |
| JS_AddModuleExport(ctx, m, "in"); |
| JS_AddModuleExport(ctx, m, "out"); |
| JS_AddModuleExport(ctx, m, "err"); |
| return m; |
| } |
| |
| /**********************************************************/ |
| /* 'os' object */ |
| |
| static JSValue js_os_open(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *filename; |
| int flags, mode, ret; |
| |
| filename = JS_ToCString(ctx, argv[0]); |
| if (!filename) |
| return JS_EXCEPTION; |
| if (JS_ToInt32(ctx, &flags, argv[1])) |
| goto fail; |
| if (argc >= 3 && !JS_IsUndefined(argv[2])) { |
| if (JS_ToInt32(ctx, &mode, argv[2])) { |
| fail: |
| JS_FreeCString(ctx, filename); |
| return JS_EXCEPTION; |
| } |
| } else { |
| mode = 0666; |
| } |
| #if defined(_WIN32) |
| /* force binary mode by default */ |
| if (!(flags & O_TEXT)) |
| flags |= O_BINARY; |
| #endif |
| ret = js_get_errno(open(filename, flags, mode)); |
| JS_FreeCString(ctx, filename); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| static JSValue js_os_close(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd, ret; |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| ret = js_get_errno(close(fd)); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd, whence; |
| int64_t pos, ret; |
| BOOL is_bigint; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| is_bigint = JS_IsBigInt(ctx, argv[1]); |
| if (JS_ToInt64Ext(ctx, &pos, argv[1])) |
| return JS_EXCEPTION; |
| if (JS_ToInt32(ctx, &whence, argv[2])) |
| return JS_EXCEPTION; |
| ret = lseek(fd, pos, whence); |
| if (ret == -1) |
| ret = -errno; |
| if (is_bigint) |
| return JS_NewBigInt64(ctx, ret); |
| else |
| return JS_NewInt64(ctx, ret); |
| } |
| |
| static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv, int magic) |
| { |
| int fd; |
| uint64_t pos, len; |
| size_t size; |
| ssize_t ret; |
| uint8_t *buf; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| if (JS_ToIndex(ctx, &pos, argv[2])) |
| return JS_EXCEPTION; |
| if (JS_ToIndex(ctx, &len, argv[3])) |
| return JS_EXCEPTION; |
| buf = JS_GetArrayBuffer(ctx, &size, argv[1]); |
| if (!buf) |
| return JS_EXCEPTION; |
| if (pos + len > size) |
| return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); |
| if (magic) |
| ret = js_get_errno(write(fd, buf + pos, len)); |
| else |
| ret = js_get_errno(read(fd, buf + pos, len)); |
| return JS_NewInt64(ctx, ret); |
| } |
| |
| static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd; |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| return JS_NewBool(ctx, isatty(fd) == 1); |
| } |
| |
| #if defined(_WIN32) |
| static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd; |
| HANDLE handle; |
| CONSOLE_SCREEN_BUFFER_INFO info; |
| JSValue obj; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| handle = (HANDLE)_get_osfhandle(fd); |
| |
| if (!GetConsoleScreenBufferInfo(handle, &info)) |
| return JS_NULL; |
| obj = JS_NewArray(ctx); |
| if (JS_IsException(obj)) |
| return obj; |
| JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E); |
| JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E); |
| return obj; |
| } |
| |
| static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd; |
| HANDLE handle; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| handle = (HANDLE)_get_osfhandle(fd); |
| |
| SetConsoleMode(handle, ENABLE_WINDOW_INPUT); |
| return JS_UNDEFINED; |
| } |
| #else |
| static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd; |
| struct winsize ws; |
| JSValue obj; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && |
| ws.ws_col >= 4 && ws.ws_row >= 4) { |
| obj = JS_NewArray(ctx); |
| if (JS_IsException(obj)) |
| return obj; |
| JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E); |
| JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E); |
| return obj; |
| } else { |
| return JS_NULL; |
| } |
| } |
| |
| static struct termios oldtty; |
| |
| static void term_exit(void) |
| { |
| tcsetattr(0, TCSANOW, &oldtty); |
| } |
| |
| /* XXX: should add a way to go back to normal mode */ |
| static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| struct termios tty; |
| int fd; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| |
| memset(&tty, 0, sizeof(tty)); |
| tcgetattr(fd, &tty); |
| oldtty = tty; |
| |
| tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP |
| |INLCR|IGNCR|ICRNL|IXON); |
| tty.c_oflag |= OPOST; |
| tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); |
| tty.c_cflag &= ~(CSIZE|PARENB); |
| tty.c_cflag |= CS8; |
| tty.c_cc[VMIN] = 1; |
| tty.c_cc[VTIME] = 0; |
| |
| tcsetattr(fd, TCSANOW, &tty); |
| |
| atexit(term_exit); |
| return JS_UNDEFINED; |
| } |
| |
| #endif /* !_WIN32 */ |
| |
| static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *filename; |
| int ret; |
| |
| filename = JS_ToCString(ctx, argv[0]); |
| if (!filename) |
| return JS_EXCEPTION; |
| ret = js_get_errno(remove(filename)); |
| JS_FreeCString(ctx, filename); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *oldpath, *newpath; |
| int ret; |
| |
| oldpath = JS_ToCString(ctx, argv[0]); |
| if (!oldpath) |
| return JS_EXCEPTION; |
| newpath = JS_ToCString(ctx, argv[1]); |
| if (!newpath) { |
| JS_FreeCString(ctx, oldpath); |
| return JS_EXCEPTION; |
| } |
| ret = js_get_errno(rename(oldpath, newpath)); |
| JS_FreeCString(ctx, oldpath); |
| JS_FreeCString(ctx, newpath); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| static BOOL is_main_thread(JSRuntime *rt) |
| { |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| return !ts->recv_pipe; |
| } |
| |
| static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) |
| { |
| JSOSRWHandler *rh; |
| struct list_head *el; |
| |
| list_for_each(el, &ts->os_rw_handlers) { |
| rh = list_entry(el, JSOSRWHandler, link); |
| if (rh->fd == fd) |
| return rh; |
| } |
| return NULL; |
| } |
| |
| static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) |
| { |
| int i; |
| list_del(&rh->link); |
| for(i = 0; i < 2; i++) { |
| JS_FreeValueRT(rt, rh->rw_func[i]); |
| } |
| js_free_rt(rt, rh); |
| } |
| |
| static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv, int magic) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| JSOSRWHandler *rh; |
| int fd; |
| JSValueConst func; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| func = argv[1]; |
| if (JS_IsNull(func)) { |
| rh = find_rh(ts, fd); |
| if (rh) { |
| JS_FreeValue(ctx, rh->rw_func[magic]); |
| rh->rw_func[magic] = JS_NULL; |
| if (JS_IsNull(rh->rw_func[0]) && |
| JS_IsNull(rh->rw_func[1])) { |
| /* remove the entry */ |
| free_rw_handler(JS_GetRuntime(ctx), rh); |
| } |
| } |
| } else { |
| if (!JS_IsFunction(ctx, func)) |
| return JS_ThrowTypeError(ctx, "not a function"); |
| rh = find_rh(ts, fd); |
| if (!rh) { |
| rh = js_mallocz(ctx, sizeof(*rh)); |
| if (!rh) |
| return JS_EXCEPTION; |
| rh->fd = fd; |
| rh->rw_func[0] = JS_NULL; |
| rh->rw_func[1] = JS_NULL; |
| list_add_tail(&rh->link, &ts->os_rw_handlers); |
| } |
| JS_FreeValue(ctx, rh->rw_func[magic]); |
| rh->rw_func[magic] = JS_DupValue(ctx, func); |
| } |
| return JS_UNDEFINED; |
| } |
| |
| #if defined(__Fuchsia__) |
| |
| static void free_handle_handler(JSRuntime *rt, JSOSHandleHandler *hh) { |
| list_del(&hh->link); |
| JS_FreeValueRT(rt, hh->func); |
| js_free_rt(rt, hh); |
| } |
| |
| #endif |
| |
| #if !defined(__Fuchsia__) |
| |
| static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) |
| { |
| JSOSSignalHandler *sh; |
| struct list_head *el; |
| list_for_each(el, &ts->os_signal_handlers) { |
| sh = list_entry(el, JSOSSignalHandler, link); |
| if (sh->sig_num == sig_num) |
| return sh; |
| } |
| return NULL; |
| } |
| |
| static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh) |
| { |
| list_del(&sh->link); |
| JS_FreeValueRT(rt, sh->func); |
| js_free_rt(rt, sh); |
| } |
| |
| static void os_signal_handler(int sig_num) |
| { |
| os_pending_signals |= ((uint64_t)1 << sig_num); |
| } |
| |
| #if defined(_WIN32) |
| typedef void (*sighandler_t)(int sig_num); |
| #endif |
| |
| static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| JSOSSignalHandler *sh; |
| uint32_t sig_num; |
| JSValueConst func; |
| sighandler_t handler; |
| |
| if (!is_main_thread(rt)) |
| return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); |
| |
| if (JS_ToUint32(ctx, &sig_num, argv[0])) |
| return JS_EXCEPTION; |
| if (sig_num >= 64) |
| return JS_ThrowRangeError(ctx, "invalid signal number"); |
| func = argv[1]; |
| /* func = null: SIG_DFL, func = undefined, SIG_IGN */ |
| if (JS_IsNull(func) || JS_IsUndefined(func)) { |
| sh = find_sh(ts, sig_num); |
| if (sh) { |
| free_sh(JS_GetRuntime(ctx), sh); |
| } |
| if (JS_IsNull(func)) |
| handler = SIG_DFL; |
| else |
| handler = SIG_IGN; |
| signal(sig_num, handler); |
| } else { |
| if (!JS_IsFunction(ctx, func)) |
| return JS_ThrowTypeError(ctx, "not a function"); |
| sh = find_sh(ts, sig_num); |
| if (!sh) { |
| sh = js_mallocz(ctx, sizeof(*sh)); |
| if (!sh) |
| return JS_EXCEPTION; |
| sh->sig_num = sig_num; |
| list_add_tail(&sh->link, &ts->os_signal_handlers); |
| } |
| JS_FreeValue(ctx, sh->func); |
| sh->func = JS_DupValue(ctx, func); |
| signal(sig_num, os_signal_handler); |
| } |
| return JS_UNDEFINED; |
| } |
| #endif // !defined(__Fuchsia__) |
| |
| #if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__) |
| static int64_t get_time_ms(void) |
| { |
| struct timespec ts; |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); |
| } |
| #else |
| /* more portable, but does not work if the date is updated */ |
| static int64_t get_time_ms(void) |
| { |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); |
| } |
| #endif |
| |
| static void unlink_timer(JSRuntime *rt, JSOSTimer *th) |
| { |
| if (th->link.prev) { |
| list_del(&th->link); |
| th->link.prev = th->link.next = NULL; |
| } |
| } |
| |
| static void free_timer(JSRuntime *rt, JSOSTimer *th) |
| { |
| JS_FreeValueRT(rt, th->func); |
| js_free_rt(rt, th); |
| } |
| |
| static JSClassID js_os_timer_class_id; |
| |
| static void js_os_timer_finalizer(JSRuntime *rt, JSValue val) |
| { |
| JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id); |
| if (th) { |
| th->has_object = FALSE; |
| if (!th->link.prev) |
| free_timer(rt, th); |
| } |
| } |
| |
| static void js_os_timer_mark(JSRuntime *rt, JSValueConst val, |
| JS_MarkFunc *mark_func) |
| { |
| JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id); |
| if (th) { |
| JS_MarkValue(rt, th->func, mark_func); |
| } |
| } |
| |
| static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| int64_t delay; |
| JSValueConst func; |
| JSOSTimer *th; |
| JSValue obj; |
| |
| func = argv[0]; |
| if (!JS_IsFunction(ctx, func)) |
| return JS_ThrowTypeError(ctx, "not a function"); |
| if (JS_ToInt64(ctx, &delay, argv[1])) |
| return JS_EXCEPTION; |
| obj = JS_NewObjectClass(ctx, js_os_timer_class_id); |
| if (JS_IsException(obj)) |
| return obj; |
| th = js_mallocz(ctx, sizeof(*th)); |
| if (!th) { |
| JS_FreeValue(ctx, obj); |
| return JS_EXCEPTION; |
| } |
| th->has_object = TRUE; |
| th->timeout = get_time_ms() + delay; |
| th->func = JS_DupValue(ctx, func); |
| list_add_tail(&th->link, &ts->os_timers); |
| JS_SetOpaque(obj, th); |
| return obj; |
| } |
| |
| static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSOSTimer *th = JS_GetOpaque2(ctx, argv[0], js_os_timer_class_id); |
| if (!th) |
| return JS_EXCEPTION; |
| unlink_timer(JS_GetRuntime(ctx), th); |
| return JS_UNDEFINED; |
| } |
| |
| static JSClassDef js_os_timer_class = { |
| "OSTimer", |
| .finalizer = js_os_timer_finalizer, |
| .gc_mark = js_os_timer_mark, |
| }; |
| |
| static void call_handler(JSContext *ctx, JSValueConst func) |
| { |
| JSValue ret, func1; |
| /* 'func' might be destroyed when calling itself (if it frees the |
| handler), so must take extra care */ |
| func1 = JS_DupValue(ctx, func); |
| ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); |
| JS_FreeValue(ctx, func1); |
| if (JS_IsException(ret)) |
| js_std_dump_error(ctx); |
| JS_FreeValue(ctx, ret); |
| } |
| |
| #if defined(_WIN32) |
| |
| static int js_os_poll(JSContext *ctx) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| int min_delay, console_fd; |
| int64_t cur_time, delay; |
| JSOSRWHandler *rh; |
| struct list_head *el; |
| |
| /* XXX: handle signals if useful */ |
| |
| if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers)) |
| return -1; /* no more events */ |
| |
| /* XXX: only timers and basic console input are supported */ |
| if (!list_empty(&ts->os_timers)) { |
| cur_time = get_time_ms(); |
| min_delay = 10000; |
| list_for_each(el, &ts->os_timers) { |
| JSOSTimer *th = list_entry(el, JSOSTimer, link); |
| delay = th->timeout - cur_time; |
| if (delay <= 0) { |
| JSValue func; |
| /* the timer expired */ |
| func = th->func; |
| th->func = JS_UNDEFINED; |
| unlink_timer(rt, th); |
| if (!th->has_object) |
| free_timer(rt, th); |
| call_handler(ctx, func); |
| JS_FreeValue(ctx, func); |
| return 0; |
| } else if (delay < min_delay) { |
| min_delay = delay; |
| } |
| } |
| } else { |
| min_delay = -1; |
| } |
| |
| console_fd = -1; |
| list_for_each(el, &ts->os_rw_handlers) { |
| rh = list_entry(el, JSOSRWHandler, link); |
| if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { |
| console_fd = rh->fd; |
| break; |
| } |
| } |
| |
| if (console_fd >= 0) { |
| DWORD ti, ret; |
| HANDLE handle; |
| if (min_delay == -1) |
| ti = INFINITE; |
| else |
| ti = min_delay; |
| handle = (HANDLE)_get_osfhandle(console_fd); |
| ret = WaitForSingleObject(handle, ti); |
| if (ret == WAIT_OBJECT_0) { |
| list_for_each(el, &ts->os_rw_handlers) { |
| rh = list_entry(el, JSOSRWHandler, link); |
| if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) { |
| call_handler(ctx, rh->rw_func[0]); |
| /* must stop because the list may have been modified */ |
| break; |
| } |
| } |
| } |
| } else { |
| Sleep(min_delay); |
| } |
| return 0; |
| } |
| #elif defined(__Fuchsia__) |
| void wait_for_zx_handle(JSContext *ctx, JSFuchsiaHandle *h, uint32_t signals, JSValueConst *value) { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| JSOSHandleHandler *hh = js_mallocz(ctx, sizeof(JSOSHandleHandler)); |
| hh->handle = h->handle; |
| hh->signals = signals; |
| hh->func = JS_DupValue(ctx, *value); |
| list_add_tail(&hh->link, &ts->os_handle_handlers); |
| } |
| |
| void stop_waiting_for_zx_handle(JSRuntime *rt, JSFuchsiaHandle *h, int n) { |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| struct list_head *el, *el1; |
| if (!list_empty(&ts->os_handle_handlers)) { |
| list_for_each_safe(el, el1, &ts->os_handle_handlers) { |
| JSOSHandleHandler *hh = list_entry(el, JSOSHandleHandler, link); |
| if (hh->handle == h->handle) { |
| free_handle_handler(rt, hh); |
| if (--n == 0) { |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| static int js_os_poll(JSContext *ctx) { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| struct list_head *el; |
| zx_status_t status; |
| zx_time_t deadline = ZX_TIME_INFINITE; |
| |
| if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_handle_handlers) && list_empty(&ts->os_timers)) { |
| return -1; |
| } |
| |
| if (!list_empty(&ts->os_timers)) { |
| int64_t cur_time = get_time_ms(); |
| |
| list_for_each(el, &ts->os_timers) { |
| JSOSTimer *th = list_entry(el, JSOSTimer, link); |
| // TODO(ianloic): make safe from overflows |
| zx_time_t timer_deadline = th->timeout * 1000000; |
| if (cur_time >= th->timeout) { |
| /* the timer expired */ |
| JSValue func = th->func; |
| th->func = JS_UNDEFINED; |
| unlink_timer(rt, th); |
| if (!th->has_object) |
| free_timer(rt, th); |
| call_handler(ctx, func); |
| JS_FreeValue(ctx, func); |
| return 0; |
| } else if (timer_deadline < deadline) { |
| deadline = timer_deadline; |
| } |
| } |
| } |
| // TODO: timer slack |
| |
| if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_handle_handlers)) { |
| // Not waiting on any IO |
| assert(deadline != ZX_TIME_INFINITE); |
| status = zx_nanosleep(deadline); |
| assert(status == ZX_OK); |
| return 0; |
| } |
| |
| zx_handle_t port = ZX_HANDLE_INVALID; |
| status = zx_port_create(0, &port); |
| assert(status == ZX_OK); |
| |
| if (!list_empty(&ts->os_handle_handlers)) { |
| list_for_each(el, &ts->os_handle_handlers) { |
| JSOSHandleHandler *hh = list_entry(el, JSOSHandleHandler, link); |
| status = zx_object_wait_async(hh->handle, port, (uint64_t)hh, hh->signals, 0); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Bad status from wait_async: %d\n", status); |
| assert(status == ZX_OK); |
| } |
| } |
| } |
| |
| if (!list_empty(&ts->os_rw_handlers)) { |
| list_for_each(el, &ts->os_rw_handlers) { |
| JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); |
| |
| fdio_t *io = fdio_unsafe_fd_to_io(rh->fd); |
| assert(io != NULL); |
| |
| zx_handle_t handle; |
| zx_signals_t signals; |
| uint32_t events = 0; |
| if (!JS_IsNull(rh->rw_func[0])) { |
| events |= POLLIN; |
| } |
| if (!JS_IsNull(rh->rw_func[1])) { |
| events |= POLLOUT; |
| } |
| fdio_unsafe_wait_begin(io, events, &handle, &signals); |
| fdio_unsafe_release(io); |
| |
| status = zx_object_wait_async(handle, port, ((uint64_t)rh) + 1, signals, 0); |
| assert(status == ZX_OK); |
| } |
| } |
| |
| zx_port_packet_t packet; |
| status = zx_port_wait(port, deadline, &packet); |
| zx_handle_close(port); |
| |
| if (status == ZX_ERR_TIMED_OUT) { |
| return 0; |
| } |
| |
| assert(status == ZX_OK); |
| assert(packet.type == ZX_PKT_TYPE_SIGNAL_ONE); |
| assert(packet.status == ZX_OK); |
| |
| if (packet.key % 2) { |
| // Packet for rw handler |
| JSOSRWHandler *rh = (JSOSRWHandler *)(packet.key - 1); |
| fdio_t *io = fdio_unsafe_fd_to_io(rh->fd); |
| assert(io != NULL); |
| |
| uint32_t events; |
| fdio_unsafe_wait_end(io, packet.signal.observed, &events); |
| fdio_unsafe_release(io); |
| |
| if (events & POLLIN && !JS_IsNull(rh->rw_func[0])) { |
| call_handler(ctx, rh->rw_func[0]); |
| } else if (events & POLLOUT && !JS_IsNull(rh->rw_func[1])) { |
| call_handler(ctx, rh->rw_func[1]); |
| } |
| } else { |
| // Packet for handle handler |
| JSOSHandleHandler *hh = (JSOSHandleHandler *)packet.key; |
| call_handler(ctx, hh->func); |
| JSFuchsiaHandle fh; |
| fh.handle = hh->handle; |
| stop_waiting_for_zx_handle(JS_GetRuntime(ctx), &fh, 1); |
| } |
| return 0; |
| } |
| |
| #else |
| |
| #ifdef USE_WORKER |
| |
| static void js_free_message(JSWorkerMessage *msg); |
| |
| /* return 1 if a message was handled, 0 if no message */ |
| static int handle_posted_message(JSRuntime *rt, JSContext *ctx, |
| JSWorkerMessageHandler *port) |
| { |
| JSWorkerMessagePipe *ps = port->recv_pipe; |
| int ret; |
| struct list_head *el; |
| JSWorkerMessage *msg; |
| JSValue obj, data_obj, func, retval; |
| |
| pthread_mutex_lock(&ps->mutex); |
| if (!list_empty(&ps->msg_queue)) { |
| el = ps->msg_queue.next; |
| msg = list_entry(el, JSWorkerMessage, link); |
| |
| /* remove the message from the queue */ |
| list_del(&msg->link); |
| |
| if (list_empty(&ps->msg_queue)) { |
| uint8_t buf[16]; |
| int ret; |
| for(;;) { |
| ret = read(ps->read_fd, buf, sizeof(buf)); |
| if (ret >= 0) |
| break; |
| if (errno != EAGAIN && errno != EINTR) |
| break; |
| } |
| } |
| |
| pthread_mutex_unlock(&ps->mutex); |
| |
| data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, |
| JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); |
| |
| js_free_message(msg); |
| |
| if (JS_IsException(data_obj)) |
| goto fail; |
| obj = JS_NewObject(ctx); |
| if (JS_IsException(obj)) { |
| JS_FreeValue(ctx, data_obj); |
| goto fail; |
| } |
| JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); |
| |
| /* 'func' might be destroyed when calling itself (if it frees the |
| handler), so must take extra care */ |
| func = JS_DupValue(ctx, port->on_message_func); |
| retval = JS_Call(ctx, func, JS_UNDEFINED, 1, (JSValueConst *)&obj); |
| JS_FreeValue(ctx, obj); |
| JS_FreeValue(ctx, func); |
| if (JS_IsException(retval)) { |
| fail: |
| js_std_dump_error(ctx); |
| } else { |
| JS_FreeValue(ctx, retval); |
| } |
| ret = 1; |
| } else { |
| pthread_mutex_unlock(&ps->mutex); |
| ret = 0; |
| } |
| return ret; |
| } |
| #else |
| static int handle_posted_message(JSRuntime *rt, JSContext *ctx, |
| JSWorkerMessageHandler *port) |
| { |
| return 0; |
| } |
| #endif |
| |
| static int js_os_poll(JSContext *ctx) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| int ret, fd_max, min_delay; |
| int64_t cur_time, delay; |
| fd_set rfds, wfds; |
| JSOSRWHandler *rh; |
| struct list_head *el; |
| struct timeval tv, *tvp; |
| |
| /* only check signals in the main thread */ |
| if (!ts->recv_pipe && |
| unlikely(os_pending_signals != 0)) { |
| JSOSSignalHandler *sh; |
| uint64_t mask; |
| |
| list_for_each(el, &ts->os_signal_handlers) { |
| sh = list_entry(el, JSOSSignalHandler, link); |
| mask = (uint64_t)1 << sh->sig_num; |
| if (os_pending_signals & mask) { |
| os_pending_signals &= ~mask; |
| call_handler(ctx, sh->func); |
| return 0; |
| } |
| } |
| } |
| |
| if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && |
| list_empty(&ts->port_list)) |
| return -1; /* no more events */ |
| |
| if (!list_empty(&ts->os_timers)) { |
| cur_time = get_time_ms(); |
| min_delay = 10000; |
| list_for_each(el, &ts->os_timers) { |
| JSOSTimer *th = list_entry(el, JSOSTimer, link); |
| delay = th->timeout - cur_time; |
| if (delay <= 0) { |
| JSValue func; |
| /* the timer expired */ |
| func = th->func; |
| th->func = JS_UNDEFINED; |
| unlink_timer(rt, th); |
| if (!th->has_object) |
| free_timer(rt, th); |
| call_handler(ctx, func); |
| JS_FreeValue(ctx, func); |
| return 0; |
| } else if (delay < min_delay) { |
| min_delay = delay; |
| } |
| } |
| tv.tv_sec = min_delay / 1000; |
| tv.tv_usec = (min_delay % 1000) * 1000; |
| tvp = &tv; |
| } else { |
| tvp = NULL; |
| } |
| |
| FD_ZERO(&rfds); |
| FD_ZERO(&wfds); |
| fd_max = -1; |
| list_for_each(el, &ts->os_rw_handlers) { |
| rh = list_entry(el, JSOSRWHandler, link); |
| fd_max = max_int(fd_max, rh->fd); |
| if (!JS_IsNull(rh->rw_func[0])) |
| FD_SET(rh->fd, &rfds); |
| if (!JS_IsNull(rh->rw_func[1])) |
| FD_SET(rh->fd, &wfds); |
| } |
| |
| list_for_each(el, &ts->port_list) { |
| JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); |
| if (!JS_IsNull(port->on_message_func)) { |
| JSWorkerMessagePipe *ps = port->recv_pipe; |
| fd_max = max_int(fd_max, ps->read_fd); |
| FD_SET(ps->read_fd, &rfds); |
| } |
| } |
| |
| ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); |
| if (ret > 0) { |
| list_for_each(el, &ts->os_rw_handlers) { |
| rh = list_entry(el, JSOSRWHandler, link); |
| if (!JS_IsNull(rh->rw_func[0]) && |
| FD_ISSET(rh->fd, &rfds)) { |
| call_handler(ctx, rh->rw_func[0]); |
| /* must stop because the list may have been modified */ |
| goto done; |
| } |
| if (!JS_IsNull(rh->rw_func[1]) && |
| FD_ISSET(rh->fd, &wfds)) { |
| call_handler(ctx, rh->rw_func[1]); |
| /* must stop because the list may have been modified */ |
| goto done; |
| } |
| } |
| |
| list_for_each(el, &ts->port_list) { |
| JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); |
| if (!JS_IsNull(port->on_message_func)) { |
| JSWorkerMessagePipe *ps = port->recv_pipe; |
| if (FD_ISSET(ps->read_fd, &rfds)) { |
| if (handle_posted_message(rt, ctx, port)) |
| goto done; |
| } |
| } |
| } |
| } |
| done: |
| return 0; |
| } |
| #endif /* !_WIN32 */ |
| |
| static JSValue make_obj_error(JSContext *ctx, |
| JSValue obj, |
| int err) |
| { |
| JSValue arr; |
| if (JS_IsException(obj)) |
| return obj; |
| arr = JS_NewArray(ctx); |
| if (JS_IsException(arr)) |
| return JS_EXCEPTION; |
| JS_DefinePropertyValueUint32(ctx, arr, 0, obj, |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err), |
| JS_PROP_C_W_E); |
| return arr; |
| } |
| |
| static JSValue make_string_error(JSContext *ctx, |
| const char *buf, |
| int err) |
| { |
| return make_obj_error(ctx, JS_NewString(ctx, buf), err); |
| } |
| |
| /* return [cwd, errorcode] */ |
| static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| char buf[PATH_MAX]; |
| int err; |
| |
| if (!getcwd(buf, sizeof(buf))) { |
| buf[0] = '\0'; |
| err = errno; |
| } else { |
| err = 0; |
| } |
| return make_string_error(ctx, buf, err); |
| } |
| |
| static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *target; |
| int err; |
| |
| target = JS_ToCString(ctx, argv[0]); |
| if (!target) |
| return JS_EXCEPTION; |
| err = js_get_errno(chdir(target)); |
| JS_FreeCString(ctx, target); |
| return JS_NewInt32(ctx, err); |
| } |
| |
| static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int mode, ret; |
| const char *path; |
| |
| if (argc >= 2) { |
| if (JS_ToInt32(ctx, &mode, argv[1])) |
| return JS_EXCEPTION; |
| } else { |
| mode = 0777; |
| } |
| path = JS_ToCString(ctx, argv[0]); |
| if (!path) |
| return JS_EXCEPTION; |
| #if defined(_WIN32) |
| (void)mode; |
| ret = js_get_errno(mkdir(path)); |
| #else |
| ret = js_get_errno(mkdir(path, mode)); |
| #endif |
| JS_FreeCString(ctx, path); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| /* return [array, errorcode] */ |
| static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *path; |
| DIR *f; |
| struct dirent *d; |
| JSValue obj; |
| int err; |
| uint32_t len; |
| |
| path = JS_ToCString(ctx, argv[0]); |
| if (!path) |
| return JS_EXCEPTION; |
| obj = JS_NewArray(ctx); |
| if (JS_IsException(obj)) { |
| JS_FreeCString(ctx, path); |
| return JS_EXCEPTION; |
| } |
| f = opendir(path); |
| if (!f) |
| err = errno; |
| else |
| err = 0; |
| JS_FreeCString(ctx, path); |
| if (!f) |
| goto done; |
| len = 0; |
| for(;;) { |
| errno = 0; |
| d = readdir(f); |
| if (!d) { |
| err = errno; |
| break; |
| } |
| JS_DefinePropertyValueUint32(ctx, obj, len++, |
| JS_NewString(ctx, d->d_name), |
| JS_PROP_C_W_E); |
| } |
| closedir(f); |
| done: |
| return make_obj_error(ctx, obj, err); |
| } |
| |
| #if !defined(_WIN32) |
| static int64_t timespec_to_ms(const struct timespec *tv) |
| { |
| return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); |
| } |
| #endif |
| |
| /* return [obj, errcode] */ |
| static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv, int is_lstat) |
| { |
| const char *path; |
| int err, res; |
| struct stat st; |
| JSValue obj; |
| |
| path = JS_ToCString(ctx, argv[0]); |
| if (!path) |
| return JS_EXCEPTION; |
| #if defined(_WIN32) |
| res = stat(path, &st); |
| #else |
| if (is_lstat) |
| res = lstat(path, &st); |
| else |
| res = stat(path, &st); |
| #endif |
| JS_FreeCString(ctx, path); |
| if (res < 0) { |
| err = errno; |
| obj = JS_NULL; |
| } else { |
| err = 0; |
| obj = JS_NewObject(ctx); |
| if (JS_IsException(obj)) |
| return JS_EXCEPTION; |
| JS_DefinePropertyValueStr(ctx, obj, "dev", |
| JS_NewInt64(ctx, st.st_dev), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "ino", |
| JS_NewInt64(ctx, st.st_ino), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "mode", |
| JS_NewInt32(ctx, st.st_mode), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "nlink", |
| JS_NewInt64(ctx, st.st_nlink), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "uid", |
| JS_NewInt64(ctx, st.st_uid), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "gid", |
| JS_NewInt64(ctx, st.st_gid), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "rdev", |
| JS_NewInt64(ctx, st.st_rdev), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "size", |
| JS_NewInt64(ctx, st.st_size), |
| JS_PROP_C_W_E); |
| #if !defined(_WIN32) |
| JS_DefinePropertyValueStr(ctx, obj, "blocks", |
| JS_NewInt64(ctx, st.st_blocks), |
| JS_PROP_C_W_E); |
| #endif |
| #if defined(_WIN32) |
| JS_DefinePropertyValueStr(ctx, obj, "atime", |
| JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "mtime", |
| JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "ctime", |
| JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), |
| JS_PROP_C_W_E); |
| #elif defined(__APPLE__) |
| JS_DefinePropertyValueStr(ctx, obj, "atime", |
| JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "mtime", |
| JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "ctime", |
| JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)), |
| JS_PROP_C_W_E); |
| #else |
| JS_DefinePropertyValueStr(ctx, obj, "atime", |
| JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "mtime", |
| JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueStr(ctx, obj, "ctime", |
| JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)), |
| JS_PROP_C_W_E); |
| #endif |
| } |
| return make_obj_error(ctx, obj, err); |
| } |
| |
| #if !defined(_WIN32) |
| static void ms_to_timeval(struct timeval *tv, uint64_t v) |
| { |
| tv->tv_sec = v / 1000; |
| tv->tv_usec = (v % 1000) * 1000; |
| } |
| #endif |
| |
| static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *path; |
| int64_t atime, mtime; |
| int ret; |
| |
| if (JS_ToInt64(ctx, &atime, argv[1])) |
| return JS_EXCEPTION; |
| if (JS_ToInt64(ctx, &mtime, argv[2])) |
| return JS_EXCEPTION; |
| path = JS_ToCString(ctx, argv[0]); |
| if (!path) |
| return JS_EXCEPTION; |
| #if defined(_WIN32) |
| { |
| struct _utimbuf times; |
| times.actime = atime / 1000; |
| times.modtime = mtime / 1000; |
| ret = js_get_errno(_utime(path, ×)); |
| } |
| #else |
| { |
| struct timeval times[2]; |
| ms_to_timeval(×[0], atime); |
| ms_to_timeval(×[1], mtime); |
| ret = js_get_errno(utimes(path, times)); |
| } |
| #endif |
| JS_FreeCString(ctx, path); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| #if !defined(_WIN32) |
| |
| /* return [path, errorcode] */ |
| static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *path; |
| char buf[PATH_MAX], *res; |
| int err; |
| |
| path = JS_ToCString(ctx, argv[0]); |
| if (!path) |
| return JS_EXCEPTION; |
| res = realpath(path, buf); |
| JS_FreeCString(ctx, path); |
| if (!res) { |
| buf[0] = '\0'; |
| err = errno; |
| } else { |
| err = 0; |
| } |
| return make_string_error(ctx, buf, err); |
| } |
| |
| static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *target, *linkpath; |
| int err; |
| |
| target = JS_ToCString(ctx, argv[0]); |
| if (!target) |
| return JS_EXCEPTION; |
| linkpath = JS_ToCString(ctx, argv[1]); |
| if (!linkpath) { |
| JS_FreeCString(ctx, target); |
| return JS_EXCEPTION; |
| } |
| err = js_get_errno(symlink(target, linkpath)); |
| JS_FreeCString(ctx, target); |
| JS_FreeCString(ctx, linkpath); |
| return JS_NewInt32(ctx, err); |
| } |
| |
| /* return [path, errorcode] */ |
| static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *path; |
| char buf[PATH_MAX]; |
| int err; |
| ssize_t res; |
| |
| path = JS_ToCString(ctx, argv[0]); |
| if (!path) |
| return JS_EXCEPTION; |
| res = readlink(path, buf, sizeof(buf) - 1); |
| if (res < 0) { |
| buf[0] = '\0'; |
| err = errno; |
| } else { |
| buf[res] = '\0'; |
| err = 0; |
| } |
| JS_FreeCString(ctx, path); |
| return make_string_error(ctx, buf, err); |
| } |
| |
| static char **build_envp(JSContext *ctx, JSValueConst obj) |
| { |
| uint32_t len, i; |
| JSPropertyEnum *tab; |
| char **envp, *pair; |
| const char *key, *str; |
| JSValue val; |
| size_t key_len, str_len; |
| |
| if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, |
| JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) |
| return NULL; |
| envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); |
| if (!envp) |
| goto fail; |
| for(i = 0; i < len; i++) { |
| val = JS_GetProperty(ctx, obj, tab[i].atom); |
| if (JS_IsException(val)) |
| goto fail; |
| str = JS_ToCString(ctx, val); |
| JS_FreeValue(ctx, val); |
| if (!str) |
| goto fail; |
| key = JS_AtomToCString(ctx, tab[i].atom); |
| if (!key) { |
| JS_FreeCString(ctx, str); |
| goto fail; |
| } |
| key_len = strlen(key); |
| str_len = strlen(str); |
| pair = js_malloc(ctx, key_len + str_len + 2); |
| if (!pair) { |
| JS_FreeCString(ctx, key); |
| JS_FreeCString(ctx, str); |
| goto fail; |
| } |
| memcpy(pair, key, key_len); |
| pair[key_len] = '='; |
| memcpy(pair + key_len + 1, str, str_len); |
| pair[key_len + 1 + str_len] = '\0'; |
| envp[i] = pair; |
| JS_FreeCString(ctx, key); |
| JS_FreeCString(ctx, str); |
| } |
| done: |
| for(i = 0; i < len; i++) |
| JS_FreeAtom(ctx, tab[i].atom); |
| js_free(ctx, tab); |
| return envp; |
| fail: |
| if (envp) { |
| for(i = 0; i < len; i++) |
| js_free(ctx, envp[i]); |
| js_free(ctx, envp); |
| envp = NULL; |
| } |
| goto done; |
| } |
| |
| /* execvpe is not available on non GNU systems */ |
| static int my_execvpe(const char *filename, char **argv, char **envp) |
| { |
| char *path, *p, *p_next, *p1; |
| char buf[PATH_MAX]; |
| size_t filename_len, path_len; |
| BOOL eacces_error; |
| |
| filename_len = strlen(filename); |
| if (filename_len == 0) { |
| errno = ENOENT; |
| return -1; |
| } |
| if (strchr(filename, '/')) |
| return execve(filename, argv, envp); |
| |
| path = getenv("PATH"); |
| if (!path) |
| path = (char *)"/bin:/usr/bin"; |
| eacces_error = FALSE; |
| p = path; |
| for(p = path; p != NULL; p = p_next) { |
| p1 = strchr(p, ':'); |
| if (!p1) { |
| p_next = NULL; |
| path_len = strlen(p); |
| } else { |
| p_next = p1 + 1; |
| path_len = p1 - p; |
| } |
| /* path too long */ |
| if ((path_len + 1 + filename_len + 1) > PATH_MAX) |
| continue; |
| memcpy(buf, p, path_len); |
| buf[path_len] = '/'; |
| memcpy(buf + path_len + 1, filename, filename_len); |
| buf[path_len + 1 + filename_len] = '\0'; |
| |
| execve(buf, argv, envp); |
| |
| switch(errno) { |
| case EACCES: |
| eacces_error = TRUE; |
| break; |
| case ENOENT: |
| case ENOTDIR: |
| break; |
| default: |
| return -1; |
| } |
| } |
| if (eacces_error) |
| errno = EACCES; |
| return -1; |
| } |
| |
| /* exec(args[, options]) -> exitcode */ |
| static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSValueConst options, args = argv[0]; |
| JSValue val, ret_val; |
| const char **exec_argv, *file = NULL, *str, *cwd = NULL; |
| char **envp = environ; |
| uint32_t exec_argc, i; |
| int ret, pid, status; |
| BOOL block_flag = TRUE, use_path = TRUE; |
| static const char *std_name[3] = { "stdin", "stdout", "stderr" }; |
| int std_fds[3]; |
| uint32_t uid = -1, gid = -1; |
| |
| val = JS_GetPropertyStr(ctx, args, "length"); |
| if (JS_IsException(val)) |
| return JS_EXCEPTION; |
| ret = JS_ToUint32(ctx, &exec_argc, val); |
| JS_FreeValue(ctx, val); |
| if (ret) |
| return JS_EXCEPTION; |
| /* arbitrary limit to avoid overflow */ |
| if (exec_argc < 1 || exec_argc > 65535) { |
| return JS_ThrowTypeError(ctx, "invalid number of arguments"); |
| } |
| exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1)); |
| if (!exec_argv) |
| return JS_EXCEPTION; |
| for(i = 0; i < exec_argc; i++) { |
| val = JS_GetPropertyUint32(ctx, args, i); |
| if (JS_IsException(val)) |
| goto exception; |
| str = JS_ToCString(ctx, val); |
| JS_FreeValue(ctx, val); |
| if (!str) |
| goto exception; |
| exec_argv[i] = str; |
| } |
| exec_argv[exec_argc] = NULL; |
| |
| for(i = 0; i < 3; i++) |
| std_fds[i] = i; |
| |
| /* get the options, if any */ |
| if (argc >= 2) { |
| options = argv[1]; |
| |
| if (get_bool_option(ctx, &block_flag, options, "block")) |
| goto exception; |
| if (get_bool_option(ctx, &use_path, options, "usePath")) |
| goto exception; |
| |
| val = JS_GetPropertyStr(ctx, options, "file"); |
| if (JS_IsException(val)) |
| goto exception; |
| if (!JS_IsUndefined(val)) { |
| file = JS_ToCString(ctx, val); |
| JS_FreeValue(ctx, val); |
| if (!file) |
| goto exception; |
| } |
| |
| val = JS_GetPropertyStr(ctx, options, "cwd"); |
| if (JS_IsException(val)) |
| goto exception; |
| if (!JS_IsUndefined(val)) { |
| cwd = JS_ToCString(ctx, val); |
| JS_FreeValue(ctx, val); |
| if (!cwd) |
| goto exception; |
| } |
| |
| /* stdin/stdout/stderr handles */ |
| for(i = 0; i < 3; i++) { |
| val = JS_GetPropertyStr(ctx, options, std_name[i]); |
| if (JS_IsException(val)) |
| goto exception; |
| if (!JS_IsUndefined(val)) { |
| int fd; |
| ret = JS_ToInt32(ctx, &fd, val); |
| JS_FreeValue(ctx, val); |
| if (ret) |
| goto exception; |
| std_fds[i] = fd; |
| } |
| } |
| |
| val = JS_GetPropertyStr(ctx, options, "env"); |
| if (JS_IsException(val)) |
| goto exception; |
| if (!JS_IsUndefined(val)) { |
| envp = build_envp(ctx, val); |
| JS_FreeValue(ctx, val); |
| if (!envp) |
| goto exception; |
| } |
| |
| val = JS_GetPropertyStr(ctx, options, "uid"); |
| if (JS_IsException(val)) |
| goto exception; |
| if (!JS_IsUndefined(val)) { |
| ret = JS_ToUint32(ctx, &uid, val); |
| JS_FreeValue(ctx, val); |
| if (ret) |
| goto exception; |
| } |
| |
| val = JS_GetPropertyStr(ctx, options, "gid"); |
| if (JS_IsException(val)) |
| goto exception; |
| if (!JS_IsUndefined(val)) { |
| ret = JS_ToUint32(ctx, &gid, val); |
| JS_FreeValue(ctx, val); |
| if (ret) |
| goto exception; |
| } |
| } |
| |
| pid = fork(); |
| if (pid < 0) { |
| JS_ThrowTypeError(ctx, "fork error"); |
| goto exception; |
| } |
| if (pid == 0) { |
| /* child */ |
| int fd_max = sysconf(_SC_OPEN_MAX); |
| |
| /* remap the stdin/stdout/stderr handles if necessary */ |
| for(i = 0; i < 3; i++) { |
| if (std_fds[i] != i) { |
| if (dup2(std_fds[i], i) < 0) |
| _exit(127); |
| } |
| } |
| |
| for(i = 3; i < fd_max; i++) |
| close(i); |
| if (cwd) { |
| if (chdir(cwd) < 0) |
| _exit(127); |
| } |
| if (uid != -1) { |
| if (setuid(uid) < 0) |
| _exit(127); |
| } |
| if (gid != -1) { |
| if (setgid(gid) < 0) |
| _exit(127); |
| } |
| |
| if (!file) |
| file = exec_argv[0]; |
| if (use_path) |
| ret = my_execvpe(file, (char **)exec_argv, envp); |
| else |
| ret = execve(file, (char **)exec_argv, envp); |
| _exit(127); |
| } |
| /* parent */ |
| if (block_flag) { |
| for(;;) { |
| ret = waitpid(pid, &status, 0); |
| if (ret == pid) { |
| if (WIFEXITED(status)) { |
| ret = WEXITSTATUS(status); |
| break; |
| } else if (WIFSIGNALED(status)) { |
| ret = -WTERMSIG(status); |
| break; |
| } |
| } |
| } |
| } else { |
| ret = pid; |
| } |
| ret_val = JS_NewInt32(ctx, ret); |
| done: |
| JS_FreeCString(ctx, file); |
| JS_FreeCString(ctx, cwd); |
| for(i = 0; i < exec_argc; i++) |
| JS_FreeCString(ctx, exec_argv[i]); |
| js_free(ctx, exec_argv); |
| if (envp != environ) { |
| char **p; |
| p = envp; |
| while (*p != NULL) { |
| js_free(ctx, *p); |
| p++; |
| } |
| js_free(ctx, envp); |
| } |
| return ret_val; |
| exception: |
| ret_val = JS_EXCEPTION; |
| goto done; |
| } |
| |
| /* waitpid(pid, block) -> [pid, status] */ |
| static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int pid, status, options, ret; |
| JSValue obj; |
| |
| if (JS_ToInt32(ctx, &pid, argv[0])) |
| return JS_EXCEPTION; |
| if (JS_ToInt32(ctx, &options, argv[1])) |
| return JS_EXCEPTION; |
| |
| ret = waitpid(pid, &status, options); |
| if (ret < 0) { |
| ret = -errno; |
| status = 0; |
| } |
| |
| obj = JS_NewArray(ctx); |
| if (JS_IsException(obj)) |
| return obj; |
| JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status), |
| JS_PROP_C_W_E); |
| return obj; |
| } |
| |
| /* pipe() -> [read_fd, write_fd] or null if error */ |
| static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int pipe_fds[2], ret; |
| JSValue obj; |
| |
| ret = pipe(pipe_fds); |
| if (ret < 0) |
| return JS_NULL; |
| obj = JS_NewArray(ctx); |
| if (JS_IsException(obj)) |
| return obj; |
| JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), |
| JS_PROP_C_W_E); |
| JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), |
| JS_PROP_C_W_E); |
| return obj; |
| } |
| |
| /* kill(pid, sig) */ |
| static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int pid, sig, ret; |
| |
| if (JS_ToInt32(ctx, &pid, argv[0])) |
| return JS_EXCEPTION; |
| if (JS_ToInt32(ctx, &sig, argv[1])) |
| return JS_EXCEPTION; |
| ret = js_get_errno(kill(pid, sig)); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| /* sleep(delay_ms) */ |
| static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int64_t delay; |
| struct timespec ts; |
| int ret; |
| |
| if (JS_ToInt64(ctx, &delay, argv[0])) |
| return JS_EXCEPTION; |
| ts.tv_sec = delay / 1000; |
| ts.tv_nsec = (delay % 1000) * 1000000; |
| ret = js_get_errno(nanosleep(&ts, NULL)); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| /* dup(fd) */ |
| static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd, ret; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| ret = js_get_errno(dup(fd)); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| /* dup2(fd) */ |
| static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int fd, fd2, ret; |
| |
| if (JS_ToInt32(ctx, &fd, argv[0])) |
| return JS_EXCEPTION; |
| if (JS_ToInt32(ctx, &fd2, argv[1])) |
| return JS_EXCEPTION; |
| ret = js_get_errno(dup2(fd, fd2)); |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| #endif /* !_WIN32 */ |
| |
| #ifdef USE_WORKER |
| |
| /* Worker */ |
| |
| typedef struct { |
| JSWorkerMessagePipe *recv_pipe; |
| JSWorkerMessagePipe *send_pipe; |
| JSWorkerMessageHandler *msg_handler; |
| } JSWorkerData; |
| |
| typedef struct { |
| char *filename; /* module filename */ |
| char *basename; /* module base name */ |
| JSWorkerMessagePipe *recv_pipe, *send_pipe; |
| } WorkerFuncArgs; |
| |
| typedef struct { |
| int ref_count; |
| uint64_t buf[0]; |
| } JSSABHeader; |
| |
| static JSClassID js_worker_class_id; |
| static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); |
| |
| static int atomic_add_int(int *ptr, int v) |
| { |
| return atomic_fetch_add((_Atomic(uint32_t) *)ptr, v) + v; |
| } |
| |
| /* shared array buffer allocator */ |
| static void *js_sab_alloc(void *opaque, size_t size) |
| { |
| JSSABHeader *sab; |
| sab = malloc(sizeof(JSSABHeader) + size); |
| if (!sab) |
| return NULL; |
| sab->ref_count = 1; |
| return sab->buf; |
| } |
| |
| static void js_sab_free(void *opaque, void *ptr) |
| { |
| JSSABHeader *sab; |
| int ref_count; |
| sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); |
| ref_count = atomic_add_int(&sab->ref_count, -1); |
| assert(ref_count >= 0); |
| if (ref_count == 0) { |
| free(sab); |
| } |
| } |
| |
| static void js_sab_dup(void *opaque, void *ptr) |
| { |
| JSSABHeader *sab; |
| sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); |
| atomic_add_int(&sab->ref_count, 1); |
| } |
| |
| static JSWorkerMessagePipe *js_new_message_pipe(void) |
| { |
| JSWorkerMessagePipe *ps; |
| int pipe_fds[2]; |
| |
| if (pipe(pipe_fds) < 0) |
| return NULL; |
| |
| ps = malloc(sizeof(*ps)); |
| if (!ps) { |
| close(pipe_fds[0]); |
| close(pipe_fds[1]); |
| return NULL; |
| } |
| ps->ref_count = 1; |
| init_list_head(&ps->msg_queue); |
| pthread_mutex_init(&ps->mutex, NULL); |
| ps->read_fd = pipe_fds[0]; |
| ps->write_fd = pipe_fds[1]; |
| return ps; |
| } |
| |
| static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) |
| { |
| atomic_add_int(&ps->ref_count, 1); |
| return ps; |
| } |
| |
| static void js_free_message(JSWorkerMessage *msg) |
| { |
| size_t i; |
| /* free the SAB */ |
| for(i = 0; i < msg->sab_tab_len; i++) { |
| js_sab_free(NULL, msg->sab_tab[i]); |
| } |
| free(msg->sab_tab); |
| free(msg->data); |
| free(msg); |
| } |
| |
| static void js_free_message_pipe(JSWorkerMessagePipe *ps) |
| { |
| struct list_head *el, *el1; |
| JSWorkerMessage *msg; |
| int ref_count; |
| |
| if (!ps) |
| return; |
| |
| ref_count = atomic_add_int(&ps->ref_count, -1); |
| assert(ref_count >= 0); |
| if (ref_count == 0) { |
| list_for_each_safe(el, el1, &ps->msg_queue) { |
| msg = list_entry(el, JSWorkerMessage, link); |
| js_free_message(msg); |
| } |
| pthread_mutex_destroy(&ps->mutex); |
| close(ps->read_fd); |
| close(ps->write_fd); |
| free(ps); |
| } |
| } |
| |
| static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) |
| { |
| if (port) { |
| js_free_message_pipe(port->recv_pipe); |
| JS_FreeValueRT(rt, port->on_message_func); |
| list_del(&port->link); |
| js_free_rt(rt, port); |
| } |
| } |
| |
| static void js_worker_finalizer(JSRuntime *rt, JSValue val) |
| { |
| JSWorkerData *worker = JS_GetOpaque(val, js_worker_class_id); |
| if (worker) { |
| js_free_message_pipe(worker->recv_pipe); |
| js_free_message_pipe(worker->send_pipe); |
| js_free_port(rt, worker->msg_handler); |
| js_free_rt(rt, worker); |
| } |
| } |
| |
| static JSClassDef js_worker_class = { |
| "Worker", |
| .finalizer = js_worker_finalizer, |
| }; |
| |
| static void *worker_func(void *opaque) |
| { |
| WorkerFuncArgs *args = opaque; |
| JSRuntime *rt; |
| JSThreadState *ts; |
| JSContext *ctx; |
| |
| rt = JS_NewRuntime(); |
| if (rt == NULL) { |
| fprintf(stderr, "JS_NewRuntime failure"); |
| exit(1); |
| } |
| js_std_init_handlers(rt); |
| |
| JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); |
| |
| /* set the pipe to communicate with the parent */ |
| ts = JS_GetRuntimeOpaque(rt); |
| ts->recv_pipe = args->recv_pipe; |
| ts->send_pipe = args->send_pipe; |
| |
| /* function pointer to avoid linking the whole JS_NewContext() if |
| not needed */ |
| ctx = js_worker_new_context_func(rt); |
| if (ctx == NULL) { |
| fprintf(stderr, "JS_NewContext failure"); |
| } |
| |
| JS_SetCanBlock(rt, TRUE); |
| |
| js_std_add_helpers(ctx, -1, NULL); |
| |
| if (!JS_RunModule(ctx, args->basename, args->filename)) |
| js_std_dump_error(ctx); |
| free(args->filename); |
| free(args->basename); |
| free(args); |
| |
| js_std_loop(ctx); |
| |
| JS_FreeContext(ctx); |
| js_std_free_handlers(rt); |
| JS_FreeRuntime(rt); |
| return NULL; |
| } |
| |
| static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, |
| JSWorkerMessagePipe *recv_pipe, |
| JSWorkerMessagePipe *send_pipe) |
| { |
| JSValue obj = JS_UNDEFINED, proto; |
| JSWorkerData *s; |
| |
| /* create the object */ |
| if (JS_IsUndefined(new_target)) { |
| proto = JS_GetClassProto(ctx, js_worker_class_id); |
| } else { |
| proto = JS_GetPropertyStr(ctx, new_target, "prototype"); |
| if (JS_IsException(proto)) |
| goto fail; |
| } |
| obj = JS_NewObjectProtoClass(ctx, proto, js_worker_class_id); |
| JS_FreeValue(ctx, proto); |
| if (JS_IsException(obj)) |
| goto fail; |
| s = js_mallocz(ctx, sizeof(*s)); |
| if (!s) |
| goto fail; |
| s->recv_pipe = js_dup_message_pipe(recv_pipe); |
| s->send_pipe = js_dup_message_pipe(send_pipe); |
| |
| JS_SetOpaque(obj, s); |
| return obj; |
| fail: |
| JS_FreeValue(ctx, obj); |
| return JS_EXCEPTION; |
| } |
| |
| static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, |
| int argc, JSValueConst *argv) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| WorkerFuncArgs *args = NULL; |
| pthread_t tid; |
| pthread_attr_t attr; |
| JSValue obj = JS_UNDEFINED; |
| int ret; |
| const char *filename = NULL, *basename; |
| JSAtom basename_atom; |
| |
| /* XXX: in order to avoid problems with resource liberation, we |
| don't support creating workers inside workers */ |
| if (!is_main_thread(rt)) |
| return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); |
| |
| /* base name, assuming the calling function is a normal JS |
| function */ |
| basename_atom = JS_GetScriptOrModuleName(ctx, 1); |
| if (basename_atom == JS_ATOM_NULL) { |
| return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); |
| } |
| basename = JS_AtomToCString(ctx, basename_atom); |
| JS_FreeAtom(ctx, basename_atom); |
| if (!basename) |
| goto fail; |
| |
| /* module name */ |
| filename = JS_ToCString(ctx, argv[0]); |
| if (!filename) |
| goto fail; |
| |
| args = malloc(sizeof(*args)); |
| if (!args) |
| goto oom_fail; |
| memset(args, 0, sizeof(*args)); |
| args->filename = strdup(filename); |
| args->basename = strdup(basename); |
| |
| /* ports */ |
| args->recv_pipe = js_new_message_pipe(); |
| if (!args->recv_pipe) |
| goto oom_fail; |
| args->send_pipe = js_new_message_pipe(); |
| if (!args->send_pipe) |
| goto oom_fail; |
| |
| obj = js_worker_ctor_internal(ctx, new_target, |
| args->send_pipe, args->recv_pipe); |
| if (JS_IsException(obj)) |
| goto fail; |
| |
| pthread_attr_init(&attr); |
| /* no join at the end */ |
| pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); |
| ret = pthread_create(&tid, &attr, worker_func, args); |
| pthread_attr_destroy(&attr); |
| if (ret != 0) { |
| JS_ThrowTypeError(ctx, "could not create worker"); |
| goto fail; |
| } |
| JS_FreeCString(ctx, basename); |
| JS_FreeCString(ctx, filename); |
| return obj; |
| oom_fail: |
| JS_ThrowOutOfMemory(ctx); |
| fail: |
| JS_FreeCString(ctx, basename); |
| JS_FreeCString(ctx, filename); |
| if (args) { |
| free(args->filename); |
| free(args->basename); |
| js_free_message_pipe(args->recv_pipe); |
| js_free_message_pipe(args->send_pipe); |
| free(args); |
| } |
| JS_FreeValue(ctx, obj); |
| return JS_EXCEPTION; |
| } |
| |
| static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); |
| JSWorkerMessagePipe *ps; |
| size_t data_len, sab_tab_len, i; |
| uint8_t *data; |
| JSWorkerMessage *msg; |
| uint8_t **sab_tab; |
| |
| if (!worker) |
| return JS_EXCEPTION; |
| |
| data = JS_WriteObject2(ctx, &data_len, argv[0], |
| JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, |
| &sab_tab, &sab_tab_len); |
| if (!data) |
| return JS_EXCEPTION; |
| |
| msg = malloc(sizeof(*msg)); |
| if (!msg) |
| goto fail; |
| msg->data = NULL; |
| msg->sab_tab = NULL; |
| |
| /* must reallocate because the allocator may be different */ |
| msg->data = malloc(data_len); |
| if (!msg->data) |
| goto fail; |
| memcpy(msg->data, data, data_len); |
| msg->data_len = data_len; |
| |
| msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab_len); |
| if (!msg->sab_tab) |
| goto fail; |
| memcpy(msg->sab_tab, sab_tab, sizeof(msg->sab_tab[0]) * sab_tab_len); |
| msg->sab_tab_len = sab_tab_len; |
| |
| js_free(ctx, data); |
| js_free(ctx, sab_tab); |
| |
| /* increment the SAB reference counts */ |
| for(i = 0; i < msg->sab_tab_len; i++) { |
| js_sab_dup(NULL, msg->sab_tab[i]); |
| } |
| |
| ps = worker->send_pipe; |
| pthread_mutex_lock(&ps->mutex); |
| /* indicate that data is present */ |
| if (list_empty(&ps->msg_queue)) { |
| uint8_t ch = '\0'; |
| int ret; |
| for(;;) { |
| ret = write(ps->write_fd, &ch, 1); |
| if (ret == 1) |
| break; |
| if (ret < 0 && (errno != EAGAIN || errno != EINTR)) |
| break; |
| } |
| } |
| list_add_tail(&msg->link, &ps->msg_queue); |
| pthread_mutex_unlock(&ps->mutex); |
| return JS_UNDEFINED; |
| fail: |
| if (msg) { |
| free(msg->data); |
| free(msg->sab_tab); |
| free(msg); |
| } |
| js_free(ctx, data); |
| js_free(ctx, sab_tab); |
| return JS_EXCEPTION; |
| |
| } |
| |
| static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, |
| JSValueConst func) |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); |
| JSWorkerMessageHandler *port; |
| |
| if (!worker) |
| return JS_EXCEPTION; |
| |
| port = worker->msg_handler; |
| if (JS_IsNull(func)) { |
| if (port) { |
| js_free_port(rt, port); |
| worker->msg_handler = NULL; |
| } |
| } else { |
| if (!JS_IsFunction(ctx, func)) |
| return JS_ThrowTypeError(ctx, "not a function"); |
| if (!port) { |
| port = js_mallocz(ctx, sizeof(*port)); |
| if (!port) |
| return JS_EXCEPTION; |
| port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); |
| port->on_message_func = JS_NULL; |
| list_add_tail(&port->link, &ts->port_list); |
| worker->msg_handler = port; |
| } |
| JS_FreeValue(ctx, port->on_message_func); |
| port->on_message_func = JS_DupValue(ctx, func); |
| } |
| return JS_UNDEFINED; |
| } |
| |
| static JSValue js_worker_get_onmessage(JSContext *ctx, JSValueConst this_val) |
| { |
| JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); |
| JSWorkerMessageHandler *port; |
| if (!worker) |
| return JS_EXCEPTION; |
| port = worker->msg_handler; |
| if (port) { |
| return JS_DupValue(ctx, port->on_message_func); |
| } else { |
| return JS_NULL; |
| } |
| } |
| |
| static const JSCFunctionListEntry js_worker_proto_funcs[] = { |
| JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), |
| JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), |
| }; |
| |
| #endif /* USE_WORKER */ |
| |
| void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) |
| { |
| #ifdef USE_WORKER |
| js_worker_new_context_func = func; |
| #endif |
| } |
| |
| #if defined(_WIN32) |
| #define OS_PLATFORM "win32" |
| #elif defined(__APPLE__) |
| #define OS_PLATFORM "darwin" |
| #elif defined(EMSCRIPTEN) |
| #define OS_PLATFORM "js" |
| #elif defined(__Fuchsia__) |
| #define OS_PLATFORM "fuchsia" |
| #else |
| #define OS_PLATFORM "linux" |
| #endif |
| |
| #define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) |
| |
| static const JSCFunctionListEntry js_os_funcs[] = { |
| JS_CFUNC_DEF("open", 2, js_os_open ), |
| OS_FLAG(O_RDONLY), |
| OS_FLAG(O_WRONLY), |
| OS_FLAG(O_RDWR), |
| OS_FLAG(O_APPEND), |
| OS_FLAG(O_CREAT), |
| OS_FLAG(O_EXCL), |
| OS_FLAG(O_TRUNC), |
| #if defined(_WIN32) |
| OS_FLAG(O_BINARY), |
| OS_FLAG(O_TEXT), |
| #endif |
| JS_CFUNC_DEF("close", 1, js_os_close ), |
| JS_CFUNC_DEF("seek", 3, js_os_seek ), |
| JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ), |
| JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ), |
| JS_CFUNC_DEF("isatty", 1, js_os_isatty ), |
| JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ), |
| JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ), |
| JS_CFUNC_DEF("remove", 1, js_os_remove ), |
| JS_CFUNC_DEF("rename", 2, js_os_rename ), |
| JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ), |
| JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ), |
| #if !defined(__Fuchsia__) |
| JS_CFUNC_DEF("signal", 2, js_os_signal ), |
| OS_FLAG(SIGINT), |
| OS_FLAG(SIGABRT), |
| OS_FLAG(SIGFPE), |
| OS_FLAG(SIGILL), |
| OS_FLAG(SIGSEGV), |
| OS_FLAG(SIGTERM), |
| #endif // !defined(__Fuchsia__) |
| #if !defined(_WIN32) |
| OS_FLAG(SIGQUIT), |
| OS_FLAG(SIGPIPE), |
| OS_FLAG(SIGALRM), |
| OS_FLAG(SIGUSR1), |
| OS_FLAG(SIGUSR2), |
| OS_FLAG(SIGCHLD), |
| OS_FLAG(SIGCONT), |
| OS_FLAG(SIGSTOP), |
| OS_FLAG(SIGTSTP), |
| OS_FLAG(SIGTTIN), |
| OS_FLAG(SIGTTOU), |
| #endif |
| JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), |
| JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), |
| JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), |
| JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), |
| JS_CFUNC_DEF("chdir", 0, js_os_chdir ), |
| JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), |
| JS_CFUNC_DEF("readdir", 1, js_os_readdir ), |
| /* st_mode constants */ |
| OS_FLAG(S_IFMT), |
| OS_FLAG(S_IFIFO), |
| OS_FLAG(S_IFCHR), |
| OS_FLAG(S_IFDIR), |
| OS_FLAG(S_IFBLK), |
| OS_FLAG(S_IFREG), |
| #if !defined(_WIN32) |
| OS_FLAG(S_IFSOCK), |
| OS_FLAG(S_IFLNK), |
| OS_FLAG(S_ISGID), |
| OS_FLAG(S_ISUID), |
| #endif |
| JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), |
| JS_CFUNC_DEF("utimes", 3, js_os_utimes ), |
| #if !defined(_WIN32) |
| JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), |
| JS_CFUNC_DEF("realpath", 1, js_os_realpath ), |
| JS_CFUNC_DEF("symlink", 2, js_os_symlink ), |
| JS_CFUNC_DEF("readlink", 1, js_os_readlink ), |
| JS_CFUNC_DEF("exec", 1, js_os_exec ), |
| JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), |
| OS_FLAG(WNOHANG), |
| JS_CFUNC_DEF("pipe", 0, js_os_pipe ), |
| JS_CFUNC_DEF("kill", 2, js_os_kill ), |
| JS_CFUNC_DEF("sleep", 1, js_os_sleep ), |
| JS_CFUNC_DEF("dup", 1, js_os_dup ), |
| JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), |
| #endif |
| }; |
| |
| static int js_os_init(JSContext *ctx, JSModuleDef *m) |
| { |
| os_poll_func = js_os_poll; |
| |
| /* OSTimer class */ |
| JS_NewClassID(&js_os_timer_class_id); |
| JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class); |
| |
| #ifdef USE_WORKER |
| { |
| JSRuntime *rt = JS_GetRuntime(ctx); |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| JSValue proto, obj; |
| /* Worker class */ |
| JS_NewClassID(&js_worker_class_id); |
| JS_NewClass(JS_GetRuntime(ctx), js_worker_class_id, &js_worker_class); |
| proto = JS_NewObject(ctx); |
| JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); |
| |
| obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, |
| JS_CFUNC_constructor, 0); |
| JS_SetConstructor(ctx, obj, proto); |
| |
| JS_SetClassProto(ctx, js_worker_class_id, proto); |
| |
| /* set 'Worker.parent' if necessary */ |
| if (ts->recv_pipe && ts->send_pipe) { |
| JS_DefinePropertyValueStr(ctx, obj, "parent", |
| js_worker_ctor_internal(ctx, JS_UNDEFINED, ts->recv_pipe, ts->send_pipe), |
| JS_PROP_C_W_E); |
| } |
| |
| JS_SetModuleExport(ctx, m, "Worker", obj); |
| } |
| #endif /* USE_WORKER */ |
| |
| return JS_SetModuleExportList(ctx, m, js_os_funcs, |
| countof(js_os_funcs)); |
| } |
| |
| JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) |
| { |
| JSModuleDef *m; |
| m = JS_NewCModule(ctx, module_name, js_os_init); |
| if (!m) |
| return NULL; |
| JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); |
| #ifdef USE_WORKER |
| JS_AddModuleExport(ctx, m, "Worker"); |
| #endif |
| return m; |
| } |
| |
| /**********************************************************/ |
| |
| static JSValue js_print(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int i; |
| const char *str; |
| size_t len; |
| |
| for(i = 0; i < argc; i++) { |
| if (i != 0) |
| putchar(' '); |
| str = JS_ToCStringLen(ctx, &len, argv[i]); |
| if (!str) |
| return JS_EXCEPTION; |
| fwrite(str, 1, len, stdout); |
| JS_FreeCString(ctx, str); |
| } |
| putchar('\n'); |
| return JS_UNDEFINED; |
| } |
| |
| void js_std_add_helpers(JSContext *ctx, int argc, char **argv) |
| { |
| JSValue global_obj, console, args; |
| int i; |
| |
| /* XXX: should these global definitions be enumerable? */ |
| global_obj = JS_GetGlobalObject(ctx); |
| |
| console = JS_NewObject(ctx); |
| JS_SetPropertyStr(ctx, console, "log", |
| JS_NewCFunction(ctx, js_print, "log", 1)); |
| JS_SetPropertyStr(ctx, global_obj, "console", console); |
| |
| /* same methods as the mozilla JS shell */ |
| if (argc >= 0) { |
| args = JS_NewArray(ctx); |
| for(i = 0; i < argc; i++) { |
| JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); |
| } |
| JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); |
| } |
| |
| JS_SetPropertyStr(ctx, global_obj, "print", |
| JS_NewCFunction(ctx, js_print, "print", 1)); |
| JS_SetPropertyStr(ctx, global_obj, "__loadScript", |
| JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1)); |
| |
| JS_FreeValue(ctx, global_obj); |
| } |
| |
| void js_std_init_handlers(JSRuntime *rt) |
| { |
| JSThreadState *ts; |
| |
| ts = malloc(sizeof(*ts)); |
| if (!ts) { |
| fprintf(stderr, "Could not allocate memory for the worker"); |
| exit(1); |
| } |
| memset(ts, 0, sizeof(*ts)); |
| init_list_head(&ts->os_rw_handlers); |
| init_list_head(&ts->os_signal_handlers); |
| init_list_head(&ts->os_timers); |
| init_list_head(&ts->port_list); |
| #if defined(__Fuchsia__) |
| init_list_head(&ts->os_handle_handlers); |
| #endif |
| |
| JS_SetRuntimeOpaque(rt, ts); |
| |
| #ifdef USE_WORKER |
| /* set the SharedArrayBuffer memory handlers */ |
| { |
| JSSharedArrayBufferFunctions sf; |
| memset(&sf, 0, sizeof(sf)); |
| sf.sab_alloc = js_sab_alloc; |
| sf.sab_free = js_sab_free; |
| sf.sab_dup = js_sab_dup; |
| JS_SetSharedArrayBufferFunctions(rt, &sf); |
| } |
| #endif |
| } |
| |
| void js_std_free_handlers(JSRuntime *rt) |
| { |
| JSThreadState *ts = JS_GetRuntimeOpaque(rt); |
| struct list_head *el, *el1; |
| |
| list_for_each_safe(el, el1, &ts->os_rw_handlers) { |
| JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); |
| free_rw_handler(rt, rh); |
| } |
| |
| #if !defined(__Fuchsia__) |
| list_for_each_safe(el, el1, &ts->os_signal_handlers) { |
| JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); |
| free_sh(rt, sh); |
| } |
| #endif // !defined(__Fuchsia__) |
| |
| list_for_each_safe(el, el1, &ts->os_timers) { |
| JSOSTimer *th = list_entry(el, JSOSTimer, link); |
| unlink_timer(rt, th); |
| if (!th->has_object) |
| free_timer(rt, th); |
| } |
| #if defined(__Fuchsia__) |
| list_for_each_safe(el, el1, &ts->os_handle_handlers) { |
| JSOSHandleHandler *hh = list_entry(el, JSOSHandleHandler, link); |
| free_handle_handler(rt, hh); |
| } |
| #endif |
| |
| #ifdef USE_WORKER |
| /* XXX: free port_list ? */ |
| js_free_message_pipe(ts->recv_pipe); |
| js_free_message_pipe(ts->send_pipe); |
| #endif |
| |
| free(ts); |
| JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ |
| } |
| |
| static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) |
| { |
| const char *str; |
| |
| str = JS_ToCString(ctx, val); |
| if (str) { |
| fprintf(f, "%s\n", str); |
| JS_FreeCString(ctx, str); |
| } else { |
| fprintf(f, "[exception]\n"); |
| } |
| } |
| |
| static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) |
| { |
| JSValue val; |
| BOOL is_error; |
| |
| is_error = JS_IsError(ctx, exception_val); |
| js_dump_obj(ctx, stderr, exception_val); |
| if (is_error) { |
| val = JS_GetPropertyStr(ctx, exception_val, "stack"); |
| if (!JS_IsUndefined(val)) { |
| js_dump_obj(ctx, stderr, val); |
| } |
| JS_FreeValue(ctx, val); |
| } |
| } |
| |
| void js_std_dump_error(JSContext *ctx) |
| { |
| JSValue exception_val; |
| |
| exception_val = JS_GetException(ctx); |
| js_std_dump_error1(ctx, exception_val); |
| JS_FreeValue(ctx, exception_val); |
| } |
| |
| void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, |
| JSValueConst reason, |
| BOOL is_handled, void *opaque) |
| { |
| if (!is_handled) { |
| fprintf(stderr, "Possibly unhandled promise rejection: "); |
| js_std_dump_error1(ctx, reason); |
| } |
| } |
| |
| /* main loop which calls the user JS callbacks */ |
| void js_std_loop(JSContext *ctx) |
| { |
| JSContext *ctx1; |
| int err; |
| |
| for(;;) { |
| /* execute the pending jobs */ |
| for(;;) { |
| err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); |
| if (err <= 0) { |
| if (err < 0) { |
| js_std_dump_error(ctx1); |
| } |
| break; |
| } |
| } |
| |
| if (!os_poll_func || os_poll_func(ctx)) |
| break; |
| } |
| } |
| |
| void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, |
| int load_only) |
| { |
| JSValue obj, val; |
| obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE); |
| if (JS_IsException(obj)) |
| goto exception; |
| if (load_only) { |
| if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { |
| js_module_set_import_meta(ctx, obj, FALSE, FALSE); |
| } |
| } else { |
| if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { |
| if (JS_ResolveModule(ctx, obj) < 0) { |
| JS_FreeValue(ctx, obj); |
| goto exception; |
| } |
| js_module_set_import_meta(ctx, obj, FALSE, TRUE); |
| } |
| val = JS_EvalFunction(ctx, obj); |
| if (JS_IsException(val)) { |
| exception: |
| js_std_dump_error(ctx); |
| exit(1); |
| } |
| JS_FreeValue(ctx, val); |
| } |
| } |