From 96a5d2ce6a53d96cb667af78f13e56fadcdb91e6 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Sun, 19 Oct 2003 18:54:10 +0000 Subject: chmod now uses fts to perform a directory traversal when -R is 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 | 138 ++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 73 insertions(+), 65 deletions(-) (limited to 'src/chmod.c') diff --git a/src/chmod.c b/src/chmod.c index aae2172d8..5e5cf7d36 100644 --- a/src/chmod.c +++ b/src/chmod.c @@ -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); } -- cgit v1.2.3-54-g00ecf