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