utimensat: add time64 syscall support, decouple 32-bit time_t
authorRich Felker <dalias@aerifal.cx>
Sun, 28 Jul 2019 22:51:20 +0000 (18:51 -0400)
committerRich Felker <dalias@aerifal.cx>
Mon, 29 Jul 2019 04:19:16 +0000 (00:19 -0400)
time64 syscall is used only if it's the only one defined for the arch,
or if either of the requested times does not fit in 32 bits. care is
taken to normalize the inputs to account for UTIME_NOW or UTIME_OMIT
in tv_nsec, in which case tv_sec should be ignored. this is needed not
only to avoid spurious time64 syscalls that might waste time failing
with ENOSYS, but also to accurately decide whether fallback is
possible.

if the requested time cannot be represented, the function fails with
ENOTSUP, defined in general as "The implementation does not support
the requested feature or value". neither the time64 syscall, nor this
error, can happen on current 32-bit archs where time_t is a 32-bit
type, and both are statically unreachable.

on 64-bit archs, there are only superficial changes to the
SYS_futimesat fallback path, which has been modified to pass long[4]
instead of struct timeval[2] to the kernel, making it suitable for use
on 32-bit archs even once time_t is changed to 64-bit. for 32-bit
archs, the call to SYS_utimensat has also been changed to copy the
timespecs through an array of long[4] rather than passing the
timespec[2] in-place.

src/stat/utimensat.c

index 49d74c22bb73d9705cd67763d4c3f4e166fb0f1a..730723a9ea70d4f88e20155f1f7e33b366a2281e 100644 (file)
@@ -4,26 +4,51 @@
 #include <errno.h>
 #include "syscall.h"
 
+#define IS32BIT(x) !((x)+0x80000000ULL>>32)
+#define NS_SPECIAL(ns) ((ns)==UTIME_NOW || (ns)==UTIME_OMIT)
+
 int utimensat(int fd, const char *path, const struct timespec times[2], int flags)
 {
+       int r;
        if (times && times[0].tv_nsec==UTIME_NOW && times[1].tv_nsec==UTIME_NOW)
                times = 0;
-       int r = __syscall(SYS_utimensat, fd, path, times, flags);
+#ifdef SYS_utimensat_time64
+       r = -ENOSYS;
+       time_t s0=0, s1=0;
+       long ns0=0, ns1=0;
+       if (times) {
+               ns0 = times[0].tv_nsec;
+               ns1 = times[1].tv_nsec;
+               if (!NS_SPECIAL(ns0)) s0 = times[0].tv_sec;
+               if (!NS_SPECIAL(ns1)) s1 = times[1].tv_sec;
+       }
+       if (SYS_utimensat == SYS_utimensat_time64 || !IS32BIT(s0) || !IS32BIT(s1))
+               r = __syscall(SYS_utimensat_time64, fd, path, times ?
+                       ((long long[]){s0, ns0, s1, ns1}) : 0, flags);
+       if (SYS_utimensat == SYS_utimensat_time64 || r!=-ENOSYS)
+               return __syscall_ret(r);
+       if (!IS32BIT(s0) || !IS32BIT(s1))
+               return __syscall_ret(-ENOTSUP);
+       r = __syscall(SYS_utimensat, fd, path,
+               times ? ((long[]){s0, ns0, s1, ns1}) : 0, flags);
+#else
+       r = __syscall(SYS_utimensat, fd, path, times, flags);
+#endif
+
 #ifdef SYS_futimesat
        if (r != -ENOSYS || flags) return __syscall_ret(r);
-       struct timeval *tv = 0, tmp[2];
+       long *tv=0, tmp[4];
        if (times) {
                int i;
                tv = tmp;
                for (i=0; i<2; i++) {
                        if (times[i].tv_nsec >= 1000000000ULL) {
-                               if (times[i].tv_nsec == UTIME_NOW
-                                || times[i].tv_nsec == UTIME_OMIT)
+                               if (NS_SPECIAL(times[i].tv_nsec))
                                        return __syscall_ret(-ENOSYS);
                                return __syscall_ret(-EINVAL);
                        }
-                       tmp[i].tv_sec = times[i].tv_sec;
-                       tmp[i].tv_usec = times[i].tv_nsec / 1000;
+                       tmp[2*i+0] = times[i].tv_sec;
+                       tmp[2*i+1] = times[i].tv_nsec / 1000;
                }
        }