(mode_compile): Upon allocation failure, free
[gnulib.git] / lib / modechange.c
1 /* modechange.c -- file mode manipulation
2    Copyright (C) 1989, 1990, 1997, 1998, 1999 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 <sys/types.h>
32 #include <sys/stat.h>
33 #include "modechange.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 /* Return newly allocated memory to hold one element of type TYPE. */
54 #define talloc(type) ((type *) malloc (sizeof (type)))
55
56 #define isodigit(c) ((c) >= '0' && (c) <= '7')
57
58 /* Return a positive integer containing the value of the ASCII
59    octal number S.  If S is not an octal number, return -1.  */
60
61 static int
62 oatoi (const char *s)
63 {
64   register int i;
65
66   if (*s == 0)
67     return -1;
68   for (i = 0; isodigit (*s); ++s)
69     i = i * 8 + *s - '0';
70   if (*s)
71     return -1;
72   return i;
73 }
74
75 /* Return a linked list of file mode change operations created from
76    MODE_STRING, an ASCII string that contains either an octal number
77    specifying an absolute mode, or symbolic mode change operations with
78    the form:
79    [ugoa...][[+-=][rwxXstugo...]...][,...]
80    MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-)
81    should not affect bits set in the umask when no users are given.
82    Operators not selected in MASKED_OPS ignore the umask.
83
84    Return MODE_INVALID if `mode_string' does not contain a valid
85    representation of file mode change operations;
86    return MODE_MEMORY_EXHAUSTED if there is insufficient memory. */
87
88 struct mode_change *
89 mode_compile (const char *mode_string, unsigned int masked_ops)
90 {
91   struct mode_change *head;     /* First element of the linked list. */
92   struct mode_change *change;   /* An element of the linked list. */
93   int i;                        /* General purpose temporary. */
94   int umask_value;              /* The umask value (surprise). */
95   unsigned short affected_bits; /* Which bits in the mode are operated on. */
96   unsigned short affected_masked; /* `affected_bits' modified by umask. */
97   unsigned ops_to_mask;         /* Operators to actually use umask on. */
98
99   i = oatoi (mode_string);
100   if (i >= 0)
101     {
102       if (i > 07777)
103         return MODE_INVALID;
104       head = talloc (struct mode_change);
105       if (head == NULL)
106         return MODE_MEMORY_EXHAUSTED;
107       head->next = NULL;
108       head->op = '=';
109       head->flags = 0;
110       head->value = i;
111       head->affected = 07777;   /* Affect all permissions. */
112       return head;
113     }
114
115   umask_value = umask (0);
116   umask (umask_value);          /* Restore the old value. */
117
118   head = NULL;
119 #ifdef lint
120   change = NULL;
121 #endif
122   --mode_string;
123
124   /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */
125   do
126     {
127       affected_bits = 0;
128       ops_to_mask = 0;
129       /* Turn on all the bits in `affected_bits' for each group given. */
130       for (++mode_string;; ++mode_string)
131         switch (*mode_string)
132           {
133           case 'u':
134             affected_bits |= 04700;
135             break;
136           case 'g':
137             affected_bits |= 02070;
138             break;
139           case 'o':
140             affected_bits |= 01007;
141             break;
142           case 'a':
143             affected_bits |= 07777;
144             break;
145           default:
146             goto no_more_affected;
147           }
148
149     no_more_affected:
150       /* If none specified, affect all bits, except perhaps those
151          set in the umask. */
152       if (affected_bits == 0)
153         {
154           affected_bits = 07777;
155           ops_to_mask = masked_ops;
156         }
157
158       while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-')
159         {
160           /* Add the element to the tail of the list, so the operations
161              are performed in the correct order. */
162           if (head == NULL)
163             {
164               head = talloc (struct mode_change);
165               if (head == NULL)
166                 return MODE_MEMORY_EXHAUSTED;
167               change = head;
168             }
169           else
170             {
171               change->next = talloc (struct mode_change);
172               if (change->next == NULL)
173                 {
174                   mode_free (head);
175                   return MODE_MEMORY_EXHAUSTED;
176                 }
177               change = change->next;
178             }
179
180           change->next = NULL;
181           change->op = *mode_string;    /* One of "=+-". */
182           affected_masked = affected_bits;
183           if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS
184                              : *mode_string == '+' ? MODE_MASK_PLUS
185                              : MODE_MASK_MINUS))
186             affected_masked &= ~umask_value;
187           change->affected = affected_masked;
188           change->value = 0;
189           change->flags = 0;
190
191           /* Set `value' according to the bits set in `affected_masked'. */
192           for (++mode_string;; ++mode_string)
193             switch (*mode_string)
194               {
195               case 'r':
196                 change->value |= 00444 & affected_masked;
197                 break;
198               case 'w':
199                 change->value |= 00222 & affected_masked;
200                 break;
201               case 'X':
202                 change->flags |= MODE_X_IF_ANY_X;
203                 /* Fall through. */
204               case 'x':
205                 change->value |= 00111 & affected_masked;
206                 break;
207               case 's':
208                 /* Set the setuid/gid bits if `u' or `g' is selected. */
209                 change->value |= 06000 & affected_masked;
210                 break;
211               case 't':
212                 /* Set the "save text image" bit if `o' is selected. */
213                 change->value |= 01000 & affected_masked;
214                 break;
215               case 'u':
216                 /* Set the affected bits to the value of the `u' bits
217                    on the same file.  */
218                 if (change->value)
219                   goto invalid;
220                 change->value = 00700;
221                 change->flags |= MODE_COPY_EXISTING;
222                 break;
223               case 'g':
224                 /* Set the affected bits to the value of the `g' bits
225                    on the same file.  */
226                 if (change->value)
227                   goto invalid;
228                 change->value = 00070;
229                 change->flags |= MODE_COPY_EXISTING;
230                 break;
231               case 'o':
232                 /* Set the affected bits to the value of the `o' bits
233                    on the same file.  */
234                 if (change->value)
235                   goto invalid;
236                 change->value = 00007;
237                 change->flags |= MODE_COPY_EXISTING;
238                 break;
239               default:
240                 goto no_more_values;
241               }
242         no_more_values:;
243         }
244   } while (*mode_string == ',');
245   if (*mode_string == 0)
246     return head;
247 invalid:
248   mode_free (head);
249   return MODE_INVALID;
250 }
251
252 /* Return a file mode change operation that sets permissions to match those
253    of REF_FILE.  Return MODE_BAD_REFERENCE if REF_FILE can't be accessed.  */
254
255 struct mode_change *
256 mode_create_from_ref (const char *ref_file)
257 {
258   struct mode_change *change;   /* the only change element */
259   struct stat ref_stats;
260
261   if (stat (ref_file, &ref_stats))
262     return MODE_BAD_REFERENCE;
263
264   change = talloc (struct mode_change);
265
266   if (change == NULL)
267     return MODE_MEMORY_EXHAUSTED;
268
269   change->op = '=';
270   change->flags = 0;
271   change->affected = 07777;
272   change->value = ref_stats.st_mode;
273   change->next = NULL;
274
275   return change;
276 }
277
278 /* Return file mode OLDMODE, adjusted as indicated by the list of change
279    operations CHANGES.  If OLDMODE is a directory, the type `X'
280    change affects it even if no execute bits were set in OLDMODE.
281    The returned value has the S_IFMT bits cleared. */
282
283 unsigned short
284 mode_adjust (unsigned int oldmode, const struct mode_change *changes)
285 {
286   unsigned short newmode;       /* The adjusted mode and one operand. */
287   unsigned short value;         /* The other operand. */
288
289   newmode = oldmode & 07777;
290
291   for (; changes; changes = changes->next)
292     {
293       if (changes->flags & MODE_COPY_EXISTING)
294         {
295           /* Isolate in `value' the bits in `newmode' to copy, given in
296              the mask `changes->value'. */
297           value = newmode & changes->value;
298
299           if (changes->value & 00700)
300             /* Copy `u' permissions onto `g' and `o'. */
301             value |= (value >> 3) | (value >> 6);
302           else if (changes->value & 00070)
303             /* Copy `g' permissions onto `u' and `o'. */
304             value |= (value << 3) | (value >> 3);
305           else
306             /* Copy `o' permissions onto `u' and `g'. */
307             value |= (value << 3) | (value << 6);
308
309           /* In order to change only `u', `g', or `o' permissions,
310              or some combination thereof, clear unselected bits.
311              This can not be done in mode_compile because the value
312              to which the `changes->affected' mask is applied depends
313              on the old mode of each file. */
314           value &= changes->affected;
315         }
316       else
317         {
318           value = changes->value;
319           /* If `X', do not affect the execute bits if the file is not a
320              directory and no execute bits are already set. */
321           if ((changes->flags & MODE_X_IF_ANY_X)
322               && !S_ISDIR (oldmode)
323               && (newmode & 00111) == 0)
324             value &= ~00111;    /* Clear the execute bits. */
325         }
326
327       switch (changes->op)
328         {
329         case '=':
330           /* Preserve the previous values in `newmode' of bits that are
331              not affected by this change operation. */
332           newmode = (newmode & ~changes->affected) | value;
333           break;
334         case '+':
335           newmode |= value;
336           break;
337         case '-':
338           newmode &= ~value;
339           break;
340         }
341     }
342   return newmode;
343 }
344
345 /* Free the memory used by the list of file mode change operations
346    CHANGES. */
347
348 void
349 mode_free (register struct mode_change *changes)
350 {
351   register struct mode_change *next;
352
353   while (changes)
354     {
355       next = changes->next;
356       free (changes);
357       changes = next;
358     }
359 }