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