fts(3) currently automatically enables FTS_NOCHDIR if FTS_LOGICAL (-L in
various utilities) is used, according to a comment because symlinks are
too hard. However, the effect of fts_instr=FTS_FOLLOW seems quite
similar to FTS_LOGICAL in chdir mode. Using one more bit in fts_flags, I
made FTS_LOGICAL work with chdir.
This has several advantages:
* it runs faster
* it eliminates the PATH_MAX restriction on deep directory trees
* find -L ... -type l -delete can be made to work without ugliness
(although it is of course still insecure if run over directory trees
where untrusted users have write access)
Question: why are the values for fts_flags in the public header file
fts.h, even though they are supposedly private?
In case the mailing list eats the patch, it is also available here:
http://www.stack.nl/~jilles/unix/fts-logical-chdir.patch
--
Jilles Tjoelker
Index: include/fts.h
===================================================================
--- include/fts.h (revision 201269)
+++ include/fts.h (working copy)
@@ -106,6 +106,7 @@
#define FTS_DONTCHDIR 0x01 /* don't chdir .. to the parent */
#define FTS_SYMFOLLOW 0x02 /* followed a symlink to get here */
#define FTS_ISW 0x04 /* this is a whiteout object */
+#define FTS_DIRSYM 0x08 /* this is a symlink to a directory */
unsigned fts_flags; /* private flags for FTSENT structure */
#define FTS_AGAIN 1 /* read node again */
Index: lib/libc/gen/fts.c
===================================================================
--- lib/libc/gen/fts.c (revision 201269)
+++ lib/libc/gen/fts.c (working copy)
@@ -141,10 +141,6 @@
/* Shush, GCC. */
tmp = NULL;
- /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
- if (ISSET(FTS_LOGICAL))
- SET(FTS_NOCHDIR);
-
/*
* Start out with 1K of path space, and enough, in any case,
* to hold the user's paths.
@@ -355,8 +351,10 @@
/* If skipped or crossed mount point, do post-order visit. */
if (instr == FTS_SKIP ||
(ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) {
- if (p->fts_flags & FTS_SYMFOLLOW)
+ if (p->fts_flags & FTS_SYMFOLLOW) {
(void)_close(p->fts_symfd);
+ p->fts_symfd = -1;
+ }
if (sp->fts_child) {
fts_lfree(sp->fts_child);
sp->fts_child = NULL;
@@ -385,6 +383,14 @@
* FTS_STOP or the fts_info field of the node.
*/
if (sp->fts_child != NULL) {
+ if (p->fts_flags & FTS_DIRSYM &&
+ !(p->fts_flags & FTS_SYMFOLLOW)) {
+ if ((p->fts_symfd = _open(".", O_RDONLY, 0)) < 0) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ } else
+ p->fts_flags |= FTS_SYMFOLLOW;
+ }
if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
p->fts_errno = errno;
p->fts_flags |= FTS_DONTCHDIR;
@@ -674,8 +680,8 @@
nlinks = 0;
/* Be quiet about nostat, GCC. */
nostat = 0;
- } else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) {
- if (fts_ufslinks(sp, cur))
+ } else if (ISSET(FTS_NOSTAT)) {
+ if (ISSET(FTS_PHYSICAL) && fts_ufslinks(sp, cur))
nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2);
else
nlinks = -1;
@@ -707,7 +713,19 @@
*/
cderrno = 0;
if (nlinks || type == BREAD) {
- if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) {
+ if (cur->fts_flags & FTS_DIRSYM &&
+ !(cur->fts_flags & FTS_SYMFOLLOW)) {
+ if ((cur->fts_symfd = _open(".", O_RDONLY, 0)) < 0) {
+ if (nlinks && type == BREAD)
+ cur->fts_errno = errno;
+ cur->fts_flags |= FTS_DONTCHDIR;
+ descend = 0;
+ cderrno = errno;
+ } else
+ cur->fts_flags |= FTS_SYMFOLLOW;
+ }
+ if (!(cur->fts_flags & FTS_DONTCHDIR) &&
+ fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) {
if (nlinks && type == BREAD)
cur->fts_errno = errno;
cur->fts_flags |= FTS_DONTCHDIR;
@@ -796,7 +814,8 @@
} else if (nlinks == 0
#ifdef DT_DIR
|| (nostat &&
- dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
+ dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN &&
+ (dp->d_type != DT_LNK || ISSET(FTS_PHYSICAL)))
#endif
) {
p->fts_accpath =
@@ -883,7 +902,7 @@
FTSENT *t;
dev_t dev;
ino_t ino;
- struct stat *sbp, sb;
+ struct stat *sbp, sb, sb2;
int saved_errno;
/* If user needs stat info, stat buffer already allocated. */
@@ -923,6 +942,20 @@
if (S_ISDIR(sbp->st_mode)) {
/*
+ * Check if this is actually a symlink to a directory.
+ * If so, record this fact so we save a file descriptor
+ * to get back instead of using "..".
+ */
+ if (ISSET(FTS_LOGICAL) && !follow) {
+ if (lstat(p->fts_accpath, &sb2)) {
+ p->fts_errno = errno;
+ goto err;
+ }
+ if (S_ISLNK(sb2.st_mode))
+ p->fts_flags |= FTS_DIRSYM;
+ }
+
+ /*
* Set the device/inode. Used to find cycles and check for
* crossing mount points. Also remember the link count, used
* in fts_build to limit the number of stat calls. It is
_______________________________________________
[email protected] mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-hackers
To unsubscribe, send any mail to "[email protected]"