Commit 9f72949f authored by David Woodhouse's avatar David Woodhouse Committed by Linus Torvalds

[PATCH] Add pselect/ppoll system call implementation

The following implementation of ppoll() and pselect() system calls
depends on the architecture providing a TIF_RESTORE_SIGMASK flag in the
thread_info.

These system calls have to change the signal mask during their
operation, and signal handlers must be invoked using the new, temporary
signal mask. The old signal mask must be restored either upon successful
exit from the system call, or upon returning from the invoked signal
handler if the system call is interrupted. We can't simply restore the
original signal mask and return to userspace, since the restored signal
mask may actually block the signal which interrupted the system call.

The TIF_RESTORE_SIGMASK flag deals with this by causing the syscall exit
path to trap into do_signal() just as TIF_SIGPENDING does, and by
causing do_signal() to use the saved signal mask instead of the current
signal mask when setting up the stack frame for the signal handler -- or
by causing do_signal() to simply restore the saved signal mask in the
case where there is no handler to be invoked.

The first patch implements the sys_pselect() and sys_ppoll() system
calls, which are present only if TIF_RESTORE_SIGMASK is defined. That
#ifdef should go away in time when all architectures have implemented
it. The second patch implements TIF_RESTORE_SIGMASK for the PowerPC
kernel (in the -mm tree), and the third patch then removes the
arch-specific implementations of sys_rt_sigsuspend() and replaces them
with generic versions using the same trick.

The fourth and fifth patches, provided by David Howells, implement
TIF_RESTORE_SIGMASK for FR-V and i386 respectively, and the sixth patch
adds the syscalls to the i386 syscall table.

This patch:

Add the pselect() and ppoll() system calls, providing core routines usable by
the original select() and poll() system calls and also the new calls (with
their semantics w.r.t timeouts).
Signed-off-by: default avatarDavid Woodhouse <dwmw2@infradead.org>
Cc: Michael Kerrisk <mtk-manpages@gmx.net>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 36a7878a
...@@ -53,6 +53,8 @@ ...@@ -53,6 +53,8 @@
#include <asm/mmu_context.h> #include <asm/mmu_context.h>
#include <asm/ioctls.h> #include <asm/ioctls.h>
extern void sigset_from_compat(sigset_t *set, compat_sigset_t *compat);
/* /*
* Not all architectures have sys_utime, so implement this in terms * Not all architectures have sys_utime, so implement this in terms
* of sys_utimes. * of sys_utimes.
...@@ -1657,36 +1659,14 @@ static void select_bits_free(void *bits, int size) ...@@ -1657,36 +1659,14 @@ static void select_bits_free(void *bits, int size)
#define MAX_SELECT_SECONDS \ #define MAX_SELECT_SECONDS \
((unsigned long) (MAX_SCHEDULE_TIMEOUT / HZ)-1) ((unsigned long) (MAX_SCHEDULE_TIMEOUT / HZ)-1)
asmlinkage long int compat_core_sys_select(int n, compat_ulong_t __user *inp,
compat_sys_select(int n, compat_ulong_t __user *inp, compat_ulong_t __user *outp, compat_ulong_t __user *outp, compat_ulong_t __user *exp, s64 *timeout)
compat_ulong_t __user *exp, struct compat_timeval __user *tvp)
{ {
fd_set_bits fds; fd_set_bits fds;
char *bits; char *bits;
long timeout;
int size, max_fdset, ret = -EINVAL; int size, max_fdset, ret = -EINVAL;
struct fdtable *fdt; struct fdtable *fdt;
timeout = MAX_SCHEDULE_TIMEOUT;
if (tvp) {
time_t sec, usec;
if (!access_ok(VERIFY_READ, tvp, sizeof(*tvp))
|| __get_user(sec, &tvp->tv_sec)
|| __get_user(usec, &tvp->tv_usec)) {
ret = -EFAULT;
goto out_nofds;
}
if (sec < 0 || usec < 0)
goto out_nofds;
if ((unsigned long) sec < MAX_SELECT_SECONDS) {
timeout = ROUND_UP(usec, 1000000/HZ);
timeout += sec * (unsigned long) HZ;
}
}
if (n < 0) if (n < 0)
goto out_nofds; goto out_nofds;
...@@ -1723,19 +1703,7 @@ compat_sys_select(int n, compat_ulong_t __user *inp, compat_ulong_t __user *outp ...@@ -1723,19 +1703,7 @@ compat_sys_select(int n, compat_ulong_t __user *inp, compat_ulong_t __user *outp
zero_fd_set(n, fds.res_out); zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex); zero_fd_set(n, fds.res_ex);
ret = do_select(n, &fds, &timeout); ret = do_select(n, &fds, timeout);
if (tvp && !(current->personality & STICKY_TIMEOUTS)) {
time_t sec = 0, usec = 0;
if (timeout) {
sec = timeout / HZ;
usec = timeout % HZ;
usec *= (1000000/HZ);
}
if (put_user(sec, &tvp->tv_sec) ||
put_user(usec, &tvp->tv_usec))
ret = -EFAULT;
}
if (ret < 0) if (ret < 0)
goto out; goto out;
...@@ -1756,6 +1724,224 @@ compat_sys_select(int n, compat_ulong_t __user *inp, compat_ulong_t __user *outp ...@@ -1756,6 +1724,224 @@ compat_sys_select(int n, compat_ulong_t __user *inp, compat_ulong_t __user *outp
return ret; return ret;
} }
asmlinkage long compat_sys_select(int n, compat_ulong_t __user *inp,
compat_ulong_t __user *outp, compat_ulong_t __user *exp,
struct compat_timeval __user *tvp)
{
s64 timeout = -1;
struct compat_timeval tv;
int ret;
if (tvp) {
if (copy_from_user(&tv, tvp, sizeof(tv)))
return -EFAULT;
if (tv.tv_sec < 0 || tv.tv_usec < 0)
return -EINVAL;
/* Cast to u64 to make GCC stop complaining */
if ((u64)tv.tv_sec >= (u64)MAX_INT64_SECONDS)
timeout = -1; /* infinite */
else {
timeout = ROUND_UP(tv.tv_sec, 1000000/HZ);
timeout += tv.tv_sec * HZ;
}
}
ret = compat_core_sys_select(n, inp, outp, exp, &timeout);
if (tvp) {
if (current->personality & STICKY_TIMEOUTS)
goto sticky;
tv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));
tv.tv_sec = timeout;
if (copy_to_user(tvp, &tv, sizeof(tv))) {
sticky:
/*
* If an application puts its timeval in read-only
* memory, we don't want the Linux-specific update to
* the timeval to cause a fault after the select has
* completed successfully. However, because we're not
* updating the timeval, we can't restart the system
* call.
*/
if (ret == -ERESTARTNOHAND)
ret = -EINTR;
}
}
return ret;
}
#ifdef TIF_RESTORE_SIGMASK
asmlinkage long compat_sys_pselect7(int n, compat_ulong_t __user *inp,
compat_ulong_t __user *outp, compat_ulong_t __user *exp,
struct compat_timespec __user *tsp, compat_sigset_t __user *sigmask,
compat_size_t sigsetsize)
{
compat_sigset_t ss32;
sigset_t ksigmask, sigsaved;
long timeout = MAX_SCHEDULE_TIMEOUT;
struct compat_timespec ts;
int ret;
if (tsp) {
if (copy_from_user(&ts, tsp, sizeof(ts)))
return -EFAULT;
if (ts.tv_sec < 0 || ts.tv_nsec < 0)
return -EINVAL;
}
if (sigmask) {
if (sigsetsize != sizeof(compat_sigset_t))
return -EINVAL;
if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
return -EFAULT;
sigset_from_compat(&ksigmask, &ss32);
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
}
do {
if (tsp) {
if ((unsigned long)ts.tv_sec < MAX_SELECT_SECONDS) {
timeout = ROUND_UP(ts.tv_nsec, 1000000000/HZ);
timeout += ts.tv_sec * (unsigned long)HZ;
ts.tv_sec = 0;
ts.tv_nsec = 0;
} else {
ts.tv_sec -= MAX_SELECT_SECONDS;
timeout = MAX_SELECT_SECONDS * HZ;
}
}
ret = compat_core_sys_select(n, inp, outp, exp, &timeout);
} while (!ret && !timeout && tsp && (ts.tv_sec || ts.tv_nsec));
if (tsp && !(current->personality & STICKY_TIMEOUTS)) {
ts.tv_sec += timeout / HZ;
ts.tv_nsec += (timeout % HZ) * (1000000000/HZ);
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
(void)copy_to_user(tsp, &ts, sizeof(ts));
}
if (ret == -ERESTARTNOHAND) {
/*
* Don't restore the signal mask yet. Let do_signal() deliver
* the signal on the way back to userspace, before the signal
* mask is restored.
*/
if (sigmask) {
memcpy(&current->saved_sigmask, &sigsaved,
sizeof(sigsaved));
set_thread_flag(TIF_RESTORE_SIGMASK);
}
} else if (sigmask)
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
return ret;
}
asmlinkage long compat_sys_pselect6(int n, compat_ulong_t __user *inp,
compat_ulong_t __user *outp, compat_ulong_t __user *exp,
struct compat_timespec __user *tsp, void __user *sig)
{
compat_size_t sigsetsize = 0;
compat_uptr_t up = 0;
if (sig) {
if (!access_ok(VERIFY_READ, sig,
sizeof(compat_uptr_t)+sizeof(compat_size_t)) ||
__get_user(up, (compat_uptr_t __user *)sig) ||
__get_user(sigsetsize,
(compat_size_t __user *)(sig+sizeof(up))))
return -EFAULT;
}
return compat_sys_pselect7(n, inp, outp, exp, tsp, compat_ptr(up),
sigsetsize);
}
asmlinkage long compat_sys_ppoll(struct pollfd __user *ufds,
unsigned int nfds, struct compat_timespec __user *tsp,
const compat_sigset_t __user *sigmask, compat_size_t sigsetsize)
{
compat_sigset_t ss32;
sigset_t ksigmask, sigsaved;
struct compat_timespec ts;
s64 timeout = -1;
int ret;
if (tsp) {
if (copy_from_user(&ts, tsp, sizeof(ts)))
return -EFAULT;
/* We assume that ts.tv_sec is always lower than
the number of seconds that can be expressed in
an s64. Otherwise the compiler bitches at us */
timeout = ROUND_UP(ts.tv_sec, 1000000000/HZ);
timeout += ts.tv_sec * HZ;
}
if (sigmask) {
if (sigsetsize |= sizeof(compat_sigset_t))
return -EINVAL;
if (copy_from_user(&ss32, sigmask, sizeof(ss32)))
return -EFAULT;
sigset_from_compat(&ksigmask, &ss32);
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
}
ret = do_sys_poll(ufds, nfds, &timeout);
/* We can restart this syscall, usually */
if (ret == -EINTR) {
/*
* Don't restore the signal mask yet. Let do_signal() deliver
* the signal on the way back to userspace, before the signal
* mask is restored.
*/
if (sigmask) {
memcpy(&current->saved_sigmask, &sigsaved,
sizeof(sigsaved));
set_thread_flag(TIF_RESTORE_SIGMASK);
}
ret = -ERESTARTNOHAND;
} else if (sigmask)
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
if (tsp && timeout >= 0) {
if (current->personality & STICKY_TIMEOUTS)
goto sticky;
/* Yes, we know it's actually an s64, but it's also positive. */
ts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * 1000;
ts.tv_sec = timeout;
if (copy_to_user(tsp, &ts, sizeof(ts))) {
sticky:
/*
* If an application puts its timeval in read-only
* memory, we don't want the Linux-specific update to
* the timeval to cause a fault after the select has
* completed successfully. However, because we're not
* updating the timeval, we can't restart the system
* call.
*/
if (ret == -ERESTARTNOHAND && timeout >= 0)
ret = -EINTR;
}
}
return ret;
}
#endif /* TIF_RESTORE_SIGMASK */
#if defined(CONFIG_NFSD) || defined(CONFIG_NFSD_MODULE) #if defined(CONFIG_NFSD) || defined(CONFIG_NFSD_MODULE)
/* Stuff for NFS server syscalls... */ /* Stuff for NFS server syscalls... */
struct compat_nfsctl_svc { struct compat_nfsctl_svc {
......
...@@ -179,12 +179,11 @@ static int max_select_fd(unsigned long n, fd_set_bits *fds) ...@@ -179,12 +179,11 @@ static int max_select_fd(unsigned long n, fd_set_bits *fds)
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR) #define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
#define POLLEX_SET (POLLPRI) #define POLLEX_SET (POLLPRI)
int do_select(int n, fd_set_bits *fds, long *timeout) int do_select(int n, fd_set_bits *fds, s64 *timeout)
{ {
struct poll_wqueues table; struct poll_wqueues table;
poll_table *wait; poll_table *wait;
int retval, i; int retval, i;
long __timeout = *timeout;
rcu_read_lock(); rcu_read_lock();
retval = max_select_fd(n, fds); retval = max_select_fd(n, fds);
...@@ -196,11 +195,12 @@ int do_select(int n, fd_set_bits *fds, long *timeout) ...@@ -196,11 +195,12 @@ int do_select(int n, fd_set_bits *fds, long *timeout)
poll_initwait(&table); poll_initwait(&table);
wait = &table.pt; wait = &table.pt;
if (!__timeout) if (!*timeout)
wait = NULL; wait = NULL;
retval = 0; retval = 0;
for (;;) { for (;;) {
unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
long __timeout;
set_current_state(TASK_INTERRUPTIBLE); set_current_state(TASK_INTERRUPTIBLE);
...@@ -255,22 +255,32 @@ int do_select(int n, fd_set_bits *fds, long *timeout) ...@@ -255,22 +255,32 @@ int do_select(int n, fd_set_bits *fds, long *timeout)
*rexp = res_ex; *rexp = res_ex;
} }
wait = NULL; wait = NULL;
if (retval || !__timeout || signal_pending(current)) if (retval || !*timeout || signal_pending(current))
break; break;
if(table.error) { if(table.error) {
retval = table.error; retval = table.error;
break; break;
} }
if (*timeout < 0) {
/* Wait indefinitely */
__timeout = MAX_SCHEDULE_TIMEOUT;
} else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) {
/* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in a loop */
__timeout = MAX_SCHEDULE_TIMEOUT - 1;
*timeout -= __timeout;
} else {
__timeout = *timeout;
*timeout = 0;
}
__timeout = schedule_timeout(__timeout); __timeout = schedule_timeout(__timeout);
if (*timeout >= 0)
*timeout += __timeout;
} }
__set_current_state(TASK_RUNNING); __set_current_state(TASK_RUNNING);
poll_freewait(&table); poll_freewait(&table);
/*
* Up-to-date the caller timeout.
*/
*timeout = __timeout;
return retval; return retval;
} }
...@@ -295,36 +305,14 @@ static void select_bits_free(void *bits, int size) ...@@ -295,36 +305,14 @@ static void select_bits_free(void *bits, int size)
#define MAX_SELECT_SECONDS \ #define MAX_SELECT_SECONDS \
((unsigned long) (MAX_SCHEDULE_TIMEOUT / HZ)-1) ((unsigned long) (MAX_SCHEDULE_TIMEOUT / HZ)-1)
asmlinkage long static int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp) fd_set __user *exp, s64 *timeout)
{ {
fd_set_bits fds; fd_set_bits fds;
char *bits; char *bits;
long timeout;
int ret, size, max_fdset; int ret, size, max_fdset;
struct fdtable *fdt; struct fdtable *fdt;
timeout = MAX_SCHEDULE_TIMEOUT;
if (tvp) {
time_t sec, usec;
if (!access_ok(VERIFY_READ, tvp, sizeof(*tvp))
|| __get_user(sec, &tvp->tv_sec)
|| __get_user(usec, &tvp->tv_usec)) {
ret = -EFAULT;
goto out_nofds;
}
ret = -EINVAL;
if (sec < 0 || usec < 0)
goto out_nofds;
if ((unsigned long) sec < MAX_SELECT_SECONDS) {
timeout = ROUND_UP(usec, 1000000/HZ);
timeout += sec * (unsigned long) HZ;
}
}
ret = -EINVAL; ret = -EINVAL;
if (n < 0) if (n < 0)
goto out_nofds; goto out_nofds;
...@@ -362,18 +350,7 @@ sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s ...@@ -362,18 +350,7 @@ sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s
zero_fd_set(n, fds.res_out); zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex); zero_fd_set(n, fds.res_ex);
ret = do_select(n, &fds, &timeout); ret = do_select(n, &fds, timeout);
if (tvp && !(current->personality & STICKY_TIMEOUTS)) {
time_t sec = 0, usec = 0;
if (timeout) {
sec = timeout / HZ;
usec = timeout % HZ;
usec *= (1000000/HZ);
}
put_user(sec, &tvp->tv_sec);
put_user(usec, &tvp->tv_usec);
}
if (ret < 0) if (ret < 0)
goto out; goto out;
...@@ -395,6 +372,154 @@ sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s ...@@ -395,6 +372,154 @@ sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, s
return ret; return ret;
} }
asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timeval __user *tvp)
{
s64 timeout = -1;
struct timeval tv;
int ret;
if (tvp) {
if (copy_from_user(&tv, tvp, sizeof(tv)))
return -EFAULT;
if (tv.tv_sec < 0 || tv.tv_usec < 0)
return -EINVAL;
/* Cast to u64 to make GCC stop complaining */
if ((u64)tv.tv_sec >= (u64)MAX_INT64_SECONDS)
timeout = -1; /* infinite */
else {
timeout = ROUND_UP(tv.tv_usec, USEC_PER_SEC/HZ);
timeout += tv.tv_sec * HZ;
}
}
ret = core_sys_select(n, inp, outp, exp, &timeout);
if (tvp) {
if (current->personality & STICKY_TIMEOUTS)
goto sticky;
tv.tv_usec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ));
tv.tv_sec = timeout;
if (copy_to_user(tvp, &tv, sizeof(tv))) {
sticky:
/*
* If an application puts its timeval in read-only
* memory, we don't want the Linux-specific update to
* the timeval to cause a fault after the select has
* completed successfully. However, because we're not
* updating the timeval, we can't restart the system
* call.
*/
if (ret == -ERESTARTNOHAND)
ret = -EINTR;
}
}
return ret;
}
#ifdef TIF_RESTORE_SIGMASK
asmlinkage long sys_pselect7(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timespec __user *tsp,
const sigset_t __user *sigmask, size_t sigsetsize)
{
s64 timeout = MAX_SCHEDULE_TIMEOUT;
sigset_t ksigmask, sigsaved;
struct timespec ts;
int ret;
if (tsp) {
if (copy_from_user(&ts, tsp, sizeof(ts)))
return -EFAULT;
if (ts.tv_sec < 0 || ts.tv_nsec < 0)
return -EINVAL;
/* Cast to u64 to make GCC stop complaining */
if ((u64)ts.tv_sec >= (u64)MAX_INT64_SECONDS)
timeout = -1; /* infinite */
else {
timeout = ROUND_UP(ts.tv_nsec, NSEC_PER_SEC/HZ);
timeout += ts.tv_sec * HZ;
}
}
if (sigmask) {
/* XXX: Don't preclude handling different sized sigset_t's. */
if (sigsetsize != sizeof(sigset_t))
return -EINVAL;
if (copy_from_user(&ksigmask, sigmask, sizeof(ksigmask)))
return -EFAULT;
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
}
ret = core_sys_select(n, inp, outp, exp, &timeout);
if (tsp) {
if (current->personality & STICKY_TIMEOUTS)
goto sticky;
ts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * 1000;
ts.tv_sec = timeout;
if (copy_to_user(tsp, &ts, sizeof(ts))) {
sticky:
/*
* If an application puts its timeval in read-only
* memory, we don't want the Linux-specific update to
* the timeval to cause a fault after the select has
* completed successfully. However, because we're not
* updating the timeval, we can't restart the system
* call.
*/
if (ret == -ERESTARTNOHAND)
ret = -EINTR;
}
}
if (ret == -ERESTARTNOHAND) {
/*
* Don't restore the signal mask yet. Let do_signal() deliver
* the signal on the way back to userspace, before the signal
* mask is restored.
*/
if (sigmask) {
memcpy(&current->saved_sigmask, &sigsaved,
sizeof(sigsaved));
set_thread_flag(TIF_RESTORE_SIGMASK);
}
} else if (sigmask)
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
return ret;
}
/*
* Most architectures can't handle 7-argument syscalls. So we provide a
* 6-argument version where the sixth argument is a pointer to a structure
* which has a pointer to the sigset_t itself followed by a size_t containing
* the sigset size.
*/
asmlinkage long sys_pselect6(int n, fd_set __user *inp, fd_set __user *outp,
fd_set __user *exp, struct timespec __user *tsp, void __user *sig)
{
size_t sigsetsize = 0;
sigset_t __user *up = NULL;
if (sig) {
if (!access_ok(VERIFY_READ, sig, sizeof(void *)+sizeof(size_t))
|| __get_user(up, (sigset_t * __user *)sig)
|| __get_user(sigsetsize,
(size_t * __user)(sig+sizeof(void *))))
return -EFAULT;
}
return sys_pselect7(n, inp, outp, exp, tsp, up, sigsetsize);
}
#endif /* TIF_RESTORE_SIGMASK */
struct poll_list { struct poll_list {
struct poll_list *next; struct poll_list *next;
int len; int len;
...@@ -436,16 +561,19 @@ static void do_pollfd(unsigned int num, struct pollfd * fdpage, ...@@ -436,16 +561,19 @@ static void do_pollfd(unsigned int num, struct pollfd * fdpage,
} }
static int do_poll(unsigned int nfds, struct poll_list *list, static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, long timeout) struct poll_wqueues *wait, s64 *timeout)
{ {
int count = 0; int count = 0;
poll_table* pt = &wait->pt; poll_table* pt = &wait->pt;
if (!timeout) /* Optimise the no-wait case */
if (!(*timeout))
pt = NULL; pt = NULL;
for (;;) { for (;;) {
struct poll_list *walk; struct poll_list *walk;
long __timeout;
set_current_state(TASK_INTERRUPTIBLE); set_current_state(TASK_INTERRUPTIBLE);
walk = list; walk = list;
while(walk != NULL) { while(walk != NULL) {
...@@ -453,18 +581,36 @@ static int do_poll(unsigned int nfds, struct poll_list *list, ...@@ -453,18 +581,36 @@ static int do_poll(unsigned int nfds, struct poll_list *list,
walk = walk->next; walk = walk->next;
} }
pt = NULL; pt = NULL;
if (count || !timeout || signal_pending(current)) if (count || !*timeout || signal_pending(current))
break; break;
count = wait->error; count = wait->error;
if (count) if (count)
break; break;
timeout = schedule_timeout(timeout);
if (*timeout < 0) {
/* Wait indefinitely */
__timeout = MAX_SCHEDULE_TIMEOUT;
} else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
/*
* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
* a loop
*/
__timeout = MAX_SCHEDULE_TIMEOUT - 1;
*timeout -= __timeout;
} else {
__timeout = *timeout;
*timeout = 0;
}
__timeout = schedule_timeout(__timeout);
if (*timeout >= 0)
*timeout += __timeout;
} }
__set_current_state(TASK_RUNNING); __set_current_state(TASK_RUNNING);
return count; return count;
} }
asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long timeout) int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{ {
struct poll_wqueues table; struct poll_wqueues table;
int fdcount, err; int fdcount, err;
...@@ -482,14 +628,6 @@ asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long ti ...@@ -482,14 +628,6 @@ asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long ti
if (nfds > max_fdset && nfds > OPEN_MAX) if (nfds > max_fdset && nfds > OPEN_MAX)
return -EINVAL; return -EINVAL;
if (timeout) {
/* Careful about overflow in the intermediate values */
if ((unsigned long) timeout < MAX_SCHEDULE_TIMEOUT / HZ)
timeout = (unsigned long)(timeout*HZ+999)/1000+1;
else /* Negative or overflow */
timeout = MAX_SCHEDULE_TIMEOUT;
}
poll_initwait(&table); poll_initwait(&table);
head = NULL; head = NULL;
...@@ -519,6 +657,7 @@ asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long ti ...@@ -519,6 +657,7 @@ asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long ti
} }
i -= pp->len; i -= pp->len;
} }
fdcount = do_poll(nfds, head, &table, timeout); fdcount = do_poll(nfds, head, &table, timeout);
/* OK, now copy the revents fields back to user space. */ /* OK, now copy the revents fields back to user space. */
...@@ -547,3 +686,98 @@ asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long ti ...@@ -547,3 +686,98 @@ asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long ti
poll_freewait(&table); poll_freewait(&table);
return err; return err;
} }
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,
long timeout_msecs)
{
s64 timeout_jiffies = 0;
if (timeout_msecs) {
#if HZ > 1000
/* We can only overflow if HZ > 1000 */
if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ)
timeout_jiffies = -1;
else
#endif
timeout_jiffies = msecs_to_jiffies(timeout_msecs);
}
return do_sys_poll(ufds, nfds, &timeout_jiffies);
}
#ifdef TIF_RESTORE_SIGMASK
asmlinkage long sys_ppoll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec __user *tsp, const sigset_t __user *sigmask,
size_t sigsetsize)
{
sigset_t ksigmask, sigsaved;
struct timespec ts;
s64 timeout = -1;
int ret;
if (tsp) {
if (copy_from_user(&ts, tsp, sizeof(ts)))
return -EFAULT;
/* Cast to u64 to make GCC stop complaining */
if ((u64)ts.tv_sec >= (u64)MAX_INT64_SECONDS)
timeout = -1; /* infinite */
else {
timeout = ROUND_UP(ts.tv_nsec, NSEC_PER_SEC/HZ);
timeout += ts.tv_sec * HZ;
}
}
if (sigmask) {
/* XXX: Don't preclude handling different sized sigset_t's. */
if (sigsetsize != sizeof(sigset_t))
return -EINVAL;
if (copy_from_user(&ksigmask, sigmask, sizeof(ksigmask)))
return -EFAULT;
sigdelsetmask(&ksigmask, sigmask(SIGKILL)|sigmask(SIGSTOP));
sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
}
ret = do_sys_poll(ufds, nfds, &timeout);
/* We can restart this syscall, usually */
if (ret == -EINTR) {
/*
* Don't restore the signal mask yet. Let do_signal() deliver
* the signal on the way back to userspace, before the signal
* mask is restored.
*/
if (sigmask) {
memcpy(&current->saved_sigmask, &sigsaved,
sizeof(sigsaved));
set_thread_flag(TIF_RESTORE_SIGMASK);
}
ret = -ERESTARTNOHAND;
} else if (sigmask)
sigprocmask(SIG_SETMASK, &sigsaved, NULL);
if (tsp && timeout >= 0) {
if (current->personality & STICKY_TIMEOUTS)
goto sticky;
/* Yes, we know it's actually an s64, but it's also positive. */
ts.tv_nsec = jiffies_to_usecs(do_div((*(u64*)&timeout), HZ)) * 1000;
ts.tv_sec = timeout;
if (copy_to_user(tsp, &ts, sizeof(ts))) {
sticky:
/*
* If an application puts its timeval in read-only
* memory, we don't want the Linux-specific update to
* the timeval to cause a fault after the select has
* completed successfully. However, because we're not
* updating the timeval, we can't restart the system
* call.
*/
if (ret == -ERESTARTNOHAND && timeout >= 0)
ret = -EINTR;
}
}
return ret;
}
#endif /* TIF_RESTORE_SIGMASK */
...@@ -92,7 +92,11 @@ void zero_fd_set(unsigned long nr, unsigned long *fdset) ...@@ -92,7 +92,11 @@ void zero_fd_set(unsigned long nr, unsigned long *fdset)
memset(fdset, 0, FDS_BYTES(nr)); memset(fdset, 0, FDS_BYTES(nr));
} }
extern int do_select(int n, fd_set_bits *fds, long *timeout); #define MAX_INT64_SECONDS (((s64)(~((u64)0)>>1)/HZ)-1)
extern int do_select(int n, fd_set_bits *fds, s64 *timeout);
extern int do_sys_poll(struct pollfd __user * ufds, unsigned int nfds,
s64 *timeout);
#endif /* KERNEL */ #endif /* KERNEL */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment