#include "args.h" #include "command.h" #include "lslib.h" #include #include #include #include #include #include #include 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; }