(main): Standardize on the diagnostics given when someone gives
[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 nonzero, change the modes of directories recursively. */
64 static int recurse;
65
66 /* If nonzero, force silence (no error messages). */
67 static int 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) (mode & CHMOD_MODE_BITS), &perms[1]);
165 }
166
167 /* Change the mode of FILE according to the list of operations CHANGES.
168    Return 0 if successful, -1 if errors occurred.  This function is called
169    once for every file system object that fts encounters.  */
170
171 static int
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   int errors = 0;
179   bool do_chmod;
180   bool symlink_changed = true;
181
182   switch (ent->fts_info)
183     {
184     case FTS_DP:
185       return 0;
186
187     case FTS_NS:
188       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
189       errors = -1;
190       break;
191
192     case FTS_ERR:
193       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
194       errors = -1;
195       break;
196
197     case FTS_DNR:
198       error (0, ent->fts_errno, _("cannot read directory %s"),
199              quote (file_full_name));
200       errors = -1;
201       break;
202
203     default:
204       break;
205     }
206
207   do_chmod = !errors;
208
209   if (do_chmod && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
210     {
211       ROOT_DEV_INO_WARN (file_full_name);
212       errors = -1;
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           errors = chmod (file, new_mode);
225
226           if (errors && !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         (!errors && 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             (errors ? 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 errors;
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    If the fts_open call fails, exit nonzero.
259    Otherwise, return nonzero upon error.  */
260
261 static int
262 process_files (char **files, int bit_flags, const struct mode_change *changes)
263 {
264   int fail = 0;
265
266   FTS *fts = xfts_open (files, bit_flags, NULL);
267
268   while (1)
269     {
270       FTSENT *ent;
271
272       ent = fts_read (fts);
273       if (ent == NULL)
274         {
275           if (errno != 0)
276             {
277               /* FIXME: try to give a better message  */
278               error (0, errno, _("fts_read failed"));
279               fail = 1;
280             }
281           break;
282         }
283
284       fail |= process_file (fts, ent, changes);
285     }
286
287   /* Ignore failure, since the only way it can do so is in failing to
288      return to the original directory, and since we're about to exit,
289      that doesn't matter.  */
290   fts_close (fts);
291
292   return fail;
293 }
294
295 void
296 usage (int status)
297 {
298   if (status != EXIT_SUCCESS)
299     fprintf (stderr, _("Try `%s --help' for more information.\n"),
300              program_name);
301   else
302     {
303       printf (_("\
304 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
305   or:  %s [OPTION]... OCTAL-MODE FILE...\n\
306   or:  %s [OPTION]... --reference=RFILE FILE...\n\
307 "),
308               program_name, program_name, program_name);
309       fputs (_("\
310 Change the mode of each FILE to MODE.\n\
311 \n\
312   -c, --changes           like verbose but report only when a change is made\n\
313 "), stdout);
314       fputs (_("\
315       --no-preserve-root  do not treat `/' specially (the default)\n\
316       --preserve-root     fail to operate recursively on `/'\n\
317 "), stdout);
318       fputs (_("\
319   -f, --silent, --quiet   suppress most error messages\n\
320   -v, --verbose           output a diagnostic for every file processed\n\
321       --reference=RFILE   use RFILE's mode instead of MODE values\n\
322   -R, --recursive         change files and directories recursively\n\
323 "), stdout);
324       fputs (HELP_OPTION_DESCRIPTION, stdout);
325       fputs (VERSION_OPTION_DESCRIPTION, stdout);
326       fputs (_("\
327 \n\
328 Each MODE is one or more of the letters ugoa, one of the symbols +-= and\n\
329 one or more of the letters rwxXstugo.\n\
330 "), stdout);
331       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
332     }
333   exit (status);
334 }
335
336 /* Parse the ASCII mode given on the command line into a linked list
337    of `struct mode_change' and apply that to each file argument. */
338
339 int
340 main (int argc, char **argv)
341 {
342   struct mode_change *changes;
343   int fail = 0;
344   int modeind = 0;              /* Index of the mode argument in `argv'. */
345   int thisind;
346   bool preserve_root = false;
347   int c;
348
349   initialize_main (&argc, &argv);
350   program_name = argv[0];
351   setlocale (LC_ALL, "");
352   bindtextdomain (PACKAGE, LOCALEDIR);
353   textdomain (PACKAGE);
354
355   atexit (close_stdout);
356
357   recurse = force_silent = 0;
358
359   while (1)
360     {
361       thisind = optind ? optind : 1;
362
363       c = getopt_long (argc, argv, "RcfvrwxXstugoa,+-=", long_options, NULL);
364       if (c == -1)
365         break;
366
367       switch (c)
368         {
369         case 0:
370           break;
371         case 'r':
372         case 'w':
373         case 'x':
374         case 'X':
375         case 's':
376         case 't':
377         case 'u':
378         case 'g':
379         case 'o':
380         case 'a':
381         case ',':
382         case '+':
383         case '-':
384         case '=':
385           if (modeind != 0 && modeind != thisind)
386             {
387               static char char_string[2] = {0, 0};
388               char_string[0] = c;
389               error (EXIT_FAILURE, 0,
390                      _("invalid character %s in mode string %s"),
391                      quote_n (0, char_string), quote_n (1, argv[thisind]));
392             }
393           modeind = thisind;
394           break;
395         case NO_PRESERVE_ROOT:
396           preserve_root = false;
397           break;
398         case PRESERVE_ROOT:
399           preserve_root = true;
400           break;
401         case REFERENCE_FILE_OPTION:
402           reference_file = optarg;
403           break;
404         case 'R':
405           recurse = 1;
406           break;
407         case 'c':
408           verbosity = V_changes_only;
409           break;
410         case 'f':
411           force_silent = 1;
412           break;
413         case 'v':
414           verbosity = V_high;
415           break;
416         case_GETOPT_HELP_CHAR;
417         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
418         default:
419           usage (EXIT_FAILURE);
420         }
421     }
422
423   if (modeind == 0 && reference_file == NULL)
424     modeind = optind++;
425
426   if (optind >= argc)
427     {
428       if (modeind == 0 || modeind != argc - 1)
429         error (0, 0, _("missing operand"));
430       else
431         error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
432       usage (EXIT_FAILURE);
433     }
434
435   changes = (reference_file ? mode_create_from_ref (reference_file)
436              : mode_compile (argv[modeind], MODE_MASK_ALL));
437
438   if (changes == MODE_INVALID)
439     error (EXIT_FAILURE, 0,
440            _("invalid mode string: %s"), quote (argv[modeind]));
441   else if (changes == MODE_MEMORY_EXHAUSTED)
442     xalloc_die ();
443   else if (changes == MODE_BAD_REFERENCE)
444     error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
445            quote (reference_file));
446
447   if (recurse && preserve_root)
448     {
449       static struct dev_ino dev_ino_buf;
450       root_dev_ino = get_root_dev_ino (&dev_ino_buf);
451       if (root_dev_ino == NULL)
452         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
453                quote ("/"));
454     }
455   else
456     {
457       root_dev_ino = NULL;
458     }
459
460   fail = process_files (argv + optind, FTS_COMFOLLOW, changes);
461
462   exit (fail ? EXIT_FAILURE : EXIT_SUCCESS);
463 }