all: replace most uses of quotearg_colon() with quote()
[coreutils.git] / src / chmod.c
blob988ba7269b1b6518de0833794883f576cb9c6adb
1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-2015 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 "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 (int dir_fd, char const *file, char const *file_full_name,
114 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 (fstatat (dir_fd, file, &new_stats, 0) != 0)
125 if (! force_silent)
126 error (0, errno, _("getting new attributes of %s"),
127 quote (file_full_name));
128 return false;
131 new_mode = new_stats.st_mode;
134 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
137 /* Tell the user how/if the MODE of FILE has been changed.
138 CHANGED describes what (if anything) has happened. */
140 static void
141 describe_change (const char *file, mode_t old_mode, mode_t mode,
142 enum Change_status changed)
144 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
145 char old_perms[12];
146 const char *fmt;
148 if (changed == CH_NOT_APPLIED)
150 printf (_("neither symbolic link %s nor referent has been changed\n"),
151 quote (file));
152 return;
155 strmode (mode, perms);
156 perms[10] = '\0'; /* Remove trailing space. */
158 strmode (old_mode, old_perms);
159 old_perms[10] = '\0'; /* Remove trailing space. */
161 switch (changed)
163 case CH_SUCCEEDED:
164 fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
165 break;
166 case CH_FAILED:
167 fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
168 break;
169 case CH_NO_CHANGE_REQUESTED:
170 fmt = _("mode of %s retained as %04lo (%s)\n");
171 printf (fmt, quote (file),
172 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
173 return;
174 default:
175 abort ();
177 printf (fmt, quote (file),
178 (unsigned long int) (old_mode & CHMOD_MODE_BITS), &old_perms[1],
179 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
182 /* Change the mode of FILE.
183 Return true if successful. This function is called
184 once for every file system object that fts encounters. */
186 static bool
187 process_file (FTS *fts, FTSENT *ent)
189 char const *file_full_name = ent->fts_path;
190 char const *file = ent->fts_accpath;
191 const struct stat *file_stats = ent->fts_statp;
192 mode_t old_mode IF_LINT ( = 0);
193 mode_t new_mode IF_LINT ( = 0);
194 bool ok = true;
195 bool chmod_succeeded = false;
197 switch (ent->fts_info)
199 case FTS_DP:
200 return true;
202 case FTS_NS:
203 /* For a top-level file or directory, this FTS_NS (stat failed)
204 indicator is determined at the time of the initial fts_open call.
205 With programs like chmod, chown, and chgrp, that modify
206 permissions, it is possible that the file in question is
207 accessible when control reaches this point. So, if this is
208 the first time we've seen the FTS_NS for this file, tell
209 fts_read to stat it "again". */
210 if (ent->fts_level == 0 && ent->fts_number == 0)
212 ent->fts_number = 1;
213 fts_set (fts, ent, FTS_AGAIN);
214 return true;
216 if (! force_silent)
217 error (0, ent->fts_errno, _("cannot access %s"),
218 quote (file_full_name));
219 ok = false;
220 break;
222 case FTS_ERR:
223 if (! force_silent)
224 error (0, ent->fts_errno, "%s", quote (file_full_name));
225 ok = false;
226 break;
228 case FTS_DNR:
229 if (! force_silent)
230 error (0, ent->fts_errno, _("cannot read directory %s"),
231 quote (file_full_name));
232 ok = false;
233 break;
235 case FTS_SLNONE:
236 if (! force_silent)
237 error (0, 0, _("cannot operate on dangling symlink %s"),
238 quote (file_full_name));
239 ok = false;
240 break;
242 case FTS_DC: /* directory that causes cycles */
243 if (cycle_warning_required (fts, ent))
245 emit_cycle_warning (file_full_name);
246 return false;
248 break;
250 default:
251 break;
254 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
256 ROOT_DEV_INO_WARN (file_full_name);
257 /* Tell fts not to traverse into this hierarchy. */
258 fts_set (fts, ent, FTS_SKIP);
259 /* Ensure that we do not process "/" on the second visit. */
260 ignore_value (fts_read (fts));
261 return false;
264 if (ok)
266 old_mode = file_stats->st_mode;
267 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
268 change, NULL);
270 if (! S_ISLNK (old_mode))
272 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
273 chmod_succeeded = true;
274 else
276 if (! force_silent)
277 error (0, errno, _("changing permissions of %s"),
278 quote (file_full_name));
279 ok = false;
284 if (verbosity != V_off)
286 bool changed = (chmod_succeeded
287 && mode_changed (fts->fts_cwd_fd, file, file_full_name,
288 old_mode, new_mode));
290 if (changed || verbosity == V_high)
292 enum Change_status ch_status =
293 (!ok ? CH_FAILED
294 : !chmod_succeeded ? CH_NOT_APPLIED
295 : !changed ? CH_NO_CHANGE_REQUESTED
296 : CH_SUCCEEDED);
297 describe_change (file_full_name, old_mode, new_mode, ch_status);
301 if (chmod_succeeded && diagnose_surprises)
303 mode_t naively_expected_mode =
304 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
305 if (new_mode & ~naively_expected_mode)
307 char new_perms[12];
308 char naively_expected_perms[12];
309 strmode (new_mode, new_perms);
310 strmode (naively_expected_mode, naively_expected_perms);
311 new_perms[10] = naively_expected_perms[10] = '\0';
312 error (0, 0,
313 _("%s: new permissions are %s, not %s"),
314 quote (file_full_name),
315 new_perms + 1, naively_expected_perms + 1);
316 ok = false;
320 if ( ! recurse)
321 fts_set (fts, ent, FTS_SKIP);
323 return ok;
326 /* Recursively change the modes of the specified FILES (the last entry
327 of which is NULL). BIT_FLAGS controls how fts works.
328 Return true if successful. */
330 static bool
331 process_files (char **files, int bit_flags)
333 bool ok = true;
335 FTS *fts = xfts_open (files, bit_flags, NULL);
337 while (1)
339 FTSENT *ent;
341 ent = fts_read (fts);
342 if (ent == NULL)
344 if (errno != 0)
346 /* FIXME: try to give a better message */
347 if (! force_silent)
348 error (0, errno, _("fts_read failed"));
349 ok = false;
351 break;
354 ok &= process_file (fts, ent);
357 if (fts_close (fts) != 0)
359 error (0, errno, _("fts_close failed"));
360 ok = false;
363 return ok;
366 void
367 usage (int status)
369 if (status != EXIT_SUCCESS)
370 emit_try_help ();
371 else
373 printf (_("\
374 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
375 or: %s [OPTION]... OCTAL-MODE FILE...\n\
376 or: %s [OPTION]... --reference=RFILE FILE...\n\
378 program_name, program_name, program_name);
379 fputs (_("\
380 Change the mode of each FILE to MODE.\n\
381 With --reference, change the mode of each FILE to that of RFILE.\n\
383 "), stdout);
384 fputs (_("\
385 -c, --changes like verbose but report only when a change is made\n\
386 -f, --silent, --quiet suppress most error messages\n\
387 -v, --verbose output a diagnostic for every file processed\n\
388 "), stdout);
389 fputs (_("\
390 --no-preserve-root do not treat '/' specially (the default)\n\
391 --preserve-root fail to operate recursively on '/'\n\
392 "), stdout);
393 fputs (_("\
394 --reference=RFILE use RFILE's mode instead of MODE values\n\
395 "), stdout);
396 fputs (_("\
397 -R, --recursive change files and directories recursively\n\
398 "), stdout);
399 fputs (HELP_OPTION_DESCRIPTION, stdout);
400 fputs (VERSION_OPTION_DESCRIPTION, stdout);
401 fputs (_("\
403 Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\
404 "), stdout);
405 emit_ancillary_info (PROGRAM_NAME);
407 exit (status);
410 /* Parse the ASCII mode given on the command line into a linked list
411 of 'struct mode_change' and apply that to each file argument. */
414 main (int argc, char **argv)
416 char *mode = NULL;
417 size_t mode_len = 0;
418 size_t mode_alloc = 0;
419 bool ok;
420 bool preserve_root = false;
421 char const *reference_file = NULL;
422 int c;
424 initialize_main (&argc, &argv);
425 set_program_name (argv[0]);
426 setlocale (LC_ALL, "");
427 bindtextdomain (PACKAGE, LOCALEDIR);
428 textdomain (PACKAGE);
430 atexit (close_stdout);
432 recurse = force_silent = diagnose_surprises = false;
434 while ((c = getopt_long (argc, argv,
435 ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::"
436 "0::1::2::3::4::5::6::7::"),
437 long_options, NULL))
438 != -1)
440 switch (c)
442 case 'r':
443 case 'w':
444 case 'x':
445 case 'X':
446 case 's':
447 case 't':
448 case 'u':
449 case 'g':
450 case 'o':
451 case 'a':
452 case ',':
453 case '+':
454 case '=':
455 case '0': case '1': case '2': case '3':
456 case '4': case '5': case '6': case '7':
457 /* Support nonportable uses like "chmod -w", but diagnose
458 surprises due to umask confusion. Even though "--", "--r",
459 etc., are valid modes, there is no "case '-'" here since
460 getopt_long reserves leading "--" for long options. */
462 /* Allocate a mode string (e.g., "-rwx") by concatenating
463 the argument containing this option. If a previous mode
464 string was given, concatenate the previous string, a
465 comma, and the new string (e.g., "-s,-rwx"). */
467 char const *arg = argv[optind - 1];
468 size_t arg_len = strlen (arg);
469 size_t mode_comma_len = mode_len + !!mode_len;
470 size_t new_mode_len = mode_comma_len + arg_len;
471 if (mode_alloc <= new_mode_len)
473 mode_alloc = new_mode_len + 1;
474 mode = X2REALLOC (mode, &mode_alloc);
476 mode[mode_len] = ',';
477 memcpy (mode + mode_comma_len, arg, arg_len + 1);
478 mode_len = new_mode_len;
480 diagnose_surprises = true;
482 break;
483 case NO_PRESERVE_ROOT:
484 preserve_root = false;
485 break;
486 case PRESERVE_ROOT:
487 preserve_root = true;
488 break;
489 case REFERENCE_FILE_OPTION:
490 reference_file = optarg;
491 break;
492 case 'R':
493 recurse = true;
494 break;
495 case 'c':
496 verbosity = V_changes_only;
497 break;
498 case 'f':
499 force_silent = true;
500 break;
501 case 'v':
502 verbosity = V_high;
503 break;
504 case_GETOPT_HELP_CHAR;
505 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
506 default:
507 usage (EXIT_FAILURE);
511 if (reference_file)
513 if (mode)
515 error (0, 0, _("cannot combine mode and --reference options"));
516 usage (EXIT_FAILURE);
519 else
521 if (!mode)
522 mode = argv[optind++];
525 if (optind >= argc)
527 if (!mode || mode != argv[optind - 1])
528 error (0, 0, _("missing operand"));
529 else
530 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
531 usage (EXIT_FAILURE);
534 if (reference_file)
536 change = mode_create_from_ref (reference_file);
537 if (!change)
538 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
539 quote (reference_file));
541 else
543 change = mode_compile (mode);
544 if (!change)
546 error (0, 0, _("invalid mode: %s"), quote (mode));
547 usage (EXIT_FAILURE);
549 umask_value = umask (0);
552 if (recurse && preserve_root)
554 static struct dev_ino dev_ino_buf;
555 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
556 if (root_dev_ino == NULL)
557 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
558 quote ("/"));
560 else
562 root_dev_ino = NULL;
565 ok = process_files (argv + optind,
566 FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
568 return ok ? EXIT_SUCCESS : EXIT_FAILURE;