diff options
Diffstat (limited to 'src/chmod.c')
-rw-r--r-- | src/chmod.c | 435 |
1 files changed, 236 insertions, 199 deletions
diff --git a/src/chmod.c b/src/chmod.c index 028c882..3ea66eb 100644 --- a/src/chmod.c +++ b/src/chmod.c @@ -1,10 +1,10 @@ /* chmod -- change permission modes of files - Copyright (C) 89, 90, 91, 1995-2006 Free Software Foundation, Inc. + Copyright (C) 1989-2016 Free Software Foundation, Inc. - This program is free software; you can redistribute it and/or modify + 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 - the Free Software Foundation; either version 2, or (at your option) - any later version. + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -12,8 +12,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */ @@ -26,17 +25,18 @@ #include "dev-ino.h" #include "error.h" #include "filemode.h" +#include "ignore-value.h" #include "modechange.h" -#include "openat.h" #include "quote.h" -#include "quotearg.h" #include "root-dev-ino.h" #include "xfts.h" -/* The official name of this program (e.g., no `g' prefix). */ +/* The official name of this program (e.g., no 'g' prefix). */ #define PROGRAM_NAME "chmod" -#define AUTHORS "David MacKenzie", "Jim Meyering" +#define AUTHORS \ + proper_name ("David MacKenzie"), \ + proper_name ("Jim Meyering") enum Change_status { @@ -58,9 +58,6 @@ enum Verbosity V_off }; -/* The name the program was run with. */ -char *program_name; - /* The desired change to the mode. */ static struct mode_change *change; @@ -70,7 +67,7 @@ static mode_t umask_value; /* If true, change the modes of directories recursively. */ static bool recurse; -/* If true, force silence (no error messages). */ +/* If true, force silence (suppress most of error messages). */ static bool force_silent; /* If true, diagnose surprises from naive misuses like "chmod -r file". @@ -81,7 +78,7 @@ static bool diagnose_surprises; /* Level of verbosity. */ static enum Verbosity verbosity = V_off; -/* Pointer to the device and inode numbers of `/', when --recursive. +/* Pointer to the device and inode numbers of '/', when --recursive. Otherwise NULL. */ static struct dev_ino *root_dev_ino; @@ -113,21 +110,23 @@ static struct option const long_options[] = The old mode was OLD_MODE, but it was changed to NEW_MODE. */ static bool -mode_changed (char const *file, mode_t old_mode, mode_t new_mode) +mode_changed (int dir_fd, char const *file, char const *file_full_name, + mode_t old_mode, mode_t new_mode) { if (new_mode & (S_ISUID | S_ISGID | S_ISVTX)) { /* The new mode contains unusual bits that the call to chmod may - have silently cleared. Check whether they actually changed. */ + have silently cleared. Check whether they actually changed. */ struct stat new_stats; - if (stat (file, &new_stats) != 0) - { - if (!force_silent) - error (0, errno, _("getting new attributes of %s"), quote (file)); - return false; - } + if (fstatat (dir_fd, file, &new_stats, 0) != 0) + { + if (! force_silent) + error (0, errno, _("getting new attributes of %s"), + quoteaf (file_full_name)); + return false; + } new_mode = new_stats.st_mode; } @@ -139,37 +138,45 @@ mode_changed (char const *file, mode_t old_mode, mode_t new_mode) CHANGED describes what (if anything) has happened. */ static void -describe_change (const char *file, mode_t mode, - enum Change_status changed) +describe_change (const char *file, mode_t old_mode, mode_t mode, + enum Change_status changed) { char perms[12]; /* "-rwxrwxrwx" ls-style modes. */ + char old_perms[12]; const char *fmt; if (changed == CH_NOT_APPLIED) { printf (_("neither symbolic link %s nor referent has been changed\n"), - quote (file)); + quoteaf (file)); return; } strmode (mode, perms); perms[10] = '\0'; /* Remove trailing space. */ + + strmode (old_mode, old_perms); + old_perms[10] = '\0'; /* Remove trailing space. */ + switch (changed) { case CH_SUCCEEDED: - fmt = _("mode of %s changed to %04lo (%s)\n"); + fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n"); break; case CH_FAILED: - fmt = _("failed to change mode of %s to %04lo (%s)\n"); + fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n"); break; case CH_NO_CHANGE_REQUESTED: fmt = _("mode of %s retained as %04lo (%s)\n"); - break; + printf (fmt, quoteaf (file), + (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]); + return; default: abort (); } - printf (fmt, quote (file), - (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]); + printf (fmt, quoteaf (file), + (unsigned long int) (old_mode & CHMOD_MODE_BITS), &old_perms[1], + (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]); } /* Change the mode of FILE. @@ -182,8 +189,8 @@ process_file (FTS *fts, FTSENT *ent) char const *file_full_name = ent->fts_path; char const *file = ent->fts_accpath; const struct stat *file_stats = ent->fts_statp; - mode_t old_mode IF_LINT (= 0); - mode_t new_mode IF_LINT (= 0); + mode_t old_mode IF_LINT ( = 0); + mode_t new_mode IF_LINT ( = 0); bool ok = true; bool chmod_succeeded = false; @@ -194,33 +201,52 @@ process_file (FTS *fts, FTSENT *ent) case FTS_NS: /* For a top-level file or directory, this FTS_NS (stat failed) - indicator is determined at the time of the initial fts_open call. - With programs like chmod, chown, and chgrp, that modify - permissions, it is possible that the file in question is - accessible when control reaches this point. So, if this is - the first time we've seen the FTS_NS for this file, tell - fts_read to stat it "again". */ + indicator is determined at the time of the initial fts_open call. + With programs like chmod, chown, and chgrp, that modify + permissions, it is possible that the file in question is + accessible when control reaches this point. So, if this is + the first time we've seen the FTS_NS for this file, tell + fts_read to stat it "again". */ if (ent->fts_level == 0 && ent->fts_number == 0) - { - ent->fts_number = 1; - fts_set (fts, ent, FTS_AGAIN); - return true; - } - error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name)); + { + ent->fts_number = 1; + fts_set (fts, ent, FTS_AGAIN); + return true; + } + if (! force_silent) + error (0, ent->fts_errno, _("cannot access %s"), + quoteaf (file_full_name)); ok = false; break; case FTS_ERR: - error (0, ent->fts_errno, _("%s"), quote (file_full_name)); + if (! force_silent) + error (0, ent->fts_errno, "%s", quotef (file_full_name)); ok = false; break; case FTS_DNR: - error (0, ent->fts_errno, _("cannot read directory %s"), - quote (file_full_name)); + if (! force_silent) + error (0, ent->fts_errno, _("cannot read directory %s"), + quoteaf (file_full_name)); ok = false; break; + case FTS_SLNONE: + if (! force_silent) + error (0, 0, _("cannot operate on dangling symlink %s"), + quoteaf (file_full_name)); + ok = false; + break; + + case FTS_DC: /* directory that causes cycles */ + if (cycle_warning_required (fts, ent)) + { + emit_cycle_warning (file_full_name); + return false; + } + break; + default: break; } @@ -231,63 +257,64 @@ process_file (FTS *fts, FTSENT *ent) /* Tell fts not to traverse into this hierarchy. */ fts_set (fts, ent, FTS_SKIP); /* Ensure that we do not process "/" on the second visit. */ - ent = fts_read (fts); - ok = false; + ignore_value (fts_read (fts)); + return false; } if (ok) { old_mode = file_stats->st_mode; new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value, - change, NULL); + change, NULL); if (! S_ISLNK (old_mode)) - { - if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0) - chmod_succeeded = true; - else - { - if (! force_silent) - error (0, errno, _("changing permissions of %s"), - quote (file_full_name)); - ok = false; - } - } + { + if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0) + chmod_succeeded = true; + else + { + if (! force_silent) + error (0, errno, _("changing permissions of %s"), + quoteaf (file_full_name)); + ok = false; + } + } } if (verbosity != V_off) { bool changed = (chmod_succeeded - && mode_changed (file, old_mode, new_mode)); + && mode_changed (fts->fts_cwd_fd, file, file_full_name, + old_mode, new_mode)); if (changed || verbosity == V_high) - { - enum Change_status ch_status = - (!ok ? CH_FAILED - : !chmod_succeeded ? CH_NOT_APPLIED - : !changed ? CH_NO_CHANGE_REQUESTED - : CH_SUCCEEDED); - describe_change (file_full_name, new_mode, ch_status); - } + { + enum Change_status ch_status = + (!ok ? CH_FAILED + : !chmod_succeeded ? CH_NOT_APPLIED + : !changed ? CH_NO_CHANGE_REQUESTED + : CH_SUCCEEDED); + describe_change (file_full_name, old_mode, new_mode, ch_status); + } } - if (chmod_succeeded & diagnose_surprises) + if (chmod_succeeded && diagnose_surprises) { mode_t naively_expected_mode = - mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL); + mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL); if (new_mode & ~naively_expected_mode) - { - char new_perms[12]; - char naively_expected_perms[12]; - strmode (new_mode, new_perms); - strmode (naively_expected_mode, naively_expected_perms); - new_perms[10] = naively_expected_perms[10] = '\0'; - error (0, 0, - _("%s: new permissions are %s, not %s"), - quotearg_colon (file_full_name), - new_perms + 1, naively_expected_perms + 1); - ok = false; - } + { + char new_perms[12]; + char naively_expected_perms[12]; + strmode (new_mode, new_perms); + strmode (naively_expected_mode, naively_expected_perms); + new_perms[10] = naively_expected_perms[10] = '\0'; + error (0, 0, + _("%s: new permissions are %s, not %s"), + quotef (file_full_name), + new_perms + 1, naively_expected_perms + 1); + ok = false; + } } if ( ! recurse) @@ -313,23 +340,25 @@ process_files (char **files, int bit_flags) ent = fts_read (fts); if (ent == NULL) - { - if (errno != 0) - { - /* FIXME: try to give a better message */ - error (0, errno, _("fts_read failed")); - ok = false; - } - break; - } + { + if (errno != 0) + { + /* FIXME: try to give a better message */ + if (! force_silent) + error (0, errno, _("fts_read failed")); + ok = false; + } + break; + } ok &= process_file (fts, ent); } - /* 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); + if (fts_close (fts) != 0) + { + error (0, errno, _("fts_close failed")); + ok = false; + } return ok; } @@ -338,8 +367,7 @@ void usage (int status) { if (status != EXIT_SUCCESS) - fprintf (stderr, _("Try `%s --help' for more information.\n"), - program_name); + emit_try_help (); else { printf (_("\ @@ -347,35 +375,40 @@ Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\ or: %s [OPTION]... OCTAL-MODE FILE...\n\ or: %s [OPTION]... --reference=RFILE FILE...\n\ "), - program_name, program_name, program_name); + program_name, program_name, program_name); fputs (_("\ Change the mode of each FILE to MODE.\n\ +With --reference, change the mode of each FILE to that of RFILE.\n\ \n\ - -c, --changes like verbose but report only when a change is made\n\ "), stdout); fputs (_("\ - --no-preserve-root do not treat `/' specially (the default)\n\ - --preserve-root fail to operate recursively on `/'\n\ + -c, --changes like verbose but report only when a change is made\n\ + -f, --silent, --quiet suppress most error messages\n\ + -v, --verbose output a diagnostic for every file processed\n\ +"), stdout); + fputs (_("\ + --no-preserve-root do not treat '/' specially (the default)\n\ + --preserve-root fail to operate recursively on '/'\n\ +"), stdout); + fputs (_("\ + --reference=RFILE use RFILE's mode instead of MODE values\n\ "), stdout); fputs (_("\ - -f, --silent, --quiet suppress most error messages\n\ - -v, --verbose output a diagnostic for every file processed\n\ - --reference=RFILE use RFILE's mode instead of MODE values\n\ - -R, --recursive change files and directories recursively\n\ + -R, --recursive change files and directories recursively\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ -Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\ +Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\ "), stdout); - printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + emit_ancillary_info (PROGRAM_NAME); } exit (status); } /* Parse the ASCII mode given on the command line into a linked list - of `struct mode_change' and apply that to each file argument. */ + of 'struct mode_change' and apply that to each file argument. */ int main (int argc, char **argv) @@ -389,7 +422,7 @@ main (int argc, char **argv) int c; initialize_main (&argc, &argv); - program_name = argv[0]; + set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); @@ -399,99 +432,102 @@ main (int argc, char **argv) recurse = force_silent = diagnose_surprises = false; while ((c = getopt_long (argc, argv, - "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::", - long_options, NULL)) - != -1) + ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::" + "0::1::2::3::4::5::6::7::"), + long_options, NULL)) + != -1) { switch (c) - { - case 'r': - case 'w': - case 'x': - case 'X': - case 's': - case 't': - case 'u': - case 'g': - case 'o': - case 'a': - case ',': - case '+': - case '=': - /* Support nonportable uses like "chmod -w", but diagnose - surprises due to umask confusion. Even though "--", "--r", - etc., are valid modes, there is no "case '-'" here since - getopt_long reserves leading "--" for long options. */ - { - /* Allocate a mode string (e.g., "-rwx") by concatenating - the argument containing this option. If a previous mode - string was given, concatenate the previous string, a - comma, and the new string (e.g., "-s,-rwx"). */ - - char const *arg = argv[optind - 1]; - size_t arg_len = strlen (arg); - size_t mode_comma_len = mode_len + !!mode_len; - size_t new_mode_len = mode_comma_len + arg_len; - if (mode_alloc <= new_mode_len) - { - mode_alloc = new_mode_len + 1; - mode = X2REALLOC (mode, &mode_alloc); - } - mode[mode_len] = ','; - strcpy (mode + mode_comma_len, arg); - mode_len = new_mode_len; - - diagnose_surprises = true; - } - break; - case NO_PRESERVE_ROOT: - preserve_root = false; - break; - case PRESERVE_ROOT: - preserve_root = true; - break; - case REFERENCE_FILE_OPTION: - reference_file = optarg; - break; - case 'R': - recurse = true; - break; - case 'c': - verbosity = V_changes_only; - break; - case 'f': - force_silent = true; - break; - case 'v': - verbosity = V_high; - break; - case_GETOPT_HELP_CHAR; - case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); - default: - usage (EXIT_FAILURE); - } + { + case 'r': + case 'w': + case 'x': + case 'X': + case 's': + case 't': + case 'u': + case 'g': + case 'o': + case 'a': + case ',': + case '+': + case '=': + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + /* Support nonportable uses like "chmod -w", but diagnose + surprises due to umask confusion. Even though "--", "--r", + etc., are valid modes, there is no "case '-'" here since + getopt_long reserves leading "--" for long options. */ + { + /* Allocate a mode string (e.g., "-rwx") by concatenating + the argument containing this option. If a previous mode + string was given, concatenate the previous string, a + comma, and the new string (e.g., "-s,-rwx"). */ + + char const *arg = argv[optind - 1]; + size_t arg_len = strlen (arg); + size_t mode_comma_len = mode_len + !!mode_len; + size_t new_mode_len = mode_comma_len + arg_len; + if (mode_alloc <= new_mode_len) + { + mode_alloc = new_mode_len + 1; + mode = X2REALLOC (mode, &mode_alloc); + } + mode[mode_len] = ','; + memcpy (mode + mode_comma_len, arg, arg_len + 1); + mode_len = new_mode_len; + + diagnose_surprises = true; + } + break; + case NO_PRESERVE_ROOT: + preserve_root = false; + break; + case PRESERVE_ROOT: + preserve_root = true; + break; + case REFERENCE_FILE_OPTION: + reference_file = optarg; + break; + case 'R': + recurse = true; + break; + case 'c': + verbosity = V_changes_only; + break; + case 'f': + force_silent = true; + break; + case 'v': + verbosity = V_high; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } } if (reference_file) { if (mode) - { - error (0, 0, _("cannot combine mode and --reference options")); - usage (EXIT_FAILURE); - } + { + error (0, 0, _("cannot combine mode and --reference options")); + usage (EXIT_FAILURE); + } } else { if (!mode) - mode = argv[optind++]; + mode = argv[optind++]; } if (optind >= argc) { if (!mode || mode != argv[optind - 1]) - error (0, 0, _("missing operand")); + error (0, 0, _("missing operand")); else - error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); usage (EXIT_FAILURE); } @@ -499,34 +535,35 @@ main (int argc, char **argv) { change = mode_create_from_ref (reference_file); if (!change) - error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), - quote (reference_file)); + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quoteaf (reference_file)); } else { change = mode_compile (mode); if (!change) - { - error (0, 0, _("invalid mode: %s"), quote (mode)); - usage (EXIT_FAILURE); - } + { + error (0, 0, _("invalid mode: %s"), quote (mode)); + usage (EXIT_FAILURE); + } umask_value = umask (0); } - if (recurse & preserve_root) + if (recurse && preserve_root) { static struct dev_ino dev_ino_buf; root_dev_ino = get_root_dev_ino (&dev_ino_buf); if (root_dev_ino == NULL) - error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), - quote ("/")); + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quoteaf ("/")); } else { root_dev_ino = NULL; } - ok = process_files (argv + optind, FTS_COMFOLLOW | FTS_PHYSICAL); + ok = process_files (argv + optind, + FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT); - exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); + return ok ? EXIT_SUCCESS : EXIT_FAILURE; } |