summaryrefslogtreecommitdiff
path: root/command/chmod.c
diff options
context:
space:
mode:
Diffstat (limited to 'command/chmod.c')
-rw-r--r--command/chmod.c232
1 files changed, 232 insertions, 0 deletions
diff --git a/command/chmod.c b/command/chmod.c
new file mode 100644
index 0000000..9e2972e
--- /dev/null
+++ b/command/chmod.c
@@ -0,0 +1,232 @@
+#include "args.h"
+#include "command.h"
+#include "lslib.h"
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+
+enum method {
+ ADD,
+ SUB,
+ SET
+};
+
+static struct {
+ bool recurse;
+ bool list_changed;
+ bool verbose;
+ bool quiet;
+ enum method method;
+ mode_t mode;
+} flags;
+
+static void help (void) {
+ printf("Usage: chmod [-Rcvf] MODE[,MODE]... FILE...\n\n");
+ printf("MODE is octal number (bit pattern sstrwxrwxrwx) or [ugoa]{+|-|=}[rwxXst]\n\n");
+ printf("\t-R\tRecurse\n");
+ printf("\t-c\tList changed files\n");
+ printf("\t-v\tVerbose\n");
+ printf("\t-f\tHide errors\n");
+}
+
+static int short_arg(char c, char* next) {
+ UNUSED(next);
+ switch (c) {
+ case 'R':
+ flags.recurse = true;
+ break;
+ case 'c':
+ flags.list_changed = true;
+ break;
+ case 'v':
+ flags.verbose = true;
+ break;
+ case 'f':
+ flags.quiet = true;
+ break;
+ case 'r':
+ case 'w':
+ case 'x':
+ case 'X':
+ case 't':
+ case 's':
+ return ARG_IGNORE;
+ default:
+ return ARG_INVALID;
+ }
+ return ARG_UNUSED;
+}
+
+static void chmod_file(char* path) {
+ int save;
+ struct stat s;
+ DIR* d;
+ struct dirent* file;
+ mode_t mode = 0;
+
+ save = push_path_buffer(path);
+
+ if (lstat(get_path_buffer(), &s) < 0) {
+ if (!flags.quiet) {
+ error_s("cannot stat '%s': %s", get_path_buffer(), strerror(errno));
+ }
+ pop_path_buffer(save);
+ return;
+ }
+
+ if (flags.method == SET) {
+ mode = flags.mode;;
+ } else if (flags.method == ADD) {
+ mode = s.st_mode | flags.mode;
+ } else if (flags.method == SUB) {
+ mode = s.st_mode & ~flags.mode;
+ }
+
+ if (chmod(get_path_buffer(), mode) < 0) {
+ if (!flags.quiet) {
+ error_s("cannot chmod '%s': %s", get_path_buffer(), strerror(errno));
+ }
+ pop_path_buffer(save);
+ return;
+ } else if (flags.list_changed) {
+ printf("chmod: changed '%s' to %o\n", get_path_buffer(), mode);
+ }
+
+ if (!flags.recurse) {
+ pop_path_buffer(save);
+ return;
+ }
+
+ if (!S_ISDIR(s.st_mode)) {
+ pop_path_buffer(save);
+ return;
+ }
+
+ d = opendir(get_path_buffer());
+ if (d == NULL) {
+ if (!flags.quiet) {
+ error_s("cannot open dir '%s': %s", get_path_buffer(), strerror(errno));
+ }
+ pop_path_buffer(save);
+ return;
+ }
+
+ while ((file = readdir(d)) != NULL) {
+ if (is_dot_dir(file->d_name)) continue;
+ chmod_file(file->d_name);
+ }
+
+ closedir(d);
+ pop_path_buffer(save);
+}
+
+static mode_t parse_mode(char** input) {
+ mode_t mode = 00000;
+ char* str = *input;
+
+ switch (*str) {
+ case '=':
+ flags.method = SET;
+ break;
+ case '+':
+ flags.method = ADD;
+ break;
+ case '-':
+ flags.method = SUB;
+ break;
+ default:
+ flags.method = SET;
+ mode = get_mode(str);
+ goto end;
+ }
+
+next:
+ str++;
+ switch (*str) {
+ case 'r':
+ mode |= 00444;
+ goto next;
+ case 'w':
+ mode |= 00200;
+ goto next;
+ case 'x':
+ mode |= 00111;
+ goto next;
+ case 'X':
+ mode |= 00000;
+ goto next;
+ case 's':
+ mode |= 06000;
+ goto next;
+ case 't':
+ mode |= 01000;
+ goto next;
+ case '\0':
+ case ',':
+ goto end;
+ default:
+ error("invalid option: %c", *str);
+ }
+
+end:
+ while (true) {
+
+ if (*str == '\0') {
+ *input = NULL;
+ break;
+ }
+
+ if (*str == ',') {
+ *input = ++str;
+ break;
+ }
+
+ str++;
+ }
+
+ return mode;
+}
+
+COMMAND(chmod_cmd) {
+
+ int start, i;
+
+ flags.recurse = false;
+ flags.list_changed = false;
+ flags.verbose = false;
+ flags.quiet = false;
+ flags.mode = 0;
+
+ start = parse_args(argc, argv, help, short_arg, NULL);
+
+ if (argc - start < 1) {
+ if (!flags.quiet) {
+ error("no mode provided");
+ } else {
+ return EXIT_FAILURE;
+ }
+ }
+
+ do {
+ flags.mode |= parse_mode(&argv[start]);
+ } while (argv[start] != NULL);
+
+ if (argc - start < 2) {
+ if (!flags.quiet) {
+ error("no files passed");
+ } else {
+ return EXIT_FAILURE;
+ }
+ }
+
+ for (i = start + 1; i < argc; i++) {
+ chmod_file(argv[i]);
+ }
+
+ return EXIT_SUCCESS;
+}