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