1 /* modechange.c -- file mode manipulation
2
3 Copyright (C) 1989-1990, 1997-1999, 2001, 2003-2006, 2009-2020 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 3 of the License, or
9 (at your option) 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, see <https://www.gnu.org/licenses/>. */
18
19 /* Written by David MacKenzie <djm@ai.mit.edu> */
20
21 /* The ASCII mode string is compiled into an array of 'struct
22 modechange', which can then be applied to each file to be changed.
23 We do this instead of re-parsing the ASCII string for each file
24 because the compiled form requires less computation to use; when
25 changing the mode of many files, this probably results in a
26 performance gain. */
27
28 #include <config.h>
29
30 #include "modechange.h"
31 #include <sys/stat.h>
32 #include "stat-macros.h"
33 #include "xalloc.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 = xmalloc (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 char const *p;
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 p = mode_string;
148 do
149 {
150 octal_mode = 8 * octal_mode + *p++ - '0';
151 if (ALLM < octal_mode)
152 return NULL;
153 }
154 while ('0' <= *p && *p < '8');
155
156 if (*p)
157 return NULL;
158
159 mode = octal_to_mode (octal_mode);
160 mentioned = (p - mode_string < 5
161 ? (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO
162 : CHMOD_MODE_BITS);
163 return make_node_op_equals (mode, mentioned);
164 }
165
166 /* Allocate enough space to hold the result. */
167 {
168 size_t needed = 1;
169 for (p = mode_string; *p; p++)
170 needed += (*p == '=' || *p == '+' || *p == '-');
171 mc = xnmalloc (needed, sizeof *mc);
172 }
173
174 /* One loop iteration for each
175 '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'. */
176 for (p = mode_string; ; p++)
177 {
178 /* Which bits in the mode are operated on. */
179 mode_t affected = 0;
180
181 /* Turn on all the bits in 'affected' for each group given. */
182 for (;; p++)
183 switch (*p)
184 {
185 default:
186 goto invalid;
187 case 'u':
188 affected |= S_ISUID | S_IRWXU;
189 break;
190 case 'g':
191 affected |= S_ISGID | S_IRWXG;
192 break;
193 case 'o':
194 affected |= S_ISVTX | S_IRWXO;
195 break;
196 case 'a':
197 affected |= CHMOD_MODE_BITS;
198 break;
199 case '=': case '+': case '-':
200 goto no_more_affected;
201 }
202 no_more_affected:;
203
204 do
205 {
206 char op = *p++;
207 mode_t value;
208 mode_t mentioned = 0;
209 char flag = MODE_COPY_EXISTING;
210 struct mode_change *change;
211
212 switch (*p)
213 {
214 case '0': case '1': case '2': case '3':
215 case '4': case '5': case '6': case '7':
216 {
217 unsigned int octal_mode = 0;
218
219 do
220 {
221 octal_mode = 8 * octal_mode + *p++ - '0';
222 if (ALLM < octal_mode)
223 goto invalid;
224 }
225 while ('0' <= *p && *p < '8');
226
227 if (affected || (*p && *p != ','))
228 goto invalid;
229 affected = mentioned = CHMOD_MODE_BITS;
230 value = octal_to_mode (octal_mode);
231 flag = MODE_ORDINARY_CHANGE;
232 break;
233 }
234
235 case 'u':
236 /* Set the affected bits to the value of the "u" bits
237 on the same file. */
238 value = S_IRWXU;
239 p++;
240 break;
241 case 'g':
242 /* Set the affected bits to the value of the "g" bits
243 on the same file. */
244 value = S_IRWXG;
245 p++;
246 break;
247 case 'o':
248 /* Set the affected bits to the value of the "o" bits
249 on the same file. */
250 value = S_IRWXO;
251 p++;
252 break;
253
254 default:
255 value = 0;
256 flag = MODE_ORDINARY_CHANGE;
257
258 for (;; p++)
259 switch (*p)
260 {
261 case 'r':
262 value |= S_IRUSR | S_IRGRP | S_IROTH;
263 break;
264 case 'w':
265 value |= S_IWUSR | S_IWGRP | S_IWOTH;
266 break;
267 case 'x':
268 value |= S_IXUSR | S_IXGRP | S_IXOTH;
269 break;
270 case 'X':
271 flag = MODE_X_IF_ANY_X;
272 break;
273 case 's':
274 /* Set the setuid/gid bits if 'u' or 'g' is selected. */
275 value |= S_ISUID | S_ISGID;
276 break;
277 case 't':
278 /* Set the "save text image" bit if 'o' is selected. */
279 value |= S_ISVTX;
280 break;
281 default:
282 goto no_more_values;
283 }
284 no_more_values:;
285 }
286
287 change = &mc[used++];
288 change->op = op;
289 change->flag = flag;
290 change->affected = affected;
291 change->value = value;
292 change->mentioned =
293 (mentioned ? mentioned : affected ? affected & value : value);
294 }
295 while (*p == '=' || *p == '+' || *p == '-');
296
297 if (*p != ',')
298 break;
299 }
300
301 if (*p == 0)
302 {
303 mc[used].flag = MODE_DONE;
304 return mc;
305 }
306
307 invalid:
308 free (mc);
309 return NULL;
310 }
311
312 /* Return a file mode change operation that sets permissions to match those
313 of REF_FILE. Return NULL (setting errno) if REF_FILE can't be accessed. */
314
315 struct mode_change *
mode_create_from_ref(const char * ref_file)316 mode_create_from_ref (const char *ref_file)
317 {
318 struct stat ref_stats;
319
320 if (stat (ref_file, &ref_stats) != 0)
321 return NULL;
322 return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
323 }
324
325 /* Return the file mode bits of OLDMODE (which is the mode of a
326 directory if DIR), assuming the umask is UMASK_VALUE, adjusted as
327 indicated by the list of change operations CHANGES. If DIR, the
328 type 'X' change affects the returned value even if no execute bits
329 were set in OLDMODE, and set user and group ID bits are preserved
330 unless CHANGES mentioned them. If PMODE_BITS is not null, store into
331 *PMODE_BITS a mask denoting file mode bits that are affected by
332 CHANGES.
333
334 The returned value and *PMODE_BITS contain only file mode bits.
335 For example, they have the S_IFMT bits cleared on a standard
336 Unix-like host. */
337
338 mode_t
mode_adjust(mode_t oldmode,bool dir,mode_t umask_value,struct mode_change const * changes,mode_t * pmode_bits)339 mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
340 struct mode_change const *changes, mode_t *pmode_bits)
341 {
342 /* The adjusted mode. */
343 mode_t newmode = oldmode & CHMOD_MODE_BITS;
344
345 /* File mode bits that CHANGES cares about. */
346 mode_t mode_bits = 0;
347
348 for (; changes->flag != MODE_DONE; changes++)
349 {
350 mode_t affected = changes->affected;
351 mode_t omit_change =
352 (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned;
353 mode_t value = changes->value;
354
355 switch (changes->flag)
356 {
357 case MODE_ORDINARY_CHANGE:
358 break;
359
360 case MODE_COPY_EXISTING:
361 /* Isolate in 'value' the bits in 'newmode' to copy. */
362 value &= newmode;
363
364 /* Copy the isolated bits to the other two parts. */
365 value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH)
366 ? S_IRUSR | S_IRGRP | S_IROTH : 0)
367 | (value & (S_IWUSR | S_IWGRP | S_IWOTH)
368 ? S_IWUSR | S_IWGRP | S_IWOTH : 0)
369 | (value & (S_IXUSR | S_IXGRP | S_IXOTH)
370 ? S_IXUSR | S_IXGRP | S_IXOTH : 0));
371 break;
372
373 case MODE_X_IF_ANY_X:
374 /* Affect the execute bits if execute bits are already set
375 or if the file is a directory. */
376 if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
377 value |= S_IXUSR | S_IXGRP | S_IXOTH;
378 break;
379 }
380
381 /* If WHO was specified, limit the change to the affected bits.
382 Otherwise, apply the umask. Either way, omit changes as
383 requested. */
384 value &= (affected ? affected : ~umask_value) & ~ omit_change;
385
386 switch (changes->op)
387 {
388 case '=':
389 /* If WHO was specified, preserve the previous values of
390 bits that are not affected by this change operation.
391 Otherwise, clear all the bits. */
392 {
393 mode_t preserved = (affected ? ~affected : 0) | omit_change;
394 mode_bits |= CHMOD_MODE_BITS & ~preserved;
395 newmode = (newmode & preserved) | value;
396 break;
397 }
398
399 case '+':
400 mode_bits |= value;
401 newmode |= value;
402 break;
403
404 case '-':
405 mode_bits |= value;
406 newmode &= ~value;
407 break;
408 }
409 }
410
411 if (pmode_bits)
412 *pmode_bits = mode_bits;
413 return newmode;
414 }
415