feat(stdlib): environment and multibyte helpers
This commit is contained in:
@@ -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