2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* file: chmod.c
|
2023-06-08 01:33:44 +00:00
|
|
|
* command: chmod
|
2023-05-16 23:15:01 +00:00
|
|
|
* author: Tyler Murphy
|
|
|
|
*/
|
|
|
|
|
2023-05-12 21:38:41 +00:00
|
|
|
#include "command.h"
|
|
|
|
#include "lslib.h"
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* If to add remove or directly set bits to a file
|
|
|
|
*/
|
2023-05-12 21:38:41 +00:00
|
|
|
enum method {
|
|
|
|
ADD,
|
|
|
|
SUB,
|
|
|
|
SET
|
|
|
|
};
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* Flags to set in chmod
|
|
|
|
*/
|
2023-05-12 21:38:41 +00:00
|
|
|
static struct {
|
2023-05-16 23:15:01 +00:00
|
|
|
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 */
|
2023-05-12 21:38:41 +00:00
|
|
|
} flags;
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* Help function for chmod
|
|
|
|
*/
|
2023-05-12 21:38:41 +00:00
|
|
|
static void help (void) {
|
|
|
|
printf("Usage: chmod [-Rcvf] MODE[,MODE]... FILE...\n\n");
|
2023-05-15 01:43:02 +00:00
|
|
|
printf("MODE is octal number (bit pattern sstrwxrwxrwx) or {+|-|=}[rwxXst]\n\n");
|
2023-05-12 21:38:41 +00:00
|
|
|
printf("\t-R\tRecurse\n");
|
|
|
|
printf("\t-c\tList changed files\n");
|
|
|
|
printf("\t-v\tVerbose\n");
|
|
|
|
printf("\t-f\tHide errors\n");
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2023-05-12 21:38:41 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* Given a file path, modify its mode, and recurse if set to
|
|
|
|
* @param path the file path to modify
|
|
|
|
*/
|
2023-05-12 21:38:41 +00:00
|
|
|
static void chmod_file(char* path) {
|
2023-05-16 23:15:01 +00:00
|
|
|
/* 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 */
|
2023-05-12 21:38:41 +00:00
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* add path to path buffer */
|
2023-05-12 21:38:41 +00:00
|
|
|
save = push_path_buffer(path);
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* get file statics, if failed return */
|
2023-05-12 21:38:41 +00:00
|
|
|
if (lstat(get_path_buffer(), &s) < 0) {
|
|
|
|
if (!flags.quiet) {
|
2023-05-16 23:15:01 +00:00
|
|
|
error_s("cannot stat '%s'", get_path_buffer()); /* error if failed */
|
2023-05-12 21:38:41 +00:00
|
|
|
}
|
|
|
|
pop_path_buffer(save);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* get the new mode given the method and proved mode */
|
2023-05-12 21:38:41 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* attempt to modify the file mode, error and return if failed */
|
2023-05-12 21:38:41 +00:00
|
|
|
if (chmod(get_path_buffer(), mode) < 0) {
|
|
|
|
if (!flags.quiet) {
|
2023-05-16 23:15:01 +00:00
|
|
|
error_s("cannot chmod '%s'", get_path_buffer()); /* error if failed */
|
2023-05-12 21:38:41 +00:00
|
|
|
}
|
2023-05-16 23:15:01 +00:00
|
|
|
pop_path_buffer(save); /* clean up */
|
2023-05-12 21:38:41 +00:00
|
|
|
return;
|
|
|
|
} else if (flags.list_changed) {
|
2023-05-16 23:15:01 +00:00
|
|
|
output("changed '%s' to %o", get_path_buffer(), mode); /* print if set to be verbose */
|
2023-05-12 21:38:41 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* if not set to recurse, dont bother to do future checks */
|
2023-05-12 21:38:41 +00:00
|
|
|
if (!flags.recurse) {
|
|
|
|
pop_path_buffer(save);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* if not a dir, cant recurse and then return */
|
2023-05-12 21:38:41 +00:00
|
|
|
if (!S_ISDIR(s.st_mode)) {
|
|
|
|
pop_path_buffer(save);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* get the directory, if failed, error and return */
|
2023-05-12 21:38:41 +00:00
|
|
|
d = opendir(get_path_buffer());
|
|
|
|
if (d == NULL) {
|
|
|
|
if (!flags.quiet) {
|
2023-05-16 23:15:01 +00:00
|
|
|
error_s("cannot open dir '%s'", get_path_buffer()); /* error if failed */
|
2023-05-12 21:38:41 +00:00
|
|
|
}
|
2023-05-16 23:15:01 +00:00
|
|
|
pop_path_buffer(save); /* clean up */
|
2023-05-12 21:38:41 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* chmod each file read */
|
2023-05-12 21:38:41 +00:00
|
|
|
while ((file = readdir(d)) != NULL) {
|
2023-05-16 23:15:01 +00:00
|
|
|
if (is_dot_dir(file->d_name)) continue; /* ignore dot dirs */
|
2023-05-12 21:38:41 +00:00
|
|
|
chmod_file(file->d_name);
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* clean up */
|
2023-05-12 21:38:41 +00:00
|
|
|
closedir(d);
|
|
|
|
pop_path_buffer(save);
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2023-05-12 21:38:41 +00:00
|
|
|
static mode_t parse_mode(char** input) {
|
2023-05-16 23:15:01 +00:00
|
|
|
mode_t mode = 00000; /* default mode */
|
|
|
|
char* str = *input; /* get the input string */
|
2023-05-12 21:38:41 +00:00
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* check method for mode */
|
2023-05-12 21:38:41 +00:00
|
|
|
switch (*str) {
|
|
|
|
case '=':
|
|
|
|
flags.method = SET;
|
|
|
|
break;
|
|
|
|
case '+':
|
|
|
|
flags.method = ADD;
|
|
|
|
break;
|
|
|
|
case '-':
|
|
|
|
flags.method = SUB;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
flags.method = SET;
|
2023-05-16 23:15:01 +00:00
|
|
|
mode = get_mode(str); /* get mode and skip next checks */
|
2023-05-12 21:38:41 +00:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
next: /* check what bits to update */
|
2023-05-12 21:38:41 +00:00
|
|
|
str++;
|
|
|
|
switch (*str) {
|
2023-05-16 23:15:01 +00:00
|
|
|
case 'r': /* read */
|
2023-05-12 21:38:41 +00:00
|
|
|
mode |= 00444;
|
|
|
|
goto next;
|
2023-05-16 23:15:01 +00:00
|
|
|
case 'w': /* read */
|
2023-05-12 21:38:41 +00:00
|
|
|
mode |= 00200;
|
|
|
|
goto next;
|
2023-05-16 23:15:01 +00:00
|
|
|
case 'x': /* execute */
|
2023-05-12 21:38:41 +00:00
|
|
|
mode |= 00111;
|
|
|
|
goto next;
|
2023-05-16 23:15:01 +00:00
|
|
|
case 'X': /* empty */
|
2023-05-12 21:38:41 +00:00
|
|
|
mode |= 00000;
|
|
|
|
goto next;
|
2023-05-16 23:15:01 +00:00
|
|
|
case 's': /* setuid and setgid */
|
2023-05-12 21:38:41 +00:00
|
|
|
mode |= 06000;
|
|
|
|
goto next;
|
2023-05-16 23:15:01 +00:00
|
|
|
case 't': /* sticky */
|
2023-05-12 21:38:41 +00:00
|
|
|
mode |= 01000;
|
|
|
|
goto next;
|
|
|
|
case '\0':
|
2023-05-16 23:15:01 +00:00
|
|
|
case ',': /* stop reading args */
|
2023-05-12 21:38:41 +00:00
|
|
|
goto end;
|
|
|
|
default:
|
2023-05-16 23:15:01 +00:00
|
|
|
error("invalid option: %c", *str); /* error when invalid */
|
2023-05-12 21:38:41 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
end: /* read till next argument and update input string pointer */
|
|
|
|
|
2023-05-12 21:38:41 +00:00
|
|
|
while (true) {
|
|
|
|
|
|
|
|
if (*str == '\0') {
|
2023-05-16 23:15:01 +00:00
|
|
|
*input = NULL; /* no mroe args */
|
2023-05-12 21:38:41 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*str == ',') {
|
2023-05-16 23:15:01 +00:00
|
|
|
*input = ++str; /* another argument found */
|
2023-05-12 21:38:41 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
str++;
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
return mode; /* return mode */
|
2023-05-12 21:38:41 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/**
|
|
|
|
* Modify a files permission mode
|
|
|
|
*/
|
2023-05-15 14:57:33 +00:00
|
|
|
COMMAND(chmod_main) {
|
2023-05-12 21:38:41 +00:00
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
int start, i;
|
2023-05-12 21:38:41 +00:00
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* get default flags */
|
2023-05-12 21:38:41 +00:00
|
|
|
flags.recurse = false;
|
|
|
|
flags.list_changed = false;
|
|
|
|
flags.verbose = false;
|
|
|
|
flags.quiet = false;
|
|
|
|
flags.mode = 0;
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* parse arguments */
|
2023-05-12 21:38:41 +00:00
|
|
|
start = parse_args(argc, argv, help, short_arg, NULL);
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* if not arguments error and return */
|
2023-05-12 21:38:41 +00:00
|
|
|
if (argc - start < 1) {
|
|
|
|
if (!flags.quiet) {
|
|
|
|
error("no mode provided");
|
|
|
|
} else {
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* parse mode continusally until no more mode arguments found */
|
2023-05-12 21:38:41 +00:00
|
|
|
do {
|
|
|
|
flags.mode |= parse_mode(&argv[start]);
|
|
|
|
} while (argv[start] != NULL);
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* if no files found error and return */
|
2023-05-12 21:38:41 +00:00
|
|
|
if (argc - start < 2) {
|
|
|
|
if (!flags.quiet) {
|
|
|
|
error("no files passed");
|
|
|
|
} else {
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
/* for each file passed, chmod */
|
2023-05-12 21:38:41 +00:00
|
|
|
for (i = start + 1; i < argc; i++) {
|
|
|
|
chmod_file(argv[i]);
|
|
|
|
}
|
|
|
|
|
2023-05-16 23:15:01 +00:00
|
|
|
return EXIT_SUCCESS; /* return */
|
2023-05-12 21:38:41 +00:00
|
|
|
}
|