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