/* * vlibc — uname/gethostname/sysconf/pathconf test (todo 32). * * Exercises the system-configuration surface end to end: * * 1. sysconf(_SC_PAGESIZE) equals the AT_PAGESZ entry read from * /proc/self/auxv (proving the page size is derived, not hardcoded) * and is a power of two; _SC_PAGE_SIZE is its POSIX synonym. * 2. sysconf(_SC_CLK_TCK) == 100 — the fixed x86_64 USER_HZ constant, * the documented contract with todo 35's times(). * 3. sysconf(_SC_ARG_MAX)/_SC_OPEN_MAX/_SC_NPROCESSORS_ONLN/ * _SC_PHYS_PAGES and friends report sane positive values. * 4. sysconf of an unknown key (999999) returns -1 WITHOUT setting * errno — proven by a host-TCB slot-1 snapshot around the call. * 5. pathconf("/", _PC_PATH_MAX) > 0 and fpathconf on a raw-opened fd * of "/" agrees with pathconf("/") on _PC_NAME_MAX. * 6. Level 2: uname fills every field; machine contains "86_64"; * gethostname returns the same name as uname's nodename, * NUL-terminated; gethostid() == 0. * * The -f mode runs the failure scenarios alone: sysconf(unknown) == -1, * pathconf/fpathconf on a missing path and a closed descriptor == -1, and * gethostname into a 1-byte buffer == -1. Only return values are asserted; * the failing paths make the LIBRARY write errno (syscall_ret / * ENAMETOOLONG), which under a host-linked binary targets glibc's private * dtv slot at %fs:0+8, so -f exits via raw SYS_exit_group (house pattern, * tests/test_stat.c). * * All diagnostics go through raw SYS_write (no host stdio): under * -Iinclude the vlibc public headers shadow GCC's internal ones, so a host * header would not compile. Not part of the library proper; compiled * manually for this todo (the make check wiring is owned by a later todo). */ #include #include #include #include "../src/internal/syscall.h" /* Kernel-UAPI values local to this test (fcntl.h owns the public names). */ #define T32_AT_FDCWD (-100) #define T32_AT_NULL 0 #define T32_AT_PAGESZ 6 #define T32_O_RDONLY 0 #define T32_UNKNOWN_KEY 999999 static int failures; /* 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); } /* Write v in decimal to fd. */ static void say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters) { char buf[24]; int i = (int)sizeof(buf); buf[--i] = '\0'; if (v == 0) { buf[--i] = '0'; } while (v > 0 && i > 0) { buf[--i] = (char)('0' + v % 10); v /= 10; } say(fd, &buf[i]); } static void check(int ok, const char *msg) { if (ok) { say(1, "PASS: "); } else { say(1, "FAIL: "); failures++; } say(1, msg); say(1, "\n"); } /* Host-TCB slot-1 access: the library's errno writes on a failing path * land at %fs:0+8, glibc's dtv pointer. Snapshotting it around a call * that must NOT write errno proves the no-errno contract (task 13 * technique); only vlibc/raw-syscall code runs between the reads. */ static unsigned long tcb_slot1(void) { return *(unsigned long *)((char *)__builtin_thread_pointer() + 8); } /* The AT_PAGESZ entry of the auxiliary vector, read directly from * /proc/self/auxv (the same derivation src/misc/sysconf.c uses), so the * test proves sysconf(_SC_PAGESIZE) tracks the kernel value. */ static long auxv_pagesize(void) { static unsigned long abuf[64] = {0}; /* 1024 bytes, larger than any auxv */ long r; long fd; fd = __syscall3(SYS_openat, T32_AT_FDCWD, (long)"/proc/self/auxv", T32_O_RDONLY); if (fd < 0) { return -1; } r = __syscall3(SYS_read, fd, (long)abuf, (long)sizeof abuf); __syscall1(SYS_close, fd); for (long i = 0; i + 1 < r / (long)sizeof(unsigned long); i += 2) { if (abuf[i] == T32_AT_NULL) { break; } if (abuf[i] == T32_AT_PAGESZ) { return (long)abuf[i + 1]; } } return -1; } /* 1-4. sysconf limits, options, derivation, and the unknown-key contract. */ static void sysconf_scenario(void) { unsigned long saved; long ps; long av; ps = sysconf(_SC_PAGESIZE); av = auxv_pagesize(); check(av > 0, "auxv AT_PAGESZ is readable"); check(ps == av, "sysconf(_SC_PAGESIZE) equals the AT_PAGESZ auxv value"); check(ps > 0 && (ps & (ps - 1)) == 0, "sysconf(_SC_PAGESIZE) is a power of two"); check(sysconf(_SC_PAGE_SIZE) == ps, "sysconf(_SC_PAGE_SIZE) is the _SC_PAGESIZE synonym"); check(sysconf(_SC_CLK_TCK) == 100, "sysconf(_SC_CLK_TCK) is the fixed x86_64 value 100"); check(sysconf(_SC_ARG_MAX) > 0, "sysconf(_SC_ARG_MAX) > 0"); check(sysconf(_SC_CHILD_MAX) > 0, "sysconf(_SC_CHILD_MAX) > 0"); check(sysconf(_SC_NGROUPS_MAX) > 0, "sysconf(_SC_NGROUPS_MAX) > 0"); check(sysconf(_SC_OPEN_MAX) > 0, "sysconf(_SC_OPEN_MAX) > 0"); check(sysconf(_SC_NPROCESSORS_ONLN) >= 1, "sysconf(_SC_NPROCESSORS_ONLN) >= 1"); check(sysconf(_SC_NPROCESSORS_CONF) >= 1, "sysconf(_SC_NPROCESSORS_CONF) >= 1"); check(sysconf(_SC_PHYS_PAGES) > 0, "sysconf(_SC_PHYS_PAGES) > 0"); check(sysconf(_SC_AVPHYS_PAGES) > 0, "sysconf(_SC_AVPHYS_PAGES) > 0"); check(sysconf(_SC_VERSION) == 200809L, "sysconf(_SC_VERSION) is 200809L"); check(sysconf(_SC_HOST_NAME_MAX) == 64, "sysconf(_SC_HOST_NAME_MAX) is 64"); check(sysconf(_SC_LOGIN_NAME_MAX) > 0, "sysconf(_SC_LOGIN_NAME_MAX) > 0"); check(sysconf(_SC_SYMLOOP_MAX) > 0, "sysconf(_SC_SYMLOOP_MAX) > 0"); check(sysconf(_SC_STREAM_MAX) > 0, "sysconf(_SC_STREAM_MAX) > 0"); check(sysconf(_SC_IOV_MAX) > 0, "sysconf(_SC_IOV_MAX) > 0"); check(sysconf(_SC_XOPEN_VERSION) == 700, "sysconf(_SC_XOPEN_VERSION) is 700"); check(sysconf(_SC_THREADS) > 0, "sysconf(_SC_THREADS) > 0"); check(sysconf(_SC_MONOTONIC_CLOCK) > 0, "sysconf(_SC_MONOTONIC_CLOCK) > 0"); saved = tcb_slot1(); check(sysconf(T32_UNKNOWN_KEY) == -1, "sysconf(unknown key) returns -1"); check(tcb_slot1() == saved, "sysconf(unknown key) does not set errno"); } /* 5. pathconf/fpathconf consistency on the root filesystem. */ static void pathconf_scenario(void) { long name_max; long fd; check(pathconf("/", _PC_PATH_MAX) > 0, "pathconf(/, _PC_PATH_MAX) > 0"); check(pathconf("/", _PC_NAME_MAX) > 0, "pathconf(/, _PC_NAME_MAX) > 0"); check(pathconf("/", _PC_2_SYMLINKS) == 1, "pathconf(/, _PC_2_SYMLINKS) == 1"); check(pathconf("/", _PC_CHOWN_RESTRICTED) == 1, "pathconf(/, _PC_CHOWN_RESTRICTED) == 1"); name_max = pathconf("/", _PC_NAME_MAX); fd = __syscall3(SYS_openat, T32_AT_FDCWD, (long)"/", T32_O_RDONLY); check(fd >= 0, "raw open of / returns a descriptor"); if (fd >= 0) { check(fpathconf((int)fd, _PC_NAME_MAX) == name_max, "fpathconf(/, _PC_NAME_MAX) agrees with pathconf(/ )"); __syscall1(SYS_close, fd); } } #if VLIBC_LEVEL_GE(2) /* 6. uname/gethostname/gethostid. */ static void uname_scenario(void) { struct utsname u; /* fully written by the kernel on a successful uname() */ char host[65]; char *p; check(sizeof(struct utsname) == 390, "sizeof(struct utsname) is 390"); check(uname(&u) == 0, "uname returns 0"); check(u.sysname[0] != '\0', "uname sysname is non-empty"); check(u.nodename[0] != '\0', "uname nodename is non-empty"); check(u.release[0] != '\0', "uname release is non-empty"); check(u.version[0] != '\0', "uname version is non-empty"); check(u.machine[0] != '\0', "uname machine is non-empty"); p = strstr(u.machine, "86_64"); check(p != (char *)0, "uname machine contains 86_64"); check(gethostname(host, sizeof host) == 0, "gethostname returns 0"); check(host[0] != '\0', "gethostname result is non-empty"); check(strcmp(u.nodename, host) == 0, "gethostname equals the uname nodename"); check(gethostid() == 0, "gethostid() returns 0"); } #endif /* VLIBC_LEVEL_GE(2) */ /* The failure scenarios. Exit via 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). */ static void failure_scenarios(void) { check(sysconf(T32_UNKNOWN_KEY) == -1, "-f sysconf(unknown key) returns -1"); check(pathconf("/nonexistent-vlibc-zzz", _PC_PATH_MAX) == -1, "pathconf on a missing path returns -1"); // NOLINTBEGIN(clang-analyzer-unix.StdCLibraryFunctions) -- deliberate EBADF probe check(fpathconf(-1, _PC_NAME_MAX) == -1, "fpathconf(-1) returns -1"); // NOLINTEND(clang-analyzer-unix.StdCLibraryFunctions) #if VLIBC_LEVEL_GE(2) { char one[1]; check(gethostname(one, 1) == -1, "gethostname into a 1-byte buffer returns -1"); } #endif /* VLIBC_LEVEL_GE(2) */ if (failures == 0) { say(1, "all uname failure scenarios passed\n"); } else { say(1, "FAILURES: "); say_dec(1, (unsigned long)failures); say(1, "\n"); } __syscall1(SYS_exit_group, failures == 0 ? 0 : 1); } int main(int argc, char **argv) { if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0') { failure_scenarios(); } sysconf_scenario(); pathconf_scenario(); #if VLIBC_LEVEL_GE(2) uname_scenario(); #endif /* VLIBC_LEVEL_GE(2) */ if (failures == 0) { say(1, "all uname tests passed\n"); } else { say(1, "FAILURES: "); say_dec(1, (unsigned long)failures); say(1, "\n"); } return failures == 0 ? 0 : 1; }