[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