1 /* chmod -- change permission modes of files
2 Copyright (C) 1989-2024 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>
28 #include "ignore-value.h"
29 #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")
48 CH_NO_CHANGE_REQUESTED
,
58 /* Print a message for each file that is processed. */
61 /* Print a message for each file whose attributes we change. */
64 /* Do not be verbose. This is the default. */
68 /* The desired change to the mode. */
69 static struct mode_change
*change
;
71 /* The initial umask value, if it might be needed. */
72 static mode_t umask_value
;
74 /* If true, change the modes of directories recursively. */
77 /* If true, force silence (suppress most of error messages). */
78 static bool force_silent
;
80 /* If true, diagnose surprises from naive misuses like "chmod -r file".
81 POSIX allows diagnostics here, as portable code is supposed to use
82 "chmod -- -r file". */
83 static bool diagnose_surprises
;
85 /* Level of verbosity. */
86 static enum Verbosity verbosity
= V_off
;
88 /* Pointer to the device and inode numbers of '/', when --recursive.
90 static struct dev_ino
*root_dev_ino
;
92 /* For long options that have no equivalent short option, use a
93 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
96 NO_PRESERVE_ROOT
= CHAR_MAX
+ 1,
101 static struct option
const long_options
[] =
103 {"changes", no_argument
, nullptr, 'c'},
104 {"recursive", no_argument
, nullptr, 'R'},
105 {"no-preserve-root", no_argument
, nullptr, NO_PRESERVE_ROOT
},
106 {"preserve-root", no_argument
, nullptr, PRESERVE_ROOT
},
107 {"quiet", no_argument
, nullptr, 'f'},
108 {"reference", required_argument
, nullptr, REFERENCE_FILE_OPTION
},
109 {"silent", no_argument
, nullptr, 'f'},
110 {"verbose", no_argument
, nullptr, 'v'},
111 {GETOPT_HELP_OPTION_DECL
},
112 {GETOPT_VERSION_OPTION_DECL
},
113 {nullptr, 0, nullptr, 0}
116 /* Return true if the chmodable permission bits of FILE changed.
117 The old mode was OLD_MODE, but it was changed to NEW_MODE. */
120 mode_changed (int dir_fd
, char const *file
, char const *file_full_name
,
121 mode_t old_mode
, mode_t new_mode
)
123 if (new_mode
& (S_ISUID
| S_ISGID
| S_ISVTX
))
125 /* The new mode contains unusual bits that the call to chmod may
126 have silently cleared. Check whether they actually changed. */
128 struct stat new_stats
;
130 if (fstatat (dir_fd
, file
, &new_stats
, 0) != 0)
133 error (0, errno
, _("getting new attributes of %s"),
134 quoteaf (file_full_name
));
138 new_mode
= new_stats
.st_mode
;
141 return ((old_mode
^ new_mode
) & CHMOD_MODE_BITS
) != 0;
144 /* Tell the user how/if the MODE of FILE has been changed.
145 CH describes what (if anything) has happened. */
148 describe_change (char const *file
, struct change_status
const *ch
)
150 char perms
[12]; /* "-rwxrwxrwx" ls-style modes. */
153 char const *quoted_file
= quoteaf (file
);
158 printf (_("neither symbolic link %s nor referent has been changed\n"),
163 printf (_("%s could not be accessed\n"), quoted_file
);
171 old_m
= ch
->old_mode
& CHMOD_MODE_BITS
,
172 m
= ch
->new_mode
& CHMOD_MODE_BITS
;
174 strmode (ch
->new_mode
, perms
);
175 perms
[10] = '\0'; /* Remove trailing space. */
177 strmode (ch
->old_mode
, old_perms
);
178 old_perms
[10] = '\0'; /* Remove trailing space. */
183 fmt
= _("mode of %s changed from %04lo (%s) to %04lo (%s)\n");
186 fmt
= _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n");
188 case CH_NO_CHANGE_REQUESTED
:
189 fmt
= _("mode of %s retained as %04lo (%s)\n");
190 printf (fmt
, quoted_file
, m
, &perms
[1]);
195 printf (fmt
, quoted_file
, old_m
, &old_perms
[1], m
, &perms
[1]);
198 /* Change the mode of FILE.
199 Return true if successful. This function is called
200 once for every file system object that fts encounters. */
203 process_file (FTS
*fts
, FTSENT
*ent
)
205 char const *file_full_name
= ent
->fts_path
;
206 char const *file
= ent
->fts_accpath
;
207 const struct stat
*file_stats
= ent
->fts_statp
;
208 struct change_status ch
= {0};
209 ch
.status
= CH_NO_STAT
;
211 switch (ent
->fts_info
)
217 /* For a top-level file or directory, this FTS_NS (stat failed)
218 indicator is determined at the time of the initial fts_open call.
219 With programs like chmod, chown, and chgrp, that modify
220 permissions, it is possible that the file in question is
221 accessible when control reaches this point. So, if this is
222 the first time we've seen the FTS_NS for this file, tell
223 fts_read to stat it "again". */
224 if (ent
->fts_level
== 0 && ent
->fts_number
== 0)
227 fts_set (fts
, ent
, FTS_AGAIN
);
231 error (0, ent
->fts_errno
, _("cannot access %s"),
232 quoteaf (file_full_name
));
237 error (0, ent
->fts_errno
, "%s", quotef (file_full_name
));
242 error (0, ent
->fts_errno
, _("cannot read directory %s"),
243 quoteaf (file_full_name
));
248 error (0, 0, _("cannot operate on dangling symlink %s"),
249 quoteaf (file_full_name
));
252 case FTS_DC
: /* directory that causes cycles */
253 if (cycle_warning_required (fts
, ent
))
255 emit_cycle_warning (file_full_name
);
260 ch
.status
= CH_NOT_APPLIED
;
264 if (ch
.status
== CH_NOT_APPLIED
265 && ROOT_DEV_INO_CHECK (root_dev_ino
, file_stats
))
267 ROOT_DEV_INO_WARN (file_full_name
);
268 /* Tell fts not to traverse into this hierarchy. */
269 fts_set (fts
, ent
, FTS_SKIP
);
270 /* Ensure that we do not process "/" on the second visit. */
271 ignore_value (fts_read (fts
));
275 if (ch
.status
== CH_NOT_APPLIED
&& ! S_ISLNK (file_stats
->st_mode
))
277 ch
.old_mode
= file_stats
->st_mode
;
278 ch
.new_mode
= mode_adjust (ch
.old_mode
, S_ISDIR (ch
.old_mode
) != 0,
279 umask_value
, change
, nullptr);
280 if (chmodat (fts
->fts_cwd_fd
, file
, ch
.new_mode
) == 0)
281 ch
.status
= CH_SUCCEEDED
;
285 error (0, errno
, _("changing permissions of %s"),
286 quoteaf (file_full_name
));
287 ch
.status
= CH_FAILED
;
291 if (verbosity
!= V_off
)
293 if (ch
.status
== CH_SUCCEEDED
294 && !mode_changed (fts
->fts_cwd_fd
, file
, file_full_name
,
295 ch
.old_mode
, ch
.new_mode
))
296 ch
.status
= CH_NO_CHANGE_REQUESTED
;
298 if (ch
.status
== CH_SUCCEEDED
|| verbosity
== V_high
)
299 describe_change (file_full_name
, &ch
);
302 if (CH_NO_CHANGE_REQUESTED
<= ch
.status
&& diagnose_surprises
)
304 mode_t naively_expected_mode
=
305 mode_adjust (ch
.old_mode
, S_ISDIR (ch
.old_mode
) != 0,
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
, nullptr);
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 specifying MODE values.\n\
397 RFILE is always dereferenced if a symbolic link.\n\
400 -R, --recursive change files and directories recursively\n\
402 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
403 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
406 Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\
408 emit_ancillary_info (PROGRAM_NAME
);
413 /* Parse the ASCII mode given on the command line into a linked list
414 of 'struct mode_change' and apply that to each file argument. */
417 main (int argc
, char **argv
)
419 char *mode
= nullptr;
421 idx_t mode_alloc
= 0;
423 bool preserve_root
= false;
424 char const *reference_file
= nullptr;
427 initialize_main (&argc
, &argv
);
428 set_program_name (argv
[0]);
429 setlocale (LC_ALL
, "");
430 bindtextdomain (PACKAGE
, LOCALEDIR
);
431 textdomain (PACKAGE
);
433 atexit (close_stdout
);
435 recurse
= force_silent
= diagnose_surprises
= false;
437 while ((c
= getopt_long (argc
, argv
,
438 ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::"
439 "0::1::2::3::4::5::6::7::"),
440 long_options
, nullptr))
458 case '0': case '1': case '2': case '3':
459 case '4': case '5': case '6': case '7':
460 /* Support non-portable uses like "chmod -w", but diagnose
461 surprises due to umask confusion. Even though "--", "--r",
462 etc., are valid modes, there is no "case '-'" here since
463 getopt_long reserves leading "--" for long options. */
465 /* Allocate a mode string (e.g., "-rwx") by concatenating
466 the argument containing this option. If a previous mode
467 string was given, concatenate the previous string, a
468 comma, and the new string (e.g., "-s,-rwx"). */
470 char const *arg
= argv
[optind
- 1];
471 idx_t arg_len
= strlen (arg
);
472 idx_t mode_comma_len
= mode_len
+ !!mode_len
;
473 idx_t new_mode_len
= mode_comma_len
+ arg_len
;
474 assume (0 <= new_mode_len
); /* Pacify GCC bug #109613. */
475 if (mode_alloc
<= new_mode_len
)
476 mode
= xpalloc (mode
, &mode_alloc
,
477 new_mode_len
+ 1 - mode_alloc
, -1, 1);
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 error (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
== nullptr)
559 error (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
564 root_dev_ino
= nullptr;
567 ok
= process_files (argv
+ optind
,
568 FTS_COMFOLLOW
| FTS_PHYSICAL
| FTS_DEFER_STAT
);
570 main_exit (ok
? EXIT_SUCCESS
: EXIT_FAILURE
);