1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-2022 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 <https://www.gnu.org/licenses/>. */
17 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
22 #include <sys/types.h>
29 #include "ignore-value.h"
30 #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")
49 CH_NO_CHANGE_REQUESTED
,
59 /* Print a message for each file that is processed. */
62 /* Print a message for each file whose attributes we change. */
65 /* Do not be verbose. This is the default. */
69 /* The desired change to the mode. */
70 static struct mode_change
*change
;
72 /* The initial umask value, if it might be needed. */
73 static mode_t umask_value
;
75 /* If true, change the modes of directories recursively. */
78 /* If true, force silence (suppress most of error messages). */
79 static bool force_silent
;
81 /* If true, diagnose surprises from naive misuses like "chmod -r file".
82 POSIX allows diagnostics here, as portable code is supposed to use
83 "chmod -- -r file". */
84 static bool diagnose_surprises
;
86 /* Level of verbosity. */
87 static enum Verbosity verbosity
= V_off
;
89 /* Pointer to the device and inode numbers of '/', when --recursive.
91 static struct dev_ino
*root_dev_ino
;
93 /* For long options that have no equivalent short option, use a
94 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
97 NO_PRESERVE_ROOT
= CHAR_MAX
+ 1,
102 static struct option
const long_options
[] =
104 {"changes", no_argument
, NULL
, 'c'},
105 {"recursive", no_argument
, NULL
, 'R'},
106 {"no-preserve-root", no_argument
, NULL
, NO_PRESERVE_ROOT
},
107 {"preserve-root", no_argument
, NULL
, PRESERVE_ROOT
},
108 {"quiet", no_argument
, NULL
, 'f'},
109 {"reference", required_argument
, NULL
, REFERENCE_FILE_OPTION
},
110 {"silent", no_argument
, NULL
, 'f'},
111 {"verbose", no_argument
, NULL
, 'v'},
112 {GETOPT_HELP_OPTION_DECL
},
113 {GETOPT_VERSION_OPTION_DECL
},
117 /* Return true if the chmodable permission bits of FILE changed.
118 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
121 mode_changed (int dir_fd
, char const *file
, char const *file_full_name
,
122 mode_t old_mode
, mode_t new_mode
)
124 if (new_mode
& (S_ISUID
| S_ISGID
| S_ISVTX
))
126 /* The new mode contains unusual bits that the call to chmod may
127 have silently cleared. Check whether they actually changed. */
129 struct stat new_stats
;
131 if (fstatat (dir_fd
, file
, &new_stats
, 0) != 0)
134 error (0, errno
, _("getting new attributes of %s"),
135 quoteaf (file_full_name
));
139 new_mode
= new_stats
.st_mode
;
142 return ((old_mode
^ new_mode
) & CHMOD_MODE_BITS
) != 0;
145 /* Tell the user how/if the MODE of FILE has been changed.
146 CH describes what (if anything) has happened. */
149 describe_change (char const *file
, struct change_status
const *ch
)
151 char perms
[12]; /* "-rwxrwxrwx" ls-style modes. */
154 char const *quoted_file
= quoteaf (file
);
159 printf (_("neither symbolic link %s nor referent has been changed\n"),
164 printf (_("%s could not be accessed\n"), quoted_file
);
172 old_m
= ch
->old_mode
& CHMOD_MODE_BITS
,
173 m
= ch
->new_mode
& CHMOD_MODE_BITS
;
175 strmode (ch
->new_mode
, perms
);
176 perms
[10] = '\0'; /* Remove trailing space. */
178 strmode (ch
->old_mode
, old_perms
);
179 old_perms
[10] = '\0'; /* Remove trailing space. */
184 fmt
= _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
187 fmt
= _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
189 case CH_NO_CHANGE_REQUESTED
:
190 fmt
= _("mode of %s retained as %04lo (%s)\n");
191 printf (fmt
, quoted_file
, m
, &perms
[1]);
196 printf (fmt
, quoted_file
, old_m
, &old_perms
[1], m
, &perms
[1]);
199 /* Change the mode of FILE.
200 Return true if successful. This function is called
201 once for every file system object that fts encounters. */
204 process_file (FTS
*fts
, FTSENT
*ent
)
206 char const *file_full_name
= ent
->fts_path
;
207 char const *file
= ent
->fts_accpath
;
208 const struct stat
*file_stats
= ent
->fts_statp
;
209 struct change_status ch
= { 0, };
210 ch
.status
= CH_NO_STAT
;
212 switch (ent
->fts_info
)
218 /* For a top-level file or directory, this FTS_NS (stat failed)
219 indicator is determined at the time of the initial fts_open call.
220 With programs like chmod, chown, and chgrp, that modify
221 permissions, it is possible that the file in question is
222 accessible when control reaches this point. So, if this is
223 the first time we've seen the FTS_NS for this file, tell
224 fts_read to stat it "again". */
225 if (ent
->fts_level
== 0 && ent
->fts_number
== 0)
228 fts_set (fts
, ent
, FTS_AGAIN
);
232 error (0, ent
->fts_errno
, _("cannot access %s"),
233 quoteaf (file_full_name
));
238 error (0, ent
->fts_errno
, "%s", quotef (file_full_name
));
243 error (0, ent
->fts_errno
, _("cannot read directory %s"),
244 quoteaf (file_full_name
));
249 error (0, 0, _("cannot operate on dangling symlink %s"),
250 quoteaf (file_full_name
));
253 case FTS_DC
: /* directory that causes cycles */
254 if (cycle_warning_required (fts
, ent
))
256 emit_cycle_warning (file_full_name
);
261 ch
.status
= CH_NOT_APPLIED
;
265 if (ch
.status
== CH_NOT_APPLIED
266 && ROOT_DEV_INO_CHECK (root_dev_ino
, file_stats
))
268 ROOT_DEV_INO_WARN (file_full_name
);
269 /* Tell fts not to traverse into this hierarchy. */
270 fts_set (fts
, ent
, FTS_SKIP
);
271 /* Ensure that we do not process "/" on the second visit. */
272 ignore_value (fts_read (fts
));
276 if (ch
.status
== CH_NOT_APPLIED
&& ! S_ISLNK (file_stats
->st_mode
))
278 ch
.old_mode
= file_stats
->st_mode
;
279 ch
.new_mode
= mode_adjust (ch
.old_mode
, S_ISDIR (ch
.old_mode
) != 0,
280 umask_value
, change
, NULL
);
281 if (chmodat (fts
->fts_cwd_fd
, file
, ch
.new_mode
) == 0)
282 ch
.status
= CH_SUCCEEDED
;
286 error (0, errno
, _("changing permissions of %s"),
287 quoteaf (file_full_name
));
288 ch
.status
= CH_FAILED
;
292 if (verbosity
!= V_off
)
294 if (ch
.status
== CH_SUCCEEDED
295 && !mode_changed (fts
->fts_cwd_fd
, file
, file_full_name
,
296 ch
.old_mode
, ch
.new_mode
))
297 ch
.status
= CH_NO_CHANGE_REQUESTED
;
299 if (ch
.status
== CH_SUCCEEDED
|| verbosity
== V_high
)
300 describe_change (file_full_name
, &ch
);
303 if (CH_NO_CHANGE_REQUESTED
<= ch
.status
&& diagnose_surprises
)
305 mode_t naively_expected_mode
=
306 mode_adjust (ch
.old_mode
, S_ISDIR (ch
.old_mode
) != 0, 0, change
, NULL
);
307 if (ch
.new_mode
& ~naively_expected_mode
)
310 char naively_expected_perms
[12];
311 strmode (ch
.new_mode
, new_perms
);
312 strmode (naively_expected_mode
, naively_expected_perms
);
313 new_perms
[10] = naively_expected_perms
[10] = '\0';
315 _("%s: new permissions are %s, not %s"),
316 quotef (file_full_name
),
317 new_perms
+ 1, naively_expected_perms
+ 1);
318 ch
.status
= CH_FAILED
;
323 fts_set (fts
, ent
, FTS_SKIP
);
325 return CH_NOT_APPLIED
<= ch
.status
;
328 /* Recursively change the modes of the specified FILES (the last entry
329 of which is NULL). BIT_FLAGS controls how fts works.
330 Return true if successful. */
333 process_files (char **files
, int bit_flags
)
337 FTS
*fts
= xfts_open (files
, bit_flags
, NULL
);
343 ent
= fts_read (fts
);
348 /* FIXME: try to give a better message */
350 error (0, errno
, _("fts_read failed"));
356 ok
&= process_file (fts
, ent
);
359 if (fts_close (fts
) != 0)
361 error (0, errno
, _("fts_close failed"));
371 if (status
!= EXIT_SUCCESS
)
376 Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
377 or: %s [OPTION]... OCTAL-MODE FILE...\n\
378 or: %s [OPTION]... --reference=RFILE FILE...\n\
380 program_name
, program_name
, program_name
);
382 Change the mode of each FILE to MODE.\n\
383 With --reference, change the mode of each FILE to that of RFILE.\n\
387 -c, --changes like verbose but report only when a change is made\n\
388 -f, --silent, --quiet suppress most error messages\n\
389 -v, --verbose output a diagnostic for every file processed\n\
392 --no-preserve-root do not treat '/' specially (the default)\n\
393 --preserve-root fail to operate recursively on '/'\n\
396 --reference=RFILE use RFILE's mode instead of MODE values\n\
399 -R, --recursive change files and directories recursively\n\
401 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
402 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
405 Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\
407 emit_ancillary_info (PROGRAM_NAME
);
412 /* Parse the ASCII mode given on the command line into a linked list
413 of 'struct mode_change' and apply that to each file argument. */
416 main (int argc
, char **argv
)
420 size_t mode_alloc
= 0;
422 bool preserve_root
= false;
423 char const *reference_file
= NULL
;
426 initialize_main (&argc
, &argv
);
427 set_program_name (argv
[0]);
428 setlocale (LC_ALL
, "");
429 bindtextdomain (PACKAGE
, LOCALEDIR
);
430 textdomain (PACKAGE
);
432 atexit (close_stdout
);
434 recurse
= force_silent
= diagnose_surprises
= false;
436 while ((c
= getopt_long (argc
, argv
,
437 ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::"
438 "0::1::2::3::4::5::6::7::"),
457 case '0': case '1': case '2': case '3':
458 case '4': case '5': case '6': case '7':
459 /* Support nonportable uses like "chmod -w", but diagnose
460 surprises due to umask confusion. Even though "--", "--r",
461 etc., are valid modes, there is no "case '-'" here since
462 getopt_long reserves leading "--" for long options. */
464 /* Allocate a mode string (e.g., "-rwx") by concatenating
465 the argument containing this option. If a previous mode
466 string was given, concatenate the previous string, a
467 comma, and the new string (e.g., "-s,-rwx"). */
469 char const *arg
= argv
[optind
- 1];
470 size_t arg_len
= strlen (arg
);
471 size_t mode_comma_len
= mode_len
+ !!mode_len
;
472 size_t new_mode_len
= mode_comma_len
+ arg_len
;
473 if (mode_alloc
<= new_mode_len
)
475 mode_alloc
= new_mode_len
+ 1;
476 mode
= X2REALLOC (mode
, &mode_alloc
);
478 mode
[mode_len
] = ',';
479 memcpy (mode
+ mode_comma_len
, arg
, arg_len
+ 1);
480 mode_len
= new_mode_len
;
482 diagnose_surprises
= true;
485 case NO_PRESERVE_ROOT
:
486 preserve_root
= false;
489 preserve_root
= true;
491 case REFERENCE_FILE_OPTION
:
492 reference_file
= optarg
;
498 verbosity
= V_changes_only
;
506 case_GETOPT_HELP_CHAR
;
507 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
509 usage (EXIT_FAILURE
);
517 error (0, 0, _("cannot combine mode and --reference options"));
518 usage (EXIT_FAILURE
);
524 mode
= argv
[optind
++];
529 if (!mode
|| mode
!= argv
[optind
- 1])
530 error (0, 0, _("missing operand"));
532 error (0, 0, _("missing operand after %s"), quote (argv
[argc
- 1]));
533 usage (EXIT_FAILURE
);
538 change
= mode_create_from_ref (reference_file
);
540 die (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
541 quoteaf (reference_file
));
545 change
= mode_compile (mode
);
548 error (0, 0, _("invalid mode: %s"), quote (mode
));
549 usage (EXIT_FAILURE
);
551 umask_value
= umask (0);
554 if (recurse
&& preserve_root
)
556 static struct dev_ino dev_ino_buf
;
557 root_dev_ino
= get_root_dev_ino (&dev_ino_buf
);
558 if (root_dev_ino
== NULL
)
559 die (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
567 ok
= process_files (argv
+ optind
,
568 FTS_COMFOLLOW
| FTS_PHYSICAL
| FTS_DEFER_STAT
);
570 IF_LINT (free (change
));
572 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;