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