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
+65
View File
@@ -0,0 +1,65 @@
#ifndef VLIBC_SYS_UTSNAME_H
#define VLIBC_SYS_UTSNAME_H
/*
* vlibc — <sys/utsname.h>.
*
* 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 <unistd.h>).
*/
#include <vlibc/features.h>
#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 */
+229
View File
@@ -24,6 +24,17 @@
* Level 2 (muslmimic): vfork, setpgrp (obsolescent), setgroups, getpgid, * Level 2 (muslmimic): vfork, setpgrp (obsolescent), setgroups, getpgid,
* getsid, tcgetpgrp, tcsetpgrp (XSI). * 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 <sys/utsname.h> (todo 32).
*
* The open-flag constants (O_RDONLY, O_CREAT, O_CLOEXEC, ...) belong to * The open-flag constants (O_RDONLY, O_CREAT, O_CLOEXEC, ...) belong to
* <fcntl.h> and are deliberately not defined here; the oflag arguments below * <fcntl.h> and are deliberately not defined here; the oflag arguments below
* are plain int and take their values from that header. The optional mode * are plain int and take their values from that header. The optional mode
@@ -297,6 +308,172 @@ getpgrp(void);
pid_t pid_t
setsid(void); 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 <bits/confname.h>, 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) #if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): Linux extensions + XSI + obsolescent. */ /* Level 2 (muslmimic): Linux extensions + XSI + obsolescent. */
@@ -370,6 +547,58 @@ tcgetpgrp(int fildes);
int int
tcsetpgrp(int fildes, pid_t pgid); 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) */ #endif /* VLIBC_LEVEL_GE(2) */
#ifdef __cplusplus #ifdef __cplusplus
+110
View File
@@ -0,0 +1,110 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#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);
}
+281
View File
@@ -0,0 +1,281 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#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;
}
}
+99
View File
@@ -0,0 +1,99 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <unistd.h>
#include <sys/utsname.h>
#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 <unistd.h>, gethostid too;
* uname and struct utsname live in <sys/utsname.h>. 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) */
+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;
}