/******************************************************************************* Copyright(C) Jonas 'Sortie' Termansen 2012. This file is part of Sortix. Sortix is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Sortix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Sortix. If not, see . poll.cpp Interface for waiting on file descriptor events. *******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "poll.h" namespace Sortix { PollChannel::PollChannel() { first = NULL; last = NULL; channel_lock = KTHREAD_MUTEX_INITIALIZER; no_pending_cond = KTHREAD_COND_INITIALIZER; } PollChannel::~PollChannel() { ScopedLock lock(&channel_lock); // TODO: Is this the correct error to signal with? SignalUnlocked(POLLHUP); // Note: We can't stop early in case of a signal, because that would mean // other threads are still using our data, and since this is the destructor, // leaving early _will_ cause data corruption. Luckily, this loop will // terminate because everyone is now woken up and will cancel, which is what // we wait for to finish. No new requests can come, since we are the // destructor - whoever owns this object is no longer using it. while ( first ) kthread_cond_wait(&no_pending_cond, &channel_lock); } void PollChannel::Signal(short events) { ScopedLock lock(&channel_lock); SignalUnlocked(events); } void PollChannel::SignalUnlocked(short events) { for ( PollNode* node = first; node; node = node->next ) if ( node->revents |= events & (node->events | POLL__ONLY_REVENTS) ) { ScopedLock node_lock(node->wake_mutex); if ( !*node->woken ) { *node->woken = true; kthread_cond_signal(node->wake_cond); } } } void PollChannel::Register(PollNode* node) { ScopedLock lock(&channel_lock); node->channel = this; if ( !first ) first = last = node, node->next = node->prev = NULL; else node->next = NULL, node->prev = last, last->next = node, last = node; } void PollChannel::Unregister(PollNode* node) { ScopedLock lock(&channel_lock); node->channel = NULL; if ( node->prev ) node->prev->next = node->next; else first = node->next; if ( node->next ) node->next->prev = node->prev; else last = node->prev; if ( !first ) kthread_cond_signal(&no_pending_cond); } void PollNode::Cancel() { if ( channel ) channel->Unregister(this); } namespace Poll { static struct pollfd* CopyFdsFromUser(struct pollfd* user_fds, nfds_t nfds) { size_t size = sizeof(struct pollfd) * nfds; struct pollfd* fds = new struct pollfd[nfds]; if ( !fds ) return NULL; if ( !CopyFromUser(fds, user_fds, size) ) { delete[] fds; return NULL; } return fds; } static bool CopyFdsToUser(struct pollfd* user_fds, const struct pollfd* kernel_fds, nfds_t nfds) { size_t size = sizeof(struct pollfd) * nfds; return CopyToUser(user_fds, kernel_fds, size); } static bool FetchTimespec(struct timespec* dest, const struct timespec* user) { if ( !user ) dest->tv_sec = -1, dest->tv_nsec = 0; else if ( !CopyFromUser(dest, user, sizeof(*dest)) ) return false; return true; } static int sys_ppoll(struct pollfd* user_fds, nfds_t nfds, const struct timespec* user_timeout_ts, const sigset_t* user_sigmask) { ioctx_t ctx; SetupKernelIOCtx(&ctx); struct timespec timeout_ts; if ( !FetchTimespec(&timeout_ts, user_timeout_ts) ) return -1; if ( 0 < timeout_ts.tv_sec || timeout_ts.tv_nsec || user_sigmask ) return errno = ENOSYS, -1; struct pollfd* fds = CopyFdsFromUser(user_fds, nfds); if ( !fds ) { return -1; } PollNode* nodes = new PollNode[nfds]; if ( !nodes ) { delete[] fds; return -1; } Process* process = CurrentProcess(); kthread_mutex_t wakeup_mutex = KTHREAD_MUTEX_INITIALIZER; kthread_cond_t wakeup_cond = KTHREAD_COND_INITIALIZER; kthread_mutex_lock(&wakeup_mutex); int ret = -1; bool self_woken = false; volatile bool remote_woken = false; bool unexpected_error = false; nfds_t reqs = nfds; for ( reqs = 0; !unexpected_error && reqs < nfds; reqs++ ) { PollNode* node = nodes + reqs; if ( fds[reqs].fd < 0 ) { fds[reqs].revents = POLLNVAL; // TODO: Should we set POLLNVAL in node->revents too? Should this // system call ignore this error and keep polling, or return to // user-space immediately? What if conditions are already true on // some of the file descriptors (those we have processed so far?)? node->revents = 0; continue; } Ref desc = process->GetDescriptor(fds[reqs].fd); if ( !desc ) { unexpected_error = true; break; } node->events = fds[reqs].events; node->revents = 0; node->wake_mutex = &wakeup_mutex; node->wake_cond = &wakeup_cond; node->woken = (bool*) &remote_woken; // TODO: How should erors be handled? if ( desc->poll(&ctx, node) == 0 ) self_woken = true; else if ( errno != EAGAIN ) unexpected_error = self_woken = true; } if ( timeout_ts.tv_sec < 0 ) self_woken = true; while ( !(self_woken || remote_woken) ) { if ( !kthread_cond_wait_signal(&wakeup_cond, &wakeup_mutex) ) errno = -EINTR, self_woken = true; } kthread_mutex_unlock(&wakeup_mutex); for ( nfds_t i = 0; i < reqs; i++ ) nodes[i].Cancel(); if ( !unexpected_error ) { int num_events = 0; for ( nfds_t i = 0; i < reqs; i++ ) { if ( fds[i].fd < -1 ) continue; if ( (fds[i].revents = nodes[i].revents) ) num_events++; } if ( CopyFdsToUser(user_fds, fds, nfds) ) ret = num_events; } delete[] nodes; delete[] fds; return ret; } void Init() { Syscall::Register(SYSCALL_PPOLL, (void*) sys_ppoll); } } // namespace Poll } // namespace Sortix