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