nolibc currently uses 32-bit types for various APIs. These are problematic as their reduced value range can lead to truncated values.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- Thomas Weißschuh (12): tools/nolibc: use 64-bit ino_t tools/nolibc: handle 64-bit off_t for llseek tools/nolibc: prefer the llseek syscall tools/nolibc: use 64-bit off_t tools/nolibc: remove now superfluous overflow check in llseek tools/nolibc: remove more __nolibc_enosys() fallbacks tools/nolibc: prefer explicit 64-bit time-related system calls tools/nolibc: gettimeofday(): avoid libgcc 64-bit divisions tools/nolibc: use a custom struct timespec tools/nolibc: always use 64-bit time types selftests/nolibc: test compatibility of timespec and __kernel_timespec tools/nolibc: remove time conversions
tools/include/nolibc/arch-s390.h | 3 + tools/include/nolibc/poll.h | 12 ++-- tools/include/nolibc/std.h | 6 +- tools/include/nolibc/sys.h | 21 +++--- tools/include/nolibc/sys/time.h | 2 +- tools/include/nolibc/sys/timerfd.h | 20 +----- tools/include/nolibc/time.h | 96 ++++++---------------------- tools/include/nolibc/types.h | 9 ++- tools/testing/selftests/nolibc/nolibc-test.c | 18 ++++++ 9 files changed, 68 insertions(+), 119 deletions(-) --- base-commit: 90ee85c0e1e4b5804ceebbd731653e10ef3849a6 change-id: 20251001-nolibc-uapi-types-1c072d10fcc7
Best regards,
The kernel uses 64-bit values for inode numbers. Currently these might be truncated to 32-bit when assigned to nolibc's ino_t values.
Switch to 64-bit ino_t consistently.
As ino_t is never used directly in kernel ABIs, no systemcall wrappers need to be adapted.
Suggested-by: Arnd Bergmann arnd@arndb.de Link: https://lore.kernel.org/lkml/cec27d94-c99d-4c57-9a12-275ea663dda8@app.fastma... Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/std.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/include/nolibc/std.h b/tools/include/nolibc/std.h index 2c1ad23b9b5c..eae457d60858 100644 --- a/tools/include/nolibc/std.h +++ b/tools/include/nolibc/std.h @@ -20,7 +20,7 @@
/* those are commonly provided by sys/types.h */ typedef unsigned int dev_t; -typedef unsigned long ino_t; +typedef uint64_t ino_t; typedef unsigned int mode_t; typedef signed int pid_t; typedef unsigned int uid_t;
Correctly handle 64-bit off_t values in preparation for 64-bit off_t on 32-bit architectures.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/sys.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 386ed80aead0..321a3bd16ff4 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -599,8 +599,7 @@ off_t sys_lseek(int fd, off_t offset, int whence) off_t result; int ret;
- /* Only exists on 32bit where nolibc off_t is also 32bit */ - ret = my_syscall5(__NR_llseek, fd, 0, offset, &loff, whence); + ret = my_syscall5(__NR_llseek, fd, offset >> 32, (uint32_t)offset, &loff, whence); if (ret < 0) result = ret; else if (loff != (off_t)loff)
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
Correctly handle 64-bit off_t values in preparation for 64-bit off_t on 32-bit architectures.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
tools/include/nolibc/sys.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 386ed80aead0..321a3bd16ff4 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -599,8 +599,7 @@ off_t sys_lseek(int fd, off_t offset, int whence) off_t result; int ret;
- /* Only exists on 32bit where nolibc off_t is also 32bit */
- ret = my_syscall5(__NR_llseek, fd, 0, offset, &loff, whence);
- ret = my_syscall5(__NR_llseek, fd, offset >> 32, (uint32_t)offset,
&loff, whence);
Acked-by: Arnd Bergmann arnd@arndb.de
This one is easier than some of the others because it passes the two halves explictly. It's a bit trickier for preadv/pwritev, preadv2/pwritev2, fallocate, truncate64/ftruncate64, sync_file_range, and fadvise64/fadvise64_64, all of which have slightly tricky calling conventions. Not sure if any of these are currently supported in nolibc.
Arnd
Make sure to always use the 64-bit safe system call in preparation for 64-bit off_t on 32 bit architectures.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/sys.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 321a3bd16ff4..58cd2bb0f6ce 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -592,9 +592,7 @@ int link(const char *old, const char *new) static __attribute__((unused)) off_t sys_lseek(int fd, off_t offset, int whence) { -#if defined(__NR_lseek) - return my_syscall3(__NR_lseek, fd, offset, whence); -#else +#if defined(__NR_llseek) __kernel_loff_t loff = 0; off_t result; int ret; @@ -608,6 +606,8 @@ off_t sys_lseek(int fd, off_t offset, int whence) result = loff;
return result; +#else + return my_syscall3(__NR_lseek, fd, offset, whence); #endif }
The kernel uses 64-bit values for file offsets. Currently these might be truncated to 32-bit when assigned to nolibc's off_t values.
Switch to 64-bit off_t consistently.
Suggested-by: Arnd Bergmann arnd@arndb.de Link: https://lore.kernel.org/lkml/cec27d94-c99d-4c57-9a12-275ea663dda8@app.fastma... Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/std.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/include/nolibc/std.h b/tools/include/nolibc/std.h index eae457d60858..392f4dd94158 100644 --- a/tools/include/nolibc/std.h +++ b/tools/include/nolibc/std.h @@ -26,7 +26,7 @@ typedef signed int pid_t; typedef unsigned int uid_t; typedef unsigned int gid_t; typedef unsigned long nlink_t; -typedef signed long off_t; +typedef int64_t off_t; typedef signed long blksize_t; typedef signed long blkcnt_t; typedef __kernel_time_t time_t;
As off_t is now always 64-bit wide this overflow can not happen anymore, remove the check.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/sys.h | 2 -- 1 file changed, 2 deletions(-)
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 58cd2bb0f6ce..e91b7d947161 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -600,8 +600,6 @@ off_t sys_lseek(int fd, off_t offset, int whence) ret = my_syscall5(__NR_llseek, fd, offset >> 32, (uint32_t)offset, &loff, whence); if (ret < 0) result = ret; - else if (loff != (off_t)loff) - result = -EOVERFLOW; else result = loff;
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
As off_t is now always 64-bit wide this overflow can not happen anymore, remove the check.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Reviewed-by: Arnd Bergmann arnd@arndb.de
Commit e6366101ce1f ("tools/nolibc: remove __nolibc_enosys() fallback from time64-related functions") removed many of these fallbacks but forgot a few.
Finish the job.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/time.h | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-)
diff --git a/tools/include/nolibc/time.h b/tools/include/nolibc/time.h index 6c276b8d646a..48e78f8becf9 100644 --- a/tools/include/nolibc/time.h +++ b/tools/include/nolibc/time.h @@ -89,13 +89,11 @@ int sys_clock_settime(clockid_t clockid, struct timespec *tp) { #if defined(__NR_clock_settime) return my_syscall2(__NR_clock_settime, clockid, tp); -#elif defined(__NR_clock_settime64) +#else struct __kernel_timespec ktp;
__nolibc_timespec_user_to_kernel(tp, &ktp); return my_syscall2(__NR_clock_settime64, clockid, &ktp); -#else - return __nolibc_enosys(__func__, clockid, tp); #endif }
@@ -111,7 +109,7 @@ int sys_clock_nanosleep(clockid_t clockid, int flags, const struct timespec *rqt { #if defined(__NR_clock_nanosleep) return my_syscall4(__NR_clock_nanosleep, clockid, flags, rqtp, rmtp); -#elif defined(__NR_clock_nanosleep_time64) +#else struct __kernel_timespec krqtp, krmtp; int ret;
@@ -120,8 +118,6 @@ int sys_clock_nanosleep(clockid_t clockid, int flags, const struct timespec *rqt if (rmtp) __nolibc_timespec_kernel_to_user(&krmtp, rmtp); return ret; -#else - return __nolibc_enosys(__func__, clockid, flags, rqtp, rmtp); #endif }
@@ -195,7 +191,7 @@ int sys_timer_gettime(timer_t timerid, struct itimerspec *curr_value) { #if defined(__NR_timer_gettime) return my_syscall2(__NR_timer_gettime, timerid, curr_value); -#elif defined(__NR_timer_gettime64) +#else struct __kernel_itimerspec kcurr_value; int ret;
@@ -203,8 +199,6 @@ int sys_timer_gettime(timer_t timerid, struct itimerspec *curr_value) __nolibc_timespec_kernel_to_user(&kcurr_value.it_interval, &curr_value->it_interval); __nolibc_timespec_kernel_to_user(&kcurr_value.it_value, &curr_value->it_value); return ret; -#else - return __nolibc_enosys(__func__, timerid, curr_value); #endif }
@@ -220,7 +214,7 @@ int sys_timer_settime(timer_t timerid, int flags, { #if defined(__NR_timer_settime) return my_syscall4(__NR_timer_settime, timerid, flags, new_value, old_value); -#elif defined(__NR_timer_settime64) +#else struct __kernel_itimerspec knew_value, kold_value; int ret;
@@ -232,8 +226,6 @@ int sys_timer_settime(timer_t timerid, int flags, __nolibc_timespec_kernel_to_user(&kold_value.it_value, &old_value->it_value); } return ret; -#else - return __nolibc_enosys(__func__, timerid, flags, new_value, old_value); #endif }
Make sure to always use the 64-bit safe system calls in preparation for 64-bit time_t on 32-bit architectures.
Also prevent issues on kernels which disable CONFIG_COMPAT_32BIT_TIME and therefore don't provide the 32-bit system calls anymore.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/poll.h | 12 ++++++------ tools/include/nolibc/sys.h | 10 +++++----- tools/include/nolibc/time.h | 36 ++++++++++++++++++------------------ 3 files changed, 29 insertions(+), 29 deletions(-)
diff --git a/tools/include/nolibc/poll.h b/tools/include/nolibc/poll.h index 0d053f93ea99..5ff911b73b34 100644 --- a/tools/include/nolibc/poll.h +++ b/tools/include/nolibc/poll.h @@ -23,22 +23,22 @@ static __attribute__((unused)) int sys_poll(struct pollfd *fds, int nfds, int timeout) { -#if defined(__NR_ppoll) - struct timespec t; +#if defined(__NR_ppoll_time64) + struct __kernel_timespec t;
if (timeout >= 0) { t.tv_sec = timeout / 1000; t.tv_nsec = (timeout % 1000) * 1000000; } - return my_syscall5(__NR_ppoll, fds, nfds, (timeout >= 0) ? &t : NULL, NULL, 0); -#elif defined(__NR_ppoll_time64) - struct __kernel_timespec t; + return my_syscall5(__NR_ppoll_time64, fds, nfds, (timeout >= 0) ? &t : NULL, NULL, 0); +#elif defined(__NR_ppoll) + struct timespec t;
if (timeout >= 0) { t.tv_sec = timeout / 1000; t.tv_nsec = (timeout % 1000) * 1000000; } - return my_syscall5(__NR_ppoll_time64, fds, nfds, (timeout >= 0) ? &t : NULL, NULL, 0); + return my_syscall5(__NR_ppoll, fds, nfds, (timeout >= 0) ? &t : NULL, NULL, 0); #else return my_syscall3(__NR_poll, fds, nfds, timeout); #endif diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index e91b7d947161..10c517a38f86 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -772,22 +772,22 @@ int sys_select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeva return my_syscall5(__NR__newselect, nfds, rfds, wfds, efds, timeout); #elif defined(__NR_select) return my_syscall5(__NR_select, nfds, rfds, wfds, efds, timeout); -#elif defined(__NR_pselect6) - struct timespec t; +#elif defined(__NR_pselect6_time64) + struct __kernel_timespec t;
if (timeout) { t.tv_sec = timeout->tv_sec; t.tv_nsec = timeout->tv_usec * 1000; } - return my_syscall6(__NR_pselect6, nfds, rfds, wfds, efds, timeout ? &t : NULL, NULL); + return my_syscall6(__NR_pselect6_time64, nfds, rfds, wfds, efds, timeout ? &t : NULL, NULL); #else - struct __kernel_timespec t; + struct timespec t;
if (timeout) { t.tv_sec = timeout->tv_sec; t.tv_nsec = timeout->tv_usec * 1000; } - return my_syscall6(__NR_pselect6_time64, nfds, rfds, wfds, efds, timeout ? &t : NULL, NULL); + return my_syscall6(__NR_pselect6, nfds, rfds, wfds, efds, timeout ? &t : NULL, NULL); #endif }
diff --git a/tools/include/nolibc/time.h b/tools/include/nolibc/time.h index 48e78f8becf9..45df9b09d7b6 100644 --- a/tools/include/nolibc/time.h +++ b/tools/include/nolibc/time.h @@ -43,9 +43,7 @@ void __nolibc_timespec_kernel_to_user(const struct __kernel_timespec *kts, struc static __attribute__((unused)) int sys_clock_getres(clockid_t clockid, struct timespec *res) { -#if defined(__NR_clock_getres) - return my_syscall2(__NR_clock_getres, clockid, res); -#else +#if defined(__NR_clock_getres_time64) struct __kernel_timespec kres; int ret;
@@ -53,6 +51,8 @@ int sys_clock_getres(clockid_t clockid, struct timespec *res) if (res) __nolibc_timespec_kernel_to_user(&kres, res); return ret; +#else + return my_syscall2(__NR_clock_getres, clockid, res); #endif }
@@ -65,9 +65,7 @@ int clock_getres(clockid_t clockid, struct timespec *res) static __attribute__((unused)) int sys_clock_gettime(clockid_t clockid, struct timespec *tp) { -#if defined(__NR_clock_gettime) - return my_syscall2(__NR_clock_gettime, clockid, tp); -#else +#if defined(__NR_clock_gettime64) struct __kernel_timespec ktp; int ret;
@@ -75,6 +73,8 @@ int sys_clock_gettime(clockid_t clockid, struct timespec *tp) if (tp) __nolibc_timespec_kernel_to_user(&ktp, tp); return ret; +#else + return my_syscall2(__NR_clock_gettime, clockid, tp); #endif }
@@ -87,13 +87,13 @@ int clock_gettime(clockid_t clockid, struct timespec *tp) static __attribute__((unused)) int sys_clock_settime(clockid_t clockid, struct timespec *tp) { -#if defined(__NR_clock_settime) - return my_syscall2(__NR_clock_settime, clockid, tp); -#else +#if defined(__NR_clock_settime64) struct __kernel_timespec ktp;
__nolibc_timespec_user_to_kernel(tp, &ktp); return my_syscall2(__NR_clock_settime64, clockid, &ktp); +#else + return my_syscall2(__NR_clock_settime, clockid, tp); #endif }
@@ -107,9 +107,7 @@ static __attribute__((unused)) int sys_clock_nanosleep(clockid_t clockid, int flags, const struct timespec *rqtp, struct timespec *rmtp) { -#if defined(__NR_clock_nanosleep) - return my_syscall4(__NR_clock_nanosleep, clockid, flags, rqtp, rmtp); -#else +#if defined(__NR_clock_nanosleep_time64) struct __kernel_timespec krqtp, krmtp; int ret;
@@ -118,6 +116,8 @@ int sys_clock_nanosleep(clockid_t clockid, int flags, const struct timespec *rqt if (rmtp) __nolibc_timespec_kernel_to_user(&krmtp, rmtp); return ret; +#else + return my_syscall4(__NR_clock_nanosleep, clockid, flags, rqtp, rmtp); #endif }
@@ -189,9 +189,7 @@ int timer_delete(timer_t timerid) static __attribute__((unused)) int sys_timer_gettime(timer_t timerid, struct itimerspec *curr_value) { -#if defined(__NR_timer_gettime) - return my_syscall2(__NR_timer_gettime, timerid, curr_value); -#else +#if defined(__NR_timer_gettime64) struct __kernel_itimerspec kcurr_value; int ret;
@@ -199,6 +197,8 @@ int sys_timer_gettime(timer_t timerid, struct itimerspec *curr_value) __nolibc_timespec_kernel_to_user(&kcurr_value.it_interval, &curr_value->it_interval); __nolibc_timespec_kernel_to_user(&kcurr_value.it_value, &curr_value->it_value); return ret; +#else + return my_syscall2(__NR_timer_gettime, timerid, curr_value); #endif }
@@ -212,9 +212,7 @@ static __attribute__((unused)) int sys_timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { -#if defined(__NR_timer_settime) - return my_syscall4(__NR_timer_settime, timerid, flags, new_value, old_value); -#else +#if defined(__NR_timer_settime64) struct __kernel_itimerspec knew_value, kold_value; int ret;
@@ -226,6 +224,8 @@ int sys_timer_settime(timer_t timerid, int flags, __nolibc_timespec_kernel_to_user(&kold_value.it_value, &old_value->it_value); } return ret; +#else + return my_syscall4(__NR_timer_settime, timerid, flags, new_value, old_value); #endif }
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
Make sure to always use the 64-bit safe system calls in preparation for 64-bit time_t on 32-bit architectures.
Also prevent issues on kernels which disable CONFIG_COMPAT_32BIT_TIME and therefore don't provide the 32-bit system calls anymore.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Thanks for working on this!
-#if defined(__NR_ppoll)
- struct timespec t;
+#if defined(__NR_ppoll_time64)
struct __kernel_timespec t;
if (timeout >= 0) { t.tv_sec = timeout / 1000; t.tv_nsec = (timeout % 1000) * 1000000; }
- return my_syscall5(__NR_ppoll, fds, nfds, (timeout >= 0) ? &t : NULL,
NULL, 0);
- return my_syscall5(__NR_ppoll_time64, fds, nfds, (timeout >= 0) ? &t
This looks good to me.
-#elif defined(__NR_ppoll_time64)
- struct __kernel_timespec t;
: NULL, NULL, 0); +#elif defined(__NR_ppoll)
struct timespec t;
if (timeout >= 0) { t.tv_sec = timeout / 1000; t.tv_nsec = (timeout % 1000) * 1000000; }
This is not wrong, but for consistency, I would use __kernel_old_timespec with the old syscall macros, rather than the nolibc-defined type.
A different approach would be to rely on timespec/timeval/time_t to always use the 64-bit types and then just pick the time64 macros on 32-bit vs the old macros on 64-bit builds.
- return my_syscall5(__NR_ppoll_time64, fds, nfds, (timeout >= 0) ? &t
: NULL, NULL, 0);
- return my_syscall5(__NR_ppoll, fds, nfds, (timeout >= 0) ? &t : NULL,
NULL, 0); #else return my_syscall3(__NR_poll, fds, nfds, timeout); #endif
I would think that we can remove the final #else clause here and just use the __NR_ppoll case as #else. It would also make sense to change the first #if to check for a 32-bit ABI.
diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index e91b7d947161..10c517a38f86 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -772,22 +772,22 @@ int sys_select(int nfds, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeva return my_syscall5(__NR__newselect, nfds, rfds, wfds, efds, timeout); #elif defined(__NR_select) return my_syscall5(__NR_select, nfds, rfds, wfds, efds, timeout); -#elif defined(__NR_pselect6)
- struct timespec t;
+#elif defined(__NR_pselect6_time64)
- struct __kernel_timespec t;
These probably need to be flipped around, so that __NR_pselect6_time64/__NR_pselect6 comes first because the other ones use the wrong type on 32-bit targets.
Probably also do the same thing here with the #ifdef checking the architecture instead of the syscall macro.
Arnd
timespec::tv_nsec is going to be 64-bit wide even on 32-bit architectures. As not all architectures support 64-bit division instructions, calls to libgcc (__divdi3()) may be emitted by the compiler which are not provided by nolibc.
As tv_nsec is guaranteed to always fit into an uint32_t, perform a 32-bit division instead.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/sys/time.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/include/nolibc/sys/time.h b/tools/include/nolibc/sys/time.h index 33782a19aae9..6dd3705c6c9d 100644 --- a/tools/include/nolibc/sys/time.h +++ b/tools/include/nolibc/sys/time.h @@ -33,7 +33,7 @@ int sys_gettimeofday(struct timeval *tv, struct timezone *tz) ret = sys_clock_gettime(CLOCK_REALTIME, &tp); if (!ret && tv) { tv->tv_sec = tp.tv_sec; - tv->tv_usec = tp.tv_nsec / 1000; + tv->tv_usec = (uint32_t)tp.tv_nsec / 1000; }
return ret;
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
timespec::tv_nsec is going to be 64-bit wide even on 32-bit architectures. As not all architectures support 64-bit division instructions, calls to libgcc (__divdi3()) may be emitted by the compiler which are not provided by nolibc.
As tv_nsec is guaranteed to always fit into an uint32_t, perform a 32-bit division instead.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Reviewed-by: Arnd Bergmann arnd@arndb.de
On Wed, Oct 29, 2025 at 05:02:58PM +0100, Thomas Weißschuh wrote:
timespec::tv_nsec is going to be 64-bit wide even on 32-bit architectures. As not all architectures support 64-bit division instructions, calls to libgcc (__divdi3()) may be emitted by the compiler which are not provided by nolibc.
As tv_nsec is guaranteed to always fit into an uint32_t, perform a 32-bit division instead.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
tools/include/nolibc/sys/time.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/include/nolibc/sys/time.h b/tools/include/nolibc/sys/time.h index 33782a19aae9..6dd3705c6c9d 100644 --- a/tools/include/nolibc/sys/time.h +++ b/tools/include/nolibc/sys/time.h @@ -33,7 +33,7 @@ int sys_gettimeofday(struct timeval *tv, struct timezone *tz) ret = sys_clock_gettime(CLOCK_REALTIME, &tp); if (!ret && tv) { tv->tv_sec = tp.tv_sec;
tv->tv_usec = tp.tv_nsec / 1000;
}tv->tv_usec = (uint32_t)tp.tv_nsec / 1000;
Good catch! I'm wondering if this one shouldn't be marked as a build fix for 5e7392dc82ed ("tools/nolibc: fall back to sys_clock_gettime() in gettimeofday()") so that it can be backported.
Acked-by: Willy Tarreau w@1wt.eu
Willy
On 2025-11-02 09:31:56+0100, Willy Tarreau wrote:
On Wed, Oct 29, 2025 at 05:02:58PM +0100, Thomas Weißschuh wrote:
timespec::tv_nsec is going to be 64-bit wide even on 32-bit architectures. As not all architectures support 64-bit division instructions, calls to libgcc (__divdi3()) may be emitted by the compiler which are not provided by nolibc.
As tv_nsec is guaranteed to always fit into an uint32_t, perform a 32-bit division instead.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
tools/include/nolibc/sys/time.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/include/nolibc/sys/time.h b/tools/include/nolibc/sys/time.h index 33782a19aae9..6dd3705c6c9d 100644 --- a/tools/include/nolibc/sys/time.h +++ b/tools/include/nolibc/sys/time.h @@ -33,7 +33,7 @@ int sys_gettimeofday(struct timeval *tv, struct timezone *tz) ret = sys_clock_gettime(CLOCK_REALTIME, &tp); if (!ret && tv) { tv->tv_sec = tp.tv_sec;
tv->tv_usec = tp.tv_nsec / 1000;
}tv->tv_usec = (uint32_t)tp.tv_nsec / 1000;Good catch! I'm wondering if this one shouldn't be marked as a build fix for 5e7392dc82ed ("tools/nolibc: fall back to sys_clock_gettime() in gettimeofday()") so that it can be backported.
Right now timespec::tv_nsec is of type 'long', so it should only be 64-bits on architectures which have native 64-bit division instructions. But marking it as fix shouldn't hurt either.
Thomas
On Sun, Nov 02, 2025 at 10:27:18AM +0100, Thomas Weißschuh wrote:
On 2025-11-02 09:31:56+0100, Willy Tarreau wrote:
On Wed, Oct 29, 2025 at 05:02:58PM +0100, Thomas Weißschuh wrote:
timespec::tv_nsec is going to be 64-bit wide even on 32-bit architectures. As not all architectures support 64-bit division instructions, calls to libgcc (__divdi3()) may be emitted by the compiler which are not provided by nolibc.
As tv_nsec is guaranteed to always fit into an uint32_t, perform a 32-bit division instead.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
tools/include/nolibc/sys/time.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/include/nolibc/sys/time.h b/tools/include/nolibc/sys/time.h index 33782a19aae9..6dd3705c6c9d 100644 --- a/tools/include/nolibc/sys/time.h +++ b/tools/include/nolibc/sys/time.h @@ -33,7 +33,7 @@ int sys_gettimeofday(struct timeval *tv, struct timezone *tz) ret = sys_clock_gettime(CLOCK_REALTIME, &tp); if (!ret && tv) { tv->tv_sec = tp.tv_sec;
tv->tv_usec = tp.tv_nsec / 1000;
}tv->tv_usec = (uint32_t)tp.tv_nsec / 1000;Good catch! I'm wondering if this one shouldn't be marked as a build fix for 5e7392dc82ed ("tools/nolibc: fall back to sys_clock_gettime() in gettimeofday()") so that it can be backported.
Right now timespec::tv_nsec is of type 'long', so it should only be 64-bits on architectures which have native 64-bit division instructions. But marking it as fix shouldn't hurt either.
Ah yeah you're right. Don't bother with that then.
Thanks! Willy
A custom 'struct timespec' will be necessary for 64-bit time types on 32-bit architectures. <linux/time.h> will define other time-related types in terms of the custom definition.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/arch-s390.h | 3 +++ tools/include/nolibc/types.h | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/tools/include/nolibc/arch-s390.h b/tools/include/nolibc/arch-s390.h index 6237211385c0..1e87ac42ab9c 100644 --- a/tools/include/nolibc/arch-s390.h +++ b/tools/include/nolibc/arch-s390.h @@ -5,6 +5,9 @@
#ifndef _NOLIBC_ARCH_S390_H #define _NOLIBC_ARCH_S390_H + +#include "types.h" + #include <linux/signal.h> #include <linux/unistd.h>
diff --git a/tools/include/nolibc/types.h b/tools/include/nolibc/types.h index 16c6e9ec9451..4f5a25567e5c 100644 --- a/tools/include/nolibc/types.h +++ b/tools/include/nolibc/types.h @@ -13,9 +13,16 @@ #include "std.h" #include <linux/mman.h> #include <linux/stat.h> -#include <linux/time.h> #include <linux/wait.h>
+struct timespec { + time_t tv_sec; + long tv_nsec; +}; +#define _STRUCT_TIMESPEC + +#include <linux/time.h> +
/* Only the generic macros and types may be defined here. The arch-specific * ones such as the O_RDONLY and related macros used by fcntl() and open()
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
+struct timespec {
- time_t tv_sec;
- long tv_nsec;
+}; +#define _STRUCT_TIMESPEC
+#include <linux/time.h>
Unfortunately this is not the definition we want on big-endian systems because it puts the tv_nsec field in the wrong place.
You can either uses the simple (non-POSIX) __kernel_timespec definition in nolibc with a 64-bit tv_nsec, or copy the more complicated definition with explicit padding that is used in musl and glibc.
Arnd
On Thu, Oct 30, 2025 at 03:46:21PM +0100, Arnd Bergmann wrote:
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
+struct timespec {
- time_t tv_sec;
- long tv_nsec;
+}; +#define _STRUCT_TIMESPEC
+#include <linux/time.h>
Unfortunately this is not the definition we want on big-endian systems because it puts the tv_nsec field in the wrong place.
Indeed!
You can either uses the simple (non-POSIX) __kernel_timespec definition in nolibc with a 64-bit tv_nsec, or copy the more complicated definition with explicit padding that is used in musl and glibc.
I think that switching this patch and the next one (10/12) would just do the trick since both fields will become __kernel_time64_t. Or maybe the two should be squashed into a single one.
Willy
On Sun, Nov 02, 2025 at 09:36:22AM +0100, Willy Tarreau wrote:
On Thu, Oct 30, 2025 at 03:46:21PM +0100, Arnd Bergmann wrote:
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
+struct timespec {
- time_t tv_sec;
- long tv_nsec;
+}; +#define _STRUCT_TIMESPEC
+#include <linux/time.h>
Unfortunately this is not the definition we want on big-endian systems because it puts the tv_nsec field in the wrong place.
Indeed!
You can either uses the simple (non-POSIX) __kernel_timespec definition in nolibc with a 64-bit tv_nsec, or copy the more complicated definition with explicit padding that is used in musl and glibc.
I think that switching this patch and the next one (10/12) would just do the trick since both fields will become __kernel_time64_t. Or maybe the two should be squashed into a single one.
OK like Arnd, I just saw with the last commit that this one will not harm. That's good.
Willy
On 2025-11-02 09:36:22+0100, Willy Tarreau wrote:
On Thu, Oct 30, 2025 at 03:46:21PM +0100, Arnd Bergmann wrote:
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
+struct timespec {
- time_t tv_sec;
- long tv_nsec;
+}; +#define _STRUCT_TIMESPEC
+#include <linux/time.h>
Unfortunately this is not the definition we want on big-endian systems because it puts the tv_nsec field in the wrong place.
Indeed!
You can either uses the simple (non-POSIX) __kernel_timespec definition in nolibc with a 64-bit tv_nsec, or copy the more complicated definition with explicit padding that is used in musl and glibc.
I think that switching this patch and the next one (10/12) would just do the trick since both fields will become __kernel_time64_t. Or maybe the two should be squashed into a single one.
Maybe I can make it clearer that this patch does not change anything. This custom definition of 'struct timespec' is the same as the one we got from linux/time.h before. This is just a preparation for the next commit. Merging would also work, but it will be a bit messy to look at.
Thomas
On Sun, Nov 02, 2025 at 10:41:39AM +0100, Thomas Weißschuh wrote:
On 2025-11-02 09:36:22+0100, Willy Tarreau wrote:
On Thu, Oct 30, 2025 at 03:46:21PM +0100, Arnd Bergmann wrote:
On Wed, Oct 29, 2025, at 17:02, Thomas Weißschuh wrote:
+struct timespec {
- time_t tv_sec;
- long tv_nsec;
+}; +#define _STRUCT_TIMESPEC
+#include <linux/time.h>
Unfortunately this is not the definition we want on big-endian systems because it puts the tv_nsec field in the wrong place.
Indeed!
You can either uses the simple (non-POSIX) __kernel_timespec definition in nolibc with a 64-bit tv_nsec, or copy the more complicated definition with explicit padding that is used in musl and glibc.
I think that switching this patch and the next one (10/12) would just do the trick since both fields will become __kernel_time64_t. Or maybe the two should be squashed into a single one.
Maybe I can make it clearer that this patch does not change anything. This custom definition of 'struct timespec' is the same as the one we got from linux/time.h before. This is just a preparation for the next commit. Merging would also work, but it will be a bit messy to look at.
Yes a slightly improved description in the patch wouldn't hurt. Since we were two to get caught, it will definitely happen in the future when people read commits.
Thanks! Willy
32-bit time types will stop working in 2038.
Switch to 64-bit time types everywhere.
Suggested-by: Arnd Bergmann arnd@arndb.de Link: https://lore.kernel.org/lkml/cec27d94-c99d-4c57-9a12-275ea663dda8@app.fastma... Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/std.h | 2 +- tools/include/nolibc/types.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/include/nolibc/std.h b/tools/include/nolibc/std.h index 392f4dd94158..b9a116123902 100644 --- a/tools/include/nolibc/std.h +++ b/tools/include/nolibc/std.h @@ -29,6 +29,6 @@ typedef unsigned long nlink_t; typedef int64_t off_t; typedef signed long blksize_t; typedef signed long blkcnt_t; -typedef __kernel_time_t time_t; +typedef __kernel_time64_t time_t;
#endif /* _NOLIBC_STD_H */ diff --git a/tools/include/nolibc/types.h b/tools/include/nolibc/types.h index 4f5a25567e5c..3b086f68b2be 100644 --- a/tools/include/nolibc/types.h +++ b/tools/include/nolibc/types.h @@ -17,7 +17,7 @@
struct timespec { time_t tv_sec; - long tv_nsec; + int64_t tv_nsec; }; #define _STRUCT_TIMESPEC
Keeping 'struct timespec' and 'struct __kernel_timespec' compatible allows the source code to stay simple.
Validate that the types stay compatible.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index f1d6d6c766dd..938af8d6e2bf 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1428,6 +1428,23 @@ int test_difftime(void) return 0; }
+int test_timespec(void) +{ + struct __kernel_timespec kts; + struct timespec ts; + + if (sizeof(ts) != sizeof(kts)) + return 1; + + if (!__builtin_types_compatible_p(__typeof__(ts.tv_sec), __typeof__(kts.tv_sec))) + return 1; + + if (!__builtin_types_compatible_p(__typeof__(ts.tv_nsec), __typeof__(kts.tv_nsec))) + return 1; + + return 0; +} + int run_stdlib(int min, int max) { int test; @@ -1551,6 +1568,7 @@ int run_stdlib(int min, int max) CASE_TEST(abs); EXPECT_EQ(1, abs(-10), 10); break; CASE_TEST(abs_noop); EXPECT_EQ(1, abs(10), 10); break; CASE_TEST(difftime); EXPECT_ZR(1, test_difftime()); break; + CASE_TEST(test_timespec); EXPECT_ZR(1, test_timespec()); break;
case __LINE__: return ret; /* must be last */
Now that 'struct timespec' and 'struct __kernel_timespec' are compatible, the conversions are not necessary anymore. The same holds true for 'struct itimerspec' and 'struct __kernel_itimerspec'.
Remove the conversions.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net --- tools/include/nolibc/sys/timerfd.h | 20 ++---------- tools/include/nolibc/time.h | 64 ++++---------------------------------- 2 files changed, 8 insertions(+), 76 deletions(-)
diff --git a/tools/include/nolibc/sys/timerfd.h b/tools/include/nolibc/sys/timerfd.h index 5dd61030c991..ae3153b7635d 100644 --- a/tools/include/nolibc/sys/timerfd.h +++ b/tools/include/nolibc/sys/timerfd.h @@ -35,13 +35,7 @@ int sys_timerfd_gettime(int fd, struct itimerspec *curr_value) #if defined(__NR_timerfd_gettime) return my_syscall2(__NR_timerfd_gettime, fd, curr_value); #else - struct __kernel_itimerspec kcurr_value; - int ret; - - ret = my_syscall2(__NR_timerfd_gettime64, fd, &kcurr_value); - __nolibc_timespec_kernel_to_user(&kcurr_value.it_interval, &curr_value->it_interval); - __nolibc_timespec_kernel_to_user(&kcurr_value.it_value, &curr_value->it_value); - return ret; + return my_syscall2(__NR_timerfd_gettime64, fd, curr_value); #endif }
@@ -59,17 +53,7 @@ int sys_timerfd_settime(int fd, int flags, #if defined(__NR_timerfd_settime) return my_syscall4(__NR_timerfd_settime, fd, flags, new_value, old_value); #else - struct __kernel_itimerspec knew_value, kold_value; - int ret; - - __nolibc_timespec_user_to_kernel(&new_value->it_value, &knew_value.it_value); - __nolibc_timespec_user_to_kernel(&new_value->it_interval, &knew_value.it_interval); - ret = my_syscall4(__NR_timerfd_settime64, fd, flags, &knew_value, &kold_value); - if (old_value) { - __nolibc_timespec_kernel_to_user(&kold_value.it_interval, &old_value->it_interval); - __nolibc_timespec_kernel_to_user(&kold_value.it_value, &old_value->it_value); - } - return ret; + return my_syscall4(__NR_timerfd_settime64, fd, flags, new_value, old_value); #endif }
diff --git a/tools/include/nolibc/time.h b/tools/include/nolibc/time.h index 45df9b09d7b6..ab67f209c99f 100644 --- a/tools/include/nolibc/time.h +++ b/tools/include/nolibc/time.h @@ -18,20 +18,6 @@ #include <linux/signal.h> #include <linux/time.h>
-static __inline__ -void __nolibc_timespec_user_to_kernel(const struct timespec *ts, struct __kernel_timespec *kts) -{ - kts->tv_sec = ts->tv_sec; - kts->tv_nsec = ts->tv_nsec; -} - -static __inline__ -void __nolibc_timespec_kernel_to_user(const struct __kernel_timespec *kts, struct timespec *ts) -{ - ts->tv_sec = kts->tv_sec; - ts->tv_nsec = kts->tv_nsec; -} - /* * int clock_getres(clockid_t clockid, struct timespec *res); * int clock_gettime(clockid_t clockid, struct timespec *tp); @@ -44,13 +30,7 @@ static __attribute__((unused)) int sys_clock_getres(clockid_t clockid, struct timespec *res) { #if defined(__NR_clock_getres_time64) - struct __kernel_timespec kres; - int ret; - - ret = my_syscall2(__NR_clock_getres_time64, clockid, &kres); - if (res) - __nolibc_timespec_kernel_to_user(&kres, res); - return ret; + return my_syscall2(__NR_clock_getres_time64, clockid, res); #else return my_syscall2(__NR_clock_getres, clockid, res); #endif @@ -66,13 +46,7 @@ static __attribute__((unused)) int sys_clock_gettime(clockid_t clockid, struct timespec *tp) { #if defined(__NR_clock_gettime64) - struct __kernel_timespec ktp; - int ret; - - ret = my_syscall2(__NR_clock_gettime64, clockid, &ktp); - if (tp) - __nolibc_timespec_kernel_to_user(&ktp, tp); - return ret; + return my_syscall2(__NR_clock_gettime64, clockid, tp); #else return my_syscall2(__NR_clock_gettime, clockid, tp); #endif @@ -88,10 +62,7 @@ static __attribute__((unused)) int sys_clock_settime(clockid_t clockid, struct timespec *tp) { #if defined(__NR_clock_settime64) - struct __kernel_timespec ktp; - - __nolibc_timespec_user_to_kernel(tp, &ktp); - return my_syscall2(__NR_clock_settime64, clockid, &ktp); + return my_syscall2(__NR_clock_settime64, clockid, tp); #else return my_syscall2(__NR_clock_settime, clockid, tp); #endif @@ -108,14 +79,7 @@ int sys_clock_nanosleep(clockid_t clockid, int flags, const struct timespec *rqt struct timespec *rmtp) { #if defined(__NR_clock_nanosleep_time64) - struct __kernel_timespec krqtp, krmtp; - int ret; - - __nolibc_timespec_user_to_kernel(rqtp, &krqtp); - ret = my_syscall4(__NR_clock_nanosleep_time64, clockid, flags, &krqtp, &krmtp); - if (rmtp) - __nolibc_timespec_kernel_to_user(&krmtp, rmtp); - return ret; + return my_syscall4(__NR_clock_nanosleep_time64, clockid, flags, rqtp, rmtp); #else return my_syscall4(__NR_clock_nanosleep, clockid, flags, rqtp, rmtp); #endif @@ -190,13 +154,7 @@ static __attribute__((unused)) int sys_timer_gettime(timer_t timerid, struct itimerspec *curr_value) { #if defined(__NR_timer_gettime64) - struct __kernel_itimerspec kcurr_value; - int ret; - - ret = my_syscall2(__NR_timer_gettime64, timerid, &kcurr_value); - __nolibc_timespec_kernel_to_user(&kcurr_value.it_interval, &curr_value->it_interval); - __nolibc_timespec_kernel_to_user(&kcurr_value.it_value, &curr_value->it_value); - return ret; + return my_syscall2(__NR_timer_gettime64, timerid, curr_value); #else return my_syscall2(__NR_timer_gettime, timerid, curr_value); #endif @@ -213,17 +171,7 @@ int sys_timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { #if defined(__NR_timer_settime64) - struct __kernel_itimerspec knew_value, kold_value; - int ret; - - __nolibc_timespec_user_to_kernel(&new_value->it_value, &knew_value.it_value); - __nolibc_timespec_user_to_kernel(&new_value->it_interval, &knew_value.it_interval); - ret = my_syscall4(__NR_timer_settime64, timerid, flags, &knew_value, &kold_value); - if (old_value) { - __nolibc_timespec_kernel_to_user(&kold_value.it_interval, &old_value->it_interval); - __nolibc_timespec_kernel_to_user(&kold_value.it_value, &old_value->it_value); - } - return ret; + return my_syscall4(__NR_timer_settime64, timerid, flags, new_value, old_value); #else return my_syscall4(__NR_timer_settime, timerid, flags, new_value, old_value); #endif
On Wed, Oct 29, 2025, at 17:03, Thomas Weißschuh wrote:
Now that 'struct timespec' and 'struct __kernel_timespec' are compatible, the conversions are not necessary anymore. The same holds true for 'struct itimerspec' and 'struct __kernel_itimerspec'.
Remove the conversions.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Ah, I started commenting before I got to the end of the series. This looks good,
Reviewed-by: Arnd Bergmann arnd@arndb.de
On Wed, Oct 29, 2025 at 05:02:50PM +0100, Thomas Weißschuh wrote:
nolibc currently uses 32-bit types for various APIs. These are problematic as their reduced value range can lead to truncated values.
Signed-off-by: Thomas Weißschuh linux@weissschuh.net
Thanks Thomas for this cleanup! Overall this looks good to me. I also tested my preinit code against it based both on latest and older UAPI headers (5.4) and everything still builds and seems to work fine.
For the whole series: Acked-by: Willy Tarreau w@1wt.eu
Willy
linux-kselftest-mirror@lists.linaro.org