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