select: overhaul for time64

major changes are made alongside adding time64 syscall support to
account for issues found during research. select historically accepts
non-normalized (tv_usec not restricted to less than 1000000) timeouts,
and the kernel normalizes them, but the normalization code is buggy
and subject to integer overflows. since normalization is needed anyway
when using SYS_pselect6 or SYS_pselect6_time64 as the backend, simply
do it up-front to eliminate both code path complexity and the
possibility of kernel bugs.

as a side effect, select no longer updates the caller's timeout
timeval with the remaining time. previously, archs that used
SYS_select updated it and archs that used SYS_pselect6 didn't. this
change may turn out to be controversial and may need revisiting, but
in any case the old behavior was not strictly conforming.

POSIX allows modification of the timeout "upon successful completion",
but the Linux syscall modifies it upon unsuccessful completion (EINTR)
as well (and presumably each time the syscall stops and restarts
before it's known whether completion will be successful). it's
possible that this language does not reflect the actual intent of the
standard, since other historical implementations probably behaved like
Linux, but that should be clarified if there's a desire to bring the
old behavior back. regardless, programs that are depending on this are
not correct and are already broken on some archs we support.
This commit is contained in:
Rich Felker 2019-07-29 21:08:43 -04:00
parent 6a4a1691a0
commit 722a1ae335
1 changed files with 32 additions and 14 deletions

View File

@ -4,22 +4,40 @@
#include <errno.h>
#include "syscall.h"
#define IS32BIT(x) !((x)+0x80000000ULL>>32)
#define CLAMP(x) (int)(IS32BIT(x) ? (x) : 0x7fffffffU+((0ULL+(x))>>63))
int select(int n, fd_set *restrict rfds, fd_set *restrict wfds, fd_set *restrict efds, struct timeval *restrict tv)
{
#ifdef SYS_select
return syscall_cp(SYS_select, n, rfds, wfds, efds, tv);
#else
syscall_arg_t data[2] = { 0, _NSIG/8 };
struct timespec ts;
if (tv) {
if (tv->tv_sec < 0 || tv->tv_usec < 0)
return __syscall_ret(-EINVAL);
time_t extra_secs = tv->tv_usec / 1000000;
ts.tv_nsec = tv->tv_usec % 1000000 * 1000;
const time_t max_time = (1ULL<<8*sizeof(time_t)-1)-1;
ts.tv_sec = extra_secs > max_time - tv->tv_sec ?
max_time : tv->tv_sec + extra_secs;
time_t s = tv ? tv->tv_sec : 0;
suseconds_t us = tv ? tv->tv_usec : 0;
long ns;
const time_t max_time = (1ULL<<8*sizeof(time_t)-1)-1;
if (s<0 || us<0) return __syscall_ret(-EINVAL);
if (us/1000000 > max_time - s) {
s = max_time;
us = 999999;
ns = 999999999;
} else {
s += us/1000000;
us %= 1000000;
ns = us*1000;
}
return syscall_cp(SYS_pselect6, n, rfds, wfds, efds, tv ? &ts : 0, data);
#ifdef SYS_pselect6_time64
int r = -ENOSYS;
if (SYS_pselect6 == SYS_pselect6_time64 || !IS32BIT(s))
r = __syscall_cp(SYS_pselect6_time64, n, rfds, wfds, efds,
tv ? ((long long[]){s, ns}) : 0,
((syscall_arg_t[]){ 0, _NSIG/8 }));
if (SYS_pselect6 == SYS_pselect6_time64 || r!=-ENOSYS)
return __syscall_ret(r);
#endif
#ifdef SYS_select
return syscall_cp(SYS_select, n, rfds, wfds, efds, ((long[]){s, us}));
#else
return syscall_cp(SYS_pselect6, n, rfds, wfds, efds,
tv ? ((long[]){s, ns}) : 0, ((syscall_arg_t[]){ 0, _NSIG/8 }));
#endif
}