aboutsummaryrefslogtreecommitdiff
path: root/lib/mlibc/tests
diff options
context:
space:
mode:
authorIan Moffett <ian@osmora.org>2024-03-07 17:28:00 -0500
committerIan Moffett <ian@osmora.org>2024-03-07 17:28:32 -0500
commitbd5969fc876a10b18613302db7087ef3c40f18e1 (patch)
tree7c2b8619afe902abf99570df2873fbdf40a4d1a1 /lib/mlibc/tests
parenta95b38b1b92b172e6cc4e8e56a88a30cc65907b0 (diff)
lib: Add mlibc
Signed-off-by: Ian Moffett <ian@osmora.org>
Diffstat (limited to 'lib/mlibc/tests')
-rw-r--r--lib/mlibc/tests/ansi/abs.c16
-rw-r--r--lib/mlibc/tests/ansi/alloc.c37
-rw-r--r--lib/mlibc/tests/ansi/calloc.c20
-rw-r--r--lib/mlibc/tests/ansi/creal-cimag.c37
-rw-r--r--lib/mlibc/tests/ansi/fenv.c91
-rw-r--r--lib/mlibc/tests/ansi/fopen.c59
-rw-r--r--lib/mlibc/tests/ansi/freopen.c49
-rw-r--r--lib/mlibc/tests/ansi/locale.c17
-rw-r--r--lib/mlibc/tests/ansi/longjmp.c29
-rw-r--r--lib/mlibc/tests/ansi/mbrtoc32.c53
-rw-r--r--lib/mlibc/tests/ansi/memmem.c22
-rw-r--r--lib/mlibc/tests/ansi/qsort.c31
-rw-r--r--lib/mlibc/tests/ansi/snprintf.c23
-rw-r--r--lib/mlibc/tests/ansi/sprintf.c191
-rw-r--r--lib/mlibc/tests/ansi/sscanf.c143
-rw-r--r--lib/mlibc/tests/ansi/strchr.c20
-rw-r--r--lib/mlibc/tests/ansi/strftime.c48
-rw-r--r--lib/mlibc/tests/ansi/strrchr.c11
-rw-r--r--lib/mlibc/tests/ansi/strtof.c83
-rw-r--r--lib/mlibc/tests/ansi/strtol.c140
-rw-r--r--lib/mlibc/tests/ansi/strverscmp.c16
-rw-r--r--lib/mlibc/tests/ansi/strxfrm.c14
-rw-r--r--lib/mlibc/tests/ansi/timegm.c45
-rw-r--r--lib/mlibc/tests/ansi/ungetc.c74
-rw-r--r--lib/mlibc/tests/ansi/utf8.c63
-rw-r--r--lib/mlibc/tests/ansi/wcsdup.c11
-rw-r--r--lib/mlibc/tests/ansi/wcsncasecmp.c11
-rw-r--r--lib/mlibc/tests/ansi/wcsrtombs.c12
-rw-r--r--lib/mlibc/tests/ansi/wmemcmp.c19
-rw-r--r--lib/mlibc/tests/bsd/ns_get_put.c21
-rw-r--r--lib/mlibc/tests/bsd/reallocarray.c7
-rw-r--r--lib/mlibc/tests/bsd/sbrk.c9
-rw-r--r--lib/mlibc/tests/bsd/strl.c56
-rw-r--r--lib/mlibc/tests/glibc/error.c6
-rw-r--r--lib/mlibc/tests/glibc/error.py11
-rw-r--r--lib/mlibc/tests/glibc/error_at_line.c6
-rw-r--r--lib/mlibc/tests/glibc/error_at_line.py11
-rw-r--r--lib/mlibc/tests/glibc/error_expect_fail.c6
-rw-r--r--lib/mlibc/tests/glibc/error_message_count.c11
-rw-r--r--lib/mlibc/tests/glibc/error_one_per_line.c13
-rw-r--r--lib/mlibc/tests/glibc/error_print_progname.c19
-rw-r--r--lib/mlibc/tests/glibc/ffsl-ffsll.c36
-rw-r--r--lib/mlibc/tests/glibc/getopt.c288
-rw-r--r--lib/mlibc/tests/glibc/gnu-basename.c13
-rw-r--r--lib/mlibc/tests/glibc/linux-syscall.c14
-rw-r--r--lib/mlibc/tests/linux/cpuset.c27
-rw-r--r--lib/mlibc/tests/linux/malloc-usable-size.c13
-rw-r--r--lib/mlibc/tests/linux/pthread_attr.c90
-rw-r--r--lib/mlibc/tests/linux/pthread_setname_np.c33
-rw-r--r--lib/mlibc/tests/linux/xattr.c80
-rw-r--r--lib/mlibc/tests/meson.build261
-rw-r--r--lib/mlibc/tests/posix/abort.c21
-rw-r--r--lib/mlibc/tests/posix/accept4.c123
-rw-r--r--lib/mlibc/tests/posix/access.c30
-rw-r--r--lib/mlibc/tests/posix/alarm.c34
-rw-r--r--lib/mlibc/tests/posix/basename.c17
-rw-r--r--lib/mlibc/tests/posix/dprintf.c39
-rw-r--r--lib/mlibc/tests/posix/fdopen.c37
-rw-r--r--lib/mlibc/tests/posix/ffs.c18
-rw-r--r--lib/mlibc/tests/posix/flockfile.c35
-rw-r--r--lib/mlibc/tests/posix/fmemopen.c150
-rw-r--r--lib/mlibc/tests/posix/fopencookie.c69
-rw-r--r--lib/mlibc/tests/posix/getaddrinfo.c52
-rw-r--r--lib/mlibc/tests/posix/getcwd.c22
-rw-r--r--lib/mlibc/tests/posix/getdelim.c90
-rw-r--r--lib/mlibc/tests/posix/getnameinfo.c22
-rw-r--r--lib/mlibc/tests/posix/getservbyname.c27
-rw-r--r--lib/mlibc/tests/posix/getservbyport.c28
-rw-r--r--lib/mlibc/tests/posix/grp.c110
-rw-r--r--lib/mlibc/tests/posix/if_indextoname.c11
-rw-r--r--lib/mlibc/tests/posix/index.c20
-rw-r--r--lib/mlibc/tests/posix/inet_ntop.c30
-rw-r--r--lib/mlibc/tests/posix/inet_pton.c16
-rw-r--r--lib/mlibc/tests/posix/memrchr.c25
-rw-r--r--lib/mlibc/tests/posix/mkstemp.c70
-rw-r--r--lib/mlibc/tests/posix/open_memstream.c65
-rw-r--r--lib/mlibc/tests/posix/pause.c49
-rw-r--r--lib/mlibc/tests/posix/popen.c42
-rw-r--r--lib/mlibc/tests/posix/posix-timer.c47
-rw-r--r--lib/mlibc/tests/posix/posix_memalign.c21
-rw-r--r--lib/mlibc/tests/posix/posix_spawn.c38
-rw-r--r--lib/mlibc/tests/posix/pthread_atfork.c54
-rw-r--r--lib/mlibc/tests/posix/pthread_attr.c157
-rw-r--r--lib/mlibc/tests/posix/pthread_barrier.c56
-rw-r--r--lib/mlibc/tests/posix/pthread_cancel.c150
-rw-r--r--lib/mlibc/tests/posix/pthread_cleanup.c42
-rw-r--r--lib/mlibc/tests/posix/pthread_cond.c106
-rw-r--r--lib/mlibc/tests/posix/pthread_create.c22
-rw-r--r--lib/mlibc/tests/posix/pthread_key.c95
-rw-r--r--lib/mlibc/tests/posix/pthread_kill.c46
-rw-r--r--lib/mlibc/tests/posix/pthread_mutex.c101
-rw-r--r--lib/mlibc/tests/posix/pthread_rwlock.c114
-rw-r--r--lib/mlibc/tests/posix/pthread_schedparam.c32
-rw-r--r--lib/mlibc/tests/posix/pthread_thread_local.c53
-rw-r--r--lib/mlibc/tests/posix/pwd.c88
-rw-r--r--lib/mlibc/tests/posix/readv-writev.c51
-rw-r--r--lib/mlibc/tests/posix/realpath.c133
-rw-r--r--lib/mlibc/tests/posix/regex.c30
-rw-r--r--lib/mlibc/tests/posix/rindex.c11
-rw-r--r--lib/mlibc/tests/posix/rlimits.c26
-rw-r--r--lib/mlibc/tests/posix/search.c51
-rw-r--r--lib/mlibc/tests/posix/setpriority.c27
-rw-r--r--lib/mlibc/tests/posix/sigaltstack.c55
-rw-r--r--lib/mlibc/tests/posix/sigsuspend.c53
-rw-r--r--lib/mlibc/tests/posix/sigtimedwait.c91
-rw-r--r--lib/mlibc/tests/posix/strdupa.c14
-rw-r--r--lib/mlibc/tests/posix/string.c29
-rw-r--r--lib/mlibc/tests/posix/system.c18
-rw-r--r--lib/mlibc/tests/posix/time.c191
-rw-r--r--lib/mlibc/tests/posix/timer.c25
-rw-r--r--lib/mlibc/tests/posix/vfork.c28
-rw-r--r--lib/mlibc/tests/posix/waitid.c20
-rw-r--r--lib/mlibc/tests/posix/wcwidth.c67
-rw-r--r--lib/mlibc/tests/posix/wordexp.c140
-rw-r--r--lib/mlibc/tests/rtdl/dl_iterate_phdr/libbar.c3
-rw-r--r--lib/mlibc/tests/rtdl/dl_iterate_phdr/libfoo.c1
-rw-r--r--lib/mlibc/tests/rtdl/dl_iterate_phdr/meson.build7
-rw-r--r--lib/mlibc/tests/rtdl/dl_iterate_phdr/test.c91
-rw-r--r--lib/mlibc/tests/rtdl/dladdr_local/libfoo.c1
-rw-r--r--lib/mlibc/tests/rtdl/dladdr_local/meson.build5
-rw-r--r--lib/mlibc/tests/rtdl/dladdr_local/test.c21
-rw-r--r--lib/mlibc/tests/rtdl/ld_library_path/libfoo.c1
-rw-r--r--lib/mlibc/tests/rtdl/ld_library_path/meson.build19
-rw-r--r--lib/mlibc/tests/rtdl/ld_library_path/test.c16
-rw-r--r--lib/mlibc/tests/rtdl/meson.build56
-rw-r--r--lib/mlibc/tests/rtdl/noload-promote/libfoo.c1
-rw-r--r--lib/mlibc/tests/rtdl/noload-promote/meson.build5
-rw-r--r--lib/mlibc/tests/rtdl/noload-promote/test.c22
-rw-r--r--lib/mlibc/tests/rtdl/preinit/libfoo.c18
-rw-r--r--lib/mlibc/tests/rtdl/preinit/meson.build18
-rw-r--r--lib/mlibc/tests/rtdl/preinit/test.c44
-rw-r--r--lib/mlibc/tests/rtdl/rtld_next/libbar.c16
-rw-r--r--lib/mlibc/tests/rtdl/rtld_next/libfoo.c17
-rw-r--r--lib/mlibc/tests/rtdl/rtld_next/meson.build25
-rw-r--r--lib/mlibc/tests/rtdl/rtld_next/test.c26
-rw-r--r--lib/mlibc/tests/rtdl/scope1/libbar.c14
-rw-r--r--lib/mlibc/tests/rtdl/scope1/libfoo.c9
-rw-r--r--lib/mlibc/tests/rtdl/scope1/meson.build7
-rw-r--r--lib/mlibc/tests/rtdl/scope1/test.c90
-rw-r--r--lib/mlibc/tests/rtdl/scope2/libbar.c5
-rw-r--r--lib/mlibc/tests/rtdl/scope2/libbaz.c3
-rw-r--r--lib/mlibc/tests/rtdl/scope2/libfoo.c3
-rw-r--r--lib/mlibc/tests/rtdl/scope2/meson.build9
-rw-r--r--lib/mlibc/tests/rtdl/scope2/test.c36
-rw-r--r--lib/mlibc/tests/rtdl/scope3/libbar.c8
-rw-r--r--lib/mlibc/tests/rtdl/scope3/libbaz.c7
-rw-r--r--lib/mlibc/tests/rtdl/scope3/libfoo.c5
-rw-r--r--lib/mlibc/tests/rtdl/scope3/meson.build9
-rw-r--r--lib/mlibc/tests/rtdl/scope3/test.c39
-rw-r--r--lib/mlibc/tests/rtdl/scope4/libbar.c3
-rw-r--r--lib/mlibc/tests/rtdl/scope4/libbaz.c1
-rw-r--r--lib/mlibc/tests/rtdl/scope4/libfoo.c3
-rw-r--r--lib/mlibc/tests/rtdl/scope4/meson.build9
-rw-r--r--lib/mlibc/tests/rtdl/scope4/test.c36
-rw-r--r--lib/mlibc/tests/rtdl/scope5/libfoo.c1
-rw-r--r--lib/mlibc/tests/rtdl/scope5/meson.build5
-rw-r--r--lib/mlibc/tests/rtdl/scope5/test.c27
-rw-r--r--lib/mlibc/tests/rtdl/soname/libbar.c1
-rw-r--r--lib/mlibc/tests/rtdl/soname/libfoo.c1
-rw-r--r--lib/mlibc/tests/rtdl/soname/meson.build17
-rw-r--r--lib/mlibc/tests/rtdl/soname/test.c33
-rw-r--r--lib/mlibc/tests/rtdl/tls_align/libbar.c1
-rw-r--r--lib/mlibc/tests/rtdl/tls_align/libfoo.c1
-rw-r--r--lib/mlibc/tests/rtdl/tls_align/meson.build7
-rw-r--r--lib/mlibc/tests/rtdl/tls_align/test.c10
165 files changed, 6763 insertions, 0 deletions
diff --git a/lib/mlibc/tests/ansi/abs.c b/lib/mlibc/tests/ansi/abs.c
new file mode 100644
index 0000000..492040c
--- /dev/null
+++ b/lib/mlibc/tests/ansi/abs.c
@@ -0,0 +1,16 @@
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+
+int main(){
+ assert(abs(-10) == 10);
+ assert(abs(2021) == 2021);
+
+ assert(labs(-256) == 256);
+ assert(labs(10034890) == 10034890);
+
+ assert(llabs(-0x2deadbeef) == 0x2deadbeef);
+ assert(llabs(49238706947) == 49238706947);
+
+ return 0;
+} \ No newline at end of file
diff --git a/lib/mlibc/tests/ansi/alloc.c b/lib/mlibc/tests/ansi/alloc.c
new file mode 100644
index 0000000..c6ce870
--- /dev/null
+++ b/lib/mlibc/tests/ansi/alloc.c
@@ -0,0 +1,37 @@
+#include <stdlib.h>
+#include <assert.h>
+#include <stdint.h>
+#include <errno.h>
+
+int main() {
+ void *p;
+
+ p = aligned_alloc(sizeof(void *), sizeof(void *));
+ assert(p != NULL && (uintptr_t)p % sizeof(void *) == 0);
+ free(p);
+
+ p = aligned_alloc(256, 256);
+ assert(p != NULL && (uintptr_t)p % 256 == 0);
+ free(p);
+
+ // small alignments are okay
+ p = aligned_alloc(1, 8);
+ assert(p != NULL);
+ free(p);
+ p = aligned_alloc(1, 1);
+ assert(p != NULL);
+ free(p);
+
+ // It seems that glibc doesn't report error in these cases.
+#if !(defined(USE_HOST_LIBC) && defined(__GLIBC__))
+ // size % align must be 0
+ p = aligned_alloc(256, 1);
+ assert(errno == EINVAL);
+ assert(p == NULL);
+
+ // align must be a 'valid alignment supported by the implementation'
+ p = aligned_alloc(3, 1);
+ assert(errno == EINVAL);
+ assert(p == NULL);
+#endif
+}
diff --git a/lib/mlibc/tests/ansi/calloc.c b/lib/mlibc/tests/ansi/calloc.c
new file mode 100644
index 0000000..6cfc87d
--- /dev/null
+++ b/lib/mlibc/tests/ansi/calloc.c
@@ -0,0 +1,20 @@
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+int main() {
+ errno = 0;
+ void *ptr = calloc((SIZE_MAX / 2) + 2, 2);
+ assert(!ptr);
+ assert(errno);
+
+ errno = 0;
+ ptr = calloc(sizeof(size_t), 10);
+ assert(ptr);
+ for(size_t i = 0; i < 10; i++) {
+ size_t *p = ptr;
+ assert(!p[i]);
+ }
+}
diff --git a/lib/mlibc/tests/ansi/creal-cimag.c b/lib/mlibc/tests/ansi/creal-cimag.c
new file mode 100644
index 0000000..fc70aa3
--- /dev/null
+++ b/lib/mlibc/tests/ansi/creal-cimag.c
@@ -0,0 +1,37 @@
+#include <assert.h>
+#include <math.h>
+#include <complex.h>
+
+// FIXME: We should create a proper floating point facility
+// in order for other functions to be tested properly
+
+#define APPROXIMATELY_EQUAL(calculated, expected) fabs((calculated) - (expected)) <= 0.0000005
+#define APPROXIMATELY_EQUALF(calculated, expected) fabsf((calculated) - (expected)) <= 0.0000005f
+#define APPROXIMATELY_EQUALL(calculated, expected) fabsl((calculated) - (expected)) <= 0.0000005L
+
+#define IS_COMPLEX_NUMBER(Z) \
+ _Generic((Z), \
+ double complex: 1, \
+ float complex: 1, \
+ long double complex: 1, \
+ default: 0 \
+ )
+
+int main() {
+ assert(IS_COMPLEX_NUMBER(CMPLX(5.2, 4.3)));
+ double complex cz = CMPLX(5.2, 4.3);
+ assert(APPROXIMATELY_EQUAL(creal(cz), 5.2));
+ assert(APPROXIMATELY_EQUAL(cimag(cz), 4.3));
+
+ assert(IS_COMPLEX_NUMBER(CMPLXF(1.2f, 2.5f)));
+ float complex czf = CMPLXF(1.2f, 2.5f);
+ assert(APPROXIMATELY_EQUALF(crealf(czf), 1.2f));
+ assert(APPROXIMATELY_EQUALF(cimagf(czf), 2.5f));
+
+ assert(IS_COMPLEX_NUMBER(CMPLXL(0.1L, 123.54L)));
+ long double complex czl = CMPLXL(0.1L, 123.54L);
+ assert(APPROXIMATELY_EQUALL(creall(czl), 0.1L));
+ assert(APPROXIMATELY_EQUALL(cimagl(czl), 123.54L));
+
+ return 0;
+} \ No newline at end of file
diff --git a/lib/mlibc/tests/ansi/fenv.c b/lib/mlibc/tests/ansi/fenv.c
new file mode 100644
index 0000000..629a5cb
--- /dev/null
+++ b/lib/mlibc/tests/ansi/fenv.c
@@ -0,0 +1,91 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <fenv.h>
+#include <float.h>
+#include <math.h>
+
+#define NO_OPTIMIZE(x) asm volatile("" :: "r,m" (x) : "memory")
+
+static void div_by_zero() {
+ volatile float zero = 0.0f;
+ NO_OPTIMIZE(69.0f / zero);
+}
+
+static bool float_cmp(float a, float b) {
+ return a == b || fabs(a - b) < (fabs(a) + fabs(b)) * FLT_EPSILON;
+}
+
+static void test_rounding(float expectation1, float expectation2) {
+ float x;
+ volatile float f = 1.968750f;
+ volatile float m = 0x1.0p23f;
+
+ NO_OPTIMIZE(x = f + m);
+ assert(float_cmp(expectation1, x));
+ NO_OPTIMIZE(x = x - m);
+ assert(x == expectation2);
+}
+
+void test0() {
+ // test whether the divide-by-zero exception is raised
+ feclearexcept(FE_ALL_EXCEPT);
+ assert(fetestexcept(FE_ALL_EXCEPT) == 0);
+
+ div_by_zero();
+ int raised = fetestexcept(FE_DIVBYZERO);
+ assert((raised & FE_DIVBYZERO));
+}
+
+void test1() {
+ // test various rounding modes
+ feclearexcept(FE_DIVBYZERO);
+ assert(fetestexcept(FE_ALL_EXCEPT) == 0);
+
+ fesetround(FE_UPWARD);
+ assert(fegetround() == FE_UPWARD);
+ test_rounding(8388610.0f, 2.0f);
+
+ fesetround(FE_DOWNWARD);
+ assert(fegetround() == FE_DOWNWARD);
+ test_rounding(8388609.0f, 1.0f);
+
+ fesetround(FE_TONEAREST);
+ assert(fegetround() == FE_TONEAREST);
+ test_rounding(8388610.0f, 2.0f);
+
+ fesetround(FE_TOWARDZERO);
+ assert(fegetround() == FE_TOWARDZERO);
+ test_rounding(8388609.0f, 1.0f);
+}
+
+void test2() {
+ // test feraiseexcept
+ feclearexcept(FE_ALL_EXCEPT);
+ assert(fetestexcept(FE_ALL_EXCEPT) == 0);
+ assert(feraiseexcept(FE_DIVBYZERO | FE_OVERFLOW) == 0);
+ assert(fetestexcept(FE_ALL_EXCEPT) == (FE_DIVBYZERO | FE_OVERFLOW));
+}
+
+void test3() {
+ // test fe{get,set}env
+ feclearexcept(FE_ALL_EXCEPT);
+ assert(fetestexcept(FE_ALL_EXCEPT) == 0);
+ assert(feraiseexcept(FE_OVERFLOW) == 0);
+
+ fenv_t state;
+ assert(fegetenv(&state) == 0);
+ assert(fetestexcept(FE_ALL_EXCEPT) == FE_OVERFLOW);
+
+ div_by_zero();
+ assert(fetestexcept(FE_ALL_EXCEPT) == (FE_DIVBYZERO | FE_OVERFLOW));
+
+ assert(fesetenv(&state) == 0);
+ assert(fetestexcept(FE_ALL_EXCEPT) == FE_OVERFLOW);
+}
+
+int main() {
+ test0();
+ test1();
+ test2();
+ test3();
+}
diff --git a/lib/mlibc/tests/ansi/fopen.c b/lib/mlibc/tests/ansi/fopen.c
new file mode 100644
index 0000000..f4818b2
--- /dev/null
+++ b/lib/mlibc/tests/ansi/fopen.c
@@ -0,0 +1,59 @@
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "fopen-host-libc.tmp"
+#else
+#define TEST_FILE "fopen.tmp"
+#endif
+
+int main() {
+ FILE *file;
+ char str[] = "mlibc fopen test";
+ char str2[] = " mlibc appending";
+ char completestr[] = "mlibc fopen test mlibc appending";
+ char buffer[100];
+ char buffer2[100];
+
+ // Clear all the buffers to zero.
+ memset(buffer, 0, sizeof(buffer));
+ memset(buffer2, 0, sizeof(buffer2));
+
+ // Open the file for writing.
+ file = fopen(TEST_FILE, "w");
+ assert(file);
+
+ // Write string minus null terminator, flush and close.
+ fwrite(str, 1, sizeof(str) - 1, file);
+ fflush(file);
+ fclose(file);
+
+ // Open the file for reading.
+ file = fopen(TEST_FILE, "r");
+ assert(file);
+
+ // Verify that we read back the written string and close the file.
+ assert(fread(buffer, 1, sizeof(str) - 1, file));
+ assert(!strcmp(buffer, str));
+ fclose(file);
+
+ // Open the file in appending mode, append string 2 (minus the null terminator) to the file, flush and close.
+ file = fopen(TEST_FILE, "a");
+ fwrite(str2, 1, sizeof(str2) - 1, file);
+ fflush(file);
+ fclose(file);
+
+ // Open the file for reading again, verify the contents, close the file and return.
+ file = fopen(TEST_FILE, "r");
+ assert(fread(buffer2, 1, sizeof(completestr) - 1, file));
+ assert(!strcmp(buffer2, completestr));
+ fclose(file);
+
+ // Check that stdout, stdin and stderr can be closed by the application (issue #12).
+ fclose(stdout);
+ fclose(stdin);
+ fclose(stderr);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/freopen.c b/lib/mlibc/tests/ansi/freopen.c
new file mode 100644
index 0000000..c91577f
--- /dev/null
+++ b/lib/mlibc/tests/ansi/freopen.c
@@ -0,0 +1,49 @@
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "freopen-host-libc.tmp"
+#else
+#define TEST_FILE "freopen.tmp"
+#endif
+
+int main() {
+ FILE *file = fopen(TEST_FILE, "w");
+ assert(file);
+
+ assert(freopen("/dev/null", "w", file));
+
+ char str[] = "mlibc freopen test";
+ fwrite(str, 1, sizeof(str) - 1, file);
+ fflush(file);
+ fclose(file);
+
+ file = fopen(TEST_FILE, "r");
+ assert(file);
+
+ char buf[sizeof(str)];
+ memset(buf, 0, sizeof(buf));
+ int ret = fread(buf, 1, sizeof(buf) - 1, file);
+ fprintf(stderr, "ret %d\n", ret);
+ assert(ret == 0);
+ fclose(file);
+
+ file = fopen("/dev/null", "w");
+ assert(file);
+
+ assert(freopen(TEST_FILE, "w", file));
+ fwrite(str, 1, sizeof(str) - 1, file);
+ fflush(file);
+ fclose(file);
+
+ memset(buf, 0, sizeof(buf));
+ file = fopen(TEST_FILE, "r");
+ assert(fread(buf, 1, sizeof(buf) - 1, file));
+
+ fprintf(stderr, "buffer content '%s'\n", buf);
+ assert(!strcmp(buf, "mlibc freopen test"));
+ fclose(file);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/locale.c b/lib/mlibc/tests/ansi/locale.c
new file mode 100644
index 0000000..056298b
--- /dev/null
+++ b/lib/mlibc/tests/ansi/locale.c
@@ -0,0 +1,17 @@
+#include <stdio.h>
+#include <wchar.h>
+#include <locale.h>
+#include <assert.h>
+
+int main() {
+ wchar_t c = 0xC9;
+ unsigned char buf[sizeof(wchar_t)] = { 0 };
+ setlocale(LC_ALL, "");
+ if (sprintf(buf, "%lc", c) < 0)
+ return -1;
+
+ assert(buf[0] == 0xc3 && buf[1] == 0x89
+ && buf[2] == '\0' && buf[3] == '\0');
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/longjmp.c b/lib/mlibc/tests/ansi/longjmp.c
new file mode 100644
index 0000000..9f9c1ed
--- /dev/null
+++ b/lib/mlibc/tests/ansi/longjmp.c
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include <setjmp.h>
+#include <stdnoreturn.h>
+#include <assert.h>
+
+jmp_buf buf;
+
+noreturn void do_jump(int arg) {
+ longjmp(buf, 2 * arg);
+}
+
+int main(void) {
+ volatile int times_called = 0;
+
+ if (setjmp(buf) != 8) {
+ do_jump(++times_called);
+ }
+
+ assert(times_called == 4);
+
+ times_called = 0;
+ int ret = setjmp(buf);
+ assert(ret == times_called);
+
+ if (!ret) {
+ times_called = 1;
+ longjmp(buf, 0);
+ }
+}
diff --git a/lib/mlibc/tests/ansi/mbrtoc32.c b/lib/mlibc/tests/ansi/mbrtoc32.c
new file mode 100644
index 0000000..781bc71
--- /dev/null
+++ b/lib/mlibc/tests/ansi/mbrtoc32.c
@@ -0,0 +1,53 @@
+#include <assert.h>
+#include <locale.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <uchar.h>
+
+struct {
+ size_t ret;
+ char32_t c32;
+ uint8_t bytes[4];
+} expected_results[] = {
+ {1, 0x7A, {0x7A, 0, 0, 0}},
+ {2, 0xDF, {0xC3, 0x9F, 0, 0}},
+ {3, 0x6C34, {0xE6, 0xB0, 0xB4, 0}},
+ {4, 0x1F34C, {0xF0, 0x9F, 0x8D, 0x8C}},
+};
+
+int main() {
+ setlocale(LC_ALL, "en_US.utf8");
+
+ size_t ret = 0;
+
+ char *str = "z\u00df\u6c34\U0001F34C";
+
+ fprintf(stderr, "string: '%s'\n", str);
+
+ mbstate_t state = {};
+ char32_t c32;
+ size_t loop = 0;
+ while((ret = mbrtoc32(&c32, str, strlen(str), &state))) {
+ assert(ret != (size_t)-3);
+ if(ret == (size_t)-1)
+ break;
+ if(ret == (size_t)-2)
+ break;
+
+ fprintf(stderr, "Next UTF-32 char: 0x%x obtained from %zu bytes [", c32, ret);
+ for(size_t n = 0; n < ret; ++n) {
+ fprintf(stderr, " 0x%02x ", (uint8_t) str[n]);
+ }
+ fprintf(stderr, "]\n");
+
+ assert(ret == expected_results[loop].ret);
+ assert(c32 == expected_results[loop].c32);
+ for(size_t n = 0; n < ret; ++n) {
+ assert((uint8_t) str[n] == expected_results[loop].bytes[n]);
+ }
+
+ str += ret;
+ loop++;
+ }
+}
diff --git a/lib/mlibc/tests/ansi/memmem.c b/lib/mlibc/tests/ansi/memmem.c
new file mode 100644
index 0000000..8de558f
--- /dev/null
+++ b/lib/mlibc/tests/ansi/memmem.c
@@ -0,0 +1,22 @@
+#include <string.h>
+#include <assert.h>
+
+int main() {
+ char *haystack = "abc123\0x45";
+
+ char *needle1 = "abc";
+ void *rv = memmem(haystack, strlen(haystack), needle1, strlen(needle1));
+ assert(rv == haystack);
+
+ char *needle2 = "123";
+ rv = memmem(haystack, strlen(haystack), needle2, strlen(needle2));
+ assert(rv == haystack + 3);
+
+ char *needle3 = "1234";
+ rv = memmem(haystack, strlen(haystack), needle3, strlen(needle3));
+ assert(rv == NULL);
+
+ char *needle4 = "23\0x45";
+ rv = memmem(haystack, 10, needle4, 6);
+ assert(rv == haystack + 4);
+}
diff --git a/lib/mlibc/tests/ansi/qsort.c b/lib/mlibc/tests/ansi/qsort.c
new file mode 100644
index 0000000..bb48a23
--- /dev/null
+++ b/lib/mlibc/tests/ansi/qsort.c
@@ -0,0 +1,31 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+static const char *arr[] = { "xyz", "abc", "ghi", "def" };
+
+static const size_t magic = 0xDEADBEEF;
+
+static int cmpstringp(const void *p1, const void *p2, void *ctx) {
+ /* The actual arguments to this function are "pointers to
+ * pointers to char", but strcmp(3) arguments are "pointers
+ * to char", hence the following cast plus dereference. */
+ assert(*(size_t *) ctx == magic);
+ return strcmp(*(const char **) p1, *(const char **) p2);
+}
+
+int main() {
+ qsort_r(&arr[0], sizeof(arr) / sizeof(*arr), sizeof(char *), cmpstringp, (void *) &magic);
+
+ assert(!strcmp(arr[0], "abc"));
+ assert(!strcmp(arr[1], "def"));
+ assert(!strcmp(arr[2], "ghi"));
+ assert(!strcmp(arr[3], "xyz"));
+
+ for(size_t i = 0; i < sizeof(arr) / sizeof(*arr); i++) {
+ fprintf(stderr, "%s\n", arr[i]);
+ }
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/snprintf.c b/lib/mlibc/tests/ansi/snprintf.c
new file mode 100644
index 0000000..82e4640
--- /dev/null
+++ b/lib/mlibc/tests/ansi/snprintf.c
@@ -0,0 +1,23 @@
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+int main() {
+ char buffer[10];
+ int ret = snprintf(buffer, 10, "%d", 123456789);
+ assert(strncmp("123456789", buffer, 10) == 0);
+ assert(ret == 9);
+
+ // We deliberately induce a warning here.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+ ret = snprintf(buffer, 10, "%d", 1234567890);
+ assert(strncmp("123456789", buffer, 10) == 0);
+ assert(ret == 10);
+#pragma GCC diagnostic pop
+
+ // mlibc issue #118.
+ ret = snprintf(NULL, 0, "%d", 123456789);
+ assert(ret == 9);
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/sprintf.c b/lib/mlibc/tests/ansi/sprintf.c
new file mode 100644
index 0000000..fd9398c
--- /dev/null
+++ b/lib/mlibc/tests/ansi/sprintf.c
@@ -0,0 +1,191 @@
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <string.h>
+
+int main() {
+ char buf[11] = { 0 };
+ sprintf(buf, "%d", 12);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%f", 3.14);
+ assert(!strcmp(buf, "3.140000"));
+
+ // Test %c right padding.
+ sprintf(buf, "%-2c", 'a');
+ assert(!strcmp(buf, "a "));
+
+ // Test %c left padding.
+ sprintf(buf, "%2c", 'a');
+ assert(!strcmp(buf, " a"));
+
+ // Test %d right padding - mlibc issue #58.
+ sprintf(buf, "%-2d", 1);
+ assert(!strcmp(buf, "1 "));
+ sprintf(buf, "%-2.2d", 1);
+ assert(!strcmp(buf, "01"));
+ sprintf(buf, "%-3.2d", 1);
+ assert(!strcmp(buf, "01 "));
+ sprintf(buf, "%-3.2d", 12);
+ assert(!strcmp(buf, "12 "));
+ sprintf(buf, "%-3.2d", 123);
+ assert(!strcmp(buf, "123"));
+ sprintf(buf, "%-3.2u", 12);
+ assert(!strcmp(buf, "12 "));
+
+ // Test %d left padding.
+ sprintf(buf, "%2d", 1);
+ assert(!strcmp(buf, " 1"));
+ sprintf(buf, "%3.2d", 1);
+ assert(!strcmp(buf, " 01"));
+ sprintf(buf, "%3.2d", 12);
+ assert(!strcmp(buf, " 12"));
+ sprintf(buf, "%3.2d", 123);
+ assert(!strcmp(buf, "123"));
+ sprintf(buf, "%3.2u", 12);
+ assert(!strcmp(buf, " 12"));
+
+ // Test %f padding.
+ // TODO: Test printing of huge numbers (larger than 2^64)
+ sprintf(buf, "%2.f", 1.2);
+ assert(!strcmp(buf, " 1"));
+ sprintf(buf, "%2.f", 12.3);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%5.2f", 1.0);
+ assert(!strcmp(buf, " 1.00"));
+ sprintf(buf, "%.1f", -4.0);
+ assert(!strcmp(buf, "-4.0"));
+ sprintf(buf, "%-3.f", 8.0);
+ assert(!strcmp(buf, "8 "));
+ sprintf(buf, "%4f", INFINITY);
+ assert(!strcmp(buf, " inf") || !strcmp(buf, "infinity"));
+ sprintf(buf, "%4f", NAN);
+ assert(!strcmp(buf, " nan"));
+ sprintf(buf, "%4F", INFINITY);
+ assert(!strcmp(buf, " INF") || !strcmp(buf, "INFINITY"));
+ sprintf(buf, "%4F", NAN);
+ assert(!strcmp(buf, " NAN"));
+ sprintf(buf, "%05.2f", 1.0);
+ assert(!strcmp(buf, "01.00"));
+ sprintf(buf, "%09f", INFINITY); // 0 ignored when padding infs
+ assert(!strcmp(buf, " inf") || !strcmp(buf, " infinity"));
+ // TODO: We don't yet round properly
+ // sprintf(buf, "%5.2f", 1.2);
+ // assert(!strcmp(buf, " 1.20"));
+ // sprintf(buf, "%5.2f", 1.23);
+ // assert(!strcmp(buf, " 1.23"));
+ // sprintf(buf, "%5.2f", 1.234);
+ // assert(!strcmp(buf, " 1.23"));
+ // sprintf(buf, "%5.2f", 12.345);
+ // assert(!strcmp(buf, "12.35"));
+ // sprintf(buf, "%-5.2f", 1.2);
+ // assert(!strcmp(buf, "1.20 "));
+
+ // Test '+' and ' ' flags - mlibc issue #229.
+ // Disable -Wformat here since we deliberately induce some warnings.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+ sprintf(buf, "%+d", 12);
+ assert(!strcmp(buf, "+12"));
+ sprintf(buf, "% d", 12);
+ assert(!strcmp(buf, " 12"));
+ sprintf(buf, "% +d", 12);
+ assert(!strcmp(buf, "+12"));
+ sprintf(buf, "%+ d", 12);
+ assert(!strcmp(buf, "+12"));
+ sprintf(buf, "%+d", -12);
+ assert(!strcmp(buf, "-12"));
+ sprintf(buf, "% d", -12);
+ assert(!strcmp(buf, "-12"));
+ sprintf(buf, "% +d", -12);
+ assert(!strcmp(buf, "-12"));
+ sprintf(buf, "%+ d", -12);
+ assert(!strcmp(buf, "-12"));
+#pragma GCC diagnostic pop
+
+ // Test '#' flag.
+ // TODO: Test with a, A, e, E, f, F, g, G conversions.
+ sprintf(buf, "%#x", 12);
+ assert(!strcmp(buf, "0xc"));
+ sprintf(buf, "%#X", 12);
+ assert(!strcmp(buf, "0XC"));
+ sprintf(buf, "%#o", 12);
+ assert(!strcmp(buf, "014"));
+ sprintf(buf, "%#x", 0);
+ assert(!strcmp(buf, "0"));
+ sprintf(buf, "%#X", 0);
+ assert(!strcmp(buf, "0"));
+ sprintf(buf, "%#o", 0);
+ assert(!strcmp(buf, "0"));
+
+ // Test 'd' with different size mods to see
+ // if they work
+ sprintf(buf, "%d", 12);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%ld", 12L);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%lld", 12LL);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%zd", (size_t)12);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%hd", 12);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%hhd", 12);
+ assert(!strcmp(buf, "12"));
+
+ // Test 'x' with different size mods to see
+ // if they work
+ sprintf(buf, "%x", 12);
+ assert(!strcmp(buf, "c"));
+ sprintf(buf, "%lx", 12L);
+ assert(!strcmp(buf, "c"));
+ sprintf(buf, "%llx", 12LL);
+ assert(!strcmp(buf, "c"));
+ sprintf(buf, "%zx", (size_t)12);
+ assert(!strcmp(buf, "c"));
+ sprintf(buf, "%hx", 12);
+ assert(!strcmp(buf, "c"));
+ sprintf(buf, "%hhx", 12);
+ assert(!strcmp(buf, "c"));
+
+ // Test 'X' with different size mods to see
+ // if they work
+ sprintf(buf, "%X", 12);
+ assert(!strcmp(buf, "C"));
+ sprintf(buf, "%lX", 12L);
+ assert(!strcmp(buf, "C"));
+ sprintf(buf, "%llX", 12LL);
+ assert(!strcmp(buf, "C"));
+ sprintf(buf, "%zX", (size_t)12);
+ assert(!strcmp(buf, "C"));
+ sprintf(buf, "%hX", 12);
+ assert(!strcmp(buf, "C"));
+ sprintf(buf, "%hhX", 12);
+ assert(!strcmp(buf, "C"));
+
+ // Test 'o' with different size mods to see
+ // if they work
+ sprintf(buf, "%o", 12);
+ assert(!strcmp(buf, "14"));
+ sprintf(buf, "%lo", 12L);
+ assert(!strcmp(buf, "14"));
+ sprintf(buf, "%llo", 12LL);
+ assert(!strcmp(buf, "14"));
+ sprintf(buf, "%zo", (size_t)12);
+ assert(!strcmp(buf, "14"));
+ sprintf(buf, "%ho", 12);
+ assert(!strcmp(buf, "14"));
+ sprintf(buf, "%hho", 12);
+ assert(!strcmp(buf, "14"));
+
+ // Test %n$ syntax.
+ sprintf(buf, "%1$d", 12);
+ assert(!strcmp(buf, "12"));
+ sprintf(buf, "%1$d %1$d", 12);
+ assert(!strcmp(buf, "12 12"));
+ sprintf(buf, "%1$d %2$d %1$d", 12, 14);
+ assert(!strcmp(buf, "12 14 12"));
+ sprintf(buf, "%1$d %2$s %2$s", 12, "foo");
+ assert(!strcmp(buf, "12 foo foo"));
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/sscanf.c b/lib/mlibc/tests/ansi/sscanf.c
new file mode 100644
index 0000000..f3a881f
--- /dev/null
+++ b/lib/mlibc/tests/ansi/sscanf.c
@@ -0,0 +1,143 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+struct format_test_cases {
+ const char *format;
+ const char *data;
+ int expected_int;
+ enum {
+ T_INT,
+ T_UINT,
+ T_CHAR,
+ T_NONE,
+ } type;
+ int ret;
+} formats[] = {
+ {"%i", "0x420", 0x420, T_INT, 1},
+ {"%i", "0420", 0420, T_INT, 1},
+ {"%i", "420", 420, T_INT, 1},
+ {"%i", "-420", -420, T_INT, 1},
+ {"%d", "-12345", -12345, T_INT, 1},
+ {"%u", "69", 69, T_UINT, 1},
+ {"%u", "0420", 420, T_UINT, 1},
+ {"%o", "0420", 0420, T_UINT, 1},
+ {"%x", "0xCB7", 0xCB7, T_UINT, 1},
+ {"%%", "%", 0, T_NONE, 0},
+ {"%c", " I am not a fan of this solution.", ' ', T_CHAR, 1},
+ {" %c", " CBT (capybara therapy)", 'C', T_CHAR, 1},
+};
+
+#pragma GCC diagnostic ignored "-Wformat-security"
+
+static void test_matrix() {
+ for(size_t i = 0; i < (sizeof(formats) / sizeof(*formats)); i++) {
+ struct format_test_cases *f = &formats[i];
+ int ret = -1;
+ int data_int;
+ unsigned int data_uint;
+ char data_char;
+
+ switch(f->type) {
+ case T_INT: {
+ ret = sscanf(f->data, f->format, &data_int);
+ assert(data_int == f->expected_int);
+ break;
+ }
+ case T_UINT: {
+ ret = sscanf(f->data, f->format, &data_uint);
+ assert(data_uint == (unsigned int) f->expected_int);
+ break;
+ }
+ case T_CHAR: {
+ ret = sscanf(f->data, f->format, &data_char);
+ assert(data_char == (unsigned char) f->expected_int);
+ break;
+ }
+ case T_NONE: {
+ ret = sscanf(f->data, f->format);
+ break;
+ }
+
+ }
+
+ assert(ret == f->ret);
+ }
+}
+
+int main() {
+ {
+ int x = 0;
+ char buf[] = "12345";
+ sscanf(buf, "%d", &x);
+ assert(x == 12345);
+ }
+
+ {
+ char c;
+ int n1;
+ int n2;
+ char buf[] = "z$ 7 5 440";;
+ int count = sscanf(buf, "%*c%c %d %*d %d", &c, &n1, &n2);
+ assert(count == 3);
+ assert(c == '$');
+ assert(n1 == 7);
+ assert(n2 == 440);
+ }
+
+ {
+ // From dsda-doom
+ char buf[] = "process_priority 0\n";
+ char def[80], strparm[128];
+ memset(def, '!', 80);
+ memset(strparm, '!', 128);
+ sscanf(buf, "%s %[^\n]\n", def, strparm);
+ assert(!strcmp(def, "process_priority"));
+ assert(!strcmp(strparm, "0"));
+ }
+
+ {
+ char buf[] = "fffff100";
+ unsigned long y = 0;
+ sscanf(buf, "%lx", &y);
+ assert(y == 0xfffff100);
+ }
+
+#if !defined(__i386__)
+ {
+ char buf[] = "fffffffff100";
+ unsigned long y = 0;
+ sscanf(buf, "%lx", &y);
+ assert(y == 0xfffffffff100);
+ }
+#endif
+
+ {
+ char buf[] = "410dc000";
+ unsigned long y = 0;
+ sscanf(buf, "%lx", &y);
+ assert(y == 0x410dc000);
+ }
+
+ {
+ // From webkitgtk
+ char buf[] = "MemTotal: 16299664 kB\n";
+ char token[51] = {0};
+ size_t amount = 0;
+ int ret = sscanf(buf, "%50s%zukB", token, &amount);
+ assert(ret == 2);
+ assert(!strcmp(token, "MemTotal:"));
+ assert(amount == 16299664);
+ }
+
+ {
+ char buf[] = "SIGINT";
+ int sig;
+ int ret = sscanf(buf, "%d", &sig);
+ assert(!ret);
+ }
+
+ test_matrix();
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/strchr.c b/lib/mlibc/tests/ansi/strchr.c
new file mode 100644
index 0000000..a80a1f9
--- /dev/null
+++ b/lib/mlibc/tests/ansi/strchr.c
@@ -0,0 +1,20 @@
+#include <assert.h>
+#include <string.h>
+#include <stddef.h>
+
+int main() {
+ char str[] = "This is a sample string";
+ char *pch;
+ // The character 's' is at position 4, 7, 11 and 18
+ pch = strchr(str, 's');
+ assert(pch - str + 1 == 4);
+ pch = strchr(pch + 1, 's');
+ assert(pch - str + 1 == 7);
+ pch = strchr(pch + 1, 's');
+ assert(pch - str + 1 == 11);
+ pch = strchr(pch + 1, 's');
+ assert(pch - str + 1 == 18);
+ pch = strchr(pch + 1, 's');
+ assert(pch == NULL);
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/strftime.c b/lib/mlibc/tests/ansi/strftime.c
new file mode 100644
index 0000000..69ec642
--- /dev/null
+++ b/lib/mlibc/tests/ansi/strftime.c
@@ -0,0 +1,48 @@
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+
+int main() {
+ // Date representation depends on locale, here only C is tested.
+ // Maybe consider testing more locales?
+ if (setlocale (LC_ALL, "C") == NULL) {
+ fputs("strftime testcase could not set locale, errors may be expected!", stderr);
+ }
+
+ char timebuf[16];
+ char result[16] = " 8";
+ struct tm tm;
+ tm.tm_sec = 0;
+ tm.tm_min = 17;
+ tm.tm_hour = 17;
+ tm.tm_mday = 8;
+ tm.tm_mon = 2;
+ tm.tm_year = 121;
+ tm.tm_wday = 2;
+ tm.tm_yday = 39;
+ strftime(timebuf, sizeof(timebuf), "%e", &tm);
+ assert(!strcmp(timebuf, result));
+
+ memset(timebuf, 0, sizeof(timebuf));
+ strftime(timebuf, sizeof(timebuf), "%x", &tm);
+ assert(!strcmp(timebuf, "03/08/21"));
+
+ memset(timebuf, 0, sizeof(timebuf));
+ strftime(timebuf, sizeof(timebuf), "%X", &tm);
+ assert(!strcmp(timebuf, "17:17:00"));
+
+ memset(timebuf, 0, sizeof(timebuf));
+ strftime(timebuf, sizeof(timebuf), "%a %A", &tm);
+ assert(!strcmp(timebuf, "Tue Tuesday"));
+
+ memset(timebuf, 0, sizeof(timebuf));
+ strftime(timebuf, sizeof(timebuf), "%b %B %h", &tm);
+ assert(!strcmp(timebuf, "Mar March Mar"));
+
+ memset(timebuf, 0, sizeof(timebuf));
+ assert(!strftime(timebuf, sizeof(timebuf), "%a %A %a %A %b %B %h", &tm));
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/strrchr.c b/lib/mlibc/tests/ansi/strrchr.c
new file mode 100644
index 0000000..2de9821
--- /dev/null
+++ b/lib/mlibc/tests/ansi/strrchr.c
@@ -0,0 +1,11 @@
+#include <string.h>
+#include <assert.h>
+
+int main() {
+ char str[] = "This is a sample string";
+ char *pch;
+ pch = strrchr(str, 's');
+ // The last occurence of 's' is at position 18
+ assert(pch - str + 1 == 18);
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/strtof.c b/lib/mlibc/tests/ansi/strtof.c
new file mode 100644
index 0000000..a13af90
--- /dev/null
+++ b/lib/mlibc/tests/ansi/strtof.c
@@ -0,0 +1,83 @@
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+#define FLT_RANGE 0.000001f
+#define DBL_RANGE 0.000001
+#define LDBL_RANGE 0.000001
+
+#define DO_TEST(str, value, off, func, range) ({ \
+ char s[] = (str); \
+ char *pEnd = NULL; \
+ __typeof(func(s, &pEnd)) result = func(s, &pEnd); \
+ assert(result >= (value) - (range)); \
+ assert(result <= (value) + (range)); \
+ assert(pEnd == (off == -1 ? s + strlen(s) : s + off)); })
+
+#define DO_TEST_SUCCESS_FUNC(str, success_func, off, func) ({ \
+ char s[] = (str); \
+ char *pEnd = NULL; \
+ assert(success_func(func(s, &pEnd))); \
+ assert(pEnd == (off == -1 ? s + strlen(s) : s + off)); })
+
+int main () {
+ DO_TEST("0", 0.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("0.12", 0.12f, -1, strtof, FLT_RANGE);
+ DO_TEST("12", 12.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("12.13", 12.13f, -1, strtof, FLT_RANGE);
+ DO_TEST("10.0e1", 100.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("10.0e10", 100000000000.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("100.0e-1", 10.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("0x0", 0.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("0x0.12", 0.0703125f, -1, strtof, FLT_RANGE);
+ DO_TEST("0x12", 18.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("0x12.13", 18.07421875f, -1, strtof, FLT_RANGE);
+ DO_TEST("0x10.0p1", 32.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("0x10.0p10", 16384.0f, -1, strtof, FLT_RANGE);
+ DO_TEST("0x100.0p-1", 128.0f, -1, strtof, FLT_RANGE);
+ DO_TEST_SUCCESS_FUNC("NAN", isnan, -1, strtof);
+ DO_TEST_SUCCESS_FUNC("nan", isnan, -1, strtof);
+ DO_TEST_SUCCESS_FUNC("INF", isinf, -1, strtof);
+ DO_TEST_SUCCESS_FUNC("INFINITY", isinf, -1, strtof);
+
+ DO_TEST("0", 0.0, -1, strtod, DBL_RANGE);
+ DO_TEST("0.12", 0.12, -1, strtod, DBL_RANGE);
+ DO_TEST("12", 12.0, -1, strtod, DBL_RANGE);
+ DO_TEST("12.13", 12.13, -1, strtod, DBL_RANGE);
+ DO_TEST("10.0e1", 100.0, -1, strtod, DBL_RANGE);
+ DO_TEST("10.0e10", 100000000000.0, -1, strtod, DBL_RANGE);
+ DO_TEST("100.0e-1", 10.0, -1, strtod, DBL_RANGE);
+ DO_TEST("0x0", 0.0, -1, strtod, DBL_RANGE);
+ DO_TEST("0x0.12", 0.0703125, -1, strtod, DBL_RANGE);
+ DO_TEST("0x12", 18.0, -1, strtod, DBL_RANGE);
+ DO_TEST("0x12.13", 18.07421875, -1, strtod, DBL_RANGE);
+ DO_TEST("0x10.0p1", 32.0, -1, strtod, DBL_RANGE);
+ DO_TEST("0x10.0p10", 16384.0, -1, strtod, DBL_RANGE);
+ DO_TEST("0x100.0p-1", 128.0, -1, strtod, DBL_RANGE);
+ DO_TEST_SUCCESS_FUNC("NAN", isnan, -1, strtod);
+ DO_TEST_SUCCESS_FUNC("nan", isnan, -1, strtod);
+ DO_TEST_SUCCESS_FUNC("INF", isinf, -1, strtod);
+ DO_TEST_SUCCESS_FUNC("INFINITY", isinf, -1, strtod);
+
+ DO_TEST("0", 0.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("0.12", 0.12, -1, strtold, LDBL_RANGE);
+ DO_TEST("12", 12.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("12.13", 12.13, -1, strtold, LDBL_RANGE);
+ DO_TEST("10.0e1", 100.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("10.0e10", 100000000000.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("100.0e-1", 10.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("0x0", 0.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("0x0.12", 0.0703125, -1, strtold, LDBL_RANGE);
+ DO_TEST("0x12", 18.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("0x12.13", 18.07421875, -1, strtold, LDBL_RANGE);
+ DO_TEST("0x10.0p1", 32.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("0x10.0p10", 16384.0, -1, strtold, LDBL_RANGE);
+ DO_TEST("0x100.0p-1", 128.0, -1, strtold, LDBL_RANGE);
+ DO_TEST_SUCCESS_FUNC("NAN", isnan, -1, strtold);
+ DO_TEST_SUCCESS_FUNC("nan", isnan, -1, strtold);
+ DO_TEST_SUCCESS_FUNC("INF", isinf, -1, strtold);
+ DO_TEST_SUCCESS_FUNC("INFINITY", isinf, -1, strtold);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/strtol.c b/lib/mlibc/tests/ansi/strtol.c
new file mode 100644
index 0000000..823cb4a
--- /dev/null
+++ b/lib/mlibc/tests/ansi/strtol.c
@@ -0,0 +1,140 @@
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#define DO_TEST(str, value, off, func, base) ({ \
+ char s[] = (str); \
+ char *pEnd = NULL; \
+ errno = 0; \
+ assert(func(s, &pEnd, base) == (value)); \
+ assert(errno == 0); \
+ assert(pEnd == (off == -1 ? s + strlen(s) : s + off)); })
+
+#define DO_ERR_TEST(str, value, err, func, base) ({ \
+ char s[] = (str); \
+ char *pEnd = NULL; \
+ errno = 0; \
+ assert(func(s, &pEnd, base) == (value)); \
+ assert(errno == err); \
+ assert(pEnd == (err == ERANGE ? s + strlen(s) : s)); })
+
+#define DO_TESTL(str, value, off, func, base) ({ \
+ wchar_t s[] = (str); \
+ wchar_t *pEnd = NULL; \
+ errno = 0; \
+ assert(func(s, &pEnd, base) == (value)); \
+ assert(errno == 0); \
+ assert(pEnd == (off == -1 ? s + wcslen(s) : s + off)); })
+
+#define DO_ERR_TESTL(str, value, err, func, base) ({ \
+ wchar_t s[] = (str); \
+ wchar_t *pEnd = NULL; \
+ errno = 0; \
+ assert(func(s, &pEnd, base) == (value)); \
+ assert(errno == err); \
+ assert(pEnd == (err == ERANGE ? s + wcslen(s) : s)); })
+
+int main () {
+ // A few generic checks.
+ DO_TEST("0", 0, -1, strtol, 0);
+ DO_TEST("0", 0, -1, strtol, 10);
+ DO_TEST("2001", 2001, -1, strtol, 10);
+ DO_TEST("+2001", 2001, -1, strtol, 10);
+ DO_TEST("60c0c0", 0x60c0c0, -1, strtol, 16);
+ DO_TEST("-1101110100110100100000", -3624224, -1, strtol, 2);
+ DO_TEST("0x6fffff", 0x6fffff, -1, strtol, 0);
+ DO_TEST("0666", 0666, -1, strtol, 0);
+ DO_TEST("0xzzz", 0, 1, strtol, 0);
+ DO_TEST("0yzzz", 0, 1, strtol, 0);
+ DO_TEST("00xzz", 0, 2, strtol, 0);
+ DO_ERR_TEST("", 0, 0, strtol, 10);
+ DO_ERR_TEST("asd", 0, 0, strtol, 10);
+ DO_ERR_TEST("999999999999999999999999999", LONG_MAX, ERANGE, strtol, 10);
+ DO_ERR_TEST("-999999999999999999999999999", LONG_MIN, ERANGE, strtol, 10);
+
+ // strtol
+#if defined(__i386__)
+ DO_TEST("-2147483648", LONG_MIN, -1, strtol, 10);
+ DO_TEST("2147483647", LONG_MAX, -1, strtol, 10);
+ DO_ERR_TEST("-2147483649", LONG_MIN, ERANGE, strtol, 10);
+ DO_ERR_TEST("2147483648", LONG_MAX, ERANGE, strtol, 10);
+#else
+ DO_TEST("-9223372036854775808", LONG_MIN, -1, strtol, 10);
+ DO_TEST("9223372036854775807", LONG_MAX, -1, strtol, 10);
+ DO_ERR_TEST("9223372036854775808", LONG_MAX, ERANGE, strtol, 10);
+ DO_ERR_TEST("-9223372036854775809", LONG_MIN, ERANGE, strtol, 10);
+#endif
+
+ // wcstol
+#if defined(__i386__)
+ DO_TESTL(L"-2147483648", LONG_MIN, -1, wcstol, 10);
+ DO_TESTL(L"2147483647", LONG_MAX, -1, wcstol, 10);
+ DO_ERR_TESTL(L"2147483648", LONG_MAX, ERANGE, wcstol, 10);
+ DO_ERR_TESTL(L"-2147483649", LONG_MIN, ERANGE, wcstol, 10);
+#else
+ DO_TESTL(L"-9223372036854775808", LONG_MIN, -1, wcstol, 10);
+ DO_TESTL(L"9223372036854775807", LONG_MAX, -1, wcstol, 10);
+ DO_ERR_TESTL(L"9223372036854775808", LONG_MAX, ERANGE, wcstol, 10);
+ DO_ERR_TESTL(L"-9223372036854775809", LONG_MIN, ERANGE, wcstol, 10);
+#endif
+
+ // strtoll
+ DO_TEST("-9223372036854775808", LLONG_MIN, -1, strtoll, 10);
+ DO_TEST("9223372036854775807", LLONG_MAX, -1, strtoll, 10);
+ DO_ERR_TEST("9223372036854775808", LLONG_MAX, ERANGE, strtoll, 10);
+ DO_ERR_TEST("-9223372036854775809", LLONG_MIN, ERANGE, strtoll, 10);
+ // wcstoll
+ DO_TESTL(L"-9223372036854775808", LLONG_MIN, -1, wcstoll, 10);
+ DO_TESTL(L"9223372036854775807", LLONG_MAX, -1, wcstoll, 10);
+ DO_ERR_TESTL(L"9223372036854775808", LLONG_MAX, ERANGE, wcstoll, 10);
+ DO_ERR_TESTL(L"-9223372036854775809", LLONG_MIN, ERANGE, wcstoll, 10);
+
+ // strtoul
+#if defined(__i386__)
+ DO_TEST("-1", -(1UL), -1, strtoul, 10);
+ DO_TEST("2147483647", LONG_MAX, -1, strtoul, 10);
+ DO_TEST("4294967295", ULONG_MAX, -1, strtoul, 10);
+ DO_TEST("-4294967295", 1UL, -1, strtoul, 10);
+ DO_ERR_TEST("4294967296", ULONG_MAX, ERANGE, strtoul, 10);
+#else
+ DO_TEST("-1", -(1UL), -1, strtoul, 10);
+ DO_TEST("9223372036854775807", LONG_MAX, -1, strtoul, 10);
+ DO_TEST("18446744073709551615", ULONG_MAX, -1, strtoul, 10);
+ DO_TEST("-18446744073709551615", 1UL, -1, strtoul, 10);
+ DO_ERR_TEST("18446744073709551616", ULONG_MAX, ERANGE, strtoul, 10);
+#endif
+
+ // wcstoul
+#if defined(__i386__)
+ DO_TESTL(L"-1", -(1UL), -1, wcstoul, 10);
+ DO_TESTL(L"2147483647", LONG_MAX, -1, wcstoul, 10);
+ DO_TESTL(L"4294967295", ULONG_MAX, -1, wcstoul, 10);
+ DO_TESTL(L"-4294967295", 1UL, -1, wcstoul, 10);
+ DO_ERR_TESTL(L"4294967296", ULONG_MAX, ERANGE, wcstoul, 10);
+#else
+ DO_TESTL(L"-1", -(1UL), -1, wcstoul, 10);
+ DO_TESTL(L"9223372036854775807", LONG_MAX, -1, wcstoul, 10);
+ DO_TESTL(L"18446744073709551615", ULONG_MAX, -1, wcstoul, 10);
+ DO_TESTL(L"-18446744073709551615", 1UL, -1, wcstoul, 10);
+ DO_ERR_TESTL(L"18446744073709551616", ULONG_MAX, ERANGE, wcstoul, 10);
+#endif
+
+ // strtoull
+ DO_TEST("-1", -(1ULL), -1, strtoull, 10);
+ DO_TEST("9223372036854775807", LLONG_MAX, -1, strtoull, 10);
+ DO_TEST("18446744073709551615", ULLONG_MAX, -1, strtoull, 10);
+ DO_TEST("-18446744073709551615", 1ULL, -1, strtoull, 10);
+ DO_ERR_TEST("18446744073709551616", ULLONG_MAX, ERANGE, strtoull, 10);
+ // wcstoull
+ DO_TESTL(L"-1", -(1ULL), -1, wcstoull, 10);
+ DO_TESTL(L"9223372036854775807", LLONG_MAX, -1, wcstoull, 10);
+ DO_TESTL(L"18446744073709551615", ULLONG_MAX, -1, wcstoull, 10);
+ DO_TESTL(L"-18446744073709551615", 1ULL, -1, wcstoull, 10);
+ DO_ERR_TESTL(L"18446744073709551616", ULLONG_MAX, ERANGE, wcstoull, 10);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/strverscmp.c b/lib/mlibc/tests/ansi/strverscmp.c
new file mode 100644
index 0000000..d6501a1
--- /dev/null
+++ b/lib/mlibc/tests/ansi/strverscmp.c
@@ -0,0 +1,16 @@
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+int main() {
+ int res;
+
+ res = strverscmp("jan1", "jan10");
+ assert(res < 0);
+
+ res = strverscmp("jan11", "jan10");
+ assert(res > 0);
+
+ res = strverscmp("jan1", "jan1");
+ assert(res == 0);
+}
diff --git a/lib/mlibc/tests/ansi/strxfrm.c b/lib/mlibc/tests/ansi/strxfrm.c
new file mode 100644
index 0000000..07a4082
--- /dev/null
+++ b/lib/mlibc/tests/ansi/strxfrm.c
@@ -0,0 +1,14 @@
+#include <assert.h>
+#include <locale.h>
+#include <string.h>
+
+int main() {
+ setlocale(LC_ALL, "C");
+
+ const char *buf = "cbtteststring";
+ char dest[14];
+ size_t ret = strxfrm(dest, buf, strlen(buf) + 1);
+ assert(ret == 13);
+ int cmp = strncmp(dest, buf, 13);
+ assert(!cmp);
+}
diff --git a/lib/mlibc/tests/ansi/timegm.c b/lib/mlibc/tests/ansi/timegm.c
new file mode 100644
index 0000000..f5af03e
--- /dev/null
+++ b/lib/mlibc/tests/ansi/timegm.c
@@ -0,0 +1,45 @@
+#include <time.h>
+#include <stdio.h>
+#include <assert.h>
+
+int main() {
+ struct tm soon = {};
+ soon.tm_sec = 0;
+ soon.tm_min = 0;
+ soon.tm_hour = 0;
+ soon.tm_mday = 1;
+ soon.tm_mon = 0;
+ soon.tm_year = 70;
+ time_t result;
+ time_t expected_result = 0;
+ // This should be epoch.
+ result = timegm(&soon);
+ printf("epoch: %ld\n", result);
+ assert(result == expected_result);
+
+ soon.tm_sec = 12;
+ soon.tm_min = 8;
+ soon.tm_hour = 16;
+ soon.tm_mday = 17;
+ soon.tm_mon = 4;
+ soon.tm_year = 122;
+ expected_result = 1652803692;
+ result = timegm(&soon);
+ // On my host, this returned 1652803692, verify this.
+ printf("epoch: %ld\n", result);
+ assert(result == expected_result);
+
+ soon.tm_sec = 45;
+ soon.tm_min = 42;
+ soon.tm_hour = 17;
+ soon.tm_mday = 16;
+ soon.tm_mon = 8;
+ soon.tm_year = 69;
+ expected_result = -9181035;
+ result = timegm(&soon);
+ // On my host, this returned -9181035, verify this.
+ printf("epoch: %ld\n", result);
+ assert(result == expected_result);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/ungetc.c b/lib/mlibc/tests/ansi/ungetc.c
new file mode 100644
index 0000000..8773c6d
--- /dev/null
+++ b/lib/mlibc/tests/ansi/ungetc.c
@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "ungetc-host-libc.tmp"
+#else
+#define TEST_FILE "ungetc.tmp"
+#endif
+
+void test(int buffering) {
+ FILE *f = fopen(TEST_FILE, "w");
+ for(int c = '0'; c <= '9'; c++) {
+ fputc(c, f);
+ }
+ fclose(f);
+
+ f = fopen(TEST_FILE, "r");
+ if (!buffering) {
+ setbuf(f, NULL);
+ }
+
+ assert(ungetc('x', f) == 'x');
+ assert(fgetc(f) == 'x');
+
+ // Test pushing back the same character
+ for (int c = '0'; c <= '9'; c++) {
+ assert(fgetc(f) == c);
+ assert(ungetc(c, f) == c);
+ assert(fgetc(f) == c);
+ }
+ assert(fgetc(f) == EOF);
+ assert(ungetc(EOF, f) == EOF);
+
+ // Even though the spec does not guarantee it, we should be able to
+ // ungetc more than one character.
+ assert(ungetc('x', f) == 'x');
+ assert(ungetc('y', f) == 'y');
+ assert(fgetc(f) == 'y');
+ assert(fgetc(f) == 'x');
+
+ // Seeking should discard the effects of ungetc.
+ assert(ungetc('x', f) == 'x');
+ rewind(f);
+
+ // Test pushing back a different character
+ for (int c = '0'; c <= '9'; c++) {
+ assert(fgetc(f) == c);
+ assert(ungetc(c - '0' + 'a', f) == c - '0' + 'a');
+ assert(fgetc(f) == c - '0' + 'a');
+ }
+
+#ifndef USE_HOST_LIBC
+ // Too many ungetcs should fail.
+ int eof = 0;
+ for (int i = 0; i < 100; i++) {
+ if (ungetc('x', f) == EOF) {
+ eof = 1;
+ break;
+ }
+ }
+ assert(eof);
+#endif
+
+ fclose(f);
+
+ // TODO: Test with other operations, like fread.
+}
+
+int main() {
+ fprintf(stderr, "with buffering...\n");
+ test(1);
+ fprintf(stderr, "without buffering...\n");
+ test(0);
+}
diff --git a/lib/mlibc/tests/ansi/utf8.c b/lib/mlibc/tests/ansi/utf8.c
new file mode 100644
index 0000000..18b0d5a
--- /dev/null
+++ b/lib/mlibc/tests/ansi/utf8.c
@@ -0,0 +1,63 @@
+#include <assert.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <locale.h>
+
+#if UINTPTR_MAX == UINT64_MAX
+#define WCHAR_SPEC ""
+#else
+#define WCHAR_SPEC "l"
+#endif
+
+#define EXPECT(func) ({ \
+ if(res != expected_ret) { \
+ printf(#func " decoded %d bytes (expected %d bytes), at %s:%d\n", \
+ res, expected_ret, __FILE__, line); \
+ fflush(stdout); \
+ abort(); \
+ } \
+ if(wc != expected) { \
+ printf(#func " output cp %" WCHAR_SPEC "x (expected cp %" WCHAR_SPEC "x), at %s:%d\n", wc, \
+ expected, __FILE__, line); \
+ fflush(stdout); \
+ abort(); \
+ } \
+ })
+
+void verify_decode(const char *input, wchar_t expected, int line) {
+ wchar_t wc;
+ int bytes = *input ? strlen(input) : 1;
+ int expected_ret = *input ? strlen(input) : 0;
+
+ // Check mbrtowc().
+ mbstate_t state;
+ memset(&state, 0, sizeof(state));
+ assert(mbrtowc(NULL, NULL, -1, &state) == 0);
+ int res = mbrtowc(&wc, input, bytes, &state);
+ EXPECT("mbrtowc");
+ res = mbrtowc(NULL, input, bytes, &state);
+ EXPECT("mbrtowc (pwc == NULL)");
+
+ // Check mbtowc().
+ assert(mbtowc(NULL, NULL, -1) == 0);
+ res = mbtowc(&wc, input, bytes);
+ EXPECT("mbtowc");
+ res = mbtowc(NULL, input, bytes);
+ EXPECT("mbtowc (pwc == NULL)");
+}
+
+int main() {
+ setlocale(LC_ALL, "C.UTF-8");
+
+#define verify_decode(in, out) verify_decode(in, out, __LINE__)
+ verify_decode("\x24", 0x24);
+ verify_decode("\xC2\xA2", 0xA2);
+ verify_decode("\xE0\xA4\xB9", 0x939);
+ verify_decode("\xE2\x82\xAC", 0x20AC);
+ verify_decode("\xED\x95\x9C", 0xD55C);
+ verify_decode("\xF0\x90\x8D\x88", 0x10348);
+ verify_decode("", L'\0');
+}
diff --git a/lib/mlibc/tests/ansi/wcsdup.c b/lib/mlibc/tests/ansi/wcsdup.c
new file mode 100644
index 0000000..564b77e
--- /dev/null
+++ b/lib/mlibc/tests/ansi/wcsdup.c
@@ -0,0 +1,11 @@
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+int main() {
+ wchar_t *wide_string = L"test string\n";
+ wchar_t *dup_string = wcsdup(wide_string);
+ assert(!memcmp(wide_string, dup_string, (wcslen(wide_string) + 1) * sizeof(wchar_t)));
+ free(dup_string);
+}
diff --git a/lib/mlibc/tests/ansi/wcsncasecmp.c b/lib/mlibc/tests/ansi/wcsncasecmp.c
new file mode 100644
index 0000000..fcbe98f
--- /dev/null
+++ b/lib/mlibc/tests/ansi/wcsncasecmp.c
@@ -0,0 +1,11 @@
+#include <assert.h>
+#include <wchar.h>
+
+int main() {
+ assert(!wcsncasecmp(L"foo", L"foo", 3));
+ assert(!wcsncasecmp(L"foo", L"FOO", 3));
+ assert(!wcsncasecmp(L"fooa", L"FOOB", 3));
+ assert(wcsncasecmp(L"foo", L"bar", 3));
+ assert(!wcsncasecmp(L"fooa", L"FOOA", 4));
+ assert(!wcsncasecmp(L"123abc", L"123AbC", 6));
+}
diff --git a/lib/mlibc/tests/ansi/wcsrtombs.c b/lib/mlibc/tests/ansi/wcsrtombs.c
new file mode 100644
index 0000000..80df257
--- /dev/null
+++ b/lib/mlibc/tests/ansi/wcsrtombs.c
@@ -0,0 +1,12 @@
+#include <stdlib.h>
+#include <wchar.h>
+#include <assert.h>
+#include <string.h>
+
+int main() {
+ const wchar_t str[] = L"wcsrtombs";
+ const wchar_t *p = str;
+ int ret = wcsrtombs(NULL, &p, 343245234, NULL);
+ assert(ret == 9);
+ return 0;
+}
diff --git a/lib/mlibc/tests/ansi/wmemcmp.c b/lib/mlibc/tests/ansi/wmemcmp.c
new file mode 100644
index 0000000..74de9bd
--- /dev/null
+++ b/lib/mlibc/tests/ansi/wmemcmp.c
@@ -0,0 +1,19 @@
+#include <assert.h>
+#include <wchar.h>
+#include <stdlib.h>
+
+int main(void){
+ wchar_t *ws1 = calloc(10, sizeof(wchar_t));
+ wchar_t *ws2 = calloc(10, sizeof(wchar_t));
+
+ mbstowcs(ws1, "Test 1", 10);
+ mbstowcs(ws2, "Tester 2", 10);
+
+ assert(wmemcmp(ws1, ws2, 10) < 0);
+ assert(wmemcmp(ws2, ws1, 10) > 0);
+ assert(wmemcmp(ws2, ws2, 10) == 0);
+
+ free(ws1);
+ free(ws2);
+ return 0;
+}
diff --git a/lib/mlibc/tests/bsd/ns_get_put.c b/lib/mlibc/tests/bsd/ns_get_put.c
new file mode 100644
index 0000000..d64bb2a
--- /dev/null
+++ b/lib/mlibc/tests/bsd/ns_get_put.c
@@ -0,0 +1,21 @@
+#include <assert.h>
+#include <arpa/nameser.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+int main() {
+ uint8_t *buf = malloc(256);
+ uint16_t original16 = 4891;
+ uint32_t original32 = 4861984;
+ memset(buf, 0x0, sizeof(*buf));
+ ns_put16(original16, buf);
+ uint16_t result16 = ns_get16(buf);
+ assert(result16 == original16);
+ memset(buf, 0x0, sizeof(*buf));
+ ns_put32(original32, buf);
+ uint32_t result32 = ns_get32(buf);
+ assert(result32 == original32);
+ free(buf);
+ return 0;
+}
diff --git a/lib/mlibc/tests/bsd/reallocarray.c b/lib/mlibc/tests/bsd/reallocarray.c
new file mode 100644
index 0000000..905d68d
--- /dev/null
+++ b/lib/mlibc/tests/bsd/reallocarray.c
@@ -0,0 +1,7 @@
+#include <stdlib.h>
+
+int main() {
+ void *ret = reallocarray(NULL, 69, 0xCB7);
+ free(ret);
+ return !ret;
+}
diff --git a/lib/mlibc/tests/bsd/sbrk.c b/lib/mlibc/tests/bsd/sbrk.c
new file mode 100644
index 0000000..88b0407
--- /dev/null
+++ b/lib/mlibc/tests/bsd/sbrk.c
@@ -0,0 +1,9 @@
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int main() {
+ void *ret = sbrk(0);
+ assert(ret != (void *) -1);
+ assert(ret);
+}
diff --git a/lib/mlibc/tests/bsd/strl.c b/lib/mlibc/tests/bsd/strl.c
new file mode 100644
index 0000000..a502850
--- /dev/null
+++ b/lib/mlibc/tests/bsd/strl.c
@@ -0,0 +1,56 @@
+#include <assert.h>
+#include <string.h>
+
+int main() {
+ char buf[256];
+
+ /* Test copy to 0-sized buffer . */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcpy(buf, "xyz", 0) == 3);
+ assert(strcmp(buf, "") == 0);
+
+ /* Test normal copy. */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcpy(buf, "xyz", sizeof(buf)) == 3);
+ assert(strcmp(buf, "xyz") == 0);
+
+ /* Test truncated copy. */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcpy(buf, "abcdefabcdef", 10) == 12);
+ assert(strcmp(buf, "abcdefabc") == 0);
+
+ /* Test concat to 0-sized buffer. */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcat(buf, "abc", 0) == 3);
+ assert(strcmp(buf, "") == 0);
+
+ /* Test concat to full buffer. */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcat(buf, "abcde", 6) == 5);
+ assert(strcmp(buf, "abcde") == 0);
+ assert(strlcat(buf, "xyz", 5) == 8);
+ assert(strcmp(buf, "abcde") == 0);
+
+ /* Test normal concat. */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcat(buf, "abc", sizeof(buf)) == 3);
+ assert(strcmp(buf, "abc") == 0);
+ assert(strlcat(buf, "xyz", sizeof(buf)) == 6);
+ assert(strcmp(buf, "abcxyz") == 0);
+
+ /* Test truncated concat. */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcat(buf, "abcabc", 10) == 6);
+ assert(strcmp(buf, "abcabc") == 0);
+ assert(strlcat(buf, "xyzxyz", 10) == 12);
+ assert(strcmp(buf, "abcabcxyz") == 0);
+
+ /* Test truncated concat w/ truncated dst. */
+ memset(buf, 0, sizeof(buf));
+ assert(strlcat(buf, "abcabc", 10) == 6);
+ assert(strcmp(buf, "abcabc") == 0);
+ assert(strlcat(buf, "xyz", 4) == 7);
+ assert(strcmp(buf, "abcabc") == 0);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/glibc/error.c b/lib/mlibc/tests/glibc/error.c
new file mode 100644
index 0000000..2ae752a
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error.c
@@ -0,0 +1,6 @@
+#include <errno.h>
+#include <error.h>
+
+int main() {
+ error(0, EINVAL, "test: %s", "error");
+}
diff --git a/lib/mlibc/tests/glibc/error.py b/lib/mlibc/tests/glibc/error.py
new file mode 100644
index 0000000..3833974
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error.py
@@ -0,0 +1,11 @@
+import subprocess
+import sys
+import os
+from pyexpect import expect
+
+wrapper = os.getenv("MESON_EXE_WRAPPER")
+wrapper = [x for x in (wrapper,) if x]
+
+output = subprocess.check_output(wrapper + [sys.argv[1]], stderr=subprocess.STDOUT)
+
+expect(bytes(sys.argv[1], 'utf-8') + b': test: error: Invalid argument (EINVAL)\n').to_equal(output)
diff --git a/lib/mlibc/tests/glibc/error_at_line.c b/lib/mlibc/tests/glibc/error_at_line.c
new file mode 100644
index 0000000..257ddd2
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error_at_line.c
@@ -0,0 +1,6 @@
+#include <errno.h>
+#include <error.h>
+
+int main() {
+ error_at_line(0, EINVAL, "error_at_line", 5, "test: %s", "error");
+}
diff --git a/lib/mlibc/tests/glibc/error_at_line.py b/lib/mlibc/tests/glibc/error_at_line.py
new file mode 100644
index 0000000..7499e05
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error_at_line.py
@@ -0,0 +1,11 @@
+import subprocess
+import sys
+import os
+from pyexpect import expect
+
+wrapper = os.getenv("MESON_EXE_WRAPPER")
+wrapper = [x for x in (wrapper,) if x]
+
+output = subprocess.check_output(wrapper + [sys.argv[1]], stderr=subprocess.STDOUT)
+
+expect(bytes(sys.argv[1], 'utf-8') + b':error_at_line:5: test: error: Invalid argument (EINVAL)\n').to_equal(output)
diff --git a/lib/mlibc/tests/glibc/error_expect_fail.c b/lib/mlibc/tests/glibc/error_expect_fail.c
new file mode 100644
index 0000000..17f0907
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error_expect_fail.c
@@ -0,0 +1,6 @@
+#include <errno.h>
+#include <error.h>
+
+int main() {
+ error(1, EINVAL, "test error #1");
+}
diff --git a/lib/mlibc/tests/glibc/error_message_count.c b/lib/mlibc/tests/glibc/error_message_count.c
new file mode 100644
index 0000000..c2c694a
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error_message_count.c
@@ -0,0 +1,11 @@
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+
+int main() {
+ assert(error_message_count == 0);
+ error(0, EINVAL, "test error #1");
+ assert(error_message_count == 1);
+ error_at_line(0, EINVAL, __FILE__, __LINE__, "test error #2");
+ assert(error_message_count == 2);
+}
diff --git a/lib/mlibc/tests/glibc/error_one_per_line.c b/lib/mlibc/tests/glibc/error_one_per_line.c
new file mode 100644
index 0000000..98721ec
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error_one_per_line.c
@@ -0,0 +1,13 @@
+#include <assert.h>
+#include <errno.h>
+#include <error.h>
+
+int main() {
+ assert(error_one_per_line == 0);
+ assert(error_message_count == 0);
+ error_one_per_line = 1;
+ error_at_line(0, EINVAL, __FILE__, 0, "test error #1");
+ assert(error_message_count == 1);
+ error_at_line(0, EINVAL, __FILE__, 0, "test error #2");
+ assert(error_message_count == 1);
+}
diff --git a/lib/mlibc/tests/glibc/error_print_progname.c b/lib/mlibc/tests/glibc/error_print_progname.c
new file mode 100644
index 0000000..8840cb2
--- /dev/null
+++ b/lib/mlibc/tests/glibc/error_print_progname.c
@@ -0,0 +1,19 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <errno.h>
+#include <error.h>
+
+bool ran = false;
+
+void test_progname(void) {
+ ran = true;
+ fprintf(stderr, "progname test");
+}
+
+int main() {
+ assert(error_print_progname == NULL);
+ error_print_progname = &test_progname;
+ error(0, EINVAL, "test error #1");
+ assert(ran == true);
+}
diff --git a/lib/mlibc/tests/glibc/ffsl-ffsll.c b/lib/mlibc/tests/glibc/ffsl-ffsll.c
new file mode 100644
index 0000000..ed70ad6
--- /dev/null
+++ b/lib/mlibc/tests/glibc/ffsl-ffsll.c
@@ -0,0 +1,36 @@
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <limits.h>
+
+int main(void){
+ // ffsl
+ assert(ffsl(0x8000) == 16);
+ assert(ffsl(0) == 0);
+#if UINTPTR_MAX == UINT64_MAX
+ assert(ffsl(LLONG_MAX - 1) == 2);
+ assert(ffsl(LLONG_MAX) == 1);
+#endif
+ assert(ffsl(LONG_MIN) == (long)(sizeof(long) * CHAR_BIT));
+ assert(ffsl(LONG_MIN + 1) == 1);
+
+ for (int i = 1; i < 0x1000; i++) {
+ assert(ffsl(i) - 1 == __builtin_ctz(i));
+ }
+
+ // ffsll
+ assert(ffsll(0x8000) == 16);
+ assert(ffsll(0) == 0);
+#if UINTPTR_MAX == UINT64_MAX
+ assert(ffsll(LLONG_MAX - 1) == 2);
+ assert(ffsll(LLONG_MAX) == 1);
+#endif
+ assert(ffsll(LLONG_MIN) == (long long)(sizeof(long long) * CHAR_BIT));
+ assert(ffsll(LLONG_MIN + 1) == 1);
+
+ for (int i = 1; i < 0x1000; i++) {
+ assert(ffsll(i) - 1 == __builtin_ctz(i));
+ }
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/glibc/getopt.c b/lib/mlibc/tests/glibc/getopt.c
new file mode 100644
index 0000000..a0e27bb
--- /dev/null
+++ b/lib/mlibc/tests/glibc/getopt.c
@@ -0,0 +1,288 @@
+#include <assert.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
+#define dump(x) fprintf(stderr, "getopt c '%c' (%d), optind %d ('%s'), optarg '%s', optopt '%c' (%d)\n", \
+ (x), (x), optind, (size_t)optind >= COUNT_OF(test_argv) ? "out of range" : test_argv[optind], optarg, optopt, optopt);
+
+void test1() {
+ const char *shortopts = "b:cdef";
+
+ const struct option longopts[] = {
+ {"foo", required_argument, NULL, 'f'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ int test_argc = 7;
+
+ char *test_argv[] = {
+ "dummy",
+ "--foo",
+ "abc",
+ "-b",
+ "abc",
+ "abc",
+ "abc"
+ };
+
+ optind = 0;
+ int c = getopt_long(test_argc, test_argv, shortopts, longopts, NULL);
+ dump(c)
+ assert(c == 'f');
+
+ c = getopt_long(test_argc, test_argv, shortopts, longopts, NULL);
+ dump(c)
+ assert(optarg && !strcmp(optarg, "abc"));
+ assert(c == 'b');
+
+ c = getopt_long(test_argc, test_argv, shortopts, longopts, NULL);
+ dump(c);
+ assert(c == -1);
+ assert(optarg == NULL);
+
+ // we have 2 non-option arguments
+ assert((optind + 2) == test_argc);
+}
+
+void test2() {
+ const struct option longopts[] = {
+ {"foo", required_argument, NULL, 'f'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ char *test_argv[] = {
+ "dummy",
+ "-c",
+ };
+
+ fputs("Situation: we pass a non-existant short option in argv\n", stderr);
+
+ optind = 0;
+ int c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:", longopts, NULL);
+ // Exprected result: return '?', optopt is set to the offender
+ assert(c == '?');
+ assert(optopt == 'c');
+
+ fputs("Situation: we pass a non-existant short option in argv, while passing a leading colon in optstring\n", stderr);
+ optind = 0;
+
+ c = getopt_long(COUNT_OF(test_argv), test_argv, ":ab:", longopts, NULL);
+ // Exprected result: return '?', optopt is set to the offender
+ assert(c == '?');
+ assert(optopt == 'c');
+
+ fputs("Situation: we omit the required arg to a short option\n", stderr);
+ optind = 0;
+
+ c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL);
+ // Exprected result: return '?', optopt is set to the offender
+ assert(c == '?');
+ assert(optopt == 'c');
+
+ fputs("Situation: we omit the required arg to a short option, while passing a leading colon in optstring\n", stderr);
+ optind = 0;
+
+ c = getopt_long(COUNT_OF(test_argv), test_argv, ":ab:c:", longopts, NULL);
+ // Exprected result: return ':', optopt is set to the offender
+ assert(c == ':');
+ assert(optopt == 'c');
+}
+
+void test3() {
+ const struct option longopts[] = {
+ {"foo", required_argument, NULL, 'f'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ char *test_argv[] = {
+ "dummy",
+ "-cmanagarm",
+ };
+
+ fputs("Situation: we pass a concatenated argument to a short option\n", stderr);
+
+ optind = 0;
+ int c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL);
+ dump(c);
+ // Exprected result:
+ assert(c == 'c');
+ assert(!strcmp(optarg, "managarm"));
+}
+
+void test4() {
+ const struct option longopts[] = {
+ {"foo", required_argument, NULL, 'f'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ char *test_argv[] = {
+ "dummy",
+ "-acmanagarm",
+ };
+
+ fputs("Situation: we pass concatenated short options to getopt\n", stderr);
+
+ optind = 0;
+ int c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL);
+ assert(c == 'a');
+
+ c = getopt_long(COUNT_OF(test_argv), test_argv, "ab:c:", longopts, NULL);
+ // Exprected result:
+ assert(c == 'c');
+ assert(!strcmp(optarg, "managarm"));
+}
+
+void test5() {
+ const struct option longopts[] = {
+ {"foo", required_argument, NULL, 'f'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ char *test_argv[] = {
+ "su",
+ "-",
+ "managarm",
+ 0
+ };
+
+ int test_argc = 3;
+
+ fputs("Situation: testing `su - managarm`\n", stderr);
+
+ optind = 0;
+ int c = getopt_long(test_argc, test_argv, "ab:", longopts, NULL);
+ dump(c);
+ assert(c == -1);
+
+ if (optind < test_argc && !strcmp(test_argv[optind], "-")) {
+ ++optind;
+ }
+
+ assert(optind < test_argc);
+ assert(!strcmp(test_argv[optind++], "managarm"));
+ assert(optind == test_argc);
+}
+
+void test6() {
+ char *test_argv[] = {
+ "telescope",
+ "gemini://electrode.codes",
+ "-S",
+ 0
+ };
+
+ int test_argc = 3;
+ optind = 0;
+
+ const struct option longopts[] = {
+ {"colors", no_argument, NULL, 'C'},
+ {"colours", no_argument, NULL, 'C'},
+ {"help", no_argument, NULL, 'h'},
+ {"safe", no_argument, NULL, 'S'},
+ {"version", no_argument, NULL, 'v'},
+ {NULL, 0, NULL, 0},
+ };
+
+ int c = getopt_long(test_argc, test_argv, "Cc:hnST:v", longopts, NULL);
+ dump(c);
+ assert(c == 'S');
+ assert(optind == 3);
+ assert(!optarg);
+}
+
+void test7() {
+ int c;
+
+ static const struct option options[] = {
+ { "debug", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ {}
+ };
+ const char *command;
+
+ char *test_argv[] = {
+ "udevadm",
+ "hwdb",
+ "--update",
+ 0
+ };
+
+ int test_argc = 3;
+ setenv("POSIXLY_CORRECT", "1", 1);
+ optind = 0;
+
+ while ((c = getopt_long(test_argc, test_argv, "+dhV", options, NULL)) >= 0) {
+ switch (c) {
+ case 'd':
+ break;
+ case 'h':
+ break;
+ case 'V':
+ break;
+ default:
+ break;
+ }
+ }
+
+ dump(c);
+ command = test_argv[optind];
+ assert(command);
+ assert(!strcmp(command, "hwdb"));
+}
+
+void test8() {
+ const char *shortopts = "b:cde";
+
+ int foo = false;
+ int bar = false;
+
+ const struct option longopts[] = {
+ {"foo", required_argument, &foo, 'x'},
+ {"bar", required_argument, &bar, 'y'},
+ {NULL, no_argument, NULL, 0}
+ };
+
+ int test_argc = 6;
+
+ char *test_argv[] = {
+ "dummy",
+ "-foo",
+ "abc",
+ "-bar=def",
+ "ghi",
+ "jkl"
+ };
+
+ #ifdef __GLIBC__
+ optind = 0;
+ #else
+ optreset = 1;
+ #endif
+ optopt = 0;
+ int c = getopt_long_only(test_argc, test_argv, shortopts, longopts, NULL);
+ dump(c);
+ assert(foo && !c);
+ assert(optind == 3);
+
+ c = getopt_long_only(test_argc, test_argv, shortopts, longopts, NULL);
+ dump(c);
+ assert(bar && !c);
+ assert(optind == 4);
+}
+
+int main() {
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+ test6();
+ test7();
+ test8();
+}
diff --git a/lib/mlibc/tests/glibc/gnu-basename.c b/lib/mlibc/tests/glibc/gnu-basename.c
new file mode 100644
index 0000000..9566532
--- /dev/null
+++ b/lib/mlibc/tests/glibc/gnu-basename.c
@@ -0,0 +1,13 @@
+#define _GNU_SOURCE
+#include <assert.h>
+#include <string.h>
+
+#define test_string(x, expectval) assert(strcmp(basename(x), expectval) == 0)
+
+int main() {
+ test_string("/usr/lib", "lib");
+ test_string("/usr/", "");
+ test_string("/", "");
+ test_string(".", ".");
+ test_string("..", "..");
+}
diff --git a/lib/mlibc/tests/glibc/linux-syscall.c b/lib/mlibc/tests/glibc/linux-syscall.c
new file mode 100644
index 0000000..2760df4
--- /dev/null
+++ b/lib/mlibc/tests/glibc/linux-syscall.c
@@ -0,0 +1,14 @@
+#include <sys/syscall.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+int main() {
+ char *data = "hello mlibc\n";
+ long ret = syscall(SYS_write, 1, data, strlen(data));
+ assert(ret == (long)strlen(data));
+
+ syscall(SYS_exit, 0);
+ abort();
+}
diff --git a/lib/mlibc/tests/linux/cpuset.c b/lib/mlibc/tests/linux/cpuset.c
new file mode 100644
index 0000000..3f55732
--- /dev/null
+++ b/lib/mlibc/tests/linux/cpuset.c
@@ -0,0 +1,27 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <sched.h>
+
+#define SET_SIZE 15
+
+int main() {
+ cpu_set_t *set = CPU_ALLOC(SET_SIZE);
+ size_t setsize = CPU_ALLOC_SIZE(SET_SIZE);
+
+ CPU_ZERO_S(setsize, set);
+
+ assert(!CPU_ISSET_S(11, setsize, set));
+
+ CPU_SET_S(11, setsize, set);
+ assert(CPU_ISSET_S(11, setsize, set));
+ assert(CPU_COUNT_S(setsize, set) == 1);
+
+ assert(!CPU_ISSET_S(CPU_SETSIZE - 1, setsize, set));
+
+ CPU_CLR_S(11, setsize, set);
+ assert(!CPU_ISSET_S(11, setsize, set));
+
+ CPU_FREE(set);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/linux/malloc-usable-size.c b/lib/mlibc/tests/linux/malloc-usable-size.c
new file mode 100644
index 0000000..3bb0394
--- /dev/null
+++ b/lib/mlibc/tests/linux/malloc-usable-size.c
@@ -0,0 +1,13 @@
+#include <assert.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+ void *p1 = malloc(1023);
+ fprintf(stderr, "size: %zu\n", malloc_usable_size(p1));
+ assert(malloc_usable_size(p1) >= 1023);
+ free(p1);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/linux/pthread_attr.c b/lib/mlibc/tests/linux/pthread_attr.c
new file mode 100644
index 0000000..4c1907c
--- /dev/null
+++ b/lib/mlibc/tests/linux/pthread_attr.c
@@ -0,0 +1,90 @@
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+
+static void test_affinity() {
+ pthread_attr_t attr;
+ cpu_set_t set = {0};
+ assert(!pthread_attr_init(&attr));
+ assert(!pthread_attr_setaffinity_np(&attr, 1, &set));
+
+ cpu_set_t other_set = {0};
+ assert(!pthread_attr_getaffinity_np(&attr, 1, &set));
+
+ assert(!memcmp(&set, &other_set, sizeof(cpu_set_t)));
+
+ pthread_attr_destroy(&attr);
+}
+
+#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32)
+static void test_sigmask() {
+ pthread_attr_t attr;
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGUSR1);
+#ifndef USE_HOST_LIBC
+ sigaddset(&set, SIGCANCEL);
+#endif
+
+ assert(!pthread_attr_init(&attr));
+ assert(!pthread_attr_setsigmask_np(&attr, &set));
+
+ sigset_t other_set;
+ sigemptyset(&other_set);
+
+ assert(!pthread_attr_getsigmask_np(&attr, &other_set));
+
+ assert(sigismember(&other_set, SIGUSR1));
+#ifndef USE_HOST_LIBC
+ // Test whether internal signals get filtered properly.
+ assert(!sigismember(&other_set, SIGCANCEL));
+#endif
+
+ pthread_attr_destroy(&attr);
+}
+
+static void *getattr_worker(void *arg) {
+ (void)arg;
+ return NULL;
+}
+
+static void test_getattrnp() {
+ pthread_attr_t attr;
+ size_t stacksize = PTHREAD_STACK_MIN;
+ assert(!pthread_attr_init(&attr));
+ assert(!pthread_attr_setstacksize(&attr, stacksize));
+
+ pthread_t thread;
+ assert(!pthread_create(&thread, &attr, getattr_worker, NULL));
+ assert(!pthread_getattr_np(thread, &attr));
+ size_t other_stacksize;
+ assert(!pthread_attr_getstacksize(&attr, &other_stacksize));
+ assert(other_stacksize == stacksize);
+ assert(!pthread_join(thread, NULL));
+
+ pthread_t own_thread = pthread_self();
+ void *stack;
+ assert(!pthread_getattr_np(own_thread, &attr));
+ assert(!pthread_attr_getstack(&attr, &stack, &other_stacksize));
+ assert(stack);
+ assert(other_stacksize);
+ // Check that we can read from the highest byte returned.
+ // pthread_getattr_np() should return the lowest byte
+ // of the stack.
+ printf("highest byte: %hhu\n", *(char *)(stack + other_stacksize - 1));
+
+ pthread_attr_destroy(&attr);
+}
+#endif // !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32)
+
+int main() {
+ test_affinity();
+#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32)
+ test_sigmask();
+ test_getattrnp();
+#endif
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/linux/pthread_setname_np.c b/lib/mlibc/tests/linux/pthread_setname_np.c
new file mode 100644
index 0000000..95c95c9
--- /dev/null
+++ b/lib/mlibc/tests/linux/pthread_setname_np.c
@@ -0,0 +1,33 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+
+int main() {
+ int ret = pthread_setname_np(pthread_self(), "mlibc-test-123");
+ assert(!ret);
+
+ char buf[16];
+ ret = pthread_getname_np(pthread_self(), buf, 16);
+ assert(!ret);
+ assert(!strcmp("mlibc-test-123", buf));
+
+ ret = pthread_setname_np(pthread_self(), "mlibc-test-123-too-long");
+ assert(ret == ERANGE);
+
+ ret = pthread_getname_np(pthread_self(), buf, 1);
+ assert(ret == ERANGE);
+
+ ret = pthread_getname_np(pthread_self(), buf, 15);
+ assert(ret == ERANGE);
+
+ ret = pthread_getname_np(pthread_self(), buf, 16);
+ assert(!ret);
+ assert(!strcmp("mlibc-test-123", buf));
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/linux/xattr.c b/lib/mlibc/tests/linux/xattr.c
new file mode 100644
index 0000000..c6dd152
--- /dev/null
+++ b/lib/mlibc/tests/linux/xattr.c
@@ -0,0 +1,80 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/xattr.h>
+
+#define assert_perror(x) \
+ ({ \
+ int res = (x); \
+ if (res < 0) { \
+ perror(#x); \
+ return 1; \
+ } \
+ res; \
+ })
+
+#define soft_assert(x, m) \
+ ({ \
+ if (!(x)) { \
+ fprintf(stderr, "cond \"%s\" failed: %s\n", #x, m); \
+ return 1; \
+ } \
+ })
+
+void remove_tmp(const char (*fname)[]) {
+ unlink(&(*fname)[0]);
+}
+#define _cleanup_file_ __attribute__((cleanup(remove_tmp)))
+
+int main(void) {
+ int ret;
+ char buf[32] = { 0 };
+ _cleanup_file_ char filename[] = "xattr_test.XXXXXX";
+ _cleanup_file_ char filename2[] = "xattr_test.XXXXXX.2";
+
+ int tmpfile = assert_perror(mkstemp(filename));
+ memcpy(filename2, filename, sizeof(filename) - 1);
+ assert_perror(symlink(filename, filename2));
+
+ assert_perror(setxattr(filename, "user.T1", "ABC", 3, XATTR_CREATE));
+ assert_perror(fsetxattr(tmpfile, "user.T2", "DEF", 3, XATTR_CREATE));
+
+ // for testing remove
+ assert_perror(fsetxattr(tmpfile, "user.T3", "DEF", 3, XATTR_CREATE));
+
+ if ((ret = getxattr(filename, "user.T1", buf, 3)) != 3) {
+ if (ret < 0) {
+ perror("getxattr");
+ return 1;
+ }
+
+ soft_assert(memcmp(buf, "ABC", 3) == 0, "xattr read wrong");
+ }
+
+ ret = lgetxattr(filename2, "user.T1", buf, 3);
+ soft_assert(ret < 0 && errno == ENODATA, "lgetxattr deref'd");
+
+ if ((ret = fgetxattr(tmpfile, "user.T3", buf, 3)) != 3) {
+ if (ret < 0) {
+ perror("fgetxattr");
+ return 1;
+ }
+
+ soft_assert(memcmp(buf, "DEF", 3) == 0, "xattr read wrong");
+ }
+
+ assert_perror(removexattr(filename, "user.T2"));
+ assert_perror(fremovexattr(tmpfile, "user.T3"));
+
+ ret = assert_perror(listxattr(filename, buf, sizeof(buf) - 1));
+ soft_assert(memmem(buf, ret, "user.T1", 7), "user.T1 not found");
+ soft_assert(!memmem(buf, ret, "user.T2", 7), "user.T2 found");
+ soft_assert(!memmem(buf, ret, "user.T3", 7), "user.T3 found");
+
+ ret = assert_perror(flistxattr(tmpfile, buf, sizeof(buf) - 1));
+ soft_assert(memmem(buf, ret, "user.T1", 7), "user.T1 not found");
+ soft_assert(!memmem(buf, ret, "user.T2", 7), "user.T2 found");
+ soft_assert(!memmem(buf, ret, "user.T3", 7), "user.T3 found");
+}
diff --git a/lib/mlibc/tests/meson.build b/lib/mlibc/tests/meson.build
new file mode 100644
index 0000000..b7d65ca
--- /dev/null
+++ b/lib/mlibc/tests/meson.build
@@ -0,0 +1,261 @@
+timeout_sec = 10
+
+all_test_cases = [
+ 'ansi/alloc',
+ 'ansi/sscanf',
+ 'ansi/sprintf',
+ 'ansi/snprintf',
+ 'ansi/utf8',
+ 'ansi/strtol',
+ 'ansi/strtof',
+ 'ansi/abs',
+ 'ansi/longjmp',
+ 'ansi/mbrtoc32',
+ 'ansi/strverscmp',
+ 'ansi/strftime',
+ 'ansi/strchr',
+ 'ansi/strrchr',
+ 'ansi/wcsrtombs',
+ 'ansi/wmemcmp',
+ 'ansi/timegm',
+ 'ansi/ungetc',
+ 'ansi/wcsdup',
+ 'ansi/wcsncasecmp',
+ 'ansi/fopen',
+ 'ansi/memmem',
+ 'ansi/creal-cimag',
+ 'ansi/fenv',
+ 'ansi/qsort',
+ 'ansi/freopen',
+ 'ansi/strxfrm',
+ 'ansi/calloc',
+ 'bsd/ns_get_put',
+ 'bsd/reallocarray',
+ 'bsd/strl',
+ 'bsd/sbrk',
+ 'posix/inet_ntop',
+ 'posix/inet_pton',
+ 'posix/access',
+ 'posix/pthread_barrier',
+ 'posix/pthread_rwlock',
+ 'posix/pthread_cond',
+ 'posix/pthread_create',
+ 'posix/pthread_cancel',
+ 'posix/pthread_atfork',
+ 'posix/pthread_cleanup',
+ 'posix/pthread_kill',
+ 'posix/pthread_mutex',
+ 'posix/pthread_key',
+ 'posix/pthread_thread_local',
+ 'posix/pthread_attr',
+ 'posix/pthread_schedparam',
+ 'posix/pwd',
+ 'posix/fdopen',
+ 'posix/fopencookie',
+ 'posix/fmemopen',
+ 'posix/getaddrinfo',
+ 'posix/getdelim',
+ 'posix/getnameinfo',
+ 'posix/getservbyname',
+ 'posix/getservbyport',
+ 'posix/grp',
+ 'posix/dprintf',
+ 'posix/posix_memalign',
+ 'posix/posix_spawn',
+ 'posix/index',
+ 'posix/rindex',
+ 'posix/search',
+ 'posix/open_memstream',
+ 'posix/popen',
+ 'posix/system', # This test should be in the ANSI tests, but it depends on sys/wait.h
+ 'posix/sigsuspend',
+ 'posix/sigaltstack',
+ 'posix/time',
+ 'posix/realpath',
+ 'posix/ffs',
+ 'posix/getcwd',
+ 'posix/memrchr',
+ 'posix/wordexp',
+ 'posix/rlimits',
+ 'posix/accept4',
+ 'posix/setpriority',
+ 'posix/alarm',
+ 'posix/abort', # This test should be in the ANSI tests, but it depends on sigaction
+ 'posix/timer',
+ 'posix/vfork',
+ 'posix/wcwidth',
+ 'posix/pause',
+ 'posix/flockfile',
+ 'posix/basename',
+ 'posix/regex',
+ 'posix/sigtimedwait',
+ 'posix/if_indextoname',
+ 'posix/readv-writev',
+ 'posix/posix-timer',
+ 'posix/strdupa',
+ 'posix/mkstemp',
+ 'posix/waitid',
+ 'glibc/getopt',
+ 'glibc/ffsl-ffsll',
+ 'glibc/error_message_count',
+ 'glibc/error_one_per_line',
+ 'glibc/error_print_progname',
+ 'glibc/error_expect_fail',
+ 'glibc/error',
+ 'glibc/error_at_line',
+ 'linux/xattr',
+ 'linux/pthread_setname_np',
+ 'linux/pthread_attr',
+ 'linux/cpuset',
+ 'linux/malloc-usable-size',
+]
+
+if host_machine.system() == 'linux'
+ all_test_cases += 'glibc/linux-syscall'
+endif
+
+fail_test_cases = [
+ 'posix/abort',
+ 'glibc/error_expect_fail',
+]
+
+wrapped_test_cases = [
+ 'glibc/error',
+ 'glibc/error_at_line',
+]
+
+host_libc_excluded_test_cases = [
+ 'bsd/strl', # These functions do not exist on Linux.
+]
+host_libc_noasan_test_cases = [
+ 'posix/pthread_cancel',
+ 'posix/pthread_attr', # does some stack overflowing to check stack size
+ 'posix/posix_memalign',
+ 'posix/search', # requires tdelete (#351)
+ 'ansi/calloc', # does some overflowing
+ 'linux/pthread_attr', # encounters memory leaks
+]
+
+extra_cflags_test_cases = {
+ 'ansi/calloc': ['-Wno-alloc-size-larger-than'],
+}
+
+test_sources = []
+test_link_args = []
+test_c_args = []
+use_pie = false
+
+test_c_args = []
+test_link_args = []
+
+# Our ubsan implementation can't be used by the tests themselves,
+# since it is internal to libc.so and ld.so.
+test_override_options = ['b_sanitize=none']
+
+if library_type == 'static'
+ libc_dep = declare_dependency(
+ include_directories: libc_include_dirs,
+ link_with: libc_static,
+ dependencies: [libc_deps, rtlib_deps]
+ )
+ use_pie = false
+ test_c_args += '-no-pie'
+ test_link_args += ['-no-pie', '-static']
+ test_sources += [
+ '../options/internal' / host_machine.cpu_family() / 'mlibc_crtbegin.S',
+ '../options/internal' / host_machine.cpu_family() / 'mlibc_crtend.S',
+ crt,
+ ]
+else
+ libc_dep = declare_dependency(
+ include_directories: libc_include_dirs,
+ link_with: libc_shared,
+ dependencies: [libc_deps, rtlib_deps]
+ )
+ test_link_args += ['-Wl,--dynamic-linker=' + meson.build_root() + '/ld.so']
+
+ if host_machine.system() in ['linux']
+ use_pie = true
+ test_sources += crt_pie
+ else
+ use_pie = false
+ test_sources += crt
+
+ # Meson doesn't set these for us (issue #4651).
+ test_c_args += '-no-pie'
+ test_link_args += '-no-pie'
+ endif
+
+ # Add the rtdl tests.
+ if not disable_posix_option
+ subdir('rtdl')
+ endif
+endif
+
+py = import('python').find_installation('python3')
+
+foreach test_name : all_test_cases
+ test_subdir = test_name.split('/')[0]
+ test_short_name = test_name.split('/')[1]
+ test_exec_name = test_name.replace('/', '-')
+
+ if test_subdir == 'ansi' and disable_ansi_option
+ continue
+ elif test_subdir == 'bsd' and disable_bsd_option
+ continue
+ elif test_subdir == 'glibc' and disable_glibc_option
+ continue
+ elif test_subdir == 'posix' and disable_posix_option
+ continue
+ elif test_subdir == 'linux' and disable_linux_option
+ continue
+ endif
+
+ should_fail = fail_test_cases.contains(test_name)
+ exec = executable(test_exec_name, [test_name + '.c', test_sources],
+ dependencies: libc_dep,
+ build_rpath: meson.build_root(),
+ override_options: test_override_options,
+ c_args: test_c_args,
+ link_args: test_link_args,
+ pie: use_pie,
+ )
+
+ if wrapped_test_cases.contains(test_name)
+ test(test_short_name,
+ py,
+ suite: test_subdir,
+ should_fail: should_fail,
+ args: [
+ meson.source_root() + '/tests/' + test_name + '.py',
+ exec
+ ],
+ timeout: timeout_sec,
+ )
+ else
+ test(test_short_name, exec, suite: test_subdir, should_fail: should_fail, timeout: timeout_sec)
+ endif
+
+ if build_tests_host_libc and not host_libc_excluded_test_cases.contains(test_name)
+ if test_name in host_libc_noasan_test_cases
+ host_libc_sanitize_options = 'b_sanitize=undefined'
+ else
+ host_libc_sanitize_options = 'b_sanitize=address,undefined'
+ endif
+
+ if test_name in extra_cflags_test_cases
+ extra_cflags = extra_cflags_test_cases[test_name]
+ else
+ extra_cflags = []
+ endif
+
+ exec = executable('host-libc-' + test_exec_name, test_name + '.c',
+ build_rpath: meson.build_root(),
+ override_options: host_libc_sanitize_options,
+ c_args: ['-D_GNU_SOURCE', '-DUSE_HOST_LIBC', '-pthread', extra_cflags],
+ link_args: ['-lresolv', '-ldl', '-pthread', '-lm', '-lrt'],
+ native: true,
+ )
+ test(test_short_name, exec, suite: ['host-libc', test_subdir], should_fail: should_fail, timeout: timeout_sec)
+ endif
+endforeach
diff --git a/lib/mlibc/tests/posix/abort.c b/lib/mlibc/tests/posix/abort.c
new file mode 100644
index 0000000..5f7bb7d
--- /dev/null
+++ b/lib/mlibc/tests/posix/abort.c
@@ -0,0 +1,21 @@
+#include <signal.h>
+#include <assert.h>
+#include <stdlib.h>
+
+void handler(int sig, siginfo_t *info, void *ctx) {
+ (void)sig;
+ (void)info;
+ (void)ctx;
+}
+
+int main() {
+ struct sigaction sa;
+ sa.sa_sigaction = handler;
+ sa.sa_flags = SA_SIGINFO;
+ sigemptyset(&sa.sa_mask);
+
+ int ret = sigaction(SIGABRT, &sa, NULL);
+ assert(!ret);
+
+ abort();
+}
diff --git a/lib/mlibc/tests/posix/accept4.c b/lib/mlibc/tests/posix/accept4.c
new file mode 100644
index 0000000..ba593ee
--- /dev/null
+++ b/lib/mlibc/tests/posix/accept4.c
@@ -0,0 +1,123 @@
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_PORT 31337
+#else
+#define TEST_PORT 42069
+#endif
+
+static struct sockaddr_in connect_addr, accept_addr;
+static int listen_fd;
+
+int permutations[] = {
+ 0,
+ SOCK_CLOEXEC,
+ SOCK_NONBLOCK,
+ SOCK_CLOEXEC | SOCK_NONBLOCK,
+};
+
+static bool run_test(int flags)
+{
+ int connect_fd;
+ int fd_flags, access;
+ socklen_t addrlen;
+
+ connect_fd = socket(AF_INET, SOCK_STREAM, 0);
+ connect(connect_fd, (struct sockaddr *)&connect_addr, sizeof(connect_addr));
+ addrlen = sizeof(accept_addr);
+
+ int accept_fd = accept4(listen_fd, (struct sockaddr *)&accept_addr, &addrlen, flags);
+
+ if(accept_fd == -1) {
+ fprintf(stderr, "accept4 failed: %s\n", strerror(errno));
+ goto cleanup;
+ }
+
+ fd_flags = fcntl(accept_fd, F_GETFD);
+ if(!(!!(fd_flags & FD_CLOEXEC) == !!(flags & SOCK_CLOEXEC))) {
+ fprintf(stderr, "CLOEXEC mismatch, got %d instead of %d\n", !!(fd_flags & FD_CLOEXEC), !!(flags & SOCK_CLOEXEC));
+ goto cleanup;
+ }
+
+ access = fcntl(accept_fd, F_GETFL);
+ if(!(!!(access & O_NONBLOCK) == !!(flags & SOCK_NONBLOCK))) {
+ fprintf(stderr, "NONBLOCK flag mismatch, %d vs %d\n", !!(access & O_NONBLOCK), !!(flags & SOCK_NONBLOCK));
+ goto cleanup;
+ }
+
+ close(accept_fd);
+ close(connect_fd);
+
+ fprintf(stderr, "tested CLOEXEC %d, NONBLOCK %d\n", !!(flags & SOCK_CLOEXEC), !!(flags & SOCK_NONBLOCK));
+ return true;
+
+cleanup:
+ close(accept_fd);
+ close(connect_fd);
+ return false;
+}
+
+static int socket_setup(void)
+{
+ struct sockaddr_in addr;
+ int reuseaddr;
+
+ memset(&addr, 0, sizeof(struct sockaddr_in));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(TEST_PORT);
+
+ int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+
+ if(socket_fd == -1) {
+ fprintf(stderr, "socket failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ reuseaddr = 1;
+ int ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
+
+ if(ret == -1) {
+ fprintf(stderr, "setsockopt failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ ret = bind(socket_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
+
+ if(ret == -1) {
+ fprintf(stderr, "bind failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ ret = listen(socket_fd, 5);
+
+ if(ret == -1) {
+ fprintf(stderr, "listen failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ return socket_fd;
+}
+
+int main() {
+ memset(&connect_addr, 0, sizeof(connect_addr));
+ connect_addr.sin_family = AF_INET;
+ connect_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ connect_addr.sin_port = htons(TEST_PORT);
+
+ for(size_t i = 0; i < 4; i++) {
+ listen_fd = socket_setup();
+ if(!run_test(permutations[i])) {
+ exit(1);
+ }
+ close(listen_fd);
+ }
+}
diff --git a/lib/mlibc/tests/posix/access.c b/lib/mlibc/tests/posix/access.c
new file mode 100644
index 0000000..e0c9f45
--- /dev/null
+++ b/lib/mlibc/tests/posix/access.c
@@ -0,0 +1,30 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <fcntl.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "access-host-libc.tmp"
+#else
+#define TEST_FILE "access.tmp"
+#endif
+
+int main() {
+ // Make sure that it wasn't created by a previous test run
+ unlink(TEST_FILE);
+
+ assert(access(TEST_FILE, F_OK) == -1);
+ assert(access(TEST_FILE, W_OK) == -1);
+ assert(access(TEST_FILE, R_OK) == -1);
+ assert(access(TEST_FILE, X_OK) == -1);
+
+ close(open(TEST_FILE, O_CREAT | O_RDWR, 0666));
+
+ assert(access(TEST_FILE, F_OK) == 0);
+ assert(access(TEST_FILE, W_OK) == 0);
+ assert(access(TEST_FILE, R_OK) == 0);
+ assert(access(TEST_FILE, X_OK) == -1);
+
+ unlink(TEST_FILE);
+}
diff --git a/lib/mlibc/tests/posix/alarm.c b/lib/mlibc/tests/posix/alarm.c
new file mode 100644
index 0000000..b8a43e3
--- /dev/null
+++ b/lib/mlibc/tests/posix/alarm.c
@@ -0,0 +1,34 @@
+#include <signal.h>
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static volatile int alarms_fired = 0;
+
+static void sigalrm_handler(int signal) {
+ if(signal == SIGALRM)
+ alarms_fired++;
+}
+
+int main() {
+ signal(SIGALRM, sigalrm_handler);
+
+ alarms_fired = 0;
+
+ unsigned int ret = alarm(10);
+ assert(!ret);
+
+ sleep(1);
+
+ ret = alarm(1);
+ assert(ret == 9);
+
+ sleep(2);
+
+ if(alarms_fired != 1) {
+ fprintf(stderr, "alarm handler fired %u times instead of 1\n", alarms_fired);
+ exit(1);
+ }
+}
diff --git a/lib/mlibc/tests/posix/basename.c b/lib/mlibc/tests/posix/basename.c
new file mode 100644
index 0000000..260bf90
--- /dev/null
+++ b/lib/mlibc/tests/posix/basename.c
@@ -0,0 +1,17 @@
+#include <assert.h>
+#include <libgen.h>
+#include <string.h>
+
+int main() {
+ /* intentionally a macro: basename modifies its argument */
+#define test_string(x, expectval) do { \
+ char str[] = x; \
+ assert(strcmp(basename(str), expectval) == 0); \
+ } while (0)
+
+ test_string("/usr/lib", "lib");
+ test_string("/usr/", "usr");
+ test_string("/", "/");
+ test_string(".", ".");
+ test_string("..", "..");
+}
diff --git a/lib/mlibc/tests/posix/dprintf.c b/lib/mlibc/tests/posix/dprintf.c
new file mode 100644
index 0000000..4746edb
--- /dev/null
+++ b/lib/mlibc/tests/posix/dprintf.c
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "dprintf-host-libc.tmp"
+#else
+#define TEST_FILE "dprintf.tmp"
+#endif
+
+int main() {
+ int fd = open(TEST_FILE, O_RDWR | O_CREAT, 0666);
+ assert(fd > -1);
+
+ int ret = dprintf(fd, "aaa");
+ assert(ret == 3);
+
+ // Check if we can read back the same things.
+ // Also checks to see if the fd is still open,
+ // which is issue #199.
+
+ off_t seek = lseek(fd, 0, SEEK_SET);
+ assert(seek == 0);
+
+ char buf[3];
+ ssize_t num_read = read(fd, buf, 3);
+
+ assert(num_read == 3);
+ assert(buf[0] == 'a'
+ && buf[1] == 'a'
+ && buf[2] == 'a');
+
+ // All the tests pass, now we can unlink the file.
+ ret = unlink(TEST_FILE);
+ assert(ret == 0);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/fdopen.c b/lib/mlibc/tests/posix/fdopen.c
new file mode 100644
index 0000000..1066bfb
--- /dev/null
+++ b/lib/mlibc/tests/posix/fdopen.c
@@ -0,0 +1,37 @@
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define TEST_FILE "fdopen.tmp"
+
+int main() {
+ int fd = open(TEST_FILE, O_CREAT | O_RDWR, 0666);
+ assert(fd >= 0);
+
+ char *str = "mlibc fdopen test";
+ assert(write(fd, str, strlen(str)));
+
+ // Seek to the beginning, then reopen with fdopen in append mode.
+ lseek(fd, 0, SEEK_SET);
+ FILE *file = fdopen(fd, "a");
+ assert(file);
+
+ // Append and close.
+ str = " appended";
+ fwrite(str, strlen(str), 1, file);
+ fflush(file);
+ fclose(file);
+
+ // Open it again and check that the append succeeded.
+ fd = open(TEST_FILE, O_RDONLY);
+ assert(fd >= 0);
+ file = fdopen(fd, "r");
+ assert(file);
+ str = "mlibc fdopen test appended";
+ char buf[100] = {0};
+ assert(fread(buf, 1, strlen(str), file));
+ assert(!strcmp(buf, str));
+ fclose(file);
+}
diff --git a/lib/mlibc/tests/posix/ffs.c b/lib/mlibc/tests/posix/ffs.c
new file mode 100644
index 0000000..3f7ba84
--- /dev/null
+++ b/lib/mlibc/tests/posix/ffs.c
@@ -0,0 +1,18 @@
+#include <assert.h>
+#include <strings.h>
+#include <limits.h>
+
+int main(void){
+ assert(ffs(0x8000) == 16);
+ assert(ffs(0) == 0);
+ assert(ffs(INT_MAX - 1) == 2);
+ assert(ffs(INT_MAX) == 1);
+ assert(ffs(INT_MIN) == (int)(sizeof(int) * CHAR_BIT));
+ assert(ffs(INT_MIN + 1) == 1);
+
+ for (int i = 1; i < 0x1000; i++) {
+ assert(ffs(i) - 1 == __builtin_ctz(i));
+ }
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/flockfile.c b/lib/mlibc/tests/posix/flockfile.c
new file mode 100644
index 0000000..982311e
--- /dev/null
+++ b/lib/mlibc/tests/posix/flockfile.c
@@ -0,0 +1,35 @@
+#include <assert.h>
+#include <time.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static void *worker(void *arg) {
+ (void)arg;
+ flockfile(stdout);
+ fputs_unlocked("hello from worker", stdout);
+ funlockfile(stdout);
+ return NULL;
+}
+
+int main() {
+ // Check that recursive locking works.
+ assert(!ftrylockfile(stdout));
+ flockfile(stdout);
+ flockfile(stdout);
+ funlockfile(stdout);
+ funlockfile(stdout);
+ funlockfile(stdout);
+
+ assert(!ftrylockfile(stdout));
+
+ pthread_t thread;
+ int ret = pthread_create(&thread, NULL, &worker, NULL);
+ assert(!ret);
+
+ sleep(1);
+ funlockfile(stdout);
+
+ assert(!pthread_join(thread, NULL));
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/fmemopen.c b/lib/mlibc/tests/posix/fmemopen.c
new file mode 100644
index 0000000..4d5046e
--- /dev/null
+++ b/lib/mlibc/tests/posix/fmemopen.c
@@ -0,0 +1,150 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define BUFFER0_SIZE 0x1000
+#define BUFFER1 "Hello world"
+#define BUFFER1_SIZE (sizeof(BUFFER1))
+
+int main() {
+ // test seek with mode "r"
+ FILE *f = fmemopen(NULL, BUFFER0_SIZE, "r");
+ assert(f);
+
+ int ret = fseek(f, 0, SEEK_END);
+ assert(!ret);
+ long pos = ftell(f);
+ fprintf(stderr, "pos = %ld\n", pos);
+ // Despite the fact that this is correct behavior (see below),
+ // this sometimes fails on glibc; newlib seems to get this wrong, too.
+ // to quote what posix says about it:
+ // > The stream shall also maintain the size of the current buffer contents;
+ // > use of fseek() or fseeko() on the stream with SEEK_END shall seek relative to this size.
+ // > For modes r and r+ the size shall be set to the value given by the size argument.
+#if !defined(__GLIBC__)
+ assert(pos == BUFFER0_SIZE);
+#endif
+
+ fclose(f);
+
+ // test seek with mode "w"
+ f = fmemopen(NULL, BUFFER0_SIZE, "w");
+ assert(f);
+
+ ret = fseek(f, 0, SEEK_END);
+ assert(!ret);
+ pos = ftell(f);
+ assert(pos == 0);
+
+ fclose(f);
+
+ // test seek with mode "a" and a NULL buffer
+ f = fmemopen(NULL, BUFFER0_SIZE, "a");
+ assert(f);
+
+ ret = fseek(f, 0, SEEK_END);
+ assert(!ret);
+ pos = ftell(f);
+ assert(pos == 0);
+
+ fclose(f);
+
+ // test seek with mode "a" and a buffer containing a '\0'
+ f = fmemopen(BUFFER1, BUFFER1_SIZE + 2, "a");
+ assert(f);
+
+ pos = ftell(f);
+ assert(pos == (long) (BUFFER1_SIZE - 1));
+
+ ret = fseek(f, 0, SEEK_SET);
+ assert(!ret);
+ pos = ftell(f);
+ assert(!pos);
+
+ ret = fseek(f, 0, SEEK_END);
+ assert(!ret);
+ pos = ftell(f);
+ assert(pos == (long) (BUFFER1_SIZE - 1));
+
+ fclose(f);
+
+ // test seek with mode "a" and a buffer not containing a '\0'
+ f = fmemopen(BUFFER1, BUFFER1_SIZE - 2, "a");
+ assert(f);
+
+ ret = fseek(f, 0, SEEK_END);
+ assert(!ret);
+ pos = ftell(f);
+ assert(pos == (long) (BUFFER1_SIZE - 2));
+
+ fclose(f);
+
+ f = fmemopen(BUFFER1, BUFFER1_SIZE, "r");
+ assert(f);
+
+ ret = fseek(f, 0, SEEK_SET);
+ assert(!ret);
+
+ char buf[BUFFER1_SIZE];
+ int read = fread(buf, 1, BUFFER1_SIZE - 2, f);
+ assert(read == BUFFER1_SIZE - 2);
+ assert(!strncmp(BUFFER1, buf, BUFFER1_SIZE - 2));
+
+ fseek(f, 0, SEEK_END);
+
+ read = fread(buf, 1, 2, f);
+ assert(read == 0);
+ assert(feof(f));
+
+ fclose(f);
+
+ // Open a buffer for read+write
+ char *buf1 = strdup(BUFFER1);
+ f = fmemopen(buf1, BUFFER1_SIZE, "w+");
+ assert(f);
+ assert(strlen(BUFFER1) == BUFFER1_SIZE - 1);
+
+ // seek to somewhere in the middle of the buffer
+ fseek(f, BUFFER1_SIZE - 5, SEEK_SET);
+ // write as much data to it as possible
+ read = fwrite(BUFFER1, 1, 9, f);
+ rewind(f);
+
+ // seek the the same position in the middle of the buffer
+ ret = fseek(f, BUFFER1_SIZE - 5, SEEK_SET);
+ assert(!ret);
+ memset(buf, 0, BUFFER1_SIZE);
+ // read what we just wrote
+ read = fread(buf, 1, 5, f);
+ // check that the write got correctly truncated
+ fprintf(stderr, "buf '%s' (%zu)\n", buf, strlen(buf));
+ assert(!strncmp(BUFFER1, buf, 4) && strlen(buf) == 4);
+
+ fclose(f);
+ free(buf1);
+
+ char *buf2 = strdup(BUFFER1);
+ f = fmemopen(buf2, 0, "r");
+ assert(f || errno == EINVAL);
+
+ if(f) {
+ memset(buf, 0, BUFFER1_SIZE);
+ read = fread(buf, 10, 1, f);
+ assert(!read);
+ rewind(f);
+
+ read = fwrite(BUFFER1, 1, 12, f);
+ assert(read == 0);
+
+ fclose(f);
+ }
+ free(buf2);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/fopencookie.c b/lib/mlibc/tests/posix/fopencookie.c
new file mode 100644
index 0000000..4325038
--- /dev/null
+++ b/lib/mlibc/tests/posix/fopencookie.c
@@ -0,0 +1,69 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+struct testcookie {
+ bool read;
+ bool write;
+ bool seek;
+ bool close;
+};
+
+ssize_t cookie_read(void *c, char *buf, size_t size) {
+ (void) buf;
+ struct testcookie *cookie = c;
+ cookie->read = true;
+ return size;
+}
+
+ssize_t cookie_write(void *c, const char *buf, size_t size) {
+ (void) buf;
+ struct testcookie *cookie = c;
+ cookie->write = true;
+ return size;
+}
+
+int cookie_seek(void *c, off64_t *offset, int whence) {
+ (void) offset;
+ (void) whence;
+ struct testcookie *cookie = c;
+ cookie->seek = true;
+ return 0;
+}
+
+int cookie_close(void *c) {
+ struct testcookie *cookie = c;
+ cookie->close = true;
+ return 0;
+}
+
+int main() {
+ struct testcookie cookie = { false, false, false, false };
+
+ cookie_io_functions_t funcs = {
+ .read = cookie_read,
+ .write = cookie_write,
+ .seek = cookie_seek,
+ .close = cookie_close,
+ };
+
+ FILE *stream = fopencookie(&cookie, "w+", funcs);
+ assert(stream);
+
+ unsigned char buf[1];
+ int ret = fread(buf, 1, 1, stream);
+ assert(ret == 1);
+ ret = fwrite(buf, 1, 1, stream);
+ assert(ret == 1);
+ ret = fseek(stream, 0, SEEK_SET);
+ assert(!ret);
+
+ ret = fclose(stream);
+ assert(!ret);
+
+ assert(cookie.read && cookie.write && cookie.seek && cookie.close);
+}
diff --git a/lib/mlibc/tests/posix/getaddrinfo.c b/lib/mlibc/tests/posix/getaddrinfo.c
new file mode 100644
index 0000000..d52c93f
--- /dev/null
+++ b/lib/mlibc/tests/posix/getaddrinfo.c
@@ -0,0 +1,52 @@
+#include <netdb.h>
+#include <assert.h>
+#include <stddef.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdio.h>
+
+int main() {
+ struct addrinfo *res = NULL;
+ struct addrinfo hints = {0};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ int ret = getaddrinfo(NULL, "443", &hints, &res);
+ assert(ret == 0);
+
+ struct sockaddr_in *addr = (struct sockaddr_in*)(res[0].ai_addr);
+ assert(addr->sin_port == htons(443));
+ assert(res[0].ai_socktype == SOCK_STREAM);
+ assert(res[0].ai_protocol == IPPROTO_TCP);
+
+ freeaddrinfo(res);
+ res = NULL;
+
+ /* check we can resolve any domain */
+ ret = getaddrinfo("example.net", NULL, &hints, &res);
+ assert(ret == 0);
+
+ freeaddrinfo(res);
+ res = NULL;
+
+ hints.ai_flags = AI_NUMERICHOST;
+ ret = getaddrinfo("10.10.10.10", NULL, &hints, &res);
+ assert(ret == 0);
+
+ addr = (struct sockaddr_in*)res[0].ai_addr;
+ assert((addr->sin_addr.s_addr & 0xFF) == 10);
+ assert(((addr->sin_addr.s_addr >> 8) & 0xFF) == 10);
+ assert(((addr->sin_addr.s_addr >> 16) & 0xFF) == 10);
+ assert(((addr->sin_addr.s_addr >> 24) & 0xFF) == 10);
+
+ freeaddrinfo(res);
+ res = NULL;
+
+ ret = getaddrinfo("example.net", NULL, &hints, &res);
+ assert(ret == EAI_NONAME);
+
+ freeaddrinfo(res);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/getcwd.c b/lib/mlibc/tests/posix/getcwd.c
new file mode 100644
index 0000000..f3ba8da
--- /dev/null
+++ b/lib/mlibc/tests/posix/getcwd.c
@@ -0,0 +1,22 @@
+#include <assert.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+int main() {
+ char buf[PATH_MAX];
+ char *ret = getcwd(buf, PATH_MAX);
+
+ assert(ret);
+ assert((strlen(ret) == strlen(buf)) && strlen(ret));
+ assert(!strcmp(ret, buf));
+
+ char *ret2 = getcwd(NULL, 0);
+ assert(ret2);
+ assert(strlen(ret2));
+ assert(!strcmp(ret, ret2));
+ free(ret2);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/getdelim.c b/lib/mlibc/tests/posix/getdelim.c
new file mode 100644
index 0000000..bc43ef6
--- /dev/null
+++ b/lib/mlibc/tests/posix/getdelim.c
@@ -0,0 +1,90 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "getdelim-host-libc.tmp"
+#else
+#define TEST_FILE "getdelim.tmp"
+#endif
+
+int main(void) {
+ FILE *fp;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t read;
+
+ // We have to open the file for writing and then reading separately,
+ // as mlibc doesn't allow us to do both at the same time (yet).
+ fp = fopen(TEST_FILE, "w");
+ assert(fp);
+ fputs("foo\nbar\nbaz\nquux\n", fp);
+ fclose(fp);
+
+ fp = fopen(TEST_FILE, "r");
+ assert(fp);
+ while ((read = getline(&line, &len, fp)) != -1) {
+ printf("read line of length %zd, capacity %zu\n", read, len);
+ }
+ assert(!ferror(fp));
+ free(line);
+ fclose(fp);
+
+ size_t nchars = 10000;
+ fp = fopen(TEST_FILE, "w");
+ assert(fp);
+ for (size_t i = 0; i < nchars; i++)
+ fputc('a', fp);
+ fputc('b', fp);
+ fclose(fp);
+
+ line = NULL;
+ len = 0;
+ fp = fopen(TEST_FILE, "r");
+ assert(fp);
+ while ((read = getdelim(&line, &len, 'b', fp)) != -1) {
+ printf("read line of length %zd, capacity %zu\n", read, len);
+ assert((size_t)read == nchars + 1);
+ }
+
+ assert(len > nchars + 1);
+ assert(!ferror(fp));
+ assert(feof(fp));
+
+ assert(getdelim(&line, &len, 'b', fp) == -1);
+ assert(feof(fp));
+
+ free(line);
+ fclose(fp);
+
+ fp = fopen(TEST_FILE, "w");
+ assert(fp);
+ assert(fwrite("1234ef\0f", 1, 8, fp) == 8);
+ fclose(fp);
+
+ fp = fopen(TEST_FILE, "r");
+ assert(fp);
+
+ /* test with line = NULL and large len */
+ line = NULL;
+ len = (size_t) ~0;
+ assert(getdelim(&line, &len, 'e', fp) == 5);
+ assert(!memcmp(line, "1234e", 6));
+ assert(5 < len);
+
+ /* test handling of internal nulls */
+ assert(getdelim(&line, &len, 'e', fp) == 3);
+ assert(!memcmp(line, "f\0f", 4));
+ assert(3 < len);
+
+ /* test handling of EOF */
+ assert(getdelim(&line, &len, 'e', fp) == -1);
+
+ free(line);
+ fclose(fp);
+
+ // Delete the file
+ unlink(TEST_FILE);
+}
diff --git a/lib/mlibc/tests/posix/getnameinfo.c b/lib/mlibc/tests/posix/getnameinfo.c
new file mode 100644
index 0000000..d22a0eb
--- /dev/null
+++ b/lib/mlibc/tests/posix/getnameinfo.c
@@ -0,0 +1,22 @@
+#include <netdb.h>
+#include <assert.h>
+#include <arpa/inet.h>
+#include <stddef.h>
+#include <string.h>
+
+int main() {
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ assert(inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr));
+
+ char host[64];
+ assert(!getnameinfo((struct sockaddr*)&addr, sizeof(addr), host,
+ sizeof(host), NULL, 0, 0));
+
+ assert(inet_pton(AF_INET, "8.8.8.8", &addr.sin_addr));
+
+ assert(!getnameinfo((struct sockaddr*)&addr, sizeof(addr), host,
+ sizeof(host), NULL, 0, 0));
+ assert(!strcmp(host, "dns.google"));
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/getservbyname.c b/lib/mlibc/tests/posix/getservbyname.c
new file mode 100644
index 0000000..8c51710
--- /dev/null
+++ b/lib/mlibc/tests/posix/getservbyname.c
@@ -0,0 +1,27 @@
+#include <assert.h>
+#include <netdb.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+int main() {
+ struct servent *ret = getservbyname("http", "tcp");
+ assert(ret);
+ assert(!strcmp("http", ret->s_name));
+ assert(ret->s_port == htons(80));
+ assert(!strcmp("tcp", ret->s_proto));
+
+ ret = getservbyname("http", NULL);
+ assert(ret);
+ assert(!strcmp("http", ret->s_name));
+ assert(ret->s_port == htons(80));
+ assert(!strcmp("tcp", ret->s_proto));
+
+ ret = getservbyname("babel", "udp");
+ assert(ret);
+ ret = getservbyname("babel", "tcp");
+ assert(!ret);
+
+ ret = getservbyname("", NULL);
+ assert(!ret);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/getservbyport.c b/lib/mlibc/tests/posix/getservbyport.c
new file mode 100644
index 0000000..f522d71
--- /dev/null
+++ b/lib/mlibc/tests/posix/getservbyport.c
@@ -0,0 +1,28 @@
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+int main() {
+ struct servent *ret = getservbyport(htons(80), "tcp");
+ assert(ret);
+ assert(!strcmp("http", ret->s_name));
+ assert(ret->s_port == htons(80));
+ assert(!strcmp("tcp", ret->s_proto));
+
+ ret = getservbyport(htons(80), NULL);
+ assert(ret);
+ assert(!strcmp("http", ret->s_name));
+ assert(ret->s_port == htons(80));
+ assert(!strcmp("tcp", ret->s_proto));
+
+ ret = getservbyport(htons(6696), "udp");
+ assert(ret);
+ ret = getservbyport(htons(6696), "tcp");
+ assert(!ret);
+
+ ret = getservbyport(htons(0), NULL);
+ assert(!ret);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/grp.c b/lib/mlibc/tests/posix/grp.c
new file mode 100644
index 0000000..3d334fe
--- /dev/null
+++ b/lib/mlibc/tests/posix/grp.c
@@ -0,0 +1,110 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <grp.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+
+void check_root_group(struct group *grp) {
+ assert(grp);
+
+ printf("group name: %s\n", grp->gr_name);
+ fflush(stdout);
+
+ assert(grp->gr_gid == 0);
+ assert(!strcmp(grp->gr_name, "root"));
+
+ // Depending on your system, the root group may or may not contain the root user.
+ if (grp->gr_mem[0] != NULL) {
+ bool found = false;
+ for (size_t i = 0; grp->gr_mem[i] != NULL; i++) {
+ printf("group member: %s\n", grp->gr_mem[i]);
+ fflush(stdout);
+
+ if (!strcmp(grp->gr_mem[i], "root")) {
+ found = true;
+ break;
+ }
+ }
+ assert(found);
+ }
+}
+
+int main()
+{
+ struct group grp, *result = NULL;
+ char *buf;
+ size_t bufsize;
+ int s;
+ bool password = false;
+
+ bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
+ assert(bufsize > 0 && bufsize < 0x100000);
+
+ buf = malloc(bufsize);
+ assert(buf);
+
+ s = getgrnam_r("root", &grp, buf, bufsize, &result);
+ assert(!s);
+ check_root_group(result);
+
+ s = getgrgid_r(0, &grp, buf, bufsize, &result);
+ assert(!s);
+ check_root_group(result);
+
+ result = getgrnam("root");
+ check_root_group(result);
+
+ result = getgrgid(0);
+ check_root_group(result);
+
+ result = getgrnam("this_group_doesnt_exist");
+ assert(!result);
+#ifndef USE_HOST_LIBC
+ // This is not guaranteed.
+ assert(errno == ESRCH);
+#endif
+
+ s = getgrnam_r("this_group_doesnt_exist", &grp, buf, bufsize, &result);
+ assert(!result);
+#ifndef USE_HOST_LIBC
+ // This is not guaranteed.
+ assert(s == ESRCH);
+#endif
+
+ errno = 0;
+ setgrent();
+ assert(errno == 0);
+
+ result = getgrent();
+ assert(result);
+ grp.gr_name = strdup(result->gr_name);
+ if(result->gr_passwd) {
+ password = true;
+ }
+ grp.gr_passwd = strdup(result->gr_passwd);
+ grp.gr_gid = result->gr_gid;
+ assert(grp.gr_name);
+ if(password)
+ assert(grp.gr_passwd);
+
+ assert(grp.gr_name);
+ if(password)
+ assert(grp.gr_passwd);
+
+ endgrent();
+
+ result = getgrent();
+ assert(result);
+ assert(strcmp(result->gr_name, grp.gr_name) == 0);
+ if(password)
+ assert(strcmp(result->gr_passwd, grp.gr_passwd) == 0);
+ assert(result->gr_gid == grp.gr_gid);
+
+ free(grp.gr_name);
+ if(password)
+ free(grp.gr_passwd);
+ free(buf);
+}
diff --git a/lib/mlibc/tests/posix/if_indextoname.c b/lib/mlibc/tests/posix/if_indextoname.c
new file mode 100644
index 0000000..15dc12a
--- /dev/null
+++ b/lib/mlibc/tests/posix/if_indextoname.c
@@ -0,0 +1,11 @@
+#include <assert.h>
+#include <net/if.h>
+#include <stdio.h>
+
+int main() {
+ char name[IF_NAMESIZE];
+
+ assert(name == if_indextoname(1, name));
+ printf("test: name '%s'\n", name);
+ assert(1 == if_nametoindex(name));
+}
diff --git a/lib/mlibc/tests/posix/index.c b/lib/mlibc/tests/posix/index.c
new file mode 100644
index 0000000..63be75d
--- /dev/null
+++ b/lib/mlibc/tests/posix/index.c
@@ -0,0 +1,20 @@
+#include <assert.h>
+#include <strings.h>
+#include <stddef.h>
+
+int main() {
+ char str[] = "This is a sample string";
+ char *pch;
+ // The character 's' is at position 4, 7, 11 and 18
+ pch = index(str, 's');
+ assert(pch - str + 1 == 4);
+ pch = index(pch + 1, 's');
+ assert(pch - str + 1 == 7);
+ pch = index(pch + 1, 's');
+ assert(pch - str + 1 == 11);
+ pch = index(pch + 1, 's');
+ assert(pch - str + 1 == 18);
+ pch = index(pch + 1, 's');
+ assert(pch == NULL);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/inet_ntop.c b/lib/mlibc/tests/posix/inet_ntop.c
new file mode 100644
index 0000000..feca26c
--- /dev/null
+++ b/lib/mlibc/tests/posix/inet_ntop.c
@@ -0,0 +1,30 @@
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+int main() {
+ struct in_addr addr;
+ addr.s_addr = (1 << 24) |
+ (1 << 16) | (1 << 8) | 1;
+ char buf[INET_ADDRSTRLEN];
+ assert(inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN) != NULL);
+ assert(strncmp("1.1.1.1", buf, INET_ADDRSTRLEN) == 0);
+
+ struct in6_addr addr2 = { .s6_addr = {0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0x1, 0, 0, 0, 0, 0, 0x1} };
+ char buf2[INET6_ADDRSTRLEN];
+ assert(inet_ntop(AF_INET6, &addr2, buf2, INET6_ADDRSTRLEN) != NULL);
+ assert(strncmp("2001:db8::1:0:0:1", buf2, INET6_ADDRSTRLEN) == 0);
+
+ struct in6_addr addr3 = { .s6_addr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} };
+ char buf3[INET6_ADDRSTRLEN];
+ assert(inet_ntop(AF_INET6, &addr3, buf3, INET6_ADDRSTRLEN) != NULL);
+ assert(strncmp("::1", buf3, INET6_ADDRSTRLEN) == 0);
+
+ struct in6_addr addr4 = { .s6_addr = {0x20, 0x1, 0xd, 0xb8, 00, 00, 00, 0x1, 00, 0x1, 00, 0x1, 00, 0x1, 00, 0x1} };
+ char buf4[INET6_ADDRSTRLEN];
+ assert(inet_ntop(AF_INET6, &addr4, buf4, INET6_ADDRSTRLEN) != NULL);
+ assert(strncmp("2001:db8:0:1:1:1:1:1", buf4, INET6_ADDRSTRLEN) == 0);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/inet_pton.c b/lib/mlibc/tests/posix/inet_pton.c
new file mode 100644
index 0000000..6c0f342
--- /dev/null
+++ b/lib/mlibc/tests/posix/inet_pton.c
@@ -0,0 +1,16 @@
+#include <arpa/inet.h>
+#include <assert.h>
+
+int main() {
+ struct in_addr addr;
+ assert(inet_pton(AF_INET, "1.1.1.1", &addr));
+ assert((addr.s_addr & 0xFF) == 1);
+ assert(((addr.s_addr >> 8) & 0xFF) == 1);
+ assert(((addr.s_addr >> 16) & 0xFF) == 1);
+ assert(((addr.s_addr >> 24) & 0xFF) == 1);
+
+ assert(!inet_pton(AF_INET, "256.999.1234.555", &addr));
+ assert(!inet_pton(AF_INET, "a.b.c.d", &addr));
+ return 0;
+}
+
diff --git a/lib/mlibc/tests/posix/memrchr.c b/lib/mlibc/tests/posix/memrchr.c
new file mode 100644
index 0000000..99e3e25
--- /dev/null
+++ b/lib/mlibc/tests/posix/memrchr.c
@@ -0,0 +1,25 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+int main () {
+ char str[] = "The Last Supper by Leonardo da Vinci";
+ char *str_temp;
+
+ // Test strstr
+ str_temp = strstr(str, "Supper"); /* Find a substring in the string */
+ assert(!strcmp(str_temp, "Supper by Leonardo da Vinci"));
+
+ /* Following calls use memory APIs for the above tasks */
+ // Test memchr
+ str_temp = (char *)memchr((void *)str, 'L', strlen(str));
+ assert(!strcmp(str_temp, "Last Supper by Leonardo da Vinci"));
+
+ // Test memrchr
+ str_temp = (char *)memrchr((void *)str, 'L', strlen(str));
+ assert(!strcmp(str_temp, "Leonardo da Vinci"));
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/mkstemp.c b/lib/mlibc/tests/posix/mkstemp.c
new file mode 100644
index 0000000..d06783e
--- /dev/null
+++ b/lib/mlibc/tests/posix/mkstemp.c
@@ -0,0 +1,70 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+
+void validate_pattern(char *p) {
+ assert(memcmp(p, "XXXXXX", 6));
+ assert(memchr(p, 0, 6) == NULL);
+
+ for (int i = 0; i < 6; i++) {
+ assert(isalnum(p[i]));
+ }
+}
+
+int main() {
+ int ret;
+
+ // Make sure the patterns themselves cannot be chosen. This
+ // *could* happen on glibc, or if we widen the character set
+ // used for generating random names. Odds are 1 in 60 billion,
+ // but I'd rather not worry about this.
+ ret = open("prefixXXXXXX", O_RDWR | O_CREAT | O_EXCL, 0600);
+ assert(ret >= 0 || (ret == -1 && errno == EEXIST));
+ ret = open("longprefixXXXXXXlongsuffix", O_RDWR | O_CREAT | O_EXCL, 0600);
+ assert(ret >= 0 || (ret == -1 && errno == EEXIST));
+
+ ret = mkstemp("short");
+ assert(ret == -1);
+ assert(errno == EINVAL);
+
+ ret = mkstemp("lessthan6XXX");
+ assert(ret == -1);
+ assert(errno == EINVAL);
+
+ ret = mkstemps("lessthan6XXXswithsuffix", 11);
+ assert(ret == -1);
+ assert(errno == EINVAL);
+
+ char *p = strdup("prefixXXXXXX");
+ ret = mkstemp(p);
+ // We can't really protect against EEXIST...
+ assert(ret >= 0 || (ret == -1 && errno == EEXIST));
+ assert(!memcmp(p, "prefix", 6));
+ assert(p[12] == 0);
+ validate_pattern(p + 6);
+
+ if (ret >= 0) {
+ ret = close(ret);
+ assert(!ret);
+ }
+ free(p);
+
+ p = strdup("longprefixXXXXXXlongsuffix");
+ ret = mkstemps(p, 10);
+ // We can't really protect against EEXIST...
+ assert(ret >= 0 || (ret == -1 && errno == EEXIST));
+ assert(!memcmp(p, "longprefix", 10));
+ assert(!memcmp(p + 16, "longsuffix", 10));
+ assert(p[26] == 0);
+ validate_pattern(p + 10);
+
+ if (ret >= 0) {
+ ret = close(ret);
+ assert(!ret);
+ }
+ free(p);
+}
diff --git a/lib/mlibc/tests/posix/open_memstream.c b/lib/mlibc/tests/posix/open_memstream.c
new file mode 100644
index 0000000..0347003
--- /dev/null
+++ b/lib/mlibc/tests/posix/open_memstream.c
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define WRITE_NO 1024
+
+int main() {
+ size_t size;
+ char *buf;
+
+ FILE *fp = open_memstream(&buf, &size);
+ assert(fp);
+
+ char c = 'A';
+ for (size_t i = 0; i < WRITE_NO; i++)
+ assert(fwrite(&c, sizeof(char), 1, fp) == 1);
+
+ // Flush the file to update the pointers.
+ assert(!fflush(fp));
+
+ assert(size == WRITE_NO);
+ for (size_t i = 0; i < size; i++)
+ assert(buf[i] == c);
+
+ // Check if the stream maintains a null-bye at the end.
+ assert(buf[size] == '\0');
+
+ // Stream should be expanded, size should be == 2*WRITE_NO.
+ assert(!fseek(fp, WRITE_NO, SEEK_END));
+ assert(!fflush(fp));
+ assert(size == 2*WRITE_NO);
+ assert(buf[size] == '\0');
+
+ // Check if it's filled with zero's.
+ for (size_t i = WRITE_NO; i < size; i++)
+ assert(buf[i] == '\0');
+
+ // Go back and overwrite the 0's with 'B'.
+ assert(!fseek(fp, -WRITE_NO, SEEK_CUR));
+ c = 'B';
+ for (size_t i = 0; i < WRITE_NO; i++)
+ assert(fwrite(&c, sizeof(char), 1, fp) == 1);
+
+ // Check if that happened.
+ assert(size == 2*WRITE_NO);
+ for (size_t i = WRITE_NO; i < size; i++)
+ assert(buf[i] == c);
+ assert(buf[size] == '\0');
+
+ // Go to the front and write 'B'.
+ assert(!fseek(fp, 0, SEEK_SET));
+ for (size_t i = 0; i < WRITE_NO; i++)
+ assert(fwrite(&c, sizeof(char), 1, fp) == 1);
+
+ // Check if that happened.
+ assert(size == 2*WRITE_NO);
+ for (size_t i = 0; i < size; i++)
+ assert(buf[i] == c);
+ assert(buf[size] == '\0');
+
+ // Close the file, we have tested everything.
+ assert(!fclose(fp));
+ free(buf);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/pause.c b/lib/mlibc/tests/posix/pause.c
new file mode 100644
index 0000000..054c5a4
--- /dev/null
+++ b/lib/mlibc/tests/posix/pause.c
@@ -0,0 +1,49 @@
+#include <assert.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+static void noop(int x) {
+ (void)x;
+}
+
+int main() {
+ signal(SIGUSR1, &noop);
+
+ int pid;
+ switch(pid = fork()) {
+ case -1:
+ perror("fork");
+ abort();
+ case 0:
+ pause();
+ assert(errno == EINTR);
+ return 0;
+ default:
+ while (1) {
+ usleep(100);
+ kill(pid, SIGUSR1);
+ usleep(100);
+ int status;
+
+ errno = 0;
+ if (waitpid(-1, &status, WNOHANG) <= 0) {
+ if (errno && errno != EAGAIN) {
+ perror("wait");
+ kill(pid, SIGKILL);
+ }
+ continue;
+ }
+
+ if (!WIFEXITED(status)) {
+ printf("wait returned %x\n", status);
+ abort();
+ }
+
+ return WEXITSTATUS(status);
+ }
+ }
+}
diff --git a/lib/mlibc/tests/posix/popen.c b/lib/mlibc/tests/posix/popen.c
new file mode 100644
index 0000000..87bbde2
--- /dev/null
+++ b/lib/mlibc/tests/posix/popen.c
@@ -0,0 +1,42 @@
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "popen-host-libc.tmp"
+#else
+#define TEST_FILE "popen.tmp"
+#endif
+
+#define TEST_STRING1 "the quick brown fox jumps over the lazy dog"
+#define TEST_STRING1_LEN 43
+#define TEST_STRING2 "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
+#define TEST_STRING2_LEN 55
+
+int main() {
+ // Test reading
+ FILE *f1 = popen("echo -n '" TEST_STRING1 "'", "r");
+ assert(f1);
+ char buf1[TEST_STRING1_LEN];
+ assert(fread(buf1, 1, TEST_STRING1_LEN, f1) == TEST_STRING1_LEN);
+ assert(memcmp(buf1, TEST_STRING1, TEST_STRING1_LEN) == 0);
+ pclose(f1);
+
+ // Test writing
+ FILE *f2 = popen("cat - > " TEST_FILE, "w");
+ assert(f2);
+ assert(fwrite(TEST_STRING2, 1, TEST_STRING2_LEN, f2) == TEST_STRING2_LEN);
+ pclose(f2);
+
+ // Read test file back
+ FILE *test_file = fopen(TEST_FILE, "r");
+ assert(test_file);
+ char buf2[TEST_STRING2_LEN];
+ assert(fread(buf2, 1, TEST_STRING2_LEN, test_file) == TEST_STRING2_LEN);
+ assert(memcmp(buf2, TEST_STRING2, TEST_STRING2_LEN) == 0);
+ fclose(test_file);
+ assert(unlink(TEST_FILE) == 0);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/posix-timer.c b/lib/mlibc/tests/posix/posix-timer.c
new file mode 100644
index 0000000..d097818
--- /dev/null
+++ b/lib/mlibc/tests/posix/posix-timer.c
@@ -0,0 +1,47 @@
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+
+int main() {
+ struct sigevent evp;
+ memset(&evp, 0, sizeof(evp));
+
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGUSR1);
+ sigprocmask(SIG_BLOCK, &set, 0);
+ evp.sigev_notify = SIGEV_SIGNAL;
+ evp.sigev_signo = SIGUSR1;
+
+ struct timeval start;
+ gettimeofday(&start, NULL);
+
+ timer_t timer;
+ if (timer_create(CLOCK_MONOTONIC, &evp, &timer)) {
+ perror("timer_create");
+ exit(1);
+ }
+
+ struct itimerspec spec;
+ memset(&spec, 0, sizeof(spec));
+ spec.it_value.tv_sec = 1;
+ spec.it_value.tv_nsec = 0;
+
+ int sig;
+ timer_settime(timer, 0, &spec, NULL);
+ sigwait(&set, &sig);
+
+ struct timeval end;
+ gettimeofday(&end, NULL);
+ timer_delete(timer);
+
+ double diff = end.tv_sec - start.tv_sec;
+ diff += (end.tv_usec - start.tv_usec) / 1000000.0;
+ assert(diff >= 1.0);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/posix_memalign.c b/lib/mlibc/tests/posix/posix_memalign.c
new file mode 100644
index 0000000..34652b9
--- /dev/null
+++ b/lib/mlibc/tests/posix/posix_memalign.c
@@ -0,0 +1,21 @@
+#include <stdlib.h>
+#include <assert.h>
+#include <stdint.h>
+#include <errno.h>
+
+int main() {
+ void *p = NULL;
+
+ // align must be a power of two
+ assert(posix_memalign(&p, 3, 1) == EINVAL && p == NULL);
+
+ // align must be a multiple of sizeof(void *)
+ assert(posix_memalign(&p, sizeof(void *) / 2, 8) == EINVAL && p == NULL);
+
+ assert(posix_memalign(&p, sizeof(void *), sizeof(void *)) == 0 && p != NULL && (uintptr_t)p % sizeof(void *) == 0);
+ free(p);
+ assert(posix_memalign(&p, 256, 1) == 0 && p != NULL && (uintptr_t)p % 256 == 0);
+ free(p);
+ assert(posix_memalign(&p, 256, 256) == 0 && p != NULL && (uintptr_t)p % 256 == 0);
+ free(p);
+}
diff --git a/lib/mlibc/tests/posix/posix_spawn.c b/lib/mlibc/tests/posix/posix_spawn.c
new file mode 100644
index 0000000..cc5ccff
--- /dev/null
+++ b/lib/mlibc/tests/posix/posix_spawn.c
@@ -0,0 +1,38 @@
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <spawn.h>
+#include <sys/wait.h>
+
+extern char **environ;
+
+void run_cmd(char *cmd)
+{
+ pid_t pid;
+ char *argv[] = {"sh", "-c", cmd, NULL};
+ int status;
+ printf("Run command: %s\n", cmd);
+ status = posix_spawn(&pid, "/bin/sh", NULL, NULL, argv, environ);
+ if(status == 0) {
+ printf("Child pid: %i\n", pid);
+ if(waitpid(pid, &status, 0) != -1) {
+ printf("Child exited with status %i\n", status);
+ printf("Child exit status: %i\n", WEXITSTATUS(status));
+ assert(WEXITSTATUS(status) == 0);
+ } else {
+ perror("waitpid");
+ assert(0 == 1);
+ }
+ } else {
+ printf("posix_spawn: %s\n", strerror(status));
+ assert(0 == 1);
+ }
+}
+
+int main() {
+ run_cmd(":");
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/pthread_atfork.c b/lib/mlibc/tests/posix/pthread_atfork.c
new file mode 100644
index 0000000..45be136
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_atfork.c
@@ -0,0 +1,54 @@
+#include <assert.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+_Atomic int prepare_order = 0;
+_Atomic int parent_order = 0;
+_Atomic int child_order = 0;
+
+static void prepare1() { prepare_order = 1; }
+static void prepare2() { prepare_order = 2; }
+
+static void parent1() { parent_order = 1; }
+static void parent2() { parent_order = 2; }
+
+static void child1() { child_order = 1; }
+static void child2() { child_order = 2; }
+
+int main() {
+ assert(!pthread_atfork(prepare1, parent1, child1));
+ assert(!pthread_atfork(prepare2, parent2, child2));
+
+ pid_t pid = fork();
+ assert(pid >= 0);
+
+ if (!pid) {
+ assert(child_order == 2);
+ exit(0);
+ } else {
+ assert(prepare_order == 1);
+ assert(parent_order == 2);
+
+ while (1) {
+ int status = 0;
+
+ int ret = waitpid(pid, &status, 0);
+
+ if (ret == -1 && errno == EINTR)
+ continue;
+
+ assert(ret > 0);
+
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGABRT)
+ return 1;
+
+ return WEXITSTATUS(status);
+ }
+ }
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/pthread_attr.c b/lib/mlibc/tests/posix/pthread_attr.c
new file mode 100644
index 0000000..c901a90
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_attr.c
@@ -0,0 +1,157 @@
+#include <pthread.h>
+#include <assert.h>
+#include <errno.h>
+#include <alloca.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <signal.h>
+
+static void test_detachstate() {
+ pthread_attr_t attr;
+ assert(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
+ int detachstate;
+ assert(!pthread_attr_getdetachstate(&attr, &detachstate));
+ assert(detachstate == PTHREAD_CREATE_DETACHED);
+ assert(pthread_attr_setdetachstate(&attr, 2* (PTHREAD_CREATE_DETACHED +
+ PTHREAD_CREATE_JOINABLE)) == EINVAL);
+}
+
+static void *stacksize_worker(void *arg) {
+ size_t default_stacksize = (*(size_t*)arg);
+ size_t alloc_size = default_stacksize + default_stacksize/2;
+ void *area = alloca(alloc_size);
+ // If the allocated stack was not enough this will crash.
+ *(volatile int*)(area + alloc_size) = 1;
+ return NULL;
+}
+
+static void test_stacksize() {
+ pthread_attr_t attr;
+ assert(!pthread_attr_init(&attr));
+ size_t stacksize;
+ assert(!pthread_attr_getstacksize(&attr, &stacksize));
+ assert(!pthread_attr_setstacksize(&attr, stacksize * 2));
+ pthread_t thread;
+ assert(!pthread_create(&thread, &attr, stacksize_worker, &stacksize));
+ assert(!pthread_join(thread, NULL));
+}
+
+static void test_guardsize() {
+ pthread_attr_t attr;
+ assert(!pthread_attr_init(&attr));
+ assert(!pthread_attr_setguardsize(&attr, 0));
+ size_t guardsize;
+ assert(!pthread_attr_getguardsize(&attr, &guardsize));
+ assert(!guardsize);
+}
+
+static void test_scope() {
+ pthread_attr_t attr;
+ assert(!pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM));
+ int scope;
+ assert(!pthread_attr_getscope(&attr, &scope));
+ assert(scope == PTHREAD_SCOPE_SYSTEM);
+ assert(pthread_attr_setscope(&attr, 2* (PTHREAD_SCOPE_SYSTEM +
+ PTHREAD_SCOPE_PROCESS)) == EINVAL);
+}
+
+static void test_inheritsched() {
+ pthread_attr_t attr;
+ assert(!pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED));
+ int inheritsched;
+ assert(!pthread_attr_getinheritsched(&attr, &inheritsched));
+ assert(inheritsched == PTHREAD_INHERIT_SCHED);
+ assert(pthread_attr_setinheritsched(&attr, 2* (PTHREAD_INHERIT_SCHED +
+ PTHREAD_EXPLICIT_SCHED)) == EINVAL);
+}
+
+static void test_schedparam() {
+ pthread_attr_t attr;
+ struct sched_param init_param = {0};
+ assert(!pthread_attr_setschedparam(&attr, &init_param));
+ struct sched_param param = {1};
+ assert(!pthread_attr_getschedparam(&attr, &param));
+ assert(param.sched_priority == init_param.sched_priority);
+}
+
+static void test_schedpolicy() {
+ pthread_attr_t attr;
+ assert(!pthread_attr_setschedpolicy(&attr, SCHED_FIFO));
+ int policy;
+ assert(!pthread_attr_getschedpolicy(&attr, &policy));
+ assert(policy == SCHED_FIFO);
+ assert(pthread_attr_setinheritsched(&attr, 2* (SCHED_FIFO + SCHED_RR +
+ SCHED_OTHER)) == EINVAL);
+}
+
+static void *stackaddr_worker(void *arg) {
+ void *addr = *(void**)arg;
+
+ void *sp;
+#if defined(__x86_64__)
+ asm volatile ("mov %%rsp, %0" : "=r"(sp));
+#elif defined(__i386__)
+ asm volatile ("mov %%esp, %0" : "=r"(sp));
+#elif defined(__aarch64__)
+ asm volatile ("mov %0, sp" : "=r"(sp));
+#elif defined (__riscv)
+ asm volatile ("mv %0, sp" : "=r"(sp));
+#else
+# error Unknown architecture
+#endif
+
+ // Check if our stack pointer is in a sane range.
+ assert(sp > addr);
+ return NULL;
+}
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+static void test_stackaddr() {
+ pthread_attr_t attr;
+ assert(!pthread_attr_init(&attr));
+ size_t size;
+ assert(!pthread_attr_getstacksize(&attr, &size));
+ void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ assert(!pthread_attr_setstack(&attr, addr, size));
+ assert(!pthread_attr_setguardsize(&attr, 0));
+ void *new_addr;
+ size_t new_size;
+ assert(!pthread_attr_getstack(&attr, &new_addr, &new_size));
+ assert(new_addr == addr);
+ assert(new_size == size);
+
+ pthread_t thread;
+ assert(!pthread_create(&thread, &attr, stackaddr_worker, &addr));
+ assert(!pthread_join(thread, NULL));
+}
+#pragma GCC diagnostic pop
+
+#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32)
+static void test_stack() {
+ pthread_attr_t attr;
+ void *stackaddr = (void*)1;
+ size_t stacksize = PTHREAD_STACK_MIN;
+
+ assert(!pthread_attr_setstack(&attr, stackaddr, stacksize));
+ void *new_addr;
+ size_t new_size;
+ assert(!pthread_attr_getstack(&attr, &new_addr, &new_size));
+ assert(new_addr == stackaddr);
+ assert(new_size == stacksize);
+}
+#endif
+
+int main() {
+ test_detachstate();
+ test_stacksize();
+ test_guardsize();
+ test_scope();
+ test_inheritsched();
+ test_schedparam();
+ test_schedpolicy();
+ test_stackaddr();
+#if !defined(USE_HOST_LIBC) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32)
+ test_stack();
+#endif
+}
diff --git a/lib/mlibc/tests/posix/pthread_barrier.c b/lib/mlibc/tests/posix/pthread_barrier.c
new file mode 100644
index 0000000..213ba8f
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_barrier.c
@@ -0,0 +1,56 @@
+#include <pthread.h>
+#include <unistd.h>
+#include <assert.h>
+
+pthread_barrier_t barrier;
+_Atomic int hitBarrierCount, pastBarrierCount;
+
+static void *worker(void *arg) {
+ (void)arg;
+ hitBarrierCount++;
+ pthread_barrier_wait(&barrier);
+ pastBarrierCount++;
+ return NULL;
+}
+
+int main() {
+ // pthread_barrierattr_t
+ pthread_barrierattr_t attr;
+ pthread_barrierattr_init(&attr);
+
+ int pshared;
+ pthread_barrierattr_getpshared(&attr, &pshared);
+ assert(pshared == PTHREAD_PROCESS_PRIVATE);
+
+ pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+ pthread_barrierattr_getpshared(&attr, &pshared);
+ assert(pshared == PTHREAD_PROCESS_SHARED);
+
+ pthread_barrierattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
+ pthread_barrierattr_getpshared(&attr, &pshared);
+ assert(pshared == PTHREAD_PROCESS_PRIVATE);
+
+ // pthread_barrier_t
+ pthread_barrier_init(&barrier, &attr, 3);
+ pthread_barrierattr_destroy(&attr);
+
+ pthread_t thread1;
+ int ret = pthread_create(&thread1, NULL, &worker, NULL);
+ assert(!ret);
+
+ pthread_t thread2;
+ ret = pthread_create(&thread2, NULL, &worker, NULL);
+ assert(!ret);
+
+ sleep(1);
+
+ // Make sure the barrier actually stops threads from proceeding.
+ assert(pastBarrierCount == 0);
+ assert(hitBarrierCount <= 2);
+
+ hitBarrierCount++;
+ pthread_barrier_wait(&barrier);
+ assert(hitBarrierCount == 3);
+
+ pthread_barrier_destroy(&barrier);
+}
diff --git a/lib/mlibc/tests/posix/pthread_cancel.c b/lib/mlibc/tests/posix/pthread_cancel.c
new file mode 100644
index 0000000..ca4ca34
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_cancel.c
@@ -0,0 +1,150 @@
+#include <assert.h>
+#include <pthread.h>
+#include <unistd.h>
+
+_Atomic int worker_ready = 0;
+_Atomic int main_ready = 0;
+
+static void *worker1(void *arg) {
+ (void)arg;
+ assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+ assert(!pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL));
+
+ worker_ready = 1;
+
+ while (1) sleep(1);
+
+ return NULL;
+}
+
+static void *worker2(void *arg) {
+ (void) arg;
+ assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+
+ worker_ready = 1;
+
+ while(!main_ready);
+
+ // Cancellation point - we should cancel right now
+ sleep(1);
+
+ assert(!"Expected to be cancelled!");
+ __builtin_unreachable();
+}
+
+static void *worker3(void *arg) {
+ (void) arg;
+ assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+
+ worker_ready = 1;
+
+ while(!main_ready);
+
+ // Cancellation point - we should cancel right now
+ pthread_testcancel();
+
+ assert(!"Expected to be cancelled!");
+ __builtin_unreachable();
+}
+
+static void *worker4(void *arg) {
+ (void) arg;
+ assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+
+ worker_ready = 1;
+ sleep(1);
+
+ // We expect to be canceled during the sleep
+
+ assert(!"Expected to be cancelled!");
+ __builtin_unreachable();
+}
+
+static void *worker5(void *arg) {
+ assert(!pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+
+ worker_ready = 1;
+
+ while(!main_ready);
+
+ // Cancellation point - we should NOT cancel right now
+ pthread_testcancel();
+
+ int *arg_int = (int*)arg;
+ *arg_int = 1;
+
+ return NULL;
+}
+
+static void check_result(pthread_t thread) {
+
+ void *ret_val = NULL;
+ int ret = pthread_join(thread, &ret_val);
+ assert(!ret);
+ assert(ret_val == PTHREAD_CANCELED);
+}
+
+int main() {
+ pthread_t thread;
+ int ret = pthread_create(&thread, NULL, &worker1, NULL);
+ assert(!ret);
+
+ while (!worker_ready);
+ ret = pthread_cancel(thread);
+ assert(!ret);
+ check_result(thread);
+
+ main_ready = 0;
+ worker_ready = 0;
+ main_ready = 0;
+ ret = pthread_create(&thread, NULL, &worker2, NULL);
+ assert(!ret);
+
+ while(!worker_ready);
+ ret = pthread_cancel(thread);
+ assert(!ret);
+ main_ready = 1;
+ check_result(thread);
+
+ main_ready = 0;
+ worker_ready = 0;
+ main_ready = 0;
+ ret = pthread_create(&thread, NULL, &worker3, NULL);
+ assert(!ret);
+
+ while(!worker_ready);
+ ret = pthread_cancel(thread);
+ assert(!ret);
+ main_ready = 1;
+ check_result(thread);
+
+ worker_ready = 0;
+ main_ready = 0;
+ ret = pthread_create(&thread, NULL, &worker4, NULL);
+ assert(!ret);
+
+ while(!worker_ready);
+ ret = pthread_cancel(thread);
+ assert(!ret);
+ main_ready = 1;
+ check_result(thread);
+
+ // Test for bug where pthread_testcancel() was not checking if
+ // cancellation was triggered properly.
+ worker_ready = 0;
+ int pthread_arg = 0;
+ main_ready = 0;
+ ret = pthread_create(&thread, NULL, &worker5, &pthread_arg);
+ assert(!ret);
+
+ while(!worker_ready);
+ main_ready = 1;
+
+ void *ret_val = NULL;
+ ret = pthread_join(thread, &ret_val);
+ assert(!ret);
+ assert(!ret_val);
+ assert(pthread_arg == 1);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/pthread_cleanup.c b/lib/mlibc/tests/posix/pthread_cleanup.c
new file mode 100644
index 0000000..b6136aa
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_cleanup.c
@@ -0,0 +1,42 @@
+#include <stdint.h>
+#include <assert.h>
+#include <pthread.h>
+
+_Atomic uintptr_t cleanup_val = 0;
+
+static void cleanup(void *arg) {
+ cleanup_val = (uintptr_t)arg;
+}
+
+static void *worker(void *arg) {
+ (void)arg;
+
+ pthread_cleanup_push(cleanup, (void *)1);
+ pthread_cleanup_push(cleanup, (void *)2);
+ pthread_cleanup_push(cleanup, (void *)3);
+ pthread_cleanup_push(cleanup, (void *)4);
+
+ pthread_cleanup_pop(1);
+ assert(cleanup_val == 4);
+
+ pthread_cleanup_pop(0);
+ assert(cleanup_val == 4);
+
+ pthread_exit(NULL);
+
+ pthread_cleanup_pop(0);
+ pthread_cleanup_pop(0);
+
+ return NULL;
+}
+
+int main() {
+ pthread_t thread;
+ assert(!pthread_create(&thread, NULL, &worker, NULL));
+ assert(!pthread_join(thread, NULL));
+
+ assert(cleanup_val == 1);
+
+ return 0;
+}
+
diff --git a/lib/mlibc/tests/posix/pthread_cond.c b/lib/mlibc/tests/posix/pthread_cond.c
new file mode 100644
index 0000000..6203928
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_cond.c
@@ -0,0 +1,106 @@
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+
+_Atomic int waiting, should_exit;
+pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+
+static void *worker(void *arg) {
+ (void)arg;
+ pthread_mutex_lock(&mtx);
+ ++waiting;
+ while (!should_exit)
+ pthread_cond_wait(&cond, &mtx);
+ pthread_mutex_unlock(&mtx);
+ return NULL;
+}
+
+static void test_broadcast_wakes_all() {
+ pthread_t t1, t2;
+ pthread_create(&t1, NULL, &worker, NULL);
+ pthread_create(&t2, NULL, &worker, NULL);
+
+ // Wait until the workers have actually entered the cond_wait
+ // before doing a broadcast.
+ while (waiting != 2 || pthread_mutex_trylock(&mtx) == EBUSY)
+ usleep(150000); // 150ms
+
+ should_exit = 1;
+ assert(!pthread_cond_broadcast(&cond));
+ pthread_mutex_unlock(&mtx);
+
+ pthread_join(t1, NULL);
+ pthread_join(t2, NULL);
+}
+
+static void test_timedwait_timedout() {
+ // Use CLOCK_MONOTONIC.
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+ assert(!pthread_condattr_setclock(&attr, CLOCK_MONOTONIC));
+
+ struct timespec before_now;
+ assert(!clock_gettime(CLOCK_MONOTONIC, &before_now));
+ before_now.tv_nsec -= 10000;
+
+ pthread_mutex_lock(&mtx);
+ int e = pthread_cond_timedwait(&cond, &mtx, &before_now);
+ assert(e == ETIMEDOUT);
+ pthread_mutex_unlock(&mtx);
+
+ long nanos_per_second = 1000000000;
+ struct timespec after_now;
+ assert(!clock_gettime(CLOCK_MONOTONIC, &after_now));
+ after_now.tv_nsec += nanos_per_second / 10; // 100ms
+ if (after_now.tv_nsec >= nanos_per_second) {
+ after_now.tv_nsec -= nanos_per_second;
+ after_now.tv_sec++;
+ }
+
+ pthread_mutex_lock(&mtx);
+ e = pthread_cond_timedwait(&cond, &mtx, &after_now);
+ assert(e == ETIMEDOUT);
+ pthread_mutex_unlock(&mtx);
+
+ after_now.tv_nsec += nanos_per_second;
+ pthread_mutex_lock(&mtx);
+ e = pthread_cond_timedwait(&cond, &mtx, &after_now);
+ assert(e == EINVAL);
+ pthread_mutex_unlock(&mtx);
+}
+
+static void test_attr() {
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+
+ clockid_t clock;
+ assert(!pthread_condattr_getclock(&attr, &clock));
+ assert(clock == CLOCK_REALTIME);
+ assert(!pthread_condattr_setclock(&attr, CLOCK_MONOTONIC));
+ assert(!pthread_condattr_getclock(&attr, &clock));
+ assert(clock == CLOCK_MONOTONIC);
+
+ int pshared;
+ assert(!pthread_condattr_getpshared(&attr, &pshared));
+ assert(pshared == PTHREAD_PROCESS_PRIVATE);
+ assert(!pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED));
+ assert(!pthread_condattr_getpshared(&attr, &pshared));
+ assert(pshared == PTHREAD_PROCESS_SHARED);
+
+ pthread_condattr_destroy(&attr);
+ pthread_condattr_init(&attr);
+
+ // Make sure that we can create a pthread_cond_t with an attr.
+ pthread_cond_t cond;
+ pthread_cond_init(&cond, &attr);
+ pthread_cond_destroy(&cond);
+ pthread_condattr_destroy(&attr);
+}
+
+int main() {
+ test_attr();
+ test_broadcast_wakes_all();
+ test_timedwait_timedout();
+}
diff --git a/lib/mlibc/tests/posix/pthread_create.c b/lib/mlibc/tests/posix/pthread_create.c
new file mode 100644
index 0000000..b78674f
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_create.c
@@ -0,0 +1,22 @@
+#include <assert.h>
+#include <time.h>
+#include <pthread.h>
+
+int variable = 0;
+
+static void *worker(void *arg) {
+ (void) arg;
+ variable = 1;
+ return NULL;
+}
+
+int main() {
+ pthread_t thread;
+ int ret = pthread_create(&thread, NULL, &worker, NULL);
+ assert(!ret);
+
+ ret = pthread_join(thread, NULL);
+ assert(!ret);
+ assert(variable == 1);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/pthread_key.c b/lib/mlibc/tests/posix/pthread_key.c
new file mode 100644
index 0000000..40bd882
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_key.c
@@ -0,0 +1,95 @@
+#include <assert.h>
+#include <pthread.h>
+#include <errno.h>
+#include <limits.h>
+
+_Atomic int dtors_entered = 0;
+pthread_key_t key3;
+
+static void dtor1(void *arg) {
+ (void)arg;
+ dtors_entered++;
+
+ // Set the key during destruction to trigger dtor2 (as it only runs if
+ // the key value is non-NULL).
+ assert(!pthread_setspecific(key3, &key3));
+ assert(pthread_getspecific(key3) == &key3);
+}
+
+static void dtor2(void *arg) {
+ (void)arg;
+ dtors_entered++;
+}
+
+static void *worker1(void *arg) {
+ (void)arg;
+
+ pthread_key_t key1, key2;
+ assert(!pthread_key_create(&key1, NULL));
+ assert(!pthread_key_create(&key2, dtor1));
+ assert(!pthread_key_create(&key3, dtor2));
+
+ assert(!pthread_setspecific(key1, &key1));
+ assert(pthread_getspecific(key1) == &key1);
+
+ assert(!pthread_setspecific(key2, &key2));
+ assert(pthread_getspecific(key2) == &key2);
+
+ pthread_exit(0);
+ return NULL;
+}
+
+static void dtor3(void *arg) {
+ (void)arg;
+
+ // Make sure that we can create and destroy keys inside the dtor.
+ pthread_key_t dtorKey;
+ assert(!pthread_key_create(&dtorKey, NULL));
+
+ assert(!pthread_setspecific(dtorKey, &dtorKey));
+ assert(pthread_getspecific(dtorKey) == &dtorKey);
+
+ assert(!pthread_key_delete(dtorKey));
+}
+
+static void *worker2(void *arg) {
+ (void)arg;
+
+ pthread_key_t key;
+ assert(!pthread_key_create(&key, dtor3));
+
+ assert(!pthread_setspecific(key, &key));
+ assert(pthread_getspecific(key) == &key);
+
+ pthread_exit(0);
+ return NULL;
+}
+
+int main() {
+ // NOTE that the EINVAL return from pthread_setspecific is mlibc-specific,
+ // POSIX specifies that accessing an invalid key is undefined behavior.
+
+ assert(pthread_getspecific(PTHREAD_KEYS_MAX) == NULL);
+ assert(pthread_setspecific(PTHREAD_KEYS_MAX, NULL) == EINVAL);
+
+ pthread_key_t key;
+ assert(!pthread_key_create(&key, NULL));
+ assert(!pthread_setspecific(key, &key));
+ assert(pthread_getspecific(key) == &key);
+ assert(!pthread_key_delete(key));
+
+ pthread_t thread;
+ assert(!pthread_create(&thread, NULL, &worker1, NULL));
+ assert(!pthread_join(thread, NULL));
+
+ assert(pthread_getspecific(key) == NULL);
+ assert(!pthread_setspecific(key, &key));
+ assert(pthread_getspecific(key) == &key);
+
+ assert(dtors_entered == 2);
+
+ assert(!pthread_create(&thread, NULL, &worker2, NULL));
+ assert(!pthread_join(thread, NULL));
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/pthread_kill.c b/lib/mlibc/tests/posix/pthread_kill.c
new file mode 100644
index 0000000..b740b70
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_kill.c
@@ -0,0 +1,46 @@
+#include <assert.h>
+#include <pthread.h>
+#include <signal.h>
+
+_Atomic int handler_ready = 0;
+_Atomic int thread_signal_ran = 0;
+
+static void sig_handler(int sig, siginfo_t *info, void *ctx) {
+ (void)sig;
+ (void)info;
+ (void)ctx;
+
+ thread_signal_ran = 1;
+}
+
+static void *worker(void *arg) {
+ (void)arg;
+
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_sigaction = sig_handler;
+ sa.sa_flags = SA_SIGINFO;
+ assert(!sigaction(SIGUSR1, &sa, NULL));
+
+ handler_ready = 1;
+
+ while (!thread_signal_ran)
+ ;
+
+ return NULL;
+}
+
+int main() {
+ pthread_t thread;
+ assert(!pthread_create(&thread, NULL, &worker, NULL));
+
+ while (!handler_ready)
+ ;
+
+ assert(!pthread_kill(thread, SIGUSR1));
+ assert(!pthread_join(thread, NULL));
+
+ assert(thread_signal_ran);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/pthread_mutex.c b/lib/mlibc/tests/posix/pthread_mutex.c
new file mode 100644
index 0000000..0a9b82f
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_mutex.c
@@ -0,0 +1,101 @@
+#include <pthread.h>
+#include <assert.h>
+#include <errno.h>
+
+#define TEST_ATTR(attr, field, value) ({ \
+ int x; \
+ assert(!pthread_mutexattr_set ## field (&(attr), (value))); \
+ assert(!pthread_mutexattr_get ## field (&(attr), &x)); \
+ assert(x == (value)); \
+ })
+
+int variable;
+pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void *worker(void *arg) {
+ (void)arg;
+ pthread_mutex_lock(&mutex);
+ variable = 1;
+ pthread_mutex_unlock(&mutex);
+ return NULL;
+}
+
+static void testAttr() {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+
+ TEST_ATTR(attr, type, PTHREAD_MUTEX_DEFAULT);
+ TEST_ATTR(attr, type, PTHREAD_MUTEX_NORMAL);
+ TEST_ATTR(attr, type, PTHREAD_MUTEX_ERRORCHECK);
+ TEST_ATTR(attr, type, PTHREAD_MUTEX_RECURSIVE);
+
+ TEST_ATTR(attr, robust, PTHREAD_MUTEX_STALLED);
+ TEST_ATTR(attr, robust, PTHREAD_MUTEX_ROBUST);
+
+ TEST_ATTR(attr, protocol, PTHREAD_PRIO_NONE);
+ TEST_ATTR(attr, protocol, PTHREAD_PRIO_INHERIT);
+ TEST_ATTR(attr, protocol, PTHREAD_PRIO_PROTECT);
+
+ TEST_ATTR(attr, pshared, PTHREAD_PROCESS_PRIVATE);
+ TEST_ATTR(attr, pshared, PTHREAD_PROCESS_SHARED);
+
+ // TODO: sched_get_priority* is unimplemented.
+ // int prio = sched_get_priority_max(SCHED_FIFO);
+ // TEST_ATTR(attr, prioceiling, prio);
+
+ pthread_mutexattr_destroy(&attr);
+}
+
+static void testNormal() {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutex_init(&mutex, &attr);
+ pthread_mutexattr_destroy(&attr);
+
+ pthread_mutex_lock(&mutex);
+ variable = 0;
+
+ pthread_t thread;
+ int ret = pthread_create(&thread, NULL, &worker, NULL);
+ assert(!ret);
+
+ assert(pthread_mutex_trylock(&mutex) == EBUSY);
+ pthread_mutex_unlock(&mutex);
+
+ ret = pthread_join(thread, NULL);
+ assert(!ret);
+ assert(variable == 1);
+
+ pthread_mutex_destroy(&mutex);
+}
+
+static void testRecursive() {
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mutex, &attr);
+ pthread_mutexattr_destroy(&attr);
+
+ pthread_mutex_lock(&mutex);
+ variable = 0;
+
+ pthread_t thread;
+ int ret = pthread_create(&thread, NULL, &worker, NULL);
+ assert(!ret);
+
+ assert(pthread_mutex_trylock(&mutex) == 0);
+ pthread_mutex_unlock(&mutex);
+ pthread_mutex_unlock(&mutex);
+
+ ret = pthread_join(thread, NULL);
+ assert(!ret);
+ assert(variable == 1);
+
+ pthread_mutex_destroy(&mutex);
+}
+
+int main() {
+ testAttr();
+ testNormal();
+ testRecursive();
+}
diff --git a/lib/mlibc/tests/posix/pthread_rwlock.c b/lib/mlibc/tests/posix/pthread_rwlock.c
new file mode 100644
index 0000000..5316372
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_rwlock.c
@@ -0,0 +1,114 @@
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+
+static void test_write_lock_unlock() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_wrlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_read_lock_unlock() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_rdlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_write_trylock_unlock() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_trywrlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_read_trylock_unlock() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_tryrdlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_write_prevents_read() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_wrlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_tryrdlock(&rw);
+ assert(res == EBUSY);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_write_prevents_write() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_wrlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_trywrlock(&rw);
+ assert(res == EBUSY);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_read_prevents_write() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_rdlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_trywrlock(&rw);
+ assert(res == EBUSY);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_read_allows_read() {
+ int res;
+ pthread_rwlock_t rw = PTHREAD_RWLOCK_INITIALIZER;
+ res = pthread_rwlock_rdlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_tryrdlock(&rw);
+ assert(!res);
+ res = pthread_rwlock_unlock(&rw);
+ assert(!res);
+}
+
+static void test_attr() {
+ pthread_rwlockattr_t attr;
+ pthread_rwlockattr_init(&attr);
+
+ int pshared;
+ pthread_rwlockattr_getpshared(&attr, &pshared);
+ assert(pshared == PTHREAD_PROCESS_PRIVATE);
+
+ pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+ pthread_rwlockattr_getpshared(&attr, &pshared);
+ assert(pshared == PTHREAD_PROCESS_SHARED);
+
+ pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
+ pthread_rwlockattr_getpshared(&attr, &pshared);
+ assert(pshared == PTHREAD_PROCESS_PRIVATE);
+
+ pthread_rwlockattr_destroy(&attr);
+}
+
+int main() {
+ test_write_lock_unlock();
+ test_read_lock_unlock();
+ test_write_trylock_unlock();
+ test_read_trylock_unlock();
+ test_write_prevents_read();
+ test_write_prevents_write();
+ test_read_prevents_write();
+ test_read_allows_read();
+ test_attr();
+}
diff --git a/lib/mlibc/tests/posix/pthread_schedparam.c b/lib/mlibc/tests/posix/pthread_schedparam.c
new file mode 100644
index 0000000..dde70fb
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_schedparam.c
@@ -0,0 +1,32 @@
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+int main() {
+ struct sched_param param = {
+ .sched_priority = 100,
+ };
+
+ int policy = 0xDEADBEEF;
+
+ int ret = pthread_getschedparam(pthread_self(), &policy, &param);
+ assert(policy == SCHED_OTHER);
+ assert(!ret);
+
+ param.sched_priority = 10;
+
+ ret = pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
+ assert(!ret || ret == EPERM);
+
+ if(ret == EPERM) {
+ exit(0);
+ }
+
+ param.sched_priority = 0xDEADBEEF;
+
+ ret = pthread_getschedparam(pthread_self(), &policy, &param);
+ assert(policy == SCHED_FIFO);
+ assert(param.sched_priority == 10);
+ assert(!ret);
+}
diff --git a/lib/mlibc/tests/posix/pthread_thread_local.c b/lib/mlibc/tests/posix/pthread_thread_local.c
new file mode 100644
index 0000000..90c6f72
--- /dev/null
+++ b/lib/mlibc/tests/posix/pthread_thread_local.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <pthread.h>
+#include <assert.h>
+
+_Thread_local unsigned int counter = 9999;
+_Thread_local unsigned int uninitialized;
+
+void *check_counter(void *arg)
+{
+ (void)arg;
+ fprintf(stderr, "counter for worker thread: %d, at %p\n", counter, &counter);
+ fflush(stderr);
+ assert(counter == 9999);
+
+ fprintf(stderr, "uninitialized data for worker thread: %d, at %p\n", uninitialized, &uninitialized);
+ fflush(stderr);
+ assert(uninitialized == 0);
+
+ ++counter;
+ ++uninitialized;
+ fprintf(stderr, "counter for worker thread: %d\n", counter);
+ fflush(stderr);
+ assert(counter == 10000);
+ assert(uninitialized == 1);
+ return NULL;
+}
+
+int main()
+{
+ fprintf(stderr, "counter for main thread: %d, at %p\n", counter, &counter);
+ fflush(stderr);
+ assert(counter == 9999);
+
+ fprintf(stderr, "uninitialized data for main thread: %d, at %p\n", uninitialized, &uninitialized);
+ fflush(stderr);
+ assert(uninitialized == 0);
+
+ ++counter;
+ ++uninitialized;
+ fprintf(stderr, "counter for main: %d\n", counter);
+ fflush(stderr);
+ assert(counter == 10000);
+ assert(uninitialized == 1);
+
+ pthread_t thd;
+ pthread_create(&thd, NULL, check_counter, NULL);
+ pthread_join(thd, NULL);
+
+ fprintf(stderr, "counter for main: %d\n", counter);
+ fflush(stderr);
+ assert(counter == 10000);
+ assert(uninitialized == 1);
+}
diff --git a/lib/mlibc/tests/posix/pwd.c b/lib/mlibc/tests/posix/pwd.c
new file mode 100644
index 0000000..a9ebd8e
--- /dev/null
+++ b/lib/mlibc/tests/posix/pwd.c
@@ -0,0 +1,88 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+int main()
+{
+ struct passwd pwd, *result = NULL;
+ char *buf;
+ size_t bufsize;
+ int s;
+
+ bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ assert(bufsize > 0 && bufsize < 0x100000);
+
+ buf = malloc(bufsize);
+ assert(buf);
+
+ s = getpwnam_r("root", &pwd, buf, bufsize, &result);
+ assert(!s);
+ assert(pwd.pw_uid == 0);
+ assert(!strcmp(pwd.pw_name, "root"));
+ assert(strlen(pwd.pw_passwd) <= 1000);
+
+ s = getpwuid_r(0, &pwd, buf, bufsize, &result);
+ assert(!s);
+ assert(pwd.pw_uid == 0);
+ assert(!strcmp(pwd.pw_name, "root"));
+ assert(strlen(pwd.pw_passwd) <= 1000);
+
+ result = getpwnam("root");
+ assert(result);
+ assert(result->pw_uid == 0);
+ assert(!strcmp(result->pw_name, "root"));
+ assert(strlen(result->pw_passwd) <= 1000);
+
+ result = getpwuid(0);
+ assert(result);
+ assert(result->pw_uid == 0);
+ assert(!strcmp(result->pw_name, "root"));
+ assert(strlen(result->pw_passwd) <= 1000);
+
+ errno = 0;
+ setpwent();
+ assert(errno == 0);
+
+ errno = 0;
+ result = getpwent();
+ assert(result);
+
+ pwd = *result;
+ pwd.pw_name = strdup(result->pw_name);
+ pwd.pw_passwd = strdup(result->pw_passwd);
+ pwd.pw_gecos = strdup(result->pw_gecos);
+ pwd.pw_dir = strdup(result->pw_dir);
+ pwd.pw_shell = strdup(result->pw_shell);
+ assert(pwd.pw_name);
+ assert(pwd.pw_passwd);
+ assert(pwd.pw_gecos);
+ assert(pwd.pw_dir);
+ assert(pwd.pw_shell);
+
+ errno = 0;
+ setpwent();
+ assert(errno == 0);
+
+ errno = 0;
+ result = getpwent();
+ assert(result);
+
+ assert(!strcmp(pwd.pw_name, result->pw_name));
+ assert(!strcmp(pwd.pw_passwd, result->pw_passwd));
+ assert(!strcmp(pwd.pw_gecos, result->pw_gecos));
+ assert(!strcmp(pwd.pw_dir, result->pw_dir));
+ assert(!strcmp(pwd.pw_shell, result->pw_shell));
+ assert(pwd.pw_uid == result->pw_uid);
+ assert(pwd.pw_gid == result->pw_gid);
+
+ free(buf);
+ free(pwd.pw_name);
+ free(pwd.pw_passwd);
+ free(pwd.pw_gecos);
+ free(pwd.pw_dir);
+ free(pwd.pw_shell);
+}
diff --git a/lib/mlibc/tests/posix/readv-writev.c b/lib/mlibc/tests/posix/readv-writev.c
new file mode 100644
index 0000000..5025ce2
--- /dev/null
+++ b/lib/mlibc/tests/posix/readv-writev.c
@@ -0,0 +1,51 @@
+#include <assert.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/uio.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_FILE "readv-writev-host-libc.tmp"
+#else
+#define TEST_FILE "readv-writev.tmp"
+#endif
+
+int main() {
+ // Make sure that it wasn't created by a previous test run
+ unlink(TEST_FILE);
+
+ int fd = open(TEST_FILE, O_RDWR | O_CREAT, 0644);
+ assert(fd != -1);
+
+ char str0[] = "hello ";
+ char str1[] = "world!";
+
+ struct iovec bufs[2] = {
+ {
+ .iov_base = &str0,
+ .iov_len = strlen(str0),
+ },
+ {
+ .iov_base = &str1,
+ .iov_len = strlen(str1),
+ },
+ };
+
+ ssize_t written = writev(fd, bufs, 2);
+ assert(written == 12);
+
+ memset(&str0, 0, strlen(str0));
+ memset(&str1, 0, strlen(str1));
+
+ assert(!lseek(fd, 0, SEEK_SET));
+
+ ssize_t read = readv(fd, bufs, 2);
+ assert(read == 12);
+
+ assert(!strncmp(str0, "hello ", 7));
+ assert(!strncmp(str1, "world!", 7));
+
+ assert(!close(fd));
+
+ unlink(TEST_FILE);
+}
diff --git a/lib/mlibc/tests/posix/realpath.c b/lib/mlibc/tests/posix/realpath.c
new file mode 100644
index 0000000..e44de81
--- /dev/null
+++ b/lib/mlibc/tests/posix/realpath.c
@@ -0,0 +1,133 @@
+#include <unistd.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+
+#ifdef USE_HOST_LIBC
+#define TEST_BASE "/tmp/mlibc-realpath-host-libc"
+#else
+#define TEST_BASE "/tmp/mlibc-realpath"
+#endif
+
+void prepare() {
+ assert(!mkdir(TEST_BASE "/", S_IRWXU));
+ assert(!mkdir(TEST_BASE "/dir1", S_IRWXU));
+ assert(!mkdir(TEST_BASE "/dir2", S_IRWXU));
+ assert(!symlink(TEST_BASE "/dir2/", TEST_BASE "/dir1/abs-link"));
+ assert(!chdir(TEST_BASE "/dir1"));
+ assert(!symlink("../dir2/", TEST_BASE "/dir1/rel-link"));
+}
+
+void cleanup(int do_assert) {
+ if (do_assert) {
+ assert(!unlink(TEST_BASE "/dir1/rel-link"));
+ assert(!unlink(TEST_BASE "/dir1/abs-link"));
+ assert(!rmdir(TEST_BASE "/dir2"));
+ assert(!rmdir(TEST_BASE "/dir1"));
+ assert(!rmdir(TEST_BASE "/"));
+ } else {
+ unlink(TEST_BASE "/dir1/rel-link");
+ unlink(TEST_BASE "/dir1/abs-link");
+ rmdir(TEST_BASE "/dir2");
+ rmdir(TEST_BASE "/dir1");
+ rmdir(TEST_BASE "/");
+ }
+}
+
+void signal_handler(int sig, siginfo_t *info, void *ctx) {
+ (void)sig;
+ (void)info;
+ (void)ctx;
+
+ cleanup(0);
+
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = 0;
+
+ sigaction(SIGABRT, &sa, NULL);
+ abort();
+}
+
+int main() {
+ char *path;
+
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_sigaction = signal_handler;
+ sa.sa_flags = SA_SIGINFO;
+ sigaction(SIGABRT, &sa, NULL);
+
+ prepare();
+
+ path = realpath(TEST_BASE "/dir1/", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir1"));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/../dir2", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir2"));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/abs-link/", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir2"));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/rel-link/", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir2"));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/abs-link/../", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE ""));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/rel-link/../", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE ""));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/abs-link/../dir1/abs-link/", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir2"));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/rel-link/../dir1/rel-link/", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir2"));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/abs-link/../dir1/rel-link/", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir2"));
+ free(path);
+
+ path = realpath(TEST_BASE "/dir1/rel-link/../dir1/abs-link/", NULL);
+ assert(path);
+ assert(!strcmp(path, TEST_BASE "/dir2"));
+ free(path);
+
+ path = realpath("/tmp", NULL);
+ assert(path);
+ assert(!strcmp(path, "/tmp"));
+ free(path);
+
+ path = realpath("/", NULL);
+ assert(path);
+ assert(!strcmp(path, "/"));
+ free(path);
+
+ path = realpath("//", NULL);
+ assert(path);
+ assert(!strcmp(path, "/"));
+ free(path);
+
+ cleanup(1);
+}
diff --git a/lib/mlibc/tests/posix/regex.c b/lib/mlibc/tests/posix/regex.c
new file mode 100644
index 0000000..6c2ca3f
--- /dev/null
+++ b/lib/mlibc/tests/posix/regex.c
@@ -0,0 +1,30 @@
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+int main(void) {
+ char *testString = "mlibc is the best best best libc";
+ char *pattern = "\\(be[a-z]t\\) \\1";
+
+ regex_t reg;
+ int rc = regcomp(&reg, pattern, 0);
+ assert(!rc);
+
+ regmatch_t matches[2];
+ rc = regexec(&reg, testString, 2, matches, 0);
+ assert(!rc);
+
+ printf("Whole pattern: \"%.*s\" at %zd-%zd.\n",
+ (int)(matches[0].rm_eo - matches[0].rm_so), &testString[matches[0].rm_so],
+ (ssize_t)matches[0].rm_so, (ssize_t)(matches[0].rm_eo - 1));
+ assert(matches[0].rm_so == 13 && matches[0].rm_eo == 22);
+
+ printf("Substring: \"%.*s\" at %zd-%zd.\n",
+ (int)(matches[1].rm_eo - matches[1].rm_so), &testString[matches[1].rm_so],
+ (ssize_t)matches[1].rm_so, (ssize_t)matches[1].rm_eo - 1);
+ assert(matches[1].rm_so == 13 && matches[1].rm_eo == 17);
+
+ regfree(&reg);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/rindex.c b/lib/mlibc/tests/posix/rindex.c
new file mode 100644
index 0000000..0eb2f54
--- /dev/null
+++ b/lib/mlibc/tests/posix/rindex.c
@@ -0,0 +1,11 @@
+#include <strings.h>
+#include <assert.h>
+
+int main() {
+ char str[] = "This is a sample string";
+ char *pch;
+ pch = rindex(str, 's');
+ // The last occurence of 's' is at position 18
+ assert(pch - str + 1 == 18);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/rlimits.c b/lib/mlibc/tests/posix/rlimits.c
new file mode 100644
index 0000000..3565521
--- /dev/null
+++ b/lib/mlibc/tests/posix/rlimits.c
@@ -0,0 +1,26 @@
+#include <sys/resource.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+
+int main() {
+ struct rlimit getlim, setlim;
+
+ setlim.rlim_cur = 16;
+ setlim.rlim_max = 4096;
+
+ int ret = setrlimit(RLIMIT_NOFILE, &setlim);
+
+ if(ret == -1) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ assert(!ret);
+ }
+
+ assert(!getrlimit(RLIMIT_NOFILE, &getlim));
+
+ assert(setlim.rlim_cur == getlim.rlim_cur);
+ assert(setlim.rlim_max == getlim.rlim_max);
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/search.c b/lib/mlibc/tests/posix/search.c
new file mode 100644
index 0000000..6b68ef9
--- /dev/null
+++ b/lib/mlibc/tests/posix/search.c
@@ -0,0 +1,51 @@
+#include <search.h>
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+static int compare(const void *pa, const void *pb) {
+ if (*(int*)pa < *(int*) pb)
+ return -1;
+ if (*(int*)pa > *(int*) pb)
+ return 1;
+ return 0;
+}
+
+static void check_key(int key, void *root) {
+ int keyp = key;
+ void *ret = tfind((void*) &keyp, &root, compare);
+ assert(ret);
+ assert(**((int **) ret) == key);
+}
+
+static void free_key(void *key) {
+ free(key);
+}
+
+int main() {
+ void *root = NULL;
+ for (int i = 0; i < 12; i++) {
+ int *ptr = malloc(sizeof(int));
+ assert(ptr);
+ *ptr = i;
+
+ void *ret = tsearch((void*) ptr, &root, compare);
+ assert(ret);
+ assert(**((int **) ret) == i);
+ }
+
+ // Test a couple of keys
+ check_key(1, root);
+ check_key(5, root);
+ check_key(10, root);
+
+ // Verify NULL on non-existent key
+ int key = -1;
+ void *ret = tfind((void*) &key, &root, compare);
+ assert(ret == NULL);
+
+ // tdelete is not implemented yet (#351)
+ (void)free_key;
+ // tdestroy(root, free_key);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/setpriority.c b/lib/mlibc/tests/posix/setpriority.c
new file mode 100644
index 0000000..11cb7ed
--- /dev/null
+++ b/lib/mlibc/tests/posix/setpriority.c
@@ -0,0 +1,27 @@
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+int main() {
+ errno = 0;
+ int original_priority = getpriority(PRIO_PROCESS, getpid());
+
+ assert(original_priority != -1 || !errno);
+
+ int ret = setpriority(PRIO_PROCESS, getpid(), original_priority + 1);
+
+ if(ret) {
+ fprintf(stderr, "%s", strerror(errno));
+ exit(1);
+ }
+
+ errno = 0;
+ int new_priority = getpriority(PRIO_PROCESS, getpid());
+
+ assert(new_priority != -1 || !errno);
+ assert(new_priority == original_priority + 1);
+}
diff --git a/lib/mlibc/tests/posix/sigaltstack.c b/lib/mlibc/tests/posix/sigaltstack.c
new file mode 100644
index 0000000..bebcc30
--- /dev/null
+++ b/lib/mlibc/tests/posix/sigaltstack.c
@@ -0,0 +1,55 @@
+#include <assert.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdlib.h>
+
+static jmp_buf env;
+static char *sigStack;
+
+static void sig_handler(int sig, siginfo_t *info, void *ctx) {
+ (void)sig;
+ (void)info;
+ (void)ctx;
+
+ longjmp(env, 1);
+}
+
+int main() {
+ if (setjmp(env)) {
+ free(sigStack);
+ return 0;
+ }
+
+ sigStack = malloc(SIGSTKSZ);
+
+ stack_t ss;
+ ss.ss_sp = sigStack;
+ ss.ss_size = SIGSTKSZ;
+ ss.ss_flags = 0;
+
+ assert(!sigaltstack(&ss, NULL));
+
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ sa.sa_sigaction = sig_handler;
+
+ assert(!sigaction(SIGSEGV, &sa, NULL));
+
+ // This is used to trash the stack to ensure sigaltstack actually switched stacks.
+#if defined(__x86_64__)
+ asm volatile ("mov $0, %rsp\n"
+ "\t" "push $0");
+#elif defined(__i386__)
+ asm volatile ("mov $0, %esp\n"
+ "\t" "push $0");
+#elif defined(__aarch64__)
+ asm volatile ("mov sp, %0\n"
+ "\t" "stp x0, x1, [sp, #-16]!" :: "r"((uint64_t)0));
+#elif defined(__riscv) && __riscv_xlen == 64
+ asm volatile ("li sp, 0\n"
+ "\t" "sd zero, 0(sp)");
+#else
+# error Unknown architecture
+#endif
+}
diff --git a/lib/mlibc/tests/posix/sigsuspend.c b/lib/mlibc/tests/posix/sigsuspend.c
new file mode 100644
index 0000000..09b9a1e
--- /dev/null
+++ b/lib/mlibc/tests/posix/sigsuspend.c
@@ -0,0 +1,53 @@
+#include <assert.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <signal.h>
+#include <errno.h>
+
+_Atomic int handler_ready = 0;
+_Atomic int thread_signal_ran = 0;
+
+static void sig_handler(int sig, siginfo_t *info, void *ctx) {
+ (void)sig;
+ (void)info;
+ (void)ctx;
+
+ thread_signal_ran = 1;
+}
+
+static void *worker(void *arg) {
+ (void)arg;
+
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_sigaction = sig_handler;
+ sa.sa_flags = SA_SIGINFO;
+ assert(!sigaction(SIGUSR1, &sa, NULL));
+
+ handler_ready = 1;
+
+ sigset_t set;
+ sigfillset(&set);
+ sigdelset(&set, SIGUSR1);
+
+ assert(sigsuspend(&set));
+ assert(thread_signal_ran);
+ assert(errno == EINTR);
+
+ return NULL;
+}
+
+int main() {
+ pthread_t thread;
+ assert(!pthread_create(&thread, NULL, &worker, NULL));
+
+ while (!handler_ready)
+ ;
+
+ sleep(1);
+
+ assert(!pthread_kill(thread, SIGUSR1));
+ assert(!pthread_join(thread, NULL));
+
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/sigtimedwait.c b/lib/mlibc/tests/posix/sigtimedwait.c
new file mode 100644
index 0000000..4793fc1
--- /dev/null
+++ b/lib/mlibc/tests/posix/sigtimedwait.c
@@ -0,0 +1,91 @@
+#include <stdio.h>
+
+#include <assert.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+pid_t parent;
+pid_t child;
+
+int fds[2];
+
+void parent_fn() {
+ int res;
+
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGUSR2);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+
+ res = write(fds[1], "!", 1);
+ assert(res == 1);
+
+ siginfo_t info;
+ res = sigwaitinfo(&set, &info);
+ assert(res == SIGUSR2);
+ assert(info.si_signo == SIGUSR2);
+
+ res = write(fds[1], "!", 1);
+ assert(res == 1);
+
+ // XXX: This may not be long enough to get scheduled
+ struct timespec tout = {1, 0};
+ res = sigtimedwait(&set, &info, &tout);
+ assert(res == SIGUSR2);
+ assert(info.si_signo == SIGUSR2);
+
+ res = write(fds[1], "!", 1);
+ assert(res == 1);
+
+ int sig;
+ res = sigwait(&set, &sig);
+ assert(res == 0);
+ assert(sig == SIGUSR2);
+
+ res = write(fds[1], "!", 1);
+ assert(res == 1);
+
+ res = sigtimedwait(&set, &info, &tout);
+ assert(res < 0);
+ assert(errno == EAGAIN);
+
+ int wsts;
+ res = waitpid(child, &wsts, 0);
+ assert(res >= 0);
+}
+
+void child_fn() {
+ int res;
+ char c;
+
+ res = read(fds[0], &c, 1);
+ assert(res == 1);
+ kill(parent, SIGUSR2);
+ res = read(fds[0], &c, 1);
+ assert(res == 1);
+ kill(parent, SIGUSR2);
+ res = read(fds[0], &c, 1);
+ assert(res == 1);
+ kill(parent, SIGUSR2);
+ res = read(fds[0], &c, 1);
+ assert(res == 1);
+}
+
+int main() {
+ int res;
+
+ parent = getpid();
+ assert(parent > 0);
+
+ res = pipe(fds);
+ assert(res == 0);
+
+ child = fork();
+ assert(child >= 0);
+ if (child)
+ parent_fn();
+ else
+ child_fn();
+}
diff --git a/lib/mlibc/tests/posix/strdupa.c b/lib/mlibc/tests/posix/strdupa.c
new file mode 100644
index 0000000..c79de8e
--- /dev/null
+++ b/lib/mlibc/tests/posix/strdupa.c
@@ -0,0 +1,14 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <assert.h>
+#include <string.h>
+
+int main() {
+ char test[19] = "Hello mlibc World!";
+ char *alloca_ed = strdupa(test);
+ assert(!strcmp(test, alloca_ed));
+
+ char *trimmed = strndupa(test, 5);
+ assert(!strcmp("Hello", trimmed));
+}
diff --git a/lib/mlibc/tests/posix/string.c b/lib/mlibc/tests/posix/string.c
new file mode 100644
index 0000000..9c6e036
--- /dev/null
+++ b/lib/mlibc/tests/posix/string.c
@@ -0,0 +1,29 @@
+#include <string.h>
+#include <assert.h>
+
+int main() {
+ char buf[4];
+
+ // stpncpy
+ assert(stpncpy(buf, "", 4) == buf);
+ assert(!strcmp(buf, ""));
+
+ assert(stpncpy(buf, "123", 4) == buf + 3);
+ assert(!strcmp(buf, "123"));
+
+ assert(stpncpy(buf, "12", 4) == buf + 2);
+ assert(buf[0] == '1' && buf[1] == '2' && buf[2] == '\0' && buf[3] == '\0');
+
+ assert(stpncpy(buf, "123456", 4) == buf + 4);
+ assert(buf[0] == '1' && buf[1] == '2' && buf[2] == '3' && buf[3] == '4');
+
+ // stpcpy
+ assert(stpcpy(buf, "") == buf);
+ assert(!strcmp(buf, ""));
+
+ assert(stpcpy(buf, "12") == buf + 2);
+ assert(!strcmp(buf, "12"));
+
+ assert(stpcpy(buf, "123") == buf + 3);
+ assert(!strcmp(buf, "123"));
+}
diff --git a/lib/mlibc/tests/posix/system.c b/lib/mlibc/tests/posix/system.c
new file mode 100644
index 0000000..d5d6317
--- /dev/null
+++ b/lib/mlibc/tests/posix/system.c
@@ -0,0 +1,18 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+int main() {
+ int res;
+
+ res = system("true");
+ assert(WIFEXITED(res));
+ assert(WEXITSTATUS(res) == 0);
+
+ res = system("false");
+ assert(WIFEXITED(res));
+ assert(WEXITSTATUS(res) == 1);
+
+ res = system(NULL);
+ assert(res == 1);
+}
diff --git a/lib/mlibc/tests/posix/time.c b/lib/mlibc/tests/posix/time.c
new file mode 100644
index 0000000..add7b92
--- /dev/null
+++ b/lib/mlibc/tests/posix/time.c
@@ -0,0 +1,191 @@
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define BUF_SIZE 1024
+
+int main() {
+ struct tm tm = {0};
+ char buf[BUF_SIZE];
+
+ char *a = strptime("%", "%%", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(strftime(buf, BUF_SIZE, "%%", &tm) == 1);
+ assert(!strcmp(buf, "%"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("1991-11-21", "%F", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_mday == 21);
+ assert(tm.tm_mon == 10);
+ assert(tm.tm_wday == 4);
+ assert(tm.tm_yday == 324);
+ assert(strftime(buf, BUF_SIZE, "%F", &tm) == 10);
+ assert(!strcmp(buf, "1991-11-21"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("10/19/91", "%D", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_mday == 19);
+ assert(tm.tm_mon == 9);
+ assert(tm.tm_year == 91);
+ assert(tm.tm_wday == 6);
+ assert(tm.tm_yday == 291);
+ assert(strftime(buf, BUF_SIZE, "%D", &tm) == 8);
+ assert(!strcmp(buf, "10/19/91"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("15:23", "%R", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_min == 23);
+ assert(tm.tm_hour == 15);
+ assert(strftime(buf, BUF_SIZE, "%R", &tm) == 5);
+ assert(!strcmp(buf, "15:23"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("17:12:56", "%T", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_sec == 56);
+ assert(tm.tm_min == 12);
+ assert(tm.tm_hour == 17);
+ assert(strftime(buf, BUF_SIZE, "%T", &tm) == 8);
+ assert(!strcmp(buf, "17:12:56"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("10", "%m", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_yday == 272);
+ assert(strftime(buf, BUF_SIZE, "%m", &tm) == 2);
+ assert(!strcmp(buf, "10"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("14 83", "%C %y", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_year == -417);
+ assert(strftime(buf, BUF_SIZE, "%C %y", &tm) == 5);
+ assert(!strcmp(buf, "14 83"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("32 16", "%y %C", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_year == -268);
+ assert(tm.tm_wday == 3);
+ assert(strftime(buf, BUF_SIZE, "%y %C", &tm) == 5);
+ assert(!strcmp(buf, "32 16"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("12", "%C", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_year == -700);
+ assert(tm.tm_wday == 5);
+ assert(strftime(buf, BUF_SIZE, "%C", &tm) == 2);
+ assert(!strcmp(buf, "12"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("1683-9-23", "%F", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_mday == 23);
+ assert(tm.tm_mon == 8);
+ assert(tm.tm_year == -217);
+ assert(tm.tm_wday == 4);
+ assert(tm.tm_yday == 265);
+ assert(strftime(buf, BUF_SIZE, "%F", &tm) == 10);
+ assert(!strcmp(buf, "1683-09-23"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("14 53", "%H%t%S", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_sec == 53);
+ assert(tm.tm_hour == 14);
+ assert(strftime(buf, BUF_SIZE, "%H%t%S", &tm) == 5);
+ assert(!strcmp(buf, "14 53"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("24", "%H", &tm);
+ assert(a == NULL);
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("0", "%I", &tm);
+ assert(a == NULL);
+ memset(&tm, 0, sizeof(tm));
+
+ setlocale(LC_TIME, "en_US.UTF-8");
+ a = strptime("10 21 PM", "%I %M %p", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_hour == 22);
+ assert(tm.tm_min == 21);
+ assert(strftime(buf, BUF_SIZE, "%I %M %p", &tm) == 8);
+ assert(!strcmp(buf, "10 21 PM"));
+ memset(&tm, 0, sizeof(tm));
+
+ tm.tm_min = 23;
+ assert(strftime(buf, BUF_SIZE, "%I %M", &tm) == 5);
+ assert(!strcmp(buf, "12 23"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("January", "%h", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_mon == 0);
+ assert(strftime(buf, BUF_SIZE, "%h %b", &tm) == 7);
+ assert(!strcmp(buf, "Jan Jan"));
+ assert(strftime(buf, BUF_SIZE, "%B", &tm) == 7);
+ assert(!strcmp(buf, "January"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("2", "%j", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_yday == 1);
+ assert(strftime(buf, BUF_SIZE, "%j", &tm) == 3);
+ assert(!strcmp(buf, "002"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("Wednesday", "%A", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_wday == 3);
+ assert(strftime(buf, BUF_SIZE, "%A", &tm) == 9);
+ assert(!strcmp(buf, "Wednesday"));
+ memset(&tm, 0, sizeof(tm));
+
+ a = strptime("11:51:13 PM", "%r", &tm);
+ assert(a != NULL);
+ assert(*a == '\0');
+ assert(tm.tm_hour == 23);
+ assert(tm.tm_min == 51);
+ assert(tm.tm_sec == 13);
+ assert(strftime(buf, BUF_SIZE, "%r", &tm) == 11);
+ assert(!strcmp(buf, "11:51:13 PM"));
+ memset(&tm, 0, sizeof(tm));
+
+ tm.tm_hour = 0;
+ tm.tm_min = 51;
+ tm.tm_sec = 13;
+ assert(strftime(buf, BUF_SIZE, "%r", &tm) == 11);
+ assert(!strcmp(buf, "12:51:13 AM"));
+ memset(&tm, 0, sizeof(tm));
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+ assert(strftime(buf, BUF_SIZE, "%", &tm) == 1);
+ fprintf(stderr, "%s\n", buf);
+ assert(!strcmp(buf, "%"));
+ memset(&tm, 0, sizeof(tm));
+#pragma GCC diagnostic pop
+}
diff --git a/lib/mlibc/tests/posix/timer.c b/lib/mlibc/tests/posix/timer.c
new file mode 100644
index 0000000..f39e928
--- /dev/null
+++ b/lib/mlibc/tests/posix/timer.c
@@ -0,0 +1,25 @@
+#include <stdio.h>
+#include <sys/time.h>
+#include <assert.h>
+
+int main() {
+ struct timeval a = {0, 0};
+ struct timeval b = {0, 0};
+ struct timeval res = {0, 0};
+ a.tv_sec = 10;
+ assert(timerisset(&a) == 1);
+ timerclear(&a);
+ assert(timerisset(&a) == 0);
+ a.tv_sec = 40;
+ a.tv_usec = 500;
+ b.tv_sec = 10;
+ b.tv_usec = 20;
+ timeradd(&a, &b, &res);
+ assert(res.tv_sec == 50);
+ assert(res.tv_usec == 520);
+ timerclear(&res);
+ timersub(&a, &b, &res);
+ assert(res.tv_sec == 30);
+ assert(res.tv_usec == 480);
+ return 0;
+}
diff --git a/lib/mlibc/tests/posix/vfork.c b/lib/mlibc/tests/posix/vfork.c
new file mode 100644
index 0000000..5f8b71d
--- /dev/null
+++ b/lib/mlibc/tests/posix/vfork.c
@@ -0,0 +1,28 @@
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+void prevent_atforks(void){
+ exit(1);
+}
+
+int main() {
+ pid_t pid;
+ int status;
+ assert(!pthread_atfork(prevent_atforks, NULL, NULL));
+
+ switch (pid = vfork()) {
+ case -1:
+ perror("vfork");
+ abort();
+ case 0:
+ _exit(12);
+ default: break;
+ }
+
+ assert(wait(&status) == pid);
+ assert(WIFEXITED(status) && WEXITSTATUS(status) == 12);
+}
diff --git a/lib/mlibc/tests/posix/waitid.c b/lib/mlibc/tests/posix/waitid.c
new file mode 100644
index 0000000..e69b1ef
--- /dev/null
+++ b/lib/mlibc/tests/posix/waitid.c
@@ -0,0 +1,20 @@
+#include <assert.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+siginfo_t si;
+
+int main() {
+ int pid = fork();
+
+ if(!pid)
+ exit(69);
+
+ int ret = waitid(P_ALL, 0, &si, WEXITED);
+ assert(ret == 0);
+ assert(si.si_pid == pid);
+ assert(si.si_signo == SIGCHLD);
+ assert(si.si_code == CLD_EXITED);
+} \ No newline at end of file
diff --git a/lib/mlibc/tests/posix/wcwidth.c b/lib/mlibc/tests/posix/wcwidth.c
new file mode 100644
index 0000000..2e6fc8c
--- /dev/null
+++ b/lib/mlibc/tests/posix/wcwidth.c
@@ -0,0 +1,67 @@
+#include <assert.h>
+#include <locale.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+
+#if UINTPTR_MAX == UINT64_MAX
+#define WCHAR_SPEC ""
+#else
+#define WCHAR_SPEC "l"
+#endif
+
+/*
+ * The code in this test is taken from https://github.com/termux/wcwidth/,
+ * under the following license:
+ *
+ * Copyright (C) Fredrik Fornwall 2016.
+ * Distributed under the MIT License.
+ *
+ * Implementation of wcwidth(3) as a C port of:
+ * https://github.com/jquast/wcwidth
+ *
+ * Report issues at:
+ * https://github.com/termux/wcwidth
+ */
+
+static int tests_run;
+static int test_failures;
+
+void assertWidthIs(int expected_width, wchar_t c) {
+ tests_run++;
+ int actual_width = wcwidth(c);
+ if (actual_width != expected_width) {
+ fprintf(stderr, "ERROR: wcwidth(U+%04" WCHAR_SPEC "x, '%lc') returned %d, expected %d\n", c, (wint_t) c, actual_width, expected_width);
+ test_failures++;
+ }
+}
+
+int main() {
+ setlocale(LC_CTYPE, "C.UTF-8");
+ assertWidthIs(1, 'a');
+ assertWidthIs(1, L'ö');
+
+ // Some wide:
+ assertWidthIs(2, L'A');
+ assertWidthIs(2, L'ï¼¢');
+ assertWidthIs(2, L'ï¼£');
+ assertWidthIs(2, L'中');
+ assertWidthIs(2, L'æ–‡');
+ assertWidthIs(2, 0x679C);
+ assertWidthIs(2, 0x679D);
+ assertWidthIs(2, 0x2070E);
+ assertWidthIs(2, 0x20731);
+
+#ifndef USE_HOST_LIBC
+ assertWidthIs(1, 0x11A3);
+#endif
+
+ assertWidthIs(2, 0x1F428); // Koala emoji.
+ assertWidthIs(2, 0x231a); // Watch emoji.
+
+ if (test_failures > 0) printf("%d tests FAILED, ", test_failures);
+ printf("%d tests OK\n", tests_run - test_failures);
+ return (test_failures == 0) ? 0 : 1;
+}
diff --git a/lib/mlibc/tests/posix/wordexp.c b/lib/mlibc/tests/posix/wordexp.c
new file mode 100644
index 0000000..063a374
--- /dev/null
+++ b/lib/mlibc/tests/posix/wordexp.c
@@ -0,0 +1,140 @@
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <wordexp.h>
+
+static const char *wordexp_return_stringify(int val, int *should_free);
+
+struct we_test {
+ int expected_return;
+ const char *words;
+ int flags;
+ const char *wordv[16];
+ size_t wordc;
+} test_cases[] = {
+ /* (unbalanced) pairs */
+ {WRDE_SYNTAX, "\\", 0, {NULL}, 0},
+ {WRDE_SYNTAX, "\'", 0, {NULL}, 0},
+ {0, "\'\'", 0, {""}, 1},
+ {0, "\'\"\'", 0, {"\""}, 1},
+ {WRDE_SYNTAX, "\"\'", 0, {NULL}, 0},
+
+ /* numbers & calculations */
+ {0, "$((5+5))", 0, {"10"}, 1},
+ {0, "$((-5+5))", 0, {"0"}, 1},
+ {0, "$((010))", 0, {"8"}, 1},
+ {0, "$((0x10))", 0, {"16"}, 1},
+
+ /* errant symbols */
+ {WRDE_BADCHAR, "(string", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "string)", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "{string", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "string}", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "<string", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "string>", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "string|", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "string;", WRDE_NOCMD, {NULL}, 0},
+ {WRDE_BADCHAR, "string&", WRDE_NOCMD, {NULL}, 0},
+
+ /* command substitution with WRDE_NOCMD */
+ {WRDE_CMDSUB, "$(pwd)", WRDE_NOCMD, {NULL}, 0},
+
+ /* env vars */
+ {WRDE_BADVAL, "$DOES_NOT_EXIST", WRDE_UNDEF, {NULL}, 0},
+
+ /* normal arguments */
+ {0, "arg1 arg2", 0, {"arg1", "arg2"}, 2},
+ {-1, NULL, 0, {NULL}, 0},
+};
+
+int main() {
+ wordexp_t we;
+
+ wordexp("$(pwd)", &we, 0);
+ char *cwd = getcwd(NULL, 0);
+ assert(!strcmp(cwd, we.we_wordv[0]));
+ wordfree(&we);
+ free(cwd);
+
+ char *home;
+ assert(asprintf(&home, "%s/.config", getenv("HOME")) != -1);
+ wordexp("$HOME ~ ~/.config", &we, WRDE_REUSE);
+ assert(!strcmp(getenv("HOME"), we.we_wordv[0]));
+ assert(!strcmp(getenv("HOME"), we.we_wordv[1]));
+ assert(!strcmp(home, we.we_wordv[2]));
+ free(home);
+ wordfree(&we);
+
+ struct passwd *pw = getpwnam("root");
+ assert(asprintf(&home, "%s/.config", pw->pw_dir) != -1);
+ wordexp("~root ~root/.config", &we, WRDE_REUSE);
+ assert(!strcmp(pw->pw_dir, we.we_wordv[0]));
+ assert(!strcmp(home, we.we_wordv[1]));
+ free(home);
+ wordfree(&we);
+
+ size_t i = 0;
+
+ for(struct we_test *test = &test_cases[i]; test->expected_return != -1; test = &test_cases[++i]) {
+ wordexp_t test_we;
+
+ int should_free;
+ const char *expected = wordexp_return_stringify(test->expected_return, &should_free);
+ fprintf(stderr, "TESTCASE %zu: '%s' with flags %d expected to return %s\n", i, test->words, test->flags, expected);
+ int test_ret = wordexp(test->words, &test_we, test->flags);
+
+ if(test_ret != test->expected_return) {
+ fprintf(stderr, "\twordexp() returned %s, but we expect %s\n", wordexp_return_stringify(test_ret, &should_free), wordexp_return_stringify(test->expected_return, &should_free));
+ }
+
+ assert(test_ret == test->expected_return);
+
+ if(test_ret != 0)
+ continue;
+
+ assert(test_we.we_wordc == test->wordc);
+
+ for(size_t j = 0; j < 16 && j < test->wordc; j++) {
+ assert(test->wordv[j] && test_we.we_wordv[j]);
+ assert(!strcmp(test->wordv[j], test_we.we_wordv[j]));
+ }
+
+ wordfree(&test_we);
+ if (should_free)
+ free((void *)expected);
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+struct wordexp_return_val {
+ int val;
+ const char *string;
+} wordexp_return_vals[] = {
+ {0, "WRDE_SUCCESS"},
+ {WRDE_BADCHAR, "WRDE_BADCHAR"},
+ {WRDE_BADVAL, "WRDE_BADVAL"},
+ {WRDE_CMDSUB, "WRDE_CMDSUB"},
+ {WRDE_SYNTAX, "WRDE_SYNTAX"},
+ {-1, NULL},
+};
+
+static const char *wordexp_return_stringify(int val, int *should_free) {
+ for(size_t i = 0; wordexp_return_vals[i].val != -1 || wordexp_return_vals[i].string; i++) {
+ if(wordexp_return_vals[i].val == val) {
+ *should_free = 0;
+ return wordexp_return_vals[i].string;
+ }
+ }
+
+ char *unknown;
+ assert(asprintf(&unknown, "unknown return value %d", val) != -1);
+ *should_free = 1;
+
+ return unknown;
+}
diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/libbar.c b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libbar.c
new file mode 100644
index 0000000..41ccc56
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libbar.c
@@ -0,0 +1,3 @@
+// Bar needs to have a relocation against foo in order to set DT_NEEDED.
+int foo(void);
+int bar() { return foo(); }
diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/libfoo.c b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libfoo.c
new file mode 100644
index 0000000..9fe07f8
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/libfoo.c
@@ -0,0 +1 @@
+int foo() { return 0; }
diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/meson.build b/lib/mlibc/tests/rtdl/dl_iterate_phdr/meson.build
new file mode 100644
index 0000000..acb679e
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/meson.build
@@ -0,0 +1,7 @@
+libfoo = shared_library('foo', 'libfoo.c')
+libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo)
+test_depends = [libfoo, libbar]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true)
+test_native_depends = [libfoo_native, libbar_native]
diff --git a/lib/mlibc/tests/rtdl/dl_iterate_phdr/test.c b/lib/mlibc/tests/rtdl/dl_iterate_phdr/test.c
new file mode 100644
index 0000000..5d48a41
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/dl_iterate_phdr/test.c
@@ -0,0 +1,91 @@
+#include <assert.h>
+#include <link.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <stdio.h>
+
+#ifdef USE_HOST_LIBC
+#define LDSO_PATTERN "ld-linux-"
+#define LIBFOO "libnative-foo.so"
+#define LIBBAR "libnative-bar.so"
+#else
+#define LDSO_PATTERN "ld.so"
+#define LIBFOO "libfoo.so"
+#define LIBBAR "libbar.so"
+#endif
+
+struct result {
+ int found_ldso;
+ int found_self;
+ int found_foo;
+ int found_bar;
+};
+
+static int ends_with(const char *suffix, const char *s) {
+ size_t suffix_len = strlen(suffix);
+ size_t s_len = strlen(s);
+ if (s_len < suffix_len)
+ return 0;
+ else {
+ return !strcmp(suffix, s + s_len - suffix_len);
+ }
+}
+
+static int contains(const char *pattern, const char *s) {
+ return !!strstr(s, pattern);
+}
+
+static int callback(struct dl_phdr_info *info, size_t size, void *data) {
+ assert(size == sizeof(struct dl_phdr_info));
+ struct result *found = (struct result *) data;
+
+ printf("%s\n", info->dlpi_name);
+ fflush(stdout);
+
+ if (ends_with("foo.so", info->dlpi_name))
+ found->found_foo++;
+ if (ends_with("bar.so", info->dlpi_name))
+ found->found_bar++;
+ if (contains(LDSO_PATTERN, info->dlpi_name))
+ found->found_ldso++;
+
+ if (!strcmp("", info->dlpi_name))
+ found->found_self++;
+
+ assert(info->dlpi_phdr);
+ return 0;
+}
+
+int main() {
+ struct result found = { 0 };
+ assert(!dl_iterate_phdr(callback, &found));
+ assert(found.found_ldso == 1);
+ assert(found.found_self == 1);
+ assert(found.found_foo == 0);
+ assert(found.found_bar == 0);
+ printf("---\n");
+
+ memset(&found, 0, sizeof(found));
+ void *bar = dlopen(LIBBAR, RTLD_LOCAL | RTLD_NOW);
+ assert(bar);
+ assert(!dl_iterate_phdr(callback, &found));
+ assert(found.found_ldso == 1);
+ assert(found.found_self == 1);
+ assert(found.found_bar == 1);
+ assert(found.found_foo == 1); // Since bar depends on foo.
+ printf("---\n");
+
+ memset(&found, 0, sizeof(found));
+ void *foo = dlopen(LIBFOO, RTLD_GLOBAL | RTLD_NOW);
+ assert(foo);
+ assert(!dl_iterate_phdr(callback, &found));
+ assert(found.found_ldso == 1);
+ assert(found.found_self == 1);
+ assert(found.found_foo == 1);
+ assert(found.found_bar == 1);
+ printf("---\n");
+
+ dlclose(bar);
+ dlclose(foo);
+ return 0;
+}
diff --git a/lib/mlibc/tests/rtdl/dladdr_local/libfoo.c b/lib/mlibc/tests/rtdl/dladdr_local/libfoo.c
new file mode 100644
index 0000000..2903a3d
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/dladdr_local/libfoo.c
@@ -0,0 +1 @@
+char foo_global[] = "";
diff --git a/lib/mlibc/tests/rtdl/dladdr_local/meson.build b/lib/mlibc/tests/rtdl/dladdr_local/meson.build
new file mode 100644
index 0000000..4ae6bb3
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/dladdr_local/meson.build
@@ -0,0 +1,5 @@
+libfoo = shared_library('foo', 'libfoo.c')
+test_depends = [libfoo]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+test_native_depends = [libfoo_native]
diff --git a/lib/mlibc/tests/rtdl/dladdr_local/test.c b/lib/mlibc/tests/rtdl/dladdr_local/test.c
new file mode 100644
index 0000000..c64d259
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/dladdr_local/test.c
@@ -0,0 +1,21 @@
+#include <dlfcn.h>
+#include <assert.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBFOO "libnative-foo.so"
+#else
+#define LIBFOO "libfoo.so"
+#endif
+
+int main() {
+ void *foo_handle = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
+ assert(foo_handle);
+
+ char *foo_global = (char *)dlsym(foo_handle, "foo_global");
+ assert(foo_global);
+
+ Dl_info info;
+ assert(dladdr((const void *)foo_global, &info) != 0);
+
+ assert(dlclose(foo_handle) == 0);
+}
diff --git a/lib/mlibc/tests/rtdl/ld_library_path/libfoo.c b/lib/mlibc/tests/rtdl/ld_library_path/libfoo.c
new file mode 100644
index 0000000..85e6cd8
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/ld_library_path/libfoo.c
@@ -0,0 +1 @@
+void foo() {}
diff --git a/lib/mlibc/tests/rtdl/ld_library_path/meson.build b/lib/mlibc/tests/rtdl/ld_library_path/meson.build
new file mode 100644
index 0000000..95629be
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/ld_library_path/meson.build
@@ -0,0 +1,19 @@
+# Remove the RPATH and set the LD_LIBRARY_PATH environment variable
+# instead when running tests to make sure the library can be found
+# in a directory specified by the user at runtime
+
+test_rpath = '$ORIGIN/'
+
+test_ld_path = meson.build_root() / 'tests' / 'rtdl' / test_name
+test_env += ['LD_LIBRARY_PATH=' + test_ld_path]
+test_native_env += ['LD_LIBRARY_PATH=' + test_ld_path]
+
+libfoo = shared_library('foo', 'libfoo.c',
+ dependencies: libc_dep,
+ link_args: test_additional_link_args,
+)
+
+libfoo_native = shared_library('native-foo', 'libfoo.c',
+ link_args: ['-ldl'] + test_additional_link_args,
+ native: true
+)
diff --git a/lib/mlibc/tests/rtdl/ld_library_path/test.c b/lib/mlibc/tests/rtdl/ld_library_path/test.c
new file mode 100644
index 0000000..15ca200
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/ld_library_path/test.c
@@ -0,0 +1,16 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBFOO "libnative-foo.so"
+#else
+#define LIBFOO "libfoo.so"
+#endif
+
+int main() {
+ void *foo = dlopen(LIBFOO, RTLD_NOW);
+ assert(foo);
+ dlclose(foo);
+}
diff --git a/lib/mlibc/tests/rtdl/meson.build b/lib/mlibc/tests/rtdl/meson.build
new file mode 100644
index 0000000..1331899
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/meson.build
@@ -0,0 +1,56 @@
+rtdl_test_cases = [
+ 'dl_iterate_phdr',
+ 'dladdr_local',
+ 'ld_library_path',
+ 'noload-promote',
+ 'rtld_next',
+ 'soname',
+ 'preinit',
+ 'scope1',
+ 'scope2',
+ 'scope3',
+ 'scope4',
+ 'scope5',
+ 'tls_align',
+]
+
+foreach test_name : rtdl_test_cases
+ test_rpath = meson.build_root() / 'tests' / 'rtdl' / test_name / ''
+ test_rpath += ':$ORIGIN/' # Workaround old and buggy qemu-user on CI
+
+ test_env = []
+ test_link_with = []
+ test_depends = []
+ test_native_env = []
+ test_native_link_with = []
+ test_native_depends = []
+ test_additional_link_args = []
+
+ # Build the needed DSOs for the test. This sets the variables above.
+ subdir(test_name)
+
+ exec = executable('rtdl-' + test_name, [test_name / 'test.c', test_sources],
+ link_with: test_link_with,
+ dependencies: libc_dep,
+ build_rpath: test_rpath,
+ override_options: test_override_options,
+ c_args: test_c_args,
+ link_args: test_link_args + test_additional_link_args,
+ )
+ test(test_name, exec, env: test_env, suite: 'rtdl', depends: test_depends)
+
+ if build_tests_host_libc and not host_libc_excluded_test_cases.contains(test_name)
+ exec = executable('host-libc-' + test_name, test_name / 'test.c',
+ link_with: test_native_link_with,
+ dependencies: rtlib_deps,
+ build_rpath: test_rpath,
+ # Don't use ASan here, due to a bug that breaks dlopen() + DT_RUNPATH:
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1449604
+ override_options: 'b_sanitize=undefined',
+ c_args: ['-D_GNU_SOURCE', '-DUSE_HOST_LIBC'],
+ link_args: ['-ldl'] + test_additional_link_args,
+ native: true,
+ )
+ test(test_name, exec, env: test_native_env, suite: ['host-libc', 'rtdl'], depends: test_native_depends)
+ endif
+endforeach
diff --git a/lib/mlibc/tests/rtdl/noload-promote/libfoo.c b/lib/mlibc/tests/rtdl/noload-promote/libfoo.c
new file mode 100644
index 0000000..85e6cd8
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/noload-promote/libfoo.c
@@ -0,0 +1 @@
+void foo() {}
diff --git a/lib/mlibc/tests/rtdl/noload-promote/meson.build b/lib/mlibc/tests/rtdl/noload-promote/meson.build
new file mode 100644
index 0000000..4ae6bb3
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/noload-promote/meson.build
@@ -0,0 +1,5 @@
+libfoo = shared_library('foo', 'libfoo.c')
+test_depends = [libfoo]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+test_native_depends = [libfoo_native]
diff --git a/lib/mlibc/tests/rtdl/noload-promote/test.c b/lib/mlibc/tests/rtdl/noload-promote/test.c
new file mode 100644
index 0000000..0a6c55c
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/noload-promote/test.c
@@ -0,0 +1,22 @@
+#include <dlfcn.h>
+#include <assert.h>
+#include <stddef.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBFOO "libnative-foo.so"
+#else
+#define LIBFOO "libfoo.so"
+#endif
+
+int main() {
+ void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
+ assert(foo);
+
+ assert(dlsym(RTLD_DEFAULT, "foo") == NULL);
+
+ // Opening a library with RTLD_NOLOAD | RTLD_GLOBAL should promote it to the global scope.
+ assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW) == foo);
+ assert(dlsym(RTLD_DEFAULT, "foo") != NULL);
+
+ assert(dlopen("does-not-exist.so.1337", RTLD_NOLOAD | RTLD_GLOBAL | RTLD_NOW) == NULL);
+}
diff --git a/lib/mlibc/tests/rtdl/preinit/libfoo.c b/lib/mlibc/tests/rtdl/preinit/libfoo.c
new file mode 100644
index 0000000..9c834ea
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/preinit/libfoo.c
@@ -0,0 +1,18 @@
+#include <stdio.h>
+#include <assert.h>
+
+int fooDone = 0;
+
+// DSOs do not support pre-initialization functions.
+
+__attribute__((constructor))
+void fooInit() {
+ dprintf(1, "initialization function called in foo\n");
+
+ assert(fooDone == 0);
+ fooDone++;
+}
+
+int isFooDone() {
+ return fooDone;
+}
diff --git a/lib/mlibc/tests/rtdl/preinit/meson.build b/lib/mlibc/tests/rtdl/preinit/meson.build
new file mode 100644
index 0000000..1a7f398
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/preinit/meson.build
@@ -0,0 +1,18 @@
+if host_machine.cpu_family() == 'riscv64'
+ # gp isn't initialized until after crt1.o runs, so to access
+ # globals in our pre-initializers we must disable it.
+ test_additional_link_args = ['-Wl,--no-relax']
+endif
+
+libfoo = shared_library('foo', 'libfoo.c',
+ dependencies: libc_dep,
+ override_options: 'b_sanitize=none',
+ link_args: test_additional_link_args,
+)
+test_link_with = [libfoo]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c',
+ link_args: ['-ldl'] + test_additional_link_args,
+ native: true
+)
+test_native_link_with = [libfoo_native]
diff --git a/lib/mlibc/tests/rtdl/preinit/test.c b/lib/mlibc/tests/rtdl/preinit/test.c
new file mode 100644
index 0000000..5b5d5e8
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/preinit/test.c
@@ -0,0 +1,44 @@
+#include <stdio.h>
+#include <assert.h>
+
+int mainDone = 0;
+
+int isFooDone();
+
+void preInit1() {
+ // Use dprintf because stdout might not be initialized yet.
+ dprintf(1, "pre-initialization function 1 called in main executable\n");
+
+ assert(isFooDone() == 0);
+ assert(mainDone == 0);
+ mainDone++;
+}
+
+void preInit2() {
+ dprintf(1, "pre-initialization function 2 called in main executable\n");
+
+ assert(isFooDone() == 0);
+ assert(mainDone == 1);
+ mainDone++;
+}
+
+__attribute__((constructor))
+void mainInit() {
+ dprintf(1, "initialization function called in main executable\n");
+
+ assert(isFooDone() == 1);
+ assert(mainDone == 2);
+ mainDone++;
+}
+
+// Manually register the pre-initialization functions.
+__attribute__((used, section(".preinit_array")))
+static void (*preinitFunctions[])(void) = {
+ &preInit1,
+ &preInit2,
+};
+
+int main() {
+ assert(isFooDone() == 1);
+ assert(mainDone == 3);
+}
diff --git a/lib/mlibc/tests/rtdl/rtld_next/libbar.c b/lib/mlibc/tests/rtdl/rtld_next/libbar.c
new file mode 100644
index 0000000..c0950c5
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/rtld_next/libbar.c
@@ -0,0 +1,16 @@
+#include <dlfcn.h>
+
+typedef char *charFn(void);
+
+__attribute__((weak))
+char *definedInBoth() {
+ return "bar";
+}
+
+charFn *barGetDefault() {
+ return (charFn *)dlsym(RTLD_DEFAULT, "definedInBoth");
+}
+
+charFn *barGetNext() {
+ return (charFn *)dlsym(RTLD_NEXT, "definedInBoth");
+}
diff --git a/lib/mlibc/tests/rtdl/rtld_next/libfoo.c b/lib/mlibc/tests/rtdl/rtld_next/libfoo.c
new file mode 100644
index 0000000..1d46b73
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/rtld_next/libfoo.c
@@ -0,0 +1,17 @@
+#include <dlfcn.h>
+
+typedef char *charFn(void);
+
+__attribute__((weak))
+char *definedInBoth() {
+ return "foo";
+}
+
+charFn *fooGetDefault() {
+ return (charFn *)dlsym(RTLD_DEFAULT, "definedInBoth");
+}
+
+charFn *fooGetNext() {
+ return (charFn *)dlsym(RTLD_NEXT, "definedInBoth");
+}
+
diff --git a/lib/mlibc/tests/rtdl/rtld_next/meson.build b/lib/mlibc/tests/rtdl/rtld_next/meson.build
new file mode 100644
index 0000000..d156c24
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/rtld_next/meson.build
@@ -0,0 +1,25 @@
+# Prevent tail calls, as it breaks libc's 'calling object' detection.
+no_tail_calls = '-fno-optimize-sibling-calls'
+
+libfoo = shared_library('foo', 'libfoo.c',
+ dependencies: libc_dep,
+ c_args: no_tail_calls,
+)
+libbar = shared_library('bar', 'libbar.c',
+ dependencies: libc_dep,
+ c_args: no_tail_calls,
+)
+test_link_with = [libfoo, libbar] # foo is linked before bar
+
+libfoo_native = shared_library('native-foo', 'libfoo.c',
+ c_args: [no_tail_calls, '-D_GNU_SOURCE'],
+ link_args: '-ldl',
+ native: true
+)
+libbar_native = shared_library('native-bar', 'libbar.c',
+ c_args: [no_tail_calls, '-D_GNU_SOURCE'],
+ link_args: '-ldl',
+ native: true
+)
+test_native_link_with = [libfoo_native, libbar_native]
+
diff --git a/lib/mlibc/tests/rtdl/rtld_next/test.c b/lib/mlibc/tests/rtdl/rtld_next/test.c
new file mode 100644
index 0000000..3e90a63
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/rtld_next/test.c
@@ -0,0 +1,26 @@
+#include <string.h>
+#include <assert.h>
+
+typedef char *charFn(void);
+charFn *fooGetDefault(void);
+charFn *fooGetNext(void);
+charFn *barGetDefault(void);
+charFn *barGetNext(void);
+
+int main() {
+ charFn *ret;
+
+ ret = fooGetDefault();
+ assert(ret != NULL);
+ assert(!strcmp(ret(), "foo"));
+
+ ret = fooGetNext();
+ assert(ret != NULL);
+ assert(!strcmp(ret(), "bar"));
+
+ ret = barGetDefault();
+ assert(ret != NULL);
+ assert(!strcmp(ret(), "foo"));
+
+ assert(barGetNext() == NULL);
+}
diff --git a/lib/mlibc/tests/rtdl/scope1/libbar.c b/lib/mlibc/tests/rtdl/scope1/libbar.c
new file mode 100644
index 0000000..ecf043e
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope1/libbar.c
@@ -0,0 +1,14 @@
+char *foo(void);
+char *foo_global(void);
+
+char *bar() {
+ return "bar";
+}
+
+char *bar_calls_foo() {
+ return foo();
+}
+
+char *bar_calls_foo_global() {
+ return foo_global();
+}
diff --git a/lib/mlibc/tests/rtdl/scope1/libfoo.c b/lib/mlibc/tests/rtdl/scope1/libfoo.c
new file mode 100644
index 0000000..b4e1b8c
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope1/libfoo.c
@@ -0,0 +1,9 @@
+char *foo() {
+ return "foo";
+}
+
+char global[] = "foo global";
+
+char *foo_global() {
+ return global;
+}
diff --git a/lib/mlibc/tests/rtdl/scope1/meson.build b/lib/mlibc/tests/rtdl/scope1/meson.build
new file mode 100644
index 0000000..acb679e
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope1/meson.build
@@ -0,0 +1,7 @@
+libfoo = shared_library('foo', 'libfoo.c')
+libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo)
+test_depends = [libfoo, libbar]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true)
+test_native_depends = [libfoo_native, libbar_native]
diff --git a/lib/mlibc/tests/rtdl/scope1/test.c b/lib/mlibc/tests/rtdl/scope1/test.c
new file mode 100644
index 0000000..c19915d
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope1/test.c
@@ -0,0 +1,90 @@
+#include <stddef.h>
+#include <dlfcn.h>
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBFOO "libnative-foo.so"
+#define LIBBAR "libnative-bar.so"
+#else
+#define LIBFOO "libfoo.so"
+#define LIBBAR "libbar.so"
+#endif
+
+typedef char *strfn(void);
+
+int main() {
+ // We haven't dlopen'd these libs yet, so symbol resolution should fail.
+ assert(dlsym(RTLD_DEFAULT, "foo") == NULL);
+ assert(dlsym(RTLD_DEFAULT, "bar") == NULL);
+
+ assert(!dlopen(LIBFOO, RTLD_NOLOAD));
+ assert(!dlopen(LIBBAR, RTLD_NOLOAD));
+
+ void *foo_handle = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
+ assert(foo_handle);
+ assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_NOW));
+
+ strfn *foo_sym = dlsym(foo_handle, "foo");
+ assert(foo_sym);
+ assert(foo_sym());
+ assert(!strcmp(foo_sym(), "foo"));
+
+ strfn *foo_global_sym = dlsym(foo_handle, "foo_global");
+ assert(foo_global_sym);
+ assert(foo_global_sym());
+ assert(!strcmp(foo_global_sym(), "foo global"));
+
+ assert(dlsym(foo_handle, "doesnotexist") == NULL);
+
+ // Nested opening should work
+ assert(dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW) == foo_handle);
+ assert(dlopen(LIBFOO, RTLD_NOLOAD | RTLD_NOW));
+
+ // Since we've loaded the same library twice, the addresses should be the same
+ assert(dlsym(foo_handle, "foo") == foo_sym);
+ assert(dlsym(foo_handle, "foo_global") == foo_global_sym);
+
+ // libfoo was opened with RTLD_LOCAL, so we shouldn't be able to lookup
+ // its symbols in the global namespace.
+ assert(dlsym(RTLD_DEFAULT, "foo") == NULL);
+
+ {
+ void *bar_handle = dlopen(LIBBAR, RTLD_GLOBAL | RTLD_NOW);
+ assert(bar_handle);
+ assert(dlopen(LIBBAR, RTLD_NOLOAD | RTLD_NOW));
+
+ strfn *bar_sym = dlsym(bar_handle, "bar");
+ assert(bar_sym);
+ assert(bar_sym());
+ assert(!strcmp(bar_sym(), "bar"));
+
+ strfn *bar_calls_foo_sym = dlsym(bar_handle, "bar_calls_foo");
+ assert(bar_calls_foo_sym);
+ assert(bar_calls_foo_sym());
+ assert(!strcmp(bar_calls_foo_sym(), "foo"));
+
+ strfn *bar_calls_foo_global_sym = dlsym(bar_handle, "bar_calls_foo_global");
+ assert(bar_calls_foo_global_sym);
+ assert(bar_calls_foo_global_sym());
+ assert(!strcmp(bar_calls_foo_global_sym(), "foo global"));
+
+ // libbar was opened with RTLD_GLOBAL, so we can find symbols by
+ // searching in the global scope.
+ strfn *new_bar_sym = dlsym(RTLD_DEFAULT, "bar");
+ assert(new_bar_sym);
+ assert(new_bar_sym == bar_sym);
+
+ // Note that we loaded libbar with RTLD_GLOBAL, which should pull
+ // in libfoo's symbols globally too.
+ strfn *new_foo_sym = dlsym(RTLD_DEFAULT, "foo");
+ assert(new_foo_sym);
+ assert(new_foo_sym == foo_sym);
+
+ assert(dlclose(bar_handle) == 0);
+ }
+
+ assert(dlclose(foo_handle) == 0);
+ assert(dlclose(foo_handle) == 0);
+}
diff --git a/lib/mlibc/tests/rtdl/scope2/libbar.c b/lib/mlibc/tests/rtdl/scope2/libbar.c
new file mode 100644
index 0000000..4783c58
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope2/libbar.c
@@ -0,0 +1,5 @@
+char *foo_baz_conflict(void);
+
+char *bar_calls_foo_baz_conflict() {
+ return foo_baz_conflict();
+}
diff --git a/lib/mlibc/tests/rtdl/scope2/libbaz.c b/lib/mlibc/tests/rtdl/scope2/libbaz.c
new file mode 100644
index 0000000..fc73adc
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope2/libbaz.c
@@ -0,0 +1,3 @@
+char *foo_baz_conflict() {
+ return "resolved to baz";
+}
diff --git a/lib/mlibc/tests/rtdl/scope2/libfoo.c b/lib/mlibc/tests/rtdl/scope2/libfoo.c
new file mode 100644
index 0000000..9f7b881
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope2/libfoo.c
@@ -0,0 +1,3 @@
+char *foo_baz_conflict() {
+ return "resolved to foo";
+}
diff --git a/lib/mlibc/tests/rtdl/scope2/meson.build b/lib/mlibc/tests/rtdl/scope2/meson.build
new file mode 100644
index 0000000..938272c
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope2/meson.build
@@ -0,0 +1,9 @@
+libfoo = shared_library('foo', 'libfoo.c')
+libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo)
+libbaz = shared_library('baz', 'libbaz.c')
+test_depends = [libfoo, libbar, libbaz]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true)
+libbaz_native = shared_library('native-baz', 'libbaz.c', native: true)
+test_native_depends = [libfoo_native, libbar_native, libbaz_native]
diff --git a/lib/mlibc/tests/rtdl/scope2/test.c b/lib/mlibc/tests/rtdl/scope2/test.c
new file mode 100644
index 0000000..f4f42dc
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope2/test.c
@@ -0,0 +1,36 @@
+#include <stddef.h>
+#include <dlfcn.h>
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBBAR "libnative-bar.so"
+#define LIBBAZ "libnative-baz.so"
+#else
+#define LIBBAR "libbar.so"
+#define LIBBAZ "libbaz.so"
+#endif
+
+typedef char *strfn(void);
+
+int main() {
+ void *baz = dlopen(LIBBAZ, RTLD_LAZY | RTLD_GLOBAL);
+ assert(baz);
+
+ // At this point, baz is loaded in the global scope. When we load bar locally,
+ // there is a relocation to `foo_baz_conflict` which is defined in both
+ // foo (which is a dependency of bar), and baz. In this case baz should win
+ // since we search the global scope first.
+
+ void *bar = dlopen(LIBBAR, RTLD_LAZY | RTLD_LOCAL);
+ assert(bar);
+
+ strfn *bfn = dlsym(bar, "bar_calls_foo_baz_conflict");
+ assert(!strcmp(bfn(), "resolved to baz"));
+
+ // TODO: Test RTLD_DEEPBIND and DT_SYMBOLIC once we implement it.
+
+ dlclose(bar);
+ dlclose(baz);
+}
diff --git a/lib/mlibc/tests/rtdl/scope3/libbar.c b/lib/mlibc/tests/rtdl/scope3/libbar.c
new file mode 100644
index 0000000..dc377b6
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope3/libbar.c
@@ -0,0 +1,8 @@
+int g = 1;
+
+int call_foo();
+
+int call_bar() {
+ return call_foo();
+}
+
diff --git a/lib/mlibc/tests/rtdl/scope3/libbaz.c b/lib/mlibc/tests/rtdl/scope3/libbaz.c
new file mode 100644
index 0000000..32524cc
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope3/libbaz.c
@@ -0,0 +1,7 @@
+int g = 2;
+
+int call_foo();
+
+int call_baz() {
+ return call_foo();
+}
diff --git a/lib/mlibc/tests/rtdl/scope3/libfoo.c b/lib/mlibc/tests/rtdl/scope3/libfoo.c
new file mode 100644
index 0000000..bc86319
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope3/libfoo.c
@@ -0,0 +1,5 @@
+int g = 0;
+
+int call_foo() {
+ return g;
+}
diff --git a/lib/mlibc/tests/rtdl/scope3/meson.build b/lib/mlibc/tests/rtdl/scope3/meson.build
new file mode 100644
index 0000000..0c98583
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope3/meson.build
@@ -0,0 +1,9 @@
+libfoo = shared_library('foo', 'libfoo.c')
+libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo)
+libbaz = shared_library('baz', 'libbaz.c', build_rpath: test_rpath, link_with: libfoo)
+test_depends = [libfoo, libbar, libbaz]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true)
+libbaz_native = shared_library('native-baz', 'libbaz.c', build_rpath: test_rpath, link_with: libfoo_native, native: true)
+test_native_depends = [libfoo_native, libbar_native, libbaz_native]
diff --git a/lib/mlibc/tests/rtdl/scope3/test.c b/lib/mlibc/tests/rtdl/scope3/test.c
new file mode 100644
index 0000000..30fc662
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope3/test.c
@@ -0,0 +1,39 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBBAR "libnative-bar.so"
+#define LIBBAZ "libnative-baz.so"
+#else
+#define LIBBAR "libbar.so"
+#define LIBBAZ "libbaz.so"
+#endif
+
+int main() {
+ // In this test, we have bar -> foo and baz -> foo (where -> means 'depends on').
+ // All three objects contain a definition of a symbol g. Bar calls into foo to retrieve
+ // what foo thinks g is, but since bar appears earlier in the scope than foo, bar's copy
+ // of g wins.
+ //
+ // Next, we load baz, which is identical to bar. When baz calls into foo to retrieve g,
+ // foo still sees bar's definition of g, so bar's copy of g wins.
+ //
+ // Swapping the load order of bar and baz should therefore change the value of g which
+ // foo sees. This behaviour is why dlmopen exists. If we ever implement that, we should
+ // write a similar test and assert that the calls return different results.
+
+ void *libbar = dlopen(LIBBAR, RTLD_LAZY | RTLD_LOCAL);
+ int (*call_bar)(void) = dlsym(libbar, "call_bar");
+ printf("call_bar: %d\n", call_bar());
+ assert(call_bar() == 1);
+
+ void *libbaz = dlopen(LIBBAZ, RTLD_LAZY | RTLD_LOCAL);
+ int (*call_baz)(void) = dlsym(libbaz, "call_baz");
+ printf("call_baz: %d\n", call_baz());
+ assert(call_baz() == 1);
+
+
+ dlclose(libbar);
+ dlclose(libbaz);
+}
diff --git a/lib/mlibc/tests/rtdl/scope4/libbar.c b/lib/mlibc/tests/rtdl/scope4/libbar.c
new file mode 100644
index 0000000..514e456
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope4/libbar.c
@@ -0,0 +1,3 @@
+// Bar needs to have a relocation against foo in order to set DT_NEEDED.
+void foo(void);
+void bar() { foo(); }
diff --git a/lib/mlibc/tests/rtdl/scope4/libbaz.c b/lib/mlibc/tests/rtdl/scope4/libbaz.c
new file mode 100644
index 0000000..256a0e3
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope4/libbaz.c
@@ -0,0 +1 @@
+void baz() {}
diff --git a/lib/mlibc/tests/rtdl/scope4/libfoo.c b/lib/mlibc/tests/rtdl/scope4/libfoo.c
new file mode 100644
index 0000000..6710db7
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope4/libfoo.c
@@ -0,0 +1,3 @@
+// Foo needs to have a relocation against baz in order to set DT_NEEDED.
+void baz(void);
+void foo() { baz(); }
diff --git a/lib/mlibc/tests/rtdl/scope4/meson.build b/lib/mlibc/tests/rtdl/scope4/meson.build
new file mode 100644
index 0000000..804a40c
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope4/meson.build
@@ -0,0 +1,9 @@
+libbaz = shared_library('baz', 'libbaz.c')
+libfoo = shared_library('foo', 'libfoo.c', build_rpath: test_rpath, link_with: libbaz)
+libbar = shared_library('bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo)
+test_depends = [libfoo, libbar, libbaz]
+
+libbaz_native = shared_library('native-baz', 'libbaz.c', native: true)
+libfoo_native = shared_library('native-foo', 'libfoo.c', build_rpath: test_rpath, link_with: libbaz_native, native: true)
+libbar_native = shared_library('native-bar', 'libbar.c', build_rpath: test_rpath, link_with: libfoo_native, native: true)
+test_native_depends = [libfoo_native, libbar_native, libbaz_native]
diff --git a/lib/mlibc/tests/rtdl/scope4/test.c b/lib/mlibc/tests/rtdl/scope4/test.c
new file mode 100644
index 0000000..2365e26
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope4/test.c
@@ -0,0 +1,36 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBFOO "libnative-foo.so"
+#define LIBBAR "libnative-bar.so"
+#define LIBBAZ "libnative-baz.so"
+#else
+#define LIBFOO "libfoo.so"
+#define LIBBAR "libbar.so"
+#define LIBBAZ "libbaz.so"
+#endif
+
+int main() {
+ // In this test, we have foo -> baz, bar -> foo (where '->' means 'depends on').
+ // We first load foo with RTLD_LOCAL, and then load bar with RTLD_GLOBAL.
+ // This should bring foo and bar into the global scope.
+
+ void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
+ assert(foo);
+ assert(dlsym(foo, "foo"));
+ assert(dlsym(foo, "baz"));
+ assert(!dlsym(RTLD_DEFAULT, "foo"));
+ assert(!dlsym(RTLD_DEFAULT, "baz"));
+
+ void *bar = dlopen(LIBBAR, RTLD_GLOBAL | RTLD_NOW);
+ assert(bar);
+ assert(dlsym(bar, "bar"));
+ assert(dlsym(RTLD_DEFAULT, "bar"));
+ assert(dlsym(RTLD_DEFAULT, "foo"));
+ assert(dlsym(RTLD_DEFAULT, "baz"));
+
+ dlclose(foo);
+ dlclose(bar);
+}
diff --git a/lib/mlibc/tests/rtdl/scope5/libfoo.c b/lib/mlibc/tests/rtdl/scope5/libfoo.c
new file mode 100644
index 0000000..85e6cd8
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope5/libfoo.c
@@ -0,0 +1 @@
+void foo() {}
diff --git a/lib/mlibc/tests/rtdl/scope5/meson.build b/lib/mlibc/tests/rtdl/scope5/meson.build
new file mode 100644
index 0000000..22ade5b
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope5/meson.build
@@ -0,0 +1,5 @@
+libfoo = shared_library('foo', 'libfoo.c')
+test_link_with = [libfoo]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+test_native_link_with = [libfoo_native]
diff --git a/lib/mlibc/tests/rtdl/scope5/test.c b/lib/mlibc/tests/rtdl/scope5/test.c
new file mode 100644
index 0000000..2c11c2e
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/scope5/test.c
@@ -0,0 +1,27 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBFOO "libnative-foo.so"
+#else
+#define LIBFOO "libfoo.so"
+#endif
+
+// We need to have a relocation against foo for DT_NEEDED.
+void foo();
+void bar() { foo(); }
+
+int main() {
+ // In this test, we have exec -> foo (where '->' means 'depends on').
+ // This means that foo is in the global scope due to DT_NEEDED.
+ // We then dlopen it again with RTLD_LOCAL, which should just return
+ // the already-loaded object, but used to crash in the mlibc linker instead.
+
+ void *foo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW);
+ assert(foo);
+ assert(dlsym(foo, "foo"));
+ assert(dlsym(RTLD_DEFAULT, "foo"));
+
+ dlclose(foo);
+}
diff --git a/lib/mlibc/tests/rtdl/soname/libbar.c b/lib/mlibc/tests/rtdl/soname/libbar.c
new file mode 100644
index 0000000..7f9f475
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/soname/libbar.c
@@ -0,0 +1 @@
+char *name() { return "bar"; }
diff --git a/lib/mlibc/tests/rtdl/soname/libfoo.c b/lib/mlibc/tests/rtdl/soname/libfoo.c
new file mode 100644
index 0000000..abe0999
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/soname/libfoo.c
@@ -0,0 +1 @@
+char *name() { return "foo"; }
diff --git a/lib/mlibc/tests/rtdl/soname/meson.build b/lib/mlibc/tests/rtdl/soname/meson.build
new file mode 100644
index 0000000..1eb97a8
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/soname/meson.build
@@ -0,0 +1,17 @@
+# Create two libraries with the same SONAME.
+bar_soname = '-Wl,-soname=libbar.so'
+
+libfoo = shared_library('foo', 'libfoo.c', link_args: '-Wl,-soname=libbar.so')
+libbar = shared_library('bar', 'libbar.c', link_args: '-Wl,-soname=libbar.so')
+test_depends = [libfoo, libbar]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c',
+ link_args: ['-ldl', '-Wl,-soname=libnative-bar.so'],
+ native: true
+)
+libbar_native = shared_library('native-bar', 'libbar.c',
+ link_args: ['-ldl', '-Wl,-soname=libnative-bar.so'],
+ native: true
+)
+test_native_depends = [libfoo_native, libbar_native]
+
diff --git a/lib/mlibc/tests/rtdl/soname/test.c b/lib/mlibc/tests/rtdl/soname/test.c
new file mode 100644
index 0000000..23e5b7a
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/soname/test.c
@@ -0,0 +1,33 @@
+#include <dlfcn.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#ifdef USE_HOST_LIBC
+#define LIBFOO "libnative-foo.so"
+#define LIBBAR "libnative-bar.so"
+#else
+#define LIBFOO "libfoo.so"
+#define LIBBAR "libbar.so"
+#endif
+
+int main() {
+ void *foo = dlopen(LIBFOO, RTLD_NOW);
+ void *bar = dlopen(LIBBAR, RTLD_NOW);
+ assert(foo);
+ assert(bar);
+
+ // Since these libraries have the same SONAME, they should return the same thing.
+ assert(foo == bar);
+
+ char *(*fooSym)(void) = dlsym(foo, "name");
+ char *(*barSym)(void) = dlsym(bar, "name");
+ assert(fooSym && barSym);
+ assert(fooSym() && barSym());
+ printf("foo: name() = \"%s\"\n", fooSym());
+ printf("bar: name() = \"%s\"\n", barSym());
+ assert(!strcmp(fooSym(), barSym()));
+
+ dlclose(foo);
+ dlclose(bar);
+}
diff --git a/lib/mlibc/tests/rtdl/tls_align/libbar.c b/lib/mlibc/tests/rtdl/tls_align/libbar.c
new file mode 100644
index 0000000..3f8d6a7
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/tls_align/libbar.c
@@ -0,0 +1 @@
+_Thread_local __attribute__((aligned(8))) char bar_thread_local[8] = "Hello!";
diff --git a/lib/mlibc/tests/rtdl/tls_align/libfoo.c b/lib/mlibc/tests/rtdl/tls_align/libfoo.c
new file mode 100644
index 0000000..8d98177
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/tls_align/libfoo.c
@@ -0,0 +1 @@
+_Thread_local __attribute__((aligned(16))) char foo_thread_local[8] = "Hello!";
diff --git a/lib/mlibc/tests/rtdl/tls_align/meson.build b/lib/mlibc/tests/rtdl/tls_align/meson.build
new file mode 100644
index 0000000..61d0fd9
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/tls_align/meson.build
@@ -0,0 +1,7 @@
+libfoo = shared_library('foo', 'libfoo.c')
+libbar = shared_library('bar', 'libbar.c')
+test_link_with = [libfoo, libbar]
+
+libfoo_native = shared_library('native-foo', 'libfoo.c', native: true)
+libbar_native = shared_library('native-bar', 'libbar.c', native: true)
+test_native_link_with = [libfoo_native, libbar_native]
diff --git a/lib/mlibc/tests/rtdl/tls_align/test.c b/lib/mlibc/tests/rtdl/tls_align/test.c
new file mode 100644
index 0000000..4381831
--- /dev/null
+++ b/lib/mlibc/tests/rtdl/tls_align/test.c
@@ -0,0 +1,10 @@
+#include <assert.h>
+#include <stdint.h>
+
+extern _Thread_local char foo_thread_local[];
+extern _Thread_local char bar_thread_local[];
+
+int main() {
+ assert(!((uintptr_t)foo_thread_local & (16 - 1)));
+ assert(!((uintptr_t)bar_thread_local & (8 - 1)));
+}