select: overhaul for time64
authorRich Felker <dalias@aerifal.cx>
Tue, 30 Jul 2019 01:08:43 +0000 (21:08 -0400)
committerRich Felker <dalias@aerifal.cx>
Tue, 30 Jul 2019 18:23:56 +0000 (14:23 -0400)
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.

src/select/select.c

index 02fd75c3828aca664bf6d695d82e30003c34619a..e84c887f668bbc4e6082a417e01d3e45811699b5 100644 (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)
 {
+       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;
+       }
+
+#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, tv);
+       return syscall_cp(SYS_select, n, rfds, wfds, efds, ((long[]){s, us}));
 #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;
-       }
-       return syscall_cp(SYS_pselect6, n, rfds, wfds, efds, tv ? &ts : 0, data);
+       return syscall_cp(SYS_pselect6, n, rfds, wfds, efds,
+               tv ? ((long[]){s, ns}) : 0, ((syscall_arg_t[]){ 0, _NSIG/8 }));
 #endif
 }