aboutsummaryrefslogtreecommitdiff
path: root/lib/libc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc')
-rw-r--r--lib/libc/gen/fts.c8
-rw-r--r--lib/libc/gen/getvfsbyname.33
-rw-r--r--lib/libc/gen/wordexp.c10
-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/net/Makefile.inc7
-rw-r--r--lib/libc/net/Symbol.map4
-rw-r--r--lib/libc/net/linkaddr.377
-rw-r--r--lib/libc/net/linkaddr.c306
-rw-r--r--lib/libc/stdlib/atexit.c61
-rw-r--r--lib/libc/stdlib/qsort.c13
-rw-r--r--lib/libc/stdtime/strptime.32
-rw-r--r--lib/libc/stdtime/strptime.c3
-rw-r--r--lib/libc/sys/stat.25
-rw-r--r--lib/libc/tests/gen/wordexp_test.c26
-rw-r--r--lib/libc/tests/net/Makefile5
-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/stdlib/Makefile3
-rw-r--r--lib/libc/tests/stdlib/cxa_atexit_test.c132
-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
23 files changed, 1725 insertions, 565 deletions
diff --git a/lib/libc/gen/fts.c b/lib/libc/gen/fts.c
index ff51d2b224c2..9ffd0bd9972d 100644
--- a/lib/libc/gen/fts.c
+++ b/lib/libc/gen/fts.c
@@ -901,10 +901,12 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
int saved_errno;
const char *path;
- if (dfd == -1)
- path = p->fts_accpath, dfd = AT_FDCWD;
- else
+ if (dfd == -1) {
+ path = p->fts_accpath;
+ dfd = AT_FDCWD;
+ } else {
path = p->fts_name;
+ }
/* If user needs stat info, stat buffer already allocated. */
sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
diff --git a/lib/libc/gen/getvfsbyname.3 b/lib/libc/gen/getvfsbyname.3
index 9365d043a1fc..19fa12419353 100644
--- a/lib/libc/gen/getvfsbyname.3
+++ b/lib/libc/gen/getvfsbyname.3
@@ -27,7 +27,7 @@
.\"
.\" @(#)kvm_getvfsbyname.3 8.3 (Berkeley) 5/4/95
.\"
-.Dd August 16, 2018
+.Dd October 28, 2025
.Dt GETVFSBYNAME 3
.Os
.Sh NAME
@@ -104,6 +104,7 @@ argument
specifies a file system that is unknown or not configured in the kernel.
.El
.Sh SEE ALSO
+.Xr lsvfs 1 ,
.Xr jail 2 ,
.Xr mount 2 ,
.Xr sysctl 3 ,
diff --git a/lib/libc/gen/wordexp.c b/lib/libc/gen/wordexp.c
index 0902814eb04f..fe23e563c3f5 100644
--- a/lib/libc/gen/wordexp.c
+++ b/lib/libc/gen/wordexp.c
@@ -266,7 +266,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/inet/inet_net_ntop.c b/lib/libc/inet/inet_net_ntop.c
index f523e460e8bb..141a7c9a327f 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.
*/
#if defined(LIBC_SCCS) && !defined(lint)
@@ -35,18 +38,10 @@ static const char rcsid[] = "$Id: inet_net_ntop.c,v 1.5 2006/06/20 02:50:14 mark
#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.
@@ -70,7 +65,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.
@@ -87,53 +82,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;
- t = dst;
- dst += SPRINTF((dst, "%u", *src++));
+ advance = snprintf(dst, ep - dst, "%u", *src++);
+ if (advance <= 0 || advance >= ep - dst)
+ goto emsgsize;
+ 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:
@@ -141,132 +146,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);
}
/*
@@ -275,5 +175,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 9fe41702738d..fb015318c369 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.
*/
#if defined(LIBC_SCCS) && !defined(lint)
@@ -26,7 +29,6 @@ static const char rcsid[] = "$Id: inet_net_pton.c,v 1.10 2008/11/14 02:36:51 mar
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
-#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <assert.h>
@@ -38,13 +40,37 @@ static const char rcsid[] = "$Id: inet_net_pton.c,v 1.10 2008/11/14 02:36:51 mar
#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.
@@ -61,22 +87,24 @@ static const char rcsid[] = "$Id: inet_net_pton.c,v 1.10 2008/11/14 02:36:51 mar
* 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;
@@ -86,14 +114,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);
}
@@ -108,16 +136,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;
}
@@ -125,10 +153,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;
@@ -136,8 +164,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;
}
@@ -151,29 +180,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';
}
@@ -188,222 +211,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);
}
/*
@@ -412,5 +261,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/net/Makefile.inc b/lib/libc/net/Makefile.inc
index 42a089f97d74..e0dc44eb7e23 100644
--- a/lib/libc/net/Makefile.inc
+++ b/lib/libc/net/Makefile.inc
@@ -106,8 +106,11 @@ MLINKS+=inet6_opt_init.3 inet6_opt_append.3 \
inet6_rthdr_space.3 inet6_rthdr_lasthop.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
-MLINKS+=rcmd.3 iruserok.3 rcmd.3 iruserok_sa.3 \
+MLINKS+=linkaddr.3 link_addr.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 \
rcmd.3 rresvport.3 rcmd.3 rresvport_af.3 \
rcmd.3 ruserok.3
diff --git a/lib/libc/net/Symbol.map b/lib/libc/net/Symbol.map
index b2ecd4f08735..093987d8c5af 100644
--- a/lib/libc/net/Symbol.map
+++ b/lib/libc/net/Symbol.map
@@ -148,6 +148,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/linkaddr.3 b/lib/libc/net/linkaddr.3
index 6c2fb6ccfdc3..ceb5c4a79e9a 100644
--- a/lib/libc/net/linkaddr.3
+++ b/lib/libc/net/linkaddr.3
@@ -30,12 +30,13 @@
.\"
.\" From: @(#)linkaddr.3 8.1 (Berkeley) 7/28/93
.\"
-.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
@@ -43,16 +44,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
@@ -62,9 +76,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
@@ -96,12 +135,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
@@ -111,15 +159,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 df1b7b94276e..c20f989143eb 100644
--- a/lib/libc/net/linkaddr.c
+++ b/lib/libc/net/linkaddr.c
@@ -35,135 +35,261 @@ static char sccsid[] = "@(#)linkaddr.c 8.1 (Berkeley) 6/4/93";
#include <sys/cdefs.h>
#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/stdlib/atexit.c b/lib/libc/stdlib/atexit.c
index 6b63d9e728c4..5b39552821ed 100644
--- a/lib/libc/stdlib/atexit.c
+++ b/lib/libc/stdlib/atexit.c
@@ -39,6 +39,7 @@ static char sccsid[] = "@(#)atexit.c 8.2 (Berkeley) 7/3/94";
#include "namespace.h"
#include <errno.h>
#include <link.h>
+#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
@@ -60,6 +61,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)
@@ -119,6 +122,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;
}
@@ -212,33 +218,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/qsort.c b/lib/libc/stdlib/qsort.c
index dc0a5452161e..8a2d5ae97cd5 100644
--- a/lib/libc/stdlib/qsort.c
+++ b/lib/libc/stdlib/qsort.c
@@ -104,13 +104,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;
@@ -139,7 +137,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;
}
@@ -147,7 +144,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;
}
@@ -156,18 +152,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/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 e9ffe7180916..b89e140aa8d5 100644
--- a/lib/libc/stdtime/strptime.c
+++ b/lib/libc/stdtime/strptime.c
@@ -554,7 +554,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/sys/stat.2 b/lib/libc/sys/stat.2
index 6b2f2a7c7eab..381674ce897e 100644
--- a/lib/libc/sys/stat.2
+++ b/lib/libc/sys/stat.2
@@ -27,7 +27,7 @@
.\"
.\" @(#)stat.2 8.4 (Berkeley) 5/1/95
.\"
-.Dd March 30, 2021
+.Dd August 17, 2025
.Dt STAT 2
.Os
.Sh NAME
@@ -171,6 +171,9 @@ Flags enabled for the file.
See
.Xr chflags 2
for the list of flags and their description.
+.It Va st_rdev
+Numeric ID of the device referenced by the file, if the file is a
+character or block special; otherwise unspecified.
.El
.Pp
The
diff --git a/lib/libc/tests/gen/wordexp_test.c b/lib/libc/tests/gen/wordexp_test.c
index ea8dc3d37828..f7e94b02697a 100644
--- a/lib/libc/tests/gen/wordexp_test.c
+++ b/lib/libc/tests/gen/wordexp_test.c
@@ -293,6 +293,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)
{
@@ -351,6 +376,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/net/Makefile b/lib/libc/tests/net/Makefile
index 6e90f22b98f5..88ceda5e11ae 100644
--- a/lib/libc/tests/net/Makefile
+++ b/lib/libc/tests/net/Makefile
@@ -4,6 +4,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/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/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile
index aa2fa7683cb7..b86dcb28cb1e 100644
--- a/lib/libc/tests/stdlib/Makefile
+++ b/lib/libc/tests/stdlib/Makefile
@@ -1,6 +1,7 @@
.include <src.opts.mk>
+ATF_TESTS_C+= cxa_atexit_test
ATF_TESTS_C+= dynthr_test
ATF_TESTS_C+= heapsort_test
ATF_TESTS_C+= mergesort_test
@@ -10,6 +11,7 @@ ATF_TESTS_C+= qsort_b_test
.endif
ATF_TESTS_C+= qsort_r_test
ATF_TESTS_C+= qsort_s_test
+ATF_TESTS_C+= qsort_bench
ATF_TESTS_C+= quick_exit_test
ATF_TESTS_C+= set_constraint_handler_s_test
ATF_TESTS_C+= strfmon_test
@@ -77,5 +79,6 @@ LIBADD.${t}+= netbsd util
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/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());
+}