fts: add/use new struct member, fts_dirp
[gnulib.git] / lib / fts.c
index 8e6e728..4d07d09 100644 (file)
--- a/lib/fts.c
+++ b/lib/fts.c
@@ -1,6 +1,6 @@
 /* Traverse a file hierarchy.
 
-   Copyright (C) 2004-2009 Free Software Foundation, Inc.
+   Copyright (C) 2004-2011 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -160,8 +160,6 @@ enum Fts_stat
 # define fchdir __fchdir
 # undef open
 # define open __open
-# undef opendir
-# define opendir __opendir
 # undef readdir
 # define readdir __readdir
 #else
@@ -205,14 +203,7 @@ static unsigned short int fts_stat (FTS *, FTSENT *, bool) internal_function;
 static int      fts_safe_changedir (FTS *, FTSENT *, int, const char *)
      internal_function;
 
-#if GNULIB_FTS
-# include "fts-cycle.c"
-#else
-static bool enter_dir (FTS *fts, FTSENT *ent) { return true; }
-static void leave_dir (FTS *fts, FTSENT *ent) {}
-static bool setup_dir (FTS *fts) { return true; }
-static void free_dir (FTS *fts) {}
-#endif
+#include "fts-cycle.c"
 
 #ifndef MAX
 # define MAX(a,b) ((a) > (b) ? (a) : (b))
@@ -223,7 +214,7 @@ 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 STREQ(a, b)     (strcmp (a, b) == 0)
 
 #define CLR(opt)        (sp->fts_options &= ~(opt))
 #define ISSET(opt)      (sp->fts_options & (opt))
@@ -297,17 +288,20 @@ fts_set_stat_required (FTSENT *p, bool required)
 /* FIXME: if others need this function, move it into lib/openat.c */
 static inline DIR *
 internal_function
-opendirat (int fd, char const *dir)
+opendirat (int fd, char const *dir, int extra_flags, int *pdir_fd)
 {
   int new_fd = openat (fd, dir,
-                       O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK);
+                       (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
+                        | extra_flags));
   DIR *dirp;
 
   if (new_fd < 0)
     return NULL;
   set_cloexec_flag (new_fd, true);
   dirp = fdopendir (new_fd);
-  if (dirp == NULL)
+  if (dirp)
+    *pdir_fd = new_fd;
+  else
     {
       int saved_errno = errno;
       close (new_fd);
@@ -354,8 +348,9 @@ static inline int
 internal_function
 diropen (FTS const *sp, char const *dir)
 {
-  int open_flags = (O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
-                    | (ISSET (FTS_PHYSICAL) ? O_NOFOLLOW : 0));
+  int open_flags = (O_SEARCH | O_DIRECTORY | O_NOCTTY | O_NONBLOCK
+                    | (ISSET (FTS_PHYSICAL) ? O_NOFOLLOW : 0)
+                    | (ISSET (FTS_NOATIME) ? O_NOATIME : 0));
 
   int fd = (ISSET (FTS_CWDFD)
             ? openat (sp->fts_cwd_fd, dir, open_flags)
@@ -412,7 +407,8 @@ fts_open (char * const *argv,
                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);
+            int fd = open (".",
+                           O_SEARCH | (ISSET (FTS_NOATIME) ? O_NOATIME : 0));
             if (fd < 0)
               {
                 /* Even if `.' is unreadable, don't revert to FTS_NOCHDIR mode
@@ -614,10 +610,8 @@ fts_close (FTS *sp)
 
         fd_ring_clear (&sp->fts_fd_ring);
 
-#if GNULIB_FTS
         if (sp->fts_leaf_optimization_works_ht)
           hash_free (sp->fts_leaf_optimization_works_ht);
-#endif
 
         free_dir (sp);
 
@@ -636,7 +630,7 @@ fts_close (FTS *sp)
 #if defined __linux__ \
   && HAVE_SYS_VFS_H && HAVE_FSTATFS && HAVE_STRUCT_STATFS_F_TYPE
 
-#include <sys/vfs.h>
+# include <sys/vfs.h>
 
 /* Linux-specific constants from coreutils' src/fs.h */
 # define S_MAGIC_TMPFS 0x1021994
@@ -717,9 +711,8 @@ static bool
 leaf_optimization_applies (int dir_fd _GL_UNUSED) { return false; }
 #endif
 
-#if GNULIB_FTS
 /* link-count-optimization entry:
-   map an stat.st_dev number to a boolean: leaf_optimization_works */
+   map a stat.st_dev number to a boolean: leaf_optimization_works */
 struct LCO_ent
 {
   dev_t st_dev;
@@ -799,7 +792,6 @@ link_count_optimize_ok (FTSENT const *p)
 
   return opt_ok;
 }
-#endif
 
 /*
  * Special case of "/" at the end of the file name so that slashes aren't
@@ -1200,6 +1192,25 @@ set_stat_type (struct stat *st, unsigned int dtype)
   st->st_mode = type;
 }
 
+#define closedir_and_clear(dirp)                \
+  do                                            \
+    {                                           \
+      closedir (dirp);                          \
+      dirp = NULL;                              \
+    }                                           \
+  while (0)
+
+#define fts_opendir(file, Pdir_fd)                              \
+        opendirat((! ISSET(FTS_NOCHDIR) && ISSET(FTS_CWDFD)     \
+                   ? sp->fts_cwd_fd : AT_FDCWD),                \
+                  file,                                         \
+                  (((ISSET(FTS_PHYSICAL)                        \
+                     && ! (ISSET(FTS_COMFOLLOW)                 \
+                           && cur->fts_level == FTS_ROOTLEVEL)) \
+                    ? O_NOFOLLOW : 0)                           \
+                   | (ISSET (FTS_NOATIME) ? O_NOATIME : 0)),    \
+                  Pdir_fd)
+
 /*
  * This is the tricky part -- do not casually change *anything* in here.  The
  * idea is to build the linked list of entries that are used by fts_children
@@ -1221,8 +1232,7 @@ fts_build (register FTS *sp, int type)
         register struct dirent *dp;
         register FTSENT *p, *head;
         register size_t nitems;
-        FTSENT *cur, *tail;
-        DIR *dirp;
+        FTSENT *tail;
         void *oldaddr;
         int saved_errno;
         bool descend;
@@ -1232,57 +1242,42 @@ fts_build (register FTS *sp, int type)
         bool nostat;
         size_t len, maxlen, new_len;
         char *cp;
+        int dir_fd;
+        FTSENT *cur = sp->fts_cur;
 
-        /* Set current node pointer. */
-        cur = sp->fts_cur;
-
-        /*
-         * Open the directory for reading.  If this fails, we're done.
-         * If being called from fts_read, set the fts_info field.
-         */
-#if defined FTS_WHITEOUT && 0
-        if (ISSET(FTS_WHITEOUT))
-                oflag = DTF_NODUP|DTF_REWIND;
-        else
-                oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND;
-#else
-# 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) {
+        /* Open the directory for reading.  If this fails, we're done.
+           If being called from fts_read, set the fts_info field.  */
+        if ((cur->fts_dirp = fts_opendir(cur->fts_accpath, &dir_fd)) == NULL) {
                 if (type == BREAD) {
                         cur->fts_info = FTS_DNR;
                         cur->fts_errno = errno;
                 }
                 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);
-       else if (sp->fts_options & FTS_TIGHT_CYCLE_CHECK) {
-                /* Now read the stat info again after opening a directory to
-                 * reveal eventual changes caused by a submount triggered by
-                 * the traversal.  But do it only for utilities which use
-                 * FTS_TIGHT_CYCLE_CHECK.  Therefore, only find and du
-                 * benefit/suffer from this feature for now.
-                 */
-                LEAVE_DIR (sp, cur, "4");
-                fts_stat (sp, cur, false);
-                if (! enter_dir (sp, cur)) {
-                         __set_errno (ENOMEM);
-                         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);
+        else if (sp->fts_options & FTS_TIGHT_CYCLE_CHECK)
+          {
+            /* Now read the stat info again after opening a directory to
+               reveal eventual changes caused by a submount triggered by
+               the traversal.  But do it only for utilities which use
+               FTS_TIGHT_CYCLE_CHECK.  Therefore, only find and du
+               benefit/suffer from this feature for now. */
+            LEAVE_DIR (sp, cur, "4");
+            fts_stat (sp, cur, false);
+            if (! enter_dir (sp, cur))
+              {
+                __set_errno (ENOMEM);
+                return NULL;
+              }
+          }
 
-        /*
-         * Nlinks is the number of possible entries of type directory in the
-         * directory if we're cheating on stat calls, 0 if we're not doing
-         * any stat calls at all, (nlink_t) -1 if we're statting everything.
-         */
+        /* Nlinks is the number of possible entries of type directory in the
+           directory if we're cheating on stat calls, 0 if we're not doing
+           any stat calls at all, (nlink_t) -1 if we're statting everything.  */
         if (type == BNAMES) {
                 nlinks = 0;
                 /* Be quiet about nostat, GCC. */
@@ -1312,21 +1307,21 @@ fts_build (register FTS *sp, int type)
          * checking FTS_NS on the returned nodes.
          */
         if (nlinks || type == BREAD) {
-                int dir_fd = dirfd(dirp);
-                if (ISSET(FTS_CWDFD) && 0 <= dir_fd)
+                if (ISSET(FTS_CWDFD))
                   {
                     dir_fd = dup (dir_fd);
-                    set_cloexec_flag (dir_fd, true);
+                    if (0 <= dir_fd)
+                      set_cloexec_flag (dir_fd, true);
                   }
                 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;
-                        closedir(dirp);
+                        closedir_and_clear(cur->fts_dirp);
                         if (ISSET(FTS_CWDFD) && 0 <= dir_fd)
                                 close (dir_fd);
-                        dirp = NULL;
+                        cur->fts_dirp = NULL;
                 } else
                         descend = true;
         } else
@@ -1357,7 +1352,7 @@ 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));) {
+        for (head = tail = NULL, nitems = 0; cur->fts_dirp && (dp = readdir(cur->fts_dirp));) {
                 bool is_dir;
 
                 if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
@@ -1378,7 +1373,7 @@ fts_build (register FTS *sp, int type)
 mem1:                           saved_errno = errno;
                                 free(p);
                                 fts_lfree(head);
-                                closedir(dirp);
+                                closedir_and_clear(cur->fts_dirp);
                                 cur->fts_info = FTS_ERR;
                                 SET(FTS_STOP);
                                 __set_errno (saved_errno);
@@ -1403,7 +1398,7 @@ mem1:                           saved_errno = errno;
                          */
                         free(p);
                         fts_lfree(head);
-                        closedir(dirp);
+                        closedir_and_clear(cur->fts_dirp);
                         cur->fts_info = FTS_ERR;
                         SET(FTS_STOP);
                         __set_errno (ENAMETOOLONG);
@@ -1413,10 +1408,6 @@ mem1:                           saved_errno = errno;
                 p->fts_parent = sp->fts_cur;
                 p->fts_pathlen = new_len;
 
-#if defined FTS_WHITEOUT && 0
-                if (dp->d_type == DT_WHT)
-                        p->fts_flags |= FTS_ISW;
-#endif
                 /* Store dirent.d_ino, in case we need to sort
                    entries before processing them.  */
                 p->fts_statp->st_ino = D_INO (dp);
@@ -1473,8 +1464,8 @@ mem1:                           saved_errno = errno;
                 }
                 ++nitems;
         }
-        if (dirp)
-                closedir(dirp);
+        if (cur->fts_dirp)
+                closedir_and_clear(cur->fts_dirp);
 
         /*
          * If realloc() changed the address of the file name, adjust the
@@ -1657,7 +1648,7 @@ fd_ring_check (FTS const *sp)
       int fd = i_ring_pop (&fd_w);
       if (0 <= fd)
         {
-          int parent_fd = openat (cwd_fd, "..", O_RDONLY);
+          int parent_fd = openat (cwd_fd, "..", O_SEARCH | O_NOATIME);
           if (parent_fd < 0)
             {
               // Warn?
@@ -1691,15 +1682,6 @@ fts_stat(FTS *sp, register FTSENT *p, bool follow)
         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) {
-                memset(sbp, '\0', sizeof (*sbp));
-                sbp->st_mode = S_IFWHT;
-                return (FTS_W);
-       }
-#endif
-
         /*
          * If doing a logical walk, or application requested FTS_FOLLOW, do
          * a stat(2).  If that fails, check for a non-existent symlink.  If
@@ -1731,27 +1713,6 @@ err:            memset(sbp, 0, sizeof(struct stat));
                         return (p->fts_level == FTS_ROOTLEVEL ? FTS_D : FTS_DOT);
                 }
 
-#if !GNULIB_FTS
-                {
-                  /*
-                   * Cycle detection is done by brute force when the directory
-                   * is first encountered.  If the tree gets deep enough or the
-                   * number of symbolic links to directories is high enough,
-                   * something faster might be worthwhile.
-                   */
-                  FTSENT *t;
-
-                  for (t = p->fts_parent;
-                       t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent)
-                    if (sbp->st_ino == t->fts_statp->st_ino
-                        && sbp->st_dev == t->fts_statp->st_dev)
-                      {
-                        p->fts_cycle = t;
-                        return (FTS_DC);
-                      }
-                }
-#endif
-
                 return (FTS_D);
         }
         if (S_ISLNK(sbp->st_mode))
@@ -1847,6 +1808,7 @@ fts_alloc (FTS *sp, const char *name, register size_t namelen)
         p->fts_fts = sp;
         p->fts_path = sp->fts_path;
         p->fts_errno = 0;
+        p->fts_dirp = NULL;
         p->fts_flags = 0;
         p->fts_instr = FTS_NOINSTR;
         p->fts_number = 0;
@@ -1863,6 +1825,8 @@ fts_lfree (register FTSENT *head)
         /* Free a linked list of structures. */
         while ((p = head)) {
                 head = head->fts_link;
+                if (p->fts_dirp)
+                        closedir (p->fts_dirp);
                 free(p);
         }
 }