A lot of system calls pass a 'struct timespec' from or to user space, and we want to change that type to be based on a 64-bit time_t by default.
This introduces a new type struct __kernel_timespec, which has the format we want to use eventually, but also has an override so all architectures that do not define CONFIG_COMPAT_TIME yet still get the old behavior. Once all architectures set this, we can remove that override.
This also introduces a get_timespec64/put_timespec64 set of functions that convert between a __kernel_timespec in user space and a timespec64 in kernel space.
The current behavior of get_timespec64 explicitly zeroes the upper half of the tv_nsec member, to allow user space to define its own 'struct timespec' with some padding in it. Whether this is a good or bad idea is open for discussion.
Signed-off-by: Arnd Bergmann arnd@arndb.de --- include/linux/time64.h | 17 +++++++++++------ include/uapi/linux/time.h | 17 +++++++++++++++++ kernel/time/time.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/include/linux/time64.h b/include/linux/time64.h index a3831478d9cf..880ebe4b4ba4 100644 --- a/include/linux/time64.h +++ b/include/linux/time64.h @@ -1,14 +1,12 @@ #ifndef _LINUX_TIME64_H #define _LINUX_TIME64_H
-#include <uapi/linux/time.h> - typedef __s64 time64_t;
-/* - * This wants to go into uapi/linux/time.h once we agreed about the - * userspace interfaces. - */ +#ifndef CONFIG_COMPAT_TIME +# define __kernel_timespec timespec +#endif + #if __BITS_PER_LONG == 64 # define timespec64 timespec #else @@ -18,6 +16,8 @@ struct timespec64 { }; #endif
+#include <uapi/linux/time.h> + /* Parameters used to convert the timespec values: */ #define MSEC_PER_SEC 1000L #define USEC_PER_MSEC 1000L @@ -187,4 +187,9 @@ static __always_inline void timespec64_add_ns(struct timespec64 *a, u64 ns)
#endif
+extern int get_timespec64(struct timespec64 *ts, + const struct __kernel_timespec __user *uts); +extern int put_timespec64(const struct timespec64 *ts, + struct __kernel_timespec __user *uts); + #endif /* _LINUX_TIME64_H */ diff --git a/include/uapi/linux/time.h b/include/uapi/linux/time.h index e75e1b6ff27f..72d894df3013 100644 --- a/include/uapi/linux/time.h +++ b/include/uapi/linux/time.h @@ -66,4 +66,21 @@ struct itimerval { */ #define TIMER_ABSTIME 0x01
+/* types based on 64-bit time_t */ +#ifndef __kernel_timespec +typedef __s64 __kernel_time64_t; + +struct __kernel_timespec { + __kernel_time64_t tv_sec; + __s64 tv_nsec; +}; +#endif + +#ifndef __kernel_itimerspec +struct __kernel_itimerspec { + struct __kernel_timespec it_interval; + struct __kernel_timespec it_value; +}; +#endif + #endif /* _UAPI_LINUX_TIME_H */ diff --git a/kernel/time/time.c b/kernel/time/time.c index 2c85b7724af4..845af1db66fa 100644 --- a/kernel/time/time.c +++ b/kernel/time/time.c @@ -30,6 +30,7 @@ #include <linux/export.h> #include <linux/timex.h> #include <linux/capability.h> +#include <linux/compat.h> #include <linux/timekeeper_internal.h> #include <linux/errno.h> #include <linux/syscalls.h> @@ -37,6 +38,7 @@ #include <linux/fs.h> #include <linux/math64.h> #include <linux/ptrace.h> +#include <linux/time64.h>
#include <asm/uaccess.h> #include <asm/unistd.h> @@ -783,3 +785,33 @@ struct timespec timespec_add_safe(const struct timespec lhs,
return res; } + +int get_timespec64(struct timespec64 *ts, + const struct __kernel_timespec __user *uts) +{ + struct __kernel_timespec kts; + int ret; + + ret = copy_from_user(&kts, uts, sizeof(kts)); + if (ret) + return -EFAULT; + + ts->tv_sec = kts.tv_sec; + if (!IS_ENABLED(CONFIG_64BIT) || is_compat_task()) + kts.tv_nsec &= 0xfffffffful; + ts->tv_nsec = kts.tv_nsec; + + return 0; +} +EXPORT_SYMBOL_GPL(get_timespec64); + +int put_timespec64(const struct timespec64 *ts, + struct __kernel_timespec __user *uts) +{ + struct __kernel_timespec kts = { + .tv_sec = ts->tv_sec, + .tv_nsec = ts->tv_nsec + }; + return copy_to_user(uts, &kts, sizeof(kts)) ? -EFAULT : 0; +} +EXPORT_SYMBOL_GPL(put_timespec64);