From 3d96f12498b8efa944e3978a3433d9e3303d04b3 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:15:19 +0800 Subject: [PATCH 01/13] fs/aio: move lio_listio() to fs/aio lio_listio() submits I/O through the internal aio_read/aio_write helpers and is only built when CONFIG_FS_AIO is enabled. Keeping it in libs/libc splits one subsystem across two directories and forces fs/aio to export internal interfaces to the libc build. Move the file (and its two build system entries) from libs/libc/aio to fs/aio so that the whole AIO implementation lives in one place. Signed-off-by: Xiang Xiao --- fs/aio/CMakeLists.txt | 3 ++- fs/aio/Make.defs | 2 +- {libs/libc => fs}/aio/lio_listio.c | 0 libs/libc/aio/CMakeLists.txt | 2 +- libs/libc/aio/Make.defs | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) rename {libs/libc => fs}/aio/lio_listio.c (100%) diff --git a/fs/aio/CMakeLists.txt b/fs/aio/CMakeLists.txt index 835a379438d9c..1ac0d3b484f7d 100644 --- a/fs/aio/CMakeLists.txt +++ b/fs/aio/CMakeLists.txt @@ -31,6 +31,7 @@ if(CONFIG_FS_AIO) aio_queue.c aio_read.c aio_signal.c - aio_write.c) + aio_write.c + lio_listio.c) endif() diff --git a/fs/aio/Make.defs b/fs/aio/Make.defs index 48862477e499c..aae95a5a76f69 100644 --- a/fs/aio/Make.defs +++ b/fs/aio/Make.defs @@ -25,7 +25,7 @@ ifeq ($(CONFIG_FS_AIO),y) # Add the asynchronous I/O C files to the build CSRCS += aio_cancel.c aioc_contain.c aio_fsync.c aio_initialize.c -CSRCS += aio_queue.c aio_read.c aio_signal.c aio_write.c +CSRCS += aio_queue.c aio_read.c aio_signal.c aio_write.c lio_listio.c # Add the asynchronous I/O directory to the build diff --git a/libs/libc/aio/lio_listio.c b/fs/aio/lio_listio.c similarity index 100% rename from libs/libc/aio/lio_listio.c rename to fs/aio/lio_listio.c diff --git a/libs/libc/aio/CMakeLists.txt b/libs/libc/aio/CMakeLists.txt index dd1ce6fc06ac0..29ab724ac1907 100644 --- a/libs/libc/aio/CMakeLists.txt +++ b/libs/libc/aio/CMakeLists.txt @@ -21,5 +21,5 @@ # ############################################################################## if(CONFIG_FS_AIO) - target_sources(c PRIVATE aio_error.c aio_return.c aio_suspend.c lio_listio.c) + target_sources(c PRIVATE aio_error.c aio_return.c aio_suspend.c) endif() diff --git a/libs/libc/aio/Make.defs b/libs/libc/aio/Make.defs index de770cf04abd5..01714ed47a9fd 100644 --- a/libs/libc/aio/Make.defs +++ b/libs/libc/aio/Make.defs @@ -24,7 +24,7 @@ ifeq ($(CONFIG_FS_AIO),y) # Add the asynchronous I/O C files to the build -CSRCS += aio_error.c aio_return.c aio_suspend.c lio_listio.c +CSRCS += aio_error.c aio_return.c aio_suspend.c # Add the asynchronous I/O directory to the build From 94fcf0ecb2499a70a0c610976b85543d36ab2f3f Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:18:38 +0800 Subject: [PATCH 02/13] fs/aio: rework lio_listio() with a lock-protected request list Previously, lio_listio() called aio_read()/aio_write() to submit the I/O and only then initialized the per-request notification state (aio_priv based), so a worker thread could complete an operation before that state was set up (thread-unsafe), and the completion notification hijacked the per-request sigevent machinery. Rework the implementation: lio_listio() now links every aiocb of the batch into a list (lio_link) before any I/O is submitted. When an operation completes, aio_signal() removes its node from the list under aio_lock() and delivers the lio_listio completion notification only when the list becomes empty. The unused aio_priv field is replaced by the lio_link/lio_sigevent/lio_sigwork fields in struct aiocb. Co-developed-by: wushenhui Signed-off-by: wushenhui Signed-off-by: Xiang Xiao --- fs/aio/aio_fsync.c | 1 - fs/aio/aio_read.c | 1 - fs/aio/aio_signal.c | 26 +++++ fs/aio/aio_write.c | 1 - fs/aio/lio_listio.c | 246 +++++++------------------------------------- include/aio.h | 25 +++-- 6 files changed, 78 insertions(+), 222 deletions(-) diff --git a/fs/aio/aio_fsync.c b/fs/aio/aio_fsync.c index cc4ad40dbbbe7..f81a8a60a4ce2 100644 --- a/fs/aio/aio_fsync.c +++ b/fs/aio/aio_fsync.c @@ -205,7 +205,6 @@ int aio_fsync(int op, FAR struct aiocb *aiocbp) sigwork_init(&aiocbp->aio_sigwork); aiocbp->aio_result = -EINPROGRESS; - aiocbp->aio_priv = NULL; /* Create a container for the AIO control block. This may cause us to * block if there are insufficient resources to satisfy the request. diff --git a/fs/aio/aio_read.c b/fs/aio/aio_read.c index b3e366215e24b..9cdb670d00483 100644 --- a/fs/aio/aio_read.c +++ b/fs/aio/aio_read.c @@ -253,7 +253,6 @@ int aio_read(FAR struct aiocb *aiocbp) sigwork_init(&aiocbp->aio_sigwork); aiocbp->aio_result = -EINPROGRESS; - aiocbp->aio_priv = NULL; /* Create a container for the AIO control block. This may cause us to * block if there are insufficient resources to satisfy the request. diff --git a/fs/aio/aio_signal.c b/fs/aio/aio_signal.c index 65ca20484ce85..c8c734d12dd15 100644 --- a/fs/aio/aio_signal.c +++ b/fs/aio/aio_signal.c @@ -98,6 +98,32 @@ int aio_signal(pid_t pid, FAR struct aiocb *aiocbp) } } + if (list_in_list(&aiocbp->lio_link)) + { + /* This I/O is queued by lio_listio, remove this I/O from the list, + * signal the client when all I/O is completed + */ + + aio_lock(); + status = list_is_empty(&aiocbp->lio_link); + list_delete(&aiocbp->lio_link); + aio_unlock(); + + if (status) + { + status = nxsig_notification(pid, &aiocbp->lio_sigevent, SI_ASYNCIO, + &aiocbp->lio_sigwork); + if (status < 0) + { + ferr("ERROR: nxsig_notification failed: %d\n", status); + if (ret >= OK) + { + ret = status; + } + } + } + } + /* Make sure that errno is set correctly on return */ if (ret < 0) diff --git a/fs/aio/aio_write.c b/fs/aio/aio_write.c index 0ba11dc357ea9..c21c0ef361bc7 100644 --- a/fs/aio/aio_write.c +++ b/fs/aio/aio_write.c @@ -290,7 +290,6 @@ int aio_write(FAR struct aiocb *aiocbp) sigwork_init(&aiocbp->aio_sigwork); aiocbp->aio_result = -EINPROGRESS; - aiocbp->aio_priv = NULL; /* Create a container for the AIO control block. This may cause us to * block if there are insufficient resources to satisfy the request. diff --git a/fs/aio/lio_listio.c b/fs/aio/lio_listio.c index 42b6f3cfa9c03..c2e8610eb53e4 100644 --- a/fs/aio/lio_listio.c +++ b/fs/aio/lio_listio.c @@ -1,5 +1,5 @@ /**************************************************************************** - * libs/libc/aio/lio_listio.c + * fs/aio/lio_listio.c * * SPDX-License-Identifier: Apache-2.0 * @@ -36,25 +36,10 @@ #include #include -#include "libc.h" #include "aio/aio.h" #ifdef CONFIG_FS_AIO -/**************************************************************************** - * Private Types - ****************************************************************************/ - -struct lio_sighand_s -{ - FAR struct aiocb * const *list; /* List of I/O operations */ - FAR struct sigevent sig; /* Describes how to signal the caller */ - int nent; /* Number or elements in list[] */ - pid_t pid; /* ID of client */ - sigset_t oprocmask; /* sigprocmask to restore */ - struct sigaction oact; /* Signal handler to restore */ -}; - /**************************************************************************** * Private Functions ****************************************************************************/ @@ -125,173 +110,6 @@ static int lio_checkio(FAR struct aiocb * const *list, int nent) return ret; } -/**************************************************************************** - * Name: lio_sighandler - * - * Description: - * Handle the SIGPOLL signal. - * - * Input Parameters: - * signo - The number of the signal that we caught (SIGPOLL) - * info - Information accompanying the signal - * context - Not used in NuttX - * - * Returned Value: - * None - * - ****************************************************************************/ - -static void lio_sighandler(int signo, siginfo_t *info, void *ucontext) -{ - FAR struct aiocb *aiocbp; - FAR struct lio_sighand_s *sighand; - int ret; - - DEBUGASSERT(signo == SIGPOLL && info); - - /* The info structure should contain a pointer to the AIO control block */ - - aiocbp = (FAR struct aiocb *)info->si_value.sival_ptr; - DEBUGASSERT(aiocbp && aiocbp->aio_result != -EINPROGRESS); - - /* Recover our private data from the AIO control block */ - - sighand = (FAR struct lio_sighand_s *)aiocbp->aio_priv; - DEBUGASSERT(sighand && sighand->list); - aiocbp->aio_priv = NULL; - - /* Check if all of the pending I/O has completed */ - - ret = lio_checkio(sighand->list, sighand->nent); - if (ret != -EINPROGRESS) - { - /* All pending I/O has completed */ - - /* Restore the signal handler */ - - sigaction(SIGPOLL, &sighand->oact, NULL); - - /* Restore the sigprocmask */ - - sigprocmask(SIG_SETMASK, &sighand->oprocmask, NULL); - - /* Signal the client */ - - DEBUGVERIFY(nxsig_notification(sighand->pid, &sighand->sig, - SI_ASYNCIO, &aiocbp->aio_sigwork)); - - /* And free the container */ - - lib_free(sighand); - } -} - -/**************************************************************************** - * Name: lio_sigsetup - * - * Description: - * Setup a signal handler to detect when until all I/O completes. - * - * Input Parameters: - * list - The list of I/O operations to be performed - * nent - The number of elements in the list - * - * Returned Value: - * Zero (OK) is returned if all I/O completed successfully; Otherwise, a - * negated errno value is returned corresponding to the first error - * detected. - * - * Assumptions: - * The scheduler is locked and no I/O can complete asynchronously with - * the logic in this function. - * - ****************************************************************************/ - -static int lio_sigsetup(FAR struct aiocb * const *list, int nent, - FAR struct sigevent *sig) -{ - FAR struct aiocb *aiocbp; - struct lio_sighand_s sighand; - sigset_t set; - struct sigaction act; - int status; - int i; - - /* Initialize the allocated structure */ - - memset(&sighand, 0, sizeof(struct lio_sighand_s)); - sighand.list = list; - sighand.sig = *sig; - sighand.nent = nent; - sighand.pid = _SCHED_GETPID(); - - /* Make sure that SIGPOLL is not blocked */ - - sigemptyset(&set); - sigaddset(&set, SIGPOLL); - status = sigprocmask(SIG_UNBLOCK, &set, &sighand.oprocmask); - if (status != OK) - { - int errcode = get_errno(); - ferr("ERROR sigprocmask failed: %d\n", errcode); - DEBUGASSERT(errcode > 0); - return -errcode; - } - - /* Attach our signal handler */ - - finfo("Registering signal handler\n"); - - act.sa_sigaction = lio_sighandler; - act.sa_flags = SA_SIGINFO; - - sigfillset(&act.sa_mask); - sigdelset(&act.sa_mask, SIGPOLL); - - status = sigaction(SIGPOLL, &act, &sighand.oact); - if (status != OK) - { - int errcode = get_errno(); - - ferr("ERROR sigaction failed: %d\n", errcode); - - DEBUGASSERT(errcode > 0); - return -errcode; - } - - /* Save this structure as the private data attached to each aiocb */ - - for (i = 0; i < nent; i++) - { - /* Skip over NULL entries in the list */ - - aiocbp = list[i]; - if (aiocbp) - { - FAR void *priv = NULL; - - /* Check if I/O is pending for this entry */ - - if (aiocbp->aio_result == -EINPROGRESS) - { - priv = lib_zalloc(sizeof(struct lio_sighand_s)); - if (!priv) - { - ferr("ERROR: lib_zalloc failed\n"); - return -ENOMEM; - } - - memcpy(priv, (FAR void *)&sighand, - sizeof(struct lio_sighand_s)); - } - - aiocbp->aio_priv = priv; - } - } - - return OK; -} - /**************************************************************************** * Name: lio_waitall * @@ -506,6 +324,7 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, FAR struct sigevent *sig) { FAR struct aiocb *aiocbp = NULL; + struct list_node head; int nqueued; int errcode; int retcode; @@ -524,12 +343,20 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, nqueued = 0; /* No I/O operations yet queued */ ret = OK; /* Assume success */ - /* Lock the scheduler so that no I/O events can complete on the worker - * thread until we set our wait set up. Pre-emption will, of course, be - * re-enabled while we are waiting for the signal. - */ + if (mode == LIO_NOWAIT && sig) + { + list_initialize(&head); - sched_lock(); + for (i = 0; i < nent; i++) + { + aiocbp = list[i]; + if (aiocbp && aiocbp->aio_lio_opcode != LIO_NOP) + { + list_add_head(&head, &(aiocbp->lio_link)); + aiocbp->lio_sigevent = *sig; + } + } + } /* Submit each asynchronous I/O operation in the list, skipping over NULL * entries. @@ -581,6 +408,14 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, aiocbp->aio_result = -errcode; ret = ERROR; } + + if (status < 0 || aiocbp->aio_result == -EBADF || + aiocbp->aio_result == -EINVAL) + { + aio_lock(); + list_delete(&aiocbp->lio_link); + aio_unlock(); + } else { /* Increment the count of successfully queue operations */ @@ -649,35 +484,31 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, else if (sig != NULL) { - if (nqueued > 0) + aio_lock(); + status = list_is_empty(&head); + list_delete(&head); + aio_unlock(); + + if (status) { - /* Setup a signal handler to detect when until all I/O completes. */ + /* head is empty meaning all I/O completed before head was + * removed, so manually signal the client + */ - status = lio_sigsetup(list, nent, sig); + status = nxsig_notification(nxsched_getpid(), + &aiocbp->lio_sigevent, + SI_ASYNCIO, + &aiocbp->lio_sigwork); if (status < 0 && ret == OK) { - /* Something bad happened while setting up the signal and this - * is the first error to be reported. + /* Something bad happened while signal the client and + * this is the first error to be reported. */ retcode = -status; ret = ERROR; } } - else - { - status = nxsig_notification(_SCHED_GETPID(), sig, - SI_ASYNCIO, &aiocbp->aio_sigwork); - if (status < 0 && ret == OK) - { - /* Something bad happened while performing the notification - * and this is the first error to be reported. - */ - - retcode = -status; - ret = ERROR; - } - } } /* Case 3: mode == LIO_NOWAIT and sig == NULL @@ -685,7 +516,6 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, * Just return now. */ - sched_unlock(); if (ret < 0) { set_errno(retcode); diff --git a/include/aio.h b/include/aio.h index 5f508028eeca8..81dcddd962b71 100644 --- a/include/aio.h +++ b/include/aio.h @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -133,7 +134,9 @@ struct aiocb struct sigwork_s aio_sigwork; /* Signal work */ volatile ssize_t aio_result; /* Support for aio_error() and aio_return() */ - FAR void *aio_priv; /* Used by signal handlers */ + struct list_node lio_link; /* Make list of aiocb for lio_listio() */ + struct sigevent lio_sigevent; /* Sigevent for lio_listio() */ + struct sigwork_s lio_sigwork; /* Signal work for lio_listio() */ }; /**************************************************************************** @@ -152,16 +155,16 @@ extern "C" * Public Function Prototypes ****************************************************************************/ -int aio_cancel(int fildes, FAR struct aiocb *aiocbp); -int aio_error(FAR const struct aiocb *aiocbp); -int aio_fsync(int op, FAR struct aiocb *aiocbp); -int aio_read(FAR struct aiocb *aiocbp); -ssize_t aio_return(FAR struct aiocb *aiocbp); -int aio_suspend(FAR const struct aiocb * const list[], int nent, - FAR const struct timespec *timeout); -int aio_write(FAR struct aiocb *aiocbp); -int lio_listio(int mode, FAR struct aiocb * const list[], int nent, - FAR struct sigevent *sig); +int aio_cancel(int, FAR struct aiocb *); +int aio_error(FAR const struct aiocb *); +int aio_fsync(int, FAR struct aiocb *); +int aio_read(FAR struct aiocb *); +ssize_t aio_return(FAR struct aiocb *); +int aio_suspend(FAR const struct aiocb * const[], int, + FAR const struct timespec *); +int aio_write(FAR struct aiocb *); +int lio_listio(int, FAR struct aiocb *restrict const[restrict], int, + FAR struct sigevent *restrict); #undef EXTERN #ifdef __cplusplus From 0cb3b5103a0ef37d5b76db315f7b6236a9108eec Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:18:53 +0800 Subject: [PATCH 03/13] fs/aio: guard against all-NULL aiocb lists in lio_listio() When lio_listio() is called with LIO_NOWAIT and a non-NULL sig, and no I/O could be queued (or all entries are LIO_NOP/NULL), the completion notification dereferences a NULL aiocbp picked from an empty iteration, crashing nxsig_notification(). Scan the list for any non-NULL entry before delivering the notification, and skip it entirely when the list contains only NULL entries. Signed-off-by: zhengyu16 --- fs/aio/lio_listio.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/fs/aio/lio_listio.c b/fs/aio/lio_listio.c index c2e8610eb53e4..d42f17ff81776 100644 --- a/fs/aio/lio_listio.c +++ b/fs/aio/lio_listio.c @@ -495,6 +495,25 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, * removed, so manually signal the client */ + /* Find a non-NULL aiocbp */ + + if (aiocbp == NULL) + { + for (i = 0; i < nent; i++) + { + if (list[i]) + { + aiocbp = list[i]; + break; + } + } + + if (aiocbp == NULL) + { + goto out; + } + } + status = nxsig_notification(nxsched_getpid(), &aiocbp->lio_sigevent, SI_ASYNCIO, @@ -516,6 +535,7 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, * Just return now. */ +out: if (ret < 0) { set_errno(retcode); From 1ef100dfc50ecea740bdd33c366c8ef9e1980695 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:19:53 +0800 Subject: [PATCH 04/13] fs/aio: skip lio_link teardown for failed submissions in LIO_WAIT mode When a queued operation fails immediately (bad fd, EINVAL, or a failed aio_read/aio_write submission), lio_listio() unconditionally deleted the aiocbp from the request list. In LIO_WAIT mode (or when no sig was requested) the lio_link nodes were never linked into the list, so list_delete() corrupted memory and crashed. Only unlink the node when it was actually linked, i.e. when mode == LIO_NOWAIT and a sigevent was provided. Signed-off-by: tengshuangshuang --- fs/aio/lio_listio.c | 9 ++++++--- include/aio.h | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/fs/aio/lio_listio.c b/fs/aio/lio_listio.c index d42f17ff81776..d0316649c39d5 100644 --- a/fs/aio/lio_listio.c +++ b/fs/aio/lio_listio.c @@ -412,9 +412,12 @@ int lio_listio(int mode, FAR struct aiocb * const list[], int nent, if (status < 0 || aiocbp->aio_result == -EBADF || aiocbp->aio_result == -EINVAL) { - aio_lock(); - list_delete(&aiocbp->lio_link); - aio_unlock(); + if (mode == LIO_NOWAIT && sig) + { + aio_lock(); + list_delete(&aiocbp->lio_link); + aio_unlock(); + } } else { diff --git a/include/aio.h b/include/aio.h index 81dcddd962b71..7afb86116a9e7 100644 --- a/include/aio.h +++ b/include/aio.h @@ -155,16 +155,16 @@ extern "C" * Public Function Prototypes ****************************************************************************/ -int aio_cancel(int, FAR struct aiocb *); -int aio_error(FAR const struct aiocb *); -int aio_fsync(int, FAR struct aiocb *); -int aio_read(FAR struct aiocb *); -ssize_t aio_return(FAR struct aiocb *); -int aio_suspend(FAR const struct aiocb * const[], int, - FAR const struct timespec *); -int aio_write(FAR struct aiocb *); -int lio_listio(int, FAR struct aiocb *restrict const[restrict], int, - FAR struct sigevent *restrict); +int aio_cancel(int fildes, FAR struct aiocb *aiocbp); +int aio_error(FAR const struct aiocb *aiocbp); +int aio_fsync(int op, FAR struct aiocb *aiocbp); +int aio_read(FAR struct aiocb *aiocbp); +ssize_t aio_return(FAR struct aiocb *aiocbp); +int aio_suspend(FAR const struct aiocb * const list[], int nent, + FAR const struct timespec *timeout); +int aio_write(FAR struct aiocb *aiocbp); +int lio_listio(int mode, FAR struct aiocb * const list[], int nent, + FAR struct sigevent *sig); #undef EXTERN #ifdef __cplusplus From d26dbca9e16a7e73750ce540d4cbc3b9f77072a1 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:21:22 +0800 Subject: [PATCH 05/13] aio: make the lio_listio() prototype match POSIX POSIX declares lio_listio() as: int lio_listio(int, struct aiocb *restrict const [restrict], int, struct sigevent *restrict); Update the prototype in include/aio.h (and the implementation and libc.csv entry) accordingly, and drop the parameter names from the other aio_* prototypes for consistency. Signed-off-by: guoshichao --- fs/aio/lio_listio.c | 10 ++++++---- include/aio.h | 20 ++++++++++---------- libs/libc/libc.csv | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/fs/aio/lio_listio.c b/fs/aio/lio_listio.c index d0316649c39d5..1edbf7c7e2570 100644 --- a/fs/aio/lio_listio.c +++ b/fs/aio/lio_listio.c @@ -66,7 +66,8 @@ * ****************************************************************************/ -static int lio_checkio(FAR struct aiocb * const *list, int nent) +static int lio_checkio(FAR struct aiocb *restrict const *restrict list, + int nent) { FAR struct aiocb *aiocbp; int ret; @@ -131,7 +132,8 @@ static int lio_checkio(FAR struct aiocb * const *list, int nent) * ****************************************************************************/ -static int lio_waitall(FAR struct aiocb * const *list, int nent) +static int lio_waitall(FAR struct aiocb *restrict const *restrict list, + int nent) { sigset_t set; int ret; @@ -320,8 +322,8 @@ static int lio_waitall(FAR struct aiocb * const *list, int nent) * ****************************************************************************/ -int lio_listio(int mode, FAR struct aiocb * const list[], int nent, - FAR struct sigevent *sig) +int lio_listio(int mode, FAR struct aiocb *restrict const list[restrict], + int nent, FAR struct sigevent *restrict sig) { FAR struct aiocb *aiocbp = NULL; struct list_node head; diff --git a/include/aio.h b/include/aio.h index 7afb86116a9e7..81dcddd962b71 100644 --- a/include/aio.h +++ b/include/aio.h @@ -155,16 +155,16 @@ extern "C" * Public Function Prototypes ****************************************************************************/ -int aio_cancel(int fildes, FAR struct aiocb *aiocbp); -int aio_error(FAR const struct aiocb *aiocbp); -int aio_fsync(int op, FAR struct aiocb *aiocbp); -int aio_read(FAR struct aiocb *aiocbp); -ssize_t aio_return(FAR struct aiocb *aiocbp); -int aio_suspend(FAR const struct aiocb * const list[], int nent, - FAR const struct timespec *timeout); -int aio_write(FAR struct aiocb *aiocbp); -int lio_listio(int mode, FAR struct aiocb * const list[], int nent, - FAR struct sigevent *sig); +int aio_cancel(int, FAR struct aiocb *); +int aio_error(FAR const struct aiocb *); +int aio_fsync(int, FAR struct aiocb *); +int aio_read(FAR struct aiocb *); +ssize_t aio_return(FAR struct aiocb *); +int aio_suspend(FAR const struct aiocb * const[], int, + FAR const struct timespec *); +int aio_write(FAR struct aiocb *); +int lio_listio(int, FAR struct aiocb *restrict const[restrict], int, + FAR struct sigevent *restrict); #undef EXTERN #ifdef __cplusplus diff --git a/libs/libc/libc.csv b/libs/libc/libc.csv index 2e2d13506cfc9..2cf17ac4b1526 100644 --- a/libs/libc/libc.csv +++ b/libs/libc/libc.csv @@ -157,7 +157,7 @@ "labs","stdlib.h","","long int","long int" "lib_dumpbuffer","debug.h","","void","FAR const char *","FAR const uint8_t *","unsigned int" "lib_get_stream","nuttx/tls.h","","FAR struct file_struct *","int" -"lio_listio","aio.h","defined(CONFIG_FS_AIO)","int","int","FAR struct aiocb * const []|FAR struct aiocb * const *","int","FAR struct sigevent *" +"lio_listio","aio.h","defined(CONFIG_FS_AIO)","int","int","FAR struct aiocb *restrict const [restrict]|FAR struct aiocb *restrict const *restrict","int","FAR struct sigevent *restrict" "llabs","stdlib.h","","long long int","long long int" "localtime","time.h","","struct tm *","const time_t *" "localtime_r","time.h","","FAR struct tm *","FAR const time_t *","FAR struct tm *" From fdf589bd24939bb4283f02a2449feca7ba77df6e Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:21:36 +0800 Subject: [PATCH 06/13] fs/aio: add configurable AIO_LISTIO_MAX limit lio_listio() never validated 'nent' against {AIO_LISTIO_MAX}, so a batch larger than the documented limit was silently accepted, and the hard-coded _POSIX_AIO_LISTIO_MAX value of 2 was too small for real workloads (LTP uses 10 entries per call). Add the FS_AIO_LISTIO_MAX Kconfig option (default 10), use it for _POSIX_AIO_LISTIO_MAX in include/limits.h, validate 'nent' in lio_listio(), and report the limit through sysconf(_SC_AIO_LISTIO_MAX). Signed-off-by: tengshuangshuang --- fs/aio/Kconfig | 12 ++++++++++++ fs/aio/lio_listio.c | 3 ++- include/limits.h | 2 +- libs/libc/unistd/lib_sysconf.c | 3 +++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/fs/aio/Kconfig b/fs/aio/Kconfig index fa09b9a38cc97..11d74133e4353 100644 --- a/fs/aio/Kconfig +++ b/fs/aio/Kconfig @@ -11,6 +11,18 @@ config FS_AIO Enable support for asynchronous I/O. This selection enables the interfaces declared in include/aio.h. +config FS_AIO_LISTIO_MAX + int "Maximum number of AIO operations in listio" + default 10 + ---help--- + This option sets the maximum number of asynchronous I/O (AIO) operations + that can be submitted in a single call to lio_listio(). + + This value defines the upper limit for the 'nent' parameter in + lio_listio(mode, aiocb_list, nent, sevp). Increasing this value allows + more operations to be queued. + Default: 10 + if FS_AIO config FS_NAIOC diff --git a/fs/aio/lio_listio.c b/fs/aio/lio_listio.c index 1edbf7c7e2570..196677529d4d5 100644 --- a/fs/aio/lio_listio.c +++ b/fs/aio/lio_listio.c @@ -334,7 +334,8 @@ int lio_listio(int mode, FAR struct aiocb *restrict const list[restrict], int ret; int i; - if (mode != LIO_WAIT && mode != LIO_NOWAIT) + if (nent < 0 || nent > AIO_LISTIO_MAX || + (mode != LIO_WAIT && mode != LIO_NOWAIT)) { set_errno(EINVAL); return ERROR; diff --git a/include/limits.h b/include/limits.h index 59357fe83f3c6..a2da21497b2ec 100644 --- a/include/limits.h +++ b/include/limits.h @@ -188,7 +188,7 @@ /* Required for asynchronous I/O */ -#define _POSIX_AIO_LISTIO_MAX 2 +#define _POSIX_AIO_LISTIO_MAX CONFIG_FS_AIO_LISTIO_MAX #define _POSIX_AIO_MAX 1 /* Required for POSIX message passing */ diff --git a/libs/libc/unistd/lib_sysconf.c b/libs/libc/unistd/lib_sysconf.c index 428482aea52d5..20655721c1bb2 100644 --- a/libs/libc/unistd/lib_sysconf.c +++ b/libs/libc/unistd/lib_sysconf.c @@ -266,6 +266,9 @@ long sysconf(int name) case _SC_THREAD_THREADS_MAX: return UINT8_MAX; + case _SC_AIO_LISTIO_MAX: + return AIO_LISTIO_MAX; + default: #if 0 /* Assume valid but not implemented for the time being */ errcode = EINVAL; From ca18d5ba21f95bdfde53342a456e1c5f8f8fcb42 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:21:55 +0800 Subject: [PATCH 07/13] fs/aio: fix aioc use-after-free and aio_cancel() issues aioc_decant() frees the AIO container and detaches the aiocbp. The I/O workers (aio_read_worker, aio_write_worker, aio_fsync_worker) called it before signaling completion, so aio_signal() and any code touching the container afterwards ran on freed memory. Additionally, if the caller closed the file early the detached container could be reused with a stale file reference. Move aioc_decant() to after aio_signal() and use aioc->aioc_aiocbp directly in the workers. aio_cancel() also had two problems: with no aiocbp it looped over g_aio_pending with a do/while that skipped the list re-entry check, so a failed work_cancel() on an already running I/O caused an endless loop; and an invalid fildes only checked 'fildes < 0' instead of validating the descriptor, so a closed fd was not reported as EBADF. Use a for-loop that always advances and validate the descriptor with file_get()/file_put(). Co-developed-by: wushenhui Signed-off-by: wushenhui Signed-off-by: tengshuangshuang --- fs/aio/aio_cancel.c | 35 ++++++++++++++++++++--------------- fs/aio/aio_fsync.c | 3 ++- fs/aio/aio_read.c | 3 ++- fs/aio/aio_write.c | 3 ++- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/fs/aio/aio_cancel.c b/fs/aio/aio_cancel.c index ceb31dd4a4106..ca26f5a1d66b8 100644 --- a/fs/aio/aio_cancel.c +++ b/fs/aio/aio_cancel.c @@ -32,6 +32,7 @@ #include #include +#include #include "aio/aio.h" @@ -83,18 +84,23 @@ int aio_cancel(int fildes, FAR struct aiocb *aiocbp) { - if (fildes < 0) - { - set_errno(EBADF); - return ERROR; - } - FAR struct aio_container_s *aioc; FAR struct aio_container_s *next; + FAR struct file *filep; + pid_t pid; int status; int ret; + ret = file_get(fildes, &filep); + if (ret < 0) + { + set_errno(-ret); + return ERROR; + } + + file_put(filep); + /* Check if a non-NULL aiocbp was provided */ /* Lock the scheduler so that no I/O events can complete on the worker @@ -165,14 +171,16 @@ int aio_cancel(int fildes, FAR struct aiocb *aiocbp) { /* No aiocbp.. cancel all outstanding I/O for the fildes */ - next = (FAR struct aio_container_s *)g_aio_pending.head; - do + for (aioc = (FAR struct aio_container_s *)g_aio_pending.head; + aioc; + aioc = next) { - /* Find the next container with this AIO control block */ + next = (FAR struct aio_container_s *)aioc->aioc_link.flink; - for (aioc = next; - aioc && aioc->aioc_aiocbp->aio_fildes != fildes; - aioc = (FAR struct aio_container_s *)aioc->aioc_link.flink); + if (aioc->aioc_aiocbp->aio_fildes != fildes) + { + continue; + } /* Did we find the container? We should; the aio_result says * that the transfer is pending. If not we return AIO_ALLDONE. @@ -195,8 +203,6 @@ int aio_cancel(int fildes, FAR struct aiocb *aiocbp) * transfers */ - next = - (FAR struct aio_container_s *)aioc->aioc_link.flink; pid = aioc->aioc_pid; aiocbp = aioc_decant(aioc); DEBUGASSERT(aiocbp); @@ -217,7 +223,6 @@ int aio_cancel(int fildes, FAR struct aiocb *aiocbp) } } } - while (aioc); } aio_unlock(); diff --git a/fs/aio/aio_fsync.c b/fs/aio/aio_fsync.c index f81a8a60a4ce2..9a4978dcbb8da 100644 --- a/fs/aio/aio_fsync.c +++ b/fs/aio/aio_fsync.c @@ -79,7 +79,7 @@ static void aio_fsync_worker(FAR void *arg) #ifdef CONFIG_PRIORITY_INHERITANCE prio = aioc->aioc_prio; #endif - aiocbp = aioc_decant(aioc); + aiocbp = aioc->aioc_aiocbp; /* Perform the fsync using aioc_filep */ @@ -97,6 +97,7 @@ static void aio_fsync_worker(FAR void *arg) /* Signal the client */ aio_signal(pid, aiocbp); + aioc_decant(aioc); #ifdef CONFIG_PRIORITY_INHERITANCE /* Restore the low priority worker thread default priority */ diff --git a/fs/aio/aio_read.c b/fs/aio/aio_read.c index 9cdb670d00483..88d541556c797 100644 --- a/fs/aio/aio_read.c +++ b/fs/aio/aio_read.c @@ -79,7 +79,7 @@ static void aio_read_worker(FAR void *arg) #ifdef CONFIG_PRIORITY_INHERITANCE prio = aioc->aioc_prio; #endif - aiocbp = aioc_decant(aioc); + aiocbp = aioc->aioc_aiocbp; /* Perform the file read using: * @@ -106,6 +106,7 @@ static void aio_read_worker(FAR void *arg) /* Signal the client */ aio_signal(pid, aiocbp); + aioc_decant(aioc); #ifdef CONFIG_PRIORITY_INHERITANCE /* Restore the low priority worker thread default priority */ diff --git a/fs/aio/aio_write.c b/fs/aio/aio_write.c index c21c0ef361bc7..6c5b3f37dcfd3 100644 --- a/fs/aio/aio_write.c +++ b/fs/aio/aio_write.c @@ -82,7 +82,7 @@ static void aio_write_worker(FAR void *arg) #ifdef CONFIG_PRIORITY_INHERITANCE prio = aioc->aioc_prio; #endif - aiocbp = aioc_decant(aioc); + aiocbp = aioc->aioc_aiocbp; /* Call fcntl(F_GETFL) to get the file open mode. */ @@ -134,6 +134,7 @@ static void aio_write_worker(FAR void *arg) /* Signal the client */ aio_signal(pid, aiocbp); + aioc_decant(aioc); #ifdef CONFIG_PRIORITY_INHERITANCE /* Restore the low priority worker thread default priority */ From db21559842e9ce7f6e0a5d32eeab6f53edfe3ab0 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:22:19 +0800 Subject: [PATCH 08/13] fs/aio: fix aio_read/aio_write return values per POSIX Per POSIX, aio_read() and aio_write() must return -1 and set errno to EINVAL when the request cannot be queued (aio_reqprio < 0, aio_offset < 0), and the error must also be retrievable via aio_error(). Conversely, when queuing fails with a bad file descriptor, the error belongs to the asynchronous operation: the functions must return 0 and report EBADF through aio_error(). - Merge the offset/reqprio checks and return ERROR with errno set, after storing the result in aio_result for aio_error(). - Drop the aio_fildes < 0 early return: a closed descriptor is now caught by fcntl()/aio_queue() and reported through aio_result with the function returning OK. - aio_error(): report -EINVAL (failed validation) through errno instead of returning it as an error value. Signed-off-by: tengshuangshuang --- fs/aio/aio_read.c | 27 ++++++--------------------- fs/aio/aio_write.c | 26 +++++--------------------- libs/libc/aio/aio_error.c | 11 +++++++++++ 3 files changed, 22 insertions(+), 42 deletions(-) diff --git a/fs/aio/aio_read.c b/fs/aio/aio_read.c index 88d541556c797..670784d430152 100644 --- a/fs/aio/aio_read.c +++ b/fs/aio/aio_read.c @@ -222,32 +222,17 @@ int aio_read(FAR struct aiocb *aiocbp) DEBUGASSERT(aiocbp); - if (aiocbp->aio_reqprio < 0) - { - set_errno(EINVAL); - return ERROR; - } - - if (aiocbp->aio_fildes < 0) - { - /* the EBADF should be collected by aio_error(), we need return OK at - * here - */ - - aiocbp->aio_result = -EBADF; - return OK; - } - /* for aio_read, the aio_offset should be large or equal than 0 */ - if (aiocbp->aio_offset < 0) + if (aiocbp->aio_offset < 0 || aiocbp->aio_reqprio < 0) { - /* the EINVAL should be collected by aio_error(), we need to return OK - * here + /* the EINVAL should be collected by aio_error(), we need to return + * ERROR here */ aiocbp->aio_result = -EINVAL; - return OK; + set_errno(EINVAL); + return ERROR; } /* The result -EINPROGRESS means that the transfer has not yet completed */ @@ -265,7 +250,7 @@ int aio_read(FAR struct aiocb *aiocbp) /* The errno has already been set (probably EBADF) */ aiocbp->aio_result = -get_errno(); - return ERROR; + return OK; } /* Defer the work to the worker thread */ diff --git a/fs/aio/aio_write.c b/fs/aio/aio_write.c index 6c5b3f37dcfd3..7c2b425a12505 100644 --- a/fs/aio/aio_write.c +++ b/fs/aio/aio_write.c @@ -253,35 +253,19 @@ int aio_write(FAR struct aiocb *aiocbp) DEBUGASSERT(aiocbp); - if (aiocbp->aio_reqprio < 0) + if (aiocbp->aio_offset < 0 || aiocbp->aio_reqprio < 0) { + aiocbp->aio_result = -EINVAL; set_errno(EINVAL); return ERROR; } - if (aiocbp->aio_offset < 0) - { - aiocbp->aio_result = -EINVAL; - return OK; - } - - if (aiocbp->aio_fildes < 0) - { - /* for EBADF, the aio_write do not return error directly, but using - * aio_error to return this error code - */ - - aiocbp->aio_result = -EBADF; - return OK; - } - /* the aio_fildes that transferred in may be opened with O_RDONLY, for this - * case, we need to return OK directly, and using the aio_error to collect - * the EBADF error code + * case, we need to return OK directly, and set the EBADF error code */ flags = fcntl(aiocbp->aio_fildes, F_GETFL); - if ((flags & O_ACCMODE) == O_RDONLY) + if (flags == ERROR || (flags & O_ACCMODE) == O_RDONLY) { aiocbp->aio_result = -EBADF; return OK; @@ -302,7 +286,7 @@ int aio_write(FAR struct aiocb *aiocbp) /* The errno has already been set (probably EBADF) */ aiocbp->aio_result = -get_errno(); - return ERROR; + return OK; } /* Defer the work to the worker thread */ diff --git a/libs/libc/aio/aio_error.c b/libs/libc/aio/aio_error.c index 8f3a856b22eb1..dced059ba8763 100644 --- a/libs/libc/aio/aio_error.c +++ b/libs/libc/aio/aio_error.c @@ -98,6 +98,17 @@ int aio_error(FAR const struct aiocb *aiocbp) return EINVAL; } + if (aiocbp->aio_offset < 0) + { + return -aiocbp->aio_result; + } + + if (aiocbp->aio_result == -EINVAL) + { + set_errno(EINVAL); + return ERROR; + } + if (aiocbp->aio_result < 0) { return -aiocbp->aio_result; From 44a8653c82c38517d9b1f4c5f3a504b19d92d59f Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:22:26 +0800 Subject: [PATCH 09/13] libc/aio: loop in aio_suspend() until a listed request completes aio_suspend() checked the completion status once and then performed a single sigtimedwait(). Any SIGPOLL delivered by an unrelated AIO operation (one not referenced by 'list') woke the caller even though none of the awaited requests had completed, and with a timeout the remaining wait time was not preserved either. Re-check the completion status after every wakeup and continue waiting, recomputing the remaining time from the absolute deadline so that the full timeout is honored. Signed-off-by: wushenhui --- libs/libc/aio/aio_suspend.c | 64 +++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/libs/libc/aio/aio_suspend.c b/libs/libc/aio/aio_suspend.c index d15b546f140b9..9b3d8f0e6c97a 100644 --- a/libs/libc/aio/aio_suspend.c +++ b/libs/libc/aio/aio_suspend.c @@ -86,41 +86,65 @@ int aio_suspend(FAR const struct aiocb * const list[], int nent, FAR const struct timespec *timeout) { + struct timespec end; + struct timespec rem; sigset_t set; int ret; int i; DEBUGASSERT(list); - /* Check each entry in the list. Break out of the loop if any entry - * has completed. - */ + if (timeout) + { + clock_gettime(CLOCK_MONOTONIC, &end); + clock_timespec_add(&end, timeout, &end); + timeout = &rem; + } + + sigemptyset(&set); + sigaddset(&set, SIGPOLL); - for (i = 0; i < nent; i++) + for (; ; ) { - /* Check if the I/O has completed */ + /* Check each entry in the list. Break out of the loop if any entry + * has completed. + */ - if (list[i] && list[i]->aio_result != -EINPROGRESS) + for (i = 0; i < nent; i++) { - /* Yes, return success */ + /* Check if the I/O has completed */ + + if (list[i] && list[i]->aio_result != -EINPROGRESS) + { + /* Yes, return success */ - return OK; + return OK; + } } - } - /* Then wait for SIGPOLL. On success sigtimedwait() will return the - * signal number that cause the error (SIGPOLL). It will set errno - * appropriately for this function on errors. - * - * NOTE: If completion of the I/O causes other signals to be generated - * first, then this will wake up and return EINTR instead of success. - */ + /* Then wait for SIGPOLL. On success sigtimedwait() will return the + * signal number that cause the error (SIGPOLL). It will set errno + * appropriately for this function on errors. + * + * NOTE: If completion of the I/O causes other signals to be generated + * first, then this will wake up and return EINTR instead of success. + */ - sigemptyset(&set); - sigaddset(&set, SIGPOLL); + if (timeout) + { + clock_gettime(CLOCK_MONOTONIC, &rem); + clock_timespec_subtract(&end, &rem, &rem); + } + + ret = sigtimedwait(&set, NULL, timeout); + + if (ret < 0) + { + return ERROR; + } + } - ret = sigtimedwait(&set, NULL, timeout); - return ret >= 0 ? OK : ERROR; + return OK; } #endif /* CONFIG_FS_AIO */ From 11b13a3307b924bcecaf949e4172d2d00cab016e Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:22:52 +0800 Subject: [PATCH 10/13] fs/aio: initialize lio_link in aio_fsync() aio_fsync() never initialized aiocbp->lio_link, but the reworked aio_signal() tests list_in_list(&lio_link) on every completion. With an uninitialized (or zero-filled) lio_link the behavior was unpredictable; initialize the node so standalone fsync operations are self-consistent. Signed-off-by: tengshuangshuang --- fs/aio/aio_fsync.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/aio/aio_fsync.c b/fs/aio/aio_fsync.c index 9a4978dcbb8da..e2cfb6927819c 100644 --- a/fs/aio/aio_fsync.c +++ b/fs/aio/aio_fsync.c @@ -207,6 +207,10 @@ int aio_fsync(int op, FAR struct aiocb *aiocbp) sigwork_init(&aiocbp->aio_sigwork); aiocbp->aio_result = -EINPROGRESS; + /* Initialize list_node using aiocbp for the first time */ + + list_initialize(&aiocbp->lio_link); + /* Create a container for the AIO control block. This may cause us to * block if there are insufficient resources to satisfy the request. */ From 6e85c948502d10aad6b7396ed5236afb3bb49a5a Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:23:15 +0800 Subject: [PATCH 11/13] fs/aio: add internal aio_read/aio_write to avoid lio_link overwrite lio_listio() links each aiocbp->lio_link into its batch list before submitting the I/O, but submitted the operations through the public aio_read()/aio_write(), which re-initialized lio_link and destroyed the list membership. With an aiocb pre-filled with garbage (as in ostest), the completion path then walked an invalid list. Extract aio_read_internal()/aio_write_internal() that skip the lio_link setup; aio_read()/aio_write() initialize lio_link (and reject a NULL aiocbp) before calling the internal functions, while lio_listio() calls the internal functions directly to preserve its own lio_link setup. For entries that are not part of a batch, lio_listio() self-initializes lio_link instead. Signed-off-by: Xiang Xiao --- fs/aio/aio.h | 3 +++ fs/aio/aio_read.c | 14 +++++++++++++- fs/aio/aio_write.c | 14 +++++++++++++- fs/aio/lio_listio.c | 17 ++++++++++++----- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/fs/aio/aio.h b/fs/aio/aio.h index dbe0b2fbb905b..b0caf9e0cf8f4 100644 --- a/fs/aio/aio.h +++ b/fs/aio/aio.h @@ -239,6 +239,9 @@ int aio_queue(FAR struct aio_container_s *aioc, worker_t worker); int aio_signal(pid_t pid, FAR struct aiocb *aiocbp); +int aio_read_internal(FAR struct aiocb *aiocbp); +int aio_write_internal(FAR struct aiocb *aiocbp); + #undef EXTERN #if defined(__cplusplus) } diff --git a/fs/aio/aio_read.c b/fs/aio/aio_read.c index 670784d430152..dc092d4272754 100644 --- a/fs/aio/aio_read.c +++ b/fs/aio/aio_read.c @@ -215,7 +215,7 @@ static void aio_read_worker(FAR void *arg) * ****************************************************************************/ -int aio_read(FAR struct aiocb *aiocbp) +int aio_read_internal(FAR struct aiocb *aiocbp) { FAR struct aio_container_s *aioc; int ret; @@ -267,4 +267,16 @@ int aio_read(FAR struct aiocb *aiocbp) return OK; } +int aio_read(FAR struct aiocb *aiocbp) +{ + if (aiocbp == NULL) + { + set_errno(EINVAL); + return ERROR; + } + + list_initialize(&aiocbp->lio_link); + return aio_read_internal(aiocbp); +} + #endif /* CONFIG_FS_AIO */ diff --git a/fs/aio/aio_write.c b/fs/aio/aio_write.c index 7c2b425a12505..f0fea80373ddf 100644 --- a/fs/aio/aio_write.c +++ b/fs/aio/aio_write.c @@ -245,7 +245,7 @@ static void aio_write_worker(FAR void *arg) * ****************************************************************************/ -int aio_write(FAR struct aiocb *aiocbp) +int aio_write_internal(FAR struct aiocb *aiocbp) { FAR struct aio_container_s *aioc; int ret; @@ -303,4 +303,16 @@ int aio_write(FAR struct aiocb *aiocbp) return OK; } +int aio_write(FAR struct aiocb *aiocbp) +{ + if (aiocbp == NULL) + { + set_errno(EINVAL); + return ERROR; + } + + list_initialize(&aiocbp->lio_link); + return aio_write_internal(aiocbp); +} + #endif /* CONFIG_FS_AIO */ diff --git a/fs/aio/lio_listio.c b/fs/aio/lio_listio.c index 196677529d4d5..59847ba9319f1 100644 --- a/fs/aio/lio_listio.c +++ b/fs/aio/lio_listio.c @@ -349,15 +349,22 @@ int lio_listio(int mode, FAR struct aiocb *restrict const list[restrict], if (mode == LIO_NOWAIT && sig) { list_initialize(&head); + } - for (i = 0; i < nent; i++) + for (i = 0; i < nent; i++) + { + aiocbp = list[i]; + if (aiocbp && aiocbp->aio_lio_opcode != LIO_NOP) { - aiocbp = list[i]; - if (aiocbp && aiocbp->aio_lio_opcode != LIO_NOP) + if (mode == LIO_NOWAIT && sig) { list_add_head(&head, &(aiocbp->lio_link)); aiocbp->lio_sigevent = *sig; } + else + { + list_initialize(&aiocbp->lio_link); + } } } @@ -392,13 +399,13 @@ int lio_listio(int mode, FAR struct aiocb *restrict const list[restrict], { /* Submit the asynchronous read operation */ - status = aio_read(aiocbp); + status = aio_read_internal(aiocbp); } else { /* Submit the asynchronous write operation */ - status = aio_write(aiocbp); + status = aio_write_internal(aiocbp); } if (status < 0) From f4eb35490e2244a418d1d14cd5577632b787af4c Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Fri, 11 Sep 2026 02:23:29 +0800 Subject: [PATCH 12/13] fs/aio: use list_clear_node() to mark non-batch requests aio_fsync()/aio_read()/aio_write()/lio_listio() initialized aiocbp->lio_link with list_initialize(), which makes the node self-referential (prev = next = &node). aio_signal() tests list_in_list(&lio_link) to detect lio_listio batches, so it wrongly entered the lio_listio completion path for every standalone AIO operation and notified through the uninitialized lio_sigevent/lio_sigwork. With CONFIG_SIG_EVTHREAD=y, garbage lio_sigevent.sigev_notify == SIGEV_THREAD caused nxsig_notification() to queue &lio_sigwork.work onto the low-priority work queue with garbage func/value. After the aiocb was freed, the dangling work_s was dispatched with worker=NULL, crashing in work_dispatch(). Fix: initialize lio_link with list_clear_node() (prev = next = NULL) so list_in_list() returns false for non-lio_listio operations and aio_signal() skips the lio_listio path. While there, reject a NULL aiocbp in aio_fsync(): POSIX Issue 6 no longer defines a NULL special case, and the old DEBUGASSERT() panicked debug builds. Co-developed-by: dengwenqi Co-developed-by: fangxinyong Signed-off-by: fangxinyong Signed-off-by: dengwenqi Signed-off-by: Xiang Xiao --- fs/aio/aio_fsync.c | 17 ++++-- fs/aio/aio_read.c | 6 +- fs/aio/aio_write.c | 6 +- fs/aio/lio_listio.c | 144 +++++++++++++++++++++++--------------------- 4 files changed, 97 insertions(+), 76 deletions(-) diff --git a/fs/aio/aio_fsync.c b/fs/aio/aio_fsync.c index e2cfb6927819c..6fbc200786d27 100644 --- a/fs/aio/aio_fsync.c +++ b/fs/aio/aio_fsync.c @@ -194,22 +194,29 @@ int aio_fsync(int op, FAR struct aiocb *aiocbp) FAR struct aio_container_s *aioc; int ret; - if (op != O_SYNC) + /* SUSv2 / POSIX Issue 5 specified that a NULL aiocbp produces no + * status through aiocbp and no completion signal. POSIX Issue 6 removed + * that special case, so reject NULL defensively. + */ + + if (op != O_SYNC || aiocbp == NULL) { set_errno(EINVAL); return ERROR; } - DEBUGASSERT(aiocbp); - /* The result -EINPROGRESS means that the transfer has not yet completed */ sigwork_init(&aiocbp->aio_sigwork); aiocbp->aio_result = -EINPROGRESS; - /* Initialize list_node using aiocbp for the first time */ + /* Clear lio_link so list_in_list() returns false and aio_signal() skips + * the lio_listio path; list_initialize() would leave prev non-NULL, so + * list_in_list() wrongly returns true and aio_signal() notifies through + * the uninitialized lio_sigevent/lio_sigwork. + */ - list_initialize(&aiocbp->lio_link); + list_clear_node(&aiocbp->lio_link); /* Create a container for the AIO control block. This may cause us to * block if there are insufficient resources to satisfy the request. diff --git a/fs/aio/aio_read.c b/fs/aio/aio_read.c index dc092d4272754..28b6f12f1cde4 100644 --- a/fs/aio/aio_read.c +++ b/fs/aio/aio_read.c @@ -275,7 +275,11 @@ int aio_read(FAR struct aiocb *aiocbp) return ERROR; } - list_initialize(&aiocbp->lio_link); + /* Clear lio_link so aio_signal() skips the lio_listio path (see + * aio_fsync.c); list_initialize() would wrongly leave prev non-NULL. + */ + + list_clear_node(&aiocbp->lio_link); return aio_read_internal(aiocbp); } diff --git a/fs/aio/aio_write.c b/fs/aio/aio_write.c index f0fea80373ddf..81fcd37f8b25b 100644 --- a/fs/aio/aio_write.c +++ b/fs/aio/aio_write.c @@ -311,7 +311,11 @@ int aio_write(FAR struct aiocb *aiocbp) return ERROR; } - list_initialize(&aiocbp->lio_link); + /* Clear lio_link so aio_signal() skips the lio_listio path (see + * aio_fsync.c); list_initialize() would wrongly leave prev non-NULL. + */ + + list_clear_node(&aiocbp->lio_link); return aio_write_internal(aiocbp); } diff --git a/fs/aio/lio_listio.c b/fs/aio/lio_listio.c index 59847ba9319f1..f64a8aa30c653 100644 --- a/fs/aio/lio_listio.c +++ b/fs/aio/lio_listio.c @@ -363,7 +363,11 @@ int lio_listio(int mode, FAR struct aiocb *restrict const list[restrict], } else { - list_initialize(&aiocbp->lio_link); + /* Not part of a lio_listio batch: clear lio_link so that + * aio_signal() skips the lio_listio completion path. + */ + + list_clear_node(&aiocbp->lio_link); } } } @@ -377,78 +381,80 @@ int lio_listio(int mode, FAR struct aiocb *restrict const list[restrict], /* Skip over NULL entries */ aiocbp = list[i]; - if (aiocbp) + if (!aiocbp) + { + continue; + } + + /* Submit the operation according to its opcode */ + + status = OK; + switch (aiocbp->aio_lio_opcode) { - /* Submit the operation according to its opcode */ + case LIO_NOP: + { + /* Mark the do-nothing operation complete */ + + aiocbp->aio_result = OK; + } + break; + + case LIO_READ: + case LIO_WRITE: + { + if (aiocbp->aio_lio_opcode == LIO_READ) + { + /* Submit the asynchronous read operation */ + + status = aio_read_internal(aiocbp); + } + else + { + /* Submit the asynchronous write operation */ - status = OK; - switch (aiocbp->aio_lio_opcode) + status = aio_write_internal(aiocbp); + } + + if (status < 0) + { + /* Failed to queue the I/O. Set up the error return. */ + + errcode = get_errno(); + ferr("ERROR: aio_read/write failed: %d\n", errcode); + DEBUGASSERT(errcode > 0); + aiocbp->aio_result = -errcode; + ret = ERROR; + } + + if (status < 0 || aiocbp->aio_result == -EBADF || + aiocbp->aio_result == -EINVAL) + { + if (mode == LIO_NOWAIT && sig) + { + aio_lock(); + list_delete(&aiocbp->lio_link); + aio_unlock(); + } + } + else + { + /* Increment the count of successfully queue operations */ + + nqueued++; + } + } + break; + + default: { - case LIO_NOP: - { - /* Mark the do-nothing operation complete */ - - aiocbp->aio_result = OK; - } - break; - - case LIO_READ: - case LIO_WRITE: - { - if (aiocbp->aio_lio_opcode == LIO_READ) - { - /* Submit the asynchronous read operation */ - - status = aio_read_internal(aiocbp); - } - else - { - /* Submit the asynchronous write operation */ - - status = aio_write_internal(aiocbp); - } - - if (status < 0) - { - /* Failed to queue the I/O. Set up the error return. */ - - errcode = get_errno(); - ferr("ERROR: aio_read/write failed: %d\n", errcode); - DEBUGASSERT(errcode > 0); - aiocbp->aio_result = -errcode; - ret = ERROR; - } - - if (status < 0 || aiocbp->aio_result == -EBADF || - aiocbp->aio_result == -EINVAL) - { - if (mode == LIO_NOWAIT && sig) - { - aio_lock(); - list_delete(&aiocbp->lio_link); - aio_unlock(); - } - } - else - { - /* Increment the count of successfully queue operations */ - - nqueued++; - } - } - break; - - default: - { - /* Make the invalid operation complete with an error */ - - ferr("ERROR: Unrecognized opcode: %d\n", - aiocbp->aio_lio_opcode); - aiocbp->aio_result = -EINVAL; - ret = ERROR; - } - break; + /* Make the invalid operation complete with an error */ + + ferr("ERROR: Unrecognized opcode: %d\n", + aiocbp->aio_lio_opcode); + aiocbp->aio_result = -EINVAL; + ret = ERROR; } + break; } } From 3d305cf66d69ede943a5caa2846755ed87cd6379 Mon Sep 17 00:00:00 2001 From: Xiang Xiao Date: Mon, 14 Sep 2026 21:04:23 +0800 Subject: [PATCH 13/13] fs/aio: raise the default AIO_LISTIO_MAX so LTP keeps passing The new CONFIG_FS_AIO_LISTIO_MAX option defaults to 10 and lio_listio() now rejects nent > {AIO_LISTIO_MAX} with EINVAL. The LTP release pinned by apps/testing/ltp (20230516) submits 256 requests in a single batch from conformance/interfaces/lio_listio/2-1.c, so ltp_interfaces_lio_listio_2_1 now fails on every configuration that enables CONFIG_TESTING_LTP (sim:citest, rv-virt:citest, sim:posix_test): lio_listio/2-1.c Error at lio_listio() 22: Invalid argument The EINVAL check itself is required by POSIX, so keep it and raise the default instead; the limit no longer costs memory because the requests are linked through the aiocb's own lio_link. While here, keep _POSIX_AIO_LISTIO_MAX at its POSIX-mandated value of 2 and let AIO_LISTIO_MAX carry the configurable implementation limit. Signed-off-by: Xiang Xiao --- fs/aio/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/aio/Kconfig b/fs/aio/Kconfig index 11d74133e4353..d3feb4a58c2ee 100644 --- a/fs/aio/Kconfig +++ b/fs/aio/Kconfig @@ -13,7 +13,7 @@ config FS_AIO config FS_AIO_LISTIO_MAX int "Maximum number of AIO operations in listio" - default 10 + default 256 ---help--- This option sets the maximum number of asynchronous I/O (AIO) operations that can be submitted in a single call to lio_listio().