git: da2025a0e894 - main - fts: Add FTS_COMFOLLOWDIR and FTS_NOSTAT_TYPE.
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 08 May 2025 14:29:42 UTC
The branch main has been updated by des:
URL: https://cgit.FreeBSD.org/src/commit/?id=da2025a0e89455fb646b542b53d5a4ddaa2acbe0
commit da2025a0e89455fb646b542b53d5a4ddaa2acbe0
Author: Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-05-08 14:28:51 +0000
Commit: Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-05-08 14:29:15 +0000
fts: Add FTS_COMFOLLOWDIR and FTS_NOSTAT_TYPE.
MFC after: never
Relnotes: yes
Sponsored by: Klara, Inc.
Reviewed by: kevans, imp
Differential Revision: https://reviews.freebsd.org/D50233
---
include/fts.h | 6 +++++-
lib/libc/gen/fts.3 | 22 +++++++++++++++++++++-
lib/libc/gen/fts.c | 45 ++++++++++++++++++++++++++++++++++++++-------
3 files changed, 64 insertions(+), 9 deletions(-)
diff --git a/include/fts.h b/include/fts.h
index f2c40b854ffb..479905bda463 100644
--- a/include/fts.h
+++ b/include/fts.h
@@ -65,7 +65,11 @@ typedef struct {
#define FTS_SEEDOT 0x000020 /* return dot and dot-dot */
#define FTS_XDEV 0x000040 /* don't cross devices */
#define FTS_WHITEOUT 0x000080 /* return whiteout information */
-#define FTS_OPTIONMASK 0x0000ff /* valid user option mask */
+ /* 0x0100 is FTS_NAMEONLY below */
+ /* 0x0200 was previously FTS_STOP */
+#define FTS_COMFOLLOWDIR 0x00400 /* like COMFOLLOW but directories only */
+#define FTS_NOSTAT_TYPE 0x000800 /* like NOSTAT but use d_type */
+#define FTS_OPTIONMASK 0x000cff /* valid user option mask */
/* valid only for fts_children() */
#define FTS_NAMEONLY 0x000100 /* child names only */
diff --git a/lib/libc/gen/fts.3 b/lib/libc/gen/fts.3
index 3007b773ec55..0c32bb0ebdb6 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 April 17, 2025
+.Dd May 7, 2025
.Dt FTS 3
.Os
.Sh NAME
@@ -394,6 +394,10 @@ 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
@@ -449,6 +453,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
@@ -820,6 +833,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 a55b4a6e2981..fa3ee9cc4c83 100644
--- a/lib/libc/gen/fts.c
+++ b/lib/libc/gen/fts.c
@@ -126,6 +126,10 @@ __fts_open(FTS *sp, char * const *argv)
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.
@@ -149,7 +153,9 @@ __fts_open(FTS *sp, char * const *argv)
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)
@@ -904,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;
@@ -980,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) {
@@ -1003,19 +1028,25 @@ fts_stat(FTS *sp, FTSENT *p, int follow, int dfd)
}
/*
- * 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)) {