[v2,7/7] y2038: linux: Provide __clock_nanosleep64 implementation

Message ID 20190429104613.16209-8-lukma@denx.de
State Superseded
Headers

Commit Message

Lukasz Majewski April 29, 2019, 10:46 a.m. UTC
  This patch provides new __clock_nanosleep64 explicit 64 bit function
for sleeping specified time on specified clock ID. Moreover, a
32 bit version - __clock_nanosleep has been refactored to internally
use __clock_nanosleep64.

The __clock_nanosleep is now supposed to be used on 32 bit systems -
hence the necessary checks and conversions to 64 bit type. After this
change it is intrinsically Y2038 safe.

The new 64 bit syscall (clock_nanosleep_time64) available from Linux
5.1+ has been used when applicable on 32 bit systems.

The __ASSUME_64BIT_TIME flag indicates if the Linux kernel provides 64 bit
version of clock_nanosleep (i.e. clock_nanosleep_time64). If defined -
return value is returned unconditionally. If not - the 32 bit version of
this syscall is executed instead.

When working on 32 bit systems without Y2038 time support the
clock_nanosleep returns error when received tv_sec is wrong (i.e.
overflowed). Moreover, the correctness of tv_nsec is checked.
The data passed to it is checked as well.

The execution path on 64 bit systems has not been changed or affected
in any way.

Tests:
- The code has been tested with x86_64/x86 (native compilation):
make PARALLELMFLAGS="-j8" && make xcheck PARALLELMFLAGS="-j8"

- Run specific tests on ARM/x86 32bit systems (qemu):
https://github.com/lmajewski/meta-y2038
and run tests:
https://github.com/lmajewski/y2038-tests/commits/master
on kernels with and without 64 bit time support.

No regressions were observed.

* include/time.h (__clock_nanosleep64):
  Add __clock_nanosleep alias according to __TIMESIZE define
* sysdeps/unix/sysv/linux/clock_nanosleep.c (__clock_nanosleep):
  Refactor this function to be used only on 32 bit machines as a
  wrapper on __clock_nanosleep64.
* sysdeps/unix/sysv/linux/clock_nanosleep.c (__clock_nanosleep64):
  Add
* sysdeps/unix/sysv/linux/clock_nanosleep.c (__clock_nanosleep64):
  Use clock_nanosleep_time64 kernel syscall (available from
  5.1-rc1+ Linux) by 32 bit Y2038 safe systems

---
Changes for v2:
- Add support for __ASSUME_64BIT_TIME flag when Linux kernel provides syscalls
supporting 64 bit time on 32 bit systems
- Provide fallback to 32 bit version of clock_nanosleep when
clock_nanosleep_time64 is not available
- Do not copy *req to timespec - this seems like an overkill as in
clock_nanosleep() the 32 bit struct timespec is copied to internal 64 bit
struct __timespec64
---
 include/time.h                            |  9 ++++
 sysdeps/unix/sysv/linux/clock_nanosleep.c | 71 +++++++++++++++++++++++++++++--
 2 files changed, 76 insertions(+), 4 deletions(-)
  

Patch

diff --git a/include/time.h b/include/time.h
index 73d8f1a0cc..e37e623bf4 100644
--- a/include/time.h
+++ b/include/time.h
@@ -156,6 +156,15 @@  extern int __clock_getres64 (clockid_t clock_id,
 libc_hidden_proto (__clock_getres64);
 #endif
 
+#if __TIMESIZE == 64
+# define __clock_nanosleep64 __clock_nanosleep
+#else
+extern int __clock_nanosleep64 (clockid_t clock_id, int flags,
+                                const struct __timespec64 *req,
+                                struct __timespec64 *rem);
+libc_hidden_proto (__clock_nanosleep64);
+#endif
+
 /* Compute the `struct tm' representation of T,
    offset OFFSET seconds east of UTC,
    and store year, yday, mon, mday, wday, hour, min, sec into *TP.
diff --git a/sysdeps/unix/sysv/linux/clock_nanosleep.c b/sysdeps/unix/sysv/linux/clock_nanosleep.c
index 0cb6614dc9..a2b3896950 100644
--- a/sysdeps/unix/sysv/linux/clock_nanosleep.c
+++ b/sysdeps/unix/sysv/linux/clock_nanosleep.c
@@ -25,8 +25,8 @@ 
 /* We can simply use the syscall.  The CPU clocks are not supported
    with this function.  */
 int
-__clock_nanosleep (clockid_t clock_id, int flags, const struct timespec *req,
-		   struct timespec *rem)
+__clock_nanosleep64 (clockid_t clock_id, int flags,
+                     const struct __timespec64 *req, struct __timespec64 *rem)
 {
   if (clock_id == CLOCK_THREAD_CPUTIME_ID)
     return EINVAL;
@@ -36,9 +36,72 @@  __clock_nanosleep (clockid_t clock_id, int flags, const struct timespec *req,
   /* If the call is interrupted by a signal handler or encounters an error,
      it returns a positive value similar to errno.  */
   INTERNAL_SYSCALL_DECL (err);
+#if defined (__TIMESIZE) && __TIMESIZE != 64
+  int r;
+# ifdef __NR_clock_nanosleep_time64
+  /* For 32 bit systems with no Y2038 support the *req may have tv_pad
+     with some random values as *req from __clock_nanosleep is converted
+     to automatically allocated struct __timespec64 (req64).
+
+     For 32 bit systems being Y2038 safe the tv_pad may be not zero,
+     as glibc exported struct timespec has 64 bit tv_sec, 32 bit
+     tv_nsec (to be still POSIX compliant -> long tv_nsec ) and 32
+     bits of unnamed padding.
+     If user program allocates the struct timespec automatically, the
+     padding may have random value and as being directly passed to
+     *req needs to be cleared.  */
+  timespec64_clear_padding (req);
+  r = INTERNAL_SYSCALL_CANCEL (clock_nanosleep_time64, err, clock_id,
+                               flags, req, rem);
+  int ret = (INTERNAL_SYSCALL_ERROR_P (r, err)
+             ? INTERNAL_SYSCALL_ERRNO (r, err) : 0);
+#  ifdef __ASSUME_64BIT_TIME
+  return ret;
+#  else
+  if (r == 0 || errno != ENOSYS)
+    /* Preserve non-error/non-ENOSYS return values.  */
+    return ret;
+#  endif
+# endif
+  struct timespec req32, rem32;
+  valid_timespec64_to_timespec(req, &req32);
+  r = INTERNAL_SYSCALL_CANCEL (clock_nanosleep, err, clock_id, flags,
+                               &req32, &rem32);
+
+  if (! INTERNAL_SYSCALL_ERROR_P (r, err))
+      valid_timespec_to_timespec64(&rem32, rem);
+#else
   int r = INTERNAL_SYSCALL_CANCEL (clock_nanosleep, err, clock_id, flags,
-				   req, rem);
+                                   req, rem);
+#endif
   return (INTERNAL_SYSCALL_ERROR_P (r, err)
-	  ? INTERNAL_SYSCALL_ERRNO (r, err) : 0);
+          ? INTERNAL_SYSCALL_ERRNO (r, err) : 0);
 }
 weak_alias (__clock_nanosleep, clock_nanosleep)
+
+#if __TIMESIZE != 64
+int
+__clock_nanosleep (clockid_t clock_id, int flags, const struct timespec *req,
+                   struct timespec *rem)
+{
+  struct __timespec64 req64, rem64;
+  int retval;
+
+  if (! in_time_t_range (req->tv_sec))
+    {
+      __set_errno (EOVERFLOW);
+      return -1;
+    }
+
+  valid_timespec_to_timespec64 (req, &req64);
+  retval = __clock_nanosleep64 (clock_id, flags, &req64, &rem64);
+
+  if (! retval && rem && ! timespec64_to_timespec (&rem64, rem))
+    {
+      __set_errno (EOVERFLOW);
+      return -1;
+    }
+
+  return retval;
+}
+#endif