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