/* * 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. */ /** * # OpenWrt uloop event loop * * The `uloop` binding provides functions for integrating with the OpenWrt * {@link https://github.com/openwrt/libubox/blob/master/uloop.h uloop library}. * * Functions can be individually imported and directly accessed using the * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import} * syntax: * * ```javascript * import { init, handle, timer, interval, process, signal, task, run } from 'uloop'; * * init(); * * handle(…); * timer(…); * interval(…); * process(…); * signal(…); * task(…); * * run(); * ``` * * Alternatively, the module namespace can be imported using a wildcard import * statement: * * ```javascript * import * as uloop from 'uloop'; * * uloop.init(); * * uloop.handle(…); * uloop.timer(…); * uloop.interval(…); * uloop.process(…); * uloop.signal(…); * uloop.task(…); * * uloop.run(); * ``` * * Additionally, the uloop binding namespace may also be imported by invoking * the `ucode` interpreter with the `-luloop` switch. * * @module uloop */ #include #include #include #include #include #include #include "ucode/module.h" #include "ucode/platform.h" #define ok_return(expr) do { last_error = 0; return (expr); } while(0) #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; } /** * Retrieves the last error message. * * This function retrieves the last error message generated by the uloop event loop. * If no error occurred, it returns `null`. * * @function module:uloop#error * * @returns {?string} * Returns the last error message as a string, or `null` if no error occurred. * * @example * // Retrieve the last error message * const errorMessage = uloop.error(); * * if (errorMessage) * printf(`Error message: ${errorMessage}\n`); * else * printf("No error occurred\n"); */ 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; } /** * Initializes the uloop event loop. * * This function initializes the uloop event loop, allowing subsequent * usage of uloop functionalities. It takes no arguments. * * Returns `true` on success. * Returns `null` if an error occurred during initialization. * * @function module:uloop#init * * @returns {?boolean} * Returns `true` on success, `null` on error. * * @example * // Initialize the uloop event loop * const success = uloop.init(); * * if (success) * printf("uloop event loop initialized successfully\n"); * else * die(`Initialization failure: ${uloop.error()}\n`); */ static uc_value_t * uc_uloop_init(uc_vm_t *vm, size_t nargs) { int rv = uloop_init(); if (rv == -1) err_return(errno); ok_return(ucv_boolean_new(true)); } /** * Runs the uloop event loop. * * This function starts running the uloop event loop, allowing it to handle * scheduled events and callbacks. If a timeout value is provided and is * non-negative, the event loop will run for that amount of milliseconds * before returning. If the timeout is omitted or negative, the event loop * runs indefinitely until explicitly stopped. * * @function module:uloop#run * * @param {number} [timeout=-1] * Optional. The timeout value in milliseconds for running the event loop. * Defaults to -1, indicating an indefinite run. * * @returns {?boolean} * Returns `true` on success, `null` on error. * * @example * // Run the uloop event loop indefinitely * const success = uloop.run(); * if (success) * printf("uloop event loop ran successfully\n"); * else * die(`Error occurred during uloop execution: ${uloop.error()}\n`); * * // Run the uloop event loop for 1000 milliseconds * const success = uloop.run(1000); * if (success) * printf("uloop event loop ran successfully\n"); * else * die(`Error occurred during uloop execution: ${uloop.error()}\n`); */ 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); ok_return(ucv_int64_new(rv)); } /** * Checks if the uloop event loop is currently shutting down. * * This function checks whether the uloop event loop is currently in the process * of shutting down. * * @function module:uloop#cancelling * * @returns {boolean} * Returns `true` if uloop is currently shutting down, `false` otherwise. * * @example * // Check if the uloop event loop is shutting down * const shuttingDown = uloop.cancelling(); * if (shuttingDown) * printf("uloop event loop is currently shutting down\n"); * else * printf("uloop event loop is not shutting down\n"); */ static uc_value_t * uc_uloop_cancelling(uc_vm_t *vm, size_t nargs) { ok_return(ucv_boolean_new(uloop_cancelling())); } /** * Checks if the uloop event loop is currently running. * * This function checks whether the uloop event loop is currently started * and running. * * @function module:uloop#running * * @returns {boolean} * Returns `true` if the event loop is currently running, `false` otherwise. * * @example * // Check if the uloop event loop is running * const isRunning = uloop.running(); * if (isRunning) * printf("uloop event loop is currently running\n"); * else * printf("uloop event loop is not running\n"); */ 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; ok_return(ucv_boolean_new(active)); } /** * Halts the uloop event loop. * * This function halts the uloop event loop, stopping its execution and * preventing further processing of scheduled events and callbacks. * * Expired timeouts and already queued event callbacks are still run to * completion. * * @function module:uloop#end * * @returns {void} * This function does not return any value. * * @example * // Halt the uloop event loop * uloop.end(); */ static uc_value_t * uc_uloop_end(uc_vm_t *vm, size_t nargs) { uloop_end(); ok_return(NULL); } /** * Stops the uloop event loop and cancels pending timeouts and events. * * This function immediately stops the uloop event loop, cancels all pending * timeouts and events, unregisters all handles, and deallocates associated * resources. * * @function module:uloop#done * * @returns {void} * This function does not return any value. * * @example * // Stop the uloop event loop and clean up resources * uloop.done(); */ static uc_value_t * uc_uloop_done(uc_vm_t *vm, size_t nargs) { uloop_done(); ok_return(NULL); } /** * Represents a uloop timer instance as returned by * {@link module:uloop#timer|timer()}. * * @class module:uloop.timer * @hideconstructor * * @see {@link module:uloop#timer|timer()} * * @example * * const timeout = uloop.timer(…); * * timeout.set(…); * timeout.remaining(); * timeout.cancel(); */ 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; } /** * Rearms the uloop timer with the specified timeout. * * This method rearms the uloop timer with the specified timeout value, * allowing it to trigger after the specified amount of time. If no timeout * value is provided or if the provided value is negative, the timer remains * disabled until rearmed with a positive timeout value. * * @function module:uloop.timer#set * * @param {number} [timeout=-1] * Optional. The timeout value in milliseconds until the timer expires. * Defaults to -1, which disables the timer until rearmed with a positive timeout. * * @returns {?boolean} * Returns `true` on success, `null` on error, such as an invalid timeout argument. * * @example * const timeout = uloop.timer(…); * * // Rearm the uloop timer with a timeout of 1000 milliseconds * timeout.set(1000); * * // Disable the uloop timer * timeout.set(); */ 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); ok_return(ucv_boolean_new(rv == 0)); } /** * Returns the number of milliseconds until the uloop timer expires. * * This method returns the remaining time until the uloop timer expires. If * the timer is not armed (i.e., disabled), it returns -1. * * @function module:uloop.timer#remaining * * @returns {number} * The number of milliseconds until the timer expires, or -1 if the timer is not armed. * * @example * // Get the remaining time until the uloop timer expires (~500ms) * const remainingTime = timer.remaining(); * if (remainingTime !== -1) * printf("Time remaining until timer expires: %d ms\n", remainingTime); * else * printf("Timer is not armed\n"); */ 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 ok_return(ucv_int64_new(rem)); } /** * Cancels the uloop timer, disarming it and removing it from the event loop. * * This method destroys the uloop timer and releases its associated resources. * * @function module:uloop.timer#cancel * * @returns {boolean} * Returns `true` on success. * * @example * // Cancel the uloop timer * timer.cancel(); */ 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); ok_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); } /** * Creates a timer instance for scheduling callbacks. * * This function creates a timer instance for scheduling callbacks to be * executed after a specified timeout duration. It takes an optional timeout * parameter, which defaults to -1, indicating that the timer is initially not * armed and can be enabled later by invoking the `.set(timeout)` method on the * instance. * * A callback function must be provided to be executed when the timer expires. * * @function module:uloop#timer * * @param {number} [timeout=-1] * Optional. The timeout duration in milliseconds. Defaults to -1, indicating * the timer is not initially armed. * * @param {Function} callback * The callback function to be executed when the timer expires. * * @returns {?module:uloop.timer} * Returns a timer instance for scheduling callbacks. * Returns `null` when the timeout or callback arguments are invalid. * * @example * // Create a timer with a callback to be executed after 1000 milliseconds * const myTimer = uloop.timer(1000, () => { * printf("Timer expired!\n"); * }); * * // Later enable the timer with a timeout of 500 milliseconds * myTimer.set(500); */ 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); ok_return(res); } /** * Represents a uloop handle instance as returned by * {@link module:uloop#handle|handle()}. * * @class module:uloop.handle * @hideconstructor * * @see {@link module:uloop#handle|handle()} * * @example * * const handle = uloop.handle(…); * * handle.fileno(); * handle.handle(); * * handle.delete(); */ 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; } /** * Returns the file descriptor number. * * This method returns the file descriptor number associated with the underlying * handle, which might refer to a socket or file instance. * * @function module:uloop.handle#fileno * * @returns {number} * The file descriptor number associated with the handle. * * @example * // Get the file descriptor number associated with the uloop handle * const fd = handle.fileno(); * printf("File descriptor number: %d\n", fd); */ 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); ok_return(ucv_int64_new((*handle)->fd.fd)); } /** * Returns the underlying file or socket instance. * * This method returns the underlying file or socket instance associated with * the uloop handle. * * @function module:uloop.handle#handle * * @returns {module:fs.file|module:fs.proc|module:socket.socket} * The underlying file or socket instance associated with the handle. * * @example * // Get the associated file or socket instance * const fileOrSocket = handle.handle(); * printf("Handle: %s\n", fileOrSocket); // e.g. or */ 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); ok_return(ucv_get((*handle)->handle)); } /** * Unregisters the uloop handle. * * This method unregisters the uloop handle from the uloop event loop and frees * any associated resources. After calling this method, the handle instance * should no longer be used. * * @function module:uloop.handle#delete * * @returns {void} * This function does not return a value. * * @example * // Unregister the uloop handle and free associated resources * handle.delete(); * printf("Handle deleted successfully\n"); */ 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); ok_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; } /** * Creates a handle instance for monitoring file descriptor events. * * This function creates a handle instance for monitoring events on a file * descriptor, file, or socket. It takes the file or socket handle, a callback * function to be invoked when the specified IO events occur, and bitwise OR-ed * flags of IO events (`ULOOP_READ`, `ULOOP_WRITE`) that the callback should be * invoked for. * * @function module:uloop#handle * * @param {number|module:fs.file|module:fs.proc|module:socket.socket} handle * The file handle (descriptor number, file or socket instance). * * @param {Function} callback * The callback function to be invoked when the specified IO events occur. * * @param {number} events * Bitwise OR-ed flags of IO events (`ULOOP_READ`, `ULOOP_WRITE`) that the * callback should be invoked for. * * @returns {?module:uloop.handle} * Returns a handle instance for monitoring file descriptor events. * Returns `null` when the handle, callback or signal arguments are invalid. * * @example * // Create a handle for monitoring read events on file descriptor 3 * const myHandle = uloop.handle(3, (events) => { * if (events & ULOOP_READ) * printf("Read event occurred!\n"); * }, uloop.ULOOP_READ); * * // Check socket for writability * const sock = socket.connect("example.org", 80); * uloop.handle(sock, (events) => { * sock.send("GET / HTTP/1.0\r\n\r\n"); * }, uloop.ULOOP_WRITE) */ 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); ok_return(res); } /** * Represents a uloop process instance as returned by * {@link module:uloop#process|process()}. * * @class module:uloop.process * @hideconstructor * * @see {@link module:uloop#process|process()} * * @example * * const proc = uloop.process(…); * * proc.pid(); * * proc.delete(); */ 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; } /** * Returns the process ID. * * This method returns the process ID (PID) of the operating system process * launched by {@link module:uloop#process|process(). * * @function module:uloop.process#pid * * @returns {number} * The process ID (PID) of the associated launched process. * * @example * const proc = uloop.process(…); * * printf("Process ID: %d\n", proc.pid()); */ 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); ok_return(ucv_int64_new((*process)->process.pid)); } /** * Unregisters the process from uloop. * * This method unregisters the process from the uloop event loop and releases * any associated resources. However, note that the operating system process * itself is not terminated by this method. * * @function module:uloop.process#delete * * @returns {boolean} * Returns `true` on success. * * @example * const proc = uloop.process(…); * * proc.delete(); */ 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); ok_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); } /** * Creates a process instance for executing external programs. * * This function creates a process instance for executing external programs. * It takes the executable path string, an optional string array as the argument * vector, an optional dictionary describing environment variables, and a * callback function to be invoked when the invoked process ends. * * @function module:uloop#process * * @param {string} executable * The path to the executable program. * * @param {string[]} [args] * Optional. An array of strings representing the arguments passed to the * executable. * * @param {Object} [env] * Optional. A dictionary describing environment variables for the process. * * @param {Function} callback * The callback function to be invoked when the invoked process ends. * * @returns {?module:uloop.process} * Returns a process instance for executing external programs. * Returns `null` on error, e.g. due to `exec()` failure or invalid arguments. * * @example * // Create a process instance for executing 'ls' command * const myProcess = uloop.process("/bin/ls", ["-l", "/tmp"], null, (code) => { * printf(`Process exited with code ${code}\n`); * }); */ 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); ok_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; } /** * Represents a uloop task communication pipe instance, passed as sole argument * to the task function by {@link module:uloop#task|task()}. * * @class module:uloop.pipe * @hideconstructor * * @see {@link module:uloop#task|task()} * * @example * * const task = uloop.task((pipe) => { * … * pipe.send(); * … * pipe.receive(); * … * }, …); */ 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); ok_return(ucv_boolean_new(true)); } /** * Sends a serialized message to the task handle. * * This method serializes the provided message and sends it over the task * communication pipe. In the main thread, the message is deserialized and * passed as an argument to the output callback function registered with the * task handle. * * @function module:uloop.pipe#send * * @param {*} msg * The message to be serialized and sent over the pipe. It can be of arbitrary type. * * @returns {?boolean} * Returns `true` on success, indicating that the message was successfully sent * over the pipe. Returns `null` on error, such as when there's no output * callback registered with the task handle. * * @example * // Send a message over the uloop pipe * const success = pipe.send(message); * * if (success) * printf("Message sent successfully\n"); * else * die(`Error sending message: ${uloop.error()}\n`); */ 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); ok_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); } /** * Reads input from the task handle. * * This method reads input from the task communication pipe. The input callback * function registered with the task handle is invoked to return the input data, * which is then serialized, sent over the pipe, and deserialized by the receive * method. * * @function module:uloop.pipe#receive * * @returns {?*} * Returns the deserialized message read from the task communication pipe. * Returns `null` on error, such as when there's no input callback registered * on the task handle. * * @example * // Read input from the task communication pipe * const message = pipe.receive(); * * if (message !== null) * printf("Received message: %s\n", message); * else * die(`Error receiving message: ${uloop.error()}\n`); */ 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; } /** * Checks if the task handle provides input. * * This method checks if the task handle has an input callback registered. * It returns a boolean value indicating whether an input callback is present. * * @function module:uloop.pipe#sending * * @returns {boolean} * Returns `true` if the remote task handle has an input callback * registered, otherwise returns `false`. * * @example * // Check if the remote task handle has an input callback * const hasInputCallback = pipe.sending(); * * if (hasInputCallback) * printf("Input callback is registered on task handle\n"); * else * printf("No input callback on the task handle\n"); */ 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); ok_return(ucv_boolean_new((*pipe)->has_sender)); } /** * Checks if the task handle reads output. * * This method checks if the task handle has an output callback registered. * It returns a boolean value indicating whether an output callback is present. * * @function module:uloop.pipe#receiving * * @returns {boolean} * Returns `true` if the task handle has an output callback registered, * otherwise returns `false`. * * @example * // Check if the task handle has an output callback * const hasOutputCallback = pipe.receiving(); * * if (hasOutputCallback) * printf("Output callback is registered on task handle\n"); * else * printf("No output callback on the task handle\n"); */ 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); ok_return(ucv_boolean_new((*pipe)->has_receiver)); } /** * Represents a uloop task instance as returned by * {@link module:uloop#task|task()}. * * @class module:uloop.task * @hideconstructor * * @see {@link module:uloop#task|task()} * * @example * * const task = uloop.task(…); * * task.pid(); * task.finished(); * * task.kill(); */ 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; } /** * Returns the process ID. * * This method returns the process ID (PID) of the underlying forked process * launched by {@link module:uloop#task|task(). * * @function module:uloop.task#pid * * @returns {number} * The process ID (PID) of the forked task process. * * @example * const task = uloop.task(…); * * printf("Process ID: %d\n", task.pid()); */ 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); ok_return(ucv_int64_new((*task)->process.pid)); } /** * Terminates the task process. * * This method terminates the task process. It sends a termination signal to * the task process, causing it to exit. Returns `true` on success, indicating * that the task process was successfully terminated. Returns `null` on error, * such as when the task process has already terminated. * * @function module:uloop.task#kill * * @returns {?boolean} * Returns `true` when the task process was successfully terminated. * Returns `null` on error, such as when the process has already terminated. * * @example * // Terminate the task process * const success = task.kill(); * * if (success) * printf("Task process terminated successfully\n"); * else * die(`Error terminating task process: ${uloop.error()}\n`); */ 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); ok_return(ucv_boolean_new(true)); } /** * Checks if the task ran to completion. * * This method checks if the task function has already run to completion. * It returns a boolean value indicating whether the task function has finished * executing. * * @function module:uloop.task#finished * * @returns {boolean} * Returns `true` if the task function has already run to completion, otherwise * returns `false`. * * @example * // Check if the task function has finished executing * const isFinished = task.finished(); * * if (isFinished) * printf("Task function has finished executing\n"); * else * printf("Task function is still running\n"); */ 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); ok_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); } /** * Creates a task instance for executing background tasks. * * This function creates a task instance for executing background tasks. * It takes the task function to be invoked as a background process, * an optional output callback function to be invoked when output is received * from the task, and an optional input callback function to be invoked * when input is required by the task. * * @function module:uloop#task * * @param {Function} taskFunction * The task function to be invoked as a background process. * * @param {Function} [outputCallback] * Optional. The output callback function to be invoked when output is received * from the task. It is invoked with the output data as the argument. * * @param {Function} [inputCallback] * Optional. The input callback function to be invoked when input is required * by the task. It is invoked with a function to send input to the task * as the argument. * * @returns {?module:uloop.task} * Returns a task instance for executing background tasks. * Returns `null` on error, e.g. due to fork failure or invalid arguments. * * @example * // Create a task instance for executing a background task * const myTask = uloop.task( * (pipe) => { * // Task logic * pipe.send("Hello from the task\n"); * const input = pipe.receive(); * printf(`Received input from main thread: ${input}\n`); * }, * (output) => { * // Output callback, invoked when task function calls pipe.send() * printf(`Received output from task: ${output}\n`); * }, * () => { * // Input callback, invoked when task function calls pipe.receive() * return "Input from main thread\n"; * } * ); */ 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); ok_return(res); } /** * Represents a uloop interval timer instance as returned by * {@link module:uloop#interval|interval()}. * * @class module:uloop.interval * @hideconstructor * * @see {@link module:uloop#interval|interval()} * * @example * * const intv = uloop.interval(…); * * intv.set(…); * intv.remaining(); * intv.expirations(); * intv.cancel(); */ #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; } /** * Rearms the uloop interval timer with the specified interval. * * This method rearms the interval timer with the specified interval value, * allowing it to trigger repeatedly after the specified amount of time. If no * interval value is provided or if the provided value is negative, the interval * remains disabled until rearmed with a positive interval value. * * @function module:uloop.interval#set * * @param {number} [interval=-1] * Optional. The interval value in milliseconds specifying when the interval * triggers again. Defaults to -1, which disables the interval until rearmed * with a positive interval value. * * @returns {?boolean} * Returns `true` on success, `null` on error, such as an invalid interval argument. * * @example * // Rearm the uloop interval with a interval of 1000 milliseconds * const success = interval.set(1000); * * if (success) * printf("Interval rearmed successfully\n"); * else * printf("Error occurred while rearming interval: ${uloop.error()}\n"); * * // Disable the uloop interval * const success = interval.set(); * * if (success) * printf("Interval disabled successfully\n"); * else * printf("Error occurred while disabling interval: ${uloop.error()}\n"); */ 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); ok_return(ucv_boolean_new(rv == 0)); } /** * Returns the milliseconds until the next expiration. * * This method returns the remaining time until the uloop interval expires * and triggers again. If the interval is not armed (i.e., disabled), * it returns -1. * * @function module:uloop.interval#remaining * * @returns {number} * The milliseconds until the next expiration of the uloop interval, or -1 if * the interval is not armed. * * @example * // Get the milliseconds until the next expiration of the uloop interval * const remainingTime = interval.remaining(); * * if (remainingTime !== -1) * printf("Milliseconds until next expiration: %d\n", remainingTime); * else * printf("Interval is not armed\n"); */ 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); ok_return(ucv_int64_new(uloop_interval_remaining(&(*interval)->interval))); } /** * Returns number of times the interval timer fired. * * This method returns the number of times the uloop interval timer has expired * (fired) since it was instantiated. * * @function module:uloop.interval#expirations * * @returns {number} * The number of times the uloop interval timer has expired (fired). * * @example * // Get the number of times the uloop interval timer has expired * const expirations = interval.expirations(); * printf("Number of expirations: %d\n", expirations); */ 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); ok_return(ucv_int64_new((*interval)->interval.expirations)); } /** * Cancels the uloop interval. * * This method cancels the uloop interval, disarming it and removing it from the * event loop. Associated resources are released. * * @function module:uloop.interval#cancel * * @returns {boolean} * Returns `true` on success. * * @example * // Cancel the uloop interval * interval.cancel(); */ 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); ok_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); } /** * Creates an interval instance for scheduling repeated callbacks. * * This function creates an interval instance for scheduling repeated callbacks * to be executed at regular intervals. It takes an optional timeout parameter, * which defaults to -1, indicating that the interval is initially not armed * and can be armed later with the `.set(timeout)` method. A callback function * must be provided to be executed when the interval expires. * * @function module:uloop#interval * * @param {number} [timeout=-1] * Optional. The interval duration in milliseconds. Defaults to -1, indicating * the interval is not initially armed. * * @param {Function} callback * The callback function to be executed when the interval expires. * * @returns {?module:uloop.interval} * Returns an interval instance for scheduling repeated callbacks. * Returns `null` when the timeout or callback arguments are invalid. * * @example * // Create an interval with a callback to be executed every 1000 milliseconds * const myInterval = uloop.interval(1000, () => { * printf("Interval callback executed!\n"); * }); * * // Later arm the interval to start executing the callback every 500 milliseconds * myInterval.set(500); */ 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); ok_return(res); } #endif /** * Represents a uloop signal Unix process signal handler as returned by * {@link module:uloop#signal|signal()}. * * @class module:uloop.signal * @hideconstructor * * @see {@link module:uloop#signal|signal()} * * @example * * const sighandler = uloop.signal(…); * * sighandler.signo(); * sighandler.delete(); */ #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; } /** * Returns the associated signal number. * * This method returns the signal number that this uloop signal handler is * configured to respond to. * * @function module:uloop.signal#signo * * @returns {number} * The signal number that this handler is responding to. * * @example * // Get the signal number that the uloop signal handler is responding to * const sighandler = uloop.signal("SIGINT", () => printf("Cought INT\n")); * printf("Signal number: %d\n", sighandler.signo()); */ 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); ok_return(ucv_int64_new((*signal)->signal.signo)); } /** * Uninstalls the signal handler. * * This method uninstalls the signal handler, restoring the previous or default * handler for the signal, and releasing any associated resources. * * @function module:uloop.signal#delete * * @returns {boolean} * Returns `true` on success. * * @example * // Uninstall the signal handler and restore the previous/default handler * const sighandler = uloop.signal(…); * sighandler.delete(); */ 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); ok_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; } /** * Creates a signal instance for handling Unix signals. * * This function creates a signal instance for handling Unix signals. * It takes the signal name string (with or without "SIG" prefix) or signal * number, and a callback function to be invoked when the specified Unix signal * is caught. * * @function module:uloop#signal * * @param {string|number} signal * The signal name string (with or without "SIG" prefix) or signal number. * * @param {Function} callback * The callback function to be invoked when the specified Unix signal is caught. * * @returns {?module:uloop.signal} * Returns a signal instance representing the installed signal handler. * Returns `null` when the signal or callback arguments are invalid. * * @example * // Create a signal instance for handling SIGINT * const mySignal = uloop.signal("SIGINT", () => { * printf("SIGINT caught!\n"); * }); */ 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); ok_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)) /** * @typedef * @name Event Mode Constants * @description * The `ULOOP_*` constants are passed as bitwise OR-ed number to the * {@link module:uloop.handle#handle|handle()} function to specify the IO * events that should be monitored on the given handle. * @property {number} ULOOP_READ - File or socket is readable. * @property {number} ULOOP_WRITE - File or socket is writable. * @property {number} ULOOP_EDGE_TRIGGER - Enable edge-triggered event mode. * @property {number} ULOOP_BLOCKING - Do not make descriptor non-blocking. */ 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); } }