feat(stdlib): environment and multibyte helpers
This commit is contained in:
@@ -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) */
|
||||
Reference in New Issue
Block a user