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