(mode_compile): Use uintmax_t, not unsigned
[gnulib.git] / lib / modechange.c
1 /* modechange.c -- file mode manipulation
2    Copyright (C) 1989, 1990, 1997-2000 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Written by David MacKenzie <djm@ai.mit.edu> */
19
20 /* The ASCII mode string is compiled into a linked list of `struct
21    modechange', which can then be applied to each file to be changed.
22    We do this instead of re-parsing the ASCII string for each file
23    because the compiled form requires less computation to use; when
24    changing the mode of many files, this probably results in a
25    performance gain. */
26
27 #if HAVE_CONFIG_H
28 # include <config.h>
29 #endif
30
31 #include "modechange.h"
32 #include <sys/stat.h>
33 #include "xstrtol.h"
34
35 #if STDC_HEADERS
36 # include <stdlib.h>
37 #else
38 char *malloc ();
39 #endif
40
41 #ifndef NULL
42 # define NULL 0
43 #endif
44
45 #if STAT_MACROS_BROKEN
46 # undef S_ISDIR
47 #endif
48
49 #if !defined(S_ISDIR) && defined(S_IFDIR)
50 # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
51 #endif
52
53 #ifndef S_ISUID
54 # define S_ISUID 04000
55 #endif
56 #ifndef S_ISGID
57 # define S_ISGID 04000
58 #endif
59 #ifndef S_ISVTX
60 # define S_ISVTX 01000
61 #endif
62 #ifndef S_IRUSR
63 # define S_IRUSR 0400
64 #endif
65 #ifndef S_IWUSR
66 # define S_IWUSR 0200
67 #endif
68 #ifndef S_IXUSR
69 # define S_IXUSR 0100
70 #endif
71 #ifndef S_IRGRP
72 # define S_IRGRP 0040
73 #endif
74 #ifndef S_IWGRP
75 # define S_IWGRP 0020
76 #endif
77 #ifndef S_IXGRP
78 # define S_IXGRP 0010
79 #endif
80 #ifndef S_IROTH
81 # define S_IROTH 0004
82 #endif
83 #ifndef S_IWOTH
84 # define S_IWOTH 0002
85 #endif
86 #ifndef S_IXOTH
87 # define S_IXOTH 0001
88 #endif
89 #ifndef S_IRWXU
90 # define S_IRWXU 0700
91 #endif
92 #ifndef S_IRWXG
93 # define S_IRWXG 0070
94 #endif
95 #ifndef S_IRWXO
96 # define S_IRWXO 0007
97 #endif
98
99 /* All the mode bits that can be affected by chmod.  */
100 #define CHMOD_MODE_BITS \
101   (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
102
103 /* Return newly allocated memory to hold one element of type TYPE. */
104 #define talloc(type) ((type *) malloc (sizeof (type)))
105
106 /* Create a mode_change entry with the specified `=ddd'-style
107    mode change operation, where NEW_MODE is `ddd'.  Return the
108    new entry, or NULL upon failure.  */
109
110 static struct mode_change *
111 make_node_op_equals (mode_t new_mode)
112 {
113   struct mode_change *p;
114   p = talloc (struct mode_change);
115   if (p == NULL)
116     return p;
117   p->next = NULL;
118   p->op = '=';
119   p->flags = 0;
120   p->value = new_mode;
121   p->affected = CHMOD_MODE_BITS;        /* Affect all permissions. */
122   return p;
123 }
124
125 /* Append entry E to the end of the link list with the specified
126    HEAD and TAIL.  */
127
128 static void
129 mode_append_entry (struct mode_change **head,
130                    struct mode_change **tail,
131                    struct mode_change *e)
132 {
133   if (*head == NULL)
134     *head = *tail = e;
135   else
136     {
137       (*tail)->next = e;
138       *tail = e;
139     }
140 }
141
142 /* Return a linked list of file mode change operations created from
143    MODE_STRING, an ASCII string that contains either an octal number
144    specifying an absolute mode, or symbolic mode change operations with
145    the form:
146    [ugoa...][[+-=][rwxXstugo...]...][,...]
147    MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-)
148    should not affect bits set in the umask when no users are given.
149    Operators not selected in MASKED_OPS ignore the umask.
150
151    Return MODE_INVALID if `mode_string' does not contain a valid
152    representation of file mode change operations;
153    return MODE_MEMORY_EXHAUSTED if there is insufficient memory. */
154
155 struct mode_change *
156 mode_compile (const char *mode_string, unsigned int masked_ops)
157 {
158   struct mode_change *head;     /* First element of the linked list. */
159   struct mode_change *tail;     /* An element of the linked list. */
160   uintmax_t mode_value;         /* The mode value, if octal.  */
161   char *string_end;             /* Pointer to end of parsed value.  */
162   mode_t umask_value;           /* The umask value (surprise). */
163
164   head = NULL;
165 #ifdef lint
166   tail = NULL;
167 #endif
168
169   if (xstrtoumax (mode_string, &string_end, 8, &mode_value, "") == LONGINT_OK)
170     {
171       struct mode_change *p;
172       if (mode_value != (mode_value & CHMOD_MODE_BITS))
173         return MODE_INVALID;
174       p = make_node_op_equals ((mode_t) mode_value);
175       if (p == NULL)
176         return MODE_MEMORY_EXHAUSTED;
177       mode_append_entry (&head, &tail, p);
178       return head;
179     }
180
181   umask_value = umask (0);
182   umask (umask_value);          /* Restore the old value. */
183   --mode_string;
184
185   /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */
186   do
187     {
188       /* Which bits in the mode are operated on. */
189       mode_t affected_bits = 0;
190       /* `affected_bits' modified by umask. */
191       mode_t affected_masked;
192       /* Operators to actually use umask on. */
193       unsigned ops_to_mask = 0;
194
195       int who_specified_p;
196
197       affected_bits = 0;
198       ops_to_mask = 0;
199       /* Turn on all the bits in `affected_bits' for each group given. */
200       for (++mode_string;; ++mode_string)
201         switch (*mode_string)
202           {
203           case 'u':
204             affected_bits |= S_ISUID | S_IRWXU;
205             break;
206           case 'g':
207             affected_bits |= S_ISGID | S_IRWXG;
208             break;
209           case 'o':
210             affected_bits |= S_ISVTX | S_IRWXO;
211             break;
212           case 'a':
213             affected_bits |= CHMOD_MODE_BITS;
214             break;
215           default:
216             goto no_more_affected;
217           }
218
219     no_more_affected:
220       /* If none specified, affect all bits, except perhaps those
221          set in the umask. */
222       if (affected_bits)
223         who_specified_p = 1;
224       else
225         {
226           who_specified_p = 0;
227           affected_bits = CHMOD_MODE_BITS;
228           ops_to_mask = masked_ops;
229         }
230
231       while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-')
232         {
233           struct mode_change *change = talloc (struct mode_change);
234           if (change == NULL)
235             {
236               mode_free (head);
237               return MODE_MEMORY_EXHAUSTED;
238             }
239
240           change->next = NULL;
241           change->op = *mode_string;    /* One of "=+-". */
242           affected_masked = affected_bits;
243
244           /* Per the Single Unix Spec, if `who' is not specified and the
245              `=' operator is used, then clear all the bits first.  */
246           if (!who_specified_p &&
247               ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS : 0))
248             {
249               struct mode_change *p = make_node_op_equals (0);
250               if (p == NULL)
251                 return MODE_MEMORY_EXHAUSTED;
252               mode_append_entry (&head, &tail, p);
253             }
254
255           if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS
256                              : *mode_string == '+' ? MODE_MASK_PLUS
257                              : MODE_MASK_MINUS))
258             affected_masked &= ~umask_value;
259           change->affected = affected_masked;
260           change->value = 0;
261           change->flags = 0;
262
263           /* Add the element to the tail of the list, so the operations
264              are performed in the correct order. */
265           mode_append_entry (&head, &tail, change);
266
267           /* Set `value' according to the bits set in `affected_masked'. */
268           for (++mode_string;; ++mode_string)
269             switch (*mode_string)
270               {
271               case 'r':
272                 change->value |= ((S_IRUSR | S_IRGRP | S_IROTH)
273                                   & affected_masked);
274                 break;
275               case 'w':
276                 change->value |= ((S_IWUSR | S_IWGRP | S_IWOTH)
277                                   & affected_masked);
278                 break;
279               case 'X':
280                 change->flags |= MODE_X_IF_ANY_X;
281                 /* Fall through. */
282               case 'x':
283                 change->value |= ((S_IXUSR | S_IXGRP | S_IXOTH)
284                                   & affected_masked);
285                 break;
286               case 's':
287                 /* Set the setuid/gid bits if `u' or `g' is selected. */
288                 change->value |= (S_ISUID | S_ISGID) & affected_masked;
289                 break;
290               case 't':
291                 /* Set the "save text image" bit if `o' is selected. */
292                 change->value |= S_ISVTX & affected_masked;
293                 break;
294               case 'u':
295                 /* Set the affected bits to the value of the `u' bits
296                    on the same file.  */
297                 if (change->value)
298                   goto invalid;
299                 change->value = S_IRWXU;
300                 change->flags |= MODE_COPY_EXISTING;
301                 break;
302               case 'g':
303                 /* Set the affected bits to the value of the `g' bits
304                    on the same file.  */
305                 if (change->value)
306                   goto invalid;
307                 change->value = S_IRWXG;
308                 change->flags |= MODE_COPY_EXISTING;
309                 break;
310               case 'o':
311                 /* Set the affected bits to the value of the `o' bits
312                    on the same file.  */
313                 if (change->value)
314                   goto invalid;
315                 change->value = S_IRWXO;
316                 change->flags |= MODE_COPY_EXISTING;
317                 break;
318               default:
319                 goto no_more_values;
320               }
321         no_more_values:;
322         }
323   } while (*mode_string == ',');
324   if (*mode_string == 0)
325     return head;
326 invalid:
327   mode_free (head);
328   return MODE_INVALID;
329 }
330
331 /* Return a file mode change operation that sets permissions to match those
332    of REF_FILE.  Return MODE_BAD_REFERENCE if REF_FILE can't be accessed.  */
333
334 struct mode_change *
335 mode_create_from_ref (const char *ref_file)
336 {
337   struct mode_change *change;   /* the only change element */
338   struct stat ref_stats;
339
340   if (stat (ref_file, &ref_stats))
341     return MODE_BAD_REFERENCE;
342
343   change = talloc (struct mode_change);
344
345   if (change == NULL)
346     return MODE_MEMORY_EXHAUSTED;
347
348   change->op = '=';
349   change->flags = 0;
350   change->affected = CHMOD_MODE_BITS;
351   change->value = ref_stats.st_mode;
352   change->next = NULL;
353
354   return change;
355 }
356
357 /* Return file mode OLDMODE, adjusted as indicated by the list of change
358    operations CHANGES.  If OLDMODE is a directory, the type `X'
359    change affects it even if no execute bits were set in OLDMODE.
360    The returned value has the S_IFMT bits cleared. */
361
362 mode_t
363 mode_adjust (mode_t oldmode, const struct mode_change *changes)
364 {
365   mode_t newmode;       /* The adjusted mode and one operand. */
366   mode_t value;         /* The other operand. */
367
368   newmode = oldmode & CHMOD_MODE_BITS;
369
370   for (; changes; changes = changes->next)
371     {
372       if (changes->flags & MODE_COPY_EXISTING)
373         {
374           /* Isolate in `value' the bits in `newmode' to copy, given in
375              the mask `changes->value'. */
376           value = newmode & changes->value;
377
378           if (changes->value & S_IRWXU)
379             /* Copy `u' permissions onto `g' and `o'. */
380             value |= ((value & S_IRUSR ? S_IRGRP | S_IROTH : 0)
381                       | (value & S_IWUSR ? S_IWGRP | S_IROTH : 0)
382                       | (value & S_IXUSR ? S_IXGRP | S_IXOTH : 0));
383           else if (changes->value & S_IRWXG)
384             /* Copy `g' permissions onto `u' and `o'. */
385             value |= ((value & S_IRGRP ? S_IRUSR | S_IROTH : 0)
386                       | (value & S_IWGRP ? S_IWUSR | S_IROTH : 0)
387                       | (value & S_IXGRP ? S_IXUSR | S_IXOTH : 0));
388           else
389             /* Copy `o' permissions onto `u' and `g'. */
390             value |= ((value & S_IROTH ? S_IRUSR | S_IRGRP : 0)
391                       | (value & S_IWOTH ? S_IWUSR | S_IRGRP : 0)
392                       | (value & S_IXOTH ? S_IXUSR | S_IXGRP : 0));
393
394           /* In order to change only `u', `g', or `o' permissions,
395              or some combination thereof, clear unselected bits.
396              This can not be done in mode_compile because the value
397              to which the `changes->affected' mask is applied depends
398              on the old mode of each file. */
399           value &= changes->affected;
400         }
401       else
402         {
403           value = changes->value;
404           /* If `X', do not affect the execute bits if the file is not a
405              directory and no execute bits are already set. */
406           if ((changes->flags & MODE_X_IF_ANY_X)
407               && !S_ISDIR (oldmode)
408               && (newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
409             /* Clear the execute bits. */
410             value &= ~ (S_IXUSR | S_IXGRP | S_IXOTH);
411         }
412
413       switch (changes->op)
414         {
415         case '=':
416           /* Preserve the previous values in `newmode' of bits that are
417              not affected by this change operation. */
418           newmode = (newmode & ~changes->affected) | value;
419           break;
420         case '+':
421           newmode |= value;
422           break;
423         case '-':
424           newmode &= ~value;
425           break;
426         }
427     }
428   return newmode;
429 }
430
431 /* Free the memory used by the list of file mode change operations
432    CHANGES. */
433
434 void
435 mode_free (register struct mode_change *changes)
436 {
437   register struct mode_change *next;
438
439   while (changes)
440     {
441       next = changes->next;
442       free (changes);
443       changes = next;
444     }
445 }