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