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
_______________________________________________ freebsd-hackers@freebsd.org mailing list http://lists.freebsd.org/mailman/listinfo/freebsd-hackers To unsubscribe, send any mail to "freebsd-hackers-unsubscr...@freebsd.org"