Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/Optional.h>
28#include <AK/StdLibExtras.h>
29#include <AK/kmalloc.h>
30#include <stdio.h>
31#include <string.h>
32#include <sys/stat.h>
33#include <unistd.h>
34
35/* the new mode will be computed using the boolean function(for each bit):
36
37 |current mode|removal mask|applying mask|result |
38 | 0 | 0 | 0 | 0 |
39 | 0 | 0 | 1 | 1 |
40 | 0 | 1 | 0 | 0 |
41 | 0 | 1 | 1 | 1 | ---> find the CNF --> find the minimal CNF
42 | 1 | 0 | 0 | 1 |
43 | 1 | 0 | 1 | 1 |
44 | 1 | 1 | 0 | 0 |
45 | 1 | 1 | 1 | 1 |
46*/
47
48class Mask {
49private:
50 mode_t removal_mask; //the bits that will be removed
51 mode_t applying_mask; //the bits that will be setted
52
53public:
54 Mask()
55 : removal_mask(0)
56 , applying_mask(0)
57 {
58 }
59
60 Mask& operator|=(const Mask& other)
61 {
62 removal_mask |= other.removal_mask;
63 applying_mask |= other.applying_mask;
64
65 return *this;
66 }
67
68 mode_t& get_removal_mask() { return removal_mask; }
69 mode_t& get_applying_mask() { return applying_mask; }
70};
71
72Optional<Mask> string_to_mode(char access_scope, const char*& access_string);
73Optional<Mask> apply_permission(char access_scope, char permission, char operation);
74
75int main(int argc, char** argv)
76{
77 if (pledge("stdio rpath fattr", nullptr) < 0) {
78 perror("pledge");
79 return 1;
80 }
81
82 if (argc < 3) {
83 printf("usage: chmod <octal-mode> <path...>\n"
84 " chmod [[ugoa][+-=][rwx...],...] <path...>\n");
85 return 1;
86 }
87
88 Mask mask;
89
90 /* compute a mask */
91 if (argv[1][0] >= '0' && argv[1][0] <= '7') {
92 if (sscanf(argv[1], "%ho", &mask.get_applying_mask()) != 1) {
93 perror("sscanf");
94 return 1;
95 }
96 mask.get_removal_mask() = ~mask.get_applying_mask();
97 } else {
98 const char* access_string = argv[1];
99
100 while (*access_string != '\0') {
101 Optional<Mask> tmp_mask;
102 switch (*access_string) {
103 case 'u':
104 tmp_mask = string_to_mode('u', ++access_string);
105 break;
106 case 'g':
107 tmp_mask = string_to_mode('g', ++access_string);
108 break;
109 case 'o':
110 tmp_mask = string_to_mode('o', ++access_string);
111 break;
112 case 'a':
113 tmp_mask = string_to_mode('a', ++access_string);
114 break;
115 case '=':
116 case '+':
117 case '-':
118 tmp_mask = string_to_mode('a', access_string);
119 break;
120 case ',':
121 ++access_string;
122 continue;
123 }
124 if (!tmp_mask.has_value()) {
125 fprintf(stderr, "chmod: invalid mode: %s\n", argv[1]);
126 return 1;
127 }
128 mask |= tmp_mask.value();
129 }
130 }
131
132 /* set the mask for each files' permissions */
133 struct stat current_access;
134 int i = 2;
135 while (i < argc) {
136 if (stat(argv[i], ¤t_access) != 0) {
137 perror("stat");
138 return 1;
139 }
140 /* found the minimal CNF by The Quine–McCluskey algorithm and use it */
141 mode_t mode = mask.get_applying_mask()
142 | (current_access.st_mode & ~mask.get_removal_mask());
143 if (chmod(argv[i++], mode) != 0) {
144 perror("chmod");
145 }
146 }
147
148 return 0;
149}
150
151Optional<Mask> string_to_mode(char access_scope, const char*& access_string)
152{
153 char operation = *access_string;
154
155 if (operation != '+' && operation != '-' && operation != '=') {
156 return {};
157 }
158
159 Mask mask;
160 if (operation == '=') {
161 switch (access_scope) {
162 case 'u':
163 mask.get_removal_mask() = (S_IRUSR | S_IWUSR | S_IXUSR);
164 break;
165 case 'g':
166 mask.get_removal_mask() = (S_IRGRP | S_IWGRP | S_IXGRP);
167 break;
168 case 'o':
169 mask.get_removal_mask() = (S_IROTH | S_IWOTH | S_IXOTH);
170 break;
171 case 'a':
172 mask.get_removal_mask() = (S_IRUSR | S_IWUSR | S_IXUSR
173 | S_IRGRP | S_IWGRP | S_IXGRP
174 | S_IROTH | S_IWOTH | S_IXOTH);
175 break;
176 }
177 operation = '+';
178 }
179
180 access_string++;
181 while (*access_string != '\0' && *access_string != ',') {
182 Optional<Mask> tmp_mask;
183 tmp_mask = apply_permission(access_scope, *access_string, operation);
184 if (!tmp_mask.has_value()) {
185 return {};
186 }
187 mask |= tmp_mask.value();
188 access_string++;
189 }
190
191 return mask;
192}
193
194Optional<Mask> apply_permission(char access_scope, char permission, char operation)
195{
196 if (permission != 'r' && permission != 'w' && permission != 'x') {
197 return {};
198 }
199
200 Mask mask;
201 mode_t tmp_mask = 0;
202 switch (access_scope) {
203 case 'u':
204 switch (permission) {
205 case 'r':
206 tmp_mask = S_IRUSR;
207 break;
208 case 'w':
209 tmp_mask = S_IWUSR;
210 break;
211 case 'x':
212 tmp_mask = S_IXUSR;
213 break;
214 }
215 break;
216 case 'g':
217 switch (permission) {
218 case 'r':
219 tmp_mask = S_IRGRP;
220 break;
221 case 'w':
222 tmp_mask = S_IWGRP;
223 break;
224 case 'x':
225 tmp_mask = S_IXGRP;
226 break;
227 }
228 break;
229 case 'o':
230 switch (permission) {
231 case 'r':
232 tmp_mask = S_IROTH;
233 break;
234 case 'w':
235 tmp_mask = S_IWOTH;
236 break;
237 case 'x':
238 tmp_mask = S_IXOTH;
239 break;
240 }
241 break;
242 case 'a':
243 mask |= apply_permission('u', permission, operation).value();
244 mask |= apply_permission('g', permission, operation).value();
245 mask |= apply_permission('o', permission, operation).value();
246 break;
247 }
248
249 if (operation == '+') {
250 mask.get_applying_mask() |= tmp_mask;
251 } else {
252 mask.get_removal_mask() |= tmp_mask;
253 }
254
255 return mask;
256}