Blame lib/modechange.c

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