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