Starting `chmod` and symbolic mode parser
[lab.git] / liblab / mode.c
blobdc96db6605faf256c6a123deca5bd14c1e1d0050
1 /* mode.c - parse and apply symbolic modes (mode_t)
2 Copyright (c) 2022, Alan Potteiger
3 See `LICENSE` for copyright and license details */
5 #define _POSIX_C_SOURCE 200809L
6 #define _XOPEN_SOURCE 700 /* Single UNIX Specification, Version 4
7 required for `t` perm, S_ISVTX bits */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <unistd.h>
14 #include <sys/stat.h>
16 /* Following is an excerpt from the POSIX definition of `chmod` containing a
17 grammar for symbolic modes.
19 symbolic_mode : clause
20 | symbolic_mode ',' clause
23 clause : actionlist
24 | wholist actionlist
27 wholist : who
28 | wholist who
31 who : 'u' | 'g' | 'o' | 'a'
34 actionlist : action
35 | actionlist action
38 action : op
39 | op permlist
40 | op permcopy
43 permcopy : 'u' | 'g' | 'o'
46 op : '+' | '-' | '='
49 permlist : perm
50 | perm permlist
52 perm : 'r' | 'w' | 'x' | 'X' | 's' | 't'
53 ; */
55 /* Accepts symbolic mode string `modestr` and applies its changes to `modeset`
56 Returns > 0 for any syntax errors */
57 int
58 modeset(char *modestr, mode_t *modeset)
60 char *ptr;
61 char *end;
62 char op, perm;
63 mode_t copy, work, who, mode;
65 ptr = modestr;
67 /* if octal number we set the mode absolutely and return */
68 end = ptr;
69 mode = (mode_t) strtol(ptr, &end, 8);
70 if (*end == '\0') {
71 *modeset = mode;
72 return 0;
75 clause:
76 mode = *modeset;
77 op = perm = 0;
78 copy = work = who = 0;
80 /* Find who's permissions we're editing */
81 for (; *ptr != '\0'; ptr++) {
82 switch (*ptr) {
83 case 'u':
84 who |= S_IRWXU;
85 continue;
86 case 'g':
87 who |= S_IRWXG;
88 continue;
89 case 'o':
90 who |= S_IRWXO;
91 continue;
92 case 'a':
93 who |= S_IRWXU | S_IRWXG | S_IRWXO;
94 continue;
95 default:
96 break;
99 break;
102 /* if no who specified, default to all */
103 if (who == 0)
104 who |= S_IRWXU | S_IRWXG | S_IRWXO;
106 /* find operator */
107 switch (*ptr) {
108 case '+':
109 case '-':
110 case '=':
111 op = *ptr;
112 break;
113 default:
114 return 1;
117 ptr++;
119 /* find permissions/permcopy and setup mask to be used */
120 for (; *ptr != '\0'; ptr++) {
121 switch (*ptr) {
122 case 'r':
123 work = who & (S_IRUSR | S_IRGRP | S_IROTH);
124 continue;
125 case 'w':
126 work = who & (S_IWUSR | S_IWGRP | S_IWOTH);
127 continue;
128 case 'x':
129 work = who & (S_IXUSR | S_IXGRP | S_IXOTH);
130 continue;
131 /* TODO
132 case 'X':
133 continue;
134 case 's':
135 continue;
137 case 't':
138 work |= S_ISVTX;
139 continue;
140 /* permcopy */
141 case 'u':
142 copy = *modeset;
143 copy |= copy >> 3;
144 copy |= copy >> 3;
145 work = who & copy;
146 break;
147 case 'g':
148 copy = *modeset;
149 copy |= copy << 3;
150 copy |= copy >> 3;
151 work = who & copy;
152 break;
153 case 'o':
154 copy = *modeset;
155 copy |= copy << 3;
156 copy |= copy << 3;
157 work = who & copy;
158 break;
159 default:
160 break;
163 if (work == 0) {
164 /* invalid or missing permission character */
165 return 1;
167 break;
170 /* do our work on the given mode depending on our operator */
171 switch (op) {
172 case '+':
173 /* + operator: turn selected bits on */
174 mode |= work;
175 break;
176 case '-':
177 /* - operator: turn selected bits off */
178 work = ~work;
179 mode &= work;
180 break;
181 case '=':
182 /* = operator: clear user permissions mentioned, then set */
183 who = ~who;
184 mode &= who;
186 mode |= work;
187 break;
190 *modeset = mode;
192 if (*ptr == ',') {
193 ptr++;
194 goto clause;
197 return 0;