aboutsummaryrefslogtreecommitdiff
path: root/lib/libc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc')
-rw-r--r--lib/libc/Makefile1
-rw-r--r--lib/libc/Versions.def6
-rw-r--r--lib/libc/aarch64/static_tls.h44
-rw-r--r--lib/libc/aarch64/string/timingsafe_memcmp.S2
-rw-r--r--lib/libc/amd64/static_tls.h44
-rw-r--r--lib/libc/amd64/string/memrchr.S224
-rw-r--r--lib/libc/arm/aeabi/Symbol.map44
-rw-r--r--lib/libc/arm/aeabi/aeabi_int_div.S2
-rw-r--r--lib/libc/arm/aeabi/aeabi_vfp.h4
-rw-r--r--lib/libc/arm/static_tls.h45
-rw-r--r--lib/libc/csu/libc_start1.c9
-rw-r--r--lib/libc/db/hash/hash.c24
-rw-r--r--lib/libc/db/man/dbm.35
-rw-r--r--lib/libc/db/man/dbopen.35
-rw-r--r--lib/libc/gdtoa/_hdtoa.c3
-rw-r--r--lib/libc/gdtoa/_hldtoa.c3
-rw-r--r--lib/libc/gen/Makefile.inc22
-rw-r--r--lib/libc/gen/Symbol.map16
-rw-r--r--lib/libc/gen/_pthread_stubs.c1
-rw-r--r--lib/libc/gen/_rand48.c34
-rw-r--r--lib/libc/gen/alarm.32
-rw-r--r--lib/libc/gen/dlfcn.c2
-rw-r--r--lib/libc/gen/drand48.c6
-rw-r--r--lib/libc/gen/dup3.315
-rw-r--r--lib/libc/gen/dup3.c9
-rw-r--r--lib/libc/gen/elf_utils.c27
-rw-r--r--lib/libc/gen/erand48.c9
-rw-r--r--lib/libc/gen/err.c51
-rw-r--r--lib/libc/gen/exec.c233
-rw-r--r--lib/libc/gen/fdopendir.c17
-rw-r--r--lib/libc/gen/fnmatch.38
-rw-r--r--lib/libc/gen/fnmatch.c227
-rw-r--r--lib/libc/gen/fts-compat.c2
-rw-r--r--lib/libc/gen/fts-compat11.c2
-rw-r--r--lib/libc/gen/fts.3277
-rw-r--r--lib/libc/gen/fts.c237
-rw-r--r--lib/libc/gen/gen-compat.h48
-rw-r--r--lib/libc/gen/gen-private.h4
-rw-r--r--lib/libc/gen/getgrouplist.350
-rw-r--r--lib/libc/gen/getgrouplist.c6
-rw-r--r--lib/libc/gen/glob.366
-rw-r--r--lib/libc/gen/glob.c75
-rw-r--r--lib/libc/gen/initgroups.3105
-rw-r--r--lib/libc/gen/initgroups.c55
-rw-r--r--lib/libc/gen/inotify.c48
-rw-r--r--lib/libc/gen/jrand48.c7
-rw-r--r--lib/libc/gen/lcong48.c12
-rw-r--r--lib/libc/gen/libc_interposing_table.c2
-rw-r--r--lib/libc/gen/lrand48.c6
-rw-r--r--lib/libc/gen/mrand48.c8
-rw-r--r--lib/libc/gen/nrand48.c6
-rw-r--r--lib/libc/gen/opendir.c7
-rw-r--r--lib/libc/gen/opendir2.c31
-rw-r--r--lib/libc/gen/posix_spawn.c2
-rw-r--r--lib/libc/gen/psignal.383
-rw-r--r--lib/libc/gen/psignal.c6
-rw-r--r--lib/libc/gen/rand48.35
-rw-r--r--lib/libc/gen/rand48.h61
-rw-r--r--lib/libc/gen/readdir.c13
-rw-r--r--lib/libc/gen/rtld_get_var.32
-rw-r--r--lib/libc/gen/scandir.3127
-rw-r--r--lib/libc/gen/scandir.c129
-rw-r--r--lib/libc/gen/seed48.c18
-rw-r--r--lib/libc/gen/sig2str.c113
-rw-r--r--lib/libc/gen/srand48.c13
-rw-r--r--lib/libc/gen/sysconf.c9
-rw-r--r--lib/libc/gen/sysctl.323
-rw-r--r--lib/libc/gen/telldir.c4
-rw-r--r--lib/libc/gen/telldir.h6
-rw-r--r--lib/libc/gen/tls.c98
-rw-r--r--lib/libc/gen/ualarm.32
-rw-r--r--lib/libc/gen/uexterr_format.c35
-rw-r--r--lib/libc/gen/uexterr_gettext.c43
-rw-r--r--lib/libc/gen/wordexp.c10
-rw-r--r--lib/libc/i386/gen/makecontext.c2
-rw-r--r--lib/libc/i386/static_tls.h44
-rw-r--r--lib/libc/include/block_abi.h2
-rw-r--r--lib/libc/include/compat.h4
-rw-r--r--lib/libc/include/libc_private.h10
-rw-r--r--lib/libc/include/port_before.h1
-rw-r--r--lib/libc/inet/inet_net_ntop.c206
-rw-r--r--lib/libc/inet/inet_net_pton.c363
-rw-r--r--lib/libc/locale/collate.c259
-rw-r--r--lib/libc/locale/collate.h11
-rw-r--r--lib/libc/locale/euc.c2
-rw-r--r--lib/libc/md/Makefile.inc3
-rw-r--r--lib/libc/net/Makefile.inc15
-rw-r--r--lib/libc/net/Symbol.map4
-rw-r--r--lib/libc/net/gai_strerror.c44
-rw-r--r--lib/libc/net/gethostbydns.c6
-rw-r--r--lib/libc/net/linkaddr.377
-rw-r--r--lib/libc/net/linkaddr.c306
-rw-r--r--lib/libc/net/res_config.h4
-rw-r--r--lib/libc/nls/Makefile.inc5
-rw-r--r--lib/libc/powerpc/static_tls.h44
-rw-r--r--lib/libc/powerpc64/gen/_ctx_start.S14
-rw-r--r--lib/libc/powerpc64/gen/makecontext.c3
-rw-r--r--lib/libc/powerpc64/static_tls.h44
-rw-r--r--lib/libc/regex/regcomp.c1
-rw-r--r--lib/libc/resolv/Symbol.map1
-rw-r--r--lib/libc/resolv/res_debug.h2
-rw-r--r--lib/libc/resolv/res_init.c67
-rw-r--r--lib/libc/resolv/res_mkquery.c5
-rw-r--r--lib/libc/resolv/res_mkupdate.c4
-rw-r--r--lib/libc/resolv/res_query.c5
-rw-r--r--lib/libc/resolv/res_send.c49
-rw-r--r--lib/libc/riscv/static_tls.h44
-rw-r--r--lib/libc/rpc/rpc_generic.c4
-rw-r--r--lib/libc/rpc/rpc_secure.33
-rw-r--r--lib/libc/rpc/svc_nl.c3
-rw-r--r--lib/libc/stdio/Symbol.map1
-rw-r--r--lib/libc/stdio/fdopen.c18
-rw-r--r--lib/libc/stdio/freopen.c10
-rw-r--r--lib/libc/stdio/mktemp.37
-rw-r--r--lib/libc/stdio/mktemp.c2
-rw-r--r--lib/libc/stdio/vfscanf.c5
-rw-r--r--lib/libc/stdio/vfwscanf.c5
-rw-r--r--lib/libc/stdlib/Makefile.inc5
-rw-r--r--lib/libc/stdlib/Symbol.map8
-rw-r--r--lib/libc/stdlib/alloca.385
-rw-r--r--lib/libc/stdlib/atexit.c61
-rw-r--r--lib/libc/stdlib/getenv.340
-rw-r--r--lib/libc/stdlib/getenv.c36
-rw-r--r--lib/libc/stdlib/getopt_long.34
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/Makefile.inc14
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h91
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_preamble.h54
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/private_namespace.h481
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/public_namespace.h4
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc.h69
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_FreeBSD.h10
-rw-r--r--lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_defs.h55
-rw-r--r--lib/libc/stdlib/qsort.c13
-rw-r--r--lib/libc/stdlib/reallocarray.337
-rw-r--r--lib/libc/stdlib/reallocarray.c14
-rw-r--r--lib/libc/stdlib/realpath.312
-rw-r--r--lib/libc/stdlib/realpath.c14
-rw-r--r--lib/libc/stdlib/recallocarray.c73
-rw-r--r--lib/libc/stdtime/Makefile.inc4
-rw-r--r--lib/libc/stdtime/Symbol.map6
-rw-r--r--lib/libc/stdtime/ctime.339
-rw-r--r--lib/libc/stdtime/strftime.c2
-rw-r--r--lib/libc/stdtime/strptime.32
-rw-r--r--lib/libc/stdtime/strptime.c3
-rw-r--r--lib/libc/stdtime/tzset.357
-rw-r--r--lib/libc/string/memccpy.32
-rw-r--r--lib/libc/string/memchr.322
-rw-r--r--lib/libc/string/memset.32
-rw-r--r--lib/libc/sys/accept.c2
-rw-r--r--lib/libc/sys/accept4.c2
-rw-r--r--lib/libc/sys/aio_suspend.c2
-rw-r--r--lib/libc/sys/clock_nanosleep.c2
-rw-r--r--lib/libc/sys/close.c2
-rw-r--r--lib/libc/sys/closefrom.c10
-rw-r--r--lib/libc/sys/connect.c2
-rw-r--r--lib/libc/sys/fork.c2
-rw-r--r--lib/libc/sys/fsync.c2
-rw-r--r--lib/libc/sys/kevent.c2
-rw-r--r--lib/libc/sys/msync.c2
-rw-r--r--lib/libc/sys/nanosleep.c2
-rw-r--r--lib/libc/sys/open.c2
-rw-r--r--lib/libc/sys/openat.c1
-rw-r--r--lib/libc/sys/poll.c2
-rw-r--r--lib/libc/sys/ppoll.c2
-rw-r--r--lib/libc/sys/pselect.c2
-rw-r--r--lib/libc/sys/read.c2
-rw-r--r--lib/libc/sys/readv.c2
-rw-r--r--lib/libc/sys/recvfrom.c2
-rw-r--r--lib/libc/sys/recvmsg.c2
-rw-r--r--lib/libc/sys/select.c2
-rw-r--r--lib/libc/sys/sendmsg.c2
-rw-r--r--lib/libc/sys/sendto.c2
-rw-r--r--lib/libc/sys/setcontext.c1
-rw-r--r--lib/libc/sys/sigaction.c1
-rw-r--r--lib/libc/sys/sigprocmask.c1
-rw-r--r--lib/libc/sys/sigsuspend.c1
-rw-r--r--lib/libc/sys/sigtimedwait.c2
-rw-r--r--lib/libc/sys/sigwaitinfo.c2
-rw-r--r--lib/libc/sys/swapcontext.c1
-rw-r--r--lib/libc/sys/wait4.c2
-rw-r--r--lib/libc/sys/wait6.c2
-rw-r--r--lib/libc/sys/write.c2
-rw-r--r--lib/libc/sys/writev.c2
-rw-r--r--lib/libc/tests/db/Makefile4
-rw-r--r--lib/libc/tests/db/dbm_nextkey_test.c53
-rw-r--r--lib/libc/tests/db/dbm_open_test.c52
-rw-r--r--lib/libc/tests/db/dbm_perm_test.c98
-rw-r--r--lib/libc/tests/gen/Makefile28
-rw-r--r--lib/libc/tests/gen/fnmatch_test.c81
-rw-r--r--lib/libc/tests/gen/fts_blocks_test.c68
-rw-r--r--lib/libc/tests/gen/fts_misc_test.c78
-rw-r--r--lib/libc/tests/gen/fts_options_test.c394
-rw-r--r--lib/libc/tests/gen/fts_test.h81
-rw-r--r--lib/libc/tests/gen/glob2_test.c75
-rw-r--r--lib/libc/tests/gen/glob_blocks_test.c62
-rw-r--r--lib/libc/tests/gen/opendir_test.c145
-rw-r--r--lib/libc/tests/gen/posix_spawn_test.c50
-rw-r--r--lib/libc/tests/gen/realpath2_test.c106
-rw-r--r--lib/libc/tests/gen/scandir_blocks_test.c118
-rw-r--r--lib/libc/tests/gen/scandir_test.c195
-rw-r--r--lib/libc/tests/gen/sig2str_test.c213
-rw-r--r--lib/libc/tests/gen/wordexp_test.c26
-rw-r--r--lib/libc/tests/locale/wctomb_test.c13
-rw-r--r--lib/libc/tests/net/Makefile5
-rw-r--r--lib/libc/tests/net/getaddrinfo/Makefile38
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/basics_v4.exp (renamed from lib/libc/tests/net/getaddrinfo/basics_v4.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/basics_v4_only.exp50
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/basics_v4v6.exp (renamed from lib/libc/tests/net/getaddrinfo/basics_v4v6.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/basics_v4v6_prefer_v4.exp50
-rwxr-xr-xlib/libc/tests/net/getaddrinfo/data/generate_testdata.sh45
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_host_v4.exp (renamed from lib/libc/tests/net/getaddrinfo/no_host_v4.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_host_v4_only.exp (renamed from lib/libc/tests/net/getaddrinfo/no_host_v4v6.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_host_v4v6.exp68
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_host_v4v6_prefer_v4.exp68
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_serv_v4.exp (renamed from lib/libc/tests/net/getaddrinfo/no_serv_v4.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_serv_v4_only.exp20
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6.exp (renamed from lib/libc/tests/net/getaddrinfo/no_serv_v4v6.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6_prefer_v4.exp20
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/scoped.exp (renamed from lib/libc/tests/net/getaddrinfo/scoped.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/scoped_v4_only.exp5
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/scoped_v4v6_prefer_v4.exp5
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/sock_raw_v4.exp (renamed from lib/libc/tests/net/getaddrinfo/sock_raw_v4.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/sock_raw_v4_only.exp15
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6.exp (renamed from lib/libc/tests/net/getaddrinfo/sock_raw_v4v6.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6_prefer_v4.exp15
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/spec_fam_v4.exp (renamed from lib/libc/tests/net/getaddrinfo/spec_fam_v4.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/spec_fam_v4_only.exp (renamed from lib/libc/tests/net/getaddrinfo/spec_fam_v4v6.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6.exp10
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6_prefer_v4.exp10
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/unsup_fam.exp (renamed from lib/libc/tests/net/getaddrinfo/unsup_fam.exp)0
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4_only.exp2
-rw-r--r--lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4v6_prefer_v4.exp2
-rwxr-xr-xlib/libc/tests/net/getaddrinfo/getaddrinfo_test.sh443
-rw-r--r--lib/libc/tests/net/inet_net_test.cc333
-rw-r--r--lib/libc/tests/net/link_addr_test.cc532
-rw-r--r--lib/libc/tests/secure/Makefile1
-rw-r--r--lib/libc/tests/secure/fortify_signal_test.c316
-rw-r--r--lib/libc/tests/secure/fortify_stdlib_test.c147
-rwxr-xr-xlib/libc/tests/secure/generate-fortify-tests.lua21
-rw-r--r--lib/libc/tests/stdio/flushlbuf_test.c10
-rw-r--r--lib/libc/tests/stdio/printfloat_test.c13
-rw-r--r--lib/libc/tests/stdio/scanfloat_test.c136
-rw-r--r--lib/libc/tests/stdio/sscanf_test.c25
-rw-r--r--lib/libc/tests/stdio/swscanf_test.c25
-rw-r--r--lib/libc/tests/stdlib/Makefile10
-rw-r--r--lib/libc/tests/stdlib/cxa_atexit_test.c132
-rw-r--r--lib/libc/tests/stdlib/getenv_r_test.c69
-rw-r--r--lib/libc/tests/stdlib/libatexit/Makefile11
-rw-r--r--lib/libc/tests/stdlib/libatexit/libatexit.cc67
-rw-r--r--lib/libc/tests/stdlib/qsort_bench.c113
-rw-r--r--lib/libc/tests/stdtime/Makefile8
-rw-r--r--lib/libc/tests/stdtime/detect_tz_changes_test.c477
-rw-r--r--lib/libc/tests/string/memcmp_test.c12
-rw-r--r--lib/libc/tests/sys/Makefile4
-rw-r--r--lib/libc/tests/sys/swapcontext_test.c63
-rw-r--r--lib/libc/tests/tls/dso/Makefile1
-rw-r--r--lib/libc/tests/tls_dso/Makefile1
257 files changed, 9146 insertions, 2292 deletions
diff --git a/lib/libc/Makefile b/lib/libc/Makefile
index d0c254e33396..8705568f6d34 100644
--- a/lib/libc/Makefile
+++ b/lib/libc/Makefile
@@ -109,7 +109,6 @@ NOASM=
.include "${LIBC_SRCTOP}/inet/Makefile.inc"
.include "${LIBC_SRCTOP}/isc/Makefile.inc"
.include "${LIBC_SRCTOP}/locale/Makefile.inc"
-.include "${LIBC_SRCTOP}/md/Makefile.inc"
.include "${LIBC_SRCTOP}/nameser/Makefile.inc"
.include "${LIBC_SRCTOP}/net/Makefile.inc"
.include "${LIBC_SRCTOP}/nls/Makefile.inc"
diff --git a/lib/libc/Versions.def b/lib/libc/Versions.def
index 184e107d225a..1c7b34bef35b 100644
--- a/lib/libc/Versions.def
+++ b/lib/libc/Versions.def
@@ -42,6 +42,10 @@ FBSD_1.7 {
FBSD_1.8 {
} FBSD_1.7;
+# This version was first added to 16.0-current.
+FBSD_1.9 {
+} FBSD_1.8;
+
# This is our private namespace. Any global interfaces that are
# strictly for use only by other FreeBSD applications and libraries
# are listed here. We use a separate namespace so we can write
@@ -49,4 +53,4 @@ FBSD_1.8 {
#
# Please do NOT increment the version of this namespace.
FBSDprivate_1.0 {
-} FBSD_1.8;
+} FBSD_1.9;
diff --git a/lib/libc/aarch64/static_tls.h b/lib/libc/aarch64/static_tls.h
deleted file mode 100644
index 2bb717eb75ce..000000000000
--- a/lib/libc/aarch64/static_tls.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 The FreeBSD Foundation
- *
- * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
- * under sponsorship from the FreeBSD Foundation.
- *
- * 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.
- */
-
-#ifndef _LIBC_AARCH64_STATIC_TLS_H
-#define _LIBC_AARCH64_STATIC_TLS_H
-
-static __inline uintptr_t
-_libc_get_static_tls_base(size_t offset)
-{
- uintptr_t tlsbase;
-
- __asm __volatile("mrs %x0, tpidr_el0" : "=r" (tlsbase));
- tlsbase += offset;
- return (tlsbase);
-}
-
-#endif
diff --git a/lib/libc/aarch64/string/timingsafe_memcmp.S b/lib/libc/aarch64/string/timingsafe_memcmp.S
index 28fdd911a387..4cc10a152aa8 100644
--- a/lib/libc/aarch64/string/timingsafe_memcmp.S
+++ b/lib/libc/aarch64/string/timingsafe_memcmp.S
@@ -114,4 +114,4 @@ ENTRY(timingsafe_memcmp)
csetm w0, lo
csinc w0, w0, wzr, ls
ret
-END(timingsafe_bcmp)
+END(timingsafe_memcmp)
diff --git a/lib/libc/amd64/static_tls.h b/lib/libc/amd64/static_tls.h
deleted file mode 100644
index 1ee738b231c7..000000000000
--- a/lib/libc/amd64/static_tls.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 The FreeBSD Foundation
- *
- * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
- * under sponsorship from the FreeBSD Foundation.
- *
- * 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.
- */
-
-#ifndef _LIBC_AMD64_STATIC_TLS_H
-#define _LIBC_AMD64_STATIC_TLS_H
-
-static __inline uintptr_t
-_libc_get_static_tls_base(size_t offset)
-{
- uintptr_t tlsbase;
-
- __asm __volatile("movq %%fs:0, %0" : "=r" (tlsbase));
- tlsbase -= offset;
- return (tlsbase);
-}
-
-#endif
diff --git a/lib/libc/amd64/string/memrchr.S b/lib/libc/amd64/string/memrchr.S
index 4f6c5a238daa..80fb306af2a3 100644
--- a/lib/libc/amd64/string/memrchr.S
+++ b/lib/libc/amd64/string/memrchr.S
@@ -1,7 +1,7 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
- * Copyright (c) 2023 Robert Clausecker
+ * Copyright (c) 2023, 2025 Robert Clausecker <fuz@FreeBSD.org>
*/
#include <machine/asm.h>
@@ -16,150 +16,142 @@ ARCHFUNCS(memrchr)
ENDARCHFUNCS(memrchr)
ARCHENTRY(memrchr, scalar)
- xor %eax, %eax # prospective return value
- sub $4, %rdx # 4 bytes left to process?
- jb 1f
+ lea -1(%rdi, %rdx, 1), %rax # point to last char in buffer
+ sub $4, %rdx # 4 bytes left to process?
+ jb .Ltail
ALIGN_TEXT
-0: xor %r8, %r8
- lea 2(%rdi), %r10
- cmp %sil, 2(%rdi)
- cmovne %r8, %r10 # point to null if no match
+0: cmp %sil, (%rax) # match at last entry?
+ je 1f
- cmp %sil, (%rdi)
- cmove %rdi, %r8 # point to first char if match
+ cmp %sil, -1(%rax) # match at second to last entry?
+ je 2f
- lea 1(%rdi), %r9
- cmp %sil, 1(%rdi)
- cmovne %r8, %r9 # point to first result if no match in second
+ cmp %sil, -2(%rax) # match at third to last entry?
+ je 3f
- lea 3(%rdi), %r11
- cmp %sil, 3(%rdi)
- cmovne %r10, %r11
+ cmp %sil, -3(%rax) # match at fourth to last entry?
+ je 4f
- test %r11, %r11
- cmovz %r9, %r11 # take first pair match if none in second
+ sub $4, %rax
+ sub $4, %rdx
+ jae 0b
- test %r11, %r11
- cmovnz %r11, %rax # take match in current set if any
+.Ltail: cmp $-3, %edx # at least one character left to process?
+ jb .Lnotfound
- add $4, %rdi
- sub $4, %rdx
- jae 0b
+ cmp %sil, (%rax)
+ je 1f
-1: cmp $-3, %edx # a least one character left to process?
- jb 2f
+ cmp $-2, %edx # at least two characters left to process?
+ jb .Lnotfound
- cmp %sil, (%rdi)
- cmove %rdi, %rax
+ cmp %sil, -1(%rax)
+ je 2f
- lea 1(%rdi), %rcx
- cmp $-2, %edx # at least two characters left to process?
- jb 2f
+ cmp $-1, %edx # at least three characters left to process?
+ jb .Lnotfound
- cmp %sil, 1(%rdi)
- cmove %rcx, %rax
+ cmp %sil, -2(%rax)
+ je 3f
- lea 2(%rdi), %rcx
- cmp $-1, %edx # at least three character left to process?
- jb 2f
-
- cmp %sil, 2(%rdi)
- cmove %rcx, %rax
+.Lnotfound:
+ xor %eax, %eax
+ ret
-2: ret
+ /* match found -- adjust rax to point to matching byte */
+4: dec %rax
+3: dec %rax
+2: dec %rax
+1: ret
ARCHEND(memrchr, scalar)
ARCHENTRY(memrchr, baseline)
- movd %esi, %xmm4
- test %rdx, %rdx # empty buffer?
- jz .L0 # if yes, return immediately
+ test %rdx, %rdx # empty input?
+ je .Lnomatchb
+
+
+ lea (%rdi, %rdx, 1), %ecx # pointer to end of buffer
+ lea -1(%rdi, %rdx, 1), %rdx # pointer to last char in buffer
+ movd %esi, %xmm2
+ and $~0x1f, %rdx # pointer to final 32 buffer bytes
+ movdqa (%rdx), %xmm0 # load last 32 bytes
+ movdqa 16(%rdx), %xmm1
+
+ punpcklbw %xmm2, %xmm2 # c -> cc
- punpcklbw %xmm4, %xmm4 # c -> cc
- mov %edi, %ecx
- punpcklwd %xmm4, %xmm4 # cc -> cccc
- and $~0xf, %rdi # align source pointer
- pshufd $0, %xmm4, %xmm4 # cccc -> cccccccccccccccc
- and $0xf, %ecx
- movdqa %xmm4, %xmm0
mov $-1, %r8d
- pcmpeqb (%rdi), %xmm0 # compare aligned head
- shl %cl, %r8d # mask of bytes in the head of the buffer
- pmovmskb %xmm0, %eax
+ neg %ecx
+ mov %r8d, %r9d
+ shr %cl, %r8d # mask with zeroes after the string
- sub $16, %rcx
- and %r8d, %eax # match mask
- add %rcx, %rdx # advance past head
- cmc
- jbe .Lrunt # did the string end in the buffer?
+ punpcklwd %xmm2, %xmm2 # cc -> cccc
- mov %rdi, %rsi # pointer to matching chunk
- add $16, %rdi
- sub $16, %rdx # enough left for another round?
- jbe 1f
+ mov %edi, %ecx
+ mov %r9d, %eax
+ shl %cl, %r9d # mask with zeroes before the string
- /* main loop unrolled twice */
- ALIGN_TEXT
-0: movdqa %xmm4, %xmm0
- pcmpeqb (%rdi), %xmm0
- pmovmskb %xmm0, %r8d
+ pshufd $0, %xmm2, %xmm2 # cccc -> cccccccccccccccc
- cmp $16, %rdx # enough left for second chunk?
- jbe 2f
+ cmp %rdx, %rdi # tail is beginning of buffer?
+ cmovae %r9d, %eax # if yes, do combined head/tail processing
+ and %r8d, %eax # mak of bytes in tail part of string
- movdqa %xmm4, %xmm0
- pcmpeqb 16(%rdi), %xmm0
+ /* process tail */
+ pcmpeqb %xmm2, %xmm1
+ pcmpeqb %xmm2, %xmm0
+ pmovmskb %xmm1, %esi
pmovmskb %xmm0, %ecx
+ shl $16, %esi
+ or %esi, %ecx # locations of matches
+ and %ecx, %eax # any match inside buffer?
+ jnz .Lprecisematchb
- lea 16(%rdi), %r9
- test %ecx, %ecx # match found in second chunk?
- cmovz %r8d, %ecx # if not, use match data from first chunk
- cmovz %rdi, %r9
-
- test %ecx, %ecx # any match found?
- cmovnz %ecx, %eax # if yes, overwrite previously found match
- cmovnz %r9, %rsi
-
- add $32, %rdi # advance to next iteration
- sub $32, %rdx # advance to next chunks
- ja 0b
-
- /* process remaining 1--16 bytes */
-1: pcmpeqb (%rdi), %xmm4
- mov $0xffff, %r8d
- xor %ecx, %ecx
- sub %edx, %ecx # number of bytes to be masked out
- pmovmskb %xmm4, %r9d
- shr %cl, %r8d # mask of bytes to be kept in the buffer
- and %r9d, %r8d
- cmovnz %r8d, %eax
- cmovnz %rdi, %rsi
- bsr %eax, %eax
- lea (%rsi, %rax, 1), %rsi # pointer to match (or junk)
- cmovnz %rsi, %rax # if any match was found, return it
- ret
+ cmp %rdx, %rdi # did the buffer begin here?
+ jae .Lnomatchb # if yes, we are done
- /* end of chunk reached within first half iteration */
-2: test %r8d, %r8d # match in previous chunk?
- cmovnz %r8d, %eax # if yes, overwrite previous chunks
- cmovnz %rdi, %rsi
- add $16, %rdi # point to tail
- sub $16, %edx
- jmp 1b # handle tail the same otherwise
-
- /* runt: string ends within head, edx has negated amount of invalid head bytes */
-.Lrunt: mov $0xffff, %r8d
- xor %ecx, %ecx
- sub %edx, %ecx
- shr %cl, %r8d
- and %r8d, %eax
- bsr %eax, %eax
- lea (%rdi, %rax, 1), %rdi
- cmovnz %rdi, %rax
+ /* main loop */
+ ALIGN_TEXT
+0: movdqa -32(%rdx), %xmm0 # load previous string chunk
+ movdqa -16(%rdx), %xmm1
+ sub $32, %rdx # beginning of string reached?
+ cmp %rdx, %rdi
+ jae .Ltailb
+
+ pcmpeqb %xmm2, %xmm0
+ pcmpeqb %xmm2, %xmm1
+ por %xmm1, %xmm0 # match in either half?
+ pmovmskb %xmm0, %eax
+ test %eax, %eax
+ jz 0b
+
+.Lmatchb:
+ pcmpeqb (%rdx), %xmm2 # redo comparison of first 16 bytes
+ pmovmskb %xmm1, %ecx
+ pmovmskb %xmm2, %eax
+ shl $16, %ecx
+ or %ecx, %eax # location of matches
+
+.Lprecisematchb:
+ bsr %eax, %eax # find location of match
+ add %rdx, %rax # point to matching byte
ret
- /* empty buffer: return a null pointer */
-.L0: xor %eax, %eax
+.Ltailb:
+ pcmpeqb %xmm2, %xmm1
+ pcmpeqb %xmm2, %xmm0
+ pmovmskb %xmm1, %ecx
+ pmovmskb %xmm0, %eax
+ shl $16, %ecx
+ or %ecx, %eax # location of matches
+ and %r9d, %eax # mask out matches before buffer
+ bsr %eax, %edi # location of match
+ lea (%rdx, %rdi, 1), %rdx # pointer to match (if any)
+ cmovnz %rdx, %rax # point to match if present,
+ ret # else null pointer
+
+.Lnomatchb:
+ xor %eax, %eax # return null pointer
ret
ARCHEND(memrchr, baseline)
diff --git a/lib/libc/arm/aeabi/Symbol.map b/lib/libc/arm/aeabi/Symbol.map
index 175884c82881..515794004ba7 100644
--- a/lib/libc/arm/aeabi/Symbol.map
+++ b/lib/libc/arm/aeabi/Symbol.map
@@ -6,50 +6,6 @@
FBSDprivate_1.0 {
__aeabi_atexit;
- __aeabi_dcmpeq;
- __aeabi_dcmplt;
- __aeabi_dcmple;
- __aeabi_dcmpge;
- __aeabi_dcmpgt;
- __aeabi_dcmpun;
-
- __aeabi_cdcmpeq;
- __aeabi_cdcmple;
- __aeabi_cdrcmple;
-
- __aeabi_d2iz;
- __aeabi_d2f;
-
- __aeabi_dadd;
- __aeabi_ddiv;
- __aeabi_dmul;
- __aeabi_dsub;
-
-
- __aeabi_fcmpeq;
- __aeabi_fcmplt;
- __aeabi_fcmple;
- __aeabi_fcmpge;
- __aeabi_fcmpgt;
- __aeabi_fcmpun;
-
- __aeabi_cfcmpeq;
- __aeabi_cfcmple;
- __aeabi_cfrcmple;
-
- __aeabi_f2iz;
- __aeabi_f2d;
-
- __aeabi_fadd;
- __aeabi_fdiv;
- __aeabi_fmul;
- __aeabi_fsub;
-
-
- __aeabi_i2d;
- __aeabi_i2f;
-
-
__aeabi_memclr;
__aeabi_memclr4;
__aeabi_memclr8;
diff --git a/lib/libc/arm/aeabi/aeabi_int_div.S b/lib/libc/arm/aeabi/aeabi_int_div.S
index 708b478b3881..bb002c2716f7 100644
--- a/lib/libc/arm/aeabi/aeabi_int_div.S
+++ b/lib/libc/arm/aeabi/aeabi_int_div.S
@@ -37,11 +37,13 @@
*/
ENTRY(__aeabi_uidiv_compat)
+ .hidden __aeabi_uidiv_compat
.symver __aeabi_uidiv_compat, __aeabi_uidiv@
b __udivsi3
END(__aeabi_uidiv_compat)
ENTRY(__aeabi_idiv_compat)
+ .hidden __aeabi_idiv_compat
.symver __aeabi_idiv_compat, __aeabi_idiv@
b __divsi3
END(__aeabi_idiv_compat)
diff --git a/lib/libc/arm/aeabi/aeabi_vfp.h b/lib/libc/arm/aeabi/aeabi_vfp.h
index 3b70fe06fab3..4f3bb2ef969b 100644
--- a/lib/libc/arm/aeabi/aeabi_vfp.h
+++ b/lib/libc/arm/aeabi/aeabi_vfp.h
@@ -40,7 +40,9 @@
#define AEABI_ENTRY(x) ENTRY(__aeabi_ ## x ## _vfp)
#define AEABI_END(x) END(__aeabi_ ## x ## _vfp)
#else
-#define AEABI_ENTRY(x) ENTRY(__aeabi_ ## x)
+#define AEABI_ENTRY(x) \
+ ENTRY(__aeabi_ ## x) \
+ .symver __aeabi_##x, __aeabi_##x##@FBSDprivate_1.0;
#define AEABI_END(x) END(__aeabi_ ## x)
#endif
diff --git a/lib/libc/arm/static_tls.h b/lib/libc/arm/static_tls.h
deleted file mode 100644
index 557748d75347..000000000000
--- a/lib/libc/arm/static_tls.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 The FreeBSD Foundation
- *
- * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
- * under sponsorship from the FreeBSD Foundation.
- *
- * 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.
- */
-
-#ifndef _LIBC_ARM_STATIC_TLS_H
-#define _LIBC_ARM_STATIC_TLS_H
-
-static __inline uintptr_t
-_libc_get_static_tls_base(size_t offset)
-{
- uintptr_t tlsbase;
-
- __asm __volatile("mrc p15, 0, %0, c13, c0, 3" : "=r" (tlsbase));
-
- tlsbase += offset;
- return (tlsbase);
-}
-
-#endif
diff --git a/lib/libc/csu/libc_start1.c b/lib/libc/csu/libc_start1.c
index 045ea1e68141..5b08ba03ba8c 100644
--- a/lib/libc/csu/libc_start1.c
+++ b/lib/libc/csu/libc_start1.c
@@ -28,6 +28,7 @@
#include <sys/param.h>
#include <sys/elf.h>
#include <sys/elf_common.h>
+#include <errno.h>
#include <stdlib.h>
#include "libc_private.h"
@@ -169,6 +170,13 @@ __libc_start1(int argc, char *argv[], char *env[], void (*cleanup)(void),
}
handle_static_init(argc, argv, env);
+
+ /*
+ * C17 4.3 paragraph 3:
+ * The value of errno in the initial thread is zero at program
+ * startup.
+ */
+ errno = 0;
exit(mainX(argc, argv, env));
}
@@ -194,5 +202,6 @@ __libc_start1_gcrt(int argc, char *argv[], char *env[],
monstartup(eprolp, etextp);
handle_static_init(argc, argv, env);
+ errno = 0;
exit(mainX(argc, argv, env));
}
diff --git a/lib/libc/db/hash/hash.c b/lib/libc/db/hash/hash.c
index 7a66f5443d94..88a3ffeab828 100644
--- a/lib/libc/db/hash/hash.c
+++ b/lib/libc/db/hash/hash.c
@@ -99,11 +99,6 @@ __hash_open(const char *file, int flags, int mode,
DB *dbp;
int bpages, hdrsize, new_table, nsegs, save_errno;
- if ((flags & O_ACCMODE) == O_WRONLY) {
- errno = EINVAL;
- return (NULL);
- }
-
if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB))))
return (NULL);
hashp->fp = -1;
@@ -115,12 +110,17 @@ __hash_open(const char *file, int flags, int mode,
* we can check accesses.
*/
hashp->flags = flags;
+ if ((flags & O_ACCMODE) == O_WRONLY) {
+ flags &= ~O_WRONLY;
+ flags |= O_RDWR;
+ }
if (file) {
if ((hashp->fp = _open(file, flags | O_CLOEXEC, mode)) == -1)
RETURN_ERROR(errno, error0);
new_table = _fstat(hashp->fp, &statbuf) == 0 &&
- statbuf.st_size == 0 && (flags & O_ACCMODE) != O_RDONLY;
+ statbuf.st_size == 0 &&
+ ((flags & O_ACCMODE) != O_RDONLY || (flags & O_CREAT) != 0);
} else
new_table = 1;
@@ -179,7 +179,7 @@ __hash_open(const char *file, int flags, int mode,
__buf_init(hashp, DEF_BUFSIZE);
hashp->new_file = new_table;
- hashp->save_file = file && (hashp->flags & O_RDWR);
+ hashp->save_file = file && (flags & O_RDWR);
hashp->cbucket = -1;
if (!(dbp = (DB *)malloc(sizeof(DB)))) {
save_errno = errno;
@@ -523,6 +523,10 @@ hash_get(const DB *dbp, const DBT *key, DBT *data, u_int32_t flag)
hashp->error = errno = EINVAL;
return (ERROR);
}
+ if ((hashp->flags & O_ACCMODE) == O_WRONLY) {
+ hashp->error = errno = EPERM;
+ return (ERROR);
+ }
return (hash_access(hashp, HASH_GET, (DBT *)key, data));
}
@@ -700,17 +704,19 @@ hash_seq(const DB *dbp, DBT *key, DBT *data, u_int32_t flag)
u_int16_t *bp, ndx;
hashp = (HTAB *)dbp->internal;
- if (flag && flag != R_FIRST && flag != R_NEXT) {
+ if (flag != R_FIRST && flag != R_NEXT) {
hashp->error = errno = EINVAL;
return (ERROR);
}
#ifdef HASH_STATISTICS
hash_accesses++;
#endif
- if ((hashp->cbucket < 0) || (flag == R_FIRST)) {
+ if (flag == R_FIRST) {
hashp->cbucket = 0;
hashp->cndx = 1;
hashp->cpage = NULL;
+ } else if (hashp->cbucket < 0) { /* R_NEXT */
+ return (ABNORMAL);
}
next_bucket:
for (bp = NULL; !bp || !bp[0]; ) {
diff --git a/lib/libc/db/man/dbm.3 b/lib/libc/db/man/dbm.3
index c5a83c7acef4..30787600ad2d 100644
--- a/lib/libc/db/man/dbm.3
+++ b/lib/libc/db/man/dbm.3
@@ -13,7 +13,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd April 2, 2022
+.Dd July 25, 2025
.Dt DBM 3
.Os
.Sh NAME
@@ -99,9 +99,6 @@ is a typical value for
.Li 0660
is a typical value for
.Fa mode .
-.Dv O_WRONLY
-is not allowed in
-.Fa flags .
The pointer returned by
.Fn dbm_open
identifies the database and is the
diff --git a/lib/libc/db/man/dbopen.3 b/lib/libc/db/man/dbopen.3
index 64cef88506d8..7fe515f17849 100644
--- a/lib/libc/db/man/dbopen.3
+++ b/lib/libc/db/man/dbopen.3
@@ -76,13 +76,10 @@ are as specified to the
.Xr open 2
routine, however, only the
.Dv O_CREAT , O_EXCL , O_EXLOCK , O_NOFOLLOW , O_NONBLOCK ,
-.Dv O_RDONLY , O_RDWR , O_SHLOCK , O_SYNC
+.Dv O_RDONLY , O_RDWR , O_SHLOCK , O_SYNC, O_WRONLY,
and
.Dv O_TRUNC
flags are meaningful.
-(Note, opening a database file
-.Dv O_WRONLY
-is not possible.)
.\"Three additional options may be specified by
.\".Em or Ns 'ing
.\"them into the
diff --git a/lib/libc/gdtoa/_hdtoa.c b/lib/libc/gdtoa/_hdtoa.c
index 8ae739acf0db..9c42630cd918 100644
--- a/lib/libc/gdtoa/_hdtoa.c
+++ b/lib/libc/gdtoa/_hdtoa.c
@@ -40,6 +40,7 @@
#define DBL_ADJ (DBL_MAX_EXP - 2)
#define SIGFIGS ((DBL_MANT_DIG + 3) / 4 + 1)
+#define MAX_HEX_DIGITS ((DBL_MANT_DIG + 3 - 1) / 4 + 1)
static const float one[] = { 1.0f, -1.0f };
@@ -111,7 +112,7 @@ __hdtoa(double d, const char *xdigs, int ndigits, int *decpt, int *sign,
s0 = rv_alloc(bufsize);
/* Round to the desired number of digits. */
- if (SIGFIGS > ndigits && ndigits > 0) {
+ if (MAX_HEX_DIGITS > ndigits && ndigits > 0) {
float redux = one[u.bits.sign];
int offset = 4 * ndigits + DBL_MAX_EXP - 4 - DBL_MANT_DIG;
u.bits.exp = offset;
diff --git a/lib/libc/gdtoa/_hldtoa.c b/lib/libc/gdtoa/_hldtoa.c
index 965d2349d103..5f10d12c5c09 100644
--- a/lib/libc/gdtoa/_hldtoa.c
+++ b/lib/libc/gdtoa/_hldtoa.c
@@ -65,6 +65,7 @@ typedef uint32_t manl_t;
#define LDBL_ADJ (LDBL_MAX_EXP - 2)
#define SIGFIGS ((LDBL_MANT_DIG + 3) / 4 + 1)
+#define MAX_HEX_DIGITS ((LDBL_MANT_DIG + 3 - 1) / 4 + 1)
static const float one[] = { 1.0f, -1.0f };
@@ -125,7 +126,7 @@ __hldtoa(long double e, const char *xdigs, int ndigits, int *decpt, int *sign,
s0 = rv_alloc(bufsize);
/* Round to the desired number of digits. */
- if (SIGFIGS > ndigits && ndigits > 0) {
+ if (MAX_HEX_DIGITS > ndigits && ndigits > 0) {
float redux = one[u.bits.sign];
int offset = 4 * ndigits + LDBL_MAX_EXP - 4 - LDBL_MANT_DIG;
#ifdef __i386__
diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc
index 9b92e52cdfad..4d064d18d36e 100644
--- a/lib/libc/gen/Makefile.inc
+++ b/lib/libc/gen/Makefile.inc
@@ -89,6 +89,7 @@ SRCS+= \
glob.c \
glob-compat11.c \
initgroups.c \
+ inotify.c \
isatty.c \
isinf.c \
isnan.c \
@@ -134,6 +135,7 @@ SRCS+= \
setmode.c \
setproctitle.c \
setprogname.c \
+ sig2str.c \
siginterrupt.c \
siglist.c \
signal.c \
@@ -159,6 +161,8 @@ SRCS+= \
ttyname.c \
ttyslot.c \
ualarm.c \
+ uexterr_format.c \
+ uexterr_gettext.c \
ulimit.c \
uname.c \
unvis-compat.c \
@@ -168,6 +172,11 @@ SRCS+= \
valloc.c \
wordexp.c
+.if ${COMPILER_FEATURES:Mblocks}
+CFLAGS.fts.c= -fblocks
+CFLAGS.glob.c= -fblocks
+.endif
+
CFLAGS.arc4random.c= -I${SRCTOP}/sys -I${SRCTOP}/sys/crypto/chacha20
CFLAGS.sysconf.c= -I${SRCTOP}/contrib/tzcode
@@ -362,7 +371,8 @@ MLINKS+=exec.3 execl.3 \
exec.3 exect.3 \
exec.3 execv.3 \
exec.3 execvP.3 \
- exec.3 execvp.3
+ exec.3 execvp.3 \
+ exec.3 execvpe.3
MLINKS+=fpclassify.3 finite.3 \
fpclassify.3 finitef.3 \
fpclassify.3 isfinite.3 \
@@ -465,7 +475,10 @@ MLINKS+=posix_spawn.3 posix_spawnp.3 \
posix_spawnattr_getsigdefault.3 posix_spawnattr_setsigdefault.3 \
posix_spawnattr_getsigmask.3 posix_spawnattr_setsigmask.3 \
posix_spawnattr_init.3 posix_spawnattr_destroy.3
-MLINKS+=psignal.3 strsignal.3 \
+MLINKS+=psignal.3 psiginfo.3 \
+ psignal.3 sig2str.3 \
+ psignal.3 str2sig.3 \
+ psignal.3 strsignal.3 \
psignal.3 sys_siglist.3 \
psignal.3 sys_signame.3
MLINKS+=pwcache.3 gid_from_group.3 \
@@ -487,8 +500,11 @@ MLINKS+=rand48.3 _rand48.3 \
MLINKS+=rtld_get_var.3 \
rtld_set_var.3
MLINKS+=scandir.3 alphasort.3 \
- scandir.3 scandirat.3 \
+ scandir.3 fdscandir.3 \
+ scandir.3 fdscandir_b.3 \
scandir.3 scandir_b.3 \
+ scandir.3 scandirat.3 \
+ scandir.3 scandirat_b.3 \
scandir.3 versionsort.3
MLINKS+=sem_open.3 sem_close.3 \
sem_open.3 sem_unlink.3
diff --git a/lib/libc/gen/Symbol.map b/lib/libc/gen/Symbol.map
index ca3974e6b747..494b65bc5cc1 100644
--- a/lib/libc/gen/Symbol.map
+++ b/lib/libc/gen/Symbol.map
@@ -193,7 +193,6 @@ FBSD_1.0 {
__isinff;
__isinfl;
isatty;
- initgroups;
jrand48;
lcong48;
ldexp;
@@ -458,8 +457,21 @@ FBSD_1.8 {
aio_read2;
aio_write2;
execvpe;
+ fdscandir;
+ fdscandir_b;
+ fts_open_b;
+ glob_b;
+ initgroups;
+ inotify_add_watch;
+ inotify_init;
+ inotify_init1;
+ psiginfo;
rtld_get_var;
rtld_set_var;
+ scandirat_b;
+ uexterr_gettext;
+ sig2str;
+ str2sig;
};
FBSDprivate_1.0 {
@@ -584,10 +596,10 @@ FBSDprivate_1.0 {
__libc_tcdrain;
- __pthread_distribute_static_tls;
__pthread_map_stacks_exec;
__fillcontextx;
__fillcontextx2;
__getcontextx_size;
__makecontext;
+ __uexterr_format;
};
diff --git a/lib/libc/gen/_pthread_stubs.c b/lib/libc/gen/_pthread_stubs.c
index 2a0cebadd5fd..d867ee4db51e 100644
--- a/lib/libc/gen/_pthread_stubs.c
+++ b/lib/libc/gen/_pthread_stubs.c
@@ -63,6 +63,7 @@ static int stub_getname_np(pthread_t, char *, size_t);
#define PJT_DUAL_ENTRY(entry) \
(pthread_func_t)entry, (pthread_func_t)entry
+__attribute__((visibility("protected")))
pthread_func_entry_t __thr_jtable[PJT_MAX] = {
[PJT_ATFORK] = {PJT_DUAL_ENTRY(stub_zero)},
[PJT_ATTR_DESTROY] = {PJT_DUAL_ENTRY(stub_zero)},
diff --git a/lib/libc/gen/_rand48.c b/lib/libc/gen/_rand48.c
index 990e2c86949b..114c1595b33d 100644
--- a/lib/libc/gen/_rand48.c
+++ b/lib/libc/gen/_rand48.c
@@ -13,34 +13,6 @@
#include "rand48.h"
-unsigned short _rand48_seed[3] = {
- RAND48_SEED_0,
- RAND48_SEED_1,
- RAND48_SEED_2
-};
-unsigned short _rand48_mult[3] = {
- RAND48_MULT_0,
- RAND48_MULT_1,
- RAND48_MULT_2
-};
-unsigned short _rand48_add = RAND48_ADD;
-
-void
-_dorand48(unsigned short xseed[3])
-{
- unsigned long accu;
- unsigned short temp[2];
-
- accu = (unsigned long) _rand48_mult[0] * (unsigned long) xseed[0] +
- (unsigned long) _rand48_add;
- temp[0] = (unsigned short) accu; /* lower 16 bits */
- accu >>= sizeof(unsigned short) * 8;
- accu += (unsigned long) _rand48_mult[0] * (unsigned long) xseed[1] +
- (unsigned long) _rand48_mult[1] * (unsigned long) xseed[0];
- temp[1] = (unsigned short) accu; /* middle 16 bits */
- accu >>= sizeof(unsigned short) * 8;
- accu += _rand48_mult[0] * xseed[2] + _rand48_mult[1] * xseed[1] + _rand48_mult[2] * xseed[0];
- xseed[0] = temp[0];
- xseed[1] = temp[1];
- xseed[2] = (unsigned short) accu;
-}
+uint48 _rand48_seed = RAND48_SEED;
+uint48 _rand48_mult = RAND48_MULT;
+uint48 _rand48_add = RAND48_ADD;
diff --git a/lib/libc/gen/alarm.3 b/lib/libc/gen/alarm.3
index 8aeda29481eb..4a24cc6d7216 100644
--- a/lib/libc/gen/alarm.3
+++ b/lib/libc/gen/alarm.3
@@ -63,7 +63,7 @@ Due to
.Xr setitimer 2
restriction the maximum number of
.Fa seconds
-allowed is 100000000.
+allowed is 100,000,000.
.Sh RETURN VALUES
The return value of
.Fn alarm
diff --git a/lib/libc/gen/dlfcn.c b/lib/libc/gen/dlfcn.c
index bffee3952e0d..ae1c8d83df19 100644
--- a/lib/libc/gen/dlfcn.c
+++ b/lib/libc/gen/dlfcn.c
@@ -229,7 +229,7 @@ _dl_iterate_phdr_locked(
return (1);
_once(&dl_phdr_info_once, dl_init_phdr_info);
ti.ti_module = 1;
- ti.ti_offset = 0;
+ ti.ti_offset = -TLS_DTV_OFFSET;
phdr_info.dlpi_tls_data = __tls_get_addr(&ti);
ret = callback(&phdr_info, sizeof(phdr_info), data);
return (ret);
diff --git a/lib/libc/gen/drand48.c b/lib/libc/gen/drand48.c
index cec04a6a2425..f7f43ff20468 100644
--- a/lib/libc/gen/drand48.c
+++ b/lib/libc/gen/drand48.c
@@ -13,10 +13,10 @@
#include "rand48.h"
-extern unsigned short _rand48_seed[3];
-
double
drand48(void)
{
- return erand48(_rand48_seed);
+ ERAND48_BEGIN;
+ _DORAND48(_rand48_seed);
+ ERAND48_END(_rand48_seed);
}
diff --git a/lib/libc/gen/dup3.3 b/lib/libc/gen/dup3.3
index f2798930797b..338a9ae74c64 100644
--- a/lib/libc/gen/dup3.3
+++ b/lib/libc/gen/dup3.3
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd August 16, 2013
+.Dd May 17, 2025
.Dt DUP3 3
.Os
.Sh NAME
@@ -47,6 +47,11 @@ The close-on-exec flag on the new file descriptor is determined by the
bit in
.Fa flags .
.Pp
+The close-on-fork flag on the new file descriptor is determined by the
+.Dv O_CLOFORK
+bit in
+.Fa flags .
+.Pp
If
.Fa oldd
\*(Ne
@@ -91,7 +96,9 @@ argument.
The
.Fa flags
argument has bits set other than
-.Dv O_CLOEXEC .
+.Dv O_CLOEXEC
+or
+.Dv O_CLOFORK .
.El
.Sh SEE ALSO
.Xr accept 2 ,
@@ -112,3 +119,7 @@ The
.Fn dup3
function appeared in
.Fx 10.0 .
+The
+.Dv O_CLOFORK
+flag appeared in
+.Fx 15.0 .
diff --git a/lib/libc/gen/dup3.c b/lib/libc/gen/dup3.c
index fca1e99fb47b..1401c1f5b607 100644
--- a/lib/libc/gen/dup3.c
+++ b/lib/libc/gen/dup3.c
@@ -39,21 +39,22 @@ int __dup3(int, int, int);
int
__dup3(int oldfd, int newfd, int flags)
{
- int how;
+ int fdflags;
if (oldfd == newfd) {
errno = EINVAL;
return (-1);
}
- if (flags & ~O_CLOEXEC) {
+ if ((flags & ~(O_CLOEXEC | O_CLOFORK)) != 0) {
errno = EINVAL;
return (-1);
}
- how = (flags & O_CLOEXEC) ? F_DUP2FD_CLOEXEC : F_DUP2FD;
+ fdflags = ((flags & O_CLOEXEC) != 0 ? FD_CLOEXEC : 0) |
+ ((flags & O_CLOFORK) != 0 ? FD_CLOFORK : 0);
- return (_fcntl(oldfd, how, newfd));
+ return (_fcntl(oldfd, F_DUP3FD | (fdflags << F_DUP3FD_SHIFT), newfd));
}
__weak_reference(__dup3, dup3);
diff --git a/lib/libc/gen/elf_utils.c b/lib/libc/gen/elf_utils.c
index 5b87e012d0eb..3714a0dc42b5 100644
--- a/lib/libc/gen/elf_utils.c
+++ b/lib/libc/gen/elf_utils.c
@@ -31,14 +31,16 @@
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
+
+#include <machine/tls.h>
+
#include <link.h>
#include <stddef.h>
#include <string.h>
+
#include "libc_private.h"
-#include "static_tls.h"
void __pthread_map_stacks_exec(void);
-void __pthread_distribute_static_tls(size_t, void *, size_t, size_t);
int
__elf_phdr_match_addr(struct dl_phdr_info *phdr_info, void *addr)
@@ -102,24 +104,3 @@ __pthread_map_stacks_exec(void)
((void (*)(void))__libc_interposing[INTERPOS_map_stacks_exec])();
}
-
-void
-__libc_distribute_static_tls(size_t offset, void *src, size_t len,
- size_t total_len)
-{
- uintptr_t tlsbase;
-
- tlsbase = _libc_get_static_tls_base(offset);
- memcpy((void *)tlsbase, src, len);
- memset((char *)tlsbase + len, 0, total_len - len);
-}
-
-#pragma weak __pthread_distribute_static_tls
-void
-__pthread_distribute_static_tls(size_t offset, void *src, size_t len,
- size_t total_len)
-{
-
- ((void (*)(size_t, void *, size_t, size_t))__libc_interposing[
- INTERPOS_distribute_static_tls])(offset, src, len, total_len);
-}
diff --git a/lib/libc/gen/erand48.c b/lib/libc/gen/erand48.c
index 286904c27839..38d4774a9fe6 100644
--- a/lib/libc/gen/erand48.c
+++ b/lib/libc/gen/erand48.c
@@ -16,8 +16,9 @@
double
erand48(unsigned short xseed[3])
{
- _dorand48(xseed);
- return ldexp((double) xseed[0], -48) +
- ldexp((double) xseed[1], -32) +
- ldexp((double) xseed[2], -16);
+ uint48 tmp;
+
+ ERAND48_BEGIN;
+ DORAND48(tmp, xseed);
+ ERAND48_END(tmp);
}
diff --git a/lib/libc/gen/err.c b/lib/libc/gen/err.c
index 24ea242560b8..16cbe27693e7 100644
--- a/lib/libc/gen/err.c
+++ b/lib/libc/gen/err.c
@@ -30,9 +30,12 @@
*/
#include "namespace.h"
+#include <sys/exterrvar.h>
#include <err.h>
#include <errno.h>
+#include <exterr.h>
#include <stdarg.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -43,6 +46,11 @@
static FILE *err_file; /* file to use for error output */
static void (*err_exit)(int);
+static void verrci(bool doexterr, int eval, int code, const char *fmt,
+ va_list ap) __printf0like(4, 0) __dead2;
+static void vwarnci(bool doexterr, int code, const char *fmt, va_list ap)
+ __printf0like(3, 0);
+
/*
* This is declared to take a `void *' so that the caller is not required
* to include <stdio.h> first. However, it is really a `FILE *', and the
@@ -70,14 +78,14 @@ _err(int eval, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
- verrc(eval, errno, fmt, ap);
+ verrci(true, eval, errno, fmt, ap);
va_end(ap);
}
void
verr(int eval, const char *fmt, va_list ap)
{
- verrc(eval, errno, fmt, ap);
+ verrci(true, eval, errno, fmt, ap);
}
void
@@ -85,13 +93,24 @@ errc(int eval, int code, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
- verrc(eval, code, fmt, ap);
+ verrci(false, eval, code, fmt, ap);
va_end(ap);
}
void
verrc(int eval, int code, const char *fmt, va_list ap)
{
+ verrci(false, eval, code, fmt, ap);
+}
+
+static void
+vexterr(bool doexterr, int code, const char *fmt, va_list ap)
+{
+ char exterr[UEXTERROR_MAXLEN]; /* libc knows the buffer size */
+ int extstatus;
+
+ if (doexterr)
+ extstatus = uexterr_gettext(exterr, sizeof(exterr));
if (err_file == NULL)
err_set_file(NULL);
fprintf(err_file, "%s: ", _getprogname());
@@ -99,7 +118,16 @@ verrc(int eval, int code, const char *fmt, va_list ap)
vfprintf(err_file, fmt, ap);
fprintf(err_file, ": ");
}
- fprintf(err_file, "%s\n", strerror(code));
+ fprintf(err_file, "%s", strerror(code));
+ if (doexterr && extstatus == 0 && exterr[0] != '\0')
+ fprintf(err_file, " (extended error %s)", exterr);
+ fprintf(err_file, "\n");
+}
+
+static void
+verrci(bool doexterr, int eval, int code, const char *fmt, va_list ap)
+{
+ vexterr(doexterr, code, fmt, ap);
if (err_exit)
err_exit(eval);
exit(eval);
@@ -157,17 +185,16 @@ warnc(int code, const char *fmt, ...)
void
vwarnc(int code, const char *fmt, va_list ap)
{
+ vwarnci(false, code, fmt, ap);
+}
+
+static void
+vwarnci(bool doexterr, int code, const char *fmt, va_list ap)
+{
int saved_errno;
saved_errno = errno;
- if (err_file == NULL)
- err_set_file(NULL);
- fprintf(err_file, "%s: ", _getprogname());
- if (fmt != NULL) {
- vfprintf(err_file, fmt, ap);
- fprintf(err_file, ": ");
- }
- fprintf(err_file, "%s\n", strerror(code));
+ vexterr(doexterr, code, fmt, ap);
errno = saved_errno;
}
diff --git a/lib/libc/gen/exec.c b/lib/libc/gen/exec.c
index d9285733e965..12020a79f6b4 100644
--- a/lib/libc/gen/exec.c
+++ b/lib/libc/gen/exec.c
@@ -32,8 +32,10 @@
#include "namespace.h"
#include <sys/param.h>
#include <sys/stat.h>
+#include <assert.h>
#include <errno.h>
#include <unistd.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@@ -136,29 +138,132 @@ execv(const char *name, char * const *argv)
int
execvp(const char *name, char * const *argv)
{
- return (execvpe(name, argv, environ));
+ return (__libc_execvpe(name, argv, environ));
+}
+
+/*
+ * Returns 0 if we don't consider this a terminal condition, -1 if we do.
+ */
+static int
+execvPe_prog(const char *path, char * const *argv, char * const *envp)
+{
+ struct stat sb;
+ const char **memp;
+ size_t cnt;
+ int save_errno;
+
+ (void)_execve(path, argv, envp);
+ /* Grouped roughly by never terminal vs. usually terminal conditions */
+ switch (errno) {
+ case ELOOP:
+ case ENAMETOOLONG:
+ case ENOENT:
+ case ENOTDIR:
+ /* Non-terminal: property of the path we're trying */
+ break;
+ case ENOEXEC:
+ /*
+ * Failures here are considered terminal because we must handle
+ * this via the ENOEXEC fallback path; doing any further
+ * searching would be categorically incorrect.
+ */
+
+ for (cnt = 0; argv[cnt] != NULL; ++cnt)
+ ;
+
+ /*
+ * cnt may be 0 above; always allocate at least
+ * 3 entries so that we can at least fit "sh", path, and
+ * the NULL terminator. We can rely on cnt to take into
+ * account the NULL terminator in all other scenarios,
+ * as we drop argv[0].
+ */
+ memp = alloca(MAX(3, cnt + 2) * sizeof(char *));
+ assert(memp != NULL);
+ if (cnt > 0) {
+ memp[0] = argv[0];
+ memp[1] = path;
+ memcpy(&memp[2], &argv[1], cnt * sizeof(char *));
+ } else {
+ memp[0] = "sh";
+ memp[1] = path;
+ memp[2] = NULL;
+ }
+
+ (void)_execve(_PATH_BSHELL, __DECONST(char **, memp), envp);
+ return (-1);
+ case ENOMEM:
+ case E2BIG:
+ /* Terminal: persistent condition */
+ return (-1);
+ case ETXTBSY:
+ /*
+ * Terminal: we used to retry here, but sh(1) doesn't.
+ */
+ return (-1);
+ default:
+ /*
+ * EACCES may be for an inaccessible directory or
+ * a non-executable file. Call stat() to decide
+ * which. This also handles ambiguities for EFAULT
+ * and EIO, and undocumented errors like ESTALE.
+ * We hope that the race for a stat() is unimportant.
+ */
+ save_errno = errno;
+ if (stat(path, &sb) == -1) {
+ /*
+ * We force errno to ENOENT here to disambiguate the
+ * EACCESS case; the results of execve(2) are somewhat
+ * inconclusive because either the file did not exist or
+ * we just don't have search permissions, but the caller
+ * only really wants to see EACCES if the file did exist
+ * but was not accessible.
+ */
+ if (save_errno == EACCES)
+ errno = ENOENT;
+ break;
+ }
+
+ errno = save_errno;
+
+ /*
+ * Non-terminal: the file did exist and we just didn't have
+ * access to it, so we surface the EACCES and let the search
+ * continue for a candidate that we do have access to.
+ */
+ if (errno == EACCES)
+ break;
+
+ /*
+ * All other errors here are terminal, as prescribed by exec(3).
+ */
+ return (-1);
+ }
+
+ return (0);
}
static int
execvPe(const char *name, const char *path, char * const *argv,
char * const *envp)
{
- const char **memp;
- size_t cnt, lp, ln;
- int eacces, save_errno;
char buf[MAXPATHLEN];
- const char *bp, *np, *op, *p;
- struct stat sb;
+ size_t ln, lp;
+ const char *np, *op, *p;
+ bool eacces;
- eacces = 0;
+ eacces = false;
/* If it's an absolute or relative path name, it's easy. */
- if (strchr(name, '/')) {
- bp = name;
- op = NULL;
- goto retry;
+ if (strchr(name, '/') != NULL) {
+ /*
+ * We ignore non-terminal conditions because we don't have any
+ * further paths to try -- we can just bubble up the errno from
+ * execve(2) here.
+ */
+ (void)execvPe_prog(name, argv, envp);
+ return (-1);
}
- bp = buf;
/* If it's an empty path name, fail in the usual POSIX way. */
if (*name == '\0') {
@@ -192,9 +297,13 @@ execvPe(const char *name, const char *path, char * const *argv,
op = np + 1;
/*
- * If the path is too long complain. This is a possible
- * security issue; given a way to make the path too long
- * the user may execute the wrong program.
+ * If the path is too long, then complain. This is a possible
+ * security issue: given a way to make the path too long, the
+ * user may execute the wrong program.
+ *
+ * Remember to exercise caution here with assembling our final
+ * buf and any output, as we may be running in a vfork() context
+ * via posix_spawnp().
*/
if (lp + ln + 2 > sizeof(buf)) {
(void)_write(STDERR_FILENO, execvPe_err_preamble,
@@ -202,82 +311,40 @@ execvPe(const char *name, const char *path, char * const *argv,
(void)_write(STDERR_FILENO, p, lp);
(void)_write(STDERR_FILENO, execvPe_err_trailer,
sizeof(execvPe_err_trailer) - 1);
+
continue;
}
- bcopy(p, buf, lp);
+
+ memcpy(&buf[0], p, lp);
buf[lp] = '/';
- bcopy(name, buf + lp + 1, ln);
+ memcpy(&buf[lp + 1], name, ln);
buf[lp + ln + 1] = '\0';
-retry: (void)_execve(bp, argv, envp);
- switch (errno) {
- case E2BIG:
- goto done;
- case ELOOP:
- case ENAMETOOLONG:
- case ENOENT:
- break;
- case ENOEXEC:
- for (cnt = 0; argv[cnt]; ++cnt)
- ;
-
- /*
- * cnt may be 0 above; always allocate at least
- * 3 entries so that we can at least fit "sh", bp, and
- * the NULL terminator. We can rely on cnt to take into
- * account the NULL terminator in all other scenarios,
- * as we drop argv[0].
- */
- memp = alloca(MAX(3, cnt + 2) * sizeof(char *));
- if (memp == NULL) {
- /* errno = ENOMEM; XXX override ENOEXEC? */
- goto done;
- }
- if (cnt > 0) {
- memp[0] = argv[0];
- memp[1] = bp;
- bcopy(argv + 1, memp + 2, cnt * sizeof(char *));
- } else {
- memp[0] = "sh";
- memp[1] = bp;
- memp[2] = NULL;
- }
- (void)_execve(_PATH_BSHELL,
- __DECONST(char **, memp), envp);
- goto done;
- case ENOMEM:
- goto done;
- case ENOTDIR:
- break;
- case ETXTBSY:
- /*
- * We used to retry here, but sh(1) doesn't.
- */
- goto done;
- default:
- /*
- * EACCES may be for an inaccessible directory or
- * a non-executable file. Call stat() to decide
- * which. This also handles ambiguities for EFAULT
- * and EIO, and undocumented errors like ESTALE.
- * We hope that the race for a stat() is unimportant.
- */
- save_errno = errno;
- if (stat(bp, &sb) != 0)
- break;
- if (save_errno == EACCES) {
- eacces = 1;
- continue;
- }
- errno = save_errno;
- goto done;
- }
+ /*
+ * For terminal conditions we can just return immediately. If
+ * it was non-terminal, we just need to note if we had an
+ * EACCES -- execvPe_prog would do a stat(2) and leave us with
+ * an errno of EACCES only if the file did exist; otherwise it
+ * would coerce it to an ENOENT because we may not know if a
+ * file actually existed there or not.
+ */
+ if (execvPe_prog(buf, argv, envp) == -1)
+ return (-1);
+ if (errno == EACCES)
+ eacces = true;
}
+
+ /*
+ * We don't often preserve errors encountering during the PATH search,
+ * so we override it here. ENOENT would be misleading if we found a
+ * candidate but couldn't access it, but most of the other conditions
+ * are either terminal or indicate that nothing was there.
+ */
if (eacces)
errno = EACCES;
else
errno = ENOENT;
-done:
+
return (-1);
}
@@ -288,7 +355,7 @@ execvP(const char *name, const char *path, char * const argv[])
}
int
-execvpe(const char *name, char * const argv[], char * const envp[])
+__libc_execvpe(const char *name, char * const argv[], char * const envp[])
{
const char *path;
@@ -298,3 +365,5 @@ execvpe(const char *name, char * const argv[], char * const envp[])
return (execvPe(name, path, argv, envp));
}
+
+__weak_reference(__libc_execvpe, execvpe);
diff --git a/lib/libc/gen/fdopendir.c b/lib/libc/gen/fdopendir.c
index 67c0766b6d83..9393cbe28f85 100644
--- a/lib/libc/gen/fdopendir.c
+++ b/lib/libc/gen/fdopendir.c
@@ -30,14 +30,13 @@
*/
#include "namespace.h"
-#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
+#include <stdbool.h>
#include "un-namespace.h"
#include "gen-private.h"
@@ -49,8 +48,16 @@
DIR *
fdopendir(int fd)
{
+ int flags, rc;
- if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+ flags = _fcntl(fd, F_GETFD, 0);
+ if (flags == -1)
return (NULL);
+
+ if ((flags & FD_CLOEXEC) == 0) {
+ rc = _fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+ if (rc == -1)
+ return (NULL);
+ }
return (__opendir_common(fd, DTF_HIDEW | DTF_NODUP, true));
}
diff --git a/lib/libc/gen/fnmatch.3 b/lib/libc/gen/fnmatch.3
index 804bc968c1ce..7f020fec58e3 100644
--- a/lib/libc/gen/fnmatch.3
+++ b/lib/libc/gen/fnmatch.3
@@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd April 2, 2022
+.Dd April 7, 2025
.Dt FNMATCH 3
.Os
.Sh NAME
@@ -129,12 +129,8 @@ otherwise, it returns the value
.Sh STANDARDS
The current implementation of the
.Fn fnmatch
-function
-.Em does not
-conform to
+function is expected to conform to
.St -p1003.2 .
-Collating symbol expressions, equivalence class expressions and
-character class expressions are not supported.
.Sh HISTORY
A predecessor to
.Fn fnmatch ,
diff --git a/lib/libc/gen/fnmatch.c b/lib/libc/gen/fnmatch.c
index fb1829e69502..1c583a9d23e2 100644
--- a/lib/libc/gen/fnmatch.c
+++ b/lib/libc/gen/fnmatch.c
@@ -67,7 +67,8 @@
#define RANGE_NOMATCH 0
#define RANGE_ERROR (-1)
-static int rangematch(const char *, wchar_t, int, char **, mbstate_t *);
+static int rangematch(const char *, wchar_t, const char *, int, char **,
+ char **, mbstate_t *, mbstate_t *);
static int fnmatch1(const char *, const char *, const char *, int, mbstate_t,
mbstate_t);
@@ -85,7 +86,7 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
{
const char *bt_pattern, *bt_string;
mbstate_t bt_patmbs, bt_strmbs;
- char *newp;
+ char *newp, *news;
char c;
wchar_t pc, sc;
size_t pclen, sclen;
@@ -151,8 +152,10 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
* there is no way having it match more characters
* can help us, given that we are already here.
*/
- bt_pattern = pattern, bt_patmbs = patmbs;
- bt_string = string, bt_strmbs = strmbs;
+ bt_pattern = pattern;
+ bt_patmbs = patmbs;
+ bt_string = string;
+ bt_strmbs = strmbs;
break;
case '[':
if (sc == EOS)
@@ -164,17 +167,17 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
((flags & FNM_PATHNAME) && *(string - 1) == '/')))
goto backtrack;
- switch (rangematch(pattern, sc, flags, &newp,
- &patmbs)) {
+ switch (rangematch(pattern, sc, string + sclen, flags,
+ &newp, &news, &patmbs, &strmbs)) {
case RANGE_ERROR:
goto norm;
case RANGE_MATCH:
pattern = newp;
+ string = news;
break;
case RANGE_NOMATCH:
goto backtrack;
}
- string += sclen;
break;
case '\\':
if (!(flags & FNM_NOESCAPE)) {
@@ -218,8 +221,10 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
if (sc == '/' && flags & FNM_PATHNAME)
return (FNM_NOMATCH);
bt_string += sclen;
- pattern = bt_pattern, patmbs = bt_patmbs;
- string = bt_string, strmbs = bt_strmbs;
+ pattern = bt_pattern;
+ patmbs = bt_patmbs;
+ string = bt_string;
+ strmbs = bt_strmbs;
}
break;
}
@@ -228,15 +233,20 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
}
static int
-rangematch(const char *pattern, wchar_t test, int flags, char **newp,
- mbstate_t *patmbs)
+rangematch(const char *pattern, wchar_t test, const char *string, int flags,
+ char **newp, char **news, mbstate_t *patmbs, mbstate_t *strmbs)
{
int negate, ok;
wchar_t c, c2;
size_t pclen;
const char *origpat;
struct xlocale_collate *table =
- (struct xlocale_collate*)__get_locale()->components[XLC_COLLATE];
+ (struct xlocale_collate *)__get_locale()->components[XLC_COLLATE];
+ wchar_t buf[COLLATE_STR_LEN]; /* STR_LEN defined in collate.h */
+ const char *cp, *savestring;
+ int special;
+ mbstate_t save;
+ size_t sclen, len;
/*
* A bracket expression starting with an unquoted circumflex
@@ -259,20 +269,132 @@ rangematch(const char *pattern, wchar_t test, int flags, char **newp,
ok = 0;
origpat = pattern;
for (;;) {
+ c = 0;
if (*pattern == ']' && pattern > origpat) {
- pattern++;
break;
} else if (*pattern == '\0') {
return (RANGE_ERROR);
} else if (*pattern == '/' && (flags & FNM_PATHNAME)) {
return (RANGE_NOMATCH);
- } else if (*pattern == '\\' && !(flags & FNM_NOESCAPE))
+ } else if (*pattern == '\\' && !(flags & FNM_NOESCAPE)) {
pattern++;
- pclen = mbrtowc(&c, pattern, MB_LEN_MAX, patmbs);
- if (pclen == (size_t)-1 || pclen == (size_t)-2)
- return (RANGE_NOMATCH);
- pattern += pclen;
+ } else if (*pattern == '[' &&
+ ((special = *(pattern + 1)) == '.' ||
+ special == '=' || special == ':')) {
+ cp = (pattern += 2);
+ while ((cp = strchr(cp, special))) {
+ if (*(cp + 1) == ']')
+ break;
+ cp++;
+ }
+ if (!cp)
+ return (RANGE_ERROR);
+ if (special == '.') {
+treat_like_collating_symbol:
+ len = __collate_collating_symbol(buf,
+ COLLATE_STR_LEN, pattern,
+ cp - pattern, patmbs);
+ if (len == (size_t)-1 || len == 0)
+ return (RANGE_ERROR);
+ pattern = cp + 2;
+ if (len > 1) {
+ wchar_t *wp, sc;
+ /*
+ * No multi-character collation
+ * symbols as start of range.
+ */
+ if (*(cp + 2) == '-' &&
+ *(cp + 3) != EOS &&
+ *(cp + 3) != ']')
+ return (RANGE_ERROR);
+ wp = buf;
+ if (test != *wp++)
+ continue;
+ if (len == 1) {
+ ok = 1;
+ break;
+ }
+ memcpy(&save, strmbs, sizeof(save));
+ savestring = string;
+ while (--len > 0) {
+ sclen = mbrtowc(&sc, string,
+ MB_LEN_MAX, strmbs);
+ if (sclen == (size_t)-1 ||
+ sclen == (size_t)-2) {
+ sc = (unsigned char)*string;
+ sclen = 1;
+ memset(&strmbs, 0,
+ sizeof(strmbs));
+ }
+ if (sc != *wp++) {
+ memcpy(strmbs, &save,
+ sizeof(save));
+ string = savestring;
+ break;
+ }
+ string += sclen;
+ }
+ if (len == 0) {
+ ok = 1;
+ break;
+ }
+ continue; /* no match */
+ }
+ c = *buf;
+ } else if (special == '=') {
+ int ec;
+ memcpy(&save, patmbs, sizeof(save));
+ ec = __collate_equiv_class(pattern,
+ cp - pattern, patmbs);
+ if (ec < 0)
+ return (RANGE_ERROR);
+ if (ec == 0) {
+ memcpy(patmbs, &save, sizeof(save));
+ goto treat_like_collating_symbol;
+ }
+ pattern = cp + 2;
+ /* no equivalence classes as start of range */
+ if (*(cp + 2) == '-' && *(cp + 3) != EOS &&
+ *(cp + 3) != ']')
+ return (RANGE_ERROR);
+ len = __collate_equiv_match(ec, NULL, 0, test,
+ string, strlen(string), strmbs, &sclen);
+ if (len < 0)
+ return (RANGE_ERROR);
+ if (len > 0) {
+ ok = 1;
+ string += sclen;
+ break;
+ }
+ continue;
+ } else { /* special == ':' */
+ wctype_t charclass;
+ char name[CHARCLASS_NAME_MAX + 1];
+ /* no character classes as start of range */
+ if (*(cp + 2) == '-' && *(cp + 3) != EOS &&
+ *(cp + 3) != ']')
+ return (RANGE_ERROR);
+ /* assume character class names are ascii */
+ if (cp - pattern > CHARCLASS_NAME_MAX)
+ return (RANGE_ERROR);
+ strlcpy(name, pattern, cp - pattern + 1);
+ pattern = cp + 2;
+ if ((charclass = wctype(name)) == 0)
+ return (RANGE_ERROR);
+ if (iswctype(test, charclass)) {
+ ok = 1;
+ break;
+ }
+ continue;
+ }
+ }
+ if (!c) {
+ pclen = mbrtowc(&c, pattern, MB_LEN_MAX, patmbs);
+ if (pclen == (size_t)-1 || pclen == (size_t)-2)
+ return (RANGE_NOMATCH);
+ pattern += pclen;
+ }
if (flags & FNM_CASEFOLD)
c = towlower(c);
@@ -288,6 +410,37 @@ rangematch(const char *pattern, wchar_t test, int flags, char **newp,
if (c2 == EOS)
return (RANGE_ERROR);
+ if ((c2 == '[' && (special = *pattern) == '.') ||
+ special == '=' || special == ':') {
+
+ /*
+ * No equivalence classes or character
+ * classes as end of range.
+ */
+ if (special == '=' || special == ':')
+ return (RANGE_ERROR);
+ cp = ++pattern;
+ while ((cp = strchr(cp, special))) {
+ if (*(cp + 1) == ']')
+ break;
+ cp++;
+ }
+ if (!cp)
+ return (RANGE_ERROR);
+ len = __collate_collating_symbol(buf,
+ COLLATE_STR_LEN, pattern,
+ cp - pattern, patmbs);
+
+ /*
+ * No multi-character collation symbols
+ * as end of range.
+ */
+ if (len != 1)
+ return (RANGE_ERROR);
+ pattern = cp + 2;
+ c2 = *buf;
+ }
+
if (flags & FNM_CASEFOLD)
c2 = towlower(c2);
@@ -295,12 +448,44 @@ rangematch(const char *pattern, wchar_t test, int flags, char **newp,
c <= test && test <= c2 :
__wcollate_range_cmp(c, test) <= 0
&& __wcollate_range_cmp(test, c2) <= 0
- )
+ ) {
ok = 1;
- } else if (c == test)
+ break;
+ }
+ } else if (c == test) {
ok = 1;
+ break;
+ }
}
- *newp = (char *)pattern;
+ /* go to end of bracket expression */
+ special = 0;
+ while (*pattern != ']') {
+ if (*pattern == 0)
+ return (RANGE_ERROR);
+ if (*pattern == special) {
+ if (*++pattern == ']') {
+ special = 0;
+ pattern++;
+ }
+ continue;
+ }
+ if (!special && *pattern == '[') {
+ special = *++pattern;
+ if (special != '.' && special != '=' && special != ':')
+ special = 0;
+ else
+ pattern++;
+ continue;
+ }
+ pclen = mbrtowc(&c, pattern, MB_LEN_MAX, patmbs);
+ if (pclen == (size_t)-1 || pclen == (size_t)-2)
+ return (RANGE_NOMATCH);
+ pattern += pclen;
+ }
+
+ *newp = (char *)++pattern;
+ *news = (char *)string;
+
return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
}
diff --git a/lib/libc/gen/fts-compat.c b/lib/libc/gen/fts-compat.c
index f87cabf085f7..62a1e0a81f62 100644
--- a/lib/libc/gen/fts-compat.c
+++ b/lib/libc/gen/fts-compat.c
@@ -44,9 +44,9 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include "gen-compat.h"
#include "fts-compat.h"
#include "un-namespace.h"
+#include "gen-compat.h"
#include "gen-private.h"
diff --git a/lib/libc/gen/fts-compat11.c b/lib/libc/gen/fts-compat11.c
index 0351ce5ac690..5abb378f5f08 100644
--- a/lib/libc/gen/fts-compat11.c
+++ b/lib/libc/gen/fts-compat11.c
@@ -43,9 +43,9 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include "gen-compat.h"
#include "fts-compat11.h"
#include "un-namespace.h"
+#include "gen-compat.h"
#include "gen-private.h"
diff --git a/lib/libc/gen/fts.3 b/lib/libc/gen/fts.3
index 468b14115ec6..da304e59ee72 100644
--- a/lib/libc/gen/fts.3
+++ b/lib/libc/gen/fts.3
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd January 12, 2014
+.Dd October 6, 2025
.Dt FTS 3
.Os
.Sh NAME
@@ -37,6 +37,8 @@
.In fts.h
.Ft FTS *
.Fn fts_open "char * const *path_argv" "int options" "int (*compar)(const FTSENT * const *, const FTSENT * const *)"
+.Ft FTS *
+.Fn fts_open_b "char * const *path_argv" "int options" "int (^compar)(const FTSENT * const *, const FTSENT * const *)"
.Ft FTSENT *
.Fn fts_read "FTS *ftsp"
.Ft FTSENT *
@@ -59,20 +61,23 @@ functions are provided for traversing
file hierarchies.
A simple overview is that the
.Fn fts_open
-function returns a
+and
+.Fn fts_open_b
+functions return a
.Dq handle
on a file hierarchy, which is then supplied to
the other
.Nm
functions.
-The function
+The
.Fn fts_read
-returns a pointer to a structure describing one of the files in the file
-hierarchy.
-The function
+function returns a pointer to a structure describing one of the files
+in the file hierarchy.
+The
.Fn fts_children
-returns a pointer to a linked list of structures, each of which describes
-one of the files contained in a directory in the hierarchy.
+function returns a pointer to a linked list of structures, each of
+which describes one of the files contained in a directory in the
+hierarchy.
In general, directories are visited two distinguishable times; in pre-order
(before any of their descendants are visited) and in post-order (after all
of their descendants have been visited).
@@ -176,6 +181,12 @@ by one of the other
values.
.It Dv FTS_DNR
A directory which cannot be read.
+This immediately follows
+.Dv FTS_D ,
+in place of
+.Dv FTS_DP ,
+when the directory could not be entered, or could be entered but not
+read.
This is an error return, and the
.Fa fts_errno
field will be set to indicate what caused the error.
@@ -186,6 +197,8 @@ or
.Ql ..\&
which was not specified as a file name to
.Fn fts_open
+or
+.Fn fts_open_b
(see
.Dv FTS_SEEDOT ) .
.It Dv FTS_DP
@@ -234,6 +247,8 @@ A path for accessing the file from the current directory.
The path for the file relative to the root of the traversal.
This path contains the path specified to
.Fn fts_open
+or
+.Fn fts_open_b
as a prefix.
.It Fa fts_pathlen
The length of the string referenced by
@@ -362,7 +377,44 @@ The
.Fa fts_name
field is always
.Dv NUL Ns -terminated .
-.Sh FTS_OPEN
+.Ss Thread Safety
+The
+.Nm
+functions can safely be used in multi-threaded programs provided no
+two threads access the same
+.Vt FTS
+or
+.Vt FTSENT
+structure simultaneously.
+However, unless the
+.Dv FTS_NOCHDIR
+flag was passed to
+.Fn fts_open
+or
+.Fn fts_open_b ,
+calls to
+.Fn fts_read
+and
+.Fn fts_children
+may change the current working directory, which will affect all
+threads.
+Conversely, changing the current working directory either during or
+between calls to
+.Fn fts_read
+or
+.Fn fts_children
+(even in a single-thread program) may cause
+.Nm
+to malfunction unless the
+.Dv FTS_NOCHDIR
+flag was passed to
+.Fn fts_open
+or
+.Fn fts_open_b
+and all paths in
+.Va path_argv
+were absolute.
+.Ss Fn fts_open
The
.Fn fts_open
function takes a pointer to an array of character pointers naming one
@@ -380,12 +432,16 @@ must be specified.
The options are selected by
.Em or Ns 'ing
the following values:
-.Bl -tag -width "FTS_PHYSICAL"
+.Bl -tag -width "FTS_COMFOLLOWDIR"
.It Dv FTS_COMFOLLOW
This option causes any symbolic link specified as a root path to be
followed immediately whether or not
.Dv FTS_LOGICAL
is also specified.
+.It Dv FTS_COMFOLLOWDIR
+This option is similar to
+.Dv FTS_COMFOLLOW ,
+but only follows symbolic links to directories.
.It Dv FTS_LOGICAL
This option causes the
.Nm
@@ -441,6 +497,15 @@ field to
and leave the contents of the
.Fa statp
field undefined.
+.It Dv FTS_NOSTAT_TYPE
+This option is similar to
+.Dv FTS_NOSTAT ,
+but attempts to populate
+.Fa fts_info
+based on information from the
+.Fa d_type
+field of
+.Vt struct dirent .
.It Dv FTS_PHYSICAL
This option causes the
.Nm
@@ -480,10 +545,10 @@ from descending into directories that have a different device number
than the file from which the descent began.
.El
.Pp
-The argument
-.Fn compar
-specifies a user-defined function which may be used to order the traversal
-of the hierarchy.
+The
+.Fa compar
+argument points to a user-defined function which may be used to order
+the traversal of the hierarchy.
It
takes two pointers to pointers to
.Vt FTSENT
@@ -518,7 +583,16 @@ the directory traversal order is in the order listed in
.Fa path_argv
for the root paths, and in the order listed in the directory for
everything else.
-.Sh FTS_READ
+.Ss Fn fts_open_b
+The
+.Fn fts_open_b
+function is identical to
+.Fn fts_open
+except that it takes a block pointer instead of a function pointer.
+The block is copied before
+.Fn fts_open_b
+returns, so the original can safely go out of scope or be released.
+.Ss Fn fts_read
The
.Fn fts_read
function returns a pointer to an
@@ -552,6 +626,15 @@ structure is returned, and
.Va errno
may or may not have been set (see
.Fa fts_info ) .
+Note that
+.Fn fts_read
+will not set
+.Va errno
+to 0 if called again with the same
+.Fa ftsp
+argument after the
+.Dv FTS_STOP
+flag has been set or the end of the stream has been reached.
.Pp
The
.Vt FTSENT
@@ -566,10 +649,10 @@ directory, in which case they will not be overwritten until after a call to
.Fn fts_read
after the
.Vt FTSENT
-structure has been returned by the function
+structure has been returned by the
.Fn fts_read
-in post-order.
-.Sh FTS_CHILDREN
+function in post-order.
+.Ss Fn fts_children
The
.Fn fts_children
function returns a pointer to an
@@ -593,9 +676,13 @@ As a special case, if
has not yet been called for a hierarchy,
.Fn fts_children
will return a pointer to the files in the logical directory specified to
-.Fn fts_open ,
+.Fn fts_open
+or
+.Fn fts_open_b ,
i.e., the arguments specified to
-.Fn fts_open .
+.Fn fts_open
+or
+.Fn fts_open_b .
Otherwise, if the
.Vt FTSENT
structure most recently returned by
@@ -639,11 +726,11 @@ and
.Fa fts_namelen
fields.
.El
-.Sh FTS_SET
-The function
+.Ss Fn fts_set
+The
.Fn fts_set
-allows the user application to determine further processing for the
-file
+function allows the user application to determine further processing
+for the file
.Fa f
of the stream
.Fa ftsp .
@@ -709,43 +796,156 @@ The file may be one of those most recently returned by either
or
.Fn fts_read .
.El
-.Sh FTS_CLOSE
+.Ss Fn fts_set_clientptr , Fn fts_get_clientptr
+The
+.Fn fts_set_clientptr
+function sets the client data pointer for the stream
+.Fa ftsp
+to
+.Fa clientdata .
+The
+.Fn fts_get_clientptr
+function returns the client data pointer associated with
+.Fa ftsp .
+This can be used to pass per-stream data to the comparison function.
+.Pp
+For performance reasons,
+.Fn fts_get_clientptr
+may be shadowed by a preprocessor macro.
+.Ss Fn fts_get_stream
+The
+.Fn fts_get_stream
+function returns the
+.Nm
+stream associated with the file entry
+.Fa f .
+A typical use for this would be for a comparison function to first call
+.Fn fts_get_stream
+on one of its arguments, then call
+.Fn fts_get_clientptr
+to obtain the client data pointer, which in turn points to information
+necessary to correctly order the two entries.
+.Pp
+For performance reasons,
+.Fn fts_get_stream
+may be shadowed by a preprocessor macro.
+.Ss Fn fts_close
The
.Fn fts_close
function closes a file hierarchy stream
.Fa ftsp
and restores the current directory to the directory from which
.Fn fts_open
+or
+.Fn fts_open_b
was called to open
.Fa ftsp .
+.Sh RETURN VALUES
+The
+.Fn fts_open
+and
+.Fn fts_open_b
+functions return a pointer to the new
+.Nm
+stream on success and
+.Dv NULL
+on failure.
+.Pp
+The
+.Fn fts_read
+function returns a pointer to the next file entry on success, or if an
+error occurs that relates specifically to that file entry.
+On reaching the end of the file hierarchy, it returns
+.Dv NULL
+and sets the external variable
+.Va errno
+to 0.
+On failure, it returns
+.Dv NULL
+and sets
+.Va errno
+to an appropriate non-zero value.
+If called again after the
+.Dv FTS_STOP
+flag has been set or the end of the stream has been reached,
+.Fn fts_read
+returns
+.Dv NULL
+and leaves
+.Va errno
+untouched.
+.Pp
+The
+.Fn fts_children
+function returns a pointer to a linked list of file entries on
+success.
+On reaching the end of the file hierarchy, it returns
+.Dv NULL
+and sets the external variable
+.Va errno
+to 0.
+On failure, it returns
+.Dv NULL
+and sets
+.Va errno
+to an appropriate non-zero value.
+.Pp
+The
+.Fn fts_set
+function returns 0 on success and \-1 if its
+.Fa instr
+argument is invalid.
+.Pp
+The
+.Fn fts_get_clientptr
+function returns the client data pointer associated with its argument,
+or
+.Dv NULL
+if none has been set.
+.Pp
+The
+.Fn fts_get_stream
+function returns a pointer to the
+.Nm
+stream associated with its argument.
+.Pp
The
.Fn fts_close
function
returns 0 on success, and \-1 if an error occurs.
.Sh ERRORS
-The function
+The
.Fn fts_open
-may fail and set
+and
+.Fn fts_open_b
+functions may fail and set
.Va errno
for any of the errors specified for the library functions
.Xr open 2
and
.Xr malloc 3 .
+The
+.Fn fts_open_b
+function may also fail and set
+.Va errno
+to
+.Dv ENOSYS
+if the blocks runtime is missing.
.Pp
-The function
+The
.Fn fts_close
-may fail and set
+function may fail and set
.Va errno
for any of the errors specified for the library functions
.Xr chdir 2
and
.Xr close 2 .
.Pp
-The functions
+The
.Fn fts_read
and
.Fn fts_children
-may fail and set
+functions may fail and set
.Va errno
for any of the errors specified for the library functions
.Xr chdir 2 ,
@@ -755,17 +955,17 @@ for any of the errors specified for the library functions
and
.Xr stat 2 .
.Pp
-In addition,
+In addition, the
.Fn fts_children ,
-.Fn fts_open
+.Fn fts_open ,
and
.Fn fts_set
-may fail and set
+functions may fail and set
.Va errno
as follows:
.Bl -tag -width Er
.It Bq Er EINVAL
-The options were invalid, or the list were empty.
+The options were invalid, or the list was empty.
.El
.Sh SEE ALSO
.Xr find 1 ,
@@ -788,6 +988,13 @@ functions were introduced in
principally to provide for alternative interfaces to the
.Nm
functionality using different data structures.
+Blocks support and the
+.Dv FTS_COMFOLLOWDIR
+and
+.Dv FTS_NOSTAT
+options were added in
+.Fx 15.0
+based on similar functionality in macOS.
.Sh BUGS
The
.Fn fts_open
diff --git a/lib/libc/gen/fts.c b/lib/libc/gen/fts.c
index 0c26c91feb84..4aa386d777cd 100644
--- a/lib/libc/gen/fts.c
+++ b/lib/libc/gen/fts.c
@@ -48,6 +48,19 @@
#include "gen-private.h"
+#ifdef __BLOCKS__
+#include <Block.h>
+#else
+#include "block_abi.h"
+typedef DECLARE_BLOCK(int, fts_block,
+ const FTSENT * const *, const FTSENT * const *);
+void qsort_b(void *, size_t, size_t, fts_block);
+#endif /* __BLOCKS__ */
+/* only present if linked with blocks runtime */
+void *_Block_copy(const void *) __weak_symbol;
+void _Block_release(const void *) __weak_symbol;
+extern void *_NSConcreteGlobalBlock[] __weak_symbol;
+
static FTSENT *fts_alloc(FTS *, char *, size_t);
static FTSENT *fts_build(FTS *, int);
static void fts_lfree(FTSENT *);
@@ -93,7 +106,6 @@ struct _fts_private {
* This assumption only holds for UFS-like filesystems that implement
* links and directories this way, so we must punt for others.
*/
-
static const char *ufslike_filesystems[] = {
"ufs",
"zfs",
@@ -102,39 +114,21 @@ static const char *ufslike_filesystems[] = {
0
};
-FTS *
-fts_open(char * const *argv, int options,
- int (*compar)(const FTSENT * const *, const FTSENT * const *))
+static FTS *
+__fts_open(FTS *sp, char * const *argv)
{
- struct _fts_private *priv;
- FTS *sp;
FTSENT *p, *root;
FTSENT *parent, *tmp;
size_t len, nitems;
- /* Options check. */
- if (options & ~FTS_OPTIONMASK) {
- errno = EINVAL;
- return (NULL);
- }
-
- /* fts_open() requires at least one path */
- if (*argv == NULL) {
- errno = EINVAL;
- return (NULL);
- }
-
- /* Allocate/initialize the stream. */
- if ((priv = calloc(1, sizeof(*priv))) == NULL)
- return (NULL);
- sp = &priv->ftsp_fts;
- sp->fts_compar = compar;
- sp->fts_options = options;
-
/* Logical walks turn on NOCHDIR; symbolic links are too hard. */
if (ISSET(FTS_LOGICAL))
SET(FTS_NOCHDIR);
+ /* NOSTAT_TYPE implies NOSTAT */
+ if (ISSET(FTS_NOSTAT_TYPE))
+ SET(FTS_NOSTAT);
+
/*
* Start out with 1K of path space, and enough, in any case,
* to hold the user's paths.
@@ -158,7 +152,9 @@ fts_open(char * const *argv, int options,
p->fts_level = FTS_ROOTLEVEL;
p->fts_parent = parent;
p->fts_accpath = p->fts_name;
- p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW), -1);
+ p->fts_info = fts_stat(sp, p,
+ ISSET(FTS_COMFOLLOWDIR) ? -1 : ISSET(FTS_COMFOLLOW),
+ -1);
/* Command-line "." and ".." are real directories. */
if (p->fts_info == FTS_DOT)
@@ -168,7 +164,7 @@ fts_open(char * const *argv, int options,
* If comparison routine supplied, traverse in sorted
* order; otherwise traverse in the order specified.
*/
- if (compar) {
+ if (sp->fts_compar) {
p->fts_link = root;
root = p;
} else {
@@ -181,7 +177,7 @@ fts_open(char * const *argv, int options,
}
}
}
- if (compar && nitems > 1)
+ if (sp->fts_compar && nitems > 1)
root = fts_sort(sp, root, nitems);
/*
@@ -214,6 +210,97 @@ mem1: free(sp);
return (NULL);
}
+FTS *
+fts_open(char * const *argv, int options,
+ int (*compar)(const FTSENT * const *, const FTSENT * const *))
+{
+ struct _fts_private *priv;
+ FTS *sp;
+
+ /* Options check. */
+ if (options & ~FTS_OPTIONMASK) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* fts_open() requires at least one path */
+ if (*argv == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Allocate/initialize the stream. */
+ if ((priv = calloc(1, sizeof(*priv))) == NULL)
+ return (NULL);
+ sp = &priv->ftsp_fts;
+ sp->fts_compar = compar;
+ sp->fts_options = options;
+
+ return (__fts_open(sp, argv));
+}
+
+#ifdef __BLOCKS__
+FTS *
+fts_open_b(char * const *argv, int options,
+ int (^compar)(const FTSENT * const *, const FTSENT * const *))
+#else
+FTS *
+fts_open_b(char * const *argv, int options, fts_block compar)
+#endif /* __BLOCKS__ */
+{
+ struct _fts_private *priv;
+ FTS *sp;
+
+ /* No blocks, no problems. */
+ if (compar == NULL)
+ return (fts_open(argv, options, NULL));
+
+ /* Avoid segfault if blocks runtime is missing. */
+ if (_Block_copy == NULL) {
+ errno = ENOSYS;
+ return (NULL);
+ }
+
+ /* Options check. */
+ if (options & ~FTS_OPTIONMASK) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* fts_open() requires at least one path */
+ if (*argv == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Allocate/initialize the stream. */
+ if ((priv = calloc(1, sizeof(*priv))) == NULL)
+ return (NULL);
+ sp = &priv->ftsp_fts;
+#ifdef __BLOCKS__
+ compar = Block_copy(compar);
+#else
+ if (compar->isa != &_NSConcreteGlobalBlock)
+ compar = _Block_copy(compar);
+#endif /* __BLOCKS__ */
+ if (compar == NULL) {
+ free(priv);
+ return (NULL);
+ }
+ sp->fts_compar_b = compar;
+ sp->fts_options = options | FTS_COMPAR_B;
+
+ if ((sp = __fts_open(sp, argv)) == NULL) {
+#ifdef __BLOCKS__
+ Block_release(compar);
+#else
+ if (compar->isa != &_NSConcreteGlobalBlock)
+ _Block_release(compar);
+#endif /* __BLOCKS__ */
+ }
+ return (sp);
+}
+
static void
fts_load(FTS *sp, FTSENT *p)
{
@@ -265,6 +352,17 @@ fts_close(FTS *sp)
free(sp->fts_array);
free(sp->fts_path);
+ /* Free up any block pointer. */
+ if (ISSET(FTS_COMPAR_B) && sp->fts_compar_b != NULL) {
+#ifdef __BLOCKS__
+ Block_release(sp->fts_compar_b);
+#else
+ if (((fts_block)(sp->fts_compar_b))->isa !=
+ &_NSConcreteGlobalBlock)
+ _Block_release(sp->fts_compar_b);
+#endif /* __BLOCKS__ */
+ }
+
/* Return to original directory, save errno if necessary. */
if (!ISSET(FTS_NOCHDIR)) {
saved_errno = fchdir(sp->fts_rfd) ? errno : 0;
@@ -580,7 +678,6 @@ fts_children(FTS *sp, int instr)
void *
(fts_get_clientptr)(FTS *sp)
{
-
return (fts_get_clientptr(sp));
}
@@ -597,7 +694,6 @@ FTS *
void
fts_set_clientptr(FTS *sp, void *clientptr)
{
-
sp->fts_clientptr = clientptr;
}
@@ -650,14 +746,10 @@ fts_build(FTS *sp, int type)
* Open the directory for reading. If this fails, we're done.
* If being called from fts_read, set the fts_info field.
*/
-#ifdef FTS_WHITEOUT
if (ISSET(FTS_WHITEOUT))
oflag = DTF_NODUP;
else
oflag = DTF_HIDEW | DTF_NODUP;
-#else
-#define __opendir2(path, flag) opendir(path)
-#endif
if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) {
if (type == BREAD) {
cur->fts_info = FTS_DNR;
@@ -784,10 +876,8 @@ mem1: saved_errno = errno;
p->fts_parent = sp->fts_cur;
p->fts_pathlen = len + dnamlen;
-#ifdef FTS_WHITEOUT
if (dp->d_type == DT_WHT)
p->fts_flags |= FTS_ISW;
-#endif
if (cderrno) {
if (nlinks) {
@@ -796,12 +886,8 @@ mem1: saved_errno = errno;
} else
p->fts_info = FTS_NSOK;
p->fts_accpath = cur->fts_accpath;
- } else if (nlinks == 0
-#ifdef DT_DIR
- || (nostat &&
- dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
-#endif
- ) {
+ } else if (nlinks == 0 || (nostat &&
+ dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)) {
p->fts_accpath =
ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
p->fts_info = FTS_NSOK;
@@ -821,6 +907,25 @@ mem1: saved_errno = errno;
p->fts_info == FTS_DC || p->fts_info == FTS_DOT))
--nlinks;
}
+ if (p->fts_info == FTS_NSOK && ISSET(FTS_NOSTAT_TYPE)) {
+ switch (dp->d_type) {
+ case DT_FIFO:
+ case DT_CHR:
+ case DT_BLK:
+ case DT_SOCK:
+ p->fts_info = FTS_DEFAULT;
+ break;
+ case DT_REG:
+ p->fts_info = FTS_F;
+ break;
+ case DT_LNK:
+ p->fts_info = FTS_SL;
+ break;
+ case DT_WHT:
+ p->fts_info = FTS_W;
+ break;
+ }
+ }
/* We walk in directory order so "ls -f" doesn't get upset. */
p->fts_link = NULL;
@@ -897,7 +1002,7 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
dev_t dev;
ino_t ino;
struct stat *sbp, sb;
- int saved_errno;
+ int ret, saved_errno;
const char *path;
if (dfd == -1) {
@@ -910,7 +1015,6 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
/* If user needs stat info, stat buffer already allocated. */
sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
-#ifdef FTS_WHITEOUT
/* Check for whiteout. */
if (p->fts_flags & FTS_ISW) {
if (sbp != &sb) {
@@ -919,22 +1023,27 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
}
return (FTS_W);
}
-#endif
/*
- * If doing a logical walk, or application requested FTS_FOLLOW, do
- * a stat(2). If that fails, check for a non-existent symlink. If
- * fail, set the errno from the stat call.
+ * If doing a logical walk, or caller requested FTS_COMFOLLOW, do
+ * a full stat(2). If that fails, do an lstat(2) to check for a
+ * non-existent symlink. If that fails, set the errno from the
+ * stat(2) call.
+ *
+ * As a special case, if stat(2) succeeded but the target is not a
+ * directory and follow is negative (indicating FTS_COMFOLLOWDIR
+ * rather than FTS_COMFOLLOW), we also revert to lstat(2).
*/
if (ISSET(FTS_LOGICAL) || follow) {
- if (fstatat(dfd, path, sbp, 0)) {
+ if ((ret = fstatat(dfd, path, sbp, 0)) != 0 ||
+ (follow < 0 && !S_ISDIR(sbp->st_mode))) {
saved_errno = errno;
if (fstatat(dfd, path, sbp, AT_SYMLINK_NOFOLLOW)) {
p->fts_errno = saved_errno;
goto err;
}
errno = 0;
- if (S_ISLNK(sbp->st_mode))
+ if (ret != 0 && S_ISLNK(sbp->st_mode))
return (FTS_SLNONE);
}
} else if (fstatat(dfd, path, sbp, AT_SYMLINK_NOFOLLOW)) {
@@ -979,21 +1088,6 @@ err: memset(sbp, 0, sizeof(struct stat));
return (FTS_DEFAULT);
}
-/*
- * The comparison function takes pointers to pointers to FTSENT structures.
- * Qsort wants a comparison function that takes pointers to void.
- * (Both with appropriate levels of const-poisoning, of course!)
- * Use a trampoline function to deal with the difference.
- */
-static int
-fts_compar(const void *a, const void *b)
-{
- FTS *parent;
-
- parent = (*(const FTSENT * const *)a)->fts_fts;
- return (*parent->fts_compar)(a, b);
-}
-
static FTSENT *
fts_sort(FTS *sp, FTSENT *head, size_t nitems)
{
@@ -1016,7 +1110,18 @@ fts_sort(FTS *sp, FTSENT *head, size_t nitems)
}
for (ap = sp->fts_array, p = head; p; p = p->fts_link)
*ap++ = p;
- qsort(sp->fts_array, nitems, sizeof(FTSENT *), fts_compar);
+ if (ISSET(FTS_COMPAR_B)) {
+#ifdef __BLOCKS__
+ qsort_b(sp->fts_array, nitems, sizeof(FTSENT *),
+ (int (^)(const void *, const void *))sp->fts_compar_b);
+#else
+ qsort_b(sp->fts_array, nitems, sizeof(FTSENT *),
+ sp->fts_compar_b);
+#endif /* __BLOCKS__ */
+ } else {
+ qsort(sp->fts_array, nitems, sizeof(FTSENT *),
+ (int (*)(const void *, const void *))sp->fts_compar);
+ }
for (head = *(ap = sp->fts_array); --nitems; ++ap)
ap[0]->fts_link = ap[1];
ap[0]->fts_link = NULL;
diff --git a/lib/libc/gen/gen-compat.h b/lib/libc/gen/gen-compat.h
index 08e80ede6b6e..19b9addb4321 100644
--- a/lib/libc/gen/gen-compat.h
+++ b/lib/libc/gen/gen-compat.h
@@ -40,16 +40,50 @@ struct freebsd11_statfs;
struct freebsd11_dirent *freebsd11_readdir(DIR *);
int freebsd11_readdir_r(DIR *, struct freebsd11_dirent *,
struct freebsd11_dirent **);
-int freebsd11_stat(const char *, struct freebsd11_stat *);
-int freebsd11_lstat(const char *, struct freebsd11_stat *);
-int freebsd11_fstat(int, struct freebsd11_stat *);
-int freebsd11_fstatat(int, const char *, struct freebsd11_stat *, int);
-int freebsd11_statfs(const char *, struct freebsd11_statfs *);
-int freebsd11_getfsstat(struct freebsd11_statfs *, long, int);
int freebsd11_getmntinfo(struct freebsd11_statfs **, int);
char *freebsd11_devname(__uint32_t dev, __mode_t type);
-char *freebsd11_devname_r(__uint32_t dev, __mode_t type, char *buf, int len);
+char *freebsd11_devname_r(__uint32_t dev, __mode_t type, char *buf,
+ int len);
+
+/*
+ * We want freebsd11_fstat in C source to result in resolution to
+ * - fstat@FBSD_1.0 for libc.so (but we do not need the _definition_
+ * of this fstat, it is provided by libsys.so which we want to use).
+ * - freebsd11_fstat for libc.a (since if we make it fstat@FBSD_1.0
+ * for libc.a, then final linkage into static object ignores version
+ * and would reference fstat, which is the current syscall, not the
+ * compat syscall). libc.a provides the freebsd11_fstat implementation.
+ * Note that freebsd11_fstat from libc.a is not used for anything, but
+ * we make it correct nonetheless, just in case it would.
+ * This is arranged by COMPAT_SYSCALL, and libc can just use freebsd11_fstat.
+ */
+#ifdef PIC
+#define COMPAT_SYSCALL(rtype, fun, args, sym, ver) \
+ rtype fun args; __sym_compat(sym, fun, ver);
+#else
+#define COMPAT_SYSCALL(rtype, fun, args, sym, ver) \
+ rtype fun args;
+#endif
+
+COMPAT_SYSCALL(int, freebsd11_stat, (const char *, struct freebsd11_stat *),
+ stat, FBSD_1.0);
+COMPAT_SYSCALL(int, freebsd11_lstat, (const char *, struct freebsd11_stat *),
+ lstat, FBSD_1.0);
+COMPAT_SYSCALL(int, freebsd11_fstat, (int, struct freebsd11_stat *),
+ fstat, FBSD_1.0);
+COMPAT_SYSCALL(int, freebsd11_fstatat, (int, const char *,
+ struct freebsd11_stat *, int), fstatat, FBSD_1.1);
+
+COMPAT_SYSCALL(int, freebsd11_statfs, (const char *,
+ struct freebsd11_statfs *), statfs, FBSD_1.0);
+COMPAT_SYSCALL(int, freebsd11_getfsstat, (struct freebsd11_statfs *, long,
+ int), getfsstat, FBSD_1.0);
+
+COMPAT_SYSCALL(int, freebsd14_setgroups, (int gidsize, const __gid_t *gidset),
+ setgroups, FBSD_1.0);
+
+#undef COMPAT_SYSCALL
#endif /* _GEN_COMPAT_H_ */
diff --git a/lib/libc/gen/gen-private.h b/lib/libc/gen/gen-private.h
index 3792a61ff942..b6749b3435cd 100644
--- a/lib/libc/gen/gen-private.h
+++ b/lib/libc/gen/gen-private.h
@@ -43,8 +43,8 @@ struct pthread_mutex;
*/
struct _dirdesc {
int dd_fd; /* file descriptor associated with directory */
- long dd_loc; /* offset in current buffer */
- long dd_size; /* amount of data returned by getdirentries */
+ size_t dd_loc; /* offset in current buffer */
+ size_t dd_size; /* amount of data returned by getdirentries */
char *dd_buf; /* data buffer */
int dd_len; /* size of data buffer */
off_t dd_seek; /* magic cookie returned by getdirentries */
diff --git a/lib/libc/gen/getgrouplist.3 b/lib/libc/gen/getgrouplist.3
index e9a980f99751..9e05ff7e7a29 100644
--- a/lib/libc/gen/getgrouplist.3
+++ b/lib/libc/gen/getgrouplist.3
@@ -1,5 +1,13 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-3-Clause
+.\"
.\" Copyright (c) 1991, 1993
.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 2025 The FreeBSD Foundation
+.\"
+.\" Portions of this documentation were written by Olivier Certner
+.\" <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD
+.\" Foundation.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
@@ -25,12 +33,12 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd October 26, 2014
+.Dd October 9, 2025
.Dt GETGROUPLIST 3
.Os
.Sh NAME
.Nm getgrouplist
-.Nd calculate group access list
+.Nd produce a user's effective group list
.Sh LIBRARY
.Lb libc
.Sh SYNOPSIS
@@ -40,36 +48,44 @@
.Sh DESCRIPTION
The
.Fn getgrouplist
-function reads through the group file and calculates
-the group access list for the user specified in
-.Fa name .
-The
+function retrieves from the group database the supplementary groups for the user
+specified in
+.Fa name
+and returns the effective group list, whose first group is the value of
+.Fa basegid
+and the others are the supplementary groups.
.Fa basegid
-is automatically included in the groups list.
-Typically this value is given as
-the group number from the password file.
+typically is the user's initial numerical group ID from the password database.
.Pp
-The resulting group list is returned in the array pointed to by
+The effective group list is returned in the array pointed to by
.Fa groups .
-The caller specifies the size of the
+The caller specifies the length of the
.Fa groups
array in the integer pointed to by
-.Fa ngroups ;
-the actual number of groups found is returned in
+.Fa ngroups .
+The number of groups of the effective group list, which may be greater than the
+.Fa groups
+array's length, is returned through
.Fa ngroups .
.Sh RETURN VALUES
The
.Fn getgrouplist
-function
-returns 0 on success and \-1 if the size of the group list is too small to
-hold all the user's groups.
-Here, the group array will be filled with as many groups as will fit.
+function returns 0 on success and \-1 if the length of the group list is too
+small to hold all the user's groups.
+In the latter case, the
+.Fa groups
+array is filled with as many groups as possible from the start of the effective
+group list, and the length pointed to by
+.Fa ngroups
+is set to the full length of the latter, thus to a value strictly greater than
+before the call.
.Sh FILES
.Bl -tag -width /etc/group -compact
.It Pa /etc/group
group membership list
.El
.Sh SEE ALSO
+.Xr setcred 2 ,
.Xr setgroups 2 ,
.Xr initgroups 3
.Sh HISTORY
diff --git a/lib/libc/gen/getgrouplist.c b/lib/libc/gen/getgrouplist.c
index 5bd06bc5121f..9c57b7031336 100644
--- a/lib/libc/gen/getgrouplist.c
+++ b/lib/libc/gen/getgrouplist.c
@@ -29,13 +29,8 @@
* SUCH DAMAGE.
*/
-/*
- * get credential
- */
#include <sys/types.h>
-#include <grp.h>
-#include <string.h>
#include <unistd.h>
#include <ssp/ssp.h>
@@ -46,4 +41,3 @@ __ssp_real(getgrouplist)(const char *uname, gid_t agroup, gid_t *groups, int *gr
{
return __getgroupmembership(uname, agroup, groups, *grpcnt, grpcnt);
}
-
diff --git a/lib/libc/gen/glob.3 b/lib/libc/gen/glob.3
index 006e8decb3db..839d98ce02b5 100644
--- a/lib/libc/gen/glob.3
+++ b/lib/libc/gen/glob.3
@@ -27,11 +27,12 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 20, 2011
+.Dd June 23, 2025
.Dt GLOB 3
.Os
.Sh NAME
.Nm glob ,
+.Nm glob_b ,
.Nm globfree
.Nd generate pathnames matching a pattern
.Sh LIBRARY
@@ -39,7 +40,9 @@
.Sh SYNOPSIS
.In glob.h
.Ft int
-.Fn glob "const char * restrict pattern" "int flags" "int (*errfunc)(const char *, int)" "glob_t * restrict pglob"
+.Fn glob "const char * restrict pattern" "int flags" "int (*errfunc)(const char *epath, int errno)" "glob_t * restrict pglob"
+.Ft int
+.Fn glob_b "const char * restrict pattern" "int flags" "int (^errblk)(const char *epath, int errno)" "glob_t * restrict pglob"
.Ft void
.Fn globfree "glob_t *pglob"
.Sh DESCRIPTION
@@ -272,10 +275,24 @@ is
.Pf non- Dv NULL ,
.Fn glob
calls
-.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) ,
+.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) .
+This may be unintuitive: a pattern like
+.Ql */Makefile
+will try to
+.Xr stat 2
+.Ql foo/Makefile
+even if
+.Ql foo
+is not a directory, resulting in a
+call to
+.Fa errfunc .
+The error routine can suppress this action by testing for
+.Er ENOENT
+and
+.Er ENOTDIR ;
however, the
.Dv GLOB_ERR
-flag will cause an immediate
+flag will still cause an immediate
return when this happens.
.Pp
If
@@ -307,16 +324,27 @@ or
returns zero, the error is ignored.
.Pp
The
+.Fn glob_b
+function is like
+.Fn glob
+except that the error callback is a block pointer instead of a function
+pointer.
+.Pp
+The
.Fn globfree
function frees any space associated with
.Fa pglob
from a previous call(s) to
-.Fn glob .
+.Fn glob
+or
+.Fn glob_b .
.Sh RETURN VALUES
On successful completion,
.Fn glob
-returns zero.
-In addition the fields of
+and
+.Fn glob_b
+return zero.
+In addition, the fields of
.Fa pglob
contain the values described below:
.Bl -tag -width GLOB_NOCHECK
@@ -324,12 +352,16 @@ contain the values described below:
contains the total number of matched pathnames so far.
This includes other matches from previous invocations of
.Fn glob
+or
+.Fn glob_b
if
.Dv GLOB_APPEND
was specified.
.It Fa gl_matchc
contains the number of matched pathnames in the current invocation of
-.Fn glob .
+.Fn glob
+or
+.Fn glob_b .
.It Fa gl_flags
contains a copy of the
.Fa flags
@@ -352,6 +384,8 @@ are undefined.
.Pp
If
.Fn glob
+or
+.Fn glob_b
terminates due to an error, it sets errno and returns one of the
following non-zero constants, which are defined in the include
file
@@ -397,6 +431,14 @@ g.gl_pathv[0] = "ls";
g.gl_pathv[1] = "-l";
execvp("ls", g.gl_pathv);
.Ed
+.Sh CAVEATS
+The
+.Fn glob
+and
+.Fn glob_b
+functions
+will not match filenames that begin with a period
+unless this is specifically requested (e.g., by ".*").
.Sh SEE ALSO
.Xr sh 1 ,
.Xr fnmatch 3 ,
@@ -435,6 +477,10 @@ and
.Fn globfree
functions first appeared in
.Bx 4.4 .
+The
+.Fn glob_b
+function first appeared in
+.Fx 15.0 .
.Sh BUGS
Patterns longer than
.Dv MAXPATHLEN
@@ -442,7 +488,9 @@ may cause unchecked errors.
.Pp
The
.Fn glob
-argument
+and
+.Fn glob_b
+functions
may fail and set errno for any of the errors specified for the
library routines
.Xr stat 2 ,
diff --git a/lib/libc/gen/glob.c b/lib/libc/gen/glob.c
index 7a988196549a..f7da5f0cda09 100644
--- a/lib/libc/gen/glob.c
+++ b/lib/libc/gen/glob.c
@@ -88,8 +88,11 @@
#include <unistd.h>
#include <wchar.h>
+#include "block_abi.h"
#include "collate.h"
+typedef DECLARE_BLOCK(int, glob_b_block, const char*, int);
+
/*
* glob(3) expansion limits. Stop the expansion if any of these limits
* is reached. This caps the runtime in the face of DoS attacks. See
@@ -104,8 +107,8 @@
struct glob_limit {
size_t l_brace_cnt;
size_t l_path_lim;
- size_t l_readdir_cnt;
- size_t l_stat_cnt;
+ size_t l_readdir_cnt;
+ size_t l_stat_cnt;
size_t l_string_cnt;
};
@@ -179,9 +182,8 @@ static int err_aborted(glob_t *, int, char *);
static void qprintf(const char *, Char *);
#endif
-int
-glob(const char * __restrict pattern, int flags,
- int (*errfunc)(const char *, int), glob_t * __restrict pglob)
+static int
+__glob(const char *pattern, glob_t *pglob)
{
struct glob_limit limit = { 0, 0, 0, 0, 0 };
const char *patnext;
@@ -192,25 +194,23 @@ glob(const char * __restrict pattern, int flags,
int too_long;
patnext = pattern;
- if (!(flags & GLOB_APPEND)) {
+ if (!(pglob->gl_flags & GLOB_APPEND)) {
pglob->gl_pathc = 0;
pglob->gl_pathv = NULL;
- if (!(flags & GLOB_DOOFFS))
+ if (!(pglob->gl_flags & GLOB_DOOFFS))
pglob->gl_offs = 0;
}
- if (flags & GLOB_LIMIT) {
+ if (pglob->gl_flags & GLOB_LIMIT) {
limit.l_path_lim = pglob->gl_matchc;
if (limit.l_path_lim == 0)
limit.l_path_lim = GLOB_LIMIT_PATH;
}
- pglob->gl_flags = flags & ~GLOB_MAGCHAR;
- pglob->gl_errfunc = errfunc;
pglob->gl_matchc = 0;
bufnext = patbuf;
bufend = bufnext + MAXPATHLEN - 1;
too_long = 1;
- if (flags & GLOB_NOESCAPE) {
+ if (pglob->gl_flags & GLOB_NOESCAPE) {
memset(&mbs, 0, sizeof(mbs));
while (bufnext <= bufend) {
clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
@@ -250,15 +250,45 @@ glob(const char * __restrict pattern, int flags,
return (err_nomatch(pglob, &limit, pattern));
*bufnext = EOS;
- if (flags & GLOB_BRACE)
+ if (pglob->gl_flags & GLOB_BRACE)
return (globexp0(patbuf, pglob, &limit, pattern));
else
return (glob0(patbuf, pglob, &limit, pattern));
}
+int
+glob(const char * __restrict pattern, int flags,
+ int (*errfunc)(const char *, int), glob_t * __restrict pglob)
+{
+ int rv;
+
+ pglob->gl_flags = flags & ~(GLOB_MAGCHAR | _GLOB_ERR_BLOCK);
+ pglob->gl_errfunc = errfunc;
+ rv = __glob(pattern, pglob);
+ pglob->gl_errfunc = NULL;
+
+ return (rv);
+}
+
+int
+glob_b(const char * __restrict pattern, int flags,
+ glob_b_block block, glob_t * __restrict pglob)
+{
+ int rv;
+
+ pglob->gl_flags = flags & ~GLOB_MAGCHAR;
+ pglob->gl_flags |= _GLOB_ERR_BLOCK;
+ pglob->gl_errblk = block;
+ rv = __glob(pattern, pglob);
+ pglob->gl_errblk = NULL;
+
+ return (rv);
+}
+
static int
globexp0(const Char *pattern, glob_t *pglob, struct glob_limit *limit,
- const char *origpat) {
+ const char *origpat)
+{
int rv;
size_t oldpathc;
@@ -724,7 +754,7 @@ glob3(Char *pathbuf, Char *pathend, Char *pathend_last,
return (GLOB_NOSPACE);
}
*pathend = EOS;
- if (pglob->gl_errfunc != NULL &&
+ if ((pglob->gl_errfunc != NULL || pglob->gl_errblk != NULL) &&
g_Ctoc(pathbuf, buf, sizeof(buf))) {
errno = E2BIG;
return (GLOB_NOSPACE);
@@ -1085,10 +1115,21 @@ err_nomatch(glob_t *pglob, struct glob_limit *limit, const char *origpat) {
}
static int
-err_aborted(glob_t *pglob, int err, char *buf) {
- if ((pglob->gl_errfunc != NULL && pglob->gl_errfunc(buf, err)) ||
- (pglob->gl_flags & GLOB_ERR))
+err_aborted(glob_t *pglob, int err, char *buf)
+{
+ int rv = 0;
+
+ if ((pglob->gl_flags & _GLOB_ERR_BLOCK) != 0) {
+ if (pglob->gl_errblk != NULL)
+ rv = CALL_BLOCK((glob_b_block)pglob->gl_errblk, buf,
+ errno);
+ } else if (pglob->gl_errfunc != NULL) {
+ rv = pglob->gl_errfunc(buf, errno);
+ }
+ /* GLOB_ERR is allowed to override the error callback function. */
+ if (rv != 0 || pglob->gl_flags & GLOB_ERR) {
return (GLOB_ABORTED);
+ }
return (0);
}
diff --git a/lib/libc/gen/initgroups.3 b/lib/libc/gen/initgroups.3
index 03bd07494fc9..74133e7d7048 100644
--- a/lib/libc/gen/initgroups.3
+++ b/lib/libc/gen/initgroups.3
@@ -1,5 +1,13 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-3-Clause
+.\"
.\" Copyright (c) 1983, 1991, 1993
.\" The Regents of the University of California. All rights reserved.
+.\" Copyright (c) 2025 The FreeBSD Foundation
+.\"
+.\" Portions of this documentation were written by Olivier Certner
+.\" <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD
+.\" Foundation.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
@@ -25,12 +33,12 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd October 26, 2014
+.Dd October 9, 2025
.Dt INITGROUPS 3
.Os
.Sh NAME
.Nm initgroups
-.Nd initialize group access list
+.Nd initialize supplementary groups as per the group database
.Sh LIBRARY
.Lb libc
.Sh SYNOPSIS
@@ -40,19 +48,18 @@
.Sh DESCRIPTION
The
.Fn initgroups
-function
-uses the
-.Xr getgrouplist 3
-function to calculate the group access list for the user
-specified in
+function initializes the current process' supplementary groups as prescribed by
+its arguments and the system's group database.
+.Pp
+It first uses the
+.Fn getgrouplist
+function to compute a list of groups containing the passed
+.Fa basegid ,
+which typically is the user's initial numerical group ID from the password
+database, and the supplementary groups in the group database for the user named
.Fa name .
-This group list is then setup for the current process using
-.Xr setgroups 2 .
-The
-.Fa basegid
-is automatically included in the groups list.
-Typically this value is given as
-the group number from the password file.
+It then installs this list as the current process' supplementary groups using
+.Fn setgroups .
.Sh RETURN VALUES
.Rv -std initgroups
.Sh ERRORS
@@ -60,9 +67,9 @@ The
.Fn initgroups
function may fail and set
.Va errno
-for any of the errors specified for the library function
-.Xr setgroups 2 .
-It may also return:
+to any of the errors specified for the
+.Xr setgroups 2
+system call, or to:
.Bl -tag -width Er
.It Bq Er ENOMEM
The
@@ -77,3 +84,67 @@ The
.Fn initgroups
function appeared in
.Bx 4.2 .
+.Pp
+The
+.Fn initgroups
+function changed semantics in
+.Fx 15 ,
+following that of
+.Xr setgroups 2
+in the same release.
+Before that, it would also set the effective group ID to
+.Fa basegid ,
+and would not include the latter in the supplementary groups except before
+.Fx 8 .
+Its current behavior in these respects is known to be compatible with that of
+the following systems up to the specified versions that are current at time of
+this writing:
+.Bl -dash -width "-" -compact
+.It
+Linux (up to 6.6) with the GNU libc (up to 2.42)
+.It
+.Nx 1.1 and greater (up to 10)
+.It
+.Ox (up to 7.7)
+.It
+Systems based on illumos (up to August 2025 sources)
+.El
+.Sh SECURITY CONSIDERATIONS
+As
+.Fa basegid
+is typically the user's initial numerical group ID, to which the current
+process' effective group ID is generally initialized, processes using functions
+to change their effective group ID
+.Pq via Xr setgid 2 or similar
+or that are spawned from executables with the set-group-ID mode bit set will not
+be able to relinquish the access rights deriving from being a member of
+.Fa basegid ,
+as these functions do not change the supplementary groups.
+.Pp
+This behavior is generally desirable in order to paper over the difference of
+treatment between the effective group and supplementary ones in this situation,
+as they are all in the end indiscriminately used in traditional UNIX
+discretionary access checks.
+It blends well with the practice of allocating each user its own private group,
+as processes launched from a set-group-ID executable keep the same user and
+consistently stay also in the same user's group.
+Finally, it was also chosen for compatibility with other systems
+.Po
+see the
+.Sx HISTORY
+section
+.Pc .
+.Pp
+This convention of including
+.Fa basegid
+in the supplementary groups is however only enforced by the
+.Fn initgroups
+function, and not by the
+.Xr setgroups 2
+system call, so applications expressly wanting to include in the supplementary
+groups only those specified by the group database can themselves call
+.Fn getgrouplist
+and then
+.Fn setgroups
+on the result with the first element skipped
+.Pq see Xr getgrouplist 3 .
diff --git a/lib/libc/gen/initgroups.c b/lib/libc/gen/initgroups.c
index b6697dd7ed8f..a1a7d92250e2 100644
--- a/lib/libc/gen/initgroups.c
+++ b/lib/libc/gen/initgroups.c
@@ -3,6 +3,11 @@
*
* Copyright (c) 1983, 1993
* The Regents of the University of California. All rights reserved.
+ * Copyright (c) 2025 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Olivier Certner
+ * <olce@FreeBSD.org> at Kumacom SARL under sponsorship from the FreeBSD
+ * Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -29,34 +34,52 @@
* SUCH DAMAGE.
*/
-#include <sys/param.h>
+/* For __sym_compat(). */
+#include <sys/cdefs.h>
-#include "namespace.h"
-#include <err.h>
-#include "un-namespace.h"
#include <errno.h>
-#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
-int
-initgroups(const char *uname, gid_t agroup)
+/* For freebsd14_setgroups(). */
+#include "gen-compat.h"
+
+static int
+initgroups_impl(const char *uname, gid_t agroup,
+ int (*setgroups)(int, const gid_t *))
{
- int ngroups, ret;
- long ngroups_max;
gid_t *groups;
+ long ngroups_max;
+ int ngroups, ret;
/*
- * Provide space for one group more than possible to allow
- * setgroups to fail and set errno.
+ * Provide space for one group more than possible to allow setgroups()
+ * to fail and set 'errno' in case we get back more than {NGROUPS_MAX} +
+ * 1 groups.
*/
ngroups_max = sysconf(_SC_NGROUPS_MAX) + 2;
- if ((groups = malloc(sizeof(*groups) * ngroups_max)) == NULL)
- return (ENOMEM);
+ groups = malloc(sizeof(*groups) * ngroups_max);
+ if (groups == NULL)
+ return (-1); /* malloc() set 'errno'. */
ngroups = (int)ngroups_max;
- getgrouplist(uname, agroup, groups, &ngroups);
- ret = setgroups(ngroups, groups);
+ (void)getgrouplist(uname, agroup, groups, &ngroups);
+ ret = (*setgroups)(ngroups, groups);
+
free(groups);
- return (ret);
+ return (ret); /* setgroups() set 'errno'. */
}
+
+int
+initgroups(const char *uname, gid_t agroup)
+{
+ return (initgroups_impl(uname, agroup, setgroups));
+}
+
+int
+freebsd14_initgroups(const char *uname, gid_t agroup)
+{
+ return (initgroups_impl(uname, agroup, freebsd14_setgroups));
+}
+
+__sym_compat(initgroups, freebsd14_initgroups, FBSD_1.0);
diff --git a/lib/libc/gen/inotify.c b/lib/libc/gen/inotify.c
new file mode 100644
index 000000000000..7ce53aaccd58
--- /dev/null
+++ b/lib/libc/gen/inotify.c
@@ -0,0 +1,48 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Klara, Inc.
+ */
+
+#include "namespace.h"
+#include <sys/fcntl.h>
+#include <sys/inotify.h>
+#include <sys/specialfd.h>
+#include "un-namespace.h"
+#include "libc_private.h"
+
+/*
+ * Provide compatibility with libinotify, which uses different values for these
+ * flags.
+ */
+#define IN_NONBLOCK_OLD 0x80000
+#define IN_CLOEXEC_OLD 0x00800
+
+int
+inotify_add_watch(int fd, const char *pathname, uint32_t mask)
+{
+ return (inotify_add_watch_at(fd, AT_FDCWD, pathname, mask));
+}
+
+int
+inotify_init1(int flags)
+{
+ struct specialfd_inotify args;
+
+ if ((flags & IN_NONBLOCK_OLD) != 0) {
+ flags &= ~IN_NONBLOCK_OLD;
+ flags |= IN_NONBLOCK;
+ }
+ if ((flags & IN_CLOEXEC_OLD) != 0) {
+ flags &= ~IN_CLOEXEC_OLD;
+ flags |= IN_CLOEXEC;
+ }
+ args.flags = flags;
+ return (__sys___specialfd(SPECIALFD_INOTIFY, &args, sizeof(args)));
+}
+
+int
+inotify_init(void)
+{
+ return (inotify_init1(0));
+}
diff --git a/lib/libc/gen/jrand48.c b/lib/libc/gen/jrand48.c
index 0a9f780a9e5c..93442439d49e 100644
--- a/lib/libc/gen/jrand48.c
+++ b/lib/libc/gen/jrand48.c
@@ -11,14 +11,13 @@
* to anyone/anything when using this software.
*/
-#include <stdint.h>
-
#include "rand48.h"
long
jrand48(unsigned short xseed[3])
{
+ uint48 tmp;
- _dorand48(xseed);
- return ((int32_t)(((uint32_t)xseed[2] << 16) | (uint32_t)xseed[1]));
+ DORAND48(tmp, xseed);
+ return ((int)((tmp >> 16) & 0xffffffff));
}
diff --git a/lib/libc/gen/lcong48.c b/lib/libc/gen/lcong48.c
index f13826b3d3f3..871b2110ed94 100644
--- a/lib/libc/gen/lcong48.c
+++ b/lib/libc/gen/lcong48.c
@@ -13,18 +13,10 @@
#include "rand48.h"
-extern unsigned short _rand48_seed[3];
-extern unsigned short _rand48_mult[3];
-extern unsigned short _rand48_add;
-
void
lcong48(unsigned short p[7])
{
- _rand48_seed[0] = p[0];
- _rand48_seed[1] = p[1];
- _rand48_seed[2] = p[2];
- _rand48_mult[0] = p[3];
- _rand48_mult[1] = p[4];
- _rand48_mult[2] = p[5];
+ LOADRAND48(_rand48_seed, &p[0]);
+ LOADRAND48(_rand48_mult, &p[3]);
_rand48_add = p[6];
}
diff --git a/lib/libc/gen/libc_interposing_table.c b/lib/libc/gen/libc_interposing_table.c
index d15d32711910..025a67ac3eac 100644
--- a/lib/libc/gen/libc_interposing_table.c
+++ b/lib/libc/gen/libc_interposing_table.c
@@ -42,7 +42,7 @@ interpos_func_t __libc_interposing[INTERPOS_MAX] = {
SLOT(spinlock, __libc_spinlock_stub),
SLOT(spinunlock, __libc_spinunlock_stub),
SLOT(map_stacks_exec, __libc_map_stacks_exec),
- SLOT(distribute_static_tls, __libc_distribute_static_tls),
+ SLOT(uexterr_gettext, __libc_uexterr_gettext),
};
#undef SLOT
diff --git a/lib/libc/gen/lrand48.c b/lib/libc/gen/lrand48.c
index a3d0111cf4d5..cc07044b8af9 100644
--- a/lib/libc/gen/lrand48.c
+++ b/lib/libc/gen/lrand48.c
@@ -13,11 +13,9 @@
#include "rand48.h"
-extern unsigned short _rand48_seed[3];
-
long
lrand48(void)
{
- _dorand48(_rand48_seed);
- return ((long) _rand48_seed[2] << 15) + ((long) _rand48_seed[1] >> 1);
+ _DORAND48(_rand48_seed);
+ return (_rand48_seed >> 17) & 0x7fffffff;
}
diff --git a/lib/libc/gen/mrand48.c b/lib/libc/gen/mrand48.c
index 15b0bfb1bd6e..f9128a6d4188 100644
--- a/lib/libc/gen/mrand48.c
+++ b/lib/libc/gen/mrand48.c
@@ -15,13 +15,9 @@
#include "rand48.h"
-extern unsigned short _rand48_seed[3];
-
long
mrand48(void)
{
-
- _dorand48(_rand48_seed);
- return ((int32_t)(((uint32_t)_rand48_seed[2] << 16) |
- (uint32_t)_rand48_seed[1]));
+ _DORAND48(_rand48_seed);
+ return ((int)((_rand48_seed >> 16) & 0xffffffff));
}
diff --git a/lib/libc/gen/nrand48.c b/lib/libc/gen/nrand48.c
index 6c54065e7e0f..f6f4e231105c 100644
--- a/lib/libc/gen/nrand48.c
+++ b/lib/libc/gen/nrand48.c
@@ -16,6 +16,8 @@
long
nrand48(unsigned short xseed[3])
{
- _dorand48(xseed);
- return ((long) xseed[2] << 15) + ((long) xseed[1] >> 1);
+ uint48 tmp;
+
+ DORAND48(tmp, xseed);
+ return ((tmp >> 17) & 0x7fffffff);
}
diff --git a/lib/libc/gen/opendir.c b/lib/libc/gen/opendir.c
index 956c92c321e8..08d9eb10eaa2 100644
--- a/lib/libc/gen/opendir.c
+++ b/lib/libc/gen/opendir.c
@@ -30,14 +30,7 @@
*/
#include "namespace.h"
-#include <sys/param.h>
-
#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
#include "un-namespace.h"
#include "gen-private.h"
diff --git a/lib/libc/gen/opendir2.c b/lib/libc/gen/opendir2.c
index 30a9030693e4..c5c2e662efd8 100644
--- a/lib/libc/gen/opendir2.c
+++ b/lib/libc/gen/opendir2.c
@@ -30,11 +30,12 @@
*/
#include "namespace.h"
-#include <sys/param.h>
+#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -52,8 +53,7 @@ __opendir2(const char *name, int flags)
if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
return (NULL);
- if ((fd = _open(name,
- O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
+ if ((fd = _open(name, O_DIRECTORY | O_RDONLY | O_CLOEXEC)) == -1)
return (NULL);
dir = __opendir_common(fd, flags, false);
@@ -243,20 +243,18 @@ _filldir(DIR *dirp, bool use_current_pos)
return (true);
}
+/*
+ * Return true if the file descriptor is associated with a file from a
+ * union file system or from a file system mounted with the union flag.
+ */
static bool
is_unionstack(int fd)
{
- int unionstack;
-
- unionstack = _fcntl(fd, F_ISUNIONSTACK, 0);
- if (unionstack != -1)
- return (unionstack);
-
/*
- * Should not happen unless running on a kernel without the op,
- * but no use rendering the system useless in such a case.
+ * This call shouldn't fail, but if it does, just assume that the
+ * answer is no.
*/
- return (0);
+ return (_fcntl(fd, F_ISUNIONSTACK, 0) > 0);
}
/*
@@ -266,6 +264,7 @@ DIR *
__opendir_common(int fd, int flags, bool use_current_pos)
{
DIR *dirp;
+ ssize_t ret;
int incr;
int saved_errno;
bool unionstack;
@@ -315,13 +314,11 @@ __opendir_common(int fd, int flags, bool use_current_pos)
* to prime dd_seek. This also checks if the
* fd passed to fdopendir() is a directory.
*/
- dirp->dd_size = _getdirentries(dirp->dd_fd,
+ ret = _getdirentries(dirp->dd_fd,
dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
- if (dirp->dd_size < 0) {
- if (errno == EINVAL)
- errno = ENOTDIR;
+ if (ret < 0)
goto fail;
- }
+ dirp->dd_size = (size_t)ret;
dirp->dd_flags |= __DTF_SKIPREAD;
} else {
dirp->dd_size = 0;
diff --git a/lib/libc/gen/posix_spawn.c b/lib/libc/gen/posix_spawn.c
index ee2ce05ff317..a5b732696b8c 100644
--- a/lib/libc/gen/posix_spawn.c
+++ b/lib/libc/gen/posix_spawn.c
@@ -261,7 +261,7 @@ _posix_spawn_thr(void *data)
}
envp = psa->envp != NULL ? psa->envp : environ;
if (psa->use_env_path)
- execvpe(psa->path, psa->argv, envp);
+ __libc_execvpe(psa->path, psa->argv, envp);
else
_execve(psa->path, psa->argv, envp);
psa->error = errno;
diff --git a/lib/libc/gen/psignal.3 b/lib/libc/gen/psignal.3
index cd3cef0b44a9..bf6a99b4b113 100644
--- a/lib/libc/gen/psignal.3
+++ b/lib/libc/gen/psignal.3
@@ -25,14 +25,17 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 30, 2016
+.Dd September 23, 2025
.Dt PSIGNAL 3
.Os
.Sh NAME
.Nm psignal ,
+.Nm psiginfo ,
.Nm strsignal ,
.Nm sys_siglist ,
-.Nm sys_signame
+.Nm sys_signame ,
+.Nm sig2str ,
+.Nm str2sig
.Nd system signal messages
.Sh LIBRARY
.Lb libc
@@ -40,11 +43,17 @@
.In signal.h
.Ft void
.Fn psignal "int sig" "const char *s"
+.Ft void
+.Fn psiginfo "const siginfo_t *si" "const char *s"
.Vt extern const char * const sys_siglist[] ;
.Vt extern const char * const sys_signame[] ;
.In string.h
.Ft "char *"
.Fn strsignal "int sig"
+.Ft int
+.Fn sig2str "int signum" "char *str"
+.Ft int
+.Fn str2sig "char *str" "int *pnum"
.Sh DESCRIPTION
The
.Fn psignal
@@ -79,6 +88,16 @@ the string
.Dq "Unknown signal"
is produced.
.Pp
+The
+.Fn psiginfo
+function is similar to
+.Fn psignal ,
+except that the signal number information is taken from the
+.Fa si
+argument which is a
+.Vt siginfo_t
+structure.
+.Pp
The message strings can be accessed directly
through the external array
.Va sys_siglist ,
@@ -95,12 +114,72 @@ contains a count of the strings in
.Va sys_siglist
and
.Va sys_signame .
+.Pp
+The
+.Fn sig2str
+function translates the signal number
+.Fa signum
+to the signal name, without the
+.Dq SIG
+prefix, and stores it at the location specified by
+.Fa str ,
+which should be large enough to hold the name and the terminating
+.Dv NUL
+byte.
+The symbol
+.Dv SIG2STR_MAX
+gives the maximum size in bytes required.
+.Pp
+The
+.Fn str2sig
+function translates the signal name
+.Fa str
+to a signal number and stores it in the location referenced by
+.Fa pnum .
+The name in
+.Fa str
+can be either the name of the signal, with or without the
+.Dq SIG
+prefix, or a decimal number.
+.Sh RETURN VALUES
+The
+.Fn sig2str
+and
+.Fn str2sig
+return 0 on success and -1 on translation failure.
+In the latter case the memory to store the translation result is left intact.
.Sh SEE ALSO
.Xr sigaction 2 ,
.Xr perror 3 ,
.Xr strerror 3
+.Sh STANDARDS
+The
+.Fn psignal
+and
+.Fn psiginfo
+functions are defined by
+.St -p1003.1-2008
+, while the
+.Fn sig2str
+and
+.Fn str2sig
+functions are defined by
+.St -p1003.1-2024 .
.Sh HISTORY
The
.Fn psignal
function appeared in
.Bx 4.2 .
+The
+.Fn psiginfo
+function appeared in
+.Fx 14.3 ,
+.Nx 6.0 ,
+and
+.Dx 4.1 .
+The
+.Fn sig2str
+and
+.Fn str2sig
+functions appeared in
+.Fx 15.0 .
diff --git a/lib/libc/gen/psignal.c b/lib/libc/gen/psignal.c
index ab044f683d86..291a6a9337a0 100644
--- a/lib/libc/gen/psignal.c
+++ b/lib/libc/gen/psignal.c
@@ -55,3 +55,9 @@ psignal(int sig, const char *s)
(void)_write(STDERR_FILENO, c, strlen(c));
(void)_write(STDERR_FILENO, "\n", 1);
}
+
+void
+psiginfo(const siginfo_t *si, const char *s)
+{
+ psignal(si->si_signo, s);
+}
diff --git a/lib/libc/gen/rand48.3 b/lib/libc/gen/rand48.3
index 1e47c843058e..3ea649354270 100644
--- a/lib/libc/gen/rand48.3
+++ b/lib/libc/gen/rand48.3
@@ -9,7 +9,7 @@
.\" of any kind. I shall in no event be liable for anything that happens
.\" to anyone/anything when using this software.
.\"
-.Dd September 4, 2012
+.Dd September 11, 2025
.Dt RAND48 3
.Os
.Sh NAME
@@ -183,5 +183,8 @@ generator calls.
.Xr arc4random 3 ,
.Xr rand 3 ,
.Xr random 3
+.Sh STANDARDS
+The functions described in this page are expected to conform to
+.St -p1003.1-2008 .
.Sh AUTHORS
.An Martin Birgmeier
diff --git a/lib/libc/gen/rand48.h b/lib/libc/gen/rand48.h
index 9861e99683cb..d3326e851491 100644
--- a/lib/libc/gen/rand48.h
+++ b/lib/libc/gen/rand48.h
@@ -14,10 +14,11 @@
#ifndef _RAND48_H_
#define _RAND48_H_
+#include <sys/types.h>
#include <math.h>
#include <stdlib.h>
-void _dorand48(unsigned short[3]);
+#include "fpmath.h"
#define RAND48_SEED_0 (0x330e)
#define RAND48_SEED_1 (0xabcd)
@@ -27,4 +28,62 @@ void _dorand48(unsigned short[3]);
#define RAND48_MULT_2 (0x0005)
#define RAND48_ADD (0x000b)
+typedef uint64_t uint48;
+
+extern uint48 _rand48_seed;
+extern uint48 _rand48_mult;
+extern uint48 _rand48_add;
+
+#define TOUINT48(x, y, z) \
+ ((uint48)(x) + (((uint48)(y)) << 16) + (((uint48)(z)) << 32))
+
+#define RAND48_SEED TOUINT48(RAND48_SEED_0, RAND48_SEED_1, RAND48_SEED_2)
+#define RAND48_MULT TOUINT48(RAND48_MULT_0, RAND48_MULT_1, RAND48_MULT_2)
+
+#define LOADRAND48(l, x) do { \
+ (l) = TOUINT48((x)[0], (x)[1], (x)[2]); \
+} while (0)
+
+#define STORERAND48(l, x) do { \
+ (x)[0] = (unsigned short)(l); \
+ (x)[1] = (unsigned short)((l) >> 16); \
+ (x)[2] = (unsigned short)((l) >> 32); \
+} while (0)
+
+#define _DORAND48(l) do { \
+ (l) = (l) * _rand48_mult + _rand48_add; \
+} while (0)
+
+#define DORAND48(l, x) do { \
+ LOADRAND48(l, x); \
+ _DORAND48(l); \
+ STORERAND48(l, x); \
+} while (0)
+
+#define ERAND48_BEGIN \
+ union { \
+ union IEEEd2bits ieee; \
+ uint64_t u64; \
+ } u; \
+ int s
+
+/*
+ * Optimization for speed: assume doubles are IEEE 754 and use bit fiddling
+ * rather than converting to double. Specifically, clamp the result to 48 bits
+ * and convert to a double in [0.0, 1.0) via division by 2^48. Normalize by
+ * shifting the most significant bit into the implicit one position and
+ * adjusting the exponent accordingly. The store to the exponent field
+ * overwrites the implicit one.
+ */
+#define ERAND48_END(x) do { \
+ u.u64 = ((x) & 0xffffffffffffULL); \
+ if (u.u64 == 0) \
+ return (0.0); \
+ u.u64 <<= 5; \
+ for (s = 0; !(u.u64 & (1LL << 52)); s++, u.u64 <<= 1) \
+ ; \
+ u.ieee.bits.exp = 1022 - s; \
+ return (u.ieee.d); \
+} while (0)
+
#endif /* _RAND48_H_ */
diff --git a/lib/libc/gen/readdir.c b/lib/libc/gen/readdir.c
index 2a2fa999b7ce..94d2b2e8d877 100644
--- a/lib/libc/gen/readdir.c
+++ b/lib/libc/gen/readdir.c
@@ -48,8 +48,9 @@ struct dirent *
_readdir_unlocked(DIR *dirp, int flags)
{
struct dirent *dp;
- long initial_seek;
- long initial_loc = 0;
+ off_t initial_seek;
+ size_t initial_loc = 0;
+ ssize_t ret;
for (;;) {
if (dirp->dd_loc >= dirp->dd_size) {
@@ -61,11 +62,13 @@ _readdir_unlocked(DIR *dirp, int flags)
}
if (dirp->dd_loc == 0 &&
!(dirp->dd_flags & (__DTF_READALL | __DTF_SKIPREAD))) {
+ dirp->dd_size = 0;
initial_seek = dirp->dd_seek;
- dirp->dd_size = _getdirentries(dirp->dd_fd,
+ ret = _getdirentries(dirp->dd_fd,
dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
- if (dirp->dd_size <= 0)
+ if (ret <= 0)
return (NULL);
+ dirp->dd_size = (size_t)ret;
_fixtelldir(dirp, initial_seek, initial_loc);
}
dirp->dd_flags &= ~__DTF_SKIPREAD;
@@ -131,3 +134,5 @@ __readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
}
__strong_reference(__readdir_r, readdir_r);
+__warn_references(readdir_r,
+ "warning: this program uses readdir_r(), which is unsafe.");
diff --git a/lib/libc/gen/rtld_get_var.3 b/lib/libc/gen/rtld_get_var.3
index e211d78345e3..092114e86d78 100644
--- a/lib/libc/gen/rtld_get_var.3
+++ b/lib/libc/gen/rtld_get_var.3
@@ -103,4 +103,4 @@ is unknown.
The
.Nm
function first appeared in
-.Fx 15.0 .
+.Fx 14.3 .
diff --git a/lib/libc/gen/scandir.3 b/lib/libc/gen/scandir.3
index 6656842c251f..3da4500cefb9 100644
--- a/lib/libc/gen/scandir.3
+++ b/lib/libc/gen/scandir.3
@@ -25,13 +25,16 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd August 31, 2023
+.Dd June 25, 2025
.Dt SCANDIR 3
.Os
.Sh NAME
.Nm scandir ,
+.Nm fdscandir ,
.Nm scandirat ,
.Nm scandir_b ,
+.Nm fdscandir_b ,
+.Nm fdscandirat_b ,
.Nm alphasort ,
.Nm versionsort
.Nd scan a directory
@@ -47,6 +50,13 @@
.Fa "int \*(lp*compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
+.Fo fdscandir
+.Fa "int dirfd"
+.Fa "struct dirent ***namelist"
+.Fa "int \*(lp*select\*(rp\*(lpconst struct dirent *\*(rp"
+.Fa "int \*(lp*compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
+.Fc
+.Ft int
.Fo scandirat
.Fa "int dirfd"
.Fa "const char *dirname"
@@ -62,6 +72,21 @@
.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
+.Fo fdscandir_b
+.Fa "int dirfd"
+.Fa "struct dirent ***namelist"
+.Fa "int \*(lp^select\*(rp\*(lpconst struct dirent *\*(rp"
+.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
+.Fc
+.Ft int
+.Fo scandirat_b
+.Fa "int dirfd"
+.Fa "const char *dirname"
+.Fa "struct dirent ***namelist"
+.Fa "int \*(lp^select\*(rp\*(lpconst struct dirent *\*(rp"
+.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
+.Fc
+.Ft int
.Fn alphasort "const struct dirent **d1" "const struct dirent **d2"
.Ft int
.Fn versionsort "const struct dirent **d1" "const struct dirent **d2"
@@ -77,7 +102,8 @@ entries using
It returns the number of entries in the array.
A pointer to the array of directory entries is stored in the location
referenced by
-.Fa namelist .
+.Fa namelist
+(even if no entries were selected).
.Pp
The
.Fa select
@@ -118,6 +144,13 @@ The memory allocated for the array can be deallocated with
by freeing each pointer in the array and then the array itself.
.Pp
The
+.Fn fdscandir
+function is similar to
+.Fn scandir ,
+but takes a file descriptor referencing a directory instead of a path.
+The file descriptor is left open on return, regardless of outcome.
+.Pp
+The
.Fn scandirat
function is similar to
.Fn scandir ,
@@ -151,17 +184,67 @@ See
for additional details.
.Pp
The
-.Fn scandir_b
-function behaves in the same way as
+.Fn scandir_b ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions behave in the same way as
.Fn scandir ,
-but takes blocks as arguments instead of function pointers and calls
+.Fn fdscandir ,
+and
+.Fn scandirat ,
+respectively,
+but take blocks as arguments instead of function pointers and call
.Fn qsort_b
rather than
.Fn qsort .
.Sh DIAGNOSTICS
-Returns \-1 if the directory cannot be opened for reading or if
+The
+.Fn scandir ,
+.Fn fdscandir ,
+.Fn scandirat ,
+.Fn scandir_b ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions return the number of directory entries found on succes.
+If the directory cannot be opened for reading, an error occurs
+while reading the directory, or
.Xr malloc 3
-cannot allocate enough memory to hold all the data structures.
+cannot allocate enough memory to hold all the directory entries,
+they return \-1 and set
+.Va errno
+to an appropriate value.
+.Sh ERRORS
+The
+.Fn scandir ,
+.Fn scandirat ,
+.Fn scandir_b ,
+and
+.Fn scandirat_b
+functions may fail and set
+.Va errno
+for any of the errors specified for the
+.Xr opendir 3 ,
+.Xr malloc 3 ,
+.Xr readdir 3 ,
+and
+.Xr closedir 3
+functions.
+.Pp
+The
+.Fn fdscandir
+and
+.Fn fdscandir_b
+functions may fail and set
+.Va errno
+for any of the errors specified for the
+.Xr fdopendir 3 ,
+.Xr malloc 3 ,
+.Xr readdir 3 ,
+and
+.Xr closedir 3
+functions.
.Sh SEE ALSO
.Xr openat 2 ,
.Xr directory 3 ,
@@ -172,8 +255,25 @@ cannot allocate enough memory to hold all the data structures.
.Xr dir 5
.Sh STANDARDS
The
+.Fn alphasort
+and
+.Fn scandir
+functions are expected to conform to
+.St -p1003.1-2008 .
+The
+.Fn scandirat
+and
.Fn versionsort
-function is a GNU extension and conforms to no standard.
+functions are GNU extensions and conform to no standard.
+The
+.Fn fdscandir ,
+.Fn scandir_b ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions are
+.Fx
+extensions.
.Sh HISTORY
The
.Fn scandir
@@ -182,8 +282,19 @@ and
functions appeared in
.Bx 4.2 .
The
+.Fn scandir_b
+function was added in
+.Fx 11.0 .
+The
.Fn scandirat
and
.Fn versionsort
functions were added in
.Fx 13.2 .
+The
+.Fn fdscandir ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions were added in
+.Fx 15.0 .
diff --git a/lib/libc/gen/scandir.c b/lib/libc/gen/scandir.c
index f59f57047278..fb589f4b36b6 100644
--- a/lib/libc/gen/scandir.c
+++ b/lib/libc/gen/scandir.c
@@ -38,6 +38,7 @@
#include "namespace.h"
#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@@ -64,71 +65,76 @@ static int scandir_thunk_cmp(const void *p1, const void *p2, void *thunk);
static int
#ifdef I_AM_SCANDIR_B
-scandir_b_dirp(DIR *dirp, struct dirent ***namelist, select_block select,
+scandir_dirp_b(DIR *dirp, struct dirent ***namelist, select_block select,
dcomp_block dcomp)
#else
scandir_dirp(DIR *dirp, struct dirent ***namelist,
- int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
- const struct dirent **))
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
#endif
{
- struct dirent *d, *p, **names = NULL;
- size_t arraysz, numitems;
+ struct dirent *d, *p = NULL, **names = NULL, **names2;
+ size_t arraysz = 32, numitems = 0;
+ int serrno;
- numitems = 0;
- arraysz = 32; /* initial estimate of the array size */
- names = (struct dirent **)malloc(arraysz * sizeof(struct dirent *));
+ names = malloc(arraysz * sizeof(*names));
if (names == NULL)
- goto fail;
+ return (-1);
- while ((d = readdir(dirp)) != NULL) {
+ while (errno = 0, (d = readdir(dirp)) != NULL) {
if (select != NULL && !SELECT(d))
continue; /* just selected names */
/*
* Make a minimum size copy of the data
*/
- p = (struct dirent *)malloc(_GENERIC_DIRSIZ(d));
+ p = malloc(_GENERIC_DIRSIZ(d));
if (p == NULL)
goto fail;
p->d_fileno = d->d_fileno;
p->d_type = d->d_type;
p->d_reclen = d->d_reclen;
p->d_namlen = d->d_namlen;
- bcopy(d->d_name, p->d_name, p->d_namlen + 1);
+ memcpy(p->d_name, d->d_name, p->d_namlen + 1);
/*
* Check to make sure the array has space left and
* realloc the maximum size.
*/
if (numitems >= arraysz) {
- struct dirent **names2;
-
- names2 = reallocarray(names, arraysz,
- 2 * sizeof(struct dirent *));
- if (names2 == NULL) {
- free(p);
+ arraysz = arraysz * 2;
+ names2 = reallocarray(names, arraysz, sizeof(*names));
+ if (names2 == NULL)
goto fail;
- }
names = names2;
- arraysz *= 2;
}
names[numitems++] = p;
}
- closedir(dirp);
- if (numitems && dcomp != NULL)
+ /*
+ * Since we can't simultaneously return both -1 and a count, we
+ * must either suppress the error or discard the partial result.
+ * The latter seems the lesser of two evils.
+ */
+ if (errno != 0)
+ goto fail;
+ if (numitems > 0 && dcomp != NULL) {
#ifdef I_AM_SCANDIR_B
- qsort_b(names, numitems, sizeof(struct dirent *), (void*)dcomp);
+ qsort_b(names, numitems, sizeof(struct dirent *),
+ (void *)dcomp);
#else
qsort_r(names, numitems, sizeof(struct dirent *),
scandir_thunk_cmp, &dcomp);
#endif
+ }
*namelist = names;
return (numitems);
fail:
+ serrno = errno;
+ if (numitems == 0 || names[numitems - 1] != p)
+ free(p);
while (numitems > 0)
free(names[--numitems]);
free(names);
- closedir(dirp);
+ errno = serrno;
return (-1);
}
@@ -138,44 +144,87 @@ scandir_b(const char *dirname, struct dirent ***namelist, select_block select,
dcomp_block dcomp)
#else
scandir(const char *dirname, struct dirent ***namelist,
- int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
- const struct dirent **))
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
#endif
{
DIR *dirp;
+ int ret, serrno;
dirp = opendir(dirname);
if (dirp == NULL)
return (-1);
- return (
+ ret =
#ifdef I_AM_SCANDIR_B
- scandir_b_dirp
+ scandir_dirp_b
#else
scandir_dirp
#endif
- (dirp, namelist, select, dcomp));
+ (dirp, namelist, select, dcomp);
+ serrno = errno;
+ closedir(dirp);
+ errno = serrno;
+ return (ret);
}
-#ifndef I_AM_SCANDIR_B
int
-scandirat(int dirfd, const char *dirname, struct dirent ***namelist,
- int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
- const struct dirent **))
+#ifdef I_AM_SCANDIR_B
+fdscandir_b(int dirfd, struct dirent ***namelist, select_block select,
+ dcomp_block dcomp)
+#else
+fdscandir(int dirfd, struct dirent ***namelist,
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
+#endif
{
DIR *dirp;
- int fd;
+ int ret, serrno;
+
+ dirp = fdopendir(dirfd);
+ if (dirp == NULL)
+ return (-1);
+ ret =
+#ifdef I_AM_SCANDIR_B
+ scandir_dirp_b
+#else
+ scandir_dirp
+#endif
+ (dirp, namelist, select, dcomp);
+ serrno = errno;
+ fdclosedir(dirp);
+ errno = serrno;
+ return (ret);
+}
+
+int
+#ifdef I_AM_SCANDIR_B
+scandirat_b(int dirfd, const char *dirname, struct dirent ***namelist,
+ select_block select, dcomp_block dcomp)
+#else
+scandirat(int dirfd, const char *dirname, struct dirent ***namelist,
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
+#endif
+{
+ int fd, ret, serrno;
fd = _openat(dirfd, dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (fd == -1)
return (-1);
- dirp = fdopendir(fd);
- if (dirp == NULL) {
- _close(fd);
- return (-1);
- }
- return (scandir_dirp(dirp, namelist, select, dcomp));
+ ret =
+#ifdef I_AM_SCANDIR_B
+ fdscandir_b
+#else
+ fdscandir
+#endif
+ (fd, namelist, select, dcomp);
+ serrno = errno;
+ _close(fd);
+ errno = serrno;
+ return (ret);
}
+#ifndef I_AM_SCANDIR_B
/*
* Alphabetic order comparison routine for those who want it.
* POSIX 2008 requires that alphasort() uses strcoll().
diff --git a/lib/libc/gen/seed48.c b/lib/libc/gen/seed48.c
index 258c4bac3c9f..f57656ce1121 100644
--- a/lib/libc/gen/seed48.c
+++ b/lib/libc/gen/seed48.c
@@ -13,24 +13,14 @@
#include "rand48.h"
-extern unsigned short _rand48_seed[3];
-extern unsigned short _rand48_mult[3];
-extern unsigned short _rand48_add;
-
unsigned short *
seed48(unsigned short xseed[3])
{
static unsigned short sseed[3];
- sseed[0] = _rand48_seed[0];
- sseed[1] = _rand48_seed[1];
- sseed[2] = _rand48_seed[2];
- _rand48_seed[0] = xseed[0];
- _rand48_seed[1] = xseed[1];
- _rand48_seed[2] = xseed[2];
- _rand48_mult[0] = RAND48_MULT_0;
- _rand48_mult[1] = RAND48_MULT_1;
- _rand48_mult[2] = RAND48_MULT_2;
+ STORERAND48(_rand48_seed, sseed);
+ LOADRAND48(_rand48_seed, xseed);
+ _rand48_mult = RAND48_MULT;
_rand48_add = RAND48_ADD;
- return sseed;
+ return (sseed);
}
diff --git a/lib/libc/gen/sig2str.c b/lib/libc/gen/sig2str.c
new file mode 100644
index 000000000000..869a09ab9db4
--- /dev/null
+++ b/lib/libc/gen/sig2str.c
@@ -0,0 +1,113 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Ricardo Branco <rbranco@suse.de>.
+ *
+ * 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.
+ */
+
+/*
+ * Translate between signal names and numbers
+ */
+#include "namespace.h"
+#include <ctype.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ssp/ssp.h>
+#include "un-namespace.h"
+
+static const char rtmin_str[] = "RTMIN";
+static const char rtmax_str[] = "RTMAX";
+
+int
+__ssp_real(sig2str)(int signum, char *str)
+{
+ if (signum <= 0 || signum > SIGRTMAX)
+ return (-1);
+
+ if (signum < sys_nsig)
+ (void)strlcpy(str, sys_signame[signum], SIG2STR_MAX);
+ else if (signum < SIGRTMIN)
+ (void)snprintf(str, SIG2STR_MAX, "%d", signum);
+ else if (signum == SIGRTMIN)
+ (void)strlcpy(str, rtmin_str, SIG2STR_MAX);
+ else if (signum == SIGRTMAX)
+ (void)strlcpy(str, rtmax_str, SIG2STR_MAX);
+ else if (signum <= (SIGRTMIN + SIGRTMAX) / 2)
+ (void)snprintf(str, SIG2STR_MAX, "%s+%d",
+ rtmin_str, signum - SIGRTMIN);
+ else
+ (void)snprintf(str, SIG2STR_MAX, "%s-%d",
+ rtmax_str, SIGRTMAX - signum);
+
+ return (0);
+}
+
+int
+str2sig(const char * restrict str, int * restrict pnum)
+{
+ const char *errstr;
+ long long n;
+ int sig;
+ int rtend = sizeof(rtmin_str) - 1;
+
+ if (strncasecmp(str, "SIG", 3) == 0)
+ str += 3;
+
+ if (strncasecmp(str, rtmin_str, sizeof(rtmin_str) - 1) == 0 ||
+ strncasecmp(str, rtmax_str, sizeof(rtmax_str) - 1) == 0) {
+ sig = (toupper(str[4]) == 'X') ? SIGRTMAX : SIGRTMIN;
+ n = 0;
+ if (str[rtend] == '+' || str[rtend] == '-') {
+ n = strtonum(str + rtend, INT_MIN, INT_MAX, &errstr);
+ if (n == 0 || errstr != NULL)
+ return (-1);
+ } else if (str[rtend] != '\0') {
+ return (-1);
+ }
+ sig += (int)n;
+ if (sig < SIGRTMIN || sig > SIGRTMAX)
+ return (-1);
+ *pnum = sig;
+ return (0);
+ }
+
+ if (isdigit((unsigned char)str[0])) {
+ n = strtonum(str, 1, SIGRTMAX, &errstr);
+ if (errstr == NULL) {
+ *pnum = (int)n;
+ return (0);
+ }
+ }
+
+ for (sig = 1; sig < sys_nsig; sig++) {
+ if (strcasecmp(sys_signame[sig], str) == 0) {
+ *pnum = sig;
+ return (0);
+ }
+ }
+
+ return (-1);
+}
diff --git a/lib/libc/gen/srand48.c b/lib/libc/gen/srand48.c
index fd369a094c51..4b82ece72db8 100644
--- a/lib/libc/gen/srand48.c
+++ b/lib/libc/gen/srand48.c
@@ -13,18 +13,11 @@
#include "rand48.h"
-extern unsigned short _rand48_seed[3];
-extern unsigned short _rand48_mult[3];
-extern unsigned short _rand48_add;
-
void
srand48(long seed)
{
- _rand48_seed[0] = RAND48_SEED_0;
- _rand48_seed[1] = (unsigned short) seed;
- _rand48_seed[2] = (unsigned short) (seed >> 16);
- _rand48_mult[0] = RAND48_MULT_0;
- _rand48_mult[1] = RAND48_MULT_1;
- _rand48_mult[2] = RAND48_MULT_2;
+ _rand48_seed = TOUINT48(RAND48_SEED_0, (unsigned short)seed,
+ (unsigned short)(seed >> 16));
+ _rand48_mult = RAND48_MULT;
_rand48_add = RAND48_ADD;
}
diff --git a/lib/libc/gen/sysconf.c b/lib/libc/gen/sysconf.c
index be42dd238b56..b5b732eed05d 100644
--- a/lib/libc/gen/sysconf.c
+++ b/lib/libc/gen/sysconf.c
@@ -34,6 +34,7 @@
#include "namespace.h"
#include <sys/param.h>
+#include <sys/exterrvar.h>
#include <sys/time.h>
#include <sys/sysctl.h>
#include <sys/resource.h>
@@ -50,7 +51,7 @@
#include "un-namespace.h"
#include "../stdlib/atexit.h"
-#include "tzdir.h" /* from ../../../contrib/tzcode/stdtime */
+#include "tzdir.h" /* from ../../../contrib/tzcode */
#include "libc_private.h"
#define _PATH_ZONEINFO TZDIR /* from tzfile.h */
@@ -286,6 +287,8 @@ do_NAME_MAX:
mib[0] = CTL_P1003_1B;
mib[1] = CTL_P1003_1B_MQ_OPEN_MAX;
goto yesno;
+ case _SC_NSIG:
+ return (_SIG_MAXSIG);
case _SC_PAGESIZE:
return (getpagesize());
case _SC_RTSIG_MAX:
@@ -567,7 +570,7 @@ yesno:
case _SC_IPV6:
#if _POSIX_IPV6 == 0
sverrno = errno;
- value = _socket(PF_INET6, SOCK_DGRAM, 0);
+ value = _socket(PF_INET6, SOCK_CLOEXEC | SOCK_DGRAM, 0);
errno = sverrno;
if (value >= 0) {
_close(value);
@@ -602,6 +605,8 @@ yesno:
return (-1);
return ((long)value);
#endif
+ case _SC_UEXTERR_MAXLEN:
+ return (UEXTERROR_MAXLEN);
default:
errno = EINVAL;
diff --git a/lib/libc/gen/sysctl.3 b/lib/libc/gen/sysctl.3
index fb68ddd60edc..ef897c653728 100644
--- a/lib/libc/gen/sysctl.3
+++ b/lib/libc/gen/sysctl.3
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd March 16, 2023
+.Dd July 31, 2025
.Dt SYSCTL 3
.Os
.Sh NAME
@@ -502,7 +502,6 @@ specifies the current process.
.It Dv KERN_PROC_ENV Ta "Set of strings"
.It Dv KERN_PROC_AUXV Ta "Elf_Auxinfo []"
.It Dv KERN_PROC_RLIMIT Ta "Integer"
-.It Dv KERN_PROC_RLIMIT_USAGE Ta "rlim_t []"
.It Dv KERN_PROC_PS_STRINGS Ta "Integer"
.It Dv KERN_PROC_UMASK Ta "Integer/short"
.It Dv KERN_PROC_OSREL Ta "Integer"
@@ -511,6 +510,7 @@ specifies the current process.
.It Dv KERN_PROC_NFDS Ta "Integer"
.It Dv KERN_PROC_SIGFASTBLK Ta "Integer"
.It Dv KERN_PROC_VM_LAYOUT Ta "struct kinfo_vm_layout"
+.It Dv KERN_PROC_RLIMIT_USAGE Ta "rlim_t []"
.It Dv KERN_PROC_KQUEUE Ta "struct kinfo_knote []"
.El
.Pp
@@ -527,8 +527,12 @@ The path of the process' text file is returned.
The in-kernel call stacks for the threads of the specified process.
.It Dv KERN_PROC_VMMAP
The description of the map entries for the process.
+Also refer to
+.Xr kinfo_getvmmap 3 .
.It Dv KERN_PROC_FILEDESC
The file descriptors for files opened in the specified process.
+Also refer to
+.Xr kinfo_getfile 3 .
.It Dv KERN_PROC_GROUPS
Groups associated with the process.
.It Dv KERN_PROC_ENV
@@ -549,13 +553,6 @@ Additinal OID name element must be supplied, specifiing the resource name
as in
.Xr getrlimit 2 .
The call returns the given resource limit for the process.
-.It Dv KERN_PROC_RLIMIT_USAGE
-Like
-.Dv KERN_PROC_RLIMIT ,
-but instead of the limit, returns the accounted resource usage.
-For resources which do not have a meaningful current value,
-.Li \-1
-is returned.
.It Dv KERN_PROC_PS_STRINGS
Returns the location of the
.Vt ps_strings
@@ -585,6 +582,13 @@ Returns the address of the
location, if active.
.It Dv KERN_PROC_VM_LAYOUT
Fills a structure describing process virtual address space layout.
+.It Dv KERN_PROC_RLIMIT_USAGE
+Like
+.Dv KERN_PROC_RLIMIT ,
+but instead of the limit, returns the accounted resource usage.
+For resources which do not have a meaningful current value,
+.Li \-1
+is returned.
.It Dv KERN_PROC_KQUEUE
Fills an array of structures describing events registered with
the specified kqueue.
@@ -949,6 +953,7 @@ A process without appropriate privilege attempts to set a value.
.El
.Sh SEE ALSO
.Xr confstr 3 ,
+.Xr kinfo_getproc 3 ,
.Xr kvm 3 ,
.Xr sysconf 3 ,
.Xr sysctl 8
diff --git a/lib/libc/gen/telldir.c b/lib/libc/gen/telldir.c
index b751fafd975f..1731cc4d7a2c 100644
--- a/lib/libc/gen/telldir.c
+++ b/lib/libc/gen/telldir.c
@@ -118,7 +118,7 @@ _seekdir(DIR *dirp, long loc)
struct dirent *dp;
union ddloc_packed ddloc;
off_t loc_seek;
- long loc_loc;
+ size_t loc_loc;
ddloc.l = loc;
@@ -171,7 +171,7 @@ _seekdir(DIR *dirp, long loc)
* fetching a new block to fix any such telldir locations.
*/
void
-_fixtelldir(DIR *dirp, long oldseek, long oldloc)
+_fixtelldir(DIR *dirp, off_t oldseek, size_t oldloc)
{
struct ddloc_mem *lp;
diff --git a/lib/libc/gen/telldir.h b/lib/libc/gen/telldir.h
index 6d113491e819..02fd52af9060 100644
--- a/lib/libc/gen/telldir.h
+++ b/lib/libc/gen/telldir.h
@@ -46,9 +46,9 @@
*/
struct ddloc_mem {
LIST_ENTRY(ddloc_mem) loc_lqe; /* entry in list */
- long loc_index; /* key associated with structure */
+ size_t loc_index; /* key associated with structure */
off_t loc_seek; /* magic cookie returned by getdirentries */
- long loc_loc; /* offset of entry in buffer */
+ size_t loc_loc; /* offset of entry in buffer */
};
#ifdef __LP64__
@@ -102,7 +102,7 @@ bool _filldir(DIR *, bool);
struct dirent *_readdir_unlocked(DIR *, int);
void _reclaim_telldir(DIR *);
void _seekdir(DIR *, long);
-void _fixtelldir(DIR *dirp, long oldseek, long oldloc);
+void _fixtelldir(DIR *dirp, off_t oldseek, size_t oldloc);
DIR *__opendir_common(int, int, bool);
#define RDU_SKIP 0x0001
diff --git a/lib/libc/gen/tls.c b/lib/libc/gen/tls.c
index 30b4fc583c98..b26b13d45589 100644
--- a/lib/libc/gen/tls.c
+++ b/lib/libc/gen/tls.c
@@ -80,13 +80,13 @@ static void *libc_tls_init;
void *
__libc_tls_get_addr(void *vti)
{
- uintptr_t *dtv;
+ struct dtv *dtv;
tls_index *ti;
dtv = _tcb_get()->tcb_dtv;
ti = vti;
- return ((char *)(dtv[ti->ti_module + 1] + ti->ti_offset) +
- TLS_DTV_OFFSET);
+ return (dtv->dtv_slots[ti->ti_module - 1].dtvs_tls +
+ (ti->ti_offset + TLS_DTV_OFFSET));
}
#ifdef __i386__
@@ -151,7 +151,8 @@ libc_free_aligned(void *ptr)
* where TP points (with bias) to TLS and TCB immediately precedes TLS without
* any alignment gap[4]. Only TLS should be aligned. The TCB[0] points to DTV
* vector and DTV values are biased by constant value (TLS_DTV_OFFSET) from
- * real addresses[5].
+ * real addresses. However, like RTLD, we don't actually bias the DTV values,
+ * instead we compensate in __tls_get_addr for ti_offset's bias.
*
* [1] Ulrich Drepper: ELF Handling for Thread-Local Storage
* www.akkadia.org/drepper/tls.pdf
@@ -167,8 +168,6 @@ libc_free_aligned(void *ptr)
* but we must follow this rule due to suboptimal _tcb_set()
* (aka <ARCH>_SET_TP) implementation. This function doesn't expect TP but
* TCB as argument.
- *
- * [5] I'm not able to validate "values are biased" assertions.
*/
/*
@@ -201,11 +200,9 @@ get_tls_block_ptr(void *tcb, size_t tcbsize)
void
__libc_free_tls(void *tcb, size_t tcbsize, size_t tcbalign __unused)
{
- Elf_Addr *dtv;
- Elf_Addr **tls;
+ struct dtv *dtv;
- tls = (Elf_Addr **)tcb;
- dtv = tls[0];
+ dtv = ((struct tcb *)tcb)->tcb_dtv;
__je_bootstrap_free(dtv);
libc_free_aligned(get_tls_block_ptr(tcb, tcbsize));
}
@@ -233,7 +230,8 @@ __libc_free_tls(void *tcb, size_t tcbsize, size_t tcbalign __unused)
void *
__libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
{
- Elf_Addr *dtv, **tcb;
+ struct dtv *dtv;
+ struct tcb *tcb;
char *tls_block, *tls;
size_t extra_size, maxalign, post_size, pre_size, tls_block_size;
@@ -262,7 +260,7 @@ __libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
abort();
}
memset(tls_block, 0, tls_block_size);
- tcb = (Elf_Addr **)(tls_block + pre_size + extra_size);
+ tcb = (struct tcb *)(tls_block + pre_size + extra_size);
tls = (char *)tcb + TLS_TCB_SIZE + post_size;
if (oldtcb != NULL) {
@@ -271,19 +269,20 @@ __libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
libc_free_aligned(oldtcb);
/* Adjust the DTV. */
- dtv = tcb[0];
- dtv[2] = (Elf_Addr)(tls + TLS_DTV_OFFSET);
+ dtv = tcb->tcb_dtv;
+ dtv->dtv_slots[0].dtvs_tls = tls;
} else {
- dtv = __je_bootstrap_malloc(3 * sizeof(Elf_Addr));
+ dtv = __je_bootstrap_malloc(sizeof(struct dtv) +
+ sizeof(struct dtv_slot));
if (dtv == NULL) {
tls_msg("__libc_allocate_tls: Out of memory.\n");
abort();
}
/* Build the DTV. */
- tcb[0] = dtv;
- dtv[0] = 1; /* Generation. */
- dtv[1] = 1; /* Segments count. */
- dtv[2] = (Elf_Addr)(tls + TLS_DTV_OFFSET);
+ tcb->tcb_dtv = dtv;
+ dtv->dtv_gen = 1; /* Generation. */
+ dtv->dtv_size = 1; /* Segments count. */
+ dtv->dtv_slots[0].dtvs_tls = tls;
if (libc_tls_init_size > 0)
memcpy(tls, libc_tls_init, libc_tls_init_size);
@@ -303,8 +302,8 @@ void
__libc_free_tls(void *tcb, size_t tcbsize __unused, size_t tcbalign)
{
size_t size;
- Elf_Addr* dtv;
- Elf_Addr tlsstart, tlsend;
+ struct dtv *dtv;
+ uintptr_t tlsstart, tlsend;
/*
* Figure out the size of the initial TLS block so that we can
@@ -313,8 +312,8 @@ __libc_free_tls(void *tcb, size_t tcbsize __unused, size_t tcbalign)
tcbalign = MAX(tcbalign, libc_tls_init_align);
size = roundup2(libc_tls_static_space, tcbalign);
- dtv = ((Elf_Addr**)tcb)[1];
- tlsend = (Elf_Addr) tcb;
+ dtv = ((struct tcb *)tcb)->tcb_dtv;
+ tlsend = (uintptr_t)tcb;
tlsstart = tlsend - size;
libc_free_aligned((void*)tlsstart);
__je_bootstrap_free(dtv);
@@ -324,61 +323,60 @@ __libc_free_tls(void *tcb, size_t tcbsize __unused, size_t tcbalign)
* Allocate Static TLS using the Variant II method.
*/
void *
-__libc_allocate_tls(void *oldtls, size_t tcbsize, size_t tcbalign)
+__libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
{
size_t size;
- char *tls;
- Elf_Addr *dtv;
- Elf_Addr segbase, oldsegbase;
+ char *tls_block, *tls;
+ struct dtv *dtv;
+ struct tcb *tcb;
tcbalign = MAX(tcbalign, libc_tls_init_align);
size = roundup2(libc_tls_static_space, tcbalign);
- if (tcbsize < 2 * sizeof(Elf_Addr))
- tcbsize = 2 * sizeof(Elf_Addr);
- tls = libc_malloc_aligned(size + tcbsize, tcbalign);
- if (tls == NULL) {
+ if (tcbsize < 2 * sizeof(uintptr_t))
+ tcbsize = 2 * sizeof(uintptr_t);
+ tls_block = libc_malloc_aligned(size + tcbsize, tcbalign);
+ if (tls_block == NULL) {
tls_msg("__libc_allocate_tls: Out of memory.\n");
abort();
}
- memset(tls, 0, size + tcbsize);
- dtv = __je_bootstrap_malloc(3 * sizeof(Elf_Addr));
+ memset(tls_block, 0, size + tcbsize);
+ dtv = __je_bootstrap_malloc(sizeof(struct dtv) +
+ sizeof(struct dtv_slot));
if (dtv == NULL) {
tls_msg("__libc_allocate_tls: Out of memory.\n");
abort();
}
- segbase = (Elf_Addr)(tls + size);
- ((Elf_Addr*)segbase)[0] = segbase;
- ((Elf_Addr*)segbase)[1] = (Elf_Addr) dtv;
+ tcb = (struct tcb *)(tls_block + size);
+ tls = (char *)tcb - libc_tls_static_space;
+ tcb->tcb_self = tcb;
+ tcb->tcb_dtv = dtv;
- dtv[0] = 1;
- dtv[1] = 1;
- dtv[2] = segbase - libc_tls_static_space;
+ dtv->dtv_gen = 1;
+ dtv->dtv_size = 1;
+ dtv->dtv_slots[0].dtvs_tls = tls;
- if (oldtls) {
+ if (oldtcb != NULL) {
/*
* Copy the static TLS block over whole.
*/
- oldsegbase = (Elf_Addr) oldtls;
- memcpy((void *)(segbase - libc_tls_static_space),
- (const void *)(oldsegbase - libc_tls_static_space),
+ memcpy(tls, (const char *)oldtcb - libc_tls_static_space,
libc_tls_static_space);
/*
* We assume that this block was the one we created with
* allocate_initial_tls().
*/
- _rtld_free_tls(oldtls, 2*sizeof(Elf_Addr), sizeof(Elf_Addr));
+ _rtld_free_tls(oldtcb, 2 * sizeof(uintptr_t),
+ sizeof(uintptr_t));
} else {
- memcpy((void *)(segbase - libc_tls_static_space),
- libc_tls_init, libc_tls_init_size);
- memset((void *)(segbase - libc_tls_static_space +
- libc_tls_init_size), 0,
+ memcpy(tls, libc_tls_init, libc_tls_init_size);
+ memset(tls + libc_tls_init_size, 0,
libc_tls_static_space - libc_tls_init_size);
}
- return (void*) segbase;
+ return (tcb);
}
#endif /* TLS_VARIANT_II */
@@ -386,7 +384,7 @@ __libc_allocate_tls(void *oldtls, size_t tcbsize, size_t tcbalign)
#else
void *
-__libc_allocate_tls(void *oldtls __unused, size_t tcbsize __unused,
+__libc_allocate_tls(void *oldtcb __unused, size_t tcbsize __unused,
size_t tcbalign __unused)
{
return (0);
diff --git a/lib/libc/gen/ualarm.3 b/lib/libc/gen/ualarm.3
index f4e0973b133a..dfd9ddd5f434 100644
--- a/lib/libc/gen/ualarm.3
+++ b/lib/libc/gen/ualarm.3
@@ -70,7 +70,7 @@ restriction the maximum number of
.Fa microseconds
and
.Fa interval
-is limited to 100000000000000
+is limited to 100,000,000,000,000
(in case this value fits in the unsigned integer).
.Sh RETURN VALUES
When the signal has successfully been caught,
diff --git a/lib/libc/gen/uexterr_format.c b/lib/libc/gen/uexterr_format.c
new file mode 100644
index 000000000000..e8ddfbd578e3
--- /dev/null
+++ b/lib/libc/gen/uexterr_format.c
@@ -0,0 +1,35 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software were developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <sys/types.h>
+#include <sys/exterrvar.h>
+#include <exterr.h>
+#include <stdio.h>
+#include <string.h>
+
+int
+__uexterr_format(const struct uexterror *ue, char *buf, size_t bufsz)
+{
+ if (bufsz > UEXTERROR_MAXLEN)
+ bufsz = UEXTERROR_MAXLEN;
+ if (ue->error == 0) {
+ strlcpy(buf, "", bufsz);
+ return (0);
+ }
+ if (ue->msg[0] == '\0') {
+ snprintf(buf, bufsz,
+ "errno %d category %u (src line %u) p1 %#jx p2 %#jx",
+ ue->error, ue->cat, ue->src_line,
+ (uintmax_t)ue->p1, (uintmax_t)ue->p2);
+ } else {
+ strlcpy(buf, ue->msg, bufsz);
+ }
+ return (0);
+}
diff --git a/lib/libc/gen/uexterr_gettext.c b/lib/libc/gen/uexterr_gettext.c
new file mode 100644
index 000000000000..5b8a5cd17a48
--- /dev/null
+++ b/lib/libc/gen/uexterr_gettext.c
@@ -0,0 +1,43 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software were developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#define _WANT_P_OSREL
+#include <sys/param.h>
+#include <sys/exterrvar.h>
+#include <exterr.h>
+#include <string.h>
+#include "libc_private.h"
+
+static struct uexterror uexterr = {
+ .ver = UEXTERROR_VER,
+};
+
+int __getosreldate(void);
+
+static void uexterr_ctr(void) __attribute__((constructor));
+static void
+uexterr_ctr(void)
+{
+ if (__getosreldate() >= P_OSREL_EXTERRCTL)
+ exterrctl(EXTERRCTL_ENABLE, 0, &uexterr);
+}
+
+int
+__libc_uexterr_gettext(char *buf, size_t bufsz)
+{
+ return (__uexterr_format(&uexterr, buf, bufsz));
+}
+
+int
+uexterr_gettext(char *buf, size_t bufsz)
+{
+ return (((int (*)(char *, size_t))
+ __libc_interposing[INTERPOS_uexterr_gettext])(buf, bufsz));
+}
diff --git a/lib/libc/gen/wordexp.c b/lib/libc/gen/wordexp.c
index f1437e30bbe2..f8080c20d121 100644
--- a/lib/libc/gen/wordexp.c
+++ b/lib/libc/gen/wordexp.c
@@ -265,7 +265,15 @@ cleanup:
errno = serrno;
return (error);
}
- if (wpid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ /*
+ * Check process exit status, but ignore ECHILD as the child may have
+ * been automatically reaped if the process had set SIG_IGN or
+ * SA_NOCLDWAIT for SIGCHLD, and our reason for waitpid was just to
+ * reap our own child on behalf of the calling process.
+ */
+ if (wpid < 0 && errno != ECHILD)
+ return (WRDE_NOSPACE); /* abort for unknown reason */
+ if (wpid >= 0 && (!WIFEXITED(status) || WEXITSTATUS(status) != 0))
return (WRDE_NOSPACE); /* abort for unknown reason */
/*
diff --git a/lib/libc/i386/gen/makecontext.c b/lib/libc/i386/gen/makecontext.c
index 7b4845ac6bee..9fedd8b99c47 100644
--- a/lib/libc/i386/gen/makecontext.c
+++ b/lib/libc/i386/gen/makecontext.c
@@ -124,7 +124,7 @@ __makecontext(ucontext_t *ucp, void (*start)(void), int argc, ...)
/*
* Setup the top of the stack with the user start routine
- * followed by all of its aguments and the pointer to the
+ * followed by all of its arguments and the pointer to the
* ucontext. We need to leave a spare spot at the top of
* the stack because setcontext will move eip to the top
* of the stack before returning.
diff --git a/lib/libc/i386/static_tls.h b/lib/libc/i386/static_tls.h
deleted file mode 100644
index baf9dfc59a5a..000000000000
--- a/lib/libc/i386/static_tls.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 The FreeBSD Foundation
- *
- * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
- * under sponsorship from the FreeBSD Foundation.
- *
- * 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.
- */
-
-#ifndef _LIBC_I386_STATIC_TLS_H
-#define _LIBC_I386_STATIC_TLS_H
-
-static __inline uintptr_t
-_libc_get_static_tls_base(size_t offset)
-{
- uintptr_t tlsbase;
-
- __asm __volatile("movl %%gs:0, %0" : "=r" (tlsbase));
- tlsbase -= offset;
- return (tlsbase);
-}
-
-#endif
diff --git a/lib/libc/include/block_abi.h b/lib/libc/include/block_abi.h
index e04998d6148d..cf670c6c079e 100644
--- a/lib/libc/include/block_abi.h
+++ b/lib/libc/include/block_abi.h
@@ -37,7 +37,7 @@
* compilers that support blocks and exposing the ABI in the source for other
* compilers.
*/
-#define CALL_BLOCK(name, ...) name(__VA_ARGS__)
+#define CALL_BLOCK(name, ...) (name)(__VA_ARGS__)
#else // !__BLOCKS__
#define DECLARE_BLOCK(retTy, name, argTys, ...)\
struct {\
diff --git a/lib/libc/include/compat.h b/lib/libc/include/compat.h
index 70fb8dcd97f3..630ffe7daae3 100644
--- a/lib/libc/include/compat.h
+++ b/lib/libc/include/compat.h
@@ -69,6 +69,9 @@ __sym_compat(kevent, freebsd11_kevent, FBSD_1.0);
__sym_compat(swapoff, freebsd13_swapoff, FBSD_1.0);
+__sym_compat(getgroups, freebsd14_getgroups, FBSD_1.0);
+__sym_compat(setgroups, freebsd14_setgroups, FBSD_1.0);
+
#undef __sym_compat
#define __weak_reference(sym,alias) \
@@ -77,4 +80,3 @@ __sym_compat(swapoff, freebsd13_swapoff, FBSD_1.0);
#undef __weak_reference
#endif /* __LIBC_COMPAT_H__ */
-
diff --git a/lib/libc/include/libc_private.h b/lib/libc/include/libc_private.h
index 4c244f962b2b..db4cbc32be35 100644
--- a/lib/libc/include/libc_private.h
+++ b/lib/libc/include/libc_private.h
@@ -249,8 +249,9 @@ enum {
INTERPOS_map_stacks_exec,
INTERPOS_fdatasync,
INTERPOS_clock_nanosleep,
- INTERPOS_distribute_static_tls,
+ INTERPOS__reserved0, /* was distribute_static_tls */
INTERPOS_pdfork,
+ INTERPOS_uexterr_gettext,
INTERPOS_MAX
};
@@ -345,6 +346,7 @@ struct __ucontext;
struct __wrusage;
enum idtype;
+int __libc_execvpe(const char *, char * const *, char * const *);
int __libc_sigaction(int, const struct sigaction *,
struct sigaction *) __hidden;
int __libc_sigprocmask(int, const __sigset_t *, __sigset_t *)
@@ -359,8 +361,6 @@ struct dl_phdr_info;
int __elf_phdr_match_addr(struct dl_phdr_info *, void *);
void __init_elf_aux_vector(void);
void __libc_map_stacks_exec(void);
-void __libc_distribute_static_tls(__size_t, void *, __size_t, __size_t);
-__uintptr_t __libc_static_tls_base(__size_t);
void _pthread_cancel_enter(int);
void _pthread_cancel_leave(int);
@@ -379,4 +379,8 @@ struct __nl_cat_d *__catopen_l(const char *name, int type,
int __strerror_rl(int errnum, char *strerrbuf, size_t buflen,
struct _xlocale *locale);
+struct uexterror;
+int __uexterr_format(const struct uexterror *ue, char *buf, size_t bufsz);
+int __libc_uexterr_gettext(char *buf, size_t bufsz);
+
#endif /* _LIBC_PRIVATE_H_ */
diff --git a/lib/libc/include/port_before.h b/lib/libc/include/port_before.h
index cfc43c53f157..aa2cd394104a 100644
--- a/lib/libc/include/port_before.h
+++ b/lib/libc/include/port_before.h
@@ -5,7 +5,6 @@
#define _LIBC 1
#define DO_PTHREADS 1
#define USE_POLL 1
-#define HAVE_MD5 1
#define ISC_SOCKLEN_T socklen_t
#define ISC_FORMAT_PRINTF(fmt, args) \
diff --git a/lib/libc/inet/inet_net_ntop.c b/lib/libc/inet/inet_net_ntop.c
index 9d98dbb5ca99..30dd5c0571f2 100644
--- a/lib/libc/inet/inet_net_ntop.c
+++ b/lib/libc/inet/inet_net_ntop.c
@@ -1,20 +1,23 @@
-/*-
- * SPDX-License-Identifier: ISC
+/* $OpenBSD: inet_net_ntop.c,v 1.9 2019/07/03 03:24:04 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2012 by Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 1996 by Internet Software Consortium.
*
- * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
- * Copyright (c) 1996,1999 by Internet Software Consortium.
+ * SPDX-License-Identifier: ISC
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
- * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
*/
#include "port_before.h"
@@ -31,18 +34,10 @@
#include "port_after.h"
-#ifdef SPRINTF_CHAR
-# define SPRINTF(x) strlen(sprintf/**/x)
-#else
-# define SPRINTF(x) ((size_t)sprintf x)
-#endif
-
-static char * inet_net_ntop_ipv4(const u_char *src, int bits, char *dst,
- size_t size);
-static char * inet_net_ntop_ipv6(const u_char *src, int bits, char *dst,
- size_t size);
+static char *inet_net_ntop_ipv4(const u_char *, int, char *, size_t);
+static char *inet_net_ntop_ipv6(const u_char *, int, char *, size_t);
-/*%
+/*
* char *
* inet_net_ntop(af, src, bits, dst, size)
* convert network number from network to presentation format.
@@ -66,7 +61,7 @@ inet_net_ntop(int af, const void *src, int bits, char *dst, size_t size)
}
}
-/*%
+/*
* static char *
* inet_net_ntop_ipv4(src, bits, dst, size)
* convert IPv4 network number from network to presentation format.
@@ -83,53 +78,63 @@ static char *
inet_net_ntop_ipv4(const u_char *src, int bits, char *dst, size_t size)
{
char *odst = dst;
- char *t;
u_int m;
int b;
+ char *ep;
+ int advance;
+
+ ep = dst + size;
+ if (ep <= dst)
+ goto emsgsize;
if (bits < 0 || bits > 32) {
errno = EINVAL;
return (NULL);
}
-
if (bits == 0) {
- if (size < sizeof "0")
+ if (ep - dst < sizeof "0")
goto emsgsize;
*dst++ = '0';
- size--;
*dst = '\0';
}
/* Format whole octets. */
for (b = bits / 8; b > 0; b--) {
- if (size <= sizeof "255.")
+ if (ep - dst < sizeof "255.")
+ goto emsgsize;
+ advance = snprintf(dst, ep - dst, "%u", *src++);
+ if (advance <= 0 || advance >= ep - dst)
goto emsgsize;
- t = dst;
- dst += SPRINTF((dst, "%u", *src++));
+ dst += advance;
if (b > 1) {
+ if (dst + 1 >= ep)
+ goto emsgsize;
*dst++ = '.';
*dst = '\0';
}
- size -= (size_t)(dst - t);
}
/* Format partial octet. */
b = bits % 8;
if (b > 0) {
- if (size <= sizeof ".255")
+ if (ep - dst < sizeof ".255")
goto emsgsize;
- t = dst;
if (dst != odst)
*dst++ = '.';
m = ((1 << b) - 1) << (8 - b);
- dst += SPRINTF((dst, "%u", *src & m));
- size -= (size_t)(dst - t);
+ advance = snprintf(dst, ep - dst, "%u", *src & m);
+ if (advance <= 0 || advance >= ep - dst)
+ goto emsgsize;
+ dst += advance;
}
/* Format CIDR /width. */
- if (size <= sizeof "/32")
+ if (ep - dst < sizeof "/32")
+ goto emsgsize;
+ advance = snprintf(dst, ep - dst, "/%u", bits);
+ if (advance <= 0 || advance >= ep - dst)
goto emsgsize;
- dst += SPRINTF((dst, "/%u", bits));
+ dst += advance;
return (odst);
emsgsize:
@@ -137,132 +142,27 @@ inet_net_ntop_ipv4(const u_char *src, int bits, char *dst, size_t size)
return (NULL);
}
-/*%
- * static char *
- * inet_net_ntop_ipv6(src, bits, fakebits, dst, size)
- * convert IPv6 network number from network to presentation format.
- * generates CIDR style result always. Picks the shortest representation
- * unless the IP is really IPv4.
- * always prints specified number of bits (bits).
- * return:
- * pointer to dst, or NULL if an error occurred (check errno).
- * note:
- * network byte order assumed. this means 192.5.5.240/28 has
- * 0b11110000 in its fourth octet.
- * author:
- * Vadim Kogan (UCB), June 2001
- * Original version (IPv4) by Paul Vixie (ISC), July 1996
- */
-
static char *
-inet_net_ntop_ipv6(const u_char *src, int bits, char *dst, size_t size) {
- u_int m;
- int b;
- int p;
- int zero_s, zero_l, tmp_zero_s, tmp_zero_l;
- int i;
- int is_ipv4 = 0;
- unsigned char inbuf[16];
- char outbuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")];
- char *cp;
- int words;
- u_char *s;
+inet_net_ntop_ipv6(const u_char *src, int bits, char *dst, size_t size)
+{
+ int ret;
+ char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")];
if (bits < 0 || bits > 128) {
errno = EINVAL;
return (NULL);
}
- cp = outbuf;
-
- if (bits == 0) {
- *cp++ = ':';
- *cp++ = ':';
- *cp = '\0';
- } else {
- /* Copy src to private buffer. Zero host part. */
- p = (bits + 7) / 8;
- memcpy(inbuf, src, p);
- memset(inbuf + p, 0, 16 - p);
- b = bits % 8;
- if (b != 0) {
- m = ~0 << (8 - b);
- inbuf[p-1] &= m;
- }
-
- s = inbuf;
-
- /* how many words need to be displayed in output */
- words = (bits + 15) / 16;
- if (words == 1)
- words = 2;
-
- /* Find the longest substring of zero's */
- zero_s = zero_l = tmp_zero_s = tmp_zero_l = 0;
- for (i = 0; i < (words * 2); i += 2) {
- if ((s[i] | s[i+1]) == 0) {
- if (tmp_zero_l == 0)
- tmp_zero_s = i / 2;
- tmp_zero_l++;
- } else {
- if (tmp_zero_l && zero_l < tmp_zero_l) {
- zero_s = tmp_zero_s;
- zero_l = tmp_zero_l;
- tmp_zero_l = 0;
- }
- }
- }
-
- if (tmp_zero_l && zero_l < tmp_zero_l) {
- zero_s = tmp_zero_s;
- zero_l = tmp_zero_l;
- }
-
- if (zero_l != words && zero_s == 0 && ((zero_l == 6) ||
- ((zero_l == 5 && s[10] == 0xff && s[11] == 0xff) ||
- ((zero_l == 7 && s[14] != 0 && s[15] != 1)))))
- is_ipv4 = 1;
-
- /* Format whole words. */
- for (p = 0; p < words; p++) {
- if (zero_l != 0 && p >= zero_s && p < zero_s + zero_l) {
- /* Time to skip some zeros */
- if (p == zero_s)
- *cp++ = ':';
- if (p == words - 1)
- *cp++ = ':';
- s++;
- s++;
- continue;
- }
+ if (inet_ntop(AF_INET6, src, buf, size) == NULL)
+ return (NULL);
- if (is_ipv4 && p > 5 ) {
- *cp++ = (p == 6) ? ':' : '.';
- cp += SPRINTF((cp, "%u", *s++));
- /* we can potentially drop the last octet */
- if (p != 7 || bits > 120) {
- *cp++ = '.';
- cp += SPRINTF((cp, "%u", *s++));
- }
- } else {
- if (cp != outbuf)
- *cp++ = ':';
- cp += SPRINTF((cp, "%x", *s * 256 + s[1]));
- s += 2;
- }
- }
+ ret = snprintf(dst, size, "%s/%d", buf, bits);
+ if (ret < 0 || ret >= size) {
+ errno = EMSGSIZE;
+ return (NULL);
}
- /* Format CIDR /width. */
- sprintf(cp, "/%u", bits);
- if (strlen(outbuf) + 1 > size)
- goto emsgsize;
- strcpy(dst, outbuf);
return (dst);
-
-emsgsize:
- errno = EMSGSIZE;
- return (NULL);
}
/*
@@ -271,5 +171,3 @@ emsgsize:
*/
#undef inet_net_ntop
__weak_reference(__inet_net_ntop, inet_net_ntop);
-
-/*! \file */
diff --git a/lib/libc/inet/inet_net_pton.c b/lib/libc/inet/inet_net_pton.c
index d566a0e1d3c3..14c88eb72014 100644
--- a/lib/libc/inet/inet_net_pton.c
+++ b/lib/libc/inet/inet_net_pton.c
@@ -1,20 +1,23 @@
-/*-
- * SPDX-License-Identifier: ISC
+/* $OpenBSD: inet_net_pton.c,v 1.14 2022/12/27 17:10:06 jmc Exp $ */
+
+/*
+ * Copyright (c) 2012 by Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 1996,1999 by Internet Software Consortium.
*
- * Copyright (C) 2004, 2005, 2008 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 1996, 1998, 1999, 2001, 2003 Internet Software Consortium.
+ * SPDX-License-Identifier: ISC
*
- * Permission to use, copy, modify, and/or distribute this software for any
+ * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
*/
#include "port_before.h"
@@ -22,7 +25,6 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
-#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <assert.h>
@@ -34,13 +36,37 @@
#include "port_after.h"
-#ifdef SPRINTF_CHAR
-# define SPRINTF(x) strlen(sprintf/**/x)
-#else
-# define SPRINTF(x) ((size_t)sprintf x)
-#endif
+static int inet_net_pton_ipv4(const char *, u_char *, size_t);
+static int inet_net_pton_ipv6(const char *, u_char *, size_t);
-/*%
+/*
+ * static int
+ * inet_net_pton(af, src, dst, size)
+ * convert network number from presentation to network format.
+ * accepts hex octets, hex strings, decimal octets, and /CIDR.
+ * "size" is in bytes and describes "dst".
+ * return:
+ * number of bits, either imputed classfully or specified with /CIDR,
+ * or -1 if some failure occurred (check errno). ENOENT means it was
+ * not a valid network specification.
+ * author:
+ * Paul Vixie (ISC), June 1996
+ */
+int
+inet_net_pton(int af, const char *src, void *dst, size_t size)
+{
+ switch (af) {
+ case AF_INET:
+ return (inet_net_pton_ipv4(src, dst, size));
+ case AF_INET6:
+ return (inet_net_pton_ipv6(src, dst, size));
+ default:
+ errno = EAFNOSUPPORT;
+ return (-1);
+ }
+}
+
+/*
* static int
* inet_net_pton_ipv4(src, dst, size)
* convert IPv4 network number from presentation to network format.
@@ -57,22 +83,24 @@
* Paul Vixie (ISC), June 1996
*/
static int
-inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) {
- static const char xdigits[] = "0123456789abcdef";
- static const char digits[] = "0123456789";
- int n, ch, tmp = 0, dirty, bits;
+inet_net_pton_ipv4(const char *src, u_char *dst, size_t size)
+{
+ static const char
+ xdigits[] = "0123456789abcdef",
+ digits[] = "0123456789";
+ int n, ch, tmp, dirty, bits;
const u_char *odst = dst;
- ch = *src++;
+ ch = (unsigned char)*src++;
if (ch == '0' && (src[0] == 'x' || src[0] == 'X')
- && isascii((unsigned char)(src[1]))
- && isxdigit((unsigned char)(src[1]))) {
+ && isascii((unsigned char)src[1]) && isxdigit((unsigned char)src[1])) {
/* Hexadecimal: Eat nybble string. */
- if (size <= 0U)
+ if (size == 0)
goto emsgsize;
- dirty = 0;
- src++; /*%< skip x or X. */
- while ((ch = *src++) != '\0' && isascii(ch) && isxdigit(ch)) {
+ tmp = 0, dirty = 0;
+ src++; /* skip x or X. */
+ while ((ch = (unsigned char)*src++) != '\0' &&
+ isascii(ch) && isxdigit(ch)) {
if (isupper(ch))
ch = tolower(ch);
n = strchr(xdigits, ch) - xdigits;
@@ -82,14 +110,14 @@ inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) {
else
tmp = (tmp << 4) | n;
if (++dirty == 2) {
- if (size-- <= 0U)
+ if (size-- == 0)
goto emsgsize;
*dst++ = (u_char) tmp;
dirty = 0;
}
}
- if (dirty) { /*%< Odd trailing nybble? */
- if (size-- <= 0U)
+ if (dirty) { /* Odd trailing nybble? */
+ if (size-- == 0)
goto emsgsize;
*dst++ = (u_char) (tmp << 4);
}
@@ -104,16 +132,16 @@ inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) {
tmp += n;
if (tmp > 255)
goto enoent;
- } while ((ch = *src++) != '\0' &&
+ } while ((ch = (unsigned char)*src++) != '\0' &&
isascii(ch) && isdigit(ch));
- if (size-- <= 0U)
+ if (size-- == 0)
goto emsgsize;
*dst++ = (u_char) tmp;
if (ch == '\0' || ch == '/')
break;
if (ch != '.')
goto enoent;
- ch = *src++;
+ ch = (unsigned char)*src++;
if (!isascii(ch) || !isdigit(ch))
goto enoent;
}
@@ -121,10 +149,10 @@ inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) {
goto enoent;
bits = -1;
- if (ch == '/' && isascii((unsigned char)(src[0])) &&
- isdigit((unsigned char)(src[0])) && dst > odst) {
+ if (ch == '/' && isascii((unsigned char)src[0]) &&
+ isdigit((unsigned char)src[0]) && dst > odst) {
/* CIDR width specifier. Nothing can follow it. */
- ch = *src++; /*%< Skip over the /. */
+ ch = (unsigned char)*src++; /* Skip over the /. */
bits = 0;
do {
n = strchr(digits, ch) - digits;
@@ -132,8 +160,9 @@ inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) {
bits *= 10;
bits += n;
if (bits > 32)
- goto enoent;
- } while ((ch = *src++) != '\0' && isascii(ch) && isdigit(ch));
+ goto emsgsize;
+ } while ((ch = (unsigned char)*src++) != '\0' &&
+ isascii(ch) && isdigit(ch));
if (ch != '\0')
goto enoent;
}
@@ -147,29 +176,23 @@ inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) {
goto enoent;
/* If no CIDR spec was given, infer width from net class. */
if (bits == -1) {
- if (*odst >= 240) /*%< Class E */
+ if (*odst >= 240) /* Class E */
bits = 32;
- else if (*odst >= 224) /*%< Class D */
- bits = 8;
- else if (*odst >= 192) /*%< Class C */
+ else if (*odst >= 224) /* Class D */
+ bits = 4;
+ else if (*odst >= 192) /* Class C */
bits = 24;
- else if (*odst >= 128) /*%< Class B */
+ else if (*odst >= 128) /* Class B */
bits = 16;
- else /*%< Class A */
+ else /* Class A */
bits = 8;
/* If imputed mask is narrower than specified octets, widen. */
if (bits < ((dst - odst) * 8))
bits = (dst - odst) * 8;
- /*
- * If there are no additional bits specified for a class D
- * address adjust bits to 4.
- */
- if (bits == 8 && *odst == 224)
- bits = 4;
}
/* Extend network to cover the actual mask. */
while (bits > ((dst - odst) * 8)) {
- if (size-- <= 0U)
+ if (size-- == 0)
goto emsgsize;
*dst++ = '\0';
}
@@ -184,222 +207,48 @@ inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) {
return (-1);
}
-static int
-getbits(const char *src, int *bitsp) {
- static const char digits[] = "0123456789";
- int n;
- int val;
- char ch;
-
- val = 0;
- n = 0;
- while ((ch = *src++) != '\0') {
- const char *pch;
-
- pch = strchr(digits, ch);
- if (pch != NULL) {
- if (n++ != 0 && val == 0) /*%< no leading zeros */
- return (0);
- val *= 10;
- val += (pch - digits);
- if (val > 128) /*%< range */
- return (0);
- continue;
- }
- return (0);
- }
- if (n == 0)
- return (0);
- *bitsp = val;
- return (1);
-}
static int
-getv4(const char *src, u_char *dst, int *bitsp) {
- static const char digits[] = "0123456789";
- u_char *odst = dst;
- int n;
- u_int val;
- char ch;
-
- val = 0;
- n = 0;
- while ((ch = *src++) != '\0') {
- const char *pch;
+inet_net_pton_ipv6(const char *src, u_char *dst, size_t size)
+{
+ struct in6_addr in6;
+ int ret;
+ int bits;
+ size_t bytes;
+ char buf[INET6_ADDRSTRLEN + sizeof("/128")];
+ char *sep;
+ const char *errstr;
- pch = strchr(digits, ch);
- if (pch != NULL) {
- if (n++ != 0 && val == 0) /*%< no leading zeros */
- return (0);
- val *= 10;
- val += (pch - digits);
- if (val > 255) /*%< range */
- return (0);
- continue;
- }
- if (ch == '.' || ch == '/') {
- if (dst - odst > 3) /*%< too many octets? */
- return (0);
- *dst++ = val;
- if (ch == '/')
- return (getbits(src, bitsp));
- val = 0;
- n = 0;
- continue;
- }
- return (0);
+ if (strlcpy(buf, src, sizeof buf) >= sizeof buf) {
+ errno = EMSGSIZE;
+ return (-1);
}
- if (n == 0)
- return (0);
- if (dst - odst > 3) /*%< too many octets? */
- return (0);
- *dst++ = val;
- return (1);
-}
-static int
-inet_net_pton_ipv6(const char *src, u_char *dst, size_t size) {
- static const char xdigits_l[] = "0123456789abcdef",
- xdigits_u[] = "0123456789ABCDEF";
- u_char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
- const char *xdigits, *curtok;
- int ch, saw_xdigit;
- u_int val;
- int digits;
- int bits;
- size_t bytes;
- int words;
- int ipv4;
+ sep = strchr(buf, '/');
+ if (sep != NULL)
+ *sep++ = '\0';
- memset((tp = tmp), '\0', NS_IN6ADDRSZ);
- endp = tp + NS_IN6ADDRSZ;
- colonp = NULL;
- /* Leading :: requires some special handling. */
- if (*src == ':')
- if (*++src != ':')
- goto enoent;
- curtok = src;
- saw_xdigit = 0;
- val = 0;
- digits = 0;
- bits = -1;
- ipv4 = 0;
- while ((ch = *src++) != '\0') {
- const char *pch;
+ ret = inet_pton(AF_INET6, buf, &in6);
+ if (ret != 1)
+ return (-1);
- if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
- pch = strchr((xdigits = xdigits_u), ch);
- if (pch != NULL) {
- val <<= 4;
- val |= (pch - xdigits);
- if (++digits > 4)
- goto enoent;
- saw_xdigit = 1;
- continue;
- }
- if (ch == ':') {
- curtok = src;
- if (!saw_xdigit) {
- if (colonp)
- goto enoent;
- colonp = tp;
- continue;
- } else if (*src == '\0')
- goto enoent;
- if (tp + NS_INT16SZ > endp)
- return (0);
- *tp++ = (u_char) (val >> 8) & 0xff;
- *tp++ = (u_char) val & 0xff;
- saw_xdigit = 0;
- digits = 0;
- val = 0;
- continue;
- }
- if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
- getv4(curtok, tp, &bits) > 0) {
- tp += NS_INADDRSZ;
- saw_xdigit = 0;
- ipv4 = 1;
- break; /*%< '\\0' was seen by inet_pton4(). */
- }
- if (ch == '/' && getbits(src, &bits) > 0)
- break;
- goto enoent;
- }
- if (saw_xdigit) {
- if (tp + NS_INT16SZ > endp)
- goto enoent;
- *tp++ = (u_char) (val >> 8) & 0xff;
- *tp++ = (u_char) val & 0xff;
- }
- if (bits == -1)
+ if (sep == NULL)
bits = 128;
-
- words = (bits + 15) / 16;
- if (words < 2)
- words = 2;
- if (ipv4)
- words = 8;
- endp = tmp + 2 * words;
-
- if (colonp != NULL) {
- /*
- * Since some memmove()'s erroneously fail to handle
- * overlapping regions, we'll do the shift by hand.
- */
- const int n = tp - colonp;
- int i;
-
- if (tp == endp)
- goto enoent;
- for (i = 1; i <= n; i++) {
- endp[- i] = colonp[n - i];
- colonp[n - i] = 0;
+ else {
+ bits = strtonum(sep, 0, 128, &errstr);
+ if (errstr) {
+ errno = EINVAL;
+ return (-1);
}
- tp = endp;
}
- if (tp != endp)
- goto enoent;
bytes = (bits + 7) / 8;
- if (bytes > size)
- goto emsgsize;
- memcpy(dst, tmp, bytes);
- return (bits);
-
- enoent:
- errno = ENOENT;
- return (-1);
-
- emsgsize:
- errno = EMSGSIZE;
- return (-1);
-}
-
-/*%
- * int
- * inet_net_pton(af, src, dst, size)
- * convert network number from presentation to network format.
- * accepts hex octets, hex strings, decimal octets, and /CIDR.
- * "size" is in bytes and describes "dst".
- * return:
- * number of bits, either imputed classfully or specified with /CIDR,
- * or -1 if some failure occurred (check errno). ENOENT means it was
- * not a valid network specification.
- * author:
- * Paul Vixie (ISC), June 1996
- */
-int
-inet_net_pton(int af, const char *src, void *dst, size_t size) {
- switch (af) {
- case AF_INET:
- return (inet_net_pton_ipv4(src, dst, size));
- case AF_INET6:
- return (inet_net_pton_ipv6(src, dst, size));
- default:
- errno = EAFNOSUPPORT;
+ if (bytes > size) {
+ errno = EMSGSIZE;
return (-1);
}
+ memcpy(dst, &in6.s6_addr, bytes);
+ return (bits);
}
/*
@@ -408,5 +257,3 @@ inet_net_pton(int af, const char *src, void *dst, size_t size) {
*/
#undef inet_net_pton
__weak_reference(__inet_net_pton, inet_net_pton);
-
-/*! \file */
diff --git a/lib/libc/locale/collate.c b/lib/libc/locale/collate.c
index 8e3635485f10..c0fc4c91481d 100644
--- a/lib/libc/locale/collate.c
+++ b/lib/libc/locale/collate.c
@@ -43,6 +43,7 @@
#include <sys/mman.h>
#include <assert.h>
+#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -732,3 +733,261 @@ __collate_equiv_value(locale_t locale, const wchar_t *str, size_t len)
}
return (0);
}
+
+/*
+ * __collate_collating_symbol takes the multibyte string specified by
+ * src and slen, and using ps, converts that to a wide character. Then
+ * it is checked to verify it is a collating symbol, and then copies
+ * it to the wide character string specified by dst and dlen (the
+ * results are not null terminated). The length of the wide characters
+ * copied to dst is returned if successful. Zero is returned if no such
+ * collating symbol exists. (size_t)-1 is returned if there are wide-character
+ * conversion errors, if the length of the converted string is greater that
+ * COLLATE_STR_LEN or if dlen is too small. It is up to the calling routine to
+ * preserve the mbstate_t structure as needed.
+ */
+size_t
+__collate_collating_symbol(wchar_t *dst, size_t dlen, const char *src,
+ size_t slen, mbstate_t *ps)
+{
+ wchar_t wname[COLLATE_STR_LEN];
+ wchar_t w, *wp;
+ struct xlocale_collate *table;
+ size_t len, l;
+
+ table =
+ (struct xlocale_collate *)__get_locale()->components[XLC_COLLATE];
+ /* POSIX locale */
+ if (table->__collate_load_error) {
+ if (dlen < 1)
+ return ((size_t)-1);
+ if (slen != 1 || !isascii(*src))
+ return (0);
+ *dst = *src;
+ return (1);
+ }
+ for (wp = wname, len = 0; slen > 0; len++) {
+ l = mbrtowc(&w, src, slen, ps);
+ if (l == (size_t)-1 || l == (size_t)-2)
+ return ((size_t)-1);
+ if (l == 0)
+ break;
+ if (len >= COLLATE_STR_LEN)
+ return ((size_t)-1);
+ *wp++ = w;
+ src += l;
+ slen -= l;
+ }
+ if (len == 0 || len > dlen)
+ return ((size_t)-1);
+ if (len == 1) {
+ if (*wname <= UCHAR_MAX) {
+ if (table->char_pri_table[*wname].pri[0] >= 0) {
+ if (dlen > 0)
+ *dst = *wname;
+ return (1);
+ }
+ return (0);
+ } else if (table->info->large_count > 0) {
+ collate_large_t *match;
+ match = largesearch(table, *wname);
+ if (match && match->pri.pri[0] >= 0) {
+ if (dlen > 0)
+ *dst = *wname;
+ return (1);
+ }
+ }
+ return (0);
+ }
+ *wp = 0;
+ if (table->info->chain_count > 0) {
+ collate_chain_t *match;
+ int ll;
+ match = chainsearch(table, wname, &ll);
+ if (match) {
+ if (ll < dlen)
+ dlen = ll;
+ wcsncpy(dst, wname, dlen);
+ return (dlen);
+ }
+ }
+ return (0);
+}
+
+/*
+ * __collate_equiv_class returns the equivalence class number for the symbol
+ * specified by src and slen, using ps to convert from multi-byte to wide
+ * character. Zero is returned if the symbol is not in an equivalence
+ * class. -1 is returned if there are wide character conversion errors,
+ * if there are any greater-than-8-bit characters or if a multi-byte symbol
+ * is greater or equal to COLLATE_STR_LEN in length. It is up to the calling
+ * routine to preserve the mbstate_t structure as needed.
+ */
+int
+__collate_equiv_class(const char *src, size_t slen, mbstate_t *ps)
+{
+ wchar_t wname[COLLATE_STR_LEN];
+ wchar_t w, *wp;
+ struct xlocale_collate *table;
+ size_t len, l;
+ int e;
+
+ table =
+ (struct xlocale_collate *)__get_locale()->components[XLC_COLLATE];
+ /* POSIX locale */
+ if (table->__collate_load_error)
+ return (0);
+ for (wp = wname, len = 0; slen > 0; len++) {
+ l = mbrtowc(&w, src, slen, ps);
+ if (l == (size_t)-1 || l == (size_t)-2)
+ return (-1);
+ if (l == 0)
+ break;
+ if (len >= COLLATE_STR_LEN)
+ return (-1);
+ *wp++ = w;
+ src += l;
+ slen -= l;
+ }
+ if (len == 0)
+ return (-1);
+ if (len == 1) {
+ e = -1;
+ if (*wname <= UCHAR_MAX)
+ e = table->char_pri_table[*wname].pri[0];
+ else if (table->info->large_count > 0) {
+ collate_large_t *match;
+ match = largesearch(table, *wname);
+ if (match)
+ e = match->pri.pri[0];
+ }
+ if (e == 0)
+ return (IGNORE_EQUIV_CLASS);
+ return (e > 0 ? e : 0);
+ }
+ *wp = 0;
+ if (table->info->chain_count > 0) {
+ collate_chain_t *match;
+ int ll;
+ match = chainsearch(table, wname, &ll);
+ if (match) {
+ e = match->pri[0];
+ if (e == 0)
+ return (IGNORE_EQUIV_CLASS);
+ return (e < 0 ? -e : e);
+ }
+ }
+ return (0);
+}
+
+
+/*
+ * __collate_equiv_match tries to match any single or multi-character symbol
+ * in equivalence class equiv_class in the multi-byte string specified by src
+ * and slen. If start is non-zero, it is taken to be the first (pre-converted)
+ * wide character. Subsequence wide characters, if needed, will use ps in
+ * the conversion. On a successful match, the length of the matched string
+ * is returned (including the start character). If dst is non-NULL, the
+ * matched wide-character string is copied to dst, a wide character array of
+ * length dlen (the results are not zero-terminated). If rlen is non-NULL,
+ * the number of character in src actually used is returned. Zero is
+ * returned by __collate_equiv_match if there is no match. (size_t)-1 is
+ * returned on error: if there were conversion errors or if dlen is too small
+ * to accept the results. On no match or error, ps is restored to its incoming
+ * state.
+ */
+size_t
+__collate_equiv_match(int equiv_class, wchar_t *dst, size_t dlen, wchar_t start,
+ const char *src, size_t slen, mbstate_t *ps, size_t *rlen)
+{
+ wchar_t w;
+ size_t len, l, clen;
+ int i;
+ wchar_t buf[COLLATE_STR_LEN], *wp;
+ mbstate_t save;
+ const char *s = src;
+ struct xlocale_collate *table;
+ size_t sl = slen;
+ collate_chain_t *ch = NULL;
+
+ table =
+ (struct xlocale_collate *)__get_locale()->components[XLC_COLLATE];
+ /* POSIX locale */
+ if (table->__collate_load_error)
+ return ((size_t)-1);
+ if (equiv_class == IGNORE_EQUIV_CLASS)
+ equiv_class = 0;
+ if (ps)
+ save = *ps;
+ wp = buf;
+ len = clen = 0;
+ if (start) {
+ *wp++ = start;
+ len = 1;
+ }
+ /* convert up to the max chain length */
+ while (sl > 0 && len < table->info->chain_max_len) {
+ l = mbrtowc(&w, s, sl, ps);
+ if (l == (size_t)-1 || l == (size_t)-2 || l == 0)
+ break;
+ *wp++ = w;
+ s += l;
+ clen += l;
+ sl -= l;
+ len++;
+ }
+ *wp = 0;
+ if (len > 1 && (ch = chainsearch(table, buf, &i)) != NULL) {
+ int e = ch->pri[0];
+ if (e < 0)
+ e = -e;
+ if (e == equiv_class)
+ goto found;
+ }
+ /* try single character */
+ i = 1;
+ if (*buf <= UCHAR_MAX) {
+ if (equiv_class == table->char_pri_table[*buf].pri[0])
+ goto found;
+ } else if (table->info->large_count > 0) {
+ collate_large_t *match;
+ match = largesearch(table, *buf);
+ if (match && equiv_class == match->pri.pri[0])
+ goto found;
+ }
+ /* no match */
+ if (ps)
+ *ps = save;
+ return (0);
+found:
+ /*
+ * If we converted more than we used, restore to initial
+ * and reconvert up to what did match.
+ */
+ if (i < len) {
+ len = i;
+ if (ps)
+ *ps = save;
+ if (start)
+ i--;
+ clen = 0;
+ while (i-- > 0) {
+ l = mbrtowc(&w, src, slen, ps);
+ src += l;
+ clen += l;
+ slen -= l;
+ }
+ }
+ if (dst) {
+ if (dlen < len) {
+ if (ps)
+ *ps = save;
+ return ((size_t)-1);
+ }
+ for (wp = buf; len > 0; len--)
+ *dst++ = *wp++;
+ }
+ if (rlen)
+ *rlen = clen;
+ return (len);
+}
diff --git a/lib/libc/locale/collate.h b/lib/libc/locale/collate.h
index f157d8651899..64e0e6f2337d 100644
--- a/lib/libc/locale/collate.h
+++ b/lib/libc/locale/collate.h
@@ -38,6 +38,7 @@
#include <sys/types.h>
#include <limits.h>
+#include <wchar.h>
#include "xlocale_private.h"
/*
@@ -65,6 +66,8 @@
#define DIRECTIVE_DIRECTION_MASK (DIRECTIVE_FORWARD | DIRECTIVE_BACKWARD)
+#define IGNORE_EQUIV_CLASS 1
+
/*
* The collate file format is as follows:
*
@@ -85,6 +88,7 @@
typedef struct collate_info {
uint8_t directive_count;
uint8_t directive[COLL_WEIGHTS_MAX];
+ uint8_t chain_max_len; /* In padding */
int32_t pri_count[COLL_WEIGHTS_MAX];
int32_t flags;
int32_t chain_count;
@@ -126,8 +130,13 @@ struct xlocale_collate {
};
__BEGIN_DECLS
-int __collate_load_tables(const char *);
+size_t __collate_collating_symbol(wchar_t *, size_t, const char *, size_t,
+ mbstate_t *);
+int __collate_equiv_class(const char *, size_t, mbstate_t *);
int __collate_equiv_value(locale_t, const wchar_t *, size_t);
+size_t __collate_equiv_match(int, wchar_t *, size_t, wchar_t, const char *,
+ size_t, mbstate_t *, size_t *);
+int __collate_load_tables(const char *);
void _collate_lookup(struct xlocale_collate *,const wchar_t *, int *, int *,
int, const int **);
int __collate_range_cmp(char, char);
diff --git a/lib/libc/locale/euc.c b/lib/libc/locale/euc.c
index c54a6ceceff2..c110fc61b375 100644
--- a/lib/libc/locale/euc.c
+++ b/lib/libc/locale/euc.c
@@ -426,7 +426,7 @@ _EUC_wcrtomb_impl(char * __restrict s, wchar_t wc,
/* This first check excludes CS1, which is implicitly valid. */
if ((wc < 0xa100) || (wc > 0xffff)) {
/* Check for valid CS2 or CS3 */
- nm = (wc >> ((len - 1) * 8));
+ nm = (wc >> ((len - 1) * 8)) & 0xff;
if (nm == cs2) {
if (len != cs2width) {
errno = EILSEQ;
diff --git a/lib/libc/md/Makefile.inc b/lib/libc/md/Makefile.inc
deleted file mode 100644
index 82c5f0670485..000000000000
--- a/lib/libc/md/Makefile.inc
+++ /dev/null
@@ -1,3 +0,0 @@
-.PATH: ${SRCTOP}/sys/kern
-
-SRCS+= md5c.c
diff --git a/lib/libc/net/Makefile.inc b/lib/libc/net/Makefile.inc
index 558cf7be5771..7e9e5295c78c 100644
--- a/lib/libc/net/Makefile.inc
+++ b/lib/libc/net/Makefile.inc
@@ -1,7 +1,7 @@
# machine-independent net sources
.PATH: ${LIBC_SRCTOP}/net
-CONFS+= net/hosts net/hosts.equiv net/networks net/nsswitch.conf net/protocols
+CONFS+= net/hosts net/hosts.equiv net/networks ${.OBJDIR}/nsswitch.conf net/protocols
SRCS+= base64.c ether_addr.c eui64.c \
gai_strerror.c getaddrinfo.c \
gethostbydns.c gethostbyht.c gethostbynis.c gethostnamadr.c \
@@ -141,7 +141,8 @@ MLINKS+=inet6_opt_init.3 inet6_opt_append.3 \
inet6_rthdr_space.3 inet6_rthdr_reverse.3 \
inet6_rthdr_space.3 inet6_rthdr_segments.3
MLINKS+=linkaddr.3 link_addr.3 \
- linkaddr.3 link_ntoa.3
+ linkaddr.3 link_ntoa.3 \
+ linkaddr.3 link_ntoa_r.3
MLINKS+=rcmd.3 iruserok.3 \
rcmd.3 iruserok_sa.3 \
rcmd.3 rcmd_af.3 \
@@ -176,9 +177,13 @@ MLINKS+=hesiod.3 hesiod_init.3 \
hesiod.3 hesiod_end.3
.endif
-afterinstallconfig: modify-nsswitch-conf
-modify-nsswitch-conf: .PHONY
+all: ${.OBJDIR}/nsswitch.conf
+
+# Manually stage nsswitch.conf in the object directory so that we can edit it
+# before installing.
+${.OBJDIR}/nsswitch.conf: ${LIBC_SRCTOP}/net/nsswitch.conf
+ cp -f ${LIBC_SRCTOP}/net/nsswitch.conf ${.TARGET}
.if ${MK_NIS} == "no"
sed -i "" -e 's/.*_compat:/# &/' -e 's/compat$$/files/' \
- ${DESTDIR}/etc/nsswitch.conf
+ ${.TARGET}
.endif
diff --git a/lib/libc/net/Symbol.map b/lib/libc/net/Symbol.map
index ebc3a56b549f..25e79b4e5807 100644
--- a/lib/libc/net/Symbol.map
+++ b/lib/libc/net/Symbol.map
@@ -138,6 +138,10 @@ FBSD_1.3 {
sctp_sendv;
};
+FBSD_1.8 {
+ link_ntoa_r;
+};
+
FBSDprivate_1.0 {
_nsdispatch;
_nsyyerror; /* generated from nslexer.l */
diff --git a/lib/libc/net/gai_strerror.c b/lib/libc/net/gai_strerror.c
index 0d4303e76a73..23f58b763573 100644
--- a/lib/libc/net/gai_strerror.c
+++ b/lib/libc/net/gai_strerror.c
@@ -45,22 +45,22 @@
* Entries EAI_ADDRFAMILY (1) and EAI_NODATA (7) were omitted from RFC 3493,
* but are or may be used as extensions or in old code.
*/
-static const char *ai_errlist[] = {
- "Success", /* 0 */
- "Address family for hostname not supported", /* EAI_ADDRFAMILY */
- "Name could not be resolved at this time", /* EAI_AGAIN */
- "Flags parameter had an invalid value", /* EAI_BADFLAGS */
- "Non-recoverable failure in name resolution", /* EAI_FAIL */
- "Address family not recognized", /* EAI_FAMILY */
- "Memory allocation failure", /* EAI_MEMORY */
- "No address associated with hostname", /* EAI_NODATA*/
- "Name does not resolve", /* EAI_NONAME */
- "Service was not recognized for socket type", /* EAI_SERVICE */
- "Intended socket type was not recognized", /* EAI_SOCKTYPE */
- "System error returned in errno", /* EAI_SYSTEM */
- "Invalid value for hints", /* EAI_BADHINTS */
- "Resolved protocol is unknown", /* EAI_PROTOCOL */
- "Argument buffer overflow" /* EAI_OVERFLOW */
+static const char *const ai_errlist[] = {
+ [0] = "Success",
+ [EAI_ADDRFAMILY] = "Address family for hostname not supported",
+ [EAI_AGAIN] = "Name could not be resolved at this time",
+ [EAI_BADFLAGS] = "Flags parameter had an invalid value",
+ [EAI_FAIL] = "Non-recoverable failure in name resolution",
+ [EAI_FAMILY] = "Address family not recognized",
+ [EAI_MEMORY] = "Memory allocation failure",
+ [EAI_NODATA] = "No address associated with hostname",
+ [EAI_NONAME] = "Name does not resolve",
+ [EAI_SERVICE] = "Service was not recognized for socket type",
+ [EAI_SOCKTYPE] = "Intended socket type was not recognized",
+ [EAI_SYSTEM] = "System error returned in errno",
+ [EAI_BADHINTS] = "Invalid value for hints",
+ [EAI_PROTOCOL] = "Resolved protocol is unknown",
+ [EAI_OVERFLOW] = "Argument buffer overflow",
};
#if defined(NLS)
@@ -72,7 +72,7 @@ static int gai_keycreated = 0;
static void
gai_keycreate(void)
{
- gai_keycreated = (thr_keycreate(&gai_key, free) == 0);
+ gai_keycreated = thr_keycreate(&gai_key, free) == 0;
}
#endif
@@ -82,7 +82,9 @@ gai_strerror(int ecode)
#if defined(NLS)
nl_catd catd;
char *buf;
+ int saved_errno;
+ saved_errno = errno;
if (thr_main() != 0)
buf = gai_buf;
else {
@@ -110,11 +112,13 @@ gai_strerror(int ecode)
strlcpy(buf, catgets(catd, 3, NL_MSGMAX, "Unknown error"),
sizeof(gai_buf));
catclose(catd);
- return buf;
+ errno = saved_errno;
+ return (buf);
thr_err:
+ errno = saved_errno;
#endif
if (ecode >= 0 && ecode < EAI_MAX)
- return ai_errlist[ecode];
- return "Unknown error";
+ return (ai_errlist[ecode]);
+ return ("Unknown error");
}
diff --git a/lib/libc/net/gethostbydns.c b/lib/libc/net/gethostbydns.c
index b29fa1cdd845..216fc9bcf9a4 100644
--- a/lib/libc/net/gethostbydns.c
+++ b/lib/libc/net/gethostbydns.c
@@ -74,8 +74,10 @@
#define SPRINTF(x) ((size_t)sprintf x)
+#ifdef DEBUG
static const char AskedForGot[] =
"gethostby*.gethostanswer: asked for \"%s\", got \"%s\"";
+#endif
#ifdef RESOLVSORT
static void addrsort(char **, int, res_state);
@@ -299,8 +301,10 @@ gethostanswer(const querybuf *answer, int anslen, const char *qname, int qtype,
switch (type) {
case T_PTR:
if (strcasecmp(tname, bp) != 0) {
+#ifdef DEBUG
syslog(LOG_NOTICE|LOG_AUTH,
AskedForGot, qname, bp);
+#endif
cp += n;
continue; /* XXX - had_error++ ? */
}
@@ -347,8 +351,10 @@ gethostanswer(const querybuf *answer, int anslen, const char *qname, int qtype,
case T_A:
case T_AAAA:
if (strcasecmp(he->h_name, bp) != 0) {
+#ifdef DEBUG
syslog(LOG_NOTICE|LOG_AUTH,
AskedForGot, he->h_name, bp);
+#endif
cp += n;
continue; /* XXX - had_error++ ? */
}
diff --git a/lib/libc/net/linkaddr.3 b/lib/libc/net/linkaddr.3
index ee3ea3056c71..38ee6a0aedea 100644
--- a/lib/libc/net/linkaddr.3
+++ b/lib/libc/net/linkaddr.3
@@ -28,12 +28,13 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd February 28, 2007
+.Dd May 9, 2025
.Dt LINK_ADDR 3
.Os
.Sh NAME
.Nm link_addr ,
-.Nm link_ntoa
+.Nm link_ntoa ,
+.Nm link_ntoa_r
.Nd elementary address specification routines for link level access
.Sh LIBRARY
.Lb libc
@@ -41,16 +42,29 @@
.In sys/types.h
.In sys/socket.h
.In net/if_dl.h
-.Ft void
+.Ft int
.Fn link_addr "const char *addr" "struct sockaddr_dl *sdl"
.Ft char *
.Fn link_ntoa "const struct sockaddr_dl *sdl"
+.Ft int
+.Fn link_ntoa_r "const struct sockaddr_dl *sdl" "char *obuf" "size_t *buflen"
.Sh DESCRIPTION
The routine
.Fn link_addr
-interprets character strings representing
-link-level addresses, returning binary information suitable
-for use in system calls.
+parses a character string
+.Fa addr
+representing a link-level address,
+and stores the resulting address in the structure pointed to by
+.Fa sdl .
+A link-level address consists of an optional interface name, followed by
+a colon (which is required in all cases), followed by an address
+consisting of either a string of hexadecimal digits, or a series of
+hexadecimal octets separated by one of the characters
+.Sq "." ,
+.Sq ":" ,
+or
+.Sq - .
+.Pp
The routine
.Fn link_ntoa
takes
@@ -60,9 +74,34 @@ address and returns an
string representing some of the information present,
including the link level address itself, and the interface name
or number, if present.
+The returned string is stored in a static buffer.
This facility is experimental and is
still subject to change.
.Pp
+The routine
+.Fn link_ntoa_r
+behaves like
+.Fn link_ntoa ,
+except the string is placed in the provided buffer instead of a static
+buffer.
+The caller should initialize
+.Fa buflen
+to the number of bytes available in
+.Fa obuf .
+On return,
+.Fa buflen
+is set to the actual number of bytes required for the output buffer,
+including the NUL terminator.
+If
+.Fa obuf
+is NULL, then
+.Fa buflen
+is set as described, but nothing is written.
+This may be used to determine the required length of the buffer before
+calling
+.Fn link_ntoa_r
+a second time.
+.Pp
For
.Fn link_addr ,
the string
@@ -94,12 +133,21 @@ The
.Fn link_ntoa
function
always returns a null terminated string.
+.Pp
+The
+.Fn link_ntoa_r
+function returns 0 on success, or -1 if the provided buffer was not
+large enough; in the latter case, the contents of the buffer are
+indeterminate, but a trailing NUL will always be written if the buffer
+was at least one byte in size.
+.Pp
The
.Fn link_addr
-function
-has no return value.
-(See
-.Sx BUGS . )
+function returns 0 on success.
+If the address did not appear to be a valid link-level address, -1 is
+returned and
+.Va errno
+is set to indicate the error.
.Sh SEE ALSO
.Xr getnameinfo 3
.Sh HISTORY
@@ -109,15 +157,14 @@ and
.Fn link_ntoa
functions appeared in
.Bx 4.3 Reno .
+The
+.Fn link_ntoa_r
+function appeared in
+.Fx 15.0 .
.Sh BUGS
The returned values for link_ntoa
reside in a static memory area.
.Pp
-The function
-.Fn link_addr
-should diagnose improperly formed input, and there should be an unambiguous
-way to recognize this.
-.Pp
If the
.Va sdl_len
field of the link socket address
diff --git a/lib/libc/net/linkaddr.c b/lib/libc/net/linkaddr.c
index dedc910b9474..5be4c0a7a43e 100644
--- a/lib/libc/net/linkaddr.c
+++ b/lib/libc/net/linkaddr.c
@@ -31,135 +31,261 @@
#include <sys/types.h>
#include <sys/socket.h>
+
#include <net/if.h>
#include <net/if_dl.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
#include <string.h>
-/* States*/
-#define NAMING 0
-#define GOTONE 1
-#define GOTTWO 2
-#define RESET 3
-/* Inputs */
-#define DIGIT (4*0)
-#define END (4*1)
-#define DELIM (4*2)
-#define LETTER (4*3)
-
-void
+int
link_addr(const char *addr, struct sockaddr_dl *sdl)
{
char *cp = sdl->sdl_data;
char *cplim = sdl->sdl_len + (char *)sdl;
- int byte = 0, state = NAMING, new;
+ const char *nptr;
+ size_t newsize;
+ int error = 0;
+ char delim = 0;
+ /* Initialise the sdl to zero, except for sdl_len. */
bzero((char *)&sdl->sdl_family, sdl->sdl_len - 1);
sdl->sdl_family = AF_LINK;
- do {
- state &= ~LETTER;
- if ((*addr >= '0') && (*addr <= '9')) {
- new = *addr - '0';
- } else if ((*addr >= 'a') && (*addr <= 'f')) {
- new = *addr - 'a' + 10;
- } else if ((*addr >= 'A') && (*addr <= 'F')) {
- new = *addr - 'A' + 10;
- } else if (*addr == 0) {
- state |= END;
- } else if (state == NAMING &&
- (((*addr >= 'A') && (*addr <= 'Z')) ||
- ((*addr >= 'a') && (*addr <= 'z'))))
- state |= LETTER;
- else
- state |= DELIM;
- addr++;
- switch (state /* | INPUT */) {
- case NAMING | DIGIT:
- case NAMING | LETTER:
- *cp++ = addr[-1];
- continue;
- case NAMING | DELIM:
- state = RESET;
- sdl->sdl_nlen = cp - sdl->sdl_data;
- continue;
- case GOTTWO | DIGIT:
- *cp++ = byte;
- /* FALLTHROUGH */
- case RESET | DIGIT:
- state = GOTONE;
- byte = new;
- continue;
- case GOTONE | DIGIT:
- state = GOTTWO;
- byte = new + (byte << 4);
- continue;
- default: /* | DELIM */
- state = RESET;
- *cp++ = byte;
- byte = 0;
- continue;
- case GOTONE | END:
- case GOTTWO | END:
- *cp++ = byte;
- /* FALLTHROUGH */
- case RESET | END:
+
+ /*
+ * Everything up to the first ':' is the interface name. Usually the
+ * ':' should always be present even if there's no interface name, but
+ * since this interface was poorly specified in the past, accept a
+ * missing colon as meaning no interface name.
+ */
+ if ((nptr = strchr(addr, ':')) != NULL) {
+ size_t namelen = nptr - addr;
+
+ /* Ensure the sdl is large enough to store the name. */
+ if (namelen > cplim - cp) {
+ errno = ENOSPC;
+ return (-1);
+ }
+
+ memcpy(cp, addr, namelen);
+ cp += namelen;
+ sdl->sdl_nlen = namelen;
+ /* Skip the interface name and the colon. */
+ addr += namelen + 1;
+ }
+
+ /*
+ * The remainder of the string should be hex digits representing the
+ * address, with optional delimiters. Each two hex digits form one
+ * octet, but octet output can be forced using a delimiter, so we accept
+ * a long string of hex digits, or a mix of delimited and undelimited
+ * digits like "1122.3344.5566", or delimited one- or two-digit octets
+ * like "1.22.3".
+ *
+ * If anything fails at this point, exit the loop so we set sdl_alen and
+ * sdl_len based on whatever we did manage to parse. This preserves
+ * compatibility with the 4.3BSD version of link_addr, which had no way
+ * to indicate an error and would just return.
+ */
+#define DIGIT(c) \
+ (((c) >= '0' && (c) <= '9') ? ((c) - '0') \
+ : ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) \
+ : ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) \
+ : (-1))
+#define ISDELIM(c) (((c) == '.' || (c) == ':' || (c) == '-') && \
+ (delim == 0 || delim == (c)))
+
+ for (;;) {
+ int digit, digit2;
+
+ /*
+ * Treat any leading delimiters as empty bytes. This supports
+ * the (somewhat obsolete) form of Ethernet addresses with empty
+ * octets, e.g. "1::3:4:5:6".
+ */
+ while (ISDELIM(*addr) && cp < cplim) {
+ delim = *addr++;
+ *cp++ = 0;
+ }
+
+ /* Did we reach the end of the string? */
+ if (*addr == '\0')
+ break;
+
+ /*
+ * If not, the next character must be a digit, so make sure we
+ * have room for at least one more octet.
+ */
+
+ if (cp >= cplim) {
+ error = ENOSPC;
+ break;
+ }
+
+ if ((digit = DIGIT(*addr)) == -1) {
+ error = EINVAL;
break;
}
- break;
- } while (cp < cplim);
+
+ ++addr;
+
+ /* If the next character is another digit, consume it. */
+ if ((digit2 = DIGIT(*addr)) != -1) {
+ digit = (digit << 4) | digit2;
+ ++addr;
+ }
+
+ if (ISDELIM(*addr)) {
+ /*
+ * If the digit is followed by a delimiter, write it
+ * and consume the delimiter.
+ */
+ delim = *addr++;
+ *cp++ = digit;
+ } else if (DIGIT(*addr) != -1) {
+ /*
+ * If two digits are followed by a third digit, treat
+ * the two digits we have as a single octet and
+ * continue.
+ */
+ *cp++ = digit;
+ } else if (*addr == '\0') {
+ /* If the digit is followed by EOS, we're done. */
+ *cp++ = digit;
+ break;
+ } else {
+ /* Otherwise, the input was invalid. */
+ error = EINVAL;
+ break;
+ }
+ }
+#undef DIGIT
+#undef ISDELIM
+
+ /* How many bytes did we write to the address? */
sdl->sdl_alen = cp - LLADDR(sdl);
- new = cp - (char *)sdl;
- if (new > sizeof(*sdl))
- sdl->sdl_len = new;
- return;
+
+ /*
+ * The user might have given us an sdl which is larger than sizeof(sdl);
+ * in that case, record the actual size of the new sdl.
+ */
+ newsize = cp - (char *)sdl;
+ if (newsize > sizeof(*sdl))
+ sdl->sdl_len = (u_char)newsize;
+
+ if (error == 0)
+ return (0);
+
+ errno = error;
+ return (-1);
}
-static const char hexlist[] = "0123456789abcdef";
char *
link_ntoa(const struct sockaddr_dl *sdl)
{
static char obuf[64];
+ size_t buflen;
_Static_assert(sizeof(obuf) >= IFNAMSIZ + 20, "obuf is too small");
+
+ /*
+ * Ignoring the return value of link_ntoa_r() is safe here because it
+ * always writes the terminating NUL. This preserves the traditional
+ * behaviour of link_ntoa().
+ */
+ buflen = sizeof(obuf);
+ (void)link_ntoa_r(sdl, obuf, &buflen);
+ return obuf;
+}
+
+int
+link_ntoa_r(const struct sockaddr_dl *sdl, char *obuf, size_t *buflen)
+{
+ static const char hexlist[] = "0123456789abcdef";
char *out;
const u_char *in, *inlim;
int namelen, i, rem;
+ size_t needed;
- namelen = (sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ;
+ assert(sdl);
+ assert(buflen);
+ /* obuf may be null */
+ needed = 1; /* 1 for the NUL */
out = obuf;
- rem = sizeof(obuf);
+ if (obuf)
+ rem = *buflen;
+ else
+ rem = 0;
+
+/*
+ * Check if at least n bytes are available in the output buffer, plus 1 for the
+ * trailing NUL. If not, set rem = 0 so we stop writing.
+ * Either way, increment needed by the amount we would have written.
+ */
+#define CHECK(n) do { \
+ if ((SIZE_MAX - (n)) >= needed) \
+ needed += (n); \
+ if (rem >= ((n) + 1)) \
+ rem -= (n); \
+ else \
+ rem = 0; \
+ } while (0)
+
+/*
+ * Write the char c to the output buffer, unless the buffer is full.
+ * Note that if obuf is NULL, rem is always zero.
+ */
+#define OUT(c) do { \
+ if (rem > 0) \
+ *out++ = (c); \
+ } while (0)
+
+ namelen = (sdl->sdl_nlen <= IFNAMSIZ) ? sdl->sdl_nlen : IFNAMSIZ;
if (namelen > 0) {
- bcopy(sdl->sdl_data, out, namelen);
- out += namelen;
- rem -= namelen;
+ CHECK(namelen);
+ if (rem > 0) {
+ bcopy(sdl->sdl_data, out, namelen);
+ out += namelen;
+ }
+
if (sdl->sdl_alen > 0) {
- *out++ = ':';
- rem--;
+ CHECK(1);
+ OUT(':');
}
}
- in = (const u_char *)sdl->sdl_data + sdl->sdl_nlen;
+ in = (const u_char *)LLADDR(sdl);
inlim = in + sdl->sdl_alen;
- while (in < inlim && rem > 1) {
- if (in != (const u_char *)sdl->sdl_data + sdl->sdl_nlen) {
- *out++ = '.';
- rem--;
+ while (in < inlim) {
+ if (in != (const u_char *)LLADDR(sdl)) {
+ CHECK(1);
+ OUT('.');
}
i = *in++;
if (i > 0xf) {
- if (rem < 3)
- break;
- *out++ = hexlist[i >> 4];
- *out++ = hexlist[i & 0xf];
- rem -= 2;
+ CHECK(2);
+ OUT(hexlist[i >> 4]);
+ OUT(hexlist[i & 0xf]);
} else {
- if (rem < 2)
- break;
- *out++ = hexlist[i];
- rem--;
+ CHECK(1);
+ OUT(hexlist[i]);
}
}
- *out = 0;
- return (obuf);
+
+#undef CHECK
+#undef OUT
+
+ /*
+ * We always leave enough room for the NUL if possible, but the user
+ * might have passed a NULL or zero-length buffer.
+ */
+ if (out && *buflen)
+ *out = '\0';
+
+ *buflen = needed;
+ return ((rem > 0) ? 0 : -1);
}
diff --git a/lib/libc/net/res_config.h b/lib/libc/net/res_config.h
index f049d6817b7a..39a1b5f1486f 100644
--- a/lib/libc/net/res_config.h
+++ b/lib/libc/net/res_config.h
@@ -1,5 +1,5 @@
-#define DEBUG 1 /* enable debugging code (needed for dig) */
+//#define DEBUG /* enable debugging code */
#define RESOLVSORT /* allow sorting of addresses in gethostbyname */
-#undef SUNSECURITY /* verify gethostbyaddr() calls - WE DON'T NEED IT */
+//#define SUNSECURITY /* verify gethostbyaddr() calls */
#define MULTI_PTRS_ARE_ALIASES 1 /* fold multiple PTR records into aliases */
diff --git a/lib/libc/nls/Makefile.inc b/lib/libc/nls/Makefile.inc
index f26e04c187a5..c211026aba72 100644
--- a/lib/libc/nls/Makefile.inc
+++ b/lib/libc/nls/Makefile.inc
@@ -12,6 +12,11 @@ MAN+= catclose.3 catgets.3 catopen.3
# for translators.
NLSNAME= libc
+# We don't want libc's NLS catalogues to be installed in the clibs package.
+# Put them in locales instead, since anyone interested in NLS will have
+# that installed.
+NLSPACKAGE= locales
+
NLS+= be_BY.UTF-8
NLS+= ca_ES.ISO8859-1
NLS+= de_DE.ISO8859-1
diff --git a/lib/libc/powerpc/static_tls.h b/lib/libc/powerpc/static_tls.h
deleted file mode 100644
index 9ae38c71a515..000000000000
--- a/lib/libc/powerpc/static_tls.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 The FreeBSD Foundation
- *
- * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
- * under sponsorship from the FreeBSD Foundation.
- *
- * 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.
- */
-
-#ifndef _LIBC_POWERPC_STATIC_TLS_H
-#define _LIBC_POWERPC_STATIC_TLS_H
-
-static __inline uintptr_t
-_libc_get_static_tls_base(size_t offset)
-{
- uintptr_t tlsbase;
-
- __asm __volatile("mr %0,2" : "=r"(tlsbase));
- tlsbase += offset - 0x7008;
- return (tlsbase);
-}
-
-#endif
diff --git a/lib/libc/powerpc64/gen/_ctx_start.S b/lib/libc/powerpc64/gen/_ctx_start.S
index c2f8abfd6486..98225f9c1138 100644
--- a/lib/libc/powerpc64/gen/_ctx_start.S
+++ b/lib/libc/powerpc64/gen/_ctx_start.S
@@ -34,6 +34,16 @@
ld %r2,8(%r14)
ld %r14,0(%r14)
#else
+ /*
+ * The stack frame was already set up in makecontext(),
+ * so we can safely use the guaranteed fields here.
+ *
+ * Note we do step on the allocated stack frame's TOC,
+ * but since we never return from this function (i.e.
+ * never restore the stack frame) this should be safe.
+ */
+ std %r2,24(%r1) /* save TOC */
+
/* Load global entry point */
mr %r12,%r14
#endif
@@ -41,6 +51,10 @@
blrl /* branch to start function */
mr %r3,%r15 /* pass pointer to ucontext as argument */
nop
+#if defined(_CALL_ELF) && _CALL_ELF != 1
+ /* Restore TOC */
+ ld %r2,24(%r1)
+#endif
bl CNAME(_ctx_done) /* branch to ctxt completion func */
/*
* we should never return from the
diff --git a/lib/libc/powerpc64/gen/makecontext.c b/lib/libc/powerpc64/gen/makecontext.c
index 75c2d40bdd60..9e3a976fa1bd 100644
--- a/lib/libc/powerpc64/gen/makecontext.c
+++ b/lib/libc/powerpc64/gen/makecontext.c
@@ -78,7 +78,7 @@ __makecontext(ucontext_t *ucp, void (*start)(void), int argc, ...)
*/
stackargs = (argc > 8) ? argc - 8 : 0;
sp = (char *) ucp->uc_stack.ss_sp + ucp->uc_stack.ss_size
- - sizeof(uintptr_t)*(stackargs + 2);
+ - sizeof(uintptr_t)*(stackargs + 6);
sp = (char *)((uintptr_t)sp & ~0x1f);
mc = &ucp->uc_mcontext;
@@ -119,6 +119,7 @@ __makecontext(ucontext_t *ucp, void (*start)(void), int argc, ...)
mc->mc_srr0 = *(uintptr_t *)_ctx_start;
#else
mc->mc_srr0 = (uintptr_t) _ctx_start;
+ mc->mc_gpr[12] = (uintptr_t) _ctx_start;/* required for prologue */
#endif
mc->mc_gpr[1] = (uintptr_t) sp; /* new stack pointer */
mc->mc_gpr[14] = (uintptr_t) start; /* r14 <- start */
diff --git a/lib/libc/powerpc64/static_tls.h b/lib/libc/powerpc64/static_tls.h
deleted file mode 100644
index 014b8a931453..000000000000
--- a/lib/libc/powerpc64/static_tls.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 The FreeBSD Foundation
- *
- * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
- * under sponsorship from the FreeBSD Foundation.
- *
- * 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.
- */
-
-#ifndef _LIBC_POWERPC64_STATIC_TLS_H
-#define _LIBC_POWERPC64_STATIC_TLS_H
-
-static __inline uintptr_t
-_libc_get_static_tls_base(size_t offset)
-{
- uintptr_t tlsbase;
-
- __asm __volatile("mr %0,13" : "=r"(tlsbase));
- tlsbase += offset - 0x7010;
- return (tlsbase);
-}
-
-#endif
diff --git a/lib/libc/regex/regcomp.c b/lib/libc/regex/regcomp.c
index f34dc322d0bb..aebea2b02435 100644
--- a/lib/libc/regex/regcomp.c
+++ b/lib/libc/regex/regcomp.c
@@ -1183,6 +1183,7 @@ p_b_pseudoclass(struct parse *p, char c) {
/* PASSTHROUGH */
case 'w':
p_b_cclass_named(p, cs, "alnum");
+ CHadd(p, cs, '_');
break;
case 'S':
cs->invert = 1;
diff --git a/lib/libc/resolv/Symbol.map b/lib/libc/resolv/Symbol.map
index 6b9c43298fb5..26daecbe2eff 100644
--- a/lib/libc/resolv/Symbol.map
+++ b/lib/libc/resolv/Symbol.map
@@ -103,6 +103,5 @@ FBSD_1.0 {
};
FBSD_1.4 {
- __res_rndinit;
__res_nrandomid;
};
diff --git a/lib/libc/resolv/res_debug.h b/lib/libc/resolv/res_debug.h
index dd048116fb49..ccae03e625aa 100644
--- a/lib/libc/resolv/res_debug.h
+++ b/lib/libc/resolv/res_debug.h
@@ -23,7 +23,7 @@
#ifndef DEBUG
# define Dprint(cond, args) /*empty*/
# define DprintQ(cond, args, query, size) /*empty*/
-# define Aerror(statp, file, string, error, address) /*empty*/
+# define Aerror(statp, file, string, error, address, alen) /*empty*/
# define Perror(statp, file, string, error) /*empty*/
#else
# define Dprint(cond, args) if (cond) {fprintf args;} else {}
diff --git a/lib/libc/resolv/res_init.c b/lib/libc/resolv/res_init.c
index 70d6bc6d3bf2..5a2fce013c8c 100644
--- a/lib/libc/resolv/res_init.c
+++ b/lib/libc/resolv/res_init.c
@@ -86,19 +86,6 @@
#include <unistd.h>
#include <netdb.h>
-#ifndef HAVE_MD5
-# include "../dst/md5.h"
-#else
-# ifdef SOLARIS2
-# include <sys/md5.h>
-# elif _LIBC
-# include <md5.h>
-# endif
-#endif
-#ifndef _MD5_H_
-# define _MD5_H_ 1 /*%< make sure we do not include rsaref md5.h file */
-#endif
-
#include "un-namespace.h"
#include "port_after.h"
@@ -108,12 +95,6 @@
#include "res_private.h"
-/*% Options. Should all be left alone. */
-#define RESOLVSORT
-#ifndef DEBUG
-#define DEBUG
-#endif
-
#ifdef SOLARIS2
#include <sys/systeminfo.h>
#endif
@@ -190,8 +171,6 @@ __res_vinit(res_state statp, int preinit) {
statp->options = RES_DEFAULT;
}
- statp->_rnd = malloc(16);
- res_rndinit(statp);
statp->id = res_nrandomid(statp);
memset(u, 0, sizeof(u));
@@ -739,48 +718,18 @@ net_mask(struct in_addr in) /*!< XXX - should really use system's version of th
}
#endif
-static u_char srnd[16];
-
void
-res_rndinit(res_state statp)
+freebsd15_res_rndinit(res_state statp)
{
- struct timeval now;
- u_int32_t u32;
- u_int16_t u16;
- u_char *rnd = statp->_rnd == NULL ? srnd : statp->_rnd;
-
- gettimeofday(&now, NULL);
- u32 = now.tv_sec;
- memcpy(rnd, &u32, 4);
- u32 = now.tv_usec;
- memcpy(rnd + 4, &u32, 4);
- u32 += now.tv_sec;
- memcpy(rnd + 8, &u32, 4);
- u16 = getpid();
- memcpy(rnd + 12, &u16, 2);
+ (void)statp;
}
+__sym_compat(__res_rndinit, freebsd15_res_rndinit, FBSD_1.4);
u_int
res_nrandomid(res_state statp) {
- struct timeval now;
- u_int16_t u16;
- MD5_CTX ctx;
- u_char *rnd = statp->_rnd == NULL ? srnd : statp->_rnd;
-
- gettimeofday(&now, NULL);
- u16 = (u_int16_t) (now.tv_sec ^ now.tv_usec);
- memcpy(rnd + 14, &u16, 2);
-#ifndef HAVE_MD5
- MD5_Init(&ctx);
- MD5_Update(&ctx, rnd, 16);
- MD5_Final(rnd, &ctx);
-#else
- MD5Init(&ctx);
- MD5Update(&ctx, rnd, 16);
- MD5Final(rnd, &ctx);
-#endif
- memcpy(&u16, rnd + 14, 2);
- return ((u_int) u16);
+ (void) statp;
+
+ return ((u_int)(arc4random() & 0xffff));
}
/*%
@@ -814,10 +763,6 @@ res_ndestroy(res_state statp) {
free(statp->_u._ext.ext);
statp->_u._ext.ext = NULL;
}
- if (statp->_rnd != NULL) {
- free(statp->_rnd);
- statp->_rnd = NULL;
- }
statp->options &= ~RES_INIT;
}
diff --git a/lib/libc/resolv/res_mkquery.c b/lib/libc/resolv/res_mkquery.c
index 0c15def5d117..f6767a92375c 100644
--- a/lib/libc/resolv/res_mkquery.c
+++ b/lib/libc/resolv/res_mkquery.c
@@ -76,11 +76,6 @@
#include <string.h>
#include "port_after.h"
-/* Options. Leave them on. */
-#ifndef DEBUG
-#define DEBUG
-#endif
-
extern const char *_res_opcodes[];
/*%
diff --git a/lib/libc/resolv/res_mkupdate.c b/lib/libc/resolv/res_mkupdate.c
index e5a3cb702cda..3f595dc4ec08 100644
--- a/lib/libc/resolv/res_mkupdate.c
+++ b/lib/libc/resolv/res_mkupdate.c
@@ -48,10 +48,6 @@
#include "port_after.h"
-/* Options. Leave them on. */
-#ifndef DEBUG
-#define DEBUG
-#endif
#define MAXPORT 1024
static int getnum_str(u_char **, u_char *);
diff --git a/lib/libc/resolv/res_query.c b/lib/libc/resolv/res_query.c
index e9c628ad8d47..f26d59e522b4 100644
--- a/lib/libc/resolv/res_query.c
+++ b/lib/libc/resolv/res_query.c
@@ -81,11 +81,6 @@
#include <unistd.h>
#include "port_after.h"
-/* Options. Leave them on. */
-#ifndef DEBUG
-#define DEBUG
-#endif
-
#if PACKETSZ > 1024
#define MAXPACKET PACKETSZ
#else
diff --git a/lib/libc/resolv/res_send.c b/lib/libc/resolv/res_send.c
index 3fb627b83d55..08c3aed7f934 100644
--- a/lib/libc/resolv/res_send.c
+++ b/lib/libc/resolv/res_send.c
@@ -112,10 +112,6 @@
#include "un-namespace.h"
-/* Options. Leave them on. */
-#ifndef DEBUG
-#define DEBUG
-#endif
#include "res_debug.h"
#include "res_private.h"
@@ -138,15 +134,12 @@ static int send_dg(res_state,
const u_char *, int,
u_char *, int, int *, int, int,
int *, int *);
+#ifdef DEBUG
static void Aerror(const res_state, FILE *, const char *, int,
const struct sockaddr *, int);
static void Perror(const res_state, FILE *, const char *, int);
-static int sock_eq(struct sockaddr *, struct sockaddr *);
-#if defined(NEED_PSELECT) && !defined(USE_POLL) && !defined(USE_KQUEUE)
-static int pselect(int, void *, void *, void *,
- struct timespec *,
- const sigset_t *);
#endif
+static int sock_eq(struct sockaddr *, struct sockaddr *);
void res_pquery(const res_state, const u_char *, int, FILE *);
static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
@@ -302,7 +295,9 @@ res_nsend(res_state statp,
#ifdef USE_KQUEUE
int kq;
#endif
+#ifdef DEBUG
char abuf[NI_MAXHOST];
+#endif
/* No name servers or res_init() failure */
if (statp->nscount == 0 || EXT(statp).ext == NULL) {
@@ -418,10 +413,10 @@ res_nsend(res_state statp,
*/
for (tries = 0; tries < statp->retry; tries++) {
for (ns = 0; ns < statp->nscount; ns++) {
- struct sockaddr *nsap;
- int nsaplen;
- nsap = get_nsaddr(statp, ns);
- nsaplen = get_salen(nsap);
+ struct sockaddr *nsap = get_nsaddr(statp, ns);
+#ifdef DEBUG
+ int nsaplen = get_salen(nsap);
+#endif
statp->_flags &= ~RES_F_LASTMASK;
statp->_flags |= (ns << RES_F_LASTSHIFT);
same_ns:
@@ -1088,6 +1083,7 @@ send_dg(res_state statp,
return (resplen);
}
+#ifdef DEBUG
static void
Aerror(const res_state statp, FILE *file, const char *string, int error,
const struct sockaddr *address, int alen)
@@ -1119,6 +1115,7 @@ Perror(const res_state statp, FILE *file, const char *string, int error) {
string, strerror(error));
errno = save;
}
+#endif
static int
sock_eq(struct sockaddr *a, struct sockaddr *b) {
@@ -1145,29 +1142,3 @@ sock_eq(struct sockaddr *a, struct sockaddr *b) {
return 0;
}
}
-
-#if defined(NEED_PSELECT) && !defined(USE_POLL) && !defined(USE_KQUEUE)
-/* XXX needs to move to the porting library. */
-static int
-pselect(int nfds, void *rfds, void *wfds, void *efds,
- struct timespec *tsp, const sigset_t *sigmask)
-{
- struct timeval tv, *tvp;
- sigset_t sigs;
- int n;
-
- if (tsp) {
- tvp = &tv;
- tv = evTimeVal(*tsp);
- } else
- tvp = NULL;
- if (sigmask)
- sigprocmask(SIG_SETMASK, sigmask, &sigs);
- n = select(nfds, rfds, wfds, efds, tvp);
- if (sigmask)
- sigprocmask(SIG_SETMASK, &sigs, NULL);
- if (tsp)
- *tsp = evTimeSpec(tv);
- return (n);
-}
-#endif
diff --git a/lib/libc/riscv/static_tls.h b/lib/libc/riscv/static_tls.h
deleted file mode 100644
index ce9fa23338a6..000000000000
--- a/lib/libc/riscv/static_tls.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*-
- * SPDX-License-Identifier: BSD-2-Clause
- *
- * Copyright (c) 2019 The FreeBSD Foundation
- *
- * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
- * under sponsorship from the FreeBSD Foundation.
- *
- * 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.
- */
-
-#ifndef _LIBC_RISCV_STATIC_TLS_H
-#define _LIBC_RISCV_STATIC_TLS_H
-
-static __inline uintptr_t
-_libc_get_static_tls_base(size_t offset)
-{
- uintptr_t tlsbase;
-
- __asm __volatile("mv %0, tp" : "=r"(tlsbase));
- tlsbase += offset;
- return (tlsbase);
-}
-
-#endif
diff --git a/lib/libc/rpc/rpc_generic.c b/lib/libc/rpc/rpc_generic.c
index 0e563f6a5996..8019f2d8f236 100644
--- a/lib/libc/rpc/rpc_generic.c
+++ b/lib/libc/rpc/rpc_generic.c
@@ -610,6 +610,10 @@ __rpc_taddr2uaddr_af(int af, const struct netbuf *nbuf)
return NULL;
break;
#endif
+ case AF_NETLINK:
+ if (asprintf(&ret, "%s", (char *)nbuf->buf) < 0)
+ return NULL;
+ break;
case AF_LOCAL:
sun = nbuf->buf;
if (asprintf(&ret, "%.*s", (int)(sun->sun_len -
diff --git a/lib/libc/rpc/rpc_secure.3 b/lib/libc/rpc/rpc_secure.3
index 11b871b5c99d..ce59bba7115f 100644
--- a/lib/libc/rpc/rpc_secure.3
+++ b/lib/libc/rpc/rpc_secure.3
@@ -266,8 +266,7 @@ Inverse of
.Fn netname2user .
.Sh SEE ALSO
.Xr rpc 3 ,
-.Xr xdr 3 ,
-.Xr keyserv 8
+.Xr xdr 3
.Pp
The following manuals:
.Rs
diff --git a/lib/libc/rpc/svc_nl.c b/lib/libc/rpc/svc_nl.c
index 5cce44d9d98f..cff364063c5c 100644
--- a/lib/libc/rpc/svc_nl.c
+++ b/lib/libc/rpc/svc_nl.c
@@ -92,7 +92,7 @@ svc_nl_create(const char *service)
.nl_family = PF_NETLINK,
};
struct nl_softc *sc;
- SVCXPRT *xprt;
+ SVCXPRT *xprt = NULL;
void *buf = NULL;
uint16_t family;
ssize_t len = 1024;
@@ -133,6 +133,7 @@ svc_nl_create(const char *service)
return (xprt);
fail:
+ free(xprt);
free(buf);
snl_free(&sc->snl);
free(sc);
diff --git a/lib/libc/stdio/Symbol.map b/lib/libc/stdio/Symbol.map
index d491b83c11fb..e44ea6554d46 100644
--- a/lib/libc/stdio/Symbol.map
+++ b/lib/libc/stdio/Symbol.map
@@ -44,7 +44,6 @@ FBSD_1.0 {
fwscanf;
getc;
getchar;
- gets;
getw;
getwc;
getwchar;
diff --git a/lib/libc/stdio/fdopen.c b/lib/libc/stdio/fdopen.c
index a0d7b71df782..49ec97eda39d 100644
--- a/lib/libc/stdio/fdopen.c
+++ b/lib/libc/stdio/fdopen.c
@@ -46,7 +46,7 @@ FILE *
fdopen(int fd, const char *mode)
{
FILE *fp;
- int flags, oflags, fdflags, tmp;
+ int flags, oflags, fdflags, rc, tmp;
/*
* File descriptors are a full int, but _file is only a short.
@@ -76,9 +76,19 @@ fdopen(int fd, const char *mode)
if ((fp = __sfp()) == NULL)
return (NULL);
- if ((oflags & O_CLOEXEC) && _fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
- fp->_flags = 0;
- return (NULL);
+ if ((oflags & O_CLOEXEC) != 0) {
+ tmp = _fcntl(fd, F_GETFD, 0);
+ if (tmp == -1) {
+ fp->_flags = 0;
+ return (NULL);
+ }
+ if ((tmp & FD_CLOEXEC) == 0) {
+ rc = _fcntl(fd, F_SETFD, tmp | FD_CLOEXEC);
+ if (rc == -1) {
+ fp->_flags = 0;
+ return (NULL);
+ }
+ }
}
fp->_flags = flags;
diff --git a/lib/libc/stdio/freopen.c b/lib/libc/stdio/freopen.c
index f0732b6d6741..048fd30b3193 100644
--- a/lib/libc/stdio/freopen.c
+++ b/lib/libc/stdio/freopen.c
@@ -55,7 +55,7 @@ freopen(const char * __restrict file, const char * __restrict mode,
FILE * __restrict fp)
{
int f;
- int dflags, flags, isopen, oflags, sverrno, wantfd;
+ int dflags, fdflags, flags, isopen, oflags, sverrno, wantfd;
if ((flags = __sflags(mode, &oflags)) == 0) {
sverrno = errno;
@@ -113,8 +113,12 @@ freopen(const char * __restrict file, const char * __restrict mode,
(void) ftruncate(fp->_file, (off_t)0);
if (!(oflags & O_APPEND))
(void) _sseek(fp, (fpos_t)0, SEEK_SET);
- if (oflags & O_CLOEXEC)
- (void) _fcntl(fp->_file, F_SETFD, FD_CLOEXEC);
+ if ((oflags & O_CLOEXEC) != 0) {
+ fdflags = _fcntl(fp->_file, F_GETFD, 0);
+ if (fdflags != -1 && (fdflags & FD_CLOEXEC) == 0)
+ (void) _fcntl(fp->_file, F_SETFD,
+ fdflags | FD_CLOEXEC);
+ }
f = fp->_file;
isopen = 0;
wantfd = -1;
diff --git a/lib/libc/stdio/mktemp.3 b/lib/libc/stdio/mktemp.3
index 2b8b2d6c9e44..8d38dd2cd57e 100644
--- a/lib/libc/stdio/mktemp.3
+++ b/lib/libc/stdio/mktemp.3
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd July 29, 2019
+.Dd July 07, 2025
.Dt MKTEMP 3
.Os
.Sh NAME
@@ -101,9 +101,10 @@ The permitted flags are
.Dv O_DIRECT ,
.Dv O_SHLOCK ,
.Dv O_EXLOCK ,
-.Dv O_SYNC
+.Dv O_SYNC ,
+.Dv O_CLOEXEC
and
-.Dv O_CLOEXEC .
+.Dv O_CLOFORK .
.Pp
The
.Fn mkstemps
diff --git a/lib/libc/stdio/mktemp.c b/lib/libc/stdio/mktemp.c
index 8aff226acf14..a2f80ee7b0dd 100644
--- a/lib/libc/stdio/mktemp.c
+++ b/lib/libc/stdio/mktemp.c
@@ -121,7 +121,7 @@ _gettemp(int dfd, char *path, int *doopen, int domkdir, int slen, int oflags)
if ((doopen != NULL && domkdir) || slen < 0 ||
(oflags & ~(O_APPEND | O_DIRECT | O_SHLOCK | O_EXLOCK | O_SYNC |
- O_CLOEXEC)) != 0) {
+ O_CLOEXEC | O_CLOFORK)) != 0) {
errno = EINVAL;
return (0);
}
diff --git a/lib/libc/stdio/vfscanf.c b/lib/libc/stdio/vfscanf.c
index a678710e1ecb..89e9e843969f 100644
--- a/lib/libc/stdio/vfscanf.c
+++ b/lib/libc/stdio/vfscanf.c
@@ -318,10 +318,9 @@ parseint_fsm(int c, enum parseint_state *state, int *base)
case '0':
if (*state == begin || *state == havesign) {
*state = havezero;
- } else {
- *state = any;
+ return 1;
}
- return 1;
+ /* FALL THROUGH */
case '1':
case '2':
case '3':
diff --git a/lib/libc/stdio/vfwscanf.c b/lib/libc/stdio/vfwscanf.c
index 57206a8407d5..7ca64eb37811 100644
--- a/lib/libc/stdio/vfwscanf.c
+++ b/lib/libc/stdio/vfwscanf.c
@@ -298,10 +298,9 @@ parseint_fsm(wchar_t c, enum parseint_state *state, int *base)
case '0':
if (*state == begin || *state == havesign) {
*state = havezero;
- } else {
- *state = any;
+ return 1;
}
- return 1;
+ /* FALL THROUGH */
case '1':
case '2':
case '3':
diff --git a/lib/libc/stdlib/Makefile.inc b/lib/libc/stdlib/Makefile.inc
index bea82445304d..e7b9955b9646 100644
--- a/lib/libc/stdlib/Makefile.inc
+++ b/lib/libc/stdlib/Makefile.inc
@@ -10,7 +10,7 @@ MISRCS+=C99_Exit.c a64l.c abort.c abs.c atexit.c atof.c atoi.c atol.c atoll.c \
insque.c l64a.c labs.c ldiv.c llabs.c lldiv.c lsearch.c \
merge.c mergesort_b.c ptsname.c qsort.c qsort_r.c qsort_r_compat.c \
qsort_s.c quick_exit.c radixsort.c rand.c \
- random.c reallocarray.c reallocf.c realpath.c remque.c \
+ random.c reallocarray.c reallocf.c realpath.c recallocarray.c remque.c \
set_constraint_handler_s.c strfmon.c strtoimax.c \
strtol.c strtold.c strtoll.c strtoq.c strtoul.c strtonum.c strtoull.c \
strtoumax.c strtouq.c system.c tdelete.c tfind.c tsearch.c twalk.c
@@ -31,7 +31,7 @@ SYM_MAPS+= ${LIBC_SRCTOP}/stdlib/Symbol.map
# machine-dependent stdlib sources
.sinclude "${LIBC_SRCTOP}/${LIBC_ARCH}/stdlib/Makefile.inc"
-MAN+= a64l.3 abort.3 abs.3 alloca.3 atexit.3 atof.3 \
+MAN+= a64l.3 abort.3 abs.3 atexit.3 atof.3 \
atoi.3 atol.3 at_quick_exit.3 bsearch.3 \
div.3 exit.3 getenv.3 getopt.3 getopt_long.3 getsubopt.3 \
hcreate.3 imaxabs.3 imaxdiv.3 insque.3 labs.3 ldiv.3 llabs.3 lldiv.3 \
@@ -76,6 +76,7 @@ MLINKS+=random.3 initstate.3 \
random.3 srandom.3 \
random.3 srandomdev.3
MLINKS+=radixsort.3 sradixsort.3
+MLINKS+=reallocarray.3 recallocarray.3
MLINKS+=set_constraint_handler_s.3 abort_handler_s.3
MLINKS+=set_constraint_handler_s.3 ignore_handler_s.3
MLINKS+=strfmon.3 strfmon_l.3
diff --git a/lib/libc/stdlib/Symbol.map b/lib/libc/stdlib/Symbol.map
index d74df869cf54..53d71bcafb7d 100644
--- a/lib/libc/stdlib/Symbol.map
+++ b/lib/libc/stdlib/Symbol.map
@@ -127,6 +127,14 @@ FBSD_1.7 {
secure_getenv;
};
+FBSD_1.8 {
+ getenv_r;
+};
+
+FBSD_1.9 {
+ recallocarray;
+};
+
FBSDprivate_1.0 {
__system;
_system;
diff --git a/lib/libc/stdlib/alloca.3 b/lib/libc/stdlib/alloca.3
deleted file mode 100644
index d2111872ae0b..000000000000
--- a/lib/libc/stdlib/alloca.3
+++ /dev/null
@@ -1,85 +0,0 @@
-.\" Copyright (c) 1980, 1991, 1993
-.\" The Regents of the University of California. 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.
-.\" 3. Neither the name of the University nor the names of its contributors
-.\" may be used to endorse or promote products derived from this software
-.\" without specific prior written permission.
-.\"
-.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
-.\"
-.Dd September 5, 2006
-.Dt ALLOCA 3
-.Os
-.Sh NAME
-.Nm alloca
-.Nd memory allocator
-.Sh LIBRARY
-.Lb libc
-.Sh SYNOPSIS
-.In stdlib.h
-.Ft void *
-.Fn alloca "size_t size"
-.Sh DESCRIPTION
-The
-.Fn alloca
-function
-allocates
-.Fa size
-bytes of space in the stack frame of the caller.
-This temporary space is automatically freed on
-return.
-.Sh RETURN VALUES
-The
-.Fn alloca
-function returns a pointer to the beginning of the allocated space.
-.Sh SEE ALSO
-.Xr brk 2 ,
-.Xr calloc 3 ,
-.Xr getpagesize 3 ,
-.Xr malloc 3 ,
-.Xr realloc 3
-.Sh HISTORY
-The
-.Fn alloca
-function appeared in
-.At 32v .
-.\" .Bx ?? .
-.\" The function appeared in 32v, pwb and pwb.2 and in 3bsd 4bsd
-.\" The first man page (or link to a man page that I can find at the
-.\" moment is 4.3...
-.Sh BUGS
-The
-.Fn alloca
-function
-is machine and compiler dependent;
-its use is discouraged.
-.Pp
-The
-.Fn alloca
-function is slightly unsafe because it cannot ensure that the pointer
-returned points to a valid and usable block of memory.
-The allocation made may exceed the bounds of the stack, or even go
-further into other objects in memory, and
-.Fn alloca
-cannot determine such an error.
-Avoid
-.Fn alloca
-with large unbounded allocations.
diff --git a/lib/libc/stdlib/atexit.c b/lib/libc/stdlib/atexit.c
index e5aa66c51f38..6e4a12f9e530 100644
--- a/lib/libc/stdlib/atexit.c
+++ b/lib/libc/stdlib/atexit.c
@@ -35,6 +35,7 @@
#include "namespace.h"
#include <errno.h>
#include <link.h>
+#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
@@ -56,6 +57,8 @@ _Block_copy(void*);
#define ATEXIT_FN_CXA 2
static pthread_mutex_t atexit_mutex = PTHREAD_MUTEX_INITIALIZER;
+static void *current_finalize_dso = NULL;
+static bool call_finalize_again = false;
#define _MUTEX_LOCK(x) if (__isthreaded) _pthread_mutex_lock(x)
#define _MUTEX_UNLOCK(x) if (__isthreaded) _pthread_mutex_unlock(x)
@@ -115,6 +118,9 @@ atexit_register(struct atexit_fn *fptr)
__atexit = p;
}
p->fns[p->ind++] = *fptr;
+ if (current_finalize_dso != NULL &&
+ current_finalize_dso == fptr->fn_dso)
+ call_finalize_again = true;
_MUTEX_UNLOCK(&atexit_mutex);
return 0;
}
@@ -208,33 +214,38 @@ __cxa_finalize(void *dso)
}
_MUTEX_LOCK(&atexit_mutex);
- for (p = __atexit; p; p = p->next) {
- for (n = p->ind; --n >= 0;) {
- if (p->fns[n].fn_type == ATEXIT_FN_EMPTY)
- continue; /* already been called */
- fn = p->fns[n];
- if (dso != NULL && dso != fn.fn_dso) {
- /* wrong DSO ? */
- if (!has_phdr || global_exit ||
- !__elf_phdr_match_addr(&phdr_info,
- fn.fn_ptr.cxa_func))
- continue;
+ current_finalize_dso = dso;
+ do {
+ call_finalize_again = false;
+ for (p = __atexit; p; p = p->next) {
+ for (n = p->ind; --n >= 0;) {
+ if (p->fns[n].fn_type == ATEXIT_FN_EMPTY)
+ continue; /* already been called */
+ fn = p->fns[n];
+ if (dso != NULL && dso != fn.fn_dso) {
+ /* wrong DSO ? */
+ if (!has_phdr || global_exit ||
+ !__elf_phdr_match_addr(&phdr_info,
+ fn.fn_ptr.cxa_func))
+ continue;
+ }
+ /*
+ Mark entry to indicate that this particular
+ handler has already been called.
+ */
+ p->fns[n].fn_type = ATEXIT_FN_EMPTY;
+ _MUTEX_UNLOCK(&atexit_mutex);
+
+ /* Call the function of correct type. */
+ if (fn.fn_type == ATEXIT_FN_CXA)
+ fn.fn_ptr.cxa_func(fn.fn_arg);
+ else if (fn.fn_type == ATEXIT_FN_STD)
+ fn.fn_ptr.std_func();
+ _MUTEX_LOCK(&atexit_mutex);
}
- /*
- Mark entry to indicate that this particular handler
- has already been called.
- */
- p->fns[n].fn_type = ATEXIT_FN_EMPTY;
- _MUTEX_UNLOCK(&atexit_mutex);
-
- /* Call the function of correct type. */
- if (fn.fn_type == ATEXIT_FN_CXA)
- fn.fn_ptr.cxa_func(fn.fn_arg);
- else if (fn.fn_type == ATEXIT_FN_STD)
- fn.fn_ptr.std_func();
- _MUTEX_LOCK(&atexit_mutex);
}
- }
+ } while (call_finalize_again);
+ current_finalize_dso = NULL;
_MUTEX_UNLOCK(&atexit_mutex);
if (dso == NULL)
_MUTEX_DESTROY(&atexit_mutex);
diff --git a/lib/libc/stdlib/getenv.3 b/lib/libc/stdlib/getenv.3
index 1337ec867237..045a1831dfdc 100644
--- a/lib/libc/stdlib/getenv.3
+++ b/lib/libc/stdlib/getenv.3
@@ -29,12 +29,13 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd March 14, 2023
+.Dd April 22, 2025
.Dt GETENV 3
.Os
.Sh NAME
.Nm clearenv ,
.Nm getenv ,
+.Nm getenv_r ,
.Nm putenv ,
.Nm secure_getenv ,
.Nm setenv ,
@@ -48,6 +49,8 @@
.Fn clearenv "void"
.Ft char *
.Fn getenv "const char *name"
+.Ft int
+.Fn getenv_r "const char *name" "char *buf" "size_t len"
.Ft char *
.Fn secure_getenv "const char *name"
.Ft int
@@ -71,7 +74,8 @@ and
.Pp
The
.Fn getenv
-function obtains the current value of the environment variable,
+function obtains the current value of the environment variable
+designated by
.Fa name .
The application should not modify the string pointed
to by the
@@ -79,13 +83,23 @@ to by the
function.
.Pp
The
+.Fn getenv_r
+function obtains the current value of the environment variable
+designated by
+.Fa name
+and copies it into the buffer
+.Fa buf
+of length
+.Fa len .
+.Pp
+The
.Fn secure_getenv
returns
.Va NULL
when the environment cannot be trusted, otherwise it acts like
.Fn getenv .
The environment currently is not trusted when
-.Xr issetugid 3
+.Xr issetugid 2
returns a non-zero value, but other conditions may be added
in the future.
.Pp
@@ -157,12 +171,13 @@ function returns
if the process is in "secure execution," otherwise it will call
.Fn getenv .
.Pp
-.Rv -std clearenv setenv putenv unsetenv
+.Rv -std clearenv getenv_r setenv putenv unsetenv
.Sh ERRORS
.Bl -tag -width Er
.It Bq Er EINVAL
The function
.Fn getenv ,
+.Fn getenv_r ,
.Fn setenv
or
.Fn unsetenv
@@ -191,6 +206,11 @@ is the first character in
This does not follow the
.Tn POSIX
specification.
+.It Bq Er ENOENT
+The function
+.Fn getenv_r
+failed because the requested variable was not found in the
+environment.
.It Bq Er ENOMEM
The function
.Fn setenv ,
@@ -198,6 +218,11 @@ The function
or
.Fn putenv
failed because they were unable to allocate memory for the environment.
+.It Bq Er ERANGE
+The function
+.Fn getenv_r
+failed because the value of the requested variable was too long to fit
+in the provided buffer.
.El
.Sh SEE ALSO
.Xr csh 1 ,
@@ -251,6 +276,13 @@ and
.Fn secure_getenv
functions were added in
.Fx 14 .
+.Pp
+The
+.Fn getenv_r
+function first appeared in
+.Nx 4.0
+and was added in
+.Fx 15 .
.Sh BUGS
Successive calls to
.Fn setenv
diff --git a/lib/libc/stdlib/getenv.c b/lib/libc/stdlib/getenv.c
index f8f59526421a..c1d0b7a559d5 100644
--- a/lib/libc/stdlib/getenv.c
+++ b/lib/libc/stdlib/getenv.c
@@ -28,6 +28,7 @@
#include "namespace.h"
#include <sys/types.h>
+#include <ssp/ssp.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
@@ -444,6 +445,41 @@ getenv(const char *name)
/*
+ * Like getenv(), but copies the value into the provided buffer.
+ */
+int
+__ssp_real(getenv_r)(const char *name, char *buf, size_t len)
+{
+ const char *val;
+ size_t nameLen;
+ int envNdx;
+
+ if (name == NULL || (nameLen = __strleneq(name)) == 0) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if (environ == NULL || environ[0] == NULL) {
+ val = NULL;
+ } else if (envVars == NULL || environ != intEnviron) {
+ val = __findenv_environ(name, nameLen);
+ } else {
+ envNdx = envVarsTotal - 1;
+ val = __findenv(name, nameLen, &envNdx, true);
+ }
+ if (val == NULL) {
+ errno = ENOENT;
+ return (-1);
+ }
+ if (strlcpy(buf, val, len) >= len) {
+ errno = ERANGE;
+ return (-1);
+ }
+ return (0);
+}
+
+
+/*
* Runs getenv() unless the current process is tainted by uid or gid changes, in
* which case it will return NULL.
*/
diff --git a/lib/libc/stdlib/getopt_long.3 b/lib/libc/stdlib/getopt_long.3
index f8bb1f1b6b11..d6998d13822a 100644
--- a/lib/libc/stdlib/getopt_long.3
+++ b/lib/libc/stdlib/getopt_long.3
@@ -217,7 +217,7 @@ an error and return
Specifying
.Ql \&:
in
-.Fa optstr
+.Fa optstring
will cause the error message to be suppressed and
.Ql \&:
to be returned instead.
@@ -229,7 +229,7 @@ a leading
or
.Ql \&-
in
-.Fa optstr
+.Fa optstring
also has special meaning.
If either of these are specified, they must appear before
.Ql \&: .
diff --git a/lib/libc/stdlib/malloc/jemalloc/Makefile.inc b/lib/libc/stdlib/malloc/jemalloc/Makefile.inc
index c10d79dbce6c..7722a7b755f3 100644
--- a/lib/libc/stdlib/malloc/jemalloc/Makefile.inc
+++ b/lib/libc/stdlib/malloc/jemalloc/Makefile.inc
@@ -1,15 +1,21 @@
.PATH: ${LIBC_SRCTOP}/stdlib/malloc/jemalloc
JEMALLOCSRCS:= jemalloc.c arena.c background_thread.c base.c bin.c bitmap.c \
- ckh.c ctl.c div.c extent.c extent_dss.c extent_mmap.c hash.c hook.c \
- large.c log.c malloc_io.c mutex.c mutex_pool.c nstime.c pages.c \
- prng.c prof.c rtree.c safety_check.c sc.c stats.c sz.c tcache.c \
- test_hooks.c ticker.c tsd.c witness.c
+ ckh.c ctl.c div.c extent.c extent_dss.c extent_mmap.c hook.c \
+ large.c log.c malloc_io.c mutex.c nstime.c pages.c \
+ prof.c rtree.c safety_check.c sc.c stats.c sz.c tcache.c \
+ test_hooks.c ticker.c tsd.c witness.c \
+ bin_info.c san.c san_bump.c counter.c prof_data.c prof_log.c prof_recent.c prof_stats.c prof_sys.c \
+ emap.c edata.c edata_cache.c pa.c pa_extra.c pac.c decay.c hpa.c hpa_hooks.c fxp.c hpdata.c pai.c \
+ ecache.c ehooks.c eset.c sec.c cache_bin.c peak_event.c psset.c inspect.c exp_grow.c thread_event.c \
+ buf_writer.c
CFLAGS+=-I${SRCTOP}/contrib/jemalloc/include -I${LIBC_SRCTOP}/stdlib/malloc/jemalloc/include
.if ${MK_JEMALLOC_LG_VADDR_WIDE} != no
CFLAGS+=-D_USE_LG_VADDR_WIDE
.endif
+CFLAGS.gcc+=-Wno-error=missing-braces
+
.for src in ${JEMALLOCSRCS}
MISRCS+=jemalloc_${src}
diff --git a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h
index 1aedb916976b..900ae867f321 100644
--- a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h
+++ b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h
@@ -45,17 +45,17 @@
#define LG_VADDR 48
/* Defined if C11 atomics are available. */
-#define JEMALLOC_C11_ATOMICS 1
+#define JEMALLOC_C11_ATOMICS
/* Defined if GCC __atomic atomics are available. */
-#define JEMALLOC_GCC_ATOMIC_ATOMICS 1
+#define JEMALLOC_GCC_ATOMIC_ATOMICS
/* and the 8-bit variant support. */
-#define JEMALLOC_GCC_U8_ATOMIC_ATOMICS 1
+#define JEMALLOC_GCC_U8_ATOMIC_ATOMICS
/* Defined if GCC __sync atomics are available. */
-#define JEMALLOC_GCC_SYNC_ATOMICS 1
+#define JEMALLOC_GCC_SYNC_ATOMICS
/* and the 8-bit variant support. */
-#define JEMALLOC_GCC_U8_SYNC_ATOMICS 1
+#define JEMALLOC_GCC_U8_SYNC_ATOMICS
/*
* Defined if __builtin_clz() and __builtin_clzl() are available.
@@ -73,7 +73,7 @@
/*
* Defined if secure_getenv(3) is available.
*/
-/* #undef JEMALLOC_HAVE_SECURE_GETENV */
+#define JEMALLOC_HAVE_SECURE_GETENV
/*
* Defined if issetugid(2) is available.
@@ -84,17 +84,23 @@
#define JEMALLOC_HAVE_PTHREAD_ATFORK
/* Defined if pthread_setname_np(3) is available. */
-/* #undef JEMALLOC_HAVE_PTHREAD_SETNAME_NP */
+#define JEMALLOC_HAVE_PTHREAD_SETNAME_NP
+
+/* Defined if pthread_getname_np(3) is available. */
+#define JEMALLOC_HAVE_PTHREAD_GETNAME_NP
+
+/* Defined if pthread_get_name_np(3) is available. */
+#define JEMALLOC_HAVE_PTHREAD_GET_NAME_NP
/*
* Defined if clock_gettime(CLOCK_MONOTONIC_COARSE, ...) is available.
*/
-/* #undef JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE */
+#define JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE
/*
* Defined if clock_gettime(CLOCK_MONOTONIC, ...) is available.
*/
-#define JEMALLOC_HAVE_CLOCK_MONOTONIC 1
+#define JEMALLOC_HAVE_CLOCK_MONOTONIC
/*
* Defined if mach_absolute_time() is available.
@@ -102,6 +108,11 @@
/* #undef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME */
/*
+ * Defined if clock_gettime(CLOCK_REALTIME, ...) is available.
+ */
+#define JEMALLOC_HAVE_CLOCK_REALTIME
+
+/*
* Defined if _malloc_thread_cleanup() exists. At least in the case of
* FreeBSD, pthread_key_create() allocates, which if used during malloc
* bootstrapping will cause recursion into the pthreads library. Therefore, if
@@ -122,7 +133,7 @@
* _pthread_mutex_init_calloc_cb(), in which case the function is used in order
* to avoid recursive allocation during mutex initialization.
*/
-#define JEMALLOC_MUTEX_INIT_CB 1
+#define JEMALLOC_MUTEX_INIT_CB
/* Non-empty if the tls_model attribute is supported. */
#define JEMALLOC_TLS_MODEL __attribute__((tls_model("initial-exec")))
@@ -163,6 +174,9 @@
/* Support utrace(2)-based tracing. */
#define JEMALLOC_UTRACE
+/* Support utrace(2)-based tracing (label based signature). */
+/* #undef JEMALLOC_UTRACE_LABEL */
+
/* Support optional abort() on OOM. */
#define JEMALLOC_XMALLOC
@@ -178,6 +192,9 @@
/* One page is 2^LG_PAGE bytes. */
#define LG_PAGE 12
+/* Maximum number of regions in a slab. */
+/* #undef CONFIG_LG_SLAB_MAXREGS */
+
/*
* One huge page is 2^LG_HUGEPAGE bytes. Note that this is defined even if the
* system does not explicitly support huge pages; system calls that require
@@ -292,16 +309,45 @@
/* #undef JEMALLOC_MADVISE_DONTDUMP */
/*
+ * Defined if MADV_[NO]CORE is supported as an argument to madvise.
+ */
+#define JEMALLOC_MADVISE_NOCORE
+
+/* Defined if mprotect(2) is available. */
+#define JEMALLOC_HAVE_MPROTECT
+
+/*
* Defined if transparent huge pages (THPs) are supported via the
* MADV_[NO]HUGEPAGE arguments to madvise(2), and THP support is enabled.
*/
/* #undef JEMALLOC_THP */
+/* Defined if posix_madvise is available. */
+/* #undef JEMALLOC_HAVE_POSIX_MADVISE */
+
+/*
+ * Method for purging unused pages using posix_madvise.
+ *
+ * posix_madvise(..., POSIX_MADV_DONTNEED)
+ */
+/* #undef JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED */
+/* #undef JEMALLOC_PURGE_POSIX_MADVISE_DONTNEED_ZEROS */
+
+/*
+ * Defined if memcntl page admin call is supported
+ */
+/* #undef JEMALLOC_HAVE_MEMCNTL */
+
+/*
+ * Defined if malloc_size is supported
+ */
+/* #undef JEMALLOC_HAVE_MALLOC_SIZE */
+
/* Define if operating system has alloca.h header. */
/* #undef JEMALLOC_HAS_ALLOCA_H */
/* C99 restrict keyword supported. */
-#define JEMALLOC_HAS_RESTRICT 1
+#define JEMALLOC_HAS_RESTRICT
/* For use by hash code. */
/* #undef JEMALLOC_BIG_ENDIAN */
@@ -334,15 +380,15 @@
#define JEMALLOC_HAVE_PTHREAD_MUTEX_ADAPTIVE_NP
/* GNU specific sched_getcpu support */
-/* #undef JEMALLOC_HAVE_SCHED_GETCPU */
+#define JEMALLOC_HAVE_SCHED_GETCPU
/* GNU specific sched_setaffinity support */
-/* #undef JEMALLOC_HAVE_SCHED_SETAFFINITY */
+#define JEMALLOC_HAVE_SCHED_SETAFFINITY
/*
* If defined, all the features necessary for background threads are present.
*/
-#define JEMALLOC_BACKGROUND_THREAD 1
+#define JEMALLOC_BACKGROUND_THREAD
/*
* If defined, jemalloc symbols are not exported (doesn't work when
@@ -354,7 +400,7 @@
#define JEMALLOC_CONFIG_MALLOC_CONF "abort_conf:false"
/* If defined, jemalloc takes the malloc/free/etc. symbol names. */
-#define JEMALLOC_IS_MALLOC 1
+#define JEMALLOC_IS_MALLOC
/*
* Defined if strerror_r returns char * if _GNU_SOURCE is defined.
@@ -364,4 +410,19 @@
/* Performs additional safety checks when defined. */
/* #undef JEMALLOC_OPT_SAFETY_CHECKS */
+/* Is C++ support being built? */
+#define JEMALLOC_ENABLE_CXX
+
+/* Performs additional size checks when defined. */
+/* #undef JEMALLOC_OPT_SIZE_CHECKS */
+
+/* Allows sampled junk and stash for checking use-after-free when defined. */
+/* #undef JEMALLOC_UAF_DETECTION */
+
+/* Darwin VM_MAKE_TAG support */
+/* #undef JEMALLOC_HAVE_VM_MAKE_TAG */
+
+/* If defined, realloc(ptr, 0) defaults to "free" instead of "alloc". */
+/* #undef JEMALLOC_ZERO_REALLOC_DEFAULT_FREE */
+
#endif /* JEMALLOC_INTERNAL_DEFS_H_ */
diff --git a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_preamble.h b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_preamble.h
index d1dcc50d5a3b..b6cf5864109d 100644
--- a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_preamble.h
+++ b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/jemalloc_preamble.h
@@ -4,12 +4,20 @@
#include "jemalloc_internal_defs.h"
#include "jemalloc/internal/jemalloc_internal_decls.h"
-#ifdef JEMALLOC_UTRACE
+#if defined(JEMALLOC_UTRACE) || defined(JEMALLOC_UTRACE_LABEL)
#include <sys/ktrace.h>
+# if defined(JEMALLOC_UTRACE)
+# define UTRACE_CALL(p, l) utrace(p, l)
+# else
+# define UTRACE_CALL(p, l) utrace("jemalloc_process", p, l)
+# define JEMALLOC_UTRACE
+# endif
#endif
+#ifndef JEMALLOC_PRIVATE_NAMESPACE
#include "un-namespace.h"
#include "libc_private.h"
+#endif
#define JEMALLOC_NO_DEMANGLE
#ifdef JEMALLOC_JET
@@ -177,6 +185,35 @@ static const bool config_opt_safety_checks =
#endif
;
+/*
+ * Extra debugging of sized deallocations too onerous to be included in the
+ * general safety checks.
+ */
+static const bool config_opt_size_checks =
+#if defined(JEMALLOC_OPT_SIZE_CHECKS) || defined(JEMALLOC_DEBUG)
+ true
+#else
+ false
+#endif
+ ;
+
+static const bool config_uaf_detection =
+#if defined(JEMALLOC_UAF_DETECTION) || defined(JEMALLOC_DEBUG)
+ true
+#else
+ false
+#endif
+ ;
+
+/* Whether or not the C++ extensions are enabled. */
+static const bool config_enable_cxx =
+#ifdef JEMALLOC_ENABLE_CXX
+ true
+#else
+ false
+#endif
+;
+
#if defined(_WIN32) || defined(JEMALLOC_HAVE_SCHED_GETCPU)
/* Currently percpu_arena depends on sched_getcpu. */
#define JEMALLOC_PERCPU_ARENA
@@ -206,5 +243,20 @@ static const bool have_background_thread =
false
#endif
;
+static const bool config_high_res_timer =
+#ifdef JEMALLOC_HAVE_CLOCK_REALTIME
+ true
+#else
+ false
+#endif
+ ;
+
+static const bool have_memcntl =
+#ifdef JEMALLOC_HAVE_MEMCNTL
+ true
+#else
+ false
+#endif
+ ;
#endif /* JEMALLOC_PREAMBLE_H */
diff --git a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/private_namespace.h b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/private_namespace.h
index a448ad63fa54..08ce9219cbf8 100644
--- a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/private_namespace.h
+++ b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/private_namespace.h
@@ -5,10 +5,9 @@
#define arena_init JEMALLOC_N(arena_init)
#define arena_migrate JEMALLOC_N(arena_migrate)
#define arena_set JEMALLOC_N(arena_set)
-#define arena_tdata_get_hard JEMALLOC_N(arena_tdata_get_hard)
#define arenas JEMALLOC_N(arenas)
#define arenas_lock JEMALLOC_N(arenas_lock)
-#define arenas_tdata_cleanup JEMALLOC_N(arenas_tdata_cleanup)
+#define batch_alloc JEMALLOC_N(batch_alloc)
#define bootstrap_calloc JEMALLOC_N(bootstrap_calloc)
#define bootstrap_free JEMALLOC_N(bootstrap_free)
#define bootstrap_malloc JEMALLOC_N(bootstrap_malloc)
@@ -16,8 +15,10 @@
#define iarena_cleanup JEMALLOC_N(iarena_cleanup)
#define je_sdallocx_noflags JEMALLOC_N(je_sdallocx_noflags)
#define jemalloc_postfork_child JEMALLOC_N(jemalloc_postfork_child)
+#define junk_alloc_callback JEMALLOC_N(junk_alloc_callback)
+#define junk_free_callback JEMALLOC_N(junk_free_callback)
#define malloc_default JEMALLOC_N(malloc_default)
-#define malloc_initialized JEMALLOC_N(malloc_initialized)
+#define malloc_init_state JEMALLOC_N(malloc_init_state)
#define malloc_slow JEMALLOC_N(malloc_slow)
#define manual_arena_base JEMALLOC_N(manual_arena_base)
#define narenas_auto JEMALLOC_N(narenas_auto)
@@ -25,49 +26,64 @@
#define ncpus JEMALLOC_N(ncpus)
#define opt_abort JEMALLOC_N(opt_abort)
#define opt_abort_conf JEMALLOC_N(opt_abort_conf)
+#define opt_cache_oblivious JEMALLOC_N(opt_cache_oblivious)
#define opt_confirm_conf JEMALLOC_N(opt_confirm_conf)
+#define opt_experimental_infallible_new JEMALLOC_N(opt_experimental_infallible_new)
+#define opt_hpa JEMALLOC_N(opt_hpa)
+#define opt_hpa_opts JEMALLOC_N(opt_hpa_opts)
+#define opt_hpa_sec_opts JEMALLOC_N(opt_hpa_sec_opts)
#define opt_junk JEMALLOC_N(opt_junk)
#define opt_junk_alloc JEMALLOC_N(opt_junk_alloc)
#define opt_junk_free JEMALLOC_N(opt_junk_free)
#define opt_narenas JEMALLOC_N(opt_narenas)
+#define opt_narenas_ratio JEMALLOC_N(opt_narenas_ratio)
+#define opt_trust_madvise JEMALLOC_N(opt_trust_madvise)
#define opt_utrace JEMALLOC_N(opt_utrace)
#define opt_xmalloc JEMALLOC_N(opt_xmalloc)
#define opt_zero JEMALLOC_N(opt_zero)
+#define opt_zero_realloc_action JEMALLOC_N(opt_zero_realloc_action)
#define sdallocx_default JEMALLOC_N(sdallocx_default)
-#define arena_alloc_junk_small JEMALLOC_N(arena_alloc_junk_small)
+#define zero_realloc_count JEMALLOC_N(zero_realloc_count)
+#define zero_realloc_mode_names JEMALLOC_N(zero_realloc_mode_names)
#define arena_basic_stats_merge JEMALLOC_N(arena_basic_stats_merge)
-#define arena_bin_choose_lock JEMALLOC_N(arena_bin_choose_lock)
+#define arena_bin_choose JEMALLOC_N(arena_bin_choose)
+#define arena_bin_offsets JEMALLOC_N(arena_bin_offsets)
+#define arena_binind_div_info JEMALLOC_N(arena_binind_div_info)
#define arena_boot JEMALLOC_N(arena_boot)
+#define arena_cache_bin_fill_small JEMALLOC_N(arena_cache_bin_fill_small)
#define arena_choose_huge JEMALLOC_N(arena_choose_huge)
-#define arena_dalloc_bin_junked_locked JEMALLOC_N(arena_dalloc_bin_junked_locked)
-#define arena_dalloc_junk_small JEMALLOC_N(arena_dalloc_junk_small)
+#define arena_config_default JEMALLOC_N(arena_config_default)
+#define arena_dalloc_bin_locked_handle_newly_empty JEMALLOC_N(arena_dalloc_bin_locked_handle_newly_empty)
+#define arena_dalloc_bin_locked_handle_newly_nonempty JEMALLOC_N(arena_dalloc_bin_locked_handle_newly_nonempty)
#define arena_dalloc_promoted JEMALLOC_N(arena_dalloc_promoted)
#define arena_dalloc_small JEMALLOC_N(arena_dalloc_small)
#define arena_decay JEMALLOC_N(arena_decay)
+#define arena_decay_ms_get JEMALLOC_N(arena_decay_ms_get)
+#define arena_decay_ms_set JEMALLOC_N(arena_decay_ms_set)
#define arena_destroy JEMALLOC_N(arena_destroy)
#define arena_dirty_decay_ms_default_get JEMALLOC_N(arena_dirty_decay_ms_default_get)
#define arena_dirty_decay_ms_default_set JEMALLOC_N(arena_dirty_decay_ms_default_set)
-#define arena_dirty_decay_ms_get JEMALLOC_N(arena_dirty_decay_ms_get)
-#define arena_dirty_decay_ms_set JEMALLOC_N(arena_dirty_decay_ms_set)
+#define arena_do_deferred_work JEMALLOC_N(arena_do_deferred_work)
#define arena_dss_prec_get JEMALLOC_N(arena_dss_prec_get)
#define arena_dss_prec_set JEMALLOC_N(arena_dss_prec_set)
+#define arena_emap_global JEMALLOC_N(arena_emap_global)
#define arena_extent_alloc_large JEMALLOC_N(arena_extent_alloc_large)
#define arena_extent_dalloc_large_prep JEMALLOC_N(arena_extent_dalloc_large_prep)
#define arena_extent_ralloc_large_expand JEMALLOC_N(arena_extent_ralloc_large_expand)
#define arena_extent_ralloc_large_shrink JEMALLOC_N(arena_extent_ralloc_large_shrink)
-#define arena_extent_sn_next JEMALLOC_N(arena_extent_sn_next)
-#define arena_extents_dirty_dalloc JEMALLOC_N(arena_extents_dirty_dalloc)
+#define arena_fill_small_fresh JEMALLOC_N(arena_fill_small_fresh)
+#define arena_get_ehooks JEMALLOC_N(arena_get_ehooks)
+#define arena_handle_deferred_work JEMALLOC_N(arena_handle_deferred_work)
#define arena_init_huge JEMALLOC_N(arena_init_huge)
#define arena_is_huge JEMALLOC_N(arena_is_huge)
#define arena_malloc_hard JEMALLOC_N(arena_malloc_hard)
#define arena_muzzy_decay_ms_default_get JEMALLOC_N(arena_muzzy_decay_ms_default_get)
#define arena_muzzy_decay_ms_default_set JEMALLOC_N(arena_muzzy_decay_ms_default_set)
-#define arena_muzzy_decay_ms_get JEMALLOC_N(arena_muzzy_decay_ms_get)
-#define arena_muzzy_decay_ms_set JEMALLOC_N(arena_muzzy_decay_ms_set)
#define arena_new JEMALLOC_N(arena_new)
#define arena_nthreads_dec JEMALLOC_N(arena_nthreads_dec)
#define arena_nthreads_get JEMALLOC_N(arena_nthreads_get)
#define arena_nthreads_inc JEMALLOC_N(arena_nthreads_inc)
+#define arena_pa_central_global JEMALLOC_N(arena_pa_central_global)
#define arena_palloc JEMALLOC_N(arena_palloc)
#define arena_postfork_child JEMALLOC_N(arena_postfork_child)
#define arena_postfork_parent JEMALLOC_N(arena_postfork_parent)
@@ -79,14 +95,15 @@
#define arena_prefork5 JEMALLOC_N(arena_prefork5)
#define arena_prefork6 JEMALLOC_N(arena_prefork6)
#define arena_prefork7 JEMALLOC_N(arena_prefork7)
+#define arena_prefork8 JEMALLOC_N(arena_prefork8)
#define arena_prof_promote JEMALLOC_N(arena_prof_promote)
#define arena_ralloc JEMALLOC_N(arena_ralloc)
#define arena_ralloc_no_move JEMALLOC_N(arena_ralloc_no_move)
#define arena_reset JEMALLOC_N(arena_reset)
#define arena_retain_grow_limit_get_set JEMALLOC_N(arena_retain_grow_limit_get_set)
+#define arena_set_extent_hooks JEMALLOC_N(arena_set_extent_hooks)
+#define arena_slab_dalloc JEMALLOC_N(arena_slab_dalloc)
#define arena_stats_merge JEMALLOC_N(arena_stats_merge)
-#define arena_tcache_fill_small JEMALLOC_N(arena_tcache_fill_small)
-#define h_steps JEMALLOC_N(h_steps)
#define opt_dirty_decay_ms JEMALLOC_N(opt_dirty_decay_ms)
#define opt_muzzy_decay_ms JEMALLOC_N(opt_muzzy_decay_ms)
#define opt_oversize_threshold JEMALLOC_N(opt_oversize_threshold)
@@ -99,13 +116,14 @@
#define background_thread_ctl_init JEMALLOC_N(background_thread_ctl_init)
#define background_thread_enabled_state JEMALLOC_N(background_thread_enabled_state)
#define background_thread_info JEMALLOC_N(background_thread_info)
-#define background_thread_interval_check JEMALLOC_N(background_thread_interval_check)
+#define background_thread_is_started JEMALLOC_N(background_thread_is_started)
#define background_thread_lock JEMALLOC_N(background_thread_lock)
#define background_thread_postfork_child JEMALLOC_N(background_thread_postfork_child)
#define background_thread_postfork_parent JEMALLOC_N(background_thread_postfork_parent)
#define background_thread_prefork0 JEMALLOC_N(background_thread_prefork0)
#define background_thread_prefork1 JEMALLOC_N(background_thread_prefork1)
#define background_thread_stats_read JEMALLOC_N(background_thread_stats_read)
+#define background_thread_wakeup_early JEMALLOC_N(background_thread_wakeup_early)
#define background_threads_disable JEMALLOC_N(background_threads_disable)
#define background_threads_enable JEMALLOC_N(background_threads_enable)
#define max_background_threads JEMALLOC_N(max_background_threads)
@@ -115,10 +133,11 @@
#define pthread_create_wrapper JEMALLOC_N(pthread_create_wrapper)
#define b0get JEMALLOC_N(b0get)
#define base_alloc JEMALLOC_N(base_alloc)
-#define base_alloc_extent JEMALLOC_N(base_alloc_extent)
+#define base_alloc_edata JEMALLOC_N(base_alloc_edata)
#define base_boot JEMALLOC_N(base_boot)
#define base_delete JEMALLOC_N(base_delete)
-#define base_extent_hooks_get JEMALLOC_N(base_extent_hooks_get)
+#define base_ehooks_get JEMALLOC_N(base_ehooks_get)
+#define base_ehooks_get_for_metadata JEMALLOC_N(base_ehooks_get_for_metadata)
#define base_extent_hooks_set JEMALLOC_N(base_extent_hooks_set)
#define base_new JEMALLOC_N(base_new)
#define base_postfork_child JEMALLOC_N(base_postfork_child)
@@ -127,17 +146,28 @@
#define base_stats_get JEMALLOC_N(base_stats_get)
#define metadata_thp_mode_names JEMALLOC_N(metadata_thp_mode_names)
#define opt_metadata_thp JEMALLOC_N(opt_metadata_thp)
-#define bin_boot JEMALLOC_N(bin_boot)
-#define bin_infos JEMALLOC_N(bin_infos)
#define bin_init JEMALLOC_N(bin_init)
#define bin_postfork_child JEMALLOC_N(bin_postfork_child)
#define bin_postfork_parent JEMALLOC_N(bin_postfork_parent)
#define bin_prefork JEMALLOC_N(bin_prefork)
#define bin_shard_sizes_boot JEMALLOC_N(bin_shard_sizes_boot)
#define bin_update_shard_size JEMALLOC_N(bin_update_shard_size)
+#define bin_info_boot JEMALLOC_N(bin_info_boot)
+#define bin_infos JEMALLOC_N(bin_infos)
#define bitmap_info_init JEMALLOC_N(bitmap_info_init)
#define bitmap_init JEMALLOC_N(bitmap_init)
#define bitmap_size JEMALLOC_N(bitmap_size)
+#define buf_writer_cb JEMALLOC_N(buf_writer_cb)
+#define buf_writer_flush JEMALLOC_N(buf_writer_flush)
+#define buf_writer_init JEMALLOC_N(buf_writer_init)
+#define buf_writer_pipe JEMALLOC_N(buf_writer_pipe)
+#define buf_writer_terminate JEMALLOC_N(buf_writer_terminate)
+#define cache_bin_info_compute_alloc JEMALLOC_N(cache_bin_info_compute_alloc)
+#define cache_bin_info_init JEMALLOC_N(cache_bin_info_init)
+#define cache_bin_init JEMALLOC_N(cache_bin_init)
+#define cache_bin_postincrement JEMALLOC_N(cache_bin_postincrement)
+#define cache_bin_preincrement JEMALLOC_N(cache_bin_preincrement)
+#define cache_bin_still_zero_initialized JEMALLOC_N(cache_bin_still_zero_initialized)
#define ckh_count JEMALLOC_N(ckh_count)
#define ckh_delete JEMALLOC_N(ckh_delete)
#define ckh_insert JEMALLOC_N(ckh_insert)
@@ -149,61 +179,116 @@
#define ckh_search JEMALLOC_N(ckh_search)
#define ckh_string_hash JEMALLOC_N(ckh_string_hash)
#define ckh_string_keycomp JEMALLOC_N(ckh_string_keycomp)
+#define counter_accum_init JEMALLOC_N(counter_accum_init)
+#define counter_postfork_child JEMALLOC_N(counter_postfork_child)
+#define counter_postfork_parent JEMALLOC_N(counter_postfork_parent)
+#define counter_prefork JEMALLOC_N(counter_prefork)
#define ctl_boot JEMALLOC_N(ctl_boot)
#define ctl_bymib JEMALLOC_N(ctl_bymib)
+#define ctl_bymibname JEMALLOC_N(ctl_bymibname)
#define ctl_byname JEMALLOC_N(ctl_byname)
+#define ctl_mibnametomib JEMALLOC_N(ctl_mibnametomib)
+#define ctl_mtx_assert_held JEMALLOC_N(ctl_mtx_assert_held)
#define ctl_nametomib JEMALLOC_N(ctl_nametomib)
#define ctl_postfork_child JEMALLOC_N(ctl_postfork_child)
#define ctl_postfork_parent JEMALLOC_N(ctl_postfork_parent)
#define ctl_prefork JEMALLOC_N(ctl_prefork)
+#define decay_deadline_init JEMALLOC_N(decay_deadline_init)
+#define decay_init JEMALLOC_N(decay_init)
+#define decay_maybe_advance_epoch JEMALLOC_N(decay_maybe_advance_epoch)
+#define decay_ms_valid JEMALLOC_N(decay_ms_valid)
+#define decay_npages_purge_in JEMALLOC_N(decay_npages_purge_in)
+#define decay_ns_until_purge JEMALLOC_N(decay_ns_until_purge)
+#define decay_reinit JEMALLOC_N(decay_reinit)
#define div_init JEMALLOC_N(div_init)
-#define extent_alloc JEMALLOC_N(extent_alloc)
+#define ecache_init JEMALLOC_N(ecache_init)
+#define ecache_postfork_child JEMALLOC_N(ecache_postfork_child)
+#define ecache_postfork_parent JEMALLOC_N(ecache_postfork_parent)
+#define ecache_prefork JEMALLOC_N(ecache_prefork)
+#define edata_avail_any JEMALLOC_N(edata_avail_any)
+#define edata_avail_empty JEMALLOC_N(edata_avail_empty)
+#define edata_avail_first JEMALLOC_N(edata_avail_first)
+#define edata_avail_insert JEMALLOC_N(edata_avail_insert)
+#define edata_avail_new JEMALLOC_N(edata_avail_new)
+#define edata_avail_remove JEMALLOC_N(edata_avail_remove)
+#define edata_avail_remove_any JEMALLOC_N(edata_avail_remove_any)
+#define edata_avail_remove_first JEMALLOC_N(edata_avail_remove_first)
+#define edata_heap_any JEMALLOC_N(edata_heap_any)
+#define edata_heap_empty JEMALLOC_N(edata_heap_empty)
+#define edata_heap_first JEMALLOC_N(edata_heap_first)
+#define edata_heap_insert JEMALLOC_N(edata_heap_insert)
+#define edata_heap_new JEMALLOC_N(edata_heap_new)
+#define edata_heap_remove JEMALLOC_N(edata_heap_remove)
+#define edata_heap_remove_any JEMALLOC_N(edata_heap_remove_any)
+#define edata_heap_remove_first JEMALLOC_N(edata_heap_remove_first)
+#define edata_cache_fast_disable JEMALLOC_N(edata_cache_fast_disable)
+#define edata_cache_fast_get JEMALLOC_N(edata_cache_fast_get)
+#define edata_cache_fast_init JEMALLOC_N(edata_cache_fast_init)
+#define edata_cache_fast_put JEMALLOC_N(edata_cache_fast_put)
+#define edata_cache_get JEMALLOC_N(edata_cache_get)
+#define edata_cache_init JEMALLOC_N(edata_cache_init)
+#define edata_cache_postfork_child JEMALLOC_N(edata_cache_postfork_child)
+#define edata_cache_postfork_parent JEMALLOC_N(edata_cache_postfork_parent)
+#define edata_cache_prefork JEMALLOC_N(edata_cache_prefork)
+#define edata_cache_put JEMALLOC_N(edata_cache_put)
+#define ehooks_default_alloc_impl JEMALLOC_N(ehooks_default_alloc_impl)
+#define ehooks_default_commit_impl JEMALLOC_N(ehooks_default_commit_impl)
+#define ehooks_default_dalloc_impl JEMALLOC_N(ehooks_default_dalloc_impl)
+#define ehooks_default_decommit_impl JEMALLOC_N(ehooks_default_decommit_impl)
+#define ehooks_default_destroy_impl JEMALLOC_N(ehooks_default_destroy_impl)
+#define ehooks_default_extent_hooks JEMALLOC_N(ehooks_default_extent_hooks)
+#define ehooks_default_guard_impl JEMALLOC_N(ehooks_default_guard_impl)
+#define ehooks_default_merge JEMALLOC_N(ehooks_default_merge)
+#define ehooks_default_merge_impl JEMALLOC_N(ehooks_default_merge_impl)
+#define ehooks_default_purge_forced_impl JEMALLOC_N(ehooks_default_purge_forced_impl)
+#define ehooks_default_purge_lazy_impl JEMALLOC_N(ehooks_default_purge_lazy_impl)
+#define ehooks_default_split_impl JEMALLOC_N(ehooks_default_split_impl)
+#define ehooks_default_unguard_impl JEMALLOC_N(ehooks_default_unguard_impl)
+#define ehooks_default_zero_impl JEMALLOC_N(ehooks_default_zero_impl)
+#define ehooks_init JEMALLOC_N(ehooks_init)
+#define emap_deregister_boundary JEMALLOC_N(emap_deregister_boundary)
+#define emap_deregister_interior JEMALLOC_N(emap_deregister_interior)
+#define emap_do_assert_mapped JEMALLOC_N(emap_do_assert_mapped)
+#define emap_do_assert_not_mapped JEMALLOC_N(emap_do_assert_not_mapped)
+#define emap_init JEMALLOC_N(emap_init)
+#define emap_merge_commit JEMALLOC_N(emap_merge_commit)
+#define emap_merge_prepare JEMALLOC_N(emap_merge_prepare)
+#define emap_register_boundary JEMALLOC_N(emap_register_boundary)
+#define emap_register_interior JEMALLOC_N(emap_register_interior)
+#define emap_release_edata JEMALLOC_N(emap_release_edata)
+#define emap_remap JEMALLOC_N(emap_remap)
+#define emap_split_commit JEMALLOC_N(emap_split_commit)
+#define emap_split_prepare JEMALLOC_N(emap_split_prepare)
+#define emap_try_acquire_edata_neighbor JEMALLOC_N(emap_try_acquire_edata_neighbor)
+#define emap_try_acquire_edata_neighbor_expand JEMALLOC_N(emap_try_acquire_edata_neighbor_expand)
+#define emap_update_edata_state JEMALLOC_N(emap_update_edata_state)
+#define eset_fit JEMALLOC_N(eset_fit)
+#define eset_init JEMALLOC_N(eset_init)
+#define eset_insert JEMALLOC_N(eset_insert)
+#define eset_nbytes_get JEMALLOC_N(eset_nbytes_get)
+#define eset_nextents_get JEMALLOC_N(eset_nextents_get)
+#define eset_npages_get JEMALLOC_N(eset_npages_get)
+#define eset_remove JEMALLOC_N(eset_remove)
+#define exp_grow_init JEMALLOC_N(exp_grow_init)
+#define ecache_alloc JEMALLOC_N(ecache_alloc)
+#define ecache_alloc_grow JEMALLOC_N(ecache_alloc_grow)
+#define ecache_dalloc JEMALLOC_N(ecache_dalloc)
+#define ecache_evict JEMALLOC_N(ecache_evict)
#define extent_alloc_wrapper JEMALLOC_N(extent_alloc_wrapper)
-#define extent_avail_any JEMALLOC_N(extent_avail_any)
-#define extent_avail_empty JEMALLOC_N(extent_avail_empty)
-#define extent_avail_first JEMALLOC_N(extent_avail_first)
-#define extent_avail_insert JEMALLOC_N(extent_avail_insert)
-#define extent_avail_new JEMALLOC_N(extent_avail_new)
-#define extent_avail_remove JEMALLOC_N(extent_avail_remove)
-#define extent_avail_remove_any JEMALLOC_N(extent_avail_remove_any)
-#define extent_avail_remove_first JEMALLOC_N(extent_avail_remove_first)
#define extent_boot JEMALLOC_N(extent_boot)
#define extent_commit_wrapper JEMALLOC_N(extent_commit_wrapper)
-#define extent_dalloc JEMALLOC_N(extent_dalloc)
+#define extent_commit_zero JEMALLOC_N(extent_commit_zero)
#define extent_dalloc_gap JEMALLOC_N(extent_dalloc_gap)
#define extent_dalloc_wrapper JEMALLOC_N(extent_dalloc_wrapper)
#define extent_decommit_wrapper JEMALLOC_N(extent_decommit_wrapper)
#define extent_destroy_wrapper JEMALLOC_N(extent_destroy_wrapper)
-#define extent_heap_any JEMALLOC_N(extent_heap_any)
-#define extent_heap_empty JEMALLOC_N(extent_heap_empty)
-#define extent_heap_first JEMALLOC_N(extent_heap_first)
-#define extent_heap_insert JEMALLOC_N(extent_heap_insert)
-#define extent_heap_new JEMALLOC_N(extent_heap_new)
-#define extent_heap_remove JEMALLOC_N(extent_heap_remove)
-#define extent_heap_remove_any JEMALLOC_N(extent_heap_remove_any)
-#define extent_heap_remove_first JEMALLOC_N(extent_heap_remove_first)
-#define extent_hooks_default JEMALLOC_N(extent_hooks_default)
-#define extent_hooks_get JEMALLOC_N(extent_hooks_get)
-#define extent_hooks_set JEMALLOC_N(extent_hooks_set)
+#define extent_gdump_add JEMALLOC_N(extent_gdump_add)
#define extent_merge_wrapper JEMALLOC_N(extent_merge_wrapper)
-#define extent_mutex_pool JEMALLOC_N(extent_mutex_pool)
#define extent_purge_forced_wrapper JEMALLOC_N(extent_purge_forced_wrapper)
#define extent_purge_lazy_wrapper JEMALLOC_N(extent_purge_lazy_wrapper)
+#define extent_record JEMALLOC_N(extent_record)
+#define extent_sn_next JEMALLOC_N(extent_sn_next)
#define extent_split_wrapper JEMALLOC_N(extent_split_wrapper)
-#define extent_util_stats_get JEMALLOC_N(extent_util_stats_get)
-#define extent_util_stats_verbose_get JEMALLOC_N(extent_util_stats_verbose_get)
-#define extents_alloc JEMALLOC_N(extents_alloc)
-#define extents_dalloc JEMALLOC_N(extents_dalloc)
-#define extents_evict JEMALLOC_N(extents_evict)
-#define extents_init JEMALLOC_N(extents_init)
-#define extents_nbytes_get JEMALLOC_N(extents_nbytes_get)
-#define extents_nextents_get JEMALLOC_N(extents_nextents_get)
-#define extents_npages_get JEMALLOC_N(extents_npages_get)
-#define extents_postfork_child JEMALLOC_N(extents_postfork_child)
-#define extents_postfork_parent JEMALLOC_N(extents_postfork_parent)
-#define extents_prefork JEMALLOC_N(extents_prefork)
-#define extents_rtree JEMALLOC_N(extents_rtree)
-#define extents_state_get JEMALLOC_N(extents_state_get)
#define opt_lg_extent_max_active_fit JEMALLOC_N(opt_lg_extent_max_active_fit)
#define dss_prec_names JEMALLOC_N(dss_prec_names)
#define extent_alloc_dss JEMALLOC_N(extent_alloc_dss)
@@ -216,24 +301,66 @@
#define extent_alloc_mmap JEMALLOC_N(extent_alloc_mmap)
#define extent_dalloc_mmap JEMALLOC_N(extent_dalloc_mmap)
#define opt_retain JEMALLOC_N(opt_retain)
+#define fxp_parse JEMALLOC_N(fxp_parse)
+#define fxp_print JEMALLOC_N(fxp_print)
+#define opt_lg_san_uaf_align JEMALLOC_N(opt_lg_san_uaf_align)
+#define opt_san_guard_large JEMALLOC_N(opt_san_guard_large)
+#define opt_san_guard_small JEMALLOC_N(opt_san_guard_small)
+#define san_cache_bin_nonfast_mask JEMALLOC_N(san_cache_bin_nonfast_mask)
+#define san_check_stashed_ptrs JEMALLOC_N(san_check_stashed_ptrs)
+#define san_guard_pages JEMALLOC_N(san_guard_pages)
+#define san_init JEMALLOC_N(san_init)
+#define san_unguard_pages JEMALLOC_N(san_unguard_pages)
+#define san_unguard_pages_pre_destroy JEMALLOC_N(san_unguard_pages_pre_destroy)
+#define tsd_san_init JEMALLOC_N(tsd_san_init)
+#define san_bump_alloc JEMALLOC_N(san_bump_alloc)
#define hook_boot JEMALLOC_N(hook_boot)
#define hook_install JEMALLOC_N(hook_install)
#define hook_invoke_alloc JEMALLOC_N(hook_invoke_alloc)
#define hook_invoke_dalloc JEMALLOC_N(hook_invoke_dalloc)
#define hook_invoke_expand JEMALLOC_N(hook_invoke_expand)
#define hook_remove JEMALLOC_N(hook_remove)
+#define hpa_central_extract JEMALLOC_N(hpa_central_extract)
+#define hpa_central_init JEMALLOC_N(hpa_central_init)
+#define hpa_shard_destroy JEMALLOC_N(hpa_shard_destroy)
+#define hpa_shard_disable JEMALLOC_N(hpa_shard_disable)
+#define hpa_shard_do_deferred_work JEMALLOC_N(hpa_shard_do_deferred_work)
+#define hpa_shard_init JEMALLOC_N(hpa_shard_init)
+#define hpa_shard_postfork_child JEMALLOC_N(hpa_shard_postfork_child)
+#define hpa_shard_postfork_parent JEMALLOC_N(hpa_shard_postfork_parent)
+#define hpa_shard_prefork3 JEMALLOC_N(hpa_shard_prefork3)
+#define hpa_shard_prefork4 JEMALLOC_N(hpa_shard_prefork4)
+#define hpa_shard_set_deferral_allowed JEMALLOC_N(hpa_shard_set_deferral_allowed)
+#define hpa_shard_stats_accum JEMALLOC_N(hpa_shard_stats_accum)
+#define hpa_shard_stats_merge JEMALLOC_N(hpa_shard_stats_merge)
+#define hpa_supported JEMALLOC_N(hpa_supported)
+#define hpa_hooks_default JEMALLOC_N(hpa_hooks_default)
+#define hpdata_age_heap_any JEMALLOC_N(hpdata_age_heap_any)
+#define hpdata_age_heap_empty JEMALLOC_N(hpdata_age_heap_empty)
+#define hpdata_age_heap_first JEMALLOC_N(hpdata_age_heap_first)
+#define hpdata_age_heap_insert JEMALLOC_N(hpdata_age_heap_insert)
+#define hpdata_age_heap_new JEMALLOC_N(hpdata_age_heap_new)
+#define hpdata_age_heap_remove JEMALLOC_N(hpdata_age_heap_remove)
+#define hpdata_age_heap_remove_any JEMALLOC_N(hpdata_age_heap_remove_any)
+#define hpdata_age_heap_remove_first JEMALLOC_N(hpdata_age_heap_remove_first)
+#define hpdata_dehugify JEMALLOC_N(hpdata_dehugify)
+#define hpdata_hugify JEMALLOC_N(hpdata_hugify)
+#define hpdata_init JEMALLOC_N(hpdata_init)
+#define hpdata_purge_begin JEMALLOC_N(hpdata_purge_begin)
+#define hpdata_purge_end JEMALLOC_N(hpdata_purge_end)
+#define hpdata_purge_next JEMALLOC_N(hpdata_purge_next)
+#define hpdata_reserve_alloc JEMALLOC_N(hpdata_reserve_alloc)
+#define hpdata_unreserve JEMALLOC_N(hpdata_unreserve)
+#define inspect_extent_util_stats_get JEMALLOC_N(inspect_extent_util_stats_get)
+#define inspect_extent_util_stats_verbose_get JEMALLOC_N(inspect_extent_util_stats_verbose_get)
#define large_dalloc JEMALLOC_N(large_dalloc)
#define large_dalloc_finish JEMALLOC_N(large_dalloc_finish)
-#define large_dalloc_junk JEMALLOC_N(large_dalloc_junk)
-#define large_dalloc_maybe_junk JEMALLOC_N(large_dalloc_maybe_junk)
-#define large_dalloc_prep_junked_locked JEMALLOC_N(large_dalloc_prep_junked_locked)
+#define large_dalloc_prep_locked JEMALLOC_N(large_dalloc_prep_locked)
#define large_malloc JEMALLOC_N(large_malloc)
#define large_palloc JEMALLOC_N(large_palloc)
-#define large_prof_alloc_time_get JEMALLOC_N(large_prof_alloc_time_get)
-#define large_prof_alloc_time_set JEMALLOC_N(large_prof_alloc_time_set)
-#define large_prof_tctx_get JEMALLOC_N(large_prof_tctx_get)
+#define large_prof_info_get JEMALLOC_N(large_prof_info_get)
+#define large_prof_info_set JEMALLOC_N(large_prof_info_set)
#define large_prof_tctx_reset JEMALLOC_N(large_prof_tctx_reset)
-#define large_prof_tctx_set JEMALLOC_N(large_prof_tctx_set)
#define large_ralloc JEMALLOC_N(large_ralloc)
#define large_ralloc_no_move JEMALLOC_N(large_ralloc_no_move)
#define large_salloc JEMALLOC_N(large_salloc)
@@ -248,14 +375,17 @@
#define malloc_vcprintf JEMALLOC_N(malloc_vcprintf)
#define malloc_vsnprintf JEMALLOC_N(malloc_vsnprintf)
#define malloc_write JEMALLOC_N(malloc_write)
+#define wrtmessage JEMALLOC_N(wrtmessage)
+#define wrtmessage_1_0 JEMALLOC_N(wrtmessage_1_0)
#define malloc_mutex_boot JEMALLOC_N(malloc_mutex_boot)
+#define malloc_mutex_first_thread JEMALLOC_N(malloc_mutex_first_thread)
#define malloc_mutex_init JEMALLOC_N(malloc_mutex_init)
#define malloc_mutex_lock_slow JEMALLOC_N(malloc_mutex_lock_slow)
#define malloc_mutex_postfork_child JEMALLOC_N(malloc_mutex_postfork_child)
#define malloc_mutex_postfork_parent JEMALLOC_N(malloc_mutex_postfork_parent)
#define malloc_mutex_prefork JEMALLOC_N(malloc_mutex_prefork)
#define malloc_mutex_prof_data_reset JEMALLOC_N(malloc_mutex_prof_data_reset)
-#define mutex_pool_init JEMALLOC_N(mutex_pool_init)
+#define opt_mutex_max_spin JEMALLOC_N(opt_mutex_max_spin)
#define nstime_add JEMALLOC_N(nstime_add)
#define nstime_compare JEMALLOC_N(nstime_compare)
#define nstime_copy JEMALLOC_N(nstime_copy)
@@ -265,14 +395,56 @@
#define nstime_imultiply JEMALLOC_N(nstime_imultiply)
#define nstime_init JEMALLOC_N(nstime_init)
#define nstime_init2 JEMALLOC_N(nstime_init2)
+#define nstime_init_update JEMALLOC_N(nstime_init_update)
#define nstime_isubtract JEMALLOC_N(nstime_isubtract)
#define nstime_monotonic JEMALLOC_N(nstime_monotonic)
#define nstime_msec JEMALLOC_N(nstime_msec)
#define nstime_ns JEMALLOC_N(nstime_ns)
+#define nstime_ns_since JEMALLOC_N(nstime_ns_since)
#define nstime_nsec JEMALLOC_N(nstime_nsec)
+#define nstime_prof_init_update JEMALLOC_N(nstime_prof_init_update)
+#define nstime_prof_update JEMALLOC_N(nstime_prof_update)
#define nstime_sec JEMALLOC_N(nstime_sec)
#define nstime_subtract JEMALLOC_N(nstime_subtract)
#define nstime_update JEMALLOC_N(nstime_update)
+#define opt_prof_time_res JEMALLOC_N(opt_prof_time_res)
+#define prof_time_res_mode_names JEMALLOC_N(prof_time_res_mode_names)
+#define pa_alloc JEMALLOC_N(pa_alloc)
+#define pa_central_init JEMALLOC_N(pa_central_init)
+#define pa_dalloc JEMALLOC_N(pa_dalloc)
+#define pa_decay_ms_get JEMALLOC_N(pa_decay_ms_get)
+#define pa_decay_ms_set JEMALLOC_N(pa_decay_ms_set)
+#define pa_expand JEMALLOC_N(pa_expand)
+#define pa_shard_destroy JEMALLOC_N(pa_shard_destroy)
+#define pa_shard_disable_hpa JEMALLOC_N(pa_shard_disable_hpa)
+#define pa_shard_do_deferred_work JEMALLOC_N(pa_shard_do_deferred_work)
+#define pa_shard_enable_hpa JEMALLOC_N(pa_shard_enable_hpa)
+#define pa_shard_init JEMALLOC_N(pa_shard_init)
+#define pa_shard_reset JEMALLOC_N(pa_shard_reset)
+#define pa_shard_retain_grow_limit_get_set JEMALLOC_N(pa_shard_retain_grow_limit_get_set)
+#define pa_shard_set_deferral_allowed JEMALLOC_N(pa_shard_set_deferral_allowed)
+#define pa_shard_time_until_deferred_work JEMALLOC_N(pa_shard_time_until_deferred_work)
+#define pa_shrink JEMALLOC_N(pa_shrink)
+#define pa_shard_basic_stats_merge JEMALLOC_N(pa_shard_basic_stats_merge)
+#define pa_shard_mtx_stats_read JEMALLOC_N(pa_shard_mtx_stats_read)
+#define pa_shard_postfork_child JEMALLOC_N(pa_shard_postfork_child)
+#define pa_shard_postfork_parent JEMALLOC_N(pa_shard_postfork_parent)
+#define pa_shard_prefork0 JEMALLOC_N(pa_shard_prefork0)
+#define pa_shard_prefork2 JEMALLOC_N(pa_shard_prefork2)
+#define pa_shard_prefork3 JEMALLOC_N(pa_shard_prefork3)
+#define pa_shard_prefork4 JEMALLOC_N(pa_shard_prefork4)
+#define pa_shard_prefork5 JEMALLOC_N(pa_shard_prefork5)
+#define pa_shard_stats_merge JEMALLOC_N(pa_shard_stats_merge)
+#define pai_alloc_batch_default JEMALLOC_N(pai_alloc_batch_default)
+#define pai_dalloc_batch_default JEMALLOC_N(pai_dalloc_batch_default)
+#define pac_decay_all JEMALLOC_N(pac_decay_all)
+#define pac_decay_ms_get JEMALLOC_N(pac_decay_ms_get)
+#define pac_decay_ms_set JEMALLOC_N(pac_decay_ms_set)
+#define pac_destroy JEMALLOC_N(pac_destroy)
+#define pac_init JEMALLOC_N(pac_init)
+#define pac_maybe_decay_purge JEMALLOC_N(pac_maybe_decay_purge)
+#define pac_reset JEMALLOC_N(pac_reset)
+#define pac_retain_grow_limit_get_set JEMALLOC_N(pac_retain_grow_limit_get_set)
#define init_system_thp_mode JEMALLOC_N(init_system_thp_mode)
#define opt_thp JEMALLOC_N(opt_thp)
#define pages_boot JEMALLOC_N(pages_boot)
@@ -282,14 +454,23 @@
#define pages_dontdump JEMALLOC_N(pages_dontdump)
#define pages_huge JEMALLOC_N(pages_huge)
#define pages_map JEMALLOC_N(pages_map)
+#define pages_mark_guards JEMALLOC_N(pages_mark_guards)
#define pages_nohuge JEMALLOC_N(pages_nohuge)
#define pages_purge_forced JEMALLOC_N(pages_purge_forced)
#define pages_purge_lazy JEMALLOC_N(pages_purge_lazy)
#define pages_set_thp_state JEMALLOC_N(pages_set_thp_state)
#define pages_unmap JEMALLOC_N(pages_unmap)
+#define pages_unmark_guards JEMALLOC_N(pages_unmark_guards)
#define thp_mode_names JEMALLOC_N(thp_mode_names)
-#define bt2gctx_mtx JEMALLOC_N(bt2gctx_mtx)
-#define bt_init JEMALLOC_N(bt_init)
+#define peak_alloc_event_handler JEMALLOC_N(peak_alloc_event_handler)
+#define peak_alloc_new_event_wait JEMALLOC_N(peak_alloc_new_event_wait)
+#define peak_alloc_postponed_event_wait JEMALLOC_N(peak_alloc_postponed_event_wait)
+#define peak_dalloc_event_handler JEMALLOC_N(peak_dalloc_event_handler)
+#define peak_dalloc_new_event_wait JEMALLOC_N(peak_dalloc_new_event_wait)
+#define peak_dalloc_postponed_event_wait JEMALLOC_N(peak_dalloc_postponed_event_wait)
+#define peak_event_max JEMALLOC_N(peak_event_max)
+#define peak_event_update JEMALLOC_N(peak_event_update)
+#define peak_event_zero JEMALLOC_N(peak_event_zero)
#define lg_prof_sample JEMALLOC_N(lg_prof_sample)
#define opt_lg_prof_interval JEMALLOC_N(opt_lg_prof_interval)
#define opt_lg_prof_sample JEMALLOC_N(opt_lg_prof_sample)
@@ -299,20 +480,25 @@
#define opt_prof_final JEMALLOC_N(opt_prof_final)
#define opt_prof_gdump JEMALLOC_N(opt_prof_gdump)
#define opt_prof_leak JEMALLOC_N(opt_prof_leak)
-#define opt_prof_log JEMALLOC_N(opt_prof_log)
+#define opt_prof_leak_error JEMALLOC_N(opt_prof_leak_error)
#define opt_prof_prefix JEMALLOC_N(opt_prof_prefix)
+#define opt_prof_sys_thread_name JEMALLOC_N(opt_prof_sys_thread_name)
#define opt_prof_thread_active_init JEMALLOC_N(opt_prof_thread_active_init)
-#define prof_accum_init JEMALLOC_N(prof_accum_init)
-#define prof_active JEMALLOC_N(prof_active)
+#define opt_prof_unbias JEMALLOC_N(opt_prof_unbias)
#define prof_active_get JEMALLOC_N(prof_active_get)
#define prof_active_set JEMALLOC_N(prof_active_set)
+#define prof_active_state JEMALLOC_N(prof_active_state)
#define prof_alloc_rollback JEMALLOC_N(prof_alloc_rollback)
-#define prof_backtrace JEMALLOC_N(prof_backtrace)
+#define prof_backtrace_hook JEMALLOC_N(prof_backtrace_hook)
+#define prof_backtrace_hook_get JEMALLOC_N(prof_backtrace_hook_get)
+#define prof_backtrace_hook_set JEMALLOC_N(prof_backtrace_hook_set)
#define prof_boot0 JEMALLOC_N(prof_boot0)
#define prof_boot1 JEMALLOC_N(prof_boot1)
#define prof_boot2 JEMALLOC_N(prof_boot2)
-#define prof_dump_header JEMALLOC_N(prof_dump_header)
-#define prof_dump_open JEMALLOC_N(prof_dump_open)
+#define prof_booted JEMALLOC_N(prof_booted)
+#define prof_dump_hook JEMALLOC_N(prof_dump_hook)
+#define prof_dump_hook_get JEMALLOC_N(prof_dump_hook_get)
+#define prof_dump_hook_set JEMALLOC_N(prof_dump_hook_set)
#define prof_free_sampled_object JEMALLOC_N(prof_free_sampled_object)
#define prof_gdump JEMALLOC_N(prof_gdump)
#define prof_gdump_get JEMALLOC_N(prof_gdump_get)
@@ -320,18 +506,16 @@
#define prof_gdump_val JEMALLOC_N(prof_gdump_val)
#define prof_idump JEMALLOC_N(prof_idump)
#define prof_interval JEMALLOC_N(prof_interval)
-#define prof_log_start JEMALLOC_N(prof_log_start)
-#define prof_log_stop JEMALLOC_N(prof_log_stop)
-#define prof_logging_state JEMALLOC_N(prof_logging_state)
-#define prof_lookup JEMALLOC_N(prof_lookup)
#define prof_malloc_sample_object JEMALLOC_N(prof_malloc_sample_object)
#define prof_mdump JEMALLOC_N(prof_mdump)
#define prof_postfork_child JEMALLOC_N(prof_postfork_child)
#define prof_postfork_parent JEMALLOC_N(prof_postfork_parent)
#define prof_prefork0 JEMALLOC_N(prof_prefork0)
#define prof_prefork1 JEMALLOC_N(prof_prefork1)
-#define prof_reset JEMALLOC_N(prof_reset)
-#define prof_sample_threshold_update JEMALLOC_N(prof_sample_threshold_update)
+#define prof_sample_event_handler JEMALLOC_N(prof_sample_event_handler)
+#define prof_sample_new_event_wait JEMALLOC_N(prof_sample_new_event_wait)
+#define prof_sample_postponed_event_wait JEMALLOC_N(prof_sample_postponed_event_wait)
+#define prof_tctx_create JEMALLOC_N(prof_tctx_create)
#define prof_tdata_cleanup JEMALLOC_N(prof_tdata_cleanup)
#define prof_tdata_init JEMALLOC_N(prof_tdata_init)
#define prof_tdata_reinit JEMALLOC_N(prof_tdata_reinit)
@@ -341,42 +525,157 @@
#define prof_thread_active_set JEMALLOC_N(prof_thread_active_set)
#define prof_thread_name_get JEMALLOC_N(prof_thread_name_get)
#define prof_thread_name_set JEMALLOC_N(prof_thread_name_set)
+#define bt2gctx_mtx JEMALLOC_N(bt2gctx_mtx)
+#define gctx_locks JEMALLOC_N(gctx_locks)
+#define prof_bt_count JEMALLOC_N(prof_bt_count)
+#define prof_bt_hash JEMALLOC_N(prof_bt_hash)
+#define prof_bt_keycomp JEMALLOC_N(prof_bt_keycomp)
+#define prof_cnt_all JEMALLOC_N(prof_cnt_all)
+#define prof_data_init JEMALLOC_N(prof_data_init)
+#define prof_dump_impl JEMALLOC_N(prof_dump_impl)
+#define prof_dump_mtx JEMALLOC_N(prof_dump_mtx)
+#define prof_lookup JEMALLOC_N(prof_lookup)
+#define prof_reset JEMALLOC_N(prof_reset)
+#define prof_shifted_unbiased_cnt JEMALLOC_N(prof_shifted_unbiased_cnt)
+#define prof_tctx_try_destroy JEMALLOC_N(prof_tctx_try_destroy)
+#define prof_tdata_count JEMALLOC_N(prof_tdata_count)
+#define prof_tdata_detach JEMALLOC_N(prof_tdata_detach)
+#define prof_tdata_init_impl JEMALLOC_N(prof_tdata_init_impl)
+#define prof_thread_name_alloc JEMALLOC_N(prof_thread_name_alloc)
+#define prof_thread_name_set_impl JEMALLOC_N(prof_thread_name_set_impl)
+#define prof_unbias_map_init JEMALLOC_N(prof_unbias_map_init)
+#define prof_unbiased_sz JEMALLOC_N(prof_unbiased_sz)
+#define tdata_locks JEMALLOC_N(tdata_locks)
+#define tdatas_mtx JEMALLOC_N(tdatas_mtx)
+#define log_mtx JEMALLOC_N(log_mtx)
+#define opt_prof_log JEMALLOC_N(opt_prof_log)
+#define prof_log_alloc_count JEMALLOC_N(prof_log_alloc_count)
+#define prof_log_bt_count JEMALLOC_N(prof_log_bt_count)
+#define prof_log_dummy_set JEMALLOC_N(prof_log_dummy_set)
+#define prof_log_init JEMALLOC_N(prof_log_init)
+#define prof_log_is_logging JEMALLOC_N(prof_log_is_logging)
+#define prof_log_rep_check JEMALLOC_N(prof_log_rep_check)
+#define prof_log_start JEMALLOC_N(prof_log_start)
+#define prof_log_stop JEMALLOC_N(prof_log_stop)
+#define prof_log_thr_count JEMALLOC_N(prof_log_thr_count)
+#define prof_logging_state JEMALLOC_N(prof_logging_state)
+#define prof_try_log JEMALLOC_N(prof_try_log)
+#define edata_prof_recent_alloc_get_no_lock_test JEMALLOC_N(edata_prof_recent_alloc_get_no_lock_test)
+#define edata_prof_recent_alloc_init JEMALLOC_N(edata_prof_recent_alloc_init)
+#define opt_prof_recent_alloc_max JEMALLOC_N(opt_prof_recent_alloc_max)
+#define prof_recent_alloc JEMALLOC_N(prof_recent_alloc)
+#define prof_recent_alloc_dump JEMALLOC_N(prof_recent_alloc_dump)
+#define prof_recent_alloc_edata_get_no_lock_test JEMALLOC_N(prof_recent_alloc_edata_get_no_lock_test)
+#define prof_recent_alloc_list JEMALLOC_N(prof_recent_alloc_list)
+#define prof_recent_alloc_max_ctl_read JEMALLOC_N(prof_recent_alloc_max_ctl_read)
+#define prof_recent_alloc_max_ctl_write JEMALLOC_N(prof_recent_alloc_max_ctl_write)
+#define prof_recent_alloc_mtx JEMALLOC_N(prof_recent_alloc_mtx)
+#define prof_recent_alloc_prepare JEMALLOC_N(prof_recent_alloc_prepare)
+#define prof_recent_alloc_reset JEMALLOC_N(prof_recent_alloc_reset)
+#define prof_recent_dump_mtx JEMALLOC_N(prof_recent_dump_mtx)
+#define prof_recent_init JEMALLOC_N(prof_recent_init)
+#define opt_prof_stats JEMALLOC_N(opt_prof_stats)
+#define prof_stats_dec JEMALLOC_N(prof_stats_dec)
+#define prof_stats_get_accum JEMALLOC_N(prof_stats_get_accum)
+#define prof_stats_get_live JEMALLOC_N(prof_stats_get_live)
+#define prof_stats_inc JEMALLOC_N(prof_stats_inc)
+#define prof_stats_mtx JEMALLOC_N(prof_stats_mtx)
+#define bt_init JEMALLOC_N(bt_init)
+#define prof_backtrace JEMALLOC_N(prof_backtrace)
+#define prof_base JEMALLOC_N(prof_base)
+#define prof_do_mock JEMALLOC_N(prof_do_mock)
+#define prof_dump_filename_mtx JEMALLOC_N(prof_dump_filename_mtx)
+#define prof_dump_open_file JEMALLOC_N(prof_dump_open_file)
+#define prof_dump_open_maps JEMALLOC_N(prof_dump_open_maps)
+#define prof_dump_write_file JEMALLOC_N(prof_dump_write_file)
+#define prof_fdump_impl JEMALLOC_N(prof_fdump_impl)
+#define prof_gdump_impl JEMALLOC_N(prof_gdump_impl)
+#define prof_get_default_filename JEMALLOC_N(prof_get_default_filename)
+#define prof_getpid JEMALLOC_N(prof_getpid)
+#define prof_hooks_init JEMALLOC_N(prof_hooks_init)
+#define prof_idump_impl JEMALLOC_N(prof_idump_impl)
+#define prof_mdump_impl JEMALLOC_N(prof_mdump_impl)
+#define prof_prefix_set JEMALLOC_N(prof_prefix_set)
+#define prof_sys_thread_name_fetch JEMALLOC_N(prof_sys_thread_name_fetch)
+#define prof_sys_thread_name_read JEMALLOC_N(prof_sys_thread_name_read)
+#define prof_unwind_init JEMALLOC_N(prof_unwind_init)
+#define psset_init JEMALLOC_N(psset_init)
+#define psset_insert JEMALLOC_N(psset_insert)
+#define psset_pick_alloc JEMALLOC_N(psset_pick_alloc)
+#define psset_pick_hugify JEMALLOC_N(psset_pick_hugify)
+#define psset_pick_purge JEMALLOC_N(psset_pick_purge)
+#define psset_remove JEMALLOC_N(psset_remove)
+#define psset_stats_accum JEMALLOC_N(psset_stats_accum)
+#define psset_update_begin JEMALLOC_N(psset_update_begin)
+#define psset_update_end JEMALLOC_N(psset_update_end)
#define rtree_ctx_data_init JEMALLOC_N(rtree_ctx_data_init)
-#define rtree_leaf_alloc JEMALLOC_N(rtree_leaf_alloc)
-#define rtree_leaf_dalloc JEMALLOC_N(rtree_leaf_dalloc)
#define rtree_leaf_elm_lookup_hard JEMALLOC_N(rtree_leaf_elm_lookup_hard)
#define rtree_new JEMALLOC_N(rtree_new)
-#define rtree_node_alloc JEMALLOC_N(rtree_node_alloc)
-#define rtree_node_dalloc JEMALLOC_N(rtree_node_dalloc)
#define safety_check_fail JEMALLOC_N(safety_check_fail)
+#define safety_check_fail_sized_dealloc JEMALLOC_N(safety_check_fail_sized_dealloc)
#define safety_check_set_abort JEMALLOC_N(safety_check_set_abort)
+#define reg_size_compute JEMALLOC_N(reg_size_compute)
+#define sc_boot JEMALLOC_N(sc_boot)
+#define sc_data_init JEMALLOC_N(sc_data_init)
+#define sc_data_update_slab_size JEMALLOC_N(sc_data_update_slab_size)
+#define sec_disable JEMALLOC_N(sec_disable)
+#define sec_flush JEMALLOC_N(sec_flush)
+#define sec_init JEMALLOC_N(sec_init)
+#define sec_mutex_stats_read JEMALLOC_N(sec_mutex_stats_read)
+#define sec_postfork_child JEMALLOC_N(sec_postfork_child)
+#define sec_postfork_parent JEMALLOC_N(sec_postfork_parent)
+#define sec_prefork2 JEMALLOC_N(sec_prefork2)
+#define sec_stats_merge JEMALLOC_N(sec_stats_merge)
#define arena_mutex_names JEMALLOC_N(arena_mutex_names)
#define global_mutex_names JEMALLOC_N(global_mutex_names)
+#define opt_stats_interval JEMALLOC_N(opt_stats_interval)
+#define opt_stats_interval_opts JEMALLOC_N(opt_stats_interval_opts)
#define opt_stats_print JEMALLOC_N(opt_stats_print)
#define opt_stats_print_opts JEMALLOC_N(opt_stats_print_opts)
+#define stats_boot JEMALLOC_N(stats_boot)
+#define stats_interval_event_handler JEMALLOC_N(stats_interval_event_handler)
+#define stats_interval_new_event_wait JEMALLOC_N(stats_interval_new_event_wait)
+#define stats_interval_postponed_event_wait JEMALLOC_N(stats_interval_postponed_event_wait)
+#define stats_postfork_child JEMALLOC_N(stats_postfork_child)
+#define stats_postfork_parent JEMALLOC_N(stats_postfork_parent)
+#define stats_prefork JEMALLOC_N(stats_prefork)
#define stats_print JEMALLOC_N(stats_print)
-#define sc_boot JEMALLOC_N(sc_boot)
-#define sc_data_global JEMALLOC_N(sc_data_global)
-#define sc_data_init JEMALLOC_N(sc_data_init)
-#define sc_data_update_slab_size JEMALLOC_N(sc_data_update_slab_size)
#define sz_boot JEMALLOC_N(sz_boot)
#define sz_index2size_tab JEMALLOC_N(sz_index2size_tab)
+#define sz_large_pad JEMALLOC_N(sz_large_pad)
#define sz_pind2sz_tab JEMALLOC_N(sz_pind2sz_tab)
+#define sz_psz_quantize_ceil JEMALLOC_N(sz_psz_quantize_ceil)
+#define sz_psz_quantize_floor JEMALLOC_N(sz_psz_quantize_floor)
#define sz_size2index_tab JEMALLOC_N(sz_size2index_tab)
#define nhbins JEMALLOC_N(nhbins)
-#define opt_lg_tcache_max JEMALLOC_N(opt_lg_tcache_max)
+#define opt_lg_tcache_flush_large_div JEMALLOC_N(opt_lg_tcache_flush_large_div)
+#define opt_lg_tcache_flush_small_div JEMALLOC_N(opt_lg_tcache_flush_small_div)
+#define opt_lg_tcache_nslots_mul JEMALLOC_N(opt_lg_tcache_nslots_mul)
#define opt_tcache JEMALLOC_N(opt_tcache)
+#define opt_tcache_gc_delay_bytes JEMALLOC_N(opt_tcache_gc_delay_bytes)
+#define opt_tcache_gc_incr_bytes JEMALLOC_N(opt_tcache_gc_incr_bytes)
+#define opt_tcache_max JEMALLOC_N(opt_tcache_max)
+#define opt_tcache_nslots_large JEMALLOC_N(opt_tcache_nslots_large)
+#define opt_tcache_nslots_small_max JEMALLOC_N(opt_tcache_nslots_small_max)
+#define opt_tcache_nslots_small_min JEMALLOC_N(opt_tcache_nslots_small_min)
#define tcache_alloc_small_hard JEMALLOC_N(tcache_alloc_small_hard)
#define tcache_arena_associate JEMALLOC_N(tcache_arena_associate)
#define tcache_arena_reassociate JEMALLOC_N(tcache_arena_reassociate)
+#define tcache_assert_initialized JEMALLOC_N(tcache_assert_initialized)
#define tcache_bin_flush_large JEMALLOC_N(tcache_bin_flush_large)
#define tcache_bin_flush_small JEMALLOC_N(tcache_bin_flush_small)
+#define tcache_bin_flush_stashed JEMALLOC_N(tcache_bin_flush_stashed)
#define tcache_bin_info JEMALLOC_N(tcache_bin_info)
#define tcache_boot JEMALLOC_N(tcache_boot)
#define tcache_cleanup JEMALLOC_N(tcache_cleanup)
#define tcache_create_explicit JEMALLOC_N(tcache_create_explicit)
-#define tcache_event_hard JEMALLOC_N(tcache_event_hard)
#define tcache_flush JEMALLOC_N(tcache_flush)
+#define tcache_gc_dalloc_event_handler JEMALLOC_N(tcache_gc_dalloc_event_handler)
+#define tcache_gc_dalloc_new_event_wait JEMALLOC_N(tcache_gc_dalloc_new_event_wait)
+#define tcache_gc_dalloc_postponed_event_wait JEMALLOC_N(tcache_gc_dalloc_postponed_event_wait)
+#define tcache_gc_event_handler JEMALLOC_N(tcache_gc_event_handler)
+#define tcache_gc_new_event_wait JEMALLOC_N(tcache_gc_new_event_wait)
+#define tcache_gc_postponed_event_wait JEMALLOC_N(tcache_gc_postponed_event_wait)
#define tcache_maxclass JEMALLOC_N(tcache_maxclass)
#define tcache_postfork_child JEMALLOC_N(tcache_postfork_child)
#define tcache_postfork_parent JEMALLOC_N(tcache_postfork_parent)
@@ -391,9 +690,13 @@
#define tsd_tcache_enabled_data_init JEMALLOC_N(tsd_tcache_enabled_data_init)
#define test_hooks_arena_new_hook JEMALLOC_N(test_hooks_arena_new_hook)
#define test_hooks_libc_hook JEMALLOC_N(test_hooks_libc_hook)
+#define te_assert_invariants_debug JEMALLOC_N(te_assert_invariants_debug)
+#define te_event_trigger JEMALLOC_N(te_event_trigger)
+#define te_recompute_fast_threshold JEMALLOC_N(te_recompute_fast_threshold)
+#define tsd_te_init JEMALLOC_N(tsd_te_init)
+#define ticker_geom_table JEMALLOC_N(ticker_geom_table)
#define malloc_tsd_boot0 JEMALLOC_N(malloc_tsd_boot0)
#define malloc_tsd_boot1 JEMALLOC_N(malloc_tsd_boot1)
-#define malloc_tsd_cleanup_register JEMALLOC_N(malloc_tsd_cleanup_register)
#define malloc_tsd_dalloc JEMALLOC_N(malloc_tsd_dalloc)
#define malloc_tsd_malloc JEMALLOC_N(malloc_tsd_malloc)
#define tsd_booted JEMALLOC_N(tsd_booted)
diff --git a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/public_namespace.h b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/public_namespace.h
index 78d5c66377c3..6363f085f34b 100644
--- a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/public_namespace.h
+++ b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/internal/public_namespace.h
@@ -7,11 +7,12 @@
#define je_mallctlnametomib JEMALLOC_N(mallctlnametomib)
#define je_malloc JEMALLOC_N(malloc)
#define je_malloc_conf JEMALLOC_N(malloc_conf)
+#define je_malloc_conf_2_conf_harder JEMALLOC_N(malloc_conf_2_conf_harder)
#define je_malloc_message JEMALLOC_N(malloc_message)
#define je_malloc_stats_print JEMALLOC_N(malloc_stats_print)
#define je_malloc_usable_size JEMALLOC_N(malloc_usable_size)
#define je_mallocx JEMALLOC_N(mallocx)
-#define je_smallocx_ea6b3e973b477b8061e0076bb257dbd7f3faa756 JEMALLOC_N(smallocx_ea6b3e973b477b8061e0076bb257dbd7f3faa756)
+#define je_smallocx_54eaed1d8b56b1aa528be3bdd1877e59c56fa90c JEMALLOC_N(smallocx_54eaed1d8b56b1aa528be3bdd1877e59c56fa90c)
#define je_nallocx JEMALLOC_N(nallocx)
#define je_posix_memalign JEMALLOC_N(posix_memalign)
#define je_rallocx JEMALLOC_N(rallocx)
@@ -19,4 +20,5 @@
#define je_sallocx JEMALLOC_N(sallocx)
#define je_sdallocx JEMALLOC_N(sdallocx)
#define je_xallocx JEMALLOC_N(xallocx)
+#define je_memalign JEMALLOC_N(memalign)
#define je_valloc JEMALLOC_N(valloc)
diff --git a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc.h b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc.h
index c3fa08e83f22..f9ed38656a87 100644
--- a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc.h
+++ b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc.h
@@ -19,6 +19,12 @@ extern "C" {
/* Defined if format(printf, ...) attribute is supported. */
#define JEMALLOC_HAVE_ATTR_FORMAT_PRINTF
+/* Defined if fallthrough attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_FALLTHROUGH
+
+/* Defined if cold attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_COLD
+
/*
* Define overrides for non-standard allocator-related functions if they are
* present on the system.
@@ -68,11 +74,12 @@ extern "C" {
# define je_mallctlnametomib mallctlnametomib
# define je_malloc malloc
# define je_malloc_conf malloc_conf
+# define je_malloc_conf_2_conf_harder malloc_conf_2_conf_harder
# define je_malloc_message malloc_message
# define je_malloc_stats_print malloc_stats_print
# define je_malloc_usable_size malloc_usable_size
# define je_mallocx mallocx
-# define je_smallocx_ea6b3e973b477b8061e0076bb257dbd7f3faa756 smallocx_ea6b3e973b477b8061e0076bb257dbd7f3faa756
+# define je_smallocx_54eaed1d8b56b1aa528be3bdd1877e59c56fa90c smallocx_54eaed1d8b56b1aa528be3bdd1877e59c56fa90c
# define je_nallocx nallocx
# define je_posix_memalign posix_memalign
# define je_rallocx rallocx
@@ -80,6 +87,7 @@ extern "C" {
# define je_sallocx sallocx
# define je_sdallocx sdallocx
# define je_xallocx xallocx
+# define je_memalign memalign
# define je_valloc valloc
#endif
@@ -91,13 +99,13 @@ extern "C" {
#include <limits.h>
#include <strings.h>
-#define JEMALLOC_VERSION "5.2.1-0-gea6b3e973b477b8061e0076bb257dbd7f3faa756"
+#define JEMALLOC_VERSION "5.3.0-0-g54eaed1d8b56b1aa528be3bdd1877e59c56fa90c"
#define JEMALLOC_VERSION_MAJOR 5
-#define JEMALLOC_VERSION_MINOR 2
-#define JEMALLOC_VERSION_BUGFIX 1
+#define JEMALLOC_VERSION_MINOR 3
+#define JEMALLOC_VERSION_BUGFIX 0
#define JEMALLOC_VERSION_NREV 0
-#define JEMALLOC_VERSION_GID "ea6b3e973b477b8061e0076bb257dbd7f3faa756"
-#define JEMALLOC_VERSION_GID_IDENT ea6b3e973b477b8061e0076bb257dbd7f3faa756
+#define JEMALLOC_VERSION_GID "54eaed1d8b56b1aa528be3bdd1877e59c56fa90c"
+#define JEMALLOC_VERSION_GID_IDENT 54eaed1d8b56b1aa528be3bdd1877e59c56fa90c
#define MALLOCX_LG_ALIGN(la) ((int)(la))
#if LG_SIZEOF_PTR == 2
@@ -158,6 +166,7 @@ extern "C" {
# endif
# define JEMALLOC_FORMAT_ARG(i)
# define JEMALLOC_FORMAT_PRINTF(s, i)
+# define JEMALLOC_FALLTHROUGH
# define JEMALLOC_NOINLINE __declspec(noinline)
# ifdef __cplusplus
# define JEMALLOC_NOTHROW __declspec(nothrow)
@@ -171,6 +180,7 @@ extern "C" {
# else
# define JEMALLOC_ALLOCATOR
# endif
+# define JEMALLOC_COLD
#elif defined(JEMALLOC_HAVE_ATTR)
# define JEMALLOC_ATTR(s) __attribute__((s))
# define JEMALLOC_ALIGNED(s) JEMALLOC_ATTR(aligned(s))
@@ -196,11 +206,21 @@ extern "C" {
# else
# define JEMALLOC_FORMAT_PRINTF(s, i)
# endif
+# ifdef JEMALLOC_HAVE_ATTR_FALLTHROUGH
+# define JEMALLOC_FALLTHROUGH JEMALLOC_ATTR(fallthrough)
+# else
+# define JEMALLOC_FALLTHROUGH
+# endif
# define JEMALLOC_NOINLINE JEMALLOC_ATTR(noinline)
# define JEMALLOC_NOTHROW JEMALLOC_ATTR(nothrow)
# define JEMALLOC_SECTION(s) JEMALLOC_ATTR(section(s))
# define JEMALLOC_RESTRICT_RETURN
# define JEMALLOC_ALLOCATOR
+# ifdef JEMALLOC_HAVE_ATTR_COLD
+# define JEMALLOC_COLD JEMALLOC_ATTR(__cold__)
+# else
+# define JEMALLOC_COLD
+# endif
#else
# define JEMALLOC_ATTR(s)
# define JEMALLOC_ALIGNED(s)
@@ -208,11 +228,19 @@ extern "C" {
# define JEMALLOC_ALLOC_SIZE2(s1, s2)
# define JEMALLOC_EXPORT
# define JEMALLOC_FORMAT_PRINTF(s, i)
+# define JEMALLOC_FALLTHROUGH
# define JEMALLOC_NOINLINE
# define JEMALLOC_NOTHROW
# define JEMALLOC_SECTION(s)
# define JEMALLOC_RESTRICT_RETURN
# define JEMALLOC_ALLOCATOR
+# define JEMALLOC_COLD
+#endif
+
+#if (defined(__APPLE__) || defined(__FreeBSD__)) && !defined(JEMALLOC_NO_RENAME)
+# define JEMALLOC_SYS_NOTHROW
+#else
+# define JEMALLOC_SYS_NOTHROW JEMALLOC_NOTHROW
#endif
/*
@@ -225,21 +253,22 @@ extern JEMALLOC_EXPORT void (*je_malloc_message)(void *cbopaque,
const char *s);
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
- void JEMALLOC_NOTHROW *je_malloc(size_t size)
+ void JEMALLOC_SYS_NOTHROW *je_malloc(size_t size)
JEMALLOC_CXX_THROW JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1);
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
- void JEMALLOC_NOTHROW *je_calloc(size_t num, size_t size)
+ void JEMALLOC_SYS_NOTHROW *je_calloc(size_t num, size_t size)
JEMALLOC_CXX_THROW JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE2(1, 2);
-JEMALLOC_EXPORT int JEMALLOC_NOTHROW je_posix_memalign(void **memptr,
- size_t alignment, size_t size) JEMALLOC_CXX_THROW JEMALLOC_ATTR(nonnull(1));
+JEMALLOC_EXPORT int JEMALLOC_SYS_NOTHROW je_posix_memalign(
+ void **memptr, size_t alignment, size_t size) JEMALLOC_CXX_THROW
+ JEMALLOC_ATTR(nonnull(1));
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
- void JEMALLOC_NOTHROW *je_aligned_alloc(size_t alignment,
+ void JEMALLOC_SYS_NOTHROW *je_aligned_alloc(size_t alignment,
size_t size) JEMALLOC_CXX_THROW JEMALLOC_ATTR(malloc)
JEMALLOC_ALLOC_SIZE(2);
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
- void JEMALLOC_NOTHROW *je_realloc(void *ptr, size_t size)
+ void JEMALLOC_SYS_NOTHROW *je_realloc(void *ptr, size_t size)
JEMALLOC_CXX_THROW JEMALLOC_ALLOC_SIZE(2);
-JEMALLOC_EXPORT void JEMALLOC_NOTHROW je_free(void *ptr)
+JEMALLOC_EXPORT void JEMALLOC_SYS_NOTHROW je_free(void *ptr)
JEMALLOC_CXX_THROW;
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
@@ -269,16 +298,20 @@ JEMALLOC_EXPORT void JEMALLOC_NOTHROW je_malloc_stats_print(
const char *opts);
JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW je_malloc_usable_size(
JEMALLOC_USABLE_SIZE_CONST void *ptr) JEMALLOC_CXX_THROW;
+#ifdef JEMALLOC_HAVE_MALLOC_SIZE
+JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW je_malloc_size(
+ const void *ptr);
+#endif
#ifdef JEMALLOC_OVERRIDE_MEMALIGN
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
- void JEMALLOC_NOTHROW *je_memalign(size_t alignment, size_t size)
+ void JEMALLOC_SYS_NOTHROW *je_memalign(size_t alignment, size_t size)
JEMALLOC_CXX_THROW JEMALLOC_ATTR(malloc);
#endif
#ifdef JEMALLOC_OVERRIDE_VALLOC
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
- void JEMALLOC_NOTHROW *je_valloc(size_t size) JEMALLOC_CXX_THROW
+ void JEMALLOC_SYS_NOTHROW *je_valloc(size_t size) JEMALLOC_CXX_THROW
JEMALLOC_ATTR(malloc);
#endif
@@ -380,11 +413,12 @@ struct extent_hooks_s {
# define mallctlnametomib je_mallctlnametomib
# define malloc je_malloc
# define malloc_conf je_malloc_conf
+# define malloc_conf_2_conf_harder je_malloc_conf_2_conf_harder
# define malloc_message je_malloc_message
# define malloc_stats_print je_malloc_stats_print
# define malloc_usable_size je_malloc_usable_size
# define mallocx je_mallocx
-# define smallocx_ea6b3e973b477b8061e0076bb257dbd7f3faa756 je_smallocx_ea6b3e973b477b8061e0076bb257dbd7f3faa756
+# define smallocx_54eaed1d8b56b1aa528be3bdd1877e59c56fa90c je_smallocx_54eaed1d8b56b1aa528be3bdd1877e59c56fa90c
# define nallocx je_nallocx
# define posix_memalign je_posix_memalign
# define rallocx je_rallocx
@@ -412,11 +446,12 @@ struct extent_hooks_s {
# undef je_mallctlnametomib
# undef je_malloc
# undef je_malloc_conf
+# undef je_malloc_conf_2_conf_harder
# undef je_malloc_message
# undef je_malloc_stats_print
# undef je_malloc_usable_size
# undef je_mallocx
-# undef je_smallocx_ea6b3e973b477b8061e0076bb257dbd7f3faa756
+# undef je_smallocx_54eaed1d8b56b1aa528be3bdd1877e59c56fa90c
# undef je_nallocx
# undef je_posix_memalign
# undef je_rallocx
diff --git a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_FreeBSD.h b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_FreeBSD.h
index e733906bc146..34c86271ab2e 100644
--- a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_FreeBSD.h
+++ b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_FreeBSD.h
@@ -2,6 +2,8 @@
* Override settings that were generated in jemalloc_defs.h as necessary.
*/
+#ifndef JEMALLOC_NO_PRIVATE_NAMESPACE
+
#undef JEMALLOC_OVERRIDE_VALLOC
#ifndef MALLOC_PRODUCTION
@@ -96,8 +98,12 @@
#include <machine/cpufunc.h>
#define CPU_SPINWAIT cpu_spinwait()
-/* Disable lazy-lock machinery, mangle isthreaded, and adjust its type. */
+/*
+ * Disable lazy-lock machinery, redirect isthreaded to libc's flag, undo
+ * jemalloc's namespace stuff for it and adjust its type.
+ */
#undef JEMALLOC_LAZY_LOCK
+#undef isthreaded
extern int __isthreaded;
#define isthreaded ((bool)__isthreaded)
@@ -197,3 +203,5 @@ __sym_compat(sallocm, weak_sallocm, FBSD_1.3);
__sym_compat(dallocm, weak_dallocm, FBSD_1.3);
__sym_compat(nallocm, weak_nallocm, FBSD_1.3);
#endif
+
+#endif
diff --git a/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_defs.h b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_defs.h
new file mode 100644
index 000000000000..fb104aaeec98
--- /dev/null
+++ b/lib/libc/stdlib/malloc/jemalloc/include/jemalloc/jemalloc_defs.h
@@ -0,0 +1,55 @@
+/* include/jemalloc/jemalloc_defs.h. Generated from jemalloc_defs.h.in by configure. */
+/* Defined if __attribute__((...)) syntax is supported. */
+#define JEMALLOC_HAVE_ATTR
+
+/* Defined if alloc_size attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_ALLOC_SIZE
+
+/* Defined if format_arg(...) attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_FORMAT_ARG
+
+/* Defined if format(gnu_printf, ...) attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF
+
+/* Defined if format(printf, ...) attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_FORMAT_PRINTF
+
+/* Defined if fallthrough attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_FALLTHROUGH
+
+/* Defined if cold attribute is supported. */
+#define JEMALLOC_HAVE_ATTR_COLD
+
+/*
+ * Define overrides for non-standard allocator-related functions if they are
+ * present on the system.
+ */
+#define JEMALLOC_OVERRIDE_MEMALIGN
+#define JEMALLOC_OVERRIDE_VALLOC
+
+/*
+ * At least Linux omits the "const" in:
+ *
+ * size_t malloc_usable_size(const void *ptr);
+ *
+ * Match the operating system's prototype.
+ */
+#define JEMALLOC_USABLE_SIZE_CONST const
+
+/*
+ * If defined, specify throw() for the public function prototypes when compiling
+ * with C++. The only justification for this is to match the prototypes that
+ * glibc defines.
+ */
+/* #undef JEMALLOC_USE_CXX_THROW */
+
+#ifdef _MSC_VER
+# ifdef _WIN64
+# define LG_SIZEOF_PTR_WIN 3
+# else
+# define LG_SIZEOF_PTR_WIN 2
+# endif
+#endif
+
+/* sizeof(void *) == 2^LG_SIZEOF_PTR. */
+#define LG_SIZEOF_PTR 3
diff --git a/lib/libc/stdlib/qsort.c b/lib/libc/stdlib/qsort.c
index e0b06494cf98..400124593d07 100644
--- a/lib/libc/stdlib/qsort.c
+++ b/lib/libc/stdlib/qsort.c
@@ -106,13 +106,11 @@ local_qsort(void *a, size_t n, size_t es, cmp_t *cmp, void *thunk)
char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
size_t d1, d2;
int cmp_result;
- int swap_cnt;
/* if there are less than 2 elements, then sorting is not needed */
if (__predict_false(n < 2))
return;
loop:
- swap_cnt = 0;
if (n < 7) {
for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
for (pl = pm;
@@ -141,7 +139,6 @@ loop:
for (;;) {
while (pb <= pc && (cmp_result = CMP(thunk, pb, a)) <= 0) {
if (cmp_result == 0) {
- swap_cnt = 1;
swapfunc(pa, pb, es);
pa += es;
}
@@ -149,7 +146,6 @@ loop:
}
while (pb <= pc && (cmp_result = CMP(thunk, pc, a)) >= 0) {
if (cmp_result == 0) {
- swap_cnt = 1;
swapfunc(pc, pd, es);
pd -= es;
}
@@ -158,18 +154,9 @@ loop:
if (pb > pc)
break;
swapfunc(pb, pc, es);
- swap_cnt = 1;
pb += es;
pc -= es;
}
- if (swap_cnt == 0) { /* Switch to insertion sort */
- for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
- for (pl = pm;
- pl > (char *)a && CMP(thunk, pl - es, pl) > 0;
- pl -= es)
- swapfunc(pl, pl - es, es);
- return;
- }
pn = (char *)a + n * es;
d1 = MIN(pa - (char *)a, pb - pa);
diff --git a/lib/libc/stdlib/reallocarray.3 b/lib/libc/stdlib/reallocarray.3
index 80035c67a497..9a2ab5c7a840 100644
--- a/lib/libc/stdlib/reallocarray.3
+++ b/lib/libc/stdlib/reallocarray.3
@@ -26,7 +26,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 1, 2015
+.Dd October 2, 2025
.Dt REALLOCARRAY 3
.Os
.Sh NAME
@@ -38,6 +38,8 @@
.In stdlib.h
.Ft void *
.Fn reallocarray "void *ptr" "size_t nmemb" "size_t size"
+.Ft void *
+.Fn recallocarray "void *ptr" "size_t oldnmeb" "size_t nmemb" size_t size"
.Sh DESCRIPTION
The
.Fn reallocarray
@@ -52,6 +54,33 @@ and checks for integer overflow in the calculation
.Fa nmemb
*
.Fa size .
+.Pp
+The
+.Fn recallocarray
+function is similar to the
+.Fn reallocarray
+function
+except it ensures newly allocated memory is cleared similar to
+.Fn calloc .
+If
+.Fa ptr
+is
+.Dv NULL ,
+.Fa oldnmemb
+is ignored and the call is equivalent to
+.Fn calloc .
+If
+.Fa ptr
+is not
+.Dv NULL ,
+.Fa oldnmemb
+must be a value such that
+.Fa oldnmemb
+*
+.Fa size
+is the size of the earlier allocation that returned
+.Fa ptr ,
+otherwise the behaviour is undefined.
.Sh RETURN VALUES
The
.Fn reallocarray
@@ -142,3 +171,9 @@ function first appeared in
.Ox 5.6
and
.Fx 11.0 .
+The
+.Fn recallocarray
+function first appeared in
+.Ox 6.1
+and
+.Fx 16.0 .
diff --git a/lib/libc/stdlib/reallocarray.c b/lib/libc/stdlib/reallocarray.c
index 0868804486cc..3632734c84de 100644
--- a/lib/libc/stdlib/reallocarray.c
+++ b/lib/libc/stdlib/reallocarray.c
@@ -17,23 +17,19 @@
#include <sys/types.h>
#include <errno.h>
+#include <stdckdint.h>
#include <stdint.h>
#include <stdlib.h>
-/*
- * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
- * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
- */
-#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
-
void *
reallocarray(void *optr, size_t nmemb, size_t size)
{
+ size_t nbytes;
- if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
- nmemb > 0 && SIZE_MAX / nmemb < size) {
+ if (ckd_mul(&nbytes, nmemb, size)) {
errno = ENOMEM;
return (NULL);
}
- return (realloc(optr, size * nmemb));
+
+ return (realloc(optr, nbytes));
}
diff --git a/lib/libc/stdlib/realpath.3 b/lib/libc/stdlib/realpath.3
index 065ba312c2ef..76f40249963b 100644
--- a/lib/libc/stdlib/realpath.3
+++ b/lib/libc/stdlib/realpath.3
@@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 11, 2012
+.Dd October 10, 2025
.Dt REALPATH 3
.Os
.Sh NAME
@@ -108,11 +108,11 @@ and
.Xr getcwd 3 .
.Sh SEE ALSO
.Xr getcwd 3
-.\" .Sh STANDARDS
-.\" The
-.\" .Fn realpath
-.\" function conforms to
-.\" .St -p1003.1-2001 .
+.Sh STANDARDS
+The
+.Fn realpath
+function conforms to
+.St -p1003.1-2001 .
.Sh HISTORY
The
.Fn realpath
diff --git a/lib/libc/stdlib/realpath.c b/lib/libc/stdlib/realpath.c
index 4c52b73319ab..18f29e95ee6b 100644
--- a/lib/libc/stdlib/realpath.c
+++ b/lib/libc/stdlib/realpath.c
@@ -49,7 +49,7 @@ realpath1(const char *path, char *resolved)
{
struct stat sb;
char *p, *q;
- size_t left_len, resolved_len, next_token_len;
+ size_t left_len, prev_len, resolved_len, next_token_len;
unsigned symlinks;
ssize_t slen;
char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
@@ -98,6 +98,7 @@ realpath1(const char *path, char *resolved)
left_len = 0;
}
+ prev_len = resolved_len;
if (resolved[resolved_len - 1] != '/') {
if (resolved_len + 1 >= PATH_MAX) {
errno = ENAMETOOLONG;
@@ -133,8 +134,17 @@ realpath1(const char *path, char *resolved)
errno = ENAMETOOLONG;
return (NULL);
}
- if (lstat(resolved, &sb) != 0)
+ if (lstat(resolved, &sb) != 0) {
+ /*
+ * EACCES means the parent directory is not
+ * readable, while ENOTDIR means the parent
+ * directory is not a directory. Rewind the path
+ * to correctly indicate where the error lies.
+ */
+ if (errno == EACCES || errno == ENOTDIR)
+ resolved[prev_len] = '\0';
return (NULL);
+ }
if (S_ISLNK(sb.st_mode)) {
if (symlinks++ > MAXSYMLINKS) {
errno = ELOOP;
diff --git a/lib/libc/stdlib/recallocarray.c b/lib/libc/stdlib/recallocarray.c
new file mode 100644
index 000000000000..cbf1fb2470cf
--- /dev/null
+++ b/lib/libc/stdlib/recallocarray.c
@@ -0,0 +1,73 @@
+/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */
+/*
+ * Copyright (c) 2008, 2017 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdckdint.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+void *recallocarray(void *, size_t, size_t, size_t);
+
+void *
+recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
+{
+ size_t oldsize, newsize;
+ void *newptr;
+
+ if (ptr == NULL)
+ return calloc(newnmemb, size);
+
+ if (ckd_mul(&newsize, newnmemb, size)) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (ckd_mul(&oldsize, oldnmemb, size)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /*
+ * Don't bother too much if we're shrinking just a bit,
+ * we do not shrink for series of small steps, oh well.
+ */
+ if (newsize <= oldsize) {
+ size_t d = oldsize - newsize;
+
+ if (d < oldsize / 2 && d < (size_t)getpagesize()) {
+ memset((char *)ptr + newsize, 0, d);
+ return ptr;
+ }
+ }
+
+ newptr = malloc(newsize);
+ if (newptr == NULL)
+ return NULL;
+
+ if (newsize > oldsize) {
+ memcpy(newptr, ptr, oldsize);
+ memset((char *)newptr + oldsize, 0, newsize - oldsize);
+ } else
+ memcpy(newptr, ptr, newsize);
+
+ explicit_bzero(ptr, oldsize);
+ free(ptr);
+
+ return newptr;
+}
diff --git a/lib/libc/stdtime/Makefile.inc b/lib/libc/stdtime/Makefile.inc
index 5d0ce7765b63..1baa39a6c0a6 100644
--- a/lib/libc/stdtime/Makefile.inc
+++ b/lib/libc/stdtime/Makefile.inc
@@ -18,6 +18,7 @@ CFLAGS.${src}+= -I${LIBC_SRCTOP}/stdtime
CFLAGS.localtime.c+= -DALL_STATE -DTHREAD_SAFE
.if ${MK_DETECT_TZ_CHANGES} != "no"
CFLAGS.localtime.c+= -DDETECT_TZ_CHANGES
+CFLAGS.Version.map+= -DDETECT_TZ_CHANGES
.endif
MAN+= ctime.3 strftime.3 strptime.3 time2posix.3 tzset.3
@@ -31,4 +32,5 @@ MLINKS+=strftime.3 strftime_l.3
MLINKS+=strptime.3 strptime_l.3
MLINKS+=time2posix.3 posix2time.3
MLINKS+=tzset.3 daylight.3 \
- tzset.3 timezone.3
+ tzset.3 timezone.3 \
+ tzset.3 tzname.3
diff --git a/lib/libc/stdtime/Symbol.map b/lib/libc/stdtime/Symbol.map
index 669d4d47907b..6a34cd3ea590 100644
--- a/lib/libc/stdtime/Symbol.map
+++ b/lib/libc/stdtime/Symbol.map
@@ -35,3 +35,9 @@ FBSD_1.8 {
daylight;
timezone;
};
+
+FBSDprivate_1.0 {
+#ifdef DETECT_TZ_CHANGES
+ __tz_change_interval;
+#endif
+};
diff --git a/lib/libc/stdtime/ctime.3 b/lib/libc/stdtime/ctime.3
index 96b7f775535a..6384e8bd959b 100644
--- a/lib/libc/stdtime/ctime.3
+++ b/lib/libc/stdtime/ctime.3
@@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd March 26, 2024
+.Dd September 23, 2025
.Dt CTIME 3
.Os
.Sh NAME
@@ -41,6 +41,8 @@
.Nm localtime ,
.Nm localtime_r ,
.Nm mktime ,
+.Nm offtime ,
+.Nm offtime_r ,
.Nm timegm
.Nd transform binary date and time values
.Sh LIBRARY
@@ -68,14 +70,19 @@
.Fn localtime_r "const time_t *clock" "struct tm *result"
.Ft time_t
.Fn mktime "struct tm *tm"
+.Ft struct tm *
+.Fn offtime "const time_t *clock" "long offset"
+.Ft struct tm *
+.Fn offtime_r "const time_t *clock" "long offset" "struct tm *result"
.Ft time_t
.Fn timegm "struct tm *tm"
.Sh DESCRIPTION
The
.Fn ctime ,
.Fn gmtime ,
+.Fn localtime ,
and
-.Fn localtime
+.Fn offtime
functions all take as argument a pointer to a time value representing
the time in seconds since the Epoch (00:00:00 UTC on January 1, 1970;
see
@@ -123,6 +130,18 @@ adjustment, and returns a pointer to a
.Vt struct tm .
.Pp
The
+.Fn offtime
+function similarly converts the time value with a time zone adjustment
+corresponding to the provided
+.Fa offset ,
+which is expressed in seconds, with positive values indicating a time
+zone ahead of UTC (east of the Prime Meridian).
+It does not call
+.Xr tzset 3
+or modify
+.Va tzname .
+.Pp
+The
.Fn ctime
function
adjusts the time value for the current time zone in the same manner as
@@ -155,13 +174,15 @@ except the caller must provide the output buffer
.Fa buf ,
which must be at least 26 characters long, to store the result in.
The
-.Fn localtime_r
+.Fn localtime_r ,
+.Fn gmtime_r ,
and
-.Fn gmtime_r
+.Fn offtime_r
functions provide the same functionality as
-.Fn localtime
+.Fn localtime ,
+.Fn gmtime ,
and
-.Fn gmtime
+.Fn offtime
respectively, except the caller must provide the output buffer
.Fa result .
.Pp
@@ -368,6 +389,12 @@ and
.Fn localtime_r
functions have been available since
.Fx 8.0 .
+The
+.Fn offtime
+and
+.Fn offtime_r
+functions were added in
+.Fx 15.0 .
.Sh BUGS
Except for
.Fn difftime ,
diff --git a/lib/libc/stdtime/strftime.c b/lib/libc/stdtime/strftime.c
index 5943e6387174..89c9e4cf88ff 100644
--- a/lib/libc/stdtime/strftime.c
+++ b/lib/libc/stdtime/strftime.c
@@ -1,4 +1,6 @@
/*
+ * SPDX-License-Identifier: BSD-4.3TAHOE
+ *
* Copyright (c) 1989 The Regents of the University of California.
* All rights reserved.
*
diff --git a/lib/libc/stdtime/strptime.3 b/lib/libc/stdtime/strptime.3
index 7df73d2d080a..9456fa757b85 100644
--- a/lib/libc/stdtime/strptime.3
+++ b/lib/libc/stdtime/strptime.3
@@ -171,7 +171,7 @@ is taken as noon.
The
.Fa %Z
format specifier only accepts time zone abbreviations of the local time zone,
-or the value "GMT".
+and the values "GMT", "UTC", or "Z".
This limitation is because of ambiguity due to of the over loading of time
zone abbreviations.
One such example is
diff --git a/lib/libc/stdtime/strptime.c b/lib/libc/stdtime/strptime.c
index 5f1293c7a267..375e49146639 100644
--- a/lib/libc/stdtime/strptime.c
+++ b/lib/libc/stdtime/strptime.c
@@ -546,7 +546,8 @@ label:
zonestr[cp - buf] = '\0';
tzset();
if (0 == strcmp(zonestr, "GMT") ||
- 0 == strcmp(zonestr, "UTC")) {
+ 0 == strcmp(zonestr, "UTC") ||
+ 0 == strcmp(zonestr, "Z")) {
*GMTp = 1;
} else if (0 == strcmp(zonestr, tzname[0])) {
tm->tm_isdst = 0;
diff --git a/lib/libc/stdtime/tzset.3 b/lib/libc/stdtime/tzset.3
index 94ccbec9aba7..33e6a556306d 100644
--- a/lib/libc/stdtime/tzset.3
+++ b/lib/libc/stdtime/tzset.3
@@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 25, 2023
+.Dd September 30, 2025
.Dt TZSET 3
.Os
.Sh NAME
@@ -58,8 +58,8 @@ specifies how this is done.
.Pp
If
.Ev TZ
-does not appear in the environment, the best available approximation to
-local wall clock time, as specified by the
+does not appear in the environment, the best available approximation
+to local wall clock time, as specified by the
.Xr tzfile 5 Ns -format
file
.Pa /etc/localtime
@@ -68,9 +68,7 @@ is used.
If
.Ev TZ
appears in the environment but its value is a null string, Coordinated
-Universal Time
-.Pq Tn UTC
-is used (without leap second correction).
+Universal Time (UTC) is used (without leap second correction).
.Pp
If
.Ev TZ
@@ -81,13 +79,12 @@ the rest of its value is used as a pathname of a
file from which to read the time conversion information.
If the first character of the pathname is a slash
.Pq Ql /
-it is used as
-an absolute pathname; otherwise, it is used as a pathname relative to
-the system time conversion information directory.
+it is used as an absolute pathname; otherwise, it is used as a
+pathname relative to the system time conversion information directory.
.Pp
-If its value does not begin with a colon, it is first used as the pathname
-of a file (as described above) from which to read the time conversion
-information.
+If its value does not begin with a colon, it is first used as the
+pathname of a file (as described above) from which to read the time
+conversion information.
If that file cannot be read, the value is then interpreted as a direct
specification (the format is described below) of the time conversion
information.
@@ -96,24 +93,23 @@ If the
.Ev TZ
environment variable does not specify a
.Xr tzfile 5 Ns -format
-file and cannot be interpreted as a direct specification,
-.Tn UTC
-is used.
+file and cannot be interpreted as a direct specification, UTC is used.
.Pp
After the first call to
.Nm tzset ,
the
.Vt timezone
-variable is set to the difference, in seconds, between Coordinated Universal
-Time (UTC) and the local time.
+variable is set to the difference, in seconds, between Coordinated
+Universal Time (UTC) and the local time.
The
.Vt daylight
variable is set to 0 if the local timezone is observing standard time.
-It is set to 1 if the local timezone ever observes an alternate time, such as summer time.
+It is set to 1 if the local timezone ever observes an alternate time,
+such as summer time.
The first element of the
.Vt tzname
-array is the timezone name of standard time, while the second element is the
-name of the altnerative time zone.
+array is the timezone name of standard time, while the second element
+is the name of the altnerative time zone.
.Sh SPECIFICATION FORMAT
When
.Ev TZ
@@ -151,8 +147,7 @@ minus
.Pq Ql \- ,
plus
.Pq Ql + ,
-and
-.Tn ASCII
+and ASCII
.Dv NUL
are allowed.
.It Em offset
@@ -294,10 +289,8 @@ by the
file
.Em posixrules
in the system time conversion information directory are used, with the
-standard and summer time offsets from
-.Tn UTC
-replaced by those specified by
-the
+standard and summer time offsets from UTC replaced by those specified
+by the
.Em offset
values in
.Ev TZ .
@@ -315,20 +308,14 @@ local time zone file
.It Pa /usr/share/zoneinfo
time zone directory
.It Pa /usr/share/zoneinfo/posixrules
-rules for
-.Tn POSIX Ns -style
-.Tn TZ Ns 's
+rules for POSIX-style TZs
.It Pa /usr/share/zoneinfo/Etc/GMT
-for
-.Tn UTC
-leap seconds
+for UTC leap seconds
.El
.Pp
If the file
.Pa /usr/share/zoneinfo/UTC
-does not exist,
-.Tn UTC
-leap seconds are loaded from
+does not exist, UTC leap seconds are loaded from
.Pa /usr/share/zoneinfo/posixrules .
.Sh SEE ALSO
.Xr date 1 ,
diff --git a/lib/libc/string/memccpy.3 b/lib/libc/string/memccpy.3
index 2de981449119..0d35a68fce67 100644
--- a/lib/libc/string/memccpy.3
+++ b/lib/libc/string/memccpy.3
@@ -80,7 +80,7 @@ The
function conforms to
.St -p1003.1-2004
and
-.St -isoC-2024 .
+.St -isoC-2023 .
.Sh HISTORY
The
.Fn memccpy
diff --git a/lib/libc/string/memchr.3 b/lib/libc/string/memchr.3
index f5d1fe5d5c7f..c50e932d3382 100644
--- a/lib/libc/string/memchr.3
+++ b/lib/libc/string/memchr.3
@@ -34,7 +34,7 @@
.Os
.Sh NAME
.Nm memchr
-.Nd locate byte in byte string
+.Nd locate byte in memory object
.Sh LIBRARY
.Lb libc
.Sh SYNOPSIS
@@ -51,8 +51,11 @@ locates the first occurrence of
.Fa c
(converted to an
.Vt "unsigned char" )
-in string
-.Fa b .
+in object
+.Fa b ,
+limited to at most
+.Fa len
+characters.
.Pp
The
.Fn memrchr
@@ -60,16 +63,19 @@ function behaves like
.Fn memchr ,
except that it locates the last occurrence of
.Fa c
-in string
-.Fa b .
+in object
+.Fa b ,
+limited to the first
+.Fa len
+characters.
.Sh RETURN VALUES
The
.Fn memchr
and
.Fn memrchr
-functions
-return a pointer to the byte located,
-or NULL if no such byte exists within
+functions return a pointer to the byte located, or
+.Dv NULL
+if no such byte exists within
.Fa len
bytes.
.Sh SEE ALSO
diff --git a/lib/libc/string/memset.3 b/lib/libc/string/memset.3
index f2dba3ec5a48..f6ab9dacb516 100644
--- a/lib/libc/string/memset.3
+++ b/lib/libc/string/memset.3
@@ -141,4 +141,4 @@ conforms to
K.3.7.4.1.
.Fn memset_explicit
conforms to
-.St -isoC-2024 .
+.St -isoC-2023 .
diff --git a/lib/libc/sys/accept.c b/lib/libc/sys/accept.c
index 95c8b4a58c74..c6fb040ab6d6 100644
--- a/lib/libc/sys/accept.c
+++ b/lib/libc/sys/accept.c
@@ -34,8 +34,6 @@
#include <sys/socket.h>
#include "libc_private.h"
-__weak_reference(__sys_accept, __accept);
-
#pragma weak accept
int
accept(int s, struct sockaddr *addr, socklen_t *addrlen)
diff --git a/lib/libc/sys/accept4.c b/lib/libc/sys/accept4.c
index 1a1f75c9f528..146dde9ae6fa 100644
--- a/lib/libc/sys/accept4.c
+++ b/lib/libc/sys/accept4.c
@@ -34,8 +34,6 @@
#include <sys/socket.h>
#include "libc_private.h"
-__weak_reference(__sys_accept4, __accept4);
-
#pragma weak accept4
int
accept4(int s, struct sockaddr *addr, socklen_t *addrlen, int flags)
diff --git a/lib/libc/sys/aio_suspend.c b/lib/libc/sys/aio_suspend.c
index 592cb5b0e1d7..5a7774705ac2 100644
--- a/lib/libc/sys/aio_suspend.c
+++ b/lib/libc/sys/aio_suspend.c
@@ -33,8 +33,6 @@
#include <sys/aio.h>
#include "libc_private.h"
-__weak_reference(__sys_aio_suspend, __aio_suspend);
-
#pragma weak aio_suspend
int
aio_suspend(const struct aiocb * const iocbs[], int niocb,
diff --git a/lib/libc/sys/clock_nanosleep.c b/lib/libc/sys/clock_nanosleep.c
index a50af30e1fb3..db14bd46f85e 100644
--- a/lib/libc/sys/clock_nanosleep.c
+++ b/lib/libc/sys/clock_nanosleep.c
@@ -34,8 +34,6 @@
#include <time.h>
#include "libc_private.h"
-__weak_reference(__sys_clock_nanosleep, __clock_nanosleep);
-
#pragma weak clock_nanosleep
int
clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp,
diff --git a/lib/libc/sys/close.c b/lib/libc/sys/close.c
index 08a5419361a9..8aa2b9b6ad13 100644
--- a/lib/libc/sys/close.c
+++ b/lib/libc/sys/close.c
@@ -34,8 +34,6 @@
#include <unistd.h>
#include "libc_private.h"
-__weak_reference(__sys_close, __close);
-
#pragma weak close
int
close(int fd)
diff --git a/lib/libc/sys/closefrom.c b/lib/libc/sys/closefrom.c
index 292702fad38c..76ed4e744397 100644
--- a/lib/libc/sys/closefrom.c
+++ b/lib/libc/sys/closefrom.c
@@ -26,20 +26,12 @@
*/
#include <sys/param.h>
-#include <sys/syscall.h>
#include <unistd.h>
#include "libc_private.h"
-#define CLOSE_RANGE_OSREL 1300091
-
void
closefrom(int lowfd)
{
-
- if (__getosreldate() >= CLOSE_RANGE_OSREL)
- __sys_close_range(MAX(0, lowfd), ~0U, 0);
- else
- /* Fallback to closefrom(2) on older kernels. */
- syscall(SYS_freebsd12_closefrom, lowfd);
+ __sys_close_range(MAX(0, lowfd), ~0U, 0);
}
diff --git a/lib/libc/sys/connect.c b/lib/libc/sys/connect.c
index e64451683abc..f27813b63df0 100644
--- a/lib/libc/sys/connect.c
+++ b/lib/libc/sys/connect.c
@@ -34,8 +34,6 @@
#include <sys/socket.h>
#include "libc_private.h"
-__weak_reference(__sys_connect, __connect);
-
#pragma weak connect
int
connect(int s, const struct sockaddr *addr, socklen_t addrlen)
diff --git a/lib/libc/sys/fork.c b/lib/libc/sys/fork.c
index e907c51b5cee..3af81c14ffb3 100644
--- a/lib/libc/sys/fork.c
+++ b/lib/libc/sys/fork.c
@@ -33,8 +33,6 @@
#include <unistd.h>
#include "libc_private.h"
-__weak_reference(__sys_fork, __fork);
-
#pragma weak fork
pid_t
fork(void)
diff --git a/lib/libc/sys/fsync.c b/lib/libc/sys/fsync.c
index e542c8cdea48..c58b29ca6a77 100644
--- a/lib/libc/sys/fsync.c
+++ b/lib/libc/sys/fsync.c
@@ -34,8 +34,6 @@
#include <unistd.h>
#include "libc_private.h"
-__weak_reference(__sys_fsync, __fsync);
-
int
fsync(int fd)
{
diff --git a/lib/libc/sys/kevent.c b/lib/libc/sys/kevent.c
index f16ac89a36c6..e395e16c67f5 100644
--- a/lib/libc/sys/kevent.c
+++ b/lib/libc/sys/kevent.c
@@ -34,8 +34,6 @@
#include <sys/time.h>
#include "libc_private.h"
-__weak_reference(__sys_kevent, __kevent);
-
#pragma weak kevent
int
kevent(int kq, const struct kevent *changelist, int nchanges,
diff --git a/lib/libc/sys/msync.c b/lib/libc/sys/msync.c
index f825db97b947..11dd988300eb 100644
--- a/lib/libc/sys/msync.c
+++ b/lib/libc/sys/msync.c
@@ -34,8 +34,6 @@
#include <sys/mman.h>
#include "libc_private.h"
-__weak_reference(__sys_msync, __msync);
-
#pragma weak msync
int
msync(void *addr, size_t len, int flags)
diff --git a/lib/libc/sys/nanosleep.c b/lib/libc/sys/nanosleep.c
index bd0794c3af8e..23c07e3bf5fc 100644
--- a/lib/libc/sys/nanosleep.c
+++ b/lib/libc/sys/nanosleep.c
@@ -33,8 +33,6 @@
#include <time.h>
#include "libc_private.h"
-__weak_reference(__sys_nanosleep, __nanosleep);
-
#pragma weak nanosleep
int
nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
diff --git a/lib/libc/sys/open.c b/lib/libc/sys/open.c
index 57c07637d2aa..dd7bedebf141 100644
--- a/lib/libc/sys/open.c
+++ b/lib/libc/sys/open.c
@@ -34,8 +34,6 @@
#include <stdarg.h>
#include "libc_private.h"
-__weak_reference(__sys_open, __open);
-
#pragma weak open
int
open(const char *path, int flags, ...)
diff --git a/lib/libc/sys/openat.c b/lib/libc/sys/openat.c
index 1cd8e7d90745..ba937cae3a3e 100644
--- a/lib/libc/sys/openat.c
+++ b/lib/libc/sys/openat.c
@@ -34,7 +34,6 @@
#include <stdarg.h>
#include "libc_private.h"
-__weak_reference(__sys_openat, __openat);
__sym_compat(openat, __impl_openat, FBSD_1.1);
__weak_reference(openat, __impl_openat);
__sym_default(openat, openat, FBSD_1.2);
diff --git a/lib/libc/sys/poll.c b/lib/libc/sys/poll.c
index 5e000f7e728f..8c6a89adb2cb 100644
--- a/lib/libc/sys/poll.c
+++ b/lib/libc/sys/poll.c
@@ -34,8 +34,6 @@
#include <ssp/ssp.h>
#include "libc_private.h"
-__weak_reference(__sys_poll, __poll);
-
int __weak_symbol
__ssp_real(poll)(struct pollfd pfd[], nfds_t nfds, int timeout)
{
diff --git a/lib/libc/sys/ppoll.c b/lib/libc/sys/ppoll.c
index 72c0b5d5e421..5ffc35a8cbb0 100644
--- a/lib/libc/sys/ppoll.c
+++ b/lib/libc/sys/ppoll.c
@@ -34,8 +34,6 @@
#include <ssp/ssp.h>
#include "libc_private.h"
-__weak_reference(__sys_ppoll, __ppoll);
-
int __weak_symbol
__ssp_real(ppoll)(struct pollfd pfd[], nfds_t nfds,
const struct timespec *__restrict timeout,
diff --git a/lib/libc/sys/pselect.c b/lib/libc/sys/pselect.c
index 38477e39af44..2c1c9896d6b9 100644
--- a/lib/libc/sys/pselect.c
+++ b/lib/libc/sys/pselect.c
@@ -33,8 +33,6 @@
#include <sys/select.h>
#include "libc_private.h"
-__weak_reference(__sys_pselect, __pselect);
-
#pragma weak pselect
int
pselect(int n, fd_set *rs, fd_set *ws, fd_set *es, const struct timespec *t,
diff --git a/lib/libc/sys/read.c b/lib/libc/sys/read.c
index b4610e0c38c3..75cc7e42b013 100644
--- a/lib/libc/sys/read.c
+++ b/lib/libc/sys/read.c
@@ -34,8 +34,6 @@
#include <unistd.h>
#include "libc_private.h"
-__weak_reference(__sys_read, __read);
-
#pragma weak read
ssize_t
read(int fd, void *buf, size_t nbytes)
diff --git a/lib/libc/sys/readv.c b/lib/libc/sys/readv.c
index 2729f6ed99da..83b4bf9ba3e6 100644
--- a/lib/libc/sys/readv.c
+++ b/lib/libc/sys/readv.c
@@ -36,8 +36,6 @@
#include <ssp/ssp.h>
#include "libc_private.h"
-__weak_reference(__sys_readv, __readv);
-
ssize_t __weak_symbol
__ssp_real(readv)(int fd, const struct iovec *iov, int iovcnt)
{
diff --git a/lib/libc/sys/recvfrom.c b/lib/libc/sys/recvfrom.c
index 0cdbd8a4ccaa..f7e2dd2ca6c8 100644
--- a/lib/libc/sys/recvfrom.c
+++ b/lib/libc/sys/recvfrom.c
@@ -35,8 +35,6 @@
#include <ssp/ssp.h>
#include "libc_private.h"
-__weak_reference(__sys_recvfrom, __recvfrom);
-
ssize_t __weak_symbol
__ssp_real(recvfrom)(int s, void *buf, size_t len, int flags,
struct sockaddr * __restrict from, socklen_t * __restrict fromlen)
diff --git a/lib/libc/sys/recvmsg.c b/lib/libc/sys/recvmsg.c
index ce0f5e0478cd..7c820c47b325 100644
--- a/lib/libc/sys/recvmsg.c
+++ b/lib/libc/sys/recvmsg.c
@@ -35,8 +35,6 @@
#include <ssp/ssp.h>
#include "libc_private.h"
-__weak_reference(__sys_recvmsg, __recvmsg);
-
ssize_t __weak_symbol
__ssp_real(recvmsg)(int s, struct msghdr *msg, int flags)
{
diff --git a/lib/libc/sys/select.c b/lib/libc/sys/select.c
index bbbcfc8bf1e0..909449f17da8 100644
--- a/lib/libc/sys/select.c
+++ b/lib/libc/sys/select.c
@@ -33,8 +33,6 @@
#include <sys/select.h>
#include "libc_private.h"
-__weak_reference(__sys_select, __select);
-
#pragma weak select
int
select(int n, fd_set *rs, fd_set *ws, fd_set *es, struct timeval *t)
diff --git a/lib/libc/sys/sendmsg.c b/lib/libc/sys/sendmsg.c
index fd4e41837c48..6c5fb4021ce3 100644
--- a/lib/libc/sys/sendmsg.c
+++ b/lib/libc/sys/sendmsg.c
@@ -34,8 +34,6 @@
#include <sys/socket.h>
#include "libc_private.h"
-__weak_reference(__sys_sendmsg, __sendmsg);
-
#pragma weak sendmsg
ssize_t
sendmsg(int s, const struct msghdr *msg, int flags)
diff --git a/lib/libc/sys/sendto.c b/lib/libc/sys/sendto.c
index 708fc6ec9368..b8a173f434a9 100644
--- a/lib/libc/sys/sendto.c
+++ b/lib/libc/sys/sendto.c
@@ -34,8 +34,6 @@
#include <sys/socket.h>
#include "libc_private.h"
-__weak_reference(__sys_sendto, __sendto);
-
#pragma weak sendto
ssize_t
sendto(int s, const void *msg, size_t len, int flags,
diff --git a/lib/libc/sys/setcontext.c b/lib/libc/sys/setcontext.c
index 96b5dc115067..a750c4dfea30 100644
--- a/lib/libc/sys/setcontext.c
+++ b/lib/libc/sys/setcontext.c
@@ -33,7 +33,6 @@
#include <ucontext.h>
#include "libc_private.h"
-__weak_reference(__sys_setcontext, __setcontext);
__sym_compat(setcontext, __impl_setcontext, FBSD_1.0);
__weak_reference(setcontext, __impl_setcontext);
__sym_default(setcontext, setcontext, FBSD_1.2);
diff --git a/lib/libc/sys/sigaction.c b/lib/libc/sys/sigaction.c
index 809881ca4d3f..7ce177895c81 100644
--- a/lib/libc/sys/sigaction.c
+++ b/lib/libc/sys/sigaction.c
@@ -33,7 +33,6 @@
#include <signal.h>
#include "libc_private.h"
-__weak_reference(__sys_sigaction, __sigaction);
__weak_reference(sigaction, __libc_sigaction);
#pragma weak sigaction
diff --git a/lib/libc/sys/sigprocmask.c b/lib/libc/sys/sigprocmask.c
index 96cb9586509b..8b7fb6fb8e74 100644
--- a/lib/libc/sys/sigprocmask.c
+++ b/lib/libc/sys/sigprocmask.c
@@ -33,7 +33,6 @@
#include <signal.h>
#include "libc_private.h"
-__weak_reference(__sys_sigprocmask, __sigprocmask);
__weak_reference(sigprocmask, __libc_sigprocmask);
#pragma weak sigprocmask
diff --git a/lib/libc/sys/sigsuspend.c b/lib/libc/sys/sigsuspend.c
index e5a9495f9f39..ac117650ed1b 100644
--- a/lib/libc/sys/sigsuspend.c
+++ b/lib/libc/sys/sigsuspend.c
@@ -33,7 +33,6 @@
#include <signal.h>
#include "libc_private.h"
-__weak_reference(__sys_sigsuspend, __sigsuspend);
__weak_reference(sigsuspend, __libc_sigsuspend);
#pragma weak sigsuspend
diff --git a/lib/libc/sys/sigtimedwait.c b/lib/libc/sys/sigtimedwait.c
index 839e5fe4d992..4efa69555f39 100644
--- a/lib/libc/sys/sigtimedwait.c
+++ b/lib/libc/sys/sigtimedwait.c
@@ -33,8 +33,6 @@
#include <signal.h>
#include "libc_private.h"
-__weak_reference(__sys_sigtimedwait, __sigtimedwait);
-
#pragma weak sigtimedwait
int
sigtimedwait(const sigset_t * __restrict set, siginfo_t * __restrict info,
diff --git a/lib/libc/sys/sigwaitinfo.c b/lib/libc/sys/sigwaitinfo.c
index 611d09c3c221..e94777b731e6 100644
--- a/lib/libc/sys/sigwaitinfo.c
+++ b/lib/libc/sys/sigwaitinfo.c
@@ -33,8 +33,6 @@
#include <signal.h>
#include "libc_private.h"
-__weak_reference(__sys_sigwaitinfo, __sigwaitinfo);
-
#pragma weak sigwaitinfo
int
sigwaitinfo(const sigset_t * __restrict set, siginfo_t * __restrict info)
diff --git a/lib/libc/sys/swapcontext.c b/lib/libc/sys/swapcontext.c
index b69a5b19e698..69d4dfea16b5 100644
--- a/lib/libc/sys/swapcontext.c
+++ b/lib/libc/sys/swapcontext.c
@@ -35,7 +35,6 @@
#include <stddef.h>
#include "libc_private.h"
-__weak_reference(__sys_swapcontext, __swapcontext);
__sym_compat(swapcontext, __impl_swapcontext, FBSD_1.0);
__weak_reference(swapcontext, __impl_swapcontext);
__sym_default(swapcontext, swapcontext, FBSD_1.2);
diff --git a/lib/libc/sys/wait4.c b/lib/libc/sys/wait4.c
index 57b0071ef87f..1d30c2d44c19 100644
--- a/lib/libc/sys/wait4.c
+++ b/lib/libc/sys/wait4.c
@@ -33,8 +33,6 @@
#include <sys/wait.h>
#include "libc_private.h"
-__weak_reference(__sys_wait4, __wait4);
-
#pragma weak wait4
pid_t
wait4(pid_t pid, int *status, int options, struct rusage *ru)
diff --git a/lib/libc/sys/wait6.c b/lib/libc/sys/wait6.c
index 118a7ef6b99e..70817bdb441c 100644
--- a/lib/libc/sys/wait6.c
+++ b/lib/libc/sys/wait6.c
@@ -34,8 +34,6 @@
#include <signal.h>
#include "libc_private.h"
-__weak_reference(__sys_wait6, __wait6);
-
#pragma weak wait6
pid_t
wait6(idtype_t idtype, id_t id, int *status, int options, struct __wrusage *ru,
diff --git a/lib/libc/sys/write.c b/lib/libc/sys/write.c
index 5880856b8af8..15e2d22fdbc5 100644
--- a/lib/libc/sys/write.c
+++ b/lib/libc/sys/write.c
@@ -34,8 +34,6 @@
#include <unistd.h>
#include "libc_private.h"
-__weak_reference(__sys_write, __write);
-
#pragma weak write
ssize_t
write(int fd, const void *buf, size_t nbytes)
diff --git a/lib/libc/sys/writev.c b/lib/libc/sys/writev.c
index 9a32cb603ab8..b642c6af9a89 100644
--- a/lib/libc/sys/writev.c
+++ b/lib/libc/sys/writev.c
@@ -35,8 +35,6 @@
#include <unistd.h>
#include "libc_private.h"
-__weak_reference(__sys_writev, __writev);
-
#pragma weak writev
ssize_t
writev(int fd, const struct iovec *iov, int iovcnt)
diff --git a/lib/libc/tests/db/Makefile b/lib/libc/tests/db/Makefile
index f1f33bd2bafc..771569183584 100644
--- a/lib/libc/tests/db/Makefile
+++ b/lib/libc/tests/db/Makefile
@@ -7,6 +7,10 @@ PROGS+= h_lfsr
${PACKAGE}FILES+= README
+ATF_TESTS_C+= dbm_open_test
+ATF_TESTS_C+= dbm_perm_test
+ATF_TESTS_C+= dbm_nextkey_test
+
NETBSD_ATF_TESTS_C+= db_hash_seq_test
NETBSD_ATF_TESTS_SH+= db_test
ATF_TESTS_SH_SED_db_test= -e 's,/bin/csh,/bin/cat,g'
diff --git a/lib/libc/tests/db/dbm_nextkey_test.c b/lib/libc/tests/db/dbm_nextkey_test.c
new file mode 100644
index 000000000000..67b745efb196
--- /dev/null
+++ b/lib/libc/tests/db/dbm_nextkey_test.c
@@ -0,0 +1,53 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fcntl.h>
+#include <ndbm.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+static const char *path = "tmp";
+static const char *dbname = "tmp.db";
+
+ATF_TC(dbm_nextkey_test);
+ATF_TC_HEAD(dbm_nextkey_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Check that dbm_nextkey always returns NULL after reaching the end of the database");
+}
+
+ATF_TC_BODY(dbm_nextkey_test, tc)
+{
+ DBM *db;
+ datum key, data;
+
+ data.dptr = "bar";
+ data.dsize = strlen("bar");
+ key.dptr = "foo";
+ key.dsize = strlen("foo");
+
+ db = dbm_open(path, O_RDWR | O_CREAT, 0755);
+ ATF_CHECK(db != NULL);
+ ATF_REQUIRE(atf_utils_file_exists(dbname));
+ ATF_REQUIRE(dbm_store(db, key, data, DBM_INSERT) != -1);
+
+ key = dbm_firstkey(db);
+ ATF_REQUIRE(key.dptr != NULL);
+ key = dbm_nextkey(db);
+ ATF_REQUIRE(key.dptr == NULL);
+ key = dbm_nextkey(db);
+ ATF_REQUIRE(key.dptr == NULL);
+
+ dbm_close(db);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, dbm_nextkey_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/db/dbm_open_test.c b/lib/libc/tests/db/dbm_open_test.c
new file mode 100644
index 000000000000..8a3e888bf72c
--- /dev/null
+++ b/lib/libc/tests/db/dbm_open_test.c
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fcntl.h>
+#include <ndbm.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+static const char *path = "tmp";
+static const char *dbname = "tmp.db";
+
+ATF_TC(dbm_open_missing_test);
+ATF_TC_HEAD(dbm_open_missing_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test dbm_open when creating a new database");
+}
+
+ATF_TC_BODY(dbm_open_missing_test, tc)
+{
+
+ /*
+ * POSIX.1 specifies that a missing database file should
+ * always get created if O_CREAT is present, except when
+ * O_EXCL is specified as well.
+ */
+ ATF_CHECK(dbm_open(path, O_RDONLY, 0755) == NULL);
+ ATF_REQUIRE(!atf_utils_file_exists(dbname));
+ ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT, 0755) != NULL);
+ ATF_REQUIRE(atf_utils_file_exists(dbname));
+ ATF_CHECK(dbm_open(path, O_RDONLY | O_CREAT | O_EXCL, 0755) == NULL);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_open_wronly_test);
+ATF_TC_BODY(dbm_open_wronly_test, tc)
+{
+ ATF_CHECK(dbm_open(path, O_WRONLY, 0755) == NULL);
+ ATF_REQUIRE(!atf_utils_file_exists(dbname));
+ ATF_CHECK(dbm_open(path, O_WRONLY | O_CREAT, 0755) != NULL);
+ ATF_REQUIRE(atf_utils_file_exists(dbname));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, dbm_open_missing_test);
+ ATF_TP_ADD_TC(tp, dbm_open_wronly_test);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/db/dbm_perm_test.c b/lib/libc/tests/db/dbm_perm_test.c
new file mode 100644
index 000000000000..c07210292014
--- /dev/null
+++ b/lib/libc/tests/db/dbm_perm_test.c
@@ -0,0 +1,98 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <ndbm.h>
+#include <stdio.h>
+
+#include <atf-c.h>
+
+static const char *path = "tmp";
+static const char *dbname = "tmp.db";
+
+static void
+create_db(void)
+{
+ DB *db;
+ datum data, key;
+
+ data.dptr = "bar";
+ data.dsize = strlen("bar");
+ key.dptr = "foo";
+ key.dsize = strlen("foo");
+
+ db = dbm_open(path, O_RDWR | O_CREAT, 0755);
+ ATF_CHECK(db != NULL);
+ ATF_REQUIRE(atf_utils_file_exists(dbname));
+ ATF_REQUIRE(dbm_store(db, key, data, DBM_INSERT) != -1);
+ dbm_close(db);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_rdonly_test);
+ATF_TC_BODY(dbm_rdonly_test, tc)
+{
+ DB *db;
+ datum data, key;
+
+ bzero(&data, sizeof(data));
+ key.dptr = "foo";
+ key.dsize = strlen("foo");
+ create_db();
+
+ db = dbm_open(path, O_RDONLY, 0755);
+ data = dbm_fetch(db, key);
+ ATF_REQUIRE(data.dptr != NULL);
+ ATF_REQUIRE(strncmp((const char*)data.dptr, "bar", data.dsize) == 0);
+ ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) == -1);
+ ATF_REQUIRE(errno == EPERM);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_wronly_test);
+ATF_TC_BODY(dbm_wronly_test, tc)
+{
+ DB *db;
+ datum data, key;
+
+ key.dptr = "foo";
+ key.dsize = strlen("foo");
+ data.dptr = "baz";
+ data.dsize = strlen("baz");
+ create_db();
+
+ db = dbm_open(path, O_WRONLY, 0755);
+ data = dbm_fetch(db, key);
+ ATF_REQUIRE(data.dptr == NULL);
+ ATF_REQUIRE(errno == EPERM);
+ ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1);
+}
+
+ATF_TC_WITHOUT_HEAD(dbm_rdwr_test);
+ATF_TC_BODY(dbm_rdwr_test, tc)
+{
+ DB *db;
+ datum data, key;
+
+ key.dptr = "foo";
+ key.dsize = strlen("foo");
+ create_db();
+
+ db = dbm_open(path, O_RDWR, 0755);
+ data = dbm_fetch(db, key);
+ ATF_REQUIRE(data.dptr != NULL);
+ data.dptr = "baz";
+ data.dsize = strlen("baz");
+ ATF_REQUIRE(dbm_store(db, key, data, DBM_REPLACE) != -1);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, dbm_rdonly_test);
+ ATF_TP_ADD_TC(tp, dbm_wronly_test);
+ ATF_TP_ADD_TC(tp, dbm_rdwr_test);
+
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile
index 87f2b8320d25..8c2151105209 100644
--- a/lib/libc/tests/gen/Makefile
+++ b/lib/libc/tests/gen/Makefile
@@ -7,14 +7,28 @@ ATF_TESTS_C+= fmtcheck2_test
ATF_TESTS_C+= fmtmsg_test
ATF_TESTS_C+= fnmatch2_test
ATF_TESTS_C+= fpclassify2_test
+.if ${COMPILER_FEATURES:Mblocks}
+ATF_TESTS_C+= fts_blocks_test
+.endif
+ATF_TESTS_C+= fts_misc_test
+ATF_TESTS_C+= fts_options_test
ATF_TESTS_C+= ftw_test
ATF_TESTS_C+= getentropy_test
ATF_TESTS_C+= getmntinfo_test
ATF_TESTS_C+= glob2_test
+.if ${COMPILER_FEATURES:Mblocks}
+ATF_TESTS_C+= glob_blocks_test
+.endif
ATF_TESTS_C+= makecontext_test
+ATF_TESTS_C+= opendir_test
ATF_TESTS_C+= popen_test
ATF_TESTS_C+= posix_spawn_test
ATF_TESTS_C+= realpath2_test
+ATF_TESTS_C+= scandir_test
+.if ${COMPILER_FEATURES:Mblocks}
+ATF_TESTS_C+= scandir_blocks_test
+.endif
+ATF_TESTS_C+= sig2str_test
ATF_TESTS_C+= sigsetops_test
ATF_TESTS_C+= wordexp_test
@@ -92,6 +106,20 @@ TEST_METADATA.setdomainname_test+= is_exclusive=true
TESTS_SUBDIRS= execve
TESTS_SUBDIRS+= posix_spawn
+# Tests that require address sanitizer
+.if ${COMPILER_FEATURES:Masan}
+.for t in scandir_test realpath2_test
+CFLAGS.${t}.c+= -fsanitize=address
+LDFLAGS.${t}+= -fsanitize=address
+.endfor
+.endif
+
+# Tests that require blocks support
+.for t in fts_blocks_test glob_blocks_test scandir_blocks_test
+CFLAGS.${t}.c+= -fblocks
+LIBADD.${t}+= BlocksRuntime
+.endfor
+
# The old testcase name
TEST_FNMATCH= test-fnmatch
CLEANFILES+= ${GEN_SH_CASE_TESTCASES}
diff --git a/lib/libc/tests/gen/fnmatch_test.c b/lib/libc/tests/gen/fnmatch_test.c
index 6cdf5f2a05fa..0ff7400a4a4f 100644
--- a/lib/libc/tests/gen/fnmatch_test.c
+++ b/lib/libc/tests/gen/fnmatch_test.c
@@ -26,6 +26,7 @@
#include <sys/param.h>
#include <errno.h>
+#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -176,10 +177,90 @@ ATF_TC_BODY(fnmatch_test, tc)
}
+ATF_TC(fnmatch_characterclass);
+ATF_TC_HEAD(fnmatch_characterclass, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test fnmatch with character classes");
+}
+
+ATF_TC_BODY(fnmatch_characterclass, tc)
+{
+ ATF_CHECK(fnmatch("[[:alnum:]]", "a", 0) == 0);
+ ATF_CHECK(fnmatch("[[:cntrl:]]", "\a", 0) == 0);
+ ATF_CHECK(fnmatch("[[:lower:]]", "a", 0) == 0);
+ ATF_CHECK(fnmatch("[[:space:]]", " ", 0) == 0);
+ ATF_CHECK(fnmatch("[[:alpha:]]", "a", 0) == 0);
+ ATF_CHECK(fnmatch("[[:digit:]]", "0", 0) == 0);
+ ATF_CHECK(fnmatch("[[:print:]]", "a", 0) == 0);
+ ATF_CHECK(fnmatch("[[:upper:]]", "A", 0) == 0);
+ ATF_CHECK(fnmatch("[[:blank:]]", " ", 0) == 0);
+ ATF_CHECK(fnmatch("[[:graph:]]", "a", 0) == 0);
+ ATF_CHECK(fnmatch("[[:punct:]]", ".", 0) == 0);
+ ATF_CHECK(fnmatch("[[:xdigit:]]", "f", 0) == 0);
+
+ /*
+ * POSIX.1, section 9.3.5. states that '[:' and ':]'
+ * should be interpreted as character classes symbol only
+ * when part of a bracket expression.
+ */
+ ATF_CHECK(fnmatch("[:alnum:]", "a", 0) == 0);
+ ATF_CHECK(fnmatch("[:alnum:]", ":", 0) == 0);
+ ATF_CHECK(fnmatch("[:alnum:]", "1", 0) != 0);
+}
+
+ATF_TC(fnmatch_collsym);
+ATF_TC_HEAD(fnmatch_collsym, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test fnmatch with collating symbols");
+}
+
+ATF_TC_BODY(fnmatch_collsym, tc)
+{
+ setlocale(LC_ALL, "cs_CZ.UTF-8");
+ ATF_CHECK(fnmatch("[ch]", "ch", 0) != 0);
+ ATF_CHECK(fnmatch("[[.ch.]]", "ch", 0) == 0);
+ ATF_CHECK(fnmatch("[[.ch.]]h", "chh", 0) == 0);
+
+ /*
+ * POSIX.1, section 9.3.5. states that '[.' and '.]'
+ * should be interpreted as a collating symbol only
+ * when part of a bracket expression.
+ */
+ ATF_CHECK(fnmatch("[.ch.]", "c", 0) == 0);
+ ATF_CHECK(fnmatch("[.ch.]", "h", 0) == 0);
+ ATF_CHECK(fnmatch("[.ch.]", ".", 0) == 0);
+}
+
+ATF_TC(fnmatch_equivclass);
+ATF_TC_HEAD(fnmatch_equivclass, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test fnmatch with equivalence classes");
+}
+
+ATF_TC_BODY(fnmatch_equivclass, tc)
+{
+ setlocale(LC_ALL, "en_US.UTF-8");
+ ATF_CHECK(fnmatch("[[=a=]]b", "ab", 0) == 0);
+ ATF_CHECK(fnmatch("[[=a=]]b", "Ab", 0) == 0);
+ ATF_CHECK(fnmatch("[[=à=]]b", "ab", 0) == 0);
+ ATF_CHECK(fnmatch("[[=a=]]b", "àb", 0) == 0);
+
+ /*
+ * POSIX.1, section 9.3.5. states that '[=' and '=]'
+ * should be interpreted as an equivalence class only
+ * when part of a bracket expression.
+ */
+ ATF_CHECK(fnmatch("[=a=]b", "=b", 0) == 0);
+ ATF_CHECK(fnmatch("[=a=]b", "ab", 0) == 0);
+}
+
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, fnmatch_test);
+ ATF_TP_ADD_TC(tp, fnmatch_collsym);
+ ATF_TP_ADD_TC(tp, fnmatch_characterclass);
+ ATF_TP_ADD_TC(tp, fnmatch_equivclass);
return (atf_no_error());
}
diff --git a/lib/libc/tests/gen/fts_blocks_test.c b/lib/libc/tests/gen/fts_blocks_test.c
new file mode 100644
index 000000000000..f020dd8dea45
--- /dev/null
+++ b/lib/libc/tests/gen/fts_blocks_test.c
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <fts.h>
+
+#include <atf-c.h>
+
+/*
+ * Create two directories with three files each in lexicographical order,
+ * then call FTS with a sort block that sorts in reverse lexicographical
+ * order. This has the least chance of getting a false positive due to
+ * differing file system semantics. UFS will return the files in the
+ * order they were created while ZFS will sort them lexicographically; in
+ * both cases, the order we expect is the reverse.
+ */
+ATF_TC(fts_blocks_test);
+ATF_TC_HEAD(fts_blocks_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test FTS with a block in lieu of a comparison function");
+}
+ATF_TC_BODY(fts_blocks_test, tc)
+{
+ char *args[] = {
+ "bar", "foo", NULL
+ };
+ char *paths[] = {
+ "foo", "z", "y", "x", "foo",
+ "bar", "c", "b", "a", "bar",
+ NULL
+ };
+ char **expect = paths;
+ FTS *fts;
+ FTSENT *ftse;
+
+ ATF_REQUIRE_EQ(0, mkdir("bar", 0755));
+ ATF_REQUIRE_EQ(0, close(creat("bar/a", 0644)));
+ ATF_REQUIRE_EQ(0, close(creat("bar/b", 0644)));
+ ATF_REQUIRE_EQ(0, close(creat("bar/c", 0644)));
+ ATF_REQUIRE_EQ(0, mkdir("foo", 0755));
+ ATF_REQUIRE_EQ(0, close(creat("foo/x", 0644)));
+ ATF_REQUIRE_EQ(0, close(creat("foo/y", 0644)));
+ ATF_REQUIRE_EQ(0, close(creat("foo/z", 0644)));
+ fts = fts_open_b(args, 0,
+ ^(const FTSENT * const *a, const FTSENT * const *b) {
+ return (strcmp((*b)->fts_name, (*a)->fts_name));
+ });
+ ATF_REQUIRE_MSG(fts != NULL, "fts_open_b(): %m");
+ while ((ftse = fts_read(fts)) != NULL && *expect != NULL) {
+ ATF_CHECK_STREQ(*expect, ftse->fts_name);
+ expect++;
+ }
+ ATF_CHECK_EQ(NULL, ftse);
+ ATF_CHECK_EQ(NULL, *expect);
+ ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, fts_blocks_test);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/fts_misc_test.c b/lib/libc/tests/gen/fts_misc_test.c
new file mode 100644
index 000000000000..91640078f63c
--- /dev/null
+++ b/lib/libc/tests/gen/fts_misc_test.c
@@ -0,0 +1,78 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <fts.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#include "fts_test.h"
+
+ATF_TC(fts_unrdir);
+ATF_TC_HEAD(fts_unrdir, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "unreadable directories");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(fts_unrdir, tc)
+{
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100));
+ ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400));
+ fts_test(tc, &(struct fts_testcase){
+ (char *[]){ "dir", NULL },
+ FTS_PHYSICAL,
+ (struct fts_expect[]){
+ { FTS_D, "dir", "dir" },
+ { FTS_D, "unr", "unr" },
+ { FTS_DNR, "unr", "unr" },
+ { FTS_D, "unx", "unx" },
+ { FTS_DP, "unx", "unx" },
+ { FTS_DP, "dir", "dir" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_unrdir_nochdir);
+ATF_TC_HEAD(fts_unrdir_nochdir, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "unreadable directories (nochdir)");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(fts_unrdir_nochdir, tc)
+{
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("dir/unr", 0100));
+ ATF_REQUIRE_EQ(0, mkdir("dir/unx", 0400));
+ fts_test(tc, &(struct fts_testcase){
+ (char *[]){ "dir", NULL },
+ FTS_PHYSICAL | FTS_NOCHDIR,
+ (struct fts_expect[]){
+ { FTS_D, "dir", "dir" },
+ { FTS_D, "unr", "dir/unr" },
+ { FTS_DNR, "unr", "dir/unr" },
+ { FTS_D, "unx", "dir/unx" },
+ { FTS_DP, "unx", "dir/unx" },
+ { FTS_DP, "dir", "dir" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ fts_check_debug();
+ ATF_TP_ADD_TC(tp, fts_unrdir);
+ ATF_TP_ADD_TC(tp, fts_unrdir_nochdir);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/fts_options_test.c b/lib/libc/tests/gen/fts_options_test.c
new file mode 100644
index 000000000000..fc3015138a49
--- /dev/null
+++ b/lib/libc/tests/gen/fts_options_test.c
@@ -0,0 +1,394 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <fts.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+#include "fts_test.h"
+
+static char *all_paths[] = {
+ "dir",
+ "dirl",
+ "file",
+ "filel",
+ "dead",
+ "noent",
+ NULL
+};
+
+/*
+ * Prepare the files and directories we will be inspecting.
+ */
+static void
+fts_options_prepare(const struct atf_tc *tc)
+{
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ ATF_REQUIRE_EQ(0, close(creat("file", 0644)));
+ ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
+ ATF_REQUIRE_EQ(0, symlink("..", "dir/up"));
+ ATF_REQUIRE_EQ(0, symlink("dir", "dirl"));
+ ATF_REQUIRE_EQ(0, symlink("file", "filel"));
+ ATF_REQUIRE_EQ(0, symlink("noent", "dead"));
+}
+
+ATF_TC(fts_options_logical);
+ATF_TC_HEAD(fts_options_logical, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL");
+}
+ATF_TC_BODY(fts_options_logical, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_LOGICAL,
+ (struct fts_expect[]){
+ { FTS_DL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_F, "file", "dir/file" },
+ { FTS_D, "up", "dir/up" },
+ { FTS_DL, "dead", "dir/up/dead" },
+ { FTS_DC, "dir", "dir/up/dir" },
+ { FTS_DC, "dirl", "dir/up/dirl" },
+ { FTS_F, "file", "dir/up/file" },
+ { FTS_F, "filel", "dir/up/filel" },
+ { FTS_DP, "up", "dir/up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_D, "dirl", "dirl" },
+ { FTS_F, "file", "dirl/file" },
+ { FTS_D, "up", "dirl/up" },
+ { FTS_DL, "dead", "dirl/up/dead" },
+ { FTS_DC, "dir", "dirl/up/dir" },
+ { FTS_DC, "dirl", "dirl/up/dirl" },
+ { FTS_F, "file", "dirl/up/file" },
+ { FTS_F, "filel", "dirl/up/filel" },
+ { FTS_DP, "up", "dirl/up" },
+ { FTS_DP, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_F, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_logical_nostat);
+ATF_TC_HEAD(fts_options_logical_nostat, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_NOSTAT");
+}
+ATF_TC_BODY(fts_options_logical_nostat, tc)
+{
+ /*
+ * While FTS_LOGICAL is not documented as being incompatible with
+ * FTS_NOSTAT, and FTS does not clear FTS_NOSTAT if FTS_LOGICAL is
+ * set, FTS_LOGICAL effectively nullifies FTS_NOSTAT by overriding
+ * the follow check in fts_stat(). In theory, FTS could easily be
+ * changed to only stat links (to check what they point to) in the
+ * FTS_LOGICAL | FTS_NOSTAT case, which would produce a different
+ * result here, so keep the test around in case that ever happens.
+ */
+ atf_tc_expect_fail("FTS_LOGICAL nullifies FTS_NOSTAT");
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_LOGICAL | FTS_NOSTAT,
+ (struct fts_expect[]){
+ { FTS_DL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_NSOK, "file", "dir/file" },
+ { FTS_D, "up", "dir/up" },
+ { FTS_DL, "dead", "dir/up/dead" },
+ { FTS_DC, "dir", "dir/up/dir" },
+ { FTS_DC, "dirl", "dir/up/dirl" },
+ { FTS_NSOK, "file", "dir/up/file" },
+ { FTS_NSOK, "filel", "dir/up/filel" },
+ { FTS_DP, "up", "dir/up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_D, "dirl", "dirl" },
+ { FTS_NSOK, "file", "dirl/file" },
+ { FTS_D, "up", "dirl/up" },
+ { FTS_DL, "dead", "dirl/up/dead" },
+ { FTS_DC, "dir", "dirl/up/dir" },
+ { FTS_DC, "dirl", "dirl/up/dirl" },
+ { FTS_NSOK, "file", "dirl/up/file" },
+ { FTS_NSOK, "filel", "dirl/up/filel" },
+ { FTS_DP, "up", "dirl/up" },
+ { FTS_DP, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_F, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_logical_seedot);
+ATF_TC_HEAD(fts_options_logical_seedot, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_LOGICAL | FTS_SEEDOT");
+}
+ATF_TC_BODY(fts_options_logical_seedot, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_LOGICAL | FTS_SEEDOT,
+ (struct fts_expect[]){
+ { FTS_DL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_DOT, ".", "dir/." },
+ { FTS_DOT, "..", "dir/.." },
+ { FTS_F, "file", "dir/file" },
+ { FTS_D, "up", "dir/up" },
+ { FTS_DOT, ".", "dir/up/." },
+ { FTS_DOT, "..", "dir/up/.." },
+ { FTS_DL, "dead", "dir/up/dead" },
+ { FTS_DC, "dir", "dir/up/dir" },
+ { FTS_DC, "dirl", "dir/up/dirl" },
+ { FTS_F, "file", "dir/up/file" },
+ { FTS_F, "filel", "dir/up/filel" },
+ { FTS_DP, "up", "dir/up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_D, "dirl", "dirl" },
+ { FTS_DOT, ".", "dirl/." },
+ { FTS_DOT, "..", "dirl/.." },
+ { FTS_F, "file", "dirl/file" },
+ { FTS_D, "up", "dirl/up" },
+ { FTS_DOT, ".", "dirl/up/." },
+ { FTS_DOT, "..", "dirl/up/.." },
+ { FTS_DL, "dead", "dirl/up/dead" },
+ { FTS_DC, "dir", "dirl/up/dir" },
+ { FTS_DC, "dirl", "dirl/up/dirl" },
+ { FTS_F, "file", "dirl/up/file" },
+ { FTS_F, "filel", "dirl/up/filel" },
+ { FTS_DP, "up", "dirl/up" },
+ { FTS_DP, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_F, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_physical);
+ATF_TC_HEAD(fts_options_physical, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL");
+}
+ATF_TC_BODY(fts_options_physical, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_PHYSICAL,
+ (struct fts_expect[]){
+ { FTS_SL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "up", "up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_SL, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_physical_nochdir);
+ATF_TC_HEAD(fts_options_physical_nochdir, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOCHDIR");
+}
+ATF_TC_BODY(fts_options_physical_nochdir, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_PHYSICAL | FTS_NOCHDIR,
+ (struct fts_expect[]){
+ { FTS_SL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_F, "file", "dir/file" },
+ { FTS_SL, "up", "dir/up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_SL, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_physical_comfollow);
+ATF_TC_HEAD(fts_options_physical_comfollow, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOW");
+}
+ATF_TC_BODY(fts_options_physical_comfollow, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_PHYSICAL | FTS_COMFOLLOW,
+ (struct fts_expect[]){
+ { FTS_DL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "up", "up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_D, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "up", "up" },
+ { FTS_DP, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_F, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_physical_comfollowdir);
+ATF_TC_HEAD(fts_options_physical_comfollowdir, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_COMFOLLOWDIR");
+}
+ATF_TC_BODY(fts_options_physical_comfollowdir, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_PHYSICAL | FTS_COMFOLLOWDIR,
+ (struct fts_expect[]){
+ { FTS_DL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "up", "up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_D, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "up", "up" },
+ { FTS_DP, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_physical_nostat);
+ATF_TC_HEAD(fts_options_physical_nostat, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT");
+}
+ATF_TC_BODY(fts_options_physical_nostat, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_PHYSICAL | FTS_NOSTAT,
+ (struct fts_expect[]){
+ { FTS_SL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_NSOK, "file", "file" },
+ { FTS_NSOK, "up", "up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_SL, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_physical_nostat_type);
+ATF_TC_HEAD(fts_options_physical_nostat_type, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_NOSTAT_TYPE");
+}
+ATF_TC_BODY(fts_options_physical_nostat_type, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_PHYSICAL | FTS_NOSTAT_TYPE,
+ (struct fts_expect[]){
+ { FTS_SL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "up", "up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_SL, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+ATF_TC(fts_options_physical_seedot);
+ATF_TC_HEAD(fts_options_physical_seedot, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "FTS_PHYSICAL | FTS_SEEDOT");
+}
+ATF_TC_BODY(fts_options_physical_seedot, tc)
+{
+ fts_options_prepare(tc);
+ fts_test(tc, &(struct fts_testcase){
+ all_paths,
+ FTS_PHYSICAL | FTS_SEEDOT,
+ (struct fts_expect[]){
+ { FTS_SL, "dead", "dead" },
+ { FTS_D, "dir", "dir" },
+ { FTS_DOT, ".", "." },
+ { FTS_DOT, "..", ".." },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "up", "up" },
+ { FTS_DP, "dir", "dir" },
+ { FTS_SL, "dirl", "dirl" },
+ { FTS_F, "file", "file" },
+ { FTS_SL, "filel", "filel" },
+ { FTS_NS, "noent", "noent" },
+ { 0 }
+ },
+ });
+}
+
+/*
+ * TODO: Add tests for FTS_XDEV and FTS_WHITEOUT
+ */
+
+ATF_TP_ADD_TCS(tp)
+{
+ fts_check_debug();
+ ATF_TP_ADD_TC(tp, fts_options_logical);
+ ATF_TP_ADD_TC(tp, fts_options_logical_nostat);
+ ATF_TP_ADD_TC(tp, fts_options_logical_seedot);
+ ATF_TP_ADD_TC(tp, fts_options_physical);
+ ATF_TP_ADD_TC(tp, fts_options_physical_nochdir);
+ ATF_TP_ADD_TC(tp, fts_options_physical_comfollow);
+ ATF_TP_ADD_TC(tp, fts_options_physical_comfollowdir);
+ ATF_TP_ADD_TC(tp, fts_options_physical_nostat);
+ ATF_TP_ADD_TC(tp, fts_options_physical_nostat_type);
+ ATF_TP_ADD_TC(tp, fts_options_physical_seedot);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/fts_test.h b/lib/libc/tests/gen/fts_test.h
new file mode 100644
index 000000000000..b3f15050f265
--- /dev/null
+++ b/lib/libc/tests/gen/fts_test.h
@@ -0,0 +1,81 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef FTS_TEST_H_INCLUDED
+#define FTS_TEST_H_INCLUDED
+
+struct fts_expect {
+ int fts_info;
+ const char *fts_name;
+ const char *fts_accpath;
+};
+
+struct fts_testcase {
+ char **paths;
+ int fts_options;
+ struct fts_expect *fts_expect;
+};
+
+/* shorter name for dead links */
+#define FTS_DL FTS_SLNONE
+
+/* are we being debugged? */
+static bool fts_test_debug;
+
+/*
+ * Set debug flag if appropriate.
+ */
+static void
+fts_check_debug(void)
+{
+ fts_test_debug = !getenv("__RUNNING_INSIDE_ATF_RUN") &&
+ isatty(STDERR_FILENO);
+}
+
+/*
+ * Lexical order for reproducability.
+ */
+static int
+fts_lexical_compar(const FTSENT * const *a, const FTSENT * const *b)
+{
+ return (strcmp((*a)->fts_name, (*b)->fts_name));
+}
+
+/*
+ * Run FTS with the specified paths and options and verify that it
+ * produces the expected result in the correct order.
+ */
+static void
+fts_test(const struct atf_tc *tc, const struct fts_testcase *fts_tc)
+{
+ FTS *fts;
+ FTSENT *ftse;
+ const struct fts_expect *expect = fts_tc->fts_expect;
+ long level = 0;
+
+ fts = fts_open(fts_tc->paths, fts_tc->fts_options, fts_lexical_compar);
+ ATF_REQUIRE_MSG(fts != NULL, "fts_open(): %m");
+ while ((ftse = fts_read(fts)) != NULL && expect->fts_name != NULL) {
+ if (expect->fts_info == FTS_DP || expect->fts_info == FTS_DNR)
+ level--;
+ if (fts_test_debug) {
+ fprintf(stderr, "%2ld %2d %s\n", level,
+ ftse->fts_info, ftse->fts_name);
+ }
+ ATF_CHECK_STREQ(expect->fts_name, ftse->fts_name);
+ ATF_CHECK_STREQ(expect->fts_accpath, ftse->fts_accpath);
+ ATF_CHECK_INTEQ(expect->fts_info, ftse->fts_info);
+ ATF_CHECK_INTEQ(level, ftse->fts_level);
+ if (expect->fts_info == FTS_D)
+ level++;
+ expect++;
+ }
+ ATF_CHECK_EQ(NULL, ftse);
+ ATF_CHECK_EQ(NULL, expect->fts_name);
+ ATF_REQUIRE_EQ_MSG(0, fts_close(fts), "fts_close(): %m");
+}
+
+#endif /* FTS_TEST_H_INCLUDED */
diff --git a/lib/libc/tests/gen/glob2_test.c b/lib/libc/tests/gen/glob2_test.c
index 8f69c36fece7..ff1b36b830b8 100644
--- a/lib/libc/tests/gen/glob2_test.c
+++ b/lib/libc/tests/gen/glob2_test.c
@@ -25,9 +25,12 @@
*/
#include <sys/param.h>
+#include <sys/stat.h>
+
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -102,10 +105,80 @@ ATF_TC_BODY(glob_pathological_test, tc)
}
}
+ATF_TC(glob_period);
+ATF_TC_HEAD(glob_period, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test behaviour when matching files that start with a period"
+ "(documented in the glob(3) CAVEATS section).");
+}
+ATF_TC_BODY(glob_period, tc)
+{
+ int i;
+ glob_t g;
+
+ atf_utils_create_file(".test", "");
+ glob(".", 0, NULL, &g);
+ ATF_REQUIRE_MSG(g.gl_matchc == 1,
+ "glob(3) shouldn't match files starting with a period when using '.'");
+ for (i = 0; i < g.gl_matchc; i++)
+ printf("%s\n", g.gl_pathv[i]);
+ glob(".*", 0, NULL, &g);
+ ATF_REQUIRE_MSG(g.gl_matchc == 3 && strcmp(g.gl_pathv[2], ".test") == 0,
+ "glob(3) should match files starting with a period when using '.*'");
+}
+
+static bool glob_callback_invoked;
+
+static int
+errfunc(const char *path, int err)
+{
+ ATF_CHECK_STREQ(path, "test/");
+ ATF_CHECK(err == EACCES);
+ glob_callback_invoked = true;
+ /* Suppress EACCES errors. */
+ return (0);
+}
+
+ATF_TC(glob_callback);
+ATF_TC_HEAD(glob_callback, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test ability of callback function to suppress errors");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(glob_callback, tc)
+{
+ glob_t g;
+ int rv;
+
+ ATF_REQUIRE_EQ(0, mkdir("test", 0755));
+ ATF_REQUIRE_EQ(0, symlink("foo", "test/foo"));
+ ATF_REQUIRE_EQ(0, chmod("test", 0));
+
+ glob_callback_invoked = false;
+ rv = glob("test/*", 0, errfunc, &g);
+ ATF_CHECK_MSG(glob_callback_invoked,
+ "glob(3) failed to invoke callback function");
+ ATF_CHECK_EQ_MSG(GLOB_NOMATCH, rv,
+ "callback function failed to suppress EACCES");
+ globfree(&g);
+
+ /* GLOB_ERR should ignore the suppressed error. */
+ glob_callback_invoked = false;
+ rv = glob("test/*", GLOB_ERR, errfunc, &g);
+ ATF_CHECK_MSG(glob_callback_invoked,
+ "glob(3) failed to invoke callback function");
+ ATF_CHECK_EQ_MSG(GLOB_ABORTED, rv,
+ "GLOB_ERR didn't override callback function");
+ globfree(&g);
+}
+
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, glob_pathological_test);
-
+ ATF_TP_ADD_TC(tp, glob_period);
+ ATF_TP_ADD_TC(tp, glob_callback);
return (atf_no_error());
}
diff --git a/lib/libc/tests/gen/glob_blocks_test.c b/lib/libc/tests/gen/glob_blocks_test.c
new file mode 100644
index 000000000000..629b90bee762
--- /dev/null
+++ b/lib/libc/tests/gen/glob_blocks_test.c
@@ -0,0 +1,62 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <glob.h>
+#include <stdbool.h>
+
+#include <atf-c.h>
+
+ATF_TC(glob_b_callback);
+ATF_TC_HEAD(glob_b_callback, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test ability of callback block to suppress errors");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(glob_b_callback, tc)
+{
+ static bool glob_callback_invoked;
+ static int (^errblk)(const char *, int) =
+ ^(const char *path, int err) {
+ ATF_CHECK_STREQ(path, "test/");
+ ATF_CHECK(err == EACCES);
+ glob_callback_invoked = true;
+ /* Suppress EACCES errors. */
+ return (0);
+ };
+ glob_t g;
+ int rv;
+
+ ATF_REQUIRE_EQ(0, mkdir("test", 0755));
+ ATF_REQUIRE_EQ(0, symlink("foo", "test/foo"));
+ ATF_REQUIRE_EQ(0, chmod("test", 0));
+
+ glob_callback_invoked = false;
+ rv = glob_b("test/*", 0, errblk, &g);
+ ATF_CHECK_MSG(glob_callback_invoked,
+ "glob(3) failed to invoke callback block");
+ ATF_CHECK_EQ_MSG(GLOB_NOMATCH, rv,
+ "callback function failed to suppress EACCES");
+ globfree(&g);
+
+ /* GLOB_ERR should ignore the suppressed error. */
+ glob_callback_invoked = false;
+ rv = glob_b("test/*", GLOB_ERR, errblk, &g);
+ ATF_CHECK_MSG(glob_callback_invoked,
+ "glob(3) failed to invoke callback block");
+ ATF_CHECK_EQ_MSG(GLOB_ABORTED, rv,
+ "GLOB_ERR didn't override callback block");
+ globfree(&g);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, glob_b_callback);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/opendir_test.c b/lib/libc/tests/gen/opendir_test.c
new file mode 100644
index 000000000000..b7481255654f
--- /dev/null
+++ b/lib/libc/tests/gen/opendir_test.c
@@ -0,0 +1,145 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+/*
+ * Create a directory with a single subdirectory.
+ */
+static void
+opendir_prepare(const struct atf_tc *tc)
+{
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("dir/subdir", 0755));
+}
+
+/*
+ * Assuming dirp represents the directory created by opendir_prepare(),
+ * verify that readdir() returns what we expected to see there.
+ */
+static void
+opendir_check(const struct atf_tc *tc, DIR *dirp)
+{
+ struct dirent *ent;
+
+ ATF_REQUIRE((ent = readdir(dirp)) != NULL);
+ ATF_CHECK_EQ(1, ent->d_namlen);
+ ATF_CHECK_STREQ(".", ent->d_name);
+ ATF_CHECK_EQ(DT_DIR, ent->d_type);
+ ATF_REQUIRE((ent = readdir(dirp)) != NULL);
+ ATF_CHECK_EQ(2, ent->d_namlen);
+ ATF_CHECK_STREQ("..", ent->d_name);
+ ATF_CHECK_EQ(DT_DIR, ent->d_type);
+ ATF_REQUIRE((ent = readdir(dirp)) != NULL);
+ ATF_CHECK_EQ(sizeof("subdir") - 1, ent->d_namlen);
+ ATF_CHECK_STREQ("subdir", ent->d_name);
+ ATF_CHECK_EQ(DT_DIR, ent->d_type);
+ ATF_CHECK(readdir(dirp) == NULL);
+ ATF_CHECK(readdir(dirp) == NULL);
+}
+
+ATF_TC(opendir_ok);
+ATF_TC_HEAD(opendir_ok, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Open a directory.");
+}
+ATF_TC_BODY(opendir_ok, tc)
+{
+ DIR *dirp;
+
+ opendir_prepare(tc);
+ ATF_REQUIRE((dirp = opendir("dir")) != NULL);
+ opendir_check(tc, dirp);
+ ATF_CHECK_EQ(0, closedir(dirp));
+}
+
+ATF_TC(opendir_fifo);
+ATF_TC_HEAD(opendir_fifo, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Do not hang if given a named pipe.");
+}
+ATF_TC_BODY(opendir_fifo, tc)
+{
+ DIR *dirp;
+ int fd;
+
+ ATF_REQUIRE((fd = mkfifo("fifo", 0644)) >= 0);
+ ATF_REQUIRE_EQ(0, close(fd));
+ ATF_REQUIRE((dirp = opendir("fifo")) == NULL);
+ ATF_CHECK_EQ(ENOTDIR, errno);
+}
+
+ATF_TC(fdopendir_ok);
+ATF_TC_HEAD(fdopendir_ok, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Open a directory from a directory descriptor.");
+}
+ATF_TC_BODY(fdopendir_ok, tc)
+{
+ DIR *dirp;
+ int dd;
+
+ opendir_prepare(tc);
+ ATF_REQUIRE((dd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
+ ATF_REQUIRE((dirp = fdopendir(dd)) != NULL);
+ opendir_check(tc, dirp);
+ ATF_CHECK_EQ(dd, fdclosedir(dirp));
+ ATF_CHECK_EQ(0, close(dd));
+}
+
+ATF_TC(fdopendir_ebadf);
+ATF_TC_HEAD(fdopendir_ebadf, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Open a directory from an invalid descriptor.");
+}
+ATF_TC_BODY(fdopendir_ebadf, tc)
+{
+ DIR *dirp;
+ int dd;
+
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ ATF_REQUIRE((dd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
+ ATF_CHECK_EQ(0, close(dd));
+ ATF_REQUIRE((dirp = fdopendir(dd)) == NULL);
+ ATF_CHECK_EQ(EBADF, errno);
+}
+
+ATF_TC(fdopendir_enotdir);
+ATF_TC_HEAD(fdopendir_enotdir, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Open a directory from a non-directory descriptor.");
+}
+ATF_TC_BODY(fdopendir_enotdir, tc)
+{
+ DIR *dirp;
+ int fd;
+
+ ATF_REQUIRE((fd = open("file", O_CREAT | O_RDWR, 0644)) >= 0);
+ ATF_REQUIRE((dirp = fdopendir(fd)) == NULL);
+ ATF_CHECK_EQ(ENOTDIR, errno);
+ ATF_CHECK_EQ(0, close(fd));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, opendir_ok);
+ ATF_TP_ADD_TC(tp, fdopendir_ok);
+ ATF_TP_ADD_TC(tp, fdopendir_ebadf);
+ ATF_TP_ADD_TC(tp, fdopendir_enotdir);
+ ATF_TP_ADD_TC(tp, opendir_fifo);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/posix_spawn_test.c b/lib/libc/tests/gen/posix_spawn_test.c
index 77c3b5a569d9..22133cf1d59a 100644
--- a/lib/libc/tests/gen/posix_spawn_test.c
+++ b/lib/libc/tests/gen/posix_spawn_test.c
@@ -29,8 +29,11 @@
* IEEE Std. 1003.1-2008.
*/
+#include <sys/param.h>
+#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
+#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -38,6 +41,10 @@
#include <atf-c.h>
+static const char true_script[] =
+ "#!/usr/bin/env\n"
+ "/usr/bin/true\n";
+
char *myenv[2] = { "answer=42", NULL };
ATF_TC_WITHOUT_HEAD(posix_spawn_simple_test);
@@ -124,6 +131,48 @@ ATF_TC_BODY(posix_spawnp_enoexec_fallback_null_argv0, tc)
ATF_REQUIRE(error == EINVAL);
}
+ATF_TC(posix_spawnp_eacces);
+ATF_TC_HEAD(posix_spawnp_eacces, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Verify EACCES behavior in posix_spawnp");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+ATF_TC_BODY(posix_spawnp_eacces, tc)
+{
+ const struct spawnp_eacces_tc {
+ const char *pathvar;
+ int error_expected;
+ } spawnp_eacces_tests[] = {
+ { ".", EACCES }, /* File exists, but not +x */
+ { "unsearchable", ENOENT }, /* File exists, dir not +x */
+ };
+ char *myargs[2] = { "eacces", NULL };
+ int error;
+
+ error = mkdir("unsearchable", 0755);
+ ATF_REQUIRE(error == 0);
+ error = symlink("/usr/bin/true", "unsearchable/eacces");
+ ATF_REQUIRE(error == 0);
+
+ (void)chmod("unsearchable", 0444);
+
+ /* this will create a non-executable file */
+ atf_utils_create_file("eacces", true_script);
+
+ for (size_t i = 0; i < nitems(spawnp_eacces_tests); i++) {
+ const struct spawnp_eacces_tc *tc = &spawnp_eacces_tests[i];
+ pid_t pid;
+
+ error = setenv("PATH", tc->pathvar, 1);
+ ATF_REQUIRE_EQ(0, error);
+
+ error = posix_spawnp(&pid, myargs[0], NULL, NULL, myargs,
+ myenv);
+ ATF_CHECK_INTEQ_MSG(tc->error_expected, error,
+ "path '%s'", tc->pathvar);
+ }
+}
+
ATF_TP_ADD_TCS(tp)
{
@@ -131,6 +180,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, posix_spawn_no_such_command_negative_test);
ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback);
ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback_null_argv0);
+ ATF_TP_ADD_TC(tp, posix_spawnp_eacces);
return (atf_no_error());
}
diff --git a/lib/libc/tests/gen/realpath2_test.c b/lib/libc/tests/gen/realpath2_test.c
index f89dd99cbb72..431df8721ae0 100644
--- a/lib/libc/tests/gen/realpath2_test.c
+++ b/lib/libc/tests/gen/realpath2_test.c
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2017 Jan Kokemüller
* All rights reserved.
+ * Copyright (c) 2025 Klara, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -25,6 +26,8 @@
*/
#include <sys/param.h>
+#include <sys/stat.h>
+
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
@@ -34,6 +37,31 @@
#include <atf-c.h>
+ATF_TC(realpath_null);
+ATF_TC_HEAD(realpath_null, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test null input");
+}
+ATF_TC_BODY(realpath_null, tc)
+{
+ ATF_REQUIRE_ERRNO(EINVAL, realpath(NULL, NULL) == NULL);
+}
+
+ATF_TC(realpath_empty);
+ATF_TC_HEAD(realpath_empty, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test empty input");
+}
+ATF_TC_BODY(realpath_empty, tc)
+{
+ char resb[PATH_MAX] = "";
+
+ ATF_REQUIRE_EQ(0, mkdir("foo", 0755));
+ ATF_REQUIRE_EQ(0, chdir("foo"));
+ ATF_REQUIRE_ERRNO(ENOENT, realpath("", resb) == NULL);
+ ATF_REQUIRE_STREQ("", resb);
+}
+
ATF_TC(realpath_buffer_overflow);
ATF_TC_HEAD(realpath_buffer_overflow, tc)
{
@@ -44,16 +72,11 @@ ATF_TC_HEAD(realpath_buffer_overflow, tc)
ATF_TC_BODY(realpath_buffer_overflow, tc)
{
- char path[MAXPATHLEN] = { 0 };
- char resb[MAXPATHLEN] = { 0 };
- size_t i;
+ char path[PATH_MAX] = "";
+ char resb[PATH_MAX] = "";
- path[0] = 'a';
+ memset(path, 'a', sizeof(path) - 1);
path[1] = '/';
- for (i = 2; i < sizeof(path) - 1; ++i) {
- path[i] = 'a';
- }
-
ATF_REQUIRE(realpath(path, resb) == NULL);
}
@@ -66,9 +89,9 @@ ATF_TC_HEAD(realpath_empty_symlink, tc)
ATF_TC_BODY(realpath_empty_symlink, tc)
{
- char path[MAXPATHLEN] = { 0 };
- char slnk[MAXPATHLEN] = { 0 };
- char resb[MAXPATHLEN] = { 0 };
+ char path[PATH_MAX] = "";
+ char slnk[PATH_MAX] = "";
+ char resb[PATH_MAX] = "";
int fd;
(void)strlcat(slnk, "empty_symlink", sizeof(slnk));
@@ -89,11 +112,70 @@ ATF_TC_BODY(realpath_empty_symlink, tc)
ATF_REQUIRE(unlink(slnk) == 0);
}
-ATF_TP_ADD_TCS(tp)
+ATF_TC(realpath_partial);
+ATF_TC_HEAD(realpath_partial, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test that failure leaves a partial result");
+ atf_tc_set_md_var(tc, "require.user", "unprivileged");
+}
+
+ATF_TC_BODY(realpath_partial, tc)
{
+ char resb[PATH_MAX] = "";
+ size_t len;
+
+ /* scenario 1: missing directory */
+ ATF_REQUIRE_EQ(0, mkdir("foo", 0755));
+ ATF_REQUIRE_ERRNO(ENOENT, realpath("foo/bar/baz", resb) == NULL);
+ len = strnlen(resb, sizeof(resb));
+ ATF_REQUIRE(len > 8 && len < sizeof(resb));
+ ATF_REQUIRE_STREQ("/foo/bar", resb + len - 8);
+
+ /* scenario 2: dead link 1 */
+ ATF_REQUIRE_EQ(0, symlink("nix", "foo/bar"));
+ ATF_REQUIRE_ERRNO(ENOENT, realpath("foo/bar/baz", resb) == NULL);
+ len = strnlen(resb, sizeof(resb));
+ ATF_REQUIRE(len > 8 && len < sizeof(resb));
+ ATF_REQUIRE_STREQ("/foo/nix", resb + len - 8);
+
+ /* scenario 3: missing file */
+ ATF_REQUIRE_EQ(0, unlink("foo/bar"));
+ ATF_REQUIRE_EQ(0, mkdir("foo/bar", 0755));
+ ATF_REQUIRE_ERRNO(ENOENT, realpath("foo/bar/baz", resb) == NULL);
+ len = strnlen(resb, sizeof(resb));
+ ATF_REQUIRE(len > 12 && len < sizeof(resb));
+ ATF_REQUIRE_STREQ("/foo/bar/baz", resb + len - 12);
+
+ /* scenario 4: dead link 2 */
+ ATF_REQUIRE_EQ(0, symlink("nix", "foo/bar/baz"));
+ ATF_REQUIRE_ERRNO(ENOENT, realpath("foo/bar/baz", resb) == NULL);
+ len = strnlen(resb, sizeof(resb));
+ ATF_REQUIRE(len > 12 && len < sizeof(resb));
+ ATF_REQUIRE_STREQ("/foo/bar/nix", resb + len - 12);
+
+ /* scenario 5: unreadable directory */
+ ATF_REQUIRE_EQ(0, chmod("foo", 000));
+ ATF_REQUIRE_ERRNO(EACCES, realpath("foo/bar/baz", resb) == NULL);
+ len = strnlen(resb, sizeof(resb));
+ ATF_REQUIRE(len > 4 && len < sizeof(resb));
+ ATF_REQUIRE_STREQ("/foo", resb + len - 4);
+
+ /* scenario 6: not a directory */
+ ATF_REQUIRE_EQ(0, close(creat("bar", 0644)));
+ ATF_REQUIRE_ERRNO(ENOTDIR, realpath("bar/baz", resb) == NULL);
+ len = strnlen(resb, sizeof(resb));
+ ATF_REQUIRE(len > 4 && len < sizeof(resb));
+ ATF_REQUIRE_STREQ("/bar", resb + len - 4);
+}
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, realpath_null);
+ ATF_TP_ADD_TC(tp, realpath_empty);
ATF_TP_ADD_TC(tp, realpath_buffer_overflow);
ATF_TP_ADD_TC(tp, realpath_empty_symlink);
+ ATF_TP_ADD_TC(tp, realpath_partial);
return atf_no_error();
}
diff --git a/lib/libc/tests/gen/scandir_blocks_test.c b/lib/libc/tests/gen/scandir_blocks_test.c
new file mode 100644
index 000000000000..b94270bc410e
--- /dev/null
+++ b/lib/libc/tests/gen/scandir_blocks_test.c
@@ -0,0 +1,118 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+static void
+scandir_blocks_prepare(const struct atf_tc *tc)
+{
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("dir/dir", 0755));
+ ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
+ ATF_REQUIRE_EQ(0, symlink("file", "dir/link"));
+ ATF_REQUIRE_EQ(0, mkdir("dir/skip", 0755));
+}
+
+static void
+scandir_blocks_verify(const struct atf_tc *tc, int n, struct dirent **namelist)
+{
+ ATF_REQUIRE_EQ_MSG(5, n, "return value is %d", n);
+ ATF_CHECK_STREQ("link", namelist[0]->d_name);
+ ATF_CHECK_STREQ("file", namelist[1]->d_name);
+ ATF_CHECK_STREQ("dir", namelist[2]->d_name);
+ ATF_CHECK_STREQ("..", namelist[3]->d_name);
+ ATF_CHECK_STREQ(".", namelist[4]->d_name);
+}
+
+ATF_TC(scandir_b_test);
+ATF_TC_HEAD(scandir_b_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test scandir_b()");
+}
+ATF_TC_BODY(scandir_b_test, tc)
+{
+ struct dirent **namelist = NULL;
+ int i, ret;
+
+ scandir_blocks_prepare(tc);
+ ret = scandir_b("dir", &namelist,
+ ^(const struct dirent *ent) {
+ return (strcmp(ent->d_name, "skip") != 0);
+ },
+ ^(const struct dirent **a, const struct dirent **b) {
+ return (strcmp((*b)->d_name, (*a)->d_name));
+ });
+ scandir_blocks_verify(tc, ret, namelist);
+ for (i = 0; i < ret; i++)
+ free(namelist[i]);
+ free(namelist);
+}
+
+ATF_TC(fdscandir_b_test);
+ATF_TC_HEAD(fdscandir_b_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test fdscandir_b()");
+}
+ATF_TC_BODY(fdscandir_b_test, tc)
+{
+ struct dirent **namelist = NULL;
+ int fd, i, ret;
+
+ scandir_blocks_prepare(tc);
+ ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
+ ret = fdscandir_b(fd, &namelist,
+ ^(const struct dirent *ent) {
+ return (strcmp(ent->d_name, "skip") != 0);
+ },
+ ^(const struct dirent **a, const struct dirent **b) {
+ return (strcmp((*b)->d_name, (*a)->d_name));
+ });
+ scandir_blocks_verify(tc, ret, namelist);
+ for (i = 0; i < ret; i++)
+ free(namelist[i]);
+ free(namelist);
+ ATF_REQUIRE_EQ(0, close(fd));
+}
+
+ATF_TC(scandirat_b_test);
+ATF_TC_HEAD(scandirat_b_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test scandirat_b()");
+}
+ATF_TC_BODY(scandirat_b_test, tc)
+{
+ struct dirent **namelist = NULL;
+ int fd, i, ret;
+
+ scandir_blocks_prepare(tc);
+ ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_SEARCH)) >= 0);
+ ret = scandirat_b(fd, ".", &namelist,
+ ^(const struct dirent *ent) {
+ return (strcmp(ent->d_name, "skip") != 0);
+ },
+ ^(const struct dirent **a, const struct dirent **b) {
+ return (strcmp((*b)->d_name, (*a)->d_name));
+ });
+ scandir_blocks_verify(tc, ret, namelist);
+ for (i = 0; i < ret; i++)
+ free(namelist[i]);
+ free(namelist);
+ ATF_REQUIRE_EQ(0, close(fd));
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, scandir_b_test);
+ ATF_TP_ADD_TC(tp, fdscandir_b_test);
+ ATF_TP_ADD_TC(tp, scandirat_b_test);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/scandir_test.c b/lib/libc/tests/gen/scandir_test.c
new file mode 100644
index 000000000000..afd25bf7c0b2
--- /dev/null
+++ b/lib/libc/tests/gen/scandir_test.c
@@ -0,0 +1,195 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+static void
+scandir_prepare(const struct atf_tc *tc)
+{
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("dir/dir", 0755));
+ ATF_REQUIRE_EQ(0, close(creat("dir/file", 0644)));
+ ATF_REQUIRE_EQ(0, symlink("file", "dir/link"));
+ ATF_REQUIRE_EQ(0, mkdir("dir/skip", 0755));
+}
+
+static void
+scandir_verify(const struct atf_tc *tc, int n, struct dirent **namelist)
+{
+ ATF_REQUIRE_EQ_MSG(5, n, "return value is %d", n);
+ ATF_CHECK_STREQ("link", namelist[0]->d_name);
+ ATF_CHECK_STREQ("file", namelist[1]->d_name);
+ ATF_CHECK_STREQ("dir", namelist[2]->d_name);
+ ATF_CHECK_STREQ("..", namelist[3]->d_name);
+ ATF_CHECK_STREQ(".", namelist[4]->d_name);
+}
+
+static int
+scandir_select(const struct dirent *ent)
+{
+ return (strcmp(ent->d_name, "skip") != 0);
+}
+
+static int
+scandir_compare(const struct dirent **a, const struct dirent **b)
+{
+ return (strcmp((*b)->d_name, (*a)->d_name));
+}
+
+ATF_TC(scandir_test);
+ATF_TC_HEAD(scandir_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test scandir()");
+}
+ATF_TC_BODY(scandir_test, tc)
+{
+ struct dirent **namelist = NULL;
+ int i, ret;
+
+ scandir_prepare(tc);
+ ret = scandir("dir", &namelist, scandir_select, scandir_compare);
+ scandir_verify(tc, ret, namelist);
+ for (i = 0; i < ret; i++)
+ free(namelist[i]);
+ free(namelist);
+}
+
+ATF_TC(fdscandir_test);
+ATF_TC_HEAD(fdscandir_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test fdscandir()");
+}
+ATF_TC_BODY(fdscandir_test, tc)
+{
+ struct dirent **namelist = NULL;
+ int fd, i, ret;
+
+ scandir_prepare(tc);
+ ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
+ ret = fdscandir(fd, &namelist, scandir_select, scandir_compare);
+ scandir_verify(tc, ret, namelist);
+ for (i = 0; i < ret; i++)
+ free(namelist[i]);
+ free(namelist);
+ ATF_REQUIRE_EQ(0, close(fd));
+}
+
+ATF_TC(scandirat_test);
+ATF_TC_HEAD(scandirat_test, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test scandirat()");
+}
+ATF_TC_BODY(scandirat_test, tc)
+{
+ struct dirent **namelist = NULL;
+ int fd, i, ret;
+
+ scandir_prepare(tc);
+ ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_SEARCH)) >= 0);
+ ret = scandirat(fd, ".", &namelist, scandir_select, scandir_compare);
+ scandir_verify(tc, ret, namelist);
+ for (i = 0; i < ret; i++)
+ free(namelist[i]);
+ free(namelist);
+ ATF_REQUIRE_EQ(0, close(fd));
+}
+
+static int
+scandir_none(const struct dirent *ent __unused)
+{
+ return (0);
+}
+
+ATF_TC(scandir_none);
+ATF_TC_HEAD(scandir_none, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test scandir() when no entries are selected");
+}
+ATF_TC_BODY(scandir_none, tc)
+{
+ struct dirent **namelist = NULL;
+
+ ATF_REQUIRE_EQ(0, scandir(".", &namelist, scandir_none, alphasort));
+ ATF_REQUIRE(namelist);
+ free(namelist);
+}
+
+/*
+ * Test that scandir() propagates errors from readdir(): we create a
+ * directory with enough entries that it can't be read in a single
+ * getdirentries() call, then abuse the selection callback to close the
+ * file descriptor scandir() is using after the first call, causing the
+ * next one to fail, and verify that readdir() returns an error instead of
+ * a partial result. We make two passes, one in which nothing was
+ * selected before the error occurred, and one in which everything was.
+ */
+static int scandir_error_count;
+static int scandir_error_fd;
+static int scandir_error_select_return;
+
+static int
+scandir_error_select(const struct dirent *ent __unused)
+{
+ if (scandir_error_count++ == 0)
+ close(scandir_error_fd);
+ return (scandir_error_select_return);
+}
+
+ATF_TC(scandir_error);
+ATF_TC_HEAD(scandir_error, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test that scandir() propagates errors from readdir()");
+}
+ATF_TC_BODY(scandir_error, tc)
+{
+ char path[16];
+ struct dirent **namelist = NULL;
+ int fd, i;
+
+ ATF_REQUIRE_EQ(0, mkdir("dir", 0755));
+ for (i = 0; i < 1024; i++) {
+ snprintf(path, sizeof(path), "dir/%04x", i);
+ ATF_REQUIRE_EQ(0, symlink(path + 4, path));
+ }
+
+ /* first pass, select nothing */
+ ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
+ scandir_error_count = 0;
+ scandir_error_fd = fd;
+ scandir_error_select_return = 0;
+ ATF_CHECK_ERRNO(EBADF,
+ fdscandir(fd, &namelist, scandir_error_select, NULL) < 0);
+ ATF_CHECK_EQ(NULL, namelist);
+
+ /* second pass, select everything */
+ ATF_REQUIRE((fd = open("dir", O_DIRECTORY | O_RDONLY)) >= 0);
+ scandir_error_count = 0;
+ scandir_error_fd = fd;
+ scandir_error_select_return = 1;
+ ATF_CHECK_ERRNO(EBADF,
+ fdscandir(fd, &namelist, scandir_error_select, NULL) < 0);
+ ATF_CHECK_EQ(NULL, namelist);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, scandir_test);
+ ATF_TP_ADD_TC(tp, fdscandir_test);
+ ATF_TP_ADD_TC(tp, scandirat_test);
+ ATF_TP_ADD_TC(tp, scandir_none);
+ ATF_TP_ADD_TC(tp, scandir_error);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/sig2str_test.c b/lib/libc/tests/gen/sig2str_test.c
new file mode 100644
index 000000000000..00b6ebb2349a
--- /dev/null
+++ b/lib/libc/tests/gen/sig2str_test.c
@@ -0,0 +1,213 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Ricardo Branco <rbranco@suse.de>
+ * 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.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <atf-c.h>
+
+static void
+test_roundtrip(int signum)
+{
+ char str[SIG2STR_MAX];
+ int sig;
+
+ ATF_REQUIRE_EQ_MSG(sig2str(signum, str), 0,
+ "sig2str(%d) failed", signum);
+ ATF_REQUIRE_EQ_MSG(str2sig(str, &sig), 0,
+ "str2sig(\"%s\") failed", str);
+ ATF_REQUIRE_INTEQ_MSG(sig, signum,
+ "Mismatch: roundtrip conversion gave %d instead of %d",
+ sig, signum);
+}
+
+ATF_TC_WITHOUT_HEAD(sig2str_valid);
+ATF_TC_BODY(sig2str_valid, tc)
+{
+ int sig;
+
+ for (sig = 1; sig < sys_nsig; sig++) {
+ test_roundtrip(sig);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(sig2str_invalid);
+ATF_TC_BODY(sig2str_invalid, tc)
+{
+ char buf[SIG2STR_MAX];
+
+ ATF_CHECK(sig2str(0, buf) != 0);
+ ATF_CHECK(sig2str(-1, buf) != 0);
+ ATF_CHECK(sig2str(SIGRTMAX + 1, buf) != 0);
+}
+
+ATF_TC_WITHOUT_HEAD(str2sig_rtmin_rtmax);
+ATF_TC_BODY(str2sig_rtmin_rtmax, tc)
+{
+ int sig;
+
+ ATF_CHECK_MSG(str2sig("RTMIN", &sig) == 0,
+ "str2sig(\"RTMIN\") failed");
+ ATF_CHECK_INTEQ_MSG(sig, SIGRTMIN,
+ "RTMIN mapped to %d, expected %d", sig, SIGRTMIN);
+
+ ATF_CHECK_MSG(str2sig("RTMAX", &sig) == 0,
+ "str2sig(\"RTMAX\") failed");
+ ATF_CHECK_INTEQ_MSG(sig, SIGRTMAX,
+ "RTMAX mapped to %d, expected %d", sig, SIGRTMAX);
+
+ ATF_CHECK_MSG(str2sig("RTMIN+1", &sig) == 0,
+ "str2sig(\"RTMIN+1\") failed");
+ ATF_CHECK_INTEQ_MSG(sig, SIGRTMIN + 1,
+ "RTMIN+1 mapped to %d, expected %d", sig, SIGRTMIN + 1);
+
+ ATF_CHECK_MSG(str2sig("RTMAX-1", &sig) == 0,
+ "str2sig(\"RTMAX-1\") failed");
+ ATF_CHECK_INTEQ_MSG(sig, SIGRTMAX - 1,
+ "RTMAX-1 mapped to %d, expected %d", sig, SIGRTMAX - 1);
+}
+
+ATF_TC_WITHOUT_HEAD(str2sig_invalid_rt);
+ATF_TC_BODY(str2sig_invalid_rt, tc)
+{
+ int i, sig;
+
+ const char *invalid[] = {
+ "RTMIN+0",
+ "RTMAX-0",
+ "RTMIN-777",
+ "RTMIN+777",
+ "RTMAX-777",
+ "RTMAX+777",
+ "RTMIN-",
+ "RTMAX-",
+ "RTMIN0",
+ "RTMAX1",
+ "RTMIN+abc",
+ "RTMIN-abc",
+ NULL
+ };
+
+ for (i = 0; invalid[i] != NULL; i++) {
+ ATF_CHECK_MSG(str2sig(invalid[i], &sig) != 0,
+ "str2sig(\"%s\") unexpectedly succeeded", invalid[i]);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(str2sig_fullname);
+ATF_TC_BODY(str2sig_fullname, tc)
+{
+ char fullname[SIG2STR_MAX + 3];
+ int n, sig;
+
+ for (sig = 1; sig < sys_nsig; sig++) {
+ snprintf(fullname, sizeof(fullname), "SIG%s", sys_signame[sig]);
+
+ ATF_CHECK_MSG(str2sig(fullname, &n) == 0,
+ "str2sig(\"%s\") failed with errno %d (%s)",
+ fullname, errno, strerror(errno));
+
+ ATF_CHECK_INTEQ_MSG(n, sig,
+ "Mismatch: %s = %d, str2sig(\"%s\") = %d",
+ sys_signame[sig], sig, fullname, n);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(str2sig_lowercase);
+ATF_TC_BODY(str2sig_lowercase, tc)
+{
+ char fullname[SIG2STR_MAX + 3];
+ int n, sig;
+
+ for (sig = 1; sig < sys_nsig; sig++) {
+ snprintf(fullname, sizeof(fullname), "sig%s", sys_signame[sig]);
+ for (size_t i = 3; i < strlen(fullname); i++)
+ fullname[i] = toupper((unsigned char)fullname[i]);
+
+ ATF_CHECK_MSG(str2sig(fullname, &n) == 0,
+ "str2sig(\"%s\") failed with errno %d (%s)",
+ fullname, errno, strerror(errno));
+
+ ATF_CHECK_INTEQ_MSG(n, sig,
+ "Mismatch: %s = %d, str2sig(\"%s\") = %d",
+ sys_signame[sig], sig, fullname, n);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(str2sig_numeric);
+ATF_TC_BODY(str2sig_numeric, tc)
+{
+ char buf[16];
+ int n, sig;
+
+ for (sig = NSIG; sig < SIGRTMIN; sig++) {
+ snprintf(buf, sizeof(buf), "%d", sig);
+ ATF_CHECK_MSG(str2sig(buf, &n) == 0,
+ "str2sig(\"%s\") failed", buf);
+ ATF_CHECK_INTEQ_MSG(n, sig,
+ "Mismatch: str2sig(\"%s\") = %d, expected %d",
+ buf, n, sig);
+ }
+}
+
+ATF_TC_WITHOUT_HEAD(str2sig_invalid);
+ATF_TC_BODY(str2sig_invalid, tc)
+{
+ const char *invalid[] = {
+ "SIGDOESNOTEXIST",
+ "DOESNOTEXIST",
+ "INTERRUPT",
+ "",
+ "SIG",
+ "123abc",
+ "sig1extra",
+ NULL
+ };
+ int i, sig;
+
+ for (i = 0; invalid[i] != NULL; i++) {
+ ATF_CHECK_MSG(str2sig(invalid[i], &sig) != 0,
+ "str2sig(\"%s\") unexpectedly succeeded", invalid[i]);
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, sig2str_valid);
+ ATF_TP_ADD_TC(tp, sig2str_invalid);
+ ATF_TP_ADD_TC(tp, str2sig_rtmin_rtmax);
+ ATF_TP_ADD_TC(tp, str2sig_invalid_rt);
+ ATF_TP_ADD_TC(tp, str2sig_fullname);
+ ATF_TP_ADD_TC(tp, str2sig_lowercase);
+ ATF_TP_ADD_TC(tp, str2sig_numeric);
+ ATF_TP_ADD_TC(tp, str2sig_invalid);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/gen/wordexp_test.c b/lib/libc/tests/gen/wordexp_test.c
index 909097fbf51e..a8b9d5509633 100644
--- a/lib/libc/tests/gen/wordexp_test.c
+++ b/lib/libc/tests/gen/wordexp_test.c
@@ -292,6 +292,31 @@ ATF_TC_BODY(with_SIGCHILD_handler_test, tc)
ATF_REQUIRE(r == 0);
}
+ATF_TC_WITHOUT_HEAD(with_SIGCHILD_ignore_test);
+ATF_TC_BODY(with_SIGCHILD_ignore_test, tc)
+{
+ struct sigaction sa;
+ wordexp_t we;
+ int r;
+
+ /* With SIGCHLD set to ignore so that the kernel auto-reaps zombies. */
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_IGN;
+ r = sigaction(SIGCHLD, &sa, NULL);
+ ATF_REQUIRE(r == 0);
+ r = wordexp("hello world", &we, 0);
+ ATF_REQUIRE(r == 0);
+ ATF_REQUIRE(we.we_wordc == 2);
+ ATF_REQUIRE(strcmp(we.we_wordv[0], "hello") == 0);
+ ATF_REQUIRE(strcmp(we.we_wordv[1], "world") == 0);
+ ATF_REQUIRE(we.we_wordv[2] == NULL);
+ wordfree(&we);
+ sa.sa_handler = SIG_DFL;
+ r = sigaction(SIGCHLD, &sa, NULL);
+ ATF_REQUIRE(r == 0);
+}
+
ATF_TC_WITHOUT_HEAD(with_unused_non_default_IFS_test);
ATF_TC_BODY(with_unused_non_default_IFS_test, tc)
{
@@ -350,6 +375,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, WRDE_BADCHAR_test);
ATF_TP_ADD_TC(tp, WRDE_SYNTAX_test);
ATF_TP_ADD_TC(tp, with_SIGCHILD_handler_test);
+ ATF_TP_ADD_TC(tp, with_SIGCHILD_ignore_test);
ATF_TP_ADD_TC(tp, with_unused_non_default_IFS_test);
ATF_TP_ADD_TC(tp, with_used_non_default_IFS_test);
diff --git a/lib/libc/tests/locale/wctomb_test.c b/lib/libc/tests/locale/wctomb_test.c
index 1e142ed74c48..ef2a6dcbe1e3 100644
--- a/lib/libc/tests/locale/wctomb_test.c
+++ b/lib/libc/tests/locale/wctomb_test.c
@@ -41,6 +41,18 @@
#include <atf-c.h>
+ATF_TC_WITHOUT_HEAD(euccs1_test);
+ATF_TC_BODY(euccs1_test, tc)
+{
+ wchar_t wc = 0x8e000000;
+ char buf[MB_LEN_MAX];
+
+ ATF_REQUIRE(strcmp(setlocale(LC_CTYPE, "zh_CN.eucCN"),
+ "zh_CN.eucCN") == 0);
+
+ ATF_REQUIRE(wctomb(&buf[0], wc) == 4);
+}
+
ATF_TC_WITHOUT_HEAD(wctomb_test);
ATF_TC_BODY(wctomb_test, tc)
{
@@ -104,6 +116,7 @@ ATF_TC_BODY(wctomb_test, tc)
ATF_TP_ADD_TCS(tp)
{
+ ATF_TP_ADD_TC(tp, euccs1_test);
ATF_TP_ADD_TC(tp, wctomb_test);
return (atf_no_error());
diff --git a/lib/libc/tests/net/Makefile b/lib/libc/tests/net/Makefile
index 016f0cd82ceb..ec0668633508 100644
--- a/lib/libc/tests/net/Makefile
+++ b/lib/libc/tests/net/Makefile
@@ -3,6 +3,11 @@ PACKAGE= tests
ATF_TESTS_C+= ether_test
ATF_TESTS_C+= eui64_aton_test
ATF_TESTS_C+= eui64_ntoa_test
+ATF_TESTS_CXX+= link_addr_test
+ATF_TESTS_CXX+= inet_net_test
+
+CXXSTD.link_addr_test= c++20
+CXXSTD.inet_net_test= c++20
CFLAGS+= -I${.CURDIR}
diff --git a/lib/libc/tests/net/getaddrinfo/Makefile b/lib/libc/tests/net/getaddrinfo/Makefile
index 1c8d68d8cc25..1299e91615b7 100644
--- a/lib/libc/tests/net/getaddrinfo/Makefile
+++ b/lib/libc/tests/net/getaddrinfo/Makefile
@@ -1,12 +1,10 @@
PACKAGE= tests
-TESTSRC= ${SRCTOP}/contrib/netbsd-tests/lib/libc/net/${.CURDIR:T}
-
.include <bsd.own.mk>
BINDIR= ${TESTSDIR}
-NETBSD_ATF_TESTS_SH= getaddrinfo_test
+ATF_TESTS_SH= getaddrinfo_test
ATF_TESTS_C= getaddrinfo
PROGS= h_gai
@@ -16,13 +14,33 @@ ${PACKAGE}DATA_FILESPACKAGE= tests
${PACKAGE}DATA_FILESDIR= ${TESTSDIR}/data
-${PACKAGE}DATA_FILES+= basics_v4.exp basics_v4v6.exp
-${PACKAGE}DATA_FILES+= no_host_v4.exp no_host_v4v6.exp
-${PACKAGE}DATA_FILES+= no_serv_v4.exp no_serv_v4v6.exp
-${PACKAGE}DATA_FILES+= sock_raw_v4.exp sock_raw_v4v6.exp
-${PACKAGE}DATA_FILES+= spec_fam_v4.exp spec_fam_v4v6.exp
-${PACKAGE}DATA_FILES+= scoped.exp
-${PACKAGE}DATA_FILES+= unsup_fam.exp
+${PACKAGE}DATA_FILES+= data/basics_v4.exp
+${PACKAGE}DATA_FILES+= data/basics_v4_only.exp
+${PACKAGE}DATA_FILES+= data/basics_v4v6.exp
+${PACKAGE}DATA_FILES+= data/basics_v4v6_prefer_v4.exp
+${PACKAGE}DATA_FILES+= data/no_host_v4.exp
+${PACKAGE}DATA_FILES+= data/no_host_v4_only.exp
+${PACKAGE}DATA_FILES+= data/no_host_v4v6.exp
+${PACKAGE}DATA_FILES+= data/no_host_v4v6_prefer_v4.exp
+${PACKAGE}DATA_FILES+= data/no_serv_v4.exp
+${PACKAGE}DATA_FILES+= data/no_serv_v4_only.exp
+${PACKAGE}DATA_FILES+= data/no_serv_v4v6.exp
+${PACKAGE}DATA_FILES+= data/no_serv_v4v6_prefer_v4.exp
+${PACKAGE}DATA_FILES+= data/scoped.exp
+${PACKAGE}DATA_FILES+= data/scoped_v4_only.exp
+${PACKAGE}DATA_FILES+= data/scoped_v4v6_prefer_v4.exp
+${PACKAGE}DATA_FILES+= data/sock_raw_v4.exp
+${PACKAGE}DATA_FILES+= data/sock_raw_v4_only.exp
+${PACKAGE}DATA_FILES+= data/sock_raw_v4v6.exp
+${PACKAGE}DATA_FILES+= data/sock_raw_v4v6_prefer_v4.exp
+${PACKAGE}DATA_FILES+= data/spec_fam_v4.exp
+${PACKAGE}DATA_FILES+= data/spec_fam_v4_only.exp
+${PACKAGE}DATA_FILES+= data/spec_fam_v4v6.exp
+${PACKAGE}DATA_FILES+= data/spec_fam_v4v6_prefer_v4.exp
+${PACKAGE}DATA_FILES+= data/unsup_fam.exp
+${PACKAGE}DATA_FILES+= data/unsup_fam_v4_only.exp
+${PACKAGE}DATA_FILES+= data/unsup_fam_v4v6_prefer_v4.exp
+
.include "../../Makefile.netbsd-tests"
diff --git a/lib/libc/tests/net/getaddrinfo/basics_v4.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4.exp
index 4f1ee3517211..4f1ee3517211 100644
--- a/lib/libc/tests/net/getaddrinfo/basics_v4.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/basics_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4_only.exp
new file mode 100644
index 000000000000..0a37d3212649
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4_only.exp
@@ -0,0 +1,50 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv http
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv http
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv tftp
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv tftp
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv tftp
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp
+ai3: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp
+ai4: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv echo
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv echo
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv echo
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo
+ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo
+ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo
+ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo
+
diff --git a/lib/libc/tests/net/getaddrinfo/basics_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6.exp
index b74758e93e2e..b74758e93e2e 100644
--- a/lib/libc/tests/net/getaddrinfo/basics_v4v6.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/basics_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6_prefer_v4.exp
new file mode 100644
index 000000000000..0a37d3212649
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/basics_v4v6_prefer_v4.exp
@@ -0,0 +1,50 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv http
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv http
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv tftp
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv tftp
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv tftp
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp
+ai3: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp
+ai4: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv echo
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv echo
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv echo
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo
+ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo
+ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo
+ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo
+
diff --git a/lib/libc/tests/net/getaddrinfo/data/generate_testdata.sh b/lib/libc/tests/net/getaddrinfo/data/generate_testdata.sh
new file mode 100755
index 000000000000..f0425a3b0283
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/generate_testdata.sh
@@ -0,0 +1,45 @@
+#service ip6addrctl prefer_ipv4
+TEST=./h_gai
+family=v4_only
+
+( $TEST ::1 http
+ $TEST 127.0.0.1 http
+ $TEST localhost http
+ $TEST ::1 tftp
+ $TEST 127.0.0.1 tftp
+ $TEST localhost tftp
+ $TEST ::1 echo
+ $TEST 127.0.0.1 echo
+ $TEST localhost echo ) > basics_${family}.exp
+
+( $TEST -4 localhost http
+ $TEST -6 localhost http ) > spec_fam_${family}.exp
+
+( $TEST '' http
+ $TEST '' echo
+ $TEST '' tftp
+ $TEST '' 80
+ $TEST -P '' http
+ $TEST -P '' echo
+ $TEST -P '' tftp
+ $TEST -P '' 80
+ $TEST -S '' 80
+ $TEST -D '' 80 ) > no_host_${family}.exp
+
+( $TEST ::1 ''
+ $TEST 127.0.0.1 ''
+ $TEST localhost ''
+ $TEST '' '' ) > no_serv_${family}.exp
+
+( $TEST -R -p 0 localhost ''
+ $TEST -R -p 59 localhost ''
+ $TEST -R -p 59 localhost 80
+ $TEST -R -p 59 localhost www
+ $TEST -R -p 59 ::1 '' ) > sock_raw_${family}.exp
+
+( $TEST -f 99 localhost '' ) > unsup_fam_${family}.exp
+
+( $TEST fe80::1%lo0 http
+# IF=`ifconfig -a | grep -v '^ ' | sed -e 's/:.*//' | head -1 | awk '{print $1}'`
+# $TEST fe80::1%$IF http
+) > scoped_${family}.exp
diff --git a/lib/libc/tests/net/getaddrinfo/no_host_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4.exp
index 7ed41cd9d88a..7ed41cd9d88a 100644
--- a/lib/libc/tests/net/getaddrinfo/no_host_v4.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4.exp
diff --git a/lib/libc/tests/net/getaddrinfo/no_host_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4_only.exp
index 596799305117..596799305117 100644
--- a/lib/libc/tests/net/getaddrinfo/no_host_v4v6.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4_only.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6.exp
new file mode 100644
index 000000000000..596799305117
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6.exp
@@ -0,0 +1,68 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo
+ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo
+ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo
+ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp
+ai3: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp
+ai4: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http
+ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http
+ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http
+ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http
+ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv echo
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv echo
+ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv echo
+ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv echo
+ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv echo
+ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv echo
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv tftp
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv tftp
+ai3: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv tftp
+ai4: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv tftp
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http
+ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http
+ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http
+ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http
+ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http
+
+arg: flags 0x2 family 0 socktype 1 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 0 socktype 2 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+
diff --git a/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6_prefer_v4.exp
new file mode 100644
index 000000000000..596799305117
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/no_host_v4v6_prefer_v4.exp
@@ -0,0 +1,68 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv echo
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv echo
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv echo
+ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv echo
+ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv echo
+ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv echo
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv tftp
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv tftp
+ai3: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv tftp
+ai4: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv tftp
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+ai4: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai5: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai6: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv http
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http
+ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http
+ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http
+ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http
+ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv echo
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv echo
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv echo
+ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv echo
+ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv echo
+ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv echo
+ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv echo
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv tftp
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv tftp
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv tftp
+ai3: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv tftp
+ai4: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv tftp
+
+arg: flags 0x3 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x3 family 28 socktype 2 protocol 17 addrlen 28 host :: serv http
+ai2: flags 0x3 family 28 socktype 1 protocol 6 addrlen 28 host :: serv http
+ai3: flags 0x3 family 28 socktype 5 protocol 132 addrlen 28 host :: serv http
+ai4: flags 0x3 family 2 socktype 2 protocol 17 addrlen 16 host 0.0.0.0 serv http
+ai5: flags 0x3 family 2 socktype 1 protocol 6 addrlen 16 host 0.0.0.0 serv http
+ai6: flags 0x3 family 2 socktype 5 protocol 132 addrlen 16 host 0.0.0.0 serv http
+
+arg: flags 0x2 family 0 socktype 1 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 0 socktype 2 protocol 0 addrlen 0 host (empty) serv 80
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+
diff --git a/lib/libc/tests/net/getaddrinfo/no_serv_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4.exp
index 667234d2161f..667234d2161f 100644
--- a/lib/libc/tests/net/getaddrinfo/no_serv_v4.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/no_serv_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4_only.exp
new file mode 100644
index 000000000000..0d28490c8d81
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4_only.exp
@@ -0,0 +1,20 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv (empty)
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv (empty)
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv (empty)
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0
+ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0
+ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0
+ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv (empty)
+Name does not resolve
diff --git a/lib/libc/tests/net/getaddrinfo/no_serv_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6.exp
index 53502547c40e..53502547c40e 100644
--- a/lib/libc/tests/net/getaddrinfo/no_serv_v4v6.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6_prefer_v4.exp
new file mode 100644
index 000000000000..0d28490c8d81
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/no_serv_v4v6_prefer_v4.exp
@@ -0,0 +1,20 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host ::1 serv (empty)
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host 127.0.0.1 serv (empty)
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host localhost serv (empty)
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv 0
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv 0
+ai4: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv 0
+ai5: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv 0
+ai6: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host (empty) serv (empty)
+Name does not resolve
diff --git a/lib/libc/tests/net/getaddrinfo/scoped.exp b/lib/libc/tests/net/getaddrinfo/data/scoped.exp
index f5ddb4bf6feb..f5ddb4bf6feb 100644
--- a/lib/libc/tests/net/getaddrinfo/scoped.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/scoped.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/scoped_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/scoped_v4_only.exp
new file mode 100644
index 000000000000..f5ddb4bf6feb
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/scoped_v4_only.exp
@@ -0,0 +1,5 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host fe80::1%lo0 serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host fe80::1%lo0 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host fe80::1%lo0 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host fe80::1%lo0 serv http
+
diff --git a/lib/libc/tests/net/getaddrinfo/data/scoped_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/scoped_v4v6_prefer_v4.exp
new file mode 100644
index 000000000000..f5ddb4bf6feb
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/scoped_v4v6_prefer_v4.exp
@@ -0,0 +1,5 @@
+arg: flags 0x2 family 0 socktype 0 protocol 0 addrlen 0 host fe80::1%lo0 serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host fe80::1%lo0 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host fe80::1%lo0 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host fe80::1%lo0 serv http
+
diff --git a/lib/libc/tests/net/getaddrinfo/sock_raw_v4.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4.exp
index 021038b81dcc..021038b81dcc 100644
--- a/lib/libc/tests/net/getaddrinfo/sock_raw_v4.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4_only.exp
new file mode 100644
index 000000000000..932c1faab0d3
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4_only.exp
@@ -0,0 +1,15 @@
+arg: flags 0x2 family 0 socktype 3 protocol 0 addrlen 0 host localhost serv (empty)
+ai1: flags 0x2 family 2 socktype 3 protocol 0 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 28 socktype 3 protocol 0 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv (empty)
+ai1: flags 0x2 family 2 socktype 3 protocol 59 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv 80
+Service was not recognized for socket type
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv www
+Service was not recognized for socket type
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host ::1 serv (empty)
+ai1: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0
+
diff --git a/lib/libc/tests/net/getaddrinfo/sock_raw_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6.exp
index e494271a3d35..e494271a3d35 100644
--- a/lib/libc/tests/net/getaddrinfo/sock_raw_v4v6.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6_prefer_v4.exp
new file mode 100644
index 000000000000..932c1faab0d3
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/sock_raw_v4v6_prefer_v4.exp
@@ -0,0 +1,15 @@
+arg: flags 0x2 family 0 socktype 3 protocol 0 addrlen 0 host localhost serv (empty)
+ai1: flags 0x2 family 2 socktype 3 protocol 0 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 28 socktype 3 protocol 0 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv (empty)
+ai1: flags 0x2 family 2 socktype 3 protocol 59 addrlen 16 host 127.0.0.1 serv 0
+ai2: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0
+
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv 80
+Service was not recognized for socket type
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host localhost serv www
+Service was not recognized for socket type
+arg: flags 0x2 family 0 socktype 3 protocol 59 addrlen 0 host ::1 serv (empty)
+ai1: flags 0x2 family 28 socktype 3 protocol 59 addrlen 28 host ::1 serv 0
+
diff --git a/lib/libc/tests/net/getaddrinfo/spec_fam_v4.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4.exp
index 924d2a16f47c..924d2a16f47c 100644
--- a/lib/libc/tests/net/getaddrinfo/spec_fam_v4.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4.exp
diff --git a/lib/libc/tests/net/getaddrinfo/spec_fam_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4_only.exp
index af3506938503..af3506938503 100644
--- a/lib/libc/tests/net/getaddrinfo/spec_fam_v4v6.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4_only.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6.exp
new file mode 100644
index 000000000000..af3506938503
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6.exp
@@ -0,0 +1,10 @@
+arg: flags 0x2 family 2 socktype 0 protocol 0 addrlen 0 host localhost serv http
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 28 socktype 0 protocol 0 addrlen 0 host localhost serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+
diff --git a/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6_prefer_v4.exp
new file mode 100644
index 000000000000..af3506938503
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/spec_fam_v4v6_prefer_v4.exp
@@ -0,0 +1,10 @@
+arg: flags 0x2 family 2 socktype 0 protocol 0 addrlen 0 host localhost serv http
+ai1: flags 0x2 family 2 socktype 2 protocol 17 addrlen 16 host 127.0.0.1 serv http
+ai2: flags 0x2 family 2 socktype 1 protocol 6 addrlen 16 host 127.0.0.1 serv http
+ai3: flags 0x2 family 2 socktype 5 protocol 132 addrlen 16 host 127.0.0.1 serv http
+
+arg: flags 0x2 family 28 socktype 0 protocol 0 addrlen 0 host localhost serv http
+ai1: flags 0x2 family 28 socktype 2 protocol 17 addrlen 28 host ::1 serv http
+ai2: flags 0x2 family 28 socktype 1 protocol 6 addrlen 28 host ::1 serv http
+ai3: flags 0x2 family 28 socktype 5 protocol 132 addrlen 28 host ::1 serv http
+
diff --git a/lib/libc/tests/net/getaddrinfo/unsup_fam.exp b/lib/libc/tests/net/getaddrinfo/data/unsup_fam.exp
index 69e6b48a854b..69e6b48a854b 100644
--- a/lib/libc/tests/net/getaddrinfo/unsup_fam.exp
+++ b/lib/libc/tests/net/getaddrinfo/data/unsup_fam.exp
diff --git a/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4_only.exp b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4_only.exp
new file mode 100644
index 000000000000..69e6b48a854b
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4_only.exp
@@ -0,0 +1,2 @@
+arg: flags 0x2 family 99 socktype 0 protocol 0 addrlen 0 host localhost serv (empty)
+Address family not recognized
diff --git a/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4v6_prefer_v4.exp b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4v6_prefer_v4.exp
new file mode 100644
index 000000000000..69e6b48a854b
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/data/unsup_fam_v4v6_prefer_v4.exp
@@ -0,0 +1,2 @@
+arg: flags 0x2 family 99 socktype 0 protocol 0 addrlen 0 host localhost serv (empty)
+Address family not recognized
diff --git a/lib/libc/tests/net/getaddrinfo/getaddrinfo_test.sh b/lib/libc/tests/net/getaddrinfo/getaddrinfo_test.sh
new file mode 100755
index 000000000000..dd17ab2e3f4a
--- /dev/null
+++ b/lib/libc/tests/net/getaddrinfo/getaddrinfo_test.sh
@@ -0,0 +1,443 @@
+# $NetBSD: t_getaddrinfo.sh,v 1.2 2011/06/15 07:54:32 jmmv Exp $
+
+#
+# Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, and 2002 WIDE Project.
+# 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.
+# 3. Neither the name of the project nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+#
+
+if [ "$(sysctl -i -n kern.features.vimage)" != 1 ]; then
+ atf_skip "This test requires VIMAGE"
+fi
+
+vnet_mkjail()
+{
+ jailname=getaddrinfo_test_$1
+ jail -c name=${jailname} persist vnet
+ ifconfig -j ${jailname} lo0 inet 127.0.0.1/8
+ # For those machines not support IPv6
+ ifconfig -j ${jailname} lo0 inet6 ::1/64 || true
+ service -j ${jailname} ip6addrctl $2 || true
+}
+
+vnet_cleanup()
+{
+ jailname=getaddrinfo_test_$1
+ jail -r ${jailname}
+}
+
+check_output()
+{
+ if [ "$2" = "none" ]; then
+ if [ "$3" = "prefer_v6" ]; then
+ exp="${1}.exp"
+ else
+ exp="${1}_v4_only.exp"
+ fi
+ elif [ "$2" = "hosts" ]; then
+ lcl=$(cat /etc/hosts | sed -e 's/#.*$//' -e 's/[ ][ ]*/ /g' | awk '/ localhost($| )/ {printf "%s ", $1}')
+ if [ "${lcl%*::*}" = "${lcl}" ]; then
+ exp="${1}_v4_only.exp"
+ else
+ if [ "$3" = "prefer_v6" ]; then
+ exp="${1}_v4v6.exp"
+ else
+ exp="${1}_v4v6_prefer_v4.exp"
+ fi
+ fi
+ elif [ "$2" = "ifconfig" ]; then
+ lcl=$(ifconfig lo0 | grep inet6)
+ if [ -n "${lcl}" ]; then
+ if [ "$3" = "prefer_v6" ]; then
+ exp="${1}_v4v6.exp"
+ else
+ exp="${1}_v4v6_prefer_v4.exp"
+ fi
+ else
+ exp="${1}_v4_only.exp"
+ fi
+ else
+ atf_fail "Invalid family_match_type $2 requested."
+ fi
+
+ cmp -s "$(atf_get_srcdir)/data/${exp}" out && return
+ diff -u "$(atf_get_srcdir)/data/${exp}" out || atf_fail "Actual output does not match expected output"
+}
+
+atf_test_case basic_prefer_v4 cleanup
+basic_prefer_v4_head()
+{
+ atf_set "descr" "Testing basic ones with prefer_v4"
+ atf_set "require.user" "root"
+}
+basic_prefer_v4_body()
+{
+ vnet_mkjail basic_prefer_v4 prefer_ipv4
+ TEST="jexec getaddrinfo_test_basic_prefer_v4 $(atf_get_srcdir)/h_gai"
+
+ ( $TEST ::1 http
+ $TEST 127.0.0.1 http
+ $TEST localhost http
+ $TEST ::1 tftp
+ $TEST 127.0.0.1 tftp
+ $TEST localhost tftp
+ $TEST ::1 echo
+ $TEST 127.0.0.1 echo
+ $TEST localhost echo ) > out 2>&1
+
+ check_output basics hosts prefer_v4
+}
+basic_prefer_v4_cleanup()
+{
+ vnet_cleanup basic_prefer_v4
+}
+
+atf_test_case basic cleanup
+basic_head()
+{
+ atf_set "descr" "Testing basic ones with prefer_v6"
+ atf_set "require.user" "root"
+}
+basic_body()
+{
+ vnet_mkjail basic prefer_ipv6
+ TEST="jexec getaddrinfo_test_basic $(atf_get_srcdir)/h_gai"
+
+ ( $TEST ::1 http
+ $TEST 127.0.0.1 http
+ $TEST localhost http
+ $TEST ::1 tftp
+ $TEST 127.0.0.1 tftp
+ $TEST localhost tftp
+ $TEST ::1 echo
+ $TEST 127.0.0.1 echo
+ $TEST localhost echo ) > out 2>&1
+
+ check_output basics ifconfig prefer_v6
+}
+basic_cleanup()
+{
+ vnet_cleanup basic
+}
+
+atf_test_case specific_prefer_v4 cleanup
+specific_prefer_v4_head()
+{
+ atf_set "descr" "Testing specific address family with prefer_v4"
+ atf_set "require.user" "root"
+}
+specific_prefer_v4_body()
+{
+ vnet_mkjail specific_prefer_v4 prefer_ipv4
+ TEST="jexec getaddrinfo_test_specific_prefer_v4 $(atf_get_srcdir)/h_gai"
+
+ ( $TEST -4 localhost http
+ $TEST -6 localhost http ) > out 2>&1
+
+ check_output spec_fam hosts prefer_v4
+}
+specific_prefer_v4_cleanup()
+{
+ vnet_cleanup specific_prefer_v4
+}
+
+atf_test_case specific cleanup
+specific_head()
+{
+ atf_set "descr" "Testing specific address family with prefer_v6"
+ atf_set "require.user" "root"
+}
+specific_body()
+{
+ vnet_mkjail specific prefer_ipv6
+ TEST="jexec getaddrinfo_test_specific $(atf_get_srcdir)/h_gai"
+
+ ( $TEST -4 localhost http
+ $TEST -6 localhost http ) > out 2>&1
+
+ check_output spec_fam hosts prefer_v6
+}
+specific_cleanup()
+{
+ vnet_cleanup specific
+}
+
+atf_test_case empty_hostname_prefer_v4 cleanup
+empty_hostname_prefer_v4_head()
+{
+ atf_set "descr" "Testing empty hostname with prefer_v4"
+ atf_set "require.user" "root"
+}
+empty_hostname_prefer_v4_body()
+{
+ vnet_mkjail empty_hostname_prefer_v4 prefer_ipv4
+ TEST="jexec getaddrinfo_test_empty_hostname_prefer_v4 $(atf_get_srcdir)/h_gai"
+
+ ( $TEST '' http
+ $TEST '' echo
+ $TEST '' tftp
+ $TEST '' 80
+ $TEST -P '' http
+ $TEST -P '' echo
+ $TEST -P '' tftp
+ $TEST -P '' 80
+ $TEST -S '' 80
+ $TEST -D '' 80 ) > out 2>&1
+
+ check_output no_host ifconfig prefer_v4
+}
+empty_hostname_prefer_v4_cleanup()
+{
+ vnet_cleanup empty_hostname_prefer_v4
+}
+
+atf_test_case empty_hostname cleanup
+empty_hostname_head()
+{
+ atf_set "descr" "Testing empty hostname with prefer_v6"
+ atf_set "require.user" "root"
+}
+empty_hostname_body()
+{
+ vnet_mkjail empty_hostname prefer_ipv6
+ TEST="jexec getaddrinfo_test_empty_hostname $(atf_get_srcdir)/h_gai"
+
+ ( $TEST '' http
+ $TEST '' echo
+ $TEST '' tftp
+ $TEST '' 80
+ $TEST -P '' http
+ $TEST -P '' echo
+ $TEST -P '' tftp
+ $TEST -P '' 80
+ $TEST -S '' 80
+ $TEST -D '' 80 ) > out 2>&1
+
+ check_output no_host ifconfig prefer_v6
+}
+empty_hostname_cleanup()
+{
+ vnet_cleanup empty_hostname
+}
+
+atf_test_case empty_servname_prefer_v4 cleanup
+empty_servname_prefer_v4_head()
+{
+ atf_set "descr" "Testing empty service name with prefer_v4"
+ atf_set "require.user" "root"
+}
+empty_servname_prefer_v4_body()
+{
+ vnet_mkjail empty_servname_prefer_v4 prefer_ipv4
+ TEST="jexec getaddrinfo_test_empty_servname_prefer_v4 $(atf_get_srcdir)/h_gai"
+
+ ( $TEST ::1 ''
+ $TEST 127.0.0.1 ''
+ $TEST localhost ''
+ $TEST '' '' ) > out 2>&1
+
+ check_output no_serv hosts prefer_v4
+}
+empty_servname_prefer_v4_cleanup()
+{
+ vnet_cleanup empty_servname_prefer_v4
+}
+
+atf_test_case empty_servname cleanup
+empty_servname_head()
+{
+ atf_set "descr" "Testing empty service name with prefer_v6"
+ atf_set "require.user" "root"
+}
+empty_servname_body()
+{
+ vnet_mkjail empty_servname prefer_ipv6
+ TEST="jexec getaddrinfo_test_empty_servname $(atf_get_srcdir)/h_gai"
+
+ ( $TEST ::1 ''
+ $TEST 127.0.0.1 ''
+ $TEST localhost ''
+ $TEST '' '' ) > out 2>&1
+
+ check_output no_serv ifconfig prefer_v6
+}
+empty_servname_cleanup()
+{
+ vnet_cleanup empty_servname
+}
+
+atf_test_case sock_raw_prefer_v4 cleanup
+sock_raw_prefer_v4_head()
+{
+ atf_set "descr" "Testing raw socket with prefer_v4"
+ atf_set "require.user" "root"
+}
+sock_raw_prefer_v4_body()
+{
+ vnet_mkjail sock_raw_prefer_v4 prefer_ipv4
+ TEST="jexec getaddrinfo_test_sock_raw_prefer_v4 $(atf_get_srcdir)/h_gai"
+
+ ( $TEST -R -p 0 localhost ''
+ $TEST -R -p 59 localhost ''
+ $TEST -R -p 59 localhost 80
+ $TEST -R -p 59 localhost www
+ $TEST -R -p 59 ::1 '' ) > out 2>&1
+
+ check_output sock_raw hosts prefer_v4
+}
+sock_raw_prefer_v4_cleanup()
+{
+ vnet_cleanup sock_raw_prefer_v4
+}
+
+atf_test_case sock_raw cleanup
+sock_raw_head()
+{
+ atf_set "descr" "Testing raw socket with prefer_v6"
+ atf_set "require.user" "root"
+}
+sock_raw_body()
+{
+ vnet_mkjail sock_raw prefer_ipv6
+ TEST="jexec getaddrinfo_test_sock_raw $(atf_get_srcdir)/h_gai"
+
+ ( $TEST -R -p 0 localhost ''
+ $TEST -R -p 59 localhost ''
+ $TEST -R -p 59 localhost 80
+ $TEST -R -p 59 localhost www
+ $TEST -R -p 59 ::1 '' ) > out 2>&1
+
+ check_output sock_raw ifconfig prefer_v6
+}
+sock_raw_cleanup()
+{
+ vnet_cleanup sock_raw
+}
+
+atf_test_case unsupported_family_prefer_v4 cleanup
+unsupported_family_prefer_v4_head()
+{
+ atf_set "descr" "Testing unsupported family with prefer_v4"
+ atf_set "require.user" "root"
+}
+unsupported_family_prefer_v4_body()
+{
+ vnet_mkjail unsupported_family_prefer_v4 prefer_ipv4
+ TEST="jexec getaddrinfo_test_unsupported_family_prefer_v4 $(atf_get_srcdir)/h_gai"
+
+ ( $TEST -f 99 localhost '' ) > out 2>&1
+
+ check_output unsup_fam ifconfig prefer_v4
+}
+unsupported_family_prefer_v4_cleanup()
+{
+ vnet_cleanup unsupported_family_prefer_v4
+}
+
+atf_test_case unsupported_family cleanup
+unsupported_family_head()
+{
+ atf_set "descr" "Testing unsupported family with prefer_v6"
+ atf_set "require.user" "root"
+}
+unsupported_family_body()
+{
+ vnet_mkjail unsupported_family prefer_ipv6
+ TEST="jexec getaddrinfo_test_unsupported_family $(atf_get_srcdir)/h_gai"
+
+ ( $TEST -f 99 localhost '' ) > out 2>&1
+
+ check_output unsup_fam none prefer_v6
+}
+unsupported_family_cleanup()
+{
+ vnet_cleanup unsupported_family
+}
+
+atf_test_case scopeaddr_prefer_v4 cleanup
+scopeaddr_prefer_v4_head()
+{
+ atf_set "descr" "Testing scoped address format with prefer_v4"
+ atf_set "require.user" "root"
+}
+scopeaddr_prefer_v4_body()
+{
+ vnet_mkjail scopeaddr_prefer_v4 prefer_ipv4
+ TEST="jexec getaddrinfo_test_scopeaddr_prefer_v4 $(atf_get_srcdir)/h_gai"
+
+ ( $TEST fe80::1%lo0 http
+# IF=ifconfig -a | grep -v '^ ' | sed -e 's/:.*//' | head -1 | awk '{print $1}'
+# $TEST fe80::1%$IF http
+ ) > out 2>&1
+
+ check_output scoped ifconfig prefer_v4
+}
+scopeaddr_prefer_v4_cleanup()
+{
+ vnet_cleanup scopeaddr_prefer_v4
+}
+
+atf_test_case scopeaddr cleanup
+scopeaddr_head()
+{
+ atf_set "descr" "Testing scoped address format with prefer_v6"
+ atf_set "require.user" "root"
+}
+scopeaddr_body()
+{
+ vnet_mkjail scopeaddr prefer_ipv6
+ TEST="jexec getaddrinfo_test_scopeaddr $(atf_get_srcdir)/h_gai"
+
+ ( $TEST fe80::1%lo0 http
+# IF=ifconfig -a | grep -v '^ ' | sed -e 's/:.*//' | head -1 | awk '{print $1}'
+# $TEST fe80::1%$IF http
+ ) > out 2>&1
+
+ check_output scoped none prefer_v6
+}
+scopeaddr_cleanup()
+{
+ vnet_cleanup scopeaddr
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case basic_prefer_v4
+ atf_add_test_case specific_prefer_v4
+ atf_add_test_case empty_hostname_prefer_v4
+ atf_add_test_case empty_servname_prefer_v4
+ atf_add_test_case sock_raw_prefer_v4
+ atf_add_test_case unsupported_family_prefer_v4
+ atf_add_test_case scopeaddr_prefer_v4
+
+ atf_add_test_case basic
+ atf_add_test_case specific
+ atf_add_test_case empty_hostname
+ atf_add_test_case empty_servname
+ atf_add_test_case sock_raw
+ atf_add_test_case unsupported_family
+ atf_add_test_case scopeaddr
+}
diff --git a/lib/libc/tests/net/inet_net_test.cc b/lib/libc/tests/net/inet_net_test.cc
new file mode 100644
index 000000000000..60b60b152eca
--- /dev/null
+++ b/lib/libc/tests/net/inet_net_test.cc
@@ -0,0 +1,333 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2025 Lexi Winter <ivy@FreeBSD.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Tests for inet_net_pton() and inet_net_ntop().
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ranges>
+#include <string>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+using namespace std::literals;
+
+/*
+ * inet_net_ntop() and inet_net_pton() for IPv4.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(inet_net_inet4)
+ATF_TEST_CASE_BODY(inet_net_inet4)
+{
+ /*
+ * Define a list of addresses we want to check. Each address is passed
+ * to inet_net_pton() to convert it to an in_addr, then we convert the
+ * in_addr back to a string and compare it with the expected value. We
+ * want to test over-long prefixes here (such as 10.0.0.1/8), so we also
+ * specify what the result is expected to be.
+ */
+
+ struct test_addr {
+ std::string input;
+ int bits;
+ std::string output;
+ };
+
+ auto test_addrs = std::vector<test_addr>{
+ // Simple prefixes that fall on octet boundaries.
+ { "10.0.0.0/8", 8, "10/8" },
+ { "10.1.0.0/16", 16, "10.1/16" },
+ { "10.1.2.0/24", 24, "10.1.2/24" },
+ { "10.1.2.3/32", 32, "10.1.2.3/32" },
+
+ // Simple prefixes with the short-form address.
+ { "10/8", 8, "10/8" },
+ { "10.1/16", 16, "10.1/16" },
+ { "10.1.2/24", 24, "10.1.2/24" },
+
+ // A prefix that doesn't fall on an octet boundary.
+ { "10.1.64/18", 18, "10.1.64/18" },
+
+ // An overlong prefix with bits that aren't part of the prefix.
+ { "10.0.0.1/8", 8, "10/8" },
+ };
+
+ for (auto const &addr: test_addrs) {
+ /*
+ * Convert the input string to an in_addr + bits, and make
+ * sure the result produces the number of bits we expected.
+ */
+
+ auto in = in_addr{};
+ auto bits = inet_net_pton(AF_INET, addr.input.c_str(),
+ &in, sizeof(in));
+ ATF_REQUIRE(bits != -1);
+ ATF_REQUIRE_EQ(bits, addr.bits);
+
+ /*
+ * Convert the in_addr back to a string
+ */
+
+ /*
+ * XXX: Should there be a constant for the size of the result
+ * buffer? For now, use ADDRSTRLEN + 3 ("/32") + 1 (NUL).
+ *
+ * Fill the buffer with 'Z', so we can check the result was
+ * properly terminated.
+ */
+ auto strbuf = std::vector<char>(INET_ADDRSTRLEN + 3 + 1, 'Z');
+ auto ret = inet_net_ntop(AF_INET, &in, bits,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret != NULL);
+ ATF_REQUIRE_EQ(ret, strbuf.data());
+
+ /* Make sure the result was NUL-terminated and find the NUL */
+ ATF_REQUIRE(strbuf.size() >= 1);
+ auto end = std::ranges::find(strbuf, '\0');
+ ATF_REQUIRE(end != strbuf.end());
+
+ /*
+ * Check the result matches what we expect. Use a temporary
+ * string here instead of std::ranges::equal because this
+ * means ATF can print the mismatch.
+ */
+ auto str = std::string(std::ranges::begin(strbuf), end);
+ ATF_REQUIRE_EQ(str, addr.output);
+ }
+}
+
+/*
+ * inet_net_ntop() and inet_net_pton() for IPv6.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(inet_net_inet6)
+ATF_TEST_CASE_BODY(inet_net_inet6)
+{
+ /*
+ * Define a list of addresses we want to check. Each address is
+ * passed to inet_net_pton() to convert it to an in6_addr, then we
+ * convert the in6_addr back to a string and compare it with the
+ * expected value. We want to test over-long prefixes here (such
+ * as 2001:db8::1/32), so we also specify what the result is
+ * expected to be.
+ */
+
+ struct test_addr {
+ std::string input;
+ int bits;
+ std::string output;
+ };
+
+ auto test_addrs = std::vector<test_addr>{
+ // A prefix with a trailing ::
+ { "2001:db8::/32", 32, "2001:db8::/32" },
+
+ // A prefix with a leading ::. Note that the output is
+ // different from the input because inet_ntop() renders
+ // this prefix with an IPv4 suffix for legacy reasons.
+ { "::ffff:0:0/96", 96, "::ffff:0.0.0.0/96" },
+
+ // The same prefix but with the IPv4 legacy form as input.
+ { "::ffff:0.0.0.0/96", 96, "::ffff:0.0.0.0/96" },
+
+ // A prefix with an infix ::.
+ { "2001:db8::1/128", 128, "2001:db8::1/128" },
+
+ // A prefix with bits set which are outside the prefix;
+ // these should be silently ignored.
+ { "2001:db8:1:1:1:1:1:1/32", 32, "2001:db8::/32" },
+
+ // As above but with infix ::.
+ { "2001:db8::1/32", 32, "2001:db8::/32" },
+
+ // A prefix with only ::, commonly used to represent the
+ // entire address space.
+ { "::/0", 0, "::/0" },
+
+ // A single address with no ::.
+ { "2001:db8:1:1:1:1:1:1/128", 128, "2001:db8:1:1:1:1:1:1/128" },
+
+ // A prefix with no ::.
+ { "2001:db8:1:1:0:0:0:0/64", 64, "2001:db8:1:1::/64" },
+
+ // A prefix which isn't on a 16-bit boundary.
+ { "2001:db8:c000::/56", 56, "2001:db8:c000::/56" },
+
+ // A prefix which isn't on a nibble boundary.
+ { "2001:db8:c100::/57", 57, "2001:db8:c100::/57" },
+
+ // An address without a prefix length, which should be treated
+ // as a /128.
+ { "2001:db8::", 128, "2001:db8::/128" },
+ { "2001:db8::1", 128, "2001:db8::1/128" },
+
+ // Test vectors provided in PR bin/289198.
+ { "fe80::1/64", 64, "fe80::/64" },
+ { "fe80::f000:74ff:fe54:bed2/64",
+ 64, "fe80::/64" },
+ { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64",
+ 64, "ffff:ffff:ffff:ffff::/64" },
+ { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128,
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" },
+ };
+
+ for (auto const &addr: test_addrs) {
+ /*
+ * Convert the input string to an in6_addr + bits, and make
+ * sure the result produces the number of bits we expected.
+ */
+
+ auto in6 = in6_addr{};
+ errno = 0;
+ auto bits = inet_net_pton(AF_INET6, addr.input.c_str(),
+ &in6, sizeof(in6));
+ ATF_REQUIRE(bits != -1);
+ ATF_REQUIRE_EQ(bits, addr.bits);
+
+ /*
+ * Convert the in6_addr back to a string
+ */
+
+ /*
+ * XXX: Should there be a constant for the size of the result
+ * buffer? For now, use ADDRSTRLEN + 4 ("/128") + 1 (NUL).
+ *
+ * Fill the buffer with 'Z', so we can check the result was
+ * properly terminated.
+ */
+ auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1, 'Z');
+ auto ret = inet_net_ntop(AF_INET6, &in6, bits,
+ strbuf.data(), strbuf.size());
+ ATF_REQUIRE(ret != NULL);
+ ATF_REQUIRE_EQ(ret, strbuf.data());
+
+ /* Make sure the result was NUL-terminated and find the NUL */
+ ATF_REQUIRE(strbuf.size() >= 1);
+ auto end = std::ranges::find(strbuf, '\0');
+ ATF_REQUIRE(end != strbuf.end());
+
+ /*
+ * Check the result matches what we expect. Use a temporary
+ * string here instead of std::ranges::equal because this
+ * means ATF can print the mismatch.
+ */
+ auto str = std::string(std::ranges::begin(strbuf), end);
+ ATF_REQUIRE_EQ(str, addr.output);
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(inet_net_pton_invalid)
+ATF_TEST_CASE_BODY(inet_net_pton_invalid)
+{
+ auto ret = int{};
+ auto addr4 = in_addr{};
+ auto str4 = "10.0.0.0"s;
+ auto addr6 = in6_addr{};
+ auto str6 = "2001:db8::"s;
+
+ /* Passing an address which is too short should be an error */
+ ret = inet_net_pton(AF_INET6, str6.c_str(), &addr6, sizeof(addr6) - 1);
+ ATF_REQUIRE_EQ(ret, -1);
+
+ ret = inet_net_pton(AF_INET, str4.c_str(), &addr4, sizeof(addr4) - 1);
+ ATF_REQUIRE_EQ(ret, -1);
+
+ /* Test some generally invalid addresses. */
+ auto invalid4 = std::vector<std::string>{
+ // Prefix length too big
+ "10.0.0.0/33",
+ // Prefix length is negative
+ "10.0.0.0/-1",
+ // Prefix length is not a number
+ "10.0.0.0/foo",
+ // Input is not a network prefix
+ "this is not an IP address",
+ };
+
+ for (auto const &addr: invalid4) {
+ auto ret = inet_net_pton(AF_INET, addr.c_str(), &addr4,
+ sizeof(addr4));
+ ATF_REQUIRE_EQ(ret, -1);
+ }
+
+ auto invalid6 = std::vector<std::string>{
+ // Prefix length too big
+ "2001:db8::/129",
+ // Prefix length is negative
+ "2001:db8::/-1",
+ // Prefix length is not a number
+ "2001:db8::/foo",
+ // Input is not a network prefix
+ "this is not an IP address",
+ };
+
+ for (auto const &addr: invalid6) {
+ auto ret = inet_net_pton(AF_INET6, addr.c_str(), &addr6,
+ sizeof(addr6));
+ ATF_REQUIRE_EQ(ret, -1);
+ }
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(inet_net_ntop_invalid)
+ATF_TEST_CASE_BODY(inet_net_ntop_invalid)
+{
+ auto addr4 = in_addr{};
+ auto addr6 = in6_addr{};
+ auto strbuf = std::vector<char>(INET6_ADDRSTRLEN + 4 + 1);
+
+ /*
+ * Passing a buffer which is too small should not overrun the buffer.
+ * Test this by initialising the buffer to 'Z', and only providing
+ * part of it to the function.
+ */
+
+ std::ranges::fill(strbuf, 'Z');
+ auto ret = inet_net_ntop(AF_INET6, &addr6, 128, strbuf.data(), 1);
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ATF_REQUIRE_EQ(strbuf[1], 'Z');
+
+ std::ranges::fill(strbuf, 'Z');
+ ret = inet_net_ntop(AF_INET, &addr4, 32, strbuf.data(), 1);
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ATF_REQUIRE_EQ(strbuf[1], 'Z');
+
+ /* Check that invalid prefix lengths return an error */
+
+ ret = inet_net_ntop(AF_INET6, &addr6, 129, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ret = inet_net_ntop(AF_INET6, &addr6, -1, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+
+ ret = inet_net_ntop(AF_INET, &addr4, 33, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+ ret = inet_net_ntop(AF_INET, &addr4, -1, strbuf.data(), strbuf.size());
+ ATF_REQUIRE_EQ(ret, nullptr);
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, inet_net_inet4);
+ ATF_ADD_TEST_CASE(tcs, inet_net_inet6);
+ ATF_ADD_TEST_CASE(tcs, inet_net_pton_invalid);
+ ATF_ADD_TEST_CASE(tcs, inet_net_ntop_invalid);
+}
diff --git a/lib/libc/tests/net/link_addr_test.cc b/lib/libc/tests/net/link_addr_test.cc
new file mode 100644
index 000000000000..b973b924dc13
--- /dev/null
+++ b/lib/libc/tests/net/link_addr_test.cc
@@ -0,0 +1,532 @@
+/*
+ * Copyright (c) 2025 Lexi Winter
+ *
+ * SPDX-License-Identifier: ISC
+ */
+
+/*
+ * Tests for link_addr() and link_ntoa().
+ *
+ * link_addr converts a string representing an (optionally null) interface name
+ * followed by an Ethernet address into a sockaddr_dl. The expected format is
+ * "[ifname]:lladdr". This means if ifname is not specified, the leading colon
+ * is still required.
+ *
+ * link_ntoa does the inverse of link_addr, returning a string representation
+ * of the address.
+ *
+ * Note that the output format of link_ntoa is not valid input for link_addr
+ * since the leading colon may be omitted. This is by design.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+
+#include <format>
+#include <iostream>
+#include <ranges>
+#include <span>
+#include <utility>
+#include <vector>
+
+#include <cstddef>
+#include <cstdint>
+
+#include <atf-c++.hpp>
+
+using namespace std::literals;
+
+/*
+ * Define operator== and operator<< for ether_addr so we can use them in
+ * ATF_EXPECT_EQ expressions.
+ */
+
+bool
+operator==(ether_addr a, ether_addr b)
+{
+ return (std::ranges::equal(a.octet, b.octet));
+}
+
+std::ostream &
+operator<<(std::ostream &s, ether_addr a)
+{
+ for (unsigned i = 0; i < ETHER_ADDR_LEN; ++i) {
+ if (i > 0)
+ s << ":";
+
+ s << std::format("{:02x}", static_cast<int>(a.octet[i]));
+ }
+
+ return (s);
+}
+
+/*
+ * Create a sockaddr_dl from a string using link_addr(), and ensure the
+ * returned struct looks valid.
+ */
+sockaddr_dl
+make_linkaddr(const std::string &addr)
+{
+ auto sdl = sockaddr_dl{};
+ int ret;
+
+ sdl.sdl_len = sizeof(sdl);
+ ret = ::link_addr(addr.c_str(), &sdl);
+
+ ATF_REQUIRE_EQ(0, ret);
+ ATF_REQUIRE_EQ(AF_LINK, static_cast<int>(sdl.sdl_family));
+ ATF_REQUIRE_EQ(true, sdl.sdl_len > 0);
+ ATF_REQUIRE_EQ(true, sdl.sdl_nlen >= 0);
+
+ return (sdl);
+}
+
+/*
+ * Return the data stored in a sockaddr_dl as a span.
+ */
+std::span<const char>
+data(const sockaddr_dl &sdl)
+{
+ // sdl_len is the entire structure, but we only want the length of the
+ // data area.
+ auto dlen = sdl.sdl_len - offsetof(sockaddr_dl, sdl_data);
+ return {&sdl.sdl_data[0], dlen};
+}
+
+/*
+ * Return the interface name stored in a sockaddr_dl as a string.
+ */
+std::string_view
+ifname(const sockaddr_dl &sdl)
+{
+ auto name = data(sdl).subspan(0, sdl.sdl_nlen);
+ return {name.begin(), name.end()};
+}
+
+/*
+ * Return the Ethernet address stored in a sockaddr_dl as an ether_addr.
+ */
+ether_addr
+addr(const sockaddr_dl &sdl)
+{
+ ether_addr ret{};
+ ATF_REQUIRE_EQ(ETHER_ADDR_LEN, sdl.sdl_alen);
+ std::ranges::copy(data(sdl).subspan(sdl.sdl_nlen, ETHER_ADDR_LEN),
+ &ret.octet[0]);
+ return (ret);
+}
+
+/*
+ * Return the link address stored in a sockaddr_dl as a span of octets.
+ */
+std::span<const std::uint8_t>
+lladdr(const sockaddr_dl &sdl)
+{
+ auto data = reinterpret_cast<const uint8_t *>(LLADDR(&sdl));
+ return {data, data + sdl.sdl_alen};
+}
+
+
+/*
+ * Some sample addresses we use for testing. Include at least one address for
+ * each format we want to support.
+ */
+
+struct test_address {
+ std::string input; /* value passed to link_addr */
+ std::string ntoa; /* expected return from link_ntoa */
+ ether_addr addr{}; /* expected return from link_addr */
+};
+
+std::vector<test_address> test_addresses{
+ // No delimiter
+ {"001122334455"s, "0.11.22.33.44.55",
+ ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
+
+ // Colon delimiter
+ {"00:11:22:33:44:55"s, "0.11.22.33.44.55",
+ ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
+
+ // Dash delimiter
+ {"00-11-22-33-44-55"s, "0.11.22.33.44.55",
+ ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
+
+ // Period delimiter (link_ntoa format)
+ {"00.11.22.33.44.55"s, "0.11.22.33.44.55",
+ ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
+
+ // Period delimiter (Cisco format)
+ {"0011.2233.4455"s, "0.11.22.33.44.55",
+ ether_addr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}},
+
+ // An addresses without leading zeroes.
+ {"0:1:02:30:4:55"s, "0.1.2.30.4.55",
+ ether_addr{0x00, 0x01, 0x02, 0x30, 0x04, 0x55}},
+
+ // An address with some uppercase letters.
+ {"AA:B:cC:Dd:e0:1f"s, "aa.b.cc.dd.e0.1f",
+ ether_addr{0xaa, 0x0b, 0xcc, 0xdd, 0xe0, 0x1f}},
+
+ // Addresses composed only of letters, to make sure they're not
+ // confused with an interface name.
+
+ {"aabbccddeeff"s, "aa.bb.cc.dd.ee.ff",
+ ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},
+
+ {"aa:bb:cc:dd:ee:ff"s, "aa.bb.cc.dd.ee.ff",
+ ether_addr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},
+
+ // Address with a blank octet; this is an old form of Ethernet address.
+ {"00:11::33:44:55"s, "0.11.0.33.44.55",
+ ether_addr{0x00, 0x11, 0x00, 0x33, 0x44, 0x55}},
+};
+
+/*
+ * Test without an interface name.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(basic)
+ATF_TEST_CASE_BODY(basic)
+{
+ for (const auto &ta : test_addresses) {
+ // This does basic tests on the returned value.
+ auto sdl = make_linkaddr(":" + ta.input);
+
+ // Check the ifname and address.
+ ATF_REQUIRE_EQ(""s, ifname(sdl));
+ ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));
+ ATF_REQUIRE_EQ(ta.addr, addr(sdl));
+
+ // Check link_ntoa returns the expected value.
+ auto ntoa = std::string(::link_ntoa(&sdl));
+ ATF_REQUIRE_EQ(ta.ntoa, ntoa);
+ }
+
+}
+
+/*
+ * Test with an interface name.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(ifname)
+ATF_TEST_CASE_BODY(ifname)
+{
+ for (const auto &ta : test_addresses) {
+ auto sdl = make_linkaddr("ix0:" + ta.input);
+
+ ATF_REQUIRE_EQ("ix0", ifname(sdl));
+ ATF_REQUIRE_EQ(ETHER_ADDR_LEN, static_cast<int>(sdl.sdl_alen));
+ ATF_REQUIRE_EQ(ta.addr, addr(sdl));
+
+ auto ntoa = std::string(::link_ntoa(&sdl));
+ ATF_REQUIRE_EQ("ix0:" + ta.ntoa, ntoa);
+ }
+
+}
+
+/*
+ * Test with some invalid addresses.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(invalid)
+ATF_TEST_CASE_BODY(invalid)
+{
+ auto const invalid_addresses = std::vector{
+ // Invalid separator
+ ":1/2/3"s,
+ "ix0:1/2/3"s,
+
+ // Multiple different separators
+ ":1.2-3"s,
+ "ix0:1.2-3"s,
+
+ // An IP address
+ ":10.1.2.200/28"s,
+ "ix0:10.1.2.200/28"s,
+
+ // Valid address followed by garbage
+ ":1.2.3xxx"s,
+ ":1.2.3.xxx"s,
+ "ix0:1.2.3xxx"s,
+ "ix0:1.2.3.xxx"s,
+ };
+
+ for (auto const &addr : invalid_addresses) {
+ int ret;
+
+ auto sdl = sockaddr_dl{};
+ sdl.sdl_len = sizeof(sdl);
+
+ ret = link_addr(addr.c_str(), &sdl);
+ ATF_REQUIRE_EQ(-1, ret);
+ }
+}
+
+/*
+ * Test some non-Ethernet addresses.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(nonether)
+ATF_TEST_CASE_BODY(nonether)
+{
+ sockaddr_dl sdl;
+
+ /* A short address */
+ sdl = make_linkaddr(":1:23:cc");
+ ATF_REQUIRE_EQ("", ifname(sdl));
+ ATF_REQUIRE_EQ("1.23.cc"s, ::link_ntoa(&sdl));
+ ATF_REQUIRE_EQ(3, sdl.sdl_alen);
+ ATF_REQUIRE_EQ(true,
+ std::ranges::equal(std::vector{0x01u, 0x23u, 0xccu}, lladdr(sdl)));
+
+ /* A long address */
+ sdl = make_linkaddr(":1:23:cc:a:b:c:d:e:f");
+ ATF_REQUIRE_EQ("", ifname(sdl));
+ ATF_REQUIRE_EQ("1.23.cc.a.b.c.d.e.f"s, ::link_ntoa(&sdl));
+ ATF_REQUIRE_EQ(9, sdl.sdl_alen);
+ ATF_REQUIRE_EQ(true, std::ranges::equal(
+ std::vector{0x01u, 0x23u, 0xccu, 0xau, 0xbu, 0xcu, 0xdu, 0xeu, 0xfu},
+ lladdr(sdl)));
+}
+
+/*
+ * Test link_addr behaviour with undersized buffers.
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(smallbuf)
+ATF_TEST_CASE_BODY(smallbuf)
+{
+ static constexpr auto garbage = std::byte{0xcc};
+ auto buf = std::vector<std::byte>();
+ sockaddr_dl *sdl;
+ int ret;
+
+ /*
+ * Make an sdl with an sdl_data member of the appropriate size, and
+ * place it in buf. Ensure it's followed by a trailing byte of garbage
+ * so we can test that link_addr doesn't write past the end.
+ */
+ auto mksdl = [&buf](std::size_t datalen) -> sockaddr_dl * {
+ auto actual_size = datalen + offsetof(sockaddr_dl, sdl_data);
+
+ buf.resize(actual_size + 1);
+ std::ranges::fill(buf, garbage);
+
+ auto *sdl = new(reinterpret_cast<sockaddr_dl *>(&buf[0]))
+ sockaddr_dl;
+ sdl->sdl_len = actual_size;
+ return (sdl);
+ };
+
+ /* An sdl large enough to store the interface name */
+ sdl = mksdl(3);
+ ret = link_addr("ix0:1.2.3", sdl);
+ ATF_REQUIRE(*rbegin(buf) == garbage);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(ENOSPC, errno);
+ ATF_REQUIRE_EQ(3, sdl->sdl_nlen);
+ ATF_REQUIRE_EQ("ix0", ifname(*sdl));
+ ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
+
+ /*
+ * For these tests, test both with and without an interface name, since
+ * that will overflow the buffer in different places.
+ */
+
+ /* An empty sdl. Nothing may grow on this cursed ground. */
+
+ sdl = mksdl(0);
+ ret = link_addr("ix0:1.2.3", sdl);
+ ATF_REQUIRE(*rbegin(buf) == garbage);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(ENOSPC, errno);
+ ATF_REQUIRE_EQ(0, sdl->sdl_nlen);
+ ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
+
+ sdl = mksdl(0);
+ ret = link_addr(":1.2.3", sdl);
+ ATF_REQUIRE(*rbegin(buf) == garbage);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(ENOSPC, errno);
+ ATF_REQUIRE_EQ(0, sdl->sdl_nlen);
+ ATF_REQUIRE_EQ(0, static_cast<int>(sdl->sdl_alen));
+
+ /*
+ * An sdl large enough to store the interface name and two octets of the
+ * address.
+ */
+
+ sdl = mksdl(5);
+ ret = link_addr("ix0:1.2.3", sdl);
+ ATF_REQUIRE(*rbegin(buf) == garbage);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(ENOSPC, errno);
+ ATF_REQUIRE_EQ("ix0", ifname(*sdl));
+ ATF_REQUIRE(std::ranges::equal(
+ std::vector{ 0x01, 0x02 }, lladdr(*sdl)));
+
+ sdl = mksdl(2);
+ ret = link_addr(":1.2.3", sdl);
+ ATF_REQUIRE(*rbegin(buf) == garbage);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(ENOSPC, errno);
+ ATF_REQUIRE_EQ("", ifname(*sdl));
+ ATF_REQUIRE(std::ranges::equal(
+ std::vector{ 0x01, 0x02 }, lladdr(*sdl)));
+
+ /*
+ * An sdl large enough to store the entire address.
+ */
+
+ sdl = mksdl(6);
+ ret = link_addr("ix0:1.2.3", sdl);
+ ATF_REQUIRE(*rbegin(buf) == garbage);
+ ATF_REQUIRE_EQ(0, ret);
+ ATF_REQUIRE_EQ("ix0", ifname(*sdl));
+ ATF_REQUIRE(std::ranges::equal(
+ std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));
+
+ sdl = mksdl(3);
+ ret = link_addr(":1.2.3", sdl);
+ ATF_REQUIRE(*rbegin(buf) == garbage);
+ ATF_REQUIRE_EQ(0, ret);
+ ATF_REQUIRE_EQ("", ifname(*sdl));
+ ATF_REQUIRE(std::ranges::equal(
+ std::vector{ 0x01, 0x02, 0x03 }, lladdr(*sdl)));
+}
+
+/*
+ * Test an extremely long address which would overflow link_ntoa's internal
+ * buffer. It should handle this by truncating the output.
+ * (Test for SA-16:37.libc / CVE-2016-6559.)
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(overlong)
+ATF_TEST_CASE_BODY(overlong)
+{
+ auto sdl = make_linkaddr(
+ ":01.02.03.04.05.06.07.08.09.0a.0b.0c.0d.0e.0f."
+ "11.12.13.14.15.16.17.18.19.1a.1b.1c.1d.1e.1f."
+ "22.22.23.24.25.26.27.28.29.2a.2b.2c.2d.2e.2f");
+
+ ATF_REQUIRE_EQ(
+ "1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.11.12.13.14.15.16.17.18.19.1a.1b."s,
+ ::link_ntoa(&sdl));
+}
+
+/*
+ * Test link_ntoa_r, the re-entrant version of link_ntoa().
+ */
+ATF_TEST_CASE_WITHOUT_HEAD(link_ntoa_r)
+ATF_TEST_CASE_BODY(link_ntoa_r)
+{
+ static constexpr char garbage = 0x41;
+ std::vector<char> buf;
+ sockaddr_dl sdl;
+ size_t len;
+ int ret;
+
+ // Return the contents of buf as a string, using the NUL terminator to
+ // determine length. This is to ensure we're using the return value in
+ // the same way C code would, but we do a bit more verification to
+ // elicit a test failure rather than a SEGV if it's broken.
+ auto bufstr = [&buf]() -> std::string_view {
+ // Find the NUL.
+ auto end = std::ranges::find(buf, '\0');
+ ATF_REQUIRE(end != buf.end());
+
+ // Intentionally chopping the NUL off.
+ return {begin(buf), end};
+ };
+
+ // Resize the buffer and set the contents to a known garbage value, so
+ // we don't accidentally have a NUL in the right place when link_ntoa_r
+ // didn't put it there.
+ auto resetbuf = [&buf, &len](std::size_t size) {
+ len = size;
+ buf.resize(len);
+ std::ranges::fill(buf, garbage);
+ };
+
+ // Test a short address with a large buffer.
+ sdl = make_linkaddr("ix0:1.2.3");
+ resetbuf(64);
+ ret = ::link_ntoa_r(&sdl, &buf[0], &len);
+ ATF_REQUIRE_EQ(0, ret);
+ ATF_REQUIRE_EQ(10, len);
+ ATF_REQUIRE_EQ("ix0:1.2.3"s, bufstr());
+
+ // Test a buffer which is exactly the right size.
+ sdl = make_linkaddr("ix0:1.2.3");
+ resetbuf(10);
+ ret = ::link_ntoa_r(&sdl, &buf[0], &len);
+ ATF_REQUIRE_EQ(0, ret);
+ ATF_REQUIRE_EQ(10, len);
+ ATF_REQUIRE_EQ("ix0:1.2.3"sv, bufstr());
+
+ // Test various short buffers, using a table of buffer length and the
+ // output we expect. All of these should produce valid but truncated
+ // strings, with a trailing NUL and with buflen set correctly.
+
+ auto buftests = std::vector<std::pair<std::size_t, std::string_view>>{
+ {1u, ""sv},
+ {2u, ""sv},
+ {3u, ""sv},
+ {4u, "ix0"sv},
+ {5u, "ix0:"sv},
+ {6u, "ix0:1"sv},
+ {7u, "ix0:1."sv},
+ {8u, "ix0:1.2"sv},
+ {9u, "ix0:1.2."sv},
+ };
+
+ for (auto const &[buflen, expected] : buftests) {
+ sdl = make_linkaddr("ix0:1.2.3");
+ resetbuf(buflen);
+ ret = ::link_ntoa_r(&sdl, &buf[0], &len);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(10, len);
+ ATF_REQUIRE_EQ(expected, bufstr());
+ }
+
+ // Test a NULL buffer, which should just set buflen.
+ sdl = make_linkaddr("ix0:1.2.3");
+ len = 0;
+ ret = ::link_ntoa_r(&sdl, NULL, &len);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(10, len);
+
+ // A NULL buffer with a non-zero length should also be accepted.
+ sdl = make_linkaddr("ix0:1.2.3");
+ len = 64;
+ ret = ::link_ntoa_r(&sdl, NULL, &len);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(10, len);
+
+ // Test a non-NULL buffer, but with a length of zero.
+ sdl = make_linkaddr("ix0:1.2.3");
+ resetbuf(1);
+ len = 0;
+ ret = ::link_ntoa_r(&sdl, &buf[0], &len);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(10, len);
+ // Check we really didn't write anything.
+ ATF_REQUIRE_EQ(garbage, buf[0]);
+
+ // Test a buffer which would be truncated in the middle of a two-digit
+ // hex octet, which should not write the truncated octet at all.
+ sdl = make_linkaddr("ix0:1.22.3");
+ resetbuf(8);
+ ret = ::link_ntoa_r(&sdl, &buf[0], &len);
+ ATF_REQUIRE_EQ(-1, ret);
+ ATF_REQUIRE_EQ(11, len);
+ ATF_REQUIRE_EQ("ix0:1."sv, bufstr());
+}
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, basic);
+ ATF_ADD_TEST_CASE(tcs, ifname);
+ ATF_ADD_TEST_CASE(tcs, smallbuf);
+ ATF_ADD_TEST_CASE(tcs, invalid);
+ ATF_ADD_TEST_CASE(tcs, nonether);
+ ATF_ADD_TEST_CASE(tcs, overlong);
+ ATF_ADD_TEST_CASE(tcs, link_ntoa_r);
+}
diff --git a/lib/libc/tests/secure/Makefile b/lib/libc/tests/secure/Makefile
index beaa01457cfe..515f8f53a43e 100644
--- a/lib/libc/tests/secure/Makefile
+++ b/lib/libc/tests/secure/Makefile
@@ -10,6 +10,7 @@ FORTIFY_TCATS+= uio
# non-sys/ headers
FORTIFY_TCATS+= poll
+FORTIFY_TCATS+= signal
FORTIFY_TCATS+= stdlib
FORTIFY_TCATS+= stdio
FORTIFY_TCATS+= string
diff --git a/lib/libc/tests/secure/fortify_signal_test.c b/lib/libc/tests/secure/fortify_signal_test.c
new file mode 100644
index 000000000000..03cfb9a9a13a
--- /dev/null
+++ b/lib/libc/tests/secure/fortify_signal_test.c
@@ -0,0 +1,316 @@
+/* @generated by `generate-fortify-tests.lua "signal"` */
+
+#define _FORTIFY_SOURCE 2
+#define TMPFILE_SIZE (1024 * 32)
+
+#include <sys/param.h>
+#include <sys/jail.h>
+#include <sys/random.h>
+#include <sys/resource.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <atf-c.h>
+
+static FILE * __unused
+new_fp(size_t __len)
+{
+ static char fpbuf[LINE_MAX];
+ FILE *fp;
+
+ ATF_REQUIRE(__len <= sizeof(fpbuf));
+
+ memset(fpbuf, 'A', sizeof(fpbuf) - 1);
+ fpbuf[sizeof(fpbuf) - 1] = '\0';
+
+ fp = fmemopen(fpbuf, sizeof(fpbuf), "rb");
+ ATF_REQUIRE(fp != NULL);
+
+ return (fp);
+}
+
+/*
+ * Create a new symlink to use for readlink(2) style tests, we'll just use a
+ * random target name to have something interesting to look at.
+ */
+static const char * __unused
+new_symlink(size_t __len)
+{
+ static const char linkname[] = "link";
+ char target[MAXNAMLEN];
+ int error;
+
+ ATF_REQUIRE(__len <= sizeof(target));
+
+ arc4random_buf(target, sizeof(target));
+
+ error = unlink(linkname);
+ ATF_REQUIRE(error == 0 || errno == ENOENT);
+
+ error = symlink(target, linkname);
+ ATF_REQUIRE(error == 0);
+
+ return (linkname);
+}
+
+/*
+ * For our purposes, first descriptor will be the reader; we'll send both
+ * raw data and a control message over it so that the result can be used for
+ * any of our recv*() tests.
+ */
+static void __unused
+new_socket(int sock[2])
+{
+ unsigned char ctrl[CMSG_SPACE(sizeof(int))] = { 0 };
+ static char sockbuf[256];
+ ssize_t rv;
+ size_t total = 0;
+ struct msghdr hdr = { 0 };
+ struct cmsghdr *cmsg;
+ int error, fd;
+
+ error = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
+ ATF_REQUIRE(error == 0);
+
+ while (total != sizeof(sockbuf)) {
+ rv = send(sock[1], &sockbuf[total], sizeof(sockbuf) - total, 0);
+
+ ATF_REQUIRE_MSG(rv > 0,
+ "expected bytes sent, got %zd with %zu left (size %zu, total %zu)",
+ rv, sizeof(sockbuf) - total, sizeof(sockbuf), total);
+ ATF_REQUIRE_MSG(total + (size_t)rv <= sizeof(sockbuf),
+ "%zd exceeds total %zu", rv, sizeof(sockbuf));
+ total += rv;
+ }
+
+ hdr.msg_control = ctrl;
+ hdr.msg_controllen = sizeof(ctrl);
+
+ cmsg = CMSG_FIRSTHDR(&hdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+ fd = STDIN_FILENO;
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
+
+ error = sendmsg(sock[1], &hdr, 0);
+ ATF_REQUIRE(error != -1);
+}
+
+/*
+ * Constructs a tmpfile that we can use for testing read(2) and friends.
+ */
+static int __unused
+new_tmpfile(void)
+{
+ char buf[1024];
+ ssize_t rv;
+ size_t written;
+ int fd;
+
+ fd = open("tmpfile", O_RDWR | O_CREAT | O_TRUNC, 0644);
+ ATF_REQUIRE(fd >= 0);
+
+ written = 0;
+ while (written < TMPFILE_SIZE) {
+ rv = write(fd, buf, sizeof(buf));
+ ATF_REQUIRE(rv > 0);
+
+ written += rv;
+ }
+
+ ATF_REQUIRE_EQ(0, lseek(fd, 0, SEEK_SET));
+ return (fd);
+}
+
+static void
+disable_coredumps(void)
+{
+ struct rlimit rl = { 0 };
+
+ if (setrlimit(RLIMIT_CORE, &rl) == -1)
+ _exit(EX_OSERR);
+}
+
+/*
+ * Replaces stdin with a file that we can actually read from, for tests where
+ * we want a FILE * or fd that we can get data from.
+ */
+static void __unused
+replace_stdin(void)
+{
+ int fd;
+
+ fd = new_tmpfile();
+
+ (void)dup2(fd, STDIN_FILENO);
+ if (fd != STDIN_FILENO)
+ close(fd);
+}
+
+ATF_TC(sig2str_before_end);
+ATF_TC_HEAD(sig2str_before_end, tc)
+{
+}
+ATF_TC_BODY(sig2str_before_end, tc)
+{
+#define BUF &__stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char __buf[SIG2STR_MAX + 1];
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(__stack.__buf);
+ const size_t __len = SIG2STR_MAX + 1;
+ const size_t __idx __unused = __len - 1;
+
+ sig2str(1, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC(sig2str_end);
+ATF_TC_HEAD(sig2str_end, tc)
+{
+}
+ATF_TC_BODY(sig2str_end, tc)
+{
+#define BUF &__stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char __buf[SIG2STR_MAX];
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(__stack.__buf);
+ const size_t __len = SIG2STR_MAX;
+ const size_t __idx __unused = __len - 1;
+
+ sig2str(1, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC(sig2str_heap_before_end);
+ATF_TC_HEAD(sig2str_heap_before_end, tc)
+{
+}
+ATF_TC_BODY(sig2str_heap_before_end, tc)
+{
+#define BUF __stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char * __buf;
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(*__stack.__buf) * (SIG2STR_MAX + 1);
+ const size_t __len = SIG2STR_MAX + 1;
+ const size_t __idx __unused = __len - 1;
+
+ __stack.__buf = malloc(__bufsz);
+
+ sig2str(1, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC(sig2str_heap_end);
+ATF_TC_HEAD(sig2str_heap_end, tc)
+{
+}
+ATF_TC_BODY(sig2str_heap_end, tc)
+{
+#define BUF __stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char * __buf;
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(*__stack.__buf) * (SIG2STR_MAX);
+ const size_t __len = SIG2STR_MAX;
+ const size_t __idx __unused = __len - 1;
+
+ __stack.__buf = malloc(__bufsz);
+
+ sig2str(1, __stack.__buf);
+#undef BUF
+
+}
+
+ATF_TC(sig2str_heap_after_end);
+ATF_TC_HEAD(sig2str_heap_after_end, tc)
+{
+}
+ATF_TC_BODY(sig2str_heap_after_end, tc)
+{
+#define BUF __stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char * __buf;
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(*__stack.__buf) * (SIG2STR_MAX - 1);
+ const size_t __len = SIG2STR_MAX - 1;
+ const size_t __idx __unused = __len - 1;
+ pid_t __child;
+ int __status;
+
+ __child = fork();
+ ATF_REQUIRE(__child >= 0);
+ if (__child > 0)
+ goto monitor;
+
+ /* Child */
+ disable_coredumps();
+ __stack.__buf = malloc(__bufsz);
+
+ sig2str(1, __stack.__buf);
+ _exit(EX_SOFTWARE); /* Should have aborted. */
+
+monitor:
+ while (waitpid(__child, &__status, 0) != __child) {
+ ATF_REQUIRE_EQ(EINTR, errno);
+ }
+
+ if (!WIFSIGNALED(__status)) {
+ switch (WEXITSTATUS(__status)) {
+ case EX_SOFTWARE:
+ atf_tc_fail("FORTIFY_SOURCE failed to abort");
+ break;
+ case EX_OSERR:
+ atf_tc_fail("setrlimit(2) failed");
+ break;
+ default:
+ atf_tc_fail("child exited with status %d",
+ WEXITSTATUS(__status));
+ }
+ } else {
+ ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
+ }
+#undef BUF
+
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, sig2str_before_end);
+ ATF_TP_ADD_TC(tp, sig2str_end);
+ ATF_TP_ADD_TC(tp, sig2str_heap_before_end);
+ ATF_TP_ADD_TC(tp, sig2str_heap_end);
+ ATF_TP_ADD_TC(tp, sig2str_heap_after_end);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/secure/fortify_stdlib_test.c b/lib/libc/tests/secure/fortify_stdlib_test.c
index ae021e8418f7..d0b1af78da86 100644
--- a/lib/libc/tests/secure/fortify_stdlib_test.c
+++ b/lib/libc/tests/secure/fortify_stdlib_test.c
@@ -305,6 +305,148 @@ monitor:
}
+ATF_TC(getenv_r_before_end);
+ATF_TC_HEAD(getenv_r_before_end, tc)
+{
+}
+ATF_TC_BODY(getenv_r_before_end, tc)
+{
+#define BUF &__stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char __buf[42];
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(__stack.__buf);
+ const size_t __len = 42 - 1;
+ const size_t __idx __unused = __len - 1;
+
+ getenv_r("PATH", __stack.__buf, __len);
+#undef BUF
+
+}
+
+ATF_TC(getenv_r_end);
+ATF_TC_HEAD(getenv_r_end, tc)
+{
+}
+ATF_TC_BODY(getenv_r_end, tc)
+{
+#define BUF &__stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char __buf[42];
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(__stack.__buf);
+ const size_t __len = 42;
+ const size_t __idx __unused = __len - 1;
+
+ getenv_r("PATH", __stack.__buf, __len);
+#undef BUF
+
+}
+
+ATF_TC(getenv_r_heap_before_end);
+ATF_TC_HEAD(getenv_r_heap_before_end, tc)
+{
+}
+ATF_TC_BODY(getenv_r_heap_before_end, tc)
+{
+#define BUF __stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char * __buf;
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
+ const size_t __len = 42 - 1;
+ const size_t __idx __unused = __len - 1;
+
+ __stack.__buf = malloc(__bufsz);
+
+ getenv_r("PATH", __stack.__buf, __len);
+#undef BUF
+
+}
+
+ATF_TC(getenv_r_heap_end);
+ATF_TC_HEAD(getenv_r_heap_end, tc)
+{
+}
+ATF_TC_BODY(getenv_r_heap_end, tc)
+{
+#define BUF __stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char * __buf;
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
+ const size_t __len = 42;
+ const size_t __idx __unused = __len - 1;
+
+ __stack.__buf = malloc(__bufsz);
+
+ getenv_r("PATH", __stack.__buf, __len);
+#undef BUF
+
+}
+
+ATF_TC(getenv_r_heap_after_end);
+ATF_TC_HEAD(getenv_r_heap_after_end, tc)
+{
+}
+ATF_TC_BODY(getenv_r_heap_after_end, tc)
+{
+#define BUF __stack.__buf
+ struct {
+ uint8_t padding_l;
+ unsigned char * __buf;
+ uint8_t padding_r;
+ } __stack;
+ const size_t __bufsz __unused = sizeof(*__stack.__buf) * (42);
+ const size_t __len = 42 + 1;
+ const size_t __idx __unused = __len - 1;
+ pid_t __child;
+ int __status;
+
+ __child = fork();
+ ATF_REQUIRE(__child >= 0);
+ if (__child > 0)
+ goto monitor;
+
+ /* Child */
+ disable_coredumps();
+ __stack.__buf = malloc(__bufsz);
+
+ getenv_r("PATH", __stack.__buf, __len);
+ _exit(EX_SOFTWARE); /* Should have aborted. */
+
+monitor:
+ while (waitpid(__child, &__status, 0) != __child) {
+ ATF_REQUIRE_EQ(EINTR, errno);
+ }
+
+ if (!WIFSIGNALED(__status)) {
+ switch (WEXITSTATUS(__status)) {
+ case EX_SOFTWARE:
+ atf_tc_fail("FORTIFY_SOURCE failed to abort");
+ break;
+ case EX_OSERR:
+ atf_tc_fail("setrlimit(2) failed");
+ break;
+ default:
+ atf_tc_fail("child exited with status %d",
+ WEXITSTATUS(__status));
+ }
+ } else {
+ ATF_REQUIRE_EQ(SIGABRT, WTERMSIG(__status));
+ }
+#undef BUF
+
+}
+
ATF_TC(realpath_before_end);
ATF_TC_HEAD(realpath_before_end, tc)
{
@@ -454,6 +596,11 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, arc4random_buf_heap_before_end);
ATF_TP_ADD_TC(tp, arc4random_buf_heap_end);
ATF_TP_ADD_TC(tp, arc4random_buf_heap_after_end);
+ ATF_TP_ADD_TC(tp, getenv_r_before_end);
+ ATF_TP_ADD_TC(tp, getenv_r_end);
+ ATF_TP_ADD_TC(tp, getenv_r_heap_before_end);
+ ATF_TP_ADD_TC(tp, getenv_r_heap_end);
+ ATF_TP_ADD_TC(tp, getenv_r_heap_after_end);
ATF_TP_ADD_TC(tp, realpath_before_end);
ATF_TP_ADD_TC(tp, realpath_end);
ATF_TP_ADD_TC(tp, realpath_heap_before_end);
diff --git a/lib/libc/tests/secure/generate-fortify-tests.lua b/lib/libc/tests/secure/generate-fortify-tests.lua
index 36ff01af7a17..c9cd9353a869 100755
--- a/lib/libc/tests/secure/generate-fortify-tests.lua
+++ b/lib/libc/tests/secure/generate-fortify-tests.lua
@@ -478,6 +478,18 @@ local all_tests = {
init = poll_init,
},
},
+ signal = {
+ -- <signal.h>
+ {
+ func = "sig2str",
+ bufsize = "SIG2STR_MAX",
+ arguments = {
+ "1",
+ "__buf",
+ },
+ exclude = excludes_stack_overflow,
+ },
+ },
stdio = {
-- <stdio.h>
{
@@ -585,6 +597,15 @@ local all_tests = {
exclude = excludes_stack_overflow,
},
{
+ func = "getenv_r",
+ arguments = {
+ "\"PATH\"",
+ "__buf",
+ "__len",
+ },
+ exclude = excludes_stack_overflow,
+ },
+ {
func = "realpath",
bufsize = "PATH_MAX",
arguments = {
diff --git a/lib/libc/tests/stdio/flushlbuf_test.c b/lib/libc/tests/stdio/flushlbuf_test.c
index 11d9ea4ecc6c..3f8a6378f933 100644
--- a/lib/libc/tests/stdio/flushlbuf_test.c
+++ b/lib/libc/tests/stdio/flushlbuf_test.c
@@ -22,22 +22,23 @@ struct stream {
unsigned int pos;
};
-static int writefn(void *cookie, const char *buf, int len)
+static int
+writefn(void *cookie, const char *buf, int len)
{
struct stream *s = cookie;
int written = 0;
if (len <= 0)
- return 0;
+ return (0);
while (len > 0 && s->pos < s->len) {
s->buf[s->pos++] = *buf++;
written++;
len--;
}
if (written > 0)
- return written;
+ return (written);
errno = EAGAIN;
- return -1;
+ return (-1);
}
ATF_TC_WITHOUT_HEAD(flushlbuf_partial);
@@ -147,7 +148,6 @@ ATF_TC_BODY(flushlbuf_full, tc)
ATF_TP_ADD_TCS(tp)
{
-
ATF_TP_ADD_TC(tp, flushlbuf_partial);
ATF_TP_ADD_TC(tp, flushlbuf_full);
diff --git a/lib/libc/tests/stdio/printfloat_test.c b/lib/libc/tests/stdio/printfloat_test.c
index 031859124163..4493fe1c15d3 100644
--- a/lib/libc/tests/stdio/printfloat_test.c
+++ b/lib/libc/tests/stdio/printfloat_test.c
@@ -398,6 +398,18 @@ ATF_TC_BODY(subnormal_float, tc)
testfmt("-0X1P-149", "%A", negative);
}
+ATF_TC_WITHOUT_HEAD(hexadecimal_rounding_fullprec);
+ATF_TC_BODY(hexadecimal_rounding_fullprec, tc)
+{
+ /* Double: %.13a with binary64 mantissa=53 */
+ testfmt("0x1.1234567890bbbp+0", "%.13a", 0x1.1234567890bbbp+0);
+
+#if defined(__aarch64__)
+ /* On arm64, long double is IEEE binary128 (mantissa=113) */
+ testfmt("0x1.3c0ca428c59fbbbbbbbbbbbbbbbbp+0", "%.28La", 0x1.3c0ca428c59fbbbbbbbbbbbbbbbbp+0L);
+#endif
+}
+
ATF_TP_ADD_TCS(tp)
{
@@ -414,6 +426,7 @@ ATF_TP_ADD_TCS(tp)
ATF_TP_ADD_TC(tp, hexadecimal_rounding);
ATF_TP_ADD_TC(tp, subnormal_double);
ATF_TP_ADD_TC(tp, subnormal_float);
+ ATF_TP_ADD_TC(tp, hexadecimal_rounding_fullprec);
return (atf_no_error());
}
diff --git a/lib/libc/tests/stdio/scanfloat_test.c b/lib/libc/tests/stdio/scanfloat_test.c
index a93238504dd1..635d93d560b3 100644
--- a/lib/libc/tests/stdio/scanfloat_test.c
+++ b/lib/libc/tests/stdio/scanfloat_test.c
@@ -59,88 +59,88 @@ ATF_TC_BODY(normalized_numbers, tc)
buf[0] = '\0';
ATF_REQUIRE(setlocale(LC_NUMERIC, ""));
- sscanf("3.141592", "%e", &f);
+ ATF_REQUIRE_EQ(1, sscanf("3.141592", "%e", &f));
ATF_REQUIRE(eq(FLT, f, 3.141592));
- sscanf("3.141592653589793", "%lf", &d);
+ ATF_REQUIRE_EQ(1, sscanf("3.141592653589793", "%lf", &d));
ATF_REQUIRE(eq(DBL, d, 3.141592653589793));
- sscanf("1.234568e+06", "%E", &f);
+ ATF_REQUIRE_EQ(1, sscanf("1.234568e+06", "%E", &f));
ATF_REQUIRE(eq(FLT, f, 1.234568e+06));
- sscanf("-1.234568e6", "%lF", &d);
+ ATF_REQUIRE_EQ(1, sscanf("-1.234568e6", "%lF", &d));
ATF_REQUIRE(eq(DBL, d, -1.234568e6));
- sscanf("+1.234568e-52", "%LG", &ld);
+ ATF_REQUIRE_EQ(1, sscanf("+1.234568e-52", "%LG", &ld));
ATF_REQUIRE(eq(LDBL, ld, 1.234568e-52L));
- sscanf("0.1", "%la", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0.1", "%la", &d));
ATF_REQUIRE(eq(DBL, d, 0.1));
- sscanf("00.2", "%lA", &d);
+ ATF_REQUIRE_EQ(1, sscanf("00.2", "%lA", &d));
ATF_REQUIRE(eq(DBL, d, 0.2));
- sscanf("123456", "%5le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("123456", "%5le%s", &d, buf));
ATF_REQUIRE(eq(DBL, d, 12345.));
ATF_REQUIRE(strcmp(buf, "6") == 0);
- sscanf("1.0Q", "%*5le%s", buf);
+ ATF_REQUIRE_EQ(1, sscanf("1.0Q", "%*5le%s", buf));
ATF_REQUIRE(strcmp(buf, "Q") == 0);
- sscanf("-1.23e", "%e%s", &f, buf);
+ ATF_REQUIRE_EQ(2, sscanf("-1.23e", "%e%s", &f, buf));
ATF_REQUIRE(eq(FLT, f, -1.23));
ATF_REQUIRE(strcmp(buf, "e") == 0);
- sscanf("1.25e+", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("1.25e+", "%le%s", &d, buf));
ATF_REQUIRE(eq(DBL, d, 1.25));
ATF_REQUIRE(strcmp(buf, "e+") == 0);
- sscanf("1.23E4E5", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("1.23E4E5", "%le%s", &d, buf));
ATF_REQUIRE(eq(DBL, d, 1.23e4));
ATF_REQUIRE(strcmp(buf, "E5") == 0);
- sscanf("12e6", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("12e6", "%le", &d));
ATF_REQUIRE(eq(DBL, d, 12e6));
- sscanf("1.a", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("1.a", "%le%s", &d, buf));
ATF_REQUIRE(eq(DBL, d, 1.0));
ATF_REQUIRE(strcmp(buf, "a") == 0);
- sscanf(".0p4", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf(".0p4", "%le%s", &d, buf));
ATF_REQUIRE(eq(DBL, d, 0.0));
ATF_REQUIRE(strcmp(buf, "p4") == 0);
d = 0.25;
- ATF_REQUIRE(sscanf(".", "%le", &d) == 0);
+ ATF_REQUIRE_EQ(0, sscanf(".", "%le", &d));
ATF_REQUIRE(d == 0.25);
- sscanf("0x08", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x08", "%le", &d));
ATF_REQUIRE(d == 0x8p0);
- sscanf("0x90a.bcdefP+09a", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("0x90a.bcdefP+09a", "%le%s", &d, buf));
ATF_REQUIRE(d == 0x90a.bcdefp+09);
ATF_REQUIRE(strcmp(buf, "a") == 0);
#if (LDBL_MANT_DIG > DBL_MANT_DIG) && !defined(__i386__)
- sscanf("3.14159265358979323846", "%Lg", &ld);
+ ATF_REQUIRE_EQ(1, sscanf("3.14159265358979323846", "%Lg", &ld));
ATF_REQUIRE(eq(LDBL, ld, 3.14159265358979323846L));
- sscanf(" 0X.0123456789abcdefffp-3g", "%Le%s", &ld, buf);
+ ATF_REQUIRE_EQ(2, sscanf(" 0X.0123456789abcdefffp-3g", "%Le%s", &ld, buf));
ATF_REQUIRE(ld == 0x0.0123456789abcdefffp-3L);
ATF_REQUIRE(strcmp(buf, "g") == 0);
#endif
- sscanf("0xg", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("0xg", "%le%s", &d, buf));
ATF_REQUIRE(d == 0.0);
ATF_REQUIRE(strcmp(buf, "xg") == 0);
ATF_REQUIRE(setlocale(LC_NUMERIC, "ru_RU.ISO8859-5")); /* decimalpoint==, */
- sscanf("1.23", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("1.23", "%le%s", &d, buf));
ATF_REQUIRE(d == 1.0);
ATF_REQUIRE(strcmp(buf, ".23") == 0);
- sscanf("1,23", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1,23", "%le", &d));
ATF_REQUIRE(d == 1.23);
ATF_REQUIRE(setlocale(LC_NUMERIC, ""));
@@ -156,40 +156,40 @@ ATF_TC_BODY(infinities_and_nans, tc)
ATF_REQUIRE(setlocale(LC_NUMERIC, "C"));
- sscanf("-Inf", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("-Inf", "%le", &d));
ATF_REQUIRE(d < 0.0 && isinf(d));
- sscanf("iNfInItY and beyond", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("iNfInItY and beyond", "%le%s", &d, buf));
ATF_REQUIRE(d > 0.0 && isinf(d));
ATF_REQUIRE(strcmp(buf, " and beyond"));
- sscanf("NaN", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("NaN", "%le", &d));
ATF_REQUIRE(isnan(d));
- sscanf("NAN(123Y", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("NAN(123Y", "%le%s", &d, buf));
ATF_REQUIRE(isnan(d));
ATF_REQUIRE(strcmp(buf, "(123Y") == 0);
- sscanf("nan(f00f)plugh", "%le%s", &d, buf);
+ ATF_REQUIRE_EQ(2, sscanf("nan(f00f)plugh", "%le%s", &d, buf));
ATF_REQUIRE(isnan(d));
ATF_REQUIRE(strcmp(buf, "plugh") == 0);
- sscanf("-nan", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("-nan", "%le", &d));
ATF_REQUIRE(isnan(d));
/* Only quiet NaNs should be returned. */
- sscanf("NaN", "%e", &f);
- sscanf("nan", "%le", &d);
- sscanf("nan", "%Le", &ld);
- feclearexcept(FE_ALL_EXCEPT);
+ ATF_REQUIRE_EQ(1, sscanf("NaN", "%e", &f));
+ ATF_REQUIRE_EQ(1, sscanf("nan", "%le", &d));
+ ATF_REQUIRE_EQ(1, sscanf("nan", "%Le", &ld));
+ ATF_REQUIRE_EQ(0, feclearexcept(FE_ALL_EXCEPT));
ATF_REQUIRE(f != f);
ATF_REQUIRE(d != d);
ATF_REQUIRE(ld != ld);
ATF_REQUIRE(fetestexcept(FE_INVALID) == 0);
- sscanf("nan(1234)", "%e", &f);
- sscanf("nan(1234)", "%le", &d);
- sscanf("nan(1234)", "%Le", &ld);
- feclearexcept(FE_ALL_EXCEPT);
+ ATF_REQUIRE_EQ(1, sscanf("nan(1234)", "%e", &f));
+ ATF_REQUIRE_EQ(1, sscanf("nan(1234)", "%le", &d));
+ ATF_REQUIRE_EQ(1, sscanf("nan(1234)", "%Le", &ld));
+ ATF_REQUIRE_EQ(0, feclearexcept(FE_ALL_EXCEPT));
ATF_REQUIRE(f != f);
ATF_REQUIRE(d != d);
ATF_REQUIRE(ld != ld);
@@ -205,74 +205,74 @@ ATF_TC_BODY(rounding_tests, tc)
ATF_REQUIRE(setlocale(LC_NUMERIC, "C"));
- fesetround(FE_DOWNWARD);
+ ATF_REQUIRE_EQ(0, fesetround(FE_DOWNWARD));
- sscanf("1.999999999999999999999999999999999", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.999999999999999999999999999999999", "%le", &d));
ATF_REQUIRE(d < 2.0);
- sscanf("0x1.ffffffffffffffp0", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.ffffffffffffffp0", "%le", &d));
ATF_REQUIRE(d < 2.0);
- sscanf("1.999999999999999999999999999999999", "%Le", &ld);
+ ATF_REQUIRE_EQ(1, sscanf("1.999999999999999999999999999999999", "%Le", &ld));
ATF_REQUIRE(ld < 2.0);
- sscanf("1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc59p0);
- sscanf("-1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == -0x1.0ea3f4af0dc5ap0);
- sscanf("1.0571892669084010", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc5ap0);
- sscanf("0x1.23p-5000", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d));
ATF_REQUIRE(d == 0.0);
- sscanf("0x1.2345678p-1050", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d));
ATF_REQUIRE(d == 0x1.234567p-1050);
- fesetround(FE_UPWARD);
+ ATF_REQUIRE_EQ(0, fesetround(FE_UPWARD));
- sscanf("1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc5ap0);
- sscanf("-1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == -0x1.0ea3f4af0dc59p0);
- sscanf("1.0571892669084010", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc5bp0);
- sscanf("0x1.23p-5000", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d));
ATF_REQUIRE(d == 0x1p-1074);
- sscanf("0x1.2345678p-1050", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d));
ATF_REQUIRE(d == 0x1.234568p-1050);
- fesetround(FE_TOWARDZERO);
+ ATF_REQUIRE_EQ(0, fesetround(FE_TOWARDZERO));
- sscanf("1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc59p0);
- sscanf("-1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == -0x1.0ea3f4af0dc59p0);
- sscanf("1.0571892669084010", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc5ap0);
- sscanf("0x1.23p-5000", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d));
ATF_REQUIRE(d == 0.0);
- sscanf("0x1.2345678p-1050", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d));
ATF_REQUIRE(d == 0x1.234567p-1050);
- fesetround(FE_TONEAREST);
+ ATF_REQUIRE_EQ(0, fesetround(FE_TONEAREST));
/* 1.0571892669084007 is slightly closer to 0x1.0ea3f4af0dc59p0 */
- sscanf("1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc59p0);
- sscanf("-1.0571892669084007", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("-1.0571892669084007", "%le", &d));
ATF_REQUIRE(d == -0x1.0ea3f4af0dc59p0);
- sscanf("1.0571892669084010", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("1.0571892669084010", "%le", &d));
ATF_REQUIRE(d == 0x1.0ea3f4af0dc5bp0);
/* strtod() should round small numbers to 0. */
- sscanf("0x1.23p-5000", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.23p-5000", "%le", &d));
ATF_REQUIRE(d == 0.0);
/* Extra digits in a denormal shouldn't break anything. */
- sscanf("0x1.2345678p-1050", "%le", &d);
+ ATF_REQUIRE_EQ(1, sscanf("0x1.2345678p-1050", "%le", &d));
ATF_REQUIRE(d == 0x1.234568p-1050);
}
@@ -287,16 +287,16 @@ ATF_TC_BODY(strtod, tc)
ATF_REQUIRE(strcmp("xy", endp) == 0);
/* This used to cause an infinite loop and round the wrong way. */
- fesetround(FE_DOWNWARD);
+ ATF_REQUIRE_EQ(0, fesetround(FE_DOWNWARD));
ATF_REQUIRE(strtof("3.5e38", &endp) == FLT_MAX);
ATF_REQUIRE(strtod("2e308", &endp) == DBL_MAX);
- fesetround(FE_UPWARD);
+ ATF_REQUIRE_EQ(0, fesetround(FE_UPWARD));
ATF_REQUIRE(strtof("3.5e38", &endp) == INFINITY);
ATF_REQUIRE(strtod("2e308", &endp) == INFINITY);
- fesetround(FE_TOWARDZERO);
+ ATF_REQUIRE_EQ(0, fesetround(FE_TOWARDZERO));
ATF_REQUIRE(strtof("3.5e38", &endp) == FLT_MAX);
ATF_REQUIRE(strtod("2e308", &endp) == DBL_MAX);
- fesetround(FE_TONEAREST);
+ ATF_REQUIRE_EQ(0, fesetround(FE_TONEAREST));
ATF_REQUIRE(strtof("3.5e38", &endp) == INFINITY);
ATF_REQUIRE(strtod("2e308", &endp) == INFINITY);
}
diff --git a/lib/libc/tests/stdio/sscanf_test.c b/lib/libc/tests/stdio/sscanf_test.c
index e916873d38c3..e43292820eeb 100644
--- a/lib/libc/tests/stdio/sscanf_test.c
+++ b/lib/libc/tests/stdio/sscanf_test.c
@@ -68,6 +68,31 @@ static const struct sscanf_test_case {
{ "0e", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, },
{ "0f", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, },
{ "0x", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ // all digits with two leading zeroes
+ { "000", { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, },
+ { "001", { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, },
+ { "002", { 1, 0, 2 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, },
+ { "003", { 1, 0, 2 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, },
+ { "004", { 1, 0, 2 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, },
+ { "005", { 1, 0, 2 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, },
+ { "006", { 1, 0, 2 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, },
+ { "007", { 1, 0, 2 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, },
+ { "008", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 8, 3 }, { 1, 8, 3 }, { 1, 0, 2 }, },
+ { "009", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 9, 3 }, { 1, 9, 3 }, { 1, 0, 2 }, },
+ { "00A", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, },
+ { "00B", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, },
+ { "00C", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, },
+ { "00D", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, },
+ { "00E", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, },
+ { "00F", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, },
+ { "00X", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, },
+ { "00a", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, },
+ { "00b", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, },
+ { "00c", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, },
+ { "00d", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, },
+ { "00e", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, },
+ { "00f", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, },
+ { "00x", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, },
// all digits with leading one
{ "10", { 1, 2, 2 }, { 1, 8, 2 }, { 1, 10, 2 }, { 1, 16, 2 }, { 1, 10, 2 }, },
{ "11", { 1, 3, 2 }, { 1, 9, 2 }, { 1, 11, 2 }, { 1, 17, 2 }, { 1, 11, 2 }, },
diff --git a/lib/libc/tests/stdio/swscanf_test.c b/lib/libc/tests/stdio/swscanf_test.c
index f7ad30b963a7..f0638081e16f 100644
--- a/lib/libc/tests/stdio/swscanf_test.c
+++ b/lib/libc/tests/stdio/swscanf_test.c
@@ -71,6 +71,31 @@ static const struct swscanf_test_case {
{ L"0e", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 14, 2 }, { 1, 0, 1 }, },
{ L"0f", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 15, 2 }, { 1, 0, 1 }, },
{ L"0x", { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, { 1, 0, 1 }, },
+ // all digits with two leading zeroes
+ { L"000", { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, { 1, 0, 3 }, },
+ { L"001", { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, { 1, 1, 3 }, },
+ { L"002", { 1, 0, 2 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, { 1, 2, 3 }, },
+ { L"003", { 1, 0, 2 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, { 1, 3, 3 }, },
+ { L"004", { 1, 0, 2 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, { 1, 4, 3 }, },
+ { L"005", { 1, 0, 2 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, { 1, 5, 3 }, },
+ { L"006", { 1, 0, 2 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, { 1, 6, 3 }, },
+ { L"007", { 1, 0, 2 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, { 1, 7, 3 }, },
+ { L"008", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 8, 3 }, { 1, 8, 3 }, { 1, 0, 2 }, },
+ { L"009", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 9, 3 }, { 1, 9, 3 }, { 1, 0, 2 }, },
+ { L"00A", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, },
+ { L"00B", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, },
+ { L"00C", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, },
+ { L"00D", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, },
+ { L"00E", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, },
+ { L"00F", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, },
+ { L"00X", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, },
+ { L"00a", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 10, 3 }, { 1, 0, 2 }, },
+ { L"00b", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 11, 3 }, { 1, 0, 2 }, },
+ { L"00c", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 12, 3 }, { 1, 0, 2 }, },
+ { L"00d", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 13, 3 }, { 1, 0, 2 }, },
+ { L"00e", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 14, 3 }, { 1, 0, 2 }, },
+ { L"00f", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 15, 3 }, { 1, 0, 2 }, },
+ { L"00x", { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, { 1, 0, 2 }, },
// all digits with leading one
{ L"10", { 1, 2, 2 }, { 1, 8, 2 }, { 1, 10, 2 }, { 1, 16, 2 }, { 1, 10, 2 }, },
{ L"11", { 1, 3, 2 }, { 1, 9, 2 }, { 1, 11, 2 }, { 1, 17, 2 }, { 1, 11, 2 }, },
diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile
index 6f57b8014a1e..9d84becfbd1f 100644
--- a/lib/libc/tests/stdlib/Makefile
+++ b/lib/libc/tests/stdlib/Makefile
@@ -1,17 +1,20 @@
.include <src.opts.mk>
ATF_TESTS_C+= clearenv_test
+ATF_TESTS_C+= cxa_atexit_test
ATF_TESTS_C+= dynthr_test
+ATF_TESTS_C+= getenv_r_test
ATF_TESTS_C+= heapsort_test
ATF_TESTS_C+= libc_exit_test
ATF_TESTS_C+= mergesort_test
ATF_TESTS_C+= qsort_test
-.if ${COMPILER_TYPE} == "clang"
+.if ${COMPILER_FEATURES:Mblocks}
ATF_TESTS_C+= qsort_b_test
.endif
ATF_TESTS_C+= qsort_r_compat_test
ATF_TESTS_C+= qsort_r_test
ATF_TESTS_C+= qsort_s_test
+ATF_TESTS_C+= qsort_bench
ATF_TESTS_C+= set_constraint_handler_s_test
ATF_TESTS_C+= strfmon_test
ATF_TESTS_C+= tsearch_test
@@ -58,11 +61,9 @@ PROGS+= h_getopt h_getopt_long
CFLAGS+= -I${.CURDIR}
-CXXSTD.cxa_thread_atexit_test= c++11
-CXXSTD.cxa_thread_atexit_nothr_test= c++11
LIBADD.cxa_thread_atexit_test+= pthread
-# Tests that requires Blocks feature
+# Tests that require blocks support
.for t in qsort_b_test
CFLAGS.${t}.c+= -fblocks
LIBADD.${t}+= BlocksRuntime
@@ -79,5 +80,6 @@ LIBADD.libc_exit_test+= pthread
LIBADD.strtod_test+= m
SUBDIR+= dynthr_mod
+SUBDIR+= libatexit
.include <bsd.test.mk>
diff --git a/lib/libc/tests/stdlib/cxa_atexit_test.c b/lib/libc/tests/stdlib/cxa_atexit_test.c
new file mode 100644
index 000000000000..7e2cafbce850
--- /dev/null
+++ b/lib/libc/tests/stdlib/cxa_atexit_test.c
@@ -0,0 +1,132 @@
+/*-
+ * Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/wait.h>
+
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <atf-c.h>
+
+#define ARBITRARY_EXIT_CODE 42
+
+static char *
+get_shlib(const char *srcdir)
+{
+ char *shlib;
+
+ shlib = NULL;
+ if (asprintf(&shlib, "%s/libatexit.so", srcdir) < 0)
+ atf_tc_fail("failed to construct path to libatexit.so");
+ return (shlib);
+}
+
+static void
+run_test(const atf_tc_t *tc, bool with_fatal_atexit, bool with_exit)
+{
+ pid_t p;
+ void (*set_fatal_atexit)(bool);
+ void (*set_exit_code)(int);
+ void *hdl;
+ char *shlib;
+
+ shlib = get_shlib(atf_tc_get_config_var(tc, "srcdir"));
+
+ hdl = dlopen(shlib, RTLD_LAZY);
+ ATF_REQUIRE_MSG(hdl != NULL, "dlopen: %s", dlerror());
+
+ free(shlib);
+
+ if (with_fatal_atexit) {
+ set_fatal_atexit = dlsym(hdl, "set_fatal_atexit");
+ ATF_REQUIRE_MSG(set_fatal_atexit != NULL,
+ "set_fatal_atexit: %s", dlerror());
+ }
+ if (with_exit) {
+ set_exit_code = dlsym(hdl, "set_exit_code");
+ ATF_REQUIRE_MSG(set_exit_code != NULL, "set_exit_code: %s",
+ dlerror());
+ }
+
+ p = atf_utils_fork();
+ if (p == 0) {
+ /*
+ * Don't let the child clobber the results file; stderr/stdout
+ * have been replaced by atf_utils_fork() to capture it. We're
+ * intentionally using exit() instead of _exit() here to run
+ * __cxa_finalize at exit, otherwise we'd just leave it be.
+ */
+ closefrom(3);
+
+ if (with_fatal_atexit)
+ set_fatal_atexit(true);
+ if (with_exit)
+ set_exit_code(ARBITRARY_EXIT_CODE);
+
+ dlclose(hdl);
+
+ /*
+ * If the dtor was supposed to exit (most cases), then we should
+ * not have made it to this point. If it's not supposed to
+ * exit, then we just exit with success here because we might
+ * be expecting either a clean exit or a signal on our way out
+ * as the final __cxa_finalize tries to run a callback in the
+ * unloaded DSO.
+ */
+ if (with_exit)
+ exit(1);
+ exit(0);
+ }
+
+ dlclose(hdl);
+ atf_utils_wait(p, with_exit ? ARBITRARY_EXIT_CODE : 0, "", "");
+}
+
+ATF_TC_WITHOUT_HEAD(simple_cxa_atexit);
+ATF_TC_BODY(simple_cxa_atexit, tc)
+{
+ /*
+ * This test exits in a global object's dtor so that we check for our
+ * dtor being run at dlclose() time. If it isn't, then the forked child
+ * will have a chance to exit(1) after dlclose() to raise a failure.
+ */
+ run_test(tc, false, true);
+}
+
+ATF_TC_WITHOUT_HEAD(late_cxa_atexit);
+ATF_TC_BODY(late_cxa_atexit, tc)
+{
+ /*
+ * This test creates another global object during a __cxa_atexit handler
+ * invocation. It's been observed in the wild that we weren't executing
+ * it, then the DSO gets torn down and it was executed at application
+ * exit time instead. In the best case scenario we would crash if
+ * something else hadn't been mapped there.
+ */
+ run_test(tc, true, false);
+}
+
+ATF_TC_WITHOUT_HEAD(late_cxa_atexit_ran);
+ATF_TC_BODY(late_cxa_atexit_ran, tc)
+{
+ /*
+ * This is a slight variation of the previous test where we trigger an
+ * exit() in our late-registered __cxa_atexit handler so that we can
+ * ensure it was ran *before* dlclose() finished and not through some
+ * weird chain of events afterwards.
+ */
+ run_test(tc, true, true);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, simple_cxa_atexit);
+ ATF_TP_ADD_TC(tp, late_cxa_atexit);
+ ATF_TP_ADD_TC(tp, late_cxa_atexit_ran);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdlib/getenv_r_test.c b/lib/libc/tests/stdlib/getenv_r_test.c
new file mode 100644
index 000000000000..8085b92b1064
--- /dev/null
+++ b/lib/libc/tests/stdlib/getenv_r_test.c
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <atf-c.h>
+
+ATF_TC_WITHOUT_HEAD(getenv_r_ok);
+ATF_TC_BODY(getenv_r_ok, tc)
+{
+ const char *ident = atf_tc_get_ident(tc);
+ char buf[256];
+
+ ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1));
+ ATF_REQUIRE_EQ(0, getenv_r("ATF_TC_IDENT", buf, sizeof(buf)));
+ ATF_REQUIRE_STREQ(ident, buf);
+}
+
+ATF_TC_WITHOUT_HEAD(getenv_r_einval);
+ATF_TC_BODY(getenv_r_einval, tc)
+{
+ char buf[256];
+
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r(NULL, buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(EINVAL, errno);
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("", buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(EINVAL, errno);
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("A=B", buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(EINVAL, errno);
+}
+
+ATF_TC_WITHOUT_HEAD(getenv_r_enoent);
+ATF_TC_BODY(getenv_r_enoent, tc)
+{
+ char buf[256];
+
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("no such variable", buf, sizeof(buf)));
+ ATF_REQUIRE_EQ(ENOENT, errno);
+}
+
+ATF_TC_WITHOUT_HEAD(getenv_r_erange);
+ATF_TC_BODY(getenv_r_erange, tc)
+{
+ const char *ident = atf_tc_get_ident(tc);
+ char buf[256];
+
+ ATF_REQUIRE_EQ(0, setenv("ATF_TC_IDENT", ident, 1));
+ errno = 0;
+ ATF_REQUIRE_EQ(-1, getenv_r("ATF_TC_IDENT", buf, strlen(ident)));
+ ATF_REQUIRE_EQ(ERANGE, errno);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, getenv_r_ok);
+ ATF_TP_ADD_TC(tp, getenv_r_einval);
+ ATF_TP_ADD_TC(tp, getenv_r_enoent);
+ ATF_TP_ADD_TC(tp, getenv_r_erange);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdlib/libatexit/Makefile b/lib/libc/tests/stdlib/libatexit/Makefile
new file mode 100644
index 000000000000..9ba04c77af62
--- /dev/null
+++ b/lib/libc/tests/stdlib/libatexit/Makefile
@@ -0,0 +1,11 @@
+SHLIB_CXX= libatexit
+SHLIB_NAME= libatexit.so
+SHLIB_MAJOR= 1
+SHLIBDIR= ${TESTSDIR}
+PACKAGE= tests
+SRCS= libatexit.cc
+
+TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/:H}
+
+
+.include <bsd.lib.mk>
diff --git a/lib/libc/tests/stdlib/libatexit/libatexit.cc b/lib/libc/tests/stdlib/libatexit/libatexit.cc
new file mode 100644
index 000000000000..bb286c97e421
--- /dev/null
+++ b/lib/libc/tests/stdlib/libatexit/libatexit.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2025 Kyle Evans <kevans@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ */
+
+#include <unistd.h>
+
+static int exit_code = -1;
+static bool fatal_atexit;
+
+extern "C" {
+ void set_fatal_atexit(bool);
+ void set_exit_code(int);
+}
+
+void
+set_fatal_atexit(bool fexit)
+{
+ fatal_atexit = fexit;
+}
+
+void
+set_exit_code(int code)
+{
+ exit_code = code;
+}
+
+struct other_object {
+ ~other_object() {
+
+ /*
+ * In previous versions of our __cxa_atexit handling, we would
+ * never actually execute this handler because it's added during
+ * ~object() below; __cxa_finalize would never revisit it. We
+ * will allow the caller to configure us to exit with a certain
+ * exit code so that it can run us twice: once to ensure we
+ * don't crash at the end, and again to make sure the handler
+ * actually ran.
+ */
+ if (exit_code != -1)
+ _exit(exit_code);
+ }
+};
+
+void
+create_staticobj()
+{
+ static other_object obj;
+}
+
+struct object {
+ ~object() {
+ /*
+ * If we're doing the fatal_atexit behavior (i.e., create an
+ * object that will add its own dtor for __cxa_finalize), then
+ * we don't exit here.
+ */
+ if (fatal_atexit)
+ create_staticobj();
+ else if (exit_code != -1)
+ _exit(exit_code);
+ }
+};
+
+static object obj;
diff --git a/lib/libc/tests/stdlib/qsort_bench.c b/lib/libc/tests/stdlib/qsort_bench.c
new file mode 100644
index 000000000000..5f2cfae40140
--- /dev/null
+++ b/lib/libc/tests/stdlib/qsort_bench.c
@@ -0,0 +1,113 @@
+/*-
+ * Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <atf-c.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+/*-
+ * Measures qsort(3) runtime with pathological input and verify that it
+ * stays close to N * log2(N).
+ *
+ * Thanks to Vivian Hussey for the proof of concept.
+ *
+ * The input we construct is similar to a sweep from 0 to N where each
+ * half, except for the first element, has been reversed; for instance,
+ * with N = 8, we get { 0, 3, 2, 1, 4, 8, 7, 6 }. This triggers a bug in
+ * the BSD qsort(3) where it will switch to insertion sort if the pivots
+ * are sorted.
+ *
+ * This article goes into more detail about the bug and its origin:
+ *
+ * https://www.raygard.net/2022/02/26/Re-engineering-a-qsort-part-3
+ *
+ * With this optimization (the `if (swap_cnt == 0)` block), qsort(3) needs
+ * roughly N * N / 4 comparisons to sort our pathological input. Without
+ * it, it needs only a little more than N * log2(N) comparisons.
+ */
+
+/* we stop testing once a single takes longer than this */
+#define MAXRUNSECS 10
+
+static bool debugging;
+
+static uintmax_t ncmp;
+
+static int
+intcmp(const void *a, const void *b)
+{
+ ncmp++;
+ return ((*(int *)a > *(int *)b) - (*(int *)a < *(int *)b));
+}
+
+static void
+qsort_bench(int log2n)
+{
+ uintmax_t n = 1LLU << log2n;
+ int *buf;
+
+ /* fill an array with a pathological pattern */
+ ATF_REQUIRE(buf = malloc(n * sizeof(*buf)));
+ buf[0] = 0;
+ buf[n / 2] = n / 2;
+ for (unsigned int i = 1; i < n / 2; i++) {
+ buf[i] = n / 2 - i;
+ buf[n / 2 + i] = n - i;
+ }
+
+ ncmp = 0;
+ qsort(buf, n, sizeof(*buf), intcmp);
+
+ /* check result and free array */
+ if (debugging) {
+ for (unsigned int i = 1; i < n; i++) {
+ ATF_REQUIRE_MSG(buf[i] > buf[i - 1],
+ "array is not sorted");
+ }
+ }
+ free(buf);
+
+ /* check that runtime does not exceed N² */
+ ATF_CHECK_MSG(ncmp / n < n,
+ "runtime %ju exceeds N² for N = %ju", ncmp, n);
+
+ /* check that runtime does not exceed N log N by much */
+ ATF_CHECK_MSG(ncmp / n <= log2n + 1,
+ "runtime %ju exceeds N log N for N = %ju", ncmp, n);
+}
+
+ATF_TC_WITHOUT_HEAD(qsort_bench);
+ATF_TC_BODY(qsort_bench, tc)
+{
+ struct timespec t0, t1;
+ uintmax_t tus;
+
+ for (int i = 10; i <= 30; i++) {
+ clock_gettime(CLOCK_UPTIME, &t0);
+ qsort_bench(i);
+ clock_gettime(CLOCK_UPTIME, &t1);
+ tus = t1.tv_sec * 1000000 + t1.tv_nsec / 1000;
+ tus -= t0.tv_sec * 1000000 + t0.tv_nsec / 1000;
+ if (debugging) {
+ fprintf(stderr, "N = 2^%d in %ju.%06jus\n",
+ i, tus / 1000000, tus % 1000000);
+ }
+ /* stop once an individual run exceeds our limit */
+ if (tus / 1000000 >= MAXRUNSECS)
+ break;
+ }
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") &&
+ isatty(STDERR_FILENO);
+ ATF_TP_ADD_TC(tp, qsort_bench);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/stdtime/Makefile b/lib/libc/tests/stdtime/Makefile
index c7a7f5b9436f..590dea22da31 100644
--- a/lib/libc/tests/stdtime/Makefile
+++ b/lib/libc/tests/stdtime/Makefile
@@ -1,6 +1,12 @@
-.include <bsd.own.mk>
+.include <src.opts.mk>
ATF_TESTS_C+= strptime_test
+ATF_TESTS_C+= detect_tz_changes_test
+
+CFLAGS.detect_tz_changes_test+= -I${SRCTOP}/contrib/tzcode
+.if ${MK_DETECT_TZ_CHANGES} != "no"
+CFLAGS.detect_tz_changes_test+= -DDETECT_TZ_CHANGES
+.endif
TESTSDIR:= ${TESTSBASE}/${RELDIR:C/libc\/tests/libc/}
diff --git a/lib/libc/tests/stdtime/detect_tz_changes_test.c b/lib/libc/tests/stdtime/detect_tz_changes_test.c
new file mode 100644
index 000000000000..06c31c9fbc3d
--- /dev/null
+++ b/lib/libc/tests/stdtime/detect_tz_changes_test.c
@@ -0,0 +1,477 @@
+/*-
+ * Copyright (c) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "tzdir.h"
+
+#include <atf-c.h>
+
+struct tzcase {
+ const char *tzfn;
+ const char *expect;
+};
+
+static const struct tzcase tzcases[] = {
+ /*
+ * A handful of time zones and the expected result of
+ * strftime("%z (%Z)", tm) when that time zone is active
+ * and tm represents a date in the summer of 2025.
+ */
+ { "America/Vancouver", "-0700 (PDT)" },
+ { "America/New_York", "-0400 (EDT)" },
+ { "Europe/London", "+0100 (BST)" },
+ { "Europe/Paris", "+0200 (CEST)" },
+ { "Asia/Kolkata", "+0530 (IST)" },
+ { "Asia/Tokyo", "+0900 (JST)" },
+ { "Australia/Canberra", "+1000 (AEST)" },
+ { "UTC", "+0000 (UTC)" },
+ { 0 },
+};
+static const struct tzcase utc = { "UTC", "+0000 (UTC)" };
+static const struct tzcase invalid = { "invalid", "+0000 (-00)" };
+static const time_t then = 1751328000; /* 2025-07-01 00:00:00 UTC */
+
+static bool debugging;
+
+static void
+debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (debugging) {
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+ }
+}
+
+static void
+change_tz(const char *tzn)
+{
+ static const char *zfn = TZDIR;
+ static const char *tfn = "root" TZDEFAULT ".tmp";
+ static const char *dfn = "root" TZDEFAULT;
+ ssize_t clen;
+ int zfd, sfd, dfd;
+
+ ATF_REQUIRE((zfd = open(zfn, O_DIRECTORY | O_SEARCH)) >= 0);
+ ATF_REQUIRE((sfd = openat(zfd, tzn, O_RDONLY)) >= 0);
+ ATF_REQUIRE((dfd = open(tfn, O_CREAT | O_TRUNC | O_WRONLY, 0644)) >= 0);
+ do {
+ clen = copy_file_range(sfd, NULL, dfd, NULL, SSIZE_MAX, 0);
+ ATF_REQUIRE_MSG(clen != -1, "failed to copy %s/%s: %m",
+ zfn, tzn);
+ } while (clen > 0);
+ ATF_CHECK_EQ(0, close(dfd));
+ ATF_CHECK_EQ(0, close(sfd));
+ ATF_CHECK_EQ(0, close(zfd));
+ ATF_REQUIRE_EQ(0, rename(tfn, dfn));
+ debug("time zone %s installed", tzn);
+}
+
+static void
+test_tz(const char *expect)
+{
+ char buf[128];
+ struct tm *tm;
+ size_t len;
+
+ ATF_REQUIRE((tm = localtime(&then)) != NULL);
+ len = strftime(buf, sizeof(buf), "%z (%Z)", tm);
+ ATF_REQUIRE(len > 0);
+ ATF_CHECK_STREQ(expect, buf);
+}
+
+ATF_TC(tz_default);
+ATF_TC_HEAD(tz_default, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test default zone");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(tz_default, tc)
+{
+ /* prepare chroot with no /etc/localtime */
+ ATF_REQUIRE_EQ(0, mkdir("root", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755));
+ /* enter chroot */
+ ATF_REQUIRE_EQ(0, chroot("root"));
+ ATF_REQUIRE_EQ(0, chdir("/"));
+ /* check timezone */
+ unsetenv("TZ");
+ test_tz("+0000 (UTC)");
+}
+
+ATF_TC(tz_invalid_file);
+ATF_TC_HEAD(tz_invalid_file, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test invalid zone file");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(tz_invalid_file, tc)
+{
+ static const char *dfn = "root/etc/localtime";
+ int fd;
+
+ /* prepare chroot with bogus /etc/localtime */
+ ATF_REQUIRE_EQ(0, mkdir("root", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755));
+ ATF_REQUIRE((fd = open(dfn, O_RDWR | O_CREAT, 0644)) >= 0);
+ ATF_REQUIRE_EQ(8, write(fd, "invalid\n", 8));
+ ATF_REQUIRE_EQ(0, close(fd));
+ /* enter chroot */
+ ATF_REQUIRE_EQ(0, chroot("root"));
+ ATF_REQUIRE_EQ(0, chdir("/"));
+ /* check timezone */
+ unsetenv("TZ");
+ test_tz(invalid.expect);
+}
+
+ATF_TC(thin_jail);
+ATF_TC_HEAD(thin_jail, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test typical thin jail scenario");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(thin_jail, tc)
+{
+ const struct tzcase *tzcase = tzcases;
+
+ /* prepare chroot */
+ ATF_REQUIRE_EQ(0, mkdir("root", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755));
+ change_tz(tzcase->tzfn);
+ /* enter chroot */
+ ATF_REQUIRE_EQ(0, chroot("root"));
+ ATF_REQUIRE_EQ(0, chdir("/"));
+ /* check timezone */
+ unsetenv("TZ");
+ test_tz(tzcase->expect);
+}
+
+#ifdef DETECT_TZ_CHANGES
+/*
+ * Test time zone change detection.
+ *
+ * The parent creates a chroot containing only /etc/localtime, initially
+ * set to UTC. It then forks a child which enters the chroot, repeatedly
+ * checks the current time zone, and prints it to stdout if it changes
+ * (including once on startup). Meanwhile, the parent waits for output
+ * from the child. Every time it receives a line of text from the child,
+ * it checks that it is as expected, then changes /etc/localtime within
+ * the chroot to the next case in the list. Once it reaches the end of
+ * the list, it closes a pipe to notify the child, which terminates.
+ *
+ * Note that ATF and / or Kyua may have set the timezone before the test
+ * case starts (even unintentionally). Therefore, we start the test only
+ * after we've received and discarded the first report from the child,
+ * which should come almost immediately on startup.
+ */
+static const char *tz_change_interval_sym = "__tz_change_interval";
+static int *tz_change_interval_p;
+static const int tz_change_interval = 3;
+static int tz_change_timeout = 90;
+
+ATF_TC(detect_tz_changes);
+ATF_TC_HEAD(detect_tz_changes, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test timezone change detection");
+ atf_tc_set_md_var(tc, "require.user", "root");
+ atf_tc_set_md_var(tc, "timeout", "600");
+}
+ATF_TC_BODY(detect_tz_changes, tc)
+{
+ char obuf[1024] = "";
+ char ebuf[1024] = "";
+ struct pollfd fds[3];
+ int opd[2], epd[2], spd[2];
+ time_t changed, now;
+ const struct tzcase *tzcase = NULL;
+ struct tm *tm;
+ size_t olen = 0, elen = 0;
+ ssize_t rlen;
+ long curoff = LONG_MIN;
+ pid_t pid;
+ int nfds, status;
+
+ /* speed up the test if possible */
+ tz_change_interval_p = dlsym(RTLD_SELF, tz_change_interval_sym);
+ if (tz_change_interval_p != NULL &&
+ *tz_change_interval_p > tz_change_interval) {
+ debug("reducing detection interval from %d to %d",
+ *tz_change_interval_p, tz_change_interval);
+ *tz_change_interval_p = tz_change_interval;
+ tz_change_timeout = tz_change_interval * 3;
+ }
+ /* prepare chroot */
+ ATF_REQUIRE_EQ(0, mkdir("root", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755));
+ change_tz("UTC");
+ time(&changed);
+ /* output, error, sync pipes */
+ if (pipe(opd) != 0 || pipe(epd) != 0 || pipe(spd) != 0)
+ atf_tc_fail("failed to pipe");
+ /* fork child */
+ if ((pid = fork()) < 0)
+ atf_tc_fail("failed to fork");
+ if (pid == 0) {
+ /* child */
+ dup2(opd[1], STDOUT_FILENO);
+ close(opd[0]);
+ close(opd[1]);
+ dup2(epd[1], STDERR_FILENO);
+ close(epd[0]);
+ close(epd[1]);
+ close(spd[0]);
+ unsetenv("TZ");
+ ATF_REQUIRE_EQ(0, chroot("root"));
+ ATF_REQUIRE_EQ(0, chdir("/"));
+ fds[0].fd = spd[1];
+ fds[0].events = POLLIN;
+ for (;;) {
+ ATF_REQUIRE(poll(fds, 1, 100) >= 0);
+ if (fds[0].revents & POLLHUP) {
+ /* parent closed sync pipe */
+ _exit(0);
+ }
+ ATF_REQUIRE((tm = localtime(&then)) != NULL);
+ if (tm->tm_gmtoff == curoff)
+ continue;
+ olen = strftime(obuf, sizeof(obuf), "%z (%Z)", tm);
+ ATF_REQUIRE(olen > 0);
+ fprintf(stdout, "%s\n", obuf);
+ fflush(stdout);
+ curoff = tm->tm_gmtoff;
+ }
+ _exit(2);
+ }
+ /* parent */
+ close(opd[1]);
+ close(epd[1]);
+ close(spd[1]);
+ /* receive output until child terminates */
+ fds[0].fd = opd[0];
+ fds[0].events = POLLIN;
+ fds[1].fd = epd[0];
+ fds[1].events = POLLIN;
+ fds[2].fd = spd[0];
+ fds[2].events = POLLIN;
+ nfds = 3;
+ for (;;) {
+ ATF_REQUIRE(poll(fds, 3, 1000) >= 0);
+ time(&now);
+ if (fds[0].revents & POLLIN && olen < sizeof(obuf)) {
+ rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen);
+ ATF_REQUIRE(rlen >= 0);
+ olen += rlen;
+ }
+ if (olen > 0) {
+ ATF_REQUIRE_EQ('\n', obuf[olen - 1]);
+ obuf[--olen] = '\0';
+ /* tzcase will be NULL at first */
+ if (tzcase != NULL) {
+ debug("%s", obuf);
+ ATF_REQUIRE_STREQ(tzcase->expect, obuf);
+ debug("change to %s detected after %d s",
+ tzcase->tzfn, (int)(now - changed));
+ if (tz_change_interval_p != NULL) {
+ ATF_CHECK((int)(now - changed) >=
+ *tz_change_interval_p - 1);
+ ATF_CHECK((int)(now - changed) <=
+ *tz_change_interval_p + 1);
+ }
+ }
+ olen = 0;
+ /* first / next test case */
+ if (tzcase == NULL)
+ tzcase = tzcases;
+ else
+ tzcase++;
+ if (tzcase->tzfn == NULL) {
+ /* test is over */
+ break;
+ }
+ change_tz(tzcase->tzfn);
+ changed = now;
+ }
+ if (fds[1].revents & POLLIN && elen < sizeof(ebuf)) {
+ rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen);
+ ATF_REQUIRE(rlen >= 0);
+ elen += rlen;
+ }
+ if (elen > 0) {
+ ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr));
+ elen = 0;
+ }
+ if (nfds > 2 && fds[2].revents & POLLHUP) {
+ /* child closed sync pipe */
+ break;
+ }
+ /*
+ * The timeout for this test case is set to 10 minutes,
+ * because it can take that long to run with the default
+ * 61-second interval. However, each individual tzcase
+ * entry should not take much longer than the detection
+ * interval to test, so we can detect a problem long
+ * before Kyua terminates us.
+ */
+ if ((now - changed) > tz_change_timeout) {
+ close(spd[0]);
+ if (tz_change_interval_p == NULL &&
+ tzcase == tzcases) {
+ /*
+ * The most likely explanation in this
+ * case is that libc was built without
+ * time zone change detection.
+ */
+ atf_tc_skip("time zone change detection "
+ "does not appear to be enabled");
+ }
+ atf_tc_fail("timed out waiting for change to %s "
+ "to be detected", tzcase->tzfn);
+ }
+ }
+ close(opd[0]);
+ close(epd[0]);
+ close(spd[0]); /* this will wake up and terminate the child */
+ if (olen > 0)
+ ATF_REQUIRE_EQ(olen, fwrite(obuf, 1, olen, stdout));
+ if (elen > 0)
+ ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr));
+ ATF_REQUIRE_EQ(pid, waitpid(pid, &status, 0));
+ ATF_REQUIRE(WIFEXITED(status));
+ ATF_REQUIRE_EQ(0, WEXITSTATUS(status));
+}
+#endif /* DETECT_TZ_CHANGES */
+
+static void
+test_tz_env(const char *tzval, const char *expect)
+{
+ setenv("TZ", tzval, 1);
+ test_tz(expect);
+}
+
+static void
+tz_env_common(void)
+{
+ char path[MAXPATHLEN];
+ const struct tzcase *tzcase = tzcases;
+ int len;
+
+ /* relative path */
+ for (tzcase = tzcases; tzcase->tzfn != NULL; tzcase++)
+ test_tz_env(tzcase->tzfn, tzcase->expect);
+ /* absolute path */
+ for (tzcase = tzcases; tzcase->tzfn != NULL; tzcase++) {
+ len = snprintf(path, sizeof(path), "%s/%s", TZDIR, tzcase->tzfn);
+ ATF_REQUIRE(len > 0 && (size_t)len < sizeof(path));
+ test_tz_env(path, tzcase->expect);
+ }
+ /* absolute path with additional slashes */
+ for (tzcase = tzcases; tzcase->tzfn != NULL; tzcase++) {
+ len = snprintf(path, sizeof(path), "%s/////%s", TZDIR, tzcase->tzfn);
+ ATF_REQUIRE(len > 0 && (size_t)len < sizeof(path));
+ test_tz_env(path, tzcase->expect);
+ }
+}
+
+ATF_TC(tz_env);
+ATF_TC_HEAD(tz_env, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test TZ environment variable");
+}
+ATF_TC_BODY(tz_env, tc)
+{
+ tz_env_common();
+ /* escape from TZDIR is permitted when not setugid */
+ test_tz_env("../zoneinfo/UTC", utc.expect);
+}
+
+
+ATF_TC(tz_invalid_env);
+ATF_TC_HEAD(tz_invalid_env, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test invalid TZ value");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(tz_invalid_env, tc)
+{
+ test_tz_env("invalid", invalid.expect);
+ test_tz_env(":invalid", invalid.expect);
+}
+
+ATF_TC(setugid);
+ATF_TC_HEAD(setugid, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test setugid process");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(setugid, tc)
+{
+ const struct tzcase *tzcase = tzcases;
+
+ /* prepare chroot */
+ ATF_REQUIRE_EQ(0, mkdir("root", 0755));
+ ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755));
+ change_tz(tzcase->tzfn);
+ /* enter chroot */
+ ATF_REQUIRE_EQ(0, chroot("root"));
+ ATF_REQUIRE_EQ(0, chdir("/"));
+ /* become setugid */
+ ATF_REQUIRE_EQ(0, seteuid(UID_NOBODY));
+ ATF_REQUIRE(issetugid());
+ /* check timezone */
+ unsetenv("TZ");
+ test_tz(tzcases->expect);
+}
+
+ATF_TC(tz_env_setugid);
+ATF_TC_HEAD(tz_env_setugid, tc)
+{
+ atf_tc_set_md_var(tc, "descr", "Test TZ environment variable "
+ "in setugid process");
+ atf_tc_set_md_var(tc, "require.user", "root");
+}
+ATF_TC_BODY(tz_env_setugid, tc)
+{
+ ATF_REQUIRE_EQ(0, seteuid(UID_NOBODY));
+ ATF_REQUIRE(issetugid());
+ tz_env_common();
+ /* escape from TZDIR is not permitted when setugid */
+ test_tz_env("../zoneinfo/UTC", invalid.expect);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") &&
+ isatty(STDERR_FILENO);
+ ATF_TP_ADD_TC(tp, tz_default);
+ ATF_TP_ADD_TC(tp, tz_invalid_file);
+ ATF_TP_ADD_TC(tp, thin_jail);
+#ifdef DETECT_TZ_CHANGES
+ ATF_TP_ADD_TC(tp, detect_tz_changes);
+#endif /* DETECT_TZ_CHANGES */
+ ATF_TP_ADD_TC(tp, tz_env);
+ ATF_TP_ADD_TC(tp, tz_invalid_env);
+ ATF_TP_ADD_TC(tp, setugid);
+ ATF_TP_ADD_TC(tp, tz_env_setugid);
+ return (atf_no_error());
+}
diff --git a/lib/libc/tests/string/memcmp_test.c b/lib/libc/tests/string/memcmp_test.c
index 5286a0b994f3..fa2f498ccfaf 100644
--- a/lib/libc/tests/string/memcmp_test.c
+++ b/lib/libc/tests/string/memcmp_test.c
@@ -41,14 +41,14 @@
#endif
/*
- * On FreeBSD we demand that memcmp returns the difference between the
- * characters at the first site of mismatch. However, ISO/IEC 9899:1990
- * only specifies that a number greater than, equal to, or less than
- * zero shall be returned. If a unit test for this less strict
- * behaviour is desired, define RES(x) to be (((x) > 0) - ((x) < 0)).
+ * On FreeBSD we previously demanded that memcmp returns the difference
+ * between the characters at the first site of mismatch. However,
+ * ISO/IEC 9899:1990 only specifies that a number greater than, equal
+ * to, or less than zero shall be returned. If a unit test for the
+ * more strict behaviour is desired, define RES(x) to be (x).
*/
#ifndef RES
-#define RES(x) (x)
+#define RES(x) (((x) > 0) - ((x) < 0))
#endif
static int (*memcmp_fn)(const void *, const void *, size_t);
diff --git a/lib/libc/tests/sys/Makefile b/lib/libc/tests/sys/Makefile
index 89d341ff400a..88f8191a16eb 100644
--- a/lib/libc/tests/sys/Makefile
+++ b/lib/libc/tests/sys/Makefile
@@ -7,11 +7,11 @@ ATF_TESTS_C+= brk_test
.endif
ATF_TESTS_C+= cpuset_test
ATF_TESTS_C+= errno_test
+ATF_TESTS_C+= swapcontext_test
ATF_TESTS_C+= queue_test
ATF_TESTS_C+= sendfile_test
-# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg,
-# swapcontext
+# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg
NETBSD_ATF_TESTS_C+= access_test
NETBSD_ATF_TESTS_C+= bind_test
NETBSD_ATF_TESTS_C+= chroot_test
diff --git a/lib/libc/tests/sys/swapcontext_test.c b/lib/libc/tests/sys/swapcontext_test.c
new file mode 100644
index 000000000000..f341a746e515
--- /dev/null
+++ b/lib/libc/tests/sys/swapcontext_test.c
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 2025 Raptor Computing Systems, LLC
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ucontext.h>
+#include <errno.h>
+
+#include <atf-c.h>
+
+#define STACK_SIZE (64ull << 10)
+
+static volatile int callback_reached = 0;
+
+static ucontext_t uctx_save, uctx_switch;
+
+static void swapcontext_callback()
+{
+ // Increment callback reached variable
+ // If this is called multiple times, we will fail the test
+ // If this is not called at all, we will fail the test
+ callback_reached++;
+}
+
+ATF_TC(swapcontext_basic);
+ATF_TC_HEAD(swapcontext_basic, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Verify basic functionality of swapcontext");
+}
+
+ATF_TC_BODY(swapcontext_basic, tc)
+{
+ char *stack;
+ int res;
+
+ stack = malloc(STACK_SIZE);
+ ATF_REQUIRE_MSG(stack != NULL, "malloc failed: %s", strerror(errno));
+ res = getcontext(&uctx_switch);
+ ATF_REQUIRE_MSG(res == 0, "getcontext failed: %s", strerror(errno));
+
+ uctx_switch.uc_stack.ss_sp = stack;
+ uctx_switch.uc_stack.ss_size = STACK_SIZE;
+ uctx_switch.uc_link = &uctx_save;
+ makecontext(&uctx_switch, swapcontext_callback, 0);
+
+ res = swapcontext(&uctx_save, &uctx_switch);
+
+ ATF_REQUIRE_MSG(res == 0, "swapcontext failed: %s", strerror(errno));
+ ATF_REQUIRE_MSG(callback_reached == 1,
+ "callback failed, reached %d times", callback_reached);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, swapcontext_basic);
+
+ return (atf_no_error());
+}
+
diff --git a/lib/libc/tests/tls/dso/Makefile b/lib/libc/tests/tls/dso/Makefile
index 5efd8b29a6bd..783534ff7aae 100644
--- a/lib/libc/tests/tls/dso/Makefile
+++ b/lib/libc/tests/tls/dso/Makefile
@@ -6,6 +6,7 @@ SRCS= h_tls_dlopen.c
MAN=
PACKAGE= tests
+NO_DEV_PACKAGE=
LIBDIR= ${TESTSBASE}/lib/libc/tls
SHLIB_MAJOR= 1
diff --git a/lib/libc/tests/tls_dso/Makefile b/lib/libc/tests/tls_dso/Makefile
index 89296c643695..7cb8f98b431e 100644
--- a/lib/libc/tests/tls_dso/Makefile
+++ b/lib/libc/tests/tls_dso/Makefile
@@ -7,6 +7,7 @@ LIBDIR= ${TESTSBASE}/lib/libc/tls
SHLIBDIR= ${TESTSBASE}/lib/libc/tls
SHLIB_MAJOR= 1
PACKAGE= tests
+NO_DEV_PACKAGE=
WITHOUT_STATIC=
WITHOUT_PROFILE=