/* chmod -- change permission modes of files This is the chmod utility
Copyright (C) 1989-2018 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */ The GNUv3 license
/* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
#include <config.h> Provides system specific information
#include <stdio.h> Provides standard I/O capability
#include <getopt.h> ...!includes auto-comment...
#include <sys/types.h> Provides system data types
#include "system.h" ...!includes auto-comment...
#include "dev-ino.h" ...!includes auto-comment...
#include "die.h" ...!includes auto-comment...
#include "error.h" ...!includes auto-comment...
#include "filemode.h" ...!includes auto-comment...
#include "ignore-value.h" ...!includes auto-comment...
#include "modechange.h" ...!includes auto-comment...
#include "quote.h" ...!includes auto-comment...
#include "root-dev-ino.h" ...!includes auto-comment......!includes auto-comment...
#include "xfts.h" ...!includes auto-comment...
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "chmod" Line 36
#define AUTHORS \ Line 38
proper_name ("David MacKenzie"), \ Line 39
proper_name ("Jim Meyering") Line 40
enum Change_status Line 42
{
CH_NOT_APPLIED, Line 44
CH_SUCCEEDED, Line 45
CH_FAILED, Line 46
CH_NO_CHANGE_REQUESTED Line 47
}; Block 1
enum Verbosity Line 50
{
/* Print a message for each file that is processed. */
V_high, Line 53
/* Print a message for each file whose attributes we change. */
V_changes_only, Line 56
/* Do not be verbose. This is the default. */
V_off Line 59
};
/* The desired change to the mode. */
static struct mode_change *change; Line 63
/* The initial umask value, if it might be needed. */
static mode_t umask_value; Line 66
/* If true, change the modes of directories recursively. */
static bool recurse; Line 69
/* If true, force silence (suppress most of error messages). */
static bool force_silent; Line 72
/* If true, diagnose surprises from naive misuses like "chmod -r file".
POSIX allows diagnostics here, as portable code is supposed to use
"chmod -- -r file". */
static bool diagnose_surprises; Line 77
/* Level of verbosity. */
static enum Verbosity verbosity = V_off; Line 80
/* Pointer to the device and inode numbers of '/', when --recursive.
Otherwise NULL. */
static struct dev_ino *root_dev_ino; Line 84
/* For long options that have no equivalent short option, use a
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum Line 88
{
NO_PRESERVE_ROOT = CHAR_MAX + 1, Line 90
PRESERVE_ROOT, Line 91
REFERENCE_FILE_OPTION Line 92
}; Block 3
static struct option const long_options[] = Line 95
{
{"changes", no_argument, NULL, 'c'}, Line 97
{"recursive", no_argument, NULL, 'R'}, Line 98
{"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT}, Line 99
{"preserve-root", no_argument, NULL, PRESERVE_ROOT}, Line 100
{"quiet", no_argument, NULL, 'f'}, Line 101
{"reference", required_argument, NULL, REFERENCE_FILE_OPTION}, Line 102
{"silent", no_argument, NULL, 'f'}, Line 103
{"verbose", no_argument, NULL, 'v'}, Line 104
{GETOPT_HELP_OPTION_DECL}, Line 105
{GETOPT_VERSION_OPTION_DECL}, Line 106
{NULL, 0, NULL, 0} Line 107
}; Block 4
/* Return true if the chmodable permission bits of FILE changed.
The old mode was OLD_MODE, but it was changed to NEW_MODE. */
static bool Line 113
mode_changed (int dir_fd, char const *file, char const *file_full_name, Line 114
mode_t old_mode, mode_t new_mode) Line 115
{
if (new_mode & (S_ISUID | S_ISGID | S_ISVTX)) Line 117
{
/* The new mode contains unusual bits that the call to chmod may
have silently cleared. Check whether they actually changed. */
struct stat new_stats; Line 122
if (fstatat (dir_fd, file, &new_stats, 0) != 0) Line 124...!syscalls auto-comment...
{
if (! force_silent) Line 126
error (0, errno, _("getting new attributes of %s"), Line 127
quoteaf (file_full_name)); Line 128
return false; Line 129
}
new_mode = new_stats.st_mode; Line 132
}
return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0; Line 135
} Block 5
/* Tell the user how/if the MODE of FILE has been changed.
CHANGED describes what (if anything) has happened. */
static void Line 141
describe_change (const char *file, mode_t old_mode, mode_t mode, Line 142
enum Change_status changed) Line 143
{
char perms[12]; /* "-rwxrwxrwx" ls-style modes. */ Line 145
char old_perms[12]; Line 146
const char *fmt; Line 147
if (changed == CH_NOT_APPLIED) Line 149
{
printf (_("neither symbolic link %s nor referent has been changed\n"), Line 151
quoteaf (file)); Line 152
return; Line 153
}
strmode (mode, perms); Line 156
perms[10] = '\0'; /* Remove trailing space. */ Line 157
strmode (old_mode, old_perms); Line 159
old_perms[10] = '\0'; /* Remove trailing space. */ Line 160
switch (changed) Line 162
{
case CH_SUCCEEDED: Line 164
fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n"); Line 165
break; Line 166
case CH_FAILED: Line 167
fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n"); Line 168
break; Line 169
case CH_NO_CHANGE_REQUESTED: Line 170
fmt = _("mode of %s retained as %04lo (%s)\n"); Line 171
printf (fmt, quoteaf (file), Line 172
(unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]); Line 173
return; Line 174
default: Line 175
abort (); ...!common auto-comment...
}
printf (fmt, quoteaf (file), Line 178
(unsigned long int) (old_mode & CHMOD_MODE_BITS), &old_perms[1], Line 179
(unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]); Line 180
} Block 6
/* Change the mode of FILE.
Return true if successful. This function is called
once for every file system object that fts encounters. */
static bool Line 187
process_file (FTS *fts, FTSENT *ent) Line 188
{
char const *file_full_name = ent->fts_path; Line 190
char const *file = ent->fts_accpath; Line 191
const struct stat *file_stats = ent->fts_statp; Line 192
mode_t old_mode IF_LINT ( = 0); Line 193
mode_t new_mode IF_LINT ( = 0); Line 194
bool ok = true; Line 195
bool chmod_succeeded = false; Line 196
switch (ent->fts_info) Line 198
{
case FTS_DP: Line 200
return true; Line 201
case FTS_NS: Line 203
/* For a top-level file or directory, this FTS_NS (stat failed)
indicator is determined at the time of the initial fts_open call.
With programs like chmod, chown, and chgrp, that modify
permissions, it is possible that the file in question is
accessible when control reaches this point. So, if this is
the first time we've seen the FTS_NS for this file, tell
fts_read to stat it "again". */
if (ent->fts_level == 0 && ent->fts_number == 0) Line 211
{
ent->fts_number = 1; Line 213
fts_set (fts, ent, FTS_AGAIN); Line 214
return true; Line 215
}
if (! force_silent) Line 217
error (0, ent->fts_errno, _("cannot access %s"), Line 218
quoteaf (file_full_name)); Line 219
ok = false; Line 220
break; Line 221
case FTS_ERR: Line 223
if (! force_silent) Line 224
error (0, ent->fts_errno, "%s", quotef (file_full_name)); Line 225
ok = false; Line 226
break; Line 227
case FTS_DNR: Line 229
if (! force_silent) Line 230
error (0, ent->fts_errno, _("cannot read directory %s"), Line 231
quoteaf (file_full_name)); Line 232
ok = false; Line 233
break; Line 234
case FTS_SLNONE: Line 236
if (! force_silent) Line 237
error (0, 0, _("cannot operate on dangling symlink %s"), Line 238
quoteaf (file_full_name)); Line 239
ok = false; Line 240
break; Line 241
case FTS_DC: /* directory that causes cycles */ Line 243
if (cycle_warning_required (fts, ent)) Line 244
{
emit_cycle_warning (file_full_name); Line 246
return false; Line 247
}
break; Line 249
default: Line 251
break; Line 252
}
if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats)) Line 255
{
ROOT_DEV_INO_WARN (file_full_name); Line 257
/* Tell fts not to traverse into this hierarchy. */
fts_set (fts, ent, FTS_SKIP); Line 259
/* Ensure that we do not process "/" on the second visit. */
ignore_value (fts_read (fts)); Line 261...!syscalls auto-comment...
return false; Line 262
}
if (ok) Line 265
{
old_mode = file_stats->st_mode; Line 267
new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value, Line 268
change, NULL); Line 269
if (! S_ISLNK (old_mode)) Line 271
{
if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0) Line 273
chmod_succeeded = true; Line 274
else Line 275
{
if (! force_silent) Line 277
error (0, errno, _("changing permissions of %s"), Line 278
quoteaf (file_full_name)); Line 279
ok = false; Line 280
}
}
}
if (verbosity != V_off) Line 285
{
bool changed = (chmod_succeeded Line 287
&& mode_changed (fts->fts_cwd_fd, file, file_full_name, Line 288
old_mode, new_mode)); Line 289
if (changed || verbosity == V_high) Line 291
{
enum Change_status ch_status = Line 293
(!ok ? CH_FAILED Line 294
: !chmod_succeeded ? CH_NOT_APPLIED Line 295
: !changed ? CH_NO_CHANGE_REQUESTED Line 296
: CH_SUCCEEDED); Line 297
describe_change (file_full_name, old_mode, new_mode, ch_status); Line 298
}
}
if (chmod_succeeded && diagnose_surprises) Line 302
{
mode_t naively_expected_mode = Line 304
mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL); Line 305
if (new_mode & ~naively_expected_mode) Line 306
{
char new_perms[12]; Line 308
char naively_expected_perms[12]; Line 309
strmode (new_mode, new_perms); Line 310
strmode (naively_expected_mode, naively_expected_perms); Line 311
new_perms[10] = naively_expected_perms[10] = '\0'; Line 312
error (0, 0, Line 313
_("%s: new permissions are %s, not %s"), Line 314
quotef (file_full_name), Line 315
new_perms + 1, naively_expected_perms + 1); Line 316
ok = false; Line 317
}
}
if ( ! recurse) Line 321
fts_set (fts, ent, FTS_SKIP); Line 322
return ok; Line 324
} Block 7
/* Recursively change the modes of the specified FILES (the last entry
of which is NULL). BIT_FLAGS controls how fts works.
Return true if successful. */
static bool Line 331
process_files (char **files, int bit_flags) Line 332
{
bool ok = true; Line 334
FTS *fts = xfts_open (files, bit_flags, NULL); Line 336...!syscalls auto-comment...
while (1) Line 338
{
FTSENT *ent; Line 340
ent = fts_read (fts); Line 342...!syscalls auto-comment...
if (ent == NULL) Line 343
{
if (errno != 0) Line 345
{
/* FIXME: try to give a better message */
if (! force_silent) Line 348
error (0, errno, _("fts_read failed")); Line 349
ok = false; Line 350
}
break; Line 352
}
ok &= process_file (fts, ent); Line 355
}
if (fts_close (fts) != 0) Line 358...!syscalls auto-comment...
{
error (0, errno, _("fts_close failed")); Line 360
ok = false; Line 361
}
return ok; Line 364
} Block 8
void Line 367
usage (int status) Line 368
{
if (status != EXIT_SUCCESS) Line 370
emit_try_help (); ...!common auto-comment...
else Line 372
{
printf (_("\ Line 374
Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\ Line 375
or: %s [OPTION]... OCTAL-MODE FILE...\n\ Line 376
or: %s [OPTION]... --reference=RFILE FILE...\n\ Line 377
"), Line 378
program_name, program_name, program_name); Line 379
fputs (_("\ Line 380
Change the mode of each FILE to MODE.\n\ Line 381
With --reference, change the mode of each FILE to that of RFILE.\n\ Line 382
\n\
"), stdout); Line 384
fputs (_("\ Line 385
-c, --changes like verbose but report only when a change is made\n\ Line 386
-f, --silent, --quiet suppress most error messages\n\ Line 387
-v, --verbose output a diagnostic for every file processed\n\ Line 388
"), stdout); Line 389
fputs (_("\ Line 390
--no-preserve-root do not treat '/' specially (the default)\n\ Line 391
--preserve-root fail to operate recursively on '/'\n\ Line 392
"), stdout); Line 393
fputs (_("\ Line 394
--reference=RFILE use RFILE's mode instead of MODE values\n\ Line 395
"), stdout); Line 396
fputs (_("\ Line 397
-R, --recursive change files and directories recursively\n\ Line 398
"), stdout); Line 399
fputs (HELP_OPTION_DESCRIPTION, stdout); Line 400
fputs (VERSION_OPTION_DESCRIPTION, stdout); Line 401
fputs (_("\ Line 402
\n\
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\ Line 404
"), stdout); Line 405
emit_ancillary_info (PROGRAM_NAME); Line 406
}
exit (status); Line 408
} Block 9
/* Parse the ASCII mode given on the command line into a linked list
of 'struct mode_change' and apply that to each file argument. */
int
main (int argc, char **argv) Line 415
{
char *mode = NULL; Line 417
size_t mode_len = 0; Line 418
size_t mode_alloc = 0; Line 419
bool ok; Line 420
bool preserve_root = false; Line 421
char const *reference_file = NULL; Line 422
int c; Line 423
initialize_main (&argc, &argv); VMS-specific entry point handling wildcard expansion
set_program_name (argv[0]); Retains program name and discards path
setlocale (LC_ALL, ""); Sets up internationalization (i18n)
bindtextdomain (PACKAGE, LOCALEDIR); Assigns i18n directorySets text domain for _() [gettext()] function
textdomain (PACKAGE); Sets text domain for _() [gettext()] function
atexit (close_stdout); Close stdout on exit (see gnulib)
recurse = force_silent = diagnose_surprises = false; Line 433
while ((c = getopt_long (argc, argv, Line 435
("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::" Line 436
"0::1::2::3::4::5::6::7::"), Line 437
long_options, NULL)) Line 438
!= -1) Line 439
{
switch (c) Line 441
{
case 'r': Line 443
case 'w': Line 444
case 'x': Line 445
case 'X': Line 446
case 's': Line 447
case 't': Line 448
case 'u': Line 449
case 'g': Line 450
case 'o': Line 451
case 'a': Line 452
case ',': Line 453
case '+': Line 454
case '=': Line 455
case '0': case '1': case '2': case '3': Line 456
case '4': case '5': case '6': case '7': Line 457
/* Support nonportable uses like "chmod -w", but diagnose
surprises due to umask confusion. Even though "--", "--r",
etc., are valid modes, there is no "case '-'" here since
getopt_long reserves leading "--" for long options. */
{
/* Allocate a mode string (e.g., "-rwx") by concatenating
the argument containing this option. If a previous mode
string was given, concatenate the previous string, a
comma, and the new string (e.g., "-s,-rwx"). */
char const *arg = argv[optind - 1]; Line 468
size_t arg_len = strlen (arg); Line 469
size_t mode_comma_len = mode_len + !!mode_len; Line 470
size_t new_mode_len = mode_comma_len + arg_len; Line 471
if (mode_alloc <= new_mode_len) Line 472
{
mode_alloc = new_mode_len + 1; Line 474
mode = X2REALLOC (mode, &mode_alloc); Line 475
}
mode[mode_len] = ','; Line 477
memcpy (mode + mode_comma_len, arg, arg_len + 1); Line 478
mode_len = new_mode_len; Line 479
diagnose_surprises = true; Line 481
}
break; Line 483
case NO_PRESERVE_ROOT: Line 484
preserve_root = false; Line 485
break; Line 486
case PRESERVE_ROOT: Line 487
preserve_root = true; Line 488
break; Line 489
case REFERENCE_FILE_OPTION: Line 490
reference_file = optarg; Line 491
break; Line 492
case 'R': Line 493
recurse = true; Line 494
break; Line 495
case 'c': Line 496
verbosity = V_changes_only; Line 497
break; Line 498
case 'f': Line 499
force_silent = true; Line 500
break; Line 501
case 'v': Line 502
verbosity = V_high; Line 503
break; Line 504
case_GETOPT_HELP_CHAR; Line 505
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); Line 506
default: Line 507
usage (EXIT_FAILURE); Line 508
}
}
if (reference_file) Line 512
{
if (mode) Line 514
{
error (0, 0, _("cannot combine mode and --reference options")); Line 516
usage (EXIT_FAILURE); Line 517
}
}
else Line 520
{
if (!mode) Line 522
mode = argv[optind++]; Line 523
}
if (optind >= argc) Line 526
{
if (!mode || mode != argv[optind - 1]) Line 528
error (0, 0, _("missing operand")); Line 529
else Line 530
error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); Line 531
usage (EXIT_FAILURE); Line 532
}
if (reference_file) Line 535
{
change = mode_create_from_ref (reference_file); Line 537
if (!change) Line 538
die (EXIT_FAILURE, errno, _("failed to get attributes of %s"), Line 539
quoteaf (reference_file)); Line 540
}
else Line 542
{
change = mode_compile (mode); Line 544
if (!change) Line 545
{
error (0, 0, _("invalid mode: %s"), quote (mode)); Line 547
usage (EXIT_FAILURE); Line 548
}
umask_value = umask (0); Line 550
}
if (recurse && preserve_root) Line 553
{
static struct dev_ino dev_ino_buf; Line 555
root_dev_ino = get_root_dev_ino (&dev_ino_buf); Line 556
if (root_dev_ino == NULL) Line 557
die (EXIT_FAILURE, errno, _("failed to get attributes of %s"), Line 558
quoteaf ("/")); Line 559
}
else Line 561
{
root_dev_ino = NULL; Line 563
}
ok = process_files (argv + optind, Line 566
FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT); Line 567
IF_LINT (free (change)); Line 569
return ok ? EXIT_SUCCESS : EXIT_FAILURE; Line 571
} Block 10