/*
* Copyright (C) 2022 Jo-Philipp Wich <jo@mein.io>
*
* 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 <errno.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <libubox/uloop.h>
#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. <socket 0x5> or <fs.proc …>
*/
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<string, *>} [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);
}
}