quickjs/quickjs-libc.c
Ben Noordhuis a5b9e54151
Re-enable stack depth checks under ASan (#161)
The default 256 kb stack is too small to run some of the test262 tests
when ASAN is enabled.

Double it to 512 kb and ensure threads created by quickjs have big
enough stacks.
2023-11-30 22:18:23 +01:00

3940 lines
109 KiB
C

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