Files
vlibc/tests/test_systime.c
T

374 lines
12 KiB
C

/*
* 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) */