diff options
| author | Enji Cooper <ngie@FreeBSD.org> | 2026-02-15 01:57:42 +0000 |
|---|---|---|
| committer | Enji Cooper <ngie@FreeBSD.org> | 2026-02-15 02:12:44 +0000 |
| commit | e8dbf2b6df199526a660f81de07d17925cfd8518 (patch) | |
| tree | cd0c09449bea5df56ef67059e797737d70587070 /lib/libpthread | |
| parent | 56a7ce8416d181a2060d7a428aed9c3c6a431e6d (diff) | |
Diffstat (limited to 'lib/libpthread')
| -rw-r--r-- | lib/libpthread/cancelpoint.h | 133 | ||||
| -rw-r--r-- | lib/libpthread/t_cancellation.c | 1543 | ||||
| -rw-r--r-- | lib/libpthread/t_compat_cancel.c | 287 | ||||
| -rw-r--r-- | lib/libpthread/t_stack.c | 491 | ||||
| -rw-r--r-- | lib/libpthread/weak/Makefile | 25 | ||||
| -rw-r--r-- | lib/libpthread/weak/Makefile.inc | 1 | ||||
| -rw-r--r-- | lib/libpthread/weak/lib/Makefile | 21 | ||||
| -rw-r--r-- | lib/libpthread/weak/lib/h_pthread_weak.c | 83 | ||||
| -rw-r--r-- | lib/libpthread/weak/lib/h_pthread_weak.h | 36 | ||||
| -rw-r--r-- | lib/libpthread/weak/t_pthread_weak_nothread.c | 64 | ||||
| -rw-r--r-- | lib/libpthread/weak/t_pthread_weak_threaded.c | 64 |
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, ¬estcancel_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(); +} |
