[[PATCH,RFC,2] 12/63] Y2038: add struct __timespec64

Message ID 20180418201819.15952-13-albert.aribaud@3adev.fr
State New, archived
Headers

Commit Message

Albert ARIBAUD April 18, 2018, 8:17 p.m. UTC
  To be Y2038-proof, struct __timespec64 needs its tv_sec field to
be a __time64_t rather than a __time_t. However, the question is
which type should the tv_nsec field be.

Keeping tv_nsec a long (32-bit) would be compatible with Posix
requirements but would result in the GLIBC struct timespec being
binary-incompatible with the Linux 64-bit struct timespec, which
contains a 64-bit, not 32-bit, signed tv_nsec field.

In order to maintain Posix compatibility yet simplify conversion
between Posix and Linux struct timespec values, the Y2038-proof
struct time stores its tv_nsec field as a 32-bit signed integer
plus a padding which can serve as a 64-bit sign extension. This
both meets Posix requirements and makes the GLIBC and Linux
struct timespec binary compatible.

Note that in the API (which is not modified here, and will be
later alongside all Y2038-sensitive APIs), this padding is made
'invisible' by defining it as an anonymous bitfield, whereas
the struct __timespec64 introduced here has a named field for
the padding, allowing implementations to read and write it.

Also, provide static inline functions and macros for checking
and converting between 32-bit and 64-bit timespecs.
---
 include/time.h                    | 49 +++++++++++++++++++++++++++++++++++++++
 time/bits/types/struct_timespec.h | 23 ++++++++++++++++++
 2 files changed, 72 insertions(+)
  

Patch

diff --git a/include/time.h b/include/time.h
index d74f66e7c6..16286178e1 100644
--- a/include/time.h
+++ b/include/time.h
@@ -130,5 +130,54 @@  fits_in_time_t (__time64_t t)
   return t == (time_t) t;
 }
 
+/* convert a known valid struct timespec into a struct __timespec64 */
+static inline void
+valid_timespec_to_timespec64(const struct timespec *ts32,
+			     struct __timespec64 *ts64)
+{
+  ts64->tv_sec = ts32->tv_sec;
+  ts64->tv_nsec = ts32->tv_nsec;
+  /* we only need to zero ts64->tv_pad if we pass it to the kernel */
+}
+
+/* convert a known valid struct __timespec64 into a struct timespec */
+static inline void
+valid_timespec64_to_timespec(const struct __timespec64 *ts64,
+			     struct timespec *ts32)
+{
+  ts32->tv_sec = (time_t) ts64->tv_sec;
+  ts32->tv_nsec = ts64->tv_nsec;
+}
+
+/* check if a value lies with the valid nanoseconds range */
+#define IS_VALID_NANOSECONDS(ns) (ns >= 0 && ns <= 999999999)
+
+/* check and convert a struct timespec into a struct __timespec64 */
+static inline bool timespec_to_timespec64(const struct timespec *ts32,
+					  struct __timespec64 *ts64)
+{
+  /* check that ts32 holds a valid count of nanoseconds */
+  if (! IS_VALID_NANOSECONDS(ts32->tv_nsec))
+    return false;
+  /* all ts32 fields can fit in ts64, so copy them */
+  valid_timespec_to_timespec64(ts32, ts64);
+  /* we only need to zero ts64->tv_pad if we pass it to the kernel */
+  return true;
+}
+
+/* check and convert a struct __timespec64 into a struct timespec */
+static inline bool timespec64_to_timespec(const struct __timespec64 *ts64,
+					  struct timespec *ts32)
+{
+  /* check that tv_nsec holds a valid count of nanoseconds */
+  if (! IS_VALID_NANOSECONDS(ts64->tv_nsec))
+    return false;
+  /* check that tv_sec can fit in a __time_t */
+  if (! fits_in_time_t(ts64->tv_sec))
+    return false;
+  /* all ts64 fields can fit in ts32, so copy them */
+  valid_timespec64_to_timespec(ts64, ts32);
+  return true;
+}
 #endif
 #endif
diff --git a/time/bits/types/struct_timespec.h b/time/bits/types/struct_timespec.h
index 644db9fdb6..7461ac0836 100644
--- a/time/bits/types/struct_timespec.h
+++ b/time/bits/types/struct_timespec.h
@@ -2,6 +2,7 @@ 
 #define __timespec_defined 1
 
 #include <bits/types.h>
+#include <endian.h>
 
 /* POSIX.1b structure for a time value.  This is like a `struct timeval' but
    has nanoseconds instead of microseconds.  */
@@ -11,4 +12,26 @@  struct timespec
   __syscall_slong_t tv_nsec;	/* Nanoseconds.  */
 };
 
+/* 64-bit time version. To keep tings Posix-ish, we keep the nanoseconds
+   field a signed long, but since Linux has a 64-bit signed int, we pad it
+   with a 32-bit int, which should always be 0.
+   Note that the public type has an anonymous bitfield as padding, so that
+   it cannot be written into (or read from). */ 
+#if BYTE_ORDER == BIG_ENDIAN
+struct __timespec64
+{
+  __time64_t tv_sec;		/* Seconds */
+  int tv_pad: 32;		/* Padding named for checking/setting */
+  __syscall_slong_t tv_nsec;	/* Nanoseconds */
+};
+#else
+struct __timespec64
+{
+  __time64_t tv_sec;		/* Seconds */
+  __syscall_slong_t tv_nsec;	/* Nanoseconds */
+  int tv_pad: 32;		/* Padding named for checking/setting */
+};
+
+#endif
+
 #endif