/* * Copyright (C) 2022 Jo-Philipp Wich * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "ucode/module.h" #include "ucode/platform.h" #define err_return(err) do { last_error = err; return NULL; } while(0) static uc_resource_type_t *timer_type, *handle_type, *process_type, *task_type, *pipe_type; #ifdef HAVE_ULOOP_INTERVAL static uc_resource_type_t *interval_type; #endif #ifdef HAVE_ULOOP_SIGNAL static uc_resource_type_t *signal_type; #endif static uc_value_t *object_registry; static int last_error = 0; static size_t uc_uloop_reg_add(uc_value_t *obj, uc_value_t *cb) { size_t i = 0; while (ucv_array_get(object_registry, i)) i += 2; ucv_array_set(object_registry, i + 0, ucv_get(obj)); ucv_array_set(object_registry, i + 1, ucv_get(cb)); return i; } static bool uc_uloop_reg_remove(size_t i) { if (i + 1 >= ucv_array_length(object_registry)) return false; ucv_array_set(object_registry, i + 0, NULL); ucv_array_set(object_registry, i + 1, NULL); return true; } static bool uc_uloop_reg_invoke(uc_vm_t *vm, size_t i, uc_value_t *arg) { uc_value_t *obj = ucv_array_get(object_registry, i + 0); uc_value_t *cb = ucv_array_get(object_registry, i + 1); if (!ucv_is_callable(cb)) return false; uc_vm_stack_push(vm, ucv_get(obj)); uc_vm_stack_push(vm, ucv_get(cb)); uc_vm_stack_push(vm, ucv_get(arg)); if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE) { uloop_end(); return false; } ucv_put(uc_vm_stack_pop(vm)); return true; } static uc_value_t * uc_uloop_error(uc_vm_t *vm, size_t nargs) { uc_value_t *errmsg; if (last_error == 0) return NULL; errmsg = ucv_string_new(strerror(last_error)); last_error = 0; return errmsg; } static uc_value_t * uc_uloop_init(uc_vm_t *vm, size_t nargs) { int rv = uloop_init(); if (rv == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_uloop_run(uc_vm_t *vm, size_t nargs) { uc_value_t *timeout = uc_fn_arg(0); int t, rv; errno = 0; t = timeout ? (int)ucv_int64_get(timeout) : -1; if (errno) err_return(errno); rv = uloop_run_timeout(t); return ucv_int64_new(rv); } static uc_value_t * uc_uloop_cancelling(uc_vm_t *vm, size_t nargs) { return ucv_boolean_new(uloop_cancelling()); } static uc_value_t * uc_uloop_running(uc_vm_t *vm, size_t nargs) { bool prev = uloop_cancelled; bool active; uloop_cancelled = true; active = uloop_cancelling(); uloop_cancelled = prev; return ucv_boolean_new(active); } static uc_value_t * uc_uloop_end(uc_vm_t *vm, size_t nargs) { uloop_end(); return NULL; } static uc_value_t * uc_uloop_done(uc_vm_t *vm, size_t nargs) { uloop_done(); return NULL; } typedef struct { struct uloop_timeout timeout; size_t registry_index; uc_vm_t *vm; } uc_uloop_timer_t; static void uc_uloop_timeout_clear(uc_uloop_timer_t **timer) { /* drop registry entries and clear data to prevent reuse */ uc_uloop_reg_remove((*timer)->registry_index); free(*timer); *timer = NULL; } static uc_value_t * uc_uloop_timer_set(uc_vm_t *vm, size_t nargs) { uc_uloop_timer_t **timer = uc_fn_this("uloop.timer"); uc_value_t *timeout = uc_fn_arg(0); int t, rv; if (!timer || !*timer) err_return(EINVAL); errno = 0; t = timeout ? (int)ucv_int64_get(timeout) : -1; if (errno) err_return(errno); rv = uloop_timeout_set(&(*timer)->timeout, t); return ucv_boolean_new(rv == 0); } static uc_value_t * uc_uloop_timer_remaining(uc_vm_t *vm, size_t nargs) { uc_uloop_timer_t **timer = uc_fn_this("uloop.timer"); int64_t rem; if (!timer || !*timer) err_return(EINVAL); #ifdef HAVE_ULOOP_TIMEOUT_REMAINING64 rem = uloop_timeout_remaining64(&(*timer)->timeout); #else rem = (int64_t)uloop_timeout_remaining(&(*timer)->timeout); #endif return ucv_int64_new(rem); } static uc_value_t * uc_uloop_timer_cancel(uc_vm_t *vm, size_t nargs) { uc_uloop_timer_t **timer = uc_fn_this("uloop.timer"); int rv; if (!timer || !*timer) err_return(EINVAL); rv = uloop_timeout_cancel(&(*timer)->timeout); uc_uloop_timeout_clear(timer); return ucv_boolean_new(rv == 0); } static void uc_uloop_timer_cb(struct uloop_timeout *timeout) { uc_uloop_timer_t *timer = (uc_uloop_timer_t *)timeout; uc_uloop_reg_invoke(timer->vm, timer->registry_index, NULL); } static uc_value_t * uc_uloop_timer(uc_vm_t *vm, size_t nargs) { uc_value_t *timeout = uc_fn_arg(0); uc_value_t *callback = uc_fn_arg(1); uc_uloop_timer_t *timer; uc_value_t *res; int t; errno = 0; t = timeout ? ucv_int64_get(timeout) : -1; if (errno) err_return(errno); if (!ucv_is_callable(callback)) err_return(EINVAL); timer = xalloc(sizeof(*timer)); timer->timeout.cb = uc_uloop_timer_cb; timer->vm = vm; if (t >= 0) uloop_timeout_set(&timer->timeout, t); res = uc_resource_new(timer_type, timer); timer->registry_index = uc_uloop_reg_add(res, callback); return res; } typedef struct { struct uloop_fd fd; size_t registry_index; uc_value_t *handle; uc_vm_t *vm; } uc_uloop_handle_t; static void uc_uloop_handle_clear(uc_uloop_handle_t **handle) { /* drop registry entries and clear data to prevent reuse */ uc_uloop_reg_remove((*handle)->registry_index); ucv_put((*handle)->handle); free(*handle); *handle = NULL; } static uc_value_t * uc_uloop_handle_fileno(uc_vm_t *vm, size_t nargs) { uc_uloop_handle_t **handle = uc_fn_this("uloop.handle"); if (!handle || !*handle) err_return(EINVAL); return ucv_int64_new((*handle)->fd.fd); } static uc_value_t * uc_uloop_handle_handle(uc_vm_t *vm, size_t nargs) { uc_uloop_handle_t **handle = uc_fn_this("uloop.handle"); if (!handle || !*handle) err_return(EINVAL); return ucv_get((*handle)->handle); } static uc_value_t * uc_uloop_handle_delete(uc_vm_t *vm, size_t nargs) { uc_uloop_handle_t **handle = uc_fn_this("uloop.handle"); int rv; if (!handle || !*handle) err_return(EINVAL); rv = uloop_fd_delete(&(*handle)->fd); uc_uloop_handle_clear(handle); if (rv != 0) err_return(errno); return ucv_boolean_new(true); } static void uc_uloop_handle_cb(struct uloop_fd *fd, unsigned int flags) { uc_uloop_handle_t *handle = (uc_uloop_handle_t *)fd; uc_value_t *f = ucv_uint64_new(flags); uc_uloop_reg_invoke(handle->vm, handle->registry_index, f); ucv_put(f); } static int get_fd(uc_vm_t *vm, uc_value_t *val) { uc_value_t *fn; int64_t n; int fd; fn = ucv_property_get(val, "fileno"); if (ucv_is_callable(fn)) { uc_vm_stack_push(vm, ucv_get(val)); uc_vm_stack_push(vm, ucv_get(fn)); if (uc_vm_call(vm, true, 0) == EXCEPTION_NONE) { val = uc_vm_stack_pop(vm); } else { errno = EBADF; val = NULL; } } else { ucv_get(val); } n = ucv_int64_get(val); if (errno) { fd = -1; } else if (n < 0 || n > (int64_t)INT_MAX) { errno = EBADF; fd = -1; } else { fd = (int)n; } ucv_put(val); return fd; } static uc_value_t * uc_uloop_handle(uc_vm_t *vm, size_t nargs) { uc_value_t *fileno = uc_fn_arg(0); uc_value_t *callback = uc_fn_arg(1); uc_value_t *flags = uc_fn_arg(2); uc_uloop_handle_t *handle; uc_value_t *res; int fd, ret; uint64_t f; fd = get_fd(vm, fileno); if (fd == -1) err_return(errno); f = ucv_uint64_get(flags); if (errno) err_return(errno); if (f == 0 || f > (uint64_t)UINT_MAX) err_return(EINVAL); if (!ucv_is_callable(callback)) err_return(EINVAL); handle = xalloc(sizeof(*handle)); handle->fd.fd = fd; handle->fd.cb = uc_uloop_handle_cb; handle->handle = ucv_get(fileno); handle->vm = vm; ret = uloop_fd_add(&handle->fd, (unsigned int)f); if (ret != 0) { free(handle); err_return(errno); } res = uc_resource_new(handle_type, handle); handle->registry_index = uc_uloop_reg_add(res, callback); return res; } typedef struct { struct uloop_process process; size_t registry_index; uc_vm_t *vm; } uc_uloop_process_t; static void uc_uloop_process_clear(uc_uloop_process_t **process) { /* drop registry entries and clear data to prevent reuse */ uc_uloop_reg_remove((*process)->registry_index); *process = NULL; } static uc_value_t * uc_uloop_process_pid(uc_vm_t *vm, size_t nargs) { uc_uloop_process_t **process = uc_fn_this("uloop.process"); if (!process || !*process) err_return(EINVAL); return ucv_int64_new((*process)->process.pid); } static uc_value_t * uc_uloop_process_delete(uc_vm_t *vm, size_t nargs) { uc_uloop_process_t **process = uc_fn_this("uloop.process"); int rv; if (!process || !*process) err_return(EINVAL); rv = uloop_process_delete(&(*process)->process); uc_uloop_process_clear(process); if (rv != 0) err_return(EINVAL); return ucv_boolean_new(true); } static void uc_uloop_process_cb(struct uloop_process *proc, int exitcode) { uc_uloop_process_t *process = (uc_uloop_process_t *)proc; uc_value_t *e = ucv_int64_new(exitcode >> 8); uc_uloop_reg_invoke(process->vm, process->registry_index, e); uc_uloop_process_clear(&process); ucv_put(e); } static uc_value_t * uc_uloop_process(uc_vm_t *vm, size_t nargs) { uc_value_t *executable = uc_fn_arg(0); uc_value_t *arguments = uc_fn_arg(1); uc_value_t *env_arg = uc_fn_arg(2); uc_value_t *callback = uc_fn_arg(3); uc_uloop_process_t *process; uc_stringbuf_t *buf; char **argp, **envp; uc_value_t *res; pid_t pid; size_t i; if (ucv_type(executable) != UC_STRING || (arguments && ucv_type(arguments) != UC_ARRAY) || (env_arg && ucv_type(env_arg) != UC_OBJECT) || !ucv_is_callable(callback)) { err_return(EINVAL); } pid = fork(); if (pid == -1) err_return(errno); if (pid == 0) { argp = calloc(ucv_array_length(arguments) + 2, sizeof(char *)); envp = calloc(ucv_object_length(env_arg) + 1, sizeof(char *)); if (!argp || !envp) _exit(-1); argp[0] = ucv_to_string(vm, executable); for (i = 0; i < ucv_array_length(arguments); i++) argp[i+1] = ucv_to_string(vm, ucv_array_get(arguments, i)); i = 0; ucv_object_foreach(env_arg, envk, envv) { buf = xprintbuf_new(); ucv_stringbuf_printf(buf, "%s=", envk); ucv_to_stringbuf(vm, buf, envv, false); envp[i++] = buf->buf; free(buf); } execvpe((const char *)ucv_string_get(executable), (char * const *)argp, (char * const *)envp); _exit(-1); } process = xalloc(sizeof(*process)); process->process.pid = pid; process->process.cb = uc_uloop_process_cb; process->vm = vm; uloop_process_add(&process->process); res = uc_resource_new(process_type, process); process->registry_index = uc_uloop_reg_add(res, callback); return res; } static bool readall(int fd, void *buf, size_t len) { ssize_t rlen; while (len > 0) { rlen = read(fd, buf, len); if (rlen == -1) { if (errno == EINTR) continue; return false; } if (rlen == 0) { errno = EINTR; return false; } buf += rlen; len -= rlen; } return true; } static bool writeall(int fd, void *buf, size_t len) { ssize_t wlen; while (len > 0) { wlen = write(fd, buf, len); if (wlen == -1) { if (errno == EINTR) continue; return false; } buf += wlen; len -= wlen; } return true; } typedef struct { int input; int output; bool has_sender; bool has_receiver; } uc_uloop_pipe_t; static uc_value_t * uc_uloop_pipe_send_common(uc_vm_t *vm, uc_value_t *msg, int fd) { uc_stringbuf_t *buf; size_t len; bool rv; buf = xprintbuf_new(); printbuf_memset(buf, 0, 0, sizeof(len)); ucv_to_stringbuf(vm, buf, msg, true); len = printbuf_length(buf); memcpy(buf->buf, &len, sizeof(len)); rv = writeall(fd, buf->buf, len); printbuf_free(buf); if (!rv) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_uloop_pipe_send(uc_vm_t *vm, size_t nargs) { uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe"); uc_value_t *msg = uc_fn_arg(0); if (!pipe || !*pipe) err_return(EINVAL); if (!(*pipe)->has_receiver) err_return(EPIPE); return uc_uloop_pipe_send_common(vm, msg, (*pipe)->output); } static bool uc_uloop_pipe_receive_common(uc_vm_t *vm, int fd, uc_value_t **res, bool skip) { enum json_tokener_error err = json_tokener_error_parse_eof; json_tokener *tok = NULL; json_object *jso = NULL; char buf[1024]; ssize_t rlen; size_t len; *res = NULL; if (!readall(fd, &len, sizeof(len))) err_return(errno); /* message length 0 is special, means input requested on other pipe */ if (len == 0) err_return(ENODATA); /* valid messages should be at least sizeof(len) plus one byte of payload */ if (len <= sizeof(len)) err_return(EINVAL); len -= sizeof(len); while (len > 0) { rlen = read(fd, buf, len < sizeof(buf) ? len : sizeof(buf)); if (rlen == -1) { if (errno == EINTR) continue; goto read_fail; } /* premature EOF */ if (rlen == 0) { errno = EPIPE; goto read_fail; } if (!skip) { if (!tok) tok = xjs_new_tokener(); jso = json_tokener_parse_ex(tok, buf, rlen); err = json_tokener_get_error(tok); } len -= rlen; } if (!skip) { if (err == json_tokener_continue) { jso = json_tokener_parse_ex(tok, "\0", 1); err = json_tokener_get_error(tok); } json_tokener_free(tok); if (err != json_tokener_success) { errno = EINVAL; goto read_fail; } *res = ucv_from_json(vm, jso); json_object_put(jso); } return true; read_fail: if (tok) json_tokener_free(tok); json_object_put(jso); err_return(errno); } static uc_value_t * uc_uloop_pipe_receive(uc_vm_t *vm, size_t nargs) { uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe"); uc_value_t *rv; size_t len = 0; if (!pipe || !*pipe) err_return(EINVAL); if (!(*pipe)->has_sender) err_return(EPIPE); /* send zero-length message to signal input request */ writeall((*pipe)->output, &len, sizeof(len)); /* receive input message */ uc_uloop_pipe_receive_common(vm, (*pipe)->input, &rv, false); return rv; } static uc_value_t * uc_uloop_pipe_sending(uc_vm_t *vm, size_t nargs) { uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe"); if (!pipe || !*pipe) err_return(EINVAL); return ucv_boolean_new((*pipe)->has_sender); } static uc_value_t * uc_uloop_pipe_receiving(uc_vm_t *vm, size_t nargs) { uc_uloop_pipe_t **pipe = uc_fn_this("uloop.pipe"); if (!pipe || !*pipe) err_return(EINVAL); return ucv_boolean_new((*pipe)->has_receiver); } typedef struct { struct uloop_process process; struct uloop_fd output; size_t registry_index; bool finished; int input_fd; uc_vm_t *vm; uc_value_t *input_cb; uc_value_t *output_cb; } uc_uloop_task_t; static int patch_devnull(int fd, bool write) { int devnull = open("/dev/null", write ? O_WRONLY : O_RDONLY); if (devnull != -1) { dup2(fd, devnull); close(fd); } return devnull; } static void uloop_fd_close(struct uloop_fd *fd) { if (fd->fd == -1) return; close(fd->fd); fd->fd = -1; } static void uc_uloop_task_clear(uc_uloop_task_t **task) { /* drop registry entries and clear data to prevent reuse */ uc_uloop_reg_remove((*task)->registry_index); *task = NULL; } static uc_value_t * uc_uloop_task_pid(uc_vm_t *vm, size_t nargs) { uc_uloop_task_t **task = uc_fn_this("uloop.task"); if (!task || !*task) err_return(EINVAL); if ((*task)->finished) err_return(ESRCH); return ucv_int64_new((*task)->process.pid); } static uc_value_t * uc_uloop_task_kill(uc_vm_t *vm, size_t nargs) { uc_uloop_task_t **task = uc_fn_this("uloop.task"); int rv; if (!task || !*task) err_return(EINVAL); if ((*task)->finished) err_return(ESRCH); rv = kill((*task)->process.pid, SIGTERM); if (rv == -1) err_return(errno); return ucv_boolean_new(true); } static uc_value_t * uc_uloop_task_finished(uc_vm_t *vm, size_t nargs) { uc_uloop_task_t **task = uc_fn_this("uloop.task"); if (!task || !*task) err_return(EINVAL); return ucv_boolean_new((*task)->finished); } static void uc_uloop_task_output_cb(struct uloop_fd *fd, unsigned int flags) { uc_uloop_task_t *task = container_of(fd, uc_uloop_task_t, output); uc_value_t *obj = ucv_array_get(object_registry, task->registry_index); uc_value_t *msg = NULL; if (flags & ULOOP_READ) { while (true) { if (!uc_uloop_pipe_receive_common(task->vm, fd->fd, &msg, !task->output_cb)) { /* input requested */ if (last_error == ENODATA) { uc_vm_stack_push(task->vm, ucv_get(obj)); uc_vm_stack_push(task->vm, ucv_get(task->input_cb)); if (uc_vm_call(task->vm, true, 0) != EXCEPTION_NONE) { uloop_end(); return; } msg = uc_vm_stack_pop(task->vm); uc_uloop_pipe_send_common(task->vm, msg, task->input_fd); ucv_put(msg); continue; } /* error */ break; } if (task->output_cb) { uc_vm_stack_push(task->vm, ucv_get(obj)); uc_vm_stack_push(task->vm, ucv_get(task->output_cb)); uc_vm_stack_push(task->vm, msg); if (uc_vm_call(task->vm, true, 1) == EXCEPTION_NONE) { ucv_put(uc_vm_stack_pop(task->vm)); } else { uloop_end(); return; } } else { ucv_put(msg); } } } if (!fd->registered && task->finished) { close(task->input_fd); task->input_fd = -1; uloop_fd_close(&task->output); uloop_process_delete(&task->process); uc_uloop_task_clear(&task); } } static void uc_uloop_task_process_cb(struct uloop_process *proc, int exitcode) { uc_uloop_task_t *task = container_of(proc, uc_uloop_task_t, process); task->finished = true; uc_uloop_task_output_cb(&task->output, ULOOP_READ); } static uc_value_t * uc_uloop_task(uc_vm_t *vm, size_t nargs) { uc_value_t *func = uc_fn_arg(0); uc_value_t *output_cb = uc_fn_arg(1); uc_value_t *input_cb = uc_fn_arg(2); int outpipe[2] = { -1, -1 }; int inpipe[2] = { -1, -1 }; uc_value_t *res, *cbs, *p; uc_uloop_pipe_t *tpipe; uc_uloop_task_t *task; pid_t pid; int err; if (!ucv_is_callable(func) || (output_cb && !ucv_is_callable(output_cb)) || (input_cb && !ucv_is_callable(input_cb))) err_return(EINVAL); if (pipe(outpipe) == -1 || pipe(inpipe) == -1) { err = errno; close(outpipe[0]); close(outpipe[1]); close(inpipe[0]); close(inpipe[1]); err_return(err); } pid = fork(); if (pid == -1) err_return(errno); if (pid == 0) { uloop_done(); patch_devnull(0, false); patch_devnull(1, true); patch_devnull(2, true); vm->output = fdopen(1, "w"); close(inpipe[1]); close(outpipe[0]); tpipe = xalloc(sizeof(*tpipe)); tpipe->input = inpipe[0]; tpipe->output = outpipe[1]; tpipe->has_sender = input_cb; tpipe->has_receiver = output_cb; p = uc_resource_new(pipe_type, tpipe); uc_vm_stack_push(vm, func); uc_vm_stack_push(vm, ucv_get(p)); if (uc_vm_call(vm, false, 1) == EXCEPTION_NONE) { res = uc_vm_stack_pop(vm); uc_uloop_pipe_send_common(vm, res, tpipe->output); ucv_put(res); } ucv_put(p); _exit(0); } close(inpipe[0]); close(outpipe[1]); task = xalloc(sizeof(*task)); task->process.pid = pid; task->process.cb = uc_uloop_task_process_cb; task->vm = vm; task->output.fd = outpipe[0]; task->output.cb = uc_uloop_task_output_cb; task->output_cb = output_cb; uloop_fd_add(&task->output, ULOOP_READ); if (input_cb) { task->input_fd = inpipe[1]; task->input_cb = input_cb; } else { task->input_fd = -1; close(inpipe[1]); } uloop_process_add(&task->process); res = uc_resource_new(task_type, task); cbs = ucv_array_new(NULL); ucv_array_set(cbs, 0, ucv_get(output_cb)); ucv_array_set(cbs, 1, ucv_get(input_cb)); task->registry_index = uc_uloop_reg_add(res, cbs); return res; } #ifdef HAVE_ULOOP_INTERVAL typedef struct { struct uloop_interval interval; size_t registry_index; uc_vm_t *vm; } uc_uloop_interval_t; static void uc_uloop_interval_clear(uc_uloop_interval_t **interval) { /* drop registry entries and clear data to prevent reuse */ uc_uloop_reg_remove((*interval)->registry_index); free(*interval); *interval = NULL; } static uc_value_t * uc_uloop_interval_set(uc_vm_t *vm, size_t nargs) { uc_uloop_interval_t **interval = uc_fn_this("uloop.interval"); uc_value_t *timeout = uc_fn_arg(0); int t, rv; if (!interval || !*interval) err_return(EINVAL); errno = 0; t = timeout ? (int)ucv_int64_get(timeout) : -1; if (errno) err_return(errno); rv = uloop_interval_set(&(*interval)->interval, t); return ucv_boolean_new(rv == 0); } static uc_value_t * uc_uloop_interval_remaining(uc_vm_t *vm, size_t nargs) { uc_uloop_interval_t **interval = uc_fn_this("uloop.interval"); if (!interval || !*interval) err_return(EINVAL); return ucv_int64_new(uloop_interval_remaining(&(*interval)->interval)); } static uc_value_t * uc_uloop_interval_expirations(uc_vm_t *vm, size_t nargs) { uc_uloop_interval_t **interval = uc_fn_this("uloop.interval"); if (!interval || !*interval) err_return(EINVAL); return ucv_int64_new((*interval)->interval.expirations); } static uc_value_t * uc_uloop_interval_cancel(uc_vm_t *vm, size_t nargs) { uc_uloop_interval_t **interval = uc_fn_this("uloop.interval"); int rv; if (!interval || !*interval) err_return(EINVAL); rv = uloop_interval_cancel(&(*interval)->interval); uc_uloop_interval_clear(interval); return ucv_boolean_new(rv == 0); } static void uc_uloop_interval_cb(struct uloop_interval *uintv) { uc_uloop_interval_t *interval = (uc_uloop_interval_t *)uintv; uc_uloop_reg_invoke(interval->vm, interval->registry_index, NULL); } static uc_value_t * uc_uloop_interval(uc_vm_t *vm, size_t nargs) { uc_value_t *timeout = uc_fn_arg(0); uc_value_t *callback = uc_fn_arg(1); uc_uloop_interval_t *interval; uc_value_t *res; int t; errno = 0; t = timeout ? ucv_int64_get(timeout) : -1; if (errno) err_return(errno); if (!ucv_is_callable(callback)) err_return(EINVAL); interval = xalloc(sizeof(*interval)); interval->interval.cb = uc_uloop_interval_cb; interval->vm = vm; if (t >= 0) uloop_interval_set(&interval->interval, t); res = uc_resource_new(interval_type, interval); interval->registry_index = uc_uloop_reg_add(res, callback); return res; } #endif #ifdef HAVE_ULOOP_SIGNAL typedef struct { struct uloop_signal signal; size_t registry_index; uc_vm_t *vm; } uc_uloop_signal_t; static void uc_uloop_signal_clear(uc_uloop_signal_t **signal) { /* drop registry entries and clear data to prevent reuse */ uc_uloop_reg_remove((*signal)->registry_index); free(*signal); *signal = NULL; } static uc_value_t * uc_uloop_signal_signo(uc_vm_t *vm, size_t nargs) { uc_uloop_signal_t **signal = uc_fn_this("uloop.signal"); if (!signal || !*signal) err_return(EINVAL); return ucv_int64_new((*signal)->signal.signo); } static uc_value_t * uc_uloop_signal_delete(uc_vm_t *vm, size_t nargs) { uc_uloop_signal_t **signal = uc_fn_this("uloop.signal"); int rv; if (!signal || !*signal) err_return(EINVAL); rv = uloop_signal_delete(&(*signal)->signal); uc_uloop_signal_clear(signal); if (rv != 0) err_return(EINVAL); return ucv_boolean_new(true); } static void uc_uloop_signal_cb(struct uloop_signal *usig) { uc_uloop_signal_t *signal = (uc_uloop_signal_t *)usig; uc_uloop_reg_invoke(signal->vm, signal->registry_index, NULL); } static int parse_signo(uc_value_t *sigspec) { if (ucv_type(sigspec) == UC_STRING) { const char *signame = ucv_string_get(sigspec); if (!strncasecmp(signame, "SIG", 3)) signame += 3; for (size_t i = 0; i < UC_SYSTEM_SIGNAL_COUNT; i++) { if (!uc_system_signal_names[i]) continue; if (strcasecmp(uc_system_signal_names[i], signame)) continue; return i; } } uc_value_t *signum = ucv_to_number(sigspec); int64_t signo = ucv_int64_get(signum); ucv_put(signum); if (signo < 1 || signo >= UC_SYSTEM_SIGNAL_COUNT) return -1; return signo; } static uc_value_t * uc_uloop_signal(uc_vm_t *vm, size_t nargs) { int signo = parse_signo(uc_fn_arg(0)); uc_value_t *callback = uc_fn_arg(1); uc_uloop_signal_t *signal; uc_value_t *res; if (signo == -1 || !ucv_is_callable(callback)) err_return(EINVAL); signal = xalloc(sizeof(*signal)); signal->signal.signo = signo; signal->signal.cb = uc_uloop_signal_cb; signal->vm = vm; uloop_signal_add(&signal->signal); res = uc_resource_new(signal_type, signal); signal->registry_index = uc_uloop_reg_add(res, callback); return res; } #endif static const uc_function_list_t timer_fns[] = { { "set", uc_uloop_timer_set }, { "remaining", uc_uloop_timer_remaining }, { "cancel", uc_uloop_timer_cancel }, }; static const uc_function_list_t handle_fns[] = { { "fileno", uc_uloop_handle_fileno }, { "handle", uc_uloop_handle_handle }, { "delete", uc_uloop_handle_delete }, }; static const uc_function_list_t process_fns[] = { { "pid", uc_uloop_process_pid }, { "delete", uc_uloop_process_delete }, }; static const uc_function_list_t task_fns[] = { { "pid", uc_uloop_task_pid }, { "kill", uc_uloop_task_kill }, { "finished", uc_uloop_task_finished }, }; static const uc_function_list_t pipe_fns[] = { { "send", uc_uloop_pipe_send }, { "receive", uc_uloop_pipe_receive }, { "sending", uc_uloop_pipe_sending }, { "receiving", uc_uloop_pipe_receiving }, }; #ifdef HAVE_ULOOP_INTERVAL static const uc_function_list_t interval_fns[] = { { "set", uc_uloop_interval_set }, { "remaining", uc_uloop_interval_remaining }, { "expirations", uc_uloop_interval_expirations }, { "cancel", uc_uloop_interval_cancel }, }; #endif #ifdef HAVE_ULOOP_SIGNAL static const uc_function_list_t signal_fns[] = { { "signo", uc_uloop_signal_signo }, { "delete", uc_uloop_signal_delete }, }; #endif static const uc_function_list_t global_fns[] = { { "error", uc_uloop_error }, { "init", uc_uloop_init }, { "run", uc_uloop_run }, { "timer", uc_uloop_timer }, { "handle", uc_uloop_handle }, { "process", uc_uloop_process }, { "task", uc_uloop_task }, { "cancelling", uc_uloop_cancelling }, { "running", uc_uloop_running }, { "done", uc_uloop_done }, { "end", uc_uloop_end }, #ifdef HAVE_ULOOP_INTERVAL { "interval", uc_uloop_interval }, #endif #ifdef HAVE_ULOOP_SIGNAL { "signal", uc_uloop_signal }, #endif }; static void close_timer(void *ud) { uc_uloop_timer_t *timer = ud; if (!timer) return; uloop_timeout_cancel(&timer->timeout); free(timer); } static void close_handle(void *ud) { uc_uloop_handle_t *handle = ud; if (!handle) return; uloop_fd_delete(&handle->fd); ucv_put(handle->handle); free(handle); } static void close_process(void *ud) { uc_uloop_process_t *process = ud; if (!process) return; uloop_process_delete(&process->process); free(process); } static void close_task(void *ud) { uc_uloop_task_t *task = ud; if (!task) return; uloop_process_delete(&task->process); uloop_fd_close(&task->output); if (task->input_fd != -1) close(task->input_fd); free(task); } static void close_pipe(void *ud) { uc_uloop_pipe_t *pipe = ud; if (!pipe) return; close(pipe->input); close(pipe->output); free(pipe); } #ifdef HAVE_ULOOP_INTERVAL static void close_interval(void *ud) { uc_uloop_interval_t *interval = ud; if (!interval) return; uloop_interval_cancel(&interval->interval); free(interval); } #endif #ifdef HAVE_ULOOP_SIGNAL static void close_signal(void *ud) { uc_uloop_signal_t *signal = ud; if (!signal) return; uloop_signal_delete(&signal->signal); free(signal); } #endif static struct { struct uloop_fd ufd; uc_vm_t *vm; } signal_handle; static void uc_uloop_vm_signal_cb(struct uloop_fd *ufd, unsigned int events) { if (uc_vm_signal_dispatch(signal_handle.vm) != EXCEPTION_NONE) uloop_end(); } void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { int signal_fd; uc_function_list_register(scope, global_fns); #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x)) ADD_CONST(ULOOP_READ); ADD_CONST(ULOOP_WRITE); ADD_CONST(ULOOP_EDGE_TRIGGER); ADD_CONST(ULOOP_BLOCKING); timer_type = uc_type_declare(vm, "uloop.timer", timer_fns, close_timer); handle_type = uc_type_declare(vm, "uloop.handle", handle_fns, close_handle); process_type = uc_type_declare(vm, "uloop.process", process_fns, close_process); task_type = uc_type_declare(vm, "uloop.task", task_fns, close_task); pipe_type = uc_type_declare(vm, "uloop.pipe", pipe_fns, close_pipe); #ifdef HAVE_ULOOP_INTERVAL interval_type = uc_type_declare(vm, "uloop.interval", interval_fns, close_interval); #endif #ifdef HAVE_ULOOP_SIGNAL signal_type = uc_type_declare(vm, "uloop.signal", signal_fns, close_signal); #endif object_registry = ucv_array_new(vm); uc_vm_registry_set(vm, "uloop.registry", object_registry); signal_fd = uc_vm_signal_notifyfd(vm); if (signal_fd != -1 && uloop_init() == 0) { signal_handle.vm = vm; signal_handle.ufd.cb = uc_uloop_vm_signal_cb; signal_handle.ufd.fd = signal_fd; uloop_fd_add(&signal_handle.ufd, ULOOP_READ); } }