/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012, 2014, 2015.
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
#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 )
{
PollNode* target = node->master;
if ( target->revents |= events & (target->events | POLL__ONLY_REVENTS) )
{
ScopedLock target_lock(target->wake_mutex);
if ( !*target->woken )
{
*target->woken = true;
kthread_cond_signal(target->wake_cond);
}
}
}
}
void PollChannel::Register(PollNode* node)
{
ScopedLock lock(&channel_lock);
assert(!node->channel);
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);
if ( slave )
slave->Cancel();
}
PollNode* PollNode::CreateSlave()
{
PollNode* new_slave = new PollNode();
if ( !new_slave )
return NULL;
new_slave->wake_mutex = wake_mutex;
new_slave->wake_cond = wake_cond;
new_slave->events = events;
new_slave->revents = revents;
new_slave->woken = woken;
new_slave->master = master;
new_slave->slave = slave;
return slave = new_slave;
}
static struct pollfd* CopyFdsFromUser(struct pollfd* user_fds, size_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, size_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;
}
struct poll_timeout
{
kthread_mutex_t* wake_mutex;
kthread_cond_t* wake_cond;
bool* woken;
};
static void poll_timeout_callback(Clock*, Timer*, void* ctx)
{
struct poll_timeout* pts = (struct poll_timeout*) ctx;
ScopedLock lock(pts->wake_mutex);
*pts->woken = true;
kthread_cond_signal(pts->wake_cond);
}
int sys_ppoll(struct pollfd* user_fds, size_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 ( 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;
bool remote_woken = false;
bool unexpected_error = false;
Timer timer;
struct poll_timeout pts;
if ( timespec_le(timespec_make(0, 1), timeout_ts) )
{
timer.Attach(Time::GetClock(CLOCK_MONOTONIC));
struct itimerspec its;
its.it_interval = timespec_nul();
its.it_value = timeout_ts;
pts.wake_mutex = &wakeup_mutex;
pts.wake_cond = &wakeup_cond;
pts.woken = &remote_woken;
timer.Set(&its, NULL, 0, poll_timeout_callback, &pts);
}
size_t reqs;
for ( reqs = 0; !unexpected_error && reqs < nfds; )
{
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;
reqs++;
continue;
}
Ref desc = process->GetDescriptor(fds[reqs].fd);
if ( !desc ) { self_woken = unexpected_error = true; break; }
node->events = fds[reqs].events | POLL__ONLY_REVENTS;
node->revents = 0;
node->wake_mutex = &wakeup_mutex;
node->wake_cond = &wakeup_cond;
node->woken = &remote_woken;
reqs++;
// TODO: How should errors be handled?
if ( desc->poll(&ctx, node) == 0 )
self_woken = true;
else if ( errno == EAGAIN )
errno = 0;
else
unexpected_error = self_woken = true;
}
if ( timeout_ts.tv_sec == 0 && timeout_ts.tv_nsec == 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 ( size_t i = 0; i < reqs; i++ )
if ( 0 <= fds[i].fd )
nodes[i].Cancel();
if ( timespec_le(timespec_make(0, 1), timeout_ts) )
{
timer.Cancel();
timer.Detach();
}
if ( !unexpected_error )
{
int num_events = 0;
for ( size_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;
}
} // namespace Sortix