feat(systime): gettimeofday/itimer/utimes
This commit is contained in:
@@ -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) */
|
||||
Reference in New Issue
Block a user