* tests/sample-test: Update copyright date to 2007.
[coreutils.git] / src / chmod.c
blob028c882d01f9b1c7c1df9e96977867ec63b7a1d2
1 /* chmod -- change permission modes of files
2 Copyright (C) 89, 90, 91, 1995-2006 Free Software Foundation, Inc.
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.
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.
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. */
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
20 #include <config.h>
21 #include <stdio.h>
22 #include <getopt.h>
23 #include <sys/types.h>
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"
36 /* The official name of this program (e.g., no `g' prefix). */
37 #define PROGRAM_NAME "chmod"
39 #define AUTHORS "David MacKenzie", "Jim Meyering"
41 enum Change_status
43 CH_NOT_APPLIED,
44 CH_SUCCEEDED,
45 CH_FAILED,
46 CH_NO_CHANGE_REQUESTED
49 enum Verbosity
51 /* Print a message for each file that is processed. */
52 V_high,
54 /* Print a message for each file whose attributes we change. */
55 V_changes_only,
57 /* Do not be verbose. This is the default. */
58 V_off
61 /* The name the program was run with. */
62 char *program_name;
64 /* The desired change to the mode. */
65 static struct mode_change *change;
67 /* The initial umask value, if it might be needed. */
68 static mode_t umask_value;
70 /* If true, change the modes of directories recursively. */
71 static bool recurse;
73 /* If true, force silence (no error messages). */
74 static bool force_silent;
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;
81 /* Level of verbosity. */
82 static enum Verbosity verbosity = V_off;
84 /* Pointer to the device and inode numbers of `/', when --recursive.
85 Otherwise NULL. */
86 static struct dev_ino *root_dev_ino;
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
92 NO_PRESERVE_ROOT = CHAR_MAX + 1,
93 PRESERVE_ROOT,
94 REFERENCE_FILE_OPTION
97 static struct option const long_options[] =
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}
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. */
115 static bool
116 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
118 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
120 /* The new mode contains unusual bits that the call to chmod may
121 have silently cleared. Check whether they actually changed. */
123 struct stat new_stats;
125 if (stat (file, &new_stats) != 0)
127 if (!force_silent)
128 error (0, errno, _("getting new attributes of %s"), quote (file));
129 return false;
132 new_mode = new_stats.st_mode;
135 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
138 /* Tell the user how/if the MODE of FILE has been changed.
139 CHANGED describes what (if anything) has happened. */
141 static void
142 describe_change (const char *file, mode_t mode,
143 enum Change_status changed)
145 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
146 const char *fmt;
148 if (changed == CH_NOT_APPLIED)
150 printf (_("neither symbolic link %s nor referent has been changed\n"),
151 quote (file));
152 return;
155 strmode (mode, perms);
156 perms[10] = '\0'; /* Remove trailing space. */
157 switch (changed)
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 ();
171 printf (fmt, quote (file),
172 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
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. */
179 static bool
180 process_file (FTS *fts, FTSENT *ent)
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;
190 switch (ent->fts_info)
192 case FTS_DP:
193 return true;
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)
205 ent->fts_number = 1;
206 fts_set (fts, ent, FTS_AGAIN);
207 return true;
209 error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
210 ok = false;
211 break;
213 case FTS_ERR:
214 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
215 ok = false;
216 break;
218 case FTS_DNR:
219 error (0, ent->fts_errno, _("cannot read directory %s"),
220 quote (file_full_name));
221 ok = false;
222 break;
224 default:
225 break;
228 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
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;
238 if (ok)
240 old_mode = file_stats->st_mode;
241 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
242 change, NULL);
244 if (! S_ISLNK (old_mode))
246 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
247 chmod_succeeded = true;
248 else
250 if (! force_silent)
251 error (0, errno, _("changing permissions of %s"),
252 quote (file_full_name));
253 ok = false;
258 if (verbosity != V_off)
260 bool changed = (chmod_succeeded
261 && mode_changed (file, old_mode, new_mode));
263 if (changed || verbosity == V_high)
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);
274 if (chmod_succeeded & diagnose_surprises)
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)
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;
293 if ( ! recurse)
294 fts_set (fts, ent, FTS_SKIP);
296 return ok;
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. */
303 static bool
304 process_files (char **files, int bit_flags)
306 bool ok = true;
308 FTS *fts = xfts_open (files, bit_flags, NULL);
310 while (1)
312 FTSENT *ent;
314 ent = fts_read (fts);
315 if (ent == NULL)
317 if (errno != 0)
319 /* FIXME: try to give a better message */
320 error (0, errno, _("fts_read failed"));
321 ok = false;
323 break;
326 ok &= process_file (fts, ent);
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);
334 return ok;
337 void
338 usage (int status)
340 if (status != EXIT_SUCCESS)
341 fprintf (stderr, _("Try `%s --help' for more information.\n"),
342 program_name);
343 else
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\
350 program_name, program_name, program_name);
351 fputs (_("\
352 Change the mode of each FILE to MODE.\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 (_("\
370 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
371 "), stdout);
372 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
374 exit (status);
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. */
381 main (int argc, char **argv)
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;
391 initialize_main (&argc, &argv);
392 program_name = argv[0];
393 setlocale (LC_ALL, "");
394 bindtextdomain (PACKAGE, LOCALEDIR);
395 textdomain (PACKAGE);
397 atexit (close_stdout);
399 recurse = force_silent = diagnose_surprises = false;
401 while ((c = getopt_long (argc, argv,
402 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
403 long_options, NULL))
404 != -1)
406 switch (c)
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. */
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"). */
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)
437 mode_alloc = new_mode_len + 1;
438 mode = X2REALLOC (mode, &mode_alloc);
440 mode[mode_len] = ',';
441 strcpy (mode + mode_comma_len, arg);
442 mode_len = new_mode_len;
444 diagnose_surprises = true;
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);
475 if (reference_file)
477 if (mode)
479 error (0, 0, _("cannot combine mode and --reference options"));
480 usage (EXIT_FAILURE);
483 else
485 if (!mode)
486 mode = argv[optind++];
489 if (optind >= argc)
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);
498 if (reference_file)
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));
505 else
507 change = mode_compile (mode);
508 if (!change)
510 error (0, 0, _("invalid mode: %s"), quote (mode));
511 usage (EXIT_FAILURE);
513 umask_value = umask (0);
516 if (recurse & preserve_root)
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 ("/"));
524 else
526 root_dev_ino = NULL;
529 ok = process_files (argv + optind, FTS_COMFOLLOW | FTS_PHYSICAL);
531 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);