root/maint/gnulib/lib/modechange.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. octal_to_mode
  2. make_node_op_equals
  3. mode_compile
  4. mode_create_from_ref
  5. mode_adjust

   1 /* modechange.c -- file mode manipulation
   2 
   3    Copyright (C) 1989-1990, 1997-1999, 2001, 2003-2006, 2009-2021 Free Software
   4    Foundation, Inc.
   5 
   6    This program is free software: you can redistribute it and/or modify
   7    it under the terms of the GNU General Public License as published by
   8    the Free Software Foundation; either version 3 of the License, or
   9    (at your option) any later version.
  10 
  11    This program is distributed in the hope that it will be useful,
  12    but WITHOUT ANY WARRANTY; without even the implied warranty of
  13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14    GNU General Public License for more details.
  15 
  16    You should have received a copy of the GNU General Public License
  17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
  18 
  19 /* Written by David MacKenzie <djm@ai.mit.edu> */
  20 
  21 /* The ASCII mode string is compiled into an array of 'struct
  22    modechange', which can then be applied to each file to be changed.
  23    We do this instead of re-parsing the ASCII string for each file
  24    because the compiled form requires less computation to use; when
  25    changing the mode of many files, this probably results in a
  26    performance gain.  */
  27 
  28 #include <config.h>
  29 
  30 #include "modechange.h"
  31 #include <sys/stat.h>
  32 #include "stat-macros.h"
  33 #include "xalloc.h"
  34 #include <stdlib.h>
  35 
  36 /* The traditional octal values corresponding to each mode bit.  */
  37 #define SUID 04000
  38 #define SGID 02000
  39 #define SVTX 01000
  40 #define RUSR 00400
  41 #define WUSR 00200
  42 #define XUSR 00100
  43 #define RGRP 00040
  44 #define WGRP 00020
  45 #define XGRP 00010
  46 #define ROTH 00004
  47 #define WOTH 00002
  48 #define XOTH 00001
  49 #define ALLM 07777 /* all octal mode bits */
  50 
  51 /* Convert OCTAL, which uses one of the traditional octal values, to
  52    an internal mode_t value.  */
  53 static mode_t
  54 octal_to_mode (unsigned int octal)
     /* [previous][next][first][last][top][bottom][index][help] */
  55 {
  56   /* Help the compiler optimize the usual case where mode_t uses
  57      the traditional octal representation.  */
  58   return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
  59            && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
  60            && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
  61            && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
  62           ? octal
  63           : (mode_t) ((octal & SUID ? S_ISUID : 0)
  64                       | (octal & SGID ? S_ISGID : 0)
  65                       | (octal & SVTX ? S_ISVTX : 0)
  66                       | (octal & RUSR ? S_IRUSR : 0)
  67                       | (octal & WUSR ? S_IWUSR : 0)
  68                       | (octal & XUSR ? S_IXUSR : 0)
  69                       | (octal & RGRP ? S_IRGRP : 0)
  70                       | (octal & WGRP ? S_IWGRP : 0)
  71                       | (octal & XGRP ? S_IXGRP : 0)
  72                       | (octal & ROTH ? S_IROTH : 0)
  73                       | (octal & WOTH ? S_IWOTH : 0)
  74                       | (octal & XOTH ? S_IXOTH : 0)));
  75 }
  76 
  77 /* Special operations flags.  */
  78 enum
  79   {
  80     /* For the sentinel at the end of the mode changes array.  */
  81     MODE_DONE,
  82 
  83     /* The typical case.  */
  84     MODE_ORDINARY_CHANGE,
  85 
  86     /* In addition to the typical case, affect the execute bits if at
  87        least one execute bit is set already, or if the file is a
  88        directory.  */
  89     MODE_X_IF_ANY_X,
  90 
  91     /* Instead of the typical case, copy some existing permissions for
  92        u, g, or o onto the other two.  Which of u, g, or o is copied
  93        is determined by which bits are set in the 'value' field.  */
  94     MODE_COPY_EXISTING
  95   };
  96 
  97 /* Description of a mode change.  */
  98 struct mode_change
  99 {
 100   char op;                      /* One of "=+-".  */
 101   char flag;                    /* Special operations flag.  */
 102   mode_t affected;              /* Set for u, g, o, or a.  */
 103   mode_t value;                 /* Bits to add/remove.  */
 104   mode_t mentioned;             /* Bits explicitly mentioned.  */
 105 };
 106 
 107 /* Return a mode_change array with the specified "=ddd"-style
 108    mode change operation, where NEW_MODE is "ddd" and MENTIONED
 109    contains the bits explicitly mentioned in the mode are MENTIONED.  */
 110 
 111 static struct mode_change *
 112 make_node_op_equals (mode_t new_mode, mode_t mentioned)
     /* [previous][next][first][last][top][bottom][index][help] */
 113 {
 114   struct mode_change *p = xmalloc (2 * sizeof *p);
 115   p->op = '=';
 116   p->flag = MODE_ORDINARY_CHANGE;
 117   p->affected = CHMOD_MODE_BITS;
 118   p->value = new_mode;
 119   p->mentioned = mentioned;
 120   p[1].flag = MODE_DONE;
 121   return p;
 122 }
 123 
 124 /* Return a pointer to an array of file mode change operations created from
 125    MODE_STRING, an ASCII string that contains either an octal number
 126    specifying an absolute mode, or symbolic mode change operations with
 127    the form:
 128    [ugoa...][[+-=][rwxXstugo...]...][,...]
 129 
 130    Return NULL if 'mode_string' does not contain a valid
 131    representation of file mode change operations.  */
 132 
 133 struct mode_change *
 134 mode_compile (char const *mode_string)
     /* [previous][next][first][last][top][bottom][index][help] */
 135 {
 136   /* The array of mode-change directives to be returned.  */
 137   struct mode_change *mc;
 138   size_t used = 0;
 139   char const *p;
 140 
 141   if ('0' <= *mode_string && *mode_string < '8')
 142     {
 143       unsigned int octal_mode = 0;
 144       mode_t mode;
 145       mode_t mentioned;
 146 
 147       p = mode_string;
 148       do
 149         {
 150           octal_mode = 8 * octal_mode + *p++ - '0';
 151           if (ALLM < octal_mode)
 152             return NULL;
 153         }
 154       while ('0' <= *p && *p < '8');
 155 
 156       if (*p)
 157         return NULL;
 158 
 159       mode = octal_to_mode (octal_mode);
 160       mentioned = (p - mode_string < 5
 161                    ? (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO
 162                    : CHMOD_MODE_BITS);
 163       return make_node_op_equals (mode, mentioned);
 164     }
 165 
 166   /* Allocate enough space to hold the result.  */
 167   {
 168     size_t needed = 1;
 169     for (p = mode_string; *p; p++)
 170       needed += (*p == '=' || *p == '+' || *p == '-');
 171     mc = xnmalloc (needed, sizeof *mc);
 172   }
 173 
 174   /* One loop iteration for each
 175      '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.  */
 176   for (p = mode_string; ; p++)
 177     {
 178       /* Which bits in the mode are operated on.  */
 179       mode_t affected = 0;
 180 
 181       /* Turn on all the bits in 'affected' for each group given.  */
 182       for (;; p++)
 183         switch (*p)
 184           {
 185           default:
 186             goto invalid;
 187           case 'u':
 188             affected |= S_ISUID | S_IRWXU;
 189             break;
 190           case 'g':
 191             affected |= S_ISGID | S_IRWXG;
 192             break;
 193           case 'o':
 194             affected |= S_ISVTX | S_IRWXO;
 195             break;
 196           case 'a':
 197             affected |= CHMOD_MODE_BITS;
 198             break;
 199           case '=': case '+': case '-':
 200             goto no_more_affected;
 201           }
 202     no_more_affected:;
 203 
 204       do
 205         {
 206           char op = *p++;
 207           mode_t value;
 208           mode_t mentioned = 0;
 209           char flag = MODE_COPY_EXISTING;
 210           struct mode_change *change;
 211 
 212           switch (*p)
 213             {
 214             case '0': case '1': case '2': case '3':
 215             case '4': case '5': case '6': case '7':
 216               {
 217                 unsigned int octal_mode = 0;
 218 
 219                 do
 220                   {
 221                     octal_mode = 8 * octal_mode + *p++ - '0';
 222                     if (ALLM < octal_mode)
 223                       goto invalid;
 224                   }
 225                 while ('0' <= *p && *p < '8');
 226 
 227                 if (affected || (*p && *p != ','))
 228                   goto invalid;
 229                 affected = mentioned = CHMOD_MODE_BITS;
 230                 value = octal_to_mode (octal_mode);
 231                 flag = MODE_ORDINARY_CHANGE;
 232                 break;
 233               }
 234 
 235             case 'u':
 236               /* Set the affected bits to the value of the "u" bits
 237                  on the same file.  */
 238               value = S_IRWXU;
 239               p++;
 240               break;
 241             case 'g':
 242               /* Set the affected bits to the value of the "g" bits
 243                  on the same file.  */
 244               value = S_IRWXG;
 245               p++;
 246               break;
 247             case 'o':
 248               /* Set the affected bits to the value of the "o" bits
 249                  on the same file.  */
 250               value = S_IRWXO;
 251               p++;
 252               break;
 253 
 254             default:
 255               value = 0;
 256               flag = MODE_ORDINARY_CHANGE;
 257 
 258               for (;; p++)
 259                 switch (*p)
 260                   {
 261                   case 'r':
 262                     value |= S_IRUSR | S_IRGRP | S_IROTH;
 263                     break;
 264                   case 'w':
 265                     value |= S_IWUSR | S_IWGRP | S_IWOTH;
 266                     break;
 267                   case 'x':
 268                     value |= S_IXUSR | S_IXGRP | S_IXOTH;
 269                     break;
 270                   case 'X':
 271                     flag = MODE_X_IF_ANY_X;
 272                     break;
 273                   case 's':
 274                     /* Set the setuid/gid bits if 'u' or 'g' is selected.  */
 275                     value |= S_ISUID | S_ISGID;
 276                     break;
 277                   case 't':
 278                     /* Set the "save text image" bit if 'o' is selected.  */
 279                     value |= S_ISVTX;
 280                     break;
 281                   default:
 282                     goto no_more_values;
 283                   }
 284             no_more_values:;
 285             }
 286 
 287           change = &mc[used++];
 288           change->op = op;
 289           change->flag = flag;
 290           change->affected = affected;
 291           change->value = value;
 292           change->mentioned =
 293             (mentioned ? mentioned : affected ? affected & value : value);
 294         }
 295       while (*p == '=' || *p == '+' || *p == '-');
 296 
 297       if (*p != ',')
 298         break;
 299     }
 300 
 301   if (*p == 0)
 302     {
 303       mc[used].flag = MODE_DONE;
 304       return mc;
 305     }
 306 
 307 invalid:
 308   free (mc);
 309   return NULL;
 310 }
 311 
 312 /* Return a file mode change operation that sets permissions to match those
 313    of REF_FILE.  Return NULL (setting errno) if REF_FILE can't be accessed.  */
 314 
 315 struct mode_change *
 316 mode_create_from_ref (const char *ref_file)
     /* [previous][next][first][last][top][bottom][index][help] */
 317 {
 318   struct stat ref_stats;
 319 
 320   if (stat (ref_file, &ref_stats) != 0)
 321     return NULL;
 322   return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
 323 }
 324 
 325 /* Return the file mode bits of OLDMODE (which is the mode of a
 326    directory if DIR), assuming the umask is UMASK_VALUE, adjusted as
 327    indicated by the list of change operations CHANGES.  If DIR, the
 328    type 'X' change affects the returned value even if no execute bits
 329    were set in OLDMODE, and set user and group ID bits are preserved
 330    unless CHANGES mentioned them.  If PMODE_BITS is not null, store into
 331    *PMODE_BITS a mask denoting file mode bits that are affected by
 332    CHANGES.
 333 
 334    The returned value and *PMODE_BITS contain only file mode bits.
 335    For example, they have the S_IFMT bits cleared on a standard
 336    Unix-like host.  */
 337 
 338 mode_t
 339 mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
     /* [previous][next][first][last][top][bottom][index][help] */
 340              struct mode_change const *changes, mode_t *pmode_bits)
 341 {
 342   /* The adjusted mode.  */
 343   mode_t newmode = oldmode & CHMOD_MODE_BITS;
 344 
 345   /* File mode bits that CHANGES cares about.  */
 346   mode_t mode_bits = 0;
 347 
 348   for (; changes->flag != MODE_DONE; changes++)
 349     {
 350       mode_t affected = changes->affected;
 351       mode_t omit_change =
 352         (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned;
 353       mode_t value = changes->value;
 354 
 355       switch (changes->flag)
 356         {
 357         case MODE_ORDINARY_CHANGE:
 358           break;
 359 
 360         case MODE_COPY_EXISTING:
 361           /* Isolate in 'value' the bits in 'newmode' to copy.  */
 362           value &= newmode;
 363 
 364           /* Copy the isolated bits to the other two parts.  */
 365           value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH)
 366                      ? S_IRUSR | S_IRGRP | S_IROTH : 0)
 367                     | (value & (S_IWUSR | S_IWGRP | S_IWOTH)
 368                        ? S_IWUSR | S_IWGRP | S_IWOTH : 0)
 369                     | (value & (S_IXUSR | S_IXGRP | S_IXOTH)
 370                        ? S_IXUSR | S_IXGRP | S_IXOTH : 0));
 371           break;
 372 
 373         case MODE_X_IF_ANY_X:
 374           /* Affect the execute bits if execute bits are already set
 375              or if the file is a directory.  */
 376           if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
 377             value |= S_IXUSR | S_IXGRP | S_IXOTH;
 378           break;
 379         }
 380 
 381       /* If WHO was specified, limit the change to the affected bits.
 382          Otherwise, apply the umask.  Either way, omit changes as
 383          requested.  */
 384       value &= (affected ? affected : ~umask_value) & ~ omit_change;
 385 
 386       switch (changes->op)
 387         {
 388         case '=':
 389           /* If WHO was specified, preserve the previous values of
 390              bits that are not affected by this change operation.
 391              Otherwise, clear all the bits.  */
 392           {
 393             mode_t preserved = (affected ? ~affected : 0) | omit_change;
 394             mode_bits |= CHMOD_MODE_BITS & ~preserved;
 395             newmode = (newmode & preserved) | value;
 396             break;
 397           }
 398 
 399         case '+':
 400           mode_bits |= value;
 401           newmode |= value;
 402           break;
 403 
 404         case '-':
 405           mode_bits |= value;
 406           newmode &= ~value;
 407           break;
 408         }
 409     }
 410 
 411   if (pmode_bits)
 412     *pmode_bits = mode_bits;
 413   return newmode;
 414 }

/* [previous][next][first][last][top][bottom][index][help] */