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