tests: unpack xz-compressed tarballs when possible, not always *.gz
[coreutils.git] / src / chmod.c
blob1a0dafa70419838b0e8e9f5f0f52ec62213e2933
1 /* chmod -- change permission modes of files
2 Copyright (C) 89, 90, 91, 1995-2009 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 "ignore-value.h"
29 #include "modechange.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 \
39 proper_name ("David MacKenzie"), \
40 proper_name ("Jim Meyering")
42 enum Change_status
44 CH_NOT_APPLIED,
45 CH_SUCCEEDED,
46 CH_FAILED,
47 CH_NO_CHANGE_REQUESTED
50 enum Verbosity
52 /* Print a message for each file that is processed. */
53 V_high,
55 /* Print a message for each file whose attributes we change. */
56 V_changes_only,
58 /* Do not be verbose. This is the default. */
59 V_off
62 /* The desired change to the mode. */
63 static struct mode_change *change;
65 /* The initial umask value, if it might be needed. */
66 static mode_t umask_value;
68 /* If true, change the modes of directories recursively. */
69 static bool recurse;
71 /* If true, force silence (suppress most of error messages). */
72 static bool force_silent;
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;
79 /* Level of verbosity. */
80 static enum Verbosity verbosity = V_off;
82 /* Pointer to the device and inode numbers of `/', when --recursive.
83 Otherwise NULL. */
84 static struct dev_ino *root_dev_ino;
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
90 NO_PRESERVE_ROOT = CHAR_MAX + 1,
91 PRESERVE_ROOT,
92 REFERENCE_FILE_OPTION
95 static struct option const long_options[] =
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}
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. */
113 static bool
114 mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
116 if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
118 /* The new mode contains unusual bits that the call to chmod may
119 have silently cleared. Check whether they actually changed. */
121 struct stat new_stats;
123 if (stat (file, &new_stats) != 0)
125 if (! force_silent)
126 error (0, errno, _("getting new attributes of %s"), quote (file));
127 return false;
130 new_mode = new_stats.st_mode;
133 return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
136 /* Tell the user how/if the MODE of FILE has been changed.
137 CHANGED describes what (if anything) has happened. */
139 static void
140 describe_change (const char *file, mode_t mode,
141 enum Change_status changed)
143 char perms[12]; /* "-rwxrwxrwx" ls-style modes. */
144 const char *fmt;
146 if (changed == CH_NOT_APPLIED)
148 printf (_("neither symbolic link %s nor referent has been changed\n"),
149 quote (file));
150 return;
153 strmode (mode, perms);
154 perms[10] = '\0'; /* Remove trailing space. */
155 switch (changed)
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 ();
169 printf (fmt, quote (file),
170 (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
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. */
177 static bool
178 process_file (FTS *fts, FTSENT *ent)
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;
188 switch (ent->fts_info)
190 case FTS_DP:
191 return true;
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)
203 ent->fts_number = 1;
204 fts_set (fts, ent, FTS_AGAIN);
205 return true;
207 if (! force_silent)
208 error (0, ent->fts_errno, _("cannot access %s"),
209 quote (file_full_name));
210 ok = false;
211 break;
213 case FTS_ERR:
214 if (! force_silent)
215 error (0, ent->fts_errno, _("%s"), quote (file_full_name));
216 ok = false;
217 break;
219 case FTS_DNR:
220 if (! force_silent)
221 error (0, ent->fts_errno, _("cannot read directory %s"),
222 quote (file_full_name));
223 ok = false;
224 break;
226 case FTS_SLNONE:
227 if (! force_silent)
228 error (0, 0, _("cannot operate on dangling symlink %s"),
229 quote (file_full_name));
230 ok = false;
231 break;
233 case FTS_DC: /* directory that causes cycles */
234 if (cycle_warning_required (fts, ent))
236 emit_cycle_warning (file_full_name);
237 return false;
239 break;
241 default:
242 break;
245 if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
247 ROOT_DEV_INO_WARN (file_full_name);
248 /* Tell fts not to traverse into this hierarchy. */
249 fts_set (fts, ent, FTS_SKIP);
250 /* Ensure that we do not process "/" on the second visit. */
251 ignore_ptr (fts_read (fts));
252 return false;
255 if (ok)
257 old_mode = file_stats->st_mode;
258 new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
259 change, NULL);
261 if (! S_ISLNK (old_mode))
263 if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
264 chmod_succeeded = true;
265 else
267 if (! force_silent)
268 error (0, errno, _("changing permissions of %s"),
269 quote (file_full_name));
270 ok = false;
275 if (verbosity != V_off)
277 bool changed = (chmod_succeeded
278 && mode_changed (file, old_mode, new_mode));
280 if (changed || verbosity == V_high)
282 enum Change_status ch_status =
283 (!ok ? CH_FAILED
284 : !chmod_succeeded ? CH_NOT_APPLIED
285 : !changed ? CH_NO_CHANGE_REQUESTED
286 : CH_SUCCEEDED);
287 describe_change (file_full_name, new_mode, ch_status);
291 if (chmod_succeeded && diagnose_surprises)
293 mode_t naively_expected_mode =
294 mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
295 if (new_mode & ~naively_expected_mode)
297 char new_perms[12];
298 char naively_expected_perms[12];
299 strmode (new_mode, new_perms);
300 strmode (naively_expected_mode, naively_expected_perms);
301 new_perms[10] = naively_expected_perms[10] = '\0';
302 error (0, 0,
303 _("%s: new permissions are %s, not %s"),
304 quotearg_colon (file_full_name),
305 new_perms + 1, naively_expected_perms + 1);
306 ok = false;
310 if ( ! recurse)
311 fts_set (fts, ent, FTS_SKIP);
313 return ok;
316 /* Recursively change the modes of the specified FILES (the last entry
317 of which is NULL). BIT_FLAGS controls how fts works.
318 Return true if successful. */
320 static bool
321 process_files (char **files, int bit_flags)
323 bool ok = true;
325 FTS *fts = xfts_open (files, bit_flags, NULL);
327 while (1)
329 FTSENT *ent;
331 ent = fts_read (fts);
332 if (ent == NULL)
334 if (errno != 0)
336 /* FIXME: try to give a better message */
337 if (! force_silent)
338 error (0, errno, _("fts_read failed"));
339 ok = false;
341 break;
344 ok &= process_file (fts, ent);
347 if (fts_close (fts) != 0)
349 error (0, errno, _("fts_close failed"));
350 ok = false;
353 return ok;
356 void
357 usage (int status)
359 if (status != EXIT_SUCCESS)
360 fprintf (stderr, _("Try `%s --help' for more information.\n"),
361 program_name);
362 else
364 printf (_("\
365 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
366 or: %s [OPTION]... OCTAL-MODE FILE...\n\
367 or: %s [OPTION]... --reference=RFILE FILE...\n\
369 program_name, program_name, program_name);
370 fputs (_("\
371 Change the mode of each FILE to MODE.\n\
373 -c, --changes like verbose but report only when a change is made\n\
374 "), stdout);
375 fputs (_("\
376 --no-preserve-root do not treat `/' specially (the default)\n\
377 --preserve-root fail to operate recursively on `/'\n\
378 "), stdout);
379 fputs (_("\
380 -f, --silent, --quiet suppress most error messages\n\
381 -v, --verbose output a diagnostic for every file processed\n\
382 --reference=RFILE use RFILE's mode instead of MODE values\n\
383 -R, --recursive change files and directories recursively\n\
384 "), stdout);
385 fputs (HELP_OPTION_DESCRIPTION, stdout);
386 fputs (VERSION_OPTION_DESCRIPTION, stdout);
387 fputs (_("\
389 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
390 "), stdout);
391 emit_ancillary_info ();
393 exit (status);
396 /* Parse the ASCII mode given on the command line into a linked list
397 of `struct mode_change' and apply that to each file argument. */
400 main (int argc, char **argv)
402 char *mode = NULL;
403 size_t mode_len = 0;
404 size_t mode_alloc = 0;
405 bool ok;
406 bool preserve_root = false;
407 char const *reference_file = NULL;
408 int c;
410 initialize_main (&argc, &argv);
411 set_program_name (argv[0]);
412 setlocale (LC_ALL, "");
413 bindtextdomain (PACKAGE, LOCALEDIR);
414 textdomain (PACKAGE);
416 atexit (close_stdout);
418 recurse = force_silent = diagnose_surprises = false;
420 while ((c = getopt_long (argc, argv,
421 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
422 long_options, NULL))
423 != -1)
425 switch (c)
427 case 'r':
428 case 'w':
429 case 'x':
430 case 'X':
431 case 's':
432 case 't':
433 case 'u':
434 case 'g':
435 case 'o':
436 case 'a':
437 case ',':
438 case '+':
439 case '=':
440 /* Support nonportable uses like "chmod -w", but diagnose
441 surprises due to umask confusion. Even though "--", "--r",
442 etc., are valid modes, there is no "case '-'" here since
443 getopt_long reserves leading "--" for long options. */
445 /* Allocate a mode string (e.g., "-rwx") by concatenating
446 the argument containing this option. If a previous mode
447 string was given, concatenate the previous string, a
448 comma, and the new string (e.g., "-s,-rwx"). */
450 char const *arg = argv[optind - 1];
451 size_t arg_len = strlen (arg);
452 size_t mode_comma_len = mode_len + !!mode_len;
453 size_t new_mode_len = mode_comma_len + arg_len;
454 if (mode_alloc <= new_mode_len)
456 mode_alloc = new_mode_len + 1;
457 mode = X2REALLOC (mode, &mode_alloc);
459 mode[mode_len] = ',';
460 strcpy (mode + mode_comma_len, arg);
461 mode_len = new_mode_len;
463 diagnose_surprises = true;
465 break;
466 case NO_PRESERVE_ROOT:
467 preserve_root = false;
468 break;
469 case PRESERVE_ROOT:
470 preserve_root = true;
471 break;
472 case REFERENCE_FILE_OPTION:
473 reference_file = optarg;
474 break;
475 case 'R':
476 recurse = true;
477 break;
478 case 'c':
479 verbosity = V_changes_only;
480 break;
481 case 'f':
482 force_silent = true;
483 break;
484 case 'v':
485 verbosity = V_high;
486 break;
487 case_GETOPT_HELP_CHAR;
488 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
489 default:
490 usage (EXIT_FAILURE);
494 if (reference_file)
496 if (mode)
498 error (0, 0, _("cannot combine mode and --reference options"));
499 usage (EXIT_FAILURE);
502 else
504 if (!mode)
505 mode = argv[optind++];
508 if (optind >= argc)
510 if (!mode || mode != argv[optind - 1])
511 error (0, 0, _("missing operand"));
512 else
513 error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
514 usage (EXIT_FAILURE);
517 if (reference_file)
519 change = mode_create_from_ref (reference_file);
520 if (!change)
521 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
522 quote (reference_file));
524 else
526 change = mode_compile (mode);
527 if (!change)
529 error (0, 0, _("invalid mode: %s"), quote (mode));
530 usage (EXIT_FAILURE);
532 umask_value = umask (0);
535 if (recurse && preserve_root)
537 static struct dev_ino dev_ino_buf;
538 root_dev_ino = get_root_dev_ino (&dev_ino_buf);
539 if (root_dev_ino == NULL)
540 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
541 quote ("/"));
543 else
545 root_dev_ino = NULL;
548 ok = process_files (argv + optind,
549 FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
551 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);