/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) #include #include #else #include #include #include #include #if defined(__APPLE__) typedef sig_t sighandler_t; #endif #endif #include "cutils.h" #include "list.h" #include "quickjs-libc.h" static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) { dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); } /* TODO: - add worker - add minimal VT100 emulation for win32 - 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; /* initialize the lists so js_std_free_handlers() can always be called */ static struct list_head os_rw_handlers = LIST_HEAD_INIT(os_rw_handlers); static struct list_head os_signal_handlers = LIST_HEAD_INIT(os_signal_handlers); static struct list_head os_timers = LIST_HEAD_INIT(os_timers); static uint64_t os_pending_signals; static int eval_script_recurse; static int (*os_poll_func)(JSContext *ctx); 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; size_t fmt_len; int32_t int32_arg; int64_t int64_arg; double double_arg; const char *string_arg; enum { PART_FLAGS, PART_WIDTH, PART_DOT, PART_PREC, PART_MODIFIER } part; int modsize; /* 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 '%' */ part = PART_FLAGS; modsize = 0; for (;;) { if (q >= fmtbuf + sizeof(fmtbuf) - 1) goto invalid; c = *fmt++; *q++ = c; *q = '\0'; switch (c) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (part != PART_PREC) { if (part <= PART_WIDTH) part = PART_WIDTH; else goto invalid; } continue; case '0': case '#': case '+': case '-': case ' ': case '\'': if (part > PART_FLAGS) goto invalid; continue; case '.': if (part > PART_DOT) goto invalid; part = PART_DOT; continue; case '*': if (part < PART_WIDTH) part = PART_DOT; else if (part == PART_DOT) part = PART_MODIFIER; else goto invalid; 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); continue; case 'h': if (modsize != 0 && modsize != -1) goto invalid; modsize--; part = PART_MODIFIER; continue; case 'l': q--; if (modsize != 0 && modsize != 1) goto invalid; modsize++; part = PART_MODIFIER; continue; 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 (modsize > 0) { q[1] = q[-1]; q[-1] = q[0] = 'l'; q[2] = '\0'; dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); } else { dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); } break; case 's': if (i >= argc) goto missing; 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; } break; } } 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) { 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 (++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, "", flags); JS_FreeCString(ctx, str); if (--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_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; 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_ToCString(ctx, argv[i]); if (!str) return JS_EXCEPTION; fputs(str, 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 ), /* 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 JSOSRWHandler *find_rh(int fd) { JSOSRWHandler *rh; struct list_head *el; list_for_each(el, &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) { 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(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(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, &os_rw_handlers); } JS_FreeValue(ctx, rh->rw_func[magic]); rh->rw_func[magic] = JS_DupValue(ctx, func); } return JS_UNDEFINED; } static JSOSSignalHandler *find_sh(int sig_num) { JSOSSignalHandler *sh; struct list_head *el; list_for_each(el, &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) { JSOSSignalHandler *sh; uint32_t sig_num; JSValueConst func; sighandler_t handler; 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(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(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, &os_signal_handlers); } JS_FreeValue(ctx, sh->func); sh->func = JS_DupValue(ctx, func); signal(sig_num, os_signal_handler); } return JS_UNDEFINED; } #if defined(__linux__) || defined(__APPLE__) 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) { 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, &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) { int min_delay, console_fd; int64_t cur_time, delay; JSOSRWHandler *rh; struct list_head *el; /* XXX: handle signals if useful */ if (list_empty(&os_rw_handlers) && list_empty(&os_timers)) return -1; /* no more events */ /* XXX: only timers and basic console input are supported */ if (!list_empty(&os_timers)) { cur_time = get_time_ms(); min_delay = 10000; list_for_each(el, &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(JS_GetRuntime(ctx), th); if (!th->has_object) free_timer(JS_GetRuntime(ctx), 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, &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, &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; } #else static int js_os_poll(JSContext *ctx) { 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; if (unlikely(os_pending_signals != 0)) { JSOSSignalHandler *sh; uint64_t mask; list_for_each(el, &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(&os_rw_handlers) && list_empty(&os_timers)) return -1; /* no more events */ if (!list_empty(&os_timers)) { cur_time = get_time_ms(); min_delay = 10000; list_for_each(el, &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(JS_GetRuntime(ctx), th); if (!th->has_object) free_timer(JS_GetRuntime(ctx), 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, &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); } ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); if (ret > 0) { list_for_each(el, &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 */ break; } if (!JS_IsNull(rh->rw_func[1])) { FD_SET(rh->fd, &wfds); call_handler(ctx, rh->rw_func[1]); /* must stop because the list may have been modified */ break; } } } 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); } #if !defined(_WIN32) 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); } /* 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_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; ret = js_get_errno(mkdir(path, mode)); JS_FreeCString(ctx, path); return JS_NewInt32(ctx, ret); } static int64_t timespec_to_ms(const struct timespec *tv) { return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); } /* 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 (is_lstat) res = lstat(path, &st); else res = stat(path, &st); 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); JS_DefinePropertyValueStr(ctx, obj, "blocks", JS_NewInt64(ctx, st.st_blocks), JS_PROP_C_W_E); #if 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); } 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); } /* 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); } static void ms_to_timeval(struct timeval *tv, uint64_t v) { tv->tv_sec = v / 1000; tv->tv_usec = (v % 1000) * 1000; } static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *path; int64_t atime, mtime; int ret; struct timeval times[2]; 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; ms_to_timeval(×[0], atime); ms_to_timeval(×[1], mtime); ret = js_get_errno(utimes(path, times)); JS_FreeCString(ctx, path); return JS_NewInt32(ctx, ret); } /* 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; 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]; 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; } } } 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 (!file) file = exec_argv[0]; if (use_path) ret = execvp(file, (char **)exec_argv); else ret = execv(file, (char **)exec_argv); _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); 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 */ #if defined(_WIN32) #define OS_PLATFORM "win32" #elif defined(__APPLE__) #define OS_PLATFORM "darwin" #elif defined(EMSCRIPTEN) #define OS_PLATFORM "js" #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 ), 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), #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 ), #if !defined(_WIN32) JS_CFUNC_DEF("chdir", 0, js_os_chdir ), JS_CFUNC_DEF("realpath", 1, js_os_realpath ), JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), /* 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), OS_FLAG(S_IFSOCK), OS_FLAG(S_IFLNK), OS_FLAG(S_ISGID), OS_FLAG(S_ISUID), JS_CFUNC_DEF("symlink", 2, js_os_symlink ), JS_CFUNC_DEF("readlink", 1, js_os_readlink ), JS_CFUNC_DEF("readdir", 1, js_os_readdir ), JS_CFUNC_DEF("utimes", 3, js_os_utimes ), 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); 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)); return m; } /**********************************************************/ static JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int i; const char *str; for(i = 0; i < argc; i++) { if (i != 0) putchar(' '); str = JS_ToCString(ctx, argv[i]); if (!str) return JS_EXCEPTION; fputs(str, 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 */ 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); /* XXX: not multi-context */ init_list_head(&os_rw_handlers); init_list_head(&os_signal_handlers); init_list_head(&os_timers); os_pending_signals = 0; } void js_std_free_handlers(JSRuntime *rt) { struct list_head *el, *el1; list_for_each_safe(el, el1, &os_rw_handlers) { JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); free_rw_handler(rt, rh); } list_for_each_safe(el, el1, &os_signal_handlers) { JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); free_sh(rt, sh); } list_for_each_safe(el, el1, &os_timers) { JSOSTimer *th = list_entry(el, JSOSTimer, link); unlink_timer(rt, th); if (!th->has_object) free_timer(rt, th); } } 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); } }