aboutsummaryrefslogtreecommitdiff
path: root/lib/libc/gen
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/gen')
-rw-r--r--lib/libc/gen/Makefile.inc22
-rw-r--r--lib/libc/gen/Symbol.map14
-rw-r--r--lib/libc/gen/_pthread_stubs.c1
-rw-r--r--lib/libc/gen/alarm.32
-rw-r--r--lib/libc/gen/dlfcn.c2
-rw-r--r--lib/libc/gen/dup3.315
-rw-r--r--lib/libc/gen/dup3.c9
-rw-r--r--lib/libc/gen/elf_utils.c27
-rw-r--r--lib/libc/gen/err.c51
-rw-r--r--lib/libc/gen/exec.c233
-rw-r--r--lib/libc/gen/fdopendir.c17
-rw-r--r--lib/libc/gen/fnmatch.38
-rw-r--r--lib/libc/gen/fnmatch.c227
-rw-r--r--lib/libc/gen/fts.386
-rw-r--r--lib/libc/gen/fts.c234
-rw-r--r--lib/libc/gen/gen-private.h4
-rw-r--r--lib/libc/gen/glob.366
-rw-r--r--lib/libc/gen/glob.c75
-rw-r--r--lib/libc/gen/inotify.c48
-rw-r--r--lib/libc/gen/libc_interposing_table.c2
-rw-r--r--lib/libc/gen/opendir.c7
-rw-r--r--lib/libc/gen/opendir2.c31
-rw-r--r--lib/libc/gen/posix_spawn.c2
-rw-r--r--lib/libc/gen/psignal.376
-rw-r--r--lib/libc/gen/psignal.c6
-rw-r--r--lib/libc/gen/readdir.c13
-rw-r--r--lib/libc/gen/rtld_get_var.32
-rw-r--r--lib/libc/gen/scandir.3127
-rw-r--r--lib/libc/gen/scandir.c129
-rw-r--r--lib/libc/gen/sig2str.c113
-rw-r--r--lib/libc/gen/sysconf.c7
-rw-r--r--lib/libc/gen/sysctl.323
-rw-r--r--lib/libc/gen/telldir.c4
-rw-r--r--lib/libc/gen/telldir.h6
-rw-r--r--lib/libc/gen/tls.c98
-rw-r--r--lib/libc/gen/ualarm.32
-rw-r--r--lib/libc/gen/uexterr_format.c35
-rw-r--r--lib/libc/gen/uexterr_gettext.c43
-rw-r--r--lib/libc/gen/wordexp.c10
39 files changed, 1463 insertions, 414 deletions
diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc
index 9b92e52cdfad..4d064d18d36e 100644
--- a/lib/libc/gen/Makefile.inc
+++ b/lib/libc/gen/Makefile.inc
@@ -89,6 +89,7 @@ SRCS+= \
glob.c \
glob-compat11.c \
initgroups.c \
+ inotify.c \
isatty.c \
isinf.c \
isnan.c \
@@ -134,6 +135,7 @@ SRCS+= \
setmode.c \
setproctitle.c \
setprogname.c \
+ sig2str.c \
siginterrupt.c \
siglist.c \
signal.c \
@@ -159,6 +161,8 @@ SRCS+= \
ttyname.c \
ttyslot.c \
ualarm.c \
+ uexterr_format.c \
+ uexterr_gettext.c \
ulimit.c \
uname.c \
unvis-compat.c \
@@ -168,6 +172,11 @@ SRCS+= \
valloc.c \
wordexp.c
+.if ${COMPILER_FEATURES:Mblocks}
+CFLAGS.fts.c= -fblocks
+CFLAGS.glob.c= -fblocks
+.endif
+
CFLAGS.arc4random.c= -I${SRCTOP}/sys -I${SRCTOP}/sys/crypto/chacha20
CFLAGS.sysconf.c= -I${SRCTOP}/contrib/tzcode
@@ -362,7 +371,8 @@ MLINKS+=exec.3 execl.3 \
exec.3 exect.3 \
exec.3 execv.3 \
exec.3 execvP.3 \
- exec.3 execvp.3
+ exec.3 execvp.3 \
+ exec.3 execvpe.3
MLINKS+=fpclassify.3 finite.3 \
fpclassify.3 finitef.3 \
fpclassify.3 isfinite.3 \
@@ -465,7 +475,10 @@ MLINKS+=posix_spawn.3 posix_spawnp.3 \
posix_spawnattr_getsigdefault.3 posix_spawnattr_setsigdefault.3 \
posix_spawnattr_getsigmask.3 posix_spawnattr_setsigmask.3 \
posix_spawnattr_init.3 posix_spawnattr_destroy.3
-MLINKS+=psignal.3 strsignal.3 \
+MLINKS+=psignal.3 psiginfo.3 \
+ psignal.3 sig2str.3 \
+ psignal.3 str2sig.3 \
+ psignal.3 strsignal.3 \
psignal.3 sys_siglist.3 \
psignal.3 sys_signame.3
MLINKS+=pwcache.3 gid_from_group.3 \
@@ -487,8 +500,11 @@ MLINKS+=rand48.3 _rand48.3 \
MLINKS+=rtld_get_var.3 \
rtld_set_var.3
MLINKS+=scandir.3 alphasort.3 \
- scandir.3 scandirat.3 \
+ scandir.3 fdscandir.3 \
+ scandir.3 fdscandir_b.3 \
scandir.3 scandir_b.3 \
+ scandir.3 scandirat.3 \
+ scandir.3 scandirat_b.3 \
scandir.3 versionsort.3
MLINKS+=sem_open.3 sem_close.3 \
sem_open.3 sem_unlink.3
diff --git a/lib/libc/gen/Symbol.map b/lib/libc/gen/Symbol.map
index ca3974e6b747..26f638568efc 100644
--- a/lib/libc/gen/Symbol.map
+++ b/lib/libc/gen/Symbol.map
@@ -458,8 +458,20 @@ FBSD_1.8 {
aio_read2;
aio_write2;
execvpe;
+ fdscandir;
+ fdscandir_b;
+ fts_open_b;
+ glob_b;
+ inotify_add_watch;
+ inotify_init;
+ inotify_init1;
+ psiginfo;
rtld_get_var;
rtld_set_var;
+ scandirat_b;
+ uexterr_gettext;
+ sig2str;
+ str2sig;
};
FBSDprivate_1.0 {
@@ -584,10 +596,10 @@ FBSDprivate_1.0 {
__libc_tcdrain;
- __pthread_distribute_static_tls;
__pthread_map_stacks_exec;
__fillcontextx;
__fillcontextx2;
__getcontextx_size;
__makecontext;
+ __uexterr_format;
};
diff --git a/lib/libc/gen/_pthread_stubs.c b/lib/libc/gen/_pthread_stubs.c
index 2a0cebadd5fd..d867ee4db51e 100644
--- a/lib/libc/gen/_pthread_stubs.c
+++ b/lib/libc/gen/_pthread_stubs.c
@@ -63,6 +63,7 @@ static int stub_getname_np(pthread_t, char *, size_t);
#define PJT_DUAL_ENTRY(entry) \
(pthread_func_t)entry, (pthread_func_t)entry
+__attribute__((visibility("protected")))
pthread_func_entry_t __thr_jtable[PJT_MAX] = {
[PJT_ATFORK] = {PJT_DUAL_ENTRY(stub_zero)},
[PJT_ATTR_DESTROY] = {PJT_DUAL_ENTRY(stub_zero)},
diff --git a/lib/libc/gen/alarm.3 b/lib/libc/gen/alarm.3
index 8aeda29481eb..4a24cc6d7216 100644
--- a/lib/libc/gen/alarm.3
+++ b/lib/libc/gen/alarm.3
@@ -63,7 +63,7 @@ Due to
.Xr setitimer 2
restriction the maximum number of
.Fa seconds
-allowed is 100000000.
+allowed is 100,000,000.
.Sh RETURN VALUES
The return value of
.Fn alarm
diff --git a/lib/libc/gen/dlfcn.c b/lib/libc/gen/dlfcn.c
index bffee3952e0d..ae1c8d83df19 100644
--- a/lib/libc/gen/dlfcn.c
+++ b/lib/libc/gen/dlfcn.c
@@ -229,7 +229,7 @@ _dl_iterate_phdr_locked(
return (1);
_once(&dl_phdr_info_once, dl_init_phdr_info);
ti.ti_module = 1;
- ti.ti_offset = 0;
+ ti.ti_offset = -TLS_DTV_OFFSET;
phdr_info.dlpi_tls_data = __tls_get_addr(&ti);
ret = callback(&phdr_info, sizeof(phdr_info), data);
return (ret);
diff --git a/lib/libc/gen/dup3.3 b/lib/libc/gen/dup3.3
index f2798930797b..338a9ae74c64 100644
--- a/lib/libc/gen/dup3.3
+++ b/lib/libc/gen/dup3.3
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd August 16, 2013
+.Dd May 17, 2025
.Dt DUP3 3
.Os
.Sh NAME
@@ -47,6 +47,11 @@ The close-on-exec flag on the new file descriptor is determined by the
bit in
.Fa flags .
.Pp
+The close-on-fork flag on the new file descriptor is determined by the
+.Dv O_CLOFORK
+bit in
+.Fa flags .
+.Pp
If
.Fa oldd
\*(Ne
@@ -91,7 +96,9 @@ argument.
The
.Fa flags
argument has bits set other than
-.Dv O_CLOEXEC .
+.Dv O_CLOEXEC
+or
+.Dv O_CLOFORK .
.El
.Sh SEE ALSO
.Xr accept 2 ,
@@ -112,3 +119,7 @@ The
.Fn dup3
function appeared in
.Fx 10.0 .
+The
+.Dv O_CLOFORK
+flag appeared in
+.Fx 15.0 .
diff --git a/lib/libc/gen/dup3.c b/lib/libc/gen/dup3.c
index fca1e99fb47b..1401c1f5b607 100644
--- a/lib/libc/gen/dup3.c
+++ b/lib/libc/gen/dup3.c
@@ -39,21 +39,22 @@ int __dup3(int, int, int);
int
__dup3(int oldfd, int newfd, int flags)
{
- int how;
+ int fdflags;
if (oldfd == newfd) {
errno = EINVAL;
return (-1);
}
- if (flags & ~O_CLOEXEC) {
+ if ((flags & ~(O_CLOEXEC | O_CLOFORK)) != 0) {
errno = EINVAL;
return (-1);
}
- how = (flags & O_CLOEXEC) ? F_DUP2FD_CLOEXEC : F_DUP2FD;
+ fdflags = ((flags & O_CLOEXEC) != 0 ? FD_CLOEXEC : 0) |
+ ((flags & O_CLOFORK) != 0 ? FD_CLOFORK : 0);
- return (_fcntl(oldfd, how, newfd));
+ return (_fcntl(oldfd, F_DUP3FD | (fdflags << F_DUP3FD_SHIFT), newfd));
}
__weak_reference(__dup3, dup3);
diff --git a/lib/libc/gen/elf_utils.c b/lib/libc/gen/elf_utils.c
index 5b87e012d0eb..3714a0dc42b5 100644
--- a/lib/libc/gen/elf_utils.c
+++ b/lib/libc/gen/elf_utils.c
@@ -31,14 +31,16 @@
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
+
+#include <machine/tls.h>
+
#include <link.h>
#include <stddef.h>
#include <string.h>
+
#include "libc_private.h"
-#include "static_tls.h"
void __pthread_map_stacks_exec(void);
-void __pthread_distribute_static_tls(size_t, void *, size_t, size_t);
int
__elf_phdr_match_addr(struct dl_phdr_info *phdr_info, void *addr)
@@ -102,24 +104,3 @@ __pthread_map_stacks_exec(void)
((void (*)(void))__libc_interposing[INTERPOS_map_stacks_exec])();
}
-
-void
-__libc_distribute_static_tls(size_t offset, void *src, size_t len,
- size_t total_len)
-{
- uintptr_t tlsbase;
-
- tlsbase = _libc_get_static_tls_base(offset);
- memcpy((void *)tlsbase, src, len);
- memset((char *)tlsbase + len, 0, total_len - len);
-}
-
-#pragma weak __pthread_distribute_static_tls
-void
-__pthread_distribute_static_tls(size_t offset, void *src, size_t len,
- size_t total_len)
-{
-
- ((void (*)(size_t, void *, size_t, size_t))__libc_interposing[
- INTERPOS_distribute_static_tls])(offset, src, len, total_len);
-}
diff --git a/lib/libc/gen/err.c b/lib/libc/gen/err.c
index 24ea242560b8..16cbe27693e7 100644
--- a/lib/libc/gen/err.c
+++ b/lib/libc/gen/err.c
@@ -30,9 +30,12 @@
*/
#include "namespace.h"
+#include <sys/exterrvar.h>
#include <err.h>
#include <errno.h>
+#include <exterr.h>
#include <stdarg.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -43,6 +46,11 @@
static FILE *err_file; /* file to use for error output */
static void (*err_exit)(int);
+static void verrci(bool doexterr, int eval, int code, const char *fmt,
+ va_list ap) __printf0like(4, 0) __dead2;
+static void vwarnci(bool doexterr, int code, const char *fmt, va_list ap)
+ __printf0like(3, 0);
+
/*
* This is declared to take a `void *' so that the caller is not required
* to include <stdio.h> first. However, it is really a `FILE *', and the
@@ -70,14 +78,14 @@ _err(int eval, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
- verrc(eval, errno, fmt, ap);
+ verrci(true, eval, errno, fmt, ap);
va_end(ap);
}
void
verr(int eval, const char *fmt, va_list ap)
{
- verrc(eval, errno, fmt, ap);
+ verrci(true, eval, errno, fmt, ap);
}
void
@@ -85,13 +93,24 @@ errc(int eval, int code, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
- verrc(eval, code, fmt, ap);
+ verrci(false, eval, code, fmt, ap);
va_end(ap);
}
void
verrc(int eval, int code, const char *fmt, va_list ap)
{
+ verrci(false, eval, code, fmt, ap);
+}
+
+static void
+vexterr(bool doexterr, int code, const char *fmt, va_list ap)
+{
+ char exterr[UEXTERROR_MAXLEN]; /* libc knows the buffer size */
+ int extstatus;
+
+ if (doexterr)
+ extstatus = uexterr_gettext(exterr, sizeof(exterr));
if (err_file == NULL)
err_set_file(NULL);
fprintf(err_file, "%s: ", _getprogname());
@@ -99,7 +118,16 @@ verrc(int eval, int code, const char *fmt, va_list ap)
vfprintf(err_file, fmt, ap);
fprintf(err_file, ": ");
}
- fprintf(err_file, "%s\n", strerror(code));
+ fprintf(err_file, "%s", strerror(code));
+ if (doexterr && extstatus == 0 && exterr[0] != '\0')
+ fprintf(err_file, " (extended error %s)", exterr);
+ fprintf(err_file, "\n");
+}
+
+static void
+verrci(bool doexterr, int eval, int code, const char *fmt, va_list ap)
+{
+ vexterr(doexterr, code, fmt, ap);
if (err_exit)
err_exit(eval);
exit(eval);
@@ -157,17 +185,16 @@ warnc(int code, const char *fmt, ...)
void
vwarnc(int code, const char *fmt, va_list ap)
{
+ vwarnci(false, code, fmt, ap);
+}
+
+static void
+vwarnci(bool doexterr, int code, const char *fmt, va_list ap)
+{
int saved_errno;
saved_errno = errno;
- if (err_file == NULL)
- err_set_file(NULL);
- fprintf(err_file, "%s: ", _getprogname());
- if (fmt != NULL) {
- vfprintf(err_file, fmt, ap);
- fprintf(err_file, ": ");
- }
- fprintf(err_file, "%s\n", strerror(code));
+ vexterr(doexterr, code, fmt, ap);
errno = saved_errno;
}
diff --git a/lib/libc/gen/exec.c b/lib/libc/gen/exec.c
index d9285733e965..12020a79f6b4 100644
--- a/lib/libc/gen/exec.c
+++ b/lib/libc/gen/exec.c
@@ -32,8 +32,10 @@
#include "namespace.h"
#include <sys/param.h>
#include <sys/stat.h>
+#include <assert.h>
#include <errno.h>
#include <unistd.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@@ -136,29 +138,132 @@ execv(const char *name, char * const *argv)
int
execvp(const char *name, char * const *argv)
{
- return (execvpe(name, argv, environ));
+ return (__libc_execvpe(name, argv, environ));
+}
+
+/*
+ * Returns 0 if we don't consider this a terminal condition, -1 if we do.
+ */
+static int
+execvPe_prog(const char *path, char * const *argv, char * const *envp)
+{
+ struct stat sb;
+ const char **memp;
+ size_t cnt;
+ int save_errno;
+
+ (void)_execve(path, argv, envp);
+ /* Grouped roughly by never terminal vs. usually terminal conditions */
+ switch (errno) {
+ case ELOOP:
+ case ENAMETOOLONG:
+ case ENOENT:
+ case ENOTDIR:
+ /* Non-terminal: property of the path we're trying */
+ break;
+ case ENOEXEC:
+ /*
+ * Failures here are considered terminal because we must handle
+ * this via the ENOEXEC fallback path; doing any further
+ * searching would be categorically incorrect.
+ */
+
+ for (cnt = 0; argv[cnt] != NULL; ++cnt)
+ ;
+
+ /*
+ * cnt may be 0 above; always allocate at least
+ * 3 entries so that we can at least fit "sh", path, and
+ * the NULL terminator. We can rely on cnt to take into
+ * account the NULL terminator in all other scenarios,
+ * as we drop argv[0].
+ */
+ memp = alloca(MAX(3, cnt + 2) * sizeof(char *));
+ assert(memp != NULL);
+ if (cnt > 0) {
+ memp[0] = argv[0];
+ memp[1] = path;
+ memcpy(&memp[2], &argv[1], cnt * sizeof(char *));
+ } else {
+ memp[0] = "sh";
+ memp[1] = path;
+ memp[2] = NULL;
+ }
+
+ (void)_execve(_PATH_BSHELL, __DECONST(char **, memp), envp);
+ return (-1);
+ case ENOMEM:
+ case E2BIG:
+ /* Terminal: persistent condition */
+ return (-1);
+ case ETXTBSY:
+ /*
+ * Terminal: we used to retry here, but sh(1) doesn't.
+ */
+ return (-1);
+ default:
+ /*
+ * EACCES may be for an inaccessible directory or
+ * a non-executable file. Call stat() to decide
+ * which. This also handles ambiguities for EFAULT
+ * and EIO, and undocumented errors like ESTALE.
+ * We hope that the race for a stat() is unimportant.
+ */
+ save_errno = errno;
+ if (stat(path, &sb) == -1) {
+ /*
+ * We force errno to ENOENT here to disambiguate the
+ * EACCESS case; the results of execve(2) are somewhat
+ * inconclusive because either the file did not exist or
+ * we just don't have search permissions, but the caller
+ * only really wants to see EACCES if the file did exist
+ * but was not accessible.
+ */
+ if (save_errno == EACCES)
+ errno = ENOENT;
+ break;
+ }
+
+ errno = save_errno;
+
+ /*
+ * Non-terminal: the file did exist and we just didn't have
+ * access to it, so we surface the EACCES and let the search
+ * continue for a candidate that we do have access to.
+ */
+ if (errno == EACCES)
+ break;
+
+ /*
+ * All other errors here are terminal, as prescribed by exec(3).
+ */
+ return (-1);
+ }
+
+ return (0);
}
static int
execvPe(const char *name, const char *path, char * const *argv,
char * const *envp)
{
- const char **memp;
- size_t cnt, lp, ln;
- int eacces, save_errno;
char buf[MAXPATHLEN];
- const char *bp, *np, *op, *p;
- struct stat sb;
+ size_t ln, lp;
+ const char *np, *op, *p;
+ bool eacces;
- eacces = 0;
+ eacces = false;
/* If it's an absolute or relative path name, it's easy. */
- if (strchr(name, '/')) {
- bp = name;
- op = NULL;
- goto retry;
+ if (strchr(name, '/') != NULL) {
+ /*
+ * We ignore non-terminal conditions because we don't have any
+ * further paths to try -- we can just bubble up the errno from
+ * execve(2) here.
+ */
+ (void)execvPe_prog(name, argv, envp);
+ return (-1);
}
- bp = buf;
/* If it's an empty path name, fail in the usual POSIX way. */
if (*name == '\0') {
@@ -192,9 +297,13 @@ execvPe(const char *name, const char *path, char * const *argv,
op = np + 1;
/*
- * If the path is too long complain. This is a possible
- * security issue; given a way to make the path too long
- * the user may execute the wrong program.
+ * If the path is too long, then complain. This is a possible
+ * security issue: given a way to make the path too long, the
+ * user may execute the wrong program.
+ *
+ * Remember to exercise caution here with assembling our final
+ * buf and any output, as we may be running in a vfork() context
+ * via posix_spawnp().
*/
if (lp + ln + 2 > sizeof(buf)) {
(void)_write(STDERR_FILENO, execvPe_err_preamble,
@@ -202,82 +311,40 @@ execvPe(const char *name, const char *path, char * const *argv,
(void)_write(STDERR_FILENO, p, lp);
(void)_write(STDERR_FILENO, execvPe_err_trailer,
sizeof(execvPe_err_trailer) - 1);
+
continue;
}
- bcopy(p, buf, lp);
+
+ memcpy(&buf[0], p, lp);
buf[lp] = '/';
- bcopy(name, buf + lp + 1, ln);
+ memcpy(&buf[lp + 1], name, ln);
buf[lp + ln + 1] = '\0';
-retry: (void)_execve(bp, argv, envp);
- switch (errno) {
- case E2BIG:
- goto done;
- case ELOOP:
- case ENAMETOOLONG:
- case ENOENT:
- break;
- case ENOEXEC:
- for (cnt = 0; argv[cnt]; ++cnt)
- ;
-
- /*
- * cnt may be 0 above; always allocate at least
- * 3 entries so that we can at least fit "sh", bp, and
- * the NULL terminator. We can rely on cnt to take into
- * account the NULL terminator in all other scenarios,
- * as we drop argv[0].
- */
- memp = alloca(MAX(3, cnt + 2) * sizeof(char *));
- if (memp == NULL) {
- /* errno = ENOMEM; XXX override ENOEXEC? */
- goto done;
- }
- if (cnt > 0) {
- memp[0] = argv[0];
- memp[1] = bp;
- bcopy(argv + 1, memp + 2, cnt * sizeof(char *));
- } else {
- memp[0] = "sh";
- memp[1] = bp;
- memp[2] = NULL;
- }
- (void)_execve(_PATH_BSHELL,
- __DECONST(char **, memp), envp);
- goto done;
- case ENOMEM:
- goto done;
- case ENOTDIR:
- break;
- case ETXTBSY:
- /*
- * We used to retry here, but sh(1) doesn't.
- */
- goto done;
- default:
- /*
- * EACCES may be for an inaccessible directory or
- * a non-executable file. Call stat() to decide
- * which. This also handles ambiguities for EFAULT
- * and EIO, and undocumented errors like ESTALE.
- * We hope that the race for a stat() is unimportant.
- */
- save_errno = errno;
- if (stat(bp, &sb) != 0)
- break;
- if (save_errno == EACCES) {
- eacces = 1;
- continue;
- }
- errno = save_errno;
- goto done;
- }
+ /*
+ * For terminal conditions we can just return immediately. If
+ * it was non-terminal, we just need to note if we had an
+ * EACCES -- execvPe_prog would do a stat(2) and leave us with
+ * an errno of EACCES only if the file did exist; otherwise it
+ * would coerce it to an ENOENT because we may not know if a
+ * file actually existed there or not.
+ */
+ if (execvPe_prog(buf, argv, envp) == -1)
+ return (-1);
+ if (errno == EACCES)
+ eacces = true;
}
+
+ /*
+ * We don't often preserve errors encountering during the PATH search,
+ * so we override it here. ENOENT would be misleading if we found a
+ * candidate but couldn't access it, but most of the other conditions
+ * are either terminal or indicate that nothing was there.
+ */
if (eacces)
errno = EACCES;
else
errno = ENOENT;
-done:
+
return (-1);
}
@@ -288,7 +355,7 @@ execvP(const char *name, const char *path, char * const argv[])
}
int
-execvpe(const char *name, char * const argv[], char * const envp[])
+__libc_execvpe(const char *name, char * const argv[], char * const envp[])
{
const char *path;
@@ -298,3 +365,5 @@ execvpe(const char *name, char * const argv[], char * const envp[])
return (execvPe(name, path, argv, envp));
}
+
+__weak_reference(__libc_execvpe, execvpe);
diff --git a/lib/libc/gen/fdopendir.c b/lib/libc/gen/fdopendir.c
index 67c0766b6d83..9393cbe28f85 100644
--- a/lib/libc/gen/fdopendir.c
+++ b/lib/libc/gen/fdopendir.c
@@ -30,14 +30,13 @@
*/
#include "namespace.h"
-#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
+#include <stdbool.h>
#include "un-namespace.h"
#include "gen-private.h"
@@ -49,8 +48,16 @@
DIR *
fdopendir(int fd)
{
+ int flags, rc;
- if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+ flags = _fcntl(fd, F_GETFD, 0);
+ if (flags == -1)
return (NULL);
+
+ if ((flags & FD_CLOEXEC) == 0) {
+ rc = _fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+ if (rc == -1)
+ return (NULL);
+ }
return (__opendir_common(fd, DTF_HIDEW | DTF_NODUP, true));
}
diff --git a/lib/libc/gen/fnmatch.3 b/lib/libc/gen/fnmatch.3
index 804bc968c1ce..7f020fec58e3 100644
--- a/lib/libc/gen/fnmatch.3
+++ b/lib/libc/gen/fnmatch.3
@@ -27,7 +27,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd April 2, 2022
+.Dd April 7, 2025
.Dt FNMATCH 3
.Os
.Sh NAME
@@ -129,12 +129,8 @@ otherwise, it returns the value
.Sh STANDARDS
The current implementation of the
.Fn fnmatch
-function
-.Em does not
-conform to
+function is expected to conform to
.St -p1003.2 .
-Collating symbol expressions, equivalence class expressions and
-character class expressions are not supported.
.Sh HISTORY
A predecessor to
.Fn fnmatch ,
diff --git a/lib/libc/gen/fnmatch.c b/lib/libc/gen/fnmatch.c
index fb1829e69502..1c583a9d23e2 100644
--- a/lib/libc/gen/fnmatch.c
+++ b/lib/libc/gen/fnmatch.c
@@ -67,7 +67,8 @@
#define RANGE_NOMATCH 0
#define RANGE_ERROR (-1)
-static int rangematch(const char *, wchar_t, int, char **, mbstate_t *);
+static int rangematch(const char *, wchar_t, const char *, int, char **,
+ char **, mbstate_t *, mbstate_t *);
static int fnmatch1(const char *, const char *, const char *, int, mbstate_t,
mbstate_t);
@@ -85,7 +86,7 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
{
const char *bt_pattern, *bt_string;
mbstate_t bt_patmbs, bt_strmbs;
- char *newp;
+ char *newp, *news;
char c;
wchar_t pc, sc;
size_t pclen, sclen;
@@ -151,8 +152,10 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
* there is no way having it match more characters
* can help us, given that we are already here.
*/
- bt_pattern = pattern, bt_patmbs = patmbs;
- bt_string = string, bt_strmbs = strmbs;
+ bt_pattern = pattern;
+ bt_patmbs = patmbs;
+ bt_string = string;
+ bt_strmbs = strmbs;
break;
case '[':
if (sc == EOS)
@@ -164,17 +167,17 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
((flags & FNM_PATHNAME) && *(string - 1) == '/')))
goto backtrack;
- switch (rangematch(pattern, sc, flags, &newp,
- &patmbs)) {
+ switch (rangematch(pattern, sc, string + sclen, flags,
+ &newp, &news, &patmbs, &strmbs)) {
case RANGE_ERROR:
goto norm;
case RANGE_MATCH:
pattern = newp;
+ string = news;
break;
case RANGE_NOMATCH:
goto backtrack;
}
- string += sclen;
break;
case '\\':
if (!(flags & FNM_NOESCAPE)) {
@@ -218,8 +221,10 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
if (sc == '/' && flags & FNM_PATHNAME)
return (FNM_NOMATCH);
bt_string += sclen;
- pattern = bt_pattern, patmbs = bt_patmbs;
- string = bt_string, strmbs = bt_strmbs;
+ pattern = bt_pattern;
+ patmbs = bt_patmbs;
+ string = bt_string;
+ strmbs = bt_strmbs;
}
break;
}
@@ -228,15 +233,20 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart,
}
static int
-rangematch(const char *pattern, wchar_t test, int flags, char **newp,
- mbstate_t *patmbs)
+rangematch(const char *pattern, wchar_t test, const char *string, int flags,
+ char **newp, char **news, mbstate_t *patmbs, mbstate_t *strmbs)
{
int negate, ok;
wchar_t c, c2;
size_t pclen;
const char *origpat;
struct xlocale_collate *table =
- (struct xlocale_collate*)__get_locale()->components[XLC_COLLATE];
+ (struct xlocale_collate *)__get_locale()->components[XLC_COLLATE];
+ wchar_t buf[COLLATE_STR_LEN]; /* STR_LEN defined in collate.h */
+ const char *cp, *savestring;
+ int special;
+ mbstate_t save;
+ size_t sclen, len;
/*
* A bracket expression starting with an unquoted circumflex
@@ -259,20 +269,132 @@ rangematch(const char *pattern, wchar_t test, int flags, char **newp,
ok = 0;
origpat = pattern;
for (;;) {
+ c = 0;
if (*pattern == ']' && pattern > origpat) {
- pattern++;
break;
} else if (*pattern == '\0') {
return (RANGE_ERROR);
} else if (*pattern == '/' && (flags & FNM_PATHNAME)) {
return (RANGE_NOMATCH);
- } else if (*pattern == '\\' && !(flags & FNM_NOESCAPE))
+ } else if (*pattern == '\\' && !(flags & FNM_NOESCAPE)) {
pattern++;
- pclen = mbrtowc(&c, pattern, MB_LEN_MAX, patmbs);
- if (pclen == (size_t)-1 || pclen == (size_t)-2)
- return (RANGE_NOMATCH);
- pattern += pclen;
+ } else if (*pattern == '[' &&
+ ((special = *(pattern + 1)) == '.' ||
+ special == '=' || special == ':')) {
+ cp = (pattern += 2);
+ while ((cp = strchr(cp, special))) {
+ if (*(cp + 1) == ']')
+ break;
+ cp++;
+ }
+ if (!cp)
+ return (RANGE_ERROR);
+ if (special == '.') {
+treat_like_collating_symbol:
+ len = __collate_collating_symbol(buf,
+ COLLATE_STR_LEN, pattern,
+ cp - pattern, patmbs);
+ if (len == (size_t)-1 || len == 0)
+ return (RANGE_ERROR);
+ pattern = cp + 2;
+ if (len > 1) {
+ wchar_t *wp, sc;
+ /*
+ * No multi-character collation
+ * symbols as start of range.
+ */
+ if (*(cp + 2) == '-' &&
+ *(cp + 3) != EOS &&
+ *(cp + 3) != ']')
+ return (RANGE_ERROR);
+ wp = buf;
+ if (test != *wp++)
+ continue;
+ if (len == 1) {
+ ok = 1;
+ break;
+ }
+ memcpy(&save, strmbs, sizeof(save));
+ savestring = string;
+ while (--len > 0) {
+ sclen = mbrtowc(&sc, string,
+ MB_LEN_MAX, strmbs);
+ if (sclen == (size_t)-1 ||
+ sclen == (size_t)-2) {
+ sc = (unsigned char)*string;
+ sclen = 1;
+ memset(&strmbs, 0,
+ sizeof(strmbs));
+ }
+ if (sc != *wp++) {
+ memcpy(strmbs, &save,
+ sizeof(save));
+ string = savestring;
+ break;
+ }
+ string += sclen;
+ }
+ if (len == 0) {
+ ok = 1;
+ break;
+ }
+ continue; /* no match */
+ }
+ c = *buf;
+ } else if (special == '=') {
+ int ec;
+ memcpy(&save, patmbs, sizeof(save));
+ ec = __collate_equiv_class(pattern,
+ cp - pattern, patmbs);
+ if (ec < 0)
+ return (RANGE_ERROR);
+ if (ec == 0) {
+ memcpy(patmbs, &save, sizeof(save));
+ goto treat_like_collating_symbol;
+ }
+ pattern = cp + 2;
+ /* no equivalence classes as start of range */
+ if (*(cp + 2) == '-' && *(cp + 3) != EOS &&
+ *(cp + 3) != ']')
+ return (RANGE_ERROR);
+ len = __collate_equiv_match(ec, NULL, 0, test,
+ string, strlen(string), strmbs, &sclen);
+ if (len < 0)
+ return (RANGE_ERROR);
+ if (len > 0) {
+ ok = 1;
+ string += sclen;
+ break;
+ }
+ continue;
+ } else { /* special == ':' */
+ wctype_t charclass;
+ char name[CHARCLASS_NAME_MAX + 1];
+ /* no character classes as start of range */
+ if (*(cp + 2) == '-' && *(cp + 3) != EOS &&
+ *(cp + 3) != ']')
+ return (RANGE_ERROR);
+ /* assume character class names are ascii */
+ if (cp - pattern > CHARCLASS_NAME_MAX)
+ return (RANGE_ERROR);
+ strlcpy(name, pattern, cp - pattern + 1);
+ pattern = cp + 2;
+ if ((charclass = wctype(name)) == 0)
+ return (RANGE_ERROR);
+ if (iswctype(test, charclass)) {
+ ok = 1;
+ break;
+ }
+ continue;
+ }
+ }
+ if (!c) {
+ pclen = mbrtowc(&c, pattern, MB_LEN_MAX, patmbs);
+ if (pclen == (size_t)-1 || pclen == (size_t)-2)
+ return (RANGE_NOMATCH);
+ pattern += pclen;
+ }
if (flags & FNM_CASEFOLD)
c = towlower(c);
@@ -288,6 +410,37 @@ rangematch(const char *pattern, wchar_t test, int flags, char **newp,
if (c2 == EOS)
return (RANGE_ERROR);
+ if ((c2 == '[' && (special = *pattern) == '.') ||
+ special == '=' || special == ':') {
+
+ /*
+ * No equivalence classes or character
+ * classes as end of range.
+ */
+ if (special == '=' || special == ':')
+ return (RANGE_ERROR);
+ cp = ++pattern;
+ while ((cp = strchr(cp, special))) {
+ if (*(cp + 1) == ']')
+ break;
+ cp++;
+ }
+ if (!cp)
+ return (RANGE_ERROR);
+ len = __collate_collating_symbol(buf,
+ COLLATE_STR_LEN, pattern,
+ cp - pattern, patmbs);
+
+ /*
+ * No multi-character collation symbols
+ * as end of range.
+ */
+ if (len != 1)
+ return (RANGE_ERROR);
+ pattern = cp + 2;
+ c2 = *buf;
+ }
+
if (flags & FNM_CASEFOLD)
c2 = towlower(c2);
@@ -295,12 +448,44 @@ rangematch(const char *pattern, wchar_t test, int flags, char **newp,
c <= test && test <= c2 :
__wcollate_range_cmp(c, test) <= 0
&& __wcollate_range_cmp(test, c2) <= 0
- )
+ ) {
ok = 1;
- } else if (c == test)
+ break;
+ }
+ } else if (c == test) {
ok = 1;
+ break;
+ }
}
- *newp = (char *)pattern;
+ /* go to end of bracket expression */
+ special = 0;
+ while (*pattern != ']') {
+ if (*pattern == 0)
+ return (RANGE_ERROR);
+ if (*pattern == special) {
+ if (*++pattern == ']') {
+ special = 0;
+ pattern++;
+ }
+ continue;
+ }
+ if (!special && *pattern == '[') {
+ special = *++pattern;
+ if (special != '.' && special != '=' && special != ':')
+ special = 0;
+ else
+ pattern++;
+ continue;
+ }
+ pclen = mbrtowc(&c, pattern, MB_LEN_MAX, patmbs);
+ if (pclen == (size_t)-1 || pclen == (size_t)-2)
+ return (RANGE_NOMATCH);
+ pattern += pclen;
+ }
+
+ *newp = (char *)++pattern;
+ *news = (char *)string;
+
return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
}
diff --git a/lib/libc/gen/fts.3 b/lib/libc/gen/fts.3
index 468b14115ec6..ee558b892c8c 100644
--- a/lib/libc/gen/fts.3
+++ b/lib/libc/gen/fts.3
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd January 12, 2014
+.Dd June 30, 2025
.Dt FTS 3
.Os
.Sh NAME
@@ -37,6 +37,8 @@
.In fts.h
.Ft FTS *
.Fn fts_open "char * const *path_argv" "int options" "int (*compar)(const FTSENT * const *, const FTSENT * const *)"
+.Ft FTS *
+.Fn fts_open_b "char * const *path_argv" "int options" "int (^compar)(const FTSENT * const *, const FTSENT * const *)"
.Ft FTSENT *
.Fn fts_read "FTS *ftsp"
.Ft FTSENT *
@@ -59,7 +61,9 @@ functions are provided for traversing
file hierarchies.
A simple overview is that the
.Fn fts_open
-function returns a
+and
+.Fn fts_open_b
+functions return a
.Dq handle
on a file hierarchy, which is then supplied to
the other
@@ -176,6 +180,12 @@ by one of the other
values.
.It Dv FTS_DNR
A directory which cannot be read.
+This immediately follows
+.Dv FTS_D ,
+in place of
+.Dv FTS_DP ,
+when the directory could not be entered, or could be entered but not
+read.
This is an error return, and the
.Fa fts_errno
field will be set to indicate what caused the error.
@@ -186,6 +196,8 @@ or
.Ql ..\&
which was not specified as a file name to
.Fn fts_open
+or
+.Fn fts_open_b
(see
.Dv FTS_SEEDOT ) .
.It Dv FTS_DP
@@ -234,6 +246,8 @@ A path for accessing the file from the current directory.
The path for the file relative to the root of the traversal.
This path contains the path specified to
.Fn fts_open
+or
+.Fn fts_open_b
as a prefix.
.It Fa fts_pathlen
The length of the string referenced by
@@ -380,12 +394,16 @@ must be specified.
The options are selected by
.Em or Ns 'ing
the following values:
-.Bl -tag -width "FTS_PHYSICAL"
+.Bl -tag -width "FTS_COMFOLLOWDIR"
.It Dv FTS_COMFOLLOW
This option causes any symbolic link specified as a root path to be
followed immediately whether or not
.Dv FTS_LOGICAL
is also specified.
+.It Dv FTS_COMFOLLOWDIR
+This option is similar to
+.Dv FTS_COMFOLLOW ,
+but only follows symbolic links to directories.
.It Dv FTS_LOGICAL
This option causes the
.Nm
@@ -441,6 +459,15 @@ field to
and leave the contents of the
.Fa statp
field undefined.
+.It Dv FTS_NOSTAT_TYPE
+This option is similar to
+.Dv FTS_NOSTAT ,
+but attempts to populate
+.Fa fts_info
+based on information from the
+.Fa d_type
+field of
+.Vt struct dirent .
.It Dv FTS_PHYSICAL
This option causes the
.Nm
@@ -518,6 +545,15 @@ the directory traversal order is in the order listed in
.Fa path_argv
for the root paths, and in the order listed in the directory for
everything else.
+.Sh FTS_OPEN_B
+The
+.Fn fts_open_b
+function is identical to
+.Fn fts_open
+except that it takes a block pointer instead of a function pointer.
+The block is copied before
+.Fn fts_open_b
+returns, so the original can safely go out of scope or be released.
.Sh FTS_READ
The
.Fn fts_read
@@ -593,9 +629,13 @@ As a special case, if
has not yet been called for a hierarchy,
.Fn fts_children
will return a pointer to the files in the logical directory specified to
-.Fn fts_open ,
+.Fn fts_open
+or
+.Fn fts_open_b ,
i.e., the arguments specified to
-.Fn fts_open .
+.Fn fts_open
+or
+.Fn fts_open_b .
Otherwise, if the
.Vt FTSENT
structure most recently returned by
@@ -716,6 +756,8 @@ function closes a file hierarchy stream
.Fa ftsp
and restores the current directory to the directory from which
.Fn fts_open
+or
+.Fn fts_open_b
was called to open
.Fa ftsp .
The
@@ -723,29 +765,38 @@ The
function
returns 0 on success, and \-1 if an error occurs.
.Sh ERRORS
-The function
+The
.Fn fts_open
-may fail and set
+and
+.Fn fts_open_b
+functions may fail and set
.Va errno
for any of the errors specified for the library functions
.Xr open 2
and
.Xr malloc 3 .
+The
+.Fn fts_open_b
+function may also fail and set
+.Va errno
+to
+.Dv ENOSYS
+if the blocks runtime is missing.
.Pp
-The function
+The
.Fn fts_close
-may fail and set
+function may fail and set
.Va errno
for any of the errors specified for the library functions
.Xr chdir 2
and
.Xr close 2 .
.Pp
-The functions
+The
.Fn fts_read
and
.Fn fts_children
-may fail and set
+functions may fail and set
.Va errno
for any of the errors specified for the library functions
.Xr chdir 2 ,
@@ -755,12 +806,12 @@ for any of the errors specified for the library functions
and
.Xr stat 2 .
.Pp
-In addition,
+In addition, the
.Fn fts_children ,
-.Fn fts_open
+.Fn fts_open ,
and
.Fn fts_set
-may fail and set
+functions may fail and set
.Va errno
as follows:
.Bl -tag -width Er
@@ -788,6 +839,13 @@ functions were introduced in
principally to provide for alternative interfaces to the
.Nm
functionality using different data structures.
+Blocks support and the
+.Dv FTS_COMFOLLOWDIR
+and
+.Dv FTS_NOSTAT
+options were added in
+.Fx 15.0
+based on similar functionality in macOS.
.Sh BUGS
The
.Fn fts_open
diff --git a/lib/libc/gen/fts.c b/lib/libc/gen/fts.c
index 0c26c91feb84..cce959ba836a 100644
--- a/lib/libc/gen/fts.c
+++ b/lib/libc/gen/fts.c
@@ -48,6 +48,19 @@
#include "gen-private.h"
+#ifdef __BLOCKS__
+#include <Block.h>
+#else
+#include "block_abi.h"
+typedef DECLARE_BLOCK(int, fts_block,
+ const FTSENT * const *, const FTSENT * const *);
+void qsort_b(void *, size_t, size_t, fts_block);
+#endif /* __BLOCKS__ */
+/* only present if linked with blocks runtime */
+void *_Block_copy(const void *) __weak_symbol;
+void _Block_release(const void *) __weak_symbol;
+extern void *_NSConcreteGlobalBlock[] __weak_symbol;
+
static FTSENT *fts_alloc(FTS *, char *, size_t);
static FTSENT *fts_build(FTS *, int);
static void fts_lfree(FTSENT *);
@@ -102,39 +115,21 @@ static const char *ufslike_filesystems[] = {
0
};
-FTS *
-fts_open(char * const *argv, int options,
- int (*compar)(const FTSENT * const *, const FTSENT * const *))
+static FTS *
+__fts_open(FTS *sp, char * const *argv)
{
- struct _fts_private *priv;
- FTS *sp;
FTSENT *p, *root;
FTSENT *parent, *tmp;
size_t len, nitems;
- /* Options check. */
- if (options & ~FTS_OPTIONMASK) {
- errno = EINVAL;
- return (NULL);
- }
-
- /* fts_open() requires at least one path */
- if (*argv == NULL) {
- errno = EINVAL;
- return (NULL);
- }
-
- /* Allocate/initialize the stream. */
- if ((priv = calloc(1, sizeof(*priv))) == NULL)
- return (NULL);
- sp = &priv->ftsp_fts;
- sp->fts_compar = compar;
- sp->fts_options = options;
-
/* Logical walks turn on NOCHDIR; symbolic links are too hard. */
if (ISSET(FTS_LOGICAL))
SET(FTS_NOCHDIR);
+ /* NOSTAT_TYPE implies NOSTAT */
+ if (ISSET(FTS_NOSTAT_TYPE))
+ SET(FTS_NOSTAT);
+
/*
* Start out with 1K of path space, and enough, in any case,
* to hold the user's paths.
@@ -158,7 +153,9 @@ fts_open(char * const *argv, int options,
p->fts_level = FTS_ROOTLEVEL;
p->fts_parent = parent;
p->fts_accpath = p->fts_name;
- p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW), -1);
+ p->fts_info = fts_stat(sp, p,
+ ISSET(FTS_COMFOLLOWDIR) ? -1 : ISSET(FTS_COMFOLLOW),
+ -1);
/* Command-line "." and ".." are real directories. */
if (p->fts_info == FTS_DOT)
@@ -168,7 +165,7 @@ fts_open(char * const *argv, int options,
* If comparison routine supplied, traverse in sorted
* order; otherwise traverse in the order specified.
*/
- if (compar) {
+ if (sp->fts_compar) {
p->fts_link = root;
root = p;
} else {
@@ -181,7 +178,7 @@ fts_open(char * const *argv, int options,
}
}
}
- if (compar && nitems > 1)
+ if (sp->fts_compar && nitems > 1)
root = fts_sort(sp, root, nitems);
/*
@@ -214,6 +211,97 @@ mem1: free(sp);
return (NULL);
}
+FTS *
+fts_open(char * const *argv, int options,
+ int (*compar)(const FTSENT * const *, const FTSENT * const *))
+{
+ struct _fts_private *priv;
+ FTS *sp;
+
+ /* Options check. */
+ if (options & ~FTS_OPTIONMASK) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* fts_open() requires at least one path */
+ if (*argv == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Allocate/initialize the stream. */
+ if ((priv = calloc(1, sizeof(*priv))) == NULL)
+ return (NULL);
+ sp = &priv->ftsp_fts;
+ sp->fts_compar = compar;
+ sp->fts_options = options;
+
+ return (__fts_open(sp, argv));
+}
+
+#ifdef __BLOCKS__
+FTS *
+fts_open_b(char * const *argv, int options,
+ int (^compar)(const FTSENT * const *, const FTSENT * const *))
+#else
+FTS *
+fts_open_b(char * const *argv, int options, fts_block compar)
+#endif /* __BLOCKS__ */
+{
+ struct _fts_private *priv;
+ FTS *sp;
+
+ /* No blocks, no problems. */
+ if (compar == NULL)
+ return (fts_open(argv, options, NULL));
+
+ /* Avoid segfault if blocks runtime is missing. */
+ if (_Block_copy == NULL) {
+ errno = ENOSYS;
+ return (NULL);
+ }
+
+ /* Options check. */
+ if (options & ~FTS_OPTIONMASK) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* fts_open() requires at least one path */
+ if (*argv == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Allocate/initialize the stream. */
+ if ((priv = calloc(1, sizeof(*priv))) == NULL)
+ return (NULL);
+ sp = &priv->ftsp_fts;
+#ifdef __BLOCKS__
+ compar = Block_copy(compar);
+#else
+ if (compar->isa != &_NSConcreteGlobalBlock)
+ compar = _Block_copy(compar);
+#endif /* __BLOCKS__ */
+ if (compar == NULL) {
+ free(priv);
+ return (NULL);
+ }
+ sp->fts_compar_b = compar;
+ sp->fts_options = options | FTS_COMPAR_B;
+
+ if ((sp = __fts_open(sp, argv)) == NULL) {
+#ifdef __BLOCKS__
+ Block_release(compar);
+#else
+ if (compar->isa != &_NSConcreteGlobalBlock)
+ _Block_release(compar);
+#endif /* __BLOCKS__ */
+ }
+ return (sp);
+}
+
static void
fts_load(FTS *sp, FTSENT *p)
{
@@ -265,6 +353,17 @@ fts_close(FTS *sp)
free(sp->fts_array);
free(sp->fts_path);
+ /* Free up any block pointer. */
+ if (ISSET(FTS_COMPAR_B) && sp->fts_compar_b != NULL) {
+#ifdef __BLOCKS__
+ Block_release(sp->fts_compar_b);
+#else
+ if (((fts_block)(sp->fts_compar_b))->isa !=
+ &_NSConcreteGlobalBlock)
+ _Block_release(sp->fts_compar_b);
+#endif /* __BLOCKS__ */
+ }
+
/* Return to original directory, save errno if necessary. */
if (!ISSET(FTS_NOCHDIR)) {
saved_errno = fchdir(sp->fts_rfd) ? errno : 0;
@@ -650,14 +749,10 @@ fts_build(FTS *sp, int type)
* Open the directory for reading. If this fails, we're done.
* If being called from fts_read, set the fts_info field.
*/
-#ifdef FTS_WHITEOUT
if (ISSET(FTS_WHITEOUT))
oflag = DTF_NODUP;
else
oflag = DTF_HIDEW | DTF_NODUP;
-#else
-#define __opendir2(path, flag) opendir(path)
-#endif
if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) {
if (type == BREAD) {
cur->fts_info = FTS_DNR;
@@ -784,10 +879,8 @@ mem1: saved_errno = errno;
p->fts_parent = sp->fts_cur;
p->fts_pathlen = len + dnamlen;
-#ifdef FTS_WHITEOUT
if (dp->d_type == DT_WHT)
p->fts_flags |= FTS_ISW;
-#endif
if (cderrno) {
if (nlinks) {
@@ -796,12 +889,8 @@ mem1: saved_errno = errno;
} else
p->fts_info = FTS_NSOK;
p->fts_accpath = cur->fts_accpath;
- } else if (nlinks == 0
-#ifdef DT_DIR
- || (nostat &&
- dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
-#endif
- ) {
+ } else if (nlinks == 0 || (nostat &&
+ dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)) {
p->fts_accpath =
ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
p->fts_info = FTS_NSOK;
@@ -821,6 +910,25 @@ mem1: saved_errno = errno;
p->fts_info == FTS_DC || p->fts_info == FTS_DOT))
--nlinks;
}
+ if (p->fts_info == FTS_NSOK && ISSET(FTS_NOSTAT_TYPE)) {
+ switch (dp->d_type) {
+ case DT_FIFO:
+ case DT_CHR:
+ case DT_BLK:
+ case DT_SOCK:
+ p->fts_info = FTS_DEFAULT;
+ break;
+ case DT_REG:
+ p->fts_info = FTS_F;
+ break;
+ case DT_LNK:
+ p->fts_info = FTS_SL;
+ break;
+ case DT_WHT:
+ p->fts_info = FTS_W;
+ break;
+ }
+ }
/* We walk in directory order so "ls -f" doesn't get upset. */
p->fts_link = NULL;
@@ -897,7 +1005,7 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
dev_t dev;
ino_t ino;
struct stat *sbp, sb;
- int saved_errno;
+ int ret, saved_errno;
const char *path;
if (dfd == -1) {
@@ -910,7 +1018,6 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
/* If user needs stat info, stat buffer already allocated. */
sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
-#ifdef FTS_WHITEOUT
/* Check for whiteout. */
if (p->fts_flags & FTS_ISW) {
if (sbp != &sb) {
@@ -919,22 +1026,27 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
}
return (FTS_W);
}
-#endif
/*
- * If doing a logical walk, or application requested FTS_FOLLOW, do
- * a stat(2). If that fails, check for a non-existent symlink. If
- * fail, set the errno from the stat call.
+ * If doing a logical walk, or caller requested FTS_COMFOLLOW, do
+ * a full stat(2). If that fails, do an lstat(2) to check for a
+ * non-existent symlink. If that fails, set the errno from the
+ * stat(2) call.
+ *
+ * As a special case, if stat(2) succeeded but the target is not a
+ * directory and follow is negative (indicating FTS_COMFOLLOWDIR
+ * rather than FTS_COMFOLLOW), we also revert to lstat(2).
*/
if (ISSET(FTS_LOGICAL) || follow) {
- if (fstatat(dfd, path, sbp, 0)) {
+ if ((ret = fstatat(dfd, path, sbp, 0)) != 0 ||
+ (follow < 0 && !S_ISDIR(sbp->st_mode))) {
saved_errno = errno;
if (fstatat(dfd, path, sbp, AT_SYMLINK_NOFOLLOW)) {
p->fts_errno = saved_errno;
goto err;
}
errno = 0;
- if (S_ISLNK(sbp->st_mode))
+ if (ret != 0 && S_ISLNK(sbp->st_mode))
return (FTS_SLNONE);
}
} else if (fstatat(dfd, path, sbp, AT_SYMLINK_NOFOLLOW)) {
@@ -979,21 +1091,6 @@ err: memset(sbp, 0, sizeof(struct stat));
return (FTS_DEFAULT);
}
-/*
- * The comparison function takes pointers to pointers to FTSENT structures.
- * Qsort wants a comparison function that takes pointers to void.
- * (Both with appropriate levels of const-poisoning, of course!)
- * Use a trampoline function to deal with the difference.
- */
-static int
-fts_compar(const void *a, const void *b)
-{
- FTS *parent;
-
- parent = (*(const FTSENT * const *)a)->fts_fts;
- return (*parent->fts_compar)(a, b);
-}
-
static FTSENT *
fts_sort(FTS *sp, FTSENT *head, size_t nitems)
{
@@ -1016,7 +1113,18 @@ fts_sort(FTS *sp, FTSENT *head, size_t nitems)
}
for (ap = sp->fts_array, p = head; p; p = p->fts_link)
*ap++ = p;
- qsort(sp->fts_array, nitems, sizeof(FTSENT *), fts_compar);
+ if (ISSET(FTS_COMPAR_B)) {
+#ifdef __BLOCKS__
+ qsort_b(sp->fts_array, nitems, sizeof(FTSENT *),
+ (int (^)(const void *, const void *))sp->fts_compar_b);
+#else
+ qsort_b(sp->fts_array, nitems, sizeof(FTSENT *),
+ sp->fts_compar_b);
+#endif /* __BLOCKS__ */
+ } else {
+ qsort(sp->fts_array, nitems, sizeof(FTSENT *),
+ (int (*)(const void *, const void *))sp->fts_compar);
+ }
for (head = *(ap = sp->fts_array); --nitems; ++ap)
ap[0]->fts_link = ap[1];
ap[0]->fts_link = NULL;
diff --git a/lib/libc/gen/gen-private.h b/lib/libc/gen/gen-private.h
index 3792a61ff942..b6749b3435cd 100644
--- a/lib/libc/gen/gen-private.h
+++ b/lib/libc/gen/gen-private.h
@@ -43,8 +43,8 @@ struct pthread_mutex;
*/
struct _dirdesc {
int dd_fd; /* file descriptor associated with directory */
- long dd_loc; /* offset in current buffer */
- long dd_size; /* amount of data returned by getdirentries */
+ size_t dd_loc; /* offset in current buffer */
+ size_t dd_size; /* amount of data returned by getdirentries */
char *dd_buf; /* data buffer */
int dd_len; /* size of data buffer */
off_t dd_seek; /* magic cookie returned by getdirentries */
diff --git a/lib/libc/gen/glob.3 b/lib/libc/gen/glob.3
index 006e8decb3db..839d98ce02b5 100644
--- a/lib/libc/gen/glob.3
+++ b/lib/libc/gen/glob.3
@@ -27,11 +27,12 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd December 20, 2011
+.Dd June 23, 2025
.Dt GLOB 3
.Os
.Sh NAME
.Nm glob ,
+.Nm glob_b ,
.Nm globfree
.Nd generate pathnames matching a pattern
.Sh LIBRARY
@@ -39,7 +40,9 @@
.Sh SYNOPSIS
.In glob.h
.Ft int
-.Fn glob "const char * restrict pattern" "int flags" "int (*errfunc)(const char *, int)" "glob_t * restrict pglob"
+.Fn glob "const char * restrict pattern" "int flags" "int (*errfunc)(const char *epath, int errno)" "glob_t * restrict pglob"
+.Ft int
+.Fn glob_b "const char * restrict pattern" "int flags" "int (^errblk)(const char *epath, int errno)" "glob_t * restrict pglob"
.Ft void
.Fn globfree "glob_t *pglob"
.Sh DESCRIPTION
@@ -272,10 +275,24 @@ is
.Pf non- Dv NULL ,
.Fn glob
calls
-.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) ,
+.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) .
+This may be unintuitive: a pattern like
+.Ql */Makefile
+will try to
+.Xr stat 2
+.Ql foo/Makefile
+even if
+.Ql foo
+is not a directory, resulting in a
+call to
+.Fa errfunc .
+The error routine can suppress this action by testing for
+.Er ENOENT
+and
+.Er ENOTDIR ;
however, the
.Dv GLOB_ERR
-flag will cause an immediate
+flag will still cause an immediate
return when this happens.
.Pp
If
@@ -307,16 +324,27 @@ or
returns zero, the error is ignored.
.Pp
The
+.Fn glob_b
+function is like
+.Fn glob
+except that the error callback is a block pointer instead of a function
+pointer.
+.Pp
+The
.Fn globfree
function frees any space associated with
.Fa pglob
from a previous call(s) to
-.Fn glob .
+.Fn glob
+or
+.Fn glob_b .
.Sh RETURN VALUES
On successful completion,
.Fn glob
-returns zero.
-In addition the fields of
+and
+.Fn glob_b
+return zero.
+In addition, the fields of
.Fa pglob
contain the values described below:
.Bl -tag -width GLOB_NOCHECK
@@ -324,12 +352,16 @@ contain the values described below:
contains the total number of matched pathnames so far.
This includes other matches from previous invocations of
.Fn glob
+or
+.Fn glob_b
if
.Dv GLOB_APPEND
was specified.
.It Fa gl_matchc
contains the number of matched pathnames in the current invocation of
-.Fn glob .
+.Fn glob
+or
+.Fn glob_b .
.It Fa gl_flags
contains a copy of the
.Fa flags
@@ -352,6 +384,8 @@ are undefined.
.Pp
If
.Fn glob
+or
+.Fn glob_b
terminates due to an error, it sets errno and returns one of the
following non-zero constants, which are defined in the include
file
@@ -397,6 +431,14 @@ g.gl_pathv[0] = "ls";
g.gl_pathv[1] = "-l";
execvp("ls", g.gl_pathv);
.Ed
+.Sh CAVEATS
+The
+.Fn glob
+and
+.Fn glob_b
+functions
+will not match filenames that begin with a period
+unless this is specifically requested (e.g., by ".*").
.Sh SEE ALSO
.Xr sh 1 ,
.Xr fnmatch 3 ,
@@ -435,6 +477,10 @@ and
.Fn globfree
functions first appeared in
.Bx 4.4 .
+The
+.Fn glob_b
+function first appeared in
+.Fx 15.0 .
.Sh BUGS
Patterns longer than
.Dv MAXPATHLEN
@@ -442,7 +488,9 @@ may cause unchecked errors.
.Pp
The
.Fn glob
-argument
+and
+.Fn glob_b
+functions
may fail and set errno for any of the errors specified for the
library routines
.Xr stat 2 ,
diff --git a/lib/libc/gen/glob.c b/lib/libc/gen/glob.c
index 7a988196549a..f7da5f0cda09 100644
--- a/lib/libc/gen/glob.c
+++ b/lib/libc/gen/glob.c
@@ -88,8 +88,11 @@
#include <unistd.h>
#include <wchar.h>
+#include "block_abi.h"
#include "collate.h"
+typedef DECLARE_BLOCK(int, glob_b_block, const char*, int);
+
/*
* glob(3) expansion limits. Stop the expansion if any of these limits
* is reached. This caps the runtime in the face of DoS attacks. See
@@ -104,8 +107,8 @@
struct glob_limit {
size_t l_brace_cnt;
size_t l_path_lim;
- size_t l_readdir_cnt;
- size_t l_stat_cnt;
+ size_t l_readdir_cnt;
+ size_t l_stat_cnt;
size_t l_string_cnt;
};
@@ -179,9 +182,8 @@ static int err_aborted(glob_t *, int, char *);
static void qprintf(const char *, Char *);
#endif
-int
-glob(const char * __restrict pattern, int flags,
- int (*errfunc)(const char *, int), glob_t * __restrict pglob)
+static int
+__glob(const char *pattern, glob_t *pglob)
{
struct glob_limit limit = { 0, 0, 0, 0, 0 };
const char *patnext;
@@ -192,25 +194,23 @@ glob(const char * __restrict pattern, int flags,
int too_long;
patnext = pattern;
- if (!(flags & GLOB_APPEND)) {
+ if (!(pglob->gl_flags & GLOB_APPEND)) {
pglob->gl_pathc = 0;
pglob->gl_pathv = NULL;
- if (!(flags & GLOB_DOOFFS))
+ if (!(pglob->gl_flags & GLOB_DOOFFS))
pglob->gl_offs = 0;
}
- if (flags & GLOB_LIMIT) {
+ if (pglob->gl_flags & GLOB_LIMIT) {
limit.l_path_lim = pglob->gl_matchc;
if (limit.l_path_lim == 0)
limit.l_path_lim = GLOB_LIMIT_PATH;
}
- pglob->gl_flags = flags & ~GLOB_MAGCHAR;
- pglob->gl_errfunc = errfunc;
pglob->gl_matchc = 0;
bufnext = patbuf;
bufend = bufnext + MAXPATHLEN - 1;
too_long = 1;
- if (flags & GLOB_NOESCAPE) {
+ if (pglob->gl_flags & GLOB_NOESCAPE) {
memset(&mbs, 0, sizeof(mbs));
while (bufnext <= bufend) {
clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
@@ -250,15 +250,45 @@ glob(const char * __restrict pattern, int flags,
return (err_nomatch(pglob, &limit, pattern));
*bufnext = EOS;
- if (flags & GLOB_BRACE)
+ if (pglob->gl_flags & GLOB_BRACE)
return (globexp0(patbuf, pglob, &limit, pattern));
else
return (glob0(patbuf, pglob, &limit, pattern));
}
+int
+glob(const char * __restrict pattern, int flags,
+ int (*errfunc)(const char *, int), glob_t * __restrict pglob)
+{
+ int rv;
+
+ pglob->gl_flags = flags & ~(GLOB_MAGCHAR | _GLOB_ERR_BLOCK);
+ pglob->gl_errfunc = errfunc;
+ rv = __glob(pattern, pglob);
+ pglob->gl_errfunc = NULL;
+
+ return (rv);
+}
+
+int
+glob_b(const char * __restrict pattern, int flags,
+ glob_b_block block, glob_t * __restrict pglob)
+{
+ int rv;
+
+ pglob->gl_flags = flags & ~GLOB_MAGCHAR;
+ pglob->gl_flags |= _GLOB_ERR_BLOCK;
+ pglob->gl_errblk = block;
+ rv = __glob(pattern, pglob);
+ pglob->gl_errblk = NULL;
+
+ return (rv);
+}
+
static int
globexp0(const Char *pattern, glob_t *pglob, struct glob_limit *limit,
- const char *origpat) {
+ const char *origpat)
+{
int rv;
size_t oldpathc;
@@ -724,7 +754,7 @@ glob3(Char *pathbuf, Char *pathend, Char *pathend_last,
return (GLOB_NOSPACE);
}
*pathend = EOS;
- if (pglob->gl_errfunc != NULL &&
+ if ((pglob->gl_errfunc != NULL || pglob->gl_errblk != NULL) &&
g_Ctoc(pathbuf, buf, sizeof(buf))) {
errno = E2BIG;
return (GLOB_NOSPACE);
@@ -1085,10 +1115,21 @@ err_nomatch(glob_t *pglob, struct glob_limit *limit, const char *origpat) {
}
static int
-err_aborted(glob_t *pglob, int err, char *buf) {
- if ((pglob->gl_errfunc != NULL && pglob->gl_errfunc(buf, err)) ||
- (pglob->gl_flags & GLOB_ERR))
+err_aborted(glob_t *pglob, int err, char *buf)
+{
+ int rv = 0;
+
+ if ((pglob->gl_flags & _GLOB_ERR_BLOCK) != 0) {
+ if (pglob->gl_errblk != NULL)
+ rv = CALL_BLOCK((glob_b_block)pglob->gl_errblk, buf,
+ errno);
+ } else if (pglob->gl_errfunc != NULL) {
+ rv = pglob->gl_errfunc(buf, errno);
+ }
+ /* GLOB_ERR is allowed to override the error callback function. */
+ if (rv != 0 || pglob->gl_flags & GLOB_ERR) {
return (GLOB_ABORTED);
+ }
return (0);
}
diff --git a/lib/libc/gen/inotify.c b/lib/libc/gen/inotify.c
new file mode 100644
index 000000000000..7ce53aaccd58
--- /dev/null
+++ b/lib/libc/gen/inotify.c
@@ -0,0 +1,48 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Klara, Inc.
+ */
+
+#include "namespace.h"
+#include <sys/fcntl.h>
+#include <sys/inotify.h>
+#include <sys/specialfd.h>
+#include "un-namespace.h"
+#include "libc_private.h"
+
+/*
+ * Provide compatibility with libinotify, which uses different values for these
+ * flags.
+ */
+#define IN_NONBLOCK_OLD 0x80000
+#define IN_CLOEXEC_OLD 0x00800
+
+int
+inotify_add_watch(int fd, const char *pathname, uint32_t mask)
+{
+ return (inotify_add_watch_at(fd, AT_FDCWD, pathname, mask));
+}
+
+int
+inotify_init1(int flags)
+{
+ struct specialfd_inotify args;
+
+ if ((flags & IN_NONBLOCK_OLD) != 0) {
+ flags &= ~IN_NONBLOCK_OLD;
+ flags |= IN_NONBLOCK;
+ }
+ if ((flags & IN_CLOEXEC_OLD) != 0) {
+ flags &= ~IN_CLOEXEC_OLD;
+ flags |= IN_CLOEXEC;
+ }
+ args.flags = flags;
+ return (__sys___specialfd(SPECIALFD_INOTIFY, &args, sizeof(args)));
+}
+
+int
+inotify_init(void)
+{
+ return (inotify_init1(0));
+}
diff --git a/lib/libc/gen/libc_interposing_table.c b/lib/libc/gen/libc_interposing_table.c
index d15d32711910..025a67ac3eac 100644
--- a/lib/libc/gen/libc_interposing_table.c
+++ b/lib/libc/gen/libc_interposing_table.c
@@ -42,7 +42,7 @@ interpos_func_t __libc_interposing[INTERPOS_MAX] = {
SLOT(spinlock, __libc_spinlock_stub),
SLOT(spinunlock, __libc_spinunlock_stub),
SLOT(map_stacks_exec, __libc_map_stacks_exec),
- SLOT(distribute_static_tls, __libc_distribute_static_tls),
+ SLOT(uexterr_gettext, __libc_uexterr_gettext),
};
#undef SLOT
diff --git a/lib/libc/gen/opendir.c b/lib/libc/gen/opendir.c
index 956c92c321e8..08d9eb10eaa2 100644
--- a/lib/libc/gen/opendir.c
+++ b/lib/libc/gen/opendir.c
@@ -30,14 +30,7 @@
*/
#include "namespace.h"
-#include <sys/param.h>
-
#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
#include "un-namespace.h"
#include "gen-private.h"
diff --git a/lib/libc/gen/opendir2.c b/lib/libc/gen/opendir2.c
index 30a9030693e4..c5c2e662efd8 100644
--- a/lib/libc/gen/opendir2.c
+++ b/lib/libc/gen/opendir2.c
@@ -30,11 +30,12 @@
*/
#include "namespace.h"
-#include <sys/param.h>
+#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -52,8 +53,7 @@ __opendir2(const char *name, int flags)
if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
return (NULL);
- if ((fd = _open(name,
- O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
+ if ((fd = _open(name, O_DIRECTORY | O_RDONLY | O_CLOEXEC)) == -1)
return (NULL);
dir = __opendir_common(fd, flags, false);
@@ -243,20 +243,18 @@ _filldir(DIR *dirp, bool use_current_pos)
return (true);
}
+/*
+ * Return true if the file descriptor is associated with a file from a
+ * union file system or from a file system mounted with the union flag.
+ */
static bool
is_unionstack(int fd)
{
- int unionstack;
-
- unionstack = _fcntl(fd, F_ISUNIONSTACK, 0);
- if (unionstack != -1)
- return (unionstack);
-
/*
- * Should not happen unless running on a kernel without the op,
- * but no use rendering the system useless in such a case.
+ * This call shouldn't fail, but if it does, just assume that the
+ * answer is no.
*/
- return (0);
+ return (_fcntl(fd, F_ISUNIONSTACK, 0) > 0);
}
/*
@@ -266,6 +264,7 @@ DIR *
__opendir_common(int fd, int flags, bool use_current_pos)
{
DIR *dirp;
+ ssize_t ret;
int incr;
int saved_errno;
bool unionstack;
@@ -315,13 +314,11 @@ __opendir_common(int fd, int flags, bool use_current_pos)
* to prime dd_seek. This also checks if the
* fd passed to fdopendir() is a directory.
*/
- dirp->dd_size = _getdirentries(dirp->dd_fd,
+ ret = _getdirentries(dirp->dd_fd,
dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
- if (dirp->dd_size < 0) {
- if (errno == EINVAL)
- errno = ENOTDIR;
+ if (ret < 0)
goto fail;
- }
+ dirp->dd_size = (size_t)ret;
dirp->dd_flags |= __DTF_SKIPREAD;
} else {
dirp->dd_size = 0;
diff --git a/lib/libc/gen/posix_spawn.c b/lib/libc/gen/posix_spawn.c
index ee2ce05ff317..a5b732696b8c 100644
--- a/lib/libc/gen/posix_spawn.c
+++ b/lib/libc/gen/posix_spawn.c
@@ -261,7 +261,7 @@ _posix_spawn_thr(void *data)
}
envp = psa->envp != NULL ? psa->envp : environ;
if (psa->use_env_path)
- execvpe(psa->path, psa->argv, envp);
+ __libc_execvpe(psa->path, psa->argv, envp);
else
_execve(psa->path, psa->argv, envp);
psa->error = errno;
diff --git a/lib/libc/gen/psignal.3 b/lib/libc/gen/psignal.3
index cd3cef0b44a9..098b7b02a9b9 100644
--- a/lib/libc/gen/psignal.3
+++ b/lib/libc/gen/psignal.3
@@ -25,14 +25,17 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd May 30, 2016
+.Dd May 10, 2025
.Dt PSIGNAL 3
.Os
.Sh NAME
.Nm psignal ,
+.Nm psiginfo ,
.Nm strsignal ,
.Nm sys_siglist ,
-.Nm sys_signame
+.Nm sys_signame ,
+.Nm sig2str ,
+.Nm str2sig
.Nd system signal messages
.Sh LIBRARY
.Lb libc
@@ -40,11 +43,17 @@
.In signal.h
.Ft void
.Fn psignal "int sig" "const char *s"
+.Ft void
+.Fn psiginfo "const siginfo_t *si" "const char *s"
.Vt extern const char * const sys_siglist[] ;
.Vt extern const char * const sys_signame[] ;
.In string.h
.Ft "char *"
.Fn strsignal "int sig"
+.Ft int
+.Fn sig2str "int signum" "char *str"
+.Ft int
+.Fn str2sig "char *str" "int *pnum"
.Sh DESCRIPTION
The
.Fn psignal
@@ -79,6 +88,16 @@ the string
.Dq "Unknown signal"
is produced.
.Pp
+The
+.Fn psiginfo
+function is similar to
+.Fn psignal ,
+except that the signal number information is taken from the
+.Fa si
+argument which is a
+.Vt siginfo_t
+structure.
+.Pp
The message strings can be accessed directly
through the external array
.Va sys_siglist ,
@@ -95,12 +114,65 @@ contains a count of the strings in
.Va sys_siglist
and
.Va sys_signame .
+.Pp
+The
+.Fn sig2str
+function translates the signal number
+.Fa signum
+to the signal name, without the
+.Dq SIG
+prefix, and stores it at the location specified by
+.Fa str ,
+which should be large enough to hold the name and the terminating
+.Dv NUL
+byte.
+The symbol
+.Dv SIG2STR_MAX
+gives the maximum size in bytes required.
+.Pp
+The
+.Fn str2sig
+function translates the signal name
+.Fa str
+to a signal number and stores it in the location referenced by
+.Fa pnum .
+The name in
+.Fa str
+can be either the name of the signal, with or without the
+.Dq SIG
+prefix, or a decimal number.
.Sh SEE ALSO
.Xr sigaction 2 ,
.Xr perror 3 ,
.Xr strerror 3
+.Sh STANDARDS
+The
+.Fn psignal
+and
+.Fn psiginfo
+functions are defined by
+.St -p1003.1-2008
+, while the
+.Fn sig2str
+and
+.Fn str2sig
+functions are defined by
+.St -p1003.1-2024 .
.Sh HISTORY
The
.Fn psignal
function appeared in
.Bx 4.2 .
+The
+.Fn psiginfo
+function appeared in
+.Fx 14.3 ,
+.Nx 6.0 ,
+and
+.Dx 4.1 .
+The
+.Fn sig2str
+and
+.Fn str2sig
+functions appeared in
+.Fx 15.0 .
diff --git a/lib/libc/gen/psignal.c b/lib/libc/gen/psignal.c
index ab044f683d86..291a6a9337a0 100644
--- a/lib/libc/gen/psignal.c
+++ b/lib/libc/gen/psignal.c
@@ -55,3 +55,9 @@ psignal(int sig, const char *s)
(void)_write(STDERR_FILENO, c, strlen(c));
(void)_write(STDERR_FILENO, "\n", 1);
}
+
+void
+psiginfo(const siginfo_t *si, const char *s)
+{
+ psignal(si->si_signo, s);
+}
diff --git a/lib/libc/gen/readdir.c b/lib/libc/gen/readdir.c
index 2a2fa999b7ce..94d2b2e8d877 100644
--- a/lib/libc/gen/readdir.c
+++ b/lib/libc/gen/readdir.c
@@ -48,8 +48,9 @@ struct dirent *
_readdir_unlocked(DIR *dirp, int flags)
{
struct dirent *dp;
- long initial_seek;
- long initial_loc = 0;
+ off_t initial_seek;
+ size_t initial_loc = 0;
+ ssize_t ret;
for (;;) {
if (dirp->dd_loc >= dirp->dd_size) {
@@ -61,11 +62,13 @@ _readdir_unlocked(DIR *dirp, int flags)
}
if (dirp->dd_loc == 0 &&
!(dirp->dd_flags & (__DTF_READALL | __DTF_SKIPREAD))) {
+ dirp->dd_size = 0;
initial_seek = dirp->dd_seek;
- dirp->dd_size = _getdirentries(dirp->dd_fd,
+ ret = _getdirentries(dirp->dd_fd,
dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
- if (dirp->dd_size <= 0)
+ if (ret <= 0)
return (NULL);
+ dirp->dd_size = (size_t)ret;
_fixtelldir(dirp, initial_seek, initial_loc);
}
dirp->dd_flags &= ~__DTF_SKIPREAD;
@@ -131,3 +134,5 @@ __readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
}
__strong_reference(__readdir_r, readdir_r);
+__warn_references(readdir_r,
+ "warning: this program uses readdir_r(), which is unsafe.");
diff --git a/lib/libc/gen/rtld_get_var.3 b/lib/libc/gen/rtld_get_var.3
index e211d78345e3..092114e86d78 100644
--- a/lib/libc/gen/rtld_get_var.3
+++ b/lib/libc/gen/rtld_get_var.3
@@ -103,4 +103,4 @@ is unknown.
The
.Nm
function first appeared in
-.Fx 15.0 .
+.Fx 14.3 .
diff --git a/lib/libc/gen/scandir.3 b/lib/libc/gen/scandir.3
index 6656842c251f..3da4500cefb9 100644
--- a/lib/libc/gen/scandir.3
+++ b/lib/libc/gen/scandir.3
@@ -25,13 +25,16 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd August 31, 2023
+.Dd June 25, 2025
.Dt SCANDIR 3
.Os
.Sh NAME
.Nm scandir ,
+.Nm fdscandir ,
.Nm scandirat ,
.Nm scandir_b ,
+.Nm fdscandir_b ,
+.Nm fdscandirat_b ,
.Nm alphasort ,
.Nm versionsort
.Nd scan a directory
@@ -47,6 +50,13 @@
.Fa "int \*(lp*compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
+.Fo fdscandir
+.Fa "int dirfd"
+.Fa "struct dirent ***namelist"
+.Fa "int \*(lp*select\*(rp\*(lpconst struct dirent *\*(rp"
+.Fa "int \*(lp*compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
+.Fc
+.Ft int
.Fo scandirat
.Fa "int dirfd"
.Fa "const char *dirname"
@@ -62,6 +72,21 @@
.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
.Fc
.Ft int
+.Fo fdscandir_b
+.Fa "int dirfd"
+.Fa "struct dirent ***namelist"
+.Fa "int \*(lp^select\*(rp\*(lpconst struct dirent *\*(rp"
+.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
+.Fc
+.Ft int
+.Fo scandirat_b
+.Fa "int dirfd"
+.Fa "const char *dirname"
+.Fa "struct dirent ***namelist"
+.Fa "int \*(lp^select\*(rp\*(lpconst struct dirent *\*(rp"
+.Fa "int \*(lp^compar\*(rp\*(lpconst struct dirent **, const struct dirent **\*(rp"
+.Fc
+.Ft int
.Fn alphasort "const struct dirent **d1" "const struct dirent **d2"
.Ft int
.Fn versionsort "const struct dirent **d1" "const struct dirent **d2"
@@ -77,7 +102,8 @@ entries using
It returns the number of entries in the array.
A pointer to the array of directory entries is stored in the location
referenced by
-.Fa namelist .
+.Fa namelist
+(even if no entries were selected).
.Pp
The
.Fa select
@@ -118,6 +144,13 @@ The memory allocated for the array can be deallocated with
by freeing each pointer in the array and then the array itself.
.Pp
The
+.Fn fdscandir
+function is similar to
+.Fn scandir ,
+but takes a file descriptor referencing a directory instead of a path.
+The file descriptor is left open on return, regardless of outcome.
+.Pp
+The
.Fn scandirat
function is similar to
.Fn scandir ,
@@ -151,17 +184,67 @@ See
for additional details.
.Pp
The
-.Fn scandir_b
-function behaves in the same way as
+.Fn scandir_b ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions behave in the same way as
.Fn scandir ,
-but takes blocks as arguments instead of function pointers and calls
+.Fn fdscandir ,
+and
+.Fn scandirat ,
+respectively,
+but take blocks as arguments instead of function pointers and call
.Fn qsort_b
rather than
.Fn qsort .
.Sh DIAGNOSTICS
-Returns \-1 if the directory cannot be opened for reading or if
+The
+.Fn scandir ,
+.Fn fdscandir ,
+.Fn scandirat ,
+.Fn scandir_b ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions return the number of directory entries found on succes.
+If the directory cannot be opened for reading, an error occurs
+while reading the directory, or
.Xr malloc 3
-cannot allocate enough memory to hold all the data structures.
+cannot allocate enough memory to hold all the directory entries,
+they return \-1 and set
+.Va errno
+to an appropriate value.
+.Sh ERRORS
+The
+.Fn scandir ,
+.Fn scandirat ,
+.Fn scandir_b ,
+and
+.Fn scandirat_b
+functions may fail and set
+.Va errno
+for any of the errors specified for the
+.Xr opendir 3 ,
+.Xr malloc 3 ,
+.Xr readdir 3 ,
+and
+.Xr closedir 3
+functions.
+.Pp
+The
+.Fn fdscandir
+and
+.Fn fdscandir_b
+functions may fail and set
+.Va errno
+for any of the errors specified for the
+.Xr fdopendir 3 ,
+.Xr malloc 3 ,
+.Xr readdir 3 ,
+and
+.Xr closedir 3
+functions.
.Sh SEE ALSO
.Xr openat 2 ,
.Xr directory 3 ,
@@ -172,8 +255,25 @@ cannot allocate enough memory to hold all the data structures.
.Xr dir 5
.Sh STANDARDS
The
+.Fn alphasort
+and
+.Fn scandir
+functions are expected to conform to
+.St -p1003.1-2008 .
+The
+.Fn scandirat
+and
.Fn versionsort
-function is a GNU extension and conforms to no standard.
+functions are GNU extensions and conform to no standard.
+The
+.Fn fdscandir ,
+.Fn scandir_b ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions are
+.Fx
+extensions.
.Sh HISTORY
The
.Fn scandir
@@ -182,8 +282,19 @@ and
functions appeared in
.Bx 4.2 .
The
+.Fn scandir_b
+function was added in
+.Fx 11.0 .
+The
.Fn scandirat
and
.Fn versionsort
functions were added in
.Fx 13.2 .
+The
+.Fn fdscandir ,
+.Fn fdscandir_b ,
+and
+.Fn scandirat_b
+functions were added in
+.Fx 15.0 .
diff --git a/lib/libc/gen/scandir.c b/lib/libc/gen/scandir.c
index f59f57047278..fb589f4b36b6 100644
--- a/lib/libc/gen/scandir.c
+++ b/lib/libc/gen/scandir.c
@@ -38,6 +38,7 @@
#include "namespace.h"
#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
@@ -64,71 +65,76 @@ static int scandir_thunk_cmp(const void *p1, const void *p2, void *thunk);
static int
#ifdef I_AM_SCANDIR_B
-scandir_b_dirp(DIR *dirp, struct dirent ***namelist, select_block select,
+scandir_dirp_b(DIR *dirp, struct dirent ***namelist, select_block select,
dcomp_block dcomp)
#else
scandir_dirp(DIR *dirp, struct dirent ***namelist,
- int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
- const struct dirent **))
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
#endif
{
- struct dirent *d, *p, **names = NULL;
- size_t arraysz, numitems;
+ struct dirent *d, *p = NULL, **names = NULL, **names2;
+ size_t arraysz = 32, numitems = 0;
+ int serrno;
- numitems = 0;
- arraysz = 32; /* initial estimate of the array size */
- names = (struct dirent **)malloc(arraysz * sizeof(struct dirent *));
+ names = malloc(arraysz * sizeof(*names));
if (names == NULL)
- goto fail;
+ return (-1);
- while ((d = readdir(dirp)) != NULL) {
+ while (errno = 0, (d = readdir(dirp)) != NULL) {
if (select != NULL && !SELECT(d))
continue; /* just selected names */
/*
* Make a minimum size copy of the data
*/
- p = (struct dirent *)malloc(_GENERIC_DIRSIZ(d));
+ p = malloc(_GENERIC_DIRSIZ(d));
if (p == NULL)
goto fail;
p->d_fileno = d->d_fileno;
p->d_type = d->d_type;
p->d_reclen = d->d_reclen;
p->d_namlen = d->d_namlen;
- bcopy(d->d_name, p->d_name, p->d_namlen + 1);
+ memcpy(p->d_name, d->d_name, p->d_namlen + 1);
/*
* Check to make sure the array has space left and
* realloc the maximum size.
*/
if (numitems >= arraysz) {
- struct dirent **names2;
-
- names2 = reallocarray(names, arraysz,
- 2 * sizeof(struct dirent *));
- if (names2 == NULL) {
- free(p);
+ arraysz = arraysz * 2;
+ names2 = reallocarray(names, arraysz, sizeof(*names));
+ if (names2 == NULL)
goto fail;
- }
names = names2;
- arraysz *= 2;
}
names[numitems++] = p;
}
- closedir(dirp);
- if (numitems && dcomp != NULL)
+ /*
+ * Since we can't simultaneously return both -1 and a count, we
+ * must either suppress the error or discard the partial result.
+ * The latter seems the lesser of two evils.
+ */
+ if (errno != 0)
+ goto fail;
+ if (numitems > 0 && dcomp != NULL) {
#ifdef I_AM_SCANDIR_B
- qsort_b(names, numitems, sizeof(struct dirent *), (void*)dcomp);
+ qsort_b(names, numitems, sizeof(struct dirent *),
+ (void *)dcomp);
#else
qsort_r(names, numitems, sizeof(struct dirent *),
scandir_thunk_cmp, &dcomp);
#endif
+ }
*namelist = names;
return (numitems);
fail:
+ serrno = errno;
+ if (numitems == 0 || names[numitems - 1] != p)
+ free(p);
while (numitems > 0)
free(names[--numitems]);
free(names);
- closedir(dirp);
+ errno = serrno;
return (-1);
}
@@ -138,44 +144,87 @@ scandir_b(const char *dirname, struct dirent ***namelist, select_block select,
dcomp_block dcomp)
#else
scandir(const char *dirname, struct dirent ***namelist,
- int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
- const struct dirent **))
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
#endif
{
DIR *dirp;
+ int ret, serrno;
dirp = opendir(dirname);
if (dirp == NULL)
return (-1);
- return (
+ ret =
#ifdef I_AM_SCANDIR_B
- scandir_b_dirp
+ scandir_dirp_b
#else
scandir_dirp
#endif
- (dirp, namelist, select, dcomp));
+ (dirp, namelist, select, dcomp);
+ serrno = errno;
+ closedir(dirp);
+ errno = serrno;
+ return (ret);
}
-#ifndef I_AM_SCANDIR_B
int
-scandirat(int dirfd, const char *dirname, struct dirent ***namelist,
- int (*select)(const struct dirent *), int (*dcomp)(const struct dirent **,
- const struct dirent **))
+#ifdef I_AM_SCANDIR_B
+fdscandir_b(int dirfd, struct dirent ***namelist, select_block select,
+ dcomp_block dcomp)
+#else
+fdscandir(int dirfd, struct dirent ***namelist,
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
+#endif
{
DIR *dirp;
- int fd;
+ int ret, serrno;
+
+ dirp = fdopendir(dirfd);
+ if (dirp == NULL)
+ return (-1);
+ ret =
+#ifdef I_AM_SCANDIR_B
+ scandir_dirp_b
+#else
+ scandir_dirp
+#endif
+ (dirp, namelist, select, dcomp);
+ serrno = errno;
+ fdclosedir(dirp);
+ errno = serrno;
+ return (ret);
+}
+
+int
+#ifdef I_AM_SCANDIR_B
+scandirat_b(int dirfd, const char *dirname, struct dirent ***namelist,
+ select_block select, dcomp_block dcomp)
+#else
+scandirat(int dirfd, const char *dirname, struct dirent ***namelist,
+ int (*select)(const struct dirent *),
+ int (*dcomp)(const struct dirent **, const struct dirent **))
+#endif
+{
+ int fd, ret, serrno;
fd = _openat(dirfd, dirname, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (fd == -1)
return (-1);
- dirp = fdopendir(fd);
- if (dirp == NULL) {
- _close(fd);
- return (-1);
- }
- return (scandir_dirp(dirp, namelist, select, dcomp));
+ ret =
+#ifdef I_AM_SCANDIR_B
+ fdscandir_b
+#else
+ fdscandir
+#endif
+ (fd, namelist, select, dcomp);
+ serrno = errno;
+ _close(fd);
+ errno = serrno;
+ return (ret);
}
+#ifndef I_AM_SCANDIR_B
/*
* Alphabetic order comparison routine for those who want it.
* POSIX 2008 requires that alphasort() uses strcoll().
diff --git a/lib/libc/gen/sig2str.c b/lib/libc/gen/sig2str.c
new file mode 100644
index 000000000000..869a09ab9db4
--- /dev/null
+++ b/lib/libc/gen/sig2str.c
@@ -0,0 +1,113 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Ricardo Branco <rbranco@suse.de>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Translate between signal names and numbers
+ */
+#include "namespace.h"
+#include <ctype.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ssp/ssp.h>
+#include "un-namespace.h"
+
+static const char rtmin_str[] = "RTMIN";
+static const char rtmax_str[] = "RTMAX";
+
+int
+__ssp_real(sig2str)(int signum, char *str)
+{
+ if (signum <= 0 || signum > SIGRTMAX)
+ return (-1);
+
+ if (signum < sys_nsig)
+ (void)strlcpy(str, sys_signame[signum], SIG2STR_MAX);
+ else if (signum < SIGRTMIN)
+ (void)snprintf(str, SIG2STR_MAX, "%d", signum);
+ else if (signum == SIGRTMIN)
+ (void)strlcpy(str, rtmin_str, SIG2STR_MAX);
+ else if (signum == SIGRTMAX)
+ (void)strlcpy(str, rtmax_str, SIG2STR_MAX);
+ else if (signum <= (SIGRTMIN + SIGRTMAX) / 2)
+ (void)snprintf(str, SIG2STR_MAX, "%s+%d",
+ rtmin_str, signum - SIGRTMIN);
+ else
+ (void)snprintf(str, SIG2STR_MAX, "%s-%d",
+ rtmax_str, SIGRTMAX - signum);
+
+ return (0);
+}
+
+int
+str2sig(const char * restrict str, int * restrict pnum)
+{
+ const char *errstr;
+ long long n;
+ int sig;
+ int rtend = sizeof(rtmin_str) - 1;
+
+ if (strncasecmp(str, "SIG", 3) == 0)
+ str += 3;
+
+ if (strncasecmp(str, rtmin_str, sizeof(rtmin_str) - 1) == 0 ||
+ strncasecmp(str, rtmax_str, sizeof(rtmax_str) - 1) == 0) {
+ sig = (toupper(str[4]) == 'X') ? SIGRTMAX : SIGRTMIN;
+ n = 0;
+ if (str[rtend] == '+' || str[rtend] == '-') {
+ n = strtonum(str + rtend, INT_MIN, INT_MAX, &errstr);
+ if (n == 0 || errstr != NULL)
+ return (-1);
+ } else if (str[rtend] != '\0') {
+ return (-1);
+ }
+ sig += (int)n;
+ if (sig < SIGRTMIN || sig > SIGRTMAX)
+ return (-1);
+ *pnum = sig;
+ return (0);
+ }
+
+ if (isdigit((unsigned char)str[0])) {
+ n = strtonum(str, 1, SIGRTMAX, &errstr);
+ if (errstr == NULL) {
+ *pnum = (int)n;
+ return (0);
+ }
+ }
+
+ for (sig = 1; sig < sys_nsig; sig++) {
+ if (strcasecmp(sys_signame[sig], str) == 0) {
+ *pnum = sig;
+ return (0);
+ }
+ }
+
+ return (-1);
+}
diff --git a/lib/libc/gen/sysconf.c b/lib/libc/gen/sysconf.c
index be42dd238b56..66562d0e29f0 100644
--- a/lib/libc/gen/sysconf.c
+++ b/lib/libc/gen/sysconf.c
@@ -34,6 +34,7 @@
#include "namespace.h"
#include <sys/param.h>
+#include <sys/exterrvar.h>
#include <sys/time.h>
#include <sys/sysctl.h>
#include <sys/resource.h>
@@ -286,6 +287,8 @@ do_NAME_MAX:
mib[0] = CTL_P1003_1B;
mib[1] = CTL_P1003_1B_MQ_OPEN_MAX;
goto yesno;
+ case _SC_NSIG:
+ return (_SIG_MAXSIG);
case _SC_PAGESIZE:
return (getpagesize());
case _SC_RTSIG_MAX:
@@ -567,7 +570,7 @@ yesno:
case _SC_IPV6:
#if _POSIX_IPV6 == 0
sverrno = errno;
- value = _socket(PF_INET6, SOCK_DGRAM, 0);
+ value = _socket(PF_INET6, SOCK_CLOEXEC | SOCK_DGRAM, 0);
errno = sverrno;
if (value >= 0) {
_close(value);
@@ -602,6 +605,8 @@ yesno:
return (-1);
return ((long)value);
#endif
+ case _SC_UEXTERR_MAXLEN:
+ return (UEXTERROR_MAXLEN);
default:
errno = EINVAL;
diff --git a/lib/libc/gen/sysctl.3 b/lib/libc/gen/sysctl.3
index fb68ddd60edc..ef897c653728 100644
--- a/lib/libc/gen/sysctl.3
+++ b/lib/libc/gen/sysctl.3
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd March 16, 2023
+.Dd July 31, 2025
.Dt SYSCTL 3
.Os
.Sh NAME
@@ -502,7 +502,6 @@ specifies the current process.
.It Dv KERN_PROC_ENV Ta "Set of strings"
.It Dv KERN_PROC_AUXV Ta "Elf_Auxinfo []"
.It Dv KERN_PROC_RLIMIT Ta "Integer"
-.It Dv KERN_PROC_RLIMIT_USAGE Ta "rlim_t []"
.It Dv KERN_PROC_PS_STRINGS Ta "Integer"
.It Dv KERN_PROC_UMASK Ta "Integer/short"
.It Dv KERN_PROC_OSREL Ta "Integer"
@@ -511,6 +510,7 @@ specifies the current process.
.It Dv KERN_PROC_NFDS Ta "Integer"
.It Dv KERN_PROC_SIGFASTBLK Ta "Integer"
.It Dv KERN_PROC_VM_LAYOUT Ta "struct kinfo_vm_layout"
+.It Dv KERN_PROC_RLIMIT_USAGE Ta "rlim_t []"
.It Dv KERN_PROC_KQUEUE Ta "struct kinfo_knote []"
.El
.Pp
@@ -527,8 +527,12 @@ The path of the process' text file is returned.
The in-kernel call stacks for the threads of the specified process.
.It Dv KERN_PROC_VMMAP
The description of the map entries for the process.
+Also refer to
+.Xr kinfo_getvmmap 3 .
.It Dv KERN_PROC_FILEDESC
The file descriptors for files opened in the specified process.
+Also refer to
+.Xr kinfo_getfile 3 .
.It Dv KERN_PROC_GROUPS
Groups associated with the process.
.It Dv KERN_PROC_ENV
@@ -549,13 +553,6 @@ Additinal OID name element must be supplied, specifiing the resource name
as in
.Xr getrlimit 2 .
The call returns the given resource limit for the process.
-.It Dv KERN_PROC_RLIMIT_USAGE
-Like
-.Dv KERN_PROC_RLIMIT ,
-but instead of the limit, returns the accounted resource usage.
-For resources which do not have a meaningful current value,
-.Li \-1
-is returned.
.It Dv KERN_PROC_PS_STRINGS
Returns the location of the
.Vt ps_strings
@@ -585,6 +582,13 @@ Returns the address of the
location, if active.
.It Dv KERN_PROC_VM_LAYOUT
Fills a structure describing process virtual address space layout.
+.It Dv KERN_PROC_RLIMIT_USAGE
+Like
+.Dv KERN_PROC_RLIMIT ,
+but instead of the limit, returns the accounted resource usage.
+For resources which do not have a meaningful current value,
+.Li \-1
+is returned.
.It Dv KERN_PROC_KQUEUE
Fills an array of structures describing events registered with
the specified kqueue.
@@ -949,6 +953,7 @@ A process without appropriate privilege attempts to set a value.
.El
.Sh SEE ALSO
.Xr confstr 3 ,
+.Xr kinfo_getproc 3 ,
.Xr kvm 3 ,
.Xr sysconf 3 ,
.Xr sysctl 8
diff --git a/lib/libc/gen/telldir.c b/lib/libc/gen/telldir.c
index b751fafd975f..1731cc4d7a2c 100644
--- a/lib/libc/gen/telldir.c
+++ b/lib/libc/gen/telldir.c
@@ -118,7 +118,7 @@ _seekdir(DIR *dirp, long loc)
struct dirent *dp;
union ddloc_packed ddloc;
off_t loc_seek;
- long loc_loc;
+ size_t loc_loc;
ddloc.l = loc;
@@ -171,7 +171,7 @@ _seekdir(DIR *dirp, long loc)
* fetching a new block to fix any such telldir locations.
*/
void
-_fixtelldir(DIR *dirp, long oldseek, long oldloc)
+_fixtelldir(DIR *dirp, off_t oldseek, size_t oldloc)
{
struct ddloc_mem *lp;
diff --git a/lib/libc/gen/telldir.h b/lib/libc/gen/telldir.h
index 6d113491e819..02fd52af9060 100644
--- a/lib/libc/gen/telldir.h
+++ b/lib/libc/gen/telldir.h
@@ -46,9 +46,9 @@
*/
struct ddloc_mem {
LIST_ENTRY(ddloc_mem) loc_lqe; /* entry in list */
- long loc_index; /* key associated with structure */
+ size_t loc_index; /* key associated with structure */
off_t loc_seek; /* magic cookie returned by getdirentries */
- long loc_loc; /* offset of entry in buffer */
+ size_t loc_loc; /* offset of entry in buffer */
};
#ifdef __LP64__
@@ -102,7 +102,7 @@ bool _filldir(DIR *, bool);
struct dirent *_readdir_unlocked(DIR *, int);
void _reclaim_telldir(DIR *);
void _seekdir(DIR *, long);
-void _fixtelldir(DIR *dirp, long oldseek, long oldloc);
+void _fixtelldir(DIR *dirp, off_t oldseek, size_t oldloc);
DIR *__opendir_common(int, int, bool);
#define RDU_SKIP 0x0001
diff --git a/lib/libc/gen/tls.c b/lib/libc/gen/tls.c
index 30b4fc583c98..b26b13d45589 100644
--- a/lib/libc/gen/tls.c
+++ b/lib/libc/gen/tls.c
@@ -80,13 +80,13 @@ static void *libc_tls_init;
void *
__libc_tls_get_addr(void *vti)
{
- uintptr_t *dtv;
+ struct dtv *dtv;
tls_index *ti;
dtv = _tcb_get()->tcb_dtv;
ti = vti;
- return ((char *)(dtv[ti->ti_module + 1] + ti->ti_offset) +
- TLS_DTV_OFFSET);
+ return (dtv->dtv_slots[ti->ti_module - 1].dtvs_tls +
+ (ti->ti_offset + TLS_DTV_OFFSET));
}
#ifdef __i386__
@@ -151,7 +151,8 @@ libc_free_aligned(void *ptr)
* where TP points (with bias) to TLS and TCB immediately precedes TLS without
* any alignment gap[4]. Only TLS should be aligned. The TCB[0] points to DTV
* vector and DTV values are biased by constant value (TLS_DTV_OFFSET) from
- * real addresses[5].
+ * real addresses. However, like RTLD, we don't actually bias the DTV values,
+ * instead we compensate in __tls_get_addr for ti_offset's bias.
*
* [1] Ulrich Drepper: ELF Handling for Thread-Local Storage
* www.akkadia.org/drepper/tls.pdf
@@ -167,8 +168,6 @@ libc_free_aligned(void *ptr)
* but we must follow this rule due to suboptimal _tcb_set()
* (aka <ARCH>_SET_TP) implementation. This function doesn't expect TP but
* TCB as argument.
- *
- * [5] I'm not able to validate "values are biased" assertions.
*/
/*
@@ -201,11 +200,9 @@ get_tls_block_ptr(void *tcb, size_t tcbsize)
void
__libc_free_tls(void *tcb, size_t tcbsize, size_t tcbalign __unused)
{
- Elf_Addr *dtv;
- Elf_Addr **tls;
+ struct dtv *dtv;
- tls = (Elf_Addr **)tcb;
- dtv = tls[0];
+ dtv = ((struct tcb *)tcb)->tcb_dtv;
__je_bootstrap_free(dtv);
libc_free_aligned(get_tls_block_ptr(tcb, tcbsize));
}
@@ -233,7 +230,8 @@ __libc_free_tls(void *tcb, size_t tcbsize, size_t tcbalign __unused)
void *
__libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
{
- Elf_Addr *dtv, **tcb;
+ struct dtv *dtv;
+ struct tcb *tcb;
char *tls_block, *tls;
size_t extra_size, maxalign, post_size, pre_size, tls_block_size;
@@ -262,7 +260,7 @@ __libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
abort();
}
memset(tls_block, 0, tls_block_size);
- tcb = (Elf_Addr **)(tls_block + pre_size + extra_size);
+ tcb = (struct tcb *)(tls_block + pre_size + extra_size);
tls = (char *)tcb + TLS_TCB_SIZE + post_size;
if (oldtcb != NULL) {
@@ -271,19 +269,20 @@ __libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
libc_free_aligned(oldtcb);
/* Adjust the DTV. */
- dtv = tcb[0];
- dtv[2] = (Elf_Addr)(tls + TLS_DTV_OFFSET);
+ dtv = tcb->tcb_dtv;
+ dtv->dtv_slots[0].dtvs_tls = tls;
} else {
- dtv = __je_bootstrap_malloc(3 * sizeof(Elf_Addr));
+ dtv = __je_bootstrap_malloc(sizeof(struct dtv) +
+ sizeof(struct dtv_slot));
if (dtv == NULL) {
tls_msg("__libc_allocate_tls: Out of memory.\n");
abort();
}
/* Build the DTV. */
- tcb[0] = dtv;
- dtv[0] = 1; /* Generation. */
- dtv[1] = 1; /* Segments count. */
- dtv[2] = (Elf_Addr)(tls + TLS_DTV_OFFSET);
+ tcb->tcb_dtv = dtv;
+ dtv->dtv_gen = 1; /* Generation. */
+ dtv->dtv_size = 1; /* Segments count. */
+ dtv->dtv_slots[0].dtvs_tls = tls;
if (libc_tls_init_size > 0)
memcpy(tls, libc_tls_init, libc_tls_init_size);
@@ -303,8 +302,8 @@ void
__libc_free_tls(void *tcb, size_t tcbsize __unused, size_t tcbalign)
{
size_t size;
- Elf_Addr* dtv;
- Elf_Addr tlsstart, tlsend;
+ struct dtv *dtv;
+ uintptr_t tlsstart, tlsend;
/*
* Figure out the size of the initial TLS block so that we can
@@ -313,8 +312,8 @@ __libc_free_tls(void *tcb, size_t tcbsize __unused, size_t tcbalign)
tcbalign = MAX(tcbalign, libc_tls_init_align);
size = roundup2(libc_tls_static_space, tcbalign);
- dtv = ((Elf_Addr**)tcb)[1];
- tlsend = (Elf_Addr) tcb;
+ dtv = ((struct tcb *)tcb)->tcb_dtv;
+ tlsend = (uintptr_t)tcb;
tlsstart = tlsend - size;
libc_free_aligned((void*)tlsstart);
__je_bootstrap_free(dtv);
@@ -324,61 +323,60 @@ __libc_free_tls(void *tcb, size_t tcbsize __unused, size_t tcbalign)
* Allocate Static TLS using the Variant II method.
*/
void *
-__libc_allocate_tls(void *oldtls, size_t tcbsize, size_t tcbalign)
+__libc_allocate_tls(void *oldtcb, size_t tcbsize, size_t tcbalign)
{
size_t size;
- char *tls;
- Elf_Addr *dtv;
- Elf_Addr segbase, oldsegbase;
+ char *tls_block, *tls;
+ struct dtv *dtv;
+ struct tcb *tcb;
tcbalign = MAX(tcbalign, libc_tls_init_align);
size = roundup2(libc_tls_static_space, tcbalign);
- if (tcbsize < 2 * sizeof(Elf_Addr))
- tcbsize = 2 * sizeof(Elf_Addr);
- tls = libc_malloc_aligned(size + tcbsize, tcbalign);
- if (tls == NULL) {
+ if (tcbsize < 2 * sizeof(uintptr_t))
+ tcbsize = 2 * sizeof(uintptr_t);
+ tls_block = libc_malloc_aligned(size + tcbsize, tcbalign);
+ if (tls_block == NULL) {
tls_msg("__libc_allocate_tls: Out of memory.\n");
abort();
}
- memset(tls, 0, size + tcbsize);
- dtv = __je_bootstrap_malloc(3 * sizeof(Elf_Addr));
+ memset(tls_block, 0, size + tcbsize);
+ dtv = __je_bootstrap_malloc(sizeof(struct dtv) +
+ sizeof(struct dtv_slot));
if (dtv == NULL) {
tls_msg("__libc_allocate_tls: Out of memory.\n");
abort();
}
- segbase = (Elf_Addr)(tls + size);
- ((Elf_Addr*)segbase)[0] = segbase;
- ((Elf_Addr*)segbase)[1] = (Elf_Addr) dtv;
+ tcb = (struct tcb *)(tls_block + size);
+ tls = (char *)tcb - libc_tls_static_space;
+ tcb->tcb_self = tcb;
+ tcb->tcb_dtv = dtv;
- dtv[0] = 1;
- dtv[1] = 1;
- dtv[2] = segbase - libc_tls_static_space;
+ dtv->dtv_gen = 1;
+ dtv->dtv_size = 1;
+ dtv->dtv_slots[0].dtvs_tls = tls;
- if (oldtls) {
+ if (oldtcb != NULL) {
/*
* Copy the static TLS block over whole.
*/
- oldsegbase = (Elf_Addr) oldtls;
- memcpy((void *)(segbase - libc_tls_static_space),
- (const void *)(oldsegbase - libc_tls_static_space),
+ memcpy(tls, (const char *)oldtcb - libc_tls_static_space,
libc_tls_static_space);
/*
* We assume that this block was the one we created with
* allocate_initial_tls().
*/
- _rtld_free_tls(oldtls, 2*sizeof(Elf_Addr), sizeof(Elf_Addr));
+ _rtld_free_tls(oldtcb, 2 * sizeof(uintptr_t),
+ sizeof(uintptr_t));
} else {
- memcpy((void *)(segbase - libc_tls_static_space),
- libc_tls_init, libc_tls_init_size);
- memset((void *)(segbase - libc_tls_static_space +
- libc_tls_init_size), 0,
+ memcpy(tls, libc_tls_init, libc_tls_init_size);
+ memset(tls + libc_tls_init_size, 0,
libc_tls_static_space - libc_tls_init_size);
}
- return (void*) segbase;
+ return (tcb);
}
#endif /* TLS_VARIANT_II */
@@ -386,7 +384,7 @@ __libc_allocate_tls(void *oldtls, size_t tcbsize, size_t tcbalign)
#else
void *
-__libc_allocate_tls(void *oldtls __unused, size_t tcbsize __unused,
+__libc_allocate_tls(void *oldtcb __unused, size_t tcbsize __unused,
size_t tcbalign __unused)
{
return (0);
diff --git a/lib/libc/gen/ualarm.3 b/lib/libc/gen/ualarm.3
index f4e0973b133a..dfd9ddd5f434 100644
--- a/lib/libc/gen/ualarm.3
+++ b/lib/libc/gen/ualarm.3
@@ -70,7 +70,7 @@ restriction the maximum number of
.Fa microseconds
and
.Fa interval
-is limited to 100000000000000
+is limited to 100,000,000,000,000
(in case this value fits in the unsigned integer).
.Sh RETURN VALUES
When the signal has successfully been caught,
diff --git a/lib/libc/gen/uexterr_format.c b/lib/libc/gen/uexterr_format.c
new file mode 100644
index 000000000000..e8ddfbd578e3
--- /dev/null
+++ b/lib/libc/gen/uexterr_format.c
@@ -0,0 +1,35 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software were developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#include <sys/types.h>
+#include <sys/exterrvar.h>
+#include <exterr.h>
+#include <stdio.h>
+#include <string.h>
+
+int
+__uexterr_format(const struct uexterror *ue, char *buf, size_t bufsz)
+{
+ if (bufsz > UEXTERROR_MAXLEN)
+ bufsz = UEXTERROR_MAXLEN;
+ if (ue->error == 0) {
+ strlcpy(buf, "", bufsz);
+ return (0);
+ }
+ if (ue->msg[0] == '\0') {
+ snprintf(buf, bufsz,
+ "errno %d category %u (src line %u) p1 %#jx p2 %#jx",
+ ue->error, ue->cat, ue->src_line,
+ (uintmax_t)ue->p1, (uintmax_t)ue->p2);
+ } else {
+ strlcpy(buf, ue->msg, bufsz);
+ }
+ return (0);
+}
diff --git a/lib/libc/gen/uexterr_gettext.c b/lib/libc/gen/uexterr_gettext.c
new file mode 100644
index 000000000000..5b8a5cd17a48
--- /dev/null
+++ b/lib/libc/gen/uexterr_gettext.c
@@ -0,0 +1,43 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software were developed by Konstantin Belousov <kib@FreeBSD.org>
+ * under sponsorship from the FreeBSD Foundation.
+ */
+
+#define _WANT_P_OSREL
+#include <sys/param.h>
+#include <sys/exterrvar.h>
+#include <exterr.h>
+#include <string.h>
+#include "libc_private.h"
+
+static struct uexterror uexterr = {
+ .ver = UEXTERROR_VER,
+};
+
+int __getosreldate(void);
+
+static void uexterr_ctr(void) __attribute__((constructor));
+static void
+uexterr_ctr(void)
+{
+ if (__getosreldate() >= P_OSREL_EXTERRCTL)
+ exterrctl(EXTERRCTL_ENABLE, 0, &uexterr);
+}
+
+int
+__libc_uexterr_gettext(char *buf, size_t bufsz)
+{
+ return (__uexterr_format(&uexterr, buf, bufsz));
+}
+
+int
+uexterr_gettext(char *buf, size_t bufsz)
+{
+ return (((int (*)(char *, size_t))
+ __libc_interposing[INTERPOS_uexterr_gettext])(buf, bufsz));
+}
diff --git a/lib/libc/gen/wordexp.c b/lib/libc/gen/wordexp.c
index f1437e30bbe2..f8080c20d121 100644
--- a/lib/libc/gen/wordexp.c
+++ b/lib/libc/gen/wordexp.c
@@ -265,7 +265,15 @@ cleanup:
errno = serrno;
return (error);
}
- if (wpid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ /*
+ * Check process exit status, but ignore ECHILD as the child may have
+ * been automatically reaped if the process had set SIG_IGN or
+ * SA_NOCLDWAIT for SIGCHLD, and our reason for waitpid was just to
+ * reap our own child on behalf of the calling process.
+ */
+ if (wpid < 0 && errno != ECHILD)
+ return (WRDE_NOSPACE); /* abort for unknown reason */
+ if (wpid >= 0 && (!WIFEXITED(status) || WEXITSTATUS(status) != 0))
return (WRDE_NOSPACE); /* abort for unknown reason */
/*