diff options
Diffstat (limited to 'lib/libc')
-rw-r--r-- | lib/libc/gen/fts.c | 8 | ||||
-rw-r--r-- | lib/libc/gen/wordexp.c | 10 | ||||
-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/tests/gen/wordexp_test.c | 26 | ||||
-rw-r--r-- | lib/libc/tests/net/Makefile | 3 | ||||
-rw-r--r-- | lib/libc/tests/net/link_addr_test.cc | 532 | ||||
-rw-r--r-- | lib/libc/tests/stdlib/Makefile | 2 | ||||
-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 |
14 files changed, 1110 insertions, 136 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/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/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/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..d939edeeb00d 100644 --- a/lib/libc/tests/net/Makefile +++ b/lib/libc/tests/net/Makefile @@ -4,6 +4,9 @@ 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 + +CXXSTD.link_addr_test= c++20 CFLAGS+= -I${.CURDIR} 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..9df98bd4f435 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 @@ -77,5 +78,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; |