1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-2020 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <getopt.h>
22 #include <sys/types.h>
23
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"
34
35 /* The official name of this program (e.g., no 'g' prefix). */
36 #define PROGRAM_NAME "chmod"
37
38 #define AUTHORS \
39 proper_name ("David MacKenzie"), \
40 proper_name ("Jim Meyering")
41
42 enum Change_status
43 {
44 CH_NOT_APPLIED,
45 CH_SUCCEEDED,
46 CH_FAILED,
47 CH_NO_CHANGE_REQUESTED
48 };
49
50 enum Verbosity
51 {
52 /* Print a message for each file that is processed. */
53 V_high,
54
55 /* Print a message for each file whose attributes we change. */
56 V_changes_only,
57
58 /* Do not be verbose. This is the default. */
59 V_off
60 };
61
62 /* The desired change to the mode. */
63 static struct mode_change *change;
64
65 /* The initial umask value, if it might be needed. */
66 static mode_t umask_value;
67
68 /* If true, change the modes of directories recursively. */
69 static bool recurse;
70
71 /* If true, force silence (suppress most of error messages). */
72 static bool force_silent;
73
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;
78
79 /* Level of verbosity. */
80 static enum Verbosity verbosity = V_off;
81
82 /* Pointer to the device and inode numbers of '/', when --recursive.
83 Otherwise NULL. */
84 static struct dev_ino *root_dev_ino;
85
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
89 {
90 NO_PRESERVE_ROOT = CHAR_MAX + 1,
91 PRESERVE_ROOT,
92 REFERENCE_FILE_OPTION
93 };
94
95 static struct option const long_options[] =
96 {
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}
108 };
109
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. */
112
113 static bool
mode_changed(int dir_fd,char const * file,char const * file_full_name,mode_t old_mode,mode_t new_mode)114 mode_changed (int dir_fd, char const *file, char const *file_full_name,
115 mode_t old_mode, mode_t new_mode)
116 {
117 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
118 {
119 /* The new mode contains unusual bits that the call to chmod may
120 have silently cleared. Check whether they actually changed. */
121
122 struct stat new_stats;
123
124 if (fstatat (dir_fd, file, &new_stats, 0) != 0)
125 {
126 if (! force_silent)
127 error (0, errno, _("getting new attributes of %s"),
128 quoteaf (file_full_name));
129 return false;
130 }
131
132 new_mode = new_stats.st_mode;
133 }
134
135 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
136 }
137
138 /* Tell the user how/if the MODE of FILE has been changed.
139 CHANGED describes what (if anything) has happened. */
140
141 static void
describe_change(const char * file,mode_t old_mode,mode_t mode,enum Change_status changed)142 describe_change (const char *file, mode_t old_mode, mode_t mode,
143 enum Change_status changed)
144 {
145 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
146 char old_perms[12];
147 const char *fmt;
148
149 if (changed == CH_NOT_APPLIED)
150 {
151 printf (_("neither symbolic link %s nor referent has been changed\n"),
152 quoteaf (file));
153 return;
154 }
155
156 strmode (mode, perms);
157 perms[10] = '\0'; /* Remove trailing space. */
158
159 strmode (old_mode, old_perms);
160 old_perms[10] = '\0'; /* Remove trailing space. */
161
162 switch (changed)
163 {
164 case CH_SUCCEEDED:
165 fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
166 break;
167 case CH_FAILED:
168 fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
169 break;
170 case CH_NO_CHANGE_REQUESTED:
171 fmt = _("mode of %s retained as %04lo (%s)\n");
172 printf (fmt, quoteaf (file),
173 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
174 return;
175 default:
176 abort ();
177 }
178 printf (fmt, quoteaf (file),
179 (unsigned long int) (old_mode & CHMOD_MODE_BITS), &old_perms[1],
180 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
181 }
182
183 /* Change the mode of FILE.
184 Return true if successful. This function is called
185 once for every file system object that fts encounters. */
186
187 static bool
process_file(FTS * fts,FTSENT * ent)188 process_file (FTS *fts, FTSENT *ent)
189 {
190 char const *file_full_name = ent->fts_path;
191 char const *file = ent->fts_accpath;
192 const struct stat *file_stats = ent->fts_statp;
193 mode_t old_mode IF_LINT ( = 0);
194 mode_t new_mode IF_LINT ( = 0);
195 bool ok = true;
196 bool chmod_succeeded = false;
197
198 switch (ent->fts_info)
199 {
200 case FTS_DP:
201 return true;
202
203 case FTS_NS:
204 /* For a top-level file or directory, this FTS_NS (stat failed)
205 indicator is determined at the time of the initial fts_open call.
206 With programs like chmod, chown, and chgrp, that modify
207 permissions, it is possible that the file in question is
208 accessible when control reaches this point. So, if this is
209 the first time we've seen the FTS_NS for this file, tell
210 fts_read to stat it "again". */
211 if (ent->fts_level == 0 && ent->fts_number == 0)
212 {
213 ent->fts_number = 1;
214 fts_set (fts, ent, FTS_AGAIN);
215 return true;
216 }
217 if (! force_silent)
218 error (0, ent->fts_errno, _("cannot access %s"),
219 quoteaf (file_full_name));
220 ok = false;
221 break;
222
223 case FTS_ERR:
224 if (! force_silent)
225 error (0, ent->fts_errno, "%s", quotef (file_full_name));
226 ok = false;
227 break;
228
229 case FTS_DNR:
230 if (! force_silent)
231 error (0, ent->fts_errno, _("cannot read directory %s"),
232 quoteaf (file_full_name));
233 ok = false;
234 break;
235
236 case FTS_SLNONE:
237 if (! force_silent)
238 error (0, 0, _("cannot operate on dangling symlink %s"),
239 quoteaf (file_full_name));
240 ok = false;
241 break;
242
243 case FTS_DC: /* directory that causes cycles */
244 if (cycle_warning_required (fts, ent))
245 {
246 emit_cycle_warning (file_full_name);
247 return false;
248 }
249 break;
250
251 default:
252 break;
253 }
254
255 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
256 {
257 ROOT_DEV_INO_WARN (file_full_name);
258 /* Tell fts not to traverse into this hierarchy. */
259 fts_set (fts, ent, FTS_SKIP);
260 /* Ensure that we do not process "/" on the second visit. */
261 ignore_value (fts_read (fts));
262 return false;
263 }
264
265 if (ok)
266 {
267 old_mode = file_stats->st_mode;
268 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
269 change, NULL);
270
271 if (! S_ISLNK (old_mode))
272 {
273 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
274 chmod_succeeded = true;
275 else
276 {
277 if (! force_silent)
278 error (0, errno, _("changing permissions of %s"),
279 quoteaf (file_full_name));
280 ok = false;
281 }
282 }
283 }
284
285 if (verbosity != V_off)
286 {
287 bool changed = (chmod_succeeded
288 && mode_changed (fts->fts_cwd_fd, file, file_full_name,
289 old_mode, new_mode));
290
291 if (changed || verbosity == V_high)
292 {
293 enum Change_status ch_status =
294 (!ok ? CH_FAILED
295 : !chmod_succeeded ? CH_NOT_APPLIED
296 : !changed ? CH_NO_CHANGE_REQUESTED
297 : CH_SUCCEEDED);
298 describe_change (file_full_name, old_mode, new_mode, ch_status);
299 }
300 }
301
302 if (chmod_succeeded && diagnose_surprises)
303 {
304 mode_t naively_expected_mode =
305 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
306 if (new_mode & ~naively_expected_mode)
307 {
308 char new_perms[12];
309 char naively_expected_perms[12];
310 strmode (new_mode, new_perms);
311 strmode (naively_expected_mode, naively_expected_perms);
312 new_perms[10] = naively_expected_perms[10] = '\0';
313 error (0, 0,
314 _("%s: new permissions are %s, not %s"),
315 quotef (file_full_name),
316 new_perms + 1, naively_expected_perms + 1);
317 ok = false;
318 }
319 }
320
321 if ( ! recurse)
322 fts_set (fts, ent, FTS_SKIP);
323
324 return ok;
325 }
326
327 /* Recursively change the modes of the specified FILES (the last entry
328 of which is NULL). BIT_FLAGS controls how fts works.
329 Return true if successful. */
330
331 static bool
process_files(char ** files,int bit_flags)332 process_files (char **files, int bit_flags)
333 {
334 bool ok = true;
335
336 FTS *fts = xfts_open (files, bit_flags, NULL);
337
338 while (1)
339 {
340 FTSENT *ent;
341
342 ent = fts_read (fts);
343 if (ent == NULL)
344 {
345 if (errno != 0)
346 {
347 /* FIXME: try to give a better message */
348 if (! force_silent)
349 error (0, errno, _("fts_read failed"));
350 ok = false;
351 }
352 break;
353 }
354
355 ok &= process_file (fts, ent);
356 }
357
358 if (fts_close (fts) != 0)
359 {
360 error (0, errno, _("fts_close failed"));
361 ok = false;
362 }
363
364 return ok;
365 }
366
367 void
usage(int status)368 usage (int status)
369 {
370 if (status != EXIT_SUCCESS)
371 emit_try_help ();
372 else
373 {
374 printf (_("\
375 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
376 or: %s [OPTION]... OCTAL-MODE FILE...\n\
377 or: %s [OPTION]... --reference=RFILE FILE...\n\
378 "),
379 program_name, program_name, program_name);
380 fputs (_("\
381 Change the mode of each FILE to MODE.\n\
382 With --reference, change the mode of each FILE to that of RFILE.\n\
383 \n\
384 "), stdout);
385 fputs (_("\
386 -c, --changes like verbose but report only when a change is made\n\
387 -f, --silent, --quiet suppress most error messages\n\
388 -v, --verbose output a diagnostic for every file processed\n\
389 "), stdout);
390 fputs (_("\
391 --no-preserve-root do not treat '/' specially (the default)\n\
392 --preserve-root fail to operate recursively on '/'\n\
393 "), stdout);
394 fputs (_("\
395 --reference=RFILE use RFILE's mode instead of MODE values\n\
396 "), stdout);
397 fputs (_("\
398 -R, --recursive change files and directories recursively\n\
399 "), stdout);
400 fputs (HELP_OPTION_DESCRIPTION, stdout);
401 fputs (VERSION_OPTION_DESCRIPTION, stdout);
402 fputs (_("\
403 \n\
404 Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\
405 "), stdout);
406 emit_ancillary_info (PROGRAM_NAME);
407 }
408 exit (status);
409 }
410
411 /* Parse the ASCII mode given on the command line into a linked list
412 of 'struct mode_change' and apply that to each file argument. */
413
414 int
main(int argc,char ** argv)415 main (int argc, char **argv)
416 {
417 char *mode = NULL;
418 size_t mode_len = 0;
419 size_t mode_alloc = 0;
420 bool ok;
421 bool preserve_root = false;
422 char const *reference_file = NULL;
423 int c;
424
425 initialize_main (&argc, &argv);
426 set_program_name (argv[0]);
427 setlocale (LC_ALL, "");
428 bindtextdomain (PACKAGE, LOCALEDIR);
429 textdomain (PACKAGE);
430
431 atexit (close_stdout);
432
433 recurse = force_silent = diagnose_surprises = false;
434
435 while ((c = getopt_long (argc, argv,
436 ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::"
437 "0::1::2::3::4::5::6::7::"),
438 long_options, NULL))
439 != -1)
440 {
441 switch (c)
442 {
443 case 'r':
444 case 'w':
445 case 'x':
446 case 'X':
447 case 's':
448 case 't':
449 case 'u':
450 case 'g':
451 case 'o':
452 case 'a':
453 case ',':
454 case '+':
455 case '=':
456 case '0': case '1': case '2': case '3':
457 case '4': case '5': case '6': case '7':
458 /* Support nonportable uses like "chmod -w", but diagnose
459 surprises due to umask confusion. Even though "--", "--r",
460 etc., are valid modes, there is no "case '-'" here since
461 getopt_long reserves leading "--" for long options. */
462 {
463 /* Allocate a mode string (e.g., "-rwx") by concatenating
464 the argument containing this option. If a previous mode
465 string was given, concatenate the previous string, a
466 comma, and the new string (e.g., "-s,-rwx"). */
467
468 char const *arg = argv[optind - 1];
469 size_t arg_len = strlen (arg);
470 size_t mode_comma_len = mode_len + !!mode_len;
471 size_t new_mode_len = mode_comma_len + arg_len;
472 if (mode_alloc <= new_mode_len)
473 {
474 mode_alloc = new_mode_len + 1;
475 mode = X2REALLOC (mode, &mode_alloc);
476 }
477 mode[mode_len] = ',';
478 memcpy (mode + mode_comma_len, arg, arg_len + 1);
479 mode_len = new_mode_len;
480
481 diagnose_surprises = true;
482 }
483 break;
484 case NO_PRESERVE_ROOT:
485 preserve_root = false;
486 break;
487 case PRESERVE_ROOT:
488 preserve_root = true;
489 break;
490 case REFERENCE_FILE_OPTION:
491 reference_file = optarg;
492 break;
493 case 'R':
494 recurse = true;
495 break;
496 case 'c':
497 verbosity = V_changes_only;
498 break;
499 case 'f':
500 force_silent = true;
501 break;
502 case 'v':
503 verbosity = V_high;
504 break;
505 case_GETOPT_HELP_CHAR;
506 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
507 default:
508 usage (EXIT_FAILURE);
509 }
510 }
511
512 if (reference_file)
513 {
514 if (mode)
515 {
516 error (0, 0, _("cannot combine mode and --reference options"));
517 usage (EXIT_FAILURE);
518 }
519 }
520 else
521 {
522 if (!mode)
523 mode = argv[optind++];
524 }
525
526 if (optind >= argc)
527 {
528 if (!mode || mode != argv[optind - 1])
529 error (0, 0, _("missing operand"));
530 else
531 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
532 usage (EXIT_FAILURE);
533 }
534
535 if (reference_file)
536 {
537 change = mode_create_from_ref (reference_file);
538 if (!change)
539 die (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
540 quoteaf (reference_file));
541 }
542 else
543 {
544 change = mode_compile (mode);
545 if (!change)
546 {
547 error (0, 0, _("invalid mode: %s"), quote (mode));
548 usage (EXIT_FAILURE);
549 }
550 umask_value = umask (0);
551 }
552
553 if (recurse && preserve_root)
554 {
555 static struct dev_ino dev_ino_buf;
556 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
557 if (root_dev_ino == NULL)
558 die (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
559 quoteaf ("/"));
560 }
561 else
562 {
563 root_dev_ino = NULL;
564 }
565
566 ok = process_files (argv + optind,
567 FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
568
569 IF_LINT (free (change));
570
571 return ok ? EXIT_SUCCESS : EXIT_FAILURE;
572 }
573