Fix futimes() regression

Fixes #647
This commit is contained in:
Justine Tunney 2022-10-05 19:25:07 -07:00
parent f155205eb0
commit 81ee11a16e
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
22 changed files with 200 additions and 138 deletions

View file

@ -26,6 +26,7 @@
* @param ts receives `CLOCK_REALTIME` timestamp
* @param base must be `TIME_UTC`
* @return `base` on success, or `0` on failure
* @see _timespec_real()
*/
int timespec_get(struct timespec *ts, int base) {
if (base == TIME_UTC && !clock_gettime(CLOCK_REALTIME, ts)) {

View file

@ -20,6 +20,13 @@
#include "libc/calls/struct/timespec.h"
#include "libc/sysv/consts/clock.h"
/**
* Returns current monotonic time.
*
* This function uses a `CLOCK_MONOTONIC` clock and never fails.
*
* @see _timespec_real()
*/
struct timespec _timespec_mono(void) {
struct timespec ts;
_npassert(!clock_gettime(CLOCK_MONOTONIC_FAST, &ts));

View file

@ -20,6 +20,15 @@
#include "libc/calls/struct/timespec.h"
#include "libc/sysv/consts/clock.h"
/**
* Returns current time.
*
* This function uses a `CLOCK_REALTIME` clock and never fails. Unlike
* clock_gettime() or timespec_real() this interface avoids the use of
* pointers which lets time handling code become more elegant.
*
* @see _timespec_mono()
*/
struct timespec _timespec_real(void) {
struct timespec ts;
_npassert(!clock_gettime(CLOCK_REALTIME_FAST, &ts));

View file

@ -21,6 +21,17 @@
/**
* Reduces `ts` from 1e-9 to 1e-6 granularity w/ ceil rounding.
*
* This function uses ceiling rounding. For example, if `ts` is one
* nanosecond, then one microsecond will be returned. Ceil rounding
* is needed by many interfaces, e.g. setitimer(), because the zero
* timestamp has a special meaning.
*
* This function also detects overflow in which case `INT64_MAX` or
* `INT64_MIN` may be returned. The `errno` variable isn't changed.
*
* @return 64-bit scalar holding microseconds since epoch
* @see _timespec_totimeval()
*/
int64_t _timespec_tomicros(struct timespec ts) {
int64_t us;

View file

@ -21,6 +21,16 @@
/**
* Reduces `ts` from 1e-9 to 1e-3 granularity w/ ceil rounding.
*
* This function uses ceiling rounding. For example, if `ts` is one
* nanosecond, then one millisecond will be returned. Ceil rounding
* is needed by many interfaces, e.g. setitimer(), because the zero
* timestamp has a special meaning.
*
* This function also detects overflow in which case `INT64_MAX` or
* `INT64_MIN` may be returned. The `errno` variable isn't changed.
*
* @return 64-bit scalar milliseconds since epoch
*/
int64_t _timespec_tomillis(struct timespec ts) {
int64_t ms;

View file

@ -20,7 +20,12 @@
#include "libc/limits.h"
/**
* Converts timespec interval to nanoseconds.
* Converts timespec to scalar.
*
* This function will detect overflow in which case `INT64_MAX` or
* `INT64_MIN` may be returned. The `errno` variable isn't changed.
*
* @return 64-bit integer holding nanoseconds since epoch
*/
int64_t _timespec_tonanos(struct timespec x) {
int64_t ns;

View file

@ -20,6 +20,14 @@
/**
* Reduces `ts` from 1e-9 to 1e-6 granularity w/ ceil rounding.
*
* This function uses ceiling rounding. For example, if `ts` is one
* nanosecond, then one microsecond will be returned. Ceil rounding
* is needed by many interfaces, e.g. setitimer(), because the zero
* timestamp has a special meaning.
*
* @return microseconds since epoch
* @see _timespec_tomicros()
*/
struct timeval _timespec_totimeval(struct timespec ts) {
if (ts.tv_nsec < 1000000000 - 999) {

View file

@ -18,6 +18,9 @@
*/
#include "libc/calls/struct/timeval.h"
/**
* Coerces `tv` from 1e-6 to 1e-9 granularity.
*/
struct timespec _timeval_totimespec(struct timeval tv) {
return (struct timespec){tv.tv_sec, tv.tv_usec * 1000};
}

View file

@ -65,14 +65,10 @@
* of your preferred platforms to see what other clocks might work
* @param flags can be 0 for relative and `TIMER_ABSTIME` for absolute
* @param req can be a relative or absolute time, depending on `flags`
* @param rem will be updated with the unslept time only when `flags`
* is zero, otherwise `rem` is ignored; when this call completes,
* that means `rem` will be set to `{0, 0}`, and shall only have
* something else when -1 is returned with `EINTR`, which means
* there was a signal delivery that happened mid-sleep and `rem`
* reflects (poorly) how much remaining time was left over, in
* case the caller wishes to retry the sleep operation, noting
* this isn't recommended since relative timestamps can drift
* @param rem shall be updated with the remainder of unslept time when
* (1) it's non-null; (2) `flags` is 0; and (3) -1 w/ `EINTR` is
* returned; if this function returns 0 then `rem` is undefined;
* if flags is `TIMER_ABSTIME` then `rem` is ignored
* @return 0 on success, or -1 w/ errno
* @raise EINTR when a signal got delivered while we were waiting
* @raise ENOTSUP if `clock` is known but we can't use it here
@ -107,14 +103,6 @@ int clock_nanosleep(int clock, int flags, const struct timespec *req,
rc = sys_clock_nanosleep_nt(clock, flags, req, rem);
}
// Linux Kernel doesn't change the remainder value on success, but
// some kernels like OpenBSD will. POSIX doesn't specify the Linux
// behavior. So we polyfill it here.
if (!rc && !flags && rem) {
rem->tv_sec = 0;
rem->tv_nsec = 0;
}
STRACE("clock_nanosleep(%s, %s, %s, [%s]) → %d% m", DescribeClockName(clock),
DescribeSleepFlags(flags), flags, DescribeTimespec(0, req),
DescribeTimespec(rc, rem), rc);

View file

@ -26,8 +26,8 @@
* Changes access/modified time on open file, the modern way.
*
* XNU only has microsecond (1e-6) accuracy. Windows only has
* hectonanosecond (1e-7) accuracy. RHEL5 is somewhat broken so utimes()
* is recommended if portability to old versions of Linux is desired.
* hectonanosecond (1e-7) accuracy. RHEL5 (Linux c. 2007) doesn't
* support this system call.
*
* @param fd is file descriptor of file whose timestamps will change
* @param ts is {access, modified} timestamps, or null for current time

View file

@ -16,26 +16,45 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/asan.internal.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/struct/timeval.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Sets atime/mtime on file descriptor.
*
* @param ts is atime/mtime, or null for current time
* @note better than microsecond precision on most platforms
* @see fstat() for reading timestamps
* @return 0 on success, or -1 w/ errno
* @raise ENOTSUP if `fd` is on zip filesystem
* @raise EBADF if `fd` isn't an open file descriptor
* @raise EPERM if pledge() is in play without `fattr` promise
* @raise EINVAL if `tv` specifies a microsecond value that's out of range
* @raise ENOSYS on RHEL5 or bare metal
* @see futimens() for modern version
* @asyncsignalsafe
* @threadsafe
*/
int futimes(int fd, const struct timeval tv[2]) {
// TODO(jart): does this work on rhel5? what's up with this?
int rc;
struct timespec ts[2];
if (tv) {
ts[0].tv_sec = tv[0].tv_sec;
ts[0].tv_nsec = tv[0].tv_usec * 1000;
ts[1].tv_sec = tv[1].tv_sec;
ts[1].tv_nsec = tv[1].tv_usec * 1000;
return utimensat(fd, NULL, ts, 0);
rc = __utimens(fd, 0, ts, 0);
} else {
return utimensat(fd, NULL, NULL, 0);
rc = __utimens(fd, 0, 0, 0);
}
STRACE("futimes(%d, {%s, %s}) → %d% m", fd, DescribeTimeval(0, tv),
DescribeTimeval(0, tv ? tv + 1 : 0), rc);
return rc;
}

View file

@ -30,13 +30,11 @@
* Sleeps for relative amount of time.
*
* @param req is the duration of time we should sleep
* @param rem if non-NULL will receive the amount of time that wasn't
* slept because a signal was delivered. If no signal's delivered
* then this value will be set to `{0, 0}`. It's also fine to set
* this value to the same pointer as `req`
* @param rem if non-null will be updated with the remainder of unslept
* time when -1 w/ `EINTR` is returned otherwise `rem` is undefined
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if `req->tv_nsec [0,1000000000)`
* @raise EINTR if a signal was delivered, and `rem` is updated
* @raise EINTR if a signal was delivered and `rem` is updated
* @raise EFAULT if `req` is NULL or `req` / `rem` is a bad pointer
* @raise ENOSYS on bare metal
* @see clock_nanosleep()
@ -58,19 +56,11 @@ int nanosleep(const struct timespec *req, struct timespec *rem) {
} else if (IsXnu()) {
rc = sys_nanosleep_xnu(req, rem);
} else if (IsMetal()) {
rc = enosys(); /* TODO: Sleep on Metal */
rc = enosys();
} else {
rc = sys_nanosleep_nt(req, rem);
}
// Linux Kernel doesn't change the remainder value on success, but
// some kernels like OpenBSD will. POSIX doesn't specify the Linux
// behavior. So we polyfill it here.
if (!rc && rem) {
rem->tv_sec = 0;
rem->tv_nsec = 0;
}
#ifdef SYSDEBUG
if (!__time_critical) {
STRACE("nanosleep(%s, [%s]) → %d% m", DescribeTimespec(rc, req),

View file

@ -16,11 +16,11 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/strace.internal.h"
#include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
#include "libc/time/time.h"
@ -29,28 +29,28 @@
*
* Raise SIGALRM every 1.5s:
*
* CHECK_NE(-1, sigaction(SIGALRM,
* &(struct sigaction){.sa_sigaction = _missingno},
* NULL));
* CHECK_NE(-1, setitimer(ITIMER_REAL,
* &(const struct itimerval){{1, 500000},
* {1, 500000}},
* NULL));
* sigaction(SIGALRM,
* &(struct sigaction){.sa_sigaction = _missingno},
* NULL);
* setitimer(ITIMER_REAL,
* &(const struct itimerval){{1, 500000},
* {1, 500000}},
* NULL);
*
* Set single-shot 50ms timer callback to interrupt laggy connect():
*
* CHECK_NE(-1, sigaction(SIGALRM,
* &(struct sigaction){.sa_sigaction = _missingno,
* .sa_flags = SA_RESETHAND},
* NULL));
* CHECK_NE(-1, setitimer(ITIMER_REAL,
* &(const struct itimerval){{0, 0}, {0, 50000}},
* NULL));
* sigaction(SIGALRM,
* &(struct sigaction){.sa_sigaction = _missingno,
* .sa_flags = SA_RESETHAND},
* NULL);
* setitimer(ITIMER_REAL,
* &(const struct itimerval){{0, 0}, {0, 50000}},
* NULL);
* if (connect(...) == -1 && errno == EINTR) { ... }
*
* Disarm timer:
*
* CHECK_NE(-1, setitimer(ITIMER_REAL, &(const struct itimerval){0}, NULL));
* setitimer(ITIMER_REAL, &(const struct itimerval){0}, NULL);
*
* Be sure to check for EINTR on your i/o calls, for best low latency.
*
@ -81,6 +81,7 @@ int setitimer(int which, const struct itimerval *newvalue,
rc = sys_setitimer_nt(which, newvalue, oldvalue);
}
#ifdef SYSDEBUG
if (newvalue && oldvalue) {
STRACE("setitimer(%d, "
"{{%'ld, %'ld}, {%'ld, %'ld}}, "
@ -100,6 +101,7 @@ int setitimer(int which, const struct itimerval *newvalue,
} else {
STRACE("setitimer(%d, NULL, NULL) → %d% m", which, rc);
}
#endif
return rc;
}

View file

@ -1,36 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2021 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/struct/timeval.internal.h"
#include "libc/sysv/consts/at.h"
textwindows int sys_utimes_nt(const char *path, const struct timeval tv[2]) {
struct timespec ts[2];
if (tv) {
ts[0].tv_sec = tv[0].tv_sec;
ts[0].tv_nsec = tv[0].tv_usec * 1000;
ts[1].tv_sec = tv[1].tv_sec;
ts[1].tv_nsec = tv[1].tv_usec * 1000;
return sys_utimensat_nt(AT_FDCWD, path, ts, 0);
} else {
return sys_utimensat_nt(AT_FDCWD, path, NULL, 0);
}
}

View file

@ -17,16 +17,16 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/struct/timeval.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/time/struct/utimbuf.h"
/**
* Changes last accessed/modified times on file.
*
* @param times if NULL means now
* @return 0 on success or -1 w/ errno
* @return 0 on success, or -1 w/ errno
* @see utimensat() for modern version
* @asyncsignalsafe
* @threadsafe
*/
int utime(const char *path, const struct utimbuf *times) {
struct timeval tv[2];

View file

@ -21,6 +21,8 @@
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/struct/timeval.internal.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
@ -46,6 +48,8 @@ int __utimens(int fd, const char *path, const struct timespec ts[2],
(path && (_weaken(__zipos_parseuri) &&
_weaken(__zipos_parseuri)(path, &zipname) != -1))) {
rc = enotsup();
} else if (IsLinux() && !__is_linux_2_6_23() && fd == AT_FDCWD && !flags) {
rc = sys_utimes(path, (void *)ts); // rhel5 truncates to seconds
} else if (!IsWindows()) {
rc = sys_utimensat(fd, path, ts, flags);
} else {

View file

@ -34,10 +34,13 @@
*
* XNU only has microsecond (1e-6) accuracy and there's no
* `dirfd`-relative support. Windows only has hectonanosecond (1e-7)
* accuracy. RHEL5 is somewhat broken so utimes() is recommended if
* portability to old versions of Linux is desired.
* accuracy. RHEL5 doesn't support `dirfd` or `flags` and will truncate
* timestamps to seconds.
*
* @param dirfd is usually `AT_FDCWD`
* If you'd rather specify an open file descriptor rather than its
* filesystem path, then consider using futimens().
*
* @param dirfd can be `AT_FDCWD` or an open directory
* @param path is filename whose timestamps should be modified
* @param ts is {access, modified} timestamps, or null for current time
* @param flags can have `AT_SYMLINK_NOFOLLOW` when `path` is specified
@ -52,21 +55,23 @@
* @raise EBADF if `dirfd` isn't a valid fd or `AT_FDCWD`
* @raise EFAULT if `path` or `ts` memory was invalid
* @raise EROFS if `path` is on read-only filesystem
* @raise ENOSYS on bare metal
* @see futimens()
* @raise ENOSYS on bare metal or on rhel5 when `dirfd` or `flags` is used
* @asyncsignalsafe
* @threadsafe
*/
int utimensat(int dirfd, const char *path, const struct timespec ts[2],
int flags) {
int rc;
if (!path) {
rc = efault(); // linux kernel abi behavior isn't supported
} else {
rc = __utimens(dirfd, path, ts, flags);
}
STRACE("utimensat(%s, %#s, {%s, %s}, %#o) → %d% m", DescribeDirfd(dirfd),
path, DescribeTimespec(0, ts), DescribeTimespec(0, ts ? ts + 1 : 0),
flags, rc);
return rc;
}

View file

@ -16,57 +16,34 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/asan.internal.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/struct/timeval.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/errfuns.h"
#include "libc/zipos/zipos.internal.h"
/**
* Changes last accessed/modified timestamps on file.
*
* @param tv is access/modified timestamps, or NULL which means now
* @return 0 on success, or -1 w/ errno
* @raise ENOTSUP if `path` is a zip filesystem path
* @raise EROFS if `path` is on a read-only filesystem
* @raise EFAULT if `path` or `tv` points to invalid memory
* @raise EPERM if pledge() is in play without fattr promise
* @raise ENOENT if `path` doesn't exist or is an empty string
* @raise EACCES if unveil() is in play and `path` isn't unveiled
* @raise ELOOP if a loop was detected resolving components of `path`
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise EINVAL if `tv` specifies a microseconds value that's out of range
* @raise EACCES if we don't have permission to search a component of `path`
* @raise ENOTDIR if a directory component in `path` exists as non-directory
* @raise ENOSYS on bare metal
* @note truncates to second precision on rhel5
* @see utimensat() for modern version
* @asyncsignalsafe
* @see stat()
* @threadsafe
*/
int utimes(const char *path, const struct timeval tv[2]) {
int rc;
struct ZiposUri zipname;
if (IsMetal()) {
rc = enosys();
} else if (IsAsan() && tv &&
(!__asan_is_valid_timeval(tv + 0) ||
!__asan_is_valid_timeval(tv + 1))) {
rc = efault();
} else if (_weaken(__zipos_parseuri) &&
_weaken(__zipos_parseuri)(path, &zipname) != -1) {
rc = enotsup();
} else if (!IsWindows()) {
// we don't modernize utimes() into utimensat() because the
// latter is poorly supported and utimes() works everywhere
rc = sys_utimes(path, tv);
struct timespec ts[2];
if (tv) {
ts[0].tv_sec = tv[0].tv_sec;
ts[0].tv_nsec = tv[0].tv_usec * 1000;
ts[1].tv_sec = tv[1].tv_sec;
ts[1].tv_nsec = tv[1].tv_usec * 1000;
rc = __utimens(AT_FDCWD, path, ts, 0);
} else {
rc = sys_utimes_nt(path, tv);
rc = __utimens(AT_FDCWD, path, 0, 0);
}
STRACE("utimes(%#s, {%s, %s}) → %d% m", path, DescribeTimeval(0, tv),
DescribeTimeval(0, tv ? tv + 1 : 0), rc);

View file

@ -130,7 +130,7 @@ microarchitecture("avx") static void bzero_avx(char *p, size_t n) {
* @return p
* @asyncsignalsafe
*/
void(bzero)(void *p, size_t n) {
void bzero(void *p, size_t n) {
char *b;
uint64_t x;
b = p;

View file

@ -64,12 +64,19 @@ TEST(_timespec_frommicros, test) {
}
TEST(_timespec_tomillis, test) {
EXPECT_EQ(0, _timespec_tomillis((struct timespec){0, 0}));
EXPECT_EQ(1, _timespec_tomillis((struct timespec){0, 1}));
EXPECT_EQ(1, _timespec_tomillis((struct timespec){0, 999999}));
EXPECT_EQ(1, _timespec_tomillis((struct timespec){0, 1000000}));
EXPECT_EQ(1000, _timespec_tomillis((struct timespec){0, 999999999}));
EXPECT_EQ(2123, _timespec_tomillis((struct timespec){2, 123000000}));
EXPECT_EQ(INT64_MAX, _timespec_tomillis((struct timespec){INT64_MAX, 0}));
EXPECT_EQ(INT64_MIN, _timespec_tomillis((struct timespec){INT64_MIN, 0}));
}
TEST(_timespec_tomicros, test) {
EXPECT_EQ(0, _timespec_tomicros((struct timespec){0, 0}));
EXPECT_EQ(1, _timespec_tomicros((struct timespec){0, 1}));
EXPECT_EQ(2000123, _timespec_tomicros((struct timespec){2, 123000}));
EXPECT_EQ(INT64_MAX, _timespec_tomicros((struct timespec){INT64_MAX, 0}));
EXPECT_EQ(INT64_MIN, _timespec_tomicros((struct timespec){INT64_MIN, 0}));

View file

@ -22,6 +22,7 @@
#include "libc/calls/struct/timespec.h"
#include "libc/errno.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/sysv/consts/clock.h"
#include "libc/sysv/consts/itimer.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
@ -41,11 +42,16 @@ TEST(nanosleep, testInvalid) {
EXPECT_SYS(EINVAL, -1, nanosleep(&ts, 0));
}
TEST(nanosleep, testNoSignalIsDelivered_remIsSetToZero) {
TEST(nanosleep, testNoSignalIsDelivered) {
struct timespec ts = {0, 1};
ASSERT_SYS(0, 0, nanosleep(&ts, &ts));
EXPECT_EQ(0, ts.tv_sec);
EXPECT_EQ(0, ts.tv_nsec);
ASSERT_SYS(0, 0, nanosleep(&ts, 0));
}
TEST(clock_nanosleep, testNoSignalIsDelivered) {
struct timespec ts = {0, 1};
ASSERT_SYS(0, 0, clock_nanosleep(CLOCK_REALTIME, 0, &ts, &ts));
ASSERT_SYS(0, 0, clock_nanosleep(CLOCK_REALTIME, 0, &ts, 0));
}
TEST(nanosleep, testInterrupt_remIsUpdated) {

View file

@ -19,8 +19,10 @@
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/utime.h"
@ -33,6 +35,50 @@ void SetUpOnce(void) {
ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath fattr", 0));
}
TEST(utimes, test) {
struct stat st;
struct timeval tv[2] = {
{1655455857, 1}, // atim: Fri Jun 17 2022 08:50:57 GMT+0000
{827727928, 2}, // mtim: Mon Mar 25 1996 04:25:28 GMT+0000
};
EXPECT_SYS(0, 0, touch("boop", 0644));
EXPECT_SYS(0, 0, utimes("boop", tv));
EXPECT_SYS(0, 0, stat("boop", &st));
EXPECT_EQ(1655455857, st.st_atim.tv_sec);
EXPECT_EQ(827727928, st.st_mtim.tv_sec);
if (IsLinux() && !__is_linux_2_6_23()) {
// rhel5 only seems to have second granularity
EXPECT_EQ(0, st.st_atim.tv_nsec);
EXPECT_EQ(0, st.st_mtim.tv_nsec);
} else {
EXPECT_EQ(1000, st.st_atim.tv_nsec);
EXPECT_EQ(2000, st.st_mtim.tv_nsec);
}
}
TEST(futimes, test) {
if (IsLinux() && !__is_linux_2_6_23()) return;
struct stat st;
struct timeval tv[2] = {{1655455857, 1}, {827727928, 2}};
EXPECT_SYS(0, 3, creat("boop", 0644));
EXPECT_SYS(0, 0, futimes(3, tv));
EXPECT_SYS(0, 0, fstat(3, &st));
EXPECT_EQ(1655455857, st.st_atim.tv_sec);
EXPECT_EQ(827727928, st.st_mtim.tv_sec);
EXPECT_EQ(1000, st.st_atim.tv_nsec);
EXPECT_EQ(2000, st.st_mtim.tv_nsec);
EXPECT_SYS(0, 0, close(3));
}
TEST(futimes, rhel5_enosys) {
if (IsLinux() && !__is_linux_2_6_23()) {
struct timeval tv[2] = {{1655455857}, {827727928}};
EXPECT_SYS(0, 3, creat("boop", 0644));
EXPECT_SYS(ENOSYS, -1, futimes(3, tv));
EXPECT_SYS(0, 0, close(3));
}
}
TEST(utimensat, test) {
struct stat st;
struct timespec ts[2] = {