aboutsummaryrefslogtreecommitdiff
path: root/lib/libpthread
diff options
context:
space:
mode:
authorEnji Cooper <ngie@FreeBSD.org>2026-02-15 01:57:42 +0000
committerEnji Cooper <ngie@FreeBSD.org>2026-02-15 02:12:44 +0000
commite8dbf2b6df199526a660f81de07d17925cfd8518 (patch)
treecd0c09449bea5df56ef67059e797737d70587070 /lib/libpthread
parent56a7ce8416d181a2060d7a428aed9c3c6a431e6d (diff)
Diffstat (limited to 'lib/libpthread')
-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
11 files changed, 2748 insertions, 0 deletions
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();
+}