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