clock_gettime: add support for 32-bit vdso with 64-bit time_t
authorRich Felker <dalias@aerifal.cx>
Fri, 2 Aug 2019 18:04:45 +0000 (14:04 -0400)
committerRich Felker <dalias@aerifal.cx>
Fri, 2 Aug 2019 18:04:45 +0000 (14:04 -0400)
this fixes a major upcoming performance regression introduced by
commit 72f50245d018af0c31b38dec83c557a4e5dd1ea8, whereby 32-bit archs
would lose vdso clock_gettime after switching to 64-bit time_t, unless
the kernel supports time64 and provides a time64 version of the vdso
function. this would incur not just one but two syscalls: first, the
failed time64 syscall, then the fallback time32 one.

overflow of the 32-bit result is detected and triggers a revert to
syscalls. normally, on a system that's not Y2038-ready, this would
still overflow, but if the process has been migrated to a
time64-capable kernel or if the kernel has been hot-patched to add
time64 syscalls, it may conceivably work.

src/time/clock_gettime.c

index 4608375924a6c336479c2085c607e2d55ffba0a2..63e9f9c8fa4b5d72b0eb29bd949387901450d190 100644 (file)
@@ -8,9 +8,41 @@
 
 static void *volatile vdso_func;
 
+#ifdef VDSO_CGT32_SYM
+static void *volatile vdso_func_32;
+static int cgt_time32_wrap(clockid_t clk, struct timespec *ts)
+{
+       long ts32[2];
+       int (*f)(clockid_t, long[2]) =
+               (int (*)(clockid_t, long[2]))vdso_func_32;
+       int r = f(clk, ts32);
+       if (!r) {
+               /* Fallback to syscalls if time32 overflowed. Maybe
+                * we lucked out and somehow migrated to a kernel with
+                * time64 syscalls available. */
+               if (ts32[0] < 0) {
+                       a_cas_p(&vdso_func, (void *)cgt_time32_wrap, 0);
+                       return -ENOSYS;
+               }
+               ts->tv_sec = ts32[0];
+               ts->tv_nsec = ts32[1];
+       }
+       return r;
+}
+#endif
+
 static int cgt_init(clockid_t clk, struct timespec *ts)
 {
        void *p = __vdsosym(VDSO_CGT_VER, VDSO_CGT_SYM);
+#ifdef VDSO_CGT32_SYM
+       if (!p) {
+               void *q = __vdsosym(VDSO_CGT32_VER, VDSO_CGT32_SYM);
+               if (q) {
+                       a_cas_p(&vdso_func_32, 0, q);
+                       p = cgt_time32_wrap;
+               }
+       }
+#endif
        int (*f)(clockid_t, struct timespec *) =
                (int (*)(clockid_t, struct timespec *))p;
        a_cas_p(&vdso_func, (void *)cgt_init, p);