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 +#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; +} diff --git a/command/chown.c b/command/chown.c new file mode 100644 index 0000000..164e3b4 --- /dev/null +++ b/command/chown.c @@ -0,0 +1,187 @@ +#include "command.h" +#include "lslib.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct { + bool recurse; + bool list_changed; + bool verbose; + bool quiet; + uid_t uid; + gid_t gid; +} flags; + +static void help (void) { + printf("Usage: chown [-Rcvf]... USER[:[GRP]] FILE...\n\n"); + printf("Change the owner and/or group of FILEs to USER and/or GRP\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; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +static void chown_file(char* path) { + int save; + struct stat s; + DIR* d; + struct dirent* file; + + save = push_path_buffer(path); + + if (chown(get_path_buffer(), flags.uid, flags.gid) < 0) { + if (!flags.quiet) { + error_s("cannot chown '%s': %s", get_path_buffer(), strerror(errno)); + } + pop_path_buffer(save); + return; + } else if (flags.list_changed) { + printf("chown: changed '%s' to %u:%u\n", get_path_buffer(), flags.uid, flags.gid); + } + + if (!flags.recurse) { + pop_path_buffer(save); + return; + } + + 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 (!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; + chown_file(file->d_name); + } + + closedir(d); + pop_path_buffer(save); +} + +static void parse_ownership(char* str) { + char* user = str; + char* group = NULL; + char* end = NULL; + unsigned long i; + struct passwd* p = NULL; + struct group* g = NULL; + + for (i = 0; i < strlen(str); i++) { + if (str[i] == ':') { + str[i] = '\0'; + group = &str[i] + 1; + break; + } + } + + flags.uid = strtol(user, &end, 10); + if (end != user) goto group; + + if ((p = getpwnam(user)) == NULL) { + error("invalid user '%s'", user); + } else { + flags.uid = p->pw_uid; + } + +group: + + if (group == NULL) { + if (p == NULL) { + flags.gid = flags.uid; + } else { + flags.gid = p->pw_gid; + } + return; + } + + flags.gid = strtol(group, &end, 10); + if (end != group) return; + + if ((g = getgrnam(group)) == NULL) { + error("invalid group '%s'", group); + } else { + flags.gid = g->gr_gid; + } +} + +COMMAND(chown_cmd) { + + int start, i; + + flags.recurse = false; + flags.list_changed = false; + flags.verbose = false; + flags.quiet = false; + + start = parse_args(argc, argv, help, short_arg, NULL); + + if (argc - start < 1) { + if (!flags.quiet) { + error("no onwership passed"); + } else { + return EXIT_FAILURE; + } + } + + parse_ownership(argv[start]); + + if (argc - start < 1) { + if (!flags.quiet) { + error("no files passed"); + } else { + return EXIT_FAILURE; + } + } + + for (i = start + 1; i < argc; i++) { + chown_file(argv[i]); + } + + return EXIT_SUCCESS; +} diff --git a/command/ls.c b/command/ls.c index 8ed796f..bbfedfb 100644 --- a/command/ls.c +++ b/command/ls.c @@ -133,35 +133,46 @@ static bool get_file_info(const char* file_name, struct FileInfo* info) { info->mode[1] = (s.st_mode & S_IRUSR) ? 'r' : '-'; info->mode[2] = (s.st_mode & S_IWUSR) ? 'w' : '-'; - if (s.st_mode & S_IXUSR) { - if (s.st_mode & S_ISUID) { - info->mode[3] = 's'; - info->set_uid = true; - } else { - info->mode[3] = 'x'; - } - if (!info->exec) info->exec = s.st_uid == uid; + if (s.st_mode & S_IXUSR && s.st_mode & S_ISUID) { + info->mode[3] = 's'; + info->set_uid = true; + if (!info->exec) info->exec = info->usr->pw_uid == uid; + } else if (s.st_mode & S_ISUID) { + info->mode[3] = 'S'; + info->set_uid = true; + } else if (s.st_mode & S_IXUSR) { + info->mode[3] = 'x'; + if (!info->exec) info->exec = info->usr->pw_uid == uid; } else { info->mode[3] = '-'; } info->mode[4] = (s.st_mode & S_IRGRP) ? 'r' : '-'; info->mode[5] = (s.st_mode & S_IWGRP) ? 'w' : '-'; - if (s.st_mode & S_IXGRP) { - if (s.st_mode & S_ISGID) { - info->mode[6] = 's'; - info->set_gid = true; - } else { - info->mode[6] = 'x'; - } - if (!info->exec) info->exec = s.st_gid == gid; + if (s.st_mode & S_IXGRP && s.st_mode & S_ISGID) { + info->mode[6] = 's'; + info->set_gid = true; + if (!info->exec) info->exec = info->grp->gr_gid == gid; + } else if (s.st_mode & S_ISGID) { + info->mode[6] = 'S'; + info->set_gid = true; + } else if (s.st_mode & S_IXGRP) { + info->mode[6] = 'x'; + if (!info->exec) info->exec = info->grp->gr_gid == gid; } else { info->mode[6] = '-'; } info->mode[7] = (s.st_mode & S_IROTH) ? 'r' : '-'; info->mode[8] = (s.st_mode & S_IWOTH) ? 'w' : '-'; - if (s.st_mode & S_IXOTH) { + if (s.st_mode & S_IXOTH && s.st_mode & S_ISVTX) { + info->mode[9] = 't'; + info->set_gid = true; + info->exec = true; + } else if (s.st_mode & S_ISVTX) { + info->mode[9] = 'T'; + info->set_gid = true; + } else if (s.st_mode & S_IXOTH) { info->mode[9] = 'x'; info->exec = true; } else { diff --git a/lib/convert.c b/lib/convert.c index 071268e..a4639c4 100644 --- a/lib/convert.c +++ b/lib/convert.c @@ -46,7 +46,7 @@ mode_t get_mode(const char* next) { mode_t mode = (mode_t)strtol(next, &end, 8); if (!end) return 0; while(isspace(*end)) end++; - if (*end != '\0' || (unsigned) mode < 010000) { + if (*end != '\0' || (unsigned) mode >= 010000) { error("invalid file mode: `%s`", next); } return mode; diff --git a/readme.md b/readme.md index f63aa5e..6eed372 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ A terrible busybox/gnu coreutils clone. Currently the only supported commands are: -`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head`, `ed`, `tee`, `true`, `false`, `tee`, `whoami`, `wc`, `xargs`, `tac`, `rm`, `cp`, `mkdir`, `mv`, `grep` +`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head`, `ed`, `tee`, `true`, `false`, `tee`, `whoami`, `wc`, `xargs`, `tac`, `rm`, `cp`, `mkdir`, `mv`, `grep`, `chown`, `chmod` ## How to diff --git a/src/command.h b/src/command.h index 87cc749..fa8b00c 100644 --- a/src/command.h +++ b/src/command.h @@ -26,5 +26,7 @@ COMMAND(cp); COMMAND(makedir); COMMAND(mv); COMMAND(grep); +COMMAND(chown_cmd); +COMMAND(chmod_cmd); #endif diff --git a/src/main.c b/src/main.c index ad64b10..2718041 100644 --- a/src/main.c +++ b/src/main.c @@ -1,4 +1,5 @@ #include "command.h" +#include "convert.h" #include "lslib.h" #include @@ -22,7 +23,7 @@ int main (ARGUMENTS) { if (argc < 2) { printf("usage: lazysphere [function [arguments]...]\n\n"); printf("currently defined functions:\n"); - printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail, head, ed, tee, true, false, tee, whoami, wc, xargs, tac, rm, cp, mkdir, mv, grep\n"); + printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail, head, ed, tee, true, false, tee, whoami, wc, xargs, tac, rm, cp, mkdir, mv, grep, chown, chmod\n"); return EXIT_SUCCESS; } argc--; @@ -84,6 +85,10 @@ int main (ARGUMENTS) { return mv(NEXT_ARGS); } else if (streql(cmd, "grep")) { return grep(NEXT_ARGS); + } else if (streql(cmd, "chown")) { + return chown_cmd(NEXT_ARGS); + } else if (streql(cmd, "chmod")) { + return chmod_cmd(NEXT_ARGS); } else { fprintf(stderr, "lazysphere: invalid command %s\n", cmd); return EXIT_FAILURE;