feat(misc): uname/gethostname/sysconf/pathconf

This commit is contained in:
2026-09-05 23:00:23 -04:00
parent 3ab43c7f10
commit e9aa002453
6 changed files with 1070 additions and 0 deletions
+286
View File
@@ -0,0 +1,286 @@
/*
* 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 <string.h>
#include <unistd.h>
#include <sys/utsname.h>
#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;
}