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> */
22 #include <sys/types.h>
28 #include "modechange.h"
31 #include "root-dev-ino.h"
34 /* The official name of this program (e.g., no `g' prefix). */
35 #define PROGRAM_NAME "chmod"
38 proper_name ("David MacKenzie"), \
39 proper_name ("Jim Meyering")
46 CH_NO_CHANGE_REQUESTED
51 /* Print a message for each file that is processed. */
54 /* Print a message for each file whose attributes we change. */
57 /* Do not be verbose. This is the default. */
61 /* The desired change to the mode. */
62 static struct mode_change
*change
;
64 /* The initial umask value, if it might be needed. */
65 static mode_t umask_value
;
67 /* If true, change the modes of directories recursively. */
70 /* If true, force silence (suppress most of error messages). */
71 static bool force_silent
;
73 /* If true, diagnose surprises from naive misuses like "chmod -r file".
74 POSIX allows diagnostics here, as portable code is supposed to use
75 "chmod -- -r file". */
76 static bool diagnose_surprises
;
78 /* Level of verbosity. */
79 static enum Verbosity verbosity
= V_off
;
81 /* Pointer to the device and inode numbers of `/', when --recursive.
83 static struct dev_ino
*root_dev_ino
;
85 /* For long options that have no equivalent short option, use a
86 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
89 NO_PRESERVE_ROOT
= CHAR_MAX
+ 1,
94 static struct option
const long_options
[] =
96 {"changes", no_argument
, NULL
, 'c'},
97 {"recursive", no_argument
, NULL
, 'R'},
98 {"no-preserve-root", no_argument
, NULL
, NO_PRESERVE_ROOT
},
99 {"preserve-root", no_argument
, NULL
, PRESERVE_ROOT
},
100 {"quiet", no_argument
, NULL
, 'f'},
101 {"reference", required_argument
, NULL
, REFERENCE_FILE_OPTION
},
102 {"silent", no_argument
, NULL
, 'f'},
103 {"verbose", no_argument
, NULL
, 'v'},
104 {GETOPT_HELP_OPTION_DECL
},
105 {GETOPT_VERSION_OPTION_DECL
},
109 /* Return true if the chmodable permission bits of FILE changed.
110 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
113 mode_changed (char const *file
, mode_t old_mode
, mode_t new_mode
)
115 if (new_mode
& (S_ISUID
| S_ISGID
| S_ISVTX
))
117 /* The new mode contains unusual bits that the call to chmod may
118 have silently cleared. Check whether they actually changed. */
120 struct stat new_stats
;
122 if (stat (file
, &new_stats
) != 0)
125 error (0, errno
, _("getting new attributes of %s"), quote (file
));
129 new_mode
= new_stats
.st_mode
;
132 return ((old_mode
^ new_mode
) & CHMOD_MODE_BITS
) != 0;
135 /* Tell the user how/if the MODE of FILE has been changed.
136 CHANGED describes what (if anything) has happened. */
139 describe_change (const char *file
, mode_t mode
,
140 enum Change_status changed
)
142 char perms
[12]; /* "-rwxrwxrwx" ls-style modes. */
145 if (changed
== CH_NOT_APPLIED
)
147 printf (_("neither symbolic link %s nor referent has been changed\n"),
152 strmode (mode
, perms
);
153 perms
[10] = '\0'; /* Remove trailing space. */
157 fmt
= _("mode of %s changed to %04lo (%s)\n");
160 fmt
= _("failed to change mode of %s to %04lo (%s)\n");
162 case CH_NO_CHANGE_REQUESTED
:
163 fmt
= _("mode of %s retained as %04lo (%s)\n");
168 printf (fmt
, quote (file
),
169 (unsigned long int) (mode
& CHMOD_MODE_BITS
), &perms
[1]);
172 /* Change the mode of FILE.
173 Return true if successful. This function is called
174 once for every file system object that fts encounters. */
177 process_file (FTS
*fts
, FTSENT
*ent
)
179 char const *file_full_name
= ent
->fts_path
;
180 char const *file
= ent
->fts_accpath
;
181 const struct stat
*file_stats
= ent
->fts_statp
;
182 mode_t old_mode
IF_LINT (= 0);
183 mode_t new_mode
IF_LINT (= 0);
185 bool chmod_succeeded
= false;
187 switch (ent
->fts_info
)
193 /* For a top-level file or directory, this FTS_NS (stat failed)
194 indicator is determined at the time of the initial fts_open call.
195 With programs like chmod, chown, and chgrp, that modify
196 permissions, it is possible that the file in question is
197 accessible when control reaches this point. So, if this is
198 the first time we've seen the FTS_NS for this file, tell
199 fts_read to stat it "again". */
200 if (ent
->fts_level
== 0 && ent
->fts_number
== 0)
203 fts_set (fts
, ent
, FTS_AGAIN
);
207 error (0, ent
->fts_errno
, _("cannot access %s"),
208 quote (file_full_name
));
214 error (0, ent
->fts_errno
, _("%s"), quote (file_full_name
));
220 error (0, ent
->fts_errno
, _("cannot read directory %s"),
221 quote (file_full_name
));
227 error (0, 0, _("cannot operate on dangling symlink %s"),
228 quote (file_full_name
));
235 if (ok
&& ROOT_DEV_INO_CHECK (root_dev_ino
, file_stats
))
237 ROOT_DEV_INO_WARN (file_full_name
);
238 /* Tell fts not to traverse into this hierarchy. */
239 fts_set (fts
, ent
, FTS_SKIP
);
240 /* Ensure that we do not process "/" on the second visit. */
241 ent
= fts_read (fts
);
247 old_mode
= file_stats
->st_mode
;
248 new_mode
= mode_adjust (old_mode
, S_ISDIR (old_mode
) != 0, umask_value
,
251 if (! S_ISLNK (old_mode
))
253 if (chmodat (fts
->fts_cwd_fd
, file
, new_mode
) == 0)
254 chmod_succeeded
= true;
258 error (0, errno
, _("changing permissions of %s"),
259 quote (file_full_name
));
265 if (verbosity
!= V_off
)
267 bool changed
= (chmod_succeeded
268 && mode_changed (file
, old_mode
, new_mode
));
270 if (changed
|| verbosity
== V_high
)
272 enum Change_status ch_status
=
274 : !chmod_succeeded
? CH_NOT_APPLIED
275 : !changed
? CH_NO_CHANGE_REQUESTED
277 describe_change (file_full_name
, new_mode
, ch_status
);
281 if (chmod_succeeded
& diagnose_surprises
)
283 mode_t naively_expected_mode
=
284 mode_adjust (old_mode
, S_ISDIR (old_mode
) != 0, 0, change
, NULL
);
285 if (new_mode
& ~naively_expected_mode
)
288 char naively_expected_perms
[12];
289 strmode (new_mode
, new_perms
);
290 strmode (naively_expected_mode
, naively_expected_perms
);
291 new_perms
[10] = naively_expected_perms
[10] = '\0';
293 _("%s: new permissions are %s, not %s"),
294 quotearg_colon (file_full_name
),
295 new_perms
+ 1, naively_expected_perms
+ 1);
301 fts_set (fts
, ent
, FTS_SKIP
);
306 /* Recursively change the modes of the specified FILES (the last entry
307 of which is NULL). BIT_FLAGS controls how fts works.
308 Return true if successful. */
311 process_files (char **files
, int bit_flags
)
315 FTS
*fts
= xfts_open (files
, bit_flags
, NULL
);
321 ent
= fts_read (fts
);
326 /* FIXME: try to give a better message */
328 error (0, errno
, _("fts_read failed"));
334 ok
&= process_file (fts
, ent
);
337 /* Ignore failure, since the only way it can do so is in failing to
338 return to the original directory, and since we're about to exit,
339 that doesn't matter. */
348 if (status
!= EXIT_SUCCESS
)
349 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
354 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
355 or: %s [OPTION]... OCTAL-MODE FILE...\n\
356 or: %s [OPTION]... --reference=RFILE FILE...\n\
358 program_name
, program_name
, program_name
);
360 Change the mode of each FILE to MODE.\n\
362 -c, --changes like verbose but report only when a change is made\n\
365 --no-preserve-root do not treat `/' specially (the default)\n\
366 --preserve-root fail to operate recursively on `/'\n\
369 -f, --silent, --quiet suppress most error messages\n\
370 -v, --verbose output a diagnostic for every file processed\n\
371 --reference=RFILE use RFILE's mode instead of MODE values\n\
372 -R, --recursive change files and directories recursively\n\
374 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
375 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
378 Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
380 emit_bug_reporting_address ();
385 /* Parse the ASCII mode given on the command line into a linked list
386 of `struct mode_change' and apply that to each file argument. */
389 main (int argc
, char **argv
)
393 size_t mode_alloc
= 0;
395 bool preserve_root
= false;
396 char const *reference_file
= NULL
;
399 initialize_main (&argc
, &argv
);
400 set_program_name (argv
[0]);
401 setlocale (LC_ALL
, "");
402 bindtextdomain (PACKAGE
, LOCALEDIR
);
403 textdomain (PACKAGE
);
405 atexit (close_stdout
);
407 recurse
= force_silent
= diagnose_surprises
= false;
409 while ((c
= getopt_long (argc
, argv
,
410 "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
429 /* Support nonportable uses like "chmod -w", but diagnose
430 surprises due to umask confusion. Even though "--", "--r",
431 etc., are valid modes, there is no "case '-'" here since
432 getopt_long reserves leading "--" for long options. */
434 /* Allocate a mode string (e.g., "-rwx") by concatenating
435 the argument containing this option. If a previous mode
436 string was given, concatenate the previous string, a
437 comma, and the new string (e.g., "-s,-rwx"). */
439 char const *arg
= argv
[optind
- 1];
440 size_t arg_len
= strlen (arg
);
441 size_t mode_comma_len
= mode_len
+ !!mode_len
;
442 size_t new_mode_len
= mode_comma_len
+ arg_len
;
443 if (mode_alloc
<= new_mode_len
)
445 mode_alloc
= new_mode_len
+ 1;
446 mode
= X2REALLOC (mode
, &mode_alloc
);
448 mode
[mode_len
] = ',';
449 strcpy (mode
+ mode_comma_len
, arg
);
450 mode_len
= new_mode_len
;
452 diagnose_surprises
= true;
455 case NO_PRESERVE_ROOT
:
456 preserve_root
= false;
459 preserve_root
= true;
461 case REFERENCE_FILE_OPTION
:
462 reference_file
= optarg
;
468 verbosity
= V_changes_only
;
476 case_GETOPT_HELP_CHAR
;
477 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
479 usage (EXIT_FAILURE
);
487 error (0, 0, _("cannot combine mode and --reference options"));
488 usage (EXIT_FAILURE
);
494 mode
= argv
[optind
++];
499 if (!mode
|| mode
!= argv
[optind
- 1])
500 error (0, 0, _("missing operand"));
502 error (0, 0, _("missing operand after %s"), quote (argv
[argc
- 1]));
503 usage (EXIT_FAILURE
);
508 change
= mode_create_from_ref (reference_file
);
510 error (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
511 quote (reference_file
));
515 change
= mode_compile (mode
);
518 error (0, 0, _("invalid mode: %s"), quote (mode
));
519 usage (EXIT_FAILURE
);
521 umask_value
= umask (0);
524 if (recurse
& preserve_root
)
526 static struct dev_ino dev_ino_buf
;
527 root_dev_ino
= get_root_dev_ino (&dev_ino_buf
);
528 if (root_dev_ino
== NULL
)
529 error (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
537 ok
= process_files (argv
+ optind
,
538 FTS_COMFOLLOW
| FTS_PHYSICAL
| FTS_DEFER_STAT
);
540 exit (ok
? EXIT_SUCCESS
: EXIT_FAILURE
);