| /* |
| * QuickJS C library |
| * |
| * Copyright (c) 2017-2021 Fabrice Bellard |
| * Copyright (c) 2017-2021 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); |
| } |
| |
| #if defined(_WIN32) |
| static void setenv(const char *name, const char *value, int overwrite) |
| { |
| char *str; |
| size_t name_len, value_len; |
| name_len = strlen(name); |
| value_len = strlen(value); |
| str = malloc(name_len + 1 + value_len + 1); |
| memcpy(str, name, name_len); |
| str[name_len] = '='; |
| memcpy(str + name_len + 1, value, value_len); |
| str[name_len + 1 + value_len] = '\0'; |
| _putenv(str); |
| free(str); |
| } |
| |
| static void unsetenv(const char *name) |
| { |
| setenv(name, "", TRUE); |
| } |
| #endif /* _WIN32 */ |
| |
| static JSValue js_std_setenv(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *name, *value; |
| name = JS_ToCString(ctx, argv[0]); |
| if (!name) |
| return JS_EXCEPTION; |
| value = JS_ToCString(ctx, argv[1]); |
| if (!value) { |
| JS_FreeCString(ctx, name); |
| return JS_EXCEPTION; |
| } |
| setenv(name, value, TRUE); |
| JS_FreeCString(ctx, name); |
| JS_FreeCString(ctx, value); |
| return JS_UNDEFINED; |
| } |
| |
| static JSValue js_std_unsetenv(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| const char *name; |
| name = JS_ToCString(ctx, argv[0]); |
| if (!name) |
| return JS_EXCEPTION; |
| unsetenv(name); |
| JS_FreeCString(ctx, name); |
| return JS_UNDEFINED; |
| } |
| |
| /* return an object containing the list of the available environment |
| variables. */ |
| static JSValue js_std_getenviron(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| char **envp; |
| const char *name, *p, *value; |
| JSValue obj; |
| uint32_t idx; |
| size_t name_len; |
| JSAtom atom; |
| int ret; |
| |
| obj = JS_NewObject(ctx); |
| if (JS_IsException(obj)) |
| return JS_EXCEPTION; |
| envp = environ; |
| for(idx = 0; envp[idx] != NULL; idx++) { |
| name = envp[idx]; |
| p = strchr(name, '='); |
| name_len = p - name; |
| if (!p) |
| continue; |
| value = p + 1; |
| atom = JS_NewAtomLen(ctx, name, name_len); |
| if (atom == JS_ATOM_NULL) |
| goto fail; |
| ret = JS_DefinePropertyValue(ctx, obj, atom, JS_NewString(ctx, value), |
| JS_PROP_C_W_E); |
| JS_FreeAtom(ctx, atom); |
| if (ret < 0) |
| goto fail; |
| } |
| return obj; |
| fail: |
| JS_FreeValue(ctx, obj); |
| return JS_EXCEPTION; |
| } |
| |
| 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("setenv", 1, js_std_setenv ), |
| JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), |
| JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), |
| 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), |
| }; |
| |
| 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) != 0)); |
| } |
| |
| #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; |
| } |
| |
| /* Windows 10 built-in VT100 emulation */ |
| #define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 |
| #define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 |
| |
| 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 | __ENABLE_VIRTUAL_TERMINAL_INPUT); |
| _setmode(fd, _O_BINARY); |
| if (fd == 0) { |
| handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ |
| SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); |
| } |
| 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; |
| #if defined(_WIN32) |
| { |
| struct stat st; |
| if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) { |
| ret = rmdir(filename); |
| } else { |
| ret = unlink(filename); |
| } |
| } |
| #else |
| ret = remove(filename); |
| #endif |
| ret = js_get_errno(ret); |
| 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); |
| } |
| |
| /* sleep(delay_ms) */ |
| static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, |
| int argc, JSValueConst *argv) |
| { |
| int64_t delay; |
| int ret; |
| |
| if (JS_ToInt64(ctx, &delay, argv[0])) |
| return JS_EXCEPTION; |
| if (delay < 0) |
| delay = 0; |
| #if defined(_WIN32) |
| { |
| if (delay > INT32_MAX) |
| delay = INT32_MAX; |
| Sleep(delay); |
| ret = 0; |
| } |
| #else |
| { |
| struct timespec ts; |
| |
| ts.tv_sec = delay / 1000; |
| ts.tv_nsec = (delay % 1000) * 1000000; |
| ret = js_get_errno(nanosleep(&ts, NULL)); |
| } |
| #endif |
| return JS_NewInt32(ctx, ret); |
| } |
| |
| #if defined(_WIN32) |
| static char *realpath(const char *path, char *buf) |
| { |
| if (!_fullpath(buf, path, PATH_MAX)) { |
| errno = ENOENT; |
| return NULL; |
| } else { |
| return buf; |
| } |
| } |
| #endif |
| |
| /* 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); |
| } |
| |
| #if !defined(_WIN32) |
| 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; |
|