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"
38 proper_name ("David MacKenzie"), \
39 proper_name ("Jim Meyering")
46 CH_NO_CHANGE_REQUESTED
51 /* Print a message for each file that is processed. */
54 /* Print a message for each file whose attributes we change. */
57 /* Do not be verbose. This is the default. */
61 /* The name the program was run with. */
62 char const *program_name;
64 /* The desired change to the mode. */
65 static struct mode_change *change;
67 /* The initial umask value, if it might be needed. */
68 static mode_t umask_value;
70 /* If true, change the modes of directories recursively. */
73 /* If true, force silence (no error messages). */
74 static bool force_silent;
76 /* If true, diagnose surprises from naive misuses like "chmod -r file".
77 POSIX allows diagnostics here, as portable code is supposed to use
78 "chmod -- -r file". */
79 static bool diagnose_surprises;
81 /* Level of verbosity. */
82 static enum Verbosity verbosity = V_off;
84 /* Pointer to the device and inode numbers of `/', when --recursive.
86 static struct dev_ino *root_dev_ino;
88 /* For long options that have no equivalent short option, use a
89 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
92 NO_PRESERVE_ROOT = CHAR_MAX + 1,
97 static struct option const long_options[] =
99 {"changes", no_argument, NULL, 'c'},
100 {"recursive", no_argument, NULL, 'R'},
101 {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
102 {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
103 {"quiet", no_argument, NULL, 'f'},
104 {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
105 {"silent", no_argument, NULL, 'f'},
106 {"verbose", no_argument, NULL, 'v'},
107 {GETOPT_HELP_OPTION_DECL},
108 {GETOPT_VERSION_OPTION_DECL},
112 /* Return true if the chmodable permission bits of FILE changed.
113 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
116 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
118 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
120 /* The new mode contains unusual bits that the call to chmod may
121 have silently cleared. Check whether they actually changed. */
123 struct stat new_stats;
125 if (stat (file, &new_stats) != 0)
128 error (0, errno, _("getting new attributes of %s"), quote (file));
132 new_mode = new_stats.st_mode;
135 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
138 /* Tell the user how/if the MODE of FILE has been changed.
139 CHANGED describes what (if anything) has happened. */
142 describe_change (const char *file, mode_t mode,
143 enum Change_status changed)
145 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
148 if (changed == CH_NOT_APPLIED)
150 printf (_("neither symbolic link %s nor referent has been changed\n"),
155 strmode (mode, perms);
156 perms[10] = '\0'; /* Remove trailing space. */
160 fmt = _("mode of %s changed to %04lo (%s)\n");
163 fmt = _("failed to change mode of %s to %04lo (%s)\n");
165 case CH_NO_CHANGE_REQUESTED:
166 fmt = _("mode of %s retained as %04lo (%s)\n");
171 printf (fmt, quote (file),
172 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
175 /* Change the mode of FILE.
176 Return true if successful. This function is called
177 once for every file system object that fts encounters. */
180 process_file (FTS *fts, FTSENT *ent)
182 char const *file_full_name = ent->fts_path;
183 char const *file = ent->fts_accpath;
184 const struct stat *file_stats = ent->fts_statp;
185 mode_t old_mode IF_LINT (= 0);
186 mode_t new_mode IF_LINT (= 0);
188 bool chmod_succeeded = false;
190 switch (ent->fts_info)
196 /* For a top-level file or directory, this FTS_NS (stat failed)
197 indicator is determined at the time of the initial fts_open call.
198 With programs like chmod, chown, and chgrp, that modify
199 permissions, it is possible that the file in question is
200 accessible when control reaches this point. So, if this is
201 the first time we've seen the FTS_NS for this file, tell
202 fts_read to stat it "again". */
203 if (ent->fts_level == 0 && ent->fts_number == 0)
206 fts_set (fts, ent, FTS_AGAIN);
209 error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
214 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
219 error (0, ent->fts_errno, _("cannot read directory %s"),
220 quote (file_full_name));
225 error (0, 0, _("cannot operate on dangling symlink %s"),
226 quote (file_full_name));
233 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
235 ROOT_DEV_INO_WARN (file_full_name);
236 /* Tell fts not to traverse into this hierarchy. */
237 fts_set (fts, ent, FTS_SKIP);
238 /* Ensure that we do not process "/" on the second visit. */
239 ent = fts_read (fts);
245 old_mode = file_stats->st_mode;
246 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
249 if (! S_ISLNK (old_mode))
251 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
252 chmod_succeeded = true;
256 error (0, errno, _("changing permissions of %s"),
257 quote (file_full_name));
263 if (verbosity != V_off)
265 bool changed = (chmod_succeeded
266 && mode_changed (file, old_mode, new_mode));
268 if (changed || verbosity == V_high)
270 enum Change_status ch_status =
272 : !chmod_succeeded ? CH_NOT_APPLIED
273 : !changed ? CH_NO_CHANGE_REQUESTED
275 describe_change (file_full_name, new_mode, ch_status);
279 if (chmod_succeeded & diagnose_surprises)
281 mode_t naively_expected_mode =
282 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
283 if (new_mode & ~naively_expected_mode)
286 char naively_expected_perms[12];
287 strmode (new_mode, new_perms);
288 strmode (naively_expected_mode, naively_expected_perms);
289 new_perms[10] = naively_expected_perms[10] = '\0';
291 _("%s: new permissions are %s, not %s"),
292 quotearg_colon (file_full_name),
293 new_perms + 1, naively_expected_perms + 1);
299 fts_set (fts, ent, FTS_SKIP);
304 /* Recursively change the modes of the specified FILES (the last entry
305 of which is NULL). BIT_FLAGS controls how fts works.
306 Return true if successful. */
309 process_files (char **files, int bit_flags)
313 FTS *fts = xfts_open (files, bit_flags, NULL);
319 ent = fts_read (fts);
324 /* FIXME: try to give a better message */
325 error (0, errno, _("fts_read failed"));
331 ok &= process_file (fts, ent);
334 /* Ignore failure, since the only way it can do so is in failing to
335 return to the original directory, and since we're about to exit,
336 that doesn't matter. */
345 if (status != EXIT_SUCCESS)
346 fprintf (stderr, _("Try `%s --help' for more information.\n"),
351 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
352 or: %s [OPTION]... OCTAL-MODE FILE...\n\
353 or: %s [OPTION]... --reference=RFILE FILE...\n\
355 program_name, program_name, program_name);
357 Change the mode of each FILE to MODE.\n\
359 -c, --changes like verbose but report only when a change is made\n\
362 --no-preserve-root do not treat `/' specially (the default)\n\
363 --preserve-root fail to operate recursively on `/'\n\
366 -f, --silent, --quiet suppress most error messages\n\
367 -v, --verbose output a diagnostic for every file processed\n\
368 --reference=RFILE use RFILE's mode instead of MODE values\n\
369 -R, --recursive change files and directories recursively\n\
371 fputs (HELP_OPTION_DESCRIPTION, stdout);
372 fputs (VERSION_OPTION_DESCRIPTION, stdout);
375 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
377 emit_bug_reporting_address ();
382 /* Parse the ASCII mode given on the command line into a linked list
383 of `struct mode_change' and apply that to each file argument. */
386 main (int argc, char **argv)
390 size_t mode_alloc = 0;
392 bool preserve_root = false;
393 char const *reference_file = NULL;
396 initialize_main (&argc, &argv);
397 program_name = argv[0];
398 setlocale (LC_ALL, "");
399 bindtextdomain (PACKAGE, LOCALEDIR);
400 textdomain (PACKAGE);
402 atexit (close_stdout);
404 recurse = force_silent = diagnose_surprises = false;
406 while ((c = getopt_long (argc, argv,
407 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
426 /* Support nonportable uses like "chmod -w", but diagnose
427 surprises due to umask confusion. Even though "--", "--r",
428 etc., are valid modes, there is no "case '-'" here since
429 getopt_long reserves leading "--" for long options. */
431 /* Allocate a mode string (e.g., "-rwx") by concatenating
432 the argument containing this option. If a previous mode
433 string was given, concatenate the previous string, a
434 comma, and the new string (e.g., "-s,-rwx"). */
436 char const *arg = argv[optind - 1];
437 size_t arg_len = strlen (arg);
438 size_t mode_comma_len = mode_len + !!mode_len;
439 size_t new_mode_len = mode_comma_len + arg_len;
440 if (mode_alloc <= new_mode_len)
442 mode_alloc = new_mode_len + 1;
443 mode = X2REALLOC (mode, &mode_alloc);
445 mode[mode_len] = ',';
446 strcpy (mode + mode_comma_len, arg);
447 mode_len = new_mode_len;
449 diagnose_surprises = true;
452 case NO_PRESERVE_ROOT:
453 preserve_root = false;
456 preserve_root = true;
458 case REFERENCE_FILE_OPTION:
459 reference_file = optarg;
465 verbosity = V_changes_only;
473 case_GETOPT_HELP_CHAR;
474 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
476 usage (EXIT_FAILURE);
484 error (0, 0, _("cannot combine mode and --reference options"));
485 usage (EXIT_FAILURE);
491 mode = argv[optind++];
496 if (!mode || mode != argv[optind - 1])
497 error (0, 0, _("missing operand"));
499 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
500 usage (EXIT_FAILURE);
505 change = mode_create_from_ref (reference_file);
507 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
508 quote (reference_file));
512 change = mode_compile (mode);
515 error (0, 0, _("invalid mode: %s"), quote (mode));
516 usage (EXIT_FAILURE);
518 umask_value = umask (0);
521 if (recurse & preserve_root)
523 static struct dev_ino dev_ino_buf;
524 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
525 if (root_dev_ino == NULL)
526 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
534 ok = process_files (argv + optind, FTS_COMFOLLOW | FTS_PHYSICAL);
536 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);