blob: db25a0dac5a7799ceb1348be4d0c6e68026bb858 [file] [log] [blame]
/*
* QuickJS C library
*
* Copyright (c) 2017-2019 Fabrice Bellard
* Copyright (c) 2017-2019 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>
#else
#include <dlfcn.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#if defined(__APPLE__)
typedef sig_t sighandler_t;
#endif
#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"
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;
#ifdef __Fuchsia__
typedef struct {
struct list_head link;
zx_handle_t handle;
zx_signals_t signals;
JSValue func;
} JSOSHandleHandler;
#endif
/* 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);
#ifdef __Fuchsia__
static struct list_head os_handle_handlers = LIST_HEAD_INIT(os_handle_handlers);
#endif
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 (modsize > 0) {
if (JS_ToInt64(ctx, &int64_arg, argv[i++]))
goto fail;
q[1] = q[-1];
q[-1] = q[0] = 'l';
q[2] = '\0';
dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg);
} else {
if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
goto fail;
dbuf_printf_fun(&dbuf, fmtbuf, int32_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;
}
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 JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
const char *str;
size_t len;
JSValue ret;
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);
}
ret = JS_Eval(ctx, str, len, "<evalScript>", JS_EVAL_TYPE_GLOBAL);
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 JSValue js_new_std_error(JSContext *ctx, int err)
{
JSValue obj;
/* XXX: could add a specific Error prototype */
obj = JS_NewError(ctx);
JS_DefinePropertyValueStr(ctx, obj, "message",
JS_NewString(ctx, strerror(err)),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
JS_DefinePropertyValueStr(ctx, obj, "errno",
JS_NewInt32(ctx, err),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
return obj;
}
static JSValue js_std_error_constructor(JSContext *ctx, JSValueConst new_target,
int argc, JSValueConst *argv)
{
int err;
if (JS_ToInt32(ctx, &err, argv[0]))
return JS_EXCEPTION;
return js_new_std_error(ctx, err);
}
static JSValue js_std_error_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_throw_errno(JSContext *ctx, int err)
{
JSValue obj;
obj = js_new_std_error(ctx, err);
if (JS_IsException(obj))
obj = JS_NULL;
return JS_Throw(ctx, 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 JSValue js_std_open(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
const char *filename, *mode = NULL;
FILE *f;
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_std_throw_errno(ctx, EINVAL);
goto fail;
}
f = fopen(filename, mode);
JS_FreeCString(ctx, filename);
JS_FreeCString(ctx, mode);
if (!f)
return js_std_throw_errno(ctx, errno);
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;
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_std_throw_errno(ctx, EINVAL);
goto fail;
}
f = popen(filename, mode);
JS_FreeCString(ctx, filename);
JS_FreeCString(ctx, mode);
if (!f)
return js_std_throw_errno(ctx, errno);
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;
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_std_throw_errno(ctx, EINVAL);
goto fail;
}
f = fdopen(fd, mode);
JS_FreeCString(ctx, mode);
if (!f)
return js_std_throw_errno(ctx, errno);
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 (!f)
return js_std_throw_errno(ctx, errno);
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_std_throw_errno(ctx, EBADF);
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);
if (!s)
return JS_EXCEPTION;
if (!s->f)
return js_std_throw_errno(ctx, EBADF);
/* XXX: could return exit code */
if (s->is_popen)
pclose(s->f);
else
fclose(s->f);
s->f = NULL;
return JS_UNDEFINED;
}
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)
{
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
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_ToInt64(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)
return js_std_throw_errno(ctx, EBADF);
return JS_UNDEFINED;
}
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_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 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_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_std_throw_errno(ctx, errno);
}
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)
goto bad_header;
status = http_get_status(buf);
if (!full_flag && !(status >= 200 && status <= 299)) {
js_std_throw_errno(ctx, ENOENT);
goto fail;
}
/* wait until there is an empty line */
for(;;) {
if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) {
bad_header:
js_std_throw_errno(ctx, EINVAL);
goto fail;
}
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);
}
js_free(ctx, buf);
buf = NULL;
pclose(f);
f = NULL;
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);
}
dbuf_free(data_buf);
data_buf = NULL;
if (JS_IsException(response))
goto fail;
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);
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_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 ),
/* 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 ),
/* setenv, ... */
};
static const JSCFunctionListEntry js_std_error_funcs[] = {
JS_CFUNC_DEF("strerror", 1, js_std_error_strerror ),
/* 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_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_DEF("tell", 0, js_std_file_tell ),
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_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, ferror, clearerr, ... */
};
static int js_std_init(JSContext *ctx, JSModuleDef *m)
{
JSValue proto, obj;
/* 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));
obj = JS_NewCFunction2(ctx, js_std_error_constructor,
"Error", 1, JS_CFUNC_constructor, 0);
JS_SetPropertyFunctionList(ctx, obj, js_std_error_funcs,
countof(js_std_error_funcs));
JS_SetModuleExport(ctx, m, "Error", obj);
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");
JS_AddModuleExport(ctx, m, "Error");
return m;
}
/**********************************************************/
/* 'os' object */
static JSValue js_os_return(JSContext *ctx, ssize_t ret)
{
if (ret < 0)
ret = -errno;
return JS_NewInt64(ctx, ret);
}
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 = open(filename, flags, mode);
JS_FreeCString(ctx, filename);
return js_os_return(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 = close(fd);
return js_os_return(ctx, ret);
}
static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
int fd, whence, ret;
int64_t pos;
if (JS_ToInt32(ctx, &fd, argv[0]))
return JS_EXCEPTION;
if (JS_ToInt64(ctx, &pos, argv[1]))
return JS_EXCEPTION;
if (JS_ToInt32(ctx, &whence, argv[2]))
return JS_EXCEPTION;
ret = lseek(fd, pos, whence);
return js_os_return(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 = write(fd, buf + pos, len);
else
ret = read(fd, buf + pos, len);
return js_os_return(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 = remove(filename);
JS_FreeCString(ctx, filename);
return js_os_return(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 = rename(oldpath, newpath);
JS_FreeCString(ctx, oldpath);
JS_FreeCString(ctx, newpath);
return js_os_return(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;
}
#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(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;
}
#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)
{
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;
}
#elif defined(__Fuchsia__)
void wait_for_zx_handle(JSContext *ctx, JSFuchsiaHandle *h, uint32_t signals, JSValueConst *value) {
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, &os_handle_handlers);
}
void stop_waiting_for_zx_handle(JSRuntime *rt, JSFuchsiaHandle *h, int n) {
struct list_head *el, *el1;
if (!list_empty(&os_handle_handlers)) {
list_for_each_safe(el, el1, &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) {
struct list_head *el;
zx_status_t status;
zx_time_t deadline = ZX_TIME_INFINITE;
if (list_empty(&os_rw_handlers) && list_empty(&os_handle_handlers) && list_empty(&os_timers)) {
return -1;
}
if (!list_empty(&os_timers)) {
int64_t cur_time = get_time_ms();
list_for_each(el, &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(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 (timer_deadline < deadline) {
deadline = timer_deadline;
}
}
}
// TODO: timer slack
if (list_empty(&os_rw_handlers) && list_empty(&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(&os_handle_handlers)) {
list_for_each(el, &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(&os_rw_handlers)) {
list_for_each(el, &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
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)
/* 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 = mkdir(path, mode);
JS_FreeCString(ctx, path);
return js_os_return(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 = symlink(target, linkpath);
JS_FreeCString(ctx, target);
JS_FreeCString(ctx, linkpath);
return js_os_return(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);
JS_FreeCString(ctx, path);
if (res < 0) {
buf[0] = '\0';
err = -errno;
} else {
buf[res] = '\0';
err = 0;
}
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);
JS_FreeCString(ctx, path);
err = 0;
if (!f) {
err = -errno;
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(&times[0], atime);
ms_to_timeval(&times[1], mtime);
ret = utimes(path, times);
JS_FreeCString(ctx, path);
return js_os_return(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 = kill(pid, sig);
return js_os_return(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 = nanosleep(&ts, NULL);
return js_os_return(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 = dup(fd);
return js_os_return(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 = dup2(fd, fd2);
return js_os_return(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"
#elif defined(__Fuchsia__)
#define OS_PLATFORM "fuchsia"
#else
#define OS_PLATFORM "linux"
#endif
#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE )
static const JSCFunctionListEntry js_os_funcs[] = {
JS_CFUNC_DEF("open", 2, js_os_open ),
OS_FLAG(O_RDONLY),
OS_FLAG(O_WRONLY),
OS_FLAG(O_RDWR),
OS_FLAG(O_APPEND),
OS_FLAG(O_CREAT),
OS_FLAG(O_EXCL),
OS_FLAG(O_TRUNC),
#if defined(_WIN32)
OS_FLAG(O_BINARY),
OS_FLAG(O_TEXT),
#endif
JS_CFUNC_DEF("close", 1, js_os_close ),
JS_CFUNC_DEF("seek", 3, js_os_seek ),
JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ),
JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ),
JS_CFUNC_DEF("isatty", 1, js_os_isatty ),
JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ),
JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ),
JS_CFUNC_DEF("remove", 1, js_os_remove ),
JS_CFUNC_DEF("rename", 2, js_os_rename ),
JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ),
JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ),
#if !defined(__Fuchsia__)
JS_CFUNC_DEF("signal", 2, js_os_signal ),
OS_FLAG(SIGINT),
OS_FLAG(SIGABRT),
OS_FLAG(SIGFPE),
OS_FLAG(SIGILL),
OS_FLAG(SIGSEGV),
OS_FLAG(SIGTERM),
#endif // !defined(__Fuchsia__)
#if !defined(_WIN32)
OS_FLAG(SIGQUIT),
OS_FLAG(SIGPIPE),
OS_FLAG(SIGALRM),
OS_FLAG(SIGUSR1),
OS_FLAG(SIGUSR2),
OS_FLAG(SIGCHLD),
OS_FLAG(SIGCONT),
OS_FLAG(SIGSTOP),
OS_FLAG(SIGTSTP),
OS_FLAG(SIGTTIN),
OS_FLAG(SIGTTOU),
#endif
JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ),
JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ),
JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ),
JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ),
#if !defined(_WIN32)
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);
#if defined(__Fuchsia__)
init_list_head(&os_handle_handlers);
#endif
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);
}
#if !defined(__Fuchsia__)
list_for_each_safe(el, el1, &os_signal_handlers) {
JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link);
free_sh(rt, sh);
}
#endif // !defined(__Fuchsia__)
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);
}
#if defined(__Fuchsia__)
list_for_each_safe(el, el1, &os_handle_handlers) {
JSOSHandleHandler *hh = list_entry(el, JSOSHandleHandler, link);
free_handle_handler(rt, hh);
}
#endif
}
void js_std_dump_error(JSContext *ctx)
{
JSValue exception_val, val;
const char *stack;
BOOL is_error;
exception_val = JS_GetException(ctx);
is_error = JS_IsError(ctx, exception_val);
if (!is_error)
printf("Throw: ");
js_print(ctx, JS_NULL, 1, (JSValueConst *)&exception_val);
if (is_error) {
val = JS_GetPropertyStr(ctx, exception_val, "stack");
if (!JS_IsUndefined(val)) {
stack = JS_ToCString(ctx, val);
printf("%s\n", stack);
JS_FreeCString(ctx, stack);
}
JS_FreeValue(ctx, val);
}
JS_FreeValue(ctx, exception_val);
}
/* 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);
}
}