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> */
22 #include <sys/types.h>
28 #include "modechange.h"
32 #include "root-dev-ino.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"
45 CH_NO_CHANGE_REQUESTED
50 /* Print a message for each file that is processed. */
53 /* Print a message for each file whose attributes we change. */
56 /* Do not be verbose. This is the default. */
60 /* The name the program was run with. */
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. */
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.
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. */
91 NO_PRESERVE_ROOT
= CHAR_MAX
+ 1,
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
},
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. */
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)
127 error (0, errno
, _("getting new attributes of %s"), quote (file
));
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. */
141 describe_change (const char *file
, mode_t mode
,
142 enum Change_status changed
)
144 char perms
[12]; /* "-rwxrwxrwx" ls-style modes. */
147 if (changed
== CH_NOT_APPLIED
)
149 printf (_("neither symbolic link %s nor referent has been changed\n"),
154 strmode (mode
, perms
);
155 perms
[10] = '\0'; /* Remove trailing space. */
159 fmt
= _("mode of %s changed to %04lo (%s)\n");
162 fmt
= _("failed to change mode of %s to %04lo (%s)\n");
164 case CH_NO_CHANGE_REQUESTED
:
165 fmt
= _("mode of %s retained as %04lo (%s)\n");
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. */
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);
187 bool chmod_succeeded
= false;
189 switch (ent
->fts_info
)
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)
205 fts_set (fts
, ent
, FTS_AGAIN
);
208 error (0, ent
->fts_errno
, _("cannot access %s"), quote (file_full_name
));
213 error (0, ent
->fts_errno
, _("%s"), quote (file_full_name
));
218 error (0, ent
->fts_errno
, _("cannot read directory %s"),
219 quote (file_full_name
));
224 error (0, 0, _("cannot operate on dangling symlink %s"),
225 quote (file_full_name
));
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
);
244 old_mode
= file_stats
->st_mode
;
245 new_mode
= mode_adjust (old_mode
, S_ISDIR (old_mode
) != 0, umask_value
,
248 if (! S_ISLNK (old_mode
))
250 if (chmodat (fts
->fts_cwd_fd
, file
, new_mode
) == 0)
251 chmod_succeeded
= true;
255 error (0, errno
, _("changing permissions of %s"),
256 quote (file_full_name
));
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
=
271 : !chmod_succeeded
? CH_NOT_APPLIED
272 : !changed
? CH_NO_CHANGE_REQUESTED
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
)
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';
290 _("%s: new permissions are %s, not %s"),
291 quotearg_colon (file_full_name
),
292 new_perms
+ 1, naively_expected_perms
+ 1);
298 fts_set (fts
, ent
, FTS_SKIP
);
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. */
308 process_files (char **files
, int bit_flags
)
312 FTS
*fts
= xfts_open (files
, bit_flags
, NULL
);
318 ent
= fts_read (fts
);
323 /* FIXME: try to give a better message */
324 error (0, errno
, _("fts_read failed"));
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. */
344 if (status
!= EXIT_SUCCESS
)
345 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
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
);
356 Change the mode of each FILE to MODE.\n\
358 -c, --changes like verbose but report only when a change is made\n\
361 --no-preserve-root do not treat `/' specially (the default)\n\
362 --preserve-root fail to operate recursively on `/'\n\
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\
370 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
371 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
374 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
376 emit_bug_reporting_address ();
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
)
389 size_t mode_alloc
= 0;
391 bool preserve_root
= false;
392 char const *reference_file
= NULL
;
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::,::+::=::",
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;
451 case NO_PRESERVE_ROOT
:
452 preserve_root
= false;
455 preserve_root
= true;
457 case REFERENCE_FILE_OPTION
:
458 reference_file
= optarg
;
464 verbosity
= V_changes_only
;
472 case_GETOPT_HELP_CHAR
;
473 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
475 usage (EXIT_FAILURE
);
483 error (0, 0, _("cannot combine mode and --reference options"));
484 usage (EXIT_FAILURE
);
490 mode
= argv
[optind
++];
495 if (!mode
|| mode
!= argv
[optind
- 1])
496 error (0, 0, _("missing operand"));
498 error (0, 0, _("missing operand after %s"), quote (argv
[argc
- 1]));
499 usage (EXIT_FAILURE
);
504 change
= mode_create_from_ref (reference_file
);
506 error (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
507 quote (reference_file
));
511 change
= mode_compile (mode
);
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"),
533 ok
= process_files (argv
+ optind
, FTS_COMFOLLOW
| FTS_PHYSICAL
);
535 exit (ok
? EXIT_SUCCESS
: EXIT_FAILURE
);