SchOS/kernel/sys/sys_poll.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

295 lines
9.3 KiB
C

// poll(2) / select(2) — real readiness-based I/O multiplexing over pipes,
// the console tty, and regular files (via file_poll()). This is the
// foundation every event-loop program (dash, toybox netcat, eventually a
// display server) sits on; the previous stubs returned -ENOSYS, which made
// anything poll-driven bail out instantly.
//
// Blocking strategy: scan all fds; if nothing is ready and the timeout
// allows, sleep until the next timer tick (10ms granularity at HZ=100) and
// rescan. A pending unblocked signal or kill interrupts the wait with
// -EINTR so handlers get a chance to run, matching POSIX.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "fs/fs.h"
#include "fs/file.h"
#include "core/proc.h"
#include "core/syscall.h"
#include "uapi/posix.h"
#include "core/uaccess.h"
#include "uapi/errno.h"
#define POLL_MAX_FDS 64
// Sleep one timer tick, or until killed/signaled. Returns 0 to keep
// waiting, -EINTR if the wait must be abandoned.
static int
poll_block_one_tick(void)
{
if (myproc()->killed)
return -EINTR;
if (myproc()->pending_signals & ~myproc()->signal_mask)
return -EINTR;
acquire(&tickslock);
uint t0 = ticks;
while (ticks == t0 && !myproc()->killed &&
!(myproc()->pending_signals & ~myproc()->signal_mask))
sleep(&ticks, &tickslock);
release(&tickslock);
return 0;
}
// Shared core: fds[] already in kernel memory. timeout_ticks < 0 means
// wait forever. Returns number of fds with nonzero revents, 0 on timeout,
// -EINTR if interrupted.
static int
do_poll(struct pollfd *fds, int nfds, int timeout_ticks)
{
for (;;) {
int nready = 0;
for (int i = 0; i < nfds; i++) {
fds[i].revents = 0;
if (fds[i].fd < 0)
continue; // negative fd: ignore, per poll(2)
if (fds[i].fd >= NOFILE || !myproc()->ofile[fds[i].fd]) {
fds[i].revents = POLLNVAL;
nready++;
continue;
}
int rev = file_poll(myproc()->ofile[fds[i].fd], fds[i].events);
if (rev) {
fds[i].revents = (short)rev;
nready++;
}
}
if (nready > 0 || timeout_ticks == 0)
return nready;
if (poll_block_one_tick() < 0)
return -EINTR;
if (timeout_ticks > 0)
timeout_ticks--;
}
}
// poll(fds, nfds, timeout_ms)
addr_t
sys_poll(void)
{
addr_t ufds;
int nfds, timeout_ms;
struct pollfd fds[POLL_MAX_FDS];
argaddr(0, &ufds);
argint(1, &nfds);
argint(2, &timeout_ms);
if (nfds < 0 || nfds > POLL_MAX_FDS)
return -EINVAL;
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
// Rounded up: a caller asking for any wait at all must not get zero ticks,
// which poll() below treats as "return immediately".
int timeout_ticks = timeout_ms < 0 ? -1
: (timeout_ms + MSEC_PER_TICK - 1) / MSEC_PER_TICK;
int r = do_poll(fds, nfds, timeout_ticks);
if (r >= 0 && nfds > 0 &&
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
return r;
}
// ppoll(fds, nfds, timespec*, sigmask) — sigmask is ignored (poll already
// breaks out for any deliverable signal); NULL timespec = wait forever.
addr_t
sys_ppoll(void)
{
addr_t ufds, utsp;
int nfds;
struct pollfd fds[POLL_MAX_FDS];
argaddr(0, &ufds);
argint(1, &nfds);
argaddr(2, &utsp);
if (nfds < 0 || nfds > POLL_MAX_FDS)
return -EINVAL;
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
int timeout_ticks = -1;
if (utsp) {
int64 ts[2]; // {tv_sec, tv_nsec}
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
return -EFAULT;
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
}
int r = do_poll(fds, nfds, timeout_ticks);
if (r >= 0 && nfds > 0 &&
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
return -EFAULT;
return r;
}
// select(nfds, readfds, writefds, exceptfds, timeval*). fd_set is a bit
// array; with NOFILE=128 the interesting part fits in two 64-bit words.
#define FDSET_WORDS (NOFILE / 64)
static int
fdset_fetch(addr_t uaddr, uint64 *set)
{
if (!uaddr) {
set[0] = set[1] = 0;
return 0;
}
return copy_from_user(set, uaddr, FDSET_WORDS * sizeof(uint64));
}
static int
fdset_isset(uint64 *set, int fd) { return (set[fd / 64] >> (fd % 64)) & 1; }
static void
fdset_set(uint64 *set, int fd) { set[fd / 64] |= 1ULL << (fd % 64); }
addr_t
sys_select(void)
{
int nfds;
addr_t urd, uwr, uex, utv;
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
argint(0, &nfds);
argaddr(1, &urd);
argaddr(2, &uwr);
argaddr(3, &uex);
argaddr(4, &utv);
if (nfds < 0 || nfds > NOFILE)
return -EINVAL;
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
fdset_fetch(uex, ex) < 0)
return -EFAULT;
int timeout_ticks = -1;
if (utv) {
int64 tv[2]; // {tv_sec, tv_usec}
if (copy_from_user(tv, utv, sizeof(tv)) < 0)
return -EFAULT;
timeout_ticks = (int)(tv[0] * HZ + tv[1] / USEC_PER_TICK);
}
for (;;) {
int nready = 0;
for (int fd = 0; fd < nfds; fd++) {
int want_r = fdset_isset(rd, fd);
int want_w = fdset_isset(wr, fd);
if (!want_r && !want_w)
continue;
struct file *f = myproc()->ofile[fd];
if (!f)
return -EBADF;
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
// Hangup/error count as readable: a read() will not block
// (it returns EOF/error immediately) — same rule Linux uses.
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
fdset_set(ord, fd);
nready++;
}
if (want_w && (rev & (POLLOUT | POLLERR))) {
fdset_set(owr, fd);
nready++;
}
}
if (nready > 0 || timeout_ticks == 0) {
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
if (uex) { // no exceptional conditions exist here; report none
uint64 zero[FDSET_WORDS] = {0};
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
}
return nready;
}
if (poll_block_one_tick() < 0)
return -EINTR;
if (timeout_ticks > 0)
timeout_ticks--;
}
}
// pselect6: select with a timespec and (ignored) sigmask.
addr_t
sys_pselect6(void)
{
// Delegate: args 0-3 line up with select's; arg 4 is a timespec instead
// of a timeval and arg 5 the sigmask. Reuse sys_select's field reads but
// treat the timeout as nanoseconds.
int nfds;
addr_t urd, uwr, uex, utsp;
argint(0, &nfds);
argaddr(1, &urd);
argaddr(2, &uwr);
argaddr(3, &uex);
argaddr(4, &utsp);
// Convert the timespec (if any) into a timeval in place on the user
// stack? No — simplest correct route: temporarily run the same logic.
// sys_select re-reads the same registers, so just fix up the timeout
// interpretation here by patching r8 semantics is not possible; instead
// duplicate the small tail:
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
if (nfds < 0 || nfds > NOFILE)
return -EINVAL;
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
fdset_fetch(uex, ex) < 0)
return -EFAULT;
int timeout_ticks = -1;
if (utsp) {
int64 ts[2];
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
return -EFAULT;
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
}
for (;;) {
int nready = 0;
for (int fd = 0; fd < nfds; fd++) {
int want_r = fdset_isset(rd, fd);
int want_w = fdset_isset(wr, fd);
if (!want_r && !want_w)
continue;
struct file *f = myproc()->ofile[fd];
if (!f)
return -EBADF;
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
fdset_set(ord, fd);
nready++;
}
if (want_w && (rev & (POLLOUT | POLLERR))) {
fdset_set(owr, fd);
nready++;
}
}
if (nready > 0 || timeout_ticks == 0) {
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
if (uex) {
uint64 zero[FDSET_WORDS] = {0};
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
}
return nready;
}
if (poll_block_one_tick() < 0)
return -EINTR;
if (timeout_ticks > 0)
timeout_ticks--;
}
}