shuf: tiny simplification
[coreutils.git] / src / chmod.c
blobb95371d0d30f36b9c1c99c280bc7dddf8b425fa2
1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-2024 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 "assure.h"
26 #include "dev-ino.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 struct change_status
43 enum
45 CH_NO_STAT,
46 CH_FAILED,
47 CH_NOT_APPLIED,
48 CH_NO_CHANGE_REQUESTED,
49 CH_SUCCEEDED
51 status;
52 mode_t old_mode;
53 mode_t new_mode;
56 enum Verbosity
58 /* Print a message for each file that is processed. */
59 V_high,
61 /* Print a message for each file whose attributes we change. */
62 V_changes_only,
64 /* Do not be verbose. This is the default. */
65 V_off
68 /* The desired change to the mode. */
69 static struct mode_change *change;
71 /* The initial umask value, if it might be needed. */
72 static mode_t umask_value;
74 /* If true, change the modes of directories recursively. */
75 static bool recurse;
77 /* 1 if --dereference, 0 if --no-dereference, -1 if neither has been
78 specified. */
79 static int dereference = -1;
81 /* If true, force silence (suppress most of error messages). */
82 static bool force_silent;
84 /* If true, diagnose surprises from naive misuses like "chmod -r file".
85 POSIX allows diagnostics here, as portable code is supposed to use
86 "chmod -- -r file". */
87 static bool diagnose_surprises;
89 /* Level of verbosity. */
90 static enum Verbosity verbosity = V_off;
92 /* Pointer to the device and inode numbers of '/', when --recursive.
93 Otherwise nullptr. */
94 static struct dev_ino *root_dev_ino;
96 /* For long options that have no equivalent short option, use a
97 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
98 enum
100 DEREFERENCE_OPTION = CHAR_MAX + 1,
101 NO_PRESERVE_ROOT,
102 PRESERVE_ROOT,
103 REFERENCE_FILE_OPTION
106 static struct option const long_options[] =
108 {"changes", no_argument, nullptr, 'c'},
109 {"dereference", no_argument, nullptr, DEREFERENCE_OPTION},
110 {"recursive", no_argument, nullptr, 'R'},
111 {"no-dereference", no_argument, nullptr, 'h'},
112 {"no-preserve-root", no_argument, nullptr, NO_PRESERVE_ROOT},
113 {"preserve-root", no_argument, nullptr, PRESERVE_ROOT},
114 {"quiet", no_argument, nullptr, 'f'},
115 {"reference", required_argument, nullptr, REFERENCE_FILE_OPTION},
116 {"silent", no_argument, nullptr, 'f'},
117 {"verbose", no_argument, nullptr, 'v'},
118 {GETOPT_HELP_OPTION_DECL},
119 {GETOPT_VERSION_OPTION_DECL},
120 {nullptr, 0, nullptr, 0}
123 /* Return true if the chmodable permission bits of FILE changed.
124 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
126 static bool
127 mode_changed (int dir_fd, char const *file, char const *file_full_name,
128 mode_t old_mode, mode_t new_mode)
130 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
132 /* The new mode contains unusual bits that the call to chmod may
133 have silently cleared. Check whether they actually changed. */
135 struct stat new_stats;
137 if (fstatat (dir_fd, file, &new_stats, 0) != 0)
139 if (! force_silent)
140 error (0, errno, _("getting new attributes of %s"),
141 quoteaf (file_full_name));
142 return false;
145 new_mode = new_stats.st_mode;
148 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
151 /* Tell the user how/if the MODE of FILE has been changed.
152 CH describes what (if anything) has happened. */
154 static void
155 describe_change (char const *file, struct change_status const *ch)
157 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
158 char old_perms[12];
159 char const *fmt;
160 char const *quoted_file = quoteaf (file);
162 switch (ch->status)
164 case CH_NOT_APPLIED:
165 printf (_("neither symbolic link %s nor referent has been changed\n"),
166 quoted_file);
167 return;
169 case CH_NO_STAT:
170 printf (_("%s could not be accessed\n"), quoted_file);
171 return;
173 default:
174 break;
177 unsigned long int
178 old_m = ch->old_mode & CHMOD_MODE_BITS,
179 m = ch->new_mode & CHMOD_MODE_BITS;
181 strmode (ch->new_mode, perms);
182 perms[10] = '\0'; /* Remove trailing space. */
184 strmode (ch->old_mode, old_perms);
185 old_perms[10] = '\0'; /* Remove trailing space. */
187 switch (ch->status)
189 case CH_SUCCEEDED:
190 fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
191 break;
192 case CH_FAILED:
193 fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
194 break;
195 case CH_NO_CHANGE_REQUESTED:
196 fmt = _("mode of %s retained as %04lo (%s)\n");
197 printf (fmt, quoted_file, m, &perms[1]);
198 return;
199 default:
200 affirm (false);
202 printf (fmt, quoted_file, old_m, &old_perms[1], m, &perms[1]);
205 /* Change the mode of FILE.
206 Return true if successful. This function is called
207 once for every file system object that fts encounters. */
209 static bool
210 process_file (FTS *fts, FTSENT *ent)
212 char const *file_full_name = ent->fts_path;
213 char const *file = ent->fts_accpath;
214 const struct stat *file_stats = ent->fts_statp;
215 struct change_status ch = {0};
216 ch.status = CH_NO_STAT;
217 struct stat stat_buf;
219 switch (ent->fts_info)
221 case FTS_DP:
222 return true;
224 case FTS_NS:
225 /* For a top-level file or directory, this FTS_NS (stat failed)
226 indicator is determined at the time of the initial fts_open call.
227 With programs like chmod, chown, and chgrp, that modify
228 permissions, it is possible that the file in question is
229 accessible when control reaches this point. So, if this is
230 the first time we've seen the FTS_NS for this file, tell
231 fts_read to stat it "again". */
232 if (ent->fts_level == 0 && ent->fts_number == 0)
234 ent->fts_number = 1;
235 fts_set (fts, ent, FTS_AGAIN);
236 return true;
238 if (! force_silent)
239 error (0, ent->fts_errno, _("cannot access %s"),
240 quoteaf (file_full_name));
241 break;
243 case FTS_ERR:
244 if (! force_silent)
245 error (0, ent->fts_errno, "%s", quotef (file_full_name));
246 break;
248 case FTS_DNR:
249 if (! force_silent)
250 error (0, ent->fts_errno, _("cannot read directory %s"),
251 quoteaf (file_full_name));
252 break;
254 case FTS_SLNONE:
255 if (dereference)
257 if (! force_silent)
258 error (0, 0, _("cannot operate on dangling symlink %s"),
259 quoteaf (file_full_name));
260 break;
262 ch.status = CH_NOT_APPLIED;
263 break;
265 case FTS_SL:
266 if (dereference == 1)
268 if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
270 if (! force_silent)
271 error (0, errno, _("cannot dereference %s"),
272 quoteaf (file_full_name));
273 break;
276 file_stats = &stat_buf;
278 ch.status = CH_NOT_APPLIED;
279 break;
281 case FTS_DC: /* directory that causes cycles */
282 if (cycle_warning_required (fts, ent))
284 emit_cycle_warning (file_full_name);
285 return false;
287 FALLTHROUGH;
288 default:
289 ch.status = CH_NOT_APPLIED;
290 break;
293 if (ch.status == CH_NOT_APPLIED
294 && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
296 ROOT_DEV_INO_WARN (file_full_name);
297 /* Tell fts not to traverse into this hierarchy. */
298 fts_set (fts, ent, FTS_SKIP);
299 /* Ensure that we do not process "/" on the second visit. */
300 ignore_value (fts_read (fts));
301 return false;
304 if (ch.status == CH_NOT_APPLIED)
306 ch.old_mode = file_stats->st_mode;
307 ch.new_mode = mode_adjust (ch.old_mode, S_ISDIR (ch.old_mode) != 0,
308 umask_value, change, nullptr);
309 bool follow_symlink = !!dereference;
310 if (dereference == -1) /* -H with/without -R, -P without -R. */
311 follow_symlink = ent->fts_level == 0;
312 if (fchmodat (fts->fts_cwd_fd, file, ch.new_mode,
313 follow_symlink ? 0 : AT_SYMLINK_NOFOLLOW) == 0)
314 ch.status = CH_SUCCEEDED;
315 else
317 if (! is_ENOTSUP (errno))
319 if (! force_silent)
320 error (0, errno, _("changing permissions of %s"),
321 quoteaf (file_full_name));
323 ch.status = CH_FAILED;
325 /* else treat not supported as not applied. */
329 if (verbosity != V_off)
331 if (ch.status == CH_SUCCEEDED
332 && !mode_changed (fts->fts_cwd_fd, file, file_full_name,
333 ch.old_mode, ch.new_mode))
334 ch.status = CH_NO_CHANGE_REQUESTED;
336 if (ch.status == CH_SUCCEEDED || verbosity == V_high)
337 describe_change (file_full_name, &ch);
340 if (CH_NO_CHANGE_REQUESTED <= ch.status && diagnose_surprises)
342 mode_t naively_expected_mode =
343 mode_adjust (ch.old_mode, S_ISDIR (ch.old_mode) != 0,
344 0, change, nullptr);
345 if (ch.new_mode & ~naively_expected_mode)
347 char new_perms[12];
348 char naively_expected_perms[12];
349 strmode (ch.new_mode, new_perms);
350 strmode (naively_expected_mode, naively_expected_perms);
351 new_perms[10] = naively_expected_perms[10] = '\0';
352 error (0, 0,
353 _("%s: new permissions are %s, not %s"),
354 quotef (file_full_name),
355 new_perms + 1, naively_expected_perms + 1);
356 ch.status = CH_FAILED;
360 if ( ! recurse)
361 fts_set (fts, ent, FTS_SKIP);
363 return CH_NOT_APPLIED <= ch.status;
366 /* Recursively change the modes of the specified FILES (the last entry
367 of which is null). BIT_FLAGS controls how fts works.
368 Return true if successful. */
370 static bool
371 process_files (char **files, int bit_flags)
373 bool ok = true;
375 FTS *fts = xfts_open (files, bit_flags, nullptr);
377 while (true)
379 FTSENT *ent;
381 ent = fts_read (fts);
382 if (ent == nullptr)
384 if (errno != 0)
386 /* FIXME: try to give a better message */
387 if (! force_silent)
388 error (0, errno, _("fts_read failed"));
389 ok = false;
391 break;
394 ok &= process_file (fts, ent);
397 if (fts_close (fts) != 0)
399 error (0, errno, _("fts_close failed"));
400 ok = false;
403 return ok;
406 void
407 usage (int status)
409 if (status != EXIT_SUCCESS)
410 emit_try_help ();
411 else
413 printf (_("\
414 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
415 or: %s [OPTION]... OCTAL-MODE FILE...\n\
416 or: %s [OPTION]... --reference=RFILE FILE...\n\
418 program_name, program_name, program_name);
419 fputs (_("\
420 Change the mode of each FILE to MODE.\n\
421 With --reference, change the mode of each FILE to that of RFILE.\n\
423 "), stdout);
424 fputs (_("\
425 -c, --changes like verbose but report only when a change is made\n\
426 -f, --silent, --quiet suppress most error messages\n\
427 -v, --verbose output a diagnostic for every file processed\n\
428 "), stdout);
429 fputs (_("\
430 --dereference affect the referent of each symbolic link,\n\
431 rather than the symbolic link itself\n\
432 -h, --no-dereference affect each symbolic link, rather than the referent\n\
433 "), stdout);
434 fputs (_("\
435 --no-preserve-root do not treat '/' specially (the default)\n\
436 --preserve-root fail to operate recursively on '/'\n\
437 "), stdout);
438 fputs (_("\
439 --reference=RFILE use RFILE's mode instead of specifying MODE values.\n\
440 RFILE is always dereferenced if a symbolic link.\n\
441 "), stdout);
442 fputs (_("\
443 -R, --recursive change files and directories recursively\n\
444 "), stdout);
445 emit_symlink_recurse_options ("-H");
446 fputs (HELP_OPTION_DESCRIPTION, stdout);
447 fputs (VERSION_OPTION_DESCRIPTION, stdout);
448 fputs (_("\
450 Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\
451 "), stdout);
452 emit_ancillary_info (PROGRAM_NAME);
454 exit (status);
457 /* Parse the ASCII mode given on the command line into a linked list
458 of 'struct mode_change' and apply that to each file argument. */
461 main (int argc, char **argv)
463 char *mode = nullptr;
464 idx_t mode_len = 0;
465 idx_t mode_alloc = 0;
466 bool ok;
467 bool preserve_root = false;
468 char const *reference_file = nullptr;
469 int c;
470 int bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL;
472 initialize_main (&argc, &argv);
473 set_program_name (argv[0]);
474 setlocale (LC_ALL, "");
475 bindtextdomain (PACKAGE, LOCALEDIR);
476 textdomain (PACKAGE);
478 atexit (close_stdout);
480 recurse = force_silent = diagnose_surprises = false;
482 while ((c = getopt_long (argc, argv,
483 ("HLPRcfhvr::w::x::X::s::t::u::g::o::a::,::+::=::"
484 "0::1::2::3::4::5::6::7::"),
485 long_options, nullptr))
486 != -1)
488 switch (c)
491 case 'H': /* Traverse command-line symlinks-to-directories. */
492 bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL;
493 break;
495 case 'L': /* Traverse all symlinks-to-directories. */
496 bit_flags = FTS_LOGICAL;
497 break;
499 case 'P': /* Traverse no symlinks-to-directories. */
500 bit_flags = FTS_PHYSICAL;
501 break;
503 case 'h': /* --no-dereference: affect symlinks */
504 dereference = 0;
505 break;
507 case DEREFERENCE_OPTION: /* --dereference: affect the referent
508 of each symlink */
509 dereference = 1;
510 break;
512 case 'r':
513 case 'w':
514 case 'x':
515 case 'X':
516 case 's':
517 case 't':
518 case 'u':
519 case 'g':
520 case 'o':
521 case 'a':
522 case ',':
523 case '+':
524 case '=':
525 case '0': case '1': case '2': case '3':
526 case '4': case '5': case '6': case '7':
527 /* Support non-portable uses like "chmod -w", but diagnose
528 surprises due to umask confusion. Even though "--", "--r",
529 etc., are valid modes, there is no "case '-'" here since
530 getopt_long reserves leading "--" for long options. */
532 /* Allocate a mode string (e.g., "-rwx") by concatenating
533 the argument containing this option. If a previous mode
534 string was given, concatenate the previous string, a
535 comma, and the new string (e.g., "-s,-rwx"). */
537 char const *arg = argv[optind - 1];
538 idx_t arg_len = strlen (arg);
539 idx_t mode_comma_len = mode_len + !!mode_len;
540 idx_t new_mode_len = mode_comma_len + arg_len;
541 assume (0 <= new_mode_len); /* Pacify GCC bug #109613. */
542 if (mode_alloc <= new_mode_len)
543 mode = xpalloc (mode, &mode_alloc,
544 new_mode_len + 1 - mode_alloc, -1, 1);
545 mode[mode_len] = ',';
546 memcpy (mode + mode_comma_len, arg, arg_len + 1);
547 mode_len = new_mode_len;
549 diagnose_surprises = true;
551 break;
552 case NO_PRESERVE_ROOT:
553 preserve_root = false;
554 break;
555 case PRESERVE_ROOT:
556 preserve_root = true;
557 break;
558 case REFERENCE_FILE_OPTION:
559 reference_file = optarg;
560 break;
561 case 'R':
562 recurse = true;
563 break;
564 case 'c':
565 verbosity = V_changes_only;
566 break;
567 case 'f':
568 force_silent = true;
569 break;
570 case 'v':
571 verbosity = V_high;
572 break;
573 case_GETOPT_HELP_CHAR;
574 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
575 default:
576 usage (EXIT_FAILURE);
580 if (recurse)
582 if (bit_flags == FTS_PHYSICAL)
584 if (dereference == 1)
585 error (EXIT_FAILURE, 0,
586 _("-R --dereference requires either -H or -L"));
587 dereference = 0;
591 if (dereference == -1 && bit_flags == FTS_LOGICAL)
592 dereference = 1;
594 if (reference_file)
596 if (mode)
598 error (0, 0, _("cannot combine mode and --reference options"));
599 usage (EXIT_FAILURE);
602 else
604 if (!mode)
605 mode = argv[optind++];
608 if (optind >= argc)
610 if (!mode || mode != argv[optind - 1])
611 error (0, 0, _("missing operand"));
612 else
613 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
614 usage (EXIT_FAILURE);
617 if (reference_file)
619 change = mode_create_from_ref (reference_file);
620 if (!change)
621 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
622 quoteaf (reference_file));
624 else
626 change = mode_compile (mode);
627 if (!change)
629 error (0, 0, _("invalid mode: %s"), quote (mode));
630 usage (EXIT_FAILURE);
632 umask_value = umask (0);
635 if (recurse && preserve_root)
637 static struct dev_ino dev_ino_buf;
638 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
639 if (root_dev_ino == nullptr)
640 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
641 quoteaf ("/"));
643 else
645 root_dev_ino = nullptr;
648 bit_flags |= FTS_DEFER_STAT;
649 ok = process_files (argv + optind, bit_flags);
651 main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);