doc: improve various BLOCKSIZE and SIZE help
[coreutils.git] / src / chmod.c
blob282eadaffbf5fd99a9faa4a97b953c9701f11f4e
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 "ignore-value.h"
29 #include "modechange.h"
30 #include "quote.h"
31 #include "quotearg.h"
32 #include "root-dev-ino.h"
33 #include "xfts.h"
35 /* The official name of this program (e.g., no `g' prefix). */
36 #define PROGRAM_NAME "chmod"
38 #define AUTHORS \
39 proper_name ("David MacKenzie"), \
40 proper_name ("Jim Meyering")
42 enum Change_status
44 CH_NOT_APPLIED,
45 CH_SUCCEEDED,
46 CH_FAILED,
47 CH_NO_CHANGE_REQUESTED
50 enum Verbosity
52 /* Print a message for each file that is processed. */
53 V_high,
55 /* Print a message for each file whose attributes we change. */
56 V_changes_only,
58 /* Do not be verbose. This is the default. */
59 V_off
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. */
69 static bool recurse;
71 /* If true, force silence (suppress most of 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.
83 Otherwise NULL. */
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. */
88 enum
90 NO_PRESERVE_ROOT = CHAR_MAX + 1,
91 PRESERVE_ROOT,
92 REFERENCE_FILE_OPTION
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},
107 {NULL, 0, NULL, 0}
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. */
113 static bool
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)
125 if (! force_silent)
126 error (0, errno, _("getting new attributes of %s"), quote (file));
127 return false;
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. */
139 static void
140 describe_change (const char *file, mode_t mode,
141 enum Change_status changed)
143 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
144 const char *fmt;
146 if (changed == CH_NOT_APPLIED)
148 printf (_("neither symbolic link %s nor referent has been changed\n"),
149 quote (file));
150 return;
153 strmode (mode, perms);
154 perms[10] = '\0'; /* Remove trailing space. */
155 switch (changed)
157 case CH_SUCCEEDED:
158 fmt = _("mode of %s changed to %04lo (%s)\n");
159 break;
160 case CH_FAILED:
161 fmt = _("failed to change mode of %s to %04lo (%s)\n");
162 break;
163 case CH_NO_CHANGE_REQUESTED:
164 fmt = _("mode of %s retained as %04lo (%s)\n");
165 break;
166 default:
167 abort ();
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. */
177 static bool
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);
185 bool ok = true;
186 bool chmod_succeeded = false;
188 switch (ent->fts_info)
190 case FTS_DP:
191 return true;
193 case FTS_NS:
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)
203 ent->fts_number = 1;
204 fts_set (fts, ent, FTS_AGAIN);
205 return true;
207 if (! force_silent)
208 error (0, ent->fts_errno, _("cannot access %s"),
209 quote (file_full_name));
210 ok = false;
211 break;
213 case FTS_ERR:
214 if (! force_silent)
215 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
216 ok = false;
217 break;
219 case FTS_DNR:
220 if (! force_silent)
221 error (0, ent->fts_errno, _("cannot read directory %s"),
222 quote (file_full_name));
223 ok = false;
224 break;
226 case FTS_SLNONE:
227 if (! force_silent)
228 error (0, 0, _("cannot operate on dangling symlink %s"),
229 quote (file_full_name));
230 ok = false;
232 default:
233 break;
236 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
238 ROOT_DEV_INO_WARN (file_full_name);
239 /* Tell fts not to traverse into this hierarchy. */
240 fts_set (fts, ent, FTS_SKIP);
241 /* Ensure that we do not process "/" on the second visit. */
242 ignore_ptr (fts_read (fts));
243 return false;
246 if (ok)
248 old_mode = file_stats->st_mode;
249 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
250 change, NULL);
252 if (! S_ISLNK (old_mode))
254 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
255 chmod_succeeded = true;
256 else
258 if (! force_silent)
259 error (0, errno, _("changing permissions of %s"),
260 quote (file_full_name));
261 ok = false;
266 if (verbosity != V_off)
268 bool changed = (chmod_succeeded
269 && mode_changed (file, old_mode, new_mode));
271 if (changed || verbosity == V_high)
273 enum Change_status ch_status =
274 (!ok ? CH_FAILED
275 : !chmod_succeeded ? CH_NOT_APPLIED
276 : !changed ? CH_NO_CHANGE_REQUESTED
277 : CH_SUCCEEDED);
278 describe_change (file_full_name, new_mode, ch_status);
282 if (chmod_succeeded & diagnose_surprises)
284 mode_t naively_expected_mode =
285 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
286 if (new_mode & ~naively_expected_mode)
288 char new_perms[12];
289 char naively_expected_perms[12];
290 strmode (new_mode, new_perms);
291 strmode (naively_expected_mode, naively_expected_perms);
292 new_perms[10] = naively_expected_perms[10] = '\0';
293 error (0, 0,
294 _("%s: new permissions are %s, not %s"),
295 quotearg_colon (file_full_name),
296 new_perms + 1, naively_expected_perms + 1);
297 ok = false;
301 if ( ! recurse)
302 fts_set (fts, ent, FTS_SKIP);
304 return ok;
307 /* Recursively change the modes of the specified FILES (the last entry
308 of which is NULL). BIT_FLAGS controls how fts works.
309 Return true if successful. */
311 static bool
312 process_files (char **files, int bit_flags)
314 bool ok = true;
316 FTS *fts = xfts_open (files, bit_flags, NULL);
318 while (1)
320 FTSENT *ent;
322 ent = fts_read (fts);
323 if (ent == NULL)
325 if (errno != 0)
327 /* FIXME: try to give a better message */
328 if (! force_silent)
329 error (0, errno, _("fts_read failed"));
330 ok = false;
332 break;
335 ok &= process_file (fts, ent);
338 if (fts_close (fts) != 0)
340 error (0, errno, _("fts_close failed"));
341 ok = false;
344 return ok;
347 void
348 usage (int status)
350 if (status != EXIT_SUCCESS)
351 fprintf (stderr, _("Try `%s --help' for more information.\n"),
352 program_name);
353 else
355 printf (_("\
356 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
357 or: %s [OPTION]... OCTAL-MODE FILE...\n\
358 or: %s [OPTION]... --reference=RFILE FILE...\n\
360 program_name, program_name, program_name);
361 fputs (_("\
362 Change the mode of each FILE to MODE.\n\
364 -c, --changes like verbose but report only when a change is made\n\
365 "), stdout);
366 fputs (_("\
367 --no-preserve-root do not treat `/' specially (the default)\n\
368 --preserve-root fail to operate recursively on `/'\n\
369 "), stdout);
370 fputs (_("\
371 -f, --silent, --quiet suppress most error messages\n\
372 -v, --verbose output a diagnostic for every file processed\n\
373 --reference=RFILE use RFILE's mode instead of MODE values\n\
374 -R, --recursive change files and directories recursively\n\
375 "), stdout);
376 fputs (HELP_OPTION_DESCRIPTION, stdout);
377 fputs (VERSION_OPTION_DESCRIPTION, stdout);
378 fputs (_("\
380 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
381 "), stdout);
382 emit_bug_reporting_address ();
384 exit (status);
387 /* Parse the ASCII mode given on the command line into a linked list
388 of `struct mode_change' and apply that to each file argument. */
391 main (int argc, char **argv)
393 char *mode = NULL;
394 size_t mode_len = 0;
395 size_t mode_alloc = 0;
396 bool ok;
397 bool preserve_root = false;
398 char const *reference_file = NULL;
399 int c;
401 initialize_main (&argc, &argv);
402 set_program_name (argv[0]);
403 setlocale (LC_ALL, "");
404 bindtextdomain (PACKAGE, LOCALEDIR);
405 textdomain (PACKAGE);
407 atexit (close_stdout);
409 recurse = force_silent = diagnose_surprises = false;
411 while ((c = getopt_long (argc, argv,
412 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
413 long_options, NULL))
414 != -1)
416 switch (c)
418 case 'r':
419 case 'w':
420 case 'x':
421 case 'X':
422 case 's':
423 case 't':
424 case 'u':
425 case 'g':
426 case 'o':
427 case 'a':
428 case ',':
429 case '+':
430 case '=':
431 /* Support nonportable uses like "chmod -w", but diagnose
432 surprises due to umask confusion. Even though "--", "--r",
433 etc., are valid modes, there is no "case '-'" here since
434 getopt_long reserves leading "--" for long options. */
436 /* Allocate a mode string (e.g., "-rwx") by concatenating
437 the argument containing this option. If a previous mode
438 string was given, concatenate the previous string, a
439 comma, and the new string (e.g., "-s,-rwx"). */
441 char const *arg = argv[optind - 1];
442 size_t arg_len = strlen (arg);
443 size_t mode_comma_len = mode_len + !!mode_len;
444 size_t new_mode_len = mode_comma_len + arg_len;
445 if (mode_alloc <= new_mode_len)
447 mode_alloc = new_mode_len + 1;
448 mode = X2REALLOC (mode, &mode_alloc);
450 mode[mode_len] = ',';
451 strcpy (mode + mode_comma_len, arg);
452 mode_len = new_mode_len;
454 diagnose_surprises = true;
456 break;
457 case NO_PRESERVE_ROOT:
458 preserve_root = false;
459 break;
460 case PRESERVE_ROOT:
461 preserve_root = true;
462 break;
463 case REFERENCE_FILE_OPTION:
464 reference_file = optarg;
465 break;
466 case 'R':
467 recurse = true;
468 break;
469 case 'c':
470 verbosity = V_changes_only;
471 break;
472 case 'f':
473 force_silent = true;
474 break;
475 case 'v':
476 verbosity = V_high;
477 break;
478 case_GETOPT_HELP_CHAR;
479 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
480 default:
481 usage (EXIT_FAILURE);
485 if (reference_file)
487 if (mode)
489 error (0, 0, _("cannot combine mode and --reference options"));
490 usage (EXIT_FAILURE);
493 else
495 if (!mode)
496 mode = argv[optind++];
499 if (optind >= argc)
501 if (!mode || mode != argv[optind - 1])
502 error (0, 0, _("missing operand"));
503 else
504 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
505 usage (EXIT_FAILURE);
508 if (reference_file)
510 change = mode_create_from_ref (reference_file);
511 if (!change)
512 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
513 quote (reference_file));
515 else
517 change = mode_compile (mode);
518 if (!change)
520 error (0, 0, _("invalid mode: %s"), quote (mode));
521 usage (EXIT_FAILURE);
523 umask_value = umask (0);
526 if (recurse & preserve_root)
528 static struct dev_ino dev_ino_buf;
529 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
530 if (root_dev_ino == NULL)
531 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
532 quote ("/"));
534 else
536 root_dev_ino = NULL;
539 ok = process_files (argv + optind,
540 FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
542 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);