(root)/
tar-1.35/
gnu/
modechange.c
       1  /* modechange.c -- file mode manipulation
       2  
       3     Copyright (C) 1989-1990, 1997-1999, 2001, 2003-2006, 2009-2023 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)
      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)
     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)
     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)
     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,
     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  }