Another bootstrap kludge.
[coreutils/ericb.git] / src / chmod.c
bloba22e5c127fc5078ca3c8561c037c05f9daa6906c
1 /* chmod -- change permission modes of files
2 Copyright (C) 89, 90, 91, 1995-2007 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 3 of the License, or
7 (at your option) 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, see <http://www.gnu.org/licenses/>. */
17 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
19 #include <config.h>
20 #include <stdio.h>
21 #include <getopt.h>
22 #include <sys/types.h>
24 #include "system.h"
25 #include "dev-ino.h"
26 #include "error.h"
27 #include "filemode.h"
28 #include "modechange.h"
29 #include "openat.h"
30 #include "quote.h"
31 #include "quotearg.h"
32 #include "root-dev-ino.h"
33 #include "xfts.h"
35 /* The official name of this program (e.g., no `g' prefix). */
36 #define PROGRAM_NAME "chmod"
38 #define AUTHORS "David MacKenzie", "Jim Meyering"
40 enum Change_status
42 CH_NOT_APPLIED,
43 CH_SUCCEEDED,
44 CH_FAILED,
45 CH_NO_CHANGE_REQUESTED
48 enum Verbosity
50 /* Print a message for each file that is processed. */
51 V_high,
53 /* Print a message for each file whose attributes we change. */
54 V_changes_only,
56 /* Do not be verbose. This is the default. */
57 V_off
60 /* The name the program was run with. */
61 char *program_name;
63 /* The desired change to the mode. */
64 static struct mode_change *change;
66 /* The initial umask value, if it might be needed. */
67 static mode_t umask_value;
69 /* If true, change the modes of directories recursively. */
70 static bool recurse;
72 /* If true, force silence (no error messages). */
73 static bool force_silent;
75 /* If true, diagnose surprises from naive misuses like "chmod -r file".
76 POSIX allows diagnostics here, as portable code is supposed to use
77 "chmod -- -r file". */
78 static bool diagnose_surprises;
80 /* Level of verbosity. */
81 static enum Verbosity verbosity = V_off;
83 /* Pointer to the device and inode numbers of `/', when --recursive.
84 Otherwise NULL. */
85 static struct dev_ino *root_dev_ino;
87 /* For long options that have no equivalent short option, use a
88 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
89 enum
91 NO_PRESERVE_ROOT = CHAR_MAX + 1,
92 PRESERVE_ROOT,
93 REFERENCE_FILE_OPTION
96 static struct option const long_options[] =
98 {"changes", no_argument, NULL, 'c'},
99 {"recursive", no_argument, NULL, 'R'},
100 {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
101 {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
102 {"quiet", no_argument, NULL, 'f'},
103 {"reference", required_argument, NULL, REFERENCE_FILE_OPTION},
104 {"silent", no_argument, NULL, 'f'},
105 {"verbose", no_argument, NULL, 'v'},
106 {GETOPT_HELP_OPTION_DECL},
107 {GETOPT_VERSION_OPTION_DECL},
108 {NULL, 0, NULL, 0}
111 /* Return true if the chmodable permission bits of FILE changed.
112 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
114 static bool
115 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
117 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
119 /* The new mode contains unusual bits that the call to chmod may
120 have silently cleared. Check whether they actually changed. */
122 struct stat new_stats;
124 if (stat (file, &new_stats) != 0)
126 if (!force_silent)
127 error (0, errno, _("getting new attributes of %s"), quote (file));
128 return false;
131 new_mode = new_stats.st_mode;
134 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
137 /* Tell the user how/if the MODE of FILE has been changed.
138 CHANGED describes what (if anything) has happened. */
140 static void
141 describe_change (const char *file, mode_t mode,
142 enum Change_status changed)
144 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
145 const char *fmt;
147 if (changed == CH_NOT_APPLIED)
149 printf (_("neither symbolic link %s nor referent has been changed\n"),
150 quote (file));
151 return;
154 strmode (mode, perms);
155 perms[10] = '\0'; /* Remove trailing space. */
156 switch (changed)
158 case CH_SUCCEEDED:
159 fmt = _("mode of %s changed to %04lo (%s)\n");
160 break;
161 case CH_FAILED:
162 fmt = _("failed to change mode of %s to %04lo (%s)\n");
163 break;
164 case CH_NO_CHANGE_REQUESTED:
165 fmt = _("mode of %s retained as %04lo (%s)\n");
166 break;
167 default:
168 abort ();
170 printf (fmt, quote (file),
171 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
174 /* Change the mode of FILE.
175 Return true if successful. This function is called
176 once for every file system object that fts encounters. */
178 static bool
179 process_file (FTS *fts, FTSENT *ent)
181 char const *file_full_name = ent->fts_path;
182 char const *file = ent->fts_accpath;
183 const struct stat *file_stats = ent->fts_statp;
184 mode_t old_mode IF_LINT (= 0);
185 mode_t new_mode IF_LINT (= 0);
186 bool ok = true;
187 bool chmod_succeeded = false;
189 switch (ent->fts_info)
191 case FTS_DP:
192 return true;
194 case FTS_NS:
195 /* For a top-level file or directory, this FTS_NS (stat failed)
196 indicator is determined at the time of the initial fts_open call.
197 With programs like chmod, chown, and chgrp, that modify
198 permissions, it is possible that the file in question is
199 accessible when control reaches this point. So, if this is
200 the first time we've seen the FTS_NS for this file, tell
201 fts_read to stat it "again". */
202 if (ent->fts_level == 0 && ent->fts_number == 0)
204 ent->fts_number = 1;
205 fts_set (fts, ent, FTS_AGAIN);
206 return true;
208 error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
209 ok = false;
210 break;
212 case FTS_ERR:
213 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
214 ok = false;
215 break;
217 case FTS_DNR:
218 error (0, ent->fts_errno, _("cannot read directory %s"),
219 quote (file_full_name));
220 ok = false;
221 break;
223 case FTS_SLNONE:
224 error (0, 0, _("cannot operate on dangling symlink %s"),
225 quote (file_full_name));
226 ok = false;
228 default:
229 break;
232 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
234 ROOT_DEV_INO_WARN (file_full_name);
235 /* Tell fts not to traverse into this hierarchy. */
236 fts_set (fts, ent, FTS_SKIP);
237 /* Ensure that we do not process "/" on the second visit. */
238 ent = fts_read (fts);
239 ok = false;
242 if (ok)
244 old_mode = file_stats->st_mode;
245 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
246 change, NULL);
248 if (! S_ISLNK (old_mode))
250 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
251 chmod_succeeded = true;
252 else
254 if (! force_silent)
255 error (0, errno, _("changing permissions of %s"),
256 quote (file_full_name));
257 ok = false;
262 if (verbosity != V_off)
264 bool changed = (chmod_succeeded
265 && mode_changed (file, old_mode, new_mode));
267 if (changed || verbosity == V_high)
269 enum Change_status ch_status =
270 (!ok ? CH_FAILED
271 : !chmod_succeeded ? CH_NOT_APPLIED
272 : !changed ? CH_NO_CHANGE_REQUESTED
273 : CH_SUCCEEDED);
274 describe_change (file_full_name, new_mode, ch_status);
278 if (chmod_succeeded & diagnose_surprises)
280 mode_t naively_expected_mode =
281 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
282 if (new_mode & ~naively_expected_mode)
284 char new_perms[12];
285 char naively_expected_perms[12];
286 strmode (new_mode, new_perms);
287 strmode (naively_expected_mode, naively_expected_perms);
288 new_perms[10] = naively_expected_perms[10] = '\0';
289 error (0, 0,
290 _("%s: new permissions are %s, not %s"),
291 quotearg_colon (file_full_name),
292 new_perms + 1, naively_expected_perms + 1);
293 ok = false;
297 if ( ! recurse)
298 fts_set (fts, ent, FTS_SKIP);
300 return ok;
303 /* Recursively change the modes of the specified FILES (the last entry
304 of which is NULL). BIT_FLAGS controls how fts works.
305 Return true if successful. */
307 static bool
308 process_files (char **files, int bit_flags)
310 bool ok = true;
312 FTS *fts = xfts_open (files, bit_flags, NULL);
314 while (1)
316 FTSENT *ent;
318 ent = fts_read (fts);
319 if (ent == NULL)
321 if (errno != 0)
323 /* FIXME: try to give a better message */
324 error (0, errno, _("fts_read failed"));
325 ok = false;
327 break;
330 ok &= process_file (fts, ent);
333 /* Ignore failure, since the only way it can do so is in failing to
334 return to the original directory, and since we're about to exit,
335 that doesn't matter. */
336 fts_close (fts);
338 return ok;
341 void
342 usage (int status)
344 if (status != EXIT_SUCCESS)
345 fprintf (stderr, _("Try `%s --help' for more information.\n"),
346 program_name);
347 else
349 printf (_("\
350 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
351 or: %s [OPTION]... OCTAL-MODE FILE...\n\
352 or: %s [OPTION]... --reference=RFILE FILE...\n\
354 program_name, program_name, program_name);
355 fputs (_("\
356 Change the mode of each FILE to MODE.\n\
358 -c, --changes like verbose but report only when a change is made\n\
359 "), stdout);
360 fputs (_("\
361 --no-preserve-root do not treat `/' specially (the default)\n\
362 --preserve-root fail to operate recursively on `/'\n\
363 "), stdout);
364 fputs (_("\
365 -f, --silent, --quiet suppress most error messages\n\
366 -v, --verbose output a diagnostic for every file processed\n\
367 --reference=RFILE use RFILE's mode instead of MODE values\n\
368 -R, --recursive change files and directories recursively\n\
369 "), stdout);
370 fputs (HELP_OPTION_DESCRIPTION, stdout);
371 fputs (VERSION_OPTION_DESCRIPTION, stdout);
372 fputs (_("\
374 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
375 "), stdout);
376 emit_bug_reporting_address ();
378 exit (status);
381 /* Parse the ASCII mode given on the command line into a linked list
382 of `struct mode_change' and apply that to each file argument. */
385 main (int argc, char **argv)
387 char *mode = NULL;
388 size_t mode_len = 0;
389 size_t mode_alloc = 0;
390 bool ok;
391 bool preserve_root = false;
392 char const *reference_file = NULL;
393 int c;
395 initialize_main (&argc, &argv);
396 program_name = argv[0];
397 setlocale (LC_ALL, "");
398 bindtextdomain (PACKAGE, LOCALEDIR);
399 textdomain (PACKAGE);
401 atexit (close_stdout);
403 recurse = force_silent = diagnose_surprises = false;
405 while ((c = getopt_long (argc, argv,
406 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
407 long_options, NULL))
408 != -1)
410 switch (c)
412 case 'r':
413 case 'w':
414 case 'x':
415 case 'X':
416 case 's':
417 case 't':
418 case 'u':
419 case 'g':
420 case 'o':
421 case 'a':
422 case ',':
423 case '+':
424 case '=':
425 /* Support nonportable uses like "chmod -w", but diagnose
426 surprises due to umask confusion. Even though "--", "--r",
427 etc., are valid modes, there is no "case '-'" here since
428 getopt_long reserves leading "--" for long options. */
430 /* Allocate a mode string (e.g., "-rwx") by concatenating
431 the argument containing this option. If a previous mode
432 string was given, concatenate the previous string, a
433 comma, and the new string (e.g., "-s,-rwx"). */
435 char const *arg = argv[optind - 1];
436 size_t arg_len = strlen (arg);
437 size_t mode_comma_len = mode_len + !!mode_len;
438 size_t new_mode_len = mode_comma_len + arg_len;
439 if (mode_alloc <= new_mode_len)
441 mode_alloc = new_mode_len + 1;
442 mode = X2REALLOC (mode, &mode_alloc);
444 mode[mode_len] = ',';
445 strcpy (mode + mode_comma_len, arg);
446 mode_len = new_mode_len;
448 diagnose_surprises = true;
450 break;
451 case NO_PRESERVE_ROOT:
452 preserve_root = false;
453 break;
454 case PRESERVE_ROOT:
455 preserve_root = true;
456 break;
457 case REFERENCE_FILE_OPTION:
458 reference_file = optarg;
459 break;
460 case 'R':
461 recurse = true;
462 break;
463 case 'c':
464 verbosity = V_changes_only;
465 break;
466 case 'f':
467 force_silent = true;
468 break;
469 case 'v':
470 verbosity = V_high;
471 break;
472 case_GETOPT_HELP_CHAR;
473 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
474 default:
475 usage (EXIT_FAILURE);
479 if (reference_file)
481 if (mode)
483 error (0, 0, _("cannot combine mode and --reference options"));
484 usage (EXIT_FAILURE);
487 else
489 if (!mode)
490 mode = argv[optind++];
493 if (optind >= argc)
495 if (!mode || mode != argv[optind - 1])
496 error (0, 0, _("missing operand"));
497 else
498 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
499 usage (EXIT_FAILURE);
502 if (reference_file)
504 change = mode_create_from_ref (reference_file);
505 if (!change)
506 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
507 quote (reference_file));
509 else
511 change = mode_compile (mode);
512 if (!change)
514 error (0, 0, _("invalid mode: %s"), quote (mode));
515 usage (EXIT_FAILURE);
517 umask_value = umask (0);
520 if (recurse & preserve_root)
522 static struct dev_ino dev_ino_buf;
523 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
524 if (root_dev_ino == NULL)
525 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
526 quote ("/"));
528 else
530 root_dev_ino = NULL;
533 ok = process_files (argv + optind, FTS_COMFOLLOW | FTS_PHYSICAL);
535 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);