LCOV - code coverage report
Current view: top level - src - chmod.c (source / functions) Hit Total Coverage
Test: coreutils.info Lines: 161 195 82.6 %
Date: 2018-01-30 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /* chmod -- change permission modes of files
       2             :    Copyright (C) 89, 90, 91, 1995-2008 Free Software Foundation, Inc.
       3             : 
       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.
       8             : 
       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.
      13             : 
      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/>.  */
      16             : 
      17             : /* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
      18             : 
      19             : #include <config.h>
      20             : #include <stdio.h>
      21             : #include <getopt.h>
      22             : #include <sys/types.h>
      23             : 
      24             : #include "system.h"
      25             : #include "dev-ino.h"
      26             : #include "error.h"
      27             : #include "filemode.h"
      28             : #include "modechange.h"
      29             : #include "quote.h"
      30             : #include "quotearg.h"
      31             : #include "root-dev-ino.h"
      32             : #include "xfts.h"
      33             : 
      34             : /* The official name of this program (e.g., no `g' prefix).  */
      35             : #define PROGRAM_NAME "chmod"
      36             : 
      37             : #define AUTHORS "David MacKenzie", "Jim Meyering"
      38             : 
      39             : enum Change_status
      40             : {
      41             :   CH_NOT_APPLIED,
      42             :   CH_SUCCEEDED,
      43             :   CH_FAILED,
      44             :   CH_NO_CHANGE_REQUESTED
      45             : };
      46             : 
      47             : enum Verbosity
      48             : {
      49             :   /* Print a message for each file that is processed.  */
      50             :   V_high,
      51             : 
      52             :   /* Print a message for each file whose attributes we change.  */
      53             :   V_changes_only,
      54             : 
      55             :   /* Do not be verbose.  This is the default. */
      56             :   V_off
      57             : };
      58             : 
      59             : /* The name the program was run with. */
      60             : char *program_name;
      61             : 
      62             : /* The desired change to the mode.  */
      63             : static struct mode_change *change;
      64             : 
      65             : /* The initial umask value, if it might be needed.  */
      66             : static mode_t umask_value;
      67             : 
      68             : /* If true, change the modes of directories recursively. */
      69             : static bool recurse;
      70             : 
      71             : /* If true, force silence (no error messages). */
      72             : static bool force_silent;
      73             : 
      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;
      78             : 
      79             : /* Level of verbosity.  */
      80             : static enum Verbosity verbosity = V_off;
      81             : 
      82             : /* Pointer to the device and inode numbers of `/', when --recursive.
      83             :    Otherwise NULL.  */
      84             : static struct dev_ino *root_dev_ino;
      85             : 
      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.  */
      88             : enum
      89             : {
      90             :   NO_PRESERVE_ROOT = CHAR_MAX + 1,
      91             :   PRESERVE_ROOT,
      92             :   REFERENCE_FILE_OPTION
      93             : };
      94             : 
      95             : static struct option const long_options[] =
      96             : {
      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},
     107             :   {NULL, 0, NULL, 0}
     108             : };
     109             : 
     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.  */
     112             : 
     113             : static bool
     114           6 : mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
     115             : {
     116           6 :   if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
     117             :     {
     118             :       /* The new mode contains unusual bits that the call to chmod may
     119             :          have silently cleared.  Check whether they actually changed.  */
     120             : 
     121             :       struct stat new_stats;
     122             : 
     123           2 :       if (stat (file, &new_stats) != 0)
     124             :         {
     125           0 :           if (!force_silent)
     126           0 :             error (0, errno, _("getting new attributes of %s"), quote (file));
     127           0 :           return false;
     128             :         }
     129             : 
     130           2 :       new_mode = new_stats.st_mode;
     131             :     }
     132             : 
     133           6 :   return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
     134             : }
     135             : 
     136             : /* Tell the user how/if the MODE of FILE has been changed.
     137             :    CHANGED describes what (if anything) has happened. */
     138             : 
     139             : static void
     140           7 : describe_change (const char *file, mode_t mode,
     141             :                  enum Change_status changed)
     142             : {
     143             :   char perms[12];               /* "-rwxrwxrwx" ls-style modes. */
     144             :   const char *fmt;
     145             : 
     146           7 :   if (changed == CH_NOT_APPLIED)
     147             :     {
     148           0 :       printf (_("neither symbolic link %s nor referent has been changed\n"),
     149             :               quote (file));
     150           0 :       return;
     151             :     }
     152             : 
     153           7 :   strmode (mode, perms);
     154           7 :   perms[10] = '\0';             /* Remove trailing space.  */
     155           7 :   switch (changed)
     156             :     {
     157           0 :     case CH_SUCCEEDED:
     158           0 :       fmt = _("mode of %s changed to %04lo (%s)\n");
     159           0 :       break;
     160           1 :     case CH_FAILED:
     161           1 :       fmt = _("failed to change mode of %s to %04lo (%s)\n");
     162           1 :       break;
     163           6 :     case CH_NO_CHANGE_REQUESTED:
     164           6 :       fmt = _("mode of %s retained as %04lo (%s)\n");
     165           6 :       break;
     166           0 :     default:
     167           0 :       abort ();
     168             :     }
     169           7 :   printf (fmt, quote (file),
     170           7 :           (unsigned long int) (mode & CHMOD_MODE_BITS), &perms[1]);
     171             : }
     172             : 
     173             : /* Change the mode of FILE.
     174             :    Return true if successful.  This function is called
     175             :    once for every file system object that fts encounters.  */
     176             : 
     177             : static bool
     178      293405 : process_file (FTS *fts, FTSENT *ent)
     179             : {
     180      293405 :   char const *file_full_name = ent->fts_path;
     181      293405 :   char const *file = ent->fts_accpath;
     182      293405 :   const struct stat *file_stats = ent->fts_statp;
     183             :   mode_t old_mode IF_LINT (= 0);
     184             :   mode_t new_mode IF_LINT (= 0);
     185      293405 :   bool ok = true;
     186      293405 :   bool chmod_succeeded = false;
     187             : 
     188      293405 :   switch (ent->fts_info)
     189             :     {
     190       26516 :     case FTS_DP:
     191       26516 :       return true;
     192             : 
     193          74 :     case FTS_NS:
     194             :       /* For a top-level file or directory, this FTS_NS (stat failed)
     195             :          indicator is determined at the time of the initial fts_open call.
     196             :          With programs like chmod, chown, and chgrp, that modify
     197             :          permissions, it is possible that the file in question is
     198             :          accessible when control reaches this point.  So, if this is
     199             :          the first time we've seen the FTS_NS for this file, tell
     200             :          fts_read to stat it "again".  */
     201          74 :       if (ent->fts_level == 0 && ent->fts_number == 0)
     202             :         {
     203          33 :           ent->fts_number = 1;
     204          33 :           fts_set (fts, ent, FTS_AGAIN);
     205          33 :           return true;
     206             :         }
     207          41 :       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
     208          41 :       ok = false;
     209          41 :       break;
     210             : 
     211           0 :     case FTS_ERR:
     212           0 :       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
     213           0 :       ok = false;
     214           0 :       break;
     215             : 
     216          14 :     case FTS_DNR:
     217          14 :       error (0, ent->fts_errno, _("cannot read directory %s"),
     218             :              quote (file_full_name));
     219          14 :       ok = false;
     220          14 :       break;
     221             : 
     222           0 :     case FTS_SLNONE:
     223           0 :       error (0, 0, _("cannot operate on dangling symlink %s"),
     224             :              quote (file_full_name));
     225           0 :       ok = false;
     226             : 
     227      266801 :     default:
     228      266801 :       break;
     229             :     }
     230             : 
     231      266856 :   if (ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
     232             :     {
     233           0 :       ROOT_DEV_INO_WARN (file_full_name);
     234             :       /* Tell fts not to traverse into this hierarchy.  */
     235           0 :       fts_set (fts, ent, FTS_SKIP);
     236             :       /* Ensure that we do not process "/" on the second visit.  */
     237           0 :       ent = fts_read (fts);
     238           0 :       ok = false;
     239             :     }
     240             : 
     241      266856 :   if (ok)
     242             :     {
     243      266801 :       old_mode = file_stats->st_mode;
     244      266801 :       new_mode = mode_adjust (old_mode, S_ISDIR (old_mode) != 0, umask_value,
     245             :                               change, NULL);
     246             : 
     247      266801 :       if (! S_ISLNK (old_mode))
     248             :         {
     249      250325 :           if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
     250       82515 :             chmod_succeeded = true;
     251             :           else
     252             :             {
     253      167810 :               if (! force_silent)
     254      167810 :                 error (0, errno, _("changing permissions of %s"),
     255             :                        quote (file_full_name));
     256      167810 :               ok = false;
     257             :             }
     258             :         }
     259             :     }
     260             : 
     261      266856 :   if (verbosity != V_off)
     262             :     {
     263           9 :       bool changed = (chmod_succeeded
     264           9 :                       && mode_changed (file, old_mode, new_mode));
     265             : 
     266           9 :       if (changed || verbosity == V_high)
     267             :         {
     268           7 :           enum Change_status ch_status =
     269           7 :             (!ok ? CH_FAILED
     270          13 :              : !chmod_succeeded ? CH_NOT_APPLIED
     271           6 :              : !changed ? CH_NO_CHANGE_REQUESTED
     272             :              : CH_SUCCEEDED);
     273           7 :           describe_change (file_full_name, new_mode, ch_status);
     274             :         }
     275             :     }
     276             : 
     277      266856 :   if (chmod_succeeded & diagnose_surprises)
     278             :     {
     279           1 :       mode_t naively_expected_mode =
     280           1 :         mode_adjust (old_mode, S_ISDIR (old_mode) != 0, 0, change, NULL);
     281           1 :       if (new_mode & ~naively_expected_mode)
     282             :         {
     283             :           char new_perms[12];
     284             :           char naively_expected_perms[12];
     285           0 :           strmode (new_mode, new_perms);
     286           0 :           strmode (naively_expected_mode, naively_expected_perms);
     287           0 :           new_perms[10] = naively_expected_perms[10] = '\0';
     288           0 :           error (0, 0,
     289             :                  _("%s: new permissions are %s, not %s"),
     290             :                  quotearg_colon (file_full_name),
     291             :                  new_perms + 1, naively_expected_perms + 1);
     292           0 :           ok = false;
     293             :         }
     294             :     }
     295             : 
     296      266856 :   if ( ! recurse)
     297          56 :     fts_set (fts, ent, FTS_SKIP);
     298             : 
     299      266856 :   return ok;
     300             : }
     301             : 
     302             : /* Recursively change the modes of the specified FILES (the last entry
     303             :    of which is NULL).  BIT_FLAGS controls how fts works.
     304             :    Return true if successful.  */
     305             : 
     306             : static bool
     307          56 : process_files (char **files, int bit_flags)
     308             : {
     309          56 :   bool ok = true;
     310             : 
     311          56 :   FTS *fts = xfts_open (files, bit_flags, NULL);
     312             : 
     313             :   while (1)
     314      293405 :     {
     315             :       FTSENT *ent;
     316             : 
     317      293454 :       ent = fts_read (fts);
     318      293454 :       if (ent == NULL)
     319             :         {
     320          49 :           if (errno != 0)
     321             :             {
     322             :               /* FIXME: try to give a better message  */
     323           0 :               error (0, errno, _("fts_read failed"));
     324           0 :               ok = false;
     325             :             }
     326          49 :           break;
     327             :         }
     328             : 
     329      293405 :       ok &= process_file (fts, ent);
     330             :     }
     331             : 
     332             :   /* Ignore failure, since the only way it can do so is in failing to
     333             :      return to the original directory, and since we're about to exit,
     334             :      that doesn't matter.  */
     335          49 :   fts_close (fts);
     336             : 
     337          49 :   return ok;
     338             : }
     339             : 
     340             : void
     341          54 : usage (int status)
     342             : {
     343          54 :   if (status != EXIT_SUCCESS)
     344          52 :     fprintf (stderr, _("Try `%s --help' for more information.\n"),
     345             :              program_name);
     346             :   else
     347             :     {
     348           2 :       printf (_("\
     349             : Usage: %s [OPTION]... MODE[,MODE]... FILE...\n\
     350             :   or:  %s [OPTION]... OCTAL-MODE FILE...\n\
     351             :   or:  %s [OPTION]... --reference=RFILE FILE...\n\
     352             : "),
     353             :               program_name, program_name, program_name);
     354           2 :       fputs (_("\
     355             : Change the mode of each FILE to MODE.\n\
     356             : \n\
     357             :   -c, --changes           like verbose but report only when a change is made\n\
     358             : "), stdout);
     359           2 :       fputs (_("\
     360             :       --no-preserve-root  do not treat `/' specially (the default)\n\
     361             :       --preserve-root     fail to operate recursively on `/'\n\
     362             : "), stdout);
     363           2 :       fputs (_("\
     364             :   -f, --silent, --quiet   suppress most error messages\n\
     365             :   -v, --verbose           output a diagnostic for every file processed\n\
     366             :       --reference=RFILE   use RFILE's mode instead of MODE values\n\
     367             :   -R, --recursive         change files and directories recursively\n\
     368             : "), stdout);
     369           2 :       fputs (HELP_OPTION_DESCRIPTION, stdout);
     370           2 :       fputs (VERSION_OPTION_DESCRIPTION, stdout);
     371           2 :       fputs (_("\
     372             : \n\
     373             : Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.\n\
     374             : "), stdout);
     375           2 :       emit_bug_reporting_address ();
     376             :     }
     377          54 :   exit (status);
     378             : }
     379             : 
     380             : /* Parse the ASCII mode given on the command line into a linked list
     381             :    of `struct mode_change' and apply that to each file argument. */
     382             : 
     383             : int
     384         112 : main (int argc, char **argv)
     385             : {
     386         112 :   char *mode = NULL;
     387         112 :   size_t mode_len = 0;
     388         112 :   size_t mode_alloc = 0;
     389             :   bool ok;
     390         112 :   bool preserve_root = false;
     391         112 :   char const *reference_file = NULL;
     392             :   int c;
     393             : 
     394             :   initialize_main (&argc, &argv);
     395         112 :   program_name = argv[0];
     396         112 :   setlocale (LC_ALL, "");
     397             :   bindtextdomain (PACKAGE, LOCALEDIR);
     398             :   textdomain (PACKAGE);
     399             : 
     400         112 :   atexit (close_stdout);
     401             : 
     402         112 :   recurse = force_silent = diagnose_surprises = false;
     403             : 
     404         268 :   while ((c = getopt_long (argc, argv,
     405             :                            "Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
     406             :                            long_options, NULL))
     407             :          != -1)
     408             :     {
     409          55 :       switch (c)
     410             :         {
     411          15 :         case 'r':
     412             :         case 'w':
     413             :         case 'x':
     414             :         case 'X':
     415             :         case 's':
     416             :         case 't':
     417             :         case 'u':
     418             :         case 'g':
     419             :         case 'o':
     420             :         case 'a':
     421             :         case ',':
     422             :         case '+':
     423             :         case '=':
     424             :           /* Support nonportable uses like "chmod -w", but diagnose
     425             :              surprises due to umask confusion.  Even though "--", "--r",
     426             :              etc., are valid modes, there is no "case '-'" here since
     427             :              getopt_long reserves leading "--" for long options.  */
     428             :           {
     429             :             /* Allocate a mode string (e.g., "-rwx") by concatenating
     430             :                the argument containing this option.  If a previous mode
     431             :                string was given, concatenate the previous string, a
     432             :                comma, and the new string (e.g., "-s,-rwx").  */
     433             : 
     434          15 :             char const *arg = argv[optind - 1];
     435          15 :             size_t arg_len = strlen (arg);
     436          15 :             size_t mode_comma_len = mode_len + !!mode_len;
     437          15 :             size_t new_mode_len = mode_comma_len + arg_len;
     438          15 :             if (mode_alloc <= new_mode_len)
     439             :               {
     440          15 :                 mode_alloc = new_mode_len + 1;
     441          15 :                 mode = X2REALLOC (mode, &mode_alloc);
     442             :               }
     443          15 :             mode[mode_len] = ',';
     444          15 :             strcpy (mode + mode_comma_len, arg);
     445          15 :             mode_len = new_mode_len;
     446             : 
     447          15 :             diagnose_surprises = true;
     448             :           }
     449          15 :           break;
     450           0 :         case NO_PRESERVE_ROOT:
     451           0 :           preserve_root = false;
     452           0 :           break;
     453           1 :         case PRESERVE_ROOT:
     454           1 :           preserve_root = true;
     455           1 :           break;
     456           5 :         case REFERENCE_FILE_OPTION:
     457           5 :           reference_file = optarg;
     458           5 :           break;
     459           9 :         case 'R':
     460           9 :           recurse = true;
     461           9 :           break;
     462           4 :         case 'c':
     463           4 :           verbosity = V_changes_only;
     464           4 :           break;
     465           2 :         case 'f':
     466           2 :           force_silent = true;
     467           2 :           break;
     468           8 :         case 'v':
     469           8 :           verbosity = V_high;
     470           8 :           break;
     471           2 :         case_GETOPT_HELP_CHAR;
     472           1 :         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
     473           8 :         default:
     474           8 :           usage (EXIT_FAILURE);
     475             :         }
     476             :     }
     477             : 
     478         101 :   if (reference_file)
     479             :     {
     480           5 :       if (mode)
     481             :         {
     482           1 :           error (0, 0, _("cannot combine mode and --reference options"));
     483           1 :           usage (EXIT_FAILURE);
     484             :         }
     485             :     }
     486             :   else
     487             :     {
     488          96 :       if (!mode)
     489          83 :         mode = argv[optind++];
     490             :     }
     491             : 
     492         100 :   if (optind >= argc)
     493             :     {
     494          26 :       if (!mode || mode != argv[optind - 1])
     495          15 :         error (0, 0, _("missing operand"));
     496             :       else
     497          11 :         error (0, 0, _("missing operand after %s"), quote (argv[argc - 1]));
     498          26 :       usage (EXIT_FAILURE);
     499             :     }
     500             : 
     501          74 :   if (reference_file)
     502             :     {
     503           2 :       change = mode_create_from_ref (reference_file);
     504           2 :       if (!change)
     505           1 :         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
     506             :                quote (reference_file));
     507             :     }
     508             :   else
     509             :     {
     510          72 :       change = mode_compile (mode);
     511          72 :       if (!change)
     512             :         {
     513          17 :           error (0, 0, _("invalid mode: %s"), quote (mode));
     514          17 :           usage (EXIT_FAILURE);
     515             :         }
     516          55 :       umask_value = umask (0);
     517             :     }
     518             : 
     519          56 :   if (recurse & preserve_root)
     520             :     {
     521             :       static struct dev_ino dev_ino_buf;
     522           0 :       root_dev_ino = get_root_dev_ino (&dev_ino_buf);
     523           0 :       if (root_dev_ino == NULL)
     524           0 :         error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
     525             :                quote ("/"));
     526             :     }
     527             :   else
     528             :     {
     529          56 :       root_dev_ino = NULL;
     530             :     }
     531             : 
     532          56 :   ok = process_files (argv + optind, FTS_COMFOLLOW | FTS_PHYSICAL);
     533             : 
     534          49 :   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
     535             : }

Generated by: LCOV version 1.10