(long_options): Use NULL, not `0'.
[platform/upstream/coreutils.git] / src / chmod.c
1 /* chmod -- change permission modes of files
2    Copyright (C) 89, 90, 91, 1995-2005 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, NULL, 'c'},
92   {"recursive", no_argument, NULL, 'R'},
93   {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
94   {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
95   {"quiet", no_argument, NULL, 'f'},
96   {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
97   {"silent", no_argument, NULL, 'f'},
98   {"verbose", no_argument, NULL, 'v'},
99   {GETOPT_HELP_OPTION_DECL},
100   {GETOPT_VERSION_OPTION_DECL},
101   {NULL, 0, NULL, 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   char *mode = NULL;
343   size_t mode_len = 0;
344   size_t mode_alloc = 0;
345   bool ok;
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 = false;
358
359   while ((c = getopt_long (argc, argv,
360                            "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::-::=::",
361                            long_options, NULL))
362          != -1)
363     {
364       switch (c)
365         {
366         case 'r':
367         case 'w':
368         case 'x':
369         case 'X':
370         case 's':
371         case 't':
372         case 'u':
373         case 'g':
374         case 'o':
375         case 'a':
376         case ',':
377         case '+':
378         case '-':
379         case '=':
380           {
381             /* Allocate a mode string (e.g., "-rwx") by concatenating
382                the argument containing this option.  If a previous mode
383                string was given, concatenate the previous string, a
384                comma, and the new string (e.g., "-s,-rwx").  */
385
386             char const *arg = argv[optind - 1];
387             size_t arg_len = strlen (arg);
388             size_t mode_comma_len = mode_len + !!mode_len;
389             size_t new_mode_len = mode_comma_len + arg_len;
390             if (mode_alloc <= new_mode_len)
391               {
392                 mode_alloc = new_mode_len + 1;
393                 mode = x2realloc (mode, &mode_alloc);
394               }
395             mode[mode_len] = ',';
396             strcpy (mode + mode_comma_len, arg);
397             mode_len = new_mode_len;
398           }
399           break;
400         case NO_PRESERVE_ROOT:
401           preserve_root = false;
402           break;
403         case PRESERVE_ROOT:
404           preserve_root = true;
405           break;
406         case REFERENCE_FILE_OPTION:
407           reference_file = optarg;
408           break;
409         case 'R':
410           recurse = true;
411           break;
412         case 'c':
413           verbosity = V_changes_only;
414           break;
415         case 'f':
416           force_silent = true;
417           break;
418         case 'v':
419           verbosity = V_high;
420           break;
421         case_GETOPT_HELP_CHAR;
422         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
423         default:
424           usage (EXIT_FAILURE);
425         }
426     }
427
428   if (reference_file)
429     {
430       if (mode)
431         error (EXIT_FAILURE, 0,
432                _("cannot combine mode and --reference options"));
433     }
434   else
435     {
436       if (!mode)
437         mode = argv[optind++];
438     }
439
440   if (optind >= argc)
441     {
442       if (!mode || mode != argv[optind - 1])
443         error (0, 0, _("missing operand"));
444       else
445         error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
446       usage (EXIT_FAILURE);
447     }
448
449   changes = (reference_file ? mode_create_from_ref (reference_file)
450              : mode_compile (mode, MODE_MASK_ALL));
451
452   if (changes == MODE_INVALID)
453     error (EXIT_FAILURE, 0, _("invalid mode: %s"), quote (mode));
454   else if (changes == MODE_MEMORY_EXHAUSTED)
455     xalloc_die ();
456   else if (changes == MODE_BAD_REFERENCE)
457     error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
458            quote (reference_file));
459
460   if (recurse & preserve_root)
461     {
462       static struct dev_ino dev_ino_buf;
463       root_dev_ino = get_root_dev_ino (&dev_ino_buf);
464       if (root_dev_ino == NULL)
465         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
466                quote ("/"));
467     }
468   else
469     {
470       root_dev_ino = NULL;
471     }
472
473   ok = process_files (argv + optind, FTS_COMFOLLOW, changes);
474
475   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
476 }