Fix missing U_MBLEN define in u??-conf-to-enc.c.
[gnulib.git] / lib / modechange.c
index 6ab9cb6..1296717 100644 (file)
@@ -1,12 +1,12 @@
 /* modechange.c -- file mode manipulation
 
-   Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005
-   Free Software Foundation, Inc.
+   Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005,
+   2006 Free Software Foundation, Inc.
 
-   This program is free software; you can redistribute it and/or modify
+   This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -14,8 +14,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software Foundation,
-   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 /* Written by David MacKenzie <djm@ai.mit.edu> */
 
@@ -26,9 +25,7 @@
    changing the mode of many files, this probably results in a
    performance gain.  */
 
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
+#include <config.h>
 
 #include "modechange.h"
 #include <sys/stat.h>
 #define XOTH 00001
 #define ALLM 07777 /* all octal mode bits */
 
+/* Convert OCTAL, which uses one of the traditional octal values, to
+   an internal mode_t value.  */
+static mode_t
+octal_to_mode (unsigned int octal)
+{
+  /* Help the compiler optimize the usual case where mode_t uses
+     the traditional octal representation.  */
+  return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
+          && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
+          && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
+          && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
+         ? octal
+         : (mode_t) ((octal & SUID ? S_ISUID : 0)
+                     | (octal & SGID ? S_ISGID : 0)
+                     | (octal & SVTX ? S_ISVTX : 0)
+                     | (octal & RUSR ? S_IRUSR : 0)
+                     | (octal & WUSR ? S_IWUSR : 0)
+                     | (octal & XUSR ? S_IXUSR : 0)
+                     | (octal & RGRP ? S_IRGRP : 0)
+                     | (octal & WGRP ? S_IWGRP : 0)
+                     | (octal & XGRP ? S_IXGRP : 0)
+                     | (octal & ROTH ? S_IROTH : 0)
+                     | (octal & WOTH ? S_IWOTH : 0)
+                     | (octal & XOTH ? S_IXOTH : 0)));
+}
+
 /* Special operations flags.  */
 enum
   {
@@ -78,19 +101,22 @@ struct mode_change
   char flag;                   /* Special operations flag.  */
   mode_t affected;             /* Set for u, g, o, or a.  */
   mode_t value;                        /* Bits to add/remove.  */
+  mode_t mentioned;            /* Bits explicitly mentioned.  */
 };
 
 /* Return a mode_change array with the specified `=ddd'-style
-   mode change operation, where NEW_MODE is `ddd'.  */
+   mode change operation, where NEW_MODE is `ddd' and MENTIONED
+   contains the bits explicitly mentioned in the mode are MENTIONED.  */
 
 static struct mode_change *
-make_node_op_equals (mode_t new_mode)
+make_node_op_equals (mode_t new_mode, mode_t mentioned)
 {
   struct mode_change *p = xmalloc (2 * sizeof *p);
   p->op = '=';
   p->flag = MODE_ORDINARY_CHANGE;
   p->affected = CHMOD_MODE_BITS;
   p->value = new_mode;
+  p->mentioned = mentioned;
   p[1].flag = MODE_DONE;
   return p;
 }
@@ -113,38 +139,24 @@ mode_compile (char const *mode_string)
 
   if ('0' <= *mode_string && *mode_string < '8')
     {
+      unsigned int octal_mode = 0;
       mode_t mode;
-      unsigned int octal_value = 0;
+      mode_t mentioned;
 
       do
        {
-         octal_value = 8 * octal_value + *mode_string++ - '0';
-         if (ALLM < octal_value)
+         octal_mode = 8 * octal_mode + *mode_string++ - '0';
+         if (ALLM < octal_mode)
            return NULL;
        }
       while ('0' <= *mode_string && *mode_string < '8');
 
-      /* Help the compiler optimize the usual case where mode_t uses
-        the traditional octal representation.  */
-      mode = ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
-              && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
-              && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
-              && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
-             ? octal_value
-             : (mode_t) ((octal_value & SUID ? S_ISUID : 0)
-                         | (octal_value & SGID ? S_ISGID : 0)
-                         | (octal_value & SVTX ? S_ISVTX : 0)
-                         | (octal_value & RUSR ? S_IRUSR : 0)
-                         | (octal_value & WUSR ? S_IWUSR : 0)
-                         | (octal_value & XUSR ? S_IXUSR : 0)
-                         | (octal_value & RGRP ? S_IRGRP : 0)
-                         | (octal_value & WGRP ? S_IWGRP : 0)
-                         | (octal_value & XGRP ? S_IXGRP : 0)
-                         | (octal_value & ROTH ? S_IROTH : 0)
-                         | (octal_value & WOTH ? S_IWOTH : 0)
-                         | (octal_value & XOTH ? S_IXOTH : 0)));
-
-      return make_node_op_equals (mode);
+      if (*mode_string)
+       return NULL;
+
+      mode = octal_to_mode (octal_mode);
+      mentioned = (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO;
+      return make_node_op_equals (mode, mentioned);
     }
 
   /* Allocate enough space to hold the result.  */
@@ -248,6 +260,7 @@ mode_compile (char const *mode_string)
          change->flag = flag;
          change->affected = affected;
          change->value = value;
+         change->mentioned = (affected ? affected & value : value);
        }
       while (*mode_string == '=' || *mode_string == '+'
             || *mode_string == '-');
@@ -277,25 +290,37 @@ mode_create_from_ref (const char *ref_file)
 
   if (stat (ref_file, &ref_stats) != 0)
     return NULL;
-  return make_node_op_equals (ref_stats.st_mode);
+  return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
 }
 
-/* Return file mode OLDMODE, adjusted as indicated by the list of change
-   operations CHANGES, which are interpreted assuming the umask is
-   UMASK_VALUE.  If OLDMODE is a directory, the type `X'
-   change affects it even if no execute bits were set in OLDMODE.
-   The returned value has the S_IFMT bits cleared.  */
+/* Return the file mode bits of OLDMODE (which is the mode of a
+   directory if DIR), assuming the umask is UMASK_VALUE, adjusted as
+   indicated by the list of change operations CHANGES.  If DIR, the
+   type 'X' change affects the returned value even if no execute bits
+   were set in OLDMODE, and set user and group ID bits are preserved
+   unless CHANGES mentioned them.  If PMODE_BITS is not null, store into
+   *PMODE_BITS a mask denoting file mode bits that are affected by
+   CHANGES.
+
+   The returned value and *PMODE_BITS contain only file mode bits.
+   For example, they have the S_IFMT bits cleared on a standard
+   Unix-like host.  */
 
 mode_t
-mode_adjust (mode_t oldmode, struct mode_change const *changes,
-            mode_t umask_value)
+mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
+            struct mode_change const *changes, mode_t *pmode_bits)
 {
   /* The adjusted mode.  */
   mode_t newmode = oldmode & CHMOD_MODE_BITS;
 
+  /* File mode bits that CHANGES cares about.  */
+  mode_t mode_bits = 0;
+
   for (; changes->flag != MODE_DONE; changes++)
     {
       mode_t affected = changes->affected;
+      mode_t omit_change =
+       (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned;
       mode_t value = changes->value;
 
       switch (changes->flag)
@@ -319,14 +344,15 @@ mode_adjust (mode_t oldmode, struct mode_change const *changes,
        case MODE_X_IF_ANY_X:
          /* Affect the execute bits if execute bits are already set
             or if the file is a directory.  */
-         if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) || S_ISDIR (oldmode))
+         if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
            value |= S_IXUSR | S_IXGRP | S_IXOTH;
          break;
        }
 
       /* If WHO was specified, limit the change to the affected bits.
-        Otherwise, apply the umask.  */
-      value &= (affected ? affected : ~umask_value);
+        Otherwise, apply the umask.  Either way, omit changes as
+        requested.  */
+      value &= (affected ? affected : ~umask_value) & ~ omit_change;
 
       switch (changes->op)
        {
@@ -334,17 +360,26 @@ mode_adjust (mode_t oldmode, struct mode_change const *changes,
          /* If WHO was specified, preserve the previous values of
             bits that are not affected by this change operation.
             Otherwise, clear all the bits.  */
-         newmode = (affected ? newmode & ~affected : 0);
-         /* Fall through.  */
+         {
+           mode_t preserved = (affected ? ~affected : 0) | omit_change;
+           mode_bits |= CHMOD_MODE_BITS & ~preserved;
+           newmode = (newmode & preserved) | value;
+           break;
+         }
+
        case '+':
+         mode_bits |= value;
          newmode |= value;
          break;
 
        case '-':
+         mode_bits |= value;
          newmode &= ~value;
          break;
        }
     }
 
+  if (pmode_bits)
+    *pmode_bits = mode_bits;
   return newmode;
 }