Update FSF postal mail address.
[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., 51 Franklin Street, Fifth Floor, 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 "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[11];               /* "-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   mode_string (mode, perms);
156   perms[10] = '\0';             /* `mode_string' does not null terminate. */
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       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
197       ok = false;
198       break;
199
200     case FTS_ERR:
201       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
202       ok = false;
203       break;
204
205     case FTS_DNR:
206       error (0, ent->fts_errno, _("cannot read directory %s"),
207              quote (file_full_name));
208       ok = false;
209       break;
210
211     default:
212       break;
213     }
214
215   if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
216     {
217       ROOT_DEV_INO_WARN (file_full_name);
218       ok = false;
219     }
220
221   if (ok)
222     {
223       old_mode = file_stats->st_mode;
224       new_mode = mode_adjust (old_mode, change, umask_value);
225
226       if (! S_ISLNK (old_mode))
227         {
228           if (chmod (file, new_mode) == 0)
229             chmod_succeeded = true;
230           else
231             {
232               if (! force_silent)
233                 error (0, errno, _("changing permissions of %s"),
234                        quote (file_full_name));
235               ok = false;
236             }
237         }
238     }
239
240   if (verbosity != V_off)
241     {
242       bool changed = (chmod_succeeded
243                       && mode_changed (file, old_mode, new_mode));
244
245       if (changed || verbosity == V_high)
246         {
247           enum Change_status ch_status =
248             (!ok ? CH_FAILED
249              : !chmod_succeeded ? CH_NOT_APPLIED
250              : !changed ? CH_NO_CHANGE_REQUESTED
251              : CH_SUCCEEDED);
252           describe_change (file_full_name, new_mode, ch_status);
253         }
254     }
255
256   if (chmod_succeeded & diagnose_surprises)
257     {
258       mode_t naively_expected_mode = mode_adjust (old_mode, change, 0);
259       if (new_mode & ~naively_expected_mode)
260         {
261           char new_perms[11];
262           char naively_expected_perms[11];
263           mode_string (new_mode, new_perms);
264           mode_string (naively_expected_mode, naively_expected_perms);
265           new_perms[10] = naively_expected_perms[10] = '\0';
266           error (0, 0,
267                  _("%s: new permissions are %s, not %s"),
268                  quotearg_colon (file_full_name),
269                  new_perms + 1, naively_expected_perms + 1);
270           ok = false;
271         }
272     }
273
274   if ( ! recurse)
275     fts_set (fts, ent, FTS_SKIP);
276
277   return ok;
278 }
279
280 /* Recursively change the modes of the specified FILES (the last entry
281    of which is NULL).  BIT_FLAGS controls how fts works.
282    Return true if successful.  */
283
284 static bool
285 process_files (char **files, int bit_flags)
286 {
287   bool ok = true;
288
289   FTS *fts = xfts_open (files, bit_flags, NULL);
290
291   while (1)
292     {
293       FTSENT *ent;
294
295       ent = fts_read (fts);
296       if (ent == NULL)
297         {
298           if (errno != 0)
299             {
300               /* FIXME: try to give a better message  */
301               error (0, errno, _("fts_read failed"));
302               ok = false;
303             }
304           break;
305         }
306
307       ok &= process_file (fts, ent);
308     }
309
310   /* Ignore failure, since the only way it can do so is in failing to
311      return to the original directory, and since we're about to exit,
312      that doesn't matter.  */
313   fts_close (fts);
314
315   return ok;
316 }
317
318 void
319 usage (int status)
320 {
321   if (status != EXIT_SUCCESS)
322     fprintf (stderr, _("Try `%s --help' for more information.\n"),
323              program_name);
324   else
325     {
326       printf (_("\
327 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
328   or:  %s [OPTION]... OCTAL-MODE FILE...\n\
329   or:  %s [OPTION]... --reference=RFILE FILE...\n\
330 "),
331               program_name, program_name, program_name);
332       fputs (_("\
333 Change the mode of each FILE to MODE.\n\
334 \n\
335   -c, --changes           like verbose but report only when a change is made\n\
336 "), stdout);
337       fputs (_("\
338       --no-preserve-root  do not treat `/' specially (the default)\n\
339       --preserve-root     fail to operate recursively on `/'\n\
340 "), stdout);
341       fputs (_("\
342   -f, --silent, --quiet   suppress most error messages\n\
343   -v, --verbose           output a diagnostic for every file processed\n\
344       --reference=RFILE   use RFILE's mode instead of MODE values\n\
345   -R, --recursive         change files and directories recursively\n\
346 "), stdout);
347       fputs (HELP_OPTION_DESCRIPTION, stdout);
348       fputs (VERSION_OPTION_DESCRIPTION, stdout);
349       fputs (_("\
350 \n\
351 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
352 "), stdout);
353       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
354     }
355   exit (status);
356 }
357
358 /* Parse the ASCII mode given on the command line into a linked list
359    of `struct mode_change' and apply that to each file argument. */
360
361 int
362 main (int argc, char **argv)
363 {
364   char *mode = NULL;
365   size_t mode_len = 0;
366   size_t mode_alloc = 0;
367   bool ok;
368   bool preserve_root = false;
369   char const *reference_file = NULL;
370   int c;
371
372   initialize_main (&argc, &argv);
373   program_name = argv[0];
374   setlocale (LC_ALL, "");
375   bindtextdomain (PACKAGE, LOCALEDIR);
376   textdomain (PACKAGE);
377
378   atexit (close_stdout);
379
380   recurse = force_silent = diagnose_surprises = false;
381
382   while ((c = getopt_long (argc, argv,
383                            "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
384                            long_options, NULL))
385          != -1)
386     {
387       switch (c)
388         {
389         case 'r':
390         case 'w':
391         case 'x':
392         case 'X':
393         case 's':
394         case 't':
395         case 'u':
396         case 'g':
397         case 'o':
398         case 'a':
399         case ',':
400         case '+':
401         case '=':
402           /* Support nonportable uses like "chmod -w", but diagnose
403              surprises due to umask confusion.  Even though "--", "--r",
404              etc., are valid modes, there is no "case '-'" here since
405              getopt_long reserves leading "--" for long options.  */
406           {
407             /* Allocate a mode string (e.g., "-rwx") by concatenating
408                the argument containing this option.  If a previous mode
409                string was given, concatenate the previous string, a
410                comma, and the new string (e.g., "-s,-rwx").  */
411
412             char const *arg = argv[optind - 1];
413             size_t arg_len = strlen (arg);
414             size_t mode_comma_len = mode_len + !!mode_len;
415             size_t new_mode_len = mode_comma_len + arg_len;
416             if (mode_alloc <= new_mode_len)
417               {
418                 mode_alloc = new_mode_len + 1;
419                 mode = x2realloc (mode, &mode_alloc);
420               }
421             mode[mode_len] = ',';
422             strcpy (mode + mode_comma_len, arg);
423             mode_len = new_mode_len;
424
425             diagnose_surprises = true;
426           }
427           break;
428         case NO_PRESERVE_ROOT:
429           preserve_root = false;
430           break;
431         case PRESERVE_ROOT:
432           preserve_root = true;
433           break;
434         case REFERENCE_FILE_OPTION:
435           reference_file = optarg;
436           break;
437         case 'R':
438           recurse = true;
439           break;
440         case 'c':
441           verbosity = V_changes_only;
442           break;
443         case 'f':
444           force_silent = true;
445           break;
446         case 'v':
447           verbosity = V_high;
448           break;
449         case_GETOPT_HELP_CHAR;
450         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
451         default:
452           usage (EXIT_FAILURE);
453         }
454     }
455
456   if (reference_file)
457     {
458       if (mode)
459         {
460           error (0, 0, _("cannot combine mode and --reference options"));
461           usage (EXIT_FAILURE);
462         }
463     }
464   else
465     {
466       if (!mode)
467         mode = argv[optind++];
468     }
469
470   if (optind >= argc)
471     {
472       if (!mode || mode != argv[optind - 1])
473         error (0, 0, _("missing operand"));
474       else
475         error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
476       usage (EXIT_FAILURE);
477     }
478
479   if (reference_file)
480     {
481       change = mode_create_from_ref (reference_file);
482       if (!change)
483         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
484                quote (reference_file));
485     }
486   else
487     {
488       change = mode_compile (mode);
489       if (!change)
490         {
491           error (0, 0, _("invalid mode: %s"), quote (mode));
492           usage (EXIT_FAILURE);
493         }
494       umask_value = umask (0);
495     }
496
497   if (recurse & preserve_root)
498     {
499       static struct dev_ino dev_ino_buf;
500       root_dev_ino = get_root_dev_ino (&dev_ino_buf);
501       if (root_dev_ino == NULL)
502         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
503                quote ("/"));
504     }
505   else
506     {
507       root_dev_ino = NULL;
508     }
509
510   ok = process_files (argv + optind, FTS_COMFOLLOW);
511
512   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
513 }