From: Felix Fietkau 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 --- --- a/lib/fs.c +++ b/lib/fs.c @@ -675,6 +675,116 @@ 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; + uc_value_t *rv; + 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); + } + + if (!n_read) + return NULL; + + 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 @@ -2991,6 +3101,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 },