#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sys_select.c,v 1.69 2025/11/07 21:43:41 andvar Exp $");
#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/bitops.h>
#include <sys/cpu.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/kernel.h>
#include <sys/lwp.h>
#include <sys/mount.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/signalvar.h>
#include <sys/sleepq.h>
#include <sys/socketvar.h>
#include <sys/socketvar.h>
#include <sys/syncobj.h>
#include <sys/syscallargs.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/uio.h>
#define SEL_RESET 0
#define SEL_SCANNING 1
#define SEL_BLOCKING 2
#define SEL_EVENT 3
#define SELCLUSTERS 64
#define SELCLUSTERMASK (SELCLUSTERS - 1)
typedef struct selcluster {
kmutex_t *sc_lock;
sleepq_t sc_sleepq;
uint64_t sc_mask;
int sc_ncoll;
} selcluster_t;
static inline int selscan(char *, const int, const size_t, register_t *);
static inline int pollscan(struct pollfd *, const int, register_t *);
static void selclear(void);
static const int sel_flag[] = {
POLLRDNORM | POLLHUP | POLLERR,
POLLWRNORM | POLLHUP | POLLERR,
POLLRDBAND
};
syncobj_t select_sobj = {
.sobj_name = "select",
.sobj_flag = SOBJ_SLEEPQ_LIFO,
.sobj_boostpri = PRI_KERNEL,
.sobj_unsleep = sleepq_unsleep,
.sobj_changepri = sleepq_changepri,
.sobj_lendpri = sleepq_lendpri,
.sobj_owner = syncobj_noowner,
};
static selcluster_t *selcluster[SELCLUSTERS] __read_mostly;
static int direct_select __read_mostly = 0;
const char selop_select[] = "select";
const char selop_poll[] = "poll";
int
sys___pselect50(struct lwp *l, const struct sys___pselect50_args *uap,
register_t *retval)
{
struct timespec ats, *ts = NULL;
sigset_t amask, *mask = NULL;
int error;
if (SCARG(uap, ts)) {
error = copyin(SCARG(uap, ts), &ats, sizeof(ats));
if (error)
return error;
ts = &ats;
}
if (SCARG(uap, mask) != NULL) {
error = copyin(SCARG(uap, mask), &amask, sizeof(amask));
if (error)
return error;
mask = &amask;
}
return selcommon(retval, SCARG(uap, nd), SCARG(uap, in),
SCARG(uap, ou), SCARG(uap, ex), ts, mask);
}
int
sys___select50(struct lwp *l, const struct sys___select50_args *uap,
register_t *retval)
{
struct timeval atv;
struct timespec ats, *ts = NULL;
int error;
if (SCARG(uap, tv)) {
error = copyin(SCARG(uap, tv), (void *)&atv, sizeof(atv));
if (error)
return error;
if (atv.tv_usec < 0 || atv.tv_usec >= 1000000)
return EINVAL;
TIMEVAL_TO_TIMESPEC(&atv, &ats);
ts = &ats;
}
return selcommon(retval, SCARG(uap, nd), SCARG(uap, in),
SCARG(uap, ou), SCARG(uap, ex), ts, NULL);
}
static int
sel_do_scan(const char *opname, void *fds, const int nf, const size_t ni,
struct timespec *ts, sigset_t *mask, register_t *retval)
{
lwp_t * const l = curlwp;
selcluster_t *sc;
kmutex_t *lock;
struct timespec sleepts;
int error, timo;
timo = 0;
if (ts && inittimeleft(ts, &sleepts) == -1) {
return EINVAL;
}
if (__predict_false(mask))
sigsuspendsetup(l, mask);
sc = curcpu()->ci_data.cpu_selcluster;
lock = sc->sc_lock;
l->l_selcluster = sc;
if (opname == selop_select) {
l->l_selbits = fds;
l->l_selni = ni;
} else {
l->l_selbits = NULL;
}
for (;;) {
int ncoll;
SLIST_INIT(&l->l_selwait);
l->l_selret = 0;
if (ts && (ts->tv_sec | ts->tv_nsec | direct_select) == 0) {
l->l_selflag = SEL_RESET;
} else {
l->l_selflag = SEL_SCANNING;
}
ncoll = sc->sc_ncoll;
membar_release();
if (opname == selop_select) {
error = selscan((char *)fds, nf, ni, retval);
} else {
error = pollscan((struct pollfd *)fds, nf, retval);
}
if (error || *retval)
break;
if (ts && (timo = gettimeleft(ts, &sleepts)) <= 0)
break;
state_check:
mutex_spin_enter(lock);
if (__predict_false(sc->sc_ncoll != ncoll)) {
mutex_spin_exit(lock);
selclear();
continue;
}
if (__predict_true(l->l_selflag == SEL_EVENT)) {
mutex_spin_exit(lock);
break;
}
if (__predict_true(l->l_selflag == SEL_RESET)) {
mutex_spin_exit(lock);
selclear();
continue;
}
l->l_selflag = SEL_BLOCKING;
KASSERT(l->l_blcnt == 0);
(void)sleepq_enter(&sc->sc_sleepq, l, lock);
sleepq_enqueue(&sc->sc_sleepq, sc, opname, &select_sobj, true);
error = sleepq_block(timo, true, &select_sobj, 0);
if (error != 0) {
break;
}
goto state_check;
}
selclear();
if (l->l_selflag == SEL_EVENT) {
KASSERT(l->l_selret != 0);
*retval += l->l_selret;
}
if (__predict_false(mask))
sigsuspendteardown(l);
if (error == ERESTART)
return EINTR;
if (error == EWOULDBLOCK)
return 0;
return error;
}
static int
anyset(void *p, size_t nbits)
{
size_t nwords;
__fd_mask mask;
__fd_mask *f = (__fd_mask *)p;
nwords = nbits / __NFDBITS;
while (nwords-- > 0)
if (*f++ != 0)
return 1;
nbits &= __NFDMASK;
if (nbits != 0) {
mask = (1U << nbits) - 1;
if ((*f & mask) != 0)
return 1;
}
return 0;
}
int
selcommon(register_t *retval, int nd, fd_set *u_in, fd_set *u_ou,
fd_set *u_ex, struct timespec *ts, sigset_t *mask)
{
char smallbits[howmany(FD_SETSIZE, NFDBITS) *
sizeof(fd_mask) * 6];
char *bits;
int error, nf, fb, db;
size_t ni;
if (nd < 0)
return EINVAL;
nf = atomic_load_consume(&curlwp->l_fd->fd_dt)->dt_nfiles;
if (nd > nf + FD_SETSIZE &&
nd > curlwp->l_proc->p_rlimit[RLIMIT_NOFILE].rlim_max + FD_SETSIZE)
return EINVAL;
fb = howmany(nf, __NFDBITS);
db = howmany(nd, __NFDBITS);
if (db > fb) {
size_t off;
ni = (db - fb) * sizeof(fd_mask);
bits = smallbits;
off = howmany(nf, __NFDBITS) * sizeof(__fd_mask);
nd -= fb * NFDBITS;
#define checkbits(name, o, sz, fds) \
do { \
if (u_ ## name != NULL) { \
error = copyin((char *)u_ ## name + o, \
bits, sz); \
if (error) \
goto fail; \
if (anyset(bits, (fds) ? \
(size_t)(fds) : CHAR_BIT * (sz))) { \
error = EBADF; \
goto fail; \
} \
} \
} while (0)
while (ni > sizeof(smallbits)) {
checkbits(in, off, sizeof(smallbits), 0);
checkbits(ou, off, sizeof(smallbits), 0);
checkbits(ex, off, sizeof(smallbits), 0);
off += sizeof(smallbits);
ni -= sizeof(smallbits);
nd -= sizeof(smallbits) * CHAR_BIT;
}
checkbits(in, off, ni, nd);
checkbits(ou, off, ni, nd);
checkbits(ex, off, ni, nd);
#undef checkbits
db = fb;
nd = db * __NFDBITS;
}
ni = db * sizeof(fd_mask);
if (ni * 6 > sizeof(smallbits))
bits = kmem_alloc(ni * 6, KM_SLEEP);
else
bits = smallbits;
#define getbits(name, x) \
do { \
if (u_ ## name) { \
error = copyin(u_ ## name, bits + ni * x, ni); \
if (error) \
goto fail; \
} else \
memset(bits + ni * x, 0, ni); \
} while (0)
getbits(in, 0);
getbits(ou, 1);
getbits(ex, 2);
#undef getbits
error = sel_do_scan(selop_select, bits, nd, ni, ts, mask, retval);
#define copyback(name, x) \
do { \
if (error == 0 && u_ ## name != NULL) \
error = copyout(bits + ni * x, \
u_ ## name, ni); \
} while (0)
copyback(in, 3);
copyback(ou, 4);
copyback(ex, 5);
#undef copyback
fail:
if (bits != smallbits)
kmem_free(bits, ni * 6);
return (error);
}
static inline int
selscan(char *bits, const int nfd, const size_t ni, register_t *retval)
{
fd_mask *ibitp, *obitp;
int msk, i, j, fd, n;
file_t *fp;
lwp_t *l;
ibitp = (fd_mask *)(bits + ni * 0);
obitp = (fd_mask *)(bits + ni * 3);
n = 0;
l = curlwp;
memset(obitp, 0, ni * 3);
for (msk = 0; msk < 3; msk++) {
for (i = 0; i < nfd; i += NFDBITS) {
fd_mask ibits, obits;
ibits = *ibitp;
obits = 0;
while ((j = ffs(ibits)) && (fd = i + --j) < nfd) {
ibits &= ~(1U << j);
if ((fp = fd_getfile(fd)) == NULL)
return (EBADF);
l->l_selrec = fd;
if ((*fp->f_ops->fo_poll)(fp, sel_flag[msk])) {
if (!direct_select) {
l->l_selflag = SEL_RESET;
}
obits |= (1U << j);
n++;
}
fd_putfile(fd);
}
if (obits != 0) {
if (direct_select) {
kmutex_t *lock;
lock = l->l_selcluster->sc_lock;
mutex_spin_enter(lock);
*obitp |= obits;
mutex_spin_exit(lock);
} else {
*obitp |= obits;
}
}
ibitp++;
obitp++;
}
}
*retval = n;
return (0);
}
int
sys_poll(struct lwp *l, const struct sys_poll_args *uap, register_t *retval)
{
struct timespec ats, *ts = NULL;
if (SCARG(uap, timeout) != INFTIM) {
ats.tv_sec = SCARG(uap, timeout) / 1000;
ats.tv_nsec = (SCARG(uap, timeout) % 1000) * 1000000;
ts = &ats;
}
return pollcommon(retval, SCARG(uap, fds), SCARG(uap, nfds), ts, NULL);
}
int
sys___pollts50(struct lwp *l, const struct sys___pollts50_args *uap,
register_t *retval)
{
struct timespec ats, *ts = NULL;
sigset_t amask, *mask = NULL;
int error;
if (SCARG(uap, ts)) {
error = copyin(SCARG(uap, ts), &ats, sizeof(ats));
if (error)
return error;
ts = &ats;
}
if (SCARG(uap, mask)) {
error = copyin(SCARG(uap, mask), &amask, sizeof(amask));
if (error)
return error;
mask = &amask;
}
return pollcommon(retval, SCARG(uap, fds), SCARG(uap, nfds), ts, mask);
}
int
pollcommon(register_t *retval, struct pollfd *u_fds, u_int nfds,
struct timespec *ts, sigset_t *mask)
{
struct pollfd smallfds[32];
struct pollfd *fds;
int error;
size_t ni;
if (nfds > curlwp->l_proc->p_rlimit[RLIMIT_NOFILE].rlim_max + 1000) {
return EINVAL;
}
ni = nfds * sizeof(struct pollfd);
if (ni > sizeof(smallfds))
fds = kmem_alloc(ni, KM_SLEEP);
else
fds = smallfds;
error = copyin(u_fds, fds, ni);
if (error)
goto fail;
error = sel_do_scan(selop_poll, fds, nfds, ni, ts, mask, retval);
if (error == 0)
error = copyout(fds, u_fds, ni);
fail:
if (fds != smallfds)
kmem_free(fds, ni);
return (error);
}
static inline int
pollscan(struct pollfd *fds, const int nfd, register_t *retval)
{
file_t *fp;
int i, n = 0, revents;
for (i = 0; i < nfd; i++, fds++) {
fds->revents = 0;
if (fds->fd < 0) {
revents = 0;
} else if ((fp = fd_getfile(fds->fd)) == NULL) {
revents = POLLNVAL;
} else {
curlwp->l_selrec = (uintptr_t)fds;
revents = (*fp->f_ops->fo_poll)(fp,
fds->events | POLLERR | POLLHUP);
fd_putfile(fds->fd);
}
if (revents) {
if (!direct_select) {
curlwp->l_selflag = SEL_RESET;
}
fds->revents = revents;
n++;
}
}
*retval = n;
return (0);
}
int
seltrue(dev_t dev, int events, lwp_t *l)
{
return (events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM));
}
void
selrecord(lwp_t *selector, struct selinfo *sip)
{
selcluster_t *sc;
lwp_t *other;
KASSERT(selector == curlwp);
sc = selector->l_selcluster;
other = sip->sel_lwp;
if (selector->l_selflag == SEL_RESET) {
} else if (other == selector) {
KASSERT(sip->sel_cluster == sc);
} else if (other == NULL) {
membar_acquire();
sip->sel_lwp = selector;
SLIST_INSERT_HEAD(&selector->l_selwait, sip, sel_chain);
sip->sel_fdinfo = selector->l_selrec;
sip->sel_cluster = sc;
} else {
sip->sel_collision |= sc->sc_mask;
KASSERT(sip->sel_cluster != NULL);
}
}
void
selrecord_knote(struct selinfo *sip, struct knote *kn)
{
klist_insert(&sip->sel_klist, kn);
}
bool
selremove_knote(struct selinfo *sip, struct knote *kn)
{
return klist_remove(&sip->sel_klist, kn);
}
static inline bool
sel_setevents(lwp_t *l, struct selinfo *sip, const int events)
{
const int oflag = l->l_selflag;
int ret = 0;
if (__predict_false(events == 0 || oflag == SEL_RESET)) {
l->l_selflag = SEL_RESET;
return true;
}
if (l->l_selbits != NULL) {
const size_t ni = l->l_selni;
fd_mask *fds = (fd_mask *)l->l_selbits;
fd_mask *ofds = (fd_mask *)((char *)fds + ni * 3);
const int fd = sip->sel_fdinfo, fbit = 1 << (fd & __NFDMASK);
const int idx = fd >> __NFDSHIFT;
int n;
for (n = 0; n < 3; n++) {
if ((fds[idx] & fbit) != 0 &&
(ofds[idx] & fbit) == 0 &&
(sel_flag[n] & events)) {
ofds[idx] |= fbit;
ret++;
}
fds = (fd_mask *)((char *)fds + ni);
ofds = (fd_mask *)((char *)ofds + ni);
}
} else {
struct pollfd *pfd = (void *)sip->sel_fdinfo;
int revents = events & (pfd->events | POLLERR | POLLHUP);
if (revents) {
if (pfd->revents == 0)
ret = 1;
pfd->revents |= revents;
}
}
if (!ret) {
return false;
}
l->l_selflag = SEL_EVENT;
l->l_selret += ret;
return true;
}
void
selnotify(struct selinfo *sip, int events, long knhint)
{
selcluster_t *sc;
uint64_t mask;
int index, oflag;
lwp_t *l;
kmutex_t *lock;
KNOTE(&sip->sel_klist, knhint);
if (sip->sel_lwp != NULL) {
sc = sip->sel_cluster;
lock = sc->sc_lock;
mutex_spin_enter(lock);
if (sip->sel_lwp != NULL) {
l = sip->sel_lwp;
oflag = l->l_selflag;
if (!direct_select) {
l->l_selflag = SEL_RESET;
} else if (!sel_setevents(l, sip, events)) {
mutex_spin_exit(lock);
return;
}
if (oflag == SEL_BLOCKING && l->l_mutex == lock) {
KASSERT(l->l_wchan == sc);
sleepq_remove(l->l_sleepq, l, true);
}
}
mutex_spin_exit(lock);
}
if ((mask = sip->sel_collision) != 0) {
sip->sel_collision = 0;
do {
index = ffs64(mask) - 1;
mask ^= __BIT(index);
sc = selcluster[index];
lock = sc->sc_lock;
mutex_spin_enter(lock);
sc->sc_ncoll++;
sleepq_wake(&sc->sc_sleepq, sc, (u_int)-1, lock);
} while (__predict_false(mask != 0));
}
}
static void
selclear(void)
{
struct selinfo *sip, *next;
selcluster_t *sc;
lwp_t *l;
kmutex_t *lock;
l = curlwp;
sc = l->l_selcluster;
lock = sc->sc_lock;
if (SLIST_EMPTY(&l->l_selwait)) {
return;
}
mutex_spin_enter(lock);
for (sip = SLIST_FIRST(&l->l_selwait); sip != NULL; sip = next) {
KASSERT(sip->sel_lwp == l);
KASSERT(sip->sel_cluster == l->l_selcluster);
next = SLIST_NEXT(sip, sel_chain);
atomic_store_release(&sip->sel_lwp, NULL);
}
mutex_spin_exit(lock);
}
void
selsysinit(struct cpu_info *ci)
{
selcluster_t *sc;
u_int index;
index = cpu_index(ci) & SELCLUSTERMASK;
sc = selcluster[index];
if (sc == NULL) {
sc = kmem_alloc(roundup2(sizeof(selcluster_t),
coherency_unit) + coherency_unit, KM_SLEEP);
sc = (void *)roundup2((uintptr_t)sc, coherency_unit);
sc->sc_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_SCHED);
sleepq_init(&sc->sc_sleepq);
sc->sc_ncoll = 0;
sc->sc_mask = __BIT(index);
selcluster[index] = sc;
}
ci->ci_data.cpu_selcluster = sc;
}
void
selinit(struct selinfo *sip)
{
memset(sip, 0, sizeof(*sip));
klist_init(&sip->sel_klist);
}
void
seldestroy(struct selinfo *sip)
{
selcluster_t *sc;
kmutex_t *lock;
lwp_t *l;
klist_fini(&sip->sel_klist);
if (sip->sel_lwp == NULL)
return;
KASSERT(sip->sel_cluster != NULL);
sc = sip->sel_cluster;
lock = sc->sc_lock;
mutex_spin_enter(lock);
if ((l = sip->sel_lwp) != NULL) {
KASSERT(l->l_selcluster == sc);
SLIST_REMOVE(&l->l_selwait, sip, selinfo, sel_chain);
sip->sel_lwp = NULL;
}
mutex_spin_exit(lock);
}
SYSCTL_SETUP(sysctl_select_setup, "sysctl select setup")
{
sysctl_createv(clog, 0, NULL, NULL,
CTLFLAG_PERMANENT | CTLFLAG_READWRITE,
CTLTYPE_INT, "direct_select",
SYSCTL_DESCR("Enable/disable direct select (for testing)"),
NULL, 0, &direct_select, 0,
CTL_KERN, CTL_CREATE, CTL_EOL);
}