aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/csu/h_hello.c48
-rw-r--r--lib/csu/h_preinit_array.c16
-rw-r--r--lib/csu/t_hello.sh150
-rw-r--r--lib/libc/gen/Makefile.inc1
-rw-r--r--lib/libc/gen/h_ctype_abuse.c138
-rw-r--r--lib/libc/gen/h_execsig.c55
-rw-r--r--lib/libc/gen/t_arc4random.c670
-rw-r--r--lib/libc/gen/t_ctype.c1236
-rw-r--r--lib/libc/gen/t_timespec_get.c123
-rw-r--r--lib/libc/locale/t_c16rtomb.c287
-rw-r--r--lib/libc/locale/t_c32rtomb.c60
-rw-r--r--lib/libc/locale/t_c8rtomb.c355
-rw-r--r--lib/libc/locale/t_mbrtoc16.c364
-rw-r--r--lib/libc/locale/t_mbrtoc32.c61
-rw-r--r--lib/libc/locale/t_mbrtoc8.c415
-rw-r--r--lib/libc/locale/t_uchar.c81
-rw-r--r--lib/libc/misc/t_vis.c165
-rw-r--r--lib/libc/regex/t_regex_binary.c80
-rw-r--r--lib/libc/setjmp/t_sigstack.c389
-rw-r--r--lib/libc/ssp/h_getcwd2.c18
-rw-r--r--lib/libc/stdlib/h_sort.c225
-rw-r--r--lib/libc/stdlib/t_sort.sh115
-rw-r--r--lib/libc/sys/t_aio_cancel.c223
-rw-r--r--lib/libc/sys/t_aio_lio.c264
-rw-r--r--lib/libc/sys/t_aio_rw.c167
-rw-r--r--lib/libc/sys/t_aio_suspend.c170
-rw-r--r--lib/libc/sys/t_ptrace_kill.c131
-rw-r--r--lib/libexecinfo/t_backtrace_sandbox.c88
-rw-r--r--lib/libm/t_errhandling.c97
-rw-r--r--lib/libm/t_next.c887
-rw-r--r--lib/libm/t_remquo.c126
-rw-r--r--lib/libpthread/cancelpoint.h133
-rw-r--r--lib/libpthread/t_cancellation.c1543
-rw-r--r--lib/libpthread/t_compat_cancel.c287
-rw-r--r--lib/libpthread/t_stack.c491
-rw-r--r--lib/libpthread/weak/Makefile25
-rw-r--r--lib/libpthread/weak/Makefile.inc1
-rw-r--r--lib/libpthread/weak/lib/Makefile21
-rw-r--r--lib/libpthread/weak/lib/h_pthread_weak.c83
-rw-r--r--lib/libpthread/weak/lib/h_pthread_weak.h36
-rw-r--r--lib/libpthread/weak/t_pthread_weak_nothread.c64
-rw-r--r--lib/libpthread/weak/t_pthread_weak_threaded.c64
-rw-r--r--lib/libstdc++/Makefile12
-rw-r--r--lib/libstdc++/h_cin_nosync.cc40
-rw-r--r--lib/libstdc++/t_sync_with_stdio.sh41
-rw-r--r--lib/libutil/t_strpct.c202
46 files changed, 10248 insertions, 0 deletions
diff --git a/lib/csu/h_hello.c b/lib/csu/h_hello.c
new file mode 100644
index 000000000000..e39284c2f5c9
--- /dev/null
+++ b/lib/csu/h_hello.c
@@ -0,0 +1,48 @@
+/* $NetBSD: h_hello.c,v 1.1 2025/04/27 04:09:35 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: h_hello.c,v 1.1 2025/04/27 04:09:35 riastradh Exp $");
+
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+ * Force some R_*_RELATIVE-type relocations.
+ */
+static int foo = 42;
+static int *volatile foop = &foo;
+
+int
+main(void)
+{
+
+ printf("%s: Hello, world! %d\n", getprogname(), *foop);
+ fflush(stdout);
+ return ferror(stdout);
+}
diff --git a/lib/csu/h_preinit_array.c b/lib/csu/h_preinit_array.c
new file mode 100644
index 000000000000..36fa37ff340b
--- /dev/null
+++ b/lib/csu/h_preinit_array.c
@@ -0,0 +1,16 @@
+static int x = 1;
+
+static void
+foo(void)
+{
+ x--;
+}
+
+static void (*fp) (void) __attribute__((__section__(".preinit_array"), __used__)) =
+ foo;
+
+int
+main(void)
+{
+ return x;
+}
diff --git a/lib/csu/t_hello.sh b/lib/csu/t_hello.sh
new file mode 100644
index 000000000000..b72f62885476
--- /dev/null
+++ b/lib/csu/t_hello.sh
@@ -0,0 +1,150 @@
+# $NetBSD: t_hello.sh,v 1.3 2025/05/02 23:04:06 riastradh Exp $
+#
+# Copyright (c) 2025 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+checksupport()
+{
+ local prog
+
+ prog=$1
+ test -f "$(atf_get_srcdir)/${prog}" || atf_skip "not supported"
+}
+
+checkrun()
+{
+ local prog
+
+ prog=$1
+ atf_check -o inline:"${prog}: Hello, world! 42\n" \
+ "$(atf_get_srcdir)/${prog}"
+}
+
+cleanup()
+{
+ local prog
+
+ prog=$1
+ test -f "${prog}.core" || return 0
+ readelf -rs "$(atf_get_srcdir)/${prog}"
+ gdb -batch -ex bt -ex 'info registers' -ex disas \
+ "$(atf_get_srcdir)/${prog}" "${prog}.core"
+}
+
+atf_test_case dynamic cleanup
+dynamic_head()
+{
+ atf_set "descr" "Test a dynamic executable"
+}
+dynamic_body()
+{
+ checksupport h_hello_dyn
+ checkrun h_hello_dyn
+}
+dynamic_cleanup()
+{
+ cleanup h_hello_dyn
+}
+
+atf_test_case dynamicpie cleanup
+dynamicpie_head()
+{
+ atf_set "descr" "Test a dynamic position-independent executable"
+}
+dynamicpie_body()
+{
+ checksupport h_hello_dynpie
+ checkrun h_hello_dynpie
+}
+dynamicpie_cleanup()
+{
+ cleanup h_hello_dynpie
+}
+
+atf_test_case relr cleanup
+relr_head()
+{
+ atf_set "descr" "Test a static PIE executable with RELR relocations"
+}
+relr_body()
+{
+ checksupport h_hello_relr
+ case `uname -p` in
+ i386|x86_64)
+ ;;
+ *) atf_expect_fail "PR lib/59359: static pies are broken"
+ ;;
+ esac
+ checkrun h_hello_relr
+}
+relr_cleanup()
+{
+ cleanup h_hello_relr
+}
+
+atf_test_case static cleanup
+static_head()
+{
+ atf_set "descr" "Test a static executable"
+}
+static_body()
+{
+ checksupport h_hello_sta
+ checkrun h_hello_sta
+}
+static_cleanup()
+{
+ cleanup h_hello_sta
+}
+
+atf_test_case staticpie cleanup
+staticpie_head()
+{
+ atf_set "descr" "Test a static position-independent executable"
+}
+staticpie_body()
+{
+ checksupport h_hello_stapie
+ case `uname -p` in
+ i386|x86_64)
+ ;;
+ *) atf_expect_fail "PR lib/59359: static pies are broken"
+ ;;
+ esac
+ checkrun h_hello_stapie
+}
+staticpie_cleanup()
+{
+ cleanup h_hello_stapie
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case dynamic
+ atf_add_test_case dynamicpie
+ atf_add_test_case relr
+ atf_add_test_case static
+ atf_add_test_case staticpie
+}
diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc
new file mode 100644
index 000000000000..01b5f23410c8
--- /dev/null
+++ b/lib/libc/gen/Makefile.inc
@@ -0,0 +1 @@
+.include "../Makefile.inc"
diff --git a/lib/libc/gen/h_ctype_abuse.c b/lib/libc/gen/h_ctype_abuse.c
new file mode 100644
index 000000000000..fdff8552f8f4
--- /dev/null
+++ b/lib/libc/gen/h_ctype_abuse.c
@@ -0,0 +1,138 @@
+/* $NetBSD: h_ctype_abuse.c,v 1.2 2025/09/15 17:32:01 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Helper program to verify effects of ctype(3) abuse.
+ *
+ * NOTE: This program intentionally triggers undefined behaviour by
+ * passing int values to the ctype(3) functions which are neither EOF
+ * nor representable by unsigned char. The purpose is to verify that
+ * NetBSD's ctype(3) _does not_ trap the undefined behaviour when the
+ * environment variable LIBC_ALLOWCTYPEABUSE. (It does not check
+ * anything about the results, which are perforce nonsense -- just that
+ * it gets a result without crashing.)
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: h_ctype_abuse.c,v 1.2 2025/09/15 17:32:01 riastradh Exp $");
+
+#include <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define FOREACHCTYPE(M) \
+ M(ISALPHA, isalpha) \
+ M(ISUPPER, isupper) \
+ M(ISLOWER, islower) \
+ M(ISDIGIT, isdigit) \
+ M(ISXDIGIT, isxdigit) \
+ M(ISALNUM, isalnum) \
+ M(ISSPACE, isspace) \
+ M(ISPUNCT, ispunct) \
+ M(ISPRINT, isprint) \
+ M(ISGRAPH, isgraph) \
+ M(ISCNTRL, iscntrl) \
+ M(ISBLANK, isblank) \
+ M(TOUPPER, toupper) \
+ M(TOLOWER, tolower)
+
+int
+main(int argc, char **argv)
+{
+ enum {
+#define M(upper, lower) upper,
+ FOREACHCTYPE(M)
+#undef M
+ } fn;
+ enum {
+ MACRO,
+ FUNCTION,
+ } mode;
+ int ch;
+ volatile int result;
+
+ setprogname(argv[0]);
+ if (argc != 3 && argc != 4) {
+ errx(1, "Usage: %s <function> <mode> [<locale>]",
+ getprogname());
+ }
+
+#define M(upper, lower) \
+ else if (strcmp(argv[1], #lower) == 0) \
+ fn = upper;
+
+ if (0)
+ ;
+ FOREACHCTYPE(M)
+ else
+ errx(1, "unknown ctype function");
+#undef M
+
+ if (strcmp(argv[2], "macro") == 0)
+ mode = MACRO;
+ else if (strcmp(argv[2], "function") == 0)
+ mode = FUNCTION;
+ else
+ errx(1, "unknown usage mode");
+
+ if (argc == 4) {
+ if (setlocale(LC_CTYPE, argv[3]) == NULL)
+ err(1, "setlocale");
+ }
+
+ /*
+ * Make sure we cover EOF as well.
+ */
+ __CTASSERT(CHAR_MIN == 0 || CHAR_MIN <= EOF);
+ __CTASSERT(EOF <= UCHAR_MAX);
+
+ for (ch = (CHAR_MIN == 0 ? EOF : CHAR_MIN); ch <= UCHAR_MAX; ch++) {
+ switch (fn) {
+#define M(upper, lower) \
+ case upper: \
+ switch (mode) { \
+ case MACRO: \
+ result = lower(ch); \
+ break; \
+ case FUNCTION: \
+ result = (lower)(ch); \
+ break; \
+ } \
+ break;
+ FOREACHCTYPE(M)
+#undef M
+ }
+ (void)result;
+ }
+
+ return 0;
+}
diff --git a/lib/libc/gen/h_execsig.c b/lib/libc/gen/h_execsig.c
new file mode 100644
index 000000000000..a954ca071094
--- /dev/null
+++ b/lib/libc/gen/h_execsig.c
@@ -0,0 +1,55 @@
+/* $NetBSD: h_execsig.c,v 1.1 2025/03/13 01:27:27 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: h_execsig.c,v 1.1 2025/03/13 01:27:27 riastradh Exp $");
+
+/*
+ * Helper program for testing signal delivery during execve(2) and
+ * posix_spawn(2). The caller will:
+ *
+ * 1. fork and exec, or spawn
+ * 2. kill the child
+ * 3. write a byte to be read from the child's stdin
+ *
+ * Since the caller issues syscalls in that order, the signal should be
+ * delivered to the child first, and it should be interrupted by a
+ * signal before it returnsa byte from read(2).
+ */
+
+#include <err.h>
+#include <unistd.h>
+
+int
+main(void)
+{
+
+ if (read(STDIN_FILENO, (char[]){0}, 1) == -1)
+ err(1, "read");
+ return 0;
+}
diff --git a/lib/libc/gen/t_arc4random.c b/lib/libc/gen/t_arc4random.c
new file mode 100644
index 000000000000..b26c1b8a931a
--- /dev/null
+++ b/lib/libc/gen/t_arc4random.c
@@ -0,0 +1,670 @@
+/* $NetBSD: t_arc4random.c,v 1.5 2025/03/09 18:11:55 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _REENTRANT
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_arc4random.c,v 1.5 2025/03/09 18:11:55 riastradh Exp $");
+
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <err.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arc4random.h"
+#include "reentrant.h"
+#include "h_macros.h"
+
+/*
+ * iszero(buf, len)
+ *
+ * True if len bytes at buf are all zero, false if any one of them
+ * is nonzero.
+ */
+static bool
+iszero(const void *buf, size_t len)
+{
+ const unsigned char *p = buf;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (p[i] != 0)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * arc4random_prng()
+ *
+ * Get a pointer to the current arc4random state, without updating
+ * any of the state, not even lazy initialization.
+ */
+static struct arc4random_prng *
+arc4random_prng(void)
+{
+ struct arc4random_prng *prng = NULL;
+
+ /*
+ * If arc4random has been initialized and there is a thread key
+ * (i.e., libc was built with _REENTRANT), get the thread-local
+ * arc4random state if there is one.
+ */
+ if (arc4random_global.per_thread)
+ prng = thr_getspecific(arc4random_global.thread_key);
+
+ /*
+ * If we couldn't get the thread-local state, get the global
+ * state instead.
+ */
+ if (prng == NULL)
+ prng = &arc4random_global.prng;
+
+ return prng;
+}
+
+/*
+ * arc4random_global_buf(buf, len)
+ *
+ * Same as arc4random_buf, but force use of the global state.
+ * Must happen before any other use of arc4random.
+ */
+static void
+arc4random_global_buf(void *buf, size_t len)
+{
+ struct rlimit rlim, orlim;
+ struct arc4random_prng *prng;
+
+ /*
+ * Save the address space limit.
+ */
+ RL(getrlimit(RLIMIT_AS, &orlim));
+ memcpy(&rlim, &orlim, sizeof(rlim));
+
+ /*
+ * Get a sample while the address space limit is zero. This
+ * should try, and fail, to allocate a thread-local arc4random
+ * state with mmap(2).
+ */
+ rlim.rlim_cur = 0;
+ RL(setrlimit(RLIMIT_AS, &rlim));
+ arc4random_buf(buf, len);
+ RL(setrlimit(RLIMIT_AS, &orlim));
+
+ /*
+ * Restore the address space limit.
+ */
+ RL(setrlimit(RLIMIT_AS, &orlim));
+
+ /*
+ * Verify the PRNG is the global one, not the thread-local one,
+ * and that it was initialized.
+ */
+ prng = arc4random_prng();
+ ATF_CHECK_EQ(prng, &arc4random_global.prng);
+ ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
+ ATF_CHECK(prng->arc4_epoch != 0);
+}
+
+/*
+ * arc4random_global_thread(cookie)
+ *
+ * Start routine for a thread that just grabs an output from the
+ * global state.
+ */
+static void *
+arc4random_global_thread(void *cookie)
+{
+ unsigned char buf[32];
+
+ arc4random_global_buf(buf, sizeof(buf));
+
+ return NULL;
+}
+
+ATF_TC(addrandom);
+ATF_TC_HEAD(addrandom, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test arc4random_addrandom updates the state");
+}
+ATF_TC_BODY(addrandom, tc)
+{
+ unsigned char buf[32], zero32[32] = {0};
+ struct arc4random_prng *prng, copy;
+
+ /*
+ * Get a sample to start things off.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * By this point, the global state must be initialized -- if
+ * not, the process should have aborted.
+ */
+ ATF_CHECK(arc4random_global.initialized);
+
+ /*
+ * Get the PRNG, global or local. By this point, the PRNG
+ * state should be nonzero (with overwhelmingly high
+ * probability) and the epoch should also be nonzero.
+ */
+ prng = arc4random_prng();
+ ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
+ ATF_CHECK(prng->arc4_epoch != 0);
+
+ /*
+ * Save a copy and update the state with arc4random_addrandom.
+ */
+ copy = *prng;
+ arc4random_addrandom(zero32, sizeof(zero32));
+
+ /*
+ * The state should have changed. (The epoch may or may not.)
+ */
+ ATF_CHECK(memcmp(&prng->arc4_prng, &copy.arc4_prng,
+ sizeof(copy.arc4_prng)) != 0);
+
+ /*
+ * Save a copy and update the state with arc4random_stir.
+ */
+ copy = *prng;
+ arc4random_stir();
+
+ /*
+ * The state should have changed. (The epoch may or may not.)
+ */
+ ATF_CHECK(memcmp(&prng->arc4_prng, &copy.arc4_prng,
+ sizeof(copy.arc4_prng)) != 0);
+}
+
+ATF_TC(consolidate);
+ATF_TC_HEAD(consolidate, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test consolidating entropy resets the epoch");
+}
+ATF_TC_BODY(consolidate, tc)
+{
+ unsigned char buf[32];
+ struct arc4random_prng *local, *global = &arc4random_global.prng;
+ unsigned localepoch, globalepoch;
+ const int consolidate = 1;
+ pthread_t thread;
+
+ /*
+ * Get a sample from the global state to make sure the global
+ * state is initialized. Remember the epoch.
+ */
+ arc4random_global_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng)));
+ ATF_CHECK((globalepoch = global->arc4_epoch) != 0);
+
+ /*
+ * Get a sample from the local state too to make sure the local
+ * state is initialized. Remember the epoch.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ local = arc4random_prng();
+ ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng)));
+ ATF_CHECK((localepoch = local->arc4_epoch) != 0);
+
+ /*
+ * Trigger entropy consolidation.
+ */
+ RL(sysctlbyname("kern.entropy.consolidate", /*oldp*/NULL, /*oldlen*/0,
+ &consolidate, sizeof(consolidate)));
+
+ /*
+ * Verify the epoch cache isn't changed yet until we ask for
+ * more data.
+ */
+ ATF_CHECK_EQ_MSG(globalepoch, global->arc4_epoch,
+ "global epoch was %u, now %u", globalepoch, global->arc4_epoch);
+ ATF_CHECK_EQ_MSG(localepoch, local->arc4_epoch,
+ "local epoch was %u, now %u", localepoch, local->arc4_epoch);
+
+ /*
+ * Request new output and verify the local epoch cache has
+ * changed.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK_MSG(localepoch != local->arc4_epoch,
+ "local epoch unchanged from %u", localepoch);
+
+ /*
+ * Create a new thread to grab output from the global state,
+ * wait for it to complete, and verify the global epoch cache
+ * has changed. (Now that we have already used the local state
+ * in this thread, we can't use the global state any more.)
+ */
+ RZ(pthread_create(&thread, NULL, &arc4random_global_thread, NULL));
+ RZ(pthread_join(thread, NULL));
+ ATF_CHECK_MSG(globalepoch != global->arc4_epoch,
+ "global epoch unchanged from %u", globalepoch);
+}
+
+ATF_TC(chroot);
+ATF_TC_HEAD(chroot, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test arc4random in an empty chroot");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(chroot, tc)
+{
+ pid_t pid;
+ int status;
+
+ /*
+ * Create an empty chroot.
+ */
+ RL(mkdir("root", 0500));
+
+ /*
+ * In a child process, enter the chroot and verify that we
+ * can't open /dev/urandom but we can use arc4random.
+ *
+ * (atf gets unhappy if we chroot in the same process, when it
+ * later tries to create a results file.)
+ */
+ RL(pid = fork());
+ if (pid == 0) {
+ unsigned char buf[32] = {0};
+
+ if (chroot("root") == -1)
+ err(1, "chroot");
+ if (open(_PATH_URANDOM, O_RDONLY) != -1)
+ errx(1, "open /dev/urandom must fail in empty chroot");
+ if (errno != ENOENT) {
+ err(1, "expected open to fail with %d=ENOENT, not %d",
+ ENOENT, errno);
+ }
+ arc4random_buf(buf, sizeof(buf));
+ if (iszero(buf, sizeof(buf))) /* Pr[fail] = 1/2^256 */
+ errx(1, "arc4random returned all-zero");
+ if (arc4random_prng()->arc4_epoch == 0)
+ errx(1, "arc4random failed to observe entropy epoch");
+ _exit(0);
+ }
+
+ /*
+ * Wait for the child process to finish.
+ */
+ RL(waitpid(pid, &status, 0));
+ ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "child exited status 0x%x", status);
+}
+
+ATF_TC(fdlimit);
+ATF_TC_HEAD(fdlimit, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test arc4random works even if we have hit the fd limit");
+}
+ATF_TC_BODY(fdlimit, tc)
+{
+ pid_t pid;
+ int status;
+
+ /*
+ * In a child process, clamp down on the file descriptor
+ * resource limit and verify that we can't open /dev/urandom
+ * but we can use arc4random.
+ *
+ * (atf gets unhappy if we chroot in the same process, when it
+ * later tries to create a results file.)
+ */
+ RL(pid = fork());
+ if (pid == 0) {
+ struct rlimit rlim = {.rlim_cur = 0, .rlim_max = 0};
+ unsigned char buf[32] = {0};
+
+ if (setrlimit(RLIMIT_NOFILE, &rlim) == -1)
+ err(1, "setrlimit(RLIMIT_NOFILE)");
+ if (open(_PATH_URANDOM, O_RDONLY) != -1)
+ errx(1, "open must fail with zero RLIMIT_NOFILE");
+ if (errno != EMFILE) {
+ err(1, "expected open to fail with %d=EMFILE, not %d",
+ EMFILE, errno);
+ }
+ arc4random_buf(buf, sizeof(buf));
+ if (iszero(buf, sizeof(buf))) /* Pr[fail] = 1/2^256 */
+ errx(1, "arc4random returned all-zero");
+ if (arc4random_prng()->arc4_epoch == 0)
+ errx(1, "arc4random failed to observe entropy epoch");
+ _exit(0);
+ }
+
+ /*
+ * Wait for the child process to finish.
+ */
+ RL(waitpid(pid, &status, 0));
+ ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0,
+ "child exited status 0x%x", status);
+}
+
+ATF_TC(fork);
+ATF_TC_HEAD(fork, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test fork zeros the state and gets independent state");
+}
+ATF_TC_BODY(fork, tc)
+{
+ unsigned char buf[32];
+ struct arc4random_prng *local, *global = &arc4random_global.prng;
+ struct arc4random_prng childstate;
+ int fd[2];
+ pid_t child, pid;
+ ssize_t nread;
+ int status;
+
+ /*
+ * Get a sample from the global state to make sure the global
+ * state is initialized.
+ */
+ arc4random_global_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng)));
+ ATF_CHECK(global->arc4_epoch != 0);
+
+ /*
+ * Get a sample from the local state too to make sure the local
+ * state is initialized.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ local = arc4random_prng();
+ ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng)));
+ ATF_CHECK(local->arc4_epoch != 0);
+
+ /*
+ * Create a pipe to transfer the state from child to parent.
+ */
+ RL(pipe(fd));
+
+ /*
+ * Fork a child.
+ */
+ RL(child = fork());
+ if (child == 0) {
+ status = 0;
+
+ /*
+ * Verify the states have been zero'd on fork.
+ */
+ if (!iszero(local, sizeof(*local))) {
+ fprintf(stderr, "failed to zero local state\n");
+ status = 1;
+ }
+ if (!iszero(global, sizeof(*global))) {
+ fprintf(stderr, "failed to zero global state\n");
+ status = 1;
+ }
+
+ /*
+ * Verify we generate nonzero output.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ if (iszero(buf, sizeof(buf))) {
+ fprintf(stderr, "failed to generate nonzero output\n");
+ status = 1;
+ }
+
+ /*
+ * Share the state to compare with parent.
+ */
+ if ((size_t)write(fd[1], local, sizeof(*local)) !=
+ sizeof(*local)) {
+ fprintf(stderr, "failed to share local state\n");
+ status = 1;
+ }
+ _exit(status);
+ }
+
+ /*
+ * Verify the global state has been zeroed as expected. (This
+ * way it is never available to the child, even shortly after
+ * the fork syscall returns before the atfork handler is
+ * called.)
+ */
+ ATF_CHECK(iszero(global, sizeof(*global)));
+
+ /*
+ * Read the state from the child.
+ */
+ RL(nread = read(fd[0], &childstate, sizeof(childstate)));
+ ATF_CHECK_EQ_MSG(nread, sizeof(childstate),
+ "nread=%zu sizeof(childstate)=%zu", nread, sizeof(childstate));
+
+ /*
+ * Verify the child state is distinct. (The global state has
+ * been zero'd so it's OK it if coincides.) Check again after
+ * we grab another output.
+ */
+ ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0);
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0);
+
+ /*
+ * Wait for the child to complete and verify it passed.
+ */
+ RL(pid = waitpid(child, &status, 0));
+ ATF_CHECK_EQ_MSG(status, 0, "child exited with nonzero status=%d",
+ status);
+}
+
+ATF_TC(global_aslimit);
+ATF_TC_HEAD(global_aslimit, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test the global state is used when address space limit is hit");
+}
+ATF_TC_BODY(global_aslimit, tc)
+{
+ unsigned char buf[32], buf1[32];
+
+ /*
+ * Get a sample from the global state (and verify it was using
+ * the global state).
+ */
+ arc4random_global_buf(buf, sizeof(buf));
+
+ /*
+ * Verify we got a sample.
+ */
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * Get a sample from whatever state and make sure it wasn't
+ * repeated, which happens only with probability 1/2^256.
+ */
+ arc4random_buf(buf1, sizeof(buf1));
+ ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
+}
+
+ATF_TC(global_threadkeylimit);
+ATF_TC_HEAD(global_threadkeylimit, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test the global state is used we run out of thread keys");
+}
+ATF_TC_BODY(global_threadkeylimit, tc)
+{
+ unsigned char buf[32], buf1[32];
+
+ /*
+ * Get a sample from the global state (and verify it was using
+ * the global state).
+ */
+ arc4random_global_buf(buf, sizeof(buf));
+
+ /*
+ * Verify we got a sample.
+ */
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * Artificially disable the per-thread state, make it an
+ * invalid thread key altogether, and clear the epoch. Make
+ * sure we're using the global PRNG state now.
+ */
+ arc4random_global.per_thread = false;
+ memset(&arc4random_global.thread_key, 0x5a,
+ sizeof(arc4random_global.thread_key));
+ arc4random_global.prng.arc4_epoch = 0;
+ ATF_CHECK(arc4random_prng() == &arc4random_global.prng);
+
+ /*
+ * Get a sample again and make sure it wasn't repeated, which
+ * happens only with probability 1/2^256.
+ */
+ arc4random_buf(buf1, sizeof(buf1));
+ ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
+
+ /*
+ * Verify this had the effect of updating the global epoch,
+ * meaning we used the global state and not the per-thread
+ * state.
+ */
+ ATF_CHECK(arc4random_global.prng.arc4_epoch != 0);
+}
+
+ATF_TC(local);
+ATF_TC_HEAD(local, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test arc4random uses thread-local state");
+ /* XXX skip if libc was built without _REENTRANT */
+}
+ATF_TC_BODY(local, tc)
+{
+ unsigned char buf[32], buf1[32];
+ struct arc4random_prng *prng;
+
+ /*
+ * Get a sample to start things off.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * Verify the arc4random state is _not_ the global state.
+ */
+ prng = arc4random_prng();
+ ATF_CHECK(prng != &arc4random_global.prng);
+ ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
+ ATF_CHECK(prng->arc4_epoch != 0);
+
+ /*
+ * Get another sample and make sure it wasn't repeated, which
+ * happens only with probability 1/2^256.
+ */
+ arc4random_buf(buf1, sizeof(buf1));
+ ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
+}
+
+ATF_TC(stackfallback);
+ATF_TC_HEAD(stackfallback, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test arc4random with pthread_atfork and thr_keycreate failure");
+}
+ATF_TC_BODY(stackfallback, tc)
+{
+ unsigned char buf[32], buf1[32];
+ struct arc4random_prng *local;
+
+ /*
+ * Get a sample to start things off. This makes the library
+ * gets initialized.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * Clear the arc4random global state, and the local state if it
+ * exists, and pretend pthread_atfork and thr_keycreate had
+ * both failed.
+ */
+ memset(&arc4random_global.prng, 0, sizeof(arc4random_global.prng));
+ if ((local = arc4random_prng()) != NULL)
+ memset(local, 0, sizeof(*local));
+ arc4random_global.forksafe = false;
+ arc4random_global.per_thread = false;
+
+ /*
+ * Make sure it still works to get a sample.
+ */
+ arc4random_buf(buf1, sizeof(buf1));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
+
+ /*
+ * Make sure the global and local epochs did not change.
+ */
+ ATF_CHECK_EQ_MSG(arc4random_global.prng.arc4_epoch, 0,
+ "global epoch: %d", arc4random_global.prng.arc4_epoch);
+ if (local != NULL) {
+ ATF_CHECK_EQ_MSG(local->arc4_epoch, 0,
+ "local epoch: %d", local->arc4_epoch);
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, addrandom);
+ ATF_TP_ADD_TC(tp, chroot);
+ ATF_TP_ADD_TC(tp, consolidate);
+ ATF_TP_ADD_TC(tp, fdlimit);
+ ATF_TP_ADD_TC(tp, fork);
+ ATF_TP_ADD_TC(tp, global_aslimit);
+ ATF_TP_ADD_TC(tp, global_threadkeylimit);
+ ATF_TP_ADD_TC(tp, local);
+ ATF_TP_ADD_TC(tp, stackfallback);
+
+ return atf_no_error();
+}
diff --git a/lib/libc/gen/t_ctype.c b/lib/libc/gen/t_ctype.c
new file mode 100644
index 000000000000..ee4db34304b7
--- /dev/null
+++ b/lib/libc/gen/t_ctype.c
@@ -0,0 +1,1236 @@
+/* $NetBSD: t_ctype.c,v 1.12 2025/09/15 00:11:55 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Tests for the ctype(3) character classification macros.
+ *
+ * NOTE: These tests intentionally trigger undefined behaviour by
+ * passing int values to the ctype(3) functions which are neither EOF
+ * nor representable by unsigned char. The purpose is to verify
+ * NetBSD's intentional trapping of this undefined behaviour -- or
+ * intentional allowing of this undefined behaviour, when the
+ * environment variable LIBC_ALLOWCTYPEABUSE is set.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_ctype.c,v 1.12 2025/09/15 00:11:55 riastradh Exp $");
+
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <ctype.h>
+#include <locale.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "h_macros.h"
+
+#ifdef __CHAR_UNSIGNED__
+enum { CHAR_UNSIGNED = 1 };
+#else
+enum { CHAR_UNSIGNED = 0 };
+#endif
+
+/*
+ * libc has a guard page for the LC_CTYPE=C ctype(3) tables only on
+ * some platforms. We skip it if char is unsigned (in which case the
+ * common kind of ctype(3) abuse is unlikely). We also skip it in
+ * static builds -- this is determined in the Makefile.
+ */
+#ifndef _CTYPE_GUARD_PAGE
+# ifdef __CHAR_UNSIGNED__
+# define _CTYPE_GUARD_PAGE 0
+# else
+# define _CTYPE_GUARD_PAGE 1
+# endif
+#endif
+
+static const char *const locales[] = { "C.UTF-8", "fr_FR.ISO8859-1", "C" };
+
+static int isalpha_wrapper(int ch) { return isalpha(ch); }
+static int isupper_wrapper(int ch) { return isupper(ch); }
+static int islower_wrapper(int ch) { return islower(ch); }
+static int isdigit_wrapper(int ch) { return isdigit(ch); }
+static int isxdigit_wrapper(int ch) { return isxdigit(ch); }
+static int isalnum_wrapper(int ch) { return isalnum(ch); }
+static int isspace_wrapper(int ch) { return isspace(ch); }
+static int ispunct_wrapper(int ch) { return ispunct(ch); }
+static int isprint_wrapper(int ch) { return isprint(ch); }
+static int isgraph_wrapper(int ch) { return isgraph(ch); }
+static int iscntrl_wrapper(int ch) { return iscntrl(ch); }
+static int isblank_wrapper(int ch) { return isblank(ch); }
+static int toupper_wrapper(int ch) { return toupper(ch); }
+static int tolower_wrapper(int ch) { return tolower(ch); }
+
+jmp_buf env;
+
+static void
+handle_signal(int signo)
+{
+
+ longjmp(env, 1);
+}
+
+static void
+test_abuse(const char *name, int (*ctypefn)(int))
+{
+ volatile int ch; /* for longjmp */
+
+ for (ch = CHAR_MIN; ch < 0; ch++) {
+ volatile int result;
+
+ if (ch == EOF)
+ continue;
+ ATF_REQUIRE_MSG(ch != (int)(unsigned char)ch, "ch=%d", ch);
+ if (setjmp(env) == 0) {
+ REQUIRE_LIBC(signal(SIGABRT, &handle_signal), SIG_ERR);
+ REQUIRE_LIBC(signal(SIGSEGV, &handle_signal), SIG_ERR);
+ result = (*ctypefn)(ch);
+ REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
+ REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
+ atf_tc_fail_nonfatal("%s failed to detect invalid %d,"
+ " returned %d",
+ name, ch, result);
+ } else {
+ REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
+ REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
+ }
+ }
+
+ for (; ch <= CHAR_MAX; ch++)
+ ATF_REQUIRE_MSG(ch == (int)(unsigned char)ch, "ch=%d", ch);
+}
+
+static void
+test_abuse_in_locales(const char *name, int (*ctypefn)(int), bool macro)
+{
+ size_t i;
+
+ for (i = 0; i < __arraycount(locales); i++) {
+ char buf[128];
+
+ if (!_CTYPE_GUARD_PAGE && macro &&
+ strcmp(locales[i], "C") == 0) {
+ fprintf(stderr, "skip LC_CTYPE=C ctype(3) abuse --"
+ " no libc guard page on this platform\n");
+ }
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, locales[i]) != NULL,
+ "locales[i]=%s", locales[i]);
+ snprintf(buf, sizeof(buf), "[%s]%s", locales[i], name);
+ test_abuse(buf, ctypefn);
+ }
+}
+
+static void
+test_use(const char *name, int (*ctypefn)(int))
+{
+ volatile int ch; /* for longjmp */
+
+ for (ch = EOF; ch <= CHAR_MAX; ch = (ch == EOF ? 0 : ch + 1)) {
+ volatile int result;
+
+ if (setjmp(env) == 0) {
+ REQUIRE_LIBC(signal(SIGABRT, &handle_signal), SIG_ERR);
+ REQUIRE_LIBC(signal(SIGSEGV, &handle_signal), SIG_ERR);
+ result = (*ctypefn)(ch);
+ REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
+ REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
+ (void)result;
+ } else {
+ REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
+ REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
+ atf_tc_fail_nonfatal("%s(%d) raised SIGSEGV",
+ name, ch);
+ }
+ }
+}
+
+static void
+test_isalpha_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isalpha", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isupper_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isupper", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_islower_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("islower", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isdigit_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isdigit", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isxdigit_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isxdigit", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isalnum_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isalnum", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isspace_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isspace", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_ispunct_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("ispunct", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isprint_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isprint", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isgraph_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isgraph", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_iscntrl_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("iscntrl", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_isblank_locale(const char *L, int (*ctypefn)(int))
+{
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("isblank", ctypefn);
+ ATF_CHECK(!(*ctypefn)(EOF));
+}
+
+static void
+test_toupper_locale(const char *L, int (*ctypefn)(int))
+{
+ int result;
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("toupper", ctypefn);
+ ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
+ "result=%d, expected EOF=%d", result, EOF);
+}
+
+static void
+test_tolower_locale(const char *L, int (*ctypefn)(int))
+{
+ int result;
+
+ ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
+ test_use("tolower", ctypefn);
+ ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
+ "result=%d, expected EOF=%d", result, EOF);
+}
+
+static void
+test_isalpha_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 'a': case 'A':
+ case 'b': case 'B':
+ case 'c': case 'C':
+ case 'd': case 'D':
+ case 'e': case 'E':
+ case 'f': case 'F':
+ case 'g': case 'G':
+ case 'h': case 'H':
+ case 'i': case 'I':
+ case 'j': case 'J':
+ case 'k': case 'K':
+ case 'l': case 'L':
+ case 'm': case 'M':
+ case 'n': case 'N':
+ case 'o': case 'O':
+ case 'p': case 'P':
+ case 'q': case 'Q':
+ case 'r': case 'R':
+ case 's': case 'S':
+ case 't': case 'T':
+ case 'u': case 'U':
+ case 'v': case 'V':
+ case 'w': case 'W':
+ case 'x': case 'X':
+ case 'y': case 'Y':
+ case 'z': case 'Z':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isupper_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_islower_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isdigit_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isxdigit_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'a': case 'A':
+ case 'b': case 'B':
+ case 'c': case 'C':
+ case 'd': case 'D':
+ case 'e': case 'E':
+ case 'f': case 'F':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isalnum_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 'a': case 'A':
+ case 'b': case 'B':
+ case 'c': case 'C':
+ case 'd': case 'D':
+ case 'e': case 'E':
+ case 'f': case 'F':
+ case 'g': case 'G':
+ case 'h': case 'H':
+ case 'i': case 'I':
+ case 'j': case 'J':
+ case 'k': case 'K':
+ case 'l': case 'L':
+ case 'm': case 'M':
+ case 'n': case 'N':
+ case 'o': case 'O':
+ case 'p': case 'P':
+ case 'q': case 'Q':
+ case 'r': case 'R':
+ case 's': case 'S':
+ case 't': case 'T':
+ case 'u': case 'U':
+ case 'v': case 'V':
+ case 'w': case 'W':
+ case 'x': case 'X':
+ case 'y': case 'Y':
+ case 'z': case 'Z':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isspace_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case ' ':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_ispunct_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ default:
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ case 0 ... 0x1f:
+ case 0x20: /* space is printing but not punctuation */
+ case 0x7f:
+ case 0x80 ... 0xff:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ case 'a': case 'A':
+ case 'b': case 'B':
+ case 'c': case 'C':
+ case 'd': case 'D':
+ case 'e': case 'E':
+ case 'f': case 'F':
+ case 'g': case 'G':
+ case 'h': case 'H':
+ case 'i': case 'I':
+ case 'j': case 'J':
+ case 'k': case 'K':
+ case 'l': case 'L':
+ case 'm': case 'M':
+ case 'n': case 'N':
+ case 'o': case 'O':
+ case 'p': case 'P':
+ case 'q': case 'Q':
+ case 'r': case 'R':
+ case 's': case 'S':
+ case 't': case 'T':
+ case 'u': case 'U':
+ case 'v': case 'V':
+ case 'w': case 'W':
+ case 'x': case 'X':
+ case 'y': case 'Y':
+ case 'z': case 'Z':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isprint_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 0x20: /* space is printing but not graphic */
+ default:
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ case 0 ... 0x1f:
+ case 0x7f:
+ case 0x80 ... 0xff:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isgraph_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ default:
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ case 0 ... 0x1f:
+ case 0x20: /* space is printing but not graphic */
+ case 0x7f:
+ case 0x80 ... 0xff:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_iscntrl_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 0 ... 0x1f:
+ case 0x7f:
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_isblank_c(int (*ctypefn)(int))
+{
+ int ch;
+
+ ATF_CHECK(!(*ctypefn)(EOF));
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ default:
+ ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
+ break;
+ }
+ }
+}
+
+static void
+test_toupper_c(int (*ctypefn)(int))
+{
+ int ch, result, expected;
+
+ ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
+ "result=%d, expected EOF=%d", result, EOF);
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 'a': case 'A': expected = 'A'; break;
+ case 'b': case 'B': expected = 'B'; break;
+ case 'c': case 'C': expected = 'C'; break;
+ case 'd': case 'D': expected = 'D'; break;
+ case 'e': case 'E': expected = 'E'; break;
+ case 'f': case 'F': expected = 'F'; break;
+ case 'g': case 'G': expected = 'G'; break;
+ case 'h': case 'H': expected = 'H'; break;
+ case 'i': case 'I': expected = 'I'; break;
+ case 'j': case 'J': expected = 'J'; break;
+ case 'k': case 'K': expected = 'K'; break;
+ case 'l': case 'L': expected = 'L'; break;
+ case 'm': case 'M': expected = 'M'; break;
+ case 'n': case 'N': expected = 'N'; break;
+ case 'o': case 'O': expected = 'O'; break;
+ case 'p': case 'P': expected = 'P'; break;
+ case 'q': case 'Q': expected = 'Q'; break;
+ case 'r': case 'R': expected = 'R'; break;
+ case 's': case 'S': expected = 'S'; break;
+ case 't': case 'T': expected = 'T'; break;
+ case 'u': case 'U': expected = 'U'; break;
+ case 'v': case 'V': expected = 'V'; break;
+ case 'w': case 'W': expected = 'W'; break;
+ case 'x': case 'X': expected = 'X'; break;
+ case 'y': case 'Y': expected = 'Y'; break;
+ case 'z': case 'Z': expected = 'Z'; break;
+ default:
+ expected = ch;
+ break;
+ }
+ ATF_CHECK_MSG((result = (*ctypefn)(ch)) == expected,
+ "result=%d expected=%d", result, expected);
+ }
+}
+
+static void
+test_tolower_c(int (*ctypefn)(int))
+{
+ int ch, result, expected;
+
+ ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
+ "result=%d, expected EOF=%d", result, EOF);
+ for (ch = 0; ch <= UCHAR_MAX; ch++) {
+ switch (ch) {
+ case 'a': case 'A': expected = 'a'; break;
+ case 'b': case 'B': expected = 'b'; break;
+ case 'c': case 'C': expected = 'c'; break;
+ case 'd': case 'D': expected = 'd'; break;
+ case 'e': case 'E': expected = 'e'; break;
+ case 'f': case 'F': expected = 'f'; break;
+ case 'g': case 'G': expected = 'g'; break;
+ case 'h': case 'H': expected = 'h'; break;
+ case 'i': case 'I': expected = 'i'; break;
+ case 'j': case 'J': expected = 'j'; break;
+ case 'k': case 'K': expected = 'k'; break;
+ case 'l': case 'L': expected = 'l'; break;
+ case 'm': case 'M': expected = 'm'; break;
+ case 'n': case 'N': expected = 'n'; break;
+ case 'o': case 'O': expected = 'o'; break;
+ case 'p': case 'P': expected = 'p'; break;
+ case 'q': case 'Q': expected = 'q'; break;
+ case 'r': case 'R': expected = 'r'; break;
+ case 's': case 'S': expected = 's'; break;
+ case 't': case 'T': expected = 't'; break;
+ case 'u': case 'U': expected = 'u'; break;
+ case 'v': case 'V': expected = 'v'; break;
+ case 'w': case 'W': expected = 'w'; break;
+ case 'x': case 'X': expected = 'x'; break;
+ case 'y': case 'Y': expected = 'y'; break;
+ case 'z': case 'Z': expected = 'z'; break;
+ default:
+ expected = ch;
+ break;
+ }
+ ATF_CHECK_MSG((result = (*ctypefn)(ch)) == expected,
+ "result=%d expected=%d", result, expected);
+ }
+}
+
+extern char **environ;
+
+static void
+test_abuse_override(const struct atf_tc *tc, const char *fn, const char *mode,
+ const char *locale)
+{
+ char h_ctype_abuse[PATH_MAX];
+ pid_t pid;
+ int status;
+
+ RL(snprintf(h_ctype_abuse, sizeof(h_ctype_abuse), "%s/h_ctype_abuse",
+ atf_tc_get_config_var(tc, "srcdir")));
+
+ RL(setenv("LIBC_ALLOWCTYPEABUSE", "", 1));
+
+ RL(pid = vfork());
+ if (pid == 0) { /* child */
+ char *const argv[] = {
+ h_ctype_abuse,
+ __UNCONST(fn),
+ __UNCONST(mode),
+ __UNCONST(locale),
+ NULL,
+ };
+
+ if (execve(argv[0], argv, environ) == -1)
+ _exit(1);
+ _exit(2);
+ }
+
+ RL(waitpid(pid, &status, 0));
+ if (WIFSIGNALED(status)) {
+ atf_tc_fail_nonfatal("child exited on signal %d (%s)",
+ WTERMSIG(status), strsignal(WTERMSIG(status)));
+ } else if (!WIFEXITED(status)) {
+ atf_tc_fail_nonfatal("child exited status=0x%x", status);
+ } else {
+ ATF_CHECK_MSG(WEXITSTATUS(status) == 0,
+ "child exited with code %d",
+ WEXITSTATUS(status));
+ }
+}
+
+static void
+test_abuse_override_in_locales(const struct atf_tc *tc, const char *fn,
+ const char *mode)
+{
+ size_t i;
+
+ for (i = 0; i < __arraycount(locales); i++) {
+ fprintf(stderr, "# locale %s\n", locales[i]);
+ test_abuse_override(tc, fn, mode, locales[i]);
+ }
+}
+
+#define ADD_TEST_ABUSE(TP, FN) do \
+{ \
+ ATF_TP_ADD_TC(TP, abuse_##FN##_macro_c); \
+ ATF_TP_ADD_TC(TP, abuse_##FN##_function_c); \
+ ATF_TP_ADD_TC(TP, abuse_##FN##_macro_locale); \
+ ATF_TP_ADD_TC(TP, abuse_##FN##_function_locale); \
+} while (0)
+
+#define DEF_TEST_ABUSE(FN) \
+ATF_TC(abuse_##FN##_macro_c); \
+ATF_TC_HEAD(abuse_##FN##_macro_c, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test abusing "#FN" macro with default LC_CTYPE=C"); \
+} \
+ATF_TC_BODY(abuse_##FN##_macro_c, tc) \
+{ \
+ if (CHAR_UNSIGNED) { \
+ atf_tc_skip("runtime ctype(3) abuse is impossible with" \
+ " unsigned char"); \
+ } \
+ if (!_CTYPE_GUARD_PAGE) \
+ atf_tc_skip("no LC_CTYPE=C guard page on this platform"); \
+ test_abuse(#FN, &FN##_wrapper); \
+} \
+ATF_TC(abuse_##FN##_function_c); \
+ATF_TC_HEAD(abuse_##FN##_function_c, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test abusing "#FN" function with default LC_CTYPE=C"); \
+} \
+ATF_TC_BODY(abuse_##FN##_function_c, tc) \
+{ \
+ if (CHAR_UNSIGNED) { \
+ atf_tc_skip("runtime ctype(3) abuse is impossible with" \
+ " unsigned char"); \
+ } \
+ if (!_CTYPE_GUARD_PAGE) \
+ atf_tc_skip("no LC_CTYPE=C guard page on this platform"); \
+ test_abuse(#FN, &FN); \
+} \
+ATF_TC(abuse_##FN##_macro_locale); \
+ATF_TC_HEAD(abuse_##FN##_macro_locale, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test abusing "#FN" macro with locales"); \
+} \
+ATF_TC_BODY(abuse_##FN##_macro_locale, tc) \
+{ \
+ if (CHAR_UNSIGNED) { \
+ atf_tc_skip("runtime ctype(3) abuse is impossible with" \
+ " unsigned char"); \
+ } \
+ test_abuse_in_locales(#FN, &FN##_wrapper, /*macro*/true); \
+} \
+ATF_TC(abuse_##FN##_function_locale); \
+ATF_TC_HEAD(abuse_##FN##_function_locale, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test abusing "#FN" function with locales"); \
+} \
+ATF_TC_BODY(abuse_##FN##_function_locale, tc) \
+{ \
+ if (CHAR_UNSIGNED) { \
+ atf_tc_skip("runtime ctype(3) abuse is impossible with" \
+ " unsigned char"); \
+ } \
+ test_abuse_in_locales(#FN, &FN, /*macro*/false); \
+}
+
+#define ADD_TEST_ABUSE_OVERRIDE(TP, FN) do \
+{ \
+ ATF_TP_ADD_TC(TP, abuse_override_##FN##_macro_c); \
+ ATF_TP_ADD_TC(TP, abuse_override_##FN##_function_c); \
+ ATF_TP_ADD_TC(TP, abuse_override_##FN##_macro_locale); \
+ ATF_TP_ADD_TC(TP, abuse_override_##FN##_function_locale); \
+} while (0)
+
+#define DEF_TEST_ABUSE_OVERRIDE(FN) \
+ATF_TC(abuse_override_##FN##_macro_c); \
+ATF_TC_HEAD(abuse_override_##FN##_macro_c, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test allowing abuse of "#FN" macro with default LC_CTYPE=C"); \
+} \
+ATF_TC_BODY(abuse_override_##FN##_macro_c, tc) \
+{ \
+ test_abuse_override(tc, #FN, "macro", NULL); \
+} \
+ATF_TC(abuse_override_##FN##_function_c); \
+ATF_TC_HEAD(abuse_override_##FN##_function_c, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test allowing abuse "#FN" function with default LC_CTYPE=C"); \
+} \
+ATF_TC_BODY(abuse_override_##FN##_function_c, tc) \
+{ \
+ test_abuse_override(tc, #FN, "function", NULL); \
+} \
+ATF_TC(abuse_override_##FN##_macro_locale); \
+ATF_TC_HEAD(abuse_override_##FN##_macro_locale, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test allowing abuse of "#FN" macro with locales"); \
+} \
+ATF_TC_BODY(abuse_override_##FN##_macro_locale, tc) \
+{ \
+ test_abuse_override_in_locales(tc, #FN, "macro"); \
+} \
+ATF_TC(abuse_override_##FN##_function_locale); \
+ATF_TC_HEAD(abuse_override_##FN##_function_locale, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test allowing abuse of "#FN" function with locales"); \
+} \
+ATF_TC_BODY(abuse_override_##FN##_function_locale, tc) \
+{ \
+ test_abuse_override_in_locales(tc, #FN, "function"); \
+}
+
+#define ADD_TEST_USE(TP, FN) do \
+{ \
+ ATF_TP_ADD_TC(TP, FN##_macro_c); \
+ ATF_TP_ADD_TC(TP, FN##_function_c); \
+ ATF_TP_ADD_TC(TP, FN##_macro_locale); \
+ ATF_TP_ADD_TC(TP, FN##_function_locale); \
+} while (0)
+
+#define DEF_TEST_USE(FN) \
+ATF_TC(FN##_macro_c); \
+ATF_TC_HEAD(FN##_macro_c, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test "#FN" macro with default LC_CTYPE=C"); \
+} \
+ATF_TC_BODY(FN##_macro_c, tc) \
+{ \
+ test_##FN##_c(&FN##_wrapper); \
+} \
+ATF_TC(FN##_function_c); \
+ATF_TC_HEAD(FN##_function_c, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test "#FN" function with default LC_CTYPE=C"); \
+} \
+ATF_TC_BODY(FN##_function_c, tc) \
+{ \
+ test_##FN##_c(&FN); \
+} \
+ATF_TC(FN##_macro_locale); \
+ATF_TC_HEAD(FN##_macro_locale, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test "#FN" macro with locales"); \
+} \
+ATF_TC_BODY(FN##_macro_locale, tc) \
+{ \
+ size_t i; \
+ \
+ for (i = 0; i < __arraycount(locales); i++) \
+ test_##FN##_locale(locales[i], &FN##_wrapper); \
+} \
+ATF_TC(FN##_function_locale); \
+ATF_TC_HEAD(FN##_function_locale, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", \
+ "Test "#FN" function with locales"); \
+} \
+ATF_TC_BODY(FN##_function_locale, tc) \
+{ \
+ size_t i; \
+ \
+ for (i = 0; i < __arraycount(locales); i++) \
+ test_##FN##_locale(locales[i], &FN); \
+}
+
+DEF_TEST_ABUSE(isalpha)
+DEF_TEST_ABUSE(isupper)
+DEF_TEST_ABUSE(islower)
+DEF_TEST_ABUSE(isdigit)
+DEF_TEST_ABUSE(isxdigit)
+DEF_TEST_ABUSE(isalnum)
+DEF_TEST_ABUSE(isspace)
+DEF_TEST_ABUSE(ispunct)
+DEF_TEST_ABUSE(isprint)
+DEF_TEST_ABUSE(isgraph)
+DEF_TEST_ABUSE(iscntrl)
+DEF_TEST_ABUSE(isblank)
+DEF_TEST_ABUSE(toupper)
+DEF_TEST_ABUSE(tolower)
+
+DEF_TEST_ABUSE_OVERRIDE(isalpha)
+DEF_TEST_ABUSE_OVERRIDE(isupper)
+DEF_TEST_ABUSE_OVERRIDE(islower)
+DEF_TEST_ABUSE_OVERRIDE(isdigit)
+DEF_TEST_ABUSE_OVERRIDE(isxdigit)
+DEF_TEST_ABUSE_OVERRIDE(isalnum)
+DEF_TEST_ABUSE_OVERRIDE(isspace)
+DEF_TEST_ABUSE_OVERRIDE(ispunct)
+DEF_TEST_ABUSE_OVERRIDE(isprint)
+DEF_TEST_ABUSE_OVERRIDE(isgraph)
+DEF_TEST_ABUSE_OVERRIDE(iscntrl)
+DEF_TEST_ABUSE_OVERRIDE(isblank)
+DEF_TEST_ABUSE_OVERRIDE(toupper)
+DEF_TEST_ABUSE_OVERRIDE(tolower)
+
+DEF_TEST_USE(isalpha)
+DEF_TEST_USE(isupper)
+DEF_TEST_USE(islower)
+DEF_TEST_USE(isdigit)
+DEF_TEST_USE(isxdigit)
+DEF_TEST_USE(isalnum)
+DEF_TEST_USE(isspace)
+DEF_TEST_USE(ispunct)
+DEF_TEST_USE(isprint)
+DEF_TEST_USE(isgraph)
+DEF_TEST_USE(iscntrl)
+DEF_TEST_USE(isblank)
+DEF_TEST_USE(toupper)
+DEF_TEST_USE(tolower)
+
+ATF_TC(eof_confusion_iso8859_1);
+ATF_TC_HEAD(eof_confusion_iso8859_1, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test potential confusion with EOF in ISO-8859-1");
+}
+ATF_TC_BODY(eof_confusion_iso8859_1, tc)
+{
+ int ydots = 0xff; /* ÿ, LATIN SMALL LETTER Y WITH DIAERESIS */
+ int ch;
+
+ /*
+ * The LATIN SMALL LETTER Y WITH DIAERESIS code point 0xff in
+ * ISO-8859-1 is curious primarily because its bit pattern
+ * coincides with an 8-bit signed -1, which is to say, EOF as
+ * an 8-bit quantity; of course, for EOF, all of the is*
+ * functions are supposed to return false (as we test above).
+ * It also has the curious property that it lacks any
+ * corresponding uppercase code point in ISO-8859-1, so we
+ * can't distinguish it from EOF by tolower/toupper.
+ */
+ ATF_REQUIRE(setlocale(LC_CTYPE, "fr_FR.ISO8859-1") != NULL);
+ ATF_CHECK(isalpha(ydots));
+ ATF_CHECK(!isupper(ydots));
+ ATF_CHECK(islower(ydots));
+ ATF_CHECK(!isdigit(ydots));
+ ATF_CHECK(!isxdigit(ydots));
+ ATF_CHECK(isalnum(ydots));
+ ATF_CHECK(!isspace(ydots));
+ ATF_CHECK(!ispunct(ydots));
+ ATF_CHECK(isprint(ydots));
+ ATF_CHECK(isgraph(ydots));
+ ATF_CHECK(!iscntrl(ydots));
+ ATF_CHECK(!isblank(ydots));
+ ATF_CHECK_MSG((ch = toupper(ydots)) == ydots, "ch=0x%x", ch);
+ ATF_CHECK_MSG((ch = tolower(ydots)) == ydots, "ch=0x%x", ch);
+}
+
+ATF_TC(eof_confusion_koi8_u);
+ATF_TC_HEAD(eof_confusion_koi8_u, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test potential confusion with EOF in KOI8-U");
+}
+ATF_TC_BODY(eof_confusion_koi8_u, tc)
+{
+ int Hard = 0xff; /* Ъ, CYRILLIC CAPITAL LETTER HARD SIGN */
+ int hard = 0xdf; /* ъ, CYRILLIC SMALL LETTER HARD SIGN */
+ int ch;
+
+ /*
+ * The CYRILLIC CAPITAL LETTER HARD SIGN code point 0xff in
+ * KOI8-U (and KOI8-R) also coincides with the bit pattern of
+ * an 8-bit signed -1. Unlike LATIN SMALL LETTER Y WITH
+ * DIAERESIS, it has a lowercase equivalent in KOI8-U.
+ */
+ ATF_REQUIRE(setlocale(LC_CTYPE, "uk_UA.KOI8-U") != NULL);
+ ATF_CHECK(isalpha(Hard));
+ ATF_CHECK(isupper(Hard));
+ ATF_CHECK(!islower(Hard));
+ ATF_CHECK(!isdigit(Hard));
+ ATF_CHECK(!isxdigit(Hard));
+ ATF_CHECK(isalnum(Hard));
+ ATF_CHECK(!isspace(Hard));
+ ATF_CHECK(!ispunct(Hard));
+ ATF_CHECK(isprint(Hard));
+ ATF_CHECK(isgraph(Hard));
+ ATF_CHECK(!iscntrl(Hard));
+ ATF_CHECK(!isblank(Hard));
+ ATF_CHECK_MSG((ch = toupper(Hard)) == Hard, "ch=0x%x", ch);
+ ATF_CHECK_MSG((ch = tolower(Hard)) == hard, "ch=0x%x", ch);
+}
+
+ATF_TC(eof_confusion_pt154);
+ATF_TC_HEAD(eof_confusion_pt154, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test potential confusion with EOF in PT154");
+}
+ATF_TC_BODY(eof_confusion_pt154, tc)
+{
+ int ya = 0xff; /* я, CYRILLIC SMALL LETTER YA */
+ int Ya = 0xdf; /* Я, CYRILLIC CAPITAL LETTER YA */
+ int ch;
+
+ /*
+ * The CYRILLIC SMALL LETTER YA code point 0xff in PT154 also
+ * coincides with the bit pattern of an 8-bit signed -1, and is
+ * lowercase with a corresponding uppercase code point in
+ * PT154.
+ */
+ ATF_REQUIRE(setlocale(LC_CTYPE, "kk_KZ.PT154") != NULL);
+ ATF_CHECK(isalpha(ya));
+ ATF_CHECK(!isupper(ya));
+ ATF_CHECK(islower(ya));
+ ATF_CHECK(!isdigit(ya));
+ ATF_CHECK(!isxdigit(ya));
+ ATF_CHECK(isalnum(ya));
+ ATF_CHECK(!isspace(ya));
+ ATF_CHECK(!ispunct(ya));
+ ATF_CHECK(isprint(ya));
+ ATF_CHECK(isgraph(ya));
+ ATF_CHECK(!iscntrl(ya));
+ ATF_CHECK(!isblank(ya));
+ ATF_CHECK_MSG((ch = toupper(ya)) == Ya, "ch=0x%x", ch);
+ ATF_CHECK_MSG((ch = tolower(ya)) == ya, "ch=0x%x", ch);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ADD_TEST_ABUSE(tp, isalpha);
+ ADD_TEST_ABUSE(tp, isupper);
+ ADD_TEST_ABUSE(tp, islower);
+ ADD_TEST_ABUSE(tp, isdigit);
+ ADD_TEST_ABUSE(tp, isxdigit);
+ ADD_TEST_ABUSE(tp, isalnum);
+ ADD_TEST_ABUSE(tp, isspace);
+ ADD_TEST_ABUSE(tp, ispunct);
+ ADD_TEST_ABUSE(tp, isprint);
+ ADD_TEST_ABUSE(tp, isgraph);
+ ADD_TEST_ABUSE(tp, iscntrl);
+ ADD_TEST_ABUSE(tp, isblank);
+ ADD_TEST_ABUSE(tp, toupper);
+ ADD_TEST_ABUSE(tp, tolower);
+
+ ADD_TEST_ABUSE_OVERRIDE(tp, isalpha);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isupper);
+ ADD_TEST_ABUSE_OVERRIDE(tp, islower);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isdigit);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isxdigit);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isalnum);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isspace);
+ ADD_TEST_ABUSE_OVERRIDE(tp, ispunct);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isprint);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isgraph);
+ ADD_TEST_ABUSE_OVERRIDE(tp, iscntrl);
+ ADD_TEST_ABUSE_OVERRIDE(tp, isblank);
+ ADD_TEST_ABUSE_OVERRIDE(tp, toupper);
+ ADD_TEST_ABUSE_OVERRIDE(tp, tolower);
+
+ ADD_TEST_USE(tp, isalpha);
+ ADD_TEST_USE(tp, isupper);
+ ADD_TEST_USE(tp, islower);
+ ADD_TEST_USE(tp, isdigit);
+ ADD_TEST_USE(tp, isxdigit);
+ ADD_TEST_USE(tp, isalnum);
+ ADD_TEST_USE(tp, isspace);
+ ADD_TEST_USE(tp, ispunct);
+ ADD_TEST_USE(tp, isprint);
+ ADD_TEST_USE(tp, isgraph);
+ ADD_TEST_USE(tp, iscntrl);
+ ADD_TEST_USE(tp, isblank);
+ ADD_TEST_USE(tp, toupper);
+ ADD_TEST_USE(tp, tolower);
+
+ ATF_TP_ADD_TC(tp, eof_confusion_iso8859_1);
+ ATF_TP_ADD_TC(tp, eof_confusion_koi8_u);
+ ATF_TP_ADD_TC(tp, eof_confusion_pt154);
+
+ return atf_no_error();
+}
diff --git a/lib/libc/gen/t_timespec_get.c b/lib/libc/gen/t_timespec_get.c
new file mode 100644
index 000000000000..b51625311032
--- /dev/null
+++ b/lib/libc/gen/t_timespec_get.c
@@ -0,0 +1,123 @@
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+
+#include <limits.h>
+#include <time.h>
+
+ATF_TC(timespec_getres);
+ATF_TC_HEAD(timespec_getres, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Resolution tests for timespec_getres");
+}
+
+ATF_TC_BODY(timespec_getres, tc)
+{
+ struct timespec ts, ts2;
+
+ ATF_REQUIRE_EQ(timespec_getres(&ts, TIME_MONOTONIC), TIME_MONOTONIC);
+ ATF_REQUIRE_EQ(clock_getres(CLOCK_MONOTONIC, &ts2), 0);
+
+ ATF_REQUIRE_EQ(ts.tv_sec, ts2.tv_sec);
+ ATF_REQUIRE_EQ(ts.tv_nsec, ts2.tv_nsec);
+
+ ATF_REQUIRE_EQ(timespec_getres(&ts, TIME_UTC), TIME_UTC);
+ ATF_REQUIRE_EQ(clock_getres(CLOCK_REALTIME, &ts2), 0);
+
+ ATF_REQUIRE_EQ(ts.tv_sec, ts2.tv_sec);
+ ATF_REQUIRE_EQ(ts.tv_nsec, ts2.tv_nsec);
+
+ /* now an invalid value */
+ ATF_REQUIRE_EQ(timespec_getres(&ts, INT_MAX), 0);
+}
+
+ATF_TC(timespec_get);
+ATF_TC_HEAD(timespec_get, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Basic tests for timespec_get");
+}
+
+ATF_TC_BODY(timespec_get, tc)
+{
+ struct timespec ts, ts2;
+
+ ATF_REQUIRE_EQ(timespec_get(&ts, TIME_UTC), TIME_UTC);
+ ATF_REQUIRE_EQ(clock_gettime(CLOCK_REALTIME, &ts2), 0);
+
+ /*
+ * basically test that these clocks (which should be the same source)
+ * aren't too wildly apart...
+ */
+
+ if (ts2.tv_sec >= ts.tv_sec) {
+ ATF_REQUIRE((ts2.tv_sec - ts.tv_sec) < 86400);
+ } else {
+ ATF_REQUIRE((ts.tv_sec - ts2.tv_sec) < 86400);
+ }
+
+ /* now an invalid value */
+ ATF_REQUIRE_EQ(timespec_get(&ts, INT_MAX), 0);
+}
+
+ATF_TC(timespec_get_monotonic);
+ATF_TC_HEAD(timespec_get_monotonic, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Monotonic tests for timespec_getres");
+}
+
+ATF_TC_BODY(timespec_get_monotonic, tc)
+{
+ struct timespec ts, ts2;
+
+ ATF_REQUIRE_EQ(timespec_get(&ts, TIME_MONOTONIC), TIME_MONOTONIC);
+ ATF_REQUIRE_EQ(clock_gettime(CLOCK_MONOTONIC, &ts2), 0);
+
+ /*
+ * basically test that these clocks (which should be the same source)
+ * aren't too wildly apart...
+ */
+
+ ATF_REQUIRE(ts2.tv_sec >= ts.tv_sec);
+ ATF_REQUIRE((ts2.tv_sec - ts.tv_sec) < 86400);
+
+ /* test that it's actually monotonic */
+ ATF_REQUIRE_EQ(timespec_get(&ts2, TIME_MONOTONIC), TIME_MONOTONIC);
+ ATF_REQUIRE(ts2.tv_sec >= ts.tv_sec);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, timespec_getres);
+ ATF_TP_ADD_TC(tp, timespec_get);
+ ATF_TP_ADD_TC(tp, timespec_get_monotonic);
+
+ return atf_no_error();
+}
+
diff --git a/lib/libc/locale/t_c16rtomb.c b/lib/libc/locale/t_c16rtomb.c
new file mode 100644
index 000000000000..3d695db8dc97
--- /dev/null
+++ b/lib/libc/locale/t_c16rtomb.c
@@ -0,0 +1,287 @@
+/* $NetBSD: t_c16rtomb.c,v 1.6 2024/08/19 16:22:10 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins
+ * All rights reserved.
+ *
+ * Copyright (c) 2013 Ed Schouten <ed@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Test program for c16rtomb() as specified by ISO/IEC 9899:2011.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_c16rtomb.c,v 1.6 2024/08/19 16:22:10 riastradh Exp $");
+
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <string.h>
+#include <uchar.h>
+
+#include <atf-c.h>
+
+static void
+require_lc_ctype(const char *locale_name)
+{
+ char *lc_ctype_set;
+
+ lc_ctype_set = setlocale(LC_CTYPE, locale_name);
+ if (lc_ctype_set == NULL)
+ atf_tc_fail("setlocale(LC_CTYPE, \"%s\") failed; errno=%d",
+ locale_name, errno);
+
+ ATF_REQUIRE_EQ_MSG(strcmp(lc_ctype_set, locale_name), 0,
+ "lc_ctype_set=%s locale_name=%s", lc_ctype_set, locale_name);
+}
+
+static mbstate_t s;
+static char buf[7*MB_LEN_MAX + 1];
+
+ATF_TC_WITHOUT_HEAD(c16rtomb_c_locale_test);
+ATF_TC_BODY(c16rtomb_c_locale_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("C");
+
+ /*
+ * If the buffer argument is NULL, c16 is implicitly 0,
+ * c16rtomb() resets its internal state.
+ */
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, L'\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, 0xdc00, NULL)), 1, "n=%zu", n);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0, &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0 &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, L'\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, L'A', NULL)), 1, "n=%zu", n);
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, L'A', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 'A' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ /* Unicode character 'Pile of poo'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xd83d, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xdca9, &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+
+ /* Incomplete Unicode character 'Pile of poo', interrupted by NUL. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xd83d, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, L'\0', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(c16rtomb_iso2022jp_locale_test);
+ATF_TC_BODY(c16rtomb_iso2022jp_locale_test, tc)
+{
+ char *p;
+ size_t n;
+
+ require_lc_ctype("ja_JP.ISO-2022-JP");
+
+ /*
+ * If the buffer argument is NULL, c16 is implicitly 0,
+ * c16rtomb() resets its internal state.
+ */
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, L'\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, 0xdc00, NULL)), 1, "n=%zu", n);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0, &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0 &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, L'\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(NULL, L'A', NULL)), 1, "n=%zu", n);
+
+ /*
+ * 1. U+0042 LATIN CAPITAL LETTER A
+ * 2. U+00A5 YEN SIGN
+ * 3. U+00A5 YEN SIGN (again, no shift needed)
+ * 4. U+30A2 KATAKANA LETTER A
+ * 5. U+30A2 KATAKANA LETTER A (again, no shift needed)
+ * 6. incomplete UTF-16 surrogate pair -- no output
+ * 7. U+0000 NUL (plus shift sequence to initial state)
+ */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ p = buf;
+ ATF_CHECK_EQ_MSG((n = c16rtomb(p, L'A', &s)), 1, "n=%zu", n); /* 1 */
+ p += 1;
+ ATF_CHECK_EQ_MSG((n = c16rtomb(p, 0xa5, &s)), 4, "n=%zu", n); /* 2 */
+ p += 4;
+ ATF_CHECK_EQ_MSG((n = c16rtomb(p, 0xa5, &s)), 1, "n=%zu", n); /* 3 */
+ p += 1;
+ ATF_CHECK_EQ_MSG((n = c16rtomb(p, 0x30a2, &s)), 5, "n=%zu", n); /* 4 */
+ p += 5;
+ ATF_CHECK_EQ_MSG((n = c16rtomb(p, 0x30a2, &s)), 2, "n=%zu", n); /* 5 */
+ p += 2;
+ ATF_CHECK_EQ_MSG((n = c16rtomb(p, 0xd800, &s)), 0, "n=%zu", n); /* 6 */
+ ATF_CHECK_EQ_MSG((n = c16rtomb(p, L'\0', &s)), 4, "n=%zu", n); /* 7 */
+ p += 4;
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 'A' &&
+ (unsigned char)buf[1] == 0x1b && /* shift ISO/IEC 646:JP */
+ (unsigned char)buf[2] == '(' &&
+ (unsigned char)buf[3] == 'J' &&
+ (unsigned char)buf[4] == 0x5c && /* YEN SIGN */
+ (unsigned char)buf[5] == 0x5c && /* YEN SIGN */
+ (unsigned char)buf[6] == 0x1b && /* shift JIS X 0208 */
+ (unsigned char)buf[7] == '$' &&
+ (unsigned char)buf[8] == 'B' &&
+ (unsigned char)buf[9] == 0x25 && /* KATAKANA LETTER A */
+ (unsigned char)buf[10] == 0x22 &&
+ (unsigned char)buf[11] == 0x25 && /* KATAKANA LETTER A */
+ (unsigned char)buf[12] == 0x22 &&
+ (unsigned char)buf[13] == 0x1b && /* shift US-ASCII */
+ (unsigned char)buf[14] == '(' &&
+ (unsigned char)buf[15] == 'B' &&
+ (unsigned char)buf[16] == '\0' &&
+ (unsigned char)buf[17] == 0xcc),
+ "buf=[%02x %02x %02x %02x %02x %02x %02x %02x "
+ " %02x %02x %02x %02x %02x %02x %02x %02x "
+ " %02x %02x]",
+ buf[0], buf[1], buf[2], buf[3],
+ buf[4], buf[5], buf[6], buf[7],
+ buf[8], buf[9], buf[10], buf[11],
+ buf[12], buf[13], buf[14], buf[15],
+ buf[16], buf[17]);
+}
+
+ATF_TC_WITHOUT_HEAD(c16rtomb_iso_8859_1_test);
+ATF_TC_BODY(c16rtomb_iso_8859_1_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-1");
+
+ /* Unicode character 'Euro sign'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0x20ac, &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+}
+
+ATF_TC_WITHOUT_HEAD(c16rtomb_iso_8859_15_test);
+ATF_TC_BODY(c16rtomb_iso_8859_15_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-15");
+
+ /* Unicode character 'Euro sign'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0x20ac, &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0xa4 &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(c16rtomb_utf_8_test);
+ATF_TC_BODY(c16rtomb_utf_8_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.UTF-8");
+
+ /* Unicode character 'Pile of poo'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xd83d, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xdca9, &s)), 4, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0xf0 &&
+ (unsigned char)buf[1] == 0x9f &&
+ (unsigned char)buf[2] == 0x92 &&
+ (unsigned char)buf[3] == 0xa9 &&
+ (unsigned char)buf[4] == 0xcc),
+ "buf=[%02x %02x %02x %02x %02x]",
+ buf[0], buf[1], buf[2], buf[3], buf[4]);
+
+ /* Invalid code; 'Pile of poo' without the trail surrogate. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xd83d, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, L'A', &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+
+ /* Invalid code; 'Pile of poo' without the lead surrogate. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xdca9, &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+
+ /* Incomplete Unicode character 'Pile of poo', interrupted by NUL. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, 0xd83d, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c16rtomb(buf, L'\0', &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, c16rtomb_c_locale_test);
+ ATF_TP_ADD_TC(tp, c16rtomb_iso2022jp_locale_test);
+ ATF_TP_ADD_TC(tp, c16rtomb_iso_8859_1_test);
+ ATF_TP_ADD_TC(tp, c16rtomb_iso_8859_15_test);
+ ATF_TP_ADD_TC(tp, c16rtomb_utf_8_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/locale/t_c32rtomb.c b/lib/libc/locale/t_c32rtomb.c
new file mode 100644
index 000000000000..7233e14f7967
--- /dev/null
+++ b/lib/libc/locale/t_c32rtomb.c
@@ -0,0 +1,60 @@
+/* $NetBSD: t_c32rtomb.c,v 1.1 2024/08/15 14:16:34 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_c32rtomb.c,v 1.1 2024/08/15 14:16:34 riastradh Exp $");
+
+#include <atf-c.h>
+#include <locale.h>
+#include <uchar.h>
+
+#include "h_macros.h"
+
+ATF_TC(c32rtomb_null);
+ATF_TC_HEAD(c32rtomb_null, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test null string output to c32rtomb");
+}
+ATF_TC_BODY(c32rtomb_null, tc)
+{
+ char *locale;
+ mbstate_t ps = {0};
+ size_t n;
+
+ REQUIRE_LIBC((locale = setlocale(LC_ALL, "C")), NULL);
+ ATF_REQUIRE_EQ_MSG(strcmp(locale, "C"), 0, "locale=%s", locale);
+
+ ATF_CHECK_EQ_MSG((n = c32rtomb(NULL, L'x', &ps)), 1, "n=%zu", n);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, c32rtomb_null);
+ return atf_no_error();
+}
diff --git a/lib/libc/locale/t_c8rtomb.c b/lib/libc/locale/t_c8rtomb.c
new file mode 100644
index 000000000000..ce03e6ed1d20
--- /dev/null
+++ b/lib/libc/locale/t_c8rtomb.c
@@ -0,0 +1,355 @@
+/* $NetBSD: t_c8rtomb.c,v 1.7 2024/08/19 16:22:10 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins
+ * All rights reserved.
+ *
+ * Copyright (c) 2013 Ed Schouten <ed@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Test program for c8rtomb() as specified by C23.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_c8rtomb.c,v 1.7 2024/08/19 16:22:10 riastradh Exp $");
+
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <string.h>
+#include <uchar.h>
+
+#include <atf-c.h>
+
+static void
+require_lc_ctype(const char *locale_name)
+{
+ char *lc_ctype_set;
+
+ lc_ctype_set = setlocale(LC_CTYPE, locale_name);
+ if (lc_ctype_set == NULL)
+ atf_tc_fail("setlocale(LC_CTYPE, \"%s\") failed; errno=%d",
+ locale_name, errno);
+
+ ATF_REQUIRE_EQ_MSG(strcmp(lc_ctype_set, locale_name), 0,
+ "lc_ctype_set=%s locale_name=%s", lc_ctype_set, locale_name);
+}
+
+static mbstate_t s;
+static char buf[7*MB_LEN_MAX + 1];
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_c_locale_test);
+ATF_TC_BODY(c8rtomb_c_locale_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("C");
+
+ /*
+ * If the buffer argument is NULL, c8 is implicitly 0,
+ * c8rtomb() resets its internal state.
+ */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, '\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0x80, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xc0, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xe0, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xf0, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xf8, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xfc, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xfe, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xff, NULL)), 1, "n=%zu", n);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0, &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0 &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, '\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 'A', NULL)), 1, "n=%zu", n);
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 'A', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 'A' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ /* Unicode character 'Pile of poo'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x92, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xa9, &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+
+ /* Incomplete Unicode character 'Pile of poo', interrupted by NUL. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, '\0', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, '\0', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x92, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, '\0', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_iso2022jp_locale_test);
+ATF_TC_BODY(c8rtomb_iso2022jp_locale_test, tc)
+{
+ char *p;
+ size_t n;
+
+ require_lc_ctype("ja_JP.ISO-2022-JP");
+
+ /*
+ * If the buffer argument is NULL, c8 is implicitly 0,
+ * c8rtomb() resets its internal state.
+ */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, '\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0x80, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xc0, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xe0, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xf0, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xf8, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xfc, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xfe, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 0xff, NULL)), 1, "n=%zu", n);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0, &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0 &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, '\0', NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(NULL, 'A', NULL)), 1, "n=%zu", n);
+
+ /*
+ * 1. U+0042 LATIN CAPITAL LETTER A
+ * 2. U+00A5 YEN SIGN
+ * 3. U+00A5 YEN SIGN (again, no shift needed)
+ * 4. U+30A2 KATAKANA LETTER A
+ * 5. U+30A2 KATAKANA LETTER A (again, no shift needed)
+ * 6. incomplete UTF-8 multibyte sequence -- no output
+ * 7. U+0000 NUL (plus shift sequence to initial state)
+ */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ p = buf;
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 'A', &s)), 1, "n=%zu", n); /* 1 */
+ p += 1;
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xc2, &s)), 0, "n=%zu", n); /* 2 */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xa5, &s)), 4, "n=%zu", n);
+ p += 4;
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xc2, &s)), 0, "n=%zu", n); /* 3 */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xa5, &s)), 1, "n=%zu", n);
+ p += 1;
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xe3, &s)), 0, "n=%zu", n); /* 4 */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0x82, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xa2, &s)), 5, "n=%zu", n);
+ p += 5;
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xe3, &s)), 0, "n=%zu", n); /* 5 */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0x82, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xa2, &s)), 2, "n=%zu", n);
+ p += 2;
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0xe3, &s)), 0, "n=%zu", n); /* 6 */
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, 0x82, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(p, '\0', &s)), 4, "n=%zu", n); /* 7 */
+ p += 4;
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 'A' &&
+ (unsigned char)buf[1] == 0x1b && /* shift ISO/IEC 646:JP */
+ (unsigned char)buf[2] == '(' &&
+ (unsigned char)buf[3] == 'J' &&
+ (unsigned char)buf[4] == 0x5c && /* YEN SIGN */
+ (unsigned char)buf[5] == 0x5c && /* YEN SIGN */
+ (unsigned char)buf[6] == 0x1b && /* shift JIS X 0208 */
+ (unsigned char)buf[7] == '$' &&
+ (unsigned char)buf[8] == 'B' &&
+ (unsigned char)buf[9] == 0x25 && /* KATAKANA LETTER A */
+ (unsigned char)buf[10] == 0x22 &&
+ (unsigned char)buf[11] == 0x25 && /* KATAKANA LETTER A */
+ (unsigned char)buf[12] == 0x22 &&
+ (unsigned char)buf[13] == 0x1b && /* shift US-ASCII */
+ (unsigned char)buf[14] == '(' &&
+ (unsigned char)buf[15] == 'B' &&
+ (unsigned char)buf[16] == '\0' &&
+ (unsigned char)buf[17] == 0xcc),
+ "buf=[%02x %02x %02x %02x %02x %02x %02x %02x "
+ " %02x %02x %02x %02x %02x %02x %02x %02x "
+ " %02x %02x]",
+ buf[0], buf[1], buf[2], buf[3],
+ buf[4], buf[5], buf[6], buf[7],
+ buf[8], buf[9], buf[10], buf[11],
+ buf[12], buf[13], buf[14], buf[15],
+ buf[16], buf[17]);
+}
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_iso_8859_1_test);
+ATF_TC_BODY(c8rtomb_iso_8859_1_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-1");
+
+ /* Unicode character 'Euro sign'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xe2, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x82, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xac, &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+}
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_iso_8859_15_test);
+ATF_TC_BODY(c8rtomb_iso_8859_15_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-15");
+
+ /* Unicode character 'Euro sign'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xe2, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x82, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xac, &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0xa4 &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(c8rtomb_utf_8_test);
+ATF_TC_BODY(c8rtomb_utf_8_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.UTF-8");
+
+ /* Unicode character 'Pile of poo'. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x92, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xa9, &s)), 4, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == 0xf0 &&
+ (unsigned char)buf[1] == 0x9f &&
+ (unsigned char)buf[2] == 0x92 &&
+ (unsigned char)buf[3] == 0xa9 &&
+ (unsigned char)buf[4] == 0xcc),
+ "buf=[%02x %02x %02x %02x %02x]",
+ buf[0], buf[1], buf[2], buf[3], buf[4]);
+
+ /* Invalid code; 'Pile of poo' without the last byte. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x92, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 'A', &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+
+ /* Invalid code; 'Pile of poo' without the first byte. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), (size_t)-1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(errno, EILSEQ, "errno=%d", errno);
+ ATF_CHECK_EQ_MSG((unsigned char)buf[0], 0xcc, "buf=[%02x]", buf[0]);
+
+ /* Incomplete Unicode character 'Pile of poo', interrupted by NUL. */
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, '\0', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, '\0', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+
+ memset(&s, 0, sizeof(s));
+ memset(buf, 0xcc, sizeof(buf));
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0xf0, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x9f, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, 0x92, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = c8rtomb(buf, '\0', &s)), 1, "n=%zu", n);
+ ATF_CHECK_MSG(((unsigned char)buf[0] == '\0' &&
+ (unsigned char)buf[1] == 0xcc),
+ "buf=[%02x %02x]", buf[0], buf[1]);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, c8rtomb_c_locale_test);
+ ATF_TP_ADD_TC(tp, c8rtomb_iso2022jp_locale_test);
+ ATF_TP_ADD_TC(tp, c8rtomb_iso_8859_1_test);
+ ATF_TP_ADD_TC(tp, c8rtomb_iso_8859_15_test);
+ ATF_TP_ADD_TC(tp, c8rtomb_utf_8_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/locale/t_mbrtoc16.c b/lib/libc/locale/t_mbrtoc16.c
new file mode 100644
index 000000000000..cac804c84eed
--- /dev/null
+++ b/lib/libc/locale/t_mbrtoc16.c
@@ -0,0 +1,364 @@
+/* $NetBSD: t_mbrtoc16.c,v 1.3 2024/08/20 17:43:09 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins
+ * All rights reserved.
+ *
+ * Copyright (c) 2013 Ed Schouten <ed@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Test program for mbrtoc16() as specified by ISO/IEC 9899:2011.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_mbrtoc16.c,v 1.3 2024/08/20 17:43:09 riastradh Exp $");
+
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <string.h>
+#include <uchar.h>
+
+#include <atf-c.h>
+
+static void
+require_lc_ctype(const char *locale_name)
+{
+ char *lc_ctype_set;
+
+ lc_ctype_set = setlocale(LC_CTYPE, locale_name);
+ if (lc_ctype_set == NULL)
+ atf_tc_fail("setlocale(LC_CTYPE, \"%s\") failed; errno=%d",
+ locale_name, errno);
+
+ ATF_REQUIRE_EQ_MSG(strcmp(lc_ctype_set, locale_name), 0,
+ "lc_ctype_set=%s locale_name=%s", lc_ctype_set, locale_name);
+}
+
+static mbstate_t s;
+static char16_t c16;
+
+ATF_TC_WITHOUT_HEAD(mbrtoc16_c_locale_test);
+ATF_TC_BODY(mbrtoc16_c_locale_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("C");
+
+ /* Null wide character, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 1, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 1, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "A", 1, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%"PRIx16" L'A'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "A", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%"PRIx16" L'A'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+
+ /* Incomplete character sequence. */
+ c16 = L'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'z', "c16=U+%"PRIx16" L'z'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'z');
+
+ /* Check that mbrtoc16() doesn't access the buffer when n == 0. */
+ c16 = L'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'z', "c16=U+%"PRIx16" L'z'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'z');
+
+ /* Check that mbrtoc16() doesn't read ahead too aggressively. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "AB", 2, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%"PRIx16" L'A'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "C", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'C', "c16=U+%"PRIx16" L'C'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'C');
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc16_iso2022jp_locale_test);
+ATF_TC_BODY(mbrtoc16_iso2022jp_locale_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("ja_JP.ISO-2022-JP");
+
+ /* Null wide character, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 1, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 1, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "A", 1, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%04"PRIx16" L'A'=U+%04"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "A", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%04"PRIx16" L'A'=U+%04"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+
+ /* Incomplete character sequence. */
+ c16 = L'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'z', "c16=U+%04"PRIx16" L'z'=U+%04"PRIx16,
+ (uint16_t)c16, (uint16_t)L'z');
+
+ /* Check that mbrtoc16() doesn't access the buffer when n == 0. */
+ c16 = L'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'z', "c16=U+%04"PRIx16" L'z'=U+%04"PRIx16,
+ (uint16_t)c16, (uint16_t)L'z');
+
+ /* Check that mbrtoc16() doesn't read ahead too aggressively. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "AB", 2, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%04"PRIx16" L'A'=U+%04"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "C", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'C', "c16=U+%04"PRIx16" L'C'=U+%04"PRIx16,
+ (uint16_t)c16, (uint16_t)L'C');
+
+ /* Incomplete character sequence (shift sequence only). */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x1b(J", 3, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+
+ /* Same as above, but complete (U+00A5 YEN SIGN). */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x1b(J\x5c", 4, &s)), 4,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xa5, "c16=U+%04"PRIx16, (uint16_t)c16);
+
+ /* Test restarting behaviour. */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x1b(", 2, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "J\x5c", 2, &s)), 2, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xa5, "c16=U+%04"PRIx16, (uint16_t)c16);
+
+ /*
+ * Test shift sequence state in various increments:
+ * 1. U+0042 LATIN CAPITAL LETTER A
+ * 2. (shift ISO/IEC 646:JP) U+00A5 YEN SIGN
+ * 3. U+00A5 YEN SIGN
+ * 4. (shift JIS X 0208) U+30A2 KATAKANA LETTER A
+ * 5. U+30A2 KATAKANA LETTER A
+ * 6. (shift to initial state) U+0000 NUL
+ */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "A\x1b(J", 4, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%04"PRIx16, (uint16_t)c16);
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x1b(J", 3, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x5c\x5c", 2, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x00a5, "c16=U+%04"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x5c\x1b$", 3, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x00a5, "c16=U+%04"PRIx16, (uint16_t)c16);
+ c16 = 0x1234;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x1b", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x1234, "c16=U+%04"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "$B\x25\x22", 4, &s)), 4,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x30a2, "c16=U+%04"PRIx16, (uint16_t)c16);
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x25", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x22\x1b(B\x00", 5, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x30a2, "c16=U+%04"PRIx16, (uint16_t)c16);
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x1b(", 2, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+ c16 = 42;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "B\x00", 2, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%04"PRIx16, (uint16_t)c16);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc16_iso_8859_1_test);
+ATF_TC_BODY(mbrtoc16_iso_8859_1_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-1");
+
+ /* Currency sign. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xa4", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xa4, "c16=U+%"PRIx16, (uint16_t)c16);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc16_iso_8859_15_test);
+ATF_TC_BODY(mbrtoc16_iso_8859_15_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-15");
+
+ /* Euro sign. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xa4", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x20ac, "c16=U+%"PRIx16, (uint16_t)c16);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc16_utf_8_test);
+ATF_TC_BODY(mbrtoc16_utf_8_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.UTF-8");
+
+ /* Null wide character, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 1, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 1, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "A", 1, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%"PRIx16" L'A'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "A", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'A', "c16=U+%"PRIx16" L'A'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'A');
+
+ /* Incomplete character sequence (zero length). */
+ c16 = L'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, L'z', "c16=U+%"PRIx16" L'z'=U+%"PRIx16,
+ (uint16_t)c16, (uint16_t)L'z');
+
+ /* Incomplete character sequence (truncated double-byte). */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xc3", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+
+ /* Same as above, but complete. */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xc3\x84", 2, &s)), 2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xc4, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Test restarting behaviour. */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xc3", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0, "c16=U+%"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xb7", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xf7, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Surrogate pair. */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xf0\x9f\x92\xa9", 4, &s)), 4,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xd83d, "c16=U+%"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xdca9, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Letter e with acute, precomposed. */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xc3\xa9", 2, &s)), 2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0xe9, "c16=U+%"PRIx16, (uint16_t)c16);
+
+ /* Letter e with acute, combined. */
+ memset(&s, 0, sizeof(s));
+ c16 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\x65\xcc\x81", 3, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x65, "c16=U+%"PRIx16, (uint16_t)c16);
+ ATF_CHECK_EQ_MSG((n = mbrtoc16(&c16, "\xcc\x81", 2, &s)), 2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c16, 0x301, "c16=U+%"PRIx16, (uint16_t)c16);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, mbrtoc16_c_locale_test);
+ ATF_TP_ADD_TC(tp, mbrtoc16_iso2022jp_locale_test);
+ ATF_TP_ADD_TC(tp, mbrtoc16_iso_8859_1_test);
+ ATF_TP_ADD_TC(tp, mbrtoc16_iso_8859_15_test);
+ ATF_TP_ADD_TC(tp, mbrtoc16_utf_8_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/locale/t_mbrtoc32.c b/lib/libc/locale/t_mbrtoc32.c
new file mode 100644
index 000000000000..8ccc421400a8
--- /dev/null
+++ b/lib/libc/locale/t_mbrtoc32.c
@@ -0,0 +1,61 @@
+/* $NetBSD: t_mbrtoc32.c,v 1.1 2024/08/15 14:16:34 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_mbrtoc32.c,v 1.1 2024/08/15 14:16:34 riastradh Exp $");
+
+#include <atf-c.h>
+#include <locale.h>
+#include <uchar.h>
+
+#include "h_macros.h"
+
+ATF_TC(mbrtoc32_null);
+ATF_TC_HEAD(mbrtoc32_null, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test null string input to mbrtoc32");
+}
+ATF_TC_BODY(mbrtoc32_null, tc)
+{
+ char *locale;
+ char32_t c32;
+ mbstate_t ps = {0};
+ size_t n;
+
+ REQUIRE_LIBC((locale = setlocale(LC_ALL, "C")), NULL);
+ ATF_REQUIRE_EQ_MSG(strcmp(locale, "C"), 0, "locale=%s", locale);
+
+ ATF_CHECK_EQ_MSG((n = mbrtoc32(&c32, NULL, 0, &ps)), 0, "n=%zu", n);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, mbrtoc32_null);
+ return atf_no_error();
+}
diff --git a/lib/libc/locale/t_mbrtoc8.c b/lib/libc/locale/t_mbrtoc8.c
new file mode 100644
index 000000000000..760f19a62f08
--- /dev/null
+++ b/lib/libc/locale/t_mbrtoc8.c
@@ -0,0 +1,415 @@
+/* $NetBSD: t_mbrtoc8.c,v 1.3 2024/08/20 17:43:09 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2002 Tim J. Robbins
+ * All rights reserved.
+ *
+ * Copyright (c) 2013 Ed Schouten <ed@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * Test program for mbrtoc8() as specified by C23.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_mbrtoc8.c,v 1.3 2024/08/20 17:43:09 riastradh Exp $");
+
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <string.h>
+#include <uchar.h>
+
+#include <atf-c.h>
+
+static void
+require_lc_ctype(const char *locale_name)
+{
+ char *lc_ctype_set;
+
+ lc_ctype_set = setlocale(LC_CTYPE, locale_name);
+ if (lc_ctype_set == NULL)
+ atf_tc_fail("setlocale(LC_CTYPE, \"%s\") failed; errno=%d",
+ locale_name, errno);
+
+ ATF_REQUIRE_EQ_MSG(strcmp(lc_ctype_set, locale_name), 0,
+ "lc_ctype_set=%s locale_name=%s", lc_ctype_set, locale_name);
+}
+
+static mbstate_t s;
+static char8_t c8;
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_c_locale_test);
+ATF_TC_BODY(mbrtoc8_c_locale_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("C");
+
+ /* Null wide character, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 1, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 1, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "A", 1, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "A", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+
+ /* Incomplete character sequence. */
+ c8 = 'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'z', "c8=0x%"PRIx8" 'z'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'z');
+
+ /* Check that mbrtoc8() doesn't access the buffer when n == 0. */
+ c8 = 'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'z', "c8=0x%"PRIx8" 'z'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'z');
+
+ /* Check that mbrtoc8() doesn't read ahead too aggressively. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "AB", 2, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "C", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'C', "c8=0x%"PRIx8" 'C'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'C');
+
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_iso2022jp_locale_test);
+ATF_TC_BODY(mbrtoc8_iso2022jp_locale_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("ja_JP.ISO-2022-JP");
+
+ /* Null wide character, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 1, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 1, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "A", 1, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "A", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+
+ /* Incomplete character sequence. */
+ c8 = 'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'z', "c8=0x%"PRIx8" 'z'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'z');
+
+ /* Check that mbrtoc8() doesn't access the buffer when n == 0. */
+ c8 = 'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'z', "c8=0x%"PRIx8" 'z'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'z');
+
+ /* Check that mbrtoc8() doesn't read ahead too aggressively. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "AB", 2, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "C", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'C', "c8=0x%"PRIx8" 'C'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'C');
+
+ /* Incomplete character sequence (shift sequence only). */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b(J", 3, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Same as above, but complete (U+00A5 YEN SIGN). */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b(J\x5c", 4, &s)), 4,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc2, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa5, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Test restarting behaviour. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b(", 2, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "J\x5c", 2, &s)), 2, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc2, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa5, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /*
+ * Test shift sequence state in various increments:
+ * 1. U+0042 LATIN CAPITAL LETTER A
+ * 2. (shift ISO/IEC 646:JP) U+00A5 YEN SIGN
+ * 3. U+00A5 YEN SIGN
+ * 4. (shift JIS X 0208) U+30A2 KATAKANA LETTER A
+ * 5. U+30A2 KATAKANA LETTER A
+ * 6. (shift to initial state) U+0000 NUL
+ */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "A\x1b(J", 4, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8, (uint8_t)c8);
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b(J", 3, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x5c\x5c", 2, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc2, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x5c\x1b$", 3, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa5, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x5c\x1b$", 3, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc2, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b", 1, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa5, "c8=0x%"PRIx8, (uint8_t)c8);
+ c8 = 0xff;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xff, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "$B\x25\x22", 4, &s)), 4,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xe3, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x25", 1, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x82, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x25", 1, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa2, "c8=0x%"PRIx8, (uint8_t)c8);
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x25", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x22\x1b(B\x00", 5, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xe3, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b(", 2, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x82, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b(", 2, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa2, "c8=0x%"PRIx8, (uint8_t)c8);
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x1b(", 2, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+ c8 = 42;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "B\x00", 2, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_iso_8859_1_test);
+ATF_TC_BODY(mbrtoc8_iso_8859_1_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-1");
+
+ /* Currency sign. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xa4", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc2, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa4, "c8=0x%"PRIx8, (uint8_t)c8);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_iso_8859_15_test);
+ATF_TC_BODY(mbrtoc8_iso_8859_15_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.ISO8859-15");
+
+ /* Euro sign. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xa4", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xe2, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x82, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xac, "c8=0x%"PRIx8, (uint8_t)c8);
+}
+
+ATF_TC_WITHOUT_HEAD(mbrtoc8_utf_8_test);
+ATF_TC_BODY(mbrtoc8_utf_8_test, tc)
+{
+ size_t n;
+
+ require_lc_ctype("en_US.UTF-8");
+
+ /* Null wide character, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 1, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Null wide character. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 1, &s)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Latin letter A, internal state. */
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(NULL, 0, 0, NULL)), 0, "n=%zu", n);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "A", 1, NULL)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+
+ /* Latin letter A. */
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "A", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'A', "c8=0x%"PRIx8" 'A'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'A');
+
+ /* Incomplete character sequence (zero length). */
+ c8 = 'z';
+ memset(&s, 0, sizeof(s));
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 'z', "c8=0x%"PRIx8" 'z'=0x%"PRIx8,
+ (uint8_t)c8, (uint8_t)'z');
+
+ /* Incomplete character sequence (truncated double-byte). */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xc3", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+
+ /* Same as above, but complete. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xc3\x84", 2, &s)), 2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc3, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x84, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Test restarting behaviour. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xc3", 1, &s)), (size_t)-2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xb7", 1, &s)), 1, "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc3, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xb7, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Four-byte sequence. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xf0\x9f\x92\xa9", 4, &s)), 4,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xf0, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x9f, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x92, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa9, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Letter e with acute, precomposed. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xc3\xa9", 2, &s)), 2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xc3, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xa9, "c8=0x%"PRIx8, (uint8_t)c8);
+
+ /* Letter e with acute, combined. */
+ memset(&s, 0, sizeof(s));
+ c8 = 0;
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\x65\xcc\x81", 3, &s)), 1,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x65, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "\xcc\x81", 2, &s)), 2,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0xcc, "c8=0x%"PRIx8, (uint8_t)c8);
+ ATF_CHECK_EQ_MSG((n = mbrtoc8(&c8, "", 0, &s)), (size_t)-3,
+ "n=%zu", n);
+ ATF_CHECK_EQ_MSG(c8, 0x81, "c8=0x%"PRIx8, (uint8_t)c8);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, mbrtoc8_c_locale_test);
+ ATF_TP_ADD_TC(tp, mbrtoc8_iso2022jp_locale_test);
+ ATF_TP_ADD_TC(tp, mbrtoc8_iso_8859_1_test);
+ ATF_TP_ADD_TC(tp, mbrtoc8_iso_8859_15_test);
+ ATF_TP_ADD_TC(tp, mbrtoc8_utf_8_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/locale/t_uchar.c b/lib/libc/locale/t_uchar.c
new file mode 100644
index 000000000000..66e4830fb88a
--- /dev/null
+++ b/lib/libc/locale/t_uchar.c
@@ -0,0 +1,81 @@
+/* $NetBSD: t_uchar.c,v 1.3 2024/10/14 06:02:14 rillig Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Include <uchar.h> first to verify it declares everything we need.
+ */
+#include <uchar.h>
+typedef mbstate_t nbtest_mbstate_t;
+typedef size_t nbtest_size_t;
+typedef char8_t nbtest_char8_t;
+typedef char16_t nbtest_char16_t;
+typedef char32_t nbtest_char32_t;
+static size_t (*nbtest_mbrtoc8)(char8_t *restrict, const char *restrict,
+ size_t, mbstate_t *restrict) __unused = &mbrtoc8;
+static size_t (*nbtest_c8rtomb)(char *restrict, char8_t,
+ mbstate_t *restrict) __unused = &c8rtomb;
+static size_t (*nbtest_mbrtoc16)(char16_t *restrict, const char *restrict,
+ size_t, mbstate_t *restrict) __unused = &mbrtoc16;
+static size_t (*nbtest_c16rtomb)(char *restrict, char16_t,
+ mbstate_t *restrict) __unused = &c16rtomb;
+static size_t (*nbtest_mbrtoc32)(char32_t *restrict, const char *restrict,
+ size_t, mbstate_t *restrict) __unused = mbrtoc32;
+static size_t (*nbtest_c32rtomb)(char *restrict, char32_t,
+ mbstate_t *restrict) __unused = &c32rtomb;
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_uchar.c,v 1.3 2024/10/14 06:02:14 rillig Exp $");
+
+#include <atf-c.h>
+#include <stdint.h>
+
+ATF_TC(uchartypes);
+ATF_TC_HEAD(uchartypes, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test <uchar.h> types are reasonable");
+}
+ATF_TC_BODY(uchartypes, tc)
+{
+
+ ATF_CHECK_EQ_MSG(sizeof(char8_t), sizeof(unsigned char),
+ "char8_t %zu, unsigned char %zu",
+ sizeof(char8_t), sizeof(unsigned char));
+ ATF_CHECK_EQ_MSG(sizeof(char16_t), sizeof(uint_least16_t),
+ "char16_t %zu, uint_least16_t %zu",
+ sizeof(char16_t), sizeof(uint_least16_t));
+ ATF_CHECK_EQ_MSG(sizeof(char32_t), sizeof(uint_least32_t),
+ "char32_t %zu, uint_least32_t %zu",
+ sizeof(char32_t), sizeof(uint_least32_t));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, uchartypes);
+ return atf_no_error();
+}
diff --git a/lib/libc/misc/t_vis.c b/lib/libc/misc/t_vis.c
new file mode 100644
index 000000000000..a712ffa2f9d2
--- /dev/null
+++ b/lib/libc/misc/t_vis.c
@@ -0,0 +1,165 @@
+/* $NetBSD: t_vis.c,v 1.2 2025/11/16 12:43:25 nia Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+#include <string.h>
+#include <stdio.h>
+#include "vis_types.h"
+#include "vis_proto.h"
+
+ATF_TC(vis_test_addsub);
+
+ATF_TC_HEAD(vis_test_addsub, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test 32-bit packed add/subtract");
+}
+
+ATF_TC_BODY(vis_test_addsub, tc)
+{
+ vis_d64 v1, v2, v3;
+ vis_f32 f1, f2;
+ vis_u32 u1, u2;
+
+ v1 = vis_to_double(8, 16);
+ v2 = vis_to_double(16, 8);
+
+ v3 = vis_fpadd32(v1, v2);
+
+ f1 = vis_read_lo(v3);
+ memcpy(&u1, &f1, sizeof(f1));
+ f2 = vis_read_hi(v3);
+ memcpy(&u2, &f2, sizeof(f2));
+
+ ATF_REQUIRE(u1 == 24 && u2 == 24);
+
+ v2 = vis_to_double(4, 4);
+ v3 = vis_fpsub32(v3, v2);
+
+ f1 = vis_read_lo(v3);
+ memcpy(&u1, &f1, sizeof(f1));
+ f2 = vis_read_hi(v3);
+ memcpy(&u2, &f2, sizeof(f2));
+
+ ATF_REQUIRE(u1 == 20 && u2 == 20);
+}
+
+ATF_TC(vis_test_bitwise);
+
+ATF_TC_HEAD(vis_test_bitwise, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test 32-bit packed bitwise");
+}
+
+ATF_TC_BODY(vis_test_bitwise, tc)
+{
+ static vis_u8 testbytes1[8] = { 1, 0, 1, 1, 1, 0, 1, 1 };
+ static vis_u8 testbytes2[8] = { 1, 1, 0, 1, 1, 1, 0, 1 };
+ static vis_u8 test_and[8] = { 1, 0, 0, 1, 1, 0, 0, 1 };
+ static vis_u8 test_or[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
+ static vis_u8 test_zero[8] = { 0 };
+ static vis_u64 test_ones = 0xffffffffffffffff;
+ double v1, v2, v3;
+
+ memcpy(&v1, testbytes1, sizeof(v1));
+ memcpy(&v2, testbytes2, sizeof(v2));
+
+ v3 = vis_fand(v1, v2);
+
+ ATF_REQUIRE(memcmp(&v3, test_and, sizeof(v3)) == 0);
+
+ v3 = vis_fone();
+
+ ATF_REQUIRE(memcmp(&v3, &test_ones, sizeof(v3)) == 0);
+
+ v3 = vis_for(v1, v2);
+
+ ATF_REQUIRE(memcmp(&v3, test_or, sizeof(v3)) == 0);
+
+ v3 = vis_fzero();
+
+ ATF_REQUIRE(memcmp(&v3, test_zero, sizeof(v3)) == 0);
+}
+
+ATF_TC(vis_test_fcmpeq16);
+
+ATF_TC_HEAD(vis_test_fcmpeq16, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test 16-bit packed compare");
+}
+
+ATF_TC_BODY(vis_test_fcmpeq16, tc)
+{
+ static vis_u16 testshort1[4] = { 16000, 16000, 16000, 16000 };
+ static vis_u16 testshort2[4] = { 32000, 16000, 32000, 16000 };
+ static vis_u16 testshort3[4] = { 48000, 48000, 48000, 48000 };
+ vis_d64 v1, v2, v3;
+
+ memcpy(&v1, testshort1, sizeof(v1));
+ memcpy(&v2, testshort2, sizeof(v2));
+ memcpy(&v3, testshort3, sizeof(v3));
+
+ ATF_REQUIRE((!!vis_fcmpeq16(v1, v2)) != 0);
+ ATF_REQUIRE((!!vis_fcmpeq16(v1, v3)) == 0);
+ ATF_REQUIRE((!!vis_fcmpne16(v1, v3)) != 0);
+}
+
+ATF_TC(vis_test_fcmpeq32);
+
+ATF_TC_HEAD(vis_test_fcmpeq32, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test 32-bit packed compare");
+}
+
+ATF_TC_BODY(vis_test_fcmpeq32, tc)
+{
+ static vis_u32 testlong1[2] = { 16000, 16000 };
+ static vis_u32 testlong2[2] = { 32000, 16000 };
+ static vis_u32 testlong3[2] = { 48000, 48000 };
+ vis_d64 v1, v2, v3;
+
+ memcpy(&v1, testlong1, sizeof(v1));
+ memcpy(&v2, testlong2, sizeof(v2));
+ memcpy(&v3, testlong3, sizeof(v3));
+
+ ATF_REQUIRE((!!vis_fcmpeq32(v1, v2)) != 0);
+ ATF_REQUIRE((!!vis_fcmpeq32(v1, v3)) == 0);
+ ATF_REQUIRE((!!vis_fcmpne32(v1, v3)) != 0);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, vis_test_addsub);
+ ATF_TP_ADD_TC(tp, vis_test_bitwise);
+ ATF_TP_ADD_TC(tp, vis_test_fcmpeq16);
+ ATF_TP_ADD_TC(tp, vis_test_fcmpeq32);
+
+ return atf_no_error();
+}
diff --git a/lib/libc/regex/t_regex_binary.c b/lib/libc/regex/t_regex_binary.c
new file mode 100644
index 000000000000..0c3d8cb9b908
--- /dev/null
+++ b/lib/libc/regex/t_regex_binary.c
@@ -0,0 +1,80 @@
+/* $NetBSD: t_regex_binary.c,v 1.1 2025/01/01 18:13:48 christos Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_regex_binary.c,v 1.1 2025/01/01 18:13:48 christos Exp $");
+
+#include <atf-c.h>
+#include <regex.h>
+
+ATF_TC(negative_ranges);
+ATF_TC_HEAD(negative_ranges, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test negative ranges compilation");
+}
+ATF_TC_BODY(negative_ranges, tc)
+{
+ regex_t re;
+ char msg[1024];
+ int e;
+
+ if ((e = regcomp(&re, "[\xe0-\xf1][\xa0-\xd1].*", REG_EXTENDED)) != 0) {
+ regerror(e, &re, msg, sizeof(msg));
+ ATF_REQUIRE_MSG(0, "regcomp failed %s", msg);
+ }
+}
+
+ATF_TC(negative_char);
+ATF_TC_HEAD(negative_char, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test negative char in braces compilation");
+}
+ATF_TC_BODY(negative_char, tc)
+{
+ regex_t re;
+ char msg[1024];
+ int e;
+
+ /* PR/58910 */
+ if ((e = regcomp(&re, ": j:[]j:[]j:[\xd9j:[]", REG_EXTENDED)) != 0) {
+ regerror(e, &re, msg, sizeof(msg));
+ ATF_REQUIRE_MSG(0, "regcomp failed %s", msg);
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, negative_ranges);
+ ATF_TP_ADD_TC(tp, negative_char);
+ return atf_no_error();
+}
diff --git a/lib/libc/setjmp/t_sigstack.c b/lib/libc/setjmp/t_sigstack.c
new file mode 100644
index 000000000000..ea827da9545c
--- /dev/null
+++ b/lib/libc/setjmp/t_sigstack.c
@@ -0,0 +1,389 @@
+/* $NetBSD: t_sigstack.c,v 1.25 2025/05/12 14:46:19 christos Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_sigstack.c,v 1.25 2025/05/12 14:46:19 christos Exp $");
+
+#include <dlfcn.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <ucontext.h>
+
+#include "h_macros.h"
+
+struct sigaltstack ss[3];
+jmp_buf jmp;
+sigjmp_buf sigjmp;
+unsigned nentries;
+const char *bailname;
+void (*bailfn)(void) __dead;
+
+/*
+ * Optional compat13 functions from when sigcontext was expanded.
+ * Fortunately the only change visible to the caller is that the size
+ * of jmp_buf increased, so we can always use the old symbols with new
+ * jmp_buf arrays.
+ */
+int (*compat13_sigsetjmp)(sigjmp_buf, int);
+void (*compat13_siglongjmp)(sigjmp_buf, int) __dead;
+int (*compat13_setjmp)(jmp_buf);
+void (*compat13_longjmp)(jmp_buf, int) __dead;
+
+/*
+ * compatsigsys(signo)
+ *
+ * Signal handler for SIGSYS in case compat_13_sigreturn13 is not
+ * implemented by the kernel -- we will just skip the test in that
+ * case.
+ */
+static void
+compatsigsys(int signo)
+{
+
+ atf_tc_skip("no compat syscalls to test");
+}
+
+static void
+compatsetup(void)
+{
+
+ /*
+ * Grab the libc library symbols if available.
+ */
+ if ((compat13_sigsetjmp = dlsym(RTLD_SELF, "sigsetjmp")) == NULL ||
+ (compat13_siglongjmp = dlsym(RTLD_SELF, "siglongjmp")) == NULL ||
+ (compat13_setjmp = dlsym(RTLD_SELF, "setjmp")) == NULL ||
+ (compat13_longjmp = dlsym(RTLD_SELF, "longjmp")) == NULL)
+ atf_tc_skip("no compat functions to test");
+
+ /*
+ * Arrange for SIGSYS to skip the test -- this happens if the
+ * libc stub has the function, but the kernel isn't built with
+ * support for the compat13 sigreturn syscall for longjmp.
+ */
+ REQUIRE_LIBC(signal(SIGSYS, &compatsigsys), SIG_ERR);
+}
+
+static void
+on_sigusr1(int signo, siginfo_t *si, void *ctx)
+{
+ ucontext_t *uc = ctx;
+ void *sp = (void *)(uintptr_t)_UC_MACHINE_SP(uc);
+ void *fp = __builtin_frame_address(0);
+ struct sigaltstack *ssp;
+
+ /*
+ * Ensure we haven't re-entered the signal handler too many
+ * times. We should enter only twice.
+ */
+ ATF_REQUIRE_MSG(nentries < 2,
+ "%u recursive signal handler entries is too many in this test",
+ nentries + 1);
+
+ /*
+ * Ensure that the signal handler was called in the alternate
+ * signal stack.
+ */
+ ssp = &ss[nentries];
+ ATF_REQUIRE_MSG((fp >= ssp->ss_sp &&
+ fp < (void *)((char *)ssp->ss_sp + ssp->ss_size)),
+ "sigaltstack failed to take effect on entry %u --"
+ " signal handler's frame pointer %p doesn't lie in sigaltstack"
+ " [%p, %p), size 0x%zx",
+ nentries,
+ fp, ssp->ss_sp, (char *)ssp->ss_sp + ssp->ss_size, ssp->ss_size);
+
+ /*
+ * Ensure that if we enter the signal handler, we are entering
+ * it from the original stack, not from any of the alternate
+ * signal stacks.
+ */
+ for (ssp = &ss[0]; ssp < &ss[__arraycount(ss)]; ssp++) {
+ ATF_REQUIRE_MSG((sp < ssp->ss_sp ||
+ sp >= (void *)((char *)ssp->ss_sp + ssp->ss_size)),
+ "%s failed to restore stack"
+ " before allowing signal on entry %u --"
+ " interrupted stack pointer %p lies in sigaltstack %zd"
+ " [%p, %p), size 0x%zx",
+ bailname,
+ nentries,
+ sp, ssp - ss,
+ ssp->ss_sp, (char *)ssp->ss_sp + ssp->ss_size,
+ ssp->ss_size);
+ }
+
+ /*
+ * First time through, we want to test whether longjmp restores
+ * the signal mask first, or restores the stack pointer first.
+ * The signal should be blocked at this point, so we re-raise
+ * the signal to queue it up for delivery as soon as it is
+ * unmasked -- which should wait until the stack pointer has
+ * been restored in longjmp.
+ */
+ if (nentries++ == 0)
+ RL(raise(SIGUSR1));
+
+ /*
+ * Set up the next sigaltstack. We can't reuse the current one
+ * for the next signal handler re-entry until the system clears
+ * the SS_ONSTACK process state -- which normal return from
+ * signal handler does, but which longjmp does not. So to keep
+ * it simple (ha), we just use another sigaltstack.
+ */
+ RL(sigaltstack(&ss[nentries], NULL));
+
+ /*
+ * Jump back to the original context.
+ */
+ (*bailfn)();
+}
+
+static void
+go(const char *name, void (*fn)(void) __dead)
+{
+ struct sigaction sa;
+ unsigned i;
+
+ bailname = name;
+ bailfn = fn;
+
+ /*
+ * Allocate a stack for the signal handler to run in, and
+ * configure the system to use the first one.
+ *
+ * XXX Should maybe use a guard page but this is simpler.
+ */
+ for (i = 0; i < __arraycount(ss); i++) {
+ ss[i].ss_size = SIGSTKSZ;
+ REQUIRE_LIBC(ss[i].ss_sp = malloc(ss[i].ss_size), NULL);
+ }
+ RL(sigaltstack(&ss[0], NULL));
+
+ /*
+ * Set up a test signal handler for SIGUSR1. Allow all
+ * signals, except SIGUSR1 (which is masked by default) -- that
+ * way we don't inadvertently obscure weird crashes in the
+ * signal handler.
+ *
+ * Set SA_SIGINFO so the system will pass siginfo -- and, more
+ * to the point, ucontext, so the signal handler can determine
+ * the stack pointer of the logic it interrupted.
+ *
+ * Set SA_ONSTACK so the system will use the alternate signal
+ * stack to call the signal handler -- that way, it can tell
+ * whether the stack was restored before the second time
+ * around.
+ */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = &on_sigusr1;
+ RL(sigemptyset(&sa.sa_mask));
+ sa.sa_flags = SA_SIGINFO|SA_ONSTACK;
+ RL(sigaction(SIGUSR1, &sa, NULL));
+
+ /*
+ * Raise the signal to enter the signal handler the first time.
+ */
+ RL(raise(SIGUSR1));
+
+ /*
+ * If we ever reach this point, something went seriously wrong.
+ */
+ atf_tc_fail("unreachable");
+}
+
+static void __dead
+bail_longjmp(void)
+{
+
+ longjmp(jmp, 1);
+}
+
+ATF_TC(setjmp);
+ATF_TC_HEAD(setjmp, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test longjmp restores stack first, then signal mask");
+}
+ATF_TC_BODY(setjmp, tc)
+{
+
+#if defined __ia64__
+ atf_tc_expect_fail("PR lib/57946:"
+ " longjmp fails to restore stack first before"
+ " restoring signal mask on most architectures");
+#endif
+
+ /*
+ * Set up a return point for the signal handler: when the
+ * signal handler does longjmp(jmp, 1), it comes flying out of
+ * here.
+ */
+ if (setjmp(jmp) == 1)
+ return;
+
+ /*
+ * Run the test with longjmp.
+ */
+ go("longjmp", &bail_longjmp);
+}
+
+static void __dead
+bail_compat13_longjmp(void)
+{
+
+ (*compat13_longjmp)(jmp, 1);
+}
+
+ATF_TC(compat13_setjmp);
+ATF_TC_HEAD(compat13_setjmp, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test compat13 longjmp restores stack first, then signal mask");
+}
+ATF_TC_BODY(compat13_setjmp, tc)
+{
+
+ compatsetup();
+
+#if defined __arm__ || defined __i386__ || defined __sh3__
+#ifndef __arm__ /* will be exposed once PR 59351 is fixed */
+ atf_tc_expect_fail("PR lib/57946:"
+ " longjmp fails to restore stack first before"
+ " restoring signal mask on most architectures");
+#endif
+#endif
+#ifdef __arm__
+ atf_tc_expect_signal(-1, "PR port-arm/59351: compat_setjmp is busted");
+#endif
+
+ /*
+ * Set up a return point for the signal handler: when the
+ * signal handler does (*compat13_longjmp)(jmp, 1), it comes
+ * flying out of here.
+ */
+ if ((*compat13_setjmp)(jmp) == 1)
+ return;
+
+ /*
+ * Run the test with compat13_longjmp.
+ */
+ go("longjmp", &bail_compat13_longjmp);
+}
+
+static void __dead
+bail_siglongjmp(void)
+{
+
+ siglongjmp(sigjmp, 1);
+}
+
+ATF_TC(sigsetjmp);
+ATF_TC_HEAD(sigsetjmp, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test siglongjmp restores stack first, then signal mask");
+}
+ATF_TC_BODY(sigsetjmp, tc)
+{
+
+#if defined __ia64__
+ atf_tc_expect_fail("PR lib/57946:"
+ " longjmp fails to restore stack first before"
+ " restoring signal mask on most architectures");
+#endif
+
+ /*
+ * Set up a return point for the signal handler: when the
+ * signal handler does siglongjmp(sigjmp, 1), it comes flying
+ * out of here.
+ */
+ if (sigsetjmp(sigjmp, /*savesigmask*/1) == 1)
+ return;
+
+ /*
+ * Run the test with siglongjmp.
+ */
+ go("siglongjmp", &bail_siglongjmp);
+}
+
+static void __dead
+bail_compat13_siglongjmp(void)
+{
+
+ (*compat13_siglongjmp)(sigjmp, 1);
+}
+
+ATF_TC(compat13_sigsetjmp);
+ATF_TC_HEAD(compat13_sigsetjmp, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test compat13 siglongjmp restores stack first,"
+ " then signal mask");
+}
+ATF_TC_BODY(compat13_sigsetjmp, tc)
+{
+
+ compatsetup();
+
+#if defined __arm__ || defined __i386__ || defined __sh3__
+#ifndef __arm__ /* will be exposed once PR 59351 is fixed */
+ atf_tc_expect_fail("PR lib/57946:"
+ " longjmp fails to restore stack first before"
+ " restoring signal mask on most architectures");
+#endif
+#endif
+#ifdef __arm__
+ atf_tc_expect_signal(-1, "PR port-arm/59351: compat_setjmp is busted");
+#endif
+
+ /*
+ * Set up a return point for the signal handler: when the
+ * signal handler does (*compat13_siglongjmp)(sigjmp, 1), it
+ * comes flying out of here.
+ */
+ if ((*compat13_sigsetjmp)(sigjmp, /*savesigmask*/1) == 1)
+ return;
+
+ /*
+ * Run the test with compat13_siglongjmp.
+ */
+ go("siglongjmp", &bail_compat13_siglongjmp);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, compat13_setjmp);
+ ATF_TP_ADD_TC(tp, compat13_sigsetjmp);
+ ATF_TP_ADD_TC(tp, setjmp);
+ ATF_TP_ADD_TC(tp, sigsetjmp);
+
+ return atf_no_error();
+}
diff --git a/lib/libc/ssp/h_getcwd2.c b/lib/libc/ssp/h_getcwd2.c
new file mode 100644
index 000000000000..86cfd09f2bb0
--- /dev/null
+++ b/lib/libc/ssp/h_getcwd2.c
@@ -0,0 +1,18 @@
+#include <err.h>
+#include <errno.h>
+#include <unistd.h>
+
+char*
+getcwd(char* buf, size_t buflen)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+int
+main(void)
+{
+ char buf[256];
+ if (getcwd(buf, sizeof buf) == NULL)
+ err(1, "getcwd failed");
+ return 0;
+}
diff --git a/lib/libc/stdlib/h_sort.c b/lib/libc/stdlib/h_sort.c
new file mode 100644
index 000000000000..1e5f4b3b0798
--- /dev/null
+++ b/lib/libc/stdlib/h_sort.c
@@ -0,0 +1,225 @@
+/* $NetBSD: h_sort.c,v 1.3 2025/03/02 23:11:19 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: h_sort.c,v 1.3 2025/03/02 23:11:19 riastradh Exp $");
+
+#include <assert.h>
+#include <err.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static void
+heapsort_r_wrapper(void *a, size_t n, size_t sz,
+ int (*cmp)(const void *, const void *, void *), void *cookie)
+{
+
+ if (heapsort_r(a, n, sz, cmp, cookie) == -1)
+ err(1, "heapsort_r");
+}
+
+static void
+mergesort_r_wrapper(void *a, size_t n, size_t sz,
+ int (*cmp)(const void *, const void *, void *), void *cookie)
+{
+
+ if (mergesort_r(a, n, sz, cmp, cookie) == -1)
+ err(1, "mergesort_r");
+}
+
+struct context {
+ const char *buf;
+ const size_t *linepos;
+};
+
+static int
+cmp(const void *va, const void *vb, void *cookie)
+{
+ const struct context *C = cookie;
+ const size_t *a = va;
+ const size_t *b = vb;
+
+ return strcmp(C->buf + C->linepos[*a], C->buf + C->linepos[*b]);
+}
+
+static void __dead
+usage(void)
+{
+
+ fprintf(stderr, "Usage: %s [-n] <sortfn>\n", getprogname());
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ int nflag = 0;
+ void (*sortfn)(void *, size_t, size_t,
+ int (*)(const void *, const void *, void *), void *);
+ char *buf = NULL;
+ size_t nbuf;
+ size_t *linepos = NULL;
+ size_t nlines;
+ size_t *permutation = NULL;
+ size_t off;
+ ssize_t nread;
+ char *p;
+ size_t i;
+ int error;
+
+ /*
+ * Parse arguments.
+ */
+ setprogname(argv[0]);
+ while ((ch = getopt(argc, argv, "hn")) != -1) {
+ switch (ch) {
+ case 'n':
+ nflag = 1;
+ break;
+ case '?':
+ case 'h':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+ if (strcmp(argv[0], "heapsort_r") == 0)
+ sortfn = &heapsort_r_wrapper;
+ else if (strcmp(argv[0], "mergesort_r") == 0)
+ sortfn = &mergesort_r_wrapper;
+ else if (strcmp(argv[0], "qsort_r") == 0)
+ sortfn = &qsort_r;
+ else
+ errx(1, "unknown sort: %s", argv[0]);
+
+ /*
+ * Allocate an initial 4K buffer.
+ */
+ nbuf = 0x1000;
+ error = reallocarr(&buf, nbuf, 1);
+ if (error)
+ errc(1, error, "reallocarr");
+
+ /*
+ * Read the input into a contiguous buffer. Reject input with
+ * embedded NULs so we can use strcmp(3) to compare lines.
+ */
+ off = 0;
+ while ((nread = read(STDIN_FILENO, buf + off, nbuf - off - 1)) != 0) {
+ if (nread == -1)
+ err(1, "read");
+ if ((size_t)nread > nbuf - off - 1)
+ errx(1, "overlong read: %zu", (size_t)nread);
+ if (memchr(buf + off, '\0', (size_t)nread) != NULL)
+ errx(1, "NUL byte in input");
+ off += (size_t)nread;
+
+ /*
+ * If we filled the buffer, reallocate it with double
+ * the size. Bail if that would overflow.
+ */
+ if (nbuf - off == 1) {
+ if (nbuf > SIZE_MAX/2)
+ errx(1, "input overflow");
+ nbuf *= 2;
+ error = reallocarr(&buf, nbuf, 1);
+ if (error)
+ errc(1, error, "reallocarr");
+ }
+ }
+
+ /*
+ * If the input was empty, nothing to do.
+ */
+ if (off == 0)
+ return 0;
+
+ /*
+ * NUL-terminate the input and count the lines. The last line
+ * may have no trailing \n.
+ */
+ buf[off] = '\0';
+ nlines = 1;
+ for (p = buf; (p = strchr(p, '\n')) != NULL;) {
+ if (*++p == '\0')
+ break;
+ nlines++;
+ }
+
+ /*
+ * Create an array of line positions to sort. NUL-terminate
+ * each line so we can use strcmp(3).
+ */
+ error = reallocarr(&linepos, nlines, sizeof(linepos[0]));
+ if (error)
+ errc(1, error, "reallocarr");
+ i = 0;
+ for (p = buf; linepos[i++] = p - buf, (p = strchr(p, '\n')) != NULL;) {
+ *p = '\0';
+ if (*++p == '\0')
+ break;
+ }
+ assert(i == nlines);
+
+ /*
+ * Create an array of permuted line numbers.
+ */
+ error = reallocarr(&permutation, nlines, sizeof(permutation[0]));
+ if (error)
+ errc(1, error, "reallocarr");
+ for (i = 0; i < nlines; i++)
+ permutation[i] = i;
+
+ /*
+ * Sort the lines via comparison function that consults the
+ * buffer as a cookie.
+ */
+ (*sortfn)(permutation, nlines, sizeof(permutation[0]), &cmp,
+ &(struct context) { .buf = buf, .linepos = linepos });
+
+ /*
+ * Print the lines in sorted order with the original line
+ * numbers.
+ */
+ for (i = 0; i < nlines; i++) {
+ const size_t j = permutation[i];
+ if (nflag)
+ printf("%zu %s\n", j, buf + linepos[j]);
+ else
+ printf("%s\n", buf + linepos[j]);
+ }
+ fflush(stdout);
+ return ferror(stdout);
+}
diff --git a/lib/libc/stdlib/t_sort.sh b/lib/libc/stdlib/t_sort.sh
new file mode 100644
index 000000000000..8987ac1ec623
--- /dev/null
+++ b/lib/libc/stdlib/t_sort.sh
@@ -0,0 +1,115 @@
+# $NetBSD: t_sort.sh,v 1.2 2025/03/02 20:00:32 riastradh Exp $
+#
+# Copyright (c) 2025 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+check_sort()
+{
+ local sortfn
+
+ set -eu
+
+ sortfn="$1"
+
+ printf 'foo\nbar\nbaz\nquux' >in1
+ printf '1 bar\n2 baz\n0 foo\n3 quux\n' >out1
+ atf_check -s exit:0 -o file:out1 \
+ "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in1
+
+ atf_check -s exit:0 -o empty sh -c 'exec shuffle -f - <in1 >in2'
+ printf 'bar\nbaz\nfoo\nquux\n' >out2
+ atf_check -s exit:0 -o file:out2 \
+ "$(atf_get_srcdir)"/h_sort "$sortfn" <in2
+}
+
+check_stablesort()
+{
+ local sortfn
+
+ set -eu
+
+ sortfn="$1"
+
+ printf 'foo\nfoo\nfoo\nfoo\nfoo' >in1
+ printf '0 foo\n1 foo\n2 foo\n3 foo\n4 foo\n' >out1
+ atf_check -s exit:0 -o file:out1 \
+ "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in1
+
+ printf 'foo\nfoo\nfoo\nfoo\nfoo\nbar\nbar\nbar\nbar\nbar' >in2
+ printf '5 bar\n6 bar\n7 bar\n8 bar\n9 bar\n' >out2
+ printf '0 foo\n1 foo\n2 foo\n3 foo\n4 foo\n' >>out2
+ atf_check -s exit:0 -o file:out2 \
+ "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in2
+
+ printf 'foo\nfoo\nbar\nbaz\nquux' >in3
+ printf '2 bar\n3 baz\n0 foo\n1 foo\n4 quux\n' >out3
+ atf_check -s exit:0 -o file:out3 \
+ "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in3
+
+ printf 'foo\nbar\nbar\nbaz\nquux' >in4
+ printf '1 bar\n2 bar\n3 baz\n0 foo\n4 quux\n' >out4
+ atf_check -s exit:0 -o file:out4 \
+ "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in4
+
+ printf 'foo\nbar\nbaz\nbaz\nquux' >in5
+ printf '1 bar\n2 baz\n3 baz\n0 foo\n4 quux\n' >out5
+ atf_check -s exit:0 -o file:out5 \
+ "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in5
+
+ printf 'foo\nbar\nbaz\nquux\nquux' >in6
+ printf '1 bar\n2 baz\n0 foo\n3 quux\n4 quux\n' >out6
+ atf_check -s exit:0 -o file:out6 \
+ "$(atf_get_srcdir)"/h_sort -n "$sortfn" <in6
+}
+
+sortfn_case()
+{
+ local sortfn
+
+ sortfn="$1"
+
+ eval "${sortfn}_head() { atf_set descr \"Test ${sortfn}\"; }"
+ eval "${sortfn}_body() { check_sort $sortfn; }"
+ atf_add_test_case "$sortfn"
+}
+
+stablesortfn_case()
+{
+ local sortfn
+
+ sortfn="$1"
+
+ eval "${sortfn}_stable_head() { atf_set descr \"Test ${sortfn}\"; }"
+ eval "${sortfn}_stable_body() { check_stablesort $sortfn; }"
+ atf_add_test_case "${sortfn}_stable"
+}
+
+atf_init_test_cases()
+{
+
+ sortfn_case heapsort_r
+ sortfn_case mergesort_r
+ sortfn_case qsort_r
+ stablesortfn_case mergesort_r
+}
diff --git a/lib/libc/sys/t_aio_cancel.c b/lib/libc/sys/t_aio_cancel.c
new file mode 100644
index 000000000000..43965cada251
--- /dev/null
+++ b/lib/libc/sys/t_aio_cancel.c
@@ -0,0 +1,223 @@
+/* $NetBSD: t_aio_cancel.c,v 1.1 2025/10/10 15:53:55 christos Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+
+#include <aio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+static int mktemp_file(char *, size_t);
+static void fill_pattern(uint8_t *, size_t, uint8_t);
+static void wait_all(const struct aiocb * const [], size_t);
+
+static int
+mktemp_file(char *path, size_t pathlen)
+{
+ int fd, n;
+
+ n = snprintf(path, pathlen, "t_aio_cancel.XXXXXX");
+ ATF_REQUIRE(n > 0 && (size_t)n < pathlen);
+
+ fd = mkstemp(path);
+ ATF_REQUIRE(fd >= 0);
+
+ return fd;
+}
+
+static void
+fill_pattern(uint8_t *buf, size_t len, uint8_t seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ buf[i] = (uint8_t)(seed + (i & 0xff));
+ }
+}
+
+static void
+wait_all(const struct aiocb * const list[], size_t nent)
+{
+ size_t i;
+ int pending, rv;
+
+ for (;;) {
+ pending = 0;
+
+ for (i = 0; i < nent; i++) {
+ int err;
+
+ if (list[i] == NULL) {
+ continue;
+ }
+
+ err = aio_error(list[i]);
+ if (err == EINPROGRESS) {
+ pending = 1;
+ }
+ }
+
+ if (!pending) {
+ break;
+ }
+
+ rv = aio_suspend(list, (int)nent, NULL);
+ ATF_REQUIRE_EQ_MSG(0, rv, "aio_suspend failed: %s",
+ strerror(errno));
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(cancel_active_write);
+ATF_TC_BODY(cancel_active_write, tc)
+{
+ char path[64];
+ int fd, rv, crv, err;
+ const size_t blksz = 0x1000;
+ uint8_t *wbuf;
+ struct aiocb cb;
+ const struct aiocb *list[1];
+
+ fd = mktemp_file(path, sizeof(path));
+
+ wbuf = malloc(blksz);
+ ATF_REQUIRE(wbuf != NULL);
+ fill_pattern(wbuf, blksz, 0x33);
+
+ memset(&cb, 0, sizeof(cb));
+ cb.aio_fildes = fd;
+ cb.aio_buf = wbuf;
+ cb.aio_nbytes = blksz;
+ cb.aio_offset = 0;
+
+ rv = aio_write(&cb);
+ ATF_REQUIRE_EQ(0, rv);
+
+ crv = aio_cancel(fd, &cb);
+ ATF_REQUIRE(crv == AIO_CANCELED || crv == AIO_NOTCANCELED
+ || crv == AIO_ALLDONE);
+
+ if (crv == AIO_CANCELED) {
+ do {
+ err = aio_error(&cb);
+ } while (err == EINPROGRESS);
+ ATF_REQUIRE_EQ(ECANCELED, err);
+ ATF_REQUIRE_EQ(-1, aio_return(&cb));
+ } else if (crv == AIO_NOTCANCELED) {
+ list[0] = &cb;
+ wait_all(list, 1);
+ ATF_REQUIRE_EQ(0, aio_error(&cb));
+ ATF_REQUIRE_EQ((ssize_t)blksz, aio_return(&cb));
+ } else {
+ do {
+ err = aio_error(&cb);
+ } while (err == EINPROGRESS);
+ ATF_REQUIRE_EQ(0, err);
+ ATF_REQUIRE_EQ((ssize_t)blksz, aio_return(&cb));
+ }
+
+ rv = close(fd);
+ ATF_REQUIRE_EQ(0, rv);
+ rv = unlink(path);
+ ATF_REQUIRE_EQ(0, rv);
+
+ free(wbuf);
+}
+
+ATF_TC_WITHOUT_HEAD(cancel_completed_request);
+ATF_TC_BODY(cancel_completed_request, tc)
+{
+ char path[64];
+ int fd, rv, crv;
+ const size_t blksz = 4096;
+ uint8_t *wbuf;
+ struct aiocb cb;
+ const struct aiocb *list[1];
+
+ fd = mktemp_file(path, sizeof(path));
+
+ wbuf = malloc(blksz);
+ ATF_REQUIRE(wbuf != NULL);
+ memset(wbuf, 0x7E, blksz);
+
+ memset(&cb, 0, sizeof(cb));
+ cb.aio_fildes = fd;
+ cb.aio_buf = wbuf;
+ cb.aio_nbytes = blksz;
+ cb.aio_offset = 0;
+
+ rv = aio_write(&cb);
+ ATF_REQUIRE_EQ(0, rv);
+
+ list[0] = &cb;
+ wait_all(list, 1);
+ ATF_REQUIRE_EQ(0, aio_error(&cb));
+ ATF_REQUIRE_EQ((ssize_t)blksz, aio_return(&cb));
+
+ crv = aio_cancel(fd, &cb);
+ ATF_REQUIRE_EQ(AIO_ALLDONE, crv);
+
+ rv = close(fd);
+ ATF_REQUIRE_EQ(0, rv);
+ rv = unlink(path);
+ ATF_REQUIRE_EQ(0, rv);
+
+ free(wbuf);
+}
+
+ATF_TC_WITHOUT_HEAD(cancel_invalid_fd);
+ATF_TC_BODY(cancel_invalid_fd, tc)
+{
+ struct aiocb cb;
+ int crv;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.aio_fildes = -1;
+
+ errno = 0;
+ crv = aio_cancel(-1, &cb);
+ ATF_REQUIRE_EQ(-1, crv);
+ ATF_REQUIRE_EQ(EBADF, errno);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, cancel_active_write);
+ ATF_TP_ADD_TC(tp, cancel_completed_request);
+ ATF_TP_ADD_TC(tp, cancel_invalid_fd);
+ return atf_no_error();
+}
diff --git a/lib/libc/sys/t_aio_lio.c b/lib/libc/sys/t_aio_lio.c
new file mode 100644
index 000000000000..991db8d5a7cd
--- /dev/null
+++ b/lib/libc/sys/t_aio_lio.c
@@ -0,0 +1,264 @@
+/* $NetBSD: t_aio_lio.c,v 1.1 2025/10/10 15:53:55 christos Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+
+#include <aio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+static int mktemp_file(char *, size_t);
+static void fill_pattern(uint8_t *, size_t, uint8_t);
+static void wait_all(const struct aiocb * const [], size_t);
+
+static int
+mktemp_file(char *path, size_t pathlen)
+{
+ int fd, n;
+
+ n = snprintf(path, pathlen, "t_aio_lio.XXXXXX");
+ ATF_REQUIRE(n > 0 && (size_t)n < pathlen);
+
+ fd = mkstemp(path);
+ ATF_REQUIRE(fd >= 0);
+
+ return fd;
+}
+
+static void
+fill_pattern(uint8_t *buf, size_t len, uint8_t seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ buf[i] = (uint8_t)(seed + (i & 0xff));
+ }
+}
+
+static void
+wait_all(const struct aiocb * const list[], size_t nent)
+{
+ size_t i;
+ int pending, rv;
+
+ for (;;) {
+ pending = 0;
+
+ for (i = 0; i < nent; i++) {
+ int err;
+
+ if (list[i] == NULL) {
+ continue;
+ }
+
+ err = aio_error(list[i]);
+ if (err == EINPROGRESS) {
+ pending = 1;
+ }
+ }
+
+ if (!pending) {
+ break;
+ }
+
+ rv = aio_suspend(list, (int)nent, NULL);
+ ATF_REQUIRE_EQ_MSG(0, rv, "aio_suspend failed: %s",
+ strerror(errno));
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(lio_nowait);
+ATF_TC_BODY(lio_nowait, tc)
+{
+ char path[64];
+ int fd, rv;
+#define NW_REQS 8
+#define NW_BLKSIZ 8192
+ uint8_t *bufs[NW_REQS];
+ struct aiocb cbs[NW_REQS];
+ struct aiocb *list[NW_REQS];
+ off_t off;
+ size_t i;
+
+ fd = mktemp_file(path, sizeof(path));
+
+ off = 0;
+ for (i = 0; i < NW_REQS; i++) {
+ bufs[i] = malloc(NW_BLKSIZ);
+ ATF_REQUIRE(bufs[i] != NULL);
+
+ fill_pattern(bufs[i], NW_BLKSIZ, (uint8_t)i);
+
+ memset(&cbs[i], 0, sizeof(cbs[i]));
+ cbs[i].aio_fildes = fd;
+ cbs[i].aio_buf = bufs[i];
+ cbs[i].aio_nbytes = NW_BLKSIZ;
+ cbs[i].aio_offset = off;
+ cbs[i].aio_lio_opcode = LIO_WRITE;
+
+ list[i] = &cbs[i];
+ off += (off_t)NW_BLKSIZ;
+ }
+
+ rv = lio_listio(LIO_NOWAIT, list, (int)NW_REQS, NULL);
+ ATF_REQUIRE_EQ_MSG(0, rv, "lio_listio failed: %s",
+ strerror(errno));
+
+ wait_all((const struct aiocb * const *)list, NW_REQS);
+
+ for (i = 0; i < NW_REQS; i++) {
+ int err;
+ ssize_t done;
+
+ err = aio_error(&cbs[i]);
+ ATF_REQUIRE_EQ(0, err);
+
+ done = aio_return(&cbs[i]);
+ ATF_REQUIRE_EQ(NW_BLKSIZ, done);
+
+ free(bufs[i]);
+ }
+
+ rv = close(fd);
+ ATF_REQUIRE_EQ(0, rv);
+ rv = unlink(path);
+ ATF_REQUIRE_EQ(0, rv);
+}
+
+ATF_TC_WITHOUT_HEAD(lio_wait_write_then_read);
+ATF_TC_BODY(lio_wait_write_then_read, tc)
+{
+ char path[64];
+ int fd, rv;
+#define WWTR_REQS 4
+#define WWTR_BLKSIZ 4096
+
+ uint8_t *wbufs[WWTR_REQS];
+ struct aiocb wcbs[WWTR_REQS];
+ struct aiocb *wlist[WWTR_REQS];
+
+ uint8_t *rbufs[WWTR_REQS];
+ struct aiocb rcbs[WWTR_REQS];
+ struct aiocb *rlist[WWTR_REQS];
+
+ size_t i;
+ off_t off;
+
+ fd = mktemp_file(path, sizeof(path));
+
+ off = 0;
+ for (i = 0; i < WWTR_REQS; i++) {
+ wbufs[i] = malloc(WWTR_BLKSIZ);
+ ATF_REQUIRE(wbufs[i] != NULL);
+
+ fill_pattern(wbufs[i], WWTR_BLKSIZ, (uint8_t)(0xA0 + i));
+
+ memset(&wcbs[i], 0, sizeof(wcbs[i]));
+ wcbs[i].aio_fildes = fd;
+ wcbs[i].aio_buf = wbufs[i];
+ wcbs[i].aio_nbytes = WWTR_BLKSIZ;
+ wcbs[i].aio_offset = off;
+ wcbs[i].aio_lio_opcode = LIO_WRITE;
+
+ wlist[i] = &wcbs[i];
+ off += WWTR_BLKSIZ;
+ }
+
+ rv = lio_listio(LIO_WAIT, wlist, (int)WWTR_REQS, NULL);
+ ATF_REQUIRE_EQ_MSG(0, rv, "lio_listio write failed: %s",
+ strerror(errno));
+
+ for (i = 0; i < WWTR_REQS; i++) {
+ int err;
+ ssize_t done;
+
+ err = aio_error(&wcbs[i]);
+ ATF_REQUIRE_EQ(0, err);
+
+ done = aio_return(&wcbs[i]);
+ ATF_REQUIRE_EQ(WWTR_BLKSIZ, done);
+ }
+
+ for (i = 0; i < WWTR_REQS; i++) {
+ rbufs[i] = calloc(1, WWTR_BLKSIZ);
+ ATF_REQUIRE(rbufs[i] != NULL);
+
+ memset(&rcbs[i], 0, sizeof(rcbs[i]));
+ rcbs[i].aio_fildes = fd;
+ rcbs[i].aio_buf = rbufs[i];
+ rcbs[i].aio_nbytes = WWTR_BLKSIZ;
+ rcbs[i].aio_offset = (off_t)i * WWTR_BLKSIZ;
+ rcbs[i].aio_lio_opcode = LIO_READ;
+
+ rlist[i] = &rcbs[i];
+ }
+
+ rv = lio_listio(LIO_NOWAIT, rlist, WWTR_REQS, NULL);
+ ATF_REQUIRE_EQ_MSG(0, rv, "lio_listio read failed: %s",
+ strerror(errno));
+
+ wait_all((const struct aiocb * const *)rlist, WWTR_REQS);
+
+ for (i = 0; i < WWTR_REQS; i++) {
+ int err;
+ ssize_t done;
+
+ err = aio_error(&rcbs[i]);
+ ATF_REQUIRE_EQ(0, err);
+
+ done = aio_return(&rcbs[i]);
+ ATF_REQUIRE_EQ(WWTR_BLKSIZ, done);
+
+ ATF_REQUIRE_EQ(0, memcmp(wbufs[i], rbufs[i], WWTR_BLKSIZ));
+
+ free(wbufs[i]);
+ free(rbufs[i]);
+ }
+
+ rv = close(fd);
+ ATF_REQUIRE_EQ(0, rv);
+ rv = unlink(path);
+ ATF_REQUIRE_EQ(0, rv);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, lio_nowait);
+ ATF_TP_ADD_TC(tp, lio_wait_write_then_read);
+
+ return atf_no_error();
+}
diff --git a/lib/libc/sys/t_aio_rw.c b/lib/libc/sys/t_aio_rw.c
new file mode 100644
index 000000000000..e7a5b4fa67d1
--- /dev/null
+++ b/lib/libc/sys/t_aio_rw.c
@@ -0,0 +1,167 @@
+/* $NetBSD: t_aio_rw.c,v 1.1 2025/10/10 15:53:55 christos Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+
+#include <aio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+static int mktemp_file(char *, size_t);
+static void fill_pattern(uint8_t *, size_t, uint8_t);
+static void wait_all(const struct aiocb * const [], size_t);
+
+static int
+mktemp_file(char *path, size_t pathlen)
+{
+ int fd, n;
+
+ n = snprintf(path, pathlen, "t_aio_rw.XXXXXX");
+ ATF_REQUIRE(n > 0 && (size_t)n < pathlen);
+
+ fd = mkstemp(path);
+ ATF_REQUIRE(fd >= 0);
+
+ return fd;
+}
+
+static void
+fill_pattern(uint8_t *buf, size_t len, uint8_t seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ buf[i] = (uint8_t)(seed + (i & 0xff));
+ }
+}
+
+static void
+wait_all(const struct aiocb * const list[], size_t nent)
+{
+ size_t i;
+ int pending, rv, error;
+
+ for (;;) {
+ pending = 0;
+
+ for (i = 0; i < nent; i++) {
+ if (list[i] == NULL) {
+ continue;
+ }
+
+ error = aio_error(list[i]);
+ if (error == EINPROGRESS) {
+ pending = 1;
+ }
+ }
+
+ if (!pending) {
+ break;
+ }
+
+ rv = aio_suspend(list, (int)nent, NULL);
+ ATF_REQUIRE_EQ_MSG(0, rv, "aio_suspend failed: %s",
+ strerror(errno));
+ }
+}
+
+/*
+ * write_then_read_back
+ * Write a block then read it back asynchronously and compare.
+ */
+ATF_TC_WITHOUT_HEAD(write_then_read_back);
+ATF_TC_BODY(write_then_read_back, tc)
+{
+ char path[64];
+ int fd, rv;
+ const size_t blksz = 0x2000;
+ uint8_t *wbuf, *rbuf;
+ struct aiocb wcb, rcb;
+ const struct aiocb *wlist[1], *rlist[1];
+
+ fd = mktemp_file(path, sizeof(path));
+
+ wbuf = malloc(blksz);
+ rbuf = calloc(1, blksz);
+ ATF_REQUIRE(wbuf != NULL && rbuf != NULL);
+
+ fill_pattern(wbuf, blksz, 0xA0);
+
+ memset(&wcb, 0, sizeof(wcb));
+ wcb.aio_fildes = fd;
+ wcb.aio_buf = wbuf;
+ wcb.aio_nbytes = blksz;
+ wcb.aio_offset = 0;
+
+ rv = aio_write(&wcb);
+ ATF_REQUIRE_EQ(0, rv);
+ wlist[0] = &wcb;
+ wait_all(wlist, 1);
+
+ ATF_REQUIRE_EQ(0, aio_error(&wcb));
+ ATF_REQUIRE_EQ((ssize_t)blksz, aio_return(&wcb));
+
+ memset(&rcb, 0, sizeof(rcb));
+ rcb.aio_fildes = fd;
+ rcb.aio_buf = rbuf;
+ rcb.aio_nbytes = blksz;
+ rcb.aio_offset = 0;
+
+ rv = aio_read(&rcb);
+ ATF_REQUIRE_EQ(0, rv);
+ rlist[0] = &rcb;
+ wait_all(rlist, 1);
+
+ ATF_REQUIRE_EQ(0, aio_error(&rcb));
+ ATF_REQUIRE_EQ((ssize_t)blksz, aio_return(&rcb));
+ ATF_REQUIRE_EQ(0, memcmp(wbuf, rbuf, blksz));
+
+ rv = close(fd);
+ ATF_REQUIRE_EQ(0, rv);
+ rv = unlink(path);
+ ATF_REQUIRE_EQ(0, rv);
+
+ free(wbuf);
+ free(rbuf);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, write_then_read_back);
+ return atf_no_error();
+}
diff --git a/lib/libc/sys/t_aio_suspend.c b/lib/libc/sys/t_aio_suspend.c
new file mode 100644
index 000000000000..96b0dc2cf6d0
--- /dev/null
+++ b/lib/libc/sys/t_aio_suspend.c
@@ -0,0 +1,170 @@
+/* $NetBSD: t_aio_suspend.c,v 1.1 2025/10/10 15:53:55 christos Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
+ * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <atf-c.h>
+
+#include <aio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+static int mktemp_file(char *, size_t);
+static void fill_pattern(uint8_t *, size_t, uint8_t);
+static void wait_cb(struct aiocb *);
+
+static int
+mktemp_file(char *path, size_t pathlen)
+{
+ int fd, n;
+
+ n = snprintf(path, pathlen, "t_aio_suspend.XXXXXX");
+ ATF_REQUIRE(n > 0 && (size_t)n < pathlen);
+
+ fd = mkstemp(path);
+ ATF_REQUIRE(fd >= 0);
+
+ return fd;
+}
+
+static void
+fill_pattern(uint8_t *buf, size_t len, uint8_t seed)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ buf[i] = (uint8_t)(seed + (i & 0xff));
+ }
+}
+
+static void
+wait_cb(struct aiocb *cb)
+{
+ const struct aiocb *one[1];
+ int rv;
+
+ one[0] = cb;
+ while (aio_error(cb) == EINPROGRESS) {
+ rv = aio_suspend(one, 1, NULL);
+ ATF_REQUIRE_EQ(0, rv);
+ }
+ if (aio_error(cb) == 0) {
+ aio_return(cb);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(suspend_any);
+ATF_TC_BODY(suspend_any, tc)
+{
+ char path[64];
+ int fd, rv;
+ const size_t blksz = 4096;
+ uint8_t *buf0, *buf1;
+ struct aiocb cb0, cb1;
+ const struct aiocb *list[2];
+ int done;
+
+ fd = mktemp_file(path, sizeof(path));
+
+ buf0 = malloc(blksz);
+ buf1 = malloc(blksz);
+ ATF_REQUIRE(buf0 != NULL && buf1 != NULL);
+ fill_pattern(buf0, blksz, 0x20);
+ fill_pattern(buf1, blksz, 0x40);
+
+ memset(&cb0, 0, sizeof(cb0));
+ cb0.aio_fildes = fd;
+ cb0.aio_buf = buf0;
+ cb0.aio_nbytes = blksz;
+ cb0.aio_offset = 0;
+
+ memset(&cb1, 0, sizeof(cb1));
+ cb1.aio_fildes = fd;
+ cb1.aio_buf = buf1;
+ cb1.aio_nbytes = blksz;
+ cb1.aio_offset = blksz;
+
+ ATF_REQUIRE_EQ(0, aio_write(&cb0));
+ ATF_REQUIRE_EQ(0, aio_write(&cb1));
+
+ list[0] = &cb0;
+ list[1] = &cb1;
+
+ rv = aio_suspend(list, 2, NULL);
+ ATF_REQUIRE_EQ(0, rv);
+
+ done = 0;
+ if (aio_error(&cb0) != EINPROGRESS) {
+ done++;
+ if (aio_error(&cb0) == 0) {
+ ATF_REQUIRE_EQ((ssize_t)blksz, aio_return(&cb0));
+ } else {
+ ATF_REQUIRE_EQ(ECANCELED, aio_error(&cb0));
+ ATF_REQUIRE_EQ(-1, aio_return(&cb0));
+ }
+ }
+ if (aio_error(&cb1) != EINPROGRESS) {
+ done++;
+ if (aio_error(&cb1) == 0) {
+ ATF_REQUIRE_EQ((ssize_t)blksz, aio_return(&cb1));
+ } else {
+ ATF_REQUIRE_EQ(ECANCELED, aio_error(&cb1));
+ ATF_REQUIRE_EQ(-1, aio_return(&cb1));
+ }
+ }
+ ATF_REQUIRE(done >= 1);
+
+ if (aio_error(&cb0) == EINPROGRESS) {
+ wait_cb(&cb0);
+ }
+ if (aio_error(&cb1) == EINPROGRESS) {
+ wait_cb(&cb1);
+ }
+
+ rv = close(fd);
+ ATF_REQUIRE_EQ(0, rv);
+ rv = unlink(path);
+ ATF_REQUIRE_EQ(0, rv);
+
+ free(buf0);
+ free(buf1);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, suspend_any);
+ return atf_no_error();
+}
diff --git a/lib/libc/sys/t_ptrace_kill.c b/lib/libc/sys/t_ptrace_kill.c
new file mode 100644
index 000000000000..fdd6298c2a8a
--- /dev/null
+++ b/lib/libc/sys/t_ptrace_kill.c
@@ -0,0 +1,131 @@
+/* $NetBSD: t_ptrace_kill.c,v 1.2 2025/05/02 02:24:32 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_ptrace_kill.c,v 1.2 2025/05/02 02:24:32 riastradh Exp $");
+
+#include <sys/types.h>
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <err.h>
+#include <atf-c.h>
+
+#define SYSCALL(a, b) ATF_REQUIRE_EQ_MSG(a, b, "%s got %s", #a, strerror(errno))
+
+ATF_TC(pt_kill);
+ATF_TC_HEAD(pt_kill, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test PT_KILL of a PT_STOP'ed process");
+}
+
+static void
+child(int *fdto, int *fdfrom)
+{
+ char p = '2', q;
+ printf("%d: born\n", getpid());
+ write(fdfrom[1], &p, 1);
+ read(fdto[0], &q, 1);
+ printf("%d: seppuku %c\n", getpid(), q);
+ write(fdfrom[1], &p, 1);
+ read(fdto[0], &q, 1);
+// *(int *)1 = 0;
+// kill(getpid(), SIGSEGV);
+// kill(getpid(), SIGSTOP);
+ for (;;)
+ sleep(1);
+
+}
+
+static void *
+waitthread(void *pidp)
+{
+ int status = 0;
+ pid_t rpid, pid;
+
+ pid = *(pid_t *)pidp;
+ printf("waiting for %d\n", pid);
+ while ((rpid = waitpid(pid, &status, 0)) != pid) {
+ printf("waitpid %d = %d status = %#x", pid, rpid, status);
+ }
+ printf("done waitpid %d = %d status = %#x", pid, rpid, status);
+ return NULL;
+}
+
+ATF_TC_BODY(pt_kill, tc)
+{
+ pid_t pid;
+ int fdto[2], fdfrom[2];
+ char p = '1', q;
+ int status;
+ pthread_t thread;
+
+ SYSCALL(pipe(fdto), 0);
+ SYSCALL(pipe(fdfrom), 0);
+ switch (pid = fork()) {
+ case 0:
+ child(fdto, fdfrom);
+ break;
+ case -1:
+ err(EXIT_FAILURE, "fork failed");
+ default:
+ SYSCALL(pthread_create(&thread, NULL, waitthread, &pid), 0);
+ sleep(1); // XXX: too lazy to sync properly
+ SYSCALL(read(fdfrom[0], &q, 1), 1);
+ printf("%d: read %c\n", pid, q);
+ SYSCALL(ptrace(PT_ATTACH, pid, NULL, 0), 0);
+ printf("%d: attached\n", pid);
+ SYSCALL(write(fdto[1], &p, 1), 1);
+ waitpid(pid, NULL, WNOHANG);
+ printf("%d: sent\n", pid);
+ SYSCALL(ptrace(PT_CONTINUE, pid, (void *)1, 0), 0);
+ SYSCALL(read(fdfrom[0], &p, 1), 1);
+ printf("%d: received\n", pid);
+ SYSCALL(ptrace(PT_STOP, pid, NULL, 0), 0);
+ SYSCALL(ptrace(PT_KILL, pid, NULL, 0), 0);
+ SYSCALL(waitpid(pid, &status, 0), pid);
+ ATF_REQUIRE(status == 9);
+ break;
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, pt_kill);
+
+ return atf_no_error();
+}
diff --git a/lib/libexecinfo/t_backtrace_sandbox.c b/lib/libexecinfo/t_backtrace_sandbox.c
new file mode 100644
index 000000000000..51d650c3424a
--- /dev/null
+++ b/lib/libexecinfo/t_backtrace_sandbox.c
@@ -0,0 +1,88 @@
+/* $NetBSD: t_backtrace_sandbox.c,v 1.3 2025/01/30 16:13:51 christos Exp $ */
+
+/*-
+ * Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_backtrace_sandbox.c,v 1.3 2025/01/30 16:13:51 christos Exp $");
+
+#include <sys/param.h>
+#include <sys/wait.h>
+#ifdef __FreeBSD__
+#include <sys/capsicum.h>
+#define __arraycount(a) nitems(a)
+#endif
+
+#include <execinfo.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#define BT_FUNCTIONS 10
+
+ATF_TC(backtrace_sandbox);
+ATF_TC_HEAD(backtrace_sandbox, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test backtrace_sandbox_init(3) in sandbox");
+#ifndef __FreeBSD__
+ atf_tc_set_md_var(tc, "require.user", "root");
+#endif
+}
+
+ATF_TC_BODY(backtrace_sandbox, tc)
+{
+ void *addr[BT_FUNCTIONS];
+ char **syms;
+ size_t frames;
+ pid_t pid;
+ int status;
+
+ frames = backtrace(addr, __arraycount(addr));
+ ATF_REQUIRE(frames > 0);
+
+ syms = backtrace_symbols_fmt(addr, frames, "%n");
+ ATF_REQUIRE(strcmp(syms[0], "atfu_backtrace_sandbox_body") == 0);
+
+ pid = fork();
+ ATF_REQUIRE(pid >= 0);
+
+ if (pid == 0) {
+
+ backtrace_sandbox_init();
+#ifdef __FreeBSD__
+ cap_enter();
+#else
+ if (chroot("/tmp") != 0)
+ _exit(EXIT_FAILURE);
+#endif
+
+ syms = backtrace_symbols_fmt(addr, frames, "%n");
+ if (strcmp(syms[0], "atfu_backtrace_sandbox_body") != 0)
+ _exit(EXIT_FAILURE);
+
+ backtrace_sandbox_fini();
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ (void)wait(&status);
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS)
+ atf_tc_fail("resolving symbols in chroot failed");
+
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, backtrace_sandbox);
+
+ return atf_no_error();
+}
diff --git a/lib/libm/t_errhandling.c b/lib/libm/t_errhandling.c
new file mode 100644
index 000000000000..7c95afe22121
--- /dev/null
+++ b/lib/libm/t_errhandling.c
@@ -0,0 +1,97 @@
+/* $NetBSD: t_errhandling.c,v 1.3 2024/09/10 17:36:12 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define __TEST_FENV
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_errhandling.c,v 1.3 2024/09/10 17:36:12 riastradh Exp $");
+
+#include <atf-c.h>
+#include <errno.h>
+#include <fenv.h>
+#include <math.h>
+
+ATF_TC(log);
+ATF_TC_HEAD(log, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "log of invalid");
+}
+ATF_TC_BODY(log, tc)
+{
+ static const struct {
+#ifdef __HAVE_FENV
+ double x;
+ int e;
+#define C(x, e) { x, e }
+#else
+ double x;
+#define C(x, e) { x }
+#endif
+ } cases[] = {
+ C(0, FE_DIVBYZERO),
+ C(-0., FE_DIVBYZERO),
+ C(-1, FE_INVALID),
+ C(-HUGE_VAL, FE_INVALID),
+ };
+ volatile double y;
+#ifdef __HAVE_FENV
+ int except;
+#endif
+ unsigned i;
+
+ for (i = 0; i < __arraycount(cases); i++) {
+ const volatile double x = cases[i].x;
+
+#ifdef __HAVE_FENV
+ feclearexcept(FE_ALL_EXCEPT);
+#endif
+ errno = 0;
+ y = log(x);
+ if (math_errhandling & MATH_ERREXCEPT) {
+#ifdef __HAVE_FENV
+ ATF_CHECK_MSG(((except = fetestexcept(FE_ALL_EXCEPT)) &
+ cases[i].e) != 0,
+ "expected=0x%x actual=0x%x", cases[i].e, except);
+#else
+ atf_tc_fail_nonfatal("MATH_ERREXCEPT but no fenv.h");
+#endif
+ }
+ if (math_errhandling & MATH_ERRNO)
+ ATF_CHECK_EQ_MSG(errno, EDOM, "errno=%d", errno);
+ }
+
+ __USE(y);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, log);
+
+ return atf_no_error();
+}
diff --git a/lib/libm/t_next.c b/lib/libm/t_next.c
new file mode 100644
index 000000000000..22363f92daf2
--- /dev/null
+++ b/lib/libm/t_next.c
@@ -0,0 +1,887 @@
+/* $NetBSD: t_next.c,v 1.8 2025/04/07 01:31:18 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_next.c,v 1.8 2025/04/07 01:31:18 riastradh Exp $");
+
+#include <atf-c.h>
+#include <float.h>
+#include <math.h>
+
+#ifdef __vax__ /* XXX PR 57881: vax libm is missing various symbols */
+
+ATF_TC(vaxafter);
+ATF_TC_HEAD(vaxafter, tc)
+{
+
+ atf_tc_set_md_var(tc, "descr", "vax nextafter/nexttoward reminder");
+}
+ATF_TC_BODY(vaxafter, tc)
+{
+
+ atf_tc_expect_fail("PR 57881: vax libm is missing various symbols");
+ atf_tc_fail("missing nextafter{,f,l} and nexttoward{,f,l} on vax");
+}
+
+#else /* !__vax__ */
+
+#define CHECK(i, next, x, d, y) do \
+{ \
+ volatile __typeof__(x) check_x = (x); \
+ volatile __typeof__(d) check_d = (d); \
+ volatile __typeof__(y) check_y = (y); \
+ const volatile __typeof__(y) check_tmp = (next)(check_x, check_d); \
+ ATF_CHECK_MSG(check_tmp == check_y, \
+ "[%u] %s(%s=%La=%Lg, %s=%La=%Lg)=%La=%Lg != %s=%La=%Lg", \
+ (i), #next, \
+ #x, (long double)check_x, (long double)check_x, \
+ #d, (long double)check_d, (long double)check_d, \
+ (long double)check_tmp, (long double)check_tmp, \
+ #y, (long double)check_y, (long double)check_y); \
+} while (0)
+
+/*
+ * check(x, n)
+ *
+ * x[0], x[1], ..., x[n - 1] are consecutive double floating-point
+ * numbers. Verify nextafter and nexttoward follow exactly this
+ * sequence, forward and back, and in negative.
+ */
+static void
+check(const double *x, unsigned n)
+{
+ unsigned i;
+
+ for (i = 0; i < n; i++) {
+ CHECK(i, nextafter, x[i], x[i], x[i]);
+ CHECK(i, nexttoward, x[i], x[i], x[i]);
+ CHECK(i, nextafter, -x[i], -x[i], -x[i]);
+ CHECK(i, nexttoward, -x[i], -x[i], -x[i]);
+ }
+
+ for (i = 0; i < n - 1; i++) {
+ ATF_REQUIRE_MSG(x[i] < x[i + 1], "i=%u", i);
+
+ if (isnormal(x[i])) {
+ CHECK(i, nexttoward, x[i], x[i]*(1 + LDBL_EPSILON),
+ x[i + 1]);
+ }
+
+ CHECK(i, nextafter, x[i], x[i + 1], x[i + 1]);
+ CHECK(i, nexttoward, x[i], x[i + 1], x[i + 1]);
+ CHECK(i, nextafter, x[i], x[n - 1], x[i + 1]);
+ CHECK(i, nexttoward, x[i], x[n - 1], x[i + 1]);
+ CHECK(i, nextafter, x[i], INFINITY, x[i + 1]);
+ CHECK(i, nexttoward, x[i], INFINITY, x[i + 1]);
+
+ CHECK(i, nextafter, -x[i], -x[i + 1], -x[i + 1]);
+ CHECK(i, nexttoward, -x[i], -x[i + 1], -x[i + 1]);
+ CHECK(i, nextafter, -x[i], -x[n - 1], -x[i + 1]);
+ CHECK(i, nexttoward, -x[i], -x[n - 1], -x[i + 1]);
+ CHECK(i, nextafter, -x[i], -INFINITY, -x[i + 1]);
+ CHECK(i, nexttoward, -x[i], -INFINITY, -x[i + 1]);
+ }
+
+ for (i = n; i --> 1;) {
+ ATF_REQUIRE_MSG(x[i - 1] < x[i], "i=%u", i);
+
+#ifdef __HAVE_LONG_DOUBLE
+ if (isnormal(x[i])) {
+ CHECK(i, nexttoward, x[i], x[i]*(1 - LDBL_EPSILON/2),
+ x[i - 1]);
+ }
+#endif
+
+ CHECK(i, nextafter, x[i], x[i - 1], x[i - 1]);
+ CHECK(i, nexttoward, x[i], x[i - 1], x[i - 1]);
+ CHECK(i, nextafter, x[i], x[0], x[i - 1]);
+ CHECK(i, nexttoward, x[i], x[0], x[i - 1]);
+ CHECK(i, nextafter, x[i], +0., x[i - 1]);
+ CHECK(i, nexttoward, x[i], +0., x[i - 1]);
+ CHECK(i, nextafter, x[i], -0., x[i - 1]);
+ CHECK(i, nexttoward, x[i], -0., x[i - 1]);
+ CHECK(i, nextafter, x[i], -x[0], x[i - 1]);
+ CHECK(i, nexttoward, x[i], -x[0], x[i - 1]);
+ CHECK(i, nextafter, x[i], -x[i], x[i - 1]);
+ CHECK(i, nexttoward, x[i], -x[i], x[i - 1]);
+ CHECK(i, nextafter, x[i], -INFINITY, x[i - 1]);
+ CHECK(i, nexttoward, x[i], -INFINITY, x[i - 1]);
+
+ CHECK(i, nextafter, -x[i], -x[i - 1], -x[i - 1]);
+ CHECK(i, nexttoward, -x[i], -x[i - 1], -x[i - 1]);
+ CHECK(i, nextafter, -x[i], -x[0], -x[i - 1]);
+ CHECK(i, nexttoward, -x[i], -x[0], -x[i - 1]);
+ CHECK(i, nextafter, -x[i], -0., -x[i - 1]);
+ CHECK(i, nexttoward, -x[i], -0., -x[i - 1]);
+ CHECK(i, nextafter, -x[i], +0., -x[i - 1]);
+ CHECK(i, nexttoward, -x[i], +0., -x[i - 1]);
+ CHECK(i, nextafter, -x[i], x[0], -x[i - 1]);
+ CHECK(i, nexttoward, -x[i], x[0], -x[i - 1]);
+ CHECK(i, nextafter, -x[i], INFINITY, -x[i - 1]);
+ CHECK(i, nexttoward, -x[i], INFINITY, -x[i - 1]);
+ }
+}
+
+/*
+ * checkf(x, n)
+ *
+ * x[0], x[1], ..., x[n - 1] are consecutive single floating-point
+ * numbers. Verify nextafterf and nexttowardf follow exactly this
+ * sequence, forward and back, and in negative.
+ */
+static void
+checkf(const float *x, unsigned n)
+{
+ unsigned i;
+
+ for (i = 0; i < n; i++) {
+ CHECK(i, nextafterf, x[i], x[i], x[i]);
+ CHECK(i, nexttowardf, x[i], x[i], x[i]);
+ CHECK(i, nextafterf, -x[i], -x[i], -x[i]);
+ CHECK(i, nexttowardf, -x[i], -x[i], -x[i]);
+ }
+
+ for (i = 0; i < n - 1; i++) {
+ ATF_REQUIRE_MSG(x[i] < x[i + 1], "i=%u", i);
+
+ if (isnormal(x[i])) {
+ CHECK(i, nexttowardf, x[i], x[i]*(1 + LDBL_EPSILON),
+ x[i + 1]);
+ }
+
+ CHECK(i, nextafterf, x[i], x[i + 1], x[i + 1]);
+ CHECK(i, nexttowardf, x[i], x[i + 1], x[i + 1]);
+ CHECK(i, nextafterf, x[i], x[n - 1], x[i + 1]);
+ CHECK(i, nexttowardf, x[i], x[n - 1], x[i + 1]);
+ CHECK(i, nextafterf, x[i], INFINITY, x[i + 1]);
+ CHECK(i, nexttowardf, x[i], INFINITY, x[i + 1]);
+
+ CHECK(i, nextafterf, -x[i], -x[i + 1], -x[i + 1]);
+ CHECK(i, nexttowardf, -x[i], -x[i + 1], -x[i + 1]);
+ CHECK(i, nextafterf, -x[i], -x[n - 1], -x[i + 1]);
+ CHECK(i, nexttowardf, -x[i], -x[n - 1], -x[i + 1]);
+ CHECK(i, nextafterf, -x[i], -INFINITY, -x[i + 1]);
+ CHECK(i, nexttowardf, -x[i], -INFINITY, -x[i + 1]);
+ }
+
+ for (i = n; i --> 1;) {
+ ATF_REQUIRE_MSG(x[i - 1] < x[i], "i=%u", i);
+
+ if (isnormal(x[i])) {
+ CHECK(i, nexttowardf, x[i], x[i]*(1 - LDBL_EPSILON/2),
+ x[i - 1]);
+ }
+
+ CHECK(i, nextafterf, x[i], x[i - 1], x[i - 1]);
+ CHECK(i, nexttowardf, x[i], x[i - 1], x[i - 1]);
+ CHECK(i, nextafterf, x[i], x[0], x[i - 1]);
+ CHECK(i, nexttowardf, x[i], x[0], x[i - 1]);
+ CHECK(i, nextafterf, x[i], +0., x[i - 1]);
+ CHECK(i, nexttowardf, x[i], +0., x[i - 1]);
+ CHECK(i, nextafterf, x[i], -0., x[i - 1]);
+ CHECK(i, nexttowardf, x[i], -0., x[i - 1]);
+ CHECK(i, nextafterf, x[i], -x[0], x[i - 1]);
+ CHECK(i, nexttowardf, x[i], -x[0], x[i - 1]);
+ CHECK(i, nextafterf, x[i], -x[i], x[i - 1]);
+ CHECK(i, nexttowardf, x[i], -x[i], x[i - 1]);
+ CHECK(i, nextafterf, x[i], -INFINITY, x[i - 1]);
+ CHECK(i, nexttowardf, x[i], -INFINITY, x[i - 1]);
+
+ CHECK(i, nextafterf, -x[i], -x[i - 1], -x[i - 1]);
+ CHECK(i, nexttowardf, -x[i], -x[i - 1], -x[i - 1]);
+ CHECK(i, nextafterf, -x[i], -x[0], -x[i - 1]);
+ CHECK(i, nexttowardf, -x[i], -x[0], -x[i - 1]);
+ CHECK(i, nextafterf, -x[i], -0., -x[i - 1]);
+ CHECK(i, nexttowardf, -x[i], -0., -x[i - 1]);
+ CHECK(i, nextafterf, -x[i], +0., -x[i - 1]);
+ CHECK(i, nexttowardf, -x[i], +0., -x[i - 1]);
+ CHECK(i, nextafterf, -x[i], x[0], -x[i - 1]);
+ CHECK(i, nexttowardf, -x[i], x[0], -x[i - 1]);
+ CHECK(i, nextafterf, -x[i], INFINITY, -x[i - 1]);
+ CHECK(i, nexttowardf, -x[i], INFINITY, -x[i - 1]);
+ }
+}
+
+/*
+ * checkl(x, n)
+ *
+ * x[0], x[1], ..., x[n - 1] are consecutive long double
+ * floating-point numbers. Verify nextafterl and nexttowardl
+ * follow exactly this sequence, forward and back, and in
+ * negative.
+ */
+static void
+checkl(const long double *x, unsigned n)
+{
+ unsigned i;
+
+ for (i = 0; i < n; i++) {
+ CHECK(i, nextafterl, x[i], x[i], x[i]);
+ CHECK(i, nexttowardl, x[i], x[i], x[i]);
+ CHECK(i, nextafterl, -x[i], -x[i], -x[i]);
+ CHECK(i, nexttowardl, -x[i], -x[i], -x[i]);
+ }
+
+ for (i = 0; i < n - 1; i++) {
+ ATF_REQUIRE_MSG(x[i] < x[i + 1], "i=%u", i);
+
+ CHECK(i, nextafterl, x[i], x[i + 1], x[i + 1]);
+ CHECK(i, nexttowardl, x[i], x[i + 1], x[i + 1]);
+ CHECK(i, nextafterl, x[i], x[n - 1], x[i + 1]);
+ CHECK(i, nexttowardl, x[i], x[n - 1], x[i + 1]);
+ CHECK(i, nextafterl, x[i], INFINITY, x[i + 1]);
+ CHECK(i, nexttowardl, x[i], INFINITY, x[i + 1]);
+
+ CHECK(i, nextafterl, -x[i], -x[i + 1], -x[i + 1]);
+ CHECK(i, nexttowardl, -x[i], -x[i + 1], -x[i + 1]);
+ CHECK(i, nextafterl, -x[i], -x[n - 1], -x[i + 1]);
+ CHECK(i, nexttowardl, -x[i], -x[n - 1], -x[i + 1]);
+ CHECK(i, nextafterl, -x[i], -INFINITY, -x[i + 1]);
+ CHECK(i, nexttowardl, -x[i], -INFINITY, -x[i + 1]);
+ }
+
+ for (i = n; i --> 1;) {
+ ATF_REQUIRE_MSG(x[i - 1] < x[i], "i=%u", i);
+
+ CHECK(i, nextafterl, x[i], x[i - 1], x[i - 1]);
+ CHECK(i, nexttowardl, x[i], x[i - 1], x[i - 1]);
+ CHECK(i, nextafterl, x[i], x[0], x[i - 1]);
+ CHECK(i, nexttowardl, x[i], x[0], x[i - 1]);
+ CHECK(i, nextafterl, x[i], +0., x[i - 1]);
+ CHECK(i, nexttowardl, x[i], +0., x[i - 1]);
+ CHECK(i, nextafterl, x[i], -0., x[i - 1]);
+ CHECK(i, nexttowardl, x[i], -0., x[i - 1]);
+ CHECK(i, nextafterl, x[i], -x[0], x[i - 1]);
+ CHECK(i, nexttowardl, x[i], -x[0], x[i - 1]);
+ CHECK(i, nextafterl, x[i], -x[i], x[i - 1]);
+ CHECK(i, nexttowardl, x[i], -x[i], x[i - 1]);
+ CHECK(i, nextafterl, x[i], -INFINITY, x[i - 1]);
+ CHECK(i, nexttowardl, x[i], -INFINITY, x[i - 1]);
+
+ CHECK(i, nextafterl, -x[i], -x[i - 1], -x[i - 1]);
+ CHECK(i, nexttowardl, -x[i], -x[i - 1], -x[i - 1]);
+ CHECK(i, nextafterl, -x[i], -x[0], -x[i - 1]);
+ CHECK(i, nexttowardl, -x[i], -x[0], -x[i - 1]);
+ CHECK(i, nextafterl, -x[i], -0., -x[i - 1]);
+ CHECK(i, nexttowardl, -x[i], -0., -x[i - 1]);
+ CHECK(i, nextafterl, -x[i], +0., -x[i - 1]);
+ CHECK(i, nexttowardl, -x[i], +0., -x[i - 1]);
+ CHECK(i, nextafterl, -x[i], x[0], -x[i - 1]);
+ CHECK(i, nexttowardl, -x[i], x[0], -x[i - 1]);
+ CHECK(i, nextafterl, -x[i], INFINITY, -x[i - 1]);
+ CHECK(i, nexttowardl, -x[i], INFINITY, -x[i - 1]);
+ }
+}
+
+ATF_TC(next_nan);
+ATF_TC_HEAD(next_nan, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafter/nexttoward on NaN");
+}
+ATF_TC_BODY(next_nan, tc)
+{
+#ifdef NAN
+ /* XXX verify the NaN is quiet */
+ ATF_CHECK(isnan(nextafter(NAN, 0)));
+ ATF_CHECK(isnan(nexttoward(NAN, 0)));
+ ATF_CHECK(isnan(nextafter(0, NAN)));
+ ATF_CHECK(isnan(nexttoward(0, NAN)));
+#else
+ atf_tc_skip("no NaNs on this architecture");
+#endif
+}
+
+ATF_TC(next_signed_0);
+ATF_TC_HEAD(next_signed_0, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafter/nexttoward on signed 0");
+}
+ATF_TC_BODY(next_signed_0, tc)
+{
+ volatile double z_pos = +0.;
+ volatile double z_neg = -0.;
+#if __DBL_HAS_DENORM__
+ volatile double m = __DBL_DENORM_MIN__;
+#else
+ volatile double m = DBL_MIN;
+#endif
+
+ if (signbit(z_pos) == signbit(z_neg))
+ atf_tc_skip("no signed zeroes on this architecture");
+
+ /*
+ * `nextUp(x) is the least floating-point number in the format
+ * of x that compares greater than x. [...] nextDown(x) is
+ * -nextUp(-x).'
+ * --IEEE 754-2019, 5.3.1 General operations, p. 19
+ *
+ * Verify that nextafter and nexttoward, which implement the
+ * nextUp and nextDown operations, obey this rule and don't
+ * send -0 to +0 or +0 to -0, respectively.
+ */
+
+ CHECK(0, nextafter, z_neg, +INFINITY, m);
+ CHECK(1, nexttoward, z_neg, +INFINITY, m);
+ CHECK(2, nextafter, z_pos, +INFINITY, m);
+ CHECK(3, nexttoward, z_pos, +INFINITY, m);
+
+ CHECK(4, nextafter, z_pos, -INFINITY, -m);
+ CHECK(5, nexttoward, z_pos, -INFINITY, -m);
+ CHECK(6, nextafter, z_neg, -INFINITY, -m);
+ CHECK(7, nexttoward, z_neg, -INFINITY, -m);
+
+ /*
+ * `If x is the negative number of least magnitude in x's
+ * format, nextUp(x) is -0.'
+ * --IEEE 754-2019, 5.3.1 General operations, p. 19
+ *
+ * Verify that nextafter and nexttoward return the correctly
+ * signed zero.
+ */
+ CHECK(8, nextafter, -m, +INFINITY, 0);
+ CHECK(9, nexttoward, -m, +INFINITY, 0);
+ ATF_CHECK(signbit(nextafter(-m, +INFINITY)) != 0);
+ CHECK(10, nextafter, m, -INFINITY, 0);
+ CHECK(11, nexttoward, m, -INFINITY, 0);
+ ATF_CHECK(signbit(nextafter(m, -INFINITY)) == 0);
+}
+
+ATF_TC(next_near_0);
+ATF_TC_HEAD(next_near_0, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafter/nexttoward near 0");
+}
+ATF_TC_BODY(next_near_0, tc)
+{
+ static const double x[] = {
+ [0] = 0,
+#if __DBL_HAS_DENORM__
+ [1] = __DBL_DENORM_MIN__,
+ [2] = 2*__DBL_DENORM_MIN__,
+ [3] = 3*__DBL_DENORM_MIN__,
+ [4] = 4*__DBL_DENORM_MIN__,
+#else
+ [1] = DBL_MIN,
+ [2] = DBL_MIN*(1 + DBL_EPSILON),
+ [3] = DBL_MIN*(1 + 2*DBL_EPSILON),
+ [4] = DBL_MIN*(1 + 3*DBL_EPSILON),
+#endif
+ };
+
+ check(x, __arraycount(x));
+}
+
+ATF_TC(next_near_sub_normal);
+ATF_TC_HEAD(next_near_sub_normal, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "nextafter/nexttoward near the subnormal/normal boundary");
+}
+ATF_TC_BODY(next_near_sub_normal, tc)
+{
+#if __DBL_HAS_DENORM__
+ static const double x[] = {
+ [0] = DBL_MIN - 3*__DBL_DENORM_MIN__,
+ [1] = DBL_MIN - 2*__DBL_DENORM_MIN__,
+ [2] = DBL_MIN - __DBL_DENORM_MIN__,
+ [3] = DBL_MIN,
+ [4] = DBL_MIN + __DBL_DENORM_MIN__,
+ [5] = DBL_MIN + 2*__DBL_DENORM_MIN__,
+ [6] = DBL_MIN + 3*__DBL_DENORM_MIN__,
+ };
+
+ check(x, __arraycount(x));
+#else /* !__DBL_HAS_DENORM__ */
+ atf_tc_skip("no subnormals on this architecture");
+#endif /* !__DBL_HAS_DENORM__ */
+}
+
+ATF_TC(next_near_1);
+ATF_TC_HEAD(next_near_1, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafter/nexttoward near 1");
+}
+ATF_TC_BODY(next_near_1, tc)
+{
+ static const double x[] = {
+ [0] = 1 - 3*DBL_EPSILON/2,
+ [1] = 1 - 2*DBL_EPSILON/2,
+ [2] = 1 - DBL_EPSILON/2,
+ [3] = 1,
+ [4] = 1 + DBL_EPSILON,
+ [5] = 1 + 2*DBL_EPSILON,
+ [6] = 1 + 3*DBL_EPSILON,
+ };
+
+ check(x, __arraycount(x));
+}
+
+ATF_TC(next_near_1_5);
+ATF_TC_HEAD(next_near_1_5, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafter/nexttoward near 1.5");
+}
+ATF_TC_BODY(next_near_1_5, tc)
+{
+ static const double x[] = {
+ [0] = 1.5 - 3*DBL_EPSILON,
+ [1] = 1.5 - 2*DBL_EPSILON,
+ [2] = 1.5 - DBL_EPSILON,
+ [3] = 1.5,
+ [4] = 1.5 + DBL_EPSILON,
+ [5] = 1.5 + 2*DBL_EPSILON,
+ [6] = 1.5 + 3*DBL_EPSILON,
+ };
+
+ check(x, __arraycount(x));
+}
+
+ATF_TC(next_near_infinity);
+ATF_TC_HEAD(next_near_infinity, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafter/nexttoward near infinity");
+}
+ATF_TC_BODY(next_near_infinity, tc)
+{
+ static const double x[] = {
+ [0] = DBL_MAX,
+ [1] = INFINITY,
+ };
+ volatile double t;
+
+ if (!isinf(INFINITY))
+ atf_tc_skip("no infinities on this architecture");
+
+ check(x, __arraycount(x));
+
+ ATF_CHECK_EQ_MSG((t = nextafter(INFINITY, INFINITY)), INFINITY,
+ "t=%a=%g", t, t);
+ ATF_CHECK_EQ_MSG((t = nextafter(-INFINITY, -INFINITY)), -INFINITY,
+ "t=%a=%g", t, t);
+}
+
+ATF_TC(nextf_nan);
+ATF_TC_HEAD(nextf_nan, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterf/nexttowardf on NaN");
+}
+ATF_TC_BODY(nextf_nan, tc)
+{
+#ifdef NAN
+ /* XXX verify the NaN is quiet */
+ ATF_CHECK(isnan(nextafterf(NAN, 0)));
+ ATF_CHECK(isnan(nexttowardf(NAN, 0)));
+ ATF_CHECK(isnan(nextafterf(0, NAN)));
+ ATF_CHECK(isnan(nexttowardf(0, NAN)));
+#else
+ atf_tc_skip("no NaNs on this architecture");
+#endif
+}
+
+ATF_TC(nextf_signed_0);
+ATF_TC_HEAD(nextf_signed_0, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterf/nexttowardf on signed 0");
+}
+ATF_TC_BODY(nextf_signed_0, tc)
+{
+ volatile float z_pos = +0.;
+ volatile float z_neg = -0.;
+#if __FLT_HAS_DENORM__
+ volatile float m = __FLT_DENORM_MIN__;
+#else
+ volatile float m = FLT_MIN;
+#endif
+
+ if (signbit(z_pos) == signbit(z_neg))
+ atf_tc_skip("no signed zeroes on this architecture");
+
+ /*
+ * `nextUp(x) is the least floating-point number in the format
+ * of x that compares greater than x. [...] nextDown(x) is
+ * -nextUp(-x).'
+ * --IEEE 754-2019, 5.3.1 General operations, p. 19
+ *
+ * Verify that nextafterf and nexttowardf, which implement the
+ * nextUp and nextDown operations, obey this rule and don't
+ * send -0 to +0 or +0 to -0, respectively.
+ */
+
+ CHECK(0, nextafterf, z_neg, +INFINITY, m);
+ CHECK(1, nexttowardf, z_neg, +INFINITY, m);
+ CHECK(2, nextafterf, z_pos, +INFINITY, m);
+ CHECK(3, nexttowardf, z_pos, +INFINITY, m);
+
+ CHECK(4, nextafterf, z_pos, -INFINITY, -m);
+ CHECK(5, nexttowardf, z_pos, -INFINITY, -m);
+ CHECK(6, nextafterf, z_neg, -INFINITY, -m);
+ CHECK(7, nexttowardf, z_neg, -INFINITY, -m);
+
+ /*
+ * `If x is the negative number of least magnitude in x's
+ * format, nextUp(x) is -0.'
+ * --IEEE 754-2019, 5.3.1 General operations, p. 19
+ */
+ CHECK(8, nextafterf, -m, +INFINITY, 0);
+ CHECK(9, nexttowardf, -m, +INFINITY, 0);
+ ATF_CHECK(signbit(nextafterf(-m, +INFINITY)) != 0);
+ CHECK(10, nextafterf, m, -INFINITY, 0);
+ CHECK(11, nexttowardf, m, -INFINITY, 0);
+ ATF_CHECK(signbit(nextafterf(m, -INFINITY)) == 0);
+}
+
+ATF_TC(nextf_near_0);
+ATF_TC_HEAD(nextf_near_0, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterf/nexttowardf near 0");
+}
+ATF_TC_BODY(nextf_near_0, tc)
+{
+ static const float x[] = {
+ [0] = 0,
+#if __FLT_HAS_DENORM__
+ [1] = __FLT_DENORM_MIN__,
+ [2] = 2*__FLT_DENORM_MIN__,
+ [3] = 3*__FLT_DENORM_MIN__,
+ [4] = 4*__FLT_DENORM_MIN__,
+#else
+ [1] = FLT_MIN,
+ [2] = FLT_MIN*(1 + FLT_EPSILON),
+ [3] = FLT_MIN*(1 + 2*FLT_EPSILON),
+ [4] = FLT_MIN*(1 + 3*FLT_EPSILON),
+#endif
+ };
+
+ checkf(x, __arraycount(x));
+}
+
+ATF_TC(nextf_near_sub_normal);
+ATF_TC_HEAD(nextf_near_sub_normal, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "nextafterf/nexttowardf near the subnormal/normal boundary");
+}
+ATF_TC_BODY(nextf_near_sub_normal, tc)
+{
+#if __FLT_HAS_DENORM__
+ static const float x[] = {
+ [0] = FLT_MIN - 3*__FLT_DENORM_MIN__,
+ [1] = FLT_MIN - 2*__FLT_DENORM_MIN__,
+ [2] = FLT_MIN - __FLT_DENORM_MIN__,
+ [3] = FLT_MIN,
+ [4] = FLT_MIN + __FLT_DENORM_MIN__,
+ [5] = FLT_MIN + 2*__FLT_DENORM_MIN__,
+ [6] = FLT_MIN + 3*__FLT_DENORM_MIN__,
+ };
+
+ checkf(x, __arraycount(x));
+#else /* !__FLT_HAS_DENORM__ */
+ atf_tc_skip("no subnormals on this architecture");
+#endif /* !__FLT_HAS_DENORM__ */
+}
+
+ATF_TC(nextf_near_1);
+ATF_TC_HEAD(nextf_near_1, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterf/nexttowardf near 1");
+}
+ATF_TC_BODY(nextf_near_1, tc)
+{
+ static const float x[] = {
+ [0] = 1 - 3*FLT_EPSILON/2,
+ [1] = 1 - 2*FLT_EPSILON/2,
+ [2] = 1 - FLT_EPSILON/2,
+ [3] = 1,
+ [4] = 1 + FLT_EPSILON,
+ [5] = 1 + 2*FLT_EPSILON,
+ [6] = 1 + 3*FLT_EPSILON,
+ };
+
+ checkf(x, __arraycount(x));
+}
+
+ATF_TC(nextf_near_1_5);
+ATF_TC_HEAD(nextf_near_1_5, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterf/nexttowardf near 1.5");
+}
+ATF_TC_BODY(nextf_near_1_5, tc)
+{
+ static const float x[] = {
+ [0] = 1.5 - 3*FLT_EPSILON,
+ [1] = 1.5 - 2*FLT_EPSILON,
+ [2] = 1.5 - FLT_EPSILON,
+ [3] = 1.5,
+ [4] = 1.5 + FLT_EPSILON,
+ [5] = 1.5 + 2*FLT_EPSILON,
+ [6] = 1.5 + 3*FLT_EPSILON,
+ };
+
+ checkf(x, __arraycount(x));
+}
+
+ATF_TC(nextf_near_infinity);
+ATF_TC_HEAD(nextf_near_infinity, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterf/nexttowardf near infinity");
+}
+ATF_TC_BODY(nextf_near_infinity, tc)
+{
+ static const float x[] = {
+ [0] = FLT_MAX,
+ [1] = INFINITY,
+ };
+ volatile float t;
+
+ if (!isinf(INFINITY))
+ atf_tc_skip("no infinities on this architecture");
+
+ checkf(x, __arraycount(x));
+
+ ATF_CHECK_EQ_MSG((t = nextafterf(INFINITY, INFINITY)), INFINITY,
+ "t=%a=%g", t, t);
+ ATF_CHECK_EQ_MSG((t = nextafterf(-INFINITY, -INFINITY)), -INFINITY,
+ "t=%a=%g", t, t);
+}
+
+ATF_TC(nextl_nan);
+ATF_TC_HEAD(nextl_nan, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterl/nexttowardl on NaN");
+}
+ATF_TC_BODY(nextl_nan, tc)
+{
+#ifdef NAN
+ /* XXX verify the NaN is quiet */
+ ATF_CHECK(isnan(nextafterl(NAN, 0)));
+ ATF_CHECK(isnan(nexttowardl(NAN, 0)));
+ ATF_CHECK(isnan(nextafterl(0, NAN)));
+ ATF_CHECK(isnan(nexttowardl(0, NAN)));
+#else
+ atf_tc_skip("no NaNs on this architecture");
+#endif
+}
+
+ATF_TC(nextl_signed_0);
+ATF_TC_HEAD(nextl_signed_0, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterl/nexttowardl on signed 0");
+}
+ATF_TC_BODY(nextl_signed_0, tc)
+{
+ volatile long double z_pos = +0.;
+ volatile long double z_neg = -0.;
+#if __LDBL_HAS_DENORM__
+ volatile long double m = __LDBL_DENORM_MIN__;
+#else
+ volatile long double m = LDBL_MIN;
+#endif
+
+ if (signbit(z_pos) == signbit(z_neg))
+ atf_tc_skip("no signed zeroes on this architecture");
+
+ /*
+ * `nextUp(x) is the least floating-point number in the format
+ * of x that compares greater than x. [...] nextDown(x) is
+ * -nextUp(-x).'
+ * --IEEE 754-2019, 5.3.1 General operations, p. 19
+ *
+ * Verify that nextafterl and nexttowardl, which implement the
+ * nextUp and nextDown operations, obey this rule and don't
+ * send -0 to +0 or +0 to -0, respectively.
+ */
+
+ CHECK(0, nextafterl, z_neg, +INFINITY, m);
+ CHECK(1, nexttowardl, z_neg, +INFINITY, m);
+ CHECK(2, nextafterl, z_pos, +INFINITY, m);
+ CHECK(3, nexttowardl, z_pos, +INFINITY, m);
+
+ CHECK(4, nextafterl, z_pos, -INFINITY, -m);
+ CHECK(5, nexttowardl, z_pos, -INFINITY, -m);
+ CHECK(6, nextafterl, z_neg, -INFINITY, -m);
+ CHECK(7, nexttowardl, z_neg, -INFINITY, -m);
+
+ /*
+ * `If x is the negative number of least magnitude in x's
+ * format, nextUp(x) is -0.'
+ * --IEEE 754-2019, 5.3.1 General operations, p. 19
+ */
+ CHECK(8, nextafterl, -m, +INFINITY, 0);
+ CHECK(9, nexttowardl, -m, +INFINITY, 0);
+ ATF_CHECK(signbit(nextafterl(-m, +INFINITY)) != 0);
+ CHECK(10, nextafterl, m, -INFINITY, 0);
+ CHECK(11, nexttowardl, m, -INFINITY, 0);
+ ATF_CHECK(signbit(nextafterl(m, -INFINITY)) == 0);
+}
+
+ATF_TC(nextl_near_0);
+ATF_TC_HEAD(nextl_near_0, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterl/nexttowardl near 0");
+}
+ATF_TC_BODY(nextl_near_0, tc)
+{
+ static const long double x[] = {
+ [0] = 0,
+#if __LDBL_HAS_DENORM__
+ [1] = __LDBL_DENORM_MIN__,
+ [2] = 2*__LDBL_DENORM_MIN__,
+ [3] = 3*__LDBL_DENORM_MIN__,
+ [4] = 4*__LDBL_DENORM_MIN__,
+#else
+ [1] = LDBL_MIN,
+ [2] = LDBL_MIN*(1 + LDBL_EPSILON),
+ [3] = LDBL_MIN*(1 + 2*LDBL_EPSILON),
+ [4] = LDBL_MIN*(1 + 3*LDBL_EPSILON),
+#endif
+ };
+
+ checkl(x, __arraycount(x));
+}
+
+ATF_TC(nextl_near_sub_normal);
+ATF_TC_HEAD(nextl_near_sub_normal, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "nextafterl/nexttowardl near the subnormal/normal boundary");
+}
+ATF_TC_BODY(nextl_near_sub_normal, tc)
+{
+#if __LDBL_HAS_DENORM__
+ static const long double x[] = {
+ [0] = LDBL_MIN - 3*__LDBL_DENORM_MIN__,
+ [1] = LDBL_MIN - 2*__LDBL_DENORM_MIN__,
+ [2] = LDBL_MIN - __LDBL_DENORM_MIN__,
+ [3] = LDBL_MIN,
+ [4] = LDBL_MIN + __LDBL_DENORM_MIN__,
+ [5] = LDBL_MIN + 2*__LDBL_DENORM_MIN__,
+ [6] = LDBL_MIN + 3*__LDBL_DENORM_MIN__,
+ };
+
+ checkl(x, __arraycount(x));
+#else /* !__LDBL_HAS_DENORM__ */
+ atf_tc_skip("no subnormals on this architecture");
+#endif /* !__LDBL_HAS_DENORM__ */
+}
+
+ATF_TC(nextl_near_1);
+ATF_TC_HEAD(nextl_near_1, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterl/nexttowardl near 1");
+}
+ATF_TC_BODY(nextl_near_1, tc)
+{
+ static const long double x[] = {
+ [0] = 1 - 3*LDBL_EPSILON/2,
+ [1] = 1 - 2*LDBL_EPSILON/2,
+ [2] = 1 - LDBL_EPSILON/2,
+ [3] = 1,
+ [4] = 1 + LDBL_EPSILON,
+ [5] = 1 + 2*LDBL_EPSILON,
+ [6] = 1 + 3*LDBL_EPSILON,
+ };
+
+ checkl(x, __arraycount(x));
+}
+
+ATF_TC(nextl_near_1_5);
+ATF_TC_HEAD(nextl_near_1_5, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterl/nexttowardl near 1.5");
+}
+ATF_TC_BODY(nextl_near_1_5, tc)
+{
+ static const long double x[] = {
+ [0] = 1.5 - 3*LDBL_EPSILON,
+ [1] = 1.5 - 2*LDBL_EPSILON,
+ [2] = 1.5 - LDBL_EPSILON,
+ [3] = 1.5,
+ [4] = 1.5 + LDBL_EPSILON,
+ [5] = 1.5 + 2*LDBL_EPSILON,
+ [6] = 1.5 + 3*LDBL_EPSILON,
+ };
+
+ checkl(x, __arraycount(x));
+}
+
+ATF_TC(nextl_near_infinity);
+ATF_TC_HEAD(nextl_near_infinity, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "nextafterl/nexttowardl near infinity");
+}
+ATF_TC_BODY(nextl_near_infinity, tc)
+{
+ static const long double x[] = {
+ [0] = LDBL_MAX,
+ [1] = INFINITY,
+ };
+ volatile long double t;
+
+ if (!isinf(INFINITY))
+ atf_tc_skip("no infinities on this architecture");
+
+ checkl(x, __arraycount(x));
+
+ ATF_CHECK_EQ_MSG((t = nextafterl(INFINITY, INFINITY)), INFINITY,
+ "t=%La=%Lg", t, t);
+ ATF_CHECK_EQ_MSG((t = nextafterl(-INFINITY, -INFINITY)), -INFINITY,
+ "t=%La=%Lg", t, t);
+}
+
+#endif /* __vax__ */
+
+ATF_TP_ADD_TCS(tp)
+{
+
+#ifdef __vax__
+ ATF_TP_ADD_TC(tp, vaxafter);
+#else
+ ATF_TP_ADD_TC(tp, next_nan);
+ ATF_TP_ADD_TC(tp, next_near_0);
+ ATF_TP_ADD_TC(tp, next_near_1);
+ ATF_TP_ADD_TC(tp, next_near_1_5);
+ ATF_TP_ADD_TC(tp, next_near_infinity);
+ ATF_TP_ADD_TC(tp, next_near_sub_normal);
+ ATF_TP_ADD_TC(tp, next_signed_0);
+ ATF_TP_ADD_TC(tp, nextf_nan);
+ ATF_TP_ADD_TC(tp, nextf_near_0);
+ ATF_TP_ADD_TC(tp, nextf_near_1);
+ ATF_TP_ADD_TC(tp, nextf_near_1_5);
+ ATF_TP_ADD_TC(tp, nextf_near_infinity);
+ ATF_TP_ADD_TC(tp, nextf_near_sub_normal);
+ ATF_TP_ADD_TC(tp, nextf_signed_0);
+ ATF_TP_ADD_TC(tp, nextl_nan);
+ ATF_TP_ADD_TC(tp, nextl_near_0);
+ ATF_TP_ADD_TC(tp, nextl_near_1);
+ ATF_TP_ADD_TC(tp, nextl_near_1_5);
+ ATF_TP_ADD_TC(tp, nextl_near_infinity);
+ ATF_TP_ADD_TC(tp, nextl_near_sub_normal);
+ ATF_TP_ADD_TC(tp, nextl_signed_0);
+#endif
+ return atf_no_error();
+}
diff --git a/lib/libm/t_remquo.c b/lib/libm/t_remquo.c
new file mode 100644
index 000000000000..a60e93877335
--- /dev/null
+++ b/lib/libm/t_remquo.c
@@ -0,0 +1,126 @@
+/* $NetBSD: t_remquo.c,v 1.2 2024/09/20 22:24:51 rin Exp $ */
+
+/*-
+ * Copyright (c) 2011 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Jukka Ruohonen and Greg Troxel.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <atf-c.h>
+#include <float.h>
+#include <math.h>
+
+/*
+ * remquo(3)
+ */
+ATF_TC(remquo_args);
+ATF_TC_HEAD(remquo_args, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test some selected arguments");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, remquo_args);
+
+ return atf_no_error();
+}
+
+#ifdef __vax__
+
+ATF_TC_BODY(remquo_args, tc)
+{
+ atf_tc_expect_fail("PR 57881: vax libm is missing various symbols");
+ atf_tc_fail("missing remquo on vax");
+}
+
+#else /* !__vax__ */
+
+static const struct {
+ double x;
+ double y;
+ double r; /* expected */
+ int quo; /* expected */
+} args[] = {
+ { -135.0, -90.0, 45.0, 2 },
+ { -45.0, -90.0, -45.0, 8 },
+ { 45.0, -90.0, 45.0, -8 },
+ { 135.0, -90.0, -45.0, -2 },
+ { -180.0, 90.0, -0.0, -2 },
+ { -135.0, 90.0, 45.0, -2 },
+ { -90.0, 90.0, -0.0, -1 },
+ { -45.0, 90.0, -45.0, -8 },
+ { 0.0, 90.0, 0.0, 0 },
+ { 45.0, 90.0, 45.0, 8 },
+ { 90.0, 90.0, 0.0, 1 },
+ { 135.0, 90.0, -45.0, 2 },
+ { 180.0, 90.0, 0.0, 2 },
+};
+
+ATF_TC_BODY(remquo_args, tc)
+{
+ const double eps = DBL_EPSILON;
+ size_t i;
+
+ for (i = 0; i < __arraycount(args); i++) {
+ double x = args[i].x;
+ double y = args[i].y;
+ double r;
+ double r_exp = args[i].r;
+ int quo;
+ int quo_exp = args[i].quo;
+
+ bool ok = true;
+
+ r = remquo(x, y, &quo);
+
+ ok &= (fabs(r - r_exp) <= eps);
+
+ /*
+ * For now, consider 0 to have positive sign.
+ */
+ if (quo_exp < 0 && quo >= 0)
+ ok = false;
+ if (quo_exp >= 0 && quo < 0)
+ ok = false;
+
+ /*
+ * The specification requires that quo be congruent to
+ * the integer remainder modulo 2^k for some k >=3.
+ */
+ ok &= ((quo & 0x7) == (quo_exp & 0x7));
+
+ if (!ok) {
+ atf_tc_fail_nonfatal("remquo(%g, %g) "
+ "r/quo expected %g/%d != %g/%d",
+ x, y, r_exp, quo_exp, r, quo);
+ }
+ }
+}
+
+#endif /* !__vax__ */
diff --git a/lib/libpthread/cancelpoint.h b/lib/libpthread/cancelpoint.h
new file mode 100644
index 000000000000..0fa332a6042c
--- /dev/null
+++ b/lib/libpthread/cancelpoint.h
@@ -0,0 +1,133 @@
+/* $NetBSD: cancelpoint.h,v 1.1 2025/04/05 11:22:32 riastradh Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TESTS_LIB_LIBPTHREAD_CANCELPOINT_H
+#define TESTS_LIB_LIBPTHREAD_CANCELPOINT_H
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "h_macros.h"
+
+extern pthread_barrier_t bar;
+extern bool cleanup_done;
+
+#if 0
+atomic_bool cancelpointreadydone;
+#endif
+
+static void
+cleanup(void *cookie)
+{
+ bool *cleanup_donep = cookie;
+
+ *cleanup_donep = true;
+}
+
+static void
+cancelpointready(void)
+{
+
+ (void)pthread_barrier_wait(&bar);
+ RL(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+#if 0
+ atomic_store_release(&cancelpointreadydone, true);
+#endif
+}
+
+static void *
+thread_cancelpoint(void *cookie)
+{
+ void (*cancelpoint)(void) = cookie;
+
+ RL(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL));
+ (void)pthread_barrier_wait(&bar);
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+ (*cancelpoint)();
+ pthread_cleanup_pop(/*execute*/0);
+
+ return NULL;
+}
+
+static void
+test_cancelpoint_before(void (*cancelpoint)(void))
+{
+ pthread_t t;
+ void *result;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+ RZ(pthread_create(&t, NULL, &thread_cancelpoint, cancelpoint));
+
+ (void)pthread_barrier_wait(&bar);
+ fprintf(stderr, "cancel\n");
+ RZ(pthread_cancel(t));
+ (void)pthread_barrier_wait(&bar);
+
+ alarm(1);
+ RZ(pthread_join(t, &result));
+ ATF_CHECK_MSG(result == PTHREAD_CANCELED,
+ "result=%p PTHREAD_CANCELED=%p", result, PTHREAD_CANCELED);
+ ATF_CHECK(cleanup_done);
+}
+
+#if 0
+static void
+test_cancelpoint_wakeup(void (*cancelpoint)(void))
+{
+ pthread_t t;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+ RZ(pthread_create(&t, NULL, &cancelpoint_thread, cancelpoint));
+
+ (void)pthread_barrier_wait(&bar);
+ while (!atomic_load_acquire(&cancelpointreadydone))
+ continue;
+ while (!pthread_sleeping(t)) /* XXX find a way to do this */
+ continue;
+ RZ(pthread_cancel(t));
+}
+#endif
+
+#define TEST_CANCELPOINT(CANCELPOINT, XFAIL) \
+ATF_TC(CANCELPOINT); \
+ATF_TC_HEAD(CANCELPOINT, tc) \
+{ \
+ atf_tc_set_md_var(tc, "descr", "Test cancellation point: " \
+ #CANCELPOINT); \
+} \
+ATF_TC_BODY(CANCELPOINT, tc) \
+{ \
+ XFAIL; \
+ test_cancelpoint_before(&CANCELPOINT); \
+}
+#define ADD_TEST_CANCELPOINT(CANCELPOINT) \
+ ATF_TP_ADD_TC(tp, CANCELPOINT)
+
+#endif /* TESTS_LIB_LIBPTHREAD_CANCELPOINT_H */
diff --git a/lib/libpthread/t_cancellation.c b/lib/libpthread/t_cancellation.c
new file mode 100644
index 000000000000..deb4ebb5efae
--- /dev/null
+++ b/lib/libpthread/t_cancellation.c
@@ -0,0 +1,1543 @@
+/* $NetBSD: t_cancellation.c,v 1.4 2025/04/05 11:22:32 riastradh Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_cancellation.c,v 1.4 2025/04/05 11:22:32 riastradh Exp $");
+
+#include <sys/event.h>
+#include <sys/mman.h>
+#include <sys/msg.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include <aio.h>
+#include <atf-c.h>
+#include <fcntl.h>
+#include <mqueue.h>
+#include <paths.h>
+#include <poll.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdatomic.h>
+#include <string.h>
+#include <termios.h>
+#include <threads.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "cancelpoint.h"
+#include "h_macros.h"
+
+static const char *
+c11thrd_err(int error)
+{
+ static char buf[32];
+
+ switch (error) {
+ case thrd_busy: return "thrd_busy";
+ case thrd_nomem: return "thrd_nomem";
+ case thrd_success: return "thrd_success";
+ case thrd_timedout: return "thrd_timedout";
+ default:
+ snprintf(buf, sizeof(buf), "thrd_%d", error);
+ return buf;
+ }
+}
+
+#define RT(x) do \
+{ \
+ int RT_rv = (x); \
+ ATF_REQUIRE_MSG(RT_rv == 0, "%s: %d (%s)", \
+ #x, RT_rv, c11thrd_err(RT_rv)); \
+} while (0)
+
+pthread_barrier_t bar;
+bool cleanup_done;
+
+/* POSIX style */
+static void *
+emptythread(void *cookie)
+{
+ return NULL;
+}
+
+/* C11 style */
+static int
+emptythrd(void *cookie)
+{
+ return 123;
+}
+
+static void
+cleanup_pthread_join(void *cookie)
+{
+ pthread_t *tp = cookie;
+ void *result;
+
+ RZ(pthread_join(*tp, &result));
+ ATF_CHECK_MSG(result == NULL, "result=%p", result);
+}
+
+static void
+cleanup_thrd_join(void *cookie)
+{
+ thrd_t *tp = cookie;
+ int result;
+
+ RT(thrd_join(*tp, &result));
+ ATF_CHECK_MSG(result == 123, "result=%d", result);
+}
+
+static void
+cleanup_msgid(void *cookie)
+{
+ int *msgidp = cookie;
+
+ /*
+ * These message queue identifiers are persistent, so make sure
+ * to clean them up; otherwise the operator will have to run
+ * `ipcrm -q all' from time to time or else the tests will fail
+ * with ENOSPC.
+ */
+ RL(msgctl(*msgidp, IPC_RMID, NULL));
+}
+
+/*
+ * List of cancellation points in POSIX:
+ *
+ * https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/V2_chap02.html#tag_16_09_05_02
+ */
+
+static int
+acceptsetup(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_STREAM, 0));
+ RL(bind(sock, (const struct sockaddr *)&sun, sizeof(sun)));
+ RL(listen(sock, 1));
+
+ return sock;
+}
+
+static void
+cancelpoint_accept(void)
+{
+ const int sock = acceptsetup();
+
+ cancelpointready();
+ RL(accept(sock, NULL, NULL));
+}
+
+static void
+cancelpoint_accept4(void)
+{
+ const int sock = acceptsetup();
+
+ cancelpointready();
+ RL(accept4(sock, NULL, NULL, O_CLOEXEC));
+}
+
+static void
+cancelpoint_aio_suspend(void)
+{
+ int fd[2];
+ char buf[32];
+ struct aiocb aio = {
+ .aio_offset = 0,
+ .aio_buf = buf,
+ .aio_nbytes = sizeof(buf),
+ .aio_fildes = -1,
+ };
+ const struct aiocb *const aiolist[] = { &aio };
+
+ RL(pipe(fd));
+ aio.aio_fildes = fd[0];
+ RL(aio_read(&aio));
+ cancelpointready();
+ RL(aio_suspend(aiolist, __arraycount(aiolist), NULL));
+}
+
+static void
+cancelpoint_clock_nanosleep(void)
+{
+ /* XXX test all CLOCK_*? */
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ cancelpointready();
+ RL(clock_nanosleep(CLOCK_MONOTONIC, 0, &t, NULL));
+}
+
+static void
+cancelpoint_close(void)
+{
+ int fd;
+
+ RL(fd = open("/dev/null", O_RDWR));
+ cancelpointready();
+ RL(close(fd));
+}
+
+static void
+cancelpoint_cnd_timedwait(void)
+{
+ cnd_t cnd;
+ mtx_t mtx;
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ RT(cnd_init(&cnd));
+ RT(mtx_init(&mtx, mtx_plain));
+ cancelpointready();
+ RT(mtx_lock(&mtx));
+ RT(cnd_timedwait(&cnd, &mtx, &t));
+ RT(mtx_unlock(&mtx));
+}
+
+static void
+cancelpoint_cnd_wait(void)
+{
+ cnd_t cnd;
+ mtx_t mtx;
+
+ RT(cnd_init(&cnd));
+ RT(mtx_init(&mtx, mtx_plain));
+ cancelpointready();
+ RT(mtx_lock(&mtx));
+ RT(cnd_wait(&cnd, &mtx));
+ RT(mtx_unlock(&mtx));
+}
+
+static void
+cancelpoint_connect(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_STREAM, 0));
+ cancelpointready();
+ RL(connect(sock, (const struct sockaddr *)&sun, sizeof(sun)));
+}
+
+static void
+cancelpoint_creat(void)
+{
+
+ cancelpointready();
+ RL(creat("file", 0666));
+}
+
+static void
+cancelpoint_fcntl_F_SETLKW(void)
+{
+ int fd;
+ struct flock fl = {
+ .l_start = 0,
+ .l_len = 0,
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ };
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(fcntl(fd, F_SETLKW, &fl));
+}
+
+static void
+cancelpoint_fcntl_F_OFD_SETLKW(void)
+{
+#ifdef F_OFD_SETLKW
+ int fd;
+ struct flock fl = {
+ .l_start = 0,
+ .l_len = 0,
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ };
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(fcntl(fd, F_OFD_SETLKW, &fl));
+#else
+ atf_tc_expect_fail("PR kern/59241: POSIX.1-2024:"
+ " OFD-owned file locks");
+ atf_tc_fail("no F_OFD_SETLKW");
+#endif
+}
+
+static void
+cancelpoint_fdatasync(void)
+{
+ int fd;
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(fdatasync(fd));
+}
+
+static void
+cancelpoint_fsync(void)
+{
+ int fd;
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(fsync(fd));
+}
+
+static void
+cancelpoint_kevent(void)
+{
+ int kq;
+ struct kevent ev;
+
+ EV_SET(&ev, SIGUSR1, EVFILT_SIGNAL, EV_ADD|EV_ENABLE,
+ /*fflags*/0, /*data*/0, /*udata*/0);
+
+ RL(kq = kqueue());
+ RL(kevent(kq, &ev, 1, NULL, 1, &(const struct timespec){0,0}));
+ cancelpointready();
+ RL(kevent(kq, NULL, 0, &ev, 1, NULL));
+}
+
+static void
+cancelpoint_lockf_F_LOCK(void)
+{
+ int fd;
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(lockf(fd, F_LOCK, 0));
+}
+
+static void
+cancelpoint_mq_receive(void)
+{
+ mqd_t mq;
+ char buf[32];
+
+ RL(mq = mq_open("mq", O_RDWR|O_CREAT, 0666, NULL));
+ cancelpointready();
+ RL(mq_receive(mq, buf, sizeof(buf), NULL));
+}
+
+static void
+cancelpoint_mq_send(void)
+{
+ mqd_t mq;
+ char buf[32] = {0};
+
+ RL(mq = mq_open("mq", O_RDWR|O_CREAT, 0666, NULL));
+ cancelpointready();
+ RL(mq_send(mq, buf, sizeof(buf), 0));
+}
+
+static void
+cancelpoint_mq_timedreceive(void)
+{
+ mqd_t mq;
+ char buf[32];
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ RL(mq = mq_open("mq", O_RDWR|O_CREAT, 0666, NULL));
+ cancelpointready();
+ RL(mq_timedreceive(mq, buf, sizeof(buf), NULL, &t));
+}
+
+static void
+cancelpoint_mq_timedsend(void)
+{
+ mqd_t mq;
+ char buf[32] = {0};
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ RL(mq = mq_open("mq", O_RDWR|O_CREAT, 0666, NULL));
+ cancelpointready();
+ RL(mq_timedsend(mq, buf, sizeof(buf), 0, &t));
+}
+
+static void
+cancelpoint_msgrcv(void)
+{
+ int msgid;
+ char buf[32];
+
+ RL(msgid = msgget(IPC_PRIVATE, IPC_CREAT));
+ pthread_cleanup_push(&cleanup_msgid, &msgid);
+ cancelpointready();
+ RL(msgrcv(msgid, buf, sizeof(buf), 0, 0));
+ pthread_cleanup_pop(/*execute*/1);
+}
+
+static void
+cancelpoint_msgsnd(void)
+{
+ int msgid;
+ char buf[32] = {0};
+
+ RL(msgid = msgget(IPC_PRIVATE, IPC_CREAT));
+ pthread_cleanup_push(&cleanup_msgid, &msgid);
+ cancelpointready();
+ RL(msgsnd(msgid, buf, sizeof(buf), 0));
+ pthread_cleanup_pop(/*execute*/1);
+}
+
+static void
+cancelpoint_msync(void)
+{
+ const unsigned long pagesize = sysconf(_SC_PAGESIZE);
+ int fd;
+ void *map;
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ RL(ftruncate(fd, pagesize));
+ REQUIRE_LIBC(map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
+ MAP_SHARED, fd, 0),
+ MAP_FAILED);
+ cancelpointready();
+ RL(msync(map, pagesize, MS_SYNC));
+}
+
+static void
+cancelpoint_nanosleep(void)
+{
+ /* XXX test all CLOCK_*? */
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ cancelpointready();
+ RL(nanosleep(&t, NULL));
+}
+
+static void
+cancelpoint_open(void)
+{
+
+ cancelpointready();
+ RL(open("file", O_RDWR));
+}
+
+static void
+cancelpoint_openat(void)
+{
+
+ cancelpointready();
+ RL(openat(AT_FDCWD, "file", O_RDWR));
+}
+
+static void
+cancelpoint_pause(void)
+{
+
+ cancelpointready();
+ RL(pause());
+}
+
+static void
+cancelpoint_poll(void)
+{
+ int fd[2];
+ struct pollfd pfd;
+
+ RL(pipe(fd));
+ pfd.fd = fd[0];
+ pfd.events = POLLIN;
+ cancelpointready();
+ RL(poll(&pfd, 1, 1000));
+}
+
+static void
+cancelpoint_posix_close(void)
+{
+#if 0
+ int fd;
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(posix_close(fd, POSIX_CLOSE_RESTART));
+#else
+ atf_tc_expect_fail("PR kern/58929: POSIX.1-2024 compliance:"
+ " posix_close, POSIX_CLOSE_RESTART");
+ atf_tc_fail("no posix_close");
+#endif
+}
+
+static void
+cancelpoint_ppoll(void)
+{
+ int fd[2];
+ struct pollfd pfd;
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ RL(pipe(fd));
+ pfd.fd = fd[0];
+ pfd.events = POLLIN;
+ cancelpointready();
+ RL(ppoll(&pfd, 1, &t, NULL));
+}
+
+static void
+cancelpoint_pread(void)
+{
+ int fd;
+ char buf[1];
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(pread(fd, buf, sizeof(buf), 1));
+}
+
+
+static void
+cancelpoint_pselect(void)
+{
+ int fd[2];
+ fd_set readfd;
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ FD_ZERO(&readfd);
+
+ RL(pipe(fd));
+ FD_SET(fd[0], &readfd);
+ cancelpointready();
+ RL(pselect(fd[0] + 1, &readfd, NULL, NULL, &t, NULL));
+}
+
+static void
+cancelpoint_pthread_cond_clockwait(void)
+{
+#if 0
+ pthread_cond_t cond;
+ pthread_mutex_t mutex;
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ RZ(pthread_cond_init(&cond, NULL));
+ RZ(pthread_mutex_init(&mutex, NULL));
+ cancelpointready();
+ RZ(pthread_mutex_lock(&mutex));
+ RZ(pthread_cond_clockwait(&cond, &mutex, CLOCK_MONOTONIC, &t));
+ RZ(pthread_mutex_unlock(&mutex));
+#else
+ atf_tc_expect_fail("PR lib/59142: POSIX.1-2024:"
+ " pthread_cond_clockwait and company");
+ atf_tc_fail("no posix_cond_clockwait");
+#endif
+}
+
+static void
+cancelpoint_pthread_cond_timedwait(void)
+{
+ pthread_cond_t cond;
+ pthread_mutex_t mutex;
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ RZ(pthread_cond_init(&cond, NULL));
+ RZ(pthread_mutex_init(&mutex, NULL));
+ cancelpointready();
+ RZ(pthread_mutex_lock(&mutex));
+ RZ(pthread_cond_timedwait(&cond, &mutex, &t));
+ RZ(pthread_mutex_unlock(&mutex));
+}
+
+static void
+cancelpoint_pthread_cond_wait(void)
+{
+ pthread_cond_t cond;
+ pthread_mutex_t mutex;
+
+ RZ(pthread_cond_init(&cond, NULL));
+ RZ(pthread_mutex_init(&mutex, NULL));
+ cancelpointready();
+ RZ(pthread_mutex_lock(&mutex));
+ RZ(pthread_cond_wait(&cond, &mutex));
+ RZ(pthread_mutex_unlock(&mutex));
+}
+
+static void
+cancelpoint_pthread_join(void)
+{
+ pthread_t t;
+
+ RZ(pthread_create(&t, NULL, &emptythread, NULL));
+ pthread_cleanup_push(&cleanup_pthread_join, &t);
+ cancelpointready();
+ RZ(pthread_join(t, NULL));
+ pthread_cleanup_pop(/*execute*/0);
+}
+
+static void
+cancelpoint_pthread_testcancel(void)
+{
+
+ cancelpointready();
+ pthread_testcancel();
+}
+
+static void
+cancelpoint_pwrite(void)
+{
+ int fd;
+ char buf[1] = {0};
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(pwrite(fd, buf, sizeof(buf), 1));
+}
+
+static void
+cancelpoint_read(void)
+{
+ int fd;
+ char buf[1];
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(read(fd, buf, sizeof(buf)));
+}
+
+static void
+cancelpoint_readv(void)
+{
+ int fd;
+ char buf[1];
+ struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(readv(fd, &iov, 1));
+}
+
+static void
+cancelpoint_recv(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+ char buf[1];
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_DGRAM, 0));
+ RL(bind(sock, (const struct sockaddr *)&sun, sizeof(sun)));
+ cancelpointready();
+ RL(recv(sock, buf, sizeof(buf), 0));
+}
+
+static void
+cancelpoint_recvfrom(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+ char buf[1];
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_DGRAM, 0));
+ RL(bind(sock, (const struct sockaddr *)&sun, sizeof(sun)));
+ cancelpointready();
+ RL(recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&ss, &len));
+}
+
+static void
+cancelpoint_recvmsg(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+ char buf[1];
+ struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_DGRAM, 0));
+ RL(bind(sock, (const struct sockaddr *)&sun, sizeof(sun)));
+ cancelpointready();
+ RL(recvmsg(sock, &msg, 0));
+}
+
+static void
+cancelpoint_select(void)
+{
+ int fd[2];
+ fd_set readfd;
+ struct timeval t = {.tv_sec = 1, .tv_usec = 0};
+
+ FD_ZERO(&readfd);
+
+ RL(pipe(fd));
+ FD_SET(fd[0], &readfd);
+ cancelpointready();
+ RL(select(fd[0] + 1, &readfd, NULL, NULL, &t));
+}
+
+static void
+cancelpoint_send(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+ char buf[1] = {0};
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_DGRAM, 0));
+ RL(bind(sock, (const struct sockaddr *)&sun, sizeof(sun)));
+ cancelpointready();
+ RL(send(sock, buf, sizeof(buf), 0));
+}
+
+static void
+cancelpoint_sendto(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+ char buf[1] = {0};
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_DGRAM, 0));
+ cancelpointready();
+ RL(sendto(sock, buf, sizeof(buf), 0, (const struct sockaddr *)&sun,
+ sizeof(sun)));
+}
+
+static void
+cancelpoint_sendmsg(void)
+{
+ struct sockaddr_un sun = { .sun_family = AF_LOCAL };
+ int sock;
+ char buf[1] = {0};
+ struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
+ struct msghdr msg = {
+ .msg_name = (struct sockaddr *)&sun,
+ .msg_namelen = sizeof(sun),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ };
+
+ strncpy(sun.sun_path, "sock", sizeof(sun.sun_path));
+ RL(sock = socket(PF_LOCAL, SOCK_DGRAM, 0));
+ cancelpointready();
+ RL(sendmsg(sock, &msg, 0));
+}
+
+static void
+cancelpoint_sigsuspend(void)
+{
+ sigset_t mask, omask;
+
+ RL(sigfillset(&mask));
+ RL(sigprocmask(SIG_BLOCK, &mask, &omask));
+ cancelpointready();
+ RL(sigsuspend(&omask));
+}
+
+static void
+cancelpoint_sigtimedwait(void)
+{
+ sigset_t mask, omask;
+ siginfo_t info;
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ RL(sigfillset(&mask));
+ RL(sigprocmask(SIG_BLOCK, &mask, &omask));
+ cancelpointready();
+ RL(sigtimedwait(&omask, &info, &t));
+}
+
+static void
+cancelpoint_sigwait(void)
+{
+ sigset_t mask, omask;
+ int sig;
+
+ RL(sigfillset(&mask));
+ RL(sigprocmask(SIG_BLOCK, &mask, &omask));
+ cancelpointready();
+ RL(sigwait(&omask, &sig));
+}
+
+static void
+cancelpoint_sigwaitinfo(void)
+{
+ sigset_t mask, omask;
+ siginfo_t info;
+
+ RL(sigfillset(&mask));
+ RL(sigprocmask(SIG_BLOCK, &mask, &omask));
+ cancelpointready();
+ RL(sigwaitinfo(&omask, &info));
+}
+
+static void
+cancelpoint_sleep(void)
+{
+
+ cancelpointready();
+ (void)sleep(1);
+}
+
+static void
+cancelpoint_tcdrain(void)
+{
+ int hostfd, appfd;
+ char *pts;
+
+ RL(hostfd = posix_openpt(O_RDWR|O_NOCTTY));
+ RL(grantpt(hostfd));
+ RL(unlockpt(hostfd));
+ REQUIRE_LIBC(pts = ptsname(hostfd), NULL);
+ RL(appfd = open(pts, O_RDWR|O_NOCTTY));
+ cancelpointready();
+ RL(tcdrain(appfd));
+}
+
+static void
+cancelpoint_thrd_join(void)
+{
+ thrd_t t;
+
+ RT(thrd_create(&t, &emptythrd, NULL));
+ pthread_cleanup_push(&cleanup_thrd_join, &t);
+ cancelpointready();
+ RT(thrd_join(t, NULL));
+ pthread_cleanup_pop(/*execute*/0);
+}
+
+static void
+cancelpoint_thrd_sleep(void)
+{
+ struct timespec t = {.tv_sec = 1, .tv_nsec = 0};
+
+ cancelpointready();
+ RT(thrd_sleep(&t, NULL));
+}
+
+static void
+cancelpoint_wait(void)
+{
+
+ cancelpointready();
+ RL(wait(NULL));
+}
+
+static void
+cancelpoint_waitid(void)
+{
+
+ cancelpointready();
+ RL(waitid(P_ALL, 0, NULL, 0));
+}
+
+static void
+cancelpoint_waitpid(void)
+{
+
+ cancelpointready();
+ RL(waitpid(-1, NULL, 0));
+}
+
+static void
+cancelpoint_write(void)
+{
+ int fd;
+ char buf[1] = {0};
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(write(fd, buf, sizeof(buf)));
+}
+
+static void
+cancelpoint_writev(void)
+{
+ int fd;
+ char buf[1] = {0};
+ struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf) };
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ cancelpointready();
+ RL(writev(fd, &iov, 1));
+}
+
+TEST_CANCELPOINT(cancelpoint_accept, __nothing)
+TEST_CANCELPOINT(cancelpoint_accept4, __nothing)
+TEST_CANCELPOINT(cancelpoint_aio_suspend, __nothing)
+TEST_CANCELPOINT(cancelpoint_clock_nanosleep, __nothing)
+TEST_CANCELPOINT(cancelpoint_close, __nothing)
+TEST_CANCELPOINT(cancelpoint_cnd_timedwait, __nothing)
+TEST_CANCELPOINT(cancelpoint_cnd_wait, __nothing)
+TEST_CANCELPOINT(cancelpoint_connect, __nothing)
+TEST_CANCELPOINT(cancelpoint_creat, __nothing)
+TEST_CANCELPOINT(cancelpoint_fcntl_F_SETLKW, __nothing)
+TEST_CANCELPOINT(cancelpoint_fcntl_F_OFD_SETLKW, __nothing)
+TEST_CANCELPOINT(cancelpoint_fdatasync, __nothing)
+TEST_CANCELPOINT(cancelpoint_fsync, __nothing)
+TEST_CANCELPOINT(cancelpoint_kevent, __nothing)
+TEST_CANCELPOINT(cancelpoint_lockf_F_LOCK, __nothing)
+TEST_CANCELPOINT(cancelpoint_mq_receive, __nothing)
+TEST_CANCELPOINT(cancelpoint_mq_send, __nothing)
+TEST_CANCELPOINT(cancelpoint_mq_timedreceive, __nothing)
+TEST_CANCELPOINT(cancelpoint_mq_timedsend, __nothing)
+TEST_CANCELPOINT(cancelpoint_msgrcv, __nothing)
+TEST_CANCELPOINT(cancelpoint_msgsnd, __nothing)
+TEST_CANCELPOINT(cancelpoint_msync, __nothing)
+TEST_CANCELPOINT(cancelpoint_nanosleep, __nothing)
+TEST_CANCELPOINT(cancelpoint_open, __nothing)
+TEST_CANCELPOINT(cancelpoint_openat, __nothing)
+TEST_CANCELPOINT(cancelpoint_pause, __nothing)
+TEST_CANCELPOINT(cancelpoint_poll, __nothing)
+TEST_CANCELPOINT(cancelpoint_posix_close, __nothing)
+TEST_CANCELPOINT(cancelpoint_ppoll, __nothing)
+TEST_CANCELPOINT(cancelpoint_pread, __nothing)
+TEST_CANCELPOINT(cancelpoint_pselect, __nothing)
+TEST_CANCELPOINT(cancelpoint_pthread_cond_clockwait, __nothing)
+TEST_CANCELPOINT(cancelpoint_pthread_cond_timedwait, __nothing)
+TEST_CANCELPOINT(cancelpoint_pthread_cond_wait, __nothing)
+TEST_CANCELPOINT(cancelpoint_pthread_join, __nothing)
+TEST_CANCELPOINT(cancelpoint_pthread_testcancel, __nothing)
+TEST_CANCELPOINT(cancelpoint_pwrite, __nothing)
+TEST_CANCELPOINT(cancelpoint_read, __nothing)
+TEST_CANCELPOINT(cancelpoint_readv, __nothing)
+TEST_CANCELPOINT(cancelpoint_recv, __nothing)
+TEST_CANCELPOINT(cancelpoint_recvfrom, __nothing)
+TEST_CANCELPOINT(cancelpoint_recvmsg, __nothing)
+TEST_CANCELPOINT(cancelpoint_select, __nothing)
+TEST_CANCELPOINT(cancelpoint_send, __nothing)
+TEST_CANCELPOINT(cancelpoint_sendto, __nothing)
+TEST_CANCELPOINT(cancelpoint_sendmsg, __nothing)
+TEST_CANCELPOINT(cancelpoint_sigsuspend, __nothing)
+TEST_CANCELPOINT(cancelpoint_sigtimedwait, __nothing)
+TEST_CANCELPOINT(cancelpoint_sigwait, __nothing)
+TEST_CANCELPOINT(cancelpoint_sigwaitinfo, __nothing)
+TEST_CANCELPOINT(cancelpoint_sleep, __nothing)
+TEST_CANCELPOINT(cancelpoint_tcdrain, __nothing)
+TEST_CANCELPOINT(cancelpoint_thrd_join, __nothing)
+TEST_CANCELPOINT(cancelpoint_thrd_sleep, __nothing)
+TEST_CANCELPOINT(cancelpoint_wait, __nothing)
+TEST_CANCELPOINT(cancelpoint_waitid, __nothing)
+TEST_CANCELPOINT(cancelpoint_waitpid, __nothing)
+TEST_CANCELPOINT(cancelpoint_write, __nothing)
+TEST_CANCELPOINT(cancelpoint_writev, __nothing)
+
+ATF_TC(cleanuppop0);
+ATF_TC_HEAD(cleanuppop0, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test pthread_cleanup_pop(0)");
+}
+ATF_TC_BODY(cleanuppop0, tc)
+{
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+ pthread_cleanup_pop(/*execute*/0);
+ ATF_CHECK(!cleanup_done);
+}
+
+ATF_TC(cleanuppop1);
+ATF_TC_HEAD(cleanuppop1, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test pthread_cleanup_pop(1)");
+}
+ATF_TC_BODY(cleanuppop1, tc)
+{
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+ pthread_cleanup_pop(/*execute*/1);
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+cancelself_async(void *cookie)
+{
+ int *n = cookie;
+
+ RZ(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL));
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+
+ *n = 1;
+ RZ(pthread_cancel(pthread_self())); /* cancel */
+ *n = 2;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL));
+ pthread_testcancel();
+ *n = 3;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+ pthread_testcancel();
+ *n = 4;
+
+ pthread_cleanup_pop(/*execute*/0);
+ return NULL;
+}
+
+ATF_TC(cancelself_async);
+ATF_TC_HEAD(cancelself_async, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test pthread_cancel(pthread_self()) async");
+}
+ATF_TC_BODY(cancelself_async, tc)
+{
+ int n = 0;
+ pthread_t t;
+
+ RZ(pthread_create(&t, NULL, &cancelself_async, &n));
+
+ alarm(1);
+ RZ(pthread_join(t, NULL));
+
+ atf_tc_expect_fail("lib/59135: PTHREAD_CANCEL_ASYNCHRONOUS"
+ " doesn't do much");
+ ATF_CHECK_MSG(n == 1, "n=%d", n);
+ atf_tc_expect_pass();
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+cancelself_deferred(void *cookie)
+{
+ int *n = cookie;
+
+ /* PTHREAD_CANCEL_DEFERRED by default */
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+
+ *n = 1;
+ RZ(pthread_cancel(pthread_self()));
+ *n = 2;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL));
+ *n = 3;
+ pthread_testcancel();
+ *n = 4;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+ *n = 5;
+ pthread_testcancel(); /* cancel */
+ *n = 6;
+
+ pthread_cleanup_pop(/*execute*/0);
+ return NULL;
+}
+
+ATF_TC(cancelself_deferred);
+ATF_TC_HEAD(cancelself_deferred, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test pthread_cancel(pthread_self()) deferred");
+}
+ATF_TC_BODY(cancelself_deferred, tc)
+{
+ int n = 0;
+ pthread_t t;
+
+ RZ(pthread_create(&t, NULL, &cancelself_deferred, &n));
+
+ alarm(1);
+ RZ(pthread_join(t, NULL));
+
+ ATF_CHECK_MSG(n == 5, "n=%d", n);
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+defaults(void *cookie)
+{
+ int state, type;
+
+ fprintf(stderr, "created thread\n");
+
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &state));
+ RZ(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &type));
+
+ ATF_CHECK_MSG(state == PTHREAD_CANCEL_ENABLE,
+ "state=%d PTHREAD_CANCEL_ENABLE=%d PTHREAD_CANCEL_DISABLE=%d",
+ state, PTHREAD_CANCEL_ENABLE, PTHREAD_CANCEL_DISABLE);
+
+ ATF_CHECK_MSG(type == PTHREAD_CANCEL_DEFERRED,
+ "type=%d"
+ " PTHREAD_CANCEL_DEFERRED=%d PTHREAD_CANCEL_ASYNCHRONOUS=%d",
+ type, PTHREAD_CANCEL_DEFERRED, PTHREAD_CANCEL_ASYNCHRONOUS);
+
+ return NULL;
+}
+
+ATF_TC(defaults);
+ATF_TC_HEAD(defaults, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test default cancelability");
+}
+ATF_TC_BODY(defaults, tc)
+{
+ pthread_t t;
+
+ fprintf(stderr, "initial thread\n");
+ (void)defaults(NULL);
+
+ RZ(pthread_create(&t, NULL, &defaults, NULL));
+
+ alarm(1);
+ RZ(pthread_join(t, NULL));
+}
+
+static void *
+disable_enable(void *cookie)
+{
+ int *n = cookie;
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+
+ *n = 1;
+ pthread_testcancel();
+ *n = 2;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL));
+ *n = 3;
+ (void)pthread_barrier_wait(&bar);
+ *n = 4;
+ pthread_testcancel();
+ *n = 5;
+ (void)pthread_barrier_wait(&bar);
+ *n = 6;
+ pthread_testcancel();
+ *n = 7;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL));
+ *n = 8;
+ pthread_testcancel(); /* cancel */
+ *n = 9;
+
+ pthread_cleanup_pop(/*execute*/0);
+ return NULL;
+}
+
+ATF_TC(disable_enable);
+ATF_TC_HEAD(disable_enable, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test disabling and re-enabling cancellation");
+}
+ATF_TC_BODY(disable_enable, tc)
+{
+ int n = 0;
+ pthread_t t;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+
+ RZ(pthread_create(&t, NULL, &disable_enable, &n));
+
+ (void)pthread_barrier_wait(&bar);
+ RZ(pthread_cancel(t));
+ (void)pthread_barrier_wait(&bar);
+
+ alarm(1);
+ RZ(pthread_join(t, NULL));
+
+ ATF_CHECK_MSG(n == 8, "n=%d", n);
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+notestcancel_loop_async(void *cookie)
+{
+
+ RZ(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL));
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+ (void)pthread_barrier_wait(&bar);
+ for (;;)
+ __insn_barrier();
+ pthread_cleanup_pop(/*execute*/0);
+
+ return NULL;
+}
+
+ATF_TC(notestcancel_loop_async);
+ATF_TC_HEAD(notestcancel_loop_async, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test nothing in a loop with PTHREAD_CANCEL_ASYNCHRONOUS");
+}
+ATF_TC_BODY(notestcancel_loop_async, tc)
+{
+ pthread_t t;
+ void *result;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+ RZ(pthread_create(&t, NULL, &notestcancel_loop_async, NULL));
+
+ (void)pthread_barrier_wait(&bar);
+ RZ(pthread_cancel(t));
+
+ atf_tc_expect_signal(SIGALRM, "lib/59135: PTHREAD_CANCEL_ASYNCHRONOUS"
+ " doesn't do much");
+ alarm(1);
+ RZ(pthread_join(t, &result));
+ ATF_CHECK_MSG(result == PTHREAD_CANCELED,
+ "result=%p PTHREAD_CANCELED=%p", result, PTHREAD_CANCELED);
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+disable_enable_async(void *cookie)
+{
+ int *n = cookie;
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+
+ RZ(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL));
+
+ *n = 1;
+ pthread_testcancel();
+ *n = 2;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL));
+ *n = 3;
+ (void)pthread_barrier_wait(&bar);
+ *n = 4;
+ pthread_testcancel();
+ *n = 5;
+ (void)pthread_barrier_wait(&bar);
+ *n = 6;
+ pthread_testcancel();
+ *n = 7;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)); /* cancel */
+ *n = 8;
+ pthread_testcancel();
+ *n = 9;
+
+ pthread_cleanup_pop(/*execute*/0);
+ return NULL;
+}
+
+ATF_TC(disable_enable_async);
+ATF_TC_HEAD(disable_enable_async, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test disabling and re-enabling cancellation when asynchronous");
+}
+ATF_TC_BODY(disable_enable_async, tc)
+{
+ int n = 0;
+ pthread_t t;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+
+ RZ(pthread_create(&t, NULL, &disable_enable_async, &n));
+
+ (void)pthread_barrier_wait(&bar);
+ RZ(pthread_cancel(t));
+ (void)pthread_barrier_wait(&bar);
+
+ alarm(1);
+ RZ(pthread_join(t, NULL));
+
+ ATF_CHECK_MSG(n == 7, "n=%d", n);
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+disable_enable_setcanceltype_async(void *cookie)
+{
+ int *n = cookie;
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+
+ *n = 1;
+ pthread_testcancel();
+ *n = 2;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL));
+ *n = 3;
+ (void)pthread_barrier_wait(&bar);
+ *n = 4;
+ pthread_testcancel();
+ *n = 5;
+ (void)pthread_barrier_wait(&bar);
+ *n = 6;
+ pthread_testcancel();
+ *n = 7;
+ RZ(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL));
+ *n = 8;
+ pthread_testcancel();
+ *n = 9;
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)); /* cancel */
+ *n = 10;
+ pthread_testcancel();
+ *n = 11;
+
+ pthread_cleanup_pop(/*execute*/0);
+ return NULL;
+}
+
+ATF_TC(disable_enable_setcanceltype_async);
+ATF_TC_HEAD(disable_enable_setcanceltype_async, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test disabling cancellation, setting it async, and re-enabling");
+}
+ATF_TC_BODY(disable_enable_setcanceltype_async, tc)
+{
+ int n = 0;
+ pthread_t t;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+
+ RZ(pthread_create(&t, NULL, &disable_enable_setcanceltype_async, &n));
+
+ (void)pthread_barrier_wait(&bar);
+ RZ(pthread_cancel(t));
+ (void)pthread_barrier_wait(&bar);
+
+ alarm(1);
+ RZ(pthread_join(t, NULL));
+
+ ATF_CHECK_MSG(n == 9, "n=%d", n);
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+setcanceltype_async(void *cookie)
+{
+ int *n = cookie;
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+
+ *n = 1;
+ pthread_testcancel();
+ *n = 2;
+ (void)pthread_barrier_wait(&bar);
+ *n = 3;
+ (void)pthread_barrier_wait(&bar);
+ *n = 4;
+ RZ(pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,
+ NULL)); /* cancel */
+ *n = 5;
+
+ pthread_cleanup_pop(/*execute*/0);
+ return NULL;
+}
+
+ATF_TC(setcanceltype_async);
+ATF_TC_HEAD(setcanceltype_async, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test disabling cancellation, setting it async, and re-enabling");
+}
+ATF_TC_BODY(setcanceltype_async, tc)
+{
+ int n = 0;
+ pthread_t t;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+
+ RZ(pthread_create(&t, NULL, &setcanceltype_async, &n));
+
+ (void)pthread_barrier_wait(&bar);
+ RZ(pthread_cancel(t));
+ (void)pthread_barrier_wait(&bar);
+
+ alarm(1);
+ RZ(pthread_join(t, NULL));
+
+ ATF_CHECK_MSG(n == 4, "n=%d", n);
+ ATF_CHECK(cleanup_done);
+}
+
+static void
+sighandler(int signo)
+{
+ int state;
+
+ RZ(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state));
+ RZ(pthread_setcancelstate(state, NULL));
+}
+
+static void *
+sigsafecancelstate(void *cookie)
+{
+ atomic_ulong *n = cookie;
+ char name[128];
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+ REQUIRE_LIBC(signal(SIGUSR1, &sighandler), SIG_ERR);
+
+ (void)pthread_barrier_wait(&bar);
+
+ while (atomic_load_explicit(n, memory_order_relaxed) != 0) {
+ /*
+ * Do some things that might take the same lock as
+ * pthread_setcancelstate.
+ */
+ RZ(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL));
+ RZ(pthread_getname_np(pthread_self(), name, sizeof(name)));
+ RZ(pthread_setname_np(pthread_self(), "%s", name));
+ }
+
+ pthread_cleanup_pop(/*execute*/1);
+ return NULL;
+}
+
+ATF_TC(sigsafecancelstate);
+ATF_TC_HEAD(sigsafecancelstate, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test pthread_setcancelstate async-signal-safety");
+}
+ATF_TC_BODY(sigsafecancelstate, tc)
+{
+ pthread_t t;
+ atomic_ulong n = 10000;
+ void *result;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+ RZ(pthread_create(&t, NULL, &sigsafecancelstate, &n));
+
+ (void)pthread_barrier_wait(&bar);
+
+ while (atomic_load_explicit(&n, memory_order_relaxed)) {
+ pthread_kill(t, SIGUSR1);
+ atomic_store_explicit(&n,
+ atomic_load_explicit(&n, memory_order_relaxed) - 1,
+ memory_order_relaxed);
+ }
+
+ alarm(1);
+ RZ(pthread_join(t, &result));
+ ATF_CHECK_MSG(result == NULL, "result=%p", result);
+ ATF_CHECK(cleanup_done);
+}
+
+static void *
+testcancel_loop(void *cookie)
+{
+
+ pthread_cleanup_push(&cleanup, &cleanup_done);
+ (void)pthread_barrier_wait(&bar);
+ for (;;)
+ pthread_testcancel();
+ pthread_cleanup_pop(/*execute*/0);
+
+ return NULL;
+}
+
+ATF_TC(testcancel_loop);
+ATF_TC_HEAD(testcancel_loop, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test pthread_testcancel in a loop");
+}
+ATF_TC_BODY(testcancel_loop, tc)
+{
+ pthread_t t;
+ void *result;
+
+ RZ(pthread_barrier_init(&bar, NULL, 2));
+ RZ(pthread_create(&t, NULL, &testcancel_loop, NULL));
+
+ (void)pthread_barrier_wait(&bar);
+ RZ(pthread_cancel(t));
+
+ alarm(1);
+ RZ(pthread_join(t, &result));
+ ATF_CHECK_MSG(result == PTHREAD_CANCELED,
+ "result=%p PTHREAD_CANCELED=%p", result, PTHREAD_CANCELED);
+ ATF_CHECK(cleanup_done);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ADD_TEST_CANCELPOINT(cancelpoint_accept);
+ ADD_TEST_CANCELPOINT(cancelpoint_accept4);
+ ADD_TEST_CANCELPOINT(cancelpoint_aio_suspend);
+ ADD_TEST_CANCELPOINT(cancelpoint_clock_nanosleep);
+ ADD_TEST_CANCELPOINT(cancelpoint_close);
+ ADD_TEST_CANCELPOINT(cancelpoint_cnd_timedwait);
+ ADD_TEST_CANCELPOINT(cancelpoint_cnd_wait);
+ ADD_TEST_CANCELPOINT(cancelpoint_connect);
+ ADD_TEST_CANCELPOINT(cancelpoint_creat);
+ ADD_TEST_CANCELPOINT(cancelpoint_fcntl_F_SETLKW);
+ ADD_TEST_CANCELPOINT(cancelpoint_fcntl_F_OFD_SETLKW);
+ ADD_TEST_CANCELPOINT(cancelpoint_fdatasync);
+ ADD_TEST_CANCELPOINT(cancelpoint_fsync);
+ ADD_TEST_CANCELPOINT(cancelpoint_kevent);
+ ADD_TEST_CANCELPOINT(cancelpoint_lockf_F_LOCK);
+ ADD_TEST_CANCELPOINT(cancelpoint_mq_receive);
+ ADD_TEST_CANCELPOINT(cancelpoint_mq_send);
+ ADD_TEST_CANCELPOINT(cancelpoint_mq_timedreceive);
+ ADD_TEST_CANCELPOINT(cancelpoint_mq_timedsend);
+ ADD_TEST_CANCELPOINT(cancelpoint_msgrcv);
+ ADD_TEST_CANCELPOINT(cancelpoint_msgsnd);
+ ADD_TEST_CANCELPOINT(cancelpoint_msync);
+ ADD_TEST_CANCELPOINT(cancelpoint_nanosleep);
+ ADD_TEST_CANCELPOINT(cancelpoint_open);
+ ADD_TEST_CANCELPOINT(cancelpoint_openat);
+ ADD_TEST_CANCELPOINT(cancelpoint_pause);
+ ADD_TEST_CANCELPOINT(cancelpoint_poll);
+ ADD_TEST_CANCELPOINT(cancelpoint_posix_close);
+ ADD_TEST_CANCELPOINT(cancelpoint_ppoll);
+ ADD_TEST_CANCELPOINT(cancelpoint_pread);
+ ADD_TEST_CANCELPOINT(cancelpoint_pselect);
+ ADD_TEST_CANCELPOINT(cancelpoint_pthread_cond_clockwait);
+ ADD_TEST_CANCELPOINT(cancelpoint_pthread_cond_timedwait);
+ ADD_TEST_CANCELPOINT(cancelpoint_pthread_cond_wait);
+ ADD_TEST_CANCELPOINT(cancelpoint_pthread_join);
+ ADD_TEST_CANCELPOINT(cancelpoint_pthread_testcancel);
+ ADD_TEST_CANCELPOINT(cancelpoint_pwrite);
+ ADD_TEST_CANCELPOINT(cancelpoint_read);
+ ADD_TEST_CANCELPOINT(cancelpoint_readv);
+ ADD_TEST_CANCELPOINT(cancelpoint_recv);
+ ADD_TEST_CANCELPOINT(cancelpoint_recvfrom);
+ ADD_TEST_CANCELPOINT(cancelpoint_recvmsg);
+ ADD_TEST_CANCELPOINT(cancelpoint_select);
+ ADD_TEST_CANCELPOINT(cancelpoint_send);
+ ADD_TEST_CANCELPOINT(cancelpoint_sendto);
+ ADD_TEST_CANCELPOINT(cancelpoint_sendmsg);
+ ADD_TEST_CANCELPOINT(cancelpoint_sigsuspend);
+ ADD_TEST_CANCELPOINT(cancelpoint_sigtimedwait);
+ ADD_TEST_CANCELPOINT(cancelpoint_sigwait);
+ ADD_TEST_CANCELPOINT(cancelpoint_sigwaitinfo);
+ ADD_TEST_CANCELPOINT(cancelpoint_sleep);
+ ADD_TEST_CANCELPOINT(cancelpoint_tcdrain);
+ ADD_TEST_CANCELPOINT(cancelpoint_thrd_join);
+ ADD_TEST_CANCELPOINT(cancelpoint_thrd_sleep);
+ ADD_TEST_CANCELPOINT(cancelpoint_wait);
+ ADD_TEST_CANCELPOINT(cancelpoint_waitid);
+ ADD_TEST_CANCELPOINT(cancelpoint_waitpid);
+ ADD_TEST_CANCELPOINT(cancelpoint_write);
+ ADD_TEST_CANCELPOINT(cancelpoint_writev);
+
+ ATF_TP_ADD_TC(tp, cleanuppop0);
+ ATF_TP_ADD_TC(tp, cleanuppop1);
+ ATF_TP_ADD_TC(tp, cancelself_async);
+ ATF_TP_ADD_TC(tp, cancelself_deferred);
+ ATF_TP_ADD_TC(tp, defaults);
+ ATF_TP_ADD_TC(tp, disable_enable);
+ ATF_TP_ADD_TC(tp, disable_enable_async);
+ ATF_TP_ADD_TC(tp, disable_enable_setcanceltype_async);
+ ATF_TP_ADD_TC(tp, setcanceltype_async);
+ ATF_TP_ADD_TC(tp, notestcancel_loop_async);
+ ATF_TP_ADD_TC(tp, sigsafecancelstate);
+ ATF_TP_ADD_TC(tp, testcancel_loop);
+
+ return atf_no_error();
+}
diff --git a/lib/libpthread/t_compat_cancel.c b/lib/libpthread/t_compat_cancel.c
new file mode 100644
index 000000000000..280d072b3dd6
--- /dev/null
+++ b/lib/libpthread/t_compat_cancel.c
@@ -0,0 +1,287 @@
+/* $NetBSD: t_compat_cancel.c,v 1.3 2025/04/25 13:09:44 riastradh Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define __LIBC12_SOURCE__ /* expose compat declarations */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_compat_cancel.c,v 1.3 2025/04/25 13:09:44 riastradh Exp $");
+
+#include <sys/event.h>
+#include <sys/mman.h>
+
+#include <aio.h>
+#include <atf-c.h>
+#include <mqueue.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include <compat/sys/event.h>
+#include <compat/sys/mman.h>
+#include <compat/sys/poll.h>
+#include <compat/sys/select.h>
+
+#include <compat/include/aio.h>
+#include <compat/include/mqueue.h>
+#include <compat/include/signal.h>
+#include <compat/include/time.h>
+
+#include "cancelpoint.h"
+#include "h_macros.h"
+
+pthread_barrier_t bar;
+bool cleanup_done;
+
+static void
+cancelpoint_compat100_kevent(void)
+{
+ int kq;
+ struct kevent100 ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.ident = SIGUSR1;
+ ev.filter = EVFILT_SIGNAL;
+ ev.flags = EV_ADD|EV_ENABLE;
+ ev.fflags = 0;
+ ev.data = 0;
+ ev.udata = 0;
+
+ RL(kq = kqueue());
+ RL(__kevent50(kq, &ev, 1, NULL, 1, &(const struct timespec){0,0}));
+ cancelpointready();
+ RL(__kevent50(kq, NULL, 0, &ev, 1, NULL));
+}
+
+static void
+cancelpoint_compat12_msync(void)
+{
+ const unsigned long pagesize = sysconf(_SC_PAGESIZE);
+ int fd;
+ void *map;
+
+ RL(fd = open("file", O_RDWR|O_CREAT, 0666));
+ RL(ftruncate(fd, pagesize));
+ REQUIRE_LIBC(map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
+ MAP_SHARED, fd, 0),
+ MAP_FAILED);
+ cancelpointready();
+ RL(msync(map, pagesize));
+}
+
+static void
+cancelpoint_compat50___sigtimedwait(void)
+{
+ sigset_t mask, omask;
+ siginfo_t info;
+ struct timespec50 t = {.tv_sec = 2, .tv_nsec = 0};
+
+ RL(__sigfillset14(&mask));
+ RL(__sigprocmask14(SIG_BLOCK, &mask, &omask));
+ cancelpointready();
+ RL(__sigtimedwait(&omask, &info, &t));
+}
+
+static void
+cancelpoint_compat50_aio_suspend(void)
+{
+ int fd[2];
+ char buf[32];
+ struct aiocb aio = {
+ .aio_offset = 0,
+ .aio_buf = buf,
+ .aio_nbytes = sizeof(buf),
+ .aio_fildes = -1,
+ };
+ const struct aiocb *const aiolist[] = { &aio };
+
+ RL(pipe(fd));
+ aio.aio_fildes = fd[0];
+ RL(aio_read(&aio));
+ cancelpointready();
+ RL(aio_suspend(aiolist, __arraycount(aiolist), NULL));
+}
+
+static void
+cancelpoint_compat50_kevent(void)
+{
+ int kq;
+ struct kevent100 ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.ident = SIGUSR1;
+ ev.filter = EVFILT_SIGNAL;
+ ev.flags = EV_ADD|EV_ENABLE;
+ ev.fflags = 0;
+ ev.data = 0;
+ ev.udata = 0;
+
+ RL(kq = kqueue());
+ RL(kevent(kq, &ev, 1, NULL, 1, &(const struct timespec50){0,0}));
+ cancelpointready();
+ RL(kevent(kq, NULL, 0, &ev, 1, NULL));
+}
+
+static void
+cancelpoint_compat50_mq_timedreceive(void)
+{
+ mqd_t mq;
+ char buf[32];
+ struct timespec50 t = {.tv_sec = 2, .tv_nsec = 0};
+
+ RL(mq = mq_open("mq", O_RDWR|O_CREAT, 0666, NULL));
+ cancelpointready();
+ RL(mq_timedreceive(mq, buf, sizeof(buf), NULL, &t));
+}
+
+static void
+cancelpoint_compat50_mq_timedsend(void)
+{
+ mqd_t mq;
+ char buf[32] = {0};
+ struct timespec50 t = {.tv_sec = 2, .tv_nsec = 0};
+
+ RL(mq = mq_open("mq", O_RDWR|O_CREAT, 0666, NULL));
+ cancelpointready();
+ RL(mq_timedsend(mq, buf, sizeof(buf), 0, &t));
+}
+
+static void
+cancelpoint_compat50_nanosleep(void)
+{
+ struct timespec50 t = {.tv_sec = 2, .tv_nsec = 0};
+
+ cancelpointready();
+ RL(nanosleep(&t, NULL));
+}
+
+static void
+cancelpoint_compat50_pollts(void)
+{
+ int fd[2];
+ struct pollfd pfd;
+ struct timespec50 t = {.tv_sec = 2, .tv_nsec = 0};
+
+ RL(pipe(fd));
+ pfd.fd = fd[0];
+ pfd.events = POLLIN;
+ cancelpointready();
+ RL(pollts(&pfd, 1, &t, NULL));
+}
+
+static void
+cancelpoint_compat50_pselect(void)
+{
+ int fd[2];
+ fd_set readfd;
+ struct timespec50 t = {.tv_sec = 2, .tv_nsec = 0};
+
+ FD_ZERO(&readfd);
+
+ RL(pipe(fd));
+ FD_SET(fd[0], &readfd);
+ cancelpointready();
+ RL(pselect(fd[0] + 1, &readfd, NULL, NULL, &t, NULL));
+}
+
+static void
+cancelpoint_compat50_select(void)
+{
+ int fd[2];
+ fd_set readfd;
+ struct timeval50 t = {.tv_sec = 1, .tv_usec = 0};
+
+ FD_ZERO(&readfd);
+
+ RL(pipe(fd));
+ FD_SET(fd[0], &readfd);
+ cancelpointready();
+ RL(select(fd[0] + 1, &readfd, NULL, NULL, &t));
+}
+
+static void
+cancelpoint_compat13_sigsuspend(void)
+{
+ sigset13_t mask, omask;
+
+ RL(sigfillset(&mask));
+ RL(sigprocmask(SIG_BLOCK, &mask, &omask));
+ cancelpointready();
+ RL(sigsuspend(&omask));
+}
+
+static void
+cancelpoint_compat50_sigtimedwait(void)
+{
+ sigset_t mask, omask;
+ siginfo_t info;
+ struct timespec50 t = {.tv_sec = 2, .tv_nsec = 0};
+
+ RL(__sigfillset14(&mask));
+ RL(__sigprocmask14(SIG_BLOCK, &mask, &omask));
+ cancelpointready();
+ RL(sigtimedwait(&omask, &info, &t));
+}
+
+TEST_CANCELPOINT(cancelpoint_compat100_kevent, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat12_msync, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat13_sigsuspend,
+ atf_tc_expect_signal(-1, "PR lib/59240: POSIX.1-2024:"
+ " cancellation point audit"))
+TEST_CANCELPOINT(cancelpoint_compat50___sigtimedwait,
+ atf_tc_expect_signal(-1, "PR lib/59240: POSIX.1-2024:"
+ " cancellation point audit"))
+TEST_CANCELPOINT(cancelpoint_compat50_aio_suspend, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_kevent, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_mq_timedreceive, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_mq_timedsend, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_nanosleep, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_pollts, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_pselect, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_select, __nothing)
+TEST_CANCELPOINT(cancelpoint_compat50_sigtimedwait,
+ atf_tc_expect_signal(-1, "PR lib/59240: POSIX.1-2024:"
+ " cancellation point audit"))
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ADD_TEST_CANCELPOINT(cancelpoint_compat100_kevent);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat12_msync);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat13_sigsuspend);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50___sigtimedwait);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_aio_suspend);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_kevent);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_mq_timedreceive);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_mq_timedsend);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_nanosleep);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_pollts);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_pselect);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_select);
+ ADD_TEST_CANCELPOINT(cancelpoint_compat50_sigtimedwait);
+
+ return atf_no_error();
+}
diff --git a/lib/libpthread/t_stack.c b/lib/libpthread/t_stack.c
new file mode 100644
index 000000000000..1c5050d5fd4c
--- /dev/null
+++ b/lib/libpthread/t_stack.c
@@ -0,0 +1,491 @@
+/* $NetBSD: t_stack.c,v 1.6 2023/11/28 02:54:33 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2023 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _KMEMUSER /* __MACHINE_STACK_GROWS_UP */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_stack.c,v 1.6 2023/11/28 02:54:33 riastradh Exp $");
+
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+#include <uvm/uvm_param.h> /* VM_THREAD_GUARD_SIZE */
+
+#include <atf-c.h>
+#include <pthread.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "h_macros.h"
+
+struct jmp_ctx {
+ jmp_buf buf;
+};
+
+/*
+ * State used by various tests.
+ */
+struct ctx {
+ size_t size; /* default stack size */
+ size_t guardsize; /* default guard size */
+ void *addr; /* user-allocated stack */
+ pthread_key_t jmp_key; /* jmp_ctx to return from SIGSEGV handler */
+} ctx, *C = &ctx;
+
+/*
+ * getdefaultstacksize()
+ *
+ * Return the default stack size for threads created with
+ * pthread_create.
+ */
+static size_t
+getdefaultstacksize(void)
+{
+ pthread_attr_t attr;
+ size_t stacksize;
+
+ /*
+ * When called from the main thread, this returns the default
+ * stack size (pthread__stacksize) used for pthreads.
+ */
+ RZ(pthread_getattr_np(pthread_self(), &attr));
+ RZ(pthread_attr_getstacksize(&attr, &stacksize));
+ RZ(pthread_attr_destroy(&attr));
+
+ /*
+ * Verify that the assumption above holds.
+ */
+ extern size_t pthread__stacksize; /* pthread_int.h */
+ ATF_CHECK_EQ_MSG(stacksize, pthread__stacksize,
+ "stacksize=%zu pthread__stacksize=%zu",
+ stacksize, pthread__stacksize);
+
+ return stacksize;
+}
+
+/*
+ * getnondefaultstacksize()
+ *
+ * Return a stack size that is not the default stack size for
+ * threads created with pthread_create.
+ */
+static size_t
+getnondefaultstacksize(void)
+{
+
+ return getdefaultstacksize() + sysconf(_SC_PAGESIZE);
+}
+
+/*
+ * getdefaultguardsize()
+ *
+ * Return the default guard size for threads created with
+ * pthread_create.
+ */
+static size_t
+getdefaultguardsize(void)
+{
+ const int mib[2] = { CTL_VM, VM_THREAD_GUARD_SIZE };
+ unsigned guardsize;
+ size_t len = sizeof(guardsize);
+
+ RL(sysctl(mib, __arraycount(mib), &guardsize, &len, NULL, 0));
+ ATF_REQUIRE_EQ_MSG(len, sizeof(guardsize),
+ "len=%zu sizeof(guardsize)=%zu", len, sizeof(guardsize));
+
+ /*
+ * Verify this matches what libpthread determined.
+ */
+ extern size_t pthread__guardsize; /* pthread_int.h */
+ ATF_CHECK_EQ_MSG(guardsize, pthread__guardsize,
+ "guardsize=%u pthread__guardsize=%zu",
+ guardsize, pthread__guardsize);
+
+ return guardsize;
+}
+
+/*
+ * alloc(nbytes)
+ *
+ * Allocate an nbytes-long page-aligned read/write region and
+ * return a pointer to it. Abort the test if allocation fails, so
+ * if this function returns it succeeds.
+ */
+static void *
+alloc(size_t nbytes)
+{
+ void *ptr;
+
+ REQUIRE_LIBC((ptr = mmap(/*hint*/NULL, nbytes,
+ PROT_READ|PROT_WRITE, MAP_ANON, /*fd*/-1, /*offset*/0)),
+ MAP_FAILED);
+
+ return ptr;
+}
+
+/*
+ * init(stacksize)
+ *
+ * Initialize state used by various tests with the specified
+ * stacksize.
+ *
+ * Make sure to allocate enough space that even if there shouldn't
+ * be a stack guard (i.e., it should be empty), adjusting the
+ * requested bounds by the default stack guard size will leave us
+ * inside allocated memory.
+ */
+static void
+init(size_t stacksize)
+{
+
+ C->size = stacksize;
+ C->guardsize = getdefaultguardsize();
+ C->addr = alloc(C->size + C->guardsize);
+ RZ(pthread_key_create(&C->jmp_key, NULL));
+}
+
+/*
+ * stack_pointer()
+ *
+ * Return the stack pointer. This is used to verify whether the
+ * stack pointer lie within a certain address range.
+ */
+static __noinline void *
+stack_pointer(void)
+{
+ return __builtin_frame_address(0);
+}
+
+/*
+ * sigsegv_ok(signo)
+ *
+ * Signal handler for SIGSEGV to return to the jmp ctx, to verify
+ * that SIGSEGV happened without crashing.
+ */
+static void
+sigsegv_ok(int signo)
+{
+ struct jmp_ctx *j = pthread_getspecific(C->jmp_key);
+
+ longjmp(j->buf, 1);
+}
+
+/*
+ * checksigsegv(p)
+ *
+ * Verify that reading *p triggers SIGSEGV. Fails test nonfatally
+ * if SIGSEGV doesn't happen.
+ */
+static void
+checksigsegv(const char *p)
+{
+ struct jmp_ctx j;
+ struct sigaction act, oact;
+ volatile struct sigaction oactsave;
+ volatile char v;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = &sigsegv_ok;
+
+ if (setjmp(j.buf) == 0) {
+ pthread_setspecific(C->jmp_key, &j);
+ RL(sigaction(SIGSEGV, &act, &oact));
+ oactsave = oact;
+ v = *p; /* trigger SIGSEGV */
+ atf_tc_fail_nonfatal("failed to trigger SIGSEGV at %p", p);
+ } else {
+ /* return from SIGSEGV handler */
+ oact = oactsave;
+ }
+ RL(sigaction(SIGSEGV, &oact, NULL));
+ pthread_setspecific(C->jmp_key, NULL);
+
+ (void)v; /* suppress unused variable warnings */
+}
+
+/*
+ * checknosigsegv(p)
+ *
+ * Verify that reading *p does not trigger SIGSEGV. Fails test
+ * nonfatally if SIGSEGV happens.
+ */
+static void
+checknosigsegv(const char *p)
+{
+ struct jmp_ctx j;
+ struct sigaction act, oact;
+ volatile struct sigaction oactsave;
+ volatile char v;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = &sigsegv_ok;
+
+ if (setjmp(j.buf) == 0) {
+ pthread_setspecific(C->jmp_key, &j);
+ RL(sigaction(SIGSEGV, &act, &oact));
+ oactsave = oact;
+ v = *p; /* better not trigger SIGSEGV */
+ } else {
+ /* return from SIGSEGV handler */
+ atf_tc_fail_nonfatal("spuriously triggered SIGSEGV at %p", p);
+ oact = oactsave;
+ }
+ RL(sigaction(SIGSEGV, &oact, NULL));
+ pthread_setspecific(C->jmp_key, NULL);
+
+ (void)v; /* suppress unused variable warnings */
+}
+
+/*
+ * checkguardaccessthread(cookie)
+ *
+ * Thread start routine that verifies it has access to the start
+ * and end of its stack, according to pthread_attr_getstack, and
+ * _does not_ have access to the start or end of its stack guard,
+ * above the stack (in stack growth direction) by
+ * pthread_attr_getguardsize bytes.
+ */
+static void *
+checkguardaccessthread(void *cookie)
+{
+ pthread_t t = pthread_self();
+ pthread_attr_t attr;
+ void *addr, *guard;
+ size_t size, guardsize;
+
+ /*
+ * Get the the stack and stack guard parameters.
+ */
+ RZ(pthread_getattr_np(t, &attr));
+ RZ(pthread_attr_getstack(&attr, &addr, &size));
+ RZ(pthread_attr_getguardsize(&attr, &guardsize));
+
+ /*
+ * Determine where the guard starts in virtual address space
+ * (not in stack growth direction).
+ */
+#ifdef __MACHINE_STACK_GROWS_UP
+ guard = (char *)addr + size;
+#else
+ guard = (char *)addr - guardsize;
+#endif
+
+ /*
+ * Verify access to the start and end of the stack itself.
+ */
+ checknosigsegv(addr);
+ checknosigsegv((char *)addr + size - 1);
+
+ /*
+ * Verify no access to the start or end of the stack guard.
+ */
+ checksigsegv(guard);
+ checksigsegv((char *)guard + guardsize - 1);
+
+ return NULL;
+}
+
+/*
+ * checkaddraccessthread(cookie)
+ *
+ * Thread start routine that verifies its stack is [C->addr,
+ * C->addr + C->size), according to pthread_attr_getstack and
+ * pthread_addr_getstacksize, and verifies it has access to that
+ * whole range.
+ */
+static void *
+checkaddraccessthread(void *cookie)
+{
+ pthread_t t = pthread_self();
+ pthread_attr_t attr;
+ void *sp;
+ void *addr;
+ size_t size, size0;
+
+ /*
+ * Verify the stack pointer lies somewhere in the allocated
+ * range.
+ */
+ sp = stack_pointer();
+ ATF_CHECK_MSG(C->addr <= sp, "sp=%p not in [%p,%p + 0x%zu) = [%p,%p)",
+ sp, C->addr, C->addr, C->size, C->addr, (char *)C->addr + C->size);
+ ATF_CHECK_MSG(sp <= (void *)((char *)C->addr + C->size),
+ "sp=%p not in [%p,%p + 0x%zu) = [%p,%p)",
+ sp, C->addr, C->addr, C->size, C->addr, (char *)C->addr + C->size);
+
+ /*
+ * Verify, if not that, then the stack pointer at least lies
+ * within the extra buffer we allocated for slop to address a
+ * bug NetBSD libpthread used to have of spuriously adding the
+ * guard size to a user-allocated stack address. This is
+ * ATF_REQUIRE, not ATF_CHECK, because if this doesn't hold, we
+ * might be clobbering some other memory like malloc pages,
+ * causing the whole test to crash with useless diagnostics.
+ */
+ ATF_REQUIRE_MSG(sp <= (void *)((char *)C->addr + C->size +
+ C->guardsize),
+ "sp=%p not even in buffer [%p,%p + 0x%zu + 0x%zu) = [%p,%p)",
+ sp, C->addr, C->addr, C->size, C->guardsize,
+ C->addr, (char *)C->addr + C->size + C->guardsize);
+
+ /*
+ * Get the stack parameters -- both via pthread_attr_getstack
+ * and via pthread_attr_getstacksize, to make sure they agree
+ * -- and verify that they are what we expect from the caller.
+ */
+ RZ(pthread_getattr_np(t, &attr));
+ RZ(pthread_attr_getstack(&attr, &addr, &size));
+ RZ(pthread_attr_getstacksize(&attr, &size0));
+ ATF_CHECK_EQ_MSG(C->addr, addr, "expected %p actual %p",
+ C->addr, addr);
+ ATF_CHECK_EQ_MSG(C->size, size, "expected %zu actual %zu",
+ C->size, size);
+ ATF_CHECK_EQ_MSG(C->size, size0, "expected %zu actual %zu",
+ C->size, size0);
+
+ /*
+ * Verify that we have access to what we expect the stack to
+ * be.
+ */
+ checknosigsegv(C->addr);
+ checknosigsegv((char *)C->addr + C->size - 1);
+
+ return NULL;
+}
+
+ATF_TC(stack1);
+ATF_TC_HEAD(stack1, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test allocating and reallocating a thread with a user stack");
+}
+ATF_TC_BODY(stack1, tc)
+{
+ pthread_attr_t attr;
+ pthread_t t, t2;
+
+ /*
+ * Allocate a stack with a non-default size to verify
+ * libpthread didn't choose the stack size for us.
+ */
+ init(getnondefaultstacksize());
+
+ /*
+ * Create a thread with user-allocated stack of a non-default
+ * size to verify the stack size and access.
+ */
+ RZ(pthread_attr_init(&attr));
+ RZ(pthread_attr_setstack(&attr, C->addr, C->size));
+ RZ(pthread_create(&t, &attr, &checkaddraccessthread, C));
+ RZ(pthread_join(t, NULL));
+
+ /*
+ * Create another thread with the same parameters, and verify
+ * that (a) it was recycled, and (b) it works the same way.
+ */
+ RZ(pthread_create(&t2, &attr, &checkaddraccessthread, C));
+ ATF_CHECK_EQ_MSG(t, t2, "t=%p t2=%p", t, t2); /* NetBSD recycles */
+ RZ(pthread_join(t2, NULL));
+}
+
+ATF_TC(stack2);
+ATF_TC_HEAD(stack2, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test reallocating a thread with a newly self-allocated stack");
+}
+ATF_TC_BODY(stack2, tc)
+{
+ pthread_attr_t attr, attr2;
+ size_t size, size2;
+ pthread_t t, t2;
+
+ /*
+ * Allocate a stack with the default size so that we verify
+ * when libpthread reuses the thread, it doesn't inadvertently
+ * reuse the libpthread-allocated stack too and instead
+ * correctly uses our user-allocated stack.
+ */
+ init(getdefaultstacksize());
+
+ /*
+ * Create a thread with a libpthread-allocated stack that
+ * verifies
+ * (a) access to its own stack, and
+ * (b) no access to its own guard pages;
+ * then get its attributes and wait for it to complete.
+ */
+ RZ(pthread_create(&t, NULL, &checkguardaccessthread, C));
+ RZ(pthread_getattr_np(t, &attr));
+ RZ(pthread_join(t, NULL));
+
+ /*
+ * Create a thread with a user-allocated stack that verifies
+ * (a) stack addr/size match request, and
+ * (b) access to the requested stack,
+ * and confirm that the first thread was recycled -- not part
+ * of POSIX semantics, but part of NetBSD's implementation;
+ * this way, we verify that, even though the thread is
+ * recycled, the thread's stack is set to the user-allocated
+ * stack and access to it works as expected. Then wait for it
+ * to complete.
+ */
+ RZ(pthread_attr_init(&attr2));
+ RZ(pthread_attr_setstack(&attr2, C->addr, C->size));
+ RZ(pthread_create(&t2, &attr2, &checkaddraccessthread, C));
+ ATF_CHECK_EQ_MSG(t, t2, "t=%p t2=%p", t, t2); /* NetBSD recycles */
+ RZ(pthread_join(t2, NULL));
+
+ /*
+ * Verify that the libpthread-allocated stack and
+ * user-allocated stack had the same size, since we chose the
+ * default size.
+ *
+ * Note: We can't say anything about the guard size, because
+ * with pthread_attr_setstack, the guard size is ignored, and
+ * it's not clear from POSIX whether any meaningful guard size
+ * is stored for retrieval with pthread_attr_getguardsize in
+ * attributes with pthread_attr_setstack.
+ */
+ RZ(pthread_attr_getstacksize(&attr, &size));
+ RZ(pthread_attr_getstacksize(&attr2, &size2));
+ ATF_CHECK_EQ_MSG(size, size2, "size=%zu size2=%zu", size, size2);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, stack1);
+ ATF_TP_ADD_TC(tp, stack2);
+
+ return atf_no_error();
+}
diff --git a/lib/libpthread/weak/Makefile b/lib/libpthread/weak/Makefile
new file mode 100644
index 000000000000..f7cdd75c2723
--- /dev/null
+++ b/lib/libpthread/weak/Makefile
@@ -0,0 +1,25 @@
+# $NetBSD: Makefile,v 1.2 2025/10/18 20:27:23 riastradh Exp $
+#
+
+TESTSDIR= ${TESTSBASE}/lib/libpthread/weak
+
+TESTS_C+= t_pthread_weak_nothread
+TESTS_C+= t_pthread_weak_threaded
+
+CPPFLAGS+= -I${.CURDIR}/lib
+
+.include <bsd.own.mk> # PRINTOBJDIR
+
+.if !defined(H_PTHREAD_WEAK_OBJDIR)
+H_PTHREAD_WEAK_OBJDIR!= cd ${.CURDIR}/lib && ${PRINTOBJDIR}
+.MAKEOVERRIDES+= H_PTHREAD_WEAK_OBJDIR
+.endif
+
+LDADD+= -L${H_PTHREAD_WEAK_OBJDIR}
+LDADD+= -Wl,-rpath,${TESTSBASE}/lib/libpthread/weak
+LDADD+= -lh_pthread_weak
+LDADD.t_pthread_weak_threaded+= -lpthread
+
+SUBDIR+= lib
+
+.include <bsd.test.mk>
diff --git a/lib/libpthread/weak/Makefile.inc b/lib/libpthread/weak/Makefile.inc
new file mode 100644
index 000000000000..921a499b55ba
--- /dev/null
+++ b/lib/libpthread/weak/Makefile.inc
@@ -0,0 +1 @@
+.include "${.PARSEDIR}/../../Makefile.inc"
diff --git a/lib/libpthread/weak/lib/Makefile b/lib/libpthread/weak/lib/Makefile
new file mode 100644
index 000000000000..0976a72efd27
--- /dev/null
+++ b/lib/libpthread/weak/lib/Makefile
@@ -0,0 +1,21 @@
+# $NetBSD: Makefile,v 1.2 2026/01/21 17:57:27 christos Exp $
+#
+
+MKPROFILE= no # XXX hack -- should be NOPROFILE
+NOLINT= # defined
+NOPICINSTALL= # defined
+NOMAN= # defined
+NOSTATICLIB= # defined
+
+LIB= h_pthread_weak
+SRCS+= h_pthread_weak.c
+
+LDADD+= -latf-c
+
+LIBDIR= ${TESTSBASE}/lib/libpthread/weak
+SHLIBDIR= ${TESTSBASE}/lib/libpthread/weak
+SHLIB_MAJOR= 1
+
+LIBISCXX= yes
+
+.include <bsd.lib.mk>
diff --git a/lib/libpthread/weak/lib/h_pthread_weak.c b/lib/libpthread/weak/lib/h_pthread_weak.c
new file mode 100644
index 000000000000..d8b9e624c07d
--- /dev/null
+++ b/lib/libpthread/weak/lib/h_pthread_weak.c
@@ -0,0 +1,83 @@
+/* $NetBSD: h_pthread_weak.c,v 1.1 2025/10/06 13:16:44 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: h_pthread_weak.c,v 1.1 2025/10/06 13:16:44 riastradh Exp $");
+
+#define _NETBSD_PTHREAD_CREATE_WEAK
+
+#include "h_pthread_weak.h"
+
+#include <atf-c.h>
+#include <pthread.h>
+
+#include "h_macros.h"
+
+static void *
+start(void *cookie)
+{
+ return cookie;
+}
+
+void
+test_mutex(void)
+{
+ pthread_mutex_t mtx;
+
+ RZ(pthread_mutex_init(&mtx, NULL));
+ RZ(pthread_mutex_lock(&mtx));
+ RZ(pthread_mutex_unlock(&mtx));
+ RZ(pthread_mutex_destroy(&mtx));
+}
+
+void
+test_thread_creation(void)
+{
+ int cookie = 123;
+ pthread_attr_t attr;
+ pthread_t t;
+ void *result;
+
+ RZ(pthread_attr_init(&attr));
+ RZ(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE));
+ RZ(pthread_create(&t, NULL, &start, &cookie));
+ RZ(pthread_attr_destroy(&attr));
+ RZ(pthread_join(t, &result));
+ ATF_CHECK_EQ(result, &cookie);
+}
+
+void
+test_thread_creation_failure(void)
+{
+ int cookie = 123;
+ pthread_t t;
+ int error;
+
+ error = pthread_create(&t, NULL, &start, &cookie);
+ ATF_CHECK_MSG(error != 0, "pthread_create unexpectedly succeeded");
+}
diff --git a/lib/libpthread/weak/lib/h_pthread_weak.h b/lib/libpthread/weak/lib/h_pthread_weak.h
new file mode 100644
index 000000000000..b970d2b020c6
--- /dev/null
+++ b/lib/libpthread/weak/lib/h_pthread_weak.h
@@ -0,0 +1,36 @@
+/* $NetBSD: h_pthread_weak.h,v 1.1 2025/10/06 13:16:44 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef H_PTHREAD_WEAK_H
+#define H_PTHREAD_WEAK_H
+
+void test_mutex(void);
+void test_thread_creation(void);
+void test_thread_creation_failure(void);
+
+#endif /* H_PTHREAD_WEAK_H */
diff --git a/lib/libpthread/weak/t_pthread_weak_nothread.c b/lib/libpthread/weak/t_pthread_weak_nothread.c
new file mode 100644
index 000000000000..a5447b695dbb
--- /dev/null
+++ b/lib/libpthread/weak/t_pthread_weak_nothread.c
@@ -0,0 +1,64 @@
+/* $NetBSD: t_pthread_weak_nothread.c,v 1.1 2025/10/18 20:27:23 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_pthread_weak_nothread.c,v 1.1 2025/10/18 20:27:23 riastradh Exp $");
+
+#include <atf-c.h>
+
+#include "h_pthread_weak.h"
+
+ATF_TC(mutex);
+ATF_TC_HEAD(mutex, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test mutex usage in library with _NETBSD_PTHREAD_CREATE_WEAK");
+}
+ATF_TC_BODY(mutex, tc)
+{
+ test_mutex();
+}
+
+ATF_TC(thread_creation_failure);
+ATF_TC_HEAD(thread_creation_failure, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test pthread_create via library fails in no-thread application");
+}
+ATF_TC_BODY(thread_creation_failure, tc)
+{
+ test_thread_creation_failure();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, mutex);
+ ATF_TP_ADD_TC(tp, thread_creation_failure);
+
+ return atf_no_error();
+}
diff --git a/lib/libpthread/weak/t_pthread_weak_threaded.c b/lib/libpthread/weak/t_pthread_weak_threaded.c
new file mode 100644
index 000000000000..70c649ea13b6
--- /dev/null
+++ b/lib/libpthread/weak/t_pthread_weak_threaded.c
@@ -0,0 +1,64 @@
+/* $NetBSD: t_pthread_weak_threaded.c,v 1.1 2025/10/18 20:27:23 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_pthread_weak_threaded.c,v 1.1 2025/10/18 20:27:23 riastradh Exp $");
+
+#include <atf-c.h>
+
+#include "h_pthread_weak.h"
+
+ATF_TC(mutex);
+ATF_TC_HEAD(mutex, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test mutex usage in library with _NETBSD_PTHREAD_CREATE_WEAK");
+}
+ATF_TC_BODY(mutex, tc)
+{
+ test_mutex();
+}
+
+ATF_TC(thread_creation);
+ATF_TC_HEAD(thread_creation, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test pthread_create via library in threaded application");
+}
+ATF_TC_BODY(thread_creation, tc)
+{
+ test_thread_creation();
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, mutex);
+ ATF_TP_ADD_TC(tp, thread_creation);
+
+ return atf_no_error();
+}
diff --git a/lib/libstdc++/Makefile b/lib/libstdc++/Makefile
new file mode 100644
index 000000000000..3562d78dd70c
--- /dev/null
+++ b/lib/libstdc++/Makefile
@@ -0,0 +1,12 @@
+# $NetBSD: Makefile,v 1.1 2024/04/28 01:21:27 riastradh Exp $
+#
+
+NOMAN= # defined
+
+TESTSDIR= ${TESTSBASE}/lib/libstdc++
+BINDIR= ${TESTSDIR}
+
+TESTS_SH+= t_sync_with_stdio
+PROG_CXX+= h_cin_nosync
+
+.include <bsd.test.mk>
diff --git a/lib/libstdc++/h_cin_nosync.cc b/lib/libstdc++/h_cin_nosync.cc
new file mode 100644
index 000000000000..a934e2c67415
--- /dev/null
+++ b/lib/libstdc++/h_cin_nosync.cc
@@ -0,0 +1,40 @@
+/* $NetBSD: h_cin_nosync.cc,v 1.1 2024/04/28 01:21:27 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+
+int
+main(void)
+{
+ char buf[128];
+
+ std::ios::sync_with_stdio(false);
+ std::cin.read(buf, sizeof(buf));
+ std::cout << std::cin.gcount() << std::endl;
+ return 0;
+}
diff --git a/lib/libstdc++/t_sync_with_stdio.sh b/lib/libstdc++/t_sync_with_stdio.sh
new file mode 100644
index 000000000000..fe76c49b988f
--- /dev/null
+++ b/lib/libstdc++/t_sync_with_stdio.sh
@@ -0,0 +1,41 @@
+# $NetBSD: t_sync_with_stdio.sh,v 1.2 2024/05/20 11:20:53 riastradh Exp $
+#
+# Copyright (c) 2024 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+cin_nosync_head()
+{
+ atf_set descr "Check cin works after std::ios::sync_with_stdio(false)"
+}
+cin_nosync_body()
+{
+ echo hello >in
+ atf_check -o inline:'6\n' "$(atf_get_srcdir)"/h_cin_nosync <in
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case cin_nosync
+}
diff --git a/lib/libutil/t_strpct.c b/lib/libutil/t_strpct.c
new file mode 100644
index 000000000000..c061ae152321
--- /dev/null
+++ b/lib/libutil/t_strpct.c
@@ -0,0 +1,202 @@
+/* $NetBSD: t_strpct.c,v 1.2 2025/05/03 07:22:52 rillig Exp $ */
+
+/*
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code was contributed to The NetBSD Foundation by Roland Illig.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__COPYRIGHT("@(#) Copyright (c) 2025\
+ The NetBSD Foundation, inc. All rights reserved.");
+__RCSID("$NetBSD: t_strpct.c,v 1.2 2025/05/03 07:22:52 rillig Exp $");
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <util.h>
+
+#include <atf-c.h>
+
+static void
+check_strspct(const char *file, unsigned line,
+ size_t bufsiz, intmax_t num, intmax_t den, size_t digits,
+ const char *want)
+{
+ char buf[128];
+
+ ATF_REQUIRE_MSG(bufsiz < sizeof(buf) - 2, "bufsiz too large");
+ memset(buf, '>', sizeof(buf));
+ buf[0] = '<';
+ buf[sizeof(buf) - 1] = '\0';
+
+ const char *have = strspct(buf + 1, bufsiz, num, den, digits);
+
+ ATF_REQUIRE_MSG(buf[0] == '<',
+ "out-of-bounds write before");
+ ATF_REQUIRE_MSG(buf[1 + bufsiz] == '>',
+ "out-of-bounds write after");
+ ATF_REQUIRE_MSG(have == buf + 1,
+ "have != buf");
+ ATF_CHECK_MSG(bufsiz > 0 ? strcmp(have, want) == 0 : true,
+ "%s:%u: want \"%s\", have \"%s\"",
+ file, line, want, have);
+}
+
+#define h_strspct(bufsiz, num, den, digits, want) \
+ check_strspct(__FILE__, __LINE__, bufsiz, num, den, digits, want)
+
+static void
+check_strpct(const char *file, unsigned line,
+ size_t bufsiz, uintmax_t num, uintmax_t den, size_t digits,
+ const char *want)
+{
+ char buf[128];
+
+ ATF_REQUIRE_MSG(bufsiz < sizeof(buf) - 2, "bufsiz too large");
+ memset(buf, '>', sizeof(buf));
+ buf[0] = '<';
+ buf[sizeof(buf) - 1] = '\0';
+
+ const char *have = strpct(buf + 1, bufsiz, num, den, digits);
+
+ ATF_REQUIRE_MSG(buf[0] == '<',
+ "out-of-bounds write before");
+ ATF_REQUIRE_MSG(buf[1 + bufsiz] == '>',
+ "out-of-bounds write after");
+ ATF_REQUIRE_MSG(have == buf + 1,
+ "have != buf");
+ ATF_CHECK_MSG(bufsiz > 0 ? strcmp(have, want) == 0 : true,
+ "%s:%u: want \"%s\", have \"%s\"",
+ file, line, want, have);
+}
+
+#define h_strpct(bufsiz, num, den, digits, want) \
+ check_strpct(__FILE__, __LINE__, bufsiz, num, den, digits, want)
+
+ATF_TC(strspct);
+ATF_TC_HEAD(strspct, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Checks strspct(3)");
+}
+ATF_TC_BODY(strspct, tc)
+{
+
+ // Very small buffers.
+ h_strspct(0, 0, 0, 0, "");
+ h_strspct(1, 0, 0, 0, "");
+
+ // Small buffers.
+ h_strspct(2, 1, 40, 0, "2");
+ h_strspct(3, 1, 40, 0, "2");
+ h_strspct(3, 1, 40, 1, "2.");
+ h_strspct(4, 1, 40, 1, "2.5");
+ h_strspct(4, 8, 40, 1, "20.");
+ h_strspct(6, 1, 5, 1, "20.0");
+ h_strspct(100, 1, 5, 5, "20.00000");
+ h_strspct( 5, 11223344, 100, 10, "1122");
+ h_strspct(10, 11223344, 100, 10, "11223344.");
+ h_strspct(11, 11223344, 100, 10, "11223344.0");
+
+ // Small buffers with negative numbers.
+ h_strspct(1, -1, 40, 0, "");
+ h_strspct(2, -1, 40, 0, "-");
+ h_strspct(3, -1, 40, 0, "-2");
+ h_strspct(3, -1, 40, 1, "-2");
+ h_strspct(4, -1, 40, 1, "-2.");
+ h_strspct(5, -1, 40, 1, "-2.5");
+ h_strspct(4, -8, 40, 1, "-20");
+ h_strspct(5, -8, 40, 1, "-20.");
+ h_strspct(6, -1, 5, 1, "-20.0");
+ h_strspct(100, -1, 5, 5, "-20.00000");
+ h_strspct( 5, -11223344, 100, 10, "-112");
+ h_strspct(10, -11223344, 100, 10, "-11223344");
+ h_strspct(11, -11223344, 100, 10, "-11223344.");
+ h_strspct(12, -11223344, 100, 10, "-11223344.0");
+
+ // Percentages are always rounded towards zero.
+ h_strspct(6, 1, 6, 1, "16.6");
+ h_strspct(7, -1, 6, 1, "-16.6");
+ h_strspct(7, 1, -6, 1, "-16.6");
+ h_strspct(7, -1, -6, 1, "16.6");
+ h_strspct(100, 1, 7, 20, "14.28571428571428571428");
+
+ // Big numbers.
+ h_strspct(100, INTMAX_MAX, INTMAX_MAX, 0, "100");
+ h_strspct(100, INTMAX_MIN, INTMAX_MIN, 25, "100.0000000000000000000000000");
+ h_strspct(100, INTMAX_MIN, INTMAX_MAX, 25, "-100.0000000000000000108420217");
+ h_strspct(100, INTMAX_MAX, INTMAX_MIN, 25, "-99.9999999999999999891579782");
+ h_strspct(100, INTMAX_MAX, INTMAX_MAX, 25, "100.0000000000000000000000000");
+}
+
+ATF_TC(strpct);
+ATF_TC_HEAD(strpct, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Checks strpct(3)");
+}
+ATF_TC_BODY(strpct, tc)
+{
+
+ // Small buffers.
+ h_strpct(0, 0, 0, 0, "");
+ h_strpct(1, 0, 0, 0, "");
+ h_strpct(2, 0, 0, 0, "0");
+ h_strpct(3, 0, 0, 0, "0");
+ h_strpct(3, 0, 0, 1, "0.");
+ h_strpct(4, 0, 0, 1, "0.0");
+ h_strpct(4, 1, 5, 1, "20.");
+ h_strpct(6, 1, 5, 1, "20.0");
+ h_strpct(100, 1, 5, 5, "20.00000");
+
+ h_strpct(100, 1, 7, 20, "14.28571428571428571428");
+
+ h_strpct( 5, 11223344, 100, 10, "1122");
+ h_strpct(10, 11223344, 100, 10, "11223344.");
+ h_strpct(11, 11223344, 100, 10, "11223344.0");
+
+ h_strpct(100, UINTMAX_MAX, UINTMAX_MAX, 0, "100");
+ h_strpct(100, UINTMAX_MAX, UINTMAX_MAX, 1, "100.0");
+ h_strpct(100, UINTMAX_MAX, UINTMAX_MAX, 5, "100.00000");
+ h_strpct(100, UINTMAX_MAX, UINTMAX_MAX, 10, "100.0000000000");
+ h_strpct(100, UINTMAX_MAX, UINTMAX_MAX, 15, "100.000000000000000");
+ h_strpct(100, UINTMAX_MAX, UINTMAX_MAX, 20, "100.00000000000000000000");
+ h_strpct(100, UINTMAX_MAX, UINTMAX_MAX, 25, "100.0000000000000000000000000");
+
+ h_strpct(100, UINTMAX_MAX - 1, UINTMAX_MAX, 25, "99.9999999999999999945789891");
+ h_strpct(100, 1, (UINTMAX_MAX >> 1) + 1, 70,
+ "0.0000000000000000108420217248550443400745280086994171142578125000000000");
+ h_strpct(100, UINTMAX_MAX, 1, 10, "1844674407370955161500.0000000000");
+ h_strpct(100, 1, UINTMAX_MAX, 30, "0.000000000000000005421010862427");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, strspct);
+ ATF_TP_ADD_TC(tp, strpct);
+
+ return atf_no_error();
+}