/* * vlibc — sys/times.h test (todo 35). * * Exercises the whole surface end to end: * * 1. times(&t) succeeds: the returned tick counter is not -1 and the four * struct tms fields are non-negative (fresh processes commonly show * zeros here, which is legal). * 2. Monotonicity across real work: a few-million-iteration busy loop * burns user CPU, and a second times() read then reports tms_utime no * smaller than the first. The tick counter itself must never go * backwards between two reads. * 3. A rapid-fire loop of times() calls (no work between them) keeps the * tick counter strictly non-decreasing across every adjacent pair. * * The -f mode runs the one failure shape — times() on an out-of-range * buffer pointer, which the kernel rejects with EFAULT — asserting the * return value 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 write (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 "../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) static int failures; static void check(int ok, const char *msg) { if (ok) { say(1, "ok "); } else { say(1, "FAIL "); failures++; } say(1, msg); say(1, "\n"); } /* Burn a few million iterations of real user CPU so tms_utime has a chance * to advance between two times() reads. The sink is volatile so the loop * cannot be optimized away. */ static volatile unsigned long t35_sink; static void burn_cpu(void) { for (unsigned long i = 0; i < 5000000UL; i++) { t35_sink += i; } } /* 1-3. Success shapes: plausible fields, monotonic across work and across * rapid reads. */ static void times_scenario(void) { struct tms t1; struct tms t2; struct tms probe; clock_t r1; clock_t r2; clock_t prev; r1 = times(&t1); check(r1 != (clock_t)-1, "times(&t) does not return -1"); check(t1.tms_utime >= 0 && t1.tms_stime >= 0, "tms_utime/tms_stime are non-negative"); check(t1.tms_cutime >= 0 && t1.tms_cstime >= 0, "tms_cutime/tms_cstime are non-negative"); burn_cpu(); r2 = times(&t2); check(r2 != (clock_t)-1, "second times(&t) does not return -1"); check(r2 >= r1, "tick counter does not go backwards across work"); check(t2.tms_utime >= t1.tms_utime, "tms_utime is nondecreasing after a CPU burn"); check(t2.tms_stime >= t1.tms_stime, "tms_stime is nondecreasing after a CPU burn"); check(t2.tms_cutime >= t1.tms_cutime && t2.tms_cstime >= t1.tms_cstime, "child-time fields are nondecreasing (no waited-for children here)"); /* Rapid-fire reads with no work between them: the kernel tick counter * is monotonic, so every adjacent pair must be non-decreasing. */ prev = times(&probe); check(prev != (clock_t)-1, "rapid-fire times() baseline is not -1"); for (int i = 0; i < 100000; i++) { clock_t now = times(&probe); if (now == (clock_t)-1) { check(0, "rapid-fire times() never returns -1"); break; } if (now < prev) { check(0, "rapid-fire tick counter never goes backwards"); break; } prev = now; } check(prev != (clock_t)-1, "rapid-fire times() loop completed"); } /* The failure shapes; return values only, never errno. Exit via a raw * SYS_exit_group: the library's errno write on this path corrupts glibc's * private dtv slot at %fs:0+8, so host cleanup must never run (house * pattern, tests/test_stat.c). * * The probe buffer is the page-start address 1: non-NULL (the modern * kernel treats times(NULL) as legal and returns the tick counter without * writing anything) but unmapped, so SYS_times' copy_to_user fails with * EFAULT and the wrapper returns -1. */ static void failure_scenarios(void) { check(times((struct tms *)(unsigned long)1) == (clock_t)-1, "-f times(out-of-range buf) returns -1"); say(1, "times: raw kernel ticks reported; not rescaled to clock() units\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(); } times_scenario(); if (failures == 0) { say(1, "all times 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) */