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