feat(stdlib): environment and multibyte helpers
This commit is contained in:
@@ -367,6 +367,170 @@ lcong48(unsigned short param[7]);
|
|||||||
|
|
||||||
#endif /* VLIBC_LEVEL_GE(2) */
|
#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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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,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,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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user