utimensat: add time64 syscall support, decouple 32-bit time_t

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.
This commit is contained in:
Rich Felker 2019-07-28 18:51:20 -04:00
parent 7aeecf3e0b
commit 01f3480d37
1 changed files with 31 additions and 6 deletions

View 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;
}
}