| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| #include <config.h> |
| #include <stdio.h> |
| #include <getopt.h> |
| #include <sys/types.h> |
|
|
| #include "system.h" |
| #include "assure.h" |
| #include "dev-ino.h" |
| #include "filemode.h" |
| #include "ignore-value.h" |
| #include "modechange.h" |
| #include "quote.h" |
| #include "root-dev-ino.h" |
| #include "xfts.h" |
|
|
| |
| #define PROGRAM_NAME "chmod" |
|
|
| #define AUTHORS \ |
| proper_name ("David MacKenzie"), \ |
| proper_name ("Jim Meyering") |
|
|
| struct change_status |
| { |
| enum |
| { |
| CH_NO_STAT, |
| CH_FAILED, |
| CH_NOT_APPLIED, |
| CH_NO_CHANGE_REQUESTED, |
| CH_SUCCEEDED |
| } |
| status; |
| mode_t old_mode; |
| mode_t new_mode; |
| }; |
|
|
| enum Verbosity |
| { |
| |
| V_high, |
|
|
| |
| V_changes_only, |
|
|
| |
| V_off |
| }; |
|
|
| |
| static struct mode_change *change; |
|
|
| |
| static mode_t umask_value; |
|
|
| |
| static bool recurse; |
|
|
| |
| |
| static int dereference = -1; |
|
|
| |
| static bool force_silent; |
|
|
| |
| |
| |
| static bool diagnose_surprises; |
|
|
| |
| static enum Verbosity verbosity = V_off; |
|
|
| |
| |
| static struct dev_ino *root_dev_ino; |
|
|
| |
| |
| enum |
| { |
| DEREFERENCE_OPTION = CHAR_MAX + 1, |
| NO_PRESERVE_ROOT, |
| PRESERVE_ROOT, |
| REFERENCE_FILE_OPTION |
| }; |
|
|
| static struct option const long_options[] = |
| { |
| {"changes", no_argument, nullptr, 'c'}, |
| {"dereference", no_argument, nullptr, DEREFERENCE_OPTION}, |
| {"recursive", no_argument, nullptr, 'R'}, |
| {"no-dereference", no_argument, nullptr, 'h'}, |
| {"no-preserve-root", no_argument, nullptr, NO_PRESERVE_ROOT}, |
| {"preserve-root", no_argument, nullptr, PRESERVE_ROOT}, |
| {"quiet", no_argument, nullptr, 'f'}, |
| {"reference", required_argument, nullptr, REFERENCE_FILE_OPTION}, |
| {"silent", no_argument, nullptr, 'f'}, |
| {"verbose", no_argument, nullptr, 'v'}, |
| {GETOPT_HELP_OPTION_DECL}, |
| {GETOPT_VERSION_OPTION_DECL}, |
| {nullptr, 0, nullptr, 0} |
| }; |
|
|
| |
| |
|
|
| static bool |
| mode_changed (int dir_fd, char const *file, char const *file_full_name, |
| mode_t old_mode, mode_t new_mode) |
| { |
| if (new_mode & (S_ISUID | S_ISGID | S_ISVTX)) |
| { |
| |
| |
|
|
| struct stat new_stats; |
|
|
| if (fstatat (dir_fd, file, &new_stats, 0) != 0) |
| { |
| if (! force_silent) |
| error (0, errno, _("getting new attributes of %s"), |
| quoteaf (file_full_name)); |
| return false; |
| } |
|
|
| new_mode = new_stats.st_mode; |
| } |
|
|
| return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0; |
| } |
|
|
| |
| |
|
|
| static void |
| describe_change (char const *file, struct change_status const *ch) |
| { |
| char perms[12]; |
| char old_perms[12]; |
| char const *fmt; |
| char const *quoted_file = quoteaf (file); |
|
|
| switch (ch->status) |
| { |
| case CH_NOT_APPLIED: |
| printf (_("neither symbolic link %s nor referent has been changed\n"), |
| quoted_file); |
| return; |
|
|
| case CH_NO_STAT: |
| printf (_("%s could not be accessed\n"), quoted_file); |
| return; |
|
|
| case CH_FAILED: case CH_NO_CHANGE_REQUESTED: case CH_SUCCEEDED: |
| break; |
| } |
|
|
| unsigned long int |
| old_m = ch->old_mode & CHMOD_MODE_BITS, |
| m = ch->new_mode & CHMOD_MODE_BITS; |
|
|
| strmode (ch->new_mode, perms); |
| perms[10] = '\0'; |
|
|
| strmode (ch->old_mode, old_perms); |
| old_perms[10] = '\0'; |
|
|
| switch (ch->status) |
| { |
| case CH_SUCCEEDED: |
| fmt = _("mode of %s changed from %04lo (%s) to %04lo (%s)\n"); |
| break; |
| case CH_FAILED: |
| fmt = _("failed to change mode of %s from %04lo (%s) to %04lo (%s)\n"); |
| break; |
| case CH_NO_CHANGE_REQUESTED: |
| fmt = _("mode of %s retained as %04lo (%s)\n"); |
| printf (fmt, quoted_file, m, &perms[1]); |
| return; |
| case CH_NO_STAT: case CH_NOT_APPLIED: default: |
| affirm (false); |
| } |
| printf (fmt, quoted_file, old_m, &old_perms[1], m, &perms[1]); |
| } |
|
|
| |
| |
| |
|
|
| static bool |
| process_file (FTS *fts, FTSENT *ent) |
| { |
| char const *file_full_name = ent->fts_path; |
| char const *file = ent->fts_accpath; |
| const struct stat *file_stats = ent->fts_statp; |
| struct change_status ch = {0}; |
| ch.status = CH_NO_STAT; |
| struct stat stat_buf; |
|
|
| switch (ent->fts_info) |
| { |
| case FTS_DP: |
| return true; |
|
|
| case FTS_NS: |
| |
| |
| |
| |
| |
| |
| |
| if (ent->fts_level == 0 && ent->fts_number == 0) |
| { |
| ent->fts_number = 1; |
| fts_set (fts, ent, FTS_AGAIN); |
| return true; |
| } |
| if (! force_silent) |
| error (0, ent->fts_errno, _("cannot access %s"), |
| quoteaf (file_full_name)); |
| break; |
|
|
| case FTS_ERR: |
| if (! force_silent) |
| error (0, ent->fts_errno, "%s", quotef (file_full_name)); |
| break; |
|
|
| case FTS_DNR: |
| if (! force_silent) |
| error (0, ent->fts_errno, _("cannot read directory %s"), |
| quoteaf (file_full_name)); |
| break; |
|
|
| case FTS_SLNONE: |
| if (dereference) |
| { |
| if (! force_silent) |
| error (0, 0, _("cannot operate on dangling symlink %s"), |
| quoteaf (file_full_name)); |
| break; |
| } |
| ch.status = CH_NOT_APPLIED; |
| break; |
|
|
| case FTS_SL: |
| if (dereference == 1) |
| { |
| if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0) |
| { |
| if (! force_silent) |
| error (0, errno, _("cannot dereference %s"), |
| quoteaf (file_full_name)); |
| break; |
| } |
|
|
| file_stats = &stat_buf; |
| } |
| ch.status = CH_NOT_APPLIED; |
| break; |
|
|
| case FTS_DC: |
| if (cycle_warning_required (fts, ent)) |
| { |
| emit_cycle_warning (file_full_name); |
| return false; |
| } |
| FALLTHROUGH; |
| default: |
| ch.status = CH_NOT_APPLIED; |
| break; |
| } |
|
|
| if (ch.status == CH_NOT_APPLIED |
| && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats)) |
| { |
| ROOT_DEV_INO_WARN (file_full_name); |
| |
| fts_set (fts, ent, FTS_SKIP); |
| |
| ignore_value (fts_read (fts)); |
| return false; |
| } |
|
|
| if (ch.status == CH_NOT_APPLIED) |
| { |
| ch.old_mode = file_stats->st_mode; |
| ch.new_mode = mode_adjust (ch.old_mode, S_ISDIR (ch.old_mode) != 0, |
| umask_value, change, nullptr); |
| bool follow_symlink = !!dereference; |
| if (dereference == -1) |
| follow_symlink = ent->fts_level == 0; |
| if (fchmodat (fts->fts_cwd_fd, file, ch.new_mode, |
| follow_symlink ? 0 : AT_SYMLINK_NOFOLLOW) == 0) |
| ch.status = CH_SUCCEEDED; |
| else |
| { |
| if (! is_ENOTSUP (errno)) |
| { |
| if (! force_silent) |
| error (0, errno, _("changing permissions of %s"), |
| quoteaf (file_full_name)); |
|
|
| ch.status = CH_FAILED; |
| } |
| |
| } |
| } |
|
|
| if (verbosity != V_off) |
| { |
| if (ch.status == CH_SUCCEEDED |
| && !mode_changed (fts->fts_cwd_fd, file, file_full_name, |
| ch.old_mode, ch.new_mode)) |
| ch.status = CH_NO_CHANGE_REQUESTED; |
|
|
| if (ch.status == CH_SUCCEEDED || verbosity == V_high) |
| describe_change (file_full_name, &ch); |
| } |
|
|
| if (CH_NO_CHANGE_REQUESTED <= ch.status && diagnose_surprises) |
| { |
| mode_t naively_expected_mode = |
| mode_adjust (ch.old_mode, S_ISDIR (ch.old_mode) != 0, |
| 0, change, nullptr); |
| if (ch.new_mode & ~naively_expected_mode) |
| { |
| char new_perms[12]; |
| char naively_expected_perms[12]; |
| strmode (ch.new_mode, new_perms); |
| strmode (naively_expected_mode, naively_expected_perms); |
| new_perms[10] = naively_expected_perms[10] = '\0'; |
| error (0, 0, |
| _("%s: new permissions are %s, not %s"), |
| quotef (file_full_name), |
| new_perms + 1, naively_expected_perms + 1); |
| ch.status = CH_FAILED; |
| } |
| } |
|
|
| if ( ! recurse) |
| fts_set (fts, ent, FTS_SKIP); |
|
|
| return CH_NOT_APPLIED <= ch.status; |
| } |
|
|
| |
| |
| |
|
|
| static bool |
| process_files (char **files, int bit_flags) |
| { |
| bool ok = true; |
|
|
| FTS *fts = xfts_open (files, bit_flags, nullptr); |
|
|
| while (true) |
| { |
| FTSENT *ent; |
|
|
| ent = fts_read (fts); |
| if (ent == nullptr) |
| { |
| if (errno != 0) |
| { |
| |
| if (! force_silent) |
| error (0, errno, _("fts_read failed")); |
| ok = false; |
| } |
| break; |
| } |
|
|
| ok &= process_file (fts, ent); |
| } |
|
|
| if (fts_close (fts) != 0) |
| { |
| error (0, errno, _("fts_close failed")); |
| ok = false; |
| } |
|
|
| return ok; |
| } |
|
|
| void |
| usage (int status) |
| { |
| if (status != EXIT_SUCCESS) |
| emit_try_help (); |
| else |
| { |
| printf (_("\ |
| Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\ |
| or: %s [OPTION]... OCTAL-MODE FILE...\n\ |
| or: %s [OPTION]... --reference=RFILE FILE...\n\ |
| "), |
| program_name, program_name, program_name); |
| fputs (_("\ |
| Change the mode of each FILE to MODE.\n\ |
| With --reference, change the mode of each FILE to that of RFILE.\n\ |
| \n\ |
| "), stdout); |
| fputs (_("\ |
| -c, --changes like verbose but report only when a change is made\n\ |
| -f, --silent, --quiet suppress most error messages\n\ |
| -v, --verbose output a diagnostic for every file processed\n\ |
| "), stdout); |
| fputs (_("\ |
| --dereference affect the referent of each symbolic link,\n\ |
| rather than the symbolic link itself\n\ |
| -h, --no-dereference affect each symbolic link, rather than the referent\n\ |
| "), stdout); |
| fputs (_("\ |
| --no-preserve-root do not treat '/' specially (the default)\n\ |
| --preserve-root fail to operate recursively on '/'\n\ |
| "), stdout); |
| fputs (_("\ |
| --reference=RFILE use RFILE's mode instead of specifying MODE values.\n\ |
| RFILE is always dereferenced if a symbolic link.\n\ |
| "), stdout); |
| fputs (_("\ |
| -R, --recursive change files and directories recursively\n\ |
| "), stdout); |
| emit_symlink_recurse_options ("-H"); |
| fputs (HELP_OPTION_DESCRIPTION, stdout); |
| fputs (VERSION_OPTION_DESCRIPTION, stdout); |
| fputs (_("\ |
| \n\ |
| Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.\n\ |
| "), stdout); |
| emit_ancillary_info (PROGRAM_NAME); |
| } |
| exit (status); |
| } |
|
|
| |
| |
|
|
| int |
| main (int argc, char **argv) |
| { |
| char *mode = nullptr; |
| idx_t mode_len = 0; |
| idx_t mode_alloc = 0; |
| bool ok; |
| bool preserve_root = false; |
| char const *reference_file = nullptr; |
| int c; |
| int bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL; |
|
|
| initialize_main (&argc, &argv); |
| set_program_name (argv[0]); |
| setlocale (LC_ALL, ""); |
| bindtextdomain (PACKAGE, LOCALEDIR); |
| textdomain (PACKAGE); |
|
|
| atexit (close_stdout); |
|
|
| recurse = force_silent = diagnose_surprises = false; |
|
|
| while ((c = getopt_long (argc, argv, |
| ("HLPRcfhvr::w::x::X::s::t::u::g::o::a::,::+::=::" |
| "0::1::2::3::4::5::6::7::"), |
| long_options, nullptr)) |
| != -1) |
| { |
| switch (c) |
| { |
|
|
| case 'H': |
| bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL; |
| break; |
|
|
| case 'L': |
| bit_flags = FTS_LOGICAL; |
| break; |
|
|
| case 'P': |
| bit_flags = FTS_PHYSICAL; |
| break; |
|
|
| case 'h': |
| dereference = 0; |
| break; |
|
|
| case DEREFERENCE_OPTION: |
| |
| dereference = 1; |
| break; |
|
|
| case 'r': |
| case 'w': |
| case 'x': |
| case 'X': |
| case 's': |
| case 't': |
| case 'u': |
| case 'g': |
| case 'o': |
| case 'a': |
| case ',': |
| case '+': |
| case '=': |
| case '0': case '1': case '2': case '3': |
| case '4': case '5': case '6': case '7': |
| |
| |
| |
| |
| { |
| |
| |
| |
| |
|
|
| char const *arg = argv[optind - 1]; |
| idx_t arg_len = strlen (arg); |
| idx_t mode_comma_len = mode_len + !!mode_len; |
| idx_t new_mode_len = mode_comma_len + arg_len; |
| assume (0 <= new_mode_len); |
| if (mode_alloc <= new_mode_len) |
| mode = xpalloc (mode, &mode_alloc, |
| new_mode_len + 1 - mode_alloc, -1, 1); |
| mode[mode_len] = ','; |
| memcpy (mode + mode_comma_len, arg, arg_len + 1); |
| mode_len = new_mode_len; |
|
|
| diagnose_surprises = true; |
| } |
| break; |
| case NO_PRESERVE_ROOT: |
| preserve_root = false; |
| break; |
| case PRESERVE_ROOT: |
| preserve_root = true; |
| break; |
| case REFERENCE_FILE_OPTION: |
| reference_file = optarg; |
| break; |
| case 'R': |
| recurse = true; |
| break; |
| case 'c': |
| verbosity = V_changes_only; |
| break; |
| case 'f': |
| force_silent = true; |
| break; |
| case 'v': |
| verbosity = V_high; |
| break; |
| case_GETOPT_HELP_CHAR; |
| case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); |
| default: |
| usage (EXIT_FAILURE); |
| } |
| } |
|
|
| if (recurse) |
| { |
| if (bit_flags == FTS_PHYSICAL) |
| { |
| if (dereference == 1) |
| error (EXIT_FAILURE, 0, |
| _("-R --dereference requires either -H or -L")); |
| dereference = 0; |
| } |
| } |
|
|
| if (dereference == -1 && bit_flags == FTS_LOGICAL) |
| dereference = 1; |
|
|
| if (reference_file) |
| { |
| if (mode) |
| { |
| error (0, 0, _("cannot combine mode and --reference options")); |
| usage (EXIT_FAILURE); |
| } |
| } |
| else |
| { |
| if (!mode) |
| mode = argv[optind++]; |
| } |
|
|
| if (optind >= argc) |
| { |
| if (!mode || mode != argv[optind - 1]) |
| error (0, 0, _("missing operand")); |
| else |
| error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); |
| usage (EXIT_FAILURE); |
| } |
|
|
| if (reference_file) |
| { |
| change = mode_create_from_ref (reference_file); |
| if (!change) |
| error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), |
| quoteaf (reference_file)); |
| } |
| else |
| { |
| change = mode_compile (mode); |
| if (!change) |
| { |
| error (0, 0, _("invalid mode: %s"), quote (mode)); |
| usage (EXIT_FAILURE); |
| } |
| umask_value = umask (0); |
| } |
|
|
| if (recurse && preserve_root) |
| { |
| static struct dev_ino dev_ino_buf; |
| root_dev_ino = get_root_dev_ino (&dev_ino_buf); |
| if (root_dev_ino == nullptr) |
| error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), |
| quoteaf ("/")); |
| } |
| else |
| { |
| root_dev_ino = nullptr; |
| } |
|
|
| bit_flags |= FTS_DEFER_STAT; |
| ok = process_files (argv + optind, bit_flags); |
|
|
| main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); |
| } |
|
|