1/* modechange.c -- file mode manipulation
2
3   Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005,
4   2006 Free Software 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
19
20/* Written by David MacKenzie <djm@ai.mit.edu> */
21
22/* The ASCII mode string is compiled into an array 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#include <config.h>
30
31#include "modechange.h"
32#include <sys/stat.h>
33#include "stat-macros.h"
34#include "xalloc.h"
35#include <stdlib.h>
36
37/* The traditional octal values corresponding to each mode bit.  */
38#define SUID 04000
39#define SGID 02000
40#define SVTX 01000
41#define RUSR 00400
42#define WUSR 00200
43#define XUSR 00100
44#define RGRP 00040
45#define WGRP 00020
46#define XGRP 00010
47#define ROTH 00004
48#define WOTH 00002
49#define XOTH 00001
50#define ALLM 07777 /* all octal mode bits */
51
52/* Convert OCTAL, which uses one of the traditional octal values, to
53   an internal mode_t value.  */
54static mode_t
55octal_to_mode (unsigned int octal)
56{
57  /* Help the compiler optimize the usual case where mode_t uses
58     the traditional octal representation.  */
59  return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX
60	   && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR
61	   && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP
62	   && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH)
63	  ? octal
64	  : (mode_t) ((octal & SUID ? S_ISUID : 0)
65		      | (octal & SGID ? S_ISGID : 0)
66		      | (octal & SVTX ? S_ISVTX : 0)
67		      | (octal & RUSR ? S_IRUSR : 0)
68		      | (octal & WUSR ? S_IWUSR : 0)
69		      | (octal & XUSR ? S_IXUSR : 0)
70		      | (octal & RGRP ? S_IRGRP : 0)
71		      | (octal & WGRP ? S_IWGRP : 0)
72		      | (octal & XGRP ? S_IXGRP : 0)
73		      | (octal & ROTH ? S_IROTH : 0)
74		      | (octal & WOTH ? S_IWOTH : 0)
75		      | (octal & XOTH ? S_IXOTH : 0)));
76}
77
78/* Special operations flags.  */
79enum
80  {
81    /* For the sentinel at the end of the mode changes array.  */
82    MODE_DONE,
83
84    /* The typical case.  */
85    MODE_ORDINARY_CHANGE,
86
87    /* In addition to the typical case, affect the execute bits if at
88       least one execute bit is set already, or if the file is a
89       directory.  */
90    MODE_X_IF_ANY_X,
91
92    /* Instead of the typical case, copy some existing permissions for
93       u, g, or o onto the other two.  Which of u, g, or o is copied
94       is determined by which bits are set in the `value' field.  */
95    MODE_COPY_EXISTING
96  };
97
98/* Description of a mode change.  */
99struct mode_change
100{
101  char op;			/* One of "=+-".  */
102  char flag;			/* Special operations flag.  */
103  mode_t affected;		/* Set for u, g, o, or a.  */
104  mode_t value;			/* Bits to add/remove.  */
105  mode_t mentioned;		/* Bits explicitly mentioned.  */
106};
107
108/* Return a mode_change array with the specified `=ddd'-style
109   mode change operation, where NEW_MODE is `ddd' and MENTIONED
110   contains the bits explicitly mentioned in the mode are MENTIONED.  */
111
112static struct mode_change *
113make_node_op_equals (mode_t new_mode, mode_t mentioned)
114{
115  struct mode_change *p = xmalloc (2 * sizeof *p);
116  p->op = '=';
117  p->flag = MODE_ORDINARY_CHANGE;
118  p->affected = CHMOD_MODE_BITS;
119  p->value = new_mode;
120  p->mentioned = mentioned;
121  p[1].flag = MODE_DONE;
122  return p;
123}
124
125/* Return a pointer to an array of file mode change operations created from
126   MODE_STRING, an ASCII string that contains either an octal number
127   specifying an absolute mode, or symbolic mode change operations with
128   the form:
129   [ugoa...][[+-=][rwxXstugo...]...][,...]
130
131   Return NULL if `mode_string' does not contain a valid
132   representation of file mode change operations.  */
133
134struct mode_change *
135mode_compile (char const *mode_string)
136{
137  /* The array of mode-change directives to be returned.  */
138  struct mode_change *mc;
139  size_t used = 0;
140
141  if ('0' <= *mode_string && *mode_string < '8')
142    {
143      unsigned int octal_mode = 0;
144      mode_t mode;
145      mode_t mentioned;
146
147      do
148	{
149	  octal_mode = 8 * octal_mode + *mode_string++ - '0';
150	  if (ALLM < octal_mode)
151	    return NULL;
152	}
153      while ('0' <= *mode_string && *mode_string < '8');
154
155      if (*mode_string)
156	return NULL;
157
158      mode = octal_to_mode (octal_mode);
159      mentioned = (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO;
160      return make_node_op_equals (mode, mentioned);
161    }
162
163  /* Allocate enough space to hold the result.  */
164  {
165    size_t needed = 1;
166    char const *p;
167    for (p = mode_string; *p; p++)
168      needed += (*p == '=' || *p == '+' || *p == '-');
169    mc = xnmalloc (needed, sizeof *mc);
170  }
171
172  /* One loop iteration for each `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'.  */
173  for (;; mode_string++)
174    {
175      /* Which bits in the mode are operated on.  */
176      mode_t affected = 0;
177
178      /* Turn on all the bits in `affected' for each group given.  */
179      for (;; mode_string++)
180	switch (*mode_string)
181	  {
182	  default:
183	    goto invalid;
184	  case 'u':
185	    affected |= S_ISUID | S_IRWXU;
186	    break;
187	  case 'g':
188	    affected |= S_ISGID | S_IRWXG;
189	    break;
190	  case 'o':
191	    affected |= S_ISVTX | S_IRWXO;
192	    break;
193	  case 'a':
194	    affected |= CHMOD_MODE_BITS;
195	    break;
196	  case '=': case '+': case '-':
197	    goto no_more_affected;
198	  }
199    no_more_affected:;
200
201      do
202	{
203	  char op = *mode_string++;
204	  mode_t value;
205	  char flag = MODE_COPY_EXISTING;
206	  struct mode_change *change;
207
208	  switch (*mode_string++)
209	    {
210	    case 'u':
211	      /* Set the affected bits to the value of the `u' bits
212		 on the same file.  */
213	      value = S_IRWXU;
214	      break;
215	    case 'g':
216	      /* Set the affected bits to the value of the `g' bits
217		 on the same file.  */
218	      value = S_IRWXG;
219	      break;
220	    case 'o':
221	      /* Set the affected bits to the value of the `o' bits
222		 on the same file.  */
223	      value = S_IRWXO;
224	      break;
225
226	    default:
227	      value = 0;
228	      flag = MODE_ORDINARY_CHANGE;
229
230	      for (mode_string--;; mode_string++)
231		switch (*mode_string)
232		  {
233		  case 'r':
234		    value |= S_IRUSR | S_IRGRP | S_IROTH;
235		    break;
236		  case 'w':
237		    value |= S_IWUSR | S_IWGRP | S_IWOTH;
238		    break;
239		  case 'x':
240		    value |= S_IXUSR | S_IXGRP | S_IXOTH;
241		    break;
242		  case 'X':
243		    flag = MODE_X_IF_ANY_X;
244		    break;
245		  case 's':
246		    /* Set the setuid/gid bits if `u' or `g' is selected.  */
247		    value |= S_ISUID | S_ISGID;
248		    break;
249		  case 't':
250		    /* Set the "save text image" bit if `o' is selected.  */
251		    value |= S_ISVTX;
252		    break;
253		  default:
254		    goto no_more_values;
255		  }
256	    no_more_values:;
257	    }
258
259	  change = &mc[used++];
260	  change->op = op;
261	  change->flag = flag;
262	  change->affected = affected;
263	  change->value = value;
264	  change->mentioned = (affected ? affected & value : value);
265	}
266      while (*mode_string == '=' || *mode_string == '+'
267	     || *mode_string == '-');
268
269      if (*mode_string != ',')
270	break;
271    }
272
273  if (*mode_string == 0)
274    {
275      mc[used].flag = MODE_DONE;
276      return mc;
277    }
278
279invalid:
280  free (mc);
281  return NULL;
282}
283
284/* Return a file mode change operation that sets permissions to match those
285   of REF_FILE.  Return NULL (setting errno) if REF_FILE can't be accessed.  */
286
287struct mode_change *
288mode_create_from_ref (const char *ref_file)
289{
290  struct stat ref_stats;
291
292  if (stat (ref_file, &ref_stats) != 0)
293    return NULL;
294  return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
295}
296
297/* Return the file mode bits of OLDMODE (which is the mode of a
298   directory if DIR), assuming the umask is UMASK_VALUE, adjusted as
299   indicated by the list of change operations CHANGES.  If DIR, the
300   type 'X' change affects the returned value even if no execute bits
301   were set in OLDMODE, and set user and group ID bits are preserved
302   unless CHANGES mentioned them.  If PMODE_BITS is not null, store into
303   *PMODE_BITS a mask denoting file mode bits that are affected by
304   CHANGES.
305
306   The returned value and *PMODE_BITS contain only file mode bits.
307   For example, they have the S_IFMT bits cleared on a standard
308   Unix-like host.  */
309
310mode_t
311mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
312	     struct mode_change const *changes, mode_t *pmode_bits)
313{
314  /* The adjusted mode.  */
315  mode_t newmode = oldmode & CHMOD_MODE_BITS;
316
317  /* File mode bits that CHANGES cares about.  */
318  mode_t mode_bits = 0;
319
320  for (; changes->flag != MODE_DONE; changes++)
321    {
322      mode_t affected = changes->affected;
323      mode_t omit_change =
324	(dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned;
325      mode_t value = changes->value;
326
327      switch (changes->flag)
328	{
329	case MODE_ORDINARY_CHANGE:
330	  break;
331
332	case MODE_COPY_EXISTING:
333	  /* Isolate in `value' the bits in `newmode' to copy.  */
334	  value &= newmode;
335
336	  /* Copy the isolated bits to the other two parts.  */
337	  value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH)
338		     ? S_IRUSR | S_IRGRP | S_IROTH : 0)
339		    | (value & (S_IWUSR | S_IWGRP | S_IWOTH)
340		       ? S_IWUSR | S_IWGRP | S_IWOTH : 0)
341		    | (value & (S_IXUSR | S_IXGRP | S_IXOTH)
342		       ? S_IXUSR | S_IXGRP | S_IXOTH : 0));
343	  break;
344
345	case MODE_X_IF_ANY_X:
346	  /* Affect the execute bits if execute bits are already set
347	     or if the file is a directory.  */
348	  if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
349	    value |= S_IXUSR | S_IXGRP | S_IXOTH;
350	  break;
351	}
352
353      /* If WHO was specified, limit the change to the affected bits.
354	 Otherwise, apply the umask.  Either way, omit changes as
355	 requested.  */
356      value &= (affected ? affected : ~umask_value) & ~ omit_change;
357
358      switch (changes->op)
359	{
360	case '=':
361	  /* If WHO was specified, preserve the previous values of
362	     bits that are not affected by this change operation.
363	     Otherwise, clear all the bits.  */
364	  {
365	    mode_t preserved = (affected ? ~affected : 0) | omit_change;
366	    mode_bits |= CHMOD_MODE_BITS & ~preserved;
367	    newmode = (newmode & preserved) | value;
368	    break;
369	  }
370
371	case '+':
372	  mode_bits |= value;
373	  newmode |= value;
374	  break;
375
376	case '-':
377	  mode_bits |= value;
378	  newmode &= ~value;
379	  break;
380	}
381    }
382
383  if (pmode_bits)
384    *pmode_bits = mode_bits;
385  return newmode;
386}
387