chmod: Added chmod
[mutos-utils.git] / chmod.c
blob65abd197f91dd8934adb01d6da8e5e95fd13e7b4
1 /*
2 Copyright © 2013 Alastair Stuart
4 This program is open source software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
15 #include <errno.h>
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
21 #include <dirent.h>
22 #include <getopt.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include <unistd.h>
27 #define VERSION "0.01"
29 bool errors = false;
31 // option flags
32 struct {
33 bool recursive;
34 } flags;
37 struct symbolic_mode
39 mode_t who;
40 char operation;
41 mode_t perm;
44 mode_t user_flags[] = {
45 S_IRUSR,
46 S_IWUSR,
47 S_IXUSR
50 mode_t group_flags[] = {
51 S_IRGRP,
52 S_IWGRP,
53 S_IXGRP
56 mode_t other_flags[] = {
57 S_IROTH,
58 S_IWOTH,
59 S_IXOTH
62 bool isoctal(char ch)
64 return (ch >= '0' && ch <= '7');
67 void usage(char* program)
69 printf("Usage: %s [options] [mode] [file ...]\n", program);
70 printf("Changes file permissions.\n"
71 "\n"
72 " -R Run recursively.\n"
73 "\n"
74 " --help Print this message.\n"
75 " --version Show version info.\n"
76 "\n"
77 "[mode] is in the form of [ugoa][+-=][rwx] or an octal number.\n");
80 struct symbolic_mode* parse_mode_str(char* mode_str)
82 struct symbolic_mode *mode = calloc(1, sizeof(struct symbolic_mode));
84 // parse "who"
85 size_t i = 0;
86 for (i = 0; i < strlen(mode_str); i++)
88 switch (mode_str[i])
90 case 'u':
91 mode->who |= S_ISUID;
92 break;
93 case 'g':
94 mode->who |= S_ISGID;
95 break;
96 case 'o':
97 mode->who |= S_ISVTX;
98 break;
99 case 'a':
100 mode->who = S_ISUID | S_ISGID | S_ISVTX;
101 break;
102 case '+':
103 case '-':
104 case '=':
105 goto exit_loop;
106 break;
107 default:
108 free(mode);
109 return NULL;
113 exit_loop:
114 mode->operation = mode_str[i];
115 i++;
116 // parse permissions
117 for ( ; i < strlen(mode_str); i++)
119 switch (mode_str[i])
121 case 'r':
122 mode->perm |= S_IROTH;
123 break;
124 case 'w':
125 mode->perm |= S_IWOTH;
126 break;
127 case 'x':
128 mode->perm |= S_IXOTH;
129 break;
130 default:
131 free(mode);
132 return NULL;
136 return mode;
139 mode_t equals_op(mode_t original, mode_t who, mode_t perm)
141 mode_t new = original;
142 mode_t *flag_array = NULL;
144 while (who != 0)
146 if (who & S_ISUID) {
147 flag_array = user_flags;
148 } else if (who & S_ISGID) {
149 flag_array = group_flags;
150 } else if (who & S_ISVTX) {
151 flag_array = other_flags;
152 } else {
153 return original;
156 // turn off all the flags for that type
157 for (int i = 0; i < 3; i++)
159 new &= ~(flag_array[i]);
162 if (perm & S_IROTH) {
163 new |= flag_array[0];
165 if (perm & S_IWOTH) {
166 new |= flag_array[1];
168 if (perm & S_IXOTH) {
169 new |= flag_array[2];
172 if (flag_array == user_flags) {
173 who &= ~S_ISUID;
174 } else if (flag_array == group_flags) {
175 who &= ~S_ISGID;
176 } else if (flag_array == other_flags) {
177 who &= ~S_ISVTX;
178 } else {
179 who = 0;
183 return new;
186 void do_chmod(char* file, struct symbolic_mode *mode, mode_t change, char* argv0)
188 struct stat st;
189 if (stat(file, &st) < 0) {
190 fprintf(stderr, "%s: %s: %s\n",
191 argv0, strerror(errno), realpath(file, NULL));
192 errors = true;
195 mode_t final_mode = 0;
197 if (mode) {
198 final_mode = st.st_mode;
200 switch (mode->operation)
202 case '+':
203 final_mode |= change;
204 break;
205 case '-':
206 final_mode &= ~change;
207 break;
208 case '=':
209 final_mode = equals_op(final_mode, mode->who, mode->perm);
210 break;
211 default:
212 fprintf(stderr, "%s: No operation specified.\n", argv0);
213 errors = true;
215 } else {
216 final_mode = change;
219 if (chmod(file, final_mode) < 0) {
220 fprintf(stderr, "%s: %s: %s\n",
221 argv0, strerror(errno), realpath(file, NULL));
222 errors = true;
225 if (S_ISDIR(st.st_mode) && flags.recursive) {
226 if (chdir(file) == 0) {
227 DIR *dp = NULL;
228 struct dirent *ep = NULL;
229 dp = opendir(".");
230 if (dp) {
231 while ((ep = readdir(dp)))
233 if (!(strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)) {
234 do_chmod(ep->d_name, mode, change, argv0);
237 closedir(dp);
238 } else {
239 fprintf(stderr, "%s: %s: %s\n",
240 argv0, strerror(errno), realpath(file, NULL));
241 errors = true;
243 chdir("..");
244 } else {
245 fprintf(stderr, "%s: %s: %s\n",
246 argv0, strerror(errno), realpath(file, NULL));
251 int main(int argc, char* argv[])
253 flags.recursive = false;
254 static struct option long_options[] = {
255 {"recursive", no_argument, NULL, 'R'},
256 {"help", no_argument, NULL, 1},
257 {"version", no_argument, NULL, 2},
258 {NULL, 0, NULL, 0}
261 int c = 0;
262 while ((c = getopt_long(argc, argv, "R",
263 long_options, NULL)) != -1)
265 switch (c)
267 case 'R':
268 flags.recursive = true;
269 break;
270 case 1:
271 usage(argv[0]);
272 return 0;
273 case 2:
274 printf("chmod (mutos) v"VERSION"\n");
275 return 0;
279 if ((argc - optind) < 2) {
280 fprintf(stderr, "%s: missing operands\n"
281 "Run '%s --help' for usage.\n",
282 argv[0], argv[0]);
283 return 1;
286 char* mode_str = argv[optind];
287 optind++;
289 // check if mode is in octal form
290 bool octal_mode = true;
291 for (size_t i = 0; i < strlen(mode_str); i++)
293 if (!isoctal(mode_str[i])) {
294 octal_mode = false;
298 if (octal_mode) {
299 // convert string to octal number
300 mode_t final_mode = strtoul(mode_str, NULL, 8);
301 for (int i = 0; i < (argc - optind); i++)
303 do_chmod(argv[optind], NULL, final_mode, argv[0]);
306 if (errors) {
307 return 1;
308 } else {
309 return 0;
314 struct symbolic_mode *mode = parse_mode_str(mode_str);
316 if (!mode) {
317 fprintf(stderr, "%s: Invalid mode: %s\n",
318 argv[0], mode_str);
319 return 1;
322 // if who is not specified, it defaults to user
323 if (mode->who == 0) {
324 mode->who = S_ISUID;
327 mode_t change = 0;
329 if (mode->who & S_ISUID) {
330 if (mode->perm & S_IROTH) {
331 change |= S_IRUSR;
333 if (mode->perm & S_IWOTH) {
334 change |= S_IWUSR;
336 if (mode->perm & S_IXOTH) {
337 change |= S_IXUSR;
341 if (mode->who & S_ISGID) {
342 if (mode->perm & S_IROTH) {
343 change |= S_IRGRP;
345 if (mode->perm & S_IWOTH) {
346 change |= S_IWGRP;
348 if (mode->perm & S_IXOTH) {
349 change |= S_IXGRP;
353 if (mode->who & S_ISVTX) {
354 if (mode->perm & S_IROTH) {
355 change |= S_IROTH;
357 if (mode->perm & S_IWOTH) {
358 change |= S_IWOTH;
360 if (mode->perm & S_IXOTH) {
361 change |= S_IXOTH;
365 for (int i = 0; i < (argc - optind); i++)
367 do_chmod(argv[optind], mode, change, argv[0]);
370 free(mode);
372 if (errors) {
373 return 1;
374 } else {
375 return 0;