From c67e890b17eae739c9028bd312e5082e836ace95 Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Sat, 5 Sep 2026 22:43:39 -0400 Subject: [PATCH] feat(systime): gettimeofday/itimer/utimes --- include/sys/select.h | 10 +- include/sys/time.h | 186 ++++++++++++++++++++ src/time/getitimer.c | 26 +++ src/time/gettimeofday.c | 29 ++++ src/time/setitimer.c | 26 +++ src/time/settimeofday.c | 34 ++++ src/time/utimes.c | 97 +++++++++++ tests/test_systime.c | 373 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 780 insertions(+), 1 deletion(-) create mode 100644 include/sys/time.h create mode 100644 src/time/getitimer.c create mode 100644 src/time/gettimeofday.c create mode 100644 src/time/setitimer.c create mode 100644 src/time/settimeofday.c create mode 100644 src/time/utimes.c create mode 100644 tests/test_systime.c diff --git a/include/sys/select.h b/include/sys/select.h index 08c3347..0c4e245 100644 --- a/include/sys/select.h +++ b/include/sys/select.h @@ -86,12 +86,20 @@ typedef struct typedef unsigned long sigset_t; #endif -/* Elapsed time in seconds and microseconds (see select()'s timeout). */ +/* + * Elapsed time in seconds and microseconds (see select()'s timeout). + * Shared guard with , 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 { time_t tv_sec; suseconds_t tv_usec; }; +#endif /* * Wait for readiness on the descriptors marked in readfds, writefds and diff --git a/include/sys/time.h b/include/sys/time.h new file mode 100644 index 0000000..f1bf89a --- /dev/null +++ b/include/sys/time.h @@ -0,0 +1,186 @@ +#ifndef VLIBC_SYS_TIME_H +#define VLIBC_SYS_TIME_H + +/* + * vlibc — . + * + * 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 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 + +#include /* 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 + * , 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 */ diff --git a/src/time/getitimer.c b/src/time/getitimer.c new file mode 100644 index 0000000..45e24a7 --- /dev/null +++ b/src/time/getitimer.c @@ -0,0 +1,26 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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) */ diff --git a/src/time/gettimeofday.c b/src/time/gettimeofday.c new file mode 100644 index 0000000..ef28357 --- /dev/null +++ b/src/time/gettimeofday.c @@ -0,0 +1,29 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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) */ diff --git a/src/time/setitimer.c b/src/time/setitimer.c new file mode 100644 index 0000000..c306073 --- /dev/null +++ b/src/time/setitimer.c @@ -0,0 +1,26 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#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) */ diff --git a/src/time/settimeofday.c b/src/time/settimeofday.c new file mode 100644 index 0000000..a4b8488 --- /dev/null +++ b/src/time/settimeofday.c @@ -0,0 +1,34 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#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; 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) */ diff --git a/src/time/utimes.c b/src/time/utimes.c new file mode 100644 index 0000000..0372736 --- /dev/null +++ b/src/time/utimes.c @@ -0,0 +1,97 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#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 (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(×[0], &kernel_times[0]); + systime_to_timespec(×[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(×[0], &kernel_times[0]); + systime_to_timespec(×[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(×[0], &kernel_times[0]); + systime_to_timespec(×[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) */ diff --git a/tests/test_systime.c b/tests/test_systime.c new file mode 100644 index 0000000..2226652 --- /dev/null +++ b/tests/test_systime.c @@ -0,0 +1,373 @@ +/* + * vlibc — sys/time.h test (todo 27). + * + * Exercises the whole 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: is entirely level 2, so at + * level 1 this TU compiles to a no-op runner. + */ + +#include + +#include + +#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 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: is level 2, not available here\n"); + return 0; +} + +#endif /* VLIBC_LEVEL_GE(2) */