#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sys_aio.c,v 1.52 2025/10/10 17:08:01 kre Exp $");
#ifdef _KERNEL_OPT
#include "opt_ddb.h"
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/bitops.h>
#include <sys/hash.h>
#include <sys/uio.h>
#include <sys/atomic.h>
#include <sys/buf.h>
#include <sys/condvar.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/lwp.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/kthread.h>
#include <sys/pool.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/sdt.h>
#include <sys/signal.h>
#include <sys/signalvar.h>
#include <sys/syscall.h>
#include <sys/syscallargs.h>
#include <sys/syscallvar.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/vnode.h>
#include <uvm/uvm_extern.h>
MODULE(MODULE_CLASS_MISC, aio, NULL);
u_int aio_listio_max = AIO_LISTIO_MAX;
static u_int aio_max = AIO_MAX;
static u_int aio_jobs_count;
static struct pool aio_job_pool;
static struct pool aio_lio_pool;
static void * aio_ehook;
static int aio_enqueue_job(int, void *, struct lio_req *);
static void aio_exit(proc_t *, void *);
static int sysctl_aio_listio_max(SYSCTLFN_PROTO);
static int sysctl_aio_max(SYSCTLFN_PROTO);
static int aiost_create(struct aiosp *, struct aiost **);
static int aiost_terminate(struct aiost *);
static void aiost_entry(void *);
static void aiost_sigsend(struct proc *, struct sigevent *);
static int aiosp_worker_extract(struct aiosp *, struct aiost **);
static int io_write(struct aio_job *);
static int io_read(struct aio_job *);
static int io_sync(struct aio_job *);
static int uio_construct(struct aio_job *, struct file **,
struct iovec *, struct uio *);
static int io_write_fallback(struct aio_job *);
static int io_read_fallback(struct aio_job *);
static void aio_job_fini(struct aio_job *);
static void aio_job_mark_complete(struct aio_job *);
static void aio_file_hold(struct file *);
static void aio_file_release(struct file *);
static void aiocbp_destroy(struct aiosp *);
static int aiocbp_init(struct aiosp *, u_int);
static int aiocbp_insert(struct aiosp *, struct aiocbp *);
static int aiocbp_lookup_job(struct aiosp *, const void *,
struct aio_job **);
static int aiocbp_remove_job(struct aiosp *, const void *,
struct aio_job **, struct aiocbp **);
static const struct syscall_package aio_syscalls[] = {
{ SYS_aio_cancel, 0, (sy_call_t *)sys_aio_cancel },
{ SYS_aio_error, 0, (sy_call_t *)sys_aio_error },
{ SYS_aio_fsync, 0, (sy_call_t *)sys_aio_fsync },
{ SYS_aio_read, 0, (sy_call_t *)sys_aio_read },
{ SYS_aio_return, 0, (sy_call_t *)sys_aio_return },
{ SYS___aio_suspend50, 0, (sy_call_t *)sys___aio_suspend50 },
{ SYS_aio_write, 0, (sy_call_t *)sys_aio_write },
{ SYS_lio_listio, 0, (sy_call_t *)sys_lio_listio },
{ 0, 0, NULL },
};
static int
aiost_file_group_cmp(struct aiost_file_group *a, struct aiost_file_group *b)
{
if (a == NULL || b == NULL) {
return (a == b) ? 0 : (a ? 1 : -1);
}
uintptr_t ap = (uintptr_t)a->fp;
uintptr_t bp = (uintptr_t)b->fp;
return (ap < bp) ? -1 : (ap > bp) ? 1 : 0;
}
RB_HEAD(aiost_file_tree, aiost_file_group);
RB_PROTOTYPE(aiost_file_tree, aiost_file_group, tree, aiost_file_group_cmp);
RB_GENERATE(aiost_file_tree, aiost_file_group, tree, aiost_file_group_cmp);
static int
aio_fini(bool interface)
{
int error;
proc_t *p;
if (interface) {
error = syscall_disestablish(NULL, aio_syscalls);
if (error != 0)
return error;
mutex_enter(&proc_lock);
PROCLIST_FOREACH(p, &allproc) {
if (p->p_aio != NULL)
break;
}
mutex_exit(&proc_lock);
if (p != NULL) {
error = syscall_establish(NULL, aio_syscalls);
KASSERT(error == 0);
return SET_ERROR(EBUSY);
}
}
KASSERT(aio_jobs_count == 0);
exithook_disestablish(aio_ehook);
pool_destroy(&aio_job_pool);
pool_destroy(&aio_lio_pool);
return 0;
}
static int
aio_init(void)
{
int error;
pool_init(&aio_job_pool, sizeof(struct aio_job), 0, 0, 0,
"aio_jobs_pool", &pool_allocator_nointr, IPL_NONE);
pool_init(&aio_lio_pool, sizeof(struct lio_req), 0, 0, 0,
"aio_lio_pool", &pool_allocator_nointr, IPL_NONE);
aio_ehook = exithook_establish(aio_exit, NULL);
error = syscall_establish(NULL, aio_syscalls);
if (error != 0) {
aio_fini(false);
}
return error;
}
static int
aio_modcmd(modcmd_t cmd, void *arg)
{
switch (cmd) {
case MODULE_CMD_INIT:
return aio_init();
case MODULE_CMD_FINI:
return aio_fini(true);
default:
return SET_ERROR(ENOTTY);
}
}
static int
aio_procinit(struct proc *p)
{
struct aioproc *aio;
int error;
aio = kmem_zalloc(sizeof(*aio), KM_SLEEP);
error = aiosp_initialize(&aio->aiosp);
if (error) {
kmem_free(aio, sizeof(*aio));
return error;
}
error = aiocbp_init(&aio->aiosp, 256);
if (error) {
aiosp_destroy(&aio->aiosp, NULL);
kmem_free(aio, sizeof(*aio));
return error;
}
mutex_init(&aio->aio_mtx, MUTEX_DEFAULT, IPL_NONE);
mutex_enter(p->p_lock);
if (p->p_aio) {
mutex_exit(p->p_lock);
aio_exit(p, aio);
return 0;
}
p->p_aio = aio;
mutex_exit(p->p_lock);
return 0;
}
static void
aio_exit(struct proc *p, void *cookie)
{
struct aioproc *aio;
if (cookie != NULL) {
aio = cookie;
} else if ((aio = p->p_aio) == NULL) {
return;
}
aiocbp_destroy(&aio->aiosp);
aiosp_destroy(&aio->aiosp, NULL);
mutex_destroy(&aio->aio_mtx);
kmem_free(aio, sizeof(*aio));
}
static void
aio_job_fini(struct aio_job *job)
{
mutex_enter(&job->mtx);
aiowaitgrouplk_fini(&job->lk);
mutex_exit(&job->mtx);
mutex_destroy(&job->mtx);
}
static void
aio_job_mark_complete(struct aio_job *job)
{
mutex_enter(&job->mtx);
job->completed = true;
aio_file_release(job->fp);
job->fp = NULL;
aiowaitgrouplk_flush(&job->lk);
mutex_exit(&job->mtx);
aiost_sigsend(job->p, &job->aiocbp.aio_sigevent);
}
static void
aio_file_hold(struct file *fp)
{
mutex_enter(&fp->f_lock);
fp->f_count++;
mutex_exit(&fp->f_lock);
}
static void
aio_file_release(struct file *fp)
{
mutex_enter(&fp->f_lock);
fp->f_count--;
if (!fp->f_count) {
mutex_exit(&fp->f_lock);
closef(fp);
return;
}
mutex_exit(&fp->f_lock);
}
static inline void
aio_job_release(struct aio_job *job)
{
if (job->fp) {
aio_file_release(job->fp);
job->fp = NULL;
}
aio_job_fini(job);
pool_put(&aio_job_pool, job);
atomic_dec_uint(&aio_jobs_count);
}
static inline void
aio_job_cancel(struct aiosp *aiosp, struct aio_job *job)
{
mutex_enter(&job->mtx);
TAILQ_REMOVE(&aiosp->jobs, job, list);
aiosp->jobs_pending--;
job->on_queue = false;
job->aiocbp._errno = ECANCELED;
mutex_exit(&job->mtx);
}
static inline void
aiosp_fg_teardown_locked(struct aiosp *sp, struct aiost_file_group *fg)
{
if (fg == NULL) {
return;
}
RB_REMOVE(aiost_file_tree, sp->fg_root, fg);
mutex_destroy(&fg->mtx);
kmem_free(fg, sizeof(*fg));
}
static inline void
aiosp_fg_teardown(struct aiosp *sp, struct aiost_file_group *fg)
{
if (fg == NULL) {
return;
}
mutex_enter(&sp->mtx);
aiosp_fg_teardown_locked(sp, fg);
mutex_exit(&sp->mtx);
}
int
aiosp_distribute_jobs(struct aiosp *sp)
{
struct aio_job *job, *tmp;
struct file *fp;
int error = 0;
mutex_enter(&sp->mtx);
if (!sp->jobs_pending) {
mutex_exit(&sp->mtx);
return 0;
}
TAILQ_FOREACH_SAFE(job, &sp->jobs, list, tmp) {
fp = job->fp;
KASSERT(fp);
struct aiost_file_group *fg = NULL;
struct aiost *aiost = NULL;
if (fp->f_vnode != NULL && fp->f_vnode->v_type == VREG) {
struct aiost_file_group key = { .fp = fp };
fg = RB_FIND(aiost_file_tree, sp->fg_root, &key);
if (fg == NULL) {
fg = kmem_zalloc(sizeof(*fg), KM_SLEEP);
fg->fp = fp;
fg->queue_size = 0;
mutex_init(&fg->mtx, MUTEX_DEFAULT, IPL_NONE);
TAILQ_INIT(&fg->queue);
error = aiosp_worker_extract(sp, &aiost);
if (error) {
kmem_free(fg, sizeof(*fg));
mutex_exit(&sp->mtx);
return error;
}
RB_INSERT(aiost_file_tree, sp->fg_root, fg);
fg->aiost = aiost;
aiost->fg = fg;
aiost->job = NULL;
} else {
aiost = fg->aiost;
}
} else {
error = aiosp_worker_extract(sp, &aiost);
if (error) {
mutex_exit(&sp->mtx);
return error;
}
aiost->fg = NULL;
aiost->job = job;
}
TAILQ_REMOVE(&sp->jobs, job, list);
sp->jobs_pending--;
job->on_queue = false;
if (fg) {
mutex_enter(&fg->mtx);
TAILQ_INSERT_TAIL(&fg->queue, job, list);
fg->queue_size++;
mutex_exit(&fg->mtx);
}
mutex_enter(&aiost->mtx);
aiost->freelist = false;
aiost->state = AIOST_STATE_OPERATION;
mutex_exit(&aiost->mtx);
cv_signal(&aiost->service_cv);
}
mutex_exit(&sp->mtx);
return error;
}
int
aiosp_suspend(struct aiosp *aiosp, struct aiocb **aiocbp_list, int nent,
struct timespec *ts, int flags)
{
struct aio_job *job;
struct aiowaitgroup *wg;
int error = 0, timo = 0;
size_t joined = 0;
if (ts) {
timo = tstohz(ts);
if (timo <= 0) {
error = SET_ERROR(EAGAIN);
return error;
}
}
wg = kmem_zalloc(sizeof(*wg), KM_SLEEP);
aiowaitgroup_init(wg);
for (int i = 0; i < nent; i++) {
if (aiocbp_list[i] == NULL) {
continue;
}
error = aiocbp_lookup_job(aiosp, aiocbp_list[i], &job);
if (error) {
goto done;
}
if (job == NULL) {
continue;
}
if (job->completed) {
mutex_enter(&wg->mtx);
wg->completed++;
wg->total++;
mutex_exit(&wg->mtx);
mutex_exit(&job->mtx);
continue;
}
aiowaitgroup_join(wg, &job->lk);
joined++;
mutex_exit(&job->mtx);
}
if (!joined) {
goto done;
}
mutex_enter(&wg->mtx);
const size_t target = (flags & AIOSP_SUSPEND_ANY) ? 1 : wg->total;
while (wg->completed < target) {
error = aiowaitgroup_wait(wg, timo);
if (error) {
break;
}
}
mutex_exit(&wg->mtx);
done:
mutex_enter(&wg->mtx);
wg->active = false;
if (--wg->refcnt == 0) {
mutex_exit(&wg->mtx);
aiowaitgroup_fini(wg);
} else {
mutex_exit(&wg->mtx);
}
return error;
}
int
aio_suspend1(struct lwp *l, struct aiocb **aiocbp_list, int nent,
struct timespec *ts)
{
struct proc *p = l->l_proc;
struct aioproc *aio = p->p_aio;
struct aiosp *aiosp = &aio->aiosp;
return aiosp_suspend(aiosp, aiocbp_list, nent, ts, AIOSP_SUSPEND_ANY);
}
int
aiosp_initialize(struct aiosp *sp)
{
mutex_init(&sp->mtx, MUTEX_DEFAULT, IPL_NONE);
TAILQ_INIT(&sp->freelist);
TAILQ_INIT(&sp->active);
TAILQ_INIT(&sp->jobs);
sp->fg_root = kmem_zalloc(sizeof(*sp->fg_root), KM_SLEEP);
RB_INIT(sp->fg_root);
return 0;
}
static int
aiosp_worker_extract(struct aiosp *sp, struct aiost **aiost)
{
int error;
if (sp->nthreads_free == 0) {
error = aiost_create(sp, aiost);
if (error) {
return error;
}
} else {
*aiost = TAILQ_LAST(&sp->freelist, aiost_list);
}
TAILQ_REMOVE(&sp->freelist, *aiost, list);
sp->nthreads_free--;
TAILQ_INSERT_TAIL(&sp->active, *aiost, list);
sp->nthreads_active++;
return 0;
}
int
aiosp_destroy(struct aiosp *sp, int *cn)
{
struct aiost *st;
int error, cnt = 0;
for (;;) {
mutex_enter(&sp->mtx);
st = TAILQ_FIRST(&sp->freelist);
if (st == NULL) {
st = TAILQ_FIRST(&sp->active);
}
mutex_exit(&sp->mtx);
if (st == NULL)
break;
error = aiost_terminate(st);
if (error) {
return error;
}
st->lwp = NULL;
kmem_free(st, sizeof(*st));
cnt++;
}
if (cn) {
*cn = cnt;
}
mutex_destroy(&sp->mtx);
return 0;
}
int
aiosp_enqueue_job(struct aiosp *aiosp, struct aio_job *job)
{
mutex_enter(&aiosp->mtx);
TAILQ_INSERT_TAIL(&aiosp->jobs, job, list);
aiosp->jobs_pending++;
job->on_queue = true;
mutex_exit(&aiosp->mtx);
return 0;
}
static int
aiost_create(struct aiosp *sp, struct aiost **ret)
{
struct proc *p = curlwp->l_proc;
struct aiost *st;
st = kmem_zalloc(sizeof(*st), KM_SLEEP);
mutex_init(&st->mtx, MUTEX_DEFAULT, IPL_NONE);
cv_init(&st->service_cv, "aioservice");
st->job = NULL;
st->state = AIOST_STATE_NONE;
st->aiosp = sp;
st->freelist = true;
TAILQ_INSERT_TAIL(&sp->freelist, st, list);
sp->nthreads_free++;
sp->nthreads_total++;
int error = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN | KTHREAD_TS,
NULL, aiost_entry, st, &st->lwp, "aio_%d_%zu", p->p_pid,
sp->nthreads_total);
if (error) {
return error;
}
if (ret) {
*ret = st;
}
return 0;
}
static void
aiost_process_singleton(struct aio_job *job)
{
if ((job->aio_op & AIO_READ) == AIO_READ) {
io_read(job);
} else if ((job->aio_op & AIO_WRITE) == AIO_WRITE) {
io_write(job);
} else if ((job->aio_op & AIO_SYNC) == AIO_SYNC) {
io_sync(job);
} else {
panic("%s: invalid operation code {%x}n", __func__,
job->aio_op);
}
aio_job_mark_complete(job);
}
static void
aiost_process_fg(struct aiosp *sp, struct aiost_file_group *fg)
{
for (struct aio_job *job;;) {
mutex_enter(&fg->mtx);
job = TAILQ_FIRST(&fg->queue);
if (job) {
TAILQ_REMOVE(&fg->queue, job, list);
fg->queue_size--;
}
mutex_exit(&fg->mtx);
if (job == NULL) {
break;
}
aiost_process_singleton(job);
}
}
static void
aiost_entry(void *arg)
{
struct aiost *st = arg;
struct aiosp *sp = st->aiosp;
int error;
mutex_enter(&st->mtx);
for(;;) {
for (; st->state == AIOST_STATE_NONE;) {
error = cv_wait_sig(&st->service_cv, &st->mtx);
if (error) {
mutex_exit(&st->mtx);
lwp_userret(curlwp);
mutex_enter(&st->mtx);
}
}
if (st->state == AIOST_STATE_TERMINATE) {
break;
}
if (st->state != AIOST_STATE_OPERATION) {
panic("aio_process: invalid aiost state {%x}\n",
st->state);
}
if (st->fg) {
struct aiost_file_group *fg = st->fg;
st->fg = NULL;
mutex_exit(&st->mtx);
aiost_process_fg(sp, fg);
mutex_enter(&st->mtx);
aiosp_fg_teardown(sp, fg);
} else if (st->job) {
struct aio_job *job = st->job;
mutex_exit(&st->mtx);
aiost_process_singleton(job);
mutex_enter(&st->mtx);
} else {
KASSERT(0);
}
if (st->state == AIOST_STATE_TERMINATE) {
break;
}
st->state = AIOST_STATE_NONE;
st->job = NULL;
st->fg = NULL;
mutex_exit(&st->mtx);
mutex_enter(&sp->mtx);
mutex_enter(&st->mtx);
st->freelist = true;
TAILQ_REMOVE(&sp->active, st, list);
sp->nthreads_active--;
TAILQ_INSERT_TAIL(&sp->freelist, st, list);
sp->nthreads_free++;
mutex_exit(&sp->mtx);
}
if (st->job) {
aio_job_release(st->job);
} else if (st->fg) {
struct aiost_file_group *fg = st->fg;
st->fg = NULL;
for (struct aio_job *job;;) {
mutex_enter(&fg->mtx);
job = TAILQ_FIRST(&fg->queue);
if (job) {
TAILQ_REMOVE(&fg->queue, job, list);
fg->queue_size--;
}
mutex_exit(&fg->mtx);
if (job == NULL) {
break;
}
aio_job_release(job);
}
aiosp_fg_teardown(sp, fg);
}
mutex_exit(&st->mtx);
mutex_enter(&sp->mtx);
if (st->freelist) {
TAILQ_REMOVE(&sp->freelist, st, list);
sp->nthreads_free--;
} else {
TAILQ_REMOVE(&sp->active, st, list);
sp->nthreads_active--;
}
sp->nthreads_total--;
mutex_exit(&sp->mtx);
kthread_exit(0);
}
static void
aiost_sigsend(struct proc *p, struct sigevent *sig)
{
ksiginfo_t ksi;
if (sig->sigev_signo == 0 || sig->sigev_notify == SIGEV_NONE)
return;
KSI_INIT(&ksi);
ksi.ksi_signo = sig->sigev_signo;
ksi.ksi_code = SI_ASYNCIO;
ksi.ksi_value = sig->sigev_value;
mutex_enter(&proc_lock);
kpsignal(p, &ksi, NULL);
mutex_exit(&proc_lock);
}
static int
io_write(struct aio_job *job)
{
return io_write_fallback(job);
}
static int
io_read(struct aio_job *job)
{
return io_read_fallback(job);
}
static int
uio_construct(struct aio_job *job, struct file **fp, struct iovec *aiov,
struct uio *auio)
{
struct aiocb *aiocbp = &job->aiocbp;
if (aiocbp->aio_nbytes > SSIZE_MAX)
return SET_ERROR(EINVAL);
*fp = job->fp;
if (*fp == NULL) {
return SET_ERROR(EBADF);
}
aiov->iov_base = aiocbp->aio_buf;
aiov->iov_len = aiocbp->aio_nbytes;
auio->uio_iov = aiov;
auio->uio_iovcnt = 1;
auio->uio_resid = aiocbp->aio_nbytes;
auio->uio_offset = aiocbp->aio_offset;
auio->uio_vmspace = job->p->p_vmspace;
return 0;
}
static int
io_write_fallback(struct aio_job *job)
{
struct file *fp = NULL;
struct iovec aiov;
struct uio auio;
struct aiocb *aiocbp = &job->aiocbp;
int error;
error = uio_construct(job, &fp, &aiov, &auio);
if (error) {
goto done;
}
if ((fp->f_flag & FWRITE) == 0) {
error = SET_ERROR(EBADF);
goto done;
}
auio.uio_rw = UIO_WRITE;
error = (*fp->f_ops->fo_write)(fp, &aiocbp->aio_offset,
&auio, fp->f_cred, FOF_UPDATE_OFFSET);
job->aiocbp.aio_nbytes -= auio.uio_resid;
job->aiocbp._retval = (error == 0) ? job->aiocbp.aio_nbytes : -1;
done:
job->aiocbp._errno = error;
job->aiocbp._state = JOB_DONE;
return 0;
}
static int
io_read_fallback(struct aio_job *job)
{
struct file *fp = NULL;
struct iovec aiov;
struct uio auio;
struct aiocb *aiocbp = &job->aiocbp;
int error;
error = uio_construct(job, &fp, &aiov, &auio);
if (error)
goto done;
if ((fp->f_flag & FREAD) == 0) {
error = SET_ERROR(EBADF);
goto done;
}
auio.uio_rw = UIO_READ;
error = (*fp->f_ops->fo_read)(fp, &aiocbp->aio_offset,
&auio, fp->f_cred, FOF_UPDATE_OFFSET);
job->aiocbp.aio_nbytes -= auio.uio_resid;
job->aiocbp._retval = (error == 0) ? job->aiocbp.aio_nbytes : -1;
done:
job->aiocbp._errno = error;
job->aiocbp._state = JOB_DONE;
return 0;
}
static int
io_sync(struct aio_job *job)
{
struct file *fp = job->fp;
int error = 0;
if (fp == NULL) {
error = SET_ERROR(EBADF);
goto done;
}
if ((fp->f_flag & FWRITE) == 0) {
error = SET_ERROR(EBADF);
goto done;
}
struct vnode *vp = fp->f_vnode;
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
if (vp->v_type == VREG) {
if (job->aio_op & AIO_DSYNC) {
error = VOP_FSYNC(vp, fp->f_cred,
FSYNC_WAIT | FSYNC_DATAONLY, 0, 0);
} else {
error = VOP_FSYNC(vp, fp->f_cred, FSYNC_WAIT, 0, 0);
}
}
VOP_UNLOCK(vp);
job->aiocbp._retval = (error == 0) ? 0 : -1;
done:
job->aiocbp._errno = error;
job->aiocbp._state = JOB_DONE;
copyout(&job->aiocbp, job->aiocb_uptr, sizeof(job->aiocbp));
return 0;
}
static int
aiost_terminate(struct aiost *st)
{
int error = 0;
mutex_enter(&st->mtx);
st->state = AIOST_STATE_TERMINATE;
mutex_exit(&st->mtx);
cv_signal(&st->service_cv);
kthread_join(st->lwp);
cv_destroy(&st->service_cv);
mutex_destroy(&st->mtx);
return error;
}
int
aiosp_validate_conflicts(struct aiosp *aiosp, const void *uptr)
{
struct aiost *st;
struct aio_job *job;
mutex_enter(&aiosp->mtx);
TAILQ_FOREACH(st, &aiosp->active, list) {
job = st->job;
if (job && st->job->aiocb_uptr == uptr) {
mutex_exit(&aiosp->mtx);
return EINVAL;
} else if (st->fg) {
mutex_enter(&st->fg->mtx);
TAILQ_FOREACH(job, &st->fg->queue, list) {
if (job->aiocb_uptr == uptr) {
mutex_exit(&st->fg->mtx);
mutex_exit(&aiosp->mtx);
return EINVAL;
}
}
mutex_exit(&st->fg->mtx);
}
}
mutex_exit(&aiosp->mtx);
return 0;
}
int
aiosp_error(struct aiosp *aiosp, const void *uptr, register_t *retval)
{
struct aio_job *job;
int error = 0;
error = aiocbp_lookup_job(aiosp, uptr, &job);
if (error || job == NULL) {
return error;
}
if (job->aiocbp._state == JOB_NONE) {
mutex_exit(&job->mtx);
return SET_ERROR(EINVAL);
}
*retval = job->aiocbp._errno;
mutex_exit(&job->mtx);
return error;
}
int
aiosp_return(struct aiosp *aiosp, const void *uptr, register_t *retval)
{
struct aiocbp *handle = NULL;
struct aio_job *job = NULL;
int error;
error = aiocbp_remove_job(aiosp, uptr, &job, &handle);
if (error) {
return error;
}
if (job == NULL) {
if (handle) {
kmem_free(handle, sizeof(*handle));
}
return SET_ERROR(ENOENT);
}
if (job->aiocbp._state != JOB_DONE) {
mutex_exit(&job->mtx);
if (handle) {
kmem_free(handle, sizeof(*handle));
}
return SET_ERROR(EINVAL);
}
*retval = job->aiocbp._retval;
if (job->fp) {
aio_file_release(job->fp);
job->fp = NULL;
}
job->aiocbp._errno = 0;
job->aiocbp._retval = -1;
job->aiocbp._state = JOB_NONE;
mutex_exit(&job->mtx);
if (handle) {
kmem_free(handle, sizeof(*handle));
}
aio_job_fini(job);
pool_put(&aio_job_pool, job);
atomic_dec_uint(&aio_jobs_count);
return 0;
}
static inline u_int
aiocbp_hash(const void *uptr)
{
return hash32_buf(&uptr, sizeof(uptr), HASH32_BUF_INIT);
}
static int
aiocbp_lookup_job(struct aiosp *aiosp, const void *uptr,
struct aio_job **jobp)
{
struct aiocbp *aiocbp;
struct aio_job *job = NULL;
u_int hash;
*jobp = NULL;
hash = aiocbp_hash(uptr) & aiosp->aio_hash_mask;
mutex_enter(&aiosp->aio_hash_mtx);
TAILQ_FOREACH(aiocbp, &aiosp->aio_hash[hash], list) {
if (aiocbp->uptr == uptr) {
job = aiocbp->job;
if (job) {
mutex_enter(&job->mtx);
}
mutex_exit(&aiosp->aio_hash_mtx);
*jobp = job;
return 0;
}
}
mutex_exit(&aiosp->aio_hash_mtx);
*jobp = NULL;
return SET_ERROR(ENOENT);
}
static int
aiocbp_remove_job(struct aiosp *aiosp, const void *uptr,
struct aio_job **jobp, struct aiocbp **handlep)
{
struct aiocbp *aiocbp;
struct aio_job *job = NULL;
u_int hash;
*jobp = NULL;
if (handlep) {
*handlep = NULL;
}
hash = aiocbp_hash(uptr) & aiosp->aio_hash_mask;
mutex_enter(&aiosp->aio_hash_mtx);
TAILQ_FOREACH(aiocbp, &aiosp->aio_hash[hash], list) {
if (aiocbp->uptr == uptr) {
job = aiocbp->job;
if (job) {
mutex_enter(&job->mtx);
}
TAILQ_REMOVE(&aiosp->aio_hash[hash], aiocbp, list);
mutex_exit(&aiosp->aio_hash_mtx);
if (handlep) {
*handlep = aiocbp;
}
*jobp = job;
return 0;
}
}
mutex_exit(&aiosp->aio_hash_mtx);
return SET_ERROR(ENOENT);
}
int
aiocbp_insert(struct aiosp *aiosp, struct aiocbp *aiocbp)
{
struct aiocbp *found;
const void *uptr;
u_int hash;
uptr = aiocbp->uptr;
hash = aiocbp_hash(uptr) & aiosp->aio_hash_mask;
mutex_enter(&aiosp->aio_hash_mtx);
TAILQ_FOREACH(found, &aiosp->aio_hash[hash], list) {
if (found->uptr == uptr) {
found->job = aiocbp->job;
mutex_exit(&aiosp->aio_hash_mtx);
return EEXIST;
}
}
TAILQ_INSERT_HEAD(&aiosp->aio_hash[hash], aiocbp, list);
mutex_exit(&aiosp->aio_hash_mtx);
return 0;
}
int
aiocbp_init(struct aiosp *aiosp, u_int hashsize)
{
if (!powerof2(hashsize)) {
return EINVAL;
}
aiosp->aio_hash = kmem_zalloc(hashsize * sizeof(*aiosp->aio_hash),
KM_SLEEP);
aiosp->aio_hash_mask = hashsize - 1;
aiosp->aio_hash_size = hashsize;
mutex_init(&aiosp->aio_hash_mtx, MUTEX_DEFAULT, IPL_NONE);
for (size_t i = 0; i < hashsize; i++) {
TAILQ_INIT(&aiosp->aio_hash[i]);
}
return 0;
}
void
aiocbp_destroy(struct aiosp *aiosp)
{
if (aiosp->aio_hash == NULL) {
return;
}
struct aiocbp *aiocbp;
mutex_enter(&aiosp->aio_hash_mtx);
for (size_t i = 0; i < aiosp->aio_hash_size; i++) {
struct aiocbp *tmp;
TAILQ_FOREACH_SAFE(aiocbp, &aiosp->aio_hash[i], list, tmp) {
TAILQ_REMOVE(&aiosp->aio_hash[i], aiocbp, list);
kmem_free(aiocbp, sizeof(*aiocbp));
}
}
mutex_exit(&aiosp->aio_hash_mtx);
kmem_free(aiosp->aio_hash,
aiosp->aio_hash_size * sizeof(*aiosp->aio_hash));
aiosp->aio_hash = NULL;
aiosp->aio_hash_mask = 0;
aiosp->aio_hash_size = 0;
mutex_destroy(&aiosp->aio_hash_mtx);
}
void
aiowaitgroup_init(struct aiowaitgroup *wg)
{
wg->completed = 0;
wg->total = 0;
wg->refcnt = 1;
wg->active = true;
cv_init(&wg->done_cv, "aiodone");
mutex_init(&wg->mtx, MUTEX_DEFAULT, IPL_NONE);
}
void
aiowaitgroup_fini(struct aiowaitgroup *wg)
{
cv_destroy(&wg->done_cv);
mutex_destroy(&wg->mtx);
kmem_free(wg, sizeof(*wg));
}
int
aiowaitgroup_wait(struct aiowaitgroup *wg, int timo)
{
int error;
error = cv_timedwait_sig(&wg->done_cv, &wg->mtx, timo);
if (error) {
if (error == EWOULDBLOCK) {
error = SET_ERROR(EAGAIN);
}
return error;
}
return 0;
}
void
aiowaitgrouplk_init(struct aiowaitgrouplk *lk)
{
mutex_init(&lk->mtx, MUTEX_DEFAULT, IPL_NONE);
lk->n = 0;
lk->s = 2;
lk->wgs = kmem_alloc(sizeof(*lk->wgs) * lk->s, KM_SLEEP);
}
void
aiowaitgrouplk_fini(struct aiowaitgrouplk *lk)
{
mutex_enter(&lk->mtx);
for (size_t i = 0; i < lk->n; i++) {
struct aiowaitgroup *wg = lk->wgs[i];
if (!wg) {
continue;
}
lk->wgs[i] = NULL;
mutex_enter(&wg->mtx);
if (--wg->refcnt == 0) {
mutex_exit(&wg->mtx);
aiowaitgroup_fini(wg);
} else {
mutex_exit(&wg->mtx);
}
}
if (lk->wgs) {
kmem_free(lk->wgs, lk->s * sizeof(*lk->wgs));
}
lk->wgs = NULL;
lk->n = 0;
lk->s = 0;
mutex_exit(&lk->mtx);
mutex_destroy(&lk->mtx);
}
void
aiowaitgrouplk_flush(struct aiowaitgrouplk *lk)
{
mutex_enter(&lk->mtx);
for (int i = 0; i < lk->n; i++) {
struct aiowaitgroup *wg = lk->wgs[i];
if (wg == NULL) {
continue;
}
mutex_enter(&wg->mtx);
if (wg->active) {
wg->completed++;
cv_signal(&wg->done_cv);
}
if (--wg->refcnt == 0) {
mutex_exit(&wg->mtx);
aiowaitgroup_fini(wg);
} else {
mutex_exit(&wg->mtx);
}
}
if (lk->n) {
kmem_free(lk->wgs, sizeof(*lk->wgs) * lk->s);
lk->n = 0;
lk->s = 2;
lk->wgs = kmem_alloc(sizeof(*lk->wgs) * lk->s, KM_SLEEP);
}
mutex_exit(&lk->mtx);
}
void
aiowaitgroup_join(struct aiowaitgroup *wg, struct aiowaitgrouplk *lk)
{
mutex_enter(&lk->mtx);
if (lk->n == lk->s) {
size_t new_size = lk->s * lk->s;
void **new_wgs = kmem_zalloc(new_size *
sizeof(*new_wgs), KM_SLEEP);
memcpy(new_wgs, lk->wgs, lk->n * sizeof(*lk->wgs));
kmem_free(lk->wgs, lk->s * sizeof(*lk->wgs));
lk->s = new_size;
lk->wgs = new_wgs;
}
lk->wgs[lk->n] = wg;
lk->n++;
wg->total++;
wg->refcnt++;
mutex_exit(&lk->mtx);
}
static int
aio_enqueue_job(int op, void *aiocb_uptr, struct lio_req *lio)
{
struct proc *p = curlwp->l_proc;
struct aioproc *aio;
struct aio_job *a_job;
struct aiocb aiocb;
struct sigevent *sig;
int error;
error = copyin(aiocb_uptr, &aiocb, sizeof(aiocb));
if (error) {
return error;
}
sig = &aiocb.aio_sigevent;
if (sig->sigev_signo < 0 || sig->sigev_signo >= NSIG ||
sig->sigev_notify < SIGEV_NONE || sig->sigev_notify > SIGEV_SA) {
return SET_ERROR(EINVAL);
}
if (((AIO_SYNC | AIO_DSYNC) & op) == 0)
if (aiocb.aio_buf == NULL || aiocb.aio_nbytes > SSIZE_MAX)
return SET_ERROR(EINVAL);
if (op == AIO_LIO) {
KASSERT(lio != NULL);
if (aiocb.aio_lio_opcode == LIO_WRITE) {
op = AIO_WRITE;
} else if (aiocb.aio_lio_opcode == LIO_READ) {
op = AIO_READ;
} else {
if (aiocb.aio_lio_opcode == LIO_NOP) {
return 0;
} else {
return SET_ERROR(EINVAL);
}
}
} else {
KASSERT(lio == NULL);
}
aio = p->p_aio;
if (aio) {
error = aiosp_validate_conflicts(&aio->aiosp, aiocb_uptr);
if (error) {
return SET_ERROR(error);
}
}
if (p->p_aio == NULL) {
if (aio_procinit(p)) {
return SET_ERROR(EAGAIN);
}
}
aio = p->p_aio;
aiocb._state = JOB_WIP;
aiocb._errno = SET_ERROR(EINPROGRESS);
aiocb._retval = -1;
error = copyout(&aiocb, aiocb_uptr, sizeof(aiocb));
if (error) {
return error;
}
a_job = pool_get(&aio_job_pool, PR_WAITOK | PR_ZERO);
memcpy(&a_job->aiocbp, &aiocb, sizeof(aiocb));
a_job->aiocb_uptr = aiocb_uptr;
a_job->aio_op |= op;
a_job->lio = lio;
mutex_init(&a_job->mtx, MUTEX_DEFAULT, IPL_NONE);
aiowaitgrouplk_init(&a_job->lk);
a_job->p = p;
a_job->on_queue = false;
a_job->completed = false;
a_job->fp = NULL;
const int fd = aiocb.aio_fildes;
struct file *fp = fd_getfile2(p, fd);
if (fp == NULL) {
aio_job_fini(a_job);
pool_put(&aio_job_pool, a_job);
return SET_ERROR(EBADF);
}
aio_file_hold(fp);
a_job->fp = fp;
struct aiocbp *aiocbp = kmem_zalloc(sizeof(*aiocbp), KM_SLEEP);
aiocbp->job = a_job;
aiocbp->uptr = aiocb_uptr;
error = aiocbp_insert(&aio->aiosp, aiocbp);
if (error) {
aio_file_release(a_job->fp);
a_job->fp = NULL;
kmem_free(aiocbp, sizeof(*aiocbp));
aio_job_fini(a_job);
pool_put(&aio_job_pool, a_job);
return SET_ERROR(error);
}
mutex_enter(&aio->aio_mtx);
if (atomic_inc_uint_nv(&aio_jobs_count) > aio_max ||
aio->jobs_count >= aio_listio_max) {
mutex_exit(&aio->aio_mtx);
error = SET_ERROR(EAGAIN);
goto error;
}
mutex_exit(&aio->aio_mtx);
error = aiosp_enqueue_job(&aio->aiosp, a_job);
if (error) {
error = SET_ERROR(EAGAIN);
goto error;
}
mutex_enter(&aio->aio_mtx);
aio->jobs_count++;
if (lio) {
lio->refcnt++;
}
mutex_exit(&aio->aio_mtx);
return 0;
error:
aiocbp_remove_job(&aio->aiosp, aiocb_uptr, &a_job, NULL);
kmem_free(aiocbp, sizeof(*aiocbp));
aio_file_release(a_job->fp);
a_job->fp = NULL;
aio_job_fini(a_job);
atomic_dec_uint(&aio_jobs_count);
pool_put(&aio_job_pool, a_job);
return SET_ERROR(error);
}
int
sys_aio_cancel(struct lwp *l, const struct sys_aio_cancel_args *uap,
register_t *retval)
{
struct proc *p = l->l_proc;
struct aioproc *aio;
struct aiocb *aiocbp_uptr;
struct filedesc *fdp = p->p_fd;
struct aiosp *aiosp;
struct aio_job *job;
struct file *fp;
struct aiost_file_group find = { 0 }, *fg;
unsigned int fildes, canceled = 0;
bool have_active = false;
fdtab_t *dt;
int error = 0;
fildes = (unsigned int)SCARG(uap, fildes);
dt = atomic_load_consume(&fdp->fd_dt);
if (fildes >= dt->dt_nfiles) {
return SET_ERROR(EBADF);
}
if (dt->dt_ff[fildes] == NULL || dt->dt_ff[fildes]->ff_file == NULL) {
return SET_ERROR(EBADF);
}
fp = dt->dt_ff[fildes]->ff_file;
if (p->p_aio == NULL) {
*retval = AIO_NOTCANCELED;
return 0;
}
aio = p->p_aio;
aiocbp_uptr = (struct aiocb *)SCARG(uap, aiocbp);
aiosp = &aio->aiosp;
mutex_enter(&aio->aio_mtx);
mutex_enter(&aiosp->mtx);
find.fp = fp;
fg = RB_FIND(aiost_file_tree, aiosp->fg_root, &find);
if (fg) {
have_active = fg->queue_size ? true : false;
}
if (aiocbp_uptr) {
error = aiocbp_lookup_job(aiosp, aiocbp_uptr, &job);
if (error || job == NULL) {
*retval = AIO_ALLDONE;
goto finish;
}
if (job->completed) {
*retval = AIO_ALLDONE;
} else {
*retval = AIO_NOTCANCELED;
}
if (job->on_queue) {
aio_job_cancel(aiosp, job);
aio_job_mark_complete(job);
*retval = AIO_CANCELED;
}
mutex_exit(&job->mtx);
} else {
struct aio_job *tmp;
TAILQ_FOREACH_SAFE(job, &aiosp->jobs, list, tmp) {
if (job->aiocbp.aio_fildes == (int)fildes) {
aio_job_cancel(aiosp, job);
aio_job_mark_complete(job);
canceled++;
}
}
if (canceled && !have_active) {
*retval = AIO_CANCELED;
} else if (!canceled) {
*retval = have_active ? AIO_NOTCANCELED : AIO_ALLDONE;
} else {
*retval = AIO_NOTCANCELED;
}
}
finish:
mutex_exit(&aiosp->mtx);
mutex_exit(&aio->aio_mtx);
return 0;
}
int
sys_aio_error(struct lwp *l, const struct sys_aio_error_args *uap,
register_t *retval)
{
struct proc *p = l->l_proc;
struct aioproc *aio = p->p_aio;
if (aio == NULL) {
return SET_ERROR(EINVAL);
}
const void *uptr = SCARG(uap, aiocbp);
return aiosp_error(&aio->aiosp, uptr, retval);
}
int
sys_aio_fsync(struct lwp *l, const struct sys_aio_fsync_args *uap,
register_t *retval)
{
int op = SCARG(uap, op);
if ((op != O_DSYNC) && (op != O_SYNC)) {
return SET_ERROR(EINVAL);
}
op = (op == O_DSYNC) ? AIO_DSYNC : AIO_SYNC;
return aio_enqueue_job(op, SCARG(uap, aiocbp), NULL);
}
int
sys_aio_read(struct lwp *l, const struct sys_aio_read_args *uap,
register_t *retval)
{
int error;
error = aio_enqueue_job(AIO_READ, SCARG(uap, aiocbp), NULL);
if (error) {
return error;
}
struct proc *p = l->l_proc;
struct aioproc *aio = p->p_aio;
KASSERT(aio);
return aiosp_distribute_jobs(&aio->aiosp);
}
int
sys_aio_return(struct lwp *l, const struct sys_aio_return_args *uap,
register_t *retval)
{
struct proc *p = l->l_proc;
struct aioproc *aio = p->p_aio;
if (aio == NULL) {
return SET_ERROR(EINVAL);
}
const void *uptr = SCARG(uap, aiocbp);
return aiosp_return(&aio->aiosp, uptr, retval);
}
int
sys___aio_suspend50(struct lwp *l, const struct sys___aio_suspend50_args *uap,
register_t *retval)
{
struct proc *p = l->l_proc;
struct aioproc *aio = p->p_aio;
struct aiocb **list;
struct timespec ts;
int error, nent;
nent = SCARG(uap, nent);
if (nent <= 0 || nent > aio_listio_max) {
return SET_ERROR(EAGAIN);
}
if (aio == NULL) {
return SET_ERROR(EINVAL);
}
if (SCARG(uap, timeout)) {
error = copyin(SCARG(uap, timeout), &ts,
sizeof(ts));
if (error)
return error;
}
list = kmem_alloc(nent * sizeof(*list), KM_SLEEP);
error = copyin(SCARG(uap, list), list, nent * sizeof(*list));
if (error) {
goto out;
}
error = aiosp_suspend(&aio->aiosp, list, nent, SCARG(uap, timeout) ?
&ts : NULL, AIOSP_SUSPEND_ANY);
out:
kmem_free(list, nent * sizeof(*list));
return error;
}
int
sys_aio_write(struct lwp *l, const struct sys_aio_write_args *uap,
register_t *retval)
{
int error;
error = aio_enqueue_job(AIO_WRITE, SCARG(uap, aiocbp), NULL);
if (error) {
return error;
}
struct proc *p = l->l_proc;
struct aioproc *aio = p->p_aio;
KASSERT(aio);
return aiosp_distribute_jobs(&aio->aiosp);
}
int
sys_lio_listio(struct lwp *l, const struct sys_lio_listio_args *uap,
register_t *retval)
{
struct proc *p = l->l_proc;
struct aioproc *aio;
struct aiocb **aiocbp_list;
struct lio_req *lio;
int i, error = 0, errcnt, mode, nent;
mode = SCARG(uap, mode);
nent = SCARG(uap, nent);
if (nent < 1 || nent > aio_listio_max) {
return SET_ERROR(EINVAL);
}
if (p->p_aio == NULL) {
if (aio_procinit(p)) {
return SET_ERROR(EAGAIN);
}
}
aio = p->p_aio;
lio = pool_get(&aio_lio_pool, PR_WAITOK);
lio->refcnt = 1;
error = 0;
switch (mode) {
case LIO_WAIT:
memset(&lio->sig, 0, sizeof(lio->sig));
break;
case LIO_NOWAIT:
if (SCARG(uap, sig)) {
struct sigevent *sig = &lio->sig;
error = copyin(SCARG(uap, sig), &lio->sig,
sizeof(lio->sig));
if (error == 0 &&
(sig->sigev_signo < 0 ||
sig->sigev_signo >= NSIG ||
sig->sigev_notify < SIGEV_NONE ||
sig->sigev_notify > SIGEV_SA))
error = SET_ERROR(EINVAL);
} else {
memset(&lio->sig, 0, sizeof(lio->sig));
}
break;
default:
error = SET_ERROR(EINVAL);
break;
}
if (error != 0) {
pool_put(&aio_lio_pool, lio);
return error;
}
aiocbp_list = kmem_alloc(nent * sizeof(*aiocbp_list), KM_SLEEP);
error = copyin(SCARG(uap, list), aiocbp_list,
nent * sizeof(*aiocbp_list));
if (error) {
mutex_enter(&aio->aio_mtx);
goto err;
}
errcnt = 0;
for (i = 0; i < nent; i++) {
error = aio_enqueue_job(AIO_LIO, aiocbp_list[i], lio);
if (error) {
errcnt++;
}
}
error = aiosp_distribute_jobs(&aio->aiosp);
if (error) {
goto err;
}
mutex_enter(&aio->aio_mtx);
if (errcnt) {
error = SET_ERROR(EIO);
goto err;
}
if (mode == LIO_WAIT) {
error = aiosp_suspend(&aio->aiosp, aiocbp_list, nent,
NULL, AIOSP_SUSPEND_ALL);
}
err:
if (--lio->refcnt != 0) {
lio = NULL;
}
mutex_exit(&aio->aio_mtx);
if (lio != NULL) {
aiost_sigsend(p, &lio->sig);
pool_put(&aio_lio_pool, lio);
}
kmem_free(aiocbp_list, nent * sizeof(*aiocbp_list));
return error;
}
static int
sysctl_aio_listio_max(SYSCTLFN_ARGS)
{
struct sysctlnode node;
int error, newsize;
node = *rnode;
node.sysctl_data = &newsize;
newsize = aio_listio_max;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL) {
return error;
}
if (newsize < 1 || newsize > aio_max) {
return SET_ERROR(EINVAL);
}
aio_listio_max = newsize;
return 0;
}
static int
sysctl_aio_max(SYSCTLFN_ARGS)
{
struct sysctlnode node;
int error, newsize;
node = *rnode;
node.sysctl_data = &newsize;
newsize = aio_max;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error || newp == NULL) {
return error;
}
if (newsize < 1 || newsize < aio_listio_max) {
return SET_ERROR(EINVAL);
}
aio_max = newsize;
return 0;
}
SYSCTL_SETUP(sysctl_aio_init, "aio sysctl")
{
int rv;
rv = sysctl_createv(clog, 0, NULL, NULL,
CTLFLAG_PERMANENT | CTLFLAG_IMMEDIATE,
CTLTYPE_INT, "posix_aio",
SYSCTL_DESCR("Version of IEEE Std 1003.1 and its "
"Asynchronous I/O option to which the "
"system attempts to conform"),
NULL, _POSIX_ASYNCHRONOUS_IO, NULL, 0,
CTL_KERN, CTL_CREATE, CTL_EOL);
if (rv != 0) {
return;
}
rv = sysctl_createv(clog, 0, NULL, NULL,
CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
CTLTYPE_INT, "aio_listio_max",
SYSCTL_DESCR("Maximum number of asynchronous I/O "
"operations in a single list I/O call"),
sysctl_aio_listio_max, 0, &aio_listio_max, 0,
CTL_KERN, CTL_CREATE, CTL_EOL);
if (rv != 0) {
return;
}
rv = sysctl_createv(clog, 0, NULL, NULL,
CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
CTLTYPE_INT, "aio_max",
SYSCTL_DESCR("Maximum number of asynchronous I/O "
"operations"),
sysctl_aio_max, 0, &aio_max, 0,
CTL_KERN, CTL_CREATE, CTL_EOL);
return;
}
#if defined(DDB)
void
aio_print_jobs(void (*pr)(const char *, ...))
{
struct proc *p = curlwp->l_proc;
struct aioproc *aio;
struct aiosp *sp;
struct aiost *st;
struct aio_job *job;
if (p == NULL) {
(*pr)("AIO: no current process context.\n");
return;
}
aio = p->p_aio;
if (aio == NULL) {
(*pr)("AIO: not initialized (pid=%d).\n", p->p_pid);
return;
}
sp = &aio->aiosp;
(*pr)("AIO: pid=%d\n", p->p_pid);
(*pr)("AIO: global jobs=%u, proc jobs=%u\n", aio_jobs_count,
aio->jobs_count);
(*pr)("AIO: sp{ total_threads=%zu active=%zu free=%zu pending=%zu\n"
" processing=%lu hash_buckets=%zu mask=%#x }\n",
sp->nthreads_total, sp->nthreads_active, sp->nthreads_free,
sp->jobs_pending, (u_long)sp->njobs_processing,
sp->aio_hash_size, sp->aio_hash_mask);
(*pr)("\nqueue (%zu pending):\n", sp->jobs_pending);
TAILQ_FOREACH(job, &sp->jobs, list) {
(*pr)(" op=%d err=%d state=%d uptr=%p completed=%d\n",
job->aio_op, job->aiocbp._errno, job->aiocbp._state,
job->aiocb_uptr, job->completed);
(*pr)(" fd=%d off=%llu buf=%p nbytes=%zu lio=%p\n",
job->aiocbp.aio_fildes,
(unsigned long long)job->aiocbp.aio_offset,
(void *)job->aiocbp.aio_buf,
(size_t)job->aiocbp.aio_nbytes,
job->lio);
}
(*pr)("\nactive threads (%zu):\n", sp->nthreads_active);
TAILQ_FOREACH(st, &sp->active, list) {
(*pr)(" lwp=%p state=%d freelist=%d\n",
st->lwp, st->state, st->freelist ? 1 : 0);
if (st->job) {
struct aio_job *j = st->job;
(*pr)(
" job: op=%d err=%d state=%d uptr=%p\n",
j->aio_op, j->aiocbp._errno,
j->aiocbp._state, j->aiocb_uptr);
(*pr)(
" fd=%d off=%llu buf=%p nbytes=%zu\n",
j->aiocbp.aio_fildes,
(unsigned long long)j->aiocbp.aio_offset,
j->aiocbp.aio_buf,
(size_t)j->aiocbp.aio_nbytes);
}
if (st->fg) {
(*pr)(" file-group: fp=%p qlen=%zu\n",
st->fg->fp, st->fg->queue_size);
}
}
(*pr)("\nfree threads (%zu)\n", sp->nthreads_free);
(*pr)("\naiocbp hash: buckets=%zu\n", sp->aio_hash_size);
if (sp->aio_hash != NULL && sp->aio_hash_size != 0) {
size_t b;
for (b = 0; b < sp->aio_hash_size; b++) {
struct aiocbp *hc;
if (TAILQ_EMPTY(&sp->aio_hash[b])) {
continue;
}
(*pr)(" [%zu]:", b);
TAILQ_FOREACH(hc, &sp->aio_hash[b], list) {
(*pr)(" uptr=%p job=%p", hc->uptr, hc->job);
}
(*pr)("\n");
}
}
}
#endif