/** * file: chmod.c * command: chmod * author: Tyler Murphy */ #include "command.h" #include "lslib.h" #include #include #include #include #include #include /** * If to add remove or directly set bits to a file */ enum method { ADD, SUB, SET }; /** * Flags to set in chmod */ static struct { bool recurse; /* if to recurse directorys */ bool list_changed; /* if to list what was updated */ bool verbose; /* if to list all output */ bool quiet; /* if to silence errors */ enum method method; /* the method to apply to the mode */ mode_t mode; /* the mode to apply with the method */ } flags; /** * Help function for chmod */ static void help (void) { printf("Usage: chmod [-Rcvf] MODE[,MODE]... FILE...\n\n"); printf("MODE is octal number (bit pattern sstrwxrwxrwx) or {+|-|=}[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"); } /** * Takes in each argument that has a single - and parses it * @param c the character after the - * @param next the next argument in argv that hasnt been parsed * @reutrn if the next arg was used or if the arg was invalid */ 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; } /** * Given a file path, modify its mode, and recurse if set to * @param path the file path to modify */ static void chmod_file(char* path) { /* allocate arguments on stack */ int save; /* path buffer save */ struct stat s; /* file info */ DIR* d; /* if recursing, open dir */ struct dirent* file; /* if recursing, file being read */ mode_t mode = 0; /* silence the uninitalized var error */ /* add path to path buffer */ save = push_path_buffer(path); /* get file statics, if failed return */ if (lstat(get_path_buffer(), &s) < 0) { if (!flags.quiet) { error_s("cannot stat '%s'", get_path_buffer()); /* error if failed */ } pop_path_buffer(save); return; } /* get the new mode given the method and proved mode */ 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; } /* attempt to modify the file mode, error and return if failed */ if (chmod(get_path_buffer(), mode) < 0) { if (!flags.quiet) { error_s("cannot chmod '%s'", get_path_buffer()); /* error if failed */ } pop_path_buffer(save); /* clean up */ return; } else if (flags.list_changed) { output("changed '%s' to %o", get_path_buffer(), mode); /* print if set to be verbose */ } /* if not set to recurse, dont bother to do future checks */ if (!flags.recurse) { pop_path_buffer(save); return; } /* if not a dir, cant recurse and then return */ if (!S_ISDIR(s.st_mode)) { pop_path_buffer(save); return; } /* get the directory, if failed, error and return */ d = opendir(get_path_buffer()); if (d == NULL) { if (!flags.quiet) { error_s("cannot open dir '%s'", get_path_buffer()); /* error if failed */ } pop_path_buffer(save); /* clean up */ return; } /* chmod each file read */ while ((file = readdir(d)) != NULL) { if (is_dot_dir(file->d_name)) continue; /* ignore dot dirs */ chmod_file(file->d_name); } /* clean up */ closedir(d); pop_path_buffer(save); } /** * Parse a given mode and its method * @param input a pointer to the string to check * @return the mode parsed, mode parsing could not be done if reached , if so call again */ static mode_t parse_mode(char** input) { mode_t mode = 00000; /* default mode */ char* str = *input; /* get the input string */ /* check method for mode */ 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); /* get mode and skip next checks */ goto end; } next: /* check what bits to update */ str++; switch (*str) { case 'r': /* read */ mode |= 00444; goto next; case 'w': /* read */ mode |= 00200; goto next; case 'x': /* execute */ mode |= 00111; goto next; case 'X': /* empty */ mode |= 00000; goto next; case 's': /* setuid and setgid */ mode |= 06000; goto next; case 't': /* sticky */ mode |= 01000; goto next; case '\0': case ',': /* stop reading args */ goto end; default: error("invalid option: %c", *str); /* error when invalid */ } end: /* read till next argument and update input string pointer */ while (true) { if (*str == '\0') { *input = NULL; /* no mroe args */ break; } if (*str == ',') { *input = ++str; /* another argument found */ break; } str++; } return mode; /* return mode */ } /** * Modify a files permission mode */ COMMAND(chmod_main) { int start, i; /* get default flags */ flags.recurse = false; flags.list_changed = false; flags.verbose = false; flags.quiet = false; flags.mode = 0; /* parse arguments */ start = parse_args(argc, argv, help, short_arg, NULL); /* if not arguments error and return */ if (argc - start < 1) { if (!flags.quiet) { error("no mode provided"); } else { return EXIT_FAILURE; } } /* parse mode continusally until no more mode arguments found */ do { flags.mode |= parse_mode(&argv[start]); } while (argv[start] != NULL); /* if no files found error and return */ if (argc - start < 2) { if (!flags.quiet) { error("no files passed"); } else { return EXIT_FAILURE; } } /* for each file passed, chmod */ for (i = start + 1; i < argc; i++) { chmod_file(argv[i]); } return EXIT_SUCCESS; /* return */ }