(change, umask_value): New static vars.
[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 /* The desired change to the mode.  */
64 static struct mode_change *change;
65
66 /* The initial umask value, if it might be needed.  */
67 static mode_t umask_value;
68
69 /* If true, change the modes of directories recursively. */
70 static bool recurse;
71
72 /* If true, force silence (no error messages). */
73 static bool force_silent;
74
75 /* Level of verbosity.  */
76 static enum Verbosity verbosity = V_off;
77
78 /* Pointer to the device and inode numbers of `/', when --recursive.
79    Otherwise NULL.  */
80 static struct dev_ino *root_dev_ino;
81
82 /* For long options that have no equivalent short option, use a
83    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
84 enum
85 {
86   NO_PRESERVE_ROOT = CHAR_MAX + 1,
87   PRESERVE_ROOT,
88   REFERENCE_FILE_OPTION
89 };
90
91 static struct option const long_options[] =
92 {
93   {"changes", no_argument, NULL, 'c'},
94   {"recursive", no_argument, NULL, 'R'},
95   {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
96   {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
97   {"quiet", no_argument, NULL, 'f'},
98   {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
99   {"silent", no_argument, NULL, 'f'},
100   {"verbose", no_argument, NULL, 'v'},
101   {GETOPT_HELP_OPTION_DECL},
102   {GETOPT_VERSION_OPTION_DECL},
103   {NULL, 0, NULL, 0}
104 };
105
106 /* Return true if the chmodable permission bits of FILE changed.
107    The old mode was OLD_MODE, but it was changed to NEW_MODE.  */
108
109 static bool
110 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
111 {
112   if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
113     {
114       /* The new mode contains unusual bits that the call to chmod may
115          have silently cleared.  Check whether they actually changed.  */
116
117       struct stat new_stats;
118
119       if (stat (file, &new_stats) != 0)
120         {
121           if (!force_silent)
122             error (0, errno, _("getting new attributes of %s"), quote (file));
123           return false;
124         }
125
126       new_mode = new_stats.st_mode;
127     }
128
129   return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
130 }
131
132 /* Tell the user how/if the MODE of FILE has been changed.
133    CHANGED describes what (if anything) has happened. */
134
135 static void
136 describe_change (const char *file, mode_t mode,
137                  enum Change_status changed)
138 {
139   char perms[11];               /* "-rwxrwxrwx" ls-style modes. */
140   const char *fmt;
141
142   if (changed == CH_NOT_APPLIED)
143     {
144       printf (_("neither symbolic link %s nor referent has been changed\n"),
145               quote (file));
146       return;
147     }
148
149   mode_string (mode, perms);
150   perms[10] = '\0';             /* `mode_string' does not null terminate. */
151   switch (changed)
152     {
153     case CH_SUCCEEDED:
154       fmt = _("mode of %s changed to %04lo (%s)\n");
155       break;
156     case CH_FAILED:
157       fmt = _("failed to change mode of %s to %04lo (%s)\n");
158       break;
159     case CH_NO_CHANGE_REQUESTED:
160       fmt = _("mode of %s retained as %04lo (%s)\n");
161       break;
162     default:
163       abort ();
164     }
165   printf (fmt, quote (file),
166           (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
167 }
168
169 /* Change the mode of FILE.
170    Return true if successful.  This function is called
171    once for every file system object that fts encounters.  */
172
173 static bool
174 process_file (FTS *fts, FTSENT *ent)
175 {
176   char const *file_full_name = ent->fts_path;
177   char const *file = ent->fts_accpath;
178   const struct stat *file_stats = ent->fts_statp;
179   mode_t new_mode IF_LINT (= 0);
180   bool ok = true;
181   bool do_chmod;
182   bool symlink_changed = true;
183
184   switch (ent->fts_info)
185     {
186     case FTS_DP:
187       return true;
188
189     case FTS_NS:
190       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
191       ok = false;
192       break;
193
194     case FTS_ERR:
195       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
196       ok = false;
197       break;
198
199     case FTS_DNR:
200       error (0, ent->fts_errno, _("cannot read directory %s"),
201              quote (file_full_name));
202       ok = false;
203       break;
204
205     default:
206       break;
207     }
208
209   do_chmod = ok;
210
211   if (do_chmod && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
212     {
213       ROOT_DEV_INO_WARN (file_full_name);
214       ok = false;
215       do_chmod = false;
216     }
217
218   if (do_chmod)
219     {
220       new_mode = mode_adjust (file_stats->st_mode, change, umask_value);
221
222       if (S_ISLNK (file_stats->st_mode))
223         symlink_changed = false;
224       else
225         {
226           ok = (chmod (file, new_mode) == 0);
227
228           if (! (ok | force_silent))
229             error (0, errno, _("changing permissions of %s"),
230                    quote (file_full_name));
231         }
232     }
233
234   if (verbosity != V_off)
235     {
236       bool changed =
237         (ok && symlink_changed
238          && mode_changed (file, file_stats->st_mode, new_mode));
239
240       if (changed || verbosity == V_high)
241         {
242           enum Change_status ch_status =
243             (!ok ? CH_FAILED
244              : !symlink_changed ? CH_NOT_APPLIED
245              : !changed ? CH_NO_CHANGE_REQUESTED
246              : CH_SUCCEEDED);
247           describe_change (file_full_name, new_mode, ch_status);
248         }
249     }
250
251   if ( ! recurse)
252     fts_set (fts, ent, FTS_SKIP);
253
254   return ok;
255 }
256
257 /* Recursively change the modes of the specified FILES (the last entry
258    of which is NULL).  BIT_FLAGS controls how fts works.
259    Return true if successful.  */
260
261 static bool
262 process_files (char **files, int bit_flags)
263 {
264   bool ok = true;
265
266   FTS *fts = xfts_open (files, bit_flags, NULL);
267
268   while (1)
269     {
270       FTSENT *ent;
271
272       ent = fts_read (fts);
273       if (ent == NULL)
274         {
275           if (errno != 0)
276             {
277               /* FIXME: try to give a better message  */
278               error (0, errno, _("fts_read failed"));
279               ok = false;
280             }
281           break;
282         }
283
284       ok &= process_file (fts, ent);
285     }
286
287   /* Ignore failure, since the only way it can do so is in failing to
288      return to the original directory, and since we're about to exit,
289      that doesn't matter.  */
290   fts_close (fts);
291
292   return ok;
293 }
294
295 void
296 usage (int status)
297 {
298   if (status != EXIT_SUCCESS)
299     fprintf (stderr, _("Try `%s --help' for more information.\n"),
300              program_name);
301   else
302     {
303       printf (_("\
304 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
305   or:  %s [OPTION]... OCTAL-MODE FILE...\n\
306   or:  %s [OPTION]... --reference=RFILE FILE...\n\
307 "),
308               program_name, program_name, program_name);
309       fputs (_("\
310 Change the mode of each FILE to MODE.\n\
311 \n\
312   -c, --changes           like verbose but report only when a change is made\n\
313 "), stdout);
314       fputs (_("\
315       --no-preserve-root  do not treat `/' specially (the default)\n\
316       --preserve-root     fail to operate recursively on `/'\n\
317 "), stdout);
318       fputs (_("\
319   -f, --silent, --quiet   suppress most error messages\n\
320   -v, --verbose           output a diagnostic for every file processed\n\
321       --reference=RFILE   use RFILE's mode instead of MODE values\n\
322   -R, --recursive         change files and directories recursively\n\
323 "), stdout);
324       fputs (HELP_OPTION_DESCRIPTION, stdout);
325       fputs (VERSION_OPTION_DESCRIPTION, stdout);
326       fputs (_("\
327 \n\
328 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\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   char *mode = NULL;
342   size_t mode_len = 0;
343   size_t mode_alloc = 0;
344   bool ok;
345   bool preserve_root = false;
346   char const *reference_file = NULL;
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         {
432           error (0, 0, _("cannot combine mode and --reference options"));
433           usage (EXIT_FAILURE);
434         }
435     }
436   else
437     {
438       if (!mode)
439         mode = argv[optind++];
440     }
441
442   if (optind >= argc)
443     {
444       if (!mode || mode != argv[optind - 1])
445         error (0, 0, _("missing operand"));
446       else
447         error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
448       usage (EXIT_FAILURE);
449     }
450
451   if (reference_file)
452     {
453       change = mode_create_from_ref (reference_file);
454       if (!change)
455         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
456                quote (reference_file));
457     }
458   else
459     {
460       change = mode_compile (mode);
461       if (!change)
462         {
463           error (0, 0, _("invalid mode: %s"), quote (mode));
464           usage (EXIT_FAILURE);
465         }
466       umask_value = umask (0);
467     }
468
469   if (recurse & preserve_root)
470     {
471       static struct dev_ino dev_ino_buf;
472       root_dev_ino = get_root_dev_ino (&dev_ino_buf);
473       if (root_dev_ino == NULL)
474         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
475                quote ("/"));
476     }
477   else
478     {
479       root_dev_ino = NULL;
480     }
481
482   ok = process_files (argv + optind, FTS_COMFOLLOW);
483
484   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
485 }