maint: revert "build: update gnulib submodule to latest"
[coreutils/ericb.git] / src / chmod.c
blob6fec84a5e5ccab1ea52c79ad89c87297e64983ce
1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-1991, 1995-2011 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 old_mode, mode_t mode,
141 enum Change_status changed)
143 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
144 char old_perms[12];
145 const char *fmt;
147 if (changed == CH_NOT_APPLIED)
149 printf (_("neither symbolic link %s nor referent has been changed\n"),
150 quote (file));
151 return;
154 strmode (mode, perms);
155 perms[10] = '\0'; /* Remove trailing space. */
157 strmode (old_mode, old_perms);
158 old_perms[10] = '\0'; /* Remove trailing space. */
160 switch (changed)
162 case CH_SUCCEEDED:
163 fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
164 break;
165 case CH_FAILED:
166 fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
167 break;
168 case CH_NO_CHANGE_REQUESTED:
169 fmt = _("mode of %s retained as %04lo (%s)\n");
170 printf (fmt, quote (file),
171 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
172 return;
173 default:
174 abort ();
176 printf (fmt, quote (file),
177 (unsigned long int) (old_mode & CHMOD_MODE_BITS), &old_perms[1],
178 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
181 /* Change the mode of FILE.
182 Return true if successful. This function is called
183 once for every file system object that fts encounters. */
185 static bool
186 process_file (FTS *fts, FTSENT *ent)
188 char const *file_full_name = ent->fts_path;
189 char const *file = ent->fts_accpath;
190 const struct stat *file_stats = ent->fts_statp;
191 mode_t old_mode IF_LINT ( = 0);
192 mode_t new_mode IF_LINT ( = 0);
193 bool ok = true;
194 bool chmod_succeeded = false;
196 switch (ent->fts_info)
198 case FTS_DP:
199 return true;
201 case FTS_NS:
202 /* For a top-level file or directory, this FTS_NS (stat failed)
203 indicator is determined at the time of the initial fts_open call.
204 With programs like chmod, chown, and chgrp, that modify
205 permissions, it is possible that the file in question is
206 accessible when control reaches this point. So, if this is
207 the first time we've seen the FTS_NS for this file, tell
208 fts_read to stat it "again". */
209 if (ent->fts_level == 0 && ent->fts_number == 0)
211 ent->fts_number = 1;
212 fts_set (fts, ent, FTS_AGAIN);
213 return true;
215 if (! force_silent)
216 error (0, ent->fts_errno, _("cannot access %s"),
217 quote (file_full_name));
218 ok = false;
219 break;
221 case FTS_ERR:
222 if (! force_silent)
223 error (0, ent->fts_errno, "%s", quote (file_full_name));
224 ok = false;
225 break;
227 case FTS_DNR:
228 if (! force_silent)
229 error (0, ent->fts_errno, _("cannot read directory %s"),
230 quote (file_full_name));
231 ok = false;
232 break;
234 case FTS_SLNONE:
235 if (! force_silent)
236 error (0, 0, _("cannot operate on dangling symlink %s"),
237 quote (file_full_name));
238 ok = false;
239 break;
241 case FTS_DC: /* directory that causes cycles */
242 if (cycle_warning_required (fts, ent))
244 emit_cycle_warning (file_full_name);
245 return false;
247 break;
249 default:
250 break;
253 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
255 ROOT_DEV_INO_WARN (file_full_name);
256 /* Tell fts not to traverse into this hierarchy. */
257 fts_set (fts, ent, FTS_SKIP);
258 /* Ensure that we do not process "/" on the second visit. */
259 ignore_value (fts_read (fts));
260 return false;
263 if (ok)
265 old_mode = file_stats->st_mode;
266 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
267 change, NULL);
269 if (! S_ISLNK (old_mode))
271 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
272 chmod_succeeded = true;
273 else
275 if (! force_silent)
276 error (0, errno, _("changing permissions of %s"),
277 quote (file_full_name));
278 ok = false;
283 if (verbosity != V_off)
285 bool changed = (chmod_succeeded
286 && mode_changed (file, old_mode, new_mode));
288 if (changed || verbosity == V_high)
290 enum Change_status ch_status =
291 (!ok ? CH_FAILED
292 : !chmod_succeeded ? CH_NOT_APPLIED
293 : !changed ? CH_NO_CHANGE_REQUESTED
294 : CH_SUCCEEDED);
295 describe_change (file_full_name, old_mode, new_mode, ch_status);
299 if (chmod_succeeded && diagnose_surprises)
301 mode_t naively_expected_mode =
302 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
303 if (new_mode & ~naively_expected_mode)
305 char new_perms[12];
306 char naively_expected_perms[12];
307 strmode (new_mode, new_perms);
308 strmode (naively_expected_mode, naively_expected_perms);
309 new_perms[10] = naively_expected_perms[10] = '\0';
310 error (0, 0,
311 _("%s: new permissions are %s, not %s"),
312 quotearg_colon (file_full_name),
313 new_perms + 1, naively_expected_perms + 1);
314 ok = false;
318 if ( ! recurse)
319 fts_set (fts, ent, FTS_SKIP);
321 return ok;
324 /* Recursively change the modes of the specified FILES (the last entry
325 of which is NULL). BIT_FLAGS controls how fts works.
326 Return true if successful. */
328 static bool
329 process_files (char **files, int bit_flags)
331 bool ok = true;
333 FTS *fts = xfts_open (files, bit_flags, NULL);
335 while (1)
337 FTSENT *ent;
339 ent = fts_read (fts);
340 if (ent == NULL)
342 if (errno != 0)
344 /* FIXME: try to give a better message */
345 if (! force_silent)
346 error (0, errno, _("fts_read failed"));
347 ok = false;
349 break;
352 ok &= process_file (fts, ent);
355 if (fts_close (fts) != 0)
357 error (0, errno, _("fts_close failed"));
358 ok = false;
361 return ok;
364 void
365 usage (int status)
367 if (status != EXIT_SUCCESS)
368 fprintf (stderr, _("Try `%s --help' for more information.\n"),
369 program_name);
370 else
372 printf (_("\
373 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
374 or: %s [OPTION]... OCTAL-MODE FILE...\n\
375 or: %s [OPTION]... --reference=RFILE FILE...\n\
377 program_name, program_name, program_name);
378 fputs (_("\
379 Change the mode of each FILE to MODE.\n\
381 -c, --changes like verbose but report only when a change is made\n\
382 "), stdout);
383 fputs (_("\
384 --no-preserve-root do not treat `/' specially (the default)\n\
385 --preserve-root fail to operate recursively on `/'\n\
386 "), stdout);
387 fputs (_("\
388 -f, --silent, --quiet suppress most error messages\n\
389 -v, --verbose output a diagnostic for every file processed\n\
390 --reference=RFILE use RFILE's mode instead of MODE values\n\
391 -R, --recursive change files and directories recursively\n\
392 "), stdout);
393 fputs (HELP_OPTION_DESCRIPTION, stdout);
394 fputs (VERSION_OPTION_DESCRIPTION, stdout);
395 fputs (_("\
397 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
398 "), stdout);
399 emit_ancillary_info ();
401 exit (status);
404 /* Parse the ASCII mode given on the command line into a linked list
405 of `struct mode_change' and apply that to each file argument. */
408 main (int argc, char **argv)
410 char *mode = NULL;
411 size_t mode_len = 0;
412 size_t mode_alloc = 0;
413 bool ok;
414 bool preserve_root = false;
415 char const *reference_file = NULL;
416 int c;
418 initialize_main (&argc, &argv);
419 set_program_name (argv[0]);
420 setlocale (LC_ALL, "");
421 bindtextdomain (PACKAGE, LOCALEDIR);
422 textdomain (PACKAGE);
424 atexit (close_stdout);
426 recurse = force_silent = diagnose_surprises = false;
428 while ((c = getopt_long (argc, argv,
429 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
430 long_options, NULL))
431 != -1)
433 switch (c)
435 case 'r':
436 case 'w':
437 case 'x':
438 case 'X':
439 case 's':
440 case 't':
441 case 'u':
442 case 'g':
443 case 'o':
444 case 'a':
445 case ',':
446 case '+':
447 case '=':
448 /* Support nonportable uses like "chmod -w", but diagnose
449 surprises due to umask confusion. Even though "--", "--r",
450 etc., are valid modes, there is no "case '-'" here since
451 getopt_long reserves leading "--" for long options. */
453 /* Allocate a mode string (e.g., "-rwx") by concatenating
454 the argument containing this option. If a previous mode
455 string was given, concatenate the previous string, a
456 comma, and the new string (e.g., "-s,-rwx"). */
458 char const *arg = argv[optind - 1];
459 size_t arg_len = strlen (arg);
460 size_t mode_comma_len = mode_len + !!mode_len;
461 size_t new_mode_len = mode_comma_len + arg_len;
462 if (mode_alloc <= new_mode_len)
464 mode_alloc = new_mode_len + 1;
465 mode = X2REALLOC (mode, &mode_alloc);
467 mode[mode_len] = ',';
468 strcpy (mode + mode_comma_len, arg);
469 mode_len = new_mode_len;
471 diagnose_surprises = true;
473 break;
474 case NO_PRESERVE_ROOT:
475 preserve_root = false;
476 break;
477 case PRESERVE_ROOT:
478 preserve_root = true;
479 break;
480 case REFERENCE_FILE_OPTION:
481 reference_file = optarg;
482 break;
483 case 'R':
484 recurse = true;
485 break;
486 case 'c':
487 verbosity = V_changes_only;
488 break;
489 case 'f':
490 force_silent = true;
491 break;
492 case 'v':
493 verbosity = V_high;
494 break;
495 case_GETOPT_HELP_CHAR;
496 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
497 default:
498 usage (EXIT_FAILURE);
502 if (reference_file)
504 if (mode)
506 error (0, 0, _("cannot combine mode and --reference options"));
507 usage (EXIT_FAILURE);
510 else
512 if (!mode)
513 mode = argv[optind++];
516 if (optind >= argc)
518 if (!mode || mode != argv[optind - 1])
519 error (0, 0, _("missing operand"));
520 else
521 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
522 usage (EXIT_FAILURE);
525 if (reference_file)
527 change = mode_create_from_ref (reference_file);
528 if (!change)
529 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
530 quote (reference_file));
532 else
534 change = mode_compile (mode);
535 if (!change)
537 error (0, 0, _("invalid mode: %s"), quote (mode));
538 usage (EXIT_FAILURE);
540 umask_value = umask (0);
543 if (recurse && preserve_root)
545 static struct dev_ino dev_ino_buf;
546 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
547 if (root_dev_ino == NULL)
548 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
549 quote ("/"));
551 else
553 root_dev_ino = NULL;
556 ok = process_files (argv + optind,
557 FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
559 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);