Page MenuHomeFreeBSD

D21206.id.diff
No OneTemporary

D21206.id.diff

diff --git a/include/stdlib.h b/include/stdlib.h
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -296,6 +296,7 @@
char *devname_r(__dev_t, __mode_t, char *, int);
char *fdevname(int);
char *fdevname_r(int, char *, int);
+int fdwalk(int (*)(void *, int), void *);
int getloadavg(double [], int);
const char *
getprogname(void);
diff --git a/lib/libc/stdlib/Makefile.inc b/lib/libc/stdlib/Makefile.inc
--- a/lib/libc/stdlib/Makefile.inc
+++ b/lib/libc/stdlib/Makefile.inc
@@ -17,6 +17,7 @@
cxa_thread_atexit_impl.c \
div.c \
exit.c \
+ fdwalk.c \
getenv.c \
getopt.c \
getopt_long.c \
@@ -90,7 +91,7 @@
MAN+= a64l.3 abort.3 abs.3 atexit.3 atof.3 \
atoi.3 atol.3 at_quick_exit.3 bsearch.3 \
- div.3 exit.3 getenv.3 getopt.3 getopt_long.3 getsubopt.3 \
+ div.3 exit.3 fdwalk.3 getenv.3 getopt.3 getopt_long.3 getsubopt.3 \
hcreate.3 insque.3 \
lsearch.3 memalignment.3 memory.3 ptsname.3 qsort.3 \
quick_exit.3 \
diff --git a/lib/libc/stdlib/Symbol.map b/lib/libc/stdlib/Symbol.map
--- a/lib/libc/stdlib/Symbol.map
+++ b/lib/libc/stdlib/Symbol.map
@@ -123,6 +123,7 @@
FBSD_1.7 {
clearenv;
+ fdwalk;
qsort_r;
secure_getenv;
};
diff --git a/lib/libc/stdlib/fdwalk.3 b/lib/libc/stdlib/fdwalk.3
new file mode 100644
--- /dev/null
+++ b/lib/libc/stdlib/fdwalk.3
@@ -0,0 +1,75 @@
+.\" Copyright (c) 2019 Justin Hibbits
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 28, 2026
+.Dt FDWALK 3
+.Os
+.Sh NAME
+.Nm fdwalk
+.Nd iterate over open file descriptors
+.Sh LIBRARY
+.Lb libc
+.Sh SYNOPSIS
+.In stdlib.h
+.Ft int
+.Fn fdwalk "int (*cb)(void *, int)" "void *cbd"
+.Sh DESCRIPTION
+The
+.Nm
+function executes a callback for each file descriptor open at the time of its
+invocation.
+The
+.Nm
+function passes the
+.Ar cbd
+argument into the callback, along with each file descriptor.
+If
+.Ar cbd
+returns non-zero, iteration is terminated.
+It is async-signal-safe.
+.Sh RETURN VALUES
+The
+.Nm
+function always returns 0.
+.Sh SEE ALSO
+.Xr closefrom 2 ,
+.Xr kinfo_getfile 3
+.Sh HISTORY
+The
+.Nm
+function first appeared in SunOS.
+.Sh BUGS
+It is undefined whether the
+.Nm
+function will observe an arbitrary file descriptor that was opened during active
+traversal.
+However, this implementation will observe file descriptors in ascending order,
+and will not re-scan for open descriptors below the highest descriptor that the
+current
+.Fa cb
+invocation has observed.
diff --git a/lib/libc/stdlib/fdwalk.c b/lib/libc/stdlib/fdwalk.c
new file mode 100644
--- /dev/null
+++ b/lib/libc/stdlib/fdwalk.c
@@ -0,0 +1,112 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Justin Hibbits
+ *
+ * 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 ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/filedesc.h>
+#include <sys/sysctl.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * Reasonable guess for how many fds the average process has opened to steer our
+ * batch size.
+ */
+#define FDWALK_BITMAP_FDCOUNT 8192
+
+#define NDENTRIES (NBBY * sizeof(NDSLOTTYPE))
+#define NDSLOT(x) ((x) / NDENTRIES)
+#define NDSLOTS(x) (((x) + NDENTRIES - 1) / NDENTRIES)
+#define FD(slot, bit) (((slot) * sizeof(NDSLOTTYPE) * NBBY) + bit)
+
+int
+fdwalk(int (*cb)(void *, int), void *cbd)
+{
+ int mib[4];
+ size_t newlen;
+ unsigned int fdrange[2] = { 0, INT_MAX };
+ int error, i, j, len;
+ struct mapret {
+ NDSLOTTYPE lowslot;
+ NDSLOTTYPE buf[NDSLOTS(FDWALK_BITMAP_FDCOUNT)];
+ } *mem;
+ NDSLOTTYPE tmp;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_FDMAP;
+ mib[3] = 0;
+
+ mem = alloca(sizeof(*mem));
+
+ for (;;) {
+ newlen = sizeof(*mem);
+ error = sysctl(mib, nitems(mib), mem, &newlen, &fdrange,
+ sizeof(fdrange));
+ if (error != 0)
+ return (0);
+
+ assert(newlen >= sizeof(mem->lowslot)); /* Kernel bug */
+
+ /*
+ * The final call will return no bitmap, just the final slot.
+ */
+ newlen -= sizeof(mem->lowslot);
+ if (newlen == 0)
+ break;
+
+ /*
+ * Go through the full file list. The fdmap is an integral
+ * multiple of sizeof(NDSLOTTYPE).
+ */
+ len = howmany(newlen, sizeof(NDSLOTTYPE));
+
+ for (i = 0; i < len; i++) {
+ /*
+ * Iterate over each bit in the slot, short-circuting
+ * when there are no more file descriptors in use in
+ * this slot.
+ */
+ for (j = 0, tmp = mem->buf[i];
+ j < NDENTRIES && tmp != 0;
+ j++, tmp >>= 1) {
+ if (tmp & 1) {
+ error = cb(cbd,
+ FD(mem->lowslot + i, j));
+ if (error != 0)
+ return (error);
+ }
+ }
+ }
+
+ /* Resume scanning at the next slot, but we might be done. */
+ fdrange[0] = FD(mem->lowslot + len, 0);
+ }
+
+ return (0);
+}
diff --git a/lib/libsys/sigaction.2 b/lib/libsys/sigaction.2
--- a/lib/libsys/sigaction.2
+++ b/lib/libsys/sigaction.2
@@ -390,7 +390,9 @@
.Fn fchown ,
.Fn fchownat ,
.Fn fcntl ,
+.Fn fdwalk ,
.Fn _Fork ,
+.Fn fork ,
.Fn fstat ,
.Fn fstatat ,
.Fn fsync ,
diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c
--- a/sys/kern/kern_descrip.c
+++ b/sys/kern/kern_descrip.c
@@ -150,6 +150,7 @@
#define NDSLOT(x) ((x) / NDENTRIES)
#define NDBIT(x) ((NDSLOTTYPE)1 << ((x) % NDENTRIES))
#define NDSLOTS(x) (((x) + NDENTRIES - 1) / NDENTRIES)
+#define NFD2MAPSIZE(x) (NDSLOTS(x) * NDSLOTSIZE)
#define FILEDESC_FOREACH_FDE(fdp, _iterator, _fde) \
struct filedesc *_fdp = (fdp); \
@@ -2040,7 +2041,7 @@
* entries than the table can hold.
*/
if (NDSLOTS(nnfiles) > NDSLOTS(onfiles)) {
- nmap = malloc(NDSLOTS(nnfiles) * NDSLOTSIZE, M_FILEDESC,
+ nmap = malloc(NFD2MAPSIZE(nnfiles), M_FILEDESC,
M_ZERO | M_WAITOK);
/* copy over the old data and update the pointer */
memcpy(nmap, omap, NDSLOTS(onfiles) * sizeof(*omap));
@@ -4622,6 +4623,149 @@
CTLFLAG_RD|CTLFLAG_CAPRD|CTLFLAG_MPSAFE, sysctl_kern_proc_nfds,
"Number of open file descriptors");
+static int
+sysctl_kern_proc_fdmap(SYSCTL_HANDLER_ARGS)
+{
+ struct filedesc *fdp;
+ NDSLOTTYPE lmap[NDSLOTS(NDFILE) * 16];
+ NDSLOTTYPE lowmask, lowslot, highslot;
+ NDSLOTTYPE *map;
+ size_t bsize, csize;
+ u_int fdrange[2], maxslot, maxoutslot;
+ int error;
+
+ CTASSERT(sizeof(lmap) <= 128);
+
+ if (*(int *)arg1 != 0)
+ return (EINVAL);
+ if (req->newptr == NULL || req->newlen != sizeof(fdrange))
+ return (EINVAL);
+ if (req->oldlen != 0 && req->oldlen < sizeof(lowslot))
+ return (EINVAL);
+
+ /*
+ * If we weren't given a bitmap to populate, we'll return the max size
+ * required and let the caller sort out how it wants to approach it.
+ */
+ fdp = curproc->p_fd;
+ if (req->oldptr == NULL) {
+ return (SYSCTL_OUT(req, NULL, sizeof(lowslot) +
+ NFD2MAPSIZE(fdp->fd_nfiles)));
+ }
+
+ error = SYSCTL_IN(req, &fdrange[0], sizeof(fdrange));
+ if (error != 0)
+ return (error);
+ else if (fdrange[0] > fdrange[1])
+ return (EINVAL);
+
+ /*
+ * Mask includes the low fd. lowslot could be an int, but it's an
+ * NDSLOTTYPE to avoid creating potential alignment issues on writing
+ * it out with the bitmap.
+ */
+ lowmask = ~0UL << (fdrange[0] % NDENTRIES);
+ lowslot = NDSLOT(fdrange[0]);
+ highslot = NDSLOTS(fdrange[1]);
+
+ /*
+ * If the low-side is above our current size, we're done here.
+ */
+ maxoutslot = NDSLOTS(req->oldlen - sizeof(lowslot));
+ maxslot = NDSLOT(fdp->fd_nfiles - 1);
+ if (lowslot > maxslot) {
+ lowslot = maxslot + 1;
+
+ return (SYSCTL_OUT(req, &lowslot, sizeof(lowslot)));
+ }
+
+ if (curproc->p_numthreads == 1 && fdp->fd_refcnt == 1) {
+ /* No need to lock anything as only we can modify the map. */
+ highslot = MIN(highslot, maxslot + 1);
+
+ /*
+ * Always start with the first non-empty slot. We don't
+ * technically have to do this, but it is a bit nicer to shift
+ * away leading sparsity.
+ */
+ for (; lowslot < highslot; lowslot++) {
+ if ((fdp->fd_map[lowslot] & lowmask) != 0)
+ break;
+ lowmask = ~0UL;
+ }
+
+ /*
+ * Apply our maxoutslot limitation *after* trimming leading
+ * sparsity so that a scan of [3, INT_MAX) will turn up fds on
+ * the high end of the range if there's nothing opened low,
+ * rather than returning 0 and forcing a scan of the whole
+ * space.
+ */
+ highslot = MIN(highslot, lowslot + maxoutslot);
+
+ error = SYSCTL_OUT(req, &lowslot, sizeof(lowslot));
+ MPASS(lowslot <= highslot);
+ if (error == 0 && lowslot != highslot)
+ error = SYSCTL_OUT(req, &fdp->fd_map[lowslot],
+ (highslot - lowslot) * NDSLOTSIZE);
+ return (error);
+ }
+
+ /* Potential other threads modifying the map. */
+ map = lmap;
+ bsize = sizeof(lmap);
+ for (;;) {
+ FILEDESC_SLOCK(fdp);
+
+ /*
+ * Since we're allocating a new map, we'll apply the maxoutslot
+ * limitation up front, but highslot doesn't get adjusted until
+ * we've figured out where the lowest non-zero entry is. In any
+ * event, we're trying to allocate the smallest map necessary.
+ */
+ maxslot = NDSLOT(fdp->fd_nfiles - 1);
+ csize = MIN(highslot, maxslot + 1) - lowslot; /* Slots */
+ csize = MIN(csize, maxoutslot) * NDSLOTSIZE;
+ if (bsize >= csize)
+ break;
+ FILEDESC_SUNLOCK(fdp);
+ if (map != lmap)
+ free(map, M_TEMP);
+ bsize = csize;
+ map = malloc(bsize, M_TEMP, M_WAITOK);
+ }
+
+ MPASS(lowslot * NDSLOTSIZE + csize <=
+ NFD2MAPSIZE(fdp->fd_nfiles));
+
+ /*
+ * Clamp highslot down to reality and trim off any leading sparsity.
+ */
+ highslot = MIN(highslot, maxslot + 1);
+ for (; lowslot < highslot; lowslot++) {
+ if ((fdp->fd_map[lowslot] & lowmask) != 0)
+ break;
+ lowmask = ~0UL;
+ }
+
+ highslot = MIN(highslot, lowslot + maxoutslot);
+
+ MPASS(lowslot <= highslot);
+ csize = (highslot - lowslot) * NDSLOTSIZE;
+ memcpy(map, &fdp->fd_map[lowslot], csize);
+ FILEDESC_SUNLOCK(fdp);
+ error = SYSCTL_OUT(req, &lowslot, sizeof(lowslot));
+ if (error == 0 && lowslot != highslot)
+ error = SYSCTL_OUT(req, map, csize);
+ if (map != lmap)
+ free(map, M_TEMP);
+ return (error);
+}
+
+static SYSCTL_NODE(_kern_proc, KERN_PROC_FDMAP, fdmap,
+ CTLFLAG_RW|CTLFLAG_CAPRW|CTLFLAG_MPSAFE, sysctl_kern_proc_fdmap,
+ "File descriptor map");
+
/*
* Get file structures globally.
*/
diff --git a/sys/sys/sysctl.h b/sys/sys/sysctl.h
--- a/sys/sys/sysctl.h
+++ b/sys/sys/sysctl.h
@@ -1065,6 +1065,7 @@
#define KERN_PROC_VM_LAYOUT 45 /* virtual address space layout info */
#define KERN_PROC_RLIMIT_USAGE 46 /* array of rlim_t */
#define KERN_PROC_KQUEUE 47 /* array of struct kinfo_knote */
+#define KERN_PROC_FDMAP 48 /* file descriptor map */
/*
* KERN_IPC identifiers

File Metadata

Mime Type
text/plain
Expires
Thu, Apr 9, 11:32 AM (8 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
31121482
Default Alt Text
D21206.id.diff (12 KB)

Event Timeline