diff options
Diffstat (limited to 'lib/libc')
| -rw-r--r-- | lib/libc/gen/fts.c | 8 | ||||
| -rw-r--r-- | lib/libc/gen/getvfsbyname.3 | 3 | ||||
| -rw-r--r-- | lib/libc/gen/wordexp.c | 10 | ||||
| -rw-r--r-- | lib/libc/inet/inet_net_ntop.c | 206 | ||||
| -rw-r--r-- | lib/libc/inet/inet_net_pton.c | 363 | ||||
| -rw-r--r-- | lib/libc/net/Makefile.inc | 7 | ||||
| -rw-r--r-- | lib/libc/net/Symbol.map | 4 | ||||
| -rw-r--r-- | lib/libc/net/linkaddr.3 | 77 | ||||
| -rw-r--r-- | lib/libc/net/linkaddr.c | 306 | ||||
| -rw-r--r-- | lib/libc/stdlib/atexit.c | 61 | ||||
| -rw-r--r-- | lib/libc/stdlib/qsort.c | 13 | ||||
| -rw-r--r-- | lib/libc/stdtime/strptime.3 | 2 | ||||
| -rw-r--r-- | lib/libc/stdtime/strptime.c | 3 | ||||
| -rw-r--r-- | lib/libc/sys/stat.2 | 5 | ||||
| -rw-r--r-- | lib/libc/tests/gen/wordexp_test.c | 26 | ||||
| -rw-r--r-- | lib/libc/tests/net/Makefile | 5 | ||||
| -rw-r--r-- | lib/libc/tests/net/inet_net_test.cc | 333 | ||||
| -rw-r--r-- | lib/libc/tests/net/link_addr_test.cc | 532 | ||||
| -rw-r--r-- | lib/libc/tests/stdlib/Makefile | 3 | ||||
| -rw-r--r-- | lib/libc/tests/stdlib/cxa_atexit_test.c | 132 | ||||
| -rw-r--r-- | lib/libc/tests/stdlib/libatexit/Makefile | 11 | ||||
| -rw-r--r-- | lib/libc/tests/stdlib/libatexit/libatexit.cc | 67 | ||||
| -rw-r--r-- | lib/libc/tests/stdlib/qsort_bench.c | 113 |
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()); +} |
