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