source: trunk/third/findutils/lib/modechange.c @ 18890

Revision 18890, 11.9 KB checked in by zacheiss, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18889, which included commits to RCS files with non-trunk default branches.
Line 
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 "modechange.h"
32#include <sys/stat.h>
33#include "xstrtol.h"
34
35#if STDC_HEADERS
36# include <stdlib.h>
37#else
38char *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
110static struct mode_change *
111make_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
128static void
129mode_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
155struct mode_change *
156mode_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;
326invalid:
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
334struct mode_change *
335mode_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
362mode_t
363mode_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
434void
435mode_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}
Note: See TracBrowser for help on using the repository browser.