xref: /original-bsd/lib/libc/gen/setmode.c (revision e66e06db)
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
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 *
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 *
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
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
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