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