Blame lib/modechange.c

Packit 8f70b4
/* modechange.c -- file mode manipulation
Packit 8f70b4
Packit 8f70b4
   Copyright (C) 1989-1990, 1997-1999, 2001, 2003-2006, 2009-2018 Free Software
Packit 8f70b4
   Foundation, Inc.
Packit 8f70b4
Packit 8f70b4
   This program is free software: you can redistribute it and/or modify
Packit 8f70b4
   it under the terms of the GNU General Public License as published by
Packit 8f70b4
   the Free Software Foundation; either version 3 of the License, or
Packit 8f70b4
   (at your option) any later version.
Packit 8f70b4
Packit 8f70b4
   This program is distributed in the hope that it will be useful,
Packit 8f70b4
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8f70b4
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8f70b4
   GNU General Public License for more details.
Packit 8f70b4
Packit 8f70b4
   You should have received a copy of the GNU General Public License
Packit 8f70b4
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
Packit 8f70b4
Packit 8f70b4
/* Written by David MacKenzie <djm@ai.mit.edu> */
Packit 8f70b4
Packit 8f70b4
/* The ASCII mode string is compiled into an array of 'struct
Packit 8f70b4
   modechange', which can then be applied to each file to be changed.
Packit 8f70b4
   We do this instead of re-parsing the ASCII string for each file
Packit 8f70b4
   because the compiled form requires less computation to use; when
Packit 8f70b4
   changing the mode of many files, this probably results in a
Packit 8f70b4
   performance gain.  */
Packit 8f70b4
Packit 8f70b4
#include <config.h>
Packit 8f70b4
Packit 8f70b4
#include "modechange.h"
Packit 8f70b4
#include <sys/stat.h>
Packit 8f70b4
#include "stat-macros.h"
Packit 8f70b4
#include "xalloc.h"
Packit 8f70b4
#include <stdlib.h>
Packit 8f70b4
Packit 8f70b4
/* The traditional octal values corresponding to each mode bit.  */
Packit 8f70b4
#define SUID 04000
Packit 8f70b4
#define SGID 02000
Packit 8f70b4
#define SVTX 01000
Packit 8f70b4
#define RUSR 00400
Packit 8f70b4
#define WUSR 00200
Packit 8f70b4
#define XUSR 00100
Packit 8f70b4
#define RGRP 00040
Packit 8f70b4
#define WGRP 00020
Packit 8f70b4
#define XGRP 00010
Packit 8f70b4
#define ROTH 00004
Packit 8f70b4
#define WOTH 00002
Packit 8f70b4
#define XOTH 00001
Packit 8f70b4
#define ALLM 07777 /* all octal mode bits */
Packit 8f70b4
Packit 8f70b4
/* Convert OCTAL, which uses one of the traditional octal values, to
Packit 8f70b4
   an internal mode_t value.  */
Packit 8f70b4
static mode_t
Packit 8f70b4
octal_to_mode (unsigned int octal)
Packit 8f70b4
{
Packit 8f70b4
  /* Help the compiler optimize the usual case where mode_t uses
Packit 8f70b4
     the traditional octal representation.  */
Packit 8f70b4
  return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
Packit 8f70b4
           && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
Packit 8f70b4
           && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
Packit 8f70b4
           && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
Packit 8f70b4
          ? octal
Packit 8f70b4
          : (mode_t) ((octal & SUID ? S_ISUID : 0)
Packit 8f70b4
                      | (octal & SGID ? S_ISGID : 0)
Packit 8f70b4
                      | (octal & SVTX ? S_ISVTX : 0)
Packit 8f70b4
                      | (octal & RUSR ? S_IRUSR : 0)
Packit 8f70b4
                      | (octal & WUSR ? S_IWUSR : 0)
Packit 8f70b4
                      | (octal & XUSR ? S_IXUSR : 0)
Packit 8f70b4
                      | (octal & RGRP ? S_IRGRP : 0)
Packit 8f70b4
                      | (octal & WGRP ? S_IWGRP : 0)
Packit 8f70b4
                      | (octal & XGRP ? S_IXGRP : 0)
Packit 8f70b4
                      | (octal & ROTH ? S_IROTH : 0)
Packit 8f70b4
                      | (octal & WOTH ? S_IWOTH : 0)
Packit 8f70b4
                      | (octal & XOTH ? S_IXOTH : 0)));
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Special operations flags.  */
Packit 8f70b4
enum
Packit 8f70b4
  {
Packit 8f70b4
    /* For the sentinel at the end of the mode changes array.  */
Packit 8f70b4
    MODE_DONE,
Packit 8f70b4
Packit 8f70b4
    /* The typical case.  */
Packit 8f70b4
    MODE_ORDINARY_CHANGE,
Packit 8f70b4
Packit 8f70b4
    /* In addition to the typical case, affect the execute bits if at
Packit 8f70b4
       least one execute bit is set already, or if the file is a
Packit 8f70b4
       directory.  */
Packit 8f70b4
    MODE_X_IF_ANY_X,
Packit 8f70b4
Packit 8f70b4
    /* Instead of the typical case, copy some existing permissions for
Packit 8f70b4
       u, g, or o onto the other two.  Which of u, g, or o is copied
Packit 8f70b4
       is determined by which bits are set in the 'value' field.  */
Packit 8f70b4
    MODE_COPY_EXISTING
Packit 8f70b4
  };
Packit 8f70b4
Packit 8f70b4
/* Description of a mode change.  */
Packit 8f70b4
struct mode_change
Packit 8f70b4
{
Packit 8f70b4
  char op;                      /* One of "=+-".  */
Packit 8f70b4
  char flag;                    /* Special operations flag.  */
Packit 8f70b4
  mode_t affected;              /* Set for u, g, o, or a.  */
Packit 8f70b4
  mode_t value;                 /* Bits to add/remove.  */
Packit 8f70b4
  mode_t mentioned;             /* Bits explicitly mentioned.  */
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
/* Return a mode_change array with the specified "=ddd"-style
Packit 8f70b4
   mode change operation, where NEW_MODE is "ddd" and MENTIONED
Packit 8f70b4
   contains the bits explicitly mentioned in the mode are MENTIONED.  */
Packit 8f70b4
Packit 8f70b4
static struct mode_change *
Packit 8f70b4
make_node_op_equals (mode_t new_mode, mode_t mentioned)
Packit 8f70b4
{
Packit 8f70b4
  struct mode_change *p = xmalloc (2 * sizeof *p);
Packit 8f70b4
  p->op = '=';
Packit 8f70b4
  p->flag = MODE_ORDINARY_CHANGE;
Packit 8f70b4
  p->affected = CHMOD_MODE_BITS;
Packit 8f70b4
  p->value = new_mode;
Packit 8f70b4
  p->mentioned = mentioned;
Packit 8f70b4
  p[1].flag = MODE_DONE;
Packit 8f70b4
  return p;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Return a pointer to an array of file mode change operations created from
Packit 8f70b4
   MODE_STRING, an ASCII string that contains either an octal number
Packit 8f70b4
   specifying an absolute mode, or symbolic mode change operations with
Packit 8f70b4
   the form:
Packit 8f70b4
   [ugoa...][[+-=][rwxXstugo...]...][,...]
Packit 8f70b4
Packit 8f70b4
   Return NULL if 'mode_string' does not contain a valid
Packit 8f70b4
   representation of file mode change operations.  */
Packit 8f70b4
Packit 8f70b4
struct mode_change *
Packit 8f70b4
mode_compile (char const *mode_string)
Packit 8f70b4
{
Packit 8f70b4
  /* The array of mode-change directives to be returned.  */
Packit 8f70b4
  struct mode_change *mc;
Packit 8f70b4
  size_t used = 0;
Packit 8f70b4
  char const *p;
Packit 8f70b4
Packit 8f70b4
  if ('0' <= *mode_string && *mode_string < '8')
Packit 8f70b4
    {
Packit 8f70b4
      unsigned int octal_mode = 0;
Packit 8f70b4
      mode_t mode;
Packit 8f70b4
      mode_t mentioned;
Packit 8f70b4
Packit 8f70b4
      p = mode_string;
Packit 8f70b4
      do
Packit 8f70b4
        {
Packit 8f70b4
          octal_mode = 8 * octal_mode + *p++ - '0';
Packit 8f70b4
          if (ALLM < octal_mode)
Packit 8f70b4
            return NULL;
Packit 8f70b4
        }
Packit 8f70b4
      while ('0' <= *p && *p < '8');
Packit 8f70b4
Packit 8f70b4
      if (*p)
Packit 8f70b4
        return NULL;
Packit 8f70b4
Packit 8f70b4
      mode = octal_to_mode (octal_mode);
Packit 8f70b4
      mentioned = (p - mode_string < 5
Packit 8f70b4
                   ? (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO
Packit 8f70b4
                   : CHMOD_MODE_BITS);
Packit 8f70b4
      return make_node_op_equals (mode, mentioned);
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* Allocate enough space to hold the result.  */
Packit 8f70b4
  {
Packit 8f70b4
    size_t needed = 1;
Packit 8f70b4
    for (p = mode_string; *p; p++)
Packit 8f70b4
      needed += (*p == '=' || *p == '+' || *p == '-');
Packit 8f70b4
    mc = xnmalloc (needed, sizeof *mc);
Packit 8f70b4
  }
Packit 8f70b4
Packit 8f70b4
  /* One loop iteration for each
Packit 8f70b4
     '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'.  */
Packit 8f70b4
  for (p = mode_string; ; p++)
Packit 8f70b4
    {
Packit 8f70b4
      /* Which bits in the mode are operated on.  */
Packit 8f70b4
      mode_t affected = 0;
Packit 8f70b4
Packit 8f70b4
      /* Turn on all the bits in 'affected' for each group given.  */
Packit 8f70b4
      for (;; p++)
Packit 8f70b4
        switch (*p)
Packit 8f70b4
          {
Packit 8f70b4
          default:
Packit 8f70b4
            goto invalid;
Packit 8f70b4
          case 'u':
Packit 8f70b4
            affected |= S_ISUID | S_IRWXU;
Packit 8f70b4
            break;
Packit 8f70b4
          case 'g':
Packit 8f70b4
            affected |= S_ISGID | S_IRWXG;
Packit 8f70b4
            break;
Packit 8f70b4
          case 'o':
Packit 8f70b4
            affected |= S_ISVTX | S_IRWXO;
Packit 8f70b4
            break;
Packit 8f70b4
          case 'a':
Packit 8f70b4
            affected |= CHMOD_MODE_BITS;
Packit 8f70b4
            break;
Packit 8f70b4
          case '=': case '+': case '-':
Packit 8f70b4
            goto no_more_affected;
Packit 8f70b4
          }
Packit 8f70b4
    no_more_affected:;
Packit 8f70b4
Packit 8f70b4
      do
Packit 8f70b4
        {
Packit 8f70b4
          char op = *p++;
Packit 8f70b4
          mode_t value;
Packit 8f70b4
          mode_t mentioned = 0;
Packit 8f70b4
          char flag = MODE_COPY_EXISTING;
Packit 8f70b4
          struct mode_change *change;
Packit 8f70b4
Packit 8f70b4
          switch (*p)
Packit 8f70b4
            {
Packit 8f70b4
            case '0': case '1': case '2': case '3':
Packit 8f70b4
            case '4': case '5': case '6': case '7':
Packit 8f70b4
              {
Packit 8f70b4
                unsigned int octal_mode = 0;
Packit 8f70b4
Packit 8f70b4
                do
Packit 8f70b4
                  {
Packit 8f70b4
                    octal_mode = 8 * octal_mode + *p++ - '0';
Packit 8f70b4
                    if (ALLM < octal_mode)
Packit 8f70b4
                      goto invalid;
Packit 8f70b4
                  }
Packit 8f70b4
                while ('0' <= *p && *p < '8');
Packit 8f70b4
Packit 8f70b4
                if (affected || (*p && *p != ','))
Packit 8f70b4
                  goto invalid;
Packit 8f70b4
                affected = mentioned = CHMOD_MODE_BITS;
Packit 8f70b4
                value = octal_to_mode (octal_mode);
Packit 8f70b4
                flag = MODE_ORDINARY_CHANGE;
Packit 8f70b4
                break;
Packit 8f70b4
              }
Packit 8f70b4
Packit 8f70b4
            case 'u':
Packit 8f70b4
              /* Set the affected bits to the value of the "u" bits
Packit 8f70b4
                 on the same file.  */
Packit 8f70b4
              value = S_IRWXU;
Packit 8f70b4
              p++;
Packit 8f70b4
              break;
Packit 8f70b4
            case 'g':
Packit 8f70b4
              /* Set the affected bits to the value of the "g" bits
Packit 8f70b4
                 on the same file.  */
Packit 8f70b4
              value = S_IRWXG;
Packit 8f70b4
              p++;
Packit 8f70b4
              break;
Packit 8f70b4
            case 'o':
Packit 8f70b4
              /* Set the affected bits to the value of the "o" bits
Packit 8f70b4
                 on the same file.  */
Packit 8f70b4
              value = S_IRWXO;
Packit 8f70b4
              p++;
Packit 8f70b4
              break;
Packit 8f70b4
Packit 8f70b4
            default:
Packit 8f70b4
              value = 0;
Packit 8f70b4
              flag = MODE_ORDINARY_CHANGE;
Packit 8f70b4
Packit 8f70b4
              for (;; p++)
Packit 8f70b4
                switch (*p)
Packit 8f70b4
                  {
Packit 8f70b4
                  case 'r':
Packit 8f70b4
                    value |= S_IRUSR | S_IRGRP | S_IROTH;
Packit 8f70b4
                    break;
Packit 8f70b4
                  case 'w':
Packit 8f70b4
                    value |= S_IWUSR | S_IWGRP | S_IWOTH;
Packit 8f70b4
                    break;
Packit 8f70b4
                  case 'x':
Packit 8f70b4
                    value |= S_IXUSR | S_IXGRP | S_IXOTH;
Packit 8f70b4
                    break;
Packit 8f70b4
                  case 'X':
Packit 8f70b4
                    flag = MODE_X_IF_ANY_X;
Packit 8f70b4
                    break;
Packit 8f70b4
                  case 's':
Packit 8f70b4
                    /* Set the setuid/gid bits if 'u' or 'g' is selected.  */
Packit 8f70b4
                    value |= S_ISUID | S_ISGID;
Packit 8f70b4
                    break;
Packit 8f70b4
                  case 't':
Packit 8f70b4
                    /* Set the "save text image" bit if 'o' is selected.  */
Packit 8f70b4
                    value |= S_ISVTX;
Packit 8f70b4
                    break;
Packit 8f70b4
                  default:
Packit 8f70b4
                    goto no_more_values;
Packit 8f70b4
                  }
Packit 8f70b4
            no_more_values:;
Packit 8f70b4
            }
Packit 8f70b4
Packit 8f70b4
          change = &mc[used++];
Packit 8f70b4
          change->op = op;
Packit 8f70b4
          change->flag = flag;
Packit 8f70b4
          change->affected = affected;
Packit 8f70b4
          change->value = value;
Packit 8f70b4
          change->mentioned =
Packit 8f70b4
            (mentioned ? mentioned : affected ? affected & value : value);
Packit 8f70b4
        }
Packit 8f70b4
      while (*p == '=' || *p == '+' || *p == '-');
Packit 8f70b4
Packit 8f70b4
      if (*p != ',')
Packit 8f70b4
        break;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (*p == 0)
Packit 8f70b4
    {
Packit 8f70b4
      mc[used].flag = MODE_DONE;
Packit 8f70b4
      return mc;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
invalid:
Packit 8f70b4
  free (mc);
Packit 8f70b4
  return NULL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Return a file mode change operation that sets permissions to match those
Packit 8f70b4
   of REF_FILE.  Return NULL (setting errno) if REF_FILE can't be accessed.  */
Packit 8f70b4
Packit 8f70b4
struct mode_change *
Packit 8f70b4
mode_create_from_ref (const char *ref_file)
Packit 8f70b4
{
Packit 8f70b4
  struct stat ref_stats;
Packit 8f70b4
Packit 8f70b4
  if (stat (ref_file, &ref_stats) != 0)
Packit 8f70b4
    return NULL;
Packit 8f70b4
  return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Return the file mode bits of OLDMODE (which is the mode of a
Packit 8f70b4
   directory if DIR), assuming the umask is UMASK_VALUE, adjusted as
Packit 8f70b4
   indicated by the list of change operations CHANGES.  If DIR, the
Packit 8f70b4
   type 'X' change affects the returned value even if no execute bits
Packit 8f70b4
   were set in OLDMODE, and set user and group ID bits are preserved
Packit 8f70b4
   unless CHANGES mentioned them.  If PMODE_BITS is not null, store into
Packit 8f70b4
   *PMODE_BITS a mask denoting file mode bits that are affected by
Packit 8f70b4
   CHANGES.
Packit 8f70b4
Packit 8f70b4
   The returned value and *PMODE_BITS contain only file mode bits.
Packit 8f70b4
   For example, they have the S_IFMT bits cleared on a standard
Packit 8f70b4
   Unix-like host.  */
Packit 8f70b4
Packit 8f70b4
mode_t
Packit 8f70b4
mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
Packit 8f70b4
             struct mode_change const *changes, mode_t *pmode_bits)
Packit 8f70b4
{
Packit 8f70b4
  /* The adjusted mode.  */
Packit 8f70b4
  mode_t newmode = oldmode & CHMOD_MODE_BITS;
Packit 8f70b4
Packit 8f70b4
  /* File mode bits that CHANGES cares about.  */
Packit 8f70b4
  mode_t mode_bits = 0;
Packit 8f70b4
Packit 8f70b4
  for (; changes->flag != MODE_DONE; changes++)
Packit 8f70b4
    {
Packit 8f70b4
      mode_t affected = changes->affected;
Packit 8f70b4
      mode_t omit_change =
Packit 8f70b4
        (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned;
Packit 8f70b4
      mode_t value = changes->value;
Packit 8f70b4
Packit 8f70b4
      switch (changes->flag)
Packit 8f70b4
        {
Packit 8f70b4
        case MODE_ORDINARY_CHANGE:
Packit 8f70b4
          break;
Packit 8f70b4
Packit 8f70b4
        case MODE_COPY_EXISTING:
Packit 8f70b4
          /* Isolate in 'value' the bits in 'newmode' to copy.  */
Packit 8f70b4
          value &= newmode;
Packit 8f70b4
Packit 8f70b4
          /* Copy the isolated bits to the other two parts.  */
Packit 8f70b4
          value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH)
Packit 8f70b4
                     ? S_IRUSR | S_IRGRP | S_IROTH : 0)
Packit 8f70b4
                    | (value & (S_IWUSR | S_IWGRP | S_IWOTH)
Packit 8f70b4
                       ? S_IWUSR | S_IWGRP | S_IWOTH : 0)
Packit 8f70b4
                    | (value & (S_IXUSR | S_IXGRP | S_IXOTH)
Packit 8f70b4
                       ? S_IXUSR | S_IXGRP | S_IXOTH : 0));
Packit 8f70b4
          break;
Packit 8f70b4
Packit 8f70b4
        case MODE_X_IF_ANY_X:
Packit 8f70b4
          /* Affect the execute bits if execute bits are already set
Packit 8f70b4
             or if the file is a directory.  */
Packit 8f70b4
          if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
Packit 8f70b4
            value |= S_IXUSR | S_IXGRP | S_IXOTH;
Packit 8f70b4
          break;
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      /* If WHO was specified, limit the change to the affected bits.
Packit 8f70b4
         Otherwise, apply the umask.  Either way, omit changes as
Packit 8f70b4
         requested.  */
Packit 8f70b4
      value &= (affected ? affected : ~umask_value) & ~ omit_change;
Packit 8f70b4
Packit 8f70b4
      switch (changes->op)
Packit 8f70b4
        {
Packit 8f70b4
        case '=':
Packit 8f70b4
          /* If WHO was specified, preserve the previous values of
Packit 8f70b4
             bits that are not affected by this change operation.
Packit 8f70b4
             Otherwise, clear all the bits.  */
Packit 8f70b4
          {
Packit 8f70b4
            mode_t preserved = (affected ? ~affected : 0) | omit_change;
Packit 8f70b4
            mode_bits |= CHMOD_MODE_BITS & ~preserved;
Packit 8f70b4
            newmode = (newmode & preserved) | value;
Packit 8f70b4
            break;
Packit 8f70b4
          }
Packit 8f70b4
Packit 8f70b4
        case '+':
Packit 8f70b4
          mode_bits |= value;
Packit 8f70b4
          newmode |= value;
Packit 8f70b4
          break;
Packit 8f70b4
Packit 8f70b4
        case '-':
Packit 8f70b4
          mode_bits |= value;
Packit 8f70b4
          newmode &= ~value;
Packit 8f70b4
          break;
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (pmode_bits)
Packit 8f70b4
    *pmode_bits = mode_bits;
Packit 8f70b4
  return newmode;
Packit 8f70b4
}