Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 1 Jan 2010 22:25:35 +0100
From:      Jilles Tjoelker <jilles@stack.nl>
To:        freebsd-hackers@freebsd.org
Subject:   [PATCH] fts(3): allow FTS_LOGICAL without FTS_NOCHDIR
Message-ID:  <20100101212535.GB60021@stack.nl>

next in thread | raw e-mail | index | archive | help

--gBBFr7Ir9EOA20Yy
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

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

--gBBFr7Ir9EOA20Yy
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="fts-logical-chdir.patch"

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

--gBBFr7Ir9EOA20Yy--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20100101212535.GB60021>