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
This commit is contained in:
Dag-Erling Smørgrav 2025-05-08 16:28:51 +02:00
parent bdc2ed1cf4
commit da2025a0e8
3 changed files with 64 additions and 9 deletions

View file

@ -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 */

View file

@ -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

View file

@ -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)) {