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