// 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 "types.h" #include "defs.h" #include "param.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "file.h" #include "proc.h" #include "syscall.h" #include "posix.h" #include "uaccess.h" #include "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; int timeout_ticks = timeout_ms < 0 ? -1 : (timeout_ms + 9) / 10; 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] * 100 + ts[1] / 10000000); } 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] * 100 + tv[1] / 10000); } 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] * 100 + ts[1] / 10000000); } 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--; } }