Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5355574121
|
||
|
|
b53ccaa1e8
|
||
|
|
bff8fe9c86
|
||
|
|
99eff26467
|
||
|
|
8bf754419f
|
||
|
|
14b0136a8e
|
||
|
|
26786e9027
|
+48
-6
@@ -75,7 +75,17 @@ VLIBC_CORE_SRCS = \
|
||||
src/string/strstr.c \
|
||||
src/string/strtok.c \
|
||||
src/malloc/malloc.c \
|
||||
src/ctype/ctype.c
|
||||
src/ctype/ctype.c \
|
||||
src/stdlib/strtox.c \
|
||||
src/stdlib/strtox_impl.c \
|
||||
src/stdlib/strtod.c \
|
||||
src/stdlib/rand.c \
|
||||
src/stdlib/qsort.c \
|
||||
src/stdlib/div.c \
|
||||
src/stdlib/inttypes.c \
|
||||
src/stdlib/env.c \
|
||||
src/stdlib/multibyte.c \
|
||||
src/stdlib/mkstemp.c
|
||||
|
||||
# Profile-gated sources, EXISTING gates preserved (strlcpy/strlcat stay L2
|
||||
# BSD, strcasestr stays L3 GNU — NOT promoted). Task 8/9 additions: stpcpy.c
|
||||
@@ -87,11 +97,14 @@ VLIBC_CORE_SRCS = \
|
||||
# ffsl/ffsll/bcmp/bcopy/bzero/index/rindex — nothing is POSIX.1-2008 base), so
|
||||
# it also joins the GE_2 conditional list. Files with L1
|
||||
# bodies plus small L2 sections (strcoll.c, strdup.c) stay in VLIBC_CORE_SRCS
|
||||
# and self-gate at level 1.
|
||||
# and self-gate at level 1. Likewise rand.c (its L2 rand_r/drand48 section)
|
||||
# and the strtox/strtod/inttypes/div/qsort C files (their L2 gates live in
|
||||
# include/stdlib.h / include/inttypes.h declarations) stay in VLIBC_CORE_SRCS.
|
||||
VLIBC_LEVEL2_SRCS = src/string/strlcpy.c src/string/strlcat.c \
|
||||
src/string/stpcpy.c src/string/memccpy.c \
|
||||
src/ctype/isascii.c src/ctype/toascii.c \
|
||||
src/string/strings_impl.c
|
||||
src/string/strings_impl.c \
|
||||
src/stdlib/pty.c src/stdlib/getsubopt.c
|
||||
VLIBC_LEVEL3_SRCS = src/string/strcasestr.c
|
||||
|
||||
vlibc_lib_LTLIBRARIES = libvlibc.la
|
||||
@@ -129,6 +142,7 @@ vlibc_include_HEADERS = \
|
||||
include/stdbool.h \
|
||||
include/stdckdint.h \
|
||||
include/stdint.h \
|
||||
include/inttypes.h \
|
||||
include/stdlib.h \
|
||||
include/stdnoreturn.h \
|
||||
include/tar.h \
|
||||
@@ -263,7 +277,8 @@ VLIBC_TEST_CFLAGS = -DVLIBC_LEVEL=$(VLIBC_TEST_LEVEL) -fno-stack-protector \
|
||||
-fno-pic -fno-pie
|
||||
|
||||
check_PROGRAMS = test_syscall test_strerror test_setjmp test_headers \
|
||||
test_startup test_malloc test_string test_ctype test_strings
|
||||
test_startup test_malloc test_string test_ctype test_strings \
|
||||
test_strtol test_qsort test_inttypes test_env
|
||||
|
||||
test_syscall_SOURCES = tests/syscall_test.c
|
||||
test_syscall_CFLAGS = $(VLIBC_TEST_CFLAGS)
|
||||
@@ -319,13 +334,39 @@ test_strings_DEPENDENCIES = crt1.o libvlibc-check.a
|
||||
test_strings_LINK = $(VLIBC_TEST_LINK)
|
||||
test_strings_LDADD = $(VLIBC_TEST_LDADD)
|
||||
|
||||
test_strtol_SOURCES = tests/test_strtol.c
|
||||
test_strtol_CFLAGS = $(VLIBC_TEST_CFLAGS)
|
||||
test_strtol_DEPENDENCIES = crt1.o libvlibc-check.a
|
||||
test_strtol_LINK = $(VLIBC_TEST_LINK)
|
||||
test_strtol_LDADD = $(VLIBC_TEST_LDADD)
|
||||
|
||||
test_qsort_SOURCES = tests/test_qsort.c
|
||||
test_qsort_CFLAGS = $(VLIBC_TEST_CFLAGS)
|
||||
test_qsort_DEPENDENCIES = crt1.o libvlibc-check.a
|
||||
test_qsort_LINK = $(VLIBC_TEST_LINK)
|
||||
test_qsort_LDADD = $(VLIBC_TEST_LDADD)
|
||||
|
||||
test_inttypes_SOURCES = tests/test_inttypes.c
|
||||
test_inttypes_CFLAGS = $(VLIBC_TEST_CFLAGS)
|
||||
test_inttypes_DEPENDENCIES = crt1.o libvlibc-check.a
|
||||
test_inttypes_LINK = $(VLIBC_TEST_LINK)
|
||||
test_inttypes_LDADD = $(VLIBC_TEST_LDADD)
|
||||
|
||||
test_env_SOURCES = tests/test_env.c
|
||||
test_env_CFLAGS = $(VLIBC_TEST_CFLAGS)
|
||||
test_env_DEPENDENCIES = crt1.o libvlibc-check.a
|
||||
test_env_LINK = $(VLIBC_TEST_LINK)
|
||||
test_env_LDADD = $(VLIBC_TEST_LDADD)
|
||||
|
||||
# test_startup's default mode exits 42 by design, so it is NOT a bare TESTS
|
||||
# entry; tests/startup_modes.sh runs its full mode matrix and the -f failure
|
||||
# scenarios of the other binaries. test_malloc/test_string/test_ctype/
|
||||
# test_strings default to the happy mode (exit 0 on all-pass) and are bare
|
||||
# TESTS entries; their -f/-p modes stay runnable by hand.
|
||||
# test_strings/test_strtol/test_qsort/test_inttypes/test_env default to the
|
||||
# happy mode (exit 0 on all-pass) and are bare TESTS entries; their -f/-p
|
||||
# modes stay runnable by hand.
|
||||
TESTS = test_syscall test_strerror test_setjmp test_headers \
|
||||
test_malloc test_string test_ctype test_strings \
|
||||
test_strtol test_qsort test_inttypes test_env \
|
||||
tests/startup_modes.sh
|
||||
|
||||
CLEANFILES = crt1.o libvlibc-check.a $(VLIBC_CHECK_OBJECTS)
|
||||
@@ -342,6 +383,7 @@ EXTRA_DIST = \
|
||||
src/internal/errno.h \
|
||||
src/internal/libc.h \
|
||||
src/internal/malloc.h \
|
||||
src/internal/strtox.h \
|
||||
src/internal/syscall.h \
|
||||
src/internal/types.h \
|
||||
src/start/start.h \
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
#ifndef VLIBC_INTTYPES_H
|
||||
#define VLIBC_INTTYPES_H
|
||||
|
||||
/*
|
||||
* vlibc — <inttypes.h>.
|
||||
*
|
||||
* Extended integer type names, the imax* conversions of the widest signed
|
||||
* type, and the PRI and SCN formatted-I/O macros (C23 7.8). Every macro below
|
||||
* expands to a string literal holding the complete conversion specifier
|
||||
* (length modifier included) for the corresponding <stdint.h> type on the
|
||||
* x86_64 LP64 target, where the compiler predefined types are:
|
||||
*
|
||||
* int8_t/uint8_t signed char / unsigned char
|
||||
* int16_t/uint16_t short / unsigned short
|
||||
* int32_t/uint32_t int / unsigned int
|
||||
* int64_t/uint64_t long / unsigned long
|
||||
* int_leastN_t identical to the exact-width types
|
||||
* int_fast8_t signed char; int_fast16/32/64_t are long
|
||||
* intmax_t/uintmax_t long / unsigned long
|
||||
* intptr_t/uintptr_t long / unsigned long
|
||||
*
|
||||
* Design rule (per plan): the fprintf macros name the length modifier of
|
||||
* the PROMOTED argument — printf receives an int for every sub-int type,
|
||||
* so PRId8 is "d", not "hhd" — while the fscanf macros name the
|
||||
* exact-width modifier — scanf receives a pointer, so SCNd8 is "hhd".
|
||||
* Both forms convert the corresponding type correctly under C23 7.8.1;
|
||||
* glibc and musl instead use the exact-width modifier in both families,
|
||||
* which is equally conformant and indistinguishable in behavior for
|
||||
* correctly-typed arguments.
|
||||
*
|
||||
* This header is ISO C core and is present in every profile.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Quotient and remainder of the widest integer division (C23 7.8.1). */
|
||||
typedef struct imaxdiv_t
|
||||
{
|
||||
intmax_t quot;
|
||||
intmax_t rem;
|
||||
} imaxdiv_t;
|
||||
|
||||
/*
|
||||
* fprintf macros for the signed exact-width types (argument promotes to
|
||||
* int, or stays long for the 64-bit width).
|
||||
*/
|
||||
#define PRId8 "d"
|
||||
#define PRId16 "d"
|
||||
#define PRId32 "d"
|
||||
#define PRId64 "ld"
|
||||
|
||||
#define PRIi8 "i"
|
||||
#define PRIi16 "i"
|
||||
#define PRIi32 "i"
|
||||
#define PRIi64 "li"
|
||||
|
||||
/* fprintf macros for the unsigned exact-width types. */
|
||||
#define PRIo8 "o"
|
||||
#define PRIo16 "o"
|
||||
#define PRIo32 "o"
|
||||
#define PRIo64 "lo"
|
||||
|
||||
#define PRIu8 "u"
|
||||
#define PRIu16 "u"
|
||||
#define PRIu32 "u"
|
||||
#define PRIu64 "lu"
|
||||
|
||||
#define PRIx8 "x"
|
||||
#define PRIx16 "x"
|
||||
#define PRIx32 "x"
|
||||
#define PRIx64 "lx"
|
||||
|
||||
#define PRIX8 "X"
|
||||
#define PRIX16 "X"
|
||||
#define PRIX32 "X"
|
||||
#define PRIX64 "lX"
|
||||
|
||||
/* fprintf macros for the signed least-width types (same typedefs as the
|
||||
* exact-width types on x86_64). */
|
||||
#define PRIdLEAST8 "d"
|
||||
#define PRIdLEAST16 "d"
|
||||
#define PRIdLEAST32 "d"
|
||||
#define PRIdLEAST64 "ld"
|
||||
|
||||
#define PRIiLEAST8 "i"
|
||||
#define PRIiLEAST16 "i"
|
||||
#define PRIiLEAST32 "i"
|
||||
#define PRIiLEAST64 "li"
|
||||
|
||||
/* fprintf macros for the unsigned least-width types. */
|
||||
#define PRIoLEAST8 "o"
|
||||
#define PRIoLEAST16 "o"
|
||||
#define PRIoLEAST32 "o"
|
||||
#define PRIoLEAST64 "lo"
|
||||
|
||||
#define PRIuLEAST8 "u"
|
||||
#define PRIuLEAST16 "u"
|
||||
#define PRIuLEAST32 "u"
|
||||
#define PRIuLEAST64 "lu"
|
||||
|
||||
#define PRIxLEAST8 "x"
|
||||
#define PRIxLEAST16 "x"
|
||||
#define PRIxLEAST32 "x"
|
||||
#define PRIxLEAST64 "lx"
|
||||
|
||||
#define PRIXLEAST8 "X"
|
||||
#define PRIXLEAST16 "X"
|
||||
#define PRIXLEAST32 "X"
|
||||
#define PRIXLEAST64 "lX"
|
||||
|
||||
/* fprintf macros for the signed fastest types (int_fast8_t is signed char,
|
||||
* which promotes to int; int_fast16/32/64_t are long). */
|
||||
#define PRIdFAST8 "d"
|
||||
#define PRIdFAST16 "ld"
|
||||
#define PRIdFAST32 "ld"
|
||||
#define PRIdFAST64 "ld"
|
||||
|
||||
#define PRIiFAST8 "i"
|
||||
#define PRIiFAST16 "li"
|
||||
#define PRIiFAST32 "li"
|
||||
#define PRIiFAST64 "li"
|
||||
|
||||
/* fprintf macros for the unsigned fastest types. */
|
||||
#define PRIoFAST8 "o"
|
||||
#define PRIoFAST16 "lo"
|
||||
#define PRIoFAST32 "lo"
|
||||
#define PRIoFAST64 "lo"
|
||||
|
||||
#define PRIuFAST8 "u"
|
||||
#define PRIuFAST16 "lu"
|
||||
#define PRIuFAST32 "lu"
|
||||
#define PRIuFAST64 "lu"
|
||||
|
||||
#define PRIxFAST8 "x"
|
||||
#define PRIxFAST16 "lx"
|
||||
#define PRIxFAST32 "lx"
|
||||
#define PRIxFAST64 "lx"
|
||||
|
||||
#define PRIXFAST8 "X"
|
||||
#define PRIXFAST16 "lX"
|
||||
#define PRIXFAST32 "lX"
|
||||
#define PRIXFAST64 "lX"
|
||||
|
||||
/* fprintf macros for the widest (intmax_t/uintmax_t) and pointer-width
|
||||
* (intptr_t/uintptr_t) types; both families are long/unsigned long. */
|
||||
#define PRIdMAX "ld"
|
||||
#define PRIiMAX "li"
|
||||
#define PRIoMAX "lo"
|
||||
#define PRIuMAX "lu"
|
||||
#define PRIxMAX "lx"
|
||||
#define PRIXMAX "lX"
|
||||
|
||||
#define PRIdPTR "ld"
|
||||
#define PRIiPTR "li"
|
||||
#define PRIoPTR "lo"
|
||||
#define PRIuPTR "lu"
|
||||
#define PRIxPTR "lx"
|
||||
#define PRIXPTR "lX"
|
||||
|
||||
/*
|
||||
* fscanf macros for the signed exact-width types: the pointer targets the
|
||||
* exact type, so the modifier is hh/h/(none)/l by width.
|
||||
*/
|
||||
#define SCNd8 "hhd"
|
||||
#define SCNd16 "hd"
|
||||
#define SCNd32 "d"
|
||||
#define SCNd64 "ld"
|
||||
|
||||
#define SCNi8 "hhi"
|
||||
#define SCNi16 "hi"
|
||||
#define SCNi32 "i"
|
||||
#define SCNi64 "li"
|
||||
|
||||
/* fscanf macros for the unsigned exact-width types. */
|
||||
#define SCNo8 "hho"
|
||||
#define SCNo16 "ho"
|
||||
#define SCNo32 "o"
|
||||
#define SCNo64 "lo"
|
||||
|
||||
#define SCNu8 "hhu"
|
||||
#define SCNu16 "hu"
|
||||
#define SCNu32 "u"
|
||||
#define SCNu64 "lu"
|
||||
|
||||
#define SCNx8 "hhx"
|
||||
#define SCNx16 "hx"
|
||||
#define SCNx32 "x"
|
||||
#define SCNx64 "lx"
|
||||
|
||||
/* fscanf macros for the signed least-width types (same typedefs as the
|
||||
* exact-width types on x86_64). */
|
||||
#define SCNdLEAST8 "hhd"
|
||||
#define SCNdLEAST16 "hd"
|
||||
#define SCNdLEAST32 "d"
|
||||
#define SCNdLEAST64 "ld"
|
||||
|
||||
#define SCNiLEAST8 "hhi"
|
||||
#define SCNiLEAST16 "hi"
|
||||
#define SCNiLEAST32 "i"
|
||||
#define SCNiLEAST64 "li"
|
||||
|
||||
/* fscanf macros for the unsigned least-width types. */
|
||||
#define SCNoLEAST8 "hho"
|
||||
#define SCNoLEAST16 "ho"
|
||||
#define SCNoLEAST32 "o"
|
||||
#define SCNoLEAST64 "lo"
|
||||
|
||||
#define SCNuLEAST8 "hhu"
|
||||
#define SCNuLEAST16 "hu"
|
||||
#define SCNuLEAST32 "u"
|
||||
#define SCNuLEAST64 "lu"
|
||||
|
||||
#define SCNxLEAST8 "hhx"
|
||||
#define SCNxLEAST16 "hx"
|
||||
#define SCNxLEAST32 "x"
|
||||
#define SCNxLEAST64 "lx"
|
||||
|
||||
/* fscanf macros for the signed fastest types (int_fast8_t is signed char;
|
||||
* int_fast16/32/64_t are long). */
|
||||
#define SCNdFAST8 "hhd"
|
||||
#define SCNdFAST16 "ld"
|
||||
#define SCNdFAST32 "ld"
|
||||
#define SCNdFAST64 "ld"
|
||||
|
||||
#define SCNiFAST8 "hhi"
|
||||
#define SCNiFAST16 "li"
|
||||
#define SCNiFAST32 "li"
|
||||
#define SCNiFAST64 "li"
|
||||
|
||||
/* fscanf macros for the unsigned fastest types. */
|
||||
#define SCNoFAST8 "hho"
|
||||
#define SCNoFAST16 "lo"
|
||||
#define SCNoFAST32 "lo"
|
||||
#define SCNoFAST64 "lo"
|
||||
|
||||
#define SCNuFAST8 "hhu"
|
||||
#define SCNuFAST16 "lu"
|
||||
#define SCNuFAST32 "lu"
|
||||
#define SCNuFAST64 "lu"
|
||||
|
||||
#define SCNxFAST8 "hhx"
|
||||
#define SCNxFAST16 "lx"
|
||||
#define SCNxFAST32 "lx"
|
||||
#define SCNxFAST64 "lx"
|
||||
|
||||
/* fscanf macros for the widest and pointer-width types. */
|
||||
#define SCNdMAX "ld"
|
||||
#define SCNiMAX "li"
|
||||
#define SCNoMAX "lo"
|
||||
#define SCNuMAX "lu"
|
||||
#define SCNxMAX "lx"
|
||||
|
||||
#define SCNdPTR "ld"
|
||||
#define SCNiPTR "li"
|
||||
#define SCNoPTR "lo"
|
||||
#define SCNuPTR "lu"
|
||||
#define SCNxPTR "lx"
|
||||
|
||||
/*
|
||||
* Return the absolute value of n. abs(INTMAX_MIN) is not representable;
|
||||
* it wraps (implementation-defined, no errno), matching GCC's two's
|
||||
* complement semantics.
|
||||
* const: result depends only on the argument (GCC predeclares imaxabs
|
||||
* with this attribute, so no weaker one may be used).
|
||||
*/
|
||||
__attribute__((const)) intmax_t
|
||||
imaxabs(intmax_t n);
|
||||
|
||||
/*
|
||||
* Return { quot, rem } of numer / denom, both truncated toward zero with
|
||||
* the remainder taking the sign of numer (C division semantics).
|
||||
* const: result depends only on the arguments.
|
||||
*/
|
||||
__attribute__((const)) imaxdiv_t
|
||||
imaxdiv(intmax_t numer, intmax_t denom);
|
||||
|
||||
/*
|
||||
* Convert the initial portion of nptr to intmax_t, as strtol with the
|
||||
* widest signed type. intmax_t is long on x86_64 LP64, so this is an
|
||||
* ABI-identical delegation to strtol (same engine, same errno policy:
|
||||
* ERANGE clamps, EINVAL for a base outside {0} union [2, 36]).
|
||||
*/
|
||||
intmax_t
|
||||
strtoimax(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/* As strtoimax, converted to uintmax_t (unsigned long); negative subject
|
||||
* sequences wrap modulo 2^64 and are never a range error (C23 7.8.2.3). */
|
||||
uintmax_t
|
||||
strtoumax(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* Wide-character forms of strtoimax/strtoumax (C23 7.8.2.4-5). The subject
|
||||
* sequence is scanned from nptr under the same rules as strtol: C-locale
|
||||
* whitespace (L' ' and L'\t'..L'\r'), optional sign, base 0/2..36 with the
|
||||
* 0x/0 prefix rules, ASCII digits only. errno and endptr (of type
|
||||
* wchar_t **) behave exactly as the narrow forms.
|
||||
*/
|
||||
intmax_t
|
||||
wcstoimax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base);
|
||||
|
||||
uintmax_t
|
||||
wcstoumax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VLIBC_INTTYPES_H */
|
||||
@@ -105,6 +105,432 @@ __attribute__((pure)) size_t
|
||||
malloc_usable_size(void *ptr);
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/* numeric conversions (todo 11) */
|
||||
|
||||
/*
|
||||
* String-to-number conversions (C23 7.24.1). All are ISO C core / POSIX
|
||||
* base and present in every profile.
|
||||
*
|
||||
* The ato* wrappers carry no error reporting: their behavior is undefined
|
||||
* if the converted value cannot be represented (C23 7.24.1.1-2), so they
|
||||
* read their input and nothing else — hence pure.
|
||||
*
|
||||
* The strto* functions report range errors through errno and store the
|
||||
* scan position through endptr; both are real side effects, so they carry
|
||||
* no intent attribute. errno behavior follows C23 7.24.1.4-5: ERANGE when
|
||||
* the subject sequence is outside the representable range (the clamped
|
||||
* maximum/minimum is returned); EINVAL when base is not 0 and not in
|
||||
* [2, 36] (endptr left at nptr, 0 returned); endptr points at nptr when
|
||||
* no subject sequence is present.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Convert the initial decimal digits of nptr to int, discarding leading
|
||||
* whitespace and an optional sign. Equivalent to (int)strtol(nptr, 0, 10).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) int
|
||||
atoi(const char *nptr);
|
||||
|
||||
/*
|
||||
* As atoi, converted to long: strtol(nptr, 0, 10).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) long
|
||||
atol(const char *nptr);
|
||||
|
||||
/*
|
||||
* As atoi, converted to long long: strtoll(nptr, 0, 10).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) long long
|
||||
atoll(const char *nptr);
|
||||
|
||||
/*
|
||||
* As atoi, converted to double: strtod(nptr, 0).
|
||||
* pure: reads memory, no side effects.
|
||||
*/
|
||||
__attribute__((pure)) double
|
||||
atof(const char *nptr);
|
||||
|
||||
/*
|
||||
* Convert the initial portion of nptr to long, stopping at the first
|
||||
* character that is not part of the subject sequence and storing its
|
||||
* position in *endptr (if endptr is not NULL). See the family comment for
|
||||
* base, errno, and endptr semantics.
|
||||
*/
|
||||
long
|
||||
strtol(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* As strtol, converted to unsigned long. A subject sequence with a minus
|
||||
* sign yields the negated value computed in the return type (modulo
|
||||
* ULONG_MAX + 1) without a range error (C23 7.24.1.4p8).
|
||||
*/
|
||||
unsigned long
|
||||
strtoul(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* As strtol, converted to long long.
|
||||
*/
|
||||
long long
|
||||
strtoll(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* As strtol, converted to unsigned long long. Negative subject sequences
|
||||
* wrap modulo ULLONG_MAX + 1 as for strtoul.
|
||||
*/
|
||||
unsigned long long
|
||||
strtoull(const char *restrict nptr, char **restrict endptr, int base);
|
||||
|
||||
/*
|
||||
* Convert the initial portion of nptr to float, double, or long double
|
||||
* (C23 7.24.1.3): optional whitespace, optional sign, then either an
|
||||
* "inf"/"infinity" or "nan"/"nan(n-char-sequence)" subject (case-
|
||||
* insensitive), a hexadecimal floating subject ("0x1.8p1"), or a decimal
|
||||
* floating subject with optional exponent. Overflow returns ±HUGE_VAL*
|
||||
* with errno ERANGE; results too small to represent return a subnormal or
|
||||
* zero value with errno ERANGE. no-conversion stores nptr in *endptr.
|
||||
*/
|
||||
double
|
||||
strtod(const char *restrict nptr, char **restrict endptr);
|
||||
|
||||
float
|
||||
strtof(const char *restrict nptr, char **restrict endptr);
|
||||
|
||||
long double
|
||||
strtold(const char *restrict nptr, char **restrict endptr);
|
||||
|
||||
/* pseudo-random numbers, search, and integer arithmetic (todo 12) */
|
||||
|
||||
/*
|
||||
* C23 7.22.2/7.22.5/7.22.6: rand/srand, qsort/bsearch, the abs family, and
|
||||
* the div family are ISO C core and present in every profile. The abs and
|
||||
* div functions compute a pure function of their arguments, so they are
|
||||
* declared const (the compiler folds and eliminates the calls in static
|
||||
* links; GCC's own builtin declarations of abs/labs/llabs are const, and
|
||||
* the attribute here matches them). rand and srand carry state, and
|
||||
* qsort/bsearch call a caller-supplied comparator, so none of those four
|
||||
* carries an intent attribute.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The largest value rand() returns: 2^31 - 1, the largest int.
|
||||
*/
|
||||
#define RAND_MAX 2147483647
|
||||
|
||||
/*
|
||||
* Quotient/remainder pair from div/ldiv/lldiv: quot is the algebraic
|
||||
* quotient truncated toward zero, rem the remainder of the same sign as
|
||||
* the dividend, and quot*denom + rem == num.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int quot;
|
||||
int rem;
|
||||
} div_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
long quot;
|
||||
long rem;
|
||||
} ldiv_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
long long quot;
|
||||
long long rem;
|
||||
} lldiv_t;
|
||||
|
||||
/*
|
||||
* Absolute value of n. The most-negative value returns itself: the
|
||||
* negation happens in the unsigned type, which wraps, so the result is
|
||||
* never undefined behavior and errno is never set.
|
||||
*/
|
||||
__attribute__((const)) int
|
||||
abs(int n);
|
||||
|
||||
__attribute__((const)) long
|
||||
labs(long n);
|
||||
|
||||
__attribute__((const)) long long
|
||||
llabs(long long n);
|
||||
|
||||
/*
|
||||
* Quotient and remainder of num/denom, truncated toward zero.
|
||||
*/
|
||||
__attribute__((const)) div_t
|
||||
div(int num, int denom);
|
||||
|
||||
__attribute__((const)) ldiv_t
|
||||
ldiv(long num, long denom);
|
||||
|
||||
__attribute__((const)) lldiv_t
|
||||
lldiv(long long num, long long denom);
|
||||
|
||||
/*
|
||||
* Pseudo-random integer in [0, RAND_MAX], deterministic for a given
|
||||
* srand seed.
|
||||
*/
|
||||
int
|
||||
rand(void);
|
||||
|
||||
/*
|
||||
* Seed the rand() sequence with seed.
|
||||
*/
|
||||
void
|
||||
srand(unsigned int seed);
|
||||
|
||||
/*
|
||||
* Sort the array of nmemb elements of size bytes at base into ascending
|
||||
* order according to compar (C23 7.22.5.2). compar receives two pointers
|
||||
* to distinct elements and returns negative/zero/positive. The sort is
|
||||
* not stable.
|
||||
*/
|
||||
void
|
||||
qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
|
||||
|
||||
/*
|
||||
* Binary-search the array of nmemb sorted elements of size bytes at base
|
||||
* for *key, calling compar(key, element) (C23 7.22.5.1). Returns a
|
||||
* pointer to the matching element, or NULL when there is none.
|
||||
*/
|
||||
void *
|
||||
bsearch(const void *key, const void *base, size_t nmemb, size_t size,
|
||||
int (*compar)(const void *, const void *));
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
/* Level 2 (muslmimic): XSI and obsolescent extensions. */
|
||||
|
||||
/*
|
||||
* Reentrant rand: the state lives in the caller's *seedp, which is
|
||||
* updated on every call, so the sequence is independent of rand()'s own
|
||||
* global state.
|
||||
*/
|
||||
int
|
||||
rand_r(unsigned int *seedp);
|
||||
|
||||
/*
|
||||
* random/srandom family (XSI): a 31-bit pseudo-random sequence in
|
||||
* [0, 2^31). initstate installs state (size bytes, at least
|
||||
* sizeof(long)) as the current state buffer, seeds it, and returns the
|
||||
* previous buffer; setstate installs the buffer and returns the previous
|
||||
* one. srandom reseeds the current buffer.
|
||||
*/
|
||||
long
|
||||
random(void);
|
||||
|
||||
void
|
||||
srandom(unsigned int seed);
|
||||
|
||||
char *
|
||||
initstate(unsigned int seed, char *state, size_t size);
|
||||
|
||||
char *
|
||||
setstate(char *state);
|
||||
|
||||
/*
|
||||
* drand48 family (XSI): a 48-bit linear congruential sequence carried in
|
||||
* three unsigned shorts, least-significant first. drand48/erand48 return
|
||||
* the current value divided by 2^48 as a double in [0, 1); lrand48/
|
||||
* nrand48 return the high 31 bits; mrand48/jrand48 the high 32 bits
|
||||
* sign-extended. The erand48/nrand48/jrand48 forms step the caller's
|
||||
* xsubi in place; srand48/seed48/lcong48 manage the shared state and the
|
||||
* multiplier/addend.
|
||||
*/
|
||||
double
|
||||
drand48(void);
|
||||
|
||||
double
|
||||
erand48(unsigned short xsubi[3]);
|
||||
|
||||
long
|
||||
lrand48(void);
|
||||
|
||||
long
|
||||
nrand48(unsigned short xsubi[3]);
|
||||
|
||||
long
|
||||
mrand48(void);
|
||||
|
||||
long
|
||||
jrand48(unsigned short xsubi[3]);
|
||||
|
||||
void
|
||||
srand48(long seedval);
|
||||
|
||||
unsigned short *
|
||||
seed48(unsigned short seed16v[3]);
|
||||
|
||||
void
|
||||
lcong48(unsigned short param[7]);
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/* environment, multibyte characters, and temporary files (todo 13) */
|
||||
|
||||
/*
|
||||
* getenv/setenv/unsetenv (POSIX base), the C-locale multibyte conversions
|
||||
* (C23 7.24.7), and mkstemp/mkdtemp are level 1. The XSI/BSD extras —
|
||||
* putenv, clearenv, mktemp, the PTY helpers, and getsubopt — are level 2.
|
||||
*
|
||||
* None of these functions carries an intent attribute. getenv and the
|
||||
* environment mutators all read or write the shared global environ, so
|
||||
* pure/const would let the compiler hoist a stale getenv result across a
|
||||
* setenv call. The multibyte conversions are locale-stateful in general,
|
||||
* the temporary-file functions create filesystem entries, and ptsname
|
||||
* writes a shared static buffer — none of them is side-effect-free.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The process environment: a NULL-terminated array of "name=value"
|
||||
* strings. The array is installed by the startup code before main; the
|
||||
* functions below mutate it (setenv/unsetenv copy strings into malloc'd
|
||||
* storage). Reassigning environ directly is permitted by POSIX, but
|
||||
* calling setenv/unsetenv/putenv/clearenv afterwards is undefined, as it
|
||||
* is everywhere (the array is then no longer guaranteed to be owned by
|
||||
* the library).
|
||||
*/
|
||||
extern char **environ;
|
||||
|
||||
/*
|
||||
* Return a pointer to the value part (the bytes after '=') of the
|
||||
* environment entry whose name matches name, or NULL when the variable
|
||||
* is not set. The pointer is valid until the next setenv, unsetenv,
|
||||
* putenv, or clearenv call. No intent attribute: the result depends on
|
||||
* the mutable global environ.
|
||||
*/
|
||||
char *
|
||||
getenv(const char *name);
|
||||
|
||||
/*
|
||||
* Set name to value. A copy of "name=value" is made, so the caller may
|
||||
* reuse or free its buffers once the call returns. With overwrite
|
||||
* nonzero an existing entry is replaced; with overwrite zero an existing
|
||||
* entry is left unchanged and 0 is returned. Returns -1 with errno
|
||||
* EINVAL when name is NULL, empty, or contains '=', and -1 with errno
|
||||
* ENOMEM on allocation failure. A NULL value is treated as the empty
|
||||
* string.
|
||||
*/
|
||||
int
|
||||
setenv(const char *name, const char *value, int overwrite);
|
||||
|
||||
/*
|
||||
* Remove every entry whose name is name, compacting the array. Returns
|
||||
* -1 with errno EINVAL when name is NULL, empty, or contains '='.
|
||||
*/
|
||||
int
|
||||
unsetenv(const char *name);
|
||||
|
||||
/*
|
||||
* C-locale multibyte conversions (C23 7.24.7). Only the "C" locale
|
||||
* exists today (locale support is a later todo), where every multibyte
|
||||
* character is a single byte: byte c corresponds to the wide character
|
||||
* (unsigned char)c. The encoding is therefore stateless — the s == NULL
|
||||
* and NULL-destination forms all report "state-independent" — and no
|
||||
* mbstate_t variant is needed. mbtowc never fails in this locale (every
|
||||
* byte, including 0x80..0xFF, is a valid character); wctomb fails with
|
||||
* -1 + errno EILSEQ for any wchar_t outside 0..255.
|
||||
*/
|
||||
int
|
||||
mblen(const char *s, size_t n);
|
||||
|
||||
int
|
||||
mbtowc(wchar_t *restrict pwc, const char *restrict s, size_t n);
|
||||
|
||||
int
|
||||
wctomb(char *s, wchar_t wc);
|
||||
|
||||
size_t
|
||||
mbstowcs(wchar_t *restrict pwcs, const char *restrict s, size_t n);
|
||||
|
||||
size_t
|
||||
wcstombs(char *restrict s, const wchar_t *restrict pwcs, size_t n);
|
||||
|
||||
/*
|
||||
* Temporary files and directories. mkstemp replaces the trailing
|
||||
* "XXXXXX" of the template with random characters, creates the file
|
||||
* with mode 0600, and returns an open file descriptor (-1 with errno
|
||||
* EINVAL when the template has no "XXXXXX" suffix). mkdtemp does the
|
||||
* same replacement, creates a directory with mode 0700, and returns the
|
||||
* template (NULL on failure). The parameter is unnamed in these
|
||||
* declarations because POSIX's `template` is a C++ keyword and this
|
||||
* header is compiled by C++ consumers too.
|
||||
*/
|
||||
int
|
||||
mkstemp(char *);
|
||||
|
||||
char *
|
||||
mkdtemp(char *);
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
/* Level 2 (muslmimic): XSI and BSD environment extras. */
|
||||
|
||||
/*
|
||||
* Add string — a "name=value" pair in CALLER-OWNED storage — to the
|
||||
* environment: the caller's buffer becomes part of the environment and
|
||||
* must remain valid for the life of the process. An existing variable of
|
||||
* the same name is replaced (the old pointer is dropped); a string
|
||||
* without '=' removes the variable instead (glibc/musl behavior).
|
||||
* Returns 0, or -1 with errno ENOMEM.
|
||||
*/
|
||||
int
|
||||
putenv(char *string);
|
||||
|
||||
/*
|
||||
* Empty the environment, leaving environ as a fresh valid empty array
|
||||
* (never NULL) so later setenv calls keep working. The previous array
|
||||
* and its entries are deliberately not freed — entries may be putenv'd
|
||||
* caller storage or initial-stack strings. Returns 0, or -1 with errno
|
||||
* ENOMEM.
|
||||
*/
|
||||
int
|
||||
clearenv(void);
|
||||
|
||||
/*
|
||||
* Obsolescent mkstemp without the file: fills in the trailing "XXXXXX"
|
||||
* and returns the template, creating nothing. The name is not guaranteed
|
||||
* unique — nothing exists to race on. NULL with errno EINVAL when the
|
||||
* template has no "XXXXXX" suffix.
|
||||
*/
|
||||
char *
|
||||
mktemp(char *);
|
||||
|
||||
/*
|
||||
* XSI PTY helpers. posix_openpt opens the master side of a
|
||||
* pseudo-terminal via /dev/ptmx (flags, plus O_NOCTTY, are passed to
|
||||
* open) and returns the fd. grantpt returns 0 without doing anything:
|
||||
* modern kernels grant the slave when the master is opened. unlockpt
|
||||
* releases the slave-side lock. ptsname formats the slave path
|
||||
* "/dev/pts/N" into a shared static buffer — the result is valid until
|
||||
* the next ptsname call and is not thread-safe.
|
||||
*/
|
||||
int
|
||||
posix_openpt(int flags);
|
||||
|
||||
char *
|
||||
ptsname(int fd);
|
||||
|
||||
int
|
||||
grantpt(int fd);
|
||||
|
||||
int
|
||||
unlockpt(int fd);
|
||||
|
||||
/*
|
||||
* Parse one comma-separated suboption out of *optionp (POSIX XSI). On
|
||||
* success the index of the matching token is returned and *valuep points
|
||||
* to the "=value" part (or is NULL when the suboption has no '='); for
|
||||
* an unrecognized suboption -1 is returned and *valuep points to the
|
||||
* whole suboption string; at the end of the list -1 is returned and
|
||||
* *valuep is NULL. *optionp always advances past the comma, which is
|
||||
* overwritten with NUL (the string is modified in place).
|
||||
*/
|
||||
int
|
||||
getsubopt(char **optionp, char *const *tokens, char **valuep);
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef VLIBC_INTERNAL_STRTOX_H
|
||||
#define VLIBC_INTERNAL_STRTOX_H
|
||||
|
||||
/*
|
||||
* vlibc — internal numeric-scan engine (todo 11).
|
||||
*
|
||||
* This header declares the shared core of the strtol/strtoul family. The
|
||||
* engine performs C23 7.24.1.4 steps 1-7 — whitespace skip, optional sign,
|
||||
* base resolution (0/2..36 with the 0x/0 prefix rules), and saturating
|
||||
* digit accumulation — and reports the subject sequence's magnitude, sign,
|
||||
* and overflow status. The public wrappers in src/stdlib/strtox_impl.c own
|
||||
* the per-type clamping and errno policy (signed clamps at LONG_MIN/LONG_MAX
|
||||
* with ERANGE; unsigned negates in the return type modulo 2^width, with
|
||||
* ERANGE only when the magnitude itself overflows the type, C23 7.24.1.4p8).
|
||||
*
|
||||
* Todo 14 (strtoimax/strtoumax, which on x86_64 are long/unsigned long)
|
||||
* consumes this header and reuses the engine unchanged; the wcsto* family
|
||||
* only needs a byte-level shim before it.
|
||||
*
|
||||
* The helpers below are static inline so the engine and every wrapper share
|
||||
* one C-locale whitespace/digit definition without a call boundary.
|
||||
*/
|
||||
|
||||
#include "libc.h"
|
||||
|
||||
/* Scan results. */
|
||||
#define STRTOX_OK 0 /* subject sequence present (digits > 0) */
|
||||
#define STRTOX_EMPTY 1 /* no subject sequence; *endptr = nptr */
|
||||
#define STRTOX_BADBASE 2 /* base not in {0} union [2, 36]; *endptr = nptr */
|
||||
|
||||
/*
|
||||
* Scan the subject sequence at nptr (C23 7.24.1.4 steps 1-7).
|
||||
*
|
||||
* On STRTOX_OK: *mag holds the magnitude (exact unless *over), *neg the
|
||||
* sign, *endptr the position past the last digit, *over whether the
|
||||
* magnitude exceeded unsigned long long (callers must clamp + ERANGE).
|
||||
* On STRTOX_EMPTY / STRTOX_BADBASE: *endptr is set to nptr, *mag and *neg
|
||||
* are zeroed; the caller sets errno = EINVAL for STRTOX_BADBASE and
|
||||
* returns 0.
|
||||
*/
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier)
|
||||
hidden int
|
||||
__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag,
|
||||
int *over);
|
||||
// NOLINTEND(bugprone-reserved-identifier)
|
||||
|
||||
/* C-locale whitespace: exactly ' ' and '\t'..'\r'. */
|
||||
static inline int
|
||||
strtox_isspace(int c)
|
||||
{
|
||||
return c == ' ' || (unsigned int)c - '\t' < 5U;
|
||||
}
|
||||
|
||||
/*
|
||||
* Value of c as a base-36 digit, or -1 when c is not an ASCII letter/digit.
|
||||
* c | 0x20 folds 'A'..'F' to 'a'..'f'; the unsigned cast keeps negative
|
||||
* values out of the range checks (same arithmetic-range idiom as ctype.c).
|
||||
*/
|
||||
static inline int
|
||||
strtox_digit(int c)
|
||||
{
|
||||
if ((unsigned int)c - '0' < 10U)
|
||||
{
|
||||
return c - '0';
|
||||
}
|
||||
if ((unsigned int)(c | 0x20) - 'a' < 26U)
|
||||
{
|
||||
return (c | 0x20) - 'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_INTERNAL_STRTOX_H */
|
||||
@@ -0,0 +1,84 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* Integer absolute value and division helpers (todo 12). The abs family
|
||||
* negates in the unsigned type, so the most-negative value wraps to
|
||||
* itself without undefined behavior and errno is never set. div computes
|
||||
* through long long intermediates, where every int quotient and
|
||||
* remainder always fits, so INT_MIN / -1 works; ldiv/lldiv special-case
|
||||
* the one overflowing pair (num == MIN && denom == -1) and otherwise
|
||||
* use plain / and %, which C23 truncates toward zero. Division by zero
|
||||
* is undefined behavior and is not handled here.
|
||||
*/
|
||||
|
||||
int
|
||||
abs(int n)
|
||||
{
|
||||
return n < 0 ? (int)(0U - (unsigned int)n) : n;
|
||||
}
|
||||
|
||||
long
|
||||
labs(long n)
|
||||
{
|
||||
return n < 0 ? (long)(0UL - (unsigned long)n) : n;
|
||||
}
|
||||
|
||||
long long
|
||||
llabs(long long n)
|
||||
{
|
||||
return n < 0 ? (long long)(0ULL - (unsigned long long)n) : n;
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
div_t
|
||||
div(int num, int denom)
|
||||
{
|
||||
long long a = num;
|
||||
long long b = denom;
|
||||
div_t r;
|
||||
|
||||
r.quot = (int)(a / b);
|
||||
r.rem = (int)(a % b);
|
||||
return r;
|
||||
}
|
||||
|
||||
ldiv_t
|
||||
ldiv(long num, long denom)
|
||||
{
|
||||
ldiv_t r;
|
||||
|
||||
if (num == LONG_MIN && denom == -1L)
|
||||
{
|
||||
r.quot = LONG_MIN;
|
||||
r.rem = 0L;
|
||||
return r;
|
||||
}
|
||||
r.quot = num / denom;
|
||||
r.rem = num % denom;
|
||||
return r;
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
lldiv_t
|
||||
lldiv(long long num, long long denom)
|
||||
{
|
||||
lldiv_t r;
|
||||
|
||||
if (num == LLONG_MIN && denom == -1LL)
|
||||
{
|
||||
r.quot = LLONG_MIN;
|
||||
r.rem = 0LL;
|
||||
return r;
|
||||
}
|
||||
r.quot = num / denom;
|
||||
r.rem = num % denom;
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* Process environment access (todo 13).
|
||||
*
|
||||
* getenv/setenv/unsetenv are POSIX base; putenv and clearenv are XSI/BSD
|
||||
* and gated at level 2. All of them operate on the global environ array
|
||||
* installed by the startup code (src/start/environ.c): a NULL-terminated
|
||||
* array of "name=value" strings.
|
||||
*
|
||||
* Ownership model (why removed strings are never freed): setenv copies
|
||||
* the name and value into malloc'd storage, but putenv stores the
|
||||
* caller's own buffer, and the initial entries live on the
|
||||
* kernel-provided stack. The library therefore cannot tell which entry
|
||||
* strings it owns, so unsetenv/clearenv/overwrite drop the pointers
|
||||
* without freeing them. Repeated setenv/unsetenv cycles leak one small
|
||||
* string each — permitted by POSIX (the function set that allocates is
|
||||
* not required to free) and safe for every entry origin.
|
||||
*
|
||||
* The pointer ARRAY itself is tracked: env_array_owned says whether
|
||||
* environ points at an array this file malloc'd. The first append to an
|
||||
* inherited array copies the pointers into fresh storage instead of
|
||||
* realloc'ing memory the allocator does not own (an initial-stack or
|
||||
* caller-provided array), and every later append reallocs in place.
|
||||
* Reassigning environ directly is POSIX-legal but then makes the
|
||||
* following functions' behavior undefined, exactly as the header
|
||||
* documents.
|
||||
*/
|
||||
|
||||
/* True when environ points at an array this file allocated. */
|
||||
static int env_array_owned;
|
||||
|
||||
/*
|
||||
* Validate name for setenv/unsetenv/getenv: non-NULL, non-empty, no '='.
|
||||
* On success *len_out receives the length (the bytes before the would-be
|
||||
* '=').
|
||||
*/
|
||||
static int
|
||||
env_name_ok(const char *name, size_t *len_out)
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
if (name == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
while (name[i] != '\0' && name[i] != '=')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
if (i == 0 || name[i] == '=')
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
*len_out = i;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* True when the entry "name=value" string has the given name prefix. */
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
static int
|
||||
env_entry_matches(const char *entry, const char *name, size_t name_len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < name_len; i++)
|
||||
{
|
||||
if (entry[i] != name[i])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return entry[name_len] == '=';
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
/* Slot in environ holding an entry with the given name, or NULL. */
|
||||
static char **
|
||||
env_find(const char *name, size_t name_len)
|
||||
{
|
||||
char **p;
|
||||
|
||||
if (environ == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
for (p = environ; *p != 0; p++)
|
||||
{
|
||||
if (env_entry_matches(*p, name, name_len))
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Append entry to environ, growing or creating the pointer array. */
|
||||
static int
|
||||
env_append(char *entry)
|
||||
{
|
||||
char **array;
|
||||
char **p;
|
||||
size_t count = 0;
|
||||
size_t i;
|
||||
|
||||
for (p = environ; p != 0 && *p != 0; p++)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
if (env_array_owned)
|
||||
{
|
||||
array = (char **)realloc((void *)environ, (count + 2) * sizeof(char *));
|
||||
}
|
||||
else
|
||||
{
|
||||
array = (char **)malloc((count + 2) * sizeof(char *));
|
||||
if (array != 0)
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
array[i] = environ[i];
|
||||
}
|
||||
env_array_owned = 1;
|
||||
}
|
||||
}
|
||||
if (array == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
array[count] = entry;
|
||||
array[count + 1] = NULL;
|
||||
environ = array;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *
|
||||
getenv(const char *name)
|
||||
{
|
||||
size_t name_len;
|
||||
char **slot;
|
||||
|
||||
if (!env_name_ok(name, &name_len))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
slot = env_find(name, name_len);
|
||||
if (slot == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return *slot + name_len + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* no-tree-loop-distribute-patterns keeps GCC -O2 from rewriting the
|
||||
* length scan and the two copy loops below into strlen/memcpy calls:
|
||||
* the stdlib slice must not pull in the string library (the string.h
|
||||
* precedent: the copy/scan family carries the same attribute), and a
|
||||
* standalone test link has no memcpy/strlen objects on the command line.
|
||||
*/
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
__attribute__((optimize("no-tree-loop-distribute-patterns"))) int
|
||||
setenv(const char *name, const char *value, int overwrite)
|
||||
{
|
||||
size_t name_len;
|
||||
size_t value_len;
|
||||
char **slot;
|
||||
char *entry;
|
||||
size_t i;
|
||||
|
||||
if (!env_name_ok(name, &name_len))
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
value_len = 0;
|
||||
if (value != NULL)
|
||||
{
|
||||
while (value[value_len] != '\0')
|
||||
{
|
||||
value_len++;
|
||||
}
|
||||
}
|
||||
slot = env_find(name, name_len);
|
||||
if (slot != 0 && !overwrite)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
entry = malloc(name_len + 1 + value_len + 1);
|
||||
if (entry == NULL)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < name_len; i++)
|
||||
{
|
||||
entry[i] = name[i];
|
||||
}
|
||||
entry[name_len] = '=';
|
||||
for (i = 0; i < value_len; i++)
|
||||
{
|
||||
entry[name_len + 1 + i] = value[i];
|
||||
}
|
||||
entry[name_len + 1 + value_len] = '\0';
|
||||
if (slot != 0)
|
||||
{
|
||||
/* The old string is not freed: it may be putenv'd caller storage. */
|
||||
*slot = entry;
|
||||
return 0;
|
||||
}
|
||||
if (env_append(entry) != 0)
|
||||
{
|
||||
free(entry);
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
int
|
||||
unsetenv(const char *name)
|
||||
{
|
||||
size_t name_len;
|
||||
char **p;
|
||||
|
||||
if (!env_name_ok(name, &name_len))
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (environ == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
p = environ;
|
||||
while (*p != 0)
|
||||
{
|
||||
if (env_entry_matches(*p, name, name_len))
|
||||
{
|
||||
char **q = p;
|
||||
|
||||
do
|
||||
{
|
||||
*q = q[1];
|
||||
q++;
|
||||
} while (q[-1] != NULL);
|
||||
/* p now holds the next entry; check it too, without advancing. */
|
||||
}
|
||||
else
|
||||
{
|
||||
p++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
int
|
||||
putenv(char *string)
|
||||
{
|
||||
size_t name_len;
|
||||
char **slot;
|
||||
|
||||
if (string == NULL)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
name_len = 0;
|
||||
while (string[name_len] != '\0' && string[name_len] != '=')
|
||||
{
|
||||
name_len++;
|
||||
}
|
||||
if (name_len == 0)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (string[name_len] == '\0')
|
||||
{
|
||||
/* A bare name removes the variable (glibc/musl behavior). */
|
||||
return unsetenv(string);
|
||||
}
|
||||
slot = env_find(string, name_len);
|
||||
if (slot != 0)
|
||||
{
|
||||
*slot = string;
|
||||
return 0;
|
||||
}
|
||||
if (env_append(string) != 0)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
clearenv(void)
|
||||
{
|
||||
char **empty = (char **)malloc(sizeof(char *));
|
||||
|
||||
if (empty == 0)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
empty[0] = NULL;
|
||||
environ = empty;
|
||||
env_array_owned = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* vlibc — getsubopt (todo 13).
|
||||
*
|
||||
* POSIX XSI comma-separated suboption parser. The whole file self-gates
|
||||
* at VLIBC_LEVEL >= 2 (the build wiring pass may compile it
|
||||
* unconditionally): at level 1 this translation unit produces nothing.
|
||||
*
|
||||
* Contract (POSIX): *optionp points into a "name[=value][,name[=value]...]"
|
||||
* string that is modified in place. The comma terminating the parsed
|
||||
* suboption is overwritten with NUL and *optionp advances past it (or to
|
||||
* NULL at the end of the list). A recognized name returns its token index
|
||||
* with *valuep pointing at the value after '=' (the '=' itself stays in
|
||||
* the string, NUL-terminated by the comma); a matched name without '='
|
||||
* sets *valuep to NULL. An unrecognized name returns -1 with *valuep
|
||||
* pointing at the whole suboption, '=' and value included — the '=' is
|
||||
* NOT consumed, because *valuep must still be able to see it.
|
||||
*/
|
||||
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
int
|
||||
getsubopt(char **optionp, char *const *tokens, char **valuep)
|
||||
{
|
||||
char *s = *optionp;
|
||||
char *end;
|
||||
int i;
|
||||
|
||||
if (s == NULL)
|
||||
{
|
||||
*valuep = NULL;
|
||||
return -1;
|
||||
}
|
||||
end = s;
|
||||
while (*end != '\0' && *end != ',')
|
||||
{
|
||||
end++;
|
||||
}
|
||||
if (*end == ',')
|
||||
{
|
||||
*end = '\0';
|
||||
*optionp = end + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
*optionp = NULL;
|
||||
}
|
||||
*valuep = s; /* no-match default: the whole suboption */
|
||||
for (i = 0; tokens[i] != NULL; i++)
|
||||
{
|
||||
const char *key = tokens[i];
|
||||
size_t j = 0;
|
||||
|
||||
while (key[j] != '\0' && s[j] == key[j])
|
||||
{
|
||||
j++;
|
||||
}
|
||||
if (key[j] != '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (s[j] == '=')
|
||||
{
|
||||
*valuep = s + j + 1;
|
||||
return i;
|
||||
}
|
||||
if (s[j] == '\0')
|
||||
{
|
||||
*valuep = NULL;
|
||||
return i;
|
||||
}
|
||||
/* s[j] is neither NUL nor '=': key is a prefix, keep scanning. */
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,268 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* inttypes.h conversions (todo 14).
|
||||
*
|
||||
* strtoimax/strtoumax are thin delegations: on x86_64 LP64 intmax_t is
|
||||
* long and uintmax_t is unsigned long (GCC __INTMAX_TYPE__), so strtol and
|
||||
* strtoul are ABI-identical — same input, same endptr, same errno policy
|
||||
* (ERANGE clamps, EINVAL for a base outside {0} union [2, 36]). They share
|
||||
* the todo-11 engine in src/stdlib/strtox.c through those calls.
|
||||
*
|
||||
* wcstoimax/wcstoumax cannot reuse the byte-based engine directly, so this
|
||||
* file carries a self-contained wide scan: a wchar_t mirror of the
|
||||
* __strtox_scan algorithm (C-locale whitespace L' ' and L'\t'..L'\r',
|
||||
* optional sign, base 0/2..36 with the 0x/0 prefix rules, saturating u64
|
||||
* magnitude, same clamping policy). wchar_t is a 32-bit code unit here
|
||||
* (GCC __WCHAR_TYPE__ is int), so digits are matched by ASCII codepoint
|
||||
* exactly as strtox_digit matches bytes; non-ASCII code units are never
|
||||
* digits under the C locale. Todo 50 (wchar.h) may later unify this scan
|
||||
* with the narrow engine.
|
||||
*/
|
||||
|
||||
/* Wide-scan results; mirrors the STRTOX_* contract of the narrow engine. */
|
||||
#define WCSTOX_OK 0
|
||||
#define WCSTOX_EMPTY 1
|
||||
#define WCSTOX_BADBASE 2
|
||||
|
||||
/* C-locale whitespace for wide code units: L' ' and L'\t'..L'\r'. */
|
||||
static int
|
||||
wcstox_isspace(wchar_t c)
|
||||
{
|
||||
return c == L' ' || (unsigned int)c - L'\t' < 5U;
|
||||
}
|
||||
|
||||
/* Value of c as a base-36 digit, or -1 when it is not an ASCII letter or
|
||||
* digit; | 0x20 folds 'A'..'F' to 'a'..'f' (same idiom as strtox_digit). */
|
||||
static int
|
||||
wcstox_digit(wchar_t c)
|
||||
{
|
||||
if ((unsigned int)c - L'0' < 10U)
|
||||
{
|
||||
return (int)c - L'0';
|
||||
}
|
||||
if ((unsigned int)(c | 0x20) - L'a' < 26U)
|
||||
{
|
||||
return (int)(c | 0x20) - L'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan the subject sequence at nptr (C23 7.8.2.4 steps 1-7, the wide
|
||||
* mirror of __strtox_scan). On WCSTOX_OK: *mag holds the magnitude (exact
|
||||
* unless *over), *neg the sign, *endptr the position past the last digit.
|
||||
* On WCSTOX_EMPTY / WCSTOX_BADBASE: *endptr is set to nptr, *mag and *neg
|
||||
* are zeroed; the caller sets errno = EINVAL for WCSTOX_BADBASE.
|
||||
*/
|
||||
static int
|
||||
wcstox_scan(const wchar_t *nptr, wchar_t **endptr, int base, int *neg, unsigned long long *mag,
|
||||
int *over)
|
||||
{
|
||||
const wchar_t *s = nptr;
|
||||
int sg = 0;
|
||||
int ov = 0;
|
||||
int digits = 0;
|
||||
int consumed_prefix = 0;
|
||||
unsigned long long m = 0;
|
||||
|
||||
while (wcstox_isspace(*s))
|
||||
{
|
||||
s++;
|
||||
}
|
||||
if (*s == L'+' || *s == L'-')
|
||||
{
|
||||
sg = (*s == L'-');
|
||||
s++;
|
||||
}
|
||||
if (base != 0 && (base < 2 || base > 36))
|
||||
{
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (wchar_t *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return WCSTOX_BADBASE;
|
||||
}
|
||||
|
||||
if (base == 0 || base == 16)
|
||||
{
|
||||
if (s[0] == L'0' && (s[1] == L'x' || s[1] == L'X'))
|
||||
{
|
||||
int d2 = wcstox_digit(s[2]);
|
||||
|
||||
if (d2 >= 0 && d2 < 16)
|
||||
{
|
||||
base = 16;
|
||||
s += 2;
|
||||
}
|
||||
else if (base == 0)
|
||||
{
|
||||
/* "0x" with no hex digit is no integer-constant form. */
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (wchar_t *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return WCSTOX_EMPTY;
|
||||
}
|
||||
/* base 16: the 0 itself is the subject; the x ends it. */
|
||||
}
|
||||
else if (base == 0 && s[0] == L'0')
|
||||
{
|
||||
/* Octal prefix: the 0 opens the subject even when no octal
|
||||
* digit follows ("0" is itself a valid integer constant). */
|
||||
base = 8;
|
||||
consumed_prefix = 1;
|
||||
s++;
|
||||
}
|
||||
else if (base == 0)
|
||||
{
|
||||
base = 10;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int d = wcstox_digit(*s);
|
||||
|
||||
if (d < 0 || d >= base)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (ov == 0)
|
||||
{
|
||||
unsigned long long lim =
|
||||
(ULLONG_MAX - (unsigned long long)d) / (unsigned long long)base;
|
||||
|
||||
if (m > lim)
|
||||
{
|
||||
ov = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
m = (m * (unsigned long long)base) + (unsigned long long)d;
|
||||
}
|
||||
}
|
||||
digits++;
|
||||
s++;
|
||||
}
|
||||
|
||||
if (digits == 0 && consumed_prefix == 0)
|
||||
{
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (wchar_t *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return WCSTOX_EMPTY;
|
||||
}
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (wchar_t *)s;
|
||||
}
|
||||
*neg = sg;
|
||||
*mag = m;
|
||||
*over = ov;
|
||||
return WCSTOX_OK;
|
||||
}
|
||||
|
||||
intmax_t
|
||||
strtoimax(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
/* ABI identity: intmax_t is long on x86_64 LP64. */
|
||||
return strtol(nptr, endptr, base);
|
||||
}
|
||||
|
||||
uintmax_t
|
||||
strtoumax(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
/* ABI identity: uintmax_t is unsigned long on x86_64 LP64. */
|
||||
return strtoul(nptr, endptr, base);
|
||||
}
|
||||
|
||||
intmax_t
|
||||
wcstoimax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = wcstox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != WCSTOX_OK)
|
||||
{
|
||||
if (rc == WCSTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)INTMAX_MAX + (unsigned long long)(neg != 0))
|
||||
{
|
||||
errno = ERANGE;
|
||||
return neg != 0 ? INTMAX_MIN : INTMAX_MAX;
|
||||
}
|
||||
return neg != 0 ? (intmax_t)(0ULL - mag) : (intmax_t)mag;
|
||||
}
|
||||
|
||||
uintmax_t
|
||||
wcstoumax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = wcstox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != WCSTOX_OK)
|
||||
{
|
||||
if (rc == WCSTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > UINTMAX_MAX)
|
||||
{
|
||||
errno = ERANGE;
|
||||
return UINTMAX_MAX;
|
||||
}
|
||||
return neg != 0 ? (uintmax_t)(0ULL - mag) : (uintmax_t)mag;
|
||||
}
|
||||
|
||||
/*
|
||||
* imaxabs: INTMAX_MIN is not representable as a positive value; the
|
||||
* unsigned computation wraps it back to INTMAX_MIN (implementation-defined
|
||||
* by the uintmax_t -> intmax_t conversion, no errno), matching GCC's
|
||||
* two's complement semantics. The const attribute lives in <inttypes.h>.
|
||||
*/
|
||||
intmax_t
|
||||
imaxabs(intmax_t n)
|
||||
{
|
||||
return n >= 0 ? n : (intmax_t)((uintmax_t)0 - (uintmax_t)n);
|
||||
}
|
||||
|
||||
imaxdiv_t
|
||||
imaxdiv(intmax_t numer, intmax_t denom)
|
||||
{
|
||||
imaxdiv_t r;
|
||||
|
||||
r.quot = numer / denom;
|
||||
r.rem = numer % denom;
|
||||
return r;
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
|
||||
/*
|
||||
* vlibc — temporary files and directories (todo 13).
|
||||
*
|
||||
* mkstemp/mkdtemp (POSIX base) replace the six trailing 'X' characters of
|
||||
* the template with a generated suffix and create the file (mode 0600) or
|
||||
* directory (mode 0700); mktemp (XSI/BSD, level 2) only fills in the
|
||||
* suffix, creating nothing — its name is not guaranteed unique, but
|
||||
* nothing exists to race on.
|
||||
*
|
||||
* The suffix comes from a small internal 64-bit LCG seeded from the pid
|
||||
* (SYS_getpid): deliberately independent of rand() and time(), so the
|
||||
* temporary-file functions never depend on the PRNG family or a clock
|
||||
* source, and the sequence is reproducible per process. A colliding name
|
||||
* (EEXIST) regenerates the suffix, up to 100 attempts.
|
||||
*
|
||||
* Creation uses raw syscalls (SYS_openat/SYS_mkdir): the public open and
|
||||
* mkdir wrappers belong to later todos. The O_ and AT_ constants are
|
||||
* defined locally for the same reason (include/fcntl.h is owned by todo
|
||||
* 21); the values are kernel UAPI.
|
||||
*/
|
||||
|
||||
/* Kernel UAPI fcntl constants (local until include/fcntl.h lands). */
|
||||
#define TEMP_AT_FDCWD (-100)
|
||||
#define TEMP_O_RDWR 0x2
|
||||
#define TEMP_O_CREAT 0x40
|
||||
#define TEMP_O_EXCL 0x80
|
||||
|
||||
/* Retry budget for EEXIST collisions (6 chars over a 62-symbol alphabet). */
|
||||
#define TEMP_TRIES 100
|
||||
|
||||
/* The 62-character suffix alphabet: digits, uppercase, lowercase. */
|
||||
static const char temp_alphabet[] =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
/* Internal LCG state, seeded lazily from the pid on first use. */
|
||||
static unsigned long long temp_state;
|
||||
static int temp_seeded;
|
||||
|
||||
static void
|
||||
temp_advance(void)
|
||||
{
|
||||
if (!temp_seeded)
|
||||
{
|
||||
long pid = __syscall0(SYS_getpid);
|
||||
|
||||
temp_state = ((unsigned long long)pid * 6364136223846793005ULL) + 1ULL;
|
||||
temp_seeded = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp_state = (temp_state * 6364136223846793005ULL) + 1ULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Overwrite the six bytes at x with a fresh generated suffix. */
|
||||
static void
|
||||
temp_fill(char *x)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
temp_advance();
|
||||
x[i] = temp_alphabet[(size_t)((temp_state >> 33) % 62)];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate the template: it must end in exactly six 'X' characters. On
|
||||
* success *suffix_out receives the address of the first 'X'. The template
|
||||
* is not modified on failure.
|
||||
*/
|
||||
static int
|
||||
temp_suffix(char *template, char **suffix_out)
|
||||
{
|
||||
size_t len = 0;
|
||||
char *p;
|
||||
int i;
|
||||
|
||||
while (template[len] != '\0')
|
||||
{
|
||||
len++;
|
||||
}
|
||||
if (len < 6)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
p = template + len - 6;
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
if (p[i] != 'X')
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
*suffix_out = p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
mkstemp(char *template)
|
||||
{
|
||||
char *suffix;
|
||||
int tries;
|
||||
|
||||
if (!temp_suffix(template, &suffix))
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
for (tries = 0; tries < TEMP_TRIES; tries++)
|
||||
{
|
||||
int fd;
|
||||
|
||||
temp_fill(suffix);
|
||||
fd = syscall_ret(__syscall4(SYS_openat, TEMP_AT_FDCWD, (long)template,
|
||||
TEMP_O_RDWR | TEMP_O_CREAT | TEMP_O_EXCL, 0600));
|
||||
if (fd >= 0)
|
||||
{
|
||||
return fd;
|
||||
}
|
||||
if (errno != EEXIST)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
/* Name collision: regenerate the suffix and try again. */
|
||||
}
|
||||
errno = EEXIST;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *
|
||||
mkdtemp(char *template)
|
||||
{
|
||||
char *suffix;
|
||||
int tries;
|
||||
|
||||
if (!temp_suffix(template, &suffix))
|
||||
{
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
for (tries = 0; tries < TEMP_TRIES; tries++)
|
||||
{
|
||||
temp_fill(suffix);
|
||||
if (syscall_ret(__syscall2(SYS_mkdir, (long)template, 0700)) == 0)
|
||||
{
|
||||
return template;
|
||||
}
|
||||
if (errno != EEXIST)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
/* Name collision: regenerate the suffix and try again. */
|
||||
}
|
||||
errno = EEXIST;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
char *
|
||||
mktemp(char *template)
|
||||
{
|
||||
char *suffix;
|
||||
|
||||
if (!temp_suffix(template, &suffix))
|
||||
{
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
temp_fill(suffix);
|
||||
return template;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,137 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* vlibc — C-locale multibyte conversions (todo 13).
|
||||
*
|
||||
* Only the "C" locale exists today (locale support is a later todo), and
|
||||
* in that locale every multibyte character is exactly one byte: byte c is
|
||||
* the wide character (unsigned char)c. The encoding is stateless, so the
|
||||
* s == NULL state-query forms all report "state-independent" (0), and no
|
||||
* mbstate_t variant is needed. mbtowc never fails here — every byte
|
||||
* value, including 0x80..0xFF, is a valid character — while wctomb fails
|
||||
* with -1 + errno EILSEQ for any wide character outside 0..255.
|
||||
*
|
||||
* The n limits are observed but cannot truncate in a 1-byte encoding: an
|
||||
* n of 0 disables conversion (mbtowc returns -1), and any n >= 1 accepts
|
||||
* one character. mbstowcs/wcstombs stop at an embedded NUL, which is
|
||||
* CONVERTED into the destination (the terminating L'\0'/'\0' is written)
|
||||
* but NOT counted in the return value.
|
||||
*/
|
||||
|
||||
int
|
||||
mblen(const char *s, size_t n)
|
||||
{
|
||||
(void)n;
|
||||
if (s == NULL)
|
||||
{
|
||||
/* State query: the C-locale encoding has no shift state. */
|
||||
return 0;
|
||||
}
|
||||
return *s == '\0' ? 0 : 1;
|
||||
}
|
||||
|
||||
int
|
||||
mbtowc(wchar_t *restrict pwc, const char *restrict s, size_t n)
|
||||
{
|
||||
if (s == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (n == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (*s == '\0')
|
||||
{
|
||||
if (pwc != NULL)
|
||||
{
|
||||
*pwc = L'\0';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (pwc != NULL)
|
||||
{
|
||||
*pwc = (wchar_t)(unsigned char)*s;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
wctomb(char *s, wchar_t wc)
|
||||
{
|
||||
if (s == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (wc < 0 || wc > 255)
|
||||
{
|
||||
errno = EILSEQ;
|
||||
return -1;
|
||||
}
|
||||
s[0] = (char)wc;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t
|
||||
mbstowcs(wchar_t *restrict pwcs, const char *restrict s, size_t n)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
unsigned char c = (unsigned char)s[i];
|
||||
|
||||
if (c == '\0')
|
||||
{
|
||||
/* The terminating NUL is converted but not counted. */
|
||||
if (pwcs != NULL)
|
||||
{
|
||||
pwcs[i] = L'\0';
|
||||
}
|
||||
return i;
|
||||
}
|
||||
if (pwcs != NULL)
|
||||
{
|
||||
pwcs[i] = (wchar_t)c;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t
|
||||
wcstombs(char *restrict s, const wchar_t *restrict pwcs, size_t n)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
wchar_t wc = pwcs[i];
|
||||
|
||||
if (wc == L'\0')
|
||||
{
|
||||
/* The terminating L'\0' is converted but not counted. */
|
||||
if (s != NULL)
|
||||
{
|
||||
s[i] = '\0';
|
||||
}
|
||||
return i;
|
||||
}
|
||||
if (wc < 0 || wc > 255)
|
||||
{
|
||||
errno = EILSEQ;
|
||||
return (size_t)-1;
|
||||
}
|
||||
if (s != NULL)
|
||||
{
|
||||
s[i] = (char)wc;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* vlibc — XSI PTY helpers (todo 13).
|
||||
*
|
||||
* The whole file self-gates at VLIBC_LEVEL >= 2 (the build wiring pass
|
||||
* may compile it unconditionally): at level 1 this translation unit
|
||||
* produces nothing, because all four functions are XSI [PTY], not
|
||||
* POSIX.1-2008 base.
|
||||
*
|
||||
* Everything goes through the raw syscall layer: the public open/ioctl
|
||||
* wrappers belong to later todos, and include/fcntl.h (with the O_*
|
||||
* constants) is #21's file, so the kernel-UAPI values needed here are
|
||||
* defined locally.
|
||||
*
|
||||
* ptsname formats into a shared static buffer by hand — no snprintf/stdio
|
||||
* exists in the tree yet. The result is valid until the next ptsname call
|
||||
* and is not thread-safe, exactly as the header documents.
|
||||
*/
|
||||
|
||||
/* Kernel UAPI fcntl/ioctl constants (local until #21/#26 land). */
|
||||
#define PTY_AT_FDCWD (-100)
|
||||
#define PTY_O_NOCTTY 0x100
|
||||
#define PTY_TIOCGPTN 0x80045430 /* _IOR('T', 0x30, unsigned int) */
|
||||
#define PTY_TIOCSPTLCK 0x40045431 /* _IOW('T', 0x31, int) */
|
||||
|
||||
int
|
||||
posix_openpt(int flags)
|
||||
{
|
||||
return syscall_ret(
|
||||
__syscall3(SYS_openat, PTY_AT_FDCWD, (long)"/dev/ptmx", flags | PTY_O_NOCTTY));
|
||||
}
|
||||
|
||||
int
|
||||
grantpt(int fd)
|
||||
{
|
||||
/*
|
||||
* No-op by design: modern kernels grant the slave pty when the master
|
||||
* is opened, so there is nothing to chmod or chown. POSIX allows a
|
||||
* grantpt that always succeeds on such systems.
|
||||
*/
|
||||
(void)fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
unlockpt(int fd)
|
||||
{
|
||||
int unlock = 0;
|
||||
|
||||
return syscall_ret(__syscall3(SYS_ioctl, fd, PTY_TIOCSPTLCK, (long)&unlock));
|
||||
}
|
||||
|
||||
char *
|
||||
ptsname(int fd)
|
||||
{
|
||||
static char buf[20]; /* "/dev/pts/" (9) + up to 10 digits + NUL */
|
||||
static const char prefix[] = "/dev/pts/";
|
||||
char digits[11];
|
||||
char *p;
|
||||
char *d;
|
||||
const char *q;
|
||||
unsigned int n = 0; /* written by TIOCGPTN; zeroed for the static analyzers */
|
||||
|
||||
if (syscall_ret(__syscall3(SYS_ioctl, fd, PTY_TIOCGPTN, (long)&n)) != 0)
|
||||
{
|
||||
/* syscall_ret left errno (ENOTTY for a non-pty fd). */
|
||||
return NULL;
|
||||
}
|
||||
/* Build the digits backward into a local, then assemble the result. */
|
||||
p = digits + sizeof(digits) - 1;
|
||||
*p = '\0';
|
||||
do
|
||||
{
|
||||
p--;
|
||||
*p = (char)('0' + (n % 10));
|
||||
n /= 10;
|
||||
} while (n != 0);
|
||||
d = buf;
|
||||
for (q = prefix; *q != '\0'; q++)
|
||||
{
|
||||
*d = *q;
|
||||
d++;
|
||||
}
|
||||
for (q = p; *q != '\0'; q++)
|
||||
{
|
||||
*d = *q;
|
||||
d++;
|
||||
}
|
||||
*d = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,281 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* qsort/bsearch (todo 12). qsort is a median-of-three introsort over raw
|
||||
* bytes: quicksort with a Hoare-style partition, an insertion sort for
|
||||
* short segments, and a depth cap of 2*log2(n) past which the segment
|
||||
* falls back to heapsort, so adversarial input can never degrade to
|
||||
* O(n^2). Only the smaller partition half is recursed; the larger half
|
||||
* is iterated, bounding recursion depth by log2(n).
|
||||
*
|
||||
* Pivot selection sorts the three sample elements by value (median of
|
||||
* three) so the median sits at mid, then parks it at hi-1; the min and
|
||||
* max samples stay at lo and hi as outer sentinels. Partitioning only
|
||||
* the range [lo + 1, hi - 2], with an i-scan bounded at hi - 2 and a
|
||||
* j-scan bounded at lo, keeps the parked pivot pointer out of both
|
||||
* scans: compar is never called with two pointers to the same element.
|
||||
* Sorting the samples (rather than just locating the median) keeps the
|
||||
* pivot a true middle value on adversarial shapes such as reverse-
|
||||
* sorted data, whose first partition would otherwise hand down halves
|
||||
* shaped [local-max, ascending] that peel one element per level.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Swap size bytes between a and b, one byte at a time. The
|
||||
* no-tree-loop-distribute-patterns attribute stops GCC from recognizing
|
||||
* the byte loop as a memcpy idiom and rewriting it into a call to
|
||||
* memcpy — which for small sizes is exactly this loop, self-recursing
|
||||
* (see src/string/memcpy.c for the same guard on its byte loops).
|
||||
*/
|
||||
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
|
||||
swap_bytes(void *a, void *b, size_t size) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
unsigned char *pa = a;
|
||||
unsigned char *pb = b;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
unsigned char t = pa[i];
|
||||
|
||||
pa[i] = pb[i];
|
||||
pb[i] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/* Address of element i of an array of size-byte elements at base. */
|
||||
static unsigned char *
|
||||
elem(void *base, size_t size, size_t i)
|
||||
{
|
||||
return (unsigned char *)base + (i * size);
|
||||
}
|
||||
|
||||
/* Insertion sort: bubble each element down; width-independent, no temp
|
||||
* buffer, no VLA. */
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
static void
|
||||
insertion_sort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 1; i < nmemb; i++)
|
||||
{
|
||||
size_t j = i;
|
||||
|
||||
while (j > 0 && compar(elem(base, size, j), elem(base, size, j - 1)) < 0)
|
||||
{
|
||||
swap_bytes(elem(base, size, j), elem(base, size, j - 1), size);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
/* Sort the three sample elements at lo, mid, hi in place so
|
||||
* elem(lo) <= elem(mid) <= elem(hi): the median value ends up at mid.
|
||||
* The three positions are distinct (the caller guarantees >= 3
|
||||
* elements), so no comparand is ever the same element twice. */
|
||||
static void
|
||||
sort_samples(void *base, size_t size, size_t lo, size_t mid, size_t hi,
|
||||
int (*compar)(const void *, const void *))
|
||||
{
|
||||
void *a = elem(base, size, lo);
|
||||
void *b = elem(base, size, mid);
|
||||
void *c = elem(base, size, hi);
|
||||
|
||||
if (compar(b, a) < 0)
|
||||
{
|
||||
swap_bytes(b, a, size);
|
||||
}
|
||||
if (compar(c, a) < 0)
|
||||
{
|
||||
swap_bytes(c, a, size);
|
||||
}
|
||||
if (compar(c, b) < 0)
|
||||
{
|
||||
swap_bytes(c, b, size);
|
||||
}
|
||||
}
|
||||
|
||||
/* Heapsort over [base, base + nmemb*size): sift-down with root/child
|
||||
* swaps, never a self-comparison. */
|
||||
static void
|
||||
sift_down(void *base, size_t nmemb, size_t size, size_t root,
|
||||
int (*compar)(const void *, const void *))
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
size_t left = (2 * root) + 1;
|
||||
size_t largest;
|
||||
|
||||
if (left >= nmemb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
largest = left;
|
||||
if (left + 1 < nmemb && compar(elem(base, size, left), elem(base, size, left + 1)) < 0)
|
||||
{
|
||||
largest = left + 1;
|
||||
}
|
||||
if (compar(elem(base, size, root), elem(base, size, largest)) >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
swap_bytes(elem(base, size, root), elem(base, size, largest), size);
|
||||
root = largest;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
heapsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = nmemb / 2; i > 0; i--)
|
||||
{
|
||||
sift_down(base, nmemb, size, i - 1, compar);
|
||||
}
|
||||
for (i = nmemb; i > 1; i--)
|
||||
{
|
||||
swap_bytes(base, elem(base, size, i - 1), size);
|
||||
sift_down(base, i - 1, size, 0, compar);
|
||||
}
|
||||
}
|
||||
|
||||
/* Sort elements lo..hi; recurses on the smaller partition half and
|
||||
* iterates the larger. depth_limit counts remaining quicksort levels. */
|
||||
static void
|
||||
introsort_range(void *base, size_t size, size_t lo, size_t hi, // NOLINT(misc-no-recursion)
|
||||
int (*compar)(const void *, const void *), int depth_limit)
|
||||
{
|
||||
while (hi - lo >= 16)
|
||||
{
|
||||
size_t p;
|
||||
|
||||
if (depth_limit == 0)
|
||||
{
|
||||
heapsort(elem(base, size, lo), hi - lo + 1, size, compar);
|
||||
return;
|
||||
}
|
||||
depth_limit--;
|
||||
|
||||
{
|
||||
size_t mid = lo + ((hi - lo) / 2);
|
||||
|
||||
/*
|
||||
* Median of three by value: sort the samples so the median
|
||||
* sits at mid, then park it just inside the max sample at
|
||||
* hi. The min sample at lo and the max at hi are the outer
|
||||
* sentinels of the partition range [lo + 1, hi - 2]: the
|
||||
* i-scan is bounded at hi - 2 and the j-scan at lo, so the
|
||||
* parked pivot pointer itself is never compared against.
|
||||
*/
|
||||
sort_samples(base, size, lo, mid, hi, compar);
|
||||
swap_bytes(elem(base, size, mid), elem(base, size, hi - 1), size);
|
||||
}
|
||||
|
||||
{
|
||||
void *pivot_value = elem(base, size, hi - 1);
|
||||
size_t i = lo + 1;
|
||||
size_t j = hi - 2;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
while (i <= hi - 2 && compar(elem(base, size, i), pivot_value) < 0)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
while (j > lo && compar(elem(base, size, j), pivot_value) > 0)
|
||||
{
|
||||
j--;
|
||||
}
|
||||
if (i >= j)
|
||||
{
|
||||
break;
|
||||
}
|
||||
swap_bytes(elem(base, size, i), elem(base, size, j), size);
|
||||
i++;
|
||||
j--;
|
||||
}
|
||||
p = i;
|
||||
if (p != hi - 1)
|
||||
{
|
||||
swap_bytes(elem(base, size, p), elem(base, size, hi - 1), size);
|
||||
}
|
||||
}
|
||||
|
||||
if (p - lo < hi - p)
|
||||
{
|
||||
if (p > lo)
|
||||
{
|
||||
introsort_range(base, size, lo, p - 1, compar, depth_limit);
|
||||
}
|
||||
lo = p + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (p < hi)
|
||||
{
|
||||
introsort_range(base, size, p + 1, hi, compar, depth_limit);
|
||||
}
|
||||
hi = p - 1;
|
||||
}
|
||||
}
|
||||
insertion_sort(elem(base, size, lo), hi - lo + 1, size, compar);
|
||||
}
|
||||
|
||||
void
|
||||
qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
|
||||
{
|
||||
int depth_limit = 0;
|
||||
size_t n = nmemb;
|
||||
|
||||
if (nmemb < 2 || size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
while (n > 1)
|
||||
{
|
||||
depth_limit++;
|
||||
n >>= 1;
|
||||
}
|
||||
depth_limit *= 2;
|
||||
introsort_range(base, size, 0, nmemb - 1, compar, depth_limit);
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
void *
|
||||
bsearch(const void *key, const void *base, size_t nmemb, size_t size,
|
||||
int (*compar)(const void *, const void *))
|
||||
{
|
||||
size_t lo = 0;
|
||||
size_t hi = nmemb;
|
||||
|
||||
while (lo < hi)
|
||||
{
|
||||
size_t mid = lo + ((hi - lo) / 2);
|
||||
const void *p = (const unsigned char *)base + (mid * size);
|
||||
int c = compar(key, p);
|
||||
|
||||
if (c < 0)
|
||||
{
|
||||
hi = mid;
|
||||
}
|
||||
else if (c > 0)
|
||||
{
|
||||
lo = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (void *)p;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
@@ -0,0 +1,214 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* Pseudo-random number generation (todo 12). C23 7.22.2 core rand/srand
|
||||
* at level 1; the reentrant rand_r and the XSI random and drand48
|
||||
* families at level 2.
|
||||
*
|
||||
* All generators are deterministic linear congruential generators with
|
||||
* fixed constants (any constants are valid: the standard pins only
|
||||
* determinism per seed and the output range). State lives in file-static
|
||||
* variables in plain snake_case — no implementation-reserved identifiers.
|
||||
*/
|
||||
|
||||
/* rand/srand: a 64-bit LCG whose top 31 bits form the result, so the
|
||||
* output spans exactly [0, 2^31 - 1] = [0, RAND_MAX]. */
|
||||
static unsigned long long rand_state = 1;
|
||||
|
||||
int
|
||||
rand(void)
|
||||
{
|
||||
rand_state = rand_state * 6364136223846793005ULL + 1ULL;
|
||||
return (int)((rand_state >> 33) & 0x7fffffff);
|
||||
}
|
||||
|
||||
void
|
||||
srand(unsigned int seed)
|
||||
{
|
||||
rand_state = (unsigned long long)seed;
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* rand_r: the reentrant form. The caller's *seedp is both input and
|
||||
* output state, entirely independent of rand()'s rand_state. The low
|
||||
* multiply is deliberately 32-bit (wrapping unsigned arithmetic), which
|
||||
* keeps the sequence portable across 64-bit and 32-bit targets.
|
||||
*/
|
||||
int
|
||||
rand_r(unsigned int *seedp)
|
||||
{
|
||||
unsigned int next = *seedp;
|
||||
|
||||
next = next * 1103515245U + 12345U;
|
||||
*seedp = next;
|
||||
return (int)(next >> 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* random/srandom/initstate/setstate: a 31-bit generator whose state
|
||||
* lives in a caller-provided long buffer. The first long of the buffer
|
||||
* is the generator value; initstate checks the size, installs the
|
||||
* buffer, seeds it, and returns the previous buffer (NULL when the
|
||||
* buffer is too small); setstate installs and returns the previous
|
||||
* buffer (NULL for a NULL argument); srandom reseeds whatever buffer is
|
||||
* installed. The default buffer is the file-static array below.
|
||||
*/
|
||||
static long default_random_state;
|
||||
|
||||
static long *random_state_pointer = &default_random_state;
|
||||
|
||||
long
|
||||
random(void)
|
||||
{
|
||||
long x = *random_state_pointer;
|
||||
|
||||
x = x * 6364136223846793005L + 1L;
|
||||
*random_state_pointer = x;
|
||||
return (x >> 33) & 0x7fffffffL;
|
||||
}
|
||||
|
||||
void
|
||||
srandom(unsigned int seed)
|
||||
{
|
||||
*random_state_pointer = (long)seed;
|
||||
}
|
||||
|
||||
char *
|
||||
initstate(unsigned int seed, char *state, size_t size)
|
||||
{
|
||||
char *old = (char *)random_state_pointer;
|
||||
|
||||
if (size < sizeof(long))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
random_state_pointer = (long *)state;
|
||||
srandom(seed);
|
||||
return old;
|
||||
}
|
||||
|
||||
char *
|
||||
setstate(char *state)
|
||||
{
|
||||
char *old = (char *)random_state_pointer;
|
||||
|
||||
if (state == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
random_state_pointer = (long *)state;
|
||||
return old;
|
||||
}
|
||||
|
||||
/*
|
||||
* drand48 family: the standard 48-bit LCG
|
||||
*
|
||||
* x' = (x * A + C) mod 2^48, A = 0x5DEECE66D, C = 0xB
|
||||
*
|
||||
* carried in three unsigned shorts, least-significant short first
|
||||
* (xsubi[0] is the low 16 bits), so the layout matches the XSI
|
||||
* declaration order. A and C are file-static: lcong48 mutates them, and
|
||||
* srand48/seed48 reset them to the defaults. The default state
|
||||
* X = 0x1234ABCD330E.
|
||||
*/
|
||||
static unsigned short drand48_state[3] = {0x330E, 0xABCD, 0x1234};
|
||||
static unsigned long long drand48_mult = 0x5DEECE66DULL;
|
||||
static unsigned long long drand48_add = 0xBULL;
|
||||
|
||||
static unsigned long long
|
||||
drand48_step(unsigned short xsubi[3])
|
||||
{
|
||||
unsigned long long x = (unsigned long long)xsubi[0] | ((unsigned long long)xsubi[1] << 16) |
|
||||
((unsigned long long)xsubi[2] << 32);
|
||||
|
||||
x = (x * drand48_mult + drand48_add) & 0xFFFFFFFFFFFFULL;
|
||||
xsubi[0] = (unsigned short)x;
|
||||
xsubi[1] = (unsigned short)(x >> 16);
|
||||
xsubi[2] = (unsigned short)(x >> 32);
|
||||
return x;
|
||||
}
|
||||
|
||||
double
|
||||
drand48(void)
|
||||
{
|
||||
return (double)drand48_step(drand48_state) / 281474976710656.0;
|
||||
}
|
||||
|
||||
double
|
||||
erand48(unsigned short xsubi[3])
|
||||
{
|
||||
return (double)drand48_step(xsubi) / 281474976710656.0;
|
||||
}
|
||||
|
||||
long
|
||||
lrand48(void)
|
||||
{
|
||||
return (long)(drand48_step(drand48_state) >> 17);
|
||||
}
|
||||
|
||||
long
|
||||
nrand48(unsigned short xsubi[3])
|
||||
{
|
||||
return (long)(drand48_step(xsubi) >> 17);
|
||||
}
|
||||
|
||||
long
|
||||
mrand48(void)
|
||||
{
|
||||
return (long)(int)(drand48_step(drand48_state) >> 16);
|
||||
}
|
||||
|
||||
long
|
||||
jrand48(unsigned short xsubi[3])
|
||||
{
|
||||
return (long)(int)(drand48_step(xsubi) >> 16);
|
||||
}
|
||||
|
||||
void
|
||||
srand48(long seedval)
|
||||
{
|
||||
unsigned long long x = (((unsigned long long)seedval << 16) | 0x330EULL) & 0xFFFFFFFFFFFFULL;
|
||||
|
||||
drand48_state[0] = (unsigned short)x;
|
||||
drand48_state[1] = (unsigned short)(x >> 16);
|
||||
drand48_state[2] = (unsigned short)(x >> 32);
|
||||
drand48_mult = 0x5DEECE66DULL;
|
||||
drand48_add = 0xBULL;
|
||||
}
|
||||
|
||||
unsigned short *
|
||||
seed48(unsigned short seed16v[3])
|
||||
{
|
||||
static unsigned short previous_state[3];
|
||||
|
||||
previous_state[0] = drand48_state[0];
|
||||
previous_state[1] = drand48_state[1];
|
||||
previous_state[2] = drand48_state[2];
|
||||
drand48_state[0] = seed16v[0];
|
||||
drand48_state[1] = seed16v[1];
|
||||
drand48_state[2] = seed16v[2];
|
||||
drand48_mult = 0x5DEECE66DULL;
|
||||
drand48_add = 0xBULL;
|
||||
return previous_state;
|
||||
}
|
||||
|
||||
void
|
||||
lcong48(unsigned short param[7])
|
||||
{
|
||||
drand48_state[0] = param[0];
|
||||
drand48_state[1] = param[1];
|
||||
drand48_state[2] = param[2];
|
||||
drand48_mult = (unsigned long long)param[3] | ((unsigned long long)param[4] << 16) |
|
||||
((unsigned long long)param[5] << 32);
|
||||
drand48_add = (unsigned long long)param[6];
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
+1028
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,149 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "../internal/strtox.h"
|
||||
|
||||
/*
|
||||
* Shared integer subject-sequence scan (todo 11). See src/internal/strtox.h
|
||||
* for the contract; this file is the engine the strtol/strtoul wrappers and
|
||||
* todo 14's strtoimax/strtoumax build on.
|
||||
*
|
||||
* Prefix rules (C23 7.24.1.4p3-4):
|
||||
* - base 0: "0x"/"0X" followed by a hex digit selects base 16 and the
|
||||
* prefix is consumed; a bare leading 0 selects base 8 and the 0 is the
|
||||
* subject itself ("0" is a valid integer constant, so the subject is
|
||||
* never empty after an octal prefix); anything else is base 10.
|
||||
* - base 16: "0x"/"0X" followed by a hex digit is consumed; without a
|
||||
* hex digit the "0" is itself a valid hex digit and the x terminates
|
||||
* the subject.
|
||||
* - "0x" without a hex digit under base 0 is no integer-constant form,
|
||||
* so there is no subject sequence at all.
|
||||
*
|
||||
* Endptr always lands at the first character that is not part of the
|
||||
* subject sequence, or at nptr when there is no subject (C23 7.24.1.4p5,
|
||||
* even when whitespace or a sign was consumed).
|
||||
*/
|
||||
|
||||
int
|
||||
__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag,
|
||||
int *over)
|
||||
{
|
||||
const char *s = nptr;
|
||||
int sg = 0;
|
||||
int ov = 0;
|
||||
int digits = 0;
|
||||
int consumed_prefix = 0;
|
||||
unsigned long long m = 0;
|
||||
|
||||
while (strtox_isspace(*s))
|
||||
{
|
||||
s++;
|
||||
}
|
||||
if (*s == '+' || *s == '-')
|
||||
{
|
||||
sg = (*s == '-');
|
||||
s++;
|
||||
}
|
||||
if (base != 0 && (base < 2 || base > 36))
|
||||
{
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_BADBASE;
|
||||
}
|
||||
|
||||
if (base == 0 || base == 16)
|
||||
{
|
||||
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
|
||||
{
|
||||
int d2 = strtox_digit(s[2]);
|
||||
|
||||
if (d2 >= 0 && d2 < 16)
|
||||
{
|
||||
base = 16;
|
||||
s += 2;
|
||||
}
|
||||
else if (base == 0)
|
||||
{
|
||||
/* "0x" with no hex digit is no integer-constant form. */
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_EMPTY;
|
||||
}
|
||||
/* base 16: the 0 itself is the subject; the x ends it. */
|
||||
}
|
||||
else if (base == 0 && s[0] == '0')
|
||||
{
|
||||
/* Octal prefix: the 0 opens the subject even when no octal
|
||||
* digit follows ("0" is itself a valid integer constant). */
|
||||
base = 8;
|
||||
consumed_prefix = 1;
|
||||
s++;
|
||||
}
|
||||
else if (base == 0)
|
||||
{
|
||||
/* No prefix: a decimal subject (base 0 auto-detection). */
|
||||
base = 10;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int d = strtox_digit(*s);
|
||||
|
||||
if (d < 0 || d >= base)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (ov == 0)
|
||||
{
|
||||
unsigned long long lim =
|
||||
(ULLONG_MAX - (unsigned long long)d) / (unsigned long long)base;
|
||||
|
||||
if (m > lim)
|
||||
{
|
||||
ov = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
m = (m * (unsigned long long)base) + (unsigned long long)d;
|
||||
}
|
||||
}
|
||||
digits++;
|
||||
s++;
|
||||
}
|
||||
|
||||
if (digits == 0 && consumed_prefix == 0)
|
||||
{
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_EMPTY;
|
||||
}
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)s;
|
||||
}
|
||||
*neg = sg;
|
||||
*mag = m;
|
||||
*over = ov;
|
||||
return STRTOX_OK;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "../internal/strtox.h"
|
||||
|
||||
/*
|
||||
* Public integer wrappers over the __strtox_scan engine (todo 11).
|
||||
*
|
||||
* Clamping policy (C23 7.24.1.4p7-8):
|
||||
* - signed: a magnitude above the type maximum (with the sign folded in:
|
||||
* -2^(N-1) is representable, -(2^(N-1)+1) is not) clamps to LONG_MAX /
|
||||
* LONG_MIN with errno ERANGE;
|
||||
* - unsigned: a magnitude above the type maximum clamps to ULONG_MAX
|
||||
* with errno ERANGE; a minus sign negates the value in the return type
|
||||
* (modulo 2^N) and is never a range error by itself.
|
||||
* - base outside {0} union [2, 36]: errno EINVAL, return 0, *endptr set
|
||||
* to nptr (POSIX.1-2008).
|
||||
*/
|
||||
|
||||
long
|
||||
strtol(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)LONG_MAX + (unsigned long long)(neg != 0))
|
||||
{
|
||||
errno = ERANGE;
|
||||
return neg != 0 ? LONG_MIN : LONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (long)(0ULL - mag) : (long)mag;
|
||||
}
|
||||
|
||||
unsigned long
|
||||
strtoul(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)ULONG_MAX)
|
||||
{
|
||||
errno = ERANGE;
|
||||
return ULONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (unsigned long)(0ULL - mag) : (unsigned long)mag;
|
||||
}
|
||||
|
||||
long long
|
||||
strtoll(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)LLONG_MAX + (unsigned long long)(neg != 0))
|
||||
{
|
||||
errno = ERANGE;
|
||||
return neg != 0 ? LLONG_MIN : LLONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (long long)(0ULL - mag) : (long long)mag;
|
||||
}
|
||||
|
||||
unsigned long long
|
||||
strtoull(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > ULLONG_MAX)
|
||||
{
|
||||
errno = ERANGE;
|
||||
return ULLONG_MAX;
|
||||
}
|
||||
return neg != 0 ? 0ULL - mag : mag;
|
||||
}
|
||||
|
||||
/*
|
||||
* atoi/atol/atoll: strto* with no endptr and base 10 (C23 7.24.1.2). The
|
||||
* result truncation is that of the target type; out-of-range input is
|
||||
* undefined behavior, so no additional checking is performed.
|
||||
*/
|
||||
int
|
||||
atoi(const char *nptr)
|
||||
{
|
||||
return (int)strtol(nptr, NULL, 10);
|
||||
}
|
||||
|
||||
long
|
||||
atol(const char *nptr)
|
||||
{
|
||||
return strtol(nptr, NULL, 10);
|
||||
}
|
||||
|
||||
long long
|
||||
atoll(const char *nptr)
|
||||
{
|
||||
return strtoll(nptr, NULL, 10);
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* vlibc — environment, multibyte, and temporary-file test (todo 13).
|
||||
*
|
||||
* Exercises the stdlib environment slice end to end:
|
||||
*
|
||||
* 1. setenv("V","1",0) installs; getenv returns the value; setenv with
|
||||
* overwrite=0 leaves it alone; overwrite=1 replaces it; unsetenv
|
||||
* removes it (getenv -> NULL).
|
||||
* 2. setenv with '=' in the name returns -1 (value check only).
|
||||
* 3. Level 2: putenv stores a caller-owned "A=B" string that getenv
|
||||
* sees; clearenv empties the environment and a later setenv still
|
||||
* works.
|
||||
* 4. C-locale multibyte round-trips: mblen/mbtowc/wctomb state-query and
|
||||
* conversion forms, embedded-NUL stop (converted, not counted), the
|
||||
* n clamps, and wctomb/wcstombs of a wide character > 255 failing
|
||||
* with -1.
|
||||
* 5. mkstemp on "/tmp/vlibcXXXXXX": fd >= 0, write/lseek/read round-trip
|
||||
* through raw syscalls, close, unlink. mkdtemp creates a directory
|
||||
* and rmdir removes it.
|
||||
* 6. Failure mode (-f): mkstemp on a template without a "XXXXXX" suffix
|
||||
* returns -1 and leaves the template byte-identical.
|
||||
*
|
||||
* errno is never read here. A few negative paths (setenv EINVAL,
|
||||
* wctomb/wcstombs EILSEQ) make the LIBRARY write errno, and vlibc's errno
|
||||
* macro addresses %fs:0+8 — under the host libc (this test links against
|
||||
* glibc) that slot is the TLS dtv pointer, glibc's private state. Those
|
||||
* calls are therefore bracketed with a save/restore of the slot: between
|
||||
* the write and the restore only vlibc code runs, so the host TCB is
|
||||
* intact again before any host code touches it (the tests/syscall_test.c
|
||||
* fake-TCB discipline). The -f mode makes one such write inside mkstemp
|
||||
* and then leaves via a raw SYS_exit_group without running host cleanup.
|
||||
*
|
||||
* All diagnostics go through raw SYS_write (no stdio, no host headers):
|
||||
* under -Iinclude the vlibc public headers shadow GCC's internal ones.
|
||||
*
|
||||
* Not part of the library proper; compiled manually for this todo (the
|
||||
* tests/ + make check wiring is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||
static 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';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Byte-compare two NUL-terminated strings (vlibc's own — the test stays
|
||||
* host-header-free, so no <string.h>). noipa keeps GCC from folding the
|
||||
* comparison against a string literal at -O2.
|
||||
*/
|
||||
static __attribute__((noipa)) int
|
||||
str_eq(const char *a, const char *b)
|
||||
{
|
||||
if (a == NULL || b == NULL)
|
||||
{
|
||||
return a == b;
|
||||
}
|
||||
while (*a == *b && *a != '\0')
|
||||
{
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
/* True when s starts with the given prefix. */
|
||||
static __attribute__((noipa)) int
|
||||
prefix_eq(const char *s, const char *prefix)
|
||||
{
|
||||
while (*prefix != '\0')
|
||||
{
|
||||
if (*s != *prefix)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
s++;
|
||||
prefix++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Save/restore the host libc's TCB slot 1 (%fs:0+8), see the banner. */
|
||||
static unsigned long
|
||||
tcb_slot1_save(void)
|
||||
{
|
||||
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
||||
}
|
||||
|
||||
static void
|
||||
tcb_slot1_restore(unsigned long v)
|
||||
{
|
||||
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = v;
|
||||
}
|
||||
|
||||
/* 1-3. Environment access: setenv/getenv/unsetenv, putenv, clearenv. */
|
||||
static void
|
||||
env_scenarios(void)
|
||||
{
|
||||
const char *v;
|
||||
unsigned long saved;
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
static char putenv_buf[] = "A=B";
|
||||
#endif
|
||||
|
||||
check(setenv("V", "1", 0) == 0, "setenv(\"V\",\"1\",0) returns 0");
|
||||
v = getenv("V");
|
||||
check(v != NULL && str_eq(v, "1"), "getenv(\"V\") == \"1\"");
|
||||
check(setenv("V", "9", 0) == 0, "setenv(\"V\",\"9\",0) no-overwrite returns 0");
|
||||
v = getenv("V");
|
||||
check(v != NULL && str_eq(v, "1"), "getenv(\"V\") still \"1\" after no-overwrite");
|
||||
check(setenv("V", "2", 1) == 0, "setenv(\"V\",\"2\",1) overwrite returns 0");
|
||||
v = getenv("V");
|
||||
check(v != NULL && str_eq(v, "2"), "getenv(\"V\") == \"2\" after overwrite");
|
||||
check(unsetenv("V") == 0, "unsetenv(\"V\") returns 0");
|
||||
check(getenv("V") == NULL, "getenv(\"V\") == NULL after unsetenv");
|
||||
saved = tcb_slot1_save();
|
||||
check(setenv("A=B", "x", 0) == -1, "setenv with '=' in the name returns -1");
|
||||
tcb_slot1_restore(saved);
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
check(putenv(putenv_buf) == 0, "putenv(\"A=B\") returns 0");
|
||||
v = getenv("A");
|
||||
check(v != NULL && str_eq(v, "B"), "getenv(\"A\") == \"B\" after putenv");
|
||||
check(clearenv() == 0, "clearenv() returns 0");
|
||||
check(getenv("A") == NULL, "getenv(\"A\") == NULL after clearenv");
|
||||
check(setenv("V", "3", 0) == 0, "setenv after clearenv returns 0");
|
||||
v = getenv("V");
|
||||
check(v != NULL && str_eq(v, "3"), "getenv(\"V\") == \"3\" after clearenv + setenv");
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
}
|
||||
|
||||
/* 4. C-locale multibyte conversions. */
|
||||
static void
|
||||
multibyte_scenarios(void)
|
||||
{
|
||||
wchar_t wc;
|
||||
wchar_t wcs[8];
|
||||
char buf[16];
|
||||
unsigned long saved;
|
||||
|
||||
check(mblen(NULL, 5) == 0, "mblen(NULL) state query returns 0");
|
||||
check(mblen("", 5) == 0, "mblen(\"\") == 0");
|
||||
check(mblen("x", 5) == 1, "mblen(\"x\") == 1");
|
||||
check(mbtowc(&wc, NULL, 5) == 0, "mbtowc with s == NULL state query returns 0");
|
||||
check(mbtowc(NULL, "x", 5) == 1, "mbtowc with pwc == NULL converts but does not store");
|
||||
check(mbtowc(&wc, "x", 0) == -1, "mbtowc with n == 0 returns -1");
|
||||
wc = L'q';
|
||||
check(mbtowc(&wc, "x", 5) == 1, "mbtowc(\"x\") == 1");
|
||||
check(wc == L'x', "mbtowc converts 'x' to L'x'");
|
||||
check(mbtowc(&wc, "", 5) == 0, "mbtowc(\"\") == 0");
|
||||
check(wc == 0, "mbtowc(\"\") stores L'\\0'");
|
||||
check(wctomb(NULL, L'x') == 0, "wctomb(NULL) state query returns 0");
|
||||
buf[0] = 'q';
|
||||
check(wctomb(buf, L'A') == 1, "wctomb(L'A') == 1");
|
||||
check(buf[0] == 'A', "wctomb stores 'A'");
|
||||
saved = tcb_slot1_save();
|
||||
check(wctomb(buf, (wchar_t)0x100) == -1, "wctomb of a wc > 255 returns -1");
|
||||
tcb_slot1_restore(saved);
|
||||
|
||||
check(mbstowcs(wcs, "abc", 8) == 3, "mbstowcs(\"abc\") == 3");
|
||||
check(wcs[0] == L'a' && wcs[1] == L'b' && wcs[2] == L'c' && wcs[3] == 0,
|
||||
"mbstowcs converts a/b/c and NUL-terminates");
|
||||
check(mbstowcs(wcs, "ab", 8) == 2, "mbstowcs stops at the embedded NUL, uncounted");
|
||||
check(wcs[2] == 0, "mbstowcs writes the terminating L'\\0'");
|
||||
check(mbstowcs(wcs, "abcd", 2) == 2, "mbstowcs clamps to n");
|
||||
check(wcstombs(buf, L"abc", 8) == 3, "wcstombs(L\"abc\") == 3");
|
||||
check(str_eq(buf, "abc"), "wcstombs produces \"abc\"");
|
||||
check(wcstombs(buf, L"", 8) == 0, "wcstombs(L\"\") == 0");
|
||||
check(buf[0] == 0, "wcstombs writes the terminating NUL");
|
||||
check(wcstombs(buf, L"abcd", 2) == 2, "wcstombs clamps to n");
|
||||
wc = (wchar_t)0x100;
|
||||
saved = tcb_slot1_save();
|
||||
check(wcstombs(buf, &wc, 8) == (size_t)-1, "wcstombs of a wc > 255 returns -1");
|
||||
tcb_slot1_restore(saved);
|
||||
}
|
||||
|
||||
/* 5. Temporary files and directories. */
|
||||
static void
|
||||
tempfile_scenarios(void)
|
||||
{
|
||||
char t1[] = "/tmp/vlibcXXXXXX";
|
||||
char t2[] = "/tmp/vlibc-dir-XXXXXX";
|
||||
char back[4] = {0}; /* written by the raw SYS_read below */
|
||||
char *r;
|
||||
int fd;
|
||||
int ok;
|
||||
long got;
|
||||
|
||||
fd = mkstemp(t1);
|
||||
check(fd >= 0, "mkstemp returns a valid fd");
|
||||
check(fd < 0 || prefix_eq(t1, "/tmp/vlibc"), "mkstemp keeps the template prefix");
|
||||
if (fd >= 0)
|
||||
{
|
||||
__syscall3(SYS_write, fd, (long)"hi42", 4);
|
||||
__syscall3(SYS_lseek, fd, 0, 0);
|
||||
got = __syscall3(SYS_read, fd, (long)back, 4);
|
||||
ok = got == 4 && back[0] == 'h' && back[1] == 'i' && back[2] == '4' && back[3] == '2';
|
||||
check(ok, "mkstemp file round-trips write/lseek/read");
|
||||
__syscall1(SYS_close, fd);
|
||||
__syscall1(SYS_unlink, (long)t1);
|
||||
}
|
||||
r = mkdtemp(t2);
|
||||
check(r == t2, "mkdtemp returns the template");
|
||||
if (r != NULL)
|
||||
{
|
||||
check(prefix_eq(t2, "/tmp/vlibc-dir"), "mkdtemp keeps the template prefix");
|
||||
__syscall1(SYS_rmdir, (long)t2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Failure scenarios (-f): bad template rejected, template unchanged. */
|
||||
static int
|
||||
failure_scenarios(void)
|
||||
{
|
||||
char bad[] = "no-suffix";
|
||||
int rc = mkstemp(bad);
|
||||
|
||||
if (rc != -1)
|
||||
{
|
||||
say(2, "FAIL: mkstemp without a XXXXXX suffix returned non-(-1)\n");
|
||||
failures++;
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "PASS: mkstemp(\"no-suffix\") -> -1 (EINVAL)\n");
|
||||
}
|
||||
if (bad[0] == 'n' && bad[1] == 'o' && bad[2] == '-' && bad[3] == 's' && bad[4] == 'u' &&
|
||||
bad[5] == 'f' && bad[6] == 'f' && bad[7] == 'i' && bad[8] == 'x' && bad[9] == '\0')
|
||||
{
|
||||
say(1, "PASS: mkstemp leaves the invalid template unchanged\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: mkstemp modified the invalid template\n");
|
||||
failures++;
|
||||
}
|
||||
return failures > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
/*
|
||||
* The failure scenario makes the library write errno inside
|
||||
* mkstemp; leave via the raw syscall so the host cleanup never
|
||||
* runs after that write (see the banner).
|
||||
*/
|
||||
int rc = failure_scenarios();
|
||||
|
||||
__syscall1(SYS_exit_group, rc);
|
||||
return rc; /* not reached */
|
||||
}
|
||||
|
||||
env_scenarios();
|
||||
multibyte_scenarios();
|
||||
tempfile_scenarios();
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all env/multibyte/tempfile tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* vlibc — inttypes.h test (todo 14).
|
||||
*
|
||||
* Covers three surfaces, all without printf (which does not exist yet —
|
||||
* todo 16 owns it):
|
||||
*
|
||||
* 1. PRI/SCN macro literals: each macro must expand to a string literal
|
||||
* with the complete conversion specifier for the x86_64 LP64 target
|
||||
* (long = 64-bit), compared with vlibc's own strcmp, never through a
|
||||
* host printf. Design rule under test: the fprintf macros carry the
|
||||
* PROMOTED argument's modifier (PRId8 == "d", PRId64 == "ld") while
|
||||
* the fscanf macros carry the exact-width pointer modifier (SCNd8 ==
|
||||
* "hhd", SCNd64 == "ld").
|
||||
* 2. strtoimax/strtoumax: thin ABI-identical delegations to strtol/
|
||||
* strtoul (intmax_t is long on LP64), including the C23 7.8.2.3
|
||||
* unsigned-wrap contract (strtoumax("-1") == UINTMAX_MAX with no
|
||||
* range error) and imaxabs/imaxdiv.
|
||||
* 3. wcstoimax/wcstoumax: the self-contained wide scan, verified
|
||||
* value + endptr (wchar_t *) against the narrow-engine semantics.
|
||||
*
|
||||
* The `-f` failure mode runs the errno-writing scenarios (overflow clamps,
|
||||
* EINVAL bases). This standalone test is host-linked, so vlibc's errno
|
||||
* macro would write glibc's TCB slot (%fs:0+8) — errno is never read
|
||||
* back; the C23 return-value clamps (INTMAX_MAX/INTMAX_MIN/UINTMAX_MAX)
|
||||
* prove the ERANGE behavior, EINVAL is proven by the zero return with
|
||||
* endptr == nptr, and -f exits through a raw SYS_exit_group to skip host
|
||||
* cleanup. imaxabs(INTMAX_MIN) is deliberately untested: the wrap is
|
||||
* implementation-defined.
|
||||
*
|
||||
* No host headers: under -Iinclude the vlibc headers shadow GCC's
|
||||
* internal ones, so all output goes through raw SYS_write. Not part of the
|
||||
* library proper; compiled manually for this todo (make check wiring is
|
||||
* owned by a later todo).
|
||||
*/
|
||||
|
||||
/* Link-trap note: strtoimax/strtoumax delegate to strtol/strtoul — standalone
|
||||
* links MUST include src/stdlib/strtox.c + strtox_impl.c (+ strtod.c) or host
|
||||
* glibc silently satisfies them and -f's EINVAL-base checks fail. */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../include/inttypes.h"
|
||||
|
||||
#include "../include/string.h"
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
static void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||
static void
|
||||
say_dec(int fd, unsigned long v)
|
||||
{
|
||||
char buf[24];
|
||||
int i = (int)sizeof(buf);
|
||||
|
||||
buf[--i] = '\0';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
check_ptr(const char *got, const char *want, const char *what)
|
||||
{
|
||||
if (got == want)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, " (endptr off by ");
|
||||
say_dec(2, (unsigned long)(got - want));
|
||||
say(2, ")\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||
|
||||
/* noipa: keep the comparison a real strcmp call so GCC cannot fold the
|
||||
* literal contents away at -O2 (test_malloc.c proxy precedent). */
|
||||
static __attribute__((noipa)) int
|
||||
str_eq(const char *a, const char *b)
|
||||
{
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
/* 1. PRI/SCN macro literals (acceptance: a macro expands to a literal,
|
||||
* not via printf). */
|
||||
static void
|
||||
macro_literals(void)
|
||||
{
|
||||
const char *s;
|
||||
|
||||
s = PRIu64;
|
||||
check(str_eq(s, "lu"), "PRIu64 == \"lu\" (LP64 ABI)");
|
||||
s = PRId64;
|
||||
check(str_eq(s, "ld"), "PRId64 == \"ld\"");
|
||||
s = PRIi64;
|
||||
check(str_eq(s, "li"), "PRIi64 == \"li\"");
|
||||
s = PRIx64;
|
||||
check(str_eq(s, "lx"), "PRIx64 == \"lx\"");
|
||||
s = PRIX64;
|
||||
check(str_eq(s, "lX"), "PRIX64 == \"lX\"");
|
||||
s = PRIuMAX;
|
||||
check(str_eq(s, "lu"), "PRIuMAX == \"lu\" (intmax_t is long)");
|
||||
s = PRIdMAX;
|
||||
check(str_eq(s, "ld"), "PRIdMAX == \"ld\"");
|
||||
s = PRIdPTR;
|
||||
check(str_eq(s, "ld"), "PRIdPTR == \"ld\"");
|
||||
s = PRIxPTR;
|
||||
check(str_eq(s, "lx"), "PRIxPTR == \"lx\"");
|
||||
s = SCNu64;
|
||||
check(str_eq(s, "lu"), "SCNu64 == \"lu\"");
|
||||
s = SCNd64;
|
||||
check(str_eq(s, "ld"), "SCNd64 == \"ld\"");
|
||||
s = SCNx64;
|
||||
check(str_eq(s, "lx"), "SCNx64 == \"lx\"");
|
||||
|
||||
/* Sub-int widths: PRI uses the promoted argument's modifier, SCN the
|
||||
* exact-width pointer modifier. */
|
||||
s = PRId8;
|
||||
check(str_eq(s, "d"), "PRId8 == \"d\" (int8_t promotes to int)");
|
||||
s = SCNd8;
|
||||
check(str_eq(s, "hhd"), "SCNd8 == \"hhd\" (pointer to int8_t)");
|
||||
s = PRId16;
|
||||
check(str_eq(s, "d"), "PRId16 == \"d\" (int16_t promotes to int)");
|
||||
s = SCNd16;
|
||||
check(str_eq(s, "hd"), "SCNd16 == \"hd\"");
|
||||
s = SCNuLEAST8;
|
||||
check(str_eq(s, "hhu"), "SCNuLEAST8 == \"hhu\"");
|
||||
s = PRIdLEAST64;
|
||||
check(str_eq(s, "ld"), "PRIdLEAST64 == \"ld\"");
|
||||
|
||||
/* Fastest types: int_fast8_t is signed char, int_fast16/32/64_t are
|
||||
* long on x86_64. */
|
||||
s = PRIdFAST8;
|
||||
check(str_eq(s, "d"), "PRIdFAST8 == \"d\" (signed char promotes)");
|
||||
s = PRIdFAST16;
|
||||
check(str_eq(s, "ld"), "PRIdFAST16 == \"ld\" (long)");
|
||||
s = SCNdFAST8;
|
||||
check(str_eq(s, "hhd"), "SCNdFAST8 == \"hhd\"");
|
||||
s = SCNdFAST16;
|
||||
check(str_eq(s, "ld"), "SCNdFAST16 == \"ld\"");
|
||||
}
|
||||
|
||||
/* 2a. strtoimax/strtoumax happy paths (no errno writes). */
|
||||
static void
|
||||
strtoimax_happy(void)
|
||||
{
|
||||
const char *s = " -42";
|
||||
char *e;
|
||||
intmax_t v = strtoimax(s, &e, 0);
|
||||
|
||||
check(v == -42, "strtoimax(\" -42\",0) value");
|
||||
check_ptr(e, s + 5, "strtoimax(\" -42\",0) endptr past digits");
|
||||
|
||||
s = "42xyz";
|
||||
v = strtoimax(s, &e, 0);
|
||||
check(v == 42, "strtoimax(\"42xyz\") value");
|
||||
check_ptr(e, s + 2, "strtoimax(\"42xyz\") endptr at 'x'");
|
||||
|
||||
s = "0x1f";
|
||||
check(strtoimax(s, &e, 0) == 31 && e == s + 4, "strtoimax(\"0x1f\",0)==31");
|
||||
|
||||
s = "18446744073709551615";
|
||||
check(strtoumax(s, &e, 10) == UINTMAX_MAX && e == s + 20,
|
||||
"strtoumax(UINTMAX_MAX digits) exact, no range error");
|
||||
s = " 42";
|
||||
check(strtoumax(s, &e, 10) == 42 && e == s + 4, "strtoumax(\" 42\")==42");
|
||||
|
||||
/* C23 7.8.2.3: a minus sign negates in the return type modulo 2^64 and
|
||||
* is never a range error by itself (errno stays 0 — proven by value
|
||||
* only, never by reading errno in this host-linked test). */
|
||||
s = "-1";
|
||||
check(strtoumax(s, &e, 10) == UINTMAX_MAX, "strtoumax(\"-1\")==UINTMAX_MAX (wrap, no errno)");
|
||||
s = "-4294967296";
|
||||
check(strtoumax(s, &e, 10) == UINTMAX_MAX - 4294967295UL, "strtoumax(-2^32)==2^64-2^32");
|
||||
}
|
||||
|
||||
/* 2b. imaxabs/imaxdiv. imaxabs(INTMAX_MIN) is deliberately skipped: the
|
||||
* wrap is implementation-defined and UB-adjacent. */
|
||||
static void
|
||||
imax_abs_div(void)
|
||||
{
|
||||
check(imaxabs(-42) == 42 && imaxabs(42) == 42 && imaxabs(0) == 0, "imaxabs basics");
|
||||
check(imaxabs(INTMAX_MAX) == INTMAX_MAX, "imaxabs(INTMAX_MAX)");
|
||||
|
||||
{
|
||||
imaxdiv_t r = imaxdiv(7, 3);
|
||||
|
||||
check(r.quot == 2 && r.rem == 1, "imaxdiv(7,3) == {2,1}");
|
||||
}
|
||||
{
|
||||
imaxdiv_t r = imaxdiv(-7, 3);
|
||||
|
||||
check(r.quot == -2 && r.rem == -1, "imaxdiv(-7,3) == {-2,-1} (trunc toward zero)");
|
||||
}
|
||||
{
|
||||
imaxdiv_t r = imaxdiv(7, -3);
|
||||
|
||||
check(r.quot == -2 && r.rem == 1, "imaxdiv(7,-3) == {-2,1}");
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. wcstoimax/wcstoumax: wide scan mirrors the narrow engine. */
|
||||
static void
|
||||
wcsto_happy(void)
|
||||
{
|
||||
const wchar_t *ws = L" -42";
|
||||
wchar_t *e;
|
||||
intmax_t v = wcstoimax(ws, &e, 0);
|
||||
|
||||
check(v == -42, "wcstoimax(L\" -42\",0) value");
|
||||
check(e == ws + 5, "wcstoimax(L\" -42\",0) endptr past digits");
|
||||
|
||||
ws = L"4x";
|
||||
v = wcstoimax(ws, &e, 10);
|
||||
check(v == 4, "wcstoimax(L\"4x\") value");
|
||||
check(e == ws + 1, "wcstoimax(L\"4x\") endptr past L'4'");
|
||||
|
||||
ws = L"42abc";
|
||||
v = wcstoimax(ws, &e, 0);
|
||||
check(v == 42, "wcstoimax(L\"42abc\") value");
|
||||
check(e == ws + 2, "wcstoimax(L\"42abc\") endptr at 'a'");
|
||||
|
||||
ws = L"-0x10";
|
||||
check(wcstoimax(ws, &e, 0) == -16 && e == ws + 5, "wcstoimax(L\"-0x10\",0)==-16");
|
||||
|
||||
ws = L"18446744073709551615";
|
||||
check(wcstoumax(ws, &e, 10) == UINTMAX_MAX && e == ws + 20,
|
||||
"wcstoumax(UINTMAX_MAX digits) exact, no range error");
|
||||
|
||||
ws = L"-1";
|
||||
check(wcstoumax(ws, &e, 10) == UINTMAX_MAX, "wcstoumax(L\"-1\")==UINTMAX_MAX (wrap)");
|
||||
|
||||
ws = L"\t\n\v\f\r +42x";
|
||||
check(wcstoumax(ws, &e, 10) == 42 && e == ws + 9, "wcstoumax wide whitespace+sign");
|
||||
|
||||
ws = L" ";
|
||||
check(wcstoimax(ws, &e, 10) == 0 && e == ws, "wcstoimax(whitespace only) endptr at start");
|
||||
|
||||
ws = L"0x";
|
||||
check(wcstoimax(ws, &e, 0) == 0 && e == ws, "wcstoimax(L\"0x\",0): no subject");
|
||||
}
|
||||
|
||||
/*
|
||||
* Failure scenarios (-f): every path that writes errno inside the library.
|
||||
* errno is never read back (host TCB hazard) — the C23 return-value clamps
|
||||
* prove ERANGE, endptr == nptr with a zero return proves EINVAL — and the
|
||||
* process exits through a raw SYS_exit_group.
|
||||
*/
|
||||
static int
|
||||
failure_scenarios(void)
|
||||
{
|
||||
const char *s;
|
||||
char *e;
|
||||
int bad = 0;
|
||||
|
||||
s = "18446744073709551616";
|
||||
if (strtoumax(s, &e, 0) != UINTMAX_MAX || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoumax overflow clamp\n");
|
||||
}
|
||||
s = "9223372036854775808";
|
||||
if (strtoimax(s, &e, 10) != INTMAX_MAX || e != s + 19)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoimax overflow clamp\n");
|
||||
}
|
||||
s = "-9223372036854775809";
|
||||
if (strtoimax(s, &e, 10) != INTMAX_MIN || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoimax negative overflow clamp\n");
|
||||
}
|
||||
s = "-18446744073709551616";
|
||||
if (strtoumax(s, &e, 10) != UINTMAX_MAX || e != s + 21)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoumax magnitude 2^64 overflow clamp\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtoimax(s, &e, 1) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoimax base 1 (EINVAL)\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtoumax(s, &e, 37) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoumax base 37 (EINVAL)\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtoumax(s, &e, -1) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoumax base -1 (EINVAL)\n");
|
||||
}
|
||||
|
||||
{
|
||||
const wchar_t *ws = L"18446744073709551616";
|
||||
wchar_t *we;
|
||||
|
||||
if (wcstoumax(ws, &we, 0) != UINTMAX_MAX || we != ws + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: wcstoumax overflow clamp\n");
|
||||
}
|
||||
ws = L"9223372036854775808";
|
||||
if (wcstoimax(ws, &we, 10) != INTMAX_MAX || we != ws + 19)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: wcstoimax overflow clamp\n");
|
||||
}
|
||||
ws = L"-9223372036854775809";
|
||||
if (wcstoimax(ws, &we, 10) != INTMAX_MIN || we != ws + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: wcstoimax negative overflow clamp\n");
|
||||
}
|
||||
ws = L"123";
|
||||
if (wcstoimax(ws, &we, 37) != 0 || we != ws)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: wcstoimax base 37 (EINVAL)\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (bad == 0)
|
||||
{
|
||||
say(1, "PASS: all overflow/EINVAL failure scenarios\n");
|
||||
}
|
||||
return bad > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
rc = failure_scenarios();
|
||||
__syscall1(SYS_exit_group, rc);
|
||||
return rc; /* not reached */
|
||||
}
|
||||
|
||||
macro_literals();
|
||||
strtoimax_happy();
|
||||
imax_abs_div();
|
||||
wcsto_happy();
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all inttypes tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
* vlibc — rand/srand, abs/div, qsort/bsearch test (todo 12).
|
||||
*
|
||||
* Exercises the stdlib todo-12 additions end to end:
|
||||
*
|
||||
* 1. qsort on 0 and 1 element (the comparator is never called), 2048
|
||||
* pseudo-random ints, duplicate-heavy, reverse-sorted, and
|
||||
* all-equal arrays — each verified ascending with the element-sum
|
||||
* checksum preserved.
|
||||
* 2. bsearch finds a present key and returns NULL for an absent one.
|
||||
* 3. rand is deterministic per seed and stays inside [0, RAND_MAX];
|
||||
* div(7,3) == {2,1}, div(-7,3) == {-2,-1}, ldiv/lldiv spot checks;
|
||||
* abs/labs/llabs basics plus the INT_MIN/LONG_MIN/LLONG_MIN wrap
|
||||
* through volatile locals so the compiler cannot fold the negation.
|
||||
* 4. Level 2: rand_r determinism and independence from rand()'s
|
||||
* global state, srand48 reseed determinism, drand48 samples in
|
||||
* [0,1), lrand48 in [0,2^31), erand48 stepping the caller's xsubi.
|
||||
*
|
||||
* The instrumented comparator counts calls and records any call where
|
||||
* both arguments point at the same element; every scenario asserts the
|
||||
* self-compare flag stays clear.
|
||||
*
|
||||
* The -f mode stress-sorts three static-BSS arrays of 100000 ints
|
||||
* (reverse-sorted, already-sorted, all-equal) with the instrumented
|
||||
* comparator, verifies ascending order and an O(n log n) comparison
|
||||
* count (< 5.2M, far below the ~5e9 an O(n^2) sort would need), and
|
||||
* exits plainly — nothing here writes errno, so a normal return is
|
||||
* safe.
|
||||
*
|
||||
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude
|
||||
* the vlibc public headers shadow GCC's internal ones, so a host
|
||||
* <stdio.h> would not compile. The stdlib.h below is vlibc's own.
|
||||
*
|
||||
* Not part of the library proper; compiled manually for this todo (the
|
||||
* tests/ + make check wiring is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "../include/stdlib.h"
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||
static 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';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/* xorshift32; deterministic, allocation-independent. */
|
||||
static unsigned
|
||||
rng_next(unsigned *state)
|
||||
{
|
||||
unsigned x = *state;
|
||||
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
*state = x;
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Instrumented int comparator: counts calls and records self-compares. */
|
||||
static long compar_count;
|
||||
static int self_compare_seen;
|
||||
|
||||
static int
|
||||
int_cmp(const void *a, const void *b)
|
||||
{
|
||||
const int *pa = a;
|
||||
const int *pb = b;
|
||||
|
||||
compar_count++;
|
||||
if (a == b)
|
||||
{
|
||||
self_compare_seen = 1;
|
||||
}
|
||||
if (*pa < *pb)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (*pa > *pb)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sort arr (n ints) and verify ascending order and a preserved sum. */
|
||||
static int
|
||||
sort_and_verify(int *arr, size_t n, const char *what)
|
||||
{
|
||||
size_t i;
|
||||
long long before = 0;
|
||||
long long after = 0;
|
||||
int asc = 1;
|
||||
int ok = 1;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
before += arr[i];
|
||||
}
|
||||
compar_count = 0;
|
||||
qsort(arr, n, sizeof(int), int_cmp);
|
||||
for (i = 1; i < n; i++)
|
||||
{
|
||||
if (arr[i - 1] > arr[i])
|
||||
{
|
||||
asc = 0;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
after += arr[i];
|
||||
}
|
||||
if (!asc)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, " - not ascending\n");
|
||||
failures++;
|
||||
ok = 0;
|
||||
}
|
||||
if (before != after)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, " - element sum changed\n");
|
||||
failures++;
|
||||
ok = 0;
|
||||
}
|
||||
if (ok)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void
|
||||
qsort_scenarios(void)
|
||||
{
|
||||
enum
|
||||
{
|
||||
N = 2048
|
||||
};
|
||||
int arr[N];
|
||||
unsigned i;
|
||||
unsigned rng = 0x2545F491U;
|
||||
int one = 7;
|
||||
|
||||
compar_count = 0;
|
||||
qsort(arr, 0, sizeof(int), int_cmp);
|
||||
qsort(&one, 1, sizeof(int), int_cmp);
|
||||
check(compar_count == 0, "qsort(0) and qsort(1) never call the comparator");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = (int)rng_next(&rng);
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 pseudo-random ints sort");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = (int)(i % 7);
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 duplicate-heavy ints sort");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = (int)(N - 1 - i);
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 reverse-sorted ints sort");
|
||||
|
||||
for (i = 0; i < N; i++)
|
||||
{
|
||||
arr[i] = 5;
|
||||
}
|
||||
sort_and_verify(arr, N, "2048 all-equal ints sort");
|
||||
|
||||
check(self_compare_seen == 0, "comparator never saw the same element twice");
|
||||
}
|
||||
|
||||
static void
|
||||
bsearch_tests(void)
|
||||
{
|
||||
int arr[16];
|
||||
unsigned i;
|
||||
int key;
|
||||
int *p;
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
{
|
||||
arr[i] = (int)(i * 2);
|
||||
}
|
||||
key = 14;
|
||||
p = bsearch(&key, arr, 16, sizeof(int), int_cmp);
|
||||
check(p != NULL && *p == key, "bsearch finds a present key");
|
||||
key = 13;
|
||||
p = bsearch(&key, arr, 16, sizeof(int), int_cmp);
|
||||
check(p == NULL, "bsearch returns NULL for an absent key");
|
||||
check(self_compare_seen == 0, "bsearch never self-compared");
|
||||
}
|
||||
|
||||
static void
|
||||
rand_tests(void)
|
||||
{
|
||||
int a[8];
|
||||
int b[8];
|
||||
unsigned i;
|
||||
int same = 1;
|
||||
int in_range = 1;
|
||||
|
||||
srand(1);
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
a[i] = rand();
|
||||
}
|
||||
srand(1);
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
b[i] = rand();
|
||||
}
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
same = 0;
|
||||
}
|
||||
}
|
||||
check(same, "srand(1) twice gives identical rand sequences");
|
||||
srand(12345);
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
int v = rand();
|
||||
|
||||
if (v < 0 || v > RAND_MAX)
|
||||
{
|
||||
in_range = 0;
|
||||
}
|
||||
}
|
||||
check(in_range, "rand values stay within [0, RAND_MAX]");
|
||||
}
|
||||
|
||||
static void
|
||||
div_tests(void)
|
||||
{
|
||||
div_t d = div(7, 3);
|
||||
ldiv_t ld = ldiv(17L, 5L);
|
||||
ldiv_t ldn = ldiv(-17L, 5L);
|
||||
lldiv_t lld = lldiv(1000000000000000LL, 3LL);
|
||||
|
||||
check(d.quot == 2 && d.rem == 1, "div(7,3) == {2,1}");
|
||||
d = div(-7, 3);
|
||||
check(d.quot == -2 && d.rem == -1, "div(-7,3) == {-2,-1}");
|
||||
check(ld.quot == 3L && ld.rem == 2L, "ldiv(17,5) == {3,2}");
|
||||
check(ldn.quot == -3L && ldn.rem == -2L, "ldiv(-17,5) == {-3,-2}");
|
||||
check(lld.quot == 333333333333333LL && lld.rem == 1LL, "lldiv(10^15,3) == {333333333333333,1}");
|
||||
}
|
||||
|
||||
/*
|
||||
* noipa proxies for the abs family: GCC knows abs/labs/llabs as const
|
||||
* builtins whose result lies in [0, type_MAX], so even with a volatile
|
||||
* argument its value-range analysis proves `abs(vi) == INT_MIN` false
|
||||
* and folds the check before the call. noipa blocks interprocedural
|
||||
* propagation through the proxy (the repo's established pattern, see
|
||||
* the SIZE_MAX-10 handling above); the volatile locals then force the
|
||||
* wrap values to exist at runtime.
|
||||
*/
|
||||
static __attribute__((noipa)) int
|
||||
abs_proxy(int n)
|
||||
{
|
||||
return abs(n);
|
||||
}
|
||||
|
||||
static __attribute__((noipa)) long
|
||||
labs_proxy(long n)
|
||||
{
|
||||
return labs(n);
|
||||
}
|
||||
|
||||
static __attribute__((noipa)) long long
|
||||
llabs_proxy(long long n)
|
||||
{
|
||||
return llabs(n);
|
||||
}
|
||||
|
||||
static void
|
||||
abs_tests(void)
|
||||
{
|
||||
volatile int vi = INT_MIN;
|
||||
volatile long vl = LONG_MIN;
|
||||
volatile long long vll = LLONG_MIN;
|
||||
|
||||
check(abs(-7) == 7 && abs(7) == 7, "abs basics");
|
||||
check(labs(-7L) == 7L, "labs basics");
|
||||
check(llabs(-7LL) == 7LL, "llabs basics");
|
||||
check(abs_proxy(vi) == INT_MIN, "abs(INT_MIN) wraps to INT_MIN");
|
||||
check(labs_proxy(vl) == LONG_MIN, "labs(LONG_MIN) wraps to LONG_MIN");
|
||||
check(llabs_proxy(vll) == LLONG_MIN, "llabs(LLONG_MIN) wraps to LLONG_MIN");
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
static void
|
||||
rand_r_tests(void)
|
||||
{
|
||||
unsigned s1 = 123;
|
||||
unsigned s2 = 123;
|
||||
unsigned s3 = 99;
|
||||
int r1 = rand_r(&s1);
|
||||
int r2 = rand_r(&s2);
|
||||
int before;
|
||||
int after;
|
||||
|
||||
check(r1 == r2 && s1 == s2, "rand_r: equal seeds give equal results and state");
|
||||
srand(7);
|
||||
before = rand();
|
||||
rand_r(&s3);
|
||||
rand();
|
||||
srand(7);
|
||||
after = rand();
|
||||
check(before == after, "rand_r does not disturb rand's global state");
|
||||
s1 = 5;
|
||||
s2 = 5;
|
||||
rand();
|
||||
rand();
|
||||
check(rand_r(&s1) == rand_r(&s2), "rand_r independent of interleaved rand() calls");
|
||||
}
|
||||
|
||||
static void
|
||||
drand48_tests(void)
|
||||
{
|
||||
double d1;
|
||||
double d2;
|
||||
unsigned i;
|
||||
int ok = 1;
|
||||
unsigned short xs[3] = {0x330E, 0xABCD, 0x1234};
|
||||
double e1 = erand48(xs);
|
||||
|
||||
srand48(42);
|
||||
d1 = drand48();
|
||||
srand48(42);
|
||||
d2 = drand48();
|
||||
check(d1 == d2, "srand48 reseed gives deterministic drand48");
|
||||
srand48(1);
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
double v = drand48();
|
||||
|
||||
if (!(v >= 0.0 && v < 1.0))
|
||||
{
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
check(ok, "drand48: 1000 samples in [0,1)");
|
||||
srand48(2);
|
||||
ok = 1;
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
long v = lrand48();
|
||||
|
||||
if (v < 0 || v >= 2147483648L)
|
||||
{
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
check(ok, "lrand48: 1000 samples in [0, 2^31)");
|
||||
check(!(xs[0] == 0x330E && xs[1] == 0xABCD && xs[2] == 0x1234),
|
||||
"erand48 steps the caller's xsubi in place");
|
||||
srand48(0x1234ABCDL);
|
||||
check(e1 == drand48(), "erand48(default xsubi) matches srand48(0x1234ABCD) drand48");
|
||||
}
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/* -f mode: three 100k-element stress sorts from static BSS (no malloc). */
|
||||
#define BIG_N 100000
|
||||
|
||||
static int big_array[BIG_N];
|
||||
|
||||
static int
|
||||
stress_case(const char *label)
|
||||
{
|
||||
unsigned i;
|
||||
int asc = 1;
|
||||
|
||||
compar_count = 0;
|
||||
qsort(big_array, BIG_N, sizeof(int), int_cmp);
|
||||
for (i = 1; i < BIG_N; i++)
|
||||
{
|
||||
if (big_array[i - 1] > big_array[i])
|
||||
{
|
||||
asc = 0;
|
||||
}
|
||||
}
|
||||
say(1, label);
|
||||
say(1, " comparisons: ");
|
||||
say_dec(1, (unsigned long)compar_count);
|
||||
say(1, "\n");
|
||||
if (!asc)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, label);
|
||||
say(2, " - not ascending\n");
|
||||
return 1;
|
||||
}
|
||||
if (compar_count >= 5200000L)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, label);
|
||||
say(2, " - comparisons >= 5200000 (not O(n log n))\n");
|
||||
return 1;
|
||||
}
|
||||
if (self_compare_seen)
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, label);
|
||||
say(2, " - self-compare observed\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "PASS: ");
|
||||
say(1, label);
|
||||
say(1, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
stress_mode(void)
|
||||
{
|
||||
unsigned i;
|
||||
int rc = 0;
|
||||
|
||||
self_compare_seen = 0;
|
||||
for (i = 0; i < BIG_N; i++)
|
||||
{
|
||||
big_array[i] = (int)(BIG_N - 1 - i);
|
||||
}
|
||||
rc |= stress_case("100k reverse-sorted");
|
||||
|
||||
for (i = 0; i < BIG_N; i++)
|
||||
{
|
||||
big_array[i] = (int)i;
|
||||
}
|
||||
rc |= stress_case("100k already-sorted");
|
||||
|
||||
for (i = 0; i < BIG_N; i++)
|
||||
{
|
||||
big_array[i] = 42;
|
||||
}
|
||||
rc |= stress_case("100k all-equal");
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
return stress_mode();
|
||||
}
|
||||
|
||||
qsort_scenarios();
|
||||
bsearch_tests();
|
||||
rand_tests();
|
||||
div_tests();
|
||||
abs_tests();
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
rand_r_tests();
|
||||
drand48_tests();
|
||||
#endif
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all rand/div/qsort/bsearch tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
/*
|
||||
* vlibc — numeric string conversions test (todo 11).
|
||||
*
|
||||
* Exercises atoi/atol/atoll/atof, strtol/strtoul/strtoll/strtoull, and
|
||||
* strtof/strtod/strtold against glibc-verified golden expectations (the
|
||||
* corpus was diffed value+endptr+errno against glibc during development;
|
||||
* the literals below are that diff's results):
|
||||
*
|
||||
* 1. base resolution: 0 (decimal/octal/0x-hex auto-detect), 2, 8, 10,
|
||||
* 16, including the "0" / "0x" prefix edge cases ("0" base 0 is an
|
||||
* octal subject of value 0 with the 0 consumed; "0x" base 0 with no
|
||||
* hex digit has no subject at all; base 16 keeps "0" and stops at x);
|
||||
* 2. leading C-locale whitespace and sign handling;
|
||||
* 3. endptr correctness: past the last digit, at the first invalid
|
||||
* character, or at nptr when no subject sequence exists;
|
||||
* 4. exact boundaries without range errors: LONG_MAX / LONG_MIN /
|
||||
* ULONG_MAX / ULLONG_MAX / DBL_MAX;
|
||||
* 5. strtoul negative wrap (value-only, modulo 2^64, C23 7.24.1.4p8);
|
||||
* 6. strtod: "inf"/"infinity"/"nan"/"nan(chars)" (case-insensitive),
|
||||
* hex floats ("0x1.8p1"), "0.1" exact bits, "3.14159";
|
||||
* 7. ato* wrappers.
|
||||
*
|
||||
* The `-f` failure mode runs the errno-writing scenarios (overflow clamps,
|
||||
* EINVAL bases, HUGE_VAL, underflow-to-zero, hex subnormal). Because this
|
||||
* standalone test is host-linked, vlibc's errno macro addresses glibc's
|
||||
* TCB slot (%fs:0+8) — writing it corrupts the host. errno behavior is
|
||||
* therefore verified only through the C23-mandated return-value clamps
|
||||
* (LONG_MAX/LONG_MIN/HUGE_VAL presence proves ERANGE happened; EINVAL is
|
||||
* proven by the zero return with endptr == nptr), never by reading errno,
|
||||
* and the -f mode exits via a raw SYS_exit_group to skip host cleanup.
|
||||
*
|
||||
* No host headers: under -Iinclude the vlibc headers shadow GCC's
|
||||
* internal ones, so all output goes through raw SYS_write. Not part of the
|
||||
* library proper; compiled manually for this todo (the make check wiring
|
||||
* is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <float.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "../include/stdlib.h"
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
static void
|
||||
say(int fd, const char *s)
|
||||
{
|
||||
long n = 0;
|
||||
|
||||
while (s[n] != '\0')
|
||||
{
|
||||
n++;
|
||||
}
|
||||
__syscall3(SYS_write, fd, (long)s, n);
|
||||
}
|
||||
|
||||
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';
|
||||
do
|
||||
{
|
||||
buf[--i] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||
}
|
||||
|
||||
static void
|
||||
check(int cond, const char *what)
|
||||
{
|
||||
if (cond)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, "\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
check_ptr(const char *got, const char *want,
|
||||
const char *what) // NOLINT(bugprone-easily-swappable-parameters)
|
||||
{
|
||||
if (got == want)
|
||||
{
|
||||
say(1, "PASS: ");
|
||||
say(1, what);
|
||||
say(1, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(2, "FAIL: ");
|
||||
say(2, what);
|
||||
say(2, " (endptr off by ");
|
||||
say_dec(2, (unsigned long)(got - want));
|
||||
say(2, ")\n");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 1. Base resolution and prefix rules. */
|
||||
static void
|
||||
base_resolution(void)
|
||||
{
|
||||
const char *s;
|
||||
char *e;
|
||||
long v;
|
||||
|
||||
s = "0x1f";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 31 && e == s + 4, "strtol(\"0x1f\",0)==31, endptr past digits");
|
||||
s = "0x1f";
|
||||
v = strtol(s, &e, 16);
|
||||
check(v == 31 && e == s + 4, "strtol(\"0x1f\",16)==31");
|
||||
s = "0x1F";
|
||||
check(strtol(s, &e, 0) == 31 && e == s + 4, "strtol(\"0x1F\",0)==31 (upper hex)");
|
||||
s = "101010";
|
||||
check(strtol(s, &e, 2) == 42 && e == s + 6, "strtol(\"101010\",2)==42");
|
||||
s = "777";
|
||||
check(strtol(s, &e, 8) == 511 && e == s + 3, "strtol(\"777\",8)==511");
|
||||
s = "42";
|
||||
check(strtol(s, &e, 10) == 42 && e == s + 2, "strtol(\"42\",10)==42");
|
||||
s = "ff";
|
||||
check(strtol(s, &e, 16) == 255 && e == s + 2, "strtol(\"ff\",16)==255");
|
||||
s = "ff";
|
||||
v = strtol(s, &e, 10);
|
||||
check(v == 0 && e == s, "strtol(\"ff\",10)==0, endptr at start (no digits)");
|
||||
s = "0";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s + 1, "strtol(\"0\",0)==0, octal-prefix 0 consumed");
|
||||
s = "08";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s + 1, "strtol(\"08\",0)==0, subject is the octal 0");
|
||||
s = "0x";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s, "strtol(\"0x\",0)==0, no subject sequence at all");
|
||||
s = "0x";
|
||||
v = strtol(s, &e, 16);
|
||||
check(v == 0 && e == s + 1, "strtol(\"0x\",16)==0, the 0 is the subject");
|
||||
s = "0xg";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s, "strtol(\"0xg\",0): no hex digit, no subject");
|
||||
s = "0b101";
|
||||
v = strtol(s, &e, 0);
|
||||
check(v == 0 && e == s + 1, "strtol(\"0b101\",0)==0, b is not octal");
|
||||
s = "010";
|
||||
check(strtol(s, &e, 0) == 8 && e == s + 3, "strtol(\"010\",0)==8 (octal)");
|
||||
s = "1234";
|
||||
check(strtol(s, &e, 36) == 49360 && e == s + 4, "strtol(\"1234\",36)==49360");
|
||||
}
|
||||
|
||||
/* 2. Whitespace and signs. */
|
||||
static void
|
||||
whitespace_signs(void)
|
||||
{
|
||||
const char *s = " -42";
|
||||
char *e;
|
||||
|
||||
check(strtol(s, &e, 0) == -42 && e == s + 5, "strtol(\" -42\",0)==-42");
|
||||
s = "\t\n\v\f\r+42x";
|
||||
check(strtol(s, &e, 10) == 42 && e == s + 8, "strtol(ws \"+42x\")==42, endptr at x");
|
||||
s = " ";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(whitespace only)==0, endptr at start");
|
||||
s = "+";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"+\")==0, endptr at start");
|
||||
s = "-";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"-\")==0, endptr at start");
|
||||
s = "";
|
||||
check(strtol(s, &e, 10) == 0 && e == s, "strtol(\"\")==0, endptr at start");
|
||||
s = "-0x10";
|
||||
check(strtol(s, &e, 0) == -16 && e == s + 5, "strtol(\"-0x10\",0)==-16");
|
||||
s = " 0x10";
|
||||
check(strtol(s, &e, 0) == 16 && e == s + 5, "strtol(\" 0x10\",0)==16");
|
||||
}
|
||||
|
||||
/* 3. Endptr lands at the first invalid character. */
|
||||
static void
|
||||
endptr_positions(void)
|
||||
{
|
||||
const char *s = "12abc";
|
||||
char *e;
|
||||
|
||||
check(strtol(s, &e, 10) == 12, "strtol(\"12abc\") value");
|
||||
check_ptr(e, s + 2, "strtol(\"12abc\") endptr at 'a'");
|
||||
s = "0x1fg";
|
||||
check(strtol(s, &e, 0) == 31, "strtol(\"0x1fg\") value");
|
||||
check_ptr(e, s + 4, "strtol(\"0x1fg\") endptr at 'g'");
|
||||
s = "3.5";
|
||||
check(strtol(s, &e, 10) == 3, "strtol(\"3.5\") value");
|
||||
check_ptr(e, s + 1, "strtol(\"3.5\") endptr at '.'");
|
||||
}
|
||||
|
||||
/* 4. Exact representable boundaries set no range error (proven by the
|
||||
* exact return values; errno itself is never read here). */
|
||||
static void
|
||||
exact_boundaries(void)
|
||||
{
|
||||
check(strtol("9223372036854775807", NULL, 10) == LONG_MAX, "strtol max == LONG_MAX");
|
||||
check(strtol("-9223372036854775808", NULL, 10) == LONG_MIN, "strtol min == LONG_MIN");
|
||||
check(strtoll("9223372036854775807", NULL, 10) == LLONG_MAX, "strtoll max == LLONG_MAX");
|
||||
check(strtoll("-9223372036854775808", NULL, 10) == LLONG_MIN, "strtoll min == LLONG_MIN");
|
||||
check(strtoul("18446744073709551615", NULL, 10) == ULONG_MAX, "strtoul max == ULONG_MAX");
|
||||
check(strtoull("18446744073709551615", NULL, 10) == ULLONG_MAX, "strtoull max == ULLONG_MAX");
|
||||
check(strtol("9223372036854775807z", NULL, 10) == LONG_MAX, "strtol max with tail");
|
||||
check(strtol("0x7fffffffffffffff", NULL, 0) == LONG_MAX, "strtol hex LONG_MAX");
|
||||
check(strtol("-0x8000000000000000", NULL, 0) == LONG_MIN, "strtol hex LONG_MIN");
|
||||
check(strtoul("0xffffffffffffffff", NULL, 0) == ULONG_MAX, "strtoul hex ULONG_MAX");
|
||||
}
|
||||
|
||||
/* 5. strtoul negative wrap: value-only negation modulo 2^64 (C23
|
||||
* 7.24.1.4p8) — never a range error, so this runs in default mode. */
|
||||
static void
|
||||
unsigned_negative_wrap(void)
|
||||
{
|
||||
check(strtoul("-1", NULL, 10) == ULONG_MAX, "strtoul(\"-1\") == ULONG_MAX (wrap)");
|
||||
check(strtoull("-1", NULL, 10) == ULLONG_MAX, "strtoull(\"-1\") == ULLONG_MAX (wrap)");
|
||||
check(strtoul("-4294967296", NULL, 10) == ULONG_MAX - 4294967295UL,
|
||||
"strtoul(-2^32) == 2^64-2^32");
|
||||
check(strtoul("-9223372036854775808", NULL, 10) == 9223372036854775808UL,
|
||||
"strtoul(-2^63) == 2^63 (no range error)");
|
||||
}
|
||||
|
||||
/* 6a. strtod special forms and hex floats. */
|
||||
static void
|
||||
strtod_special(void)
|
||||
{
|
||||
const char *s = "inf";
|
||||
char *e;
|
||||
double r;
|
||||
|
||||
r = strtod(s, &e);
|
||||
check(r == __builtin_inf() && e == s + 3, "strtod(\"inf\") == +infinity");
|
||||
s = "Infinity";
|
||||
r = strtod(s, &e);
|
||||
check(r == __builtin_inf() && e == s + 8, "strtod(\"Infinity\") == +infinity");
|
||||
s = "-INF";
|
||||
r = strtod(s, &e);
|
||||
check(r == -__builtin_inf() && e == s + 4, "strtod(\"-INF\") == -infinity");
|
||||
s = "infinityx";
|
||||
r = strtod(s, &e);
|
||||
check(r == __builtin_inf(), "strtod(\"infinityx\") value");
|
||||
check_ptr(e, s + 8, "strtod(\"infinityx\") endptr at 'x'");
|
||||
s = "nan";
|
||||
r = strtod(s, &e);
|
||||
check(r != r && e == s + 3, "strtod(\"nan\") is NaN, endptr past nan");
|
||||
s = "NAN(xyz)";
|
||||
r = strtod(s, &e);
|
||||
check(r != r, "strtod(\"NAN(xyz)\") is NaN");
|
||||
check_ptr(e, s + 8, "strtod(\"NAN(xyz)\") endptr past ')'");
|
||||
s = "nan(x";
|
||||
r = strtod(s, &e);
|
||||
check(r != r, "strtod(\"nan(x\") is NaN");
|
||||
check_ptr(e, s + 3, "strtod(\"nan(x\") endptr right after nan");
|
||||
s = "in";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0 && e == s, "strtod(\"in\")==0, endptr at start");
|
||||
s = "0x1.8p1";
|
||||
r = strtod(s, &e);
|
||||
check(r == 3.0 && e == s + 7, "strtod(\"0x1.8p1\")==3.0");
|
||||
s = "0x1p-2";
|
||||
check(strtod(s, &e) == 0.25 && e == s + 6, "strtod(\"0x1p-2\")==0.25");
|
||||
s = "0x1.8p1f";
|
||||
r = strtod(s, &e);
|
||||
check(r == 3.0, "strtod(\"0x1.8p1f\") value");
|
||||
check_ptr(e, s + 7, "strtod(\"0x1.8p1f\") endptr at 'f'");
|
||||
s = "0x8.8";
|
||||
check(strtod(s, &e) == 8.5 && e == s + 5, "strtod(\"0x8.8\")==8.5");
|
||||
s = "0x.8p1";
|
||||
check(strtod(s, &e) == 1.0 && e == s + 6, "strtod(\"0x.8p1\")==1.0");
|
||||
s = "0x1.fffffffffffffp+1023";
|
||||
check(strtod(s, &e) == DBL_MAX && e == s + 23, "strtod(hex max normal) == DBL_MAX");
|
||||
s = "0x";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0, "strtod(\"0x\") value");
|
||||
check_ptr(e, s + 1, "strtod(\"0x\") endptr after the 0 (glibc)");
|
||||
}
|
||||
|
||||
/* 6b. strtod decimal conversions. */
|
||||
static void
|
||||
strtod_decimal(void)
|
||||
{
|
||||
const char *s = "0.1";
|
||||
char *e;
|
||||
double r;
|
||||
|
||||
r = strtod(s, &e);
|
||||
check(r == 0x1.999999999999ap-4 && e == s + 3, "strtod(\"0.1\") has exact 0.1 bits");
|
||||
s = "3.14159";
|
||||
check(strtod(s, &e) == 3.14159 && e == s + 7, "strtod(\"3.14159\") == 3.14159");
|
||||
s = "1.5e2";
|
||||
check(strtod(s, &e) == 150.0 && e == s + 5, "strtod(\"1.5e2\")==150");
|
||||
s = " -0.5";
|
||||
check(strtod(s, &e) == -0.5 && e == s + 6, "strtod(\" -0.5\")==-0.5");
|
||||
s = ".5";
|
||||
check(strtod(s, &e) == 0.5 && e == s + 2, "strtod(\".5\")==0.5");
|
||||
s = "1.";
|
||||
check(strtod(s, &e) == 1.0 && e == s + 2, "strtod(\"1.\")==1.0");
|
||||
s = ".";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0 && e == s, "strtod(\".\")==0, endptr at start");
|
||||
s = "1e";
|
||||
r = strtod(s, &e);
|
||||
check(r == 1.0 && e == s + 1, "strtod(\"1e\")==1.0, bare e not part of subject");
|
||||
s = "0";
|
||||
check(strtod(s, &e) == 0.0 && e == s + 1, "strtod(\"0\")==0.0");
|
||||
s = "-0";
|
||||
r = strtod(s, &e);
|
||||
check(r == 0.0 && 1.0 / r == -__builtin_inf(), "strtod(\"-0\") == -0.0");
|
||||
s = "123456789012345678901234567890";
|
||||
r = strtod(s, &e);
|
||||
check(r > 0.0 && e == s + 30, "strtod(30-digit integer) positive, endptr at end");
|
||||
s = "9.999999999999999e22";
|
||||
check(strtod(s, &e) == 9.999999999999999e22, "strtod(19-digit) exact");
|
||||
}
|
||||
|
||||
/* 6c. strtof and strtold basics. */
|
||||
static void
|
||||
strtof_strtold(void)
|
||||
{
|
||||
const char *s = "0.1";
|
||||
char *e;
|
||||
|
||||
check(strtof(s, &e) == 0.1f && e == s + 3, "strtof(\"0.1\") == 0.1f");
|
||||
s = "inf";
|
||||
check(strtof(s, &e) == __builtin_inff() && e == s + 3, "strtof(\"inf\") == +inf");
|
||||
s = "nan";
|
||||
{
|
||||
float nf = strtof(s, &e);
|
||||
|
||||
check(nf != nf && e == s + 3, "strtof(\"nan\") is NaN");
|
||||
}
|
||||
s = "0x1.8p1";
|
||||
check(strtof(s, &e) == 3.0f && e == s + 7, "strtof(\"0x1.8p1\")==3.0f");
|
||||
s = "0.1";
|
||||
check(strtold(s, &e) == 0.1L && e == s + 3, "strtold(\"0.1\") == 0.1L");
|
||||
s = "3.14159";
|
||||
check(strtold(s, &e) == 3.14159L && e == s + 7, "strtold(\"3.14159\") == 3.14159L");
|
||||
s = "1e100";
|
||||
check(strtold(s, &e) == 1e100L && e == s + 5, "strtold(\"1e100\") == 1e100L (exact)");
|
||||
s = "0x1.8p1";
|
||||
check(strtold(s, &e) == 3.0L && e == s + 7, "strtold(\"0x1.8p1\")==3.0L");
|
||||
s = "nan";
|
||||
{
|
||||
long double nl = strtold(s, &e);
|
||||
|
||||
check(nl != nl && e == s + 3, "strtold(\"nan\") is NaN");
|
||||
}
|
||||
s = "1e999";
|
||||
{
|
||||
long double big = strtold(s, &e);
|
||||
long double rel = big / 1e999L;
|
||||
|
||||
check(e == s + 5 && rel > 0.999999999999999L && rel < 1.000000000000001L,
|
||||
"strtold(\"1e999\") within 1e-15 of 1e999 (no range error)");
|
||||
}
|
||||
}
|
||||
|
||||
/* 7. ato* wrappers. */
|
||||
static void
|
||||
ato_wrappers(void)
|
||||
{
|
||||
check(atoi("42") == 42, "atoi(\"42\")==42");
|
||||
check(atoi(" -17") == -17, "atoi(\" -17\")==-17");
|
||||
check(atol("2147483648") == 2147483648L, "atol(\"2147483648\")==2147483648");
|
||||
check(atoll("9223372036854775807") == LLONG_MAX, "atoll max == LLONG_MAX");
|
||||
check(atof("1.5") == 1.5, "atof(\"1.5\")==1.5");
|
||||
check(atof(" +2.25e2") == 225.0, "atof(\" +2.25e2\")==225.0");
|
||||
check(atof("0x1.8p1") == 3.0, "atof(\"0x1.8p1\")==3.0");
|
||||
check(atof("x") == 0.0, "atof(\"x\")==0.0");
|
||||
}
|
||||
|
||||
/*
|
||||
* Failure scenarios (-f): every path that writes errno inside the library.
|
||||
* Because the test is host-linked, those writes land in glibc's TCB slot,
|
||||
* so errno is never read back — the C23 return-value clamps prove the
|
||||
* ERANGE/EINVAL behavior — and the process exits through a raw
|
||||
* SYS_exit_group without running host cleanup.
|
||||
*/
|
||||
static int
|
||||
failure_scenarios(void)
|
||||
{
|
||||
const char *s;
|
||||
char *e;
|
||||
int bad = 0;
|
||||
|
||||
s = "9223372036854775808";
|
||||
if (strtol(s, &e, 10) != LONG_MAX || e != s + 19)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol overflow clamp\n");
|
||||
}
|
||||
s = "-9223372036854775809";
|
||||
if (strtol(s, &e, 10) != LONG_MIN || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol negative overflow clamp\n");
|
||||
}
|
||||
s = "9223372036854775808";
|
||||
if (strtoll(s, &e, 10) != LLONG_MAX || e != s + 19)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoll overflow clamp\n");
|
||||
}
|
||||
s = "-9223372036854775809";
|
||||
if (strtoll(s, &e, 10) != LLONG_MIN || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoll negative overflow clamp\n");
|
||||
}
|
||||
s = "18446744073709551616";
|
||||
if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoul overflow clamp\n");
|
||||
}
|
||||
s = "18446744073709551617";
|
||||
if (strtoull(s, &e, 10) != ULLONG_MAX || e != s + 20)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoull overflow clamp\n");
|
||||
}
|
||||
s = "-18446744073709551616";
|
||||
if (strtoul(s, &e, 10) != ULONG_MAX || e != s + 21)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoul magnitude overflow (2^64) clamp\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtol(s, &e, 1) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol base 1 (EINVAL): value/endptr\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtol(s, &e, 37) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtol base 37 (EINVAL): value/endptr\n");
|
||||
}
|
||||
s = "123";
|
||||
if (strtoul(s, &e, -1) != 0 || e != s)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtoul base -1 (EINVAL): value/endptr\n");
|
||||
}
|
||||
s = "1e999";
|
||||
if (strtod(s, &e) != __builtin_inf() || e != s + 5)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(\"1e999\") != +inf (HUGE_VAL)\n");
|
||||
}
|
||||
s = "-1e999";
|
||||
if (strtod(s, &e) != -__builtin_inf() || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(\"-1e999\") != -inf\n");
|
||||
}
|
||||
s = "1e-999";
|
||||
if (strtod(s, &e) != 0.0 || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(\"1e-999\") != 0.0 (underflow)\n");
|
||||
}
|
||||
s = "0x1.fffffffffffffp1024";
|
||||
if (strtod(s, &e) != __builtin_inf() || e != s + 22)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(hex overflow) != +inf\n");
|
||||
}
|
||||
s = "0x0.0000000000001p-1022";
|
||||
if (strtod(s, &e) != DBL_TRUE_MIN || e != s + 23)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtod(hex subnormal) != DBL_TRUE_MIN\n");
|
||||
}
|
||||
s = "1e999";
|
||||
if (strtof(s, &e) != __builtin_inff() || e != s + 5)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtof(\"1e999\") != +inf\n");
|
||||
}
|
||||
s = "1e-999";
|
||||
if (strtof(s, &e) != 0.0f || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtof(\"1e-999\") != 0.0f\n");
|
||||
}
|
||||
s = "1e5000";
|
||||
if (strtold(s, &e) != __builtin_infl() || e != s + 6)
|
||||
{
|
||||
bad++;
|
||||
say(2, "FAIL: strtold(\"1e5000\") != +inf\n");
|
||||
}
|
||||
|
||||
if (bad == 0)
|
||||
{
|
||||
say(1, "PASS: all overflow/EINVAL/underflow failure scenarios\n");
|
||||
}
|
||||
return bad > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||
{
|
||||
rc = failure_scenarios();
|
||||
__syscall1(SYS_exit_group, rc);
|
||||
return rc; /* not reached */
|
||||
}
|
||||
|
||||
base_resolution();
|
||||
whitespace_signs();
|
||||
endptr_positions();
|
||||
exact_boundaries();
|
||||
unsigned_negative_wrap();
|
||||
strtod_special();
|
||||
strtod_decimal();
|
||||
strtof_strtold();
|
||||
ato_wrappers();
|
||||
|
||||
if (failures > 0)
|
||||
{
|
||||
say(2, "FAILED (");
|
||||
say_dec(2, (unsigned long)failures);
|
||||
say(2, " check(s))\n");
|
||||
return 1;
|
||||
}
|
||||
say(1, "all strtol/strtod tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user