From e9aa0024536b78dafacb4fd6d62c4c6b5d6c84bc Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Sat, 5 Sep 2026 23:00:23 -0400 Subject: [PATCH] feat(misc): uname/gethostname/sysconf/pathconf --- include/sys/utsname.h | 65 ++++++++++ include/unistd.h | 229 +++++++++++++++++++++++++++++++++ src/misc/pathconf.c | 110 ++++++++++++++++ src/misc/sysconf.c | 281 +++++++++++++++++++++++++++++++++++++++++ src/misc/uname.c | 99 +++++++++++++++ tests/test_uname.c | 286 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1070 insertions(+) create mode 100644 include/sys/utsname.h create mode 100644 src/misc/pathconf.c create mode 100644 src/misc/sysconf.c create mode 100644 src/misc/uname.c create mode 100644 tests/test_uname.c diff --git a/include/sys/utsname.h b/include/sys/utsname.h new file mode 100644 index 0000000..cdea565 --- /dev/null +++ b/include/sys/utsname.h @@ -0,0 +1,65 @@ +#ifndef VLIBC_SYS_UTSNAME_H +#define VLIBC_SYS_UTSNAME_H + +/* + * vlibc — . + * + * System identification: uname() and struct utsname. uname is XSI [CX] in + * POSIX.1-2008 (glibc gates it behind _XOPEN_SOURCE, not _POSIX_C_SOURCE), + * so the whole surface sits at level 2 — nothing here is POSIX.1-2008 base. + * + * Level 2 (muslmimic): uname, struct utsname. + * + * struct utsname mirrors the x86_64 kernel's struct new_utsname exactly: six + * NUL-terminated 65-byte character arrays (the kernel's _UTSNAME_LENGTH, 64 + * usable characters plus the terminator). The kernel's uname syscall copies + * all 390 bytes, so the layout is an ABI fact, not a style choice. + * domainname is a Linux extension (the kernel's uts namespace domain name) + * and is not part of the POSIX struct; it is included for glibc + * compatibility — POSIX only requires the first five fields. + * + * The declaration carries no intent attribute: uname performs I/O with side + * effects and reports failures through errno, so const/pure would be + * unsound (see the rationale in ). + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if VLIBC_LEVEL_GE(2) + +/* + * The system identification strings. Each field is NUL-terminated; the + * 65-byte width matches the kernel buffer (64 characters plus the NUL). + * sysname is the operating system name ("Linux"), nodename the host name, + * release the kernel release, version the kernel build, machine the + * hardware identifier ("x86_64"), and domainname the NIS/UTS domain name. + */ +struct utsname +{ + char sysname[65]; /* operating system name */ + char nodename[65]; /* name within the communications network */ + char release[65]; /* operating system release level */ + char version[65]; /* operating system version */ + char machine[65]; /* hardware type */ + char domainname[65]; /* NIS/UTS domain name (Linux extension) */ +}; + +/* + * Fill the buffer with the current system identification strings; return + * 0, or -1 with errno set when the buffer points outside the address + * space. The strings are copies from the kernel's UTS namespace. + */ +int +uname(struct utsname *buf); + +#endif /* VLIBC_LEVEL_GE(2) */ + +#ifdef __cplusplus +} +#endif + +#endif /* VLIBC_SYS_UTSNAME_H */ diff --git a/include/unistd.h b/include/unistd.h index 2019141..abb70ec 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -24,6 +24,17 @@ * Level 2 (muslmimic): vfork, setpgrp (obsolescent), setgroups, getpgid, * getsid, tcgetpgrp, tcsetpgrp (XSI). * + * System limits and options (todo 32) are declared after the process + * block: + * + * Level 1 (onlyposix): sysconf, pathconf, fpathconf, the _SC_* and _PC_* + * configuration keys. + * Level 2 (muslmimic): gethostname, sethostname, gethostid (XSI/BSD + * system identification — NOT POSIX.1-2008 base), + * and the _CS_* keys for todo 38's confstr(). + * + * uname() and struct utsname live in (todo 32). + * * The open-flag constants (O_RDONLY, O_CREAT, O_CLOEXEC, ...) belong to * and are deliberately not defined here; the oflag arguments below * are plain int and take their values from that header. The optional mode @@ -297,6 +308,172 @@ getpgrp(void); pid_t setsid(void); +/* Level 1 (POSIX base): system limits and options. */ + +/* + * The configuration keys for sysconf(), pathconf() and fpathconf(), and + * (level 2) the _CS_* keys for confstr() (todo 38). + * + * POSIX defines the _SC_* and _PC_* names and the SEMANTICS of the values + * the configuration functions return for them, but NOT their numeric + * values: those are private to each C library (glibc numbers them + * arbitrarily in , musl in a private table). The values + * below are vlibc's OWN stable ABI, assigned in declaration order and never + * reused — program source must pass the symbolic constants, never literal + * integers. They are deliberately NOT glibc's numbers. Any value outside + * these enums is unknown to the configuration functions and yields -1 + * without errno (the unsupported-key convention, see below). + */ + +/* NOLINTBEGIN(bugprone-reserved-identifier) -- POSIX-mandated _SC_/_PC_ names */ +enum +{ + /* Process and file limits (sysconf). */ + _SC_ARG_MAX = 0, /* max bytes of arguments + environment for exec */ + _SC_CHILD_MAX, /* max simultaneous processes per real user id */ + _SC_CLK_TCK, /* clock ticks per second, the times() unit (100) */ + _SC_NGROUPS_MAX, /* max supplementary group ids */ + _SC_OPEN_MAX, /* max open file descriptors per process */ + _SC_PAGESIZE, /* memory page size in bytes (from AT_PAGESZ) */ + _SC_PAGE_SIZE, /* POSIX synonym of _SC_PAGESIZE */ + _SC_NPROCESSORS_CONF, /* number of processors configured */ + _SC_NPROCESSORS_ONLN, /* number of processors online */ + _SC_PHYS_PAGES, /* number of physical memory pages */ + _SC_AVPHYS_PAGES, /* number of currently available memory pages */ + _SC_SYMLOOP_MAX, /* max symlink traversals while resolving a path */ + _SC_STREAM_MAX, /* max open standard I/O streams (FOPEN_MAX) */ + _SC_TZNAME_MAX, /* max bytes in a timezone name */ + _SC_VERSION, /* POSIX.1 version, 200809L */ + _SC_2_VERSION, /* POSIX.2 version, 200809L */ + _SC_JOB_CONTROL, /* 1 if job control is supported */ + _SC_SAVED_IDS, /* 1 if saved set-user/group-id is supported */ + _SC_HOST_NAME_MAX, /* max bytes in a host name (excluding NUL) */ + _SC_LOGIN_NAME_MAX, /* max bytes in a login name */ + _SC_GETPW_R_SIZE_MAX, /* recommended buffer size for getpw*_r */ + _SC_IOV_MAX, /* max iovec entries in readv/writev */ + _SC_LINE_MAX, /* max bytes in an input line (POSIX.2) */ + _SC_RE_DUP_MAX, /* max duplicate regexp interval counts */ + _SC_COLL_WEIGHTS_MAX, /* max weights for collating elements */ + _SC_EXPR_NEST_MAX, /* max expression nesting in expr(1) */ + + /* POSIX option indicators (sysconf). Values follow the _POSIX_* + * convention: a version value means supported, -1 unsupported. */ + _SC_THREADS, /* _POSIX_THREADS */ + _SC_SEMAPHORES, /* _POSIX_SEMAPHORES */ + _SC_SHARED_MEMORY_OBJECTS, /* _POSIX_SHARED_MEMORY_OBJECTS */ + _SC_MESSAGE_PASSING, /* _POSIX_MESSAGE_PASSING */ + _SC_MONOTONIC_CLOCK, /* _POSIX_MONOTONIC_CLOCK */ + _SC_TIMERS, /* _POSIX_TIMERS */ + _SC_SYNCHRONIZED_IO, /* _POSIX_SYNCHRONIZED_IO */ + _SC_THREAD_SAFE_FUNCTIONS, /* _POSIX_THREAD_SAFE_FUNCTIONS */ + _SC_THREAD_ATTR_STACKADDR, /* _POSIX_THREAD_ATTR_STACKADDR */ + _SC_THREAD_ATTR_STACKSIZE, /* _POSIX_THREAD_ATTR_STACKSIZE */ + _SC_THREAD_PRIORITY_SCHEDULING, /* _POSIX_THREAD_PRIORITY_SCHEDULING */ + _SC_THREAD_PRIO_INHERIT, /* _POSIX_THREAD_PRIO_INHERIT */ + _SC_THREAD_PRIO_PROTECT, /* _POSIX_THREAD_PRIO_PROTECT */ + _SC_THREAD_PROCESS_SHARED, /* _POSIX_THREAD_PROCESS_SHARED */ + _SC_REALTIME_SIGNALS, /* _POSIX_REALTIME_SIGNALS */ + + /* POSIX.1-2008 option indicators that use plain 1 for supported. */ + _SC_REGEXP, /* 1 if the RE functions are supported */ + _SC_SHELL, /* 1 if the shell is supported */ + _SC_XOPEN_VERSION, /* XSI version, 700 */ + _SC_XOPEN_UNIX, /* 1 if XSI interfaces are supported */ + _SC_XOPEN_CRYPT, /* 1 if the crypt function is available */ + _SC_XOPEN_ENH_I18N, /* 1 if enhanced internationalization is present */ + _SC_XOPEN_SHM, /* 1 if XSI shared memory is present */ + _SC_2_C_BIND, /* POSIX.2 C-language binding */ + _SC_2_C_DEV, /* POSIX.2 C-language development utilities */ + _SC_2_FORT_DEV, /* POSIX.2 FORTRAN development utilities */ + _SC_2_FORT_RUN, /* POSIX.2 FORTRAN runtime utilities */ + _SC_2_LOCALEDEF, /* POSIX.2 locale creation utilities */ + _SC_2_SW_DEV, /* POSIX.2 software development utilities */ + _SC_2_UPE /* POSIX.2 user-portability utilities */ +}; + +/* + * The _PC_* keys for pathconf() and fpathconf(). Each names a limit or an + * option of the FILESYSTEM holding the queried path or descriptor; see the + * pathconf() documentation below for the supported keys and their values. + */ +enum +{ + _PC_LINK_MAX = 0, /* max link count of a file */ + _PC_MAX_CANON, /* max bytes in a terminal canonical input line */ + _PC_MAX_INPUT, /* max bytes available on a terminal input queue */ + _PC_NAME_MAX, /* max bytes in a file name (excluding NUL) */ + _PC_PATH_MAX, /* max bytes in a pathname (excluding NUL) */ + _PC_PIPE_BUF, /* max bytes atomically writable to a pipe */ + _PC_CHOWN_RESTRICTED, /* 1 if chown is restricted to the owner */ + _PC_NO_TRUNC, /* 1 if overlong file names are an error */ + _PC_VDISABLE, /* the terminal disable character (0 = none) */ + _PC_ASYNC_IO, /* 200809L if asynchronous I/O is supported */ + _PC_SYNC_IO, /* 200809L if synchronized I/O is supported */ + _PC_PRIO_IO, /* 200809L if prioritized I/O is supported */ + _PC_SOCK_MAXBUF, /* max bytes storable in a socket buffer */ + _PC_FILESIZEBITS, /* bits in a file size field */ + _PC_REC_INCR_XFER_SIZE, /* recommended increment for transfer sizes */ + _PC_REC_MAX_XFER_SIZE, /* recommended maximum transfer size */ + _PC_REC_MIN_XFER_SIZE, /* recommended minimum transfer size */ + _PC_REC_XFER_ALIGN, /* recommended transfer alignment */ + _PC_ALLOC_SIZE_MIN, /* minimum bytes of a file allocation */ + _PC_SYMLINK_MAX, /* max bytes in a symbolic link target */ + _PC_2_SYMLINKS /* 1 if symlinks support the ".." resolution rule */ +}; +/* NOLINTEND(bugprone-reserved-identifier) */ + +/* + * Query a system configuration value for key name (one of the _SC_* above) + * and return it as a long. A negative value means the option is not + * supported or the key is unknown; for unsupported options and for any key + * outside the _SC_* enum, -1 is returned WITHOUT setting errno (callers + * must not rely on errno after -1). + * + * The supported keys and their vlibc values: _SC_ARG_MAX (2097152), + * _SC_CHILD_MAX (65535), _SC_CLK_TCK (100, the fixed x86_64 clock-tick + * rate — see the note in src/misc/sysconf.c, todo 35's times() consumes + * that same constant), _SC_NGROUPS_MAX (65536), _SC_OPEN_MAX (the current + * RLIMIT_NOFILE soft limit, 1024 if it cannot be read), _SC_PAGESIZE and + * _SC_PAGE_SIZE (the memory page size from the AT_PAGESZ auxiliary vector + * entry, 4096 on x86_64), _SC_NPROCESSORS_CONF and _SC_NPROCESSORS_ONLN + * (the processor count), _SC_PHYS_PAGES and _SC_AVPHYS_PAGES (physical and + * available memory page counts), _SC_SYMLOOP_MAX (40), _SC_STREAM_MAX + * (16), _SC_TZNAME_MAX (6), _SC_VERSION and _SC_2_VERSION (200809L), + * _SC_JOB_CONTROL, _SC_SAVED_IDS, _SC_REGEXP, _SC_SHELL (1 each), + * _SC_HOST_NAME_MAX (64), _SC_LOGIN_NAME_MAX (256), _SC_GETPW_R_SIZE_MAX + * (1024), _SC_IOV_MAX (1024), _SC_LINE_MAX (2048), _SC_RE_DUP_MAX (32767), + * _SC_COLL_WEIGHTS_MAX (255), _SC_EXPR_NEST_MAX (32), _SC_XOPEN_VERSION + * (700), _SC_XOPEN_UNIX, _SC_XOPEN_CRYPT, _SC_XOPEN_ENH_I18N, _SC_XOPEN_SHM + * (1 each), and the thread/option version keys (_SC_THREADS ... _SC_2_UPE, + * 200809L each). + */ +long +sysconf(int name); + +/* + * Query a configurable limit or option of the filesystem holding path (the + * _PC_* keys above) and return it as a long; -1 with errno set when path + * cannot be examined. Fixed values: _PC_LINK_MAX (32000), _PC_MAX_CANON + * and _PC_MAX_INPUT (255), _PC_PATH_MAX (4096), _PC_PIPE_BUF (4096), + * _PC_CHOWN_RESTRICTED, _PC_NO_TRUNC and _PC_2_SYMLINKS (1), _PC_VDISABLE + * (0), _PC_FILESIZEBITS (64), and _PC_REC_MIN_XFER_SIZE, + * _PC_REC_XFER_ALIGN and _PC_ALLOC_SIZE_MIN (the filesystem block size). + * _PC_NAME_MAX is the filesystem's f_namelen (255 on ext4/xfs). For keys + * whose option the filesystem does not provide (_PC_ASYNC_IO, _PC_SYNC_IO, + * _PC_PRIO_IO, _PC_SOCK_MAXBUF, _PC_REC_INCR_XFER_SIZE, + * _PC_REC_MAX_XFER_SIZE, _PC_SYMLINK_MAX) and for any key outside the + * _PC_* enum, -1 is returned WITHOUT setting errno. + */ +long +pathconf(const char *path, int name); + +/* + * Like pathconf(), but the filesystem is the one holding the open + * descriptor fildes. + */ +long +fpathconf(int fildes, int name); + #if VLIBC_LEVEL_GE(2) /* Level 2 (muslmimic): Linux extensions + XSI + obsolescent. */ @@ -370,6 +547,58 @@ tcgetpgrp(int fildes); int tcsetpgrp(int fildes, pid_t pgid); + +/* Level 2 (muslmimic): system identification (XSI / BSD, not POSIX base). */ + +/* + * The _CS_* keys for confstr() (todo 38, which owns the function and its + * string results). Values are vlibc-local ABI like the _SC_ and _PC_ keys + * above; they are defined here now so todo 38 only implements confstr() + * itself. + */ +/* NOLINTBEGIN(bugprone-reserved-identifier) -- POSIX-mandated _CS_ names */ +enum +{ + _CS_PATH = 0, /* value of PATH (always a usable default) */ + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS, /* 1 if the V6 width-restricted envs exist */ + _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS, /* 1 if the V7 width-restricted envs exist */ + _CS_LFS_CFLAGS, /* cpp/cflags for large-file support */ + _CS_LFS_LDFLAGS, /* ld flags for large-file support */ + _CS_LFS_LIBS, /* libraries for large-file support */ + _CS_LFS_LINTFLAGS, /* lint flags for large-file support */ + _CS_LFS64_CFLAGS, /* cpp/cflags for large-file64 support */ + _CS_LFS64_LDFLAGS, /* ld flags for large-file64 support */ + _CS_LFS64_LIBS, /* libraries for large-file64 support */ + _CS_LFS64_LINTFLAGS, /* lint flags for large-file64 support */ + _CS_GNU_LIBC_VERSION, /* "glibc X.Y" (vlibc extension) */ + _CS_GNU_LIBPTHREAD_VERSION /* "NPTL X.Y" (vlibc extension) */ +}; +/* NOLINTEND(bugprone-reserved-identifier) */ + +/* + * Store the NUL-terminated host name of the current machine into name, + * sized len bytes. Return 0 on success; if the name (including its NUL + * terminator) does not fit in len bytes, nothing is written beyond the + * fit and -1 is returned with errno set to ENAMETOOLONG. XSI. + */ +int +gethostname(char *name, size_t len); + +/* + * Set the kernel's host name from name (len bytes, no NUL required). + * Requires privilege. Return 0, or -1 with errno set. BSD, not POSIX. + */ +int +sethostname(const char *name, size_t len); + +/* + * Return the 32-bit host identifier of the current machine. vlibc returns + * 0 (musl's behavior): the historic value was derived from the host's + * primary IP address via gethostbyname, which the modern library does not + * perform. [OB] — obsolescent in POSIX. + */ +long +gethostid(void); #endif /* VLIBC_LEVEL_GE(2) */ #ifdef __cplusplus diff --git a/src/misc/pathconf.c b/src/misc/pathconf.c new file mode 100644 index 0000000..32e7e3b --- /dev/null +++ b/src/misc/pathconf.c @@ -0,0 +1,110 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * pathconf() / fpathconf(): configurable limits and options of the + * filesystem holding a path or an open descriptor. + * + * Both functions first validate the object with statfs/fstatfs (so a + * missing path or a closed descriptor yields -1 with errno from the + * syscall layer) and then answer from the resulting filesystem data plus + * the fixed value table below. Only _PC_NAME_MAX and the three + * block-size-derived keys are truly per-filesystem; the rest are vlibc's + * fixed Linux values (see the pathconf() documentation in + * include/unistd.h). Unknown keys and keys whose option this filesystem + * does not provide return -1 WITHOUT setting errno. + * + * The name argument (one of the _PC_* keys) is forwarded to the value + * table unchanged; POSIX does not define per-file-type answers for the + * keys vlibc supports, so the object type is not inspected beyond the + * statfs validation. + */ + +/* + * Linux struct statfs (kernel ABI, x86_64; linux/statfs.h). Layout and + * sizes are LP64 facts: f_type..f_ffree are longs, f_fsid is an int pair, + * then f_namelen/f_frsize/f_flags/f_spare[4] complete the 120 bytes the + * kernel copies out. Only f_bsize and f_namelen are read here. + */ +struct vlibc_statfs +{ + long f_type; + long f_bsize; + long f_blocks; + long f_bfree; + long f_bavail; + long f_files; + long f_ffree; + int f_fsid[2]; + long f_namelen; + long f_frsize; + long f_flags; + long f_spare[4]; +}; + +/* Answer key name from the filesystem data in fs; -1 for unknown keys and + * for keys whose option this filesystem does not provide, without setting + * errno in either case. */ +static long +pathconf_value(const struct vlibc_statfs *fs, int name) +{ + switch (name) + { + case _PC_LINK_MAX: + return 32000; + case _PC_MAX_CANON: + case _PC_MAX_INPUT: + return 255; + case _PC_NAME_MAX: + return fs->f_namelen; + case _PC_PATH_MAX: + case _PC_PIPE_BUF: + return 4096; + case _PC_CHOWN_RESTRICTED: + case _PC_NO_TRUNC: + case _PC_2_SYMLINKS: + return 1; + case _PC_VDISABLE: + return 0; + case _PC_FILESIZEBITS: + return 64; + case _PC_REC_MIN_XFER_SIZE: + case _PC_REC_XFER_ALIGN: + case _PC_ALLOC_SIZE_MIN: + return fs->f_bsize; + default: + /* _PC_ASYNC_IO, _PC_SYNC_IO, _PC_PRIO_IO, _PC_SOCK_MAXBUF, + * _PC_REC_INCR_XFER_SIZE, _PC_REC_MAX_XFER_SIZE, _PC_SYMLINK_MAX + * and unknown keys. */ + return -1; + } +} + +long +pathconf(const char *path, int name) +{ + struct vlibc_statfs fs = {0}; + + if (syscall_ret(__syscall2(SYS_statfs, (long)path, (long)&fs)) != 0) + { + return -1; + } + return pathconf_value(&fs, name); +} + +long +fpathconf(int fildes, int name) // NOLINT(bugprone-easily-swappable-parameters) +{ + struct vlibc_statfs fs = {0}; + + if (syscall_ret(__syscall2(SYS_fstatfs, fildes, (long)&fs)) != 0) + { + return -1; + } + return pathconf_value(&fs, name); +} diff --git a/src/misc/sysconf.c b/src/misc/sysconf.c new file mode 100644 index 0000000..b22594a --- /dev/null +++ b/src/misc/sysconf.c @@ -0,0 +1,281 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "../internal/syscall.h" + +/* + * sysconf(): system limits and options. + * + * Every supported key returns its value directly (see the sysconf() + * documentation in include/unistd.h for the full table). Unknown keys and + * genuinely unsupported options return -1 WITHOUT setting errno — a call + * must be able to probe arbitrary _SC_* values without clobbering a + * caller's errno (the QA contract of todo 32). + * + * The fixed x86_64 clock-tick rate, defined ONCE here: USER_HZ is 100 on + * every x86_64 Linux kernel. todo 35's times() reports in exactly these + * ticks and consumes this constant rather than re-defining it. + */ +#define VLIBC_CLK_TCK 100 + +/* Kernel-UAPI values local to this file (include/fcntl.h owns the public + * AT_FDCWD / O_RDONLY spellings; asm-generic/auxvec.h owns AT_PAGESZ). */ +#define VLIBC_SYSCONF_AT_FDCWD (-100) +#define VLIBC_SYSCONF_AT_NULL 0 +#define VLIBC_SYSCONF_AT_PAGESZ 6 +#define VLIBC_SYSCONF_O_RDONLY 0 +#define VLIBC_SYSCONF_RLIMIT_NOFILE 7 + +/* + * Linux struct sysinfo (kernel ABI, x86_64; linux/sysinfo.h). The totalram/ + * freeram/... fields are memory sizes counted in mem_unit-byte units + * (current kernels report mem_unit == 1 with sizes in bytes). procs is the + * number of live PROCESSES, not a CPU count — CPU counts come from + * sched_getaffinity, not from here. + */ +struct vlibc_sysinfo +{ + long uptime; + unsigned long loads[3]; + unsigned long totalram; + unsigned long freeram; + unsigned long sharedram; + unsigned long bufferram; + unsigned long totalswap; + unsigned long freeswap; + unsigned short procs; + unsigned short pad; + unsigned long totalhigh; + unsigned long freehigh; + unsigned int mem_unit; +}; + +/* RLIMIT_NOFILE as returned by prlimit64: current and maximum limits. */ +struct vlibc_rlimit +{ + unsigned long cur; + unsigned long max; +}; + +/* + * The memory page size, read from the AT_PAGESZ entry of the auxiliary + * vector. The vlibc startup code (todo 3) does not save the auxv, so it is + * re-read from /proc/self/auxv on first use and cached; a static cache is + * safe because no threads exist yet (todo 45 must revisit). On any failure + * — auxv unreadable or AT_PAGESZ absent — the x86_64 true page size 4096 + * is used as the documented fallback (this is what AT_PAGESZ reports on + * every x86_64 kernel anyway; the value is never hardcoded as the primary + * source). + */ +static long +query_pagesize(void) +{ + static long cached; + static int done; + + if (!done) + { + unsigned char buf[1024] = {0}; + long fd; + long n; + + done = 1; + cached = 4096; + fd = __syscall3(SYS_openat, VLIBC_SYSCONF_AT_FDCWD, (long)"/proc/self/auxv", + VLIBC_SYSCONF_O_RDONLY); + if (fd < 0) + { + return cached; + } + n = __syscall3(SYS_read, fd, (long)buf, (long)sizeof buf); + __syscall1(SYS_close, fd); + for (long off = 0; n >= 16 && off <= n - 16; off += 16) + { + unsigned long type; + unsigned long value; + + /* The auxv is a sequence of (type, value) unsigned long pairs. */ + // NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + __builtin_memcpy(&type, buf + off, sizeof type); + __builtin_memcpy(&value, buf + off + sizeof type, sizeof value); + // NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) + if (type == VLIBC_SYSCONF_AT_NULL) + { + break; + } + if (type == VLIBC_SYSCONF_AT_PAGESZ) + { + cached = (long)value; + break; + } + } + } + return cached; +} + +/* + * The open-descriptor limit: the RLIMIT_NOFILE soft limit via prlimit64. + * prlimit64 cannot usefully fail on the calling process, but the raw + * result is checked anyway so no errno write happens on the fallback path + * (the probe must stay errno-clean: it may run under a seccomp filter). + */ +static long +query_open_max(void) +{ + struct vlibc_rlimit rlim = {0}; + long r; + + r = __syscall4(SYS_prlimit64, 0, VLIBC_SYSCONF_RLIMIT_NOFILE, 0, (long)&rlim); + if (r < 0 || rlim.cur == 0 || rlim.cur == ~0UL) + { + return 1024; + } + return (long)rlim.cur; +} + +/* + * The online processor count, from the calling process's CPU affinity + * mask via sched_getaffinity(pid 0): the kernel returns the task's allowed + * CPUs, which is the count of CPUs this process can actually run on. The + * configured count is reported as the same value (a cpuset could hide + * CPUs, but on a system without hotplug the configured and online counts + * agree; glibc/musl make the same simplification for the affinity path). + */ +static long +query_nprocs(void) +{ + unsigned long mask[16] = {0}; /* 1024 bits, enough for any CPU count */ + long count = 0; + long r; + + r = __syscall3(SYS_sched_getaffinity, 0, (long)sizeof mask, (long)mask); + if (r < 0) + { + return 1; + } + if (r > (long)sizeof mask) + { + r = (long)sizeof mask; + } + for (long i = 0; i < r / (long)sizeof(unsigned long); i++) + { + count += (long)__builtin_popcountl(mask[i]); + } + return count > 0 ? count : 1; +} + +/* + * Physical (avail == 0) or available (avail != 0) memory in pages, from + * SYS_sysinfo. Sizes arrive in mem_unit-byte units, so the byte count is + * totalram * mem_unit (overflow is not a concern: the product stays far + * below 2^64 for any real machine). A sysinfo failure is reported as -1, + * which the caller treats like an unsupported key. + */ +static long +query_mem_pages(int avail) +{ + struct vlibc_sysinfo si = {0}; + unsigned long unit; + unsigned long bytes; + long r; + + r = __syscall1(SYS_sysinfo, (long)&si); + if (r < 0) + { + return -1; + } + unit = si.mem_unit != 0 ? si.mem_unit : 1; + bytes = (avail != 0 ? si.freeram : si.totalram) * unit; + return (long)(bytes / (unsigned long)query_pagesize()); +} + +long +sysconf(int name) +{ + switch (name) + { + case _SC_ARG_MAX: + return 2097152; + case _SC_CHILD_MAX: + return 65535; + case _SC_CLK_TCK: + return VLIBC_CLK_TCK; + case _SC_NGROUPS_MAX: + return 65536; + case _SC_OPEN_MAX: + return query_open_max(); + case _SC_PAGESIZE: + case _SC_PAGE_SIZE: + return query_pagesize(); + case _SC_NPROCESSORS_CONF: + case _SC_NPROCESSORS_ONLN: + return query_nprocs(); + case _SC_PHYS_PAGES: + return query_mem_pages(0); + case _SC_AVPHYS_PAGES: + return query_mem_pages(1); + case _SC_SYMLOOP_MAX: + return 40; + case _SC_STREAM_MAX: + return 16; + case _SC_TZNAME_MAX: + return 6; + case _SC_HOST_NAME_MAX: + return 64; + case _SC_LOGIN_NAME_MAX: + return 256; + case _SC_GETPW_R_SIZE_MAX: + case _SC_IOV_MAX: + return 1024; + case _SC_LINE_MAX: + return 2048; + case _SC_RE_DUP_MAX: + return 32767; + case _SC_COLL_WEIGHTS_MAX: + return 255; + case _SC_EXPR_NEST_MAX: + return 32; + case _SC_XOPEN_VERSION: + return 700; + case _SC_JOB_CONTROL: + case _SC_SAVED_IDS: + case _SC_REGEXP: + case _SC_SHELL: + case _SC_XOPEN_UNIX: + case _SC_XOPEN_CRYPT: + case _SC_XOPEN_ENH_I18N: + case _SC_XOPEN_SHM: + return 1; + case _SC_VERSION: + case _SC_2_VERSION: + case _SC_THREADS: + case _SC_SEMAPHORES: + case _SC_SHARED_MEMORY_OBJECTS: + case _SC_MESSAGE_PASSING: + case _SC_MONOTONIC_CLOCK: + case _SC_TIMERS: + case _SC_SYNCHRONIZED_IO: + case _SC_THREAD_SAFE_FUNCTIONS: + case _SC_THREAD_ATTR_STACKADDR: + case _SC_THREAD_ATTR_STACKSIZE: + case _SC_THREAD_PRIORITY_SCHEDULING: + case _SC_THREAD_PRIO_INHERIT: + case _SC_THREAD_PRIO_PROTECT: + case _SC_THREAD_PROCESS_SHARED: + case _SC_REALTIME_SIGNALS: + case _SC_2_C_BIND: + case _SC_2_C_DEV: + case _SC_2_FORT_DEV: + case _SC_2_FORT_RUN: + case _SC_2_LOCALEDEF: + case _SC_2_SW_DEV: + case _SC_2_UPE: + return 200809L; + default: + /* Unknown key or unsupported option: -1, errno untouched. */ + return -1; + } +} diff --git a/src/misc/uname.c b/src/misc/uname.c new file mode 100644 index 0000000..cf52f94 --- /dev/null +++ b/src/misc/uname.c @@ -0,0 +1,99 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "../internal/syscall.h" + +/* + * uname/gethostname/sethostname/gethostid: system identification. + * + * Everything here is XSI or BSD — none of it is POSIX.1-2008 base — so the + * whole file is gated at level 2 (whole-file-L2 rule; the wiring pass puts + * it in VLIBC_LEVEL2_SRCS, and at level 1 this TU compiles empty). + * + * gethostname and sethostname are declared in , gethostid too; + * uname and struct utsname live in . struct utsname is the + * kernel's struct new_utsname layout (six 65-byte arrays), so the kernel + * writes it in place through SYS_uname. + */ + +#if VLIBC_LEVEL_GE(2) + +/* + * Fill the buffer with the kernel's UTS namespace strings (the "name" + * information of the running kernel and host). syscall_ret reports 0 on + * success and -1 with EFAULT when the buffer is not writable. + */ +int +uname(struct utsname *buf) +{ + return syscall_ret(__syscall1(SYS_uname, (long)buf)); +} + +/* + * Store the NUL-terminated nodename (the host name) into name. The + * nodename is read from the kernel through uname() into a stack buffer, so + * nothing is allocated or leaked. When the name does not fit in len bytes + * — including its terminator — -1 is returned with ENAMETOOLONG and name + * is left untouched (glibc semantics; the POSIX text leaves the truncation + * case implementation-defined). + * + * The attribute keeps GCC from recognizing the length loop as a strlen + * call or the copy loop as memcpy (house idiom, see src/string): a + * libc-owned symbol must not be pulled into an unplanned dependency. + */ +__attribute__((optimize("no-tree-loop-distribute-patterns"))) int +gethostname(char *name, size_t len) +{ + struct utsname u = {0}; /* kernel-written; zeroed for the analyzer */ + size_t n = 0; + + if (uname(&u) != 0) + { + return -1; + } + while (u.nodename[n] != '\0' && n < sizeof(u.nodename)) + { + n++; + } + if (n + 1 > len) + { + errno = ENAMETOOLONG; + return -1; + } + for (size_t i = 0; i <= n; i++) + { + name[i] = u.nodename[i]; + } + return 0; +} + +/* + * Set the kernel's host name (the uts nodename) to the len bytes at name. + * Requires CAP_SYS_ADMIN in the initial user namespace. syscall_ret + * reports 0 on success and -1 with EPERM/EINVAL/EFAULT otherwise. + */ +int +sethostname(const char *name, size_t len) +{ + return syscall_ret(__syscall2(SYS_sethostname, (long)name, (long)len)); +} + +/* + * The 32-bit host identifier. vlibc returns 0, matching musl: the classic + * value was a hash of the host's primary address obtained through + * gethostbyname, a lookup this library deliberately does not perform, and + * POSIX marks the interface obsolescent [OB]. + */ +long +gethostid(void) +{ + return 0; +} + +#endif /* VLIBC_LEVEL_GE(2) */ diff --git a/tests/test_uname.c b/tests/test_uname.c new file mode 100644 index 0000000..2c81775 --- /dev/null +++ b/tests/test_uname.c @@ -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 +#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; +}