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