dd: output final progress before syncing
[coreutils.git] / src / chmod.c
blob3776a7e057a40af68f7d9802983820ba8ab38f66
1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-2022 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 <https://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 "die.h"
27 #include "error.h"
28 #include "filemode.h"
29 #include "ignore-value.h"
30 #include "modechange.h"
31 #include "quote.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 struct change_status
44 enum
46 CH_NO_STAT,
47 CH_FAILED,
48 CH_NOT_APPLIED,
49 CH_NO_CHANGE_REQUESTED,
50 CH_SUCCEEDED
52 status;
53 mode_t old_mode;
54 mode_t new_mode;
57 enum Verbosity
59 /* Print a message for each file that is processed. */
60 V_high,
62 /* Print a message for each file whose attributes we change. */
63 V_changes_only,
65 /* Do not be verbose. This is the default. */
66 V_off
69 /* The desired change to the mode. */
70 static struct mode_change *change;
72 /* The initial umask value, if it might be needed. */
73 static mode_t umask_value;
75 /* If true, change the modes of directories recursively. */
76 static bool recurse;
78 /* If true, force silence (suppress most of error messages). */
79 static bool force_silent;
81 /* If true, diagnose surprises from naive misuses like "chmod -r file".
82 POSIX allows diagnostics here, as portable code is supposed to use
83 "chmod -- -r file". */
84 static bool diagnose_surprises;
86 /* Level of verbosity. */
87 static enum Verbosity verbosity = V_off;
89 /* Pointer to the device and inode numbers of '/', when --recursive.
90 Otherwise NULL. */
91 static struct dev_ino *root_dev_ino;
93 /* For long options that have no equivalent short option, use a
94 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
95 enum
97 NO_PRESERVE_ROOT = CHAR_MAX + 1,
98 PRESERVE_ROOT,
99 REFERENCE_FILE_OPTION
102 static struct option const long_options[] =
104 {"changes", no_argument, NULL, 'c'},
105 {"recursive", no_argument, NULL, 'R'},
106 {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
107 {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
108 {"quiet", no_argument, NULL, 'f'},
109 {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
110 {"silent", no_argument, NULL, 'f'},
111 {"verbose", no_argument, NULL, 'v'},
112 {GETOPT_HELP_OPTION_DECL},
113 {GETOPT_VERSION_OPTION_DECL},
114 {NULL, 0, NULL, 0}
117 /* Return true if the chmodable permission bits of FILE changed.
118 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
120 static bool
121 mode_changed (int dir_fd, char const *file, char const *file_full_name,
122 mode_t old_mode, mode_t new_mode)
124 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
126 /* The new mode contains unusual bits that the call to chmod may
127 have silently cleared. Check whether they actually changed. */
129 struct stat new_stats;
131 if (fstatat (dir_fd, file, &new_stats, 0) != 0)
133 if (! force_silent)
134 error (0, errno, _("getting new attributes of %s"),
135 quoteaf (file_full_name));
136 return false;
139 new_mode = new_stats.st_mode;
142 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
145 /* Tell the user how/if the MODE of FILE has been changed.
146 CH describes what (if anything) has happened. */
148 static void
149 describe_change (char const *file, struct change_status const *ch)
151 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
152 char old_perms[12];
153 char const *fmt;
154 char const *quoted_file = quoteaf (file);
156 switch (ch->status)
158 case CH_NOT_APPLIED:
159 printf (_("neither symbolic link %s nor referent has been changed\n"),
160 quoted_file);
161 return;
163 case CH_NO_STAT:
164 printf (_("%s could not be accessed\n"), quoted_file);
165 return;
167 default:
168 break;
171 unsigned long int
172 old_m = ch->old_mode & CHMOD_MODE_BITS,
173 m = ch->new_mode & CHMOD_MODE_BITS;
175 strmode (ch->new_mode, perms);
176 perms[10] = '\0'; /* Remove trailing space. */
178 strmode (ch->old_mode, old_perms);
179 old_perms[10] = '\0'; /* Remove trailing space. */
181 switch (ch->status)
183 case CH_SUCCEEDED:
184 fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
185 break;
186 case CH_FAILED:
187 fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
188 break;
189 case CH_NO_CHANGE_REQUESTED:
190 fmt = _("mode of %s retained as %04lo (%s)\n");
191 printf (fmt, quoted_file, m, &perms[1]);
192 return;
193 default:
194 abort ();
196 printf (fmt, quoted_file, old_m, &old_perms[1], m, &perms[1]);
199 /* Change the mode of FILE.
200 Return true if successful. This function is called
201 once for every file system object that fts encounters. */
203 static bool
204 process_file (FTS *fts, FTSENT *ent)
206 char const *file_full_name = ent->fts_path;
207 char const *file = ent->fts_accpath;
208 const struct stat *file_stats = ent->fts_statp;
209 struct change_status ch = { 0, };
210 ch.status = CH_NO_STAT;
212 switch (ent->fts_info)
214 case FTS_DP:
215 return true;
217 case FTS_NS:
218 /* For a top-level file or directory, this FTS_NS (stat failed)
219 indicator is determined at the time of the initial fts_open call.
220 With programs like chmod, chown, and chgrp, that modify
221 permissions, it is possible that the file in question is
222 accessible when control reaches this point. So, if this is
223 the first time we've seen the FTS_NS for this file, tell
224 fts_read to stat it "again". */
225 if (ent->fts_level == 0 && ent->fts_number == 0)
227 ent->fts_number = 1;
228 fts_set (fts, ent, FTS_AGAIN);
229 return true;
231 if (! force_silent)
232 error (0, ent->fts_errno, _("cannot access %s"),
233 quoteaf (file_full_name));
234 break;
236 case FTS_ERR:
237 if (! force_silent)
238 error (0, ent->fts_errno, "%s", quotef (file_full_name));
239 break;
241 case FTS_DNR:
242 if (! force_silent)
243 error (0, ent->fts_errno, _("cannot read directory %s"),
244 quoteaf (file_full_name));
245 break;
247 case FTS_SLNONE:
248 if (! force_silent)
249 error (0, 0, _("cannot operate on dangling symlink %s"),
250 quoteaf (file_full_name));
251 break;
253 case FTS_DC: /* directory that causes cycles */
254 if (cycle_warning_required (fts, ent))
256 emit_cycle_warning (file_full_name);
257 return false;
259 FALLTHROUGH;
260 default:
261 ch.status = CH_NOT_APPLIED;
262 break;
265 if (ch.status == CH_NOT_APPLIED
266 && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
268 ROOT_DEV_INO_WARN (file_full_name);
269 /* Tell fts not to traverse into this hierarchy. */
270 fts_set (fts, ent, FTS_SKIP);
271 /* Ensure that we do not process "/" on the second visit. */
272 ignore_value (fts_read (fts));
273 return false;
276 if (ch.status == CH_NOT_APPLIED && ! S_ISLNK (file_stats->st_mode))
278 ch.old_mode = file_stats->st_mode;
279 ch.new_mode = mode_adjust (ch.old_mode, S_ISDIR (ch.old_mode) != 0,
280 umask_value, change, NULL);
281 if (chmodat (fts->fts_cwd_fd, file, ch.new_mode) == 0)
282 ch.status = CH_SUCCEEDED;
283 else
285 if (! force_silent)
286 error (0, errno, _("changing permissions of %s"),
287 quoteaf (file_full_name));
288 ch.status = CH_FAILED;
292 if (verbosity != V_off)
294 if (ch.status == CH_SUCCEEDED
295 && !mode_changed (fts->fts_cwd_fd, file, file_full_name,
296 ch.old_mode, ch.new_mode))
297 ch.status = CH_NO_CHANGE_REQUESTED;
299 if (ch.status == CH_SUCCEEDED || verbosity == V_high)
300 describe_change (file_full_name, &ch);
303 if (CH_NO_CHANGE_REQUESTED <= ch.status && diagnose_surprises)
305 mode_t naively_expected_mode =
306 mode_adjust (ch.old_mode, S_ISDIR (ch.old_mode) != 0, 0, change, NULL);
307 if (ch.new_mode & ~naively_expected_mode)
309 char new_perms[12];
310 char naively_expected_perms[12];
311 strmode (ch.new_mode, new_perms);
312 strmode (naively_expected_mode, naively_expected_perms);
313 new_perms[10] = naively_expected_perms[10] = '\0';
314 error (0, 0,
315 _("%s: new permissions are %s, not %s"),
316 quotef (file_full_name),
317 new_perms + 1, naively_expected_perms + 1);
318 ch.status = CH_FAILED;
322 if ( ! recurse)
323 fts_set (fts, ent, FTS_SKIP);
325 return CH_NOT_APPLIED <= ch.status;
328 /* Recursively change the modes of the specified FILES (the last entry
329 of which is NULL). BIT_FLAGS controls how fts works.
330 Return true if successful. */
332 static bool
333 process_files (char **files, int bit_flags)
335 bool ok = true;
337 FTS *fts = xfts_open (files, bit_flags, NULL);
339 while (true)
341 FTSENT *ent;
343 ent = fts_read (fts);
344 if (ent == NULL)
346 if (errno != 0)
348 /* FIXME: try to give a better message */
349 if (! force_silent)
350 error (0, errno, _("fts_read failed"));
351 ok = false;
353 break;
356 ok &= process_file (fts, ent);
359 if (fts_close (fts) != 0)
361 error (0, errno, _("fts_close failed"));
362 ok = false;
365 return ok;
368 void
369 usage (int status)
371 if (status != EXIT_SUCCESS)
372 emit_try_help ();
373 else
375 printf (_("\
376 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
377 or: %s [OPTION]... OCTAL-MODE FILE...\n\
378 or: %s [OPTION]... --reference=RFILE FILE...\n\
380 program_name, program_name, program_name);
381 fputs (_("\
382 Change the mode of each FILE to MODE.\n\
383 With --reference, change the mode of each FILE to that of RFILE.\n\
385 "), stdout);
386 fputs (_("\
387 -c, --changes like verbose but report only when a change is made\n\
388 -f, --silent, --quiet suppress most error messages\n\
389 -v, --verbose output a diagnostic for every file processed\n\
390 "), stdout);
391 fputs (_("\
392 --no-preserve-root do not treat '/' specially (the default)\n\
393 --preserve-root fail to operate recursively on '/'\n\
394 "), stdout);
395 fputs (_("\
396 --reference=RFILE use RFILE's mode instead of MODE values\n\
397 "), stdout);
398 fputs (_("\
399 -R, --recursive change files and directories recursively\n\
400 "), stdout);
401 fputs (HELP_OPTION_DESCRIPTION, stdout);
402 fputs (VERSION_OPTION_DESCRIPTION, stdout);
403 fputs (_("\
405 Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\
406 "), stdout);
407 emit_ancillary_info (PROGRAM_NAME);
409 exit (status);
412 /* Parse the ASCII mode given on the command line into a linked list
413 of 'struct mode_change' and apply that to each file argument. */
416 main (int argc, char **argv)
418 char *mode = NULL;
419 size_t mode_len = 0;
420 size_t mode_alloc = 0;
421 bool ok;
422 bool preserve_root = false;
423 char const *reference_file = NULL;
424 int c;
426 initialize_main (&argc, &argv);
427 set_program_name (argv[0]);
428 setlocale (LC_ALL, "");
429 bindtextdomain (PACKAGE, LOCALEDIR);
430 textdomain (PACKAGE);
432 atexit (close_stdout);
434 recurse = force_silent = diagnose_surprises = false;
436 while ((c = getopt_long (argc, argv,
437 ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::"
438 "0::1::2::3::4::5::6::7::"),
439 long_options, NULL))
440 != -1)
442 switch (c)
444 case 'r':
445 case 'w':
446 case 'x':
447 case 'X':
448 case 's':
449 case 't':
450 case 'u':
451 case 'g':
452 case 'o':
453 case 'a':
454 case ',':
455 case '+':
456 case '=':
457 case '0': case '1': case '2': case '3':
458 case '4': case '5': case '6': case '7':
459 /* Support nonportable uses like "chmod -w", but diagnose
460 surprises due to umask confusion. Even though "--", "--r",
461 etc., are valid modes, there is no "case '-'" here since
462 getopt_long reserves leading "--" for long options. */
464 /* Allocate a mode string (e.g., "-rwx") by concatenating
465 the argument containing this option. If a previous mode
466 string was given, concatenate the previous string, a
467 comma, and the new string (e.g., "-s,-rwx"). */
469 char const *arg = argv[optind - 1];
470 size_t arg_len = strlen (arg);
471 size_t mode_comma_len = mode_len + !!mode_len;
472 size_t new_mode_len = mode_comma_len + arg_len;
473 if (mode_alloc <= new_mode_len)
475 mode_alloc = new_mode_len + 1;
476 mode = X2REALLOC (mode, &mode_alloc);
478 mode[mode_len] = ',';
479 memcpy (mode + mode_comma_len, arg, arg_len + 1);
480 mode_len = new_mode_len;
482 diagnose_surprises = true;
484 break;
485 case NO_PRESERVE_ROOT:
486 preserve_root = false;
487 break;
488 case PRESERVE_ROOT:
489 preserve_root = true;
490 break;
491 case REFERENCE_FILE_OPTION:
492 reference_file = optarg;
493 break;
494 case 'R':
495 recurse = true;
496 break;
497 case 'c':
498 verbosity = V_changes_only;
499 break;
500 case 'f':
501 force_silent = true;
502 break;
503 case 'v':
504 verbosity = V_high;
505 break;
506 case_GETOPT_HELP_CHAR;
507 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
508 default:
509 usage (EXIT_FAILURE);
513 if (reference_file)
515 if (mode)
517 error (0, 0, _("cannot combine mode and --reference options"));
518 usage (EXIT_FAILURE);
521 else
523 if (!mode)
524 mode = argv[optind++];
527 if (optind >= argc)
529 if (!mode || mode != argv[optind - 1])
530 error (0, 0, _("missing operand"));
531 else
532 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
533 usage (EXIT_FAILURE);
536 if (reference_file)
538 change = mode_create_from_ref (reference_file);
539 if (!change)
540 die (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
541 quoteaf (reference_file));
543 else
545 change = mode_compile (mode);
546 if (!change)
548 error (0, 0, _("invalid mode: %s"), quote (mode));
549 usage (EXIT_FAILURE);
551 umask_value = umask (0);
554 if (recurse && preserve_root)
556 static struct dev_ino dev_ino_buf;
557 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
558 if (root_dev_ino == NULL)
559 die (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
560 quoteaf ("/"));
562 else
564 root_dev_ino = NULL;
567 ok = process_files (argv + optind,
568 FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
570 IF_LINT (free (change));
572 return ok ? EXIT_SUCCESS : EXIT_FAILURE;