Merge from vendor branch NTPD:
[dragonfly.git] / lib / libc / gen / setmode.c
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  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *      This product includes software developed by the University of
19  *      California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  *
36  * @(#)setmode.c        8.2 (Berkeley) 3/25/94
37  * $FreeBSD: src/lib/libc/gen/setmode.c,v 1.5.2.1 2001/03/05 09:34:10 obrien Exp $
38  * $DragonFly: src/lib/libc/gen/setmode.c,v 1.5 2005/01/31 22:29:15 dillon Exp $
39  */
40
41 #include "namespace.h"
42 #include <sys/types.h>
43 #include <sys/stat.h>
44
45 #include <ctype.h>
46 #include <signal.h>
47 #include <stddef.h>
48 #include <stdlib.h>
49
50 #ifdef SETMODE_DEBUG
51 #include <stdio.h>
52 #endif
53 #include "un-namespace.h"
54
55 #define SET_LEN 6               /* initial # of bitcmd struct to malloc */
56 #define SET_LEN_INCR 4          /* # of bitcmd structs to add as needed */
57
58 typedef struct bitcmd {
59         char    cmd;
60         char    cmd2;
61         mode_t  bits;
62 } BITCMD;
63
64 #define CMD2_CLR        0x01
65 #define CMD2_SET        0x02
66 #define CMD2_GBITS      0x04
67 #define CMD2_OBITS      0x08
68 #define CMD2_UBITS      0x10
69
70 static BITCMD   *addcmd (BITCMD *, int, int, int, u_int);
71 static void      compress_mode (BITCMD *);
72 #ifdef SETMODE_DEBUG
73 static void      dumpmode (BITCMD *);
74 #endif
75
76 /*
77  * Given the old mode and an array of bitcmd structures, apply the operations
78  * described in the bitcmd structures to the old mode, and return the new mode.
79  * Note that there is no '=' command; a strict assignment is just a '-' (clear
80  * bits) followed by a '+' (set bits).
81  */
82 mode_t
83 getmode(bbox, omode)
84         void *bbox;
85         mode_t omode;
86 {
87         BITCMD *set;
88         mode_t clrval, newmode, value;
89
90         set = (BITCMD *)bbox;
91         newmode = omode;
92         for (value = 0;; set++)
93                 switch(set->cmd) {
94                 /*
95                  * When copying the user, group or other bits around, we "know"
96                  * where the bits are in the mode so that we can do shifts to
97                  * copy them around.  If we don't use shifts, it gets real
98                  * grundgy with lots of single bit checks and bit sets.
99                  */
100                 case 'u':
101                         value = (newmode & S_IRWXU) >> 6;
102                         goto common;
103
104                 case 'g':
105                         value = (newmode & S_IRWXG) >> 3;
106                         goto common;
107
108                 case 'o':
109                         value = newmode & S_IRWXO;
110 common:                 if (set->cmd2 & CMD2_CLR) {
111                                 clrval =
112                                     (set->cmd2 & CMD2_SET) ?  S_IRWXO : value;
113                                 if (set->cmd2 & CMD2_UBITS)
114                                         newmode &= ~((clrval<<6) & set->bits);
115                                 if (set->cmd2 & CMD2_GBITS)
116                                         newmode &= ~((clrval<<3) & set->bits);
117                                 if (set->cmd2 & CMD2_OBITS)
118                                         newmode &= ~(clrval & set->bits);
119                         }
120                         if (set->cmd2 & CMD2_SET) {
121                                 if (set->cmd2 & CMD2_UBITS)
122                                         newmode |= (value<<6) & set->bits;
123                                 if (set->cmd2 & CMD2_GBITS)
124                                         newmode |= (value<<3) & set->bits;
125                                 if (set->cmd2 & CMD2_OBITS)
126                                         newmode |= value & set->bits;
127                         }
128                         break;
129
130                 case '+':
131                         newmode |= set->bits;
132                         break;
133
134                 case '-':
135                         newmode &= ~set->bits;
136                         break;
137
138                 case 'X':
139                         if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
140                                 newmode |= set->bits;
141                         break;
142
143                 case '\0':
144                 default:
145 #ifdef SETMODE_DEBUG
146                         (void)printf("getmode:%04o -> %04o\n", omode, newmode);
147 #endif
148                         return (newmode);
149                 }
150 }
151
152 #define ADDCMD(a, b, c, d)                                              \
153         if (set >= endset) {                                            \
154                 BITCMD *newset;                         \
155                 setlen += SET_LEN_INCR;                                 \
156                 newset = realloc(saveset, sizeof(BITCMD) * setlen);     \
157                 if (!saveset)                                           \
158                         return (NULL);                                  \
159                 set = newset + (set - saveset);                         \
160                 saveset = newset;                                       \
161                 endset = newset + (setlen - 2);                         \
162         }                                                               \
163         set = addcmd(set, (a), (b), (c), (d))
164
165 #define STANDARD_BITS   (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
166
167 void *
168 setmode(p)
169         char *p;
170 {
171         int perm, who;
172         char op;
173         BITCMD *set, *saveset, *endset;
174         sigset_t sigset, sigoset;
175         mode_t mask;
176         int equalopdone=0, permXbits, setlen;
177
178         if (!*p)
179                 return (NULL);
180
181         /*
182          * Get a copy of the mask for the permissions that are mask relative.
183          * Flip the bits, we want what's not set.  Since it's possible that
184          * the caller is opening files inside a signal handler, protect them
185          * as best we can.
186          */
187         sigfillset(&sigset);
188         (void)_sigprocmask(SIG_BLOCK, &sigset, &sigoset);
189         (void)umask(mask = umask(0));
190         mask = ~mask;
191         (void)_sigprocmask(SIG_SETMASK, &sigoset, NULL);
192
193         setlen = SET_LEN + 2;
194
195         if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL)
196                 return (NULL);
197         saveset = set;
198         endset = set + (setlen - 2);
199
200         /*
201          * If an absolute number, get it and return; disallow non-octal digits
202          * or illegal bits.
203          */
204         if (isdigit((unsigned char)*p)) {
205                 perm = (mode_t)strtol(p, NULL, 8);
206                 if (perm & ~(STANDARD_BITS|S_ISTXT)) {
207                         free(saveset);
208                         return (NULL);
209                 }
210                 while (*++p)
211                         if (*p < '0' || *p > '7') {
212                                 free(saveset);
213                                 return (NULL);
214                         }
215                 ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
216                 return (saveset);
217         }
218
219         /*
220          * Build list of structures to set/clear/copy bits as described by
221          * each clause of the symbolic mode.
222          */
223         for (;;) {
224                 /* First, find out which bits might be modified. */
225                 for (who = 0;; ++p) {
226                         switch (*p) {
227                         case 'a':
228                                 who |= STANDARD_BITS;
229                                 break;
230                         case 'u':
231                                 who |= S_ISUID|S_IRWXU;
232                                 break;
233                         case 'g':
234                                 who |= S_ISGID|S_IRWXG;
235                                 break;
236                         case 'o':
237                                 who |= S_IRWXO;
238                                 break;
239                         default:
240                                 goto getop;
241                         }
242                 }
243
244 getop:          if ((op = *p++) != '+' && op != '-' && op != '=') {
245                         free(saveset);
246                         return (NULL);
247                 }
248                 if (op == '=')
249                         equalopdone = 0;
250
251                 who &= ~S_ISTXT;
252                 for (perm = 0, permXbits = 0;; ++p) {
253                         switch (*p) {
254                         case 'r':
255                                 perm |= S_IRUSR|S_IRGRP|S_IROTH;
256                                 break;
257                         case 's':
258                                 /* If only "other" bits ignore set-id. */
259                                 if (!who || who & ~S_IRWXO)
260                                         perm |= S_ISUID|S_ISGID;
261                                 break;
262                         case 't':
263                                 /* If only "other" bits ignore sticky. */
264                                 if (!who || who & ~S_IRWXO) {
265                                         who |= S_ISTXT;
266                                         perm |= S_ISTXT;
267                                 }
268                                 break;
269                         case 'w':
270                                 perm |= S_IWUSR|S_IWGRP|S_IWOTH;
271                                 break;
272                         case 'X':
273                                 permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
274                                 break;
275                         case 'x':
276                                 perm |= S_IXUSR|S_IXGRP|S_IXOTH;
277                                 break;
278                         case 'u':
279                         case 'g':
280                         case 'o':
281                                 /*
282                                  * When ever we hit 'u', 'g', or 'o', we have
283                                  * to flush out any partial mode that we have,
284                                  * and then do the copying of the mode bits.
285                                  */
286                                 if (perm) {
287                                         ADDCMD(op, who, perm, mask);
288                                         perm = 0;
289                                 }
290                                 if (op == '=')
291                                         equalopdone = 1;
292                                 if (op == '+' && permXbits) {
293                                         ADDCMD('X', who, permXbits, mask);
294                                         permXbits = 0;
295                                 }
296                                 ADDCMD(*p, who, op, mask);
297                                 break;
298
299                         default:
300                                 /*
301                                  * Add any permissions that we haven't already
302                                  * done.
303                                  */
304                                 if (perm || (op == '=' && !equalopdone)) {
305                                         if (op == '=')
306                                                 equalopdone = 1;
307                                         ADDCMD(op, who, perm, mask);
308                                         perm = 0;
309                                 }
310                                 if (permXbits) {
311                                         ADDCMD('X', who, permXbits, mask);
312                                         permXbits = 0;
313                                 }
314                                 goto apply;
315                         }
316                 }
317
318 apply:          if (!*p)
319                         break;
320                 if (*p != ',')
321                         goto getop;
322                 ++p;
323         }
324         set->cmd = 0;
325 #ifdef SETMODE_DEBUG
326         (void)printf("Before compress_mode()\n");
327         dumpmode(saveset);
328 #endif
329         compress_mode(saveset);
330 #ifdef SETMODE_DEBUG
331         (void)printf("After compress_mode()\n");
332         dumpmode(saveset);
333 #endif
334         return (saveset);
335 }
336
337 static BITCMD *
338 addcmd(set, op, who, oparg, mask)
339         BITCMD *set;
340         int oparg, who;
341         int op;
342         u_int mask;
343 {
344         switch (op) {
345         case '=':
346                 set->cmd = '-';
347                 set->bits = who ? who : STANDARD_BITS;
348                 set++;
349
350                 op = '+';
351                 /* FALLTHROUGH */
352         case '+':
353         case '-':
354         case 'X':
355                 set->cmd = op;
356                 set->bits = (who ? who : mask) & oparg;
357                 break;
358
359         case 'u':
360         case 'g':
361         case 'o':
362                 set->cmd = op;
363                 if (who) {
364                         set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
365                                     ((who & S_IRGRP) ? CMD2_GBITS : 0) |
366                                     ((who & S_IROTH) ? CMD2_OBITS : 0);
367                         set->bits = ~0;
368                 } else {
369                         set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
370                         set->bits = mask;
371                 }
372
373                 if (oparg == '+')
374                         set->cmd2 |= CMD2_SET;
375                 else if (oparg == '-')
376                         set->cmd2 |= CMD2_CLR;
377                 else if (oparg == '=')
378                         set->cmd2 |= CMD2_SET|CMD2_CLR;
379                 break;
380         }
381         return (set + 1);
382 }
383
384 #ifdef SETMODE_DEBUG
385 static void
386 dumpmode(set)
387         BITCMD *set;
388 {
389         for (; set->cmd; ++set)
390                 (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
391                     set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
392                     set->cmd2 & CMD2_CLR ? " CLR" : "",
393                     set->cmd2 & CMD2_SET ? " SET" : "",
394                     set->cmd2 & CMD2_UBITS ? " UBITS" : "",
395                     set->cmd2 & CMD2_GBITS ? " GBITS" : "",
396                     set->cmd2 & CMD2_OBITS ? " OBITS" : "");
397 }
398 #endif
399
400 /*
401  * Given an array of bitcmd structures, compress by compacting consecutive
402  * '+', '-' and 'X' commands into at most 3 commands, one of each.  The 'u',
403  * 'g' and 'o' commands continue to be separate.  They could probably be
404  * compacted, but it's not worth the effort.
405  */
406 static void
407 compress_mode(set)
408         BITCMD *set;
409 {
410         BITCMD *nset;
411         int setbits, clrbits, Xbits, op;
412
413         for (nset = set;;) {
414                 /* Copy over any 'u', 'g' and 'o' commands. */
415                 while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
416                         *set++ = *nset++;
417                         if (!op)
418                                 return;
419                 }
420
421                 for (setbits = clrbits = Xbits = 0;; nset++) {
422                         if ((op = nset->cmd) == '-') {
423                                 clrbits |= nset->bits;
424                                 setbits &= ~nset->bits;
425                                 Xbits &= ~nset->bits;
426                         } else if (op == '+') {
427                                 setbits |= nset->bits;
428                                 clrbits &= ~nset->bits;
429                                 Xbits &= ~nset->bits;
430                         } else if (op == 'X')
431                                 Xbits |= nset->bits & ~setbits;
432                         else
433                                 break;
434                 }
435                 if (clrbits) {
436                         set->cmd = '-';
437                         set->cmd2 = 0;
438                         set->bits = clrbits;
439                         set++;
440                 }
441                 if (setbits) {
442                         set->cmd = '+';
443                         set->cmd2 = 0;
444                         set->bits = setbits;
445                         set++;
446                 }
447                 if (Xbits) {
448                         set->cmd = 'X';
449                         set->cmd2 = 0;
450                         set->bits = Xbits;
451                         set++;
452                 }
453         }
454 }