Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F151447528
D21206.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
12 KB
Referenced Files
None
Subscribers
None
D21206.id.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D21206: libc: add fdwalk
Attached
Detach File
Event Timeline
Log In to Comment