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