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