1 /* chmod -- change permission modes of files
2 Copyright (C) 89, 90, 91, 1995-2005 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 2, or (at your option)
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, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
23 #include <sys/types.h>
30 #include "modechange.h"
32 #include "root-dev-ino.h"
35 /* The official name of this program (e.g., no `g' prefix). */
36 #define PROGRAM_NAME "chmod"
38 #define AUTHORS "David MacKenzie", "Jim Meyering"
45 CH_NO_CHANGE_REQUESTED
50 /* Print a message for each file that is processed. */
53 /* Print a message for each file whose attributes we change. */
56 /* Do not be verbose. This is the default. */
60 /* The name the program was run with. */
63 /* If true, change the modes of directories recursively. */
66 /* If true, force silence (no error messages). */
67 static bool force_silent;
69 /* Level of verbosity. */
70 static enum Verbosity verbosity = V_off;
72 /* The argument to the --reference option. Use the owner and group IDs
73 of this file. This file must exist. */
74 static char *reference_file;
76 /* Pointer to the device and inode numbers of `/', when --recursive.
78 static struct dev_ino *root_dev_ino;
80 /* For long options that have no equivalent short option, use a
81 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
84 NO_PRESERVE_ROOT = CHAR_MAX + 1,
89 static struct option const long_options[] =
91 {"changes", no_argument, NULL, 'c'},
92 {"recursive", no_argument, NULL, 'R'},
93 {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
94 {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
95 {"quiet", no_argument, NULL, 'f'},
96 {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
97 {"silent", no_argument, NULL, 'f'},
98 {"verbose", no_argument, NULL, 'v'},
99 {GETOPT_HELP_OPTION_DECL},
100 {GETOPT_VERSION_OPTION_DECL},
104 /* Return true if the chmodable permission bits of FILE changed.
105 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
108 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
110 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
112 /* The new mode contains unusual bits that the call to chmod may
113 have silently cleared. Check whether they actually changed. */
115 struct stat new_stats;
117 if (stat (file, &new_stats) != 0)
120 error (0, errno, _("getting new attributes of %s"), quote (file));
124 new_mode = new_stats.st_mode;
127 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
130 /* Tell the user how/if the MODE of FILE has been changed.
131 CHANGED describes what (if anything) has happened. */
134 describe_change (const char *file, mode_t mode,
135 enum Change_status changed)
137 char perms[11]; /* "-rwxrwxrwx" ls-style modes. */
140 if (changed == CH_NOT_APPLIED)
142 printf (_("neither symbolic link %s nor referent has been changed\n"),
147 mode_string (mode, perms);
148 perms[10] = '\0'; /* `mode_string' does not null terminate. */
152 fmt = _("mode of %s changed to %04lo (%s)\n");
155 fmt = _("failed to change mode of %s to %04lo (%s)\n");
157 case CH_NO_CHANGE_REQUESTED:
158 fmt = _("mode of %s retained as %04lo (%s)\n");
163 printf (fmt, quote (file),
164 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
167 /* Change the mode of FILE according to the list of operations CHANGES.
168 Return true if successful. This function is called
169 once for every file system object that fts encounters. */
172 process_file (FTS *fts, FTSENT *ent, const struct mode_change *changes)
174 char const *file_full_name = ent->fts_path;
175 char const *file = ent->fts_accpath;
176 const struct stat *file_stats = ent->fts_statp;
177 mode_t new_mode IF_LINT (= 0);
180 bool symlink_changed = true;
182 switch (ent->fts_info)
188 error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
193 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
198 error (0, ent->fts_errno, _("cannot read directory %s"),
199 quote (file_full_name));
209 if (do_chmod && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
211 ROOT_DEV_INO_WARN (file_full_name);
218 new_mode = mode_adjust (file_stats->st_mode, changes);
220 if (S_ISLNK (file_stats->st_mode))
221 symlink_changed = false;
224 ok = (chmod (file, new_mode) == 0);
226 if (! (ok | force_silent))
227 error (0, errno, _("changing permissions of %s"),
228 quote (file_full_name));
232 if (verbosity != V_off)
235 (ok && symlink_changed
236 && mode_changed (file, file_stats->st_mode, new_mode));
238 if (changed || verbosity == V_high)
240 enum Change_status ch_status =
242 : !symlink_changed ? CH_NOT_APPLIED
243 : !changed ? CH_NO_CHANGE_REQUESTED
245 describe_change (file_full_name, new_mode, ch_status);
250 fts_set (fts, ent, FTS_SKIP);
255 /* Recursively change the modes of the specified FILES (the last entry
256 of which is NULL) according to the list of operations CHANGES.
257 BIT_FLAGS controls how fts works.
258 Return true if successful. */
261 process_files (char **files, int bit_flags, const struct mode_change *changes)
265 FTS *fts = xfts_open (files, bit_flags, NULL);
271 ent = fts_read (fts);
276 /* FIXME: try to give a better message */
277 error (0, errno, _("fts_read failed"));
283 ok &= process_file (fts, ent, changes);
286 /* Ignore failure, since the only way it can do so is in failing to
287 return to the original directory, and since we're about to exit,
288 that doesn't matter. */
297 if (status != EXIT_SUCCESS)
298 fprintf (stderr, _("Try `%s --help' for more information.\n"),
303 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
304 or: %s [OPTION]... OCTAL-MODE FILE...\n\
305 or: %s [OPTION]... --reference=RFILE FILE...\n\
307 program_name, program_name, program_name);
309 Change the mode of each FILE to MODE.\n\
311 -c, --changes like verbose but report only when a change is made\n\
314 --no-preserve-root do not treat `/' specially (the default)\n\
315 --preserve-root fail to operate recursively on `/'\n\
318 -f, --silent, --quiet suppress most error messages\n\
319 -v, --verbose output a diagnostic for every file processed\n\
320 --reference=RFILE use RFILE's mode instead of MODE values\n\
321 -R, --recursive change files and directories recursively\n\
323 fputs (HELP_OPTION_DESCRIPTION, stdout);
324 fputs (VERSION_OPTION_DESCRIPTION, stdout);
327 Each MODE is one or more of the letters ugoa, one of the symbols +-= and\n\
328 one or more of the letters rwxXstugo.\n\
330 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
335 /* Parse the ASCII mode given on the command line into a linked list
336 of `struct mode_change' and apply that to each file argument. */
339 main (int argc, char **argv)
341 struct mode_change *changes;
344 size_t mode_alloc = 0;
346 bool preserve_root = false;
349 initialize_main (&argc, &argv);
350 program_name = argv[0];
351 setlocale (LC_ALL, "");
352 bindtextdomain (PACKAGE, LOCALEDIR);
353 textdomain (PACKAGE);
355 atexit (close_stdout);
357 recurse = force_silent = false;
359 while ((c = getopt_long (argc, argv,
360 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::-::=::",
381 /* Allocate a mode string (e.g., "-rwx") by concatenating
382 the argument containing this option. If a previous mode
383 string was given, concatenate the previous string, a
384 comma, and the new string (e.g., "-s,-rwx"). */
386 char const *arg = argv[optind - 1];
387 size_t arg_len = strlen (arg);
388 size_t mode_comma_len = mode_len + !!mode_len;
389 size_t new_mode_len = mode_comma_len + arg_len;
390 if (mode_alloc <= new_mode_len)
392 mode_alloc = new_mode_len + 1;
393 mode = x2realloc (mode, &mode_alloc);
395 mode[mode_len] = ',';
396 strcpy (mode + mode_comma_len, arg);
397 mode_len = new_mode_len;
400 case NO_PRESERVE_ROOT:
401 preserve_root = false;
404 preserve_root = true;
406 case REFERENCE_FILE_OPTION:
407 reference_file = optarg;
413 verbosity = V_changes_only;
421 case_GETOPT_HELP_CHAR;
422 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
424 usage (EXIT_FAILURE);
431 error (EXIT_FAILURE, 0,
432 _("cannot combine mode and --reference options"));
437 mode = argv[optind++];
442 if (!mode || mode != argv[optind - 1])
443 error (0, 0, _("missing operand"));
445 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
446 usage (EXIT_FAILURE);
449 changes = (reference_file ? mode_create_from_ref (reference_file)
450 : mode_compile (mode, MODE_MASK_ALL));
452 if (changes == MODE_INVALID)
453 error (EXIT_FAILURE, 0, _("invalid mode: %s"), quote (mode));
454 else if (changes == MODE_MEMORY_EXHAUSTED)
456 else if (changes == MODE_BAD_REFERENCE)
457 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
458 quote (reference_file));
460 if (recurse & preserve_root)
462 static struct dev_ino dev_ino_buf;
463 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
464 if (root_dev_ino == NULL)
465 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
473 ok = process_files (argv + optind, FTS_COMFOLLOW, changes);
475 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);