287 lines
9.8 KiB
C
287 lines
9.8 KiB
C
/*
|
|
* 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;
|
|
}
|