1 /* chmod -- change permission modes of files
2 Copyright (C) 89, 90, 91, 1995-2008 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
22 #include <sys/types.h>
28 #include "modechange.h"
31 #include "root-dev-ino.h"
34 /* The official name of this program (e.g., no `g' prefix). */
35 #define PROGRAM_NAME "chmod"
37 #define AUTHORS "David MacKenzie", "Jim Meyering"
44 CH_NO_CHANGE_REQUESTED
49 /* Print a message for each file that is processed. */
52 /* Print a message for each file whose attributes we change. */
55 /* Do not be verbose. This is the default. */
59 /* The name the program was run with. */
62 /* The desired change to the mode. */
63 static struct mode_change *change;
65 /* The initial umask value, if it might be needed. */
66 static mode_t umask_value;
68 /* If true, change the modes of directories recursively. */
71 /* If true, force silence (no error messages). */
72 static bool force_silent;
74 /* If true, diagnose surprises from naive misuses like "chmod -r file".
75 POSIX allows diagnostics here, as portable code is supposed to use
76 "chmod -- -r file". */
77 static bool diagnose_surprises;
79 /* Level of verbosity. */
80 static enum Verbosity verbosity = V_off;
82 /* Pointer to the device and inode numbers of `/', when --recursive.
84 static struct dev_ino *root_dev_ino;
86 /* For long options that have no equivalent short option, use a
87 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
90 NO_PRESERVE_ROOT = CHAR_MAX + 1,
95 static struct option const long_options[] =
97 {"changes", no_argument, NULL, 'c'},
98 {"recursive", no_argument, NULL, 'R'},
99 {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
100 {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
101 {"quiet", no_argument, NULL, 'f'},
102 {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
103 {"silent", no_argument, NULL, 'f'},
104 {"verbose", no_argument, NULL, 'v'},
105 {GETOPT_HELP_OPTION_DECL},
106 {GETOPT_VERSION_OPTION_DECL},
110 /* Return true if the chmodable permission bits of FILE changed.
111 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
114 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
116 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
118 /* The new mode contains unusual bits that the call to chmod may
119 have silently cleared. Check whether they actually changed. */
121 struct stat new_stats;
123 if (stat (file, &new_stats) != 0)
126 error (0, errno, _("getting new attributes of %s"), quote (file));
130 new_mode = new_stats.st_mode;
133 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
136 /* Tell the user how/if the MODE of FILE has been changed.
137 CHANGED describes what (if anything) has happened. */
140 describe_change (const char *file, mode_t mode,
141 enum Change_status changed)
143 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
146 if (changed == CH_NOT_APPLIED)
148 printf (_("neither symbolic link %s nor referent has been changed\n"),
153 strmode (mode, perms);
154 perms[10] = '\0'; /* Remove trailing space. */
158 fmt = _("mode of %s changed to %04lo (%s)\n");
161 fmt = _("failed to change mode of %s to %04lo (%s)\n");
163 case CH_NO_CHANGE_REQUESTED:
164 fmt = _("mode of %s retained as %04lo (%s)\n");
169 printf (fmt, quote (file),
170 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
173 /* Change the mode of FILE.
174 Return true if successful. This function is called
175 once for every file system object that fts encounters. */
178 process_file (FTS *fts, FTSENT *ent)
180 char const *file_full_name = ent->fts_path;
181 char const *file = ent->fts_accpath;
182 const struct stat *file_stats = ent->fts_statp;
183 mode_t old_mode IF_LINT (= 0);
184 mode_t new_mode IF_LINT (= 0);
186 bool chmod_succeeded = false;
188 switch (ent->fts_info)
194 /* For a top-level file or directory, this FTS_NS (stat failed)
195 indicator is determined at the time of the initial fts_open call.
196 With programs like chmod, chown, and chgrp, that modify
197 permissions, it is possible that the file in question is
198 accessible when control reaches this point. So, if this is
199 the first time we've seen the FTS_NS for this file, tell
200 fts_read to stat it "again". */
201 if (ent->fts_level == 0 && ent->fts_number == 0)
204 fts_set (fts, ent, FTS_AGAIN);
207 error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
212 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
217 error (0, ent->fts_errno, _("cannot read directory %s"),
218 quote (file_full_name));
223 error (0, 0, _("cannot operate on dangling symlink %s"),
224 quote (file_full_name));
231 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
233 ROOT_DEV_INO_WARN (file_full_name);
234 /* Tell fts not to traverse into this hierarchy. */
235 fts_set (fts, ent, FTS_SKIP);
236 /* Ensure that we do not process "/" on the second visit. */
237 ent = fts_read (fts);
243 old_mode = file_stats->st_mode;
244 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
247 if (! S_ISLNK (old_mode))
249 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
250 chmod_succeeded = true;
254 error (0, errno, _("changing permissions of %s"),
255 quote (file_full_name));
261 if (verbosity != V_off)
263 bool changed = (chmod_succeeded
264 && mode_changed (file, old_mode, new_mode));
266 if (changed || verbosity == V_high)
268 enum Change_status ch_status =
270 : !chmod_succeeded ? CH_NOT_APPLIED
271 : !changed ? CH_NO_CHANGE_REQUESTED
273 describe_change (file_full_name, new_mode, ch_status);
277 if (chmod_succeeded & diagnose_surprises)
279 mode_t naively_expected_mode =
280 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
281 if (new_mode & ~naively_expected_mode)
284 char naively_expected_perms[12];
285 strmode (new_mode, new_perms);
286 strmode (naively_expected_mode, naively_expected_perms);
287 new_perms[10] = naively_expected_perms[10] = '\0';
289 _("%s: new permissions are %s, not %s"),
290 quotearg_colon (file_full_name),
291 new_perms + 1, naively_expected_perms + 1);
297 fts_set (fts, ent, FTS_SKIP);
302 /* Recursively change the modes of the specified FILES (the last entry
303 of which is NULL). BIT_FLAGS controls how fts works.
304 Return true if successful. */
307 process_files (char **files, int bit_flags)
311 FTS *fts = xfts_open (files, bit_flags, NULL);
317 ent = fts_read (fts);
322 /* FIXME: try to give a better message */
323 error (0, errno, _("fts_read failed"));
329 ok &= process_file (fts, ent);
332 /* Ignore failure, since the only way it can do so is in failing to
333 return to the original directory, and since we're about to exit,
334 that doesn't matter. */
343 if (status != EXIT_SUCCESS)
344 fprintf (stderr, _("Try `%s --help' for more information.\n"),
349 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
350 or: %s [OPTION]... OCTAL-MODE FILE...\n\
351 or: %s [OPTION]... --reference=RFILE FILE...\n\
353 program_name, program_name, program_name);
355 Change the mode of each FILE to MODE.\n\
357 -c, --changes like verbose but report only when a change is made\n\
360 --no-preserve-root do not treat `/' specially (the default)\n\
361 --preserve-root fail to operate recursively on `/'\n\
364 -f, --silent, --quiet suppress most error messages\n\
365 -v, --verbose output a diagnostic for every file processed\n\
366 --reference=RFILE use RFILE's mode instead of MODE values\n\
367 -R, --recursive change files and directories recursively\n\
369 fputs (HELP_OPTION_DESCRIPTION, stdout);
370 fputs (VERSION_OPTION_DESCRIPTION, stdout);
373 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
375 emit_bug_reporting_address ();
380 /* Parse the ASCII mode given on the command line into a linked list
381 of `struct mode_change' and apply that to each file argument. */
384 main (int argc, char **argv)
388 size_t mode_alloc = 0;
390 bool preserve_root = false;
391 char const *reference_file = NULL;
394 initialize_main (&argc, &argv);
395 program_name = argv[0];
396 setlocale (LC_ALL, "");
397 bindtextdomain (PACKAGE, LOCALEDIR);
398 textdomain (PACKAGE);
400 atexit (close_stdout);
402 recurse = force_silent = diagnose_surprises = false;
404 while ((c = getopt_long (argc, argv,
405 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
424 /* Support nonportable uses like "chmod -w", but diagnose
425 surprises due to umask confusion. Even though "--", "--r",
426 etc., are valid modes, there is no "case '-'" here since
427 getopt_long reserves leading "--" for long options. */
429 /* Allocate a mode string (e.g., "-rwx") by concatenating
430 the argument containing this option. If a previous mode
431 string was given, concatenate the previous string, a
432 comma, and the new string (e.g., "-s,-rwx"). */
434 char const *arg = argv[optind - 1];
435 size_t arg_len = strlen (arg);
436 size_t mode_comma_len = mode_len + !!mode_len;
437 size_t new_mode_len = mode_comma_len + arg_len;
438 if (mode_alloc <= new_mode_len)
440 mode_alloc = new_mode_len + 1;
441 mode = X2REALLOC (mode, &mode_alloc);
443 mode[mode_len] = ',';
444 strcpy (mode + mode_comma_len, arg);
445 mode_len = new_mode_len;
447 diagnose_surprises = true;
450 case NO_PRESERVE_ROOT:
451 preserve_root = false;
454 preserve_root = true;
456 case REFERENCE_FILE_OPTION:
457 reference_file = optarg;
463 verbosity = V_changes_only;
471 case_GETOPT_HELP_CHAR;
472 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
474 usage (EXIT_FAILURE);
482 error (0, 0, _("cannot combine mode and --reference options"));
483 usage (EXIT_FAILURE);
489 mode = argv[optind++];
494 if (!mode || mode != argv[optind - 1])
495 error (0, 0, _("missing operand"));
497 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
498 usage (EXIT_FAILURE);
503 change = mode_create_from_ref (reference_file);
505 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
506 quote (reference_file));
510 change = mode_compile (mode);
513 error (0, 0, _("invalid mode: %s"), quote (mode));
514 usage (EXIT_FAILURE);
516 umask_value = umask (0);
519 if (recurse & preserve_root)
521 static struct dev_ino dev_ino_buf;
522 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
523 if (root_dev_ino == NULL)
524 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
532 ok = process_files (argv + optind, FTS_COMFOLLOW | FTS_PHYSICAL);
534 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);