1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-2013 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 "ignore-value.h"
29 #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"
39 proper_name ("David MacKenzie"), \
40 proper_name ("Jim Meyering")
47 CH_NO_CHANGE_REQUESTED
52 /* Print a message for each file that is processed. */
55 /* Print a message for each file whose attributes we change. */
58 /* Do not be verbose. This is the default. */
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. */
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.
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. */
90 NO_PRESERVE_ROOT
= CHAR_MAX
+ 1,
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
},
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. */
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)
126 error (0, errno
, _("getting new attributes of %s"), quote (file
));
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. */
140 describe_change (const char *file
, mode_t old_mode
, mode_t mode
,
141 enum Change_status changed
)
143 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. */
157 strmode (old_mode
, old_perms
);
158 old_perms
[10] = '\0'; /* Remove trailing space. */
163 fmt
= _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
166 fmt
= _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
168 case CH_NO_CHANGE_REQUESTED
:
169 fmt
= _("mode of %s retained as %04lo (%s)\n");
170 printf (fmt
, quote (file
),
171 (unsigned long int) (mode
& CHMOD_MODE_BITS
), &perms
[1]);
176 printf (fmt
, quote (file
),
177 (unsigned long int) (old_mode
& CHMOD_MODE_BITS
), &old_perms
[1],
178 (unsigned long int) (mode
& CHMOD_MODE_BITS
), &perms
[1]);
181 /* Change the mode of FILE.
182 Return true if successful. This function is called
183 once for every file system object that fts encounters. */
186 process_file (FTS
*fts
, FTSENT
*ent
)
188 char const *file_full_name
= ent
->fts_path
;
189 char const *file
= ent
->fts_accpath
;
190 const struct stat
*file_stats
= ent
->fts_statp
;
191 mode_t old_mode
IF_LINT ( = 0);
192 mode_t new_mode
IF_LINT ( = 0);
194 bool chmod_succeeded
= false;
196 switch (ent
->fts_info
)
202 /* For a top-level file or directory, this FTS_NS (stat failed)
203 indicator is determined at the time of the initial fts_open call.
204 With programs like chmod, chown, and chgrp, that modify
205 permissions, it is possible that the file in question is
206 accessible when control reaches this point. So, if this is
207 the first time we've seen the FTS_NS for this file, tell
208 fts_read to stat it "again". */
209 if (ent
->fts_level
== 0 && ent
->fts_number
== 0)
212 fts_set (fts
, ent
, FTS_AGAIN
);
216 error (0, ent
->fts_errno
, _("cannot access %s"),
217 quote (file_full_name
));
223 error (0, ent
->fts_errno
, "%s", quote (file_full_name
));
229 error (0, ent
->fts_errno
, _("cannot read directory %s"),
230 quote (file_full_name
));
236 error (0, 0, _("cannot operate on dangling symlink %s"),
237 quote (file_full_name
));
241 case FTS_DC
: /* directory that causes cycles */
242 if (cycle_warning_required (fts
, ent
))
244 emit_cycle_warning (file_full_name
);
253 if (ok
&& ROOT_DEV_INO_CHECK (root_dev_ino
, file_stats
))
255 ROOT_DEV_INO_WARN (file_full_name
);
256 /* Tell fts not to traverse into this hierarchy. */
257 fts_set (fts
, ent
, FTS_SKIP
);
258 /* Ensure that we do not process "/" on the second visit. */
259 ignore_value (fts_read (fts
));
265 old_mode
= file_stats
->st_mode
;
266 new_mode
= mode_adjust (old_mode
, S_ISDIR (old_mode
) != 0, umask_value
,
269 if (! S_ISLNK (old_mode
))
271 if (chmodat (fts
->fts_cwd_fd
, file
, new_mode
) == 0)
272 chmod_succeeded
= true;
276 error (0, errno
, _("changing permissions of %s"),
277 quote (file_full_name
));
283 if (verbosity
!= V_off
)
285 bool changed
= (chmod_succeeded
286 && mode_changed (file
, old_mode
, new_mode
));
288 if (changed
|| verbosity
== V_high
)
290 enum Change_status ch_status
=
292 : !chmod_succeeded
? CH_NOT_APPLIED
293 : !changed
? CH_NO_CHANGE_REQUESTED
295 describe_change (file_full_name
, old_mode
, new_mode
, ch_status
);
299 if (chmod_succeeded
&& diagnose_surprises
)
301 mode_t naively_expected_mode
=
302 mode_adjust (old_mode
, S_ISDIR (old_mode
) != 0, 0, change
, NULL
);
303 if (new_mode
& ~naively_expected_mode
)
306 char naively_expected_perms
[12];
307 strmode (new_mode
, new_perms
);
308 strmode (naively_expected_mode
, naively_expected_perms
);
309 new_perms
[10] = naively_expected_perms
[10] = '\0';
311 _("%s: new permissions are %s, not %s"),
312 quotearg_colon (file_full_name
),
313 new_perms
+ 1, naively_expected_perms
+ 1);
319 fts_set (fts
, ent
, FTS_SKIP
);
324 /* Recursively change the modes of the specified FILES (the last entry
325 of which is NULL). BIT_FLAGS controls how fts works.
326 Return true if successful. */
329 process_files (char **files
, int bit_flags
)
333 FTS
*fts
= xfts_open (files
, bit_flags
, NULL
);
339 ent
= fts_read (fts
);
344 /* FIXME: try to give a better message */
346 error (0, errno
, _("fts_read failed"));
352 ok
&= process_file (fts
, ent
);
355 if (fts_close (fts
) != 0)
357 error (0, errno
, _("fts_close failed"));
367 if (status
!= EXIT_SUCCESS
)
372 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
373 or: %s [OPTION]... OCTAL-MODE FILE...\n\
374 or: %s [OPTION]... --reference=RFILE FILE...\n\
376 program_name
, program_name
, program_name
);
378 Change the mode of each FILE to MODE.\n\
379 With --reference, change the mode of each FILE to that of RFILE.\n\
383 -c, --changes like verbose but report only when a change is made\n\
384 -f, --silent, --quiet suppress most error messages\n\
385 -v, --verbose output a diagnostic for every file processed\n\
388 --no-preserve-root do not treat '/' specially (the default)\n\
389 --preserve-root fail to operate recursively on '/'\n\
392 --reference=RFILE use RFILE's mode instead of MODE values\n\
395 -R, --recursive change files and directories recursively\n\
397 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
398 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
401 Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\
403 emit_ancillary_info ();
408 /* Parse the ASCII mode given on the command line into a linked list
409 of 'struct mode_change' and apply that to each file argument. */
412 main (int argc
, char **argv
)
416 size_t mode_alloc
= 0;
418 bool preserve_root
= false;
419 char const *reference_file
= NULL
;
422 initialize_main (&argc
, &argv
);
423 set_program_name (argv
[0]);
424 setlocale (LC_ALL
, "");
425 bindtextdomain (PACKAGE
, LOCALEDIR
);
426 textdomain (PACKAGE
);
428 atexit (close_stdout
);
430 recurse
= force_silent
= diagnose_surprises
= false;
432 while ((c
= getopt_long (argc
, argv
,
433 ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::"
434 "0::1::2::3::4::5::6::7::"),
453 case '0': case '1': case '2': case '3':
454 case '4': case '5': case '6': case '7':
455 /* Support nonportable uses like "chmod -w", but diagnose
456 surprises due to umask confusion. Even though "--", "--r",
457 etc., are valid modes, there is no "case '-'" here since
458 getopt_long reserves leading "--" for long options. */
460 /* Allocate a mode string (e.g., "-rwx") by concatenating
461 the argument containing this option. If a previous mode
462 string was given, concatenate the previous string, a
463 comma, and the new string (e.g., "-s,-rwx"). */
465 char const *arg
= argv
[optind
- 1];
466 size_t arg_len
= strlen (arg
);
467 size_t mode_comma_len
= mode_len
+ !!mode_len
;
468 size_t new_mode_len
= mode_comma_len
+ arg_len
;
469 if (mode_alloc
<= new_mode_len
)
471 mode_alloc
= new_mode_len
+ 1;
472 mode
= X2REALLOC (mode
, &mode_alloc
);
474 mode
[mode_len
] = ',';
475 memcpy (mode
+ mode_comma_len
, arg
, arg_len
+ 1);
476 mode_len
= new_mode_len
;
478 diagnose_surprises
= true;
481 case NO_PRESERVE_ROOT
:
482 preserve_root
= false;
485 preserve_root
= true;
487 case REFERENCE_FILE_OPTION
:
488 reference_file
= optarg
;
494 verbosity
= V_changes_only
;
502 case_GETOPT_HELP_CHAR
;
503 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
505 usage (EXIT_FAILURE
);
513 error (0, 0, _("cannot combine mode and --reference options"));
514 usage (EXIT_FAILURE
);
520 mode
= argv
[optind
++];
525 if (!mode
|| mode
!= argv
[optind
- 1])
526 error (0, 0, _("missing operand"));
528 error (0, 0, _("missing operand after %s"), quote (argv
[argc
- 1]));
529 usage (EXIT_FAILURE
);
534 change
= mode_create_from_ref (reference_file
);
536 error (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
537 quote (reference_file
));
541 change
= mode_compile (mode
);
544 error (0, 0, _("invalid mode: %s"), quote (mode
));
545 usage (EXIT_FAILURE
);
547 umask_value
= umask (0);
550 if (recurse
&& preserve_root
)
552 static struct dev_ino dev_ino_buf
;
553 root_dev_ino
= get_root_dev_ino (&dev_ino_buf
);
554 if (root_dev_ino
== NULL
)
555 error (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
563 ok
= process_files (argv
+ optind
,
564 FTS_COMFOLLOW
| FTS_PHYSICAL
| FTS_DEFER_STAT
);
566 exit (ok
? EXIT_SUCCESS
: EXIT_FAILURE
);