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