[openwrt/openwrt] ucode: add patches that make it easier to deal with non-blocking fds
LEDE Commits
lede-commits at lists.infradead.org
Thu Oct 9 00:58:17 PDT 2025
nbd pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/ad6df8a3c8e88c6d52a2b52cd2b39d543a890b26
commit ad6df8a3c8e88c6d52a2b52cd2b39d543a890b26
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Thu Oct 9 09:56:10 2025 +0200
ucode: add patches that make it easier to deal with non-blocking fds
This allows creating pipes for subprocesses to use as stdin/out/err
and polling them from a uloop process.
Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
...op-add-optional-setup-callback-to-process.patch | 85 +++++++++++++
.../ucode/patches/120-fs-add-dup2-function.patch | 75 ++++++++++++
...add-read_nb-method-for-non-blocking-reads.patch | 133 +++++++++++++++++++++
3 files changed, 293 insertions(+)
diff --git a/package/utils/ucode/patches/111-uloop-add-optional-setup-callback-to-process.patch b/package/utils/ucode/patches/111-uloop-add-optional-setup-callback-to-process.patch
new file mode 100644
index 0000000000..cbde295190
--- /dev/null
+++ b/package/utils/ucode/patches/111-uloop-add-optional-setup-callback-to-process.patch
@@ -0,0 +1,85 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 8 Oct 2025 22:06:46 +0200
+Subject: [PATCH] uloop: add optional setup callback to process()
+
+Add optional setup callback as 5th argument to uloop.process() that is
+invoked in the child process after fork() but before exec().
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/lib/uloop.c
++++ b/lib/uloop.c
+@@ -961,8 +961,9 @@ uc_uloop_process_cb(struct uloop_process
+ *
+ * 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.
++ * vector, an optional dictionary describing environment variables, a
++ * callback function to be invoked when the invoked process ends, and an optional
++ * setup callback to be invoked in the child process after fork().
+ *
+ * @function module:uloop#process
+ *
+@@ -979,6 +980,11 @@ uc_uloop_process_cb(struct uloop_process
+ * @param {Function} callback
+ * The callback function to be invoked when the invoked process ends.
+ *
++ * @param {Function} [setup]
++ * Optional. A callback function to be invoked in the child process after fork()
++ * but before exec(). This can be used to set up file descriptors, change working
++ * directory, or perform other initialization.
++ *
+ * @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.
+@@ -988,6 +994,16 @@ uc_uloop_process_cb(struct uloop_process
+ * const myProcess = uloop.process("/bin/ls", ["-l", "/tmp"], null, (code) => {
+ * printf(`Process exited with code ${code}\n`);
+ * });
++ *
++ * // With setup callback to redirect stderr
++ * const myProcess = uloop.process("/bin/ls", ["-l", "/tmp"], null, (code) => {
++ * printf(`Process exited with code ${code}\n`);
++ * }, () => {
++ * const fs = require('fs');
++ * const errlog = fs.open('/tmp/error.log', 'w');
++ * fs.dup2(errlog.fileno(), 2);
++ * errlog.close();
++ * });
+ */
+ static uc_value_t *
+ uc_uloop_process(uc_vm_t *vm, size_t nargs)
+@@ -996,6 +1012,7 @@ uc_uloop_process(uc_vm_t *vm, size_t nar
+ 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_value_t *setup_cb = uc_fn_arg(4);
+ uc_uloop_process_t *process;
+ uc_stringbuf_t *buf;
+ char **argp, **envp;
+@@ -1005,7 +1022,8 @@ uc_uloop_process(uc_vm_t *vm, size_t nar
+ if (ucv_type(executable) != UC_STRING ||
+ (arguments && ucv_type(arguments) != UC_ARRAY) ||
+ (env_arg && ucv_type(env_arg) != UC_OBJECT) ||
+- !ucv_is_callable(callback)) {
++ !ucv_is_callable(callback) ||
++ (setup_cb && !ucv_is_callable(setup_cb))) {
+ err_return(EINVAL);
+ }
+
+@@ -1015,6 +1033,13 @@ uc_uloop_process(uc_vm_t *vm, size_t nar
+ err_return(errno);
+
+ if (pid == 0) {
++ if (setup_cb) {
++ uc_vm_stack_push(vm, ucv_get(setup_cb));
++
++ if (uc_uloop_vm_call(vm, false, 0))
++ ucv_put(uc_vm_stack_pop(vm));
++ }
++
+ argp = calloc(ucv_array_length(arguments) + 2, sizeof(char *));
+ envp = environ;
+
diff --git a/package/utils/ucode/patches/120-fs-add-dup2-function.patch b/package/utils/ucode/patches/120-fs-add-dup2-function.patch
new file mode 100644
index 0000000000..e3097f136c
--- /dev/null
+++ b/package/utils/ucode/patches/120-fs-add-dup2-function.patch
@@ -0,0 +1,75 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 8 Oct 2025 22:15:42 +0200
+Subject: [PATCH] fs: add dup2() function
+
+Add dup2() function to duplicate file descriptors, useful for redirecting
+standard streams in child processes.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/lib/fs.c
++++ b/lib/fs.c
+@@ -1278,6 +1278,54 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
+ return ucv_resource_create(vm, "fs.file", fp);
+ }
+
++/**
++ * Duplicates a file descriptor.
++ *
++ * This function duplicates the file descriptor `oldfd` to `newfd`. If `newfd`
++ * was previously open, it is silently closed before being reused.
++ *
++ * Returns `true` on success.
++ * Returns `null` on error.
++ *
++ * @function module:fs#dup2
++ *
++ * @param {number} oldfd
++ * The file descriptor to duplicate.
++ *
++ * @param {number} newfd
++ * The file descriptor number to duplicate to.
++ *
++ * @returns {?boolean}
++ *
++ * @example
++ * // Redirect stderr to a log file
++ * const logfile = open('/tmp/error.log', 'w');
++ * dup2(logfile.fileno(), 2);
++ * logfile.close();
++ */
++static uc_value_t *
++uc_fs_dup2(uc_vm_t *vm, size_t nargs)
++{
++ uc_value_t *oldfd_arg = uc_fn_arg(0);
++ uc_value_t *newfd_arg = uc_fn_arg(1);
++ int oldfd, newfd;
++
++ oldfd = get_fd(vm, oldfd_arg);
++
++ if (oldfd == -1)
++ err_return(errno ? errno : EBADF);
++
++ newfd = get_fd(vm, newfd_arg);
++
++ if (newfd == -1)
++ err_return(errno ? errno : EBADF);
++
++ if (dup2(oldfd, newfd) == -1)
++ err_return(errno);
++
++ return ucv_boolean_new(true);
++}
++
+
+ /**
+ * Represents a handle for interacting with a directory opened by `opendir()`.
+@@ -2890,6 +2938,7 @@ static const uc_function_list_t global_f
+ { "error", uc_fs_error },
+ { "open", uc_fs_open },
+ { "fdopen", uc_fs_fdopen },
++ { "dup2", uc_fs_dup2 },
+ { "opendir", uc_fs_opendir },
+ { "popen", uc_fs_popen },
+ { "readlink", uc_fs_readlink },
diff --git a/package/utils/ucode/patches/121-fs-add-read_nb-method-for-non-blocking-reads.patch b/package/utils/ucode/patches/121-fs-add-read_nb-method-for-non-blocking-reads.patch
new file mode 100644
index 0000000000..8566ad158b
--- /dev/null
+++ b/package/utils/ucode/patches/121-fs-add-read_nb-method-for-non-blocking-reads.patch
@@ -0,0 +1,133 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Wed, 8 Oct 2025 23:03:05 +0200
+Subject: [PATCH] fs: add read_nb() method for non-blocking reads
+
+Add file handle method for reading from non-blocking file descriptors.
+Designed for use with uloop, bypasses stdio buffering, handles EAGAIN/EINTR.
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/lib/fs.c
++++ b/lib/fs.c
+@@ -674,6 +674,112 @@ uc_fs_read(uc_vm_t *vm, size_t nargs)
+ }
+
+ /**
++ * Reads data from a non-blocking file descriptor.
++ *
++ * This function is designed for use with uloop file descriptor monitoring.
++ * When called from within a uloop handle callback (after ULOOP_READ event),
++ * it reads available data from the non-blocking file descriptor.
++ *
++ * Performs a single read() operation directly on the file descriptor,
++ * bypassing stdio buffering. Properly handles EAGAIN and EINTR errors.
++ *
++ * Returns a string containing the data read, up to the specified limit.
++ *
++ * Returns an empty string if no data is available (EAGAIN/EWOULDBLOCK).
++ *
++ * Returns `null` if an error occurred.
++ *
++ * @function module:fs.file#read_nb
++ *
++ * @param {number} [limit=4096]
++ * Maximum number of bytes to read. Defaults to 4096 if not specified.
++ *
++ * @returns {?string}
++ *
++ * @example
++ * import * as uloop from 'uloop';
++ * import { fdopen } from 'fs';
++ *
++ * uloop.init();
++ *
++ * let sock = connect_socket(...);
++ * let fp = fdopen(sock, "r");
++ *
++ * uloop.handle(fp, (events) => {
++ * if (events & uloop.ULOOP_READ) {
++ * let data = fp.read_nb();
++ * if (data === null) {
++ * print("Error reading\n");
++ * } else if (length(data) > 0) {
++ * print("Received: ", data, "\n");
++ * }
++ * }
++ * }, uloop.ULOOP_READ);
++ *
++ * uloop.run();
++ */
++static uc_value_t *
++uc_fs_read_nb(uc_vm_t *vm, size_t nargs)
++{
++ uc_value_t *limit_val = uc_fn_arg(0);
++ FILE **fp = uc_fn_this("fs.file");
++ char *buf = NULL;
++ ssize_t n_read;
++ size_t limit = 4096;
++ int fd;
++
++ if (!fp || !*fp)
++ err_return(EBADF);
++
++ if (limit_val) {
++ int64_t limit_arg;
++
++ if (ucv_type(limit_val) != UC_INTEGER)
++ err_return(EINVAL);
++
++ limit_arg = ucv_int64_get(limit_val);
++
++ if (limit_arg <= 0)
++ return NULL;
++
++ limit = (size_t)limit_arg;
++ }
++
++ fd = fileno(*fp);
++
++ if (fd == -1)
++ err_return(errno);
++
++ buf = malloc(limit);
++
++ if (!buf)
++ err_return(ENOMEM);
++
++ while (true) {
++ n_read = read(fd, buf, limit);
++
++ if (n_read >= 0)
++ break;
++
++ if (errno == EINTR)
++ continue;
++
++ if (errno == EAGAIN || errno == EWOULDBLOCK) {
++ free(buf);
++ return ucv_string_new_length("", 0);
++ }
++
++ free(buf);
++ err_return(errno);
++ }
++
++ uc_value_t *rv = ucv_string_new_length(buf, (size_t)n_read);
++ free(buf);
++
++ return rv;
++}
++
++/**
+ * Writes a chunk of data to the file handle.
+ *
+ * In case the given data is not a string, it is converted to a string before
+@@ -2910,6 +3016,7 @@ static const uc_function_list_t proc_fns
+
+ static const uc_function_list_t file_fns[] = {
+ { "read", uc_fs_read },
++ { "read_nb", uc_fs_read_nb },
+ { "write", uc_fs_write },
+ { "seek", uc_fs_seek },
+ { "tell", uc_fs_tell },
More information about the lede-commits
mailing list