1 /*
2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Dave Borman at Cray Research, Inc.
7 *
8 * %sccs.include.redist.c%
9 */
10
11 #if defined(LIBC_SCCS) && !defined(lint)
12 static char sccsid[] = "@(#)setmode.c 8.2 (Berkeley) 03/25/94";
13 #endif /* LIBC_SCCS and not lint */
14
15 #include <sys/types.h>
16 #include <sys/stat.h>
17
18 #include <ctype.h>
19 #include <errno.h>
20 #include <signal.h>
21 #include <stddef.h>
22 #include <stdlib.h>
23
24 #ifdef SETMODE_DEBUG
25 #include <stdio.h>
26 #endif
27
28 #define SET_LEN 6 /* initial # of bitcmd struct to malloc */
29 #define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */
30
31 typedef struct bitcmd {
32 char cmd;
33 char cmd2;
34 mode_t bits;
35 } BITCMD;
36
37 #define CMD2_CLR 0x01
38 #define CMD2_SET 0x02
39 #define CMD2_GBITS 0x04
40 #define CMD2_OBITS 0x08
41 #define CMD2_UBITS 0x10
42
43 static BITCMD *addcmd __P((BITCMD *, int, int, int, u_int));
44 static int compress_mode __P((BITCMD *));
45 #ifdef SETMODE_DEBUG
46 static void dumpmode __P((BITCMD *));
47 #endif
48
49 /*
50 * Given the old mode and an array of bitcmd structures, apply the operations
51 * described in the bitcmd structures to the old mode, and return the new mode.
52 * Note that there is no '=' command; a strict assignment is just a '-' (clear
53 * bits) followed by a '+' (set bits).
54 */
55 mode_t
getmode(bbox,omode)56 getmode(bbox, omode)
57 void *bbox;
58 mode_t omode;
59 {
60 register BITCMD *set;
61 register mode_t clrval, newmode, value;
62
63 set = (BITCMD *)bbox;
64 newmode = omode;
65 for (value = 0;; set++)
66 switch(set->cmd) {
67 /*
68 * When copying the user, group or other bits around, we "know"
69 * where the bits are in the mode so that we can do shifts to
70 * copy them around. If we don't use shifts, it gets real
71 * grundgy with lots of single bit checks and bit sets.
72 */
73 case 'u':
74 value = (newmode & S_IRWXU) >> 6;
75 goto common;
76
77 case 'g':
78 value = (newmode & S_IRWXG) >> 3;
79 goto common;
80
81 case 'o':
82 value = newmode & S_IRWXO;
83 common: if (set->cmd2 & CMD2_CLR) {
84 clrval =
85 (set->cmd2 & CMD2_SET) ? S_IRWXO : value;
86 if (set->cmd2 & CMD2_UBITS)
87 newmode &= ~((clrval<<6) & set->bits);
88 if (set->cmd2 & CMD2_GBITS)
89 newmode &= ~((clrval<<3) & set->bits);
90 if (set->cmd2 & CMD2_OBITS)
91 newmode &= ~(clrval & set->bits);
92 }
93 if (set->cmd2 & CMD2_SET) {
94 if (set->cmd2 & CMD2_UBITS)
95 newmode |= (value<<6) & set->bits;
96 if (set->cmd2 & CMD2_GBITS)
97 newmode |= (value<<3) & set->bits;
98 if (set->cmd2 & CMD2_OBITS)
99 newmode |= value & set->bits;
100 }
101 break;
102
103 case '+':
104 newmode |= set->bits;
105 break;
106
107 case '-':
108 newmode &= ~set->bits;
109 break;
110
111 case 'X':
112 if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
113 newmode |= set->bits;
114 break;
115
116 case '\0':
117 default:
118 #ifdef SETMODE_DEBUG
119 (void)printf("getmode:%04o -> %04o\n", omode, newmode);
120 #endif
121 return (newmode);
122 }
123 }
124
125 #define ADDCMD(a, b, c, d) \
126 if (set >= endset) { \
127 register BITCMD *newset; \
128 setlen += SET_LEN_INCR; \
129 newset = realloc(saveset, sizeof(BITCMD) * setlen); \
130 if (!saveset) \
131 return (NULL); \
132 set = newset + (set - saveset); \
133 saveset = newset; \
134 endset = newset + (setlen - 2); \
135 } \
136 set = addcmd(set, (a), (b), (c), (d))
137
138 #define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
139
140 void *
setmode(p)141 setmode(p)
142 register char *p;
143 {
144 register int perm, who;
145 register char op;
146 BITCMD *set, *saveset, *endset;
147 sigset_t sigset, sigoset;
148 mode_t mask;
149 int equalopdone, permXbits, setlen;
150
151 if (!*p)
152 return (NULL);
153
154 /*
155 * Get a copy of the mask for the permissions that are mask relative.
156 * Flip the bits, we want what's not set. Since it's possible that
157 * the caller is opening files inside a signal handler, protect them
158 * as best we can.
159 */
160 sigfillset(&sigset);
161 (void)sigprocmask(SIG_BLOCK, &sigset, &sigoset);
162 (void)umask(mask = umask(0));
163 mask = ~mask;
164 (void)sigprocmask(SIG_SETMASK, &sigoset, NULL);
165
166 setlen = SET_LEN + 2;
167
168 if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL)
169 return (NULL);
170 saveset = set;
171 endset = set + (setlen - 2);
172
173 /*
174 * If an absolute number, get it and return; disallow non-octal digits
175 * or illegal bits.
176 */
177 if (isdigit(*p)) {
178 perm = (mode_t)strtol(p, NULL, 8);
179 if (perm & ~(STANDARD_BITS|S_ISTXT)) {
180 free(saveset);
181 return (NULL);
182 }
183 while (*++p)
184 if (*p < '0' || *p > '7') {
185 free(saveset);
186 return (NULL);
187 }
188 ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
189 return (saveset);
190 }
191
192 /*
193 * Build list of structures to set/clear/copy bits as described by
194 * each clause of the symbolic mode.
195 */
196 for (;;) {
197 /* First, find out which bits might be modified. */
198 for (who = 0;; ++p) {
199 switch (*p) {
200 case 'a':
201 who |= STANDARD_BITS;
202 break;
203 case 'u':
204 who |= S_ISUID|S_IRWXU;
205 break;
206 case 'g':
207 who |= S_ISGID|S_IRWXG;
208 break;
209 case 'o':
210 who |= S_IRWXO;
211 break;
212 default:
213 goto getop;
214 }
215 }
216
217 getop: if ((op = *p++) != '+' && op != '-' && op != '=') {
218 free(saveset);
219 return (NULL);
220 }
221 if (op == '=')
222 equalopdone = 0;
223
224 who &= ~S_ISTXT;
225 for (perm = 0, permXbits = 0;; ++p) {
226 switch (*p) {
227 case 'r':
228 perm |= S_IRUSR|S_IRGRP|S_IROTH;
229 break;
230 case 's':
231 /* If only "other" bits ignore set-id. */
232 if (who & ~S_IRWXO)
233 perm |= S_ISUID|S_ISGID;
234 break;
235 case 't':
236 /* If only "other" bits ignore sticky. */
237 if (who & ~S_IRWXO) {
238 who |= S_ISTXT;
239 perm |= S_ISTXT;
240 }
241 break;
242 case 'w':
243 perm |= S_IWUSR|S_IWGRP|S_IWOTH;
244 break;
245 case 'X':
246 permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
247 break;
248 case 'x':
249 perm |= S_IXUSR|S_IXGRP|S_IXOTH;
250 break;
251 case 'u':
252 case 'g':
253 case 'o':
254 /*
255 * When ever we hit 'u', 'g', or 'o', we have
256 * to flush out any partial mode that we have,
257 * and then do the copying of the mode bits.
258 */
259 if (perm) {
260 ADDCMD(op, who, perm, mask);
261 perm = 0;
262 }
263 if (op == '=')
264 equalopdone = 1;
265 if (op == '+' && permXbits) {
266 ADDCMD('X', who, permXbits, mask);
267 permXbits = 0;
268 }
269 ADDCMD(*p, who, op, mask);
270 break;
271
272 default:
273 /*
274 * Add any permissions that we haven't already
275 * done.
276 */
277 if (perm || (op == '=' && !equalopdone)) {
278 if (op == '=')
279 equalopdone = 1;
280 ADDCMD(op, who, perm, mask);
281 perm = 0;
282 }
283 if (permXbits) {
284 ADDCMD('X', who, permXbits, mask);
285 permXbits = 0;
286 }
287 goto apply;
288 }
289 }
290
291 apply: if (!*p)
292 break;
293 if (*p != ',')
294 goto getop;
295 ++p;
296 }
297 set->cmd = 0;
298 #ifdef SETMODE_DEBUG
299 (void)printf("Before compress_mode()\n");
300 dumpmode(saveset);
301 #endif
302 compress_mode(saveset);
303 #ifdef SETMODE_DEBUG
304 (void)printf("After compress_mode()\n");
305 dumpmode(saveset);
306 #endif
307 return (saveset);
308 }
309
310 static BITCMD *
addcmd(set,op,who,oparg,mask)311 addcmd(set, op, who, oparg, mask)
312 BITCMD *set;
313 register int oparg, who;
314 register int op;
315 u_int mask;
316 {
317 switch (op) {
318 case '=':
319 set->cmd = '-';
320 set->bits = who ? who : STANDARD_BITS;
321 set++;
322
323 op = '+';
324 /* FALLTHROUGH */
325 case '+':
326 case '-':
327 case 'X':
328 set->cmd = op;
329 set->bits = (who ? who : mask) & oparg;
330 break;
331
332 case 'u':
333 case 'g':
334 case 'o':
335 set->cmd = op;
336 if (who) {
337 set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
338 ((who & S_IRGRP) ? CMD2_GBITS : 0) |
339 ((who & S_IROTH) ? CMD2_OBITS : 0);
340 set->bits = ~0;
341 } else {
342 set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
343 set->bits = mask;
344 }
345
346 if (oparg == '+')
347 set->cmd2 |= CMD2_SET;
348 else if (oparg == '-')
349 set->cmd2 |= CMD2_CLR;
350 else if (oparg == '=')
351 set->cmd2 |= CMD2_SET|CMD2_CLR;
352 break;
353 }
354 return (set + 1);
355 }
356
357 #ifdef SETMODE_DEBUG
358 static void
dumpmode(set)359 dumpmode(set)
360 register BITCMD *set;
361 {
362 for (; set->cmd; ++set)
363 (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
364 set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
365 set->cmd2 & CMD2_CLR ? " CLR" : "",
366 set->cmd2 & CMD2_SET ? " SET" : "",
367 set->cmd2 & CMD2_UBITS ? " UBITS" : "",
368 set->cmd2 & CMD2_GBITS ? " GBITS" : "",
369 set->cmd2 & CMD2_OBITS ? " OBITS" : "");
370 }
371 #endif
372
373 /*
374 * Given an array of bitcmd structures, compress by compacting consecutive
375 * '+', '-' and 'X' commands into at most 3 commands, one of each. The 'u',
376 * 'g' and 'o' commands continue to be separate. They could probably be
377 * compacted, but it's not worth the effort.
378 */
379 static int
compress_mode(set)380 compress_mode(set)
381 register BITCMD *set;
382 {
383 register BITCMD *nset;
384 register int setbits, clrbits, Xbits, op;
385
386 for (nset = set;;) {
387 /* Copy over any 'u', 'g' and 'o' commands. */
388 while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
389 *set++ = *nset++;
390 if (!op)
391 return;
392 }
393
394 for (setbits = clrbits = Xbits = 0;; nset++) {
395 if ((op = nset->cmd) == '-') {
396 clrbits |= nset->bits;
397 setbits &= ~nset->bits;
398 Xbits &= ~nset->bits;
399 } else if (op == '+') {
400 setbits |= nset->bits;
401 clrbits &= ~nset->bits;
402 Xbits &= ~nset->bits;
403 } else if (op == 'X')
404 Xbits |= nset->bits & ~setbits;
405 else
406 break;
407 }
408 if (clrbits) {
409 set->cmd = '-';
410 set->cmd2 = 0;
411 set->bits = clrbits;
412 set++;
413 }
414 if (setbits) {
415 set->cmd = '+';
416 set->cmd2 = 0;
417 set->bits = setbits;
418 set++;
419 }
420 if (Xbits) {
421 set->cmd = 'X';
422 set->cmd2 = 0;
423 set->bits = Xbits;
424 set++;
425 }
426 }
427 }
428