feat(systime): gettimeofday/itimer/utimes

This commit is contained in:
2026-09-05 22:43:39 -04:00
parent d495d3eb89
commit c67e890b17
8 changed files with 780 additions and 1 deletions
+9 -1
View File
@@ -86,12 +86,20 @@ typedef struct
typedef unsigned long sigset_t; typedef unsigned long sigset_t;
#endif #endif
/* Elapsed time in seconds and microseconds (see select()'s timeout). */ /*
* Elapsed time in seconds and microseconds (see select()'s timeout).
* Shared guard with <sys/time.h>, the canonical POSIX home of struct
* timeval: both headers define the same layout under
* VLIBC_TIMEVAL_DEFINED, so either may be included first at any level.
*/
#ifndef VLIBC_TIMEVAL_DEFINED
#define VLIBC_TIMEVAL_DEFINED
struct timeval struct timeval
{ {
time_t tv_sec; time_t tv_sec;
suseconds_t tv_usec; suseconds_t tv_usec;
}; };
#endif
/* /*
* Wait for readiness on the descriptors marked in readfds, writefds and * Wait for readiness on the descriptors marked in readfds, writefds and
+186
View File
@@ -0,0 +1,186 @@
#ifndef VLIBC_SYS_TIME_H
#define VLIBC_SYS_TIME_H
/*
* vlibc — <sys/time.h>.
*
* Wall-clock access and per-process interval timers expressed in
* microseconds. Everything here is XSI or BSD legacy — POSIX.1-2008 base
* leaves only timerisset in this header — so the whole surface is gated at
* level 2:
*
* Level 2 (muslmimic): gettimeofday, settimeofday, getitimer,
* setitimer, utimes, futimes, lutimes,
* struct timeval, struct itimerval,
* struct timezone, the timer* macros.
*
* struct timeval is the same type <sys/select.h> uses for select()'s
* timeout; that header defines it under the shared guard below so the two
* headers agree and either may be included first. The layout is the kernel
* ABI on x86_64 (two longs), which is why gettimeofday/setitimer pass it
* through unmodified.
*
* ITIMER_REAL/ITIMER_VIRTUAL/ITIMER_PROF are kernel ABI values (the
* getitimer/setitimer `which` argument). None of these declarations carries
* an intent attribute: every function performs I/O with side effects and
* reports failures through errno, so const/pure would be unsound.
*/
#include <vlibc/features.h>
#include <sys/types.h> /* time_t, suseconds_t */
#ifdef __cplusplus
extern "C" {
#endif
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): XSI/BSD date-and-time interfaces. */
/* Interval-timer kinds (kernel ABI; the `which` argument). */
#define ITIMER_REAL 0 /* count down in real time */
#define ITIMER_VIRTUAL 1 /* count down in process virtual time */
#define ITIMER_PROF 2 /* count down in process virtual + system time */
/*
* Elapsed time in seconds and microseconds. Shared guard with
* <sys/select.h>, which needs struct timeval for select()'s timeout at
* level 1 and defines it under the same macro; either header may therefore
* be included first. tv_usec holds 0..999999.
*/
#ifndef VLIBC_TIMEVAL_DEFINED
#define VLIBC_TIMEVAL_DEFINED
struct timeval
{
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
#endif
/*
* An interval timer setting: it_value is the time to the next expiry,
* it_interval the period between expiries after the first (both zero for a
* one-shot or a disarmed timer). Kernel ABI layout (x86_64): two timevals.
*/
struct itimerval
{
struct timeval it_interval; /* interval between periodic expiries */
struct timeval it_value; /* time to the next expiry */
};
/*
* Historic timezone record for settimeofday(). POSIX.1-2008 removed struct
* timezone; it is kept for the legacy gettimeofday/settimeofday signatures,
* whose tz argument is ignored (see src/time/gettimeofday.c).
*/
struct timezone
{
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of daylight-saving correction */
};
/* True when the timer described by tp is armed. */
#define timerisset(tp) ((tp)->tv_sec != 0 || (tp)->tv_usec != 0)
/* Disarm the timer described by tp (zero both fields). */
#define timerclear(tp) ((tp)->tv_sec = (tp)->tv_usec = 0)
/*
* Set *result to *a plus *b, normalizing the carry into tv_sec. The
* arguments may be evaluated more than once (classic BSD form).
*/
#define timeradd(a, b, result) \
do \
{ \
(result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
if ((result)->tv_usec >= 1000000) \
{ \
(result)->tv_sec++; \
(result)->tv_usec -= 1000000; \
} \
} while (0)
/*
* Set *result to *a minus *b, normalizing the borrow out of tv_sec. The
* arguments may be evaluated more than once (classic BSD form).
*/
#define timersub(a, b, result) \
do \
{ \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) \
{ \
(result)->tv_sec--; \
(result)->tv_usec += 1000000; \
} \
} while (0)
/*
* True when *a op *b, compared first by tv_sec and, on equality, by
* tv_usec. op is a comparison operator (timercmp(x, y, <=)). The
* arguments may be evaluated more than once (classic BSD form).
*/
/* clang-format off */
#define timercmp(a, b, op) \
(((a)->tv_sec == (b)->tv_sec) ? ((a)->tv_usec op (b)->tv_usec) : ((a)->tv_sec op (b)->tv_sec))
/* clang-format on */
/*
* Read the current wall-clock time into tv (seconds since the Epoch and
* microseconds 0..999999). tz is ignored for compatibility with the
* historic two-argument form and may be anything, including NULL; tv may
* also be NULL, in which case nothing is written. Return 0, or -1 with
* errno set when tv points outside the address space.
*/
int
gettimeofday(struct timeval *restrict tv, void *restrict tz);
/*
* Set the kernel's idea of the current time from tv. tz, when not NULL, is
* honored only together with tv; passing tz alone is unsupported (ENOTSUP).
* Requires privilege. Return 0, or -1 with errno set.
*/
int
settimeofday(const struct timeval *tv, const struct timezone *tz);
/*
* Store the current setting of interval timer which (ITIMER_REAL,
* ITIMER_VIRTUAL or ITIMER_PROF) through value. Return 0, or -1 with errno
* set for an unknown which or when value points outside the address space.
*/
int
getitimer(int which, struct itimerval *value);
/*
* Arm interval timer which from value (when value is not NULL) and store
* the previous setting through ovalue (when not NULL). A value whose
* it_value is zero disarms the timer. Return 0, or -1 with errno set.
*/
int
setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);
/*
* Set the access (times[0]) and modification (times[1]) timestamps of path.
* A NULL times array sets both to the current time. Return 0, or -1 with
* errno set.
*/
int
utimes(const char *path, const struct timeval times[2]);
/* utimes() on the open file fd. */
int
futimes(int fd, const struct timeval times[2]);
/* utimes() on the symbolic link path itself, never following it. */
int
lutimes(const char *path, const struct timeval times[2]);
#endif /* VLIBC_LEVEL_GE(2) */
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_SYS_TIME_H */
+26
View File
@@ -0,0 +1,26 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/time.h>
#include "../internal/syscall.h"
#if VLIBC_LEVEL_GE(2)
/*
* getitimer: read the current setting of interval timer which (ITIMER_REAL,
* ITIMER_VIRTUAL or ITIMER_PROF), via SYS_getitimer. struct itimerval is
* layout-identical to the kernel's two-timeval structure on x86_64, so the
* buffer passes straight through and the kernel reports whether the timer
* is armed (it_value nonzero) and its period (it_interval). An unknown
* which or an invalid value pointer is rejected by the kernel (EINVAL /
* EFAULT), reported as -1 with errno set.
*/
int
getitimer(int which, struct itimerval *value)
{
return syscall_ret(__syscall2(SYS_getitimer, which, (long)value));
}
#endif /* VLIBC_LEVEL_GE(2) */
+29
View File
@@ -0,0 +1,29 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/time.h>
#include "../internal/syscall.h"
#if VLIBC_LEVEL_GE(2)
/*
* gettimeofday: the current wall-clock time, via SYS_gettimeofday.
*
* The kernel writes the two-field struct timeval (tv_sec, tv_usec) that is
* layout-identical to the public struct timeval on x86_64, so the pointer
* passes straight through. The tz argument is deliberately ignored: the
* historic second parameter is obsolete, POSIX.1-2008 removed struct
* timezone, and no caller may rely on it being filled — NULL is passed to
* the kernel, which then leaves any timezone record untouched. tv may also
* be NULL, in which case the kernel writes nothing and 0 is returned.
*/
int
gettimeofday(struct timeval *restrict tv, void *restrict tz)
{
(void)tz;
return syscall_ret(__syscall2(SYS_gettimeofday, (long)tv, 0));
}
#endif /* VLIBC_LEVEL_GE(2) */
+26
View File
@@ -0,0 +1,26 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/time.h>
#include "../internal/syscall.h"
#if VLIBC_LEVEL_GE(2)
/*
* setitimer: arm (or disarm) interval timer which (ITIMER_REAL,
* ITIMER_VIRTUAL or ITIMER_PROF), via SYS_setitimer. struct itimerval is
* layout-identical to the kernel's two-timeval structure on x86_64, so both
* pointers pass straight through: value NULL leaves the timer unchanged,
* it_value zero disarms it, and the previous setting is stored through
* ovalue when it is not NULL. An unknown which or an invalid pointer is
* rejected by the kernel (EINVAL / EFAULT), reported as -1 with errno set.
*/
int
setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue)
{
return syscall_ret(__syscall3(SYS_setitimer, which, (long)value, (long)ovalue));
}
#endif /* VLIBC_LEVEL_GE(2) */
+34
View File
@@ -0,0 +1,34 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <sys/time.h>
#include "../internal/syscall.h"
#if VLIBC_LEVEL_GE(2)
/*
* settimeofday: set the kernel clock, via SYS_settimeofday.
*
* Requires privilege (the unprivileged caller gets EPERM from the kernel).
* The tz argument is honored only alongside tv; passing a timezone record
* while tv is NULL would ask the kernel to update its timezone state alone,
* a legacy use no caller needs, so it is refused up front with ENOTSUP (the
* Linux spelling of the value musl reports here; <errno.h> carries it as
* EOPNOTSUPP).
*/
int
settimeofday(const struct timeval *tv, const struct timezone *tz)
{
if (tz != NULL && tv == NULL)
{
errno = EOPNOTSUPP;
return -1;
}
return syscall_ret(__syscall2(SYS_settimeofday, (long)tv, (long)tz));
}
#endif /* VLIBC_LEVEL_GE(2) */
+97
View File
@@ -0,0 +1,97 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
#include <sys/time.h>
#include "../internal/syscall.h"
#if VLIBC_LEVEL_GE(2)
/*
* utimes/futimes/lutimes: set file timestamps with microsecond precision,
* over SYS_utimensat (the dedicated SYS_utimes syscall is legacy and only
* accepts second+microsecond values through a translated struct; utimensat
* is the modern ABI and covers all three forms). The classic syscall family
* takes two struct timeval values, so the timeval pair is converted to the
* kernel timespec pair (tv_usec * 1000 -> tv_nsec) the way glibc's own
* wrapper does; a NULL times array is passed through unchanged and the
* kernel then sets both timestamps to the current time.
*
* The kernel AT_ flag values below come from <fcntl.h> (todo 21) and are
* transcribed locally with the house prefix so this translation unit stays
* self-contained.
*/
#define VLIBC_SYS_TIME_AT_FDCWD (-100) /* resolve path from cwd */
#define VLIBC_SYS_TIME_AT_SYMLINK_NOFOLLOW 0x100 /* operate on the link */
/* Convert one timeval into the kernel's timespec layout. */
static void
systime_to_timespec(const struct timeval *tv, struct timespec *ts)
{
ts->tv_sec = tv->tv_sec;
ts->tv_nsec = (long)tv->tv_usec * 1000L;
}
/*
* utimes: set path's access (times[0]) and modification (times[1])
* timestamps; NULL times sets both to the current time.
*/
int
utimes(const char *path, const struct timeval times[2])
{
struct timespec kernel_times[2];
if (times != NULL)
{
systime_to_timespec(&times[0], &kernel_times[0]);
systime_to_timespec(&times[1], &kernel_times[1]);
return syscall_ret(
__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path, (long)kernel_times, 0));
}
return syscall_ret(__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path, 0, 0));
}
/*
* futimes: utimes on the open file fd. On Linux, utimensat with a NULL path
* operates on the file referenced by the directory descriptor, which for a
* plain descriptor is the file itself.
*/
int
futimes(int fd, const struct timeval times[2])
{
struct timespec kernel_times[2];
if (times != NULL)
{
systime_to_timespec(&times[0], &kernel_times[0]);
systime_to_timespec(&times[1], &kernel_times[1]);
return syscall_ret(__syscall4(SYS_utimensat, fd, 0, (long)kernel_times, 0));
}
return syscall_ret(__syscall4(SYS_utimensat, fd, 0, 0, 0));
}
/*
* lutimes: utimes on the symbolic link path itself. AT_SYMLINK_NOFOLLOW
* makes utimensat update the link's own timestamps rather than its target's.
*/
int
lutimes(const char *path, const struct timeval times[2])
{
struct timespec kernel_times[2];
if (times != NULL)
{
systime_to_timespec(&times[0], &kernel_times[0]);
systime_to_timespec(&times[1], &kernel_times[1]);
return syscall_ret(__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path,
(long)kernel_times, VLIBC_SYS_TIME_AT_SYMLINK_NOFOLLOW));
}
return syscall_ret(__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path, 0,
VLIBC_SYS_TIME_AT_SYMLINK_NOFOLLOW));
}
#endif /* VLIBC_LEVEL_GE(2) */
+373
View File
@@ -0,0 +1,373 @@
/*
* vlibc — sys/time.h test (todo 27).
*
* Exercises the whole <sys/time.h> surface end to end:
*
* 1. gettimeofday: plausible post-2001 epoch with tv_usec in [0, 1e6);
* a second call reads a time no earlier than the first; the
* (tv, tz) = (NULL, NULL) form returns 0 without writing anything.
* 2. The timer* macros: timerclear/timerisset, timeradd/timersub carry
* and borrow, timercmp ordering.
* 3. ITIMER_REAL: arm a 20 ms one-shot, then poll getitimer (with raw
* 1 ms nanosleeps) until it reports a disarmed timer. SIGALRM is
* blocked first via raw rt_sigprocmask so the expiry cannot terminate
* the process; the timer still disarms on expiry. The elapsed time
* must be at least 5 ms (proving a real wait, not an instant cancel)
* and under 500 ms (proving the timer actually ran down).
* 4. utimes/futimes/lutimes on a scratch file + symlink: stat (todo 22)
* confirms the access and modification timestamps land with the exact
* second + microsecond-derived nanosecond values, and that lutimes
* changed the link's own timestamps, never the target's.
*
* The -f mode runs the failure shapes — setitimer/getitimer with an
* unknown `which`, settimeofday with tz alone (ENOTSUP), utimes/futimes/
* lutimes on nonexistent targets — asserting return values only (errno is
* never read: vlibc's errno slot collides with the host TCB). It leaves
* via a raw SYS_exit_group so no host cleanup runs after the library's
* errno writes (house pattern, tests/test_stat.c).
*
* No host libc headers are included (the -Iinclude path would shadow
* GCC's internal headers); diagnostics go through raw SYS_write. The whole
* body mirrors the header's gate: <sys/time.h> is entirely level 2, so at
* level 1 this TU compiles to a no-op runner.
*/
#include <sys/stat.h>
#include <sys/time.h>
#include "../src/internal/syscall.h"
/* Write a NUL-terminated string to fd via the raw syscall layer. The
* optimize attribute keeps GCC from lowering the length loop into a
* strlen call, which would leave a vlibc-owned symbol undefined in this
* host-linked standalone binary (house idiom, see src/string). */
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
#if VLIBC_LEVEL_GE(2)
/* Kernel-UAPI open/at values, local to this test (include/fcntl.h). */
#define T27_AT_FDCWD (-100)
#define T27_O_RDWR 0x2
#define T27_O_CREAT 0x40
#define T27_O_EXCL 0x80
/* SIGALRM number and its bit in a one-word kernel sigset. */
#define T27_SIGALRM 14
#define T27_SIG_BLOCK 0
/* Scratch paths in /tmp (the test runs from an arbitrary cwd). */
#define T27_FILE "/tmp/vlibc_systime_t27_file"
#define T27_LINK "/tmp/vlibc_systime_t27_link"
#define T27_LINK_TARGET "vlibc_systime_t27_file"
#define T27_MISSING "/nonexistent-vlibc-t27-xyz"
static int failures;
/* Monotonic clock reading in milliseconds (raw SYS_clock_gettime). */
static long
mono_ms(void)
{
struct timespec ts;
if (__syscall2(SYS_clock_gettime, 1, (long)&ts) == 0)
{
return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
}
return -1;
}
static void
check(int ok, const char *msg)
{
if (ok)
{
say(1, "ok ");
}
else
{
say(1, "FAIL ");
failures++;
}
say(1, msg);
say(1, "\n");
}
/* 1. gettimeofday: epoch sanity, tv_usec range, non-decreasing reads. */
static void
gettimeofday_scenario(void)
{
struct timeval a;
struct timeval b;
check(gettimeofday(&a, NULL) == 0, "gettimeofday(tv, NULL) returns 0");
check(a.tv_sec > 1000000000, "gettimeofday tv_sec is past 2001");
check(a.tv_usec >= 0 && a.tv_usec < 1000000, "gettimeofday tv_usec in [0, 1e6)");
check(gettimeofday(NULL, NULL) == 0, "gettimeofday(NULL, NULL) returns 0");
check(gettimeofday(&b, NULL) == 0, "second gettimeofday returns 0");
check(b.tv_sec > a.tv_sec || (b.tv_sec == a.tv_sec && b.tv_usec >= a.tv_usec),
"gettimeofday reads do not go backwards");
}
/* 2. The timer* arithmetic macros. */
static void
timer_macro_scenario(void)
{
struct timeval a = {1, 500000};
struct timeval b = {0, 900000};
struct timeval r;
timerclear(&r);
check(!timerisset(&r), "timerclear zeroes a timeval");
check(timerisset(&a), "timerisset true for an armed timeval");
timersub(&a, &b, &r);
check(r.tv_sec == 0 && r.tv_usec == 600000, "timersub borrows 1 s into 600000 us");
timeradd(&b, &r, &r);
check(r.tv_sec == 1 && r.tv_usec == 500000, "timeradd carries 1e6 us into tv_sec");
check(timercmp(&a, &r, ==), "timeradd/timersub round-trip");
check(timercmp(&a, &b, >), "timercmp(a, b, >) when a is later");
check(timercmp(&b, &a, <), "timercmp(b, a, <) when b is earlier");
check(!timercmp(&a, &b, ==), "timercmp(a, b, ==) false on unequal values");
}
/* 3. ITIMER_REAL: a 20 ms one-shot runs down and disarms. */
static void
itimer_scenario(void)
{
struct itimerval zero = {{0, 0}, {0, 0}};
struct itimerval it = {{0, 0}, {0, 20000}};
struct itimerval got;
struct timespec nap = {0, 1000000L};
unsigned long sigalrm_bit = 1UL << (T27_SIGALRM - 1);
long t0;
long elapsed;
int fired = 0;
int attempt;
check(__syscall4(SYS_rt_sigprocmask, T27_SIG_BLOCK, (long)&sigalrm_bit, 0, 8) == 0,
"raw rt_sigprocmask blocks SIGALRM");
for (attempt = 0; attempt < 5 && !fired; attempt++)
{
long r;
setitimer(ITIMER_REAL, &zero, NULL); /* disarm any leftover expiry */
t0 = mono_ms();
check(setitimer(ITIMER_REAL, &it, NULL) == 0, "setitimer(ITIMER_REAL, 20ms) returns 0");
r = getitimer(ITIMER_REAL, &got);
if (r != 0)
{
check(0, "getitimer(ITIMER_REAL) returns 0");
break;
}
if (got.it_value.tv_sec == 0 && got.it_value.tv_usec == 0)
{
continue; /* the whole interval elapsed before the first read */
}
for (;;)
{
long now;
if (getitimer(ITIMER_REAL, &got) != 0)
{
break;
}
if (got.it_value.tv_sec == 0 && got.it_value.tv_usec == 0)
{
fired = 1;
break;
}
now = mono_ms();
if (now < 0 || now - t0 >= 500)
{
break;
}
__syscall2(SYS_nanosleep, (long)&nap, 0);
}
if (!fired)
{
continue;
}
elapsed = mono_ms() - t0;
check(elapsed >= 5 && elapsed < 500,
"20ms ITIMER_REAL fired after a real (5..500 ms) wait");
}
check(fired, "ITIMER_REAL timer expired on its own");
}
/* 4. utimes/futimes/lutimes set the exact timestamps stat() reports. */
static void
utimes_scenario(void)
{
struct timeval tv[2];
struct timeval tv2[2];
struct timeval tv3[2];
struct stat st = {0};
struct stat lst = {0};
static const char payload[] = "vlibc systime\n";
long fd;
long wrote;
/* Drop leftovers from a crashed earlier run, then create the file. */
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_LINK, 0);
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_FILE, 0);
fd = __syscall4(SYS_openat, T27_AT_FDCWD, (long)T27_FILE, T27_O_RDWR | T27_O_CREAT | T27_O_EXCL,
0644);
check(fd >= 0, "raw openat O_CREAT|O_EXCL creates the scratch file");
if (fd < 0)
{
return;
}
wrote = __syscall3(SYS_write, fd, (long)payload, (long)(sizeof(payload) - 1));
check(wrote == (long)(sizeof(payload) - 1), "raw write fills the scratch file");
check(__syscall1(SYS_close, fd) == 0, "raw close of the scratch file");
/* utimes: atime {1111111111, 222222} / mtime {1234567890, 333333}. */
tv[0].tv_sec = 1111111111;
tv[0].tv_usec = 222222;
tv[1].tv_sec = 1234567890;
tv[1].tv_usec = 333333;
check(utimes(T27_FILE, tv) == 0, "utimes(file, times) returns 0");
check(stat(T27_FILE, &st) == 0, "stat(file) after utimes returns 0");
check(st.st_atim.tv_sec == 1111111111 && st.st_atim.tv_nsec == 222222000,
"utimes set atime second + microsecond-derived nsec");
check(st.st_mtim.tv_sec == 1234567890 && st.st_mtim.tv_nsec == 333333000,
"utimes set mtime second + microsecond-derived nsec");
/* futimes on a fresh descriptor of the same file. */
fd = __syscall4(SYS_openat, T27_AT_FDCWD, (long)T27_FILE, T27_O_RDWR, 0);
check(fd >= 0, "raw openat reopens the file for futimes");
if (fd < 0)
{
return;
}
tv2[0].tv_sec = 1111111112;
tv2[0].tv_usec = 444444;
tv2[1].tv_sec = 1234567891;
tv2[1].tv_usec = 555555;
check(futimes((int)fd, tv2) == 0, "futimes(fd, times) returns 0");
check(stat(T27_FILE, &st) == 0, "stat(file) after futimes returns 0");
check(st.st_atim.tv_sec == 1111111112 && st.st_atim.tv_nsec == 444444000,
"futimes set atime through the descriptor");
check(st.st_mtim.tv_sec == 1234567891 && st.st_mtim.tv_nsec == 555555000,
"futimes set mtime through the descriptor");
check(__syscall1(SYS_close, fd) == 0, "raw close of the futimes descriptor");
/* lutimes on a symlink: the link's own times move, the target's do not. */
check(__syscall2(SYS_symlink, (long)T27_LINK_TARGET, (long)T27_LINK) == 0,
"raw SYS_symlink creates the link");
tv3[0].tv_sec = 1111111113;
tv3[0].tv_usec = 666666;
tv3[1].tv_sec = 1234567892;
tv3[1].tv_usec = 777777;
check(lutimes(T27_LINK, tv3) == 0, "lutimes(link, times) returns 0");
check(lstat(T27_LINK, &lst) == 0, "lstat(link) after lutimes returns 0");
check(lst.st_mtim.tv_sec == 1234567892 && lst.st_mtim.tv_nsec == 777777000,
"lutimes set the link's own mtime");
check(stat(T27_LINK, &st) == 0, "stat(link) follows to the file");
check(st.st_mtim.tv_sec == 1234567891 && st.st_mtim.tv_nsec == 555555000,
"lutimes left the target file's mtime untouched");
/* A NULL times array sets both timestamps to "now" (after 2001). */
check(utimes(T27_FILE, NULL) == 0, "utimes(file, NULL) returns 0");
check(stat(T27_FILE, &st) == 0, "stat(file) after utimes(NULL) returns 0");
check(st.st_mtim.tv_sec > 1000000000, "utimes(NULL) set mtime to the current time");
}
/* Remove the scratch paths. */
static void
cleanup_scenario(void)
{
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_LINK, 0);
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_FILE, 0);
}
/* The failure shapes; return values only, never errno. Exit via a raw
* SYS_exit_group: the library's errno writes on these paths corrupt glibc's
* private dtv slot at %fs:0+8, so host cleanup must never run (house
* pattern, tests/test_stat.c). */
static void
failure_scenarios(void)
{
struct itimerval it;
struct itimerval got;
struct timeval tv[2];
struct timezone tz;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 0;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 1000;
tv[0].tv_sec = 1;
tv[0].tv_usec = 0;
tv[1].tv_sec = 2;
tv[1].tv_usec = 0;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
check(setitimer(999, &it, NULL) == -1, "-f setitimer(999) returns -1");
check(getitimer(999, &got) == -1, "-f getitimer(999) returns -1");
check(settimeofday(NULL, &tz) == -1, "-f settimeofday(NULL, tz) returns -1");
check(utimes(T27_MISSING, tv) == -1, "-f utimes(nonexistent) returns -1");
check(futimes(-1, tv) == -1, "-f futimes(-1) returns -1");
check(lutimes(T27_MISSING, tv) == -1, "-f lutimes(nonexistent) returns -1");
if (failures == 0)
{
say(1, "all systime failure scenarios passed\n");
}
__syscall1(SYS_exit_group, failures == 0 ? 0 : 1);
/* not reached */
}
int
main(int argc, char **argv)
{
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
{
failure_scenarios();
}
gettimeofday_scenario();
timer_macro_scenario();
itimer_scenario();
utimes_scenario();
cleanup_scenario();
if (failures == 0)
{
say(1, "all systime tests passed\n");
}
else
{
say(1, "FAILURES\n");
}
return failures == 0 ? 0 : 1;
}
#else /* !VLIBC_LEVEL_GE(2) */
/*
* Level 1: every <sys/time.h> symbol is gated at level 2, so there is
* nothing to run. Keep the TU compilable at any configured profile.
*/
int
main(void)
{
say(1, "SKIP: <sys/time.h> is level 2, not available here\n");
return 0;
}
#endif /* VLIBC_LEVEL_GE(2) */