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