X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Ffts.c;h=c15f04c8f9a540bda237dc9056e09d6afeb9ec45;hb=85fc15b1bf5d976584d8f174682a0d677692f0ea;hp=ed09697e050ae0830b17bbef2bc953a4e431c4cf;hpb=14bf04a59fbfa9ae7a9b55f7885ed96a5f0341ac;p=gnulib.git diff --git a/lib/fts.c b/lib/fts.c index ed09697e0..c15f04c8f 100644 --- a/lib/fts.c +++ b/lib/fts.c @@ -45,9 +45,7 @@ * SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -# include -#endif +#include #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; @@ -72,31 +70,33 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; #include #if ! _LIBC +# include "fcntl--.h" # include "lstat.h" +# include "openat.h" +# include "unistd--.h" +#endif + +#include +#ifndef _D_EXACT_NAMLEN +# define _D_EXACT_NAMLEN(dirent) strlen ((dirent)->d_name) #endif -#if defined _LIBC -# include -# define NAMLEN(dirent) _D_EXACT_NAMLEN (dirent) +#if HAVE_STRUCT_DIRENT_D_TYPE +/* True if the type of the directory entry D is known. */ +# define DT_IS_KNOWN(d) ((d)->d_type != DT_UNKNOWN) +/* True if the type of the directory entry D must be T. */ +# define DT_MUST_BE(d, t) ((d)->d_type == (t)) #else -# if HAVE_DIRENT_H -# include -# define NAMLEN(dirent) strlen ((dirent)->d_name) -# else -# define dirent direct -# define NAMLEN(dirent) (dirent)->d_namlen -# if HAVE_SYS_NDIR_H -# include -# endif -# if HAVE_SYS_DIR_H -# include -# endif -# if HAVE_NDIR_H -# include -# endif -# endif +# define DT_IS_KNOWN(d) false +# define DT_MUST_BE(d, t) false #endif +enum +{ + FTS_NO_STAT_REQUIRED = 1, + FTS_STAT_REQUIRED = 2 +}; + #ifdef _LIBC # undef close # define close __close @@ -129,6 +129,13 @@ static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94"; # define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) #endif +/* If this host provides the openat function, then we can avoid + attempting to open "." in some initialization code below. */ +#ifdef HAVE_OPENAT +# define HAVE_OPENAT_SUPPORT 1 +#else +# define HAVE_OPENAT_SUPPORT 0 +#endif static FTSENT *fts_alloc (FTS *, const char *, size_t) internal_function; static FTSENT *fts_build (FTS *, int) internal_function; @@ -165,12 +172,21 @@ static void free_dir (FTS *fts) {} #endif #define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2]))) +#define STREQ(a, b) (strcmp ((a), (b)) == 0) #define CLR(opt) (sp->fts_options &= ~(opt)) #define ISSET(opt) (sp->fts_options & (opt)) #define SET(opt) (sp->fts_options |= (opt)) -#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd)) +#define RESTORE_INITIAL_CWD(sp) FCHDIR (sp, (ISSET (FTS_CWDFD) \ + ? AT_FDCWD \ + : sp->fts_rfd)) + +#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) \ + && (ISSET(FTS_CWDFD) \ + ? (cwd_advance_fd (sp, fd), 0) \ + : fchdir(fd))) + /* fts_build flags */ #define BCHILD 1 /* fts_children */ @@ -195,15 +211,69 @@ bool fts_debug = false; } \ while (false) +/* Overload the fts_statp->st_size member (otherwise unused, when + fts_info is FTS_NSOK) to indicate whether fts_read should stat + this entry or not. */ +static void +fts_set_stat_required (FTSENT *p, bool required) +{ + if (p->fts_info != FTS_NSOK) + abort (); + p->fts_statp->st_size = (required + ? FTS_STAT_REQUIRED + : FTS_NO_STAT_REQUIRED); +} + +/* file-descriptor-relative opendir. */ +/* FIXME: if others need this function, move it into lib/openat.c */ +static inline DIR * +internal_function +opendirat (int fd, char const *dir) +{ + int new_fd = openat (fd, dir, O_RDONLY); + DIR *dirp; + + if (new_fd < 0) + return NULL; + dirp = fdopendir (new_fd); + if (dirp == NULL) + { + int saved_errno = errno; + close (new_fd); + errno = saved_errno; + } + return dirp; +} + +/* Virtual fchdir. Advance SP's working directory + file descriptor, SP->fts_cwd_fd, to FD, and close + the previous one, ignoring any error. */ +static void +internal_function +cwd_advance_fd (FTS *sp, int fd) +{ + int old = sp->fts_cwd_fd; + if (old == fd && old != AT_FDCWD) + abort (); + sp->fts_cwd_fd = fd; + if (0 <= old) + close (old); /* ignore any close failure */ +} + /* Open the directory DIR if possible, and return a file descriptor. Return -1 and set errno on failure. It doesn't matter whether the file descriptor has read or write access. */ -static int +static inline int internal_function -diropen (char const *dir) +diropen (FTS const *sp, char const *dir) { - return open (dir, O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK); + int open_flags = (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK + | (ISSET (FTS_PHYSICAL) ? O_NOFOLLOW : 0)); + + return (ISSET (FTS_CWDFD) + ? openat (sp->fts_cwd_fd, dir, open_flags) + : open (dir, open_flags)); } FTS * @@ -214,14 +284,24 @@ fts_open (char * const *argv, register FTS *sp; register FTSENT *p, *root; register size_t nitems; - FTSENT *parent, *tmp = NULL; /* pacify gcc */ + FTSENT *parent = NULL; + FTSENT *tmp = NULL; /* pacify gcc */ size_t len; + bool defer_stat; /* Options check. */ if (options & ~FTS_OPTIONMASK) { __set_errno (EINVAL); return (NULL); } + if ((options & FTS_NOCHDIR) && (options & FTS_CWDFD)) { + __set_errno (EINVAL); + return (NULL); + } + if ( ! (options & (FTS_LOGICAL | FTS_PHYSICAL))) { + __set_errno (EINVAL); + return (NULL); + } /* Allocate/initialize the stream */ if ((sp = malloc(sizeof(FTS))) == NULL) @@ -231,8 +311,44 @@ fts_open (char * const *argv, sp->fts_options = options; /* Logical walks turn on NOCHDIR; symbolic links are too hard. */ - if (ISSET(FTS_LOGICAL)) + if (ISSET(FTS_LOGICAL)) { SET(FTS_NOCHDIR); + CLR(FTS_CWDFD); + } + + /* Initialize fts_cwd_fd. */ + sp->fts_cwd_fd = AT_FDCWD; + if ( ISSET(FTS_CWDFD) && ! HAVE_OPENAT_SUPPORT) + { + /* While it isn't technically necessary to open "." this + early, doing it here saves us the trouble of ensuring + later (where it'd be messier) that "." can in fact + be opened. If not, revert to FTS_NOCHDIR mode. */ + int fd = open (".", O_RDONLY); + if (fd < 0) + { + /* Even if `.' is unreadable, don't revert to FTS_NOCHDIR mode + on systems like Linux+PROC_FS, where our openat emulation + is good enough. Note: on a system that emulates + openat via /proc, this technique can still fail, but + only in extreme conditions, e.g., when the working + directory cannot be saved (i.e. save_cwd fails) -- + and that happens on Linux only when "." is unreadable + and the CWD would be longer than PATH_MAX. + FIXME: once Linux kernel openat support is well established, + replace the above open call and this entire if/else block + with the body of the if-block below. */ + if ( openat_needs_fchdir ()) + { + SET(FTS_NOCHDIR); + CLR(FTS_CWDFD); + } + } + else + { + close (fd); + } + } /* * Start out with 1K of file name space, and enough, in any case, @@ -241,14 +357,31 @@ fts_open (char * const *argv, #ifndef MAXPATHLEN # define MAXPATHLEN 1024 #endif - size_t maxarglen = fts_maxarglen(argv); - if (! fts_palloc(sp, MAX(maxarglen, MAXPATHLEN))) - goto mem1; + { + size_t maxarglen = fts_maxarglen(argv); + if (! fts_palloc(sp, MAX(maxarglen, MAXPATHLEN))) + goto mem1; + } /* Allocate/initialize root's parent. */ - if ((parent = fts_alloc(sp, "", 0)) == NULL) - goto mem2; - parent->fts_level = FTS_ROOTPARENTLEVEL; + if (*argv != NULL) { + if ((parent = fts_alloc(sp, "", 0)) == NULL) + goto mem2; + parent->fts_level = FTS_ROOTPARENTLEVEL; + } + + /* The classic fts implementation would call fts_stat with + a new entry for each iteration of the loop below. + If the comparison function is not specified or if the + FTS_DEFER_STAT option is in effect, don't stat any entry + in this loop. This is an attempt to minimize the interval + between the initial stat/lstat/fstatat and the point at which + a directory argument is first opened. This matters for any + directory command line argument that resides on a file system + without genuine i-nodes. If you specify FTS_DEFER_STAT along + with a comparison function, that function must not access any + data via the fts_statp pointer. */ + defer_stat = (compar == NULL || ISSET(FTS_DEFER_STAT)); /* Allocate/initialize root(s). */ for (root = NULL, nitems = 0; *argv != NULL; ++argv, ++nitems) { @@ -263,11 +396,15 @@ fts_open (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) != 0); - - /* Command-line "." and ".." are real directories. */ - if (p->fts_info == FTS_DOT) - p->fts_info = FTS_D; + /* Even when defer_stat is true, be sure to stat the first + command line argument, since fts_read (at least with + FTS_XDEV) requires that. */ + if (defer_stat && root != NULL) { + p->fts_info = FTS_NSOK; + fts_set_stat_required(p, true); + } else { + p->fts_info = fts_stat(sp, p, false); + } /* * If comparison routine supplied, traverse in sorted @@ -299,7 +436,7 @@ fts_open (char * const *argv, sp->fts_cur->fts_link = root; sp->fts_cur->fts_info = FTS_INIT; if (! setup_dir (sp)) - goto mem3; + goto mem3; /* * If using chdir(2), grab a file descriptor pointing to dot to ensure @@ -308,8 +445,8 @@ fts_open (char * const *argv, * and ".." are all fairly nasty problems. Note, if we can't get the * descriptor we run anyway, just more slowly. */ - if (!ISSET(FTS_NOCHDIR) - && (sp->fts_rfd = diropen (".")) < 0) + if (!ISSET(FTS_NOCHDIR) && !ISSET(FTS_CWDFD) + && (sp->fts_rfd = diropen (sp, ".")) < 0) SET(FTS_NOCHDIR); return (sp); @@ -369,16 +506,21 @@ fts_close (FTS *sp) /* Free up child linked list, sort array, file name buffer. */ if (sp->fts_child) fts_lfree(sp->fts_child); - if (sp->fts_array) - free(sp->fts_array); + free(sp->fts_array); free(sp->fts_path); - /* Return to original directory, save errno if necessary. */ - if (!ISSET(FTS_NOCHDIR)) { - if (fchdir(sp->fts_rfd)) - saved_errno = errno; - (void)close(sp->fts_rfd); - } + if (ISSET(FTS_CWDFD)) + { + if (0 <= sp->fts_cwd_fd) + close (sp->fts_cwd_fd); + } + else if (!ISSET(FTS_NOCHDIR)) + { + /* Return to original directory, save errno if necessary. */ + if (fchdir(sp->fts_rfd)) + saved_errno = errno; + close(sp->fts_rfd); + } free_dir (sp); @@ -408,7 +550,6 @@ fts_read (register FTS *sp) register FTSENT *p, *tmp; register unsigned short int instr; register char *t; - int saved_errno; /* If finished or unrecoverable error, return NULL. */ if (sp->fts_cur == NULL || ISSET(FTS_STOP)) @@ -439,7 +580,7 @@ fts_read (register FTS *sp) (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) { p->fts_info = fts_stat(sp, p, true); if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { - if ((p->fts_symfd = diropen (".")) < 0) { + if ((p->fts_symfd = diropen (sp, ".")) < 0) { p->fts_errno = errno; p->fts_info = FTS_ERR; } else @@ -500,7 +641,6 @@ fts_read (register FTS *sp) subdirectory, tell the caller. */ if (p->fts_errno) p->fts_info = FTS_ERR; - /* FIXME: see if this should be in an else block */ LEAVE_DIR (sp, p, "2"); return (p); } @@ -520,8 +660,9 @@ next: tmp = p; * root. */ if (p->fts_level == FTS_ROOTLEVEL) { - if (FCHDIR(sp, sp->fts_rfd)) { + if (RESTORE_INITIAL_CWD(sp)) { SET(FTS_STOP); + sp->fts_cur = p; return (NULL); } fts_load(sp, p); @@ -538,7 +679,7 @@ next: tmp = p; if (p->fts_instr == FTS_FOLLOW) { p->fts_info = fts_stat(sp, p, true); if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) { - if ((p->fts_symfd = diropen (".")) < 0) { + if ((p->fts_symfd = diropen (sp, ".")) < 0) { p->fts_errno = errno; p->fts_info = FTS_ERR; } else @@ -551,6 +692,19 @@ name: t = sp->fts_path + NAPPEND(p->fts_parent); *t++ = '/'; memmove(t, p->fts_name, p->fts_namelen + 1); check_for_dir: + if (p->fts_info == FTS_NSOK) + { + switch (p->fts_statp->st_size) + { + case FTS_STAT_REQUIRED: + p->fts_info = fts_stat(sp, p, false); + break; + case FTS_NO_STAT_REQUIRED: + break; + default: + abort (); + } + } sp->fts_cur = p; if (p->fts_info == FTS_D) { @@ -578,22 +732,26 @@ check_for_dir: return (sp->fts_cur = NULL); } + if (p->fts_info == FTS_NSOK) + abort (); + /* NUL terminate the file name. */ sp->fts_path[p->fts_pathlen] = '\0'; /* - * Return to the parent directory. If at a root node or came through - * a symlink, go back through the file descriptor. Otherwise, cd up - * one directory. + * Return to the parent directory. If at a root node, restore + * the initial working directory. If we came through a symlink, + * go back through the file descriptor. Otherwise, move up + * one level, via "..". */ if (p->fts_level == FTS_ROOTLEVEL) { - if (FCHDIR(sp, sp->fts_rfd)) { + if (RESTORE_INITIAL_CWD(sp)) { p->fts_errno = errno; SET(FTS_STOP); } } else if (p->fts_flags & FTS_SYMFOLLOW) { if (FCHDIR(sp, p->fts_symfd)) { - saved_errno = errno; + int saved_errno = errno; (void)close(p->fts_symfd); __set_errno (saved_errno); p->fts_errno = errno; @@ -688,14 +846,24 @@ fts_children (register FTS *sp, int instr) ISSET(FTS_NOCHDIR)) return (sp->fts_child = fts_build(sp, instr)); - if ((fd = diropen (".")) < 0) + if ((fd = diropen (sp, ".")) < 0) return (sp->fts_child = NULL); sp->fts_child = fts_build(sp, instr); - if (fchdir(fd)) { - (void)close(fd); - return (NULL); - } - (void)close(fd); + if (ISSET(FTS_CWDFD)) + { + cwd_advance_fd (sp, fd); + } + else + { + if (fchdir(fd)) + { + int saved_errno = errno; + close (fd); + __set_errno (saved_errno); + return NULL; + } + close (fd); + } return (sp->fts_child); } @@ -723,7 +891,6 @@ fts_build (register FTS *sp, int type) FTSENT *cur, *tail; DIR *dirp; void *oldaddr; - int cderrno; int saved_errno; bool descend; bool doadjust; @@ -746,7 +913,10 @@ fts_build (register FTS *sp, int type) else oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND; #else -# define __opendir2(file, flag) opendir(file) +# define __opendir2(file, flag) \ + ( ! ISSET(FTS_NOCHDIR) && ISSET(FTS_CWDFD) \ + ? opendirat(sp->fts_cwd_fd, file) \ + : opendir(file)) #endif if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) { if (type == BREAD) { @@ -755,6 +925,11 @@ fts_build (register FTS *sp, int type) } return (NULL); } + /* Rather than calling fts_stat for each and every entry encountered + in the readdir loop (below), stat each directory only right after + opening it. */ + if (cur->fts_info == FTS_NSOK) + cur->fts_info = fts_stat(sp, cur, false); /* * Nlinks is the number of possible entries of type directory in the @@ -789,15 +964,18 @@ fts_build (register FTS *sp, int type) * needed sorted entries or stat information, they had better be * checking FTS_NS on the returned nodes. */ - cderrno = 0; if (nlinks || type == BREAD) { - if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) { + int dir_fd = dirfd(dirp); + if (ISSET(FTS_CWDFD) && 0 <= dir_fd) + dir_fd = dup (dir_fd); + if (dir_fd < 0 || fts_safe_changedir(sp, cur, dir_fd, NULL)) { if (nlinks && type == BREAD) cur->fts_errno = errno; cur->fts_flags |= FTS_DONTCHDIR; descend = false; - cderrno = errno; closedir(dirp); + if (ISSET(FTS_CWDFD) && 0 <= dir_fd) + close (dir_fd); dirp = NULL; } else descend = true; @@ -830,22 +1008,25 @@ fts_build (register FTS *sp, int type) /* Read the directory, attaching each entry to the `link' pointer. */ doadjust = false; for (head = tail = NULL, nitems = 0; dirp && (dp = readdir(dirp));) { + bool is_dir; + if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name)) continue; - if ((p = fts_alloc(sp, dp->d_name, NAMLEN (dp))) == NULL) + if ((p = fts_alloc (sp, dp->d_name, + _D_EXACT_NAMLEN (dp))) == NULL) goto mem1; - if (NAMLEN (dp) >= maxlen) {/* include space for NUL */ + if (_D_EXACT_NAMLEN (dp) >= maxlen) { + /* include space for NUL */ oldaddr = sp->fts_path; - if (! fts_palloc(sp, NAMLEN (dp) + len + 1)) { + if (! fts_palloc(sp, _D_EXACT_NAMLEN (dp) + len + 1)) { /* * No more memory. Save * errno, free up the current structure and the * structures already allocated. */ mem1: saved_errno = errno; - if (p) - free(p); + free(p); fts_lfree(head); closedir(dirp); cur->fts_info = FTS_ERR; @@ -862,7 +1043,7 @@ mem1: saved_errno = errno; maxlen = sp->fts_pathlen - len; } - new_len = len + NAMLEN (dp); + new_len = len + _D_EXACT_NAMLEN (dp); if (new_len < len) { /* * In the unlikely even that we would end up @@ -887,38 +1068,45 @@ mem1: saved_errno = errno; p->fts_flags |= FTS_ISW; #endif - if (cderrno) { - if (nlinks) { - p->fts_info = FTS_NS; - p->fts_errno = cderrno; - } else - p->fts_info = FTS_NSOK; - p->fts_accpath = cur->fts_accpath; - } else if (nlinks == 0 -#if HAVE_STRUCT_DIRENT_D_TYPE - || (nostat && - dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN) -#endif - ) { - p->fts_accpath = - ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name; + /* Build a file name for fts_stat to stat. */ + if (ISSET(FTS_NOCHDIR)) { + p->fts_accpath = p->fts_path; + memmove(cp, p->fts_name, p->fts_namelen + 1); + } else + p->fts_accpath = p->fts_name; + + if (sp->fts_compar == NULL || ISSET(FTS_DEFER_STAT)) { + /* Record what fts_read will have to do with this + entry. In many cases, it will simply fts_stat it, + but we can take advantage of any d_type information + to optimize away the unnecessary stat calls. I.e., + if FTS_NOSTAT is in effect and we're not following + symlinks (FTS_PHYSICAL) and d_type indicates this + is *not* a directory, then we won't have to stat it + at all. If it *is* a directory, then (currently) + we stat it regardless, in order to get device and + inode numbers. Some day we might optimize that + away, too, for directories where d_ino is known to + be valid. */ + bool skip_stat = (ISSET(FTS_PHYSICAL) + && ISSET(FTS_NOSTAT) + && DT_IS_KNOWN(dp) + && ! DT_MUST_BE(dp, DT_DIR)); p->fts_info = FTS_NSOK; + fts_set_stat_required(p, !skip_stat); + is_dir = (ISSET(FTS_PHYSICAL) && ISSET(FTS_NOSTAT) + && DT_MUST_BE(dp, DT_DIR)); } else { - /* Build a file name for fts_stat to stat. */ - if (ISSET(FTS_NOCHDIR)) { - p->fts_accpath = p->fts_path; - memmove(cp, p->fts_name, p->fts_namelen + 1); - } else - p->fts_accpath = p->fts_name; - /* Stat it. */ p->fts_info = fts_stat(sp, p, false); - - /* Decrement link count if applicable. */ - if (nlinks > 0 && (p->fts_info == FTS_D || - p->fts_info == FTS_DC || p->fts_info == FTS_DOT)) - nlinks -= nostat; + is_dir = (p->fts_info == FTS_D + || p->fts_info == FTS_DC + || p->fts_info == FTS_DOT); } + /* Decrement link count if applicable. */ + if (nlinks > 0 && is_dir) + nlinks -= nostat; + /* We walk in directory order so "ls -f" doesn't get upset. */ p->fts_link = NULL; if (head == NULL) @@ -957,11 +1145,12 @@ mem1: saved_errno = errno; * can't get back, we're done. */ if (descend && (type == BCHILD || !nitems) && - (cur->fts_level == FTS_ROOTLEVEL ? - FCHDIR(sp, sp->fts_rfd) : - fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) { + (cur->fts_level == FTS_ROOTLEVEL + ? RESTORE_INITIAL_CWD(sp) + : fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) { cur->fts_info = FTS_ERR; SET(FTS_STOP); + fts_lfree(head); return (NULL); } @@ -969,6 +1158,7 @@ mem1: saved_errno = errno; if (!nitems) { if (type == BREAD) cur->fts_info = FTS_DP; + fts_lfree(head); return (NULL); } @@ -1048,6 +1238,9 @@ fts_stat(FTS *sp, register FTSENT *p, bool follow) struct stat *sbp = p->fts_statp; int saved_errno; + if (p->fts_level == FTS_ROOTLEVEL && ISSET(FTS_COMFOLLOW)) + follow = true; + #if defined FTS_WHITEOUT && 0 /* check for whiteout */ if (p->fts_flags & FTS_ISW) { @@ -1065,22 +1258,26 @@ fts_stat(FTS *sp, register FTSENT *p, bool follow) if (ISSET(FTS_LOGICAL) || follow) { if (stat(p->fts_accpath, sbp)) { saved_errno = errno; - if (!lstat(p->fts_accpath, sbp)) { + if (errno == ENOENT + && lstat(p->fts_accpath, sbp) == 0) { __set_errno (0); return (FTS_SLNONE); } p->fts_errno = saved_errno; goto err; } - } else if (lstat(p->fts_accpath, sbp)) { + } else if (fstatat(sp->fts_cwd_fd, p->fts_accpath, sbp, + AT_SYMLINK_NOFOLLOW)) { p->fts_errno = errno; err: memset(sbp, 0, sizeof(struct stat)); return (FTS_NS); } if (S_ISDIR(sbp->st_mode)) { - if (ISDOT(p->fts_name)) - return (FTS_DOT); + if (ISDOT(p->fts_name)) { + /* Command-line "." and ".." are real directories. */ + return (p->fts_level == FTS_ROOTLEVEL ? FTS_D : FTS_DOT); + } #if _LGPL_PACKAGE { @@ -1236,10 +1433,7 @@ fts_palloc (FTS *sp, size_t more) * See if fts_pathlen would overflow. */ if (new_len < sp->fts_pathlen) { - if (sp->fts_path) { - free(sp->fts_path); - sp->fts_path = NULL; - } + free(sp->fts_path); sp->fts_path = NULL; __set_errno (ENAMETOOLONG); return false; @@ -1300,34 +1494,64 @@ fts_maxarglen (char * const *argv) * Change to dir specified by fd or file name without getting * tricked by someone changing the world out from underneath us. * Assumes p->fts_statp->st_dev and p->fts_statp->st_ino are filled in. + * If FD is non-negative, expect it to be used after this function returns, + * and to be closed eventually. So don't pass e.g., `dirfd(dirp)' and then + * do closedir(dirp), because that would invalidate the saved FD. + * Upon failure, close FD immediately and return nonzero. */ static int internal_function fts_safe_changedir (FTS *sp, FTSENT *p, int fd, char const *dir) { - int ret, oerrno, newfd; - struct stat sb; + int ret; - newfd = fd; - if (ISSET(FTS_NOCHDIR)) + int newfd = fd; + if (ISSET(FTS_NOCHDIR)) { + if (ISSET(FTS_CWDFD) && 0 <= fd) + close (fd); return (0); - if (fd < 0 && (newfd = diropen (dir)) < 0) + } + if (fd < 0 && (newfd = diropen (sp, dir)) < 0) return (-1); - if (fstat(newfd, &sb)) { + + /* The following dev/inode check is necessary if we're doing + a `logical' traversal (through symlinks, a la chown -L), + if the system lacks O_NOFOLLOW support, or if we're changing + to "..". In the latter case, O_NOFOLLOW can't help. In + general (when the target is not ".."), diropen's use of + O_NOFOLLOW ensures we don't mistakenly follow a symlink, + so we can avoid the expense of this fstat. */ + if (ISSET(FTS_LOGICAL) || ! HAVE_WORKING_O_NOFOLLOW + || (dir && STREQ (dir, ".."))) + { + struct stat sb; + if (fstat(newfd, &sb)) + { ret = -1; goto bail; - } - if (p->fts_statp->st_dev != sb.st_dev - || p->fts_statp->st_ino != sb.st_ino) { + } + if (p->fts_statp->st_dev != sb.st_dev + || p->fts_statp->st_ino != sb.st_ino) + { __set_errno (ENOENT); /* disinformation */ ret = -1; goto bail; - } + } + } + + if (ISSET(FTS_CWDFD)) + { + cwd_advance_fd (sp, newfd); + return 0; + } + ret = fchdir(newfd); bail: - oerrno = errno; if (fd < 0) - (void)close(newfd); - __set_errno (oerrno); + { + int oerrno = errno; + (void)close(newfd); + __set_errno (oerrno); + } return (ret); }