Remove unused "case 0".
[platform/upstream/coreutils.git] / src / chmod.c
1 /* chmod -- change permission modes of files
2    Copyright (C) 89, 90, 91, 1995-2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 "dirname.h"
28 #include "error.h"
29 #include "filemode.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 "David MacKenzie", "Jim Meyering"
39
40 enum Change_status
41 {
42   CH_NOT_APPLIED,
43   CH_SUCCEEDED,
44   CH_FAILED,
45   CH_NO_CHANGE_REQUESTED
46 };
47
48 enum Verbosity
49 {
50   /* Print a message for each file that is processed.  */
51   V_high,
52
53   /* Print a message for each file whose attributes we change.  */
54   V_changes_only,
55
56   /* Do not be verbose.  This is the default. */
57   V_off
58 };
59
60 /* The name the program was run with. */
61 char *program_name;
62
63 /* If true, change the modes of directories recursively. */
64 static bool recurse;
65
66 /* If true, force silence (no error messages). */
67 static bool force_silent;
68
69 /* Level of verbosity.  */
70 static enum Verbosity verbosity = V_off;
71
72 /* The argument to the --reference option.  Use the owner and group IDs
73    of this file.  This file must exist.  */
74 static char *reference_file;
75
76 /* Pointer to the device and inode numbers of `/', when --recursive.
77    Otherwise NULL.  */
78 static struct dev_ino *root_dev_ino;
79
80 /* For long options that have no equivalent short option, use a
81    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
82 enum
83 {
84   NO_PRESERVE_ROOT = CHAR_MAX + 1,
85   PRESERVE_ROOT,
86   REFERENCE_FILE_OPTION
87 };
88
89 static struct option const long_options[] =
90 {
91   {"changes", no_argument, 0, 'c'},
92   {"recursive", no_argument, 0, 'R'},
93   {"no-preserve-root", no_argument, 0, NO_PRESERVE_ROOT},
94   {"preserve-root", no_argument, 0, PRESERVE_ROOT},
95   {"quiet", no_argument, 0, 'f'},
96   {"reference", required_argument, 0, REFERENCE_FILE_OPTION},
97   {"silent", no_argument, 0, 'f'},
98   {"verbose", no_argument, 0, 'v'},
99   {GETOPT_HELP_OPTION_DECL},
100   {GETOPT_VERSION_OPTION_DECL},
101   {0, 0, 0, 0}
102 };
103
104 /* Return true if the chmodable permission bits of FILE changed.
105    The old mode was OLD_MODE, but it was changed to NEW_MODE.  */
106
107 static bool
108 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
109 {
110   if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
111     {
112       /* The new mode contains unusual bits that the call to chmod may
113          have silently cleared.  Check whether they actually changed.  */
114
115       struct stat new_stats;
116
117       if (stat (file, &new_stats) != 0)
118         {
119           if (!force_silent)
120             error (0, errno, _("getting new attributes of %s"), quote (file));
121           return false;
122         }
123
124       new_mode = new_stats.st_mode;
125     }
126
127   return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
128 }
129
130 /* Tell the user how/if the MODE of FILE has been changed.
131    CHANGED describes what (if anything) has happened. */
132
133 static void
134 describe_change (const char *file, mode_t mode,
135                  enum Change_status changed)
136 {
137   char perms[11];               /* "-rwxrwxrwx" ls-style modes. */
138   const char *fmt;
139
140   if (changed == CH_NOT_APPLIED)
141     {
142       printf (_("neither symbolic link %s nor referent has been changed\n"),
143               quote (file));
144       return;
145     }
146
147   mode_string (mode, perms);
148   perms[10] = '\0';             /* `mode_string' does not null terminate. */
149   switch (changed)
150     {
151     case CH_SUCCEEDED:
152       fmt = _("mode of %s changed to %04lo (%s)\n");
153       break;
154     case CH_FAILED:
155       fmt = _("failed to change mode of %s to %04lo (%s)\n");
156       break;
157     case CH_NO_CHANGE_REQUESTED:
158       fmt = _("mode of %s retained as %04lo (%s)\n");
159       break;
160     default:
161       abort ();
162     }
163   printf (fmt, quote (file),
164           (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
165 }
166
167 /* Change the mode of FILE according to the list of operations CHANGES.
168    Return true if successful.  This function is called
169    once for every file system object that fts encounters.  */
170
171 static bool
172 process_file (FTS *fts, FTSENT *ent, const struct mode_change *changes)
173 {
174   char const *file_full_name = ent->fts_path;
175   char const *file = ent->fts_accpath;
176   const struct stat *file_stats = ent->fts_statp;
177   mode_t new_mode IF_LINT (= 0);
178   bool ok = true;
179   bool do_chmod;
180   bool symlink_changed = true;
181
182   switch (ent->fts_info)
183     {
184     case FTS_DP:
185       return true;
186
187     case FTS_NS:
188       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
189       ok = false;
190       break;
191
192     case FTS_ERR:
193       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
194       ok = false;
195       break;
196
197     case FTS_DNR:
198       error (0, ent->fts_errno, _("cannot read directory %s"),
199              quote (file_full_name));
200       ok = false;
201       break;
202
203     default:
204       break;
205     }
206
207   do_chmod = ok;
208
209   if (do_chmod && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
210     {
211       ROOT_DEV_INO_WARN (file_full_name);
212       ok = false;
213       do_chmod = false;
214     }
215
216   if (do_chmod)
217     {
218       new_mode = mode_adjust (file_stats->st_mode, changes);
219
220       if (S_ISLNK (file_stats->st_mode))
221         symlink_changed = false;
222       else
223         {
224           ok = (chmod (file, new_mode) == 0);
225
226           if (! (ok | force_silent))
227             error (0, errno, _("changing permissions of %s"),
228                    quote (file_full_name));
229         }
230     }
231
232   if (verbosity != V_off)
233     {
234       bool changed =
235         (ok && symlink_changed
236          && mode_changed (file, file_stats->st_mode, new_mode));
237
238       if (changed || verbosity == V_high)
239         {
240           enum Change_status ch_status =
241             (!ok ? CH_FAILED
242              : !symlink_changed ? CH_NOT_APPLIED
243              : !changed ? CH_NO_CHANGE_REQUESTED
244              : CH_SUCCEEDED);
245           describe_change (file_full_name, new_mode, ch_status);
246         }
247     }
248
249   if ( ! recurse)
250     fts_set (fts, ent, FTS_SKIP);
251
252   return ok;
253 }
254
255 /* Recursively change the modes of the specified FILES (the last entry
256    of which is NULL) according to the list of operations CHANGES.
257    BIT_FLAGS controls how fts works.
258    Return true if successful.  */
259
260 static bool
261 process_files (char **files, int bit_flags, const struct mode_change *changes)
262 {
263   bool ok = true;
264
265   FTS *fts = xfts_open (files, bit_flags, NULL);
266
267   while (1)
268     {
269       FTSENT *ent;
270
271       ent = fts_read (fts);
272       if (ent == NULL)
273         {
274           if (errno != 0)
275             {
276               /* FIXME: try to give a better message  */
277               error (0, errno, _("fts_read failed"));
278               ok = false;
279             }
280           break;
281         }
282
283       ok &= process_file (fts, ent, changes);
284     }
285
286   /* Ignore failure, since the only way it can do so is in failing to
287      return to the original directory, and since we're about to exit,
288      that doesn't matter.  */
289   fts_close (fts);
290
291   return ok;
292 }
293
294 void
295 usage (int status)
296 {
297   if (status != EXIT_SUCCESS)
298     fprintf (stderr, _("Try `%s --help' for more information.\n"),
299              program_name);
300   else
301     {
302       printf (_("\
303 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
304   or:  %s [OPTION]... OCTAL-MODE FILE...\n\
305   or:  %s [OPTION]... --reference=RFILE FILE...\n\
306 "),
307               program_name, program_name, program_name);
308       fputs (_("\
309 Change the mode of each FILE to MODE.\n\
310 \n\
311   -c, --changes           like verbose but report only when a change is made\n\
312 "), stdout);
313       fputs (_("\
314       --no-preserve-root  do not treat `/' specially (the default)\n\
315       --preserve-root     fail to operate recursively on `/'\n\
316 "), stdout);
317       fputs (_("\
318   -f, --silent, --quiet   suppress most error messages\n\
319   -v, --verbose           output a diagnostic for every file processed\n\
320       --reference=RFILE   use RFILE's mode instead of MODE values\n\
321   -R, --recursive         change files and directories recursively\n\
322 "), stdout);
323       fputs (HELP_OPTION_DESCRIPTION, stdout);
324       fputs (VERSION_OPTION_DESCRIPTION, stdout);
325       fputs (_("\
326 \n\
327 Each MODE is one or more of the letters ugoa, one of the symbols +-= and\n\
328 one or more of the letters rwxXstugo.\n\
329 "), stdout);
330       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
331     }
332   exit (status);
333 }
334
335 /* Parse the ASCII mode given on the command line into a linked list
336    of `struct mode_change' and apply that to each file argument. */
337
338 int
339 main (int argc, char **argv)
340 {
341   struct mode_change *changes;
342   bool ok;
343   int modeind = 0;              /* Index of the mode argument in `argv'. */
344   int thisind;
345   bool preserve_root = false;
346   int c;
347
348   initialize_main (&argc, &argv);
349   program_name = argv[0];
350   setlocale (LC_ALL, "");
351   bindtextdomain (PACKAGE, LOCALEDIR);
352   textdomain (PACKAGE);
353
354   atexit (close_stdout);
355
356   recurse = force_silent = false;
357
358   while (1)
359     {
360       thisind = optind ? optind : 1;
361
362       c = getopt_long (argc, argv, "RcfvrwxXstugoa,+-=", long_options, NULL);
363       if (c == -1)
364         break;
365
366       switch (c)
367         {
368         case 'r':
369         case 'w':
370         case 'x':
371         case 'X':
372         case 's':
373         case 't':
374         case 'u':
375         case 'g':
376         case 'o':
377         case 'a':
378         case ',':
379         case '+':
380         case '-':
381         case '=':
382           if (modeind != 0 && modeind != thisind)
383             {
384               static char char_string[2] = {0, 0};
385               char_string[0] = c;
386               error (EXIT_FAILURE, 0,
387                      _("invalid character %s in mode string %s"),
388                      quote_n (0, char_string), quote_n (1, argv[thisind]));
389             }
390           modeind = thisind;
391           break;
392         case NO_PRESERVE_ROOT:
393           preserve_root = false;
394           break;
395         case PRESERVE_ROOT:
396           preserve_root = true;
397           break;
398         case REFERENCE_FILE_OPTION:
399           reference_file = optarg;
400           break;
401         case 'R':
402           recurse = true;
403           break;
404         case 'c':
405           verbosity = V_changes_only;
406           break;
407         case 'f':
408           force_silent = true;
409           break;
410         case 'v':
411           verbosity = V_high;
412           break;
413         case_GETOPT_HELP_CHAR;
414         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
415         default:
416           usage (EXIT_FAILURE);
417         }
418     }
419
420   if (modeind == 0 && reference_file == NULL)
421     modeind = optind++;
422
423   if (optind >= argc)
424     {
425       if (modeind == 0 || modeind != argc - 1)
426         error (0, 0, _("missing operand"));
427       else
428         error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
429       usage (EXIT_FAILURE);
430     }
431
432   changes = (reference_file ? mode_create_from_ref (reference_file)
433              : mode_compile (argv[modeind], MODE_MASK_ALL));
434
435   if (changes == MODE_INVALID)
436     error (EXIT_FAILURE, 0,
437            _("invalid mode string: %s"), quote (argv[modeind]));
438   else if (changes == MODE_MEMORY_EXHAUSTED)
439     xalloc_die ();
440   else if (changes == MODE_BAD_REFERENCE)
441     error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
442            quote (reference_file));
443
444   if (recurse & preserve_root)
445     {
446       static struct dev_ino dev_ino_buf;
447       root_dev_ino = get_root_dev_ino (&dev_ino_buf);
448       if (root_dev_ino == NULL)
449         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
450                quote ("/"));
451     }
452   else
453     {
454       root_dev_ino = NULL;
455     }
456
457   ok = process_files (argv + optind, FTS_COMFOLLOW, changes);
458
459   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
460 }