fts: reduce fd usage by storing fts_dirfd on directory entries only

Previously fts_build() called _dup(_dirfd(dirp)) for every child
entry, holding N simultaneous fds for a directory with N children.

Redefine fts_dirfd: instead of a fd for the entry's parent
directory, it is now a fd for the entry itself, set only for
directory entries. One dup per directory in fts_build() instead
of one per child.  Close fts_dirfd during the directory post-order
visit, before advancing to its sibling.

To access a file using fd-relative operations, callers should use
openat(ent->fts_parent->fts_dirfd, ent->fts_name, ...) instead of
openat(ent->fts_dirfd, ent->fts_name, ...).  The fd is valid until the
directory's post-order visit (FTS_DP).

Reported by:    Mark Johnston <[email protected]>
Fixes:          4bd01d6ae0 (fts: refactor to use fd-relative operations)
Sponsored by:   Google LLC (GSoC 2026)
Reviewed by:	asomers
Pull Request:	https://github.com/freebsd/freebsd-src/pull/2360
This commit is contained in:
Jitendra Bhati authored and Alan Somers committed 2026-08-10 10:14:16 -06:00
1 parent 99360212c7
commit dd532ad133
3 files changed
+34 -23

No files matched your search

+1 -1
View File
@@ -92,7 +92,7 @@ struct _ftsent {
char *fts_path; /* root path */
int fts_errno; /* errno for this node */
int fts_symfd; /* fd for symlink */
int fts_dirfd; /* fd for parent directory */
int fts_dirfd; /* fd for this directory, if a directory */
int __fts_reserved[3]; /* reserved for future use */
__size_t fts_pathlen; /* strlen(fts_path) */
__size_t fts_namelen; /* strlen(fts_name) */
+19 -13
View File
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd July 7, 2026
.Dd August 9, 2026
.Dt FTS 3
.Os
.Sh NAME
@@ -316,9 +316,10 @@ file is a member.
A parent structure for the initial entry point is provided as well,
however, only the
.Fa fts_level ,
.Fa fts_number
and
.Fa fts_number ,
.Fa fts_pointer
and
.Fa fts_dirfd
fields are guaranteed to be initialized.
.It Fa fts_link
Upon return from the
@@ -350,19 +351,24 @@ A pointer to
.Xr stat 2
information for the file.
.It Fa fts_dirfd
A file descriptor open on the parent directory of this entry.
It may be used with
A file descriptor open on this directory entry.
It is set only for directory entries
.Pq Dv FTS_D
and is \-1 for all other entry types.
To access a file using fd-relative operations without relying
on path-based syscalls, required in
.Xr capsicum 4
capability mode, use
.Fa fts_parent->fts_dirfd
with
.Xr openat 2
and
.Fa fts_name
to access the file without relying on path-based operations,
which is required in
.Xr capsicum 4
capability mode.
The descriptor is valid only until the next call to
.Fn fts_read
.Fa fts_name .
The descriptor is valid until the directory's post-order visit
.Pq Dv FTS_DP
and must not be closed by the caller.
For root-level entries,
For root-level entries opened with
.Fn fts_open ,
.Fa fts_dirfd
is \-1.
.El
+14 -9
View File
@@ -443,8 +443,8 @@ fts_read(FTS *sp)
(p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
p->fts_info = fts_stat(sp, p, 1, -1);
if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
if ((p->fts_symfd = p->fts_dirfd >= 0 ?
_dup(p->fts_dirfd) :
if ((p->fts_symfd = p->fts_parent->fts_dirfd >= 0 ?
_dup(p->fts_parent->fts_dirfd) :
_open(".", O_RDONLY | O_CLOEXEC, 0)) < 0) {
p->fts_errno = errno;
p->fts_info = FTS_ERR;
@@ -509,6 +509,10 @@ fts_read(FTS *sp)
/* Move to the next node on this level. */
next: tmp = p;
if (tmp->fts_dirfd >= 0 && tmp->fts_info == FTS_DP) {
(void)_close(tmp->fts_dirfd);
tmp->fts_dirfd = -1;
}
if ((p = p->fts_link) != NULL) {
/*
* If reached the top, return to the original directory (or
@@ -537,8 +541,8 @@ next: tmp = p;
p->fts_info = fts_stat(sp, p, 1, -1);
if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
if ((p->fts_symfd =
p->fts_dirfd >= 0 ?
_dup(p->fts_dirfd) :
p->fts_parent->fts_dirfd >= 0 ?
_dup(p->fts_parent->fts_dirfd) :
_open(".", O_RDONLY | O_CLOEXEC, 0)) < 0) {
p->fts_errno = errno;
p->fts_info = FTS_ERR;
@@ -680,8 +684,8 @@ fts_children(FTS *sp, int instr)
ISSET(FTS_NOCHDIR))
return (sp->fts_child = fts_build(sp, instr));
if ((fd = sp->fts_cur->fts_dirfd >= 0 ?
_dup(sp->fts_cur->fts_dirfd) :
if ((fd = sp->fts_cur->fts_parent->fts_dirfd >= 0 ?
_dup(sp->fts_cur->fts_parent->fts_dirfd) :
_open(".", O_RDONLY | O_CLOEXEC, 0)) < 0)
return (NULL);
sp->fts_child = fts_build(sp, instr);
@@ -783,6 +787,8 @@ fts_build(FTS *sp, int type)
return (NULL);
}
cur->fts_dirfd = _dup(_dirfd(dirp));
/*
* In the FTS_PHYSICAL | FTS_NOSTAT case, we want to avoid calling
* fstat() unnecessarily, but we still need to call it for
@@ -918,7 +924,6 @@ mem1: saved_errno = errno;
}
p->fts_level = level;
p->fts_dirfd = _dup(_dirfd(dirp));
p->fts_parent = sp->fts_cur;
p->fts_pathlen = len + dnamlen;
@@ -1337,8 +1342,8 @@ fts_ufslinks(FTS *sp, const FTSENT *ent)
* avoidance.
*/
if (priv->ftsp_dev != ent->fts_dev) {
if ((ent->fts_dirfd >= 0 ?
_fstatfs(ent->fts_dirfd, &priv->ftsp_statfs) :
if ((ent->fts_parent->fts_dirfd >= 0 ?
_fstatfs(ent->fts_parent->fts_dirfd, &priv->ftsp_statfs) :
statfs(ent->fts_path, &priv->ftsp_statfs)) != -1) {
priv->ftsp_dev = ent->fts_dev;
priv->ftsp_linksreliable = 0;