chmod now uses fts to perform a directory traversal when -R is
authorJim Meyering <jim@meyering.net>
Sun, 19 Oct 2003 18:54:10 +0000 (18:54 +0000)
committerJim Meyering <jim@meyering.net>
Sun, 19 Oct 2003 18:54:10 +0000 (18:54 +0000)
specified.  Before, it operated on full path names, and as such
would encounter the PATH_MAX (often 4096) limit.

Include "xfts.h".
(process_file): Rename from change_file_mode.
Adapt to be used with fts.
(process_files): New function.

src/chmod.c

index aae2172d8d05645ea2ed5eeeb2d3103615718f34..5e5cf7d366415a44231adacfbf39612cc87d8bb8 100644 (file)
@@ -30,6 +30,7 @@
 #include "modechange.h"
 #include "quote.h"
 #include "savedir.h"
+#include "xfts.h"
 
 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "chmod"
@@ -55,8 +56,6 @@ enum Verbosity
   V_off
 };
 
-static int change_dir_mode (const char *dir, const struct mode_change *changes);
-
 /* The name the program was run with. */
 char *program_name;
 
@@ -147,114 +146,124 @@ describe_change (const char *file, mode_t mode,
 }
 
 /* Change the mode of FILE according to the list of operations CHANGES.
-   If DEREF_SYMLINK is nonzero and FILE is a symbolic link, change the
-   mode of the referenced file.  If DEREF_SYMLINK is zero, ignore symbolic
-   links.  Return 0 if successful, 1 if errors occurred. */
+   Return 0 if successful, 1 if errors occurred.  This function is called
+   once for every file system object that fts encounters.  */
 
 static int
-change_file_mode (const char *file, const struct mode_change *changes,
-                 const int deref_symlink)
+process_file (FTS *fts, FTSENT *ent, const struct mode_change *changes)
 {
-  struct stat file_stats;
+  const char *file_full_name = ent->fts_path;
+  const char *file = ent->fts_accpath;
+  const struct stat *sb = ent->fts_statp;
   mode_t newmode;
   int errors = 0;
   int fail;
   int saved_errno;
 
-  if (deref_symlink ? stat (file, &file_stats) : lstat (file, &file_stats))
+  switch (ent->fts_info)
     {
-      if (force_silent == 0)
-       error (0, errno, _("failed to get attributes of %s"), quote (file));
+    case FTS_NS:
+      error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
+      return 1;
+
+    case FTS_ERR:
+      /* if (S_ISDIR (ent->fts_statp->st_mode) && FIXME */
+      error (0, ent->fts_errno, _("%s"), quote (file_full_name));
       return 1;
+
+    case FTS_DNR:
+      error (0, ent->fts_errno, _("cannot read directory %s"),
+            quote (file_full_name));
+      return 1;
+
+    default:
+      break;
     }
 
-  if (root_dev_ino && SAME_INODE (file_stats, *root_dev_ino))
+  /* If this is the second (post-order) encounter with a directory,
+     then return right away.  */
+  if (ent->fts_info == FTS_DP)
+    return 0;
+
+  if (root_dev_ino && SAME_INODE (*sb, *root_dev_ino))
     {
-      if (STREQ (file, "/"))
+      if (STREQ (file_full_name, "/"))
        error (0, 0, _("it is dangerous to operate recursively on %s"),
-              quote (file));
+              quote (file_full_name));
       else
        error (0, 0,
               _("it is dangerous to operate recursively on %s (same as %s)"),
-              quote_n (0, file), quote_n (1, "/"));
+              quote_n (0, file_full_name), quote_n (1, "/"));
       error (0, 0, _("use --no-preserve-root to override this failsafe"));
       return 1;
     }
 
-  if (S_ISLNK (file_stats.st_mode))
+  if (S_ISLNK (sb->st_mode))
     return 0;
 
-  newmode = mode_adjust (file_stats.st_mode, changes);
+  newmode = mode_adjust (sb->st_mode, changes);
 
   fail = chmod (file, newmode);
   saved_errno = errno;
 
   if (verbosity == V_high
       || (verbosity == V_changes_only
-         && !fail && mode_changed (file, file_stats.st_mode)))
-    describe_change (file, newmode, (fail ? CH_FAILED : CH_SUCCEEDED));
+         && !fail && mode_changed (file, sb->st_mode)))
+    describe_change (file_full_name, newmode,
+                    (fail ? CH_FAILED : CH_SUCCEEDED));
 
   if (fail)
     {
       if (force_silent == 0)
        error (0, saved_errno, _("changing permissions of %s"),
-              quote (file));
+              quote (file_full_name));
       errors = 1;
     }
 
-  if (recurse && S_ISDIR (file_stats.st_mode))
-    {
-      char *f;
-      ASSIGN_STRDUPA (f, file);
-      strip_trailing_slashes (f);
-      errors |= change_dir_mode (f, changes);
-    }
+  if ( ! recurse)
+    fts_set (fts, ent, FTS_SKIP);
+
   return errors;
 }
 
-/* Recursively change the modes of the files in directory DIR
-   according to the list of operations CHANGES.
-   Return 0 if successful, 1 if errors occurred. */
+/* Recursively change the modes of the specified FILES (the last entry
+   of which is NULL) according to the list of operations CHANGES.
+   BIT_FLAGS controls how fts works.
+   If the fts_open call fails, exit nonzero.
+   Otherwise, return nonzero upon error.  */
 
 static int
-change_dir_mode (const char *dir, const struct mode_change *changes)
+process_files (char **files, int bit_flags, const struct mode_change *changes)
 {
-  char *name_space, *namep;
-  char *path;                  /* Full path of each entry to process. */
-  unsigned dirlength;          /* Length of DIR and '\0'. */
-  unsigned filelength;         /* Length of each pathname to process. */
-  unsigned pathlength;         /* Bytes allocated for `path'. */
-  int errors = 0;
+  int fail = 0;
 
-  name_space = savedir (dir);
-  if (name_space == NULL)
-    {
-      if (force_silent == 0)
-       error (0, errno, "%s", quote (dir));
-      return 1;
-    }
+  FTS *fts = xfts_open (files, bit_flags, NULL);
 
-  dirlength = strlen (dir) + 1;        /* + 1 is for the trailing '/'. */
-  pathlength = dirlength + 1;
-  /* Give `path' a dummy value; it will be reallocated before first use. */
-  path = xmalloc (pathlength);
-  strcpy (path, dir);
-  path[dirlength - 1] = '/';
-
-  for (namep = name_space; *namep; namep += filelength - dirlength)
+  while (1)
     {
-      filelength = dirlength + strlen (namep) + 1;
-      if (filelength > pathlength)
+      FTSENT *ent;
+
+      ent = fts_read (fts);
+      if (ent == NULL)
        {
-         pathlength = filelength * 2;
-         path = xrealloc (path, pathlength);
+         if (errno != 0)
+           {
+             /* FIXME: try to give a better message  */
+             error (0, errno, _("fts_read failed"));
+             fail = 1;
+           }
+         break;
        }
-      strcpy (path + dirlength, namep);
-      errors |= change_file_mode (path, changes, 0);
+
+      fail |= process_file (fts, ent, changes);
     }
-  free (path);
-  free (name_space);
-  return errors;
+
+  /* Ignore failure, since the only way it can do so is in failing to
+     return to the original directory, and since we're about to exit,
+     that doesn't matter.  */
+  fts_close (fts);
+
+  return fail;
 }
 
 void
@@ -315,7 +324,7 @@ int
 main (int argc, char **argv)
 {
   struct mode_change *changes;
-  int errors = 0;
+  int fail = 0;
   int modeind = 0;             /* Index of the mode argument in `argv'. */
   int thisind;
   bool preserve_root = false;
@@ -429,8 +438,7 @@ main (int argc, char **argv)
       root_dev_ino = NULL;
     }
 
-  for (; optind < argc; ++optind)
-    errors |= change_file_mode (argv[optind], changes, 1);
+  fail = process_files (argv + optind, FTS_COMFOLLOW, changes);
 
-  exit (errors);
+  exit (fail ? EXIT_FAILURE : EXIT_SUCCESS);
 }