global: convert indentation-TABs to spaces
[coreutils.git] / src / chmod.c
blob0e39c8b01086e6b00dfa0ef40d521682910412ca
1 /* chmod -- change permission modes of files
2 Copyright (C) 89, 90, 91, 1995-2009 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> */
19 #include <config.h>
20 #include <stdio.h>
21 #include <getopt.h>
22 #include <sys/types.h>
24 #include "system.h"
25 #include "dev-ino.h"
26 #include "error.h"
27 #include "filemode.h"
28 #include "modechange.h"
29 #include "quote.h"
30 #include "quotearg.h"
31 #include "root-dev-ino.h"
32 #include "xfts.h"
34 /* The official name of this program (e.g., no `g' prefix). */
35 #define PROGRAM_NAME "chmod"
37 #define AUTHORS \
38 proper_name ("David MacKenzie"), \
39 proper_name ("Jim Meyering")
41 enum Change_status
43 CH_NOT_APPLIED,
44 CH_SUCCEEDED,
45 CH_FAILED,
46 CH_NO_CHANGE_REQUESTED
49 enum Verbosity
51 /* Print a message for each file that is processed. */
52 V_high,
54 /* Print a message for each file whose attributes we change. */
55 V_changes_only,
57 /* Do not be verbose. This is the default. */
58 V_off
61 /* The desired change to the mode. */
62 static struct mode_change *change;
64 /* The initial umask value, if it might be needed. */
65 static mode_t umask_value;
67 /* If true, change the modes of directories recursively. */
68 static bool recurse;
70 /* If true, force silence (suppress most of error messages). */
71 static bool force_silent;
73 /* If true, diagnose surprises from naive misuses like "chmod -r file".
74 POSIX allows diagnostics here, as portable code is supposed to use
75 "chmod -- -r file". */
76 static bool diagnose_surprises;
78 /* Level of verbosity. */
79 static enum Verbosity verbosity = V_off;
81 /* Pointer to the device and inode numbers of `/', when --recursive.
82 Otherwise NULL. */
83 static struct dev_ino *root_dev_ino;
85 /* For long options that have no equivalent short option, use a
86 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
87 enum
89 NO_PRESERVE_ROOT = CHAR_MAX + 1,
90 PRESERVE_ROOT,
91 REFERENCE_FILE_OPTION
94 static struct option const long_options[] =
96 {"changes", no_argument, NULL, 'c'},
97 {"recursive", no_argument, NULL, 'R'},
98 {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
99 {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
100 {"quiet", no_argument, NULL, 'f'},
101 {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
102 {"silent", no_argument, NULL, 'f'},
103 {"verbose", no_argument, NULL, 'v'},
104 {GETOPT_HELP_OPTION_DECL},
105 {GETOPT_VERSION_OPTION_DECL},
106 {NULL, 0, NULL, 0}
109 /* Return true if the chmodable permission bits of FILE changed.
110 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
112 static bool
113 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
115 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
117 /* The new mode contains unusual bits that the call to chmod may
118 have silently cleared. Check whether they actually changed. */
120 struct stat new_stats;
122 if (stat (file, &new_stats) != 0)
124 if (! force_silent)
125 error (0, errno, _("getting new attributes of %s"), quote (file));
126 return false;
129 new_mode = new_stats.st_mode;
132 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
135 /* Tell the user how/if the MODE of FILE has been changed.
136 CHANGED describes what (if anything) has happened. */
138 static void
139 describe_change (const char *file, mode_t mode,
140 enum Change_status changed)
142 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
143 const char *fmt;
145 if (changed == CH_NOT_APPLIED)
147 printf (_("neither symbolic link %s nor referent has been changed\n"),
148 quote (file));
149 return;
152 strmode (mode, perms);
153 perms[10] = '\0'; /* Remove trailing space. */
154 switch (changed)
156 case CH_SUCCEEDED:
157 fmt = _("mode of %s changed to %04lo (%s)\n");
158 break;
159 case CH_FAILED:
160 fmt = _("failed to change mode of %s to %04lo (%s)\n");
161 break;
162 case CH_NO_CHANGE_REQUESTED:
163 fmt = _("mode of %s retained as %04lo (%s)\n");
164 break;
165 default:
166 abort ();
168 printf (fmt, quote (file),
169 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
172 /* Change the mode of FILE.
173 Return true if successful. This function is called
174 once for every file system object that fts encounters. */
176 static bool
177 process_file (FTS *fts, FTSENT *ent)
179 char const *file_full_name = ent->fts_path;
180 char const *file = ent->fts_accpath;
181 const struct stat *file_stats = ent->fts_statp;
182 mode_t old_mode IF_LINT (= 0);
183 mode_t new_mode IF_LINT (= 0);
184 bool ok = true;
185 bool chmod_succeeded = false;
187 switch (ent->fts_info)
189 case FTS_DP:
190 return true;
192 case FTS_NS:
193 /* For a top-level file or directory, this FTS_NS (stat failed)
194 indicator is determined at the time of the initial fts_open call.
195 With programs like chmod, chown, and chgrp, that modify
196 permissions, it is possible that the file in question is
197 accessible when control reaches this point. So, if this is
198 the first time we've seen the FTS_NS for this file, tell
199 fts_read to stat it "again". */
200 if (ent->fts_level == 0 && ent->fts_number == 0)
202 ent->fts_number = 1;
203 fts_set (fts, ent, FTS_AGAIN);
204 return true;
206 if (! force_silent)
207 error (0, ent->fts_errno, _("cannot access %s"),
208 quote (file_full_name));
209 ok = false;
210 break;
212 case FTS_ERR:
213 if (! force_silent)
214 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
215 ok = false;
216 break;
218 case FTS_DNR:
219 if (! force_silent)
220 error (0, ent->fts_errno, _("cannot read directory %s"),
221 quote (file_full_name));
222 ok = false;
223 break;
225 case FTS_SLNONE:
226 if (! force_silent)
227 error (0, 0, _("cannot operate on dangling symlink %s"),
228 quote (file_full_name));
229 ok = false;
231 default:
232 break;
235 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
237 ROOT_DEV_INO_WARN (file_full_name);
238 /* Tell fts not to traverse into this hierarchy. */
239 fts_set (fts, ent, FTS_SKIP);
240 /* Ensure that we do not process "/" on the second visit. */
241 ent = fts_read (fts);
242 ok = false;
245 if (ok)
247 old_mode = file_stats->st_mode;
248 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
249 change, NULL);
251 if (! S_ISLNK (old_mode))
253 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
254 chmod_succeeded = true;
255 else
257 if (! force_silent)
258 error (0, errno, _("changing permissions of %s"),
259 quote (file_full_name));
260 ok = false;
265 if (verbosity != V_off)
267 bool changed = (chmod_succeeded
268 && mode_changed (file, old_mode, new_mode));
270 if (changed || verbosity == V_high)
272 enum Change_status ch_status =
273 (!ok ? CH_FAILED
274 : !chmod_succeeded ? CH_NOT_APPLIED
275 : !changed ? CH_NO_CHANGE_REQUESTED
276 : CH_SUCCEEDED);
277 describe_change (file_full_name, new_mode, ch_status);
281 if (chmod_succeeded & diagnose_surprises)
283 mode_t naively_expected_mode =
284 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
285 if (new_mode & ~naively_expected_mode)
287 char new_perms[12];
288 char naively_expected_perms[12];
289 strmode (new_mode, new_perms);
290 strmode (naively_expected_mode, naively_expected_perms);
291 new_perms[10] = naively_expected_perms[10] = '\0';
292 error (0, 0,
293 _("%s: new permissions are %s, not %s"),
294 quotearg_colon (file_full_name),
295 new_perms + 1, naively_expected_perms + 1);
296 ok = false;
300 if ( ! recurse)
301 fts_set (fts, ent, FTS_SKIP);
303 return ok;
306 /* Recursively change the modes of the specified FILES (the last entry
307 of which is NULL). BIT_FLAGS controls how fts works.
308 Return true if successful. */
310 static bool
311 process_files (char **files, int bit_flags)
313 bool ok = true;
315 FTS *fts = xfts_open (files, bit_flags, NULL);
317 while (1)
319 FTSENT *ent;
321 ent = fts_read (fts);
322 if (ent == NULL)
324 if (errno != 0)
326 /* FIXME: try to give a better message */
327 if (! force_silent)
328 error (0, errno, _("fts_read failed"));
329 ok = false;
331 break;
334 ok &= process_file (fts, ent);
337 /* Ignore failure, since the only way it can do so is in failing to
338 return to the original directory, and since we're about to exit,
339 that doesn't matter. */
340 fts_close (fts);
342 return ok;
345 void
346 usage (int status)
348 if (status != EXIT_SUCCESS)
349 fprintf (stderr, _("Try `%s --help' for more information.\n"),
350 program_name);
351 else
353 printf (_("\
354 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
355 or: %s [OPTION]... OCTAL-MODE FILE...\n\
356 or: %s [OPTION]... --reference=RFILE FILE...\n\
358 program_name, program_name, program_name);
359 fputs (_("\
360 Change the mode of each FILE to MODE.\n\
362 -c, --changes like verbose but report only when a change is made\n\
363 "), stdout);
364 fputs (_("\
365 --no-preserve-root do not treat `/' specially (the default)\n\
366 --preserve-root fail to operate recursively on `/'\n\
367 "), stdout);
368 fputs (_("\
369 -f, --silent, --quiet suppress most error messages\n\
370 -v, --verbose output a diagnostic for every file processed\n\
371 --reference=RFILE use RFILE's mode instead of MODE values\n\
372 -R, --recursive change files and directories recursively\n\
373 "), stdout);
374 fputs (HELP_OPTION_DESCRIPTION, stdout);
375 fputs (VERSION_OPTION_DESCRIPTION, stdout);
376 fputs (_("\
378 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
379 "), stdout);
380 emit_bug_reporting_address ();
382 exit (status);
385 /* Parse the ASCII mode given on the command line into a linked list
386 of `struct mode_change' and apply that to each file argument. */
389 main (int argc, char **argv)
391 char *mode = NULL;
392 size_t mode_len = 0;
393 size_t mode_alloc = 0;
394 bool ok;
395 bool preserve_root = false;
396 char const *reference_file = NULL;
397 int c;
399 initialize_main (&argc, &argv);
400 set_program_name (argv[0]);
401 setlocale (LC_ALL, "");
402 bindtextdomain (PACKAGE, LOCALEDIR);
403 textdomain (PACKAGE);
405 atexit (close_stdout);
407 recurse = force_silent = diagnose_surprises = false;
409 while ((c = getopt_long (argc, argv,
410 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
411 long_options, NULL))
412 != -1)
414 switch (c)
416 case 'r':
417 case 'w':
418 case 'x':
419 case 'X':
420 case 's':
421 case 't':
422 case 'u':
423 case 'g':
424 case 'o':
425 case 'a':
426 case ',':
427 case '+':
428 case '=':
429 /* Support nonportable uses like "chmod -w", but diagnose
430 surprises due to umask confusion. Even though "--", "--r",
431 etc., are valid modes, there is no "case '-'" here since
432 getopt_long reserves leading "--" for long options. */
434 /* Allocate a mode string (e.g., "-rwx") by concatenating
435 the argument containing this option. If a previous mode
436 string was given, concatenate the previous string, a
437 comma, and the new string (e.g., "-s,-rwx"). */
439 char const *arg = argv[optind - 1];
440 size_t arg_len = strlen (arg);
441 size_t mode_comma_len = mode_len + !!mode_len;
442 size_t new_mode_len = mode_comma_len + arg_len;
443 if (mode_alloc <= new_mode_len)
445 mode_alloc = new_mode_len + 1;
446 mode = X2REALLOC (mode, &mode_alloc);
448 mode[mode_len] = ',';
449 strcpy (mode + mode_comma_len, arg);
450 mode_len = new_mode_len;
452 diagnose_surprises = true;
454 break;
455 case NO_PRESERVE_ROOT:
456 preserve_root = false;
457 break;
458 case PRESERVE_ROOT:
459 preserve_root = true;
460 break;
461 case REFERENCE_FILE_OPTION:
462 reference_file = optarg;
463 break;
464 case 'R':
465 recurse = true;
466 break;
467 case 'c':
468 verbosity = V_changes_only;
469 break;
470 case 'f':
471 force_silent = true;
472 break;
473 case 'v':
474 verbosity = V_high;
475 break;
476 case_GETOPT_HELP_CHAR;
477 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
478 default:
479 usage (EXIT_FAILURE);
483 if (reference_file)
485 if (mode)
487 error (0, 0, _("cannot combine mode and --reference options"));
488 usage (EXIT_FAILURE);
491 else
493 if (!mode)
494 mode = argv[optind++];
497 if (optind >= argc)
499 if (!mode || mode != argv[optind - 1])
500 error (0, 0, _("missing operand"));
501 else
502 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
503 usage (EXIT_FAILURE);
506 if (reference_file)
508 change = mode_create_from_ref (reference_file);
509 if (!change)
510 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
511 quote (reference_file));
513 else
515 change = mode_compile (mode);
516 if (!change)
518 error (0, 0, _("invalid mode: %s"), quote (mode));
519 usage (EXIT_FAILURE);
521 umask_value = umask (0);
524 if (recurse & preserve_root)
526 static struct dev_ino dev_ino_buf;
527 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
528 if (root_dev_ino == NULL)
529 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
530 quote ("/"));
532 else
534 root_dev_ino = NULL;
537 ok = process_files (argv + optind,
538 FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
540 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);