From f2606d0875dbaadb3f414d98d8f37fdbdf6036ea Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Wed, 3 May 2023 12:17:56 -0400 Subject: [PATCH] add rm, cp, mkdir, mv --- Makefile | 1 + readme.md | 2 +- src/command.h | 4 + src/commands/cat.c | 2 +- src/commands/cp.c | 232 ++++++++++++++++++++++++++++++++++++++++++ src/commands/dd.c | 6 +- src/commands/ed.c | 64 ++++++------ src/commands/groups.c | 4 +- src/commands/head.c | 9 +- src/commands/id.c | 4 +- src/commands/ls.c | 20 ++-- src/commands/mkdir.c | 60 +++++++++++ src/commands/mv.c | 108 ++++++++++++++++++++ src/commands/printf.c | 3 +- src/commands/rm.c | 132 ++++++++++++++++++++++++ src/commands/tail.c | 11 +- src/commands/tee.c | 2 +- src/commands/wc.c | 2 +- src/commands/xargs.c | 4 +- src/main.c | 21 +++- src/util/shared.c | 106 +++++++++++++++---- src/util/shared.h | 14 +++ 22 files changed, 718 insertions(+), 93 deletions(-) create mode 100644 src/commands/cp.c create mode 100644 src/commands/mkdir.c create mode 100644 src/commands/mv.c create mode 100644 src/commands/rm.c diff --git a/Makefile b/Makefile index ba4984d..708443e 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ CCFLAGS = -std=c99 -Wall -Wextra -pedantic -O2 CCFLAGS += -D_DEFAULT_SOURCE -DMAJOR=$(MAJOR) -DMINOR=$(MINOR) -DPATCH=$(PATCH) -DCHECK_LINK CCFLAGS += $(INCFLAGS) +LDFLAGS = -s LDFLAGS += $(INCFLAGS) BIN = bin diff --git a/readme.md b/readme.md index a594b66..05eea07 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` +`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head`, `ed`, `tee`, `true`, `false`, `tee`, `whoami`, `wc`, `xargs`, `tac`, `rm`, `cp`, `mkdir`, `mv` ## How to diff --git a/src/command.h b/src/command.h index a73e3c8..2e468dd 100644 --- a/src/command.h +++ b/src/command.h @@ -33,3 +33,7 @@ COMMAND(whoami); COMMAND(wc); COMMAND(xargs); COMMAND(tac); +COMMAND(rm); +COMMAND(cp); +COMMAND(makedir); +COMMAND(mv); diff --git a/src/commands/cat.c b/src/commands/cat.c index 6ed77e8..8c15522 100644 --- a/src/commands/cat.c +++ b/src/commands/cat.c @@ -103,7 +103,7 @@ static int short_arg(char c, char* next) { flags.end_lines_dollar = true; break; default: - error("error: unkown flag -%c", c); + return ARG_INVALID; } return ARG_UNUSED; } diff --git a/src/commands/cp.c b/src/commands/cp.c new file mode 100644 index 0000000..37e3354 --- /dev/null +++ b/src/commands/cp.c @@ -0,0 +1,232 @@ +#include "../command.h" +#include +#include +#include +#include +#include + +static struct { + bool recurse; + bool preserve; + bool sym_link; + bool hard_link; + bool verbose; +} flags; + +static void help(void) { + printf("Usage: cp [-rplsv] SOURCE... DEST\n\n"); + printf("Copy SOURCEs to DEST\n\n"); + printf("\t-R,-r\tRecurse\n"); + printf("\t-p\tPreserve file attributes if possible\n"); + printf("\t-l,-s\tCreate (sym)links\n"); + printf("\t-v\tVerbose\n"); +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'R': + case 'r': + flags.recurse = true; + break; + case 'p': + flags.preserve = true; + break; + case 'l': + flags.hard_link = true; + flags.sym_link = false; + break; + case 's': + flags.sym_link = true; + flags.hard_link = false; + break; + case 'v': + flags.verbose = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +static bool copy_file(char* from, char* to) { + FILE* from_f = get_file_s(from, "r"); + if (from_f == NULL) { return false; } + + FILE* to_f = get_file_s(to, "w"); + if (to_f == NULL) { fclose(from_f); return false; } + + #define BS 1024 + + char buf[BS]; + int read; + + while ((read = fread(buf, 1, BS, from_f)) > 0) { + fwrite(buf, 1, read, to_f); + } + + if (flags.verbose) { + output("copied '%s'", from); + } + + return true; +} + +static bool symlink_file(char* from, char* to) { + if (symlink(from, to) < 0) { + error_s("failed to symlink '%s': %s", from, strerror(errno)); + return false; + } else if (flags.verbose) { + output("symlinked '%s'", from); + } + return true; +} + +static bool hardlink_file(char* from, char* to) { + if (link(from, to) < 0) { + error_s("failed to hardlink '%s': %s", from, strerror(errno)); + return false; + } else if (flags.verbose) { + output("hardlinked '%s'", from); + } + return true; +} + +static void run_copy(struct stat* s) { + + char* from = get_path_buffer(); + char* to = get_path_buffer_2(); + + bool result; + if (flags.sym_link) { + result = symlink_file(from, to); + } else if (flags.hard_link) { + result = hardlink_file(from, to); + } else { + result = copy_file(from, to); + } + + if (!result) return; + if (!flags.preserve) return; + + if (chmod(to, s->st_mode) < 0) { + error_s("cannot chmod '%s': %s", to, strerror(errno)); + return; + } + + if (chown(to, s->st_uid, s->st_gid) < 0) { + error_s("cannot chown '%s': %s", to, strerror(errno)); + return; + } +} + +static void cp_file(char* path); + +static void cp_directory(struct stat* s) { + if (!flags.recurse) { + error_s("-r not specified; omitting directory '%s'", get_path_buffer()); + return; + } + + if (mkdir(get_path_buffer_2(), s->st_mode) < 0 && errno != EEXIST) { + error_s("cannot create directory '%s': %s", get_path_buffer_2(), strerror(errno)); + return; + } + + DIR* d = opendir(get_path_buffer()); + + if (d == NULL) { + error_s("cannot open directory '%s': %s", get_path_buffer(), strerror(errno)); + return; + } + + struct dirent* file; + while ((file = readdir(d)) != NULL) { + if (is_dot_dir(file->d_name)) continue; + cp_file(file->d_name); + } +} + +static char* get_file_name(char* path) { + if (path[0] == '\0') return path; + + int last = 0; + int i = 0; + while (true) { + if (path[i+1] == '\0') break; + if (path[i] == '/') { + last = i; + } + i++; + } + + if (last == 0) return path; + else return path + last + 1; +} + +static void cp_file(char* path) { + + int save = push_path_buffer(path); + int save2 = push_path_buffer_2(get_file_name(path)); + + struct stat s; + if (lstat(get_path_buffer(), &s) < 0) { + pop_path_buffer(save); + error_s("cannot stat '%s': %s", get_path_buffer(), strerror(errno)); + return; + } + + if (S_ISDIR(s.st_mode)) { + cp_directory(&s); + } else { + run_copy(&s); + } + + pop_path_buffer(save); + pop_path_buffer_2(save2); +} + +COMMAND(cp) { + + flags.hard_link = false; + flags.sym_link = false; + flags.preserve = false; + flags.recurse = false; + flags.verbose = false; + + int start = parse_args(argc, argv, help, short_arg, NULL); + + if (argc - start < 2) { + global_help(help); + } + + // only when 2 args and first is a file, the 2nd will be a file + if (argc - start == 2) { + struct stat s; + if (lstat(argv[start], &s) < 0) { + error("cannot stat '%s': %s", argv[start], strerror(errno)); + } + push_path_buffer(argv[argc-2]); + push_path_buffer_2(argv[argc-1]); + if (!S_ISDIR(s.st_mode)) { + run_copy(&s); + } else { + cp_directory(&s); + } + return EXIT_SUCCESS; + } + + // push directory + push_path_buffer_2(argv[argc-1]); + + struct stat s; + if (lstat(get_path_buffer_2(), &s) < 0) { + error("target: '%s': %s", get_path_buffer_2(), strerror(errno)); + } + + for (int i = start; i < argc - 1; i++) { + cp_file(argv[i]); + } + + return EXIT_SUCCESS; +} diff --git a/src/commands/dd.c b/src/commands/dd.c index 6b973e2..78f6041 100644 --- a/src/commands/dd.c +++ b/src/commands/dd.c @@ -29,16 +29,16 @@ COMMAND(dd) { char* str = argv[i] + 3; bs = get_number(str); if (bs < 1) { - error("error: block size must be greater than 0"); + error("block size must be greater than 0"); } } else if (prefix("count=", argv[i])) { char* str = argv[i] + 6; count = get_number(str); if (count < 1) { - error("error: count must be greather than 0"); + error("count must be greather than 0"); } } else { - error("error: unkown option %s", argv[i]); + error("unkown option %s", argv[i]); } } diff --git a/src/commands/ed.c b/src/commands/ed.c index 44f1a2c..379da3c 100644 --- a/src/commands/ed.c +++ b/src/commands/ed.c @@ -50,7 +50,7 @@ static bool read_regex(char** end, re_t* regex, enum RegexDirection dir) { while(true) { c = *(index++); if (c == '\0') { - fprintf(stderr, "error: missing regex after %c\n", dir == BEFORE ? '?' : '/'); + error_s("missing regex after %c\n", dir == BEFORE ? '?' : '/'); return false; } if (c == (dir == BEFORE ? '?' : '/')) { @@ -170,7 +170,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a) n_pre = -1; } else { if (n_pre < 0) { - fprintf(stderr, "error: input cannot be negative\n"); + error_s("input cannot be negative\n"); return false; } index = end_pre; @@ -192,7 +192,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a) char* end; long int n = strtol(index, &end, 10) - 1; if (n < 0) { - fprintf(stderr, "error: input cannot be negative\n"); + error_s("input cannot be negative\n"); return false; } if (index == end) { @@ -201,7 +201,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a) address.data.index.i = line_current - n; } if (address.data.index.i < 0) { - fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + error_s("line number %ld does not exist\n", address.data.index.i + 1); return false; } break; @@ -211,7 +211,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a) char* end; long int n = strtol(index, &end, 10) - 1; if (n < 0) { - fprintf(stderr, "error: input cannot be negative\n"); + error_s("input cannot be negative\n"); return false; } if (index == end) { @@ -220,7 +220,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a) address.data.index.i = line_current + n; } if (address.data.index.i >= (long int) line_count) { - fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + error_s("line number %ld does not exist\n", address.data.index.i + 1); return false; } break; @@ -256,7 +256,7 @@ static bool read_address(char** command, bool whitespace, struct LineAddress* a) address.data.index.i = n_pre; } if (address.data.index.i < 0 || address.data.index.i >= (long int) line_count) { - fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + error_s("line number %ld does not exist\n", address.data.index.i + 1); return false; } } @@ -404,7 +404,7 @@ static void delete_lines(unsigned long a, unsigned long b) { static bool handle_append(struct LineAddress* address) { if (address->type != INDEX) { - fprintf(stderr, "error: append command requires index addressing\n"); + error_s("append command requires index addressing\n"); return false; } if (line_count == 0) { @@ -424,20 +424,20 @@ static bool handle_append(struct LineAddress* address) { static bool handle_delete(struct LineAddress* address) { if (address->empty && address->data.index.i >= (long int) line_count) { - fprintf(stderr, "error: line number %ld does not exist\n", address->data.index.i + 1); + error_s("line number %ld does not exist\n", address->data.index.i + 1); return false; } if (address->type == INDEX) { delete_lines(address->data.index.i, address->data.index.i); - printf("ed: deleted line %ld\n", address->data.index.i+1); + output("deleted line %ld\n", address->data.index.i+1); } else if (address->type == RANGE) { delete_lines(address->data.range.a, address->data.range.b); - printf("ed: deleted lines %ld-%ld\n", address->data.range.a+1, address->data.range.b+1); + output("deleted lines %ld-%ld\n", address->data.range.a+1, address->data.range.b+1); } else if (address->type == SET) { for (unsigned long i = 0; i < address->data.set.s; i++) { delete_lines(address->data.set.b[i], address->data.set.b[i]); } - printf("ed: deleted %lu lines\n", address->data.set.s); + output("deleted %lu lines\n", address->data.set.s); } return true; } @@ -446,7 +446,7 @@ static bool get_file_name(char** filename) { size_t len = strlen(*filename); if (len < 1 || (len == 1 && **filename == '\n')) { if (default_filename == NULL) { - fprintf(stderr, "error: no default filename specified\n"); + error_s("no default filename specified\n"); return false; } *filename = default_filename; @@ -468,7 +468,7 @@ static bool get_file_name(char** filename) { static void write_file(char* filename, struct LineAddress* address, char* type) { if (line_count < 1) { - fprintf(stderr, "error: cannot write empty file\n"); + error_s("cannot write empty file\n"); return; } if (!get_file_name(&filename)) return; @@ -497,7 +497,7 @@ static void write_file(char* filename, struct LineAddress* address, char* type) } pending_writes = false; fclose(file); - printf("ed: wrote %d lines from %s\n", wrote, filename); + output("wrote %d lines from %s\n", wrote, filename); } static void read_file(char* filename) { @@ -512,7 +512,7 @@ static void read_file(char* filename) { if (size < 1) { free(buf); - fprintf(stderr, "error: attempted to read a empty file\n"); + error_s("attempted to read a empty file\n"); return; } @@ -522,7 +522,7 @@ static void read_file(char* filename) { } append_lines(line, buf, size); free(buf); - printf("ed: read and appended %lu lines from %s\n", size, filename); + output("read and appended %lu lines from %s\n", size, filename); } static void expand_string(char** buf, int* capacity, int* size, char* text, int len) { @@ -582,7 +582,7 @@ static void prompt(void) { if (cmd == ',') { if (address.type != INDEX) { - fprintf(stderr, "error: comma range addressing requires two index addresses\n"); + error_s("comma range addressing requires two index addresses\n"); free_address(address); return; } @@ -593,7 +593,7 @@ static void prompt(void) { return; } if (address2.type != INDEX) { - fprintf(stderr, "error: comma range addressing requires two index addresses\n"); + error_s("comma range addressing requires two index addresses\n"); free_address(address); free_address(address2); return; @@ -606,7 +606,7 @@ static void prompt(void) { } if (address.type == RANGE && address.data.range.a > address.data.range.b) { - fprintf(stderr, "error: range addressing must be in ascending order\n"); + error_s("range addressing must be in ascending order\n"); free_address(address); return; } @@ -618,10 +618,10 @@ test: case '\n': if (address.empty) { if (line_current == line_count) { - fprintf(stderr, "error: line number %ld does not exist\n", line_current + 1); + error_s("line number %ld does not exist\n", line_current + 1); break; } else if (line_current + 1 == line_count) { - fprintf(stderr, "error: line number %ld does not exist\n", line_current + 2); + error_s("line number %ld does not exist\n", line_current + 2); break; } else { line_current++; @@ -634,7 +634,7 @@ test: } else if (address.type == RANGE) { line_current = address.data.range.b; } else if (address.type == SET) { - fprintf(stderr, "error: unexpected range addressing\n"); + error_s("unexpected range addressing\n"); break; } printf("%s", lines[line_current]); @@ -656,7 +656,7 @@ test: __attribute__((fallthrough)); case 'p': if (address.empty && address.data.index.i >= (long int) line_count) { - fprintf(stderr, "error: line number %ld does not exist\n", address.data.index.i + 1); + error_s("line number %ld does not exist\n", address.data.index.i + 1); break; } if (address.type == INDEX) { @@ -686,7 +686,7 @@ test: skip_whitespace(&index); free_address(address); if (*(index++) != '/') { - fprintf(stderr, "error: unexpected character at start of regex\n"); + error_s("unexpected character at start of regex\n"); break; } if (!parse_regex(&index, &address, ALL)) { return; } @@ -701,18 +701,18 @@ test: case 's': skip_whitespace(&index); if (*(index++) != '/') { - fprintf(stderr, "error: unexpected character at start of regex\n"); + error_s("unexpected character at start of regex\n"); break; } if (!parse_regex_lines(&index, &address)) { return; } char* replace = index; while(*index != '\0' && *index != '/') index++; if (*index != '/') { - fprintf(stderr, "error: / missing after %c\n", *index); + error_s("/ missing after %c\n", *index); break; } if (address.data.set.s < 1) { - printf("ed: no matches found\n"); + error_s("no matches found\n"); break; } *(index++) = '\0'; @@ -725,11 +725,11 @@ test: char* end; matches = strtol(index, &end, 10); if (end == index) { - fprintf(stderr, "error: invalid number: %s\n", index); + error_s("invalid number: %s\n", index); break; } if (matches < 1) { - fprintf(stderr, "error: matches cannot be less than 1\n"); + error_s("matches cannot be less than 1\n"); break; } } @@ -739,7 +739,7 @@ test: for (unsigned long i = 0; i < address.data.set.s; i++) { matches_found += substute_string(address.data.set.b[i], matches, last_regex, replace, sub_len); } - printf("ed: replaced %ld matches over %ld lines\n", matches_found, address.data.set.s); + output("replaced %ld matches over %ld lines\n", matches_found, address.data.set.s); pending_writes = true; break; case 'w': { @@ -782,7 +782,7 @@ test: printf("%ld\n", line_current + 1); break; default: - fprintf(stderr, "error: unimplemented command\n"); + error_s("unimplemented command\n"); break; } diff --git a/src/commands/groups.c b/src/commands/groups.c index aea92af..763f294 100644 --- a/src/commands/groups.c +++ b/src/commands/groups.c @@ -9,7 +9,7 @@ COMMAND_EMPTY(groups) { struct passwd* pw = getpwuid(uid); if(pw == NULL){ - perror("error: failed to fetch groups: "); + error("failed to fetch groups: %s", strerror(errno)); } int ngroups = 0; @@ -21,7 +21,7 @@ COMMAND_EMPTY(groups) { for (int i = 0; i < ngroups; i++){ struct group* gr = getgrgid(groups[i]); if(gr == NULL){ - perror("error: failed to fetch groups: "); + error("failed to fetch groups: %s", strerror(errno)); } printf("%s ",gr->gr_name); } diff --git a/src/commands/head.c b/src/commands/head.c index 44bc45b..c09dc9a 100644 --- a/src/commands/head.c +++ b/src/commands/head.c @@ -70,7 +70,7 @@ static int short_arg(char c, char* next) { check_arg(next); long int bkm = get_blkm(next); if (bkm < 1) { - error("error: bkm cannot be less than 1"); + error("bkm cannot be less than 1"); } flags.count = bkm; return ARG_USED; @@ -80,7 +80,7 @@ static int short_arg(char c, char* next) { check_arg(next); long int bkm = get_blkm(next); if (bkm < 1) { - error("error: bkm cannot be less than 1"); + error("bkm cannot be less than 1"); } flags.count = bkm; return ARG_USED; @@ -91,9 +91,8 @@ static int short_arg(char c, char* next) { case 'v': flags.print_headers = true; break; - default: { - error("error: unknown option -%c", c); - } + default: + return ARG_INVALID; } return ARG_UNUSED; } diff --git a/src/commands/id.c b/src/commands/id.c index 578c1e3..4bd7bca 100644 --- a/src/commands/id.c +++ b/src/commands/id.c @@ -10,7 +10,7 @@ COMMAND_EMPTY(user_id) { struct passwd* pw = getpwuid(uid); if(pw == NULL){ - perror("error: failed to fetch groups: "); + error("failed to fetch groups: %s", strerror(errno)); } int ngroups = 0; @@ -30,7 +30,7 @@ COMMAND_EMPTY(user_id) { for (int i = 0; i < ngroups; i++){ struct group* gr = getgrgid(groups[i]); if(gr == NULL){ - perror("error: failed to fetch groups: "); + error("failed to fetch groups: %s", strerror(errno)); } printf("%d(%s)", gr->gr_gid, gr->gr_name); if (i + 1 < ngroups) putchar(','); diff --git a/src/commands/ls.c b/src/commands/ls.c index fec8cdd..a5c60d2 100644 --- a/src/commands/ls.c +++ b/src/commands/ls.c @@ -55,9 +55,9 @@ static DIR* get_directory(char* path) { DIR* d = opendir(path); if (d == NULL) { if (errno == ENOTDIR) { - printf("\x1b[0m%s is a a file\n", path); + error_s("`%s` is a a file\n", path); } else { - printf("\x1b[0merror: failed to open directory '%s': %s\n", path, strerror(errno)); + error_s("failed to open directory '%s': %s\n", path, strerror(errno)); } } return d; @@ -74,7 +74,7 @@ static bool get_file_info(const char* file_name, struct FileInfo* info) { int save = push_path_buffer(file_name); if (lstat(get_path_buffer(), &s) < 0) { - printf("\x1b[0merror: failed to read file '%s': %s\n", get_path_buffer(), strerror(errno)); + error_s("failed to read file '%s': %s\n", get_path_buffer(), strerror(errno)); pop_path_buffer(save); return false; } @@ -156,14 +156,14 @@ static bool get_file_info(const char* file_name, struct FileInfo* info) { info->usr = getpwuid(s.st_uid); if (info->usr == NULL) { - fprintf(stderr, "error: failed to get user from %s\n", get_path_buffer()); + error_s("failed to get user from `%s`\n", get_path_buffer()); pop_path_buffer(save); return false; } info->grp = getgrgid(s.st_gid); if (info->grp == NULL) { - fprintf(stderr, "error: failed to get user from %s\n", get_path_buffer()); + error_s("failed to get user from `%s`\n", get_path_buffer()); pop_path_buffer(save); return false; } @@ -288,10 +288,6 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo if (!flags.more_info) printf("\n"); } -static bool is_dot_dir(const char* path) { - return streql(path, ".") || streql(path, ".."); -} - static int num_places (int n) { int r = 1; if (n < 0) n = (n == INT_MIN) ? INT_MAX: -n; @@ -465,7 +461,7 @@ static int short_arg(char c, char* next) { flags.more_info = true; break; default: - error("error: unkown option -%c", c); + return ARG_INVALID; } return ARG_UNUSED; } @@ -481,7 +477,7 @@ static int long_arg(char* cur, char* next) { } else if (streql("no", arg) || streql("never", arg)) { flags.colored = NO; } else { - error("error: invalid color options: %s", arg); + error("invalid color options: %s", arg); } } else { return ARG_IGNORE; @@ -496,7 +492,7 @@ COMMAND(ls) { flags.hide_dot = false; flags.one_column = false; flags.recurse = false; - flags.colored = AUTO; + flags.colored = NO; int start = parse_args(argc, argv, help, short_arg, long_arg); diff --git a/src/commands/mkdir.c b/src/commands/mkdir.c new file mode 100644 index 0000000..3ff1afd --- /dev/null +++ b/src/commands/mkdir.c @@ -0,0 +1,60 @@ +#include "../command.h" + +static struct { + bool make_parent; + mode_t mode; +} flags; + +static int short_arg(char c, char* next) { + switch (c) { + case 'p': + flags.make_parent = true; + break; + case 'm': + check_arg(next); + flags.mode = get_mode(next); + return ARG_USED; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +static void help(void) { + printf("Usage: mkdir [-m MODE] [-p] DIRECTORY...\n\n"); + printf("Create DIRECTORY\n\n"); + printf("\t-m\tMODE\n"); + printf("\t-p\tNo error if exists; make parent directories as needed\n"); +} + +static bool mkdir_parents(char* path) { + for (size_t i = 1; i < strlen(path); i++) { + if (path[i] != '/') continue; + path[i] = '\0'; + if (mkdir(path, flags.mode) < 0 && errno != EEXIST) { + error_s("failed to create directory '%s': %s", path, strerror(errno)); + return false; + }; + path[i] = '/'; + } + return true; +} + +COMMAND(makedir) { + if (argc < 1) global_help(help); + + flags.make_parent = false; + flags.mode = 0755; + int start = parse_args(argc, argv, help, short_arg, NULL); + + for (int i = start; i < argc; i++) { + if (flags.make_parent && !mkdir_parents(argv[i])) { + continue; + } + if (mkdir(argv[i], flags.mode) < 0) { + error_s("failed to create directory '%s': %s", argv[i], strerror(errno)); + } + } + + return EXIT_SUCCESS; +} diff --git a/src/commands/mv.c b/src/commands/mv.c new file mode 100644 index 0000000..f3e759a --- /dev/null +++ b/src/commands/mv.c @@ -0,0 +1,108 @@ +#include "../command.h" + +static struct { + bool prompt; + bool dont_overwrite; + bool refuse_if_dir; + bool verbose; +} flags; + +static void help(void) { + printf("Usage: mv [-inT] SOURCE... DIRECTORY\n\n"); + printf("Rename SOURCE to DEST, or move SOURCEs to DIRECTORY\n"); + printf("\t-i\tInteractive, prompt before overwriting\n"); + printf("\t-n\tDon't overwrite an existing file\n"); + printf("\t-T\tRefuse to move if DEST is a directory\n"); + printf("\t-v\tVerbose\n"); +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 't': + flags.prompt = true; + break; + case 'n': + flags.dont_overwrite = true; + break; + case 'T': + flags.refuse_if_dir = true; + break; + case 'v': + flags.verbose = true; + break; + default: + return ARG_UNUSED; + } + return ARG_USED; +} + +static void mv_dir(bool exists) { + if (exists && flags.dont_overwrite) { + if (flags.verbose) output("skipping '%s'; overwrise is false", get_path_buffer_2()); + return; + } + if (exists && flags.prompt) { + fprintf(stderr, "overwrite '%s'? ", get_path_buffer_2()); + fflush(stderr); + char c = getchar(); + if (c != 'y' && c != 'Y') { + if (flags.verbose) output("skipping..."); + return; + } + } + + if (rename(get_path_buffer(), get_path_buffer_2()) < 0) { + error_s("cannot move '%s': %s", get_path_buffer(), strerror(errno)); + } else if (flags.verbose) { + output("moved '%s'", get_path_buffer()); + } +} + +COMMAND(mv) { + + flags.refuse_if_dir = false; + flags.dont_overwrite = false; + flags.prompt = false; + flags.verbose = false; + + int start = parse_args(argc, argv, help, short_arg, NULL); + + if (argc - start < 2) { + global_help(help); + } + + push_path_buffer_2(argv[argc-1]); + + bool dest = true; + struct stat s; + if (lstat(get_path_buffer_2(), &s) < 0 && argc - start > 2) { + dest = false; + error("cannot stat '%s': %s", get_path_buffer_2(), strerror(errno)); + } + + if (dest && flags.refuse_if_dir) { + if (S_ISDIR(s.st_mode)) { + error("target '%s': Is A Directory", get_path_buffer_2()); + } + } + + if (argc - start == 2) { + push_path_buffer(argv[argc-2]); + mv_dir(dest); + return EXIT_SUCCESS; + } + + if (dest && !S_ISDIR(s.st_mode)) { + error("target '%s': Is Not A Directory", get_path_buffer_2()); + } + + for (int i = start; i < argc - 1; i++) { + int save = push_path_buffer(argv[i]); + bool exists = lstat(get_path_buffer(), &s) >= 0; + mv_dir(exists); + pop_path_buffer(save); + } + + return EXIT_SUCCESS; +} diff --git a/src/commands/printf.c b/src/commands/printf.c index f9f3931..df6ce32 100644 --- a/src/commands/printf.c +++ b/src/commands/printf.c @@ -96,7 +96,8 @@ static void help(void) { COMMAND(print) { if (argc < 1) { - error("usage: printf FORMAT [ARG]...\n"); + global_help(help); + return EXIT_SUCCESS; } parse_help(argc, argv, help); diff --git a/src/commands/rm.c b/src/commands/rm.c new file mode 100644 index 0000000..b85f91a --- /dev/null +++ b/src/commands/rm.c @@ -0,0 +1,132 @@ +#include "../command.h" +#include +#include + +static struct { + bool force; + bool prompt; + bool verbose; + bool recurse; +} flags; + +#ifdef FRENCH + static bool get_frenched; +#endif + +static void help(void) { + printf("Usage: rm [-irfv] FILE...\n\n"); + printf("Remove (unlink) FILESs\n\n"); + printf("\t-i\tAlways prompt before removing\n"); + printf("\t-f\tForce, never prompt\n"); + printf("\t-v\tVerbose\n"); + printf("\t-R,-r\tRecurse\n"); +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'i': + flags.prompt = true; + break; + case 'f': + flags.force = true; + break; + case 'v': + flags.verbose = true; + break; + case 'R': + case 'r': + flags.recurse = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +static void rm_file(char* path); + +static bool rm_dir() { + DIR* d = opendir(get_path_buffer()); + if (d == NULL) { + error_s("failed to stat '%s': %s\n", get_path_buffer(), strerror(errno)); + return false; + } + + struct dirent* file; + while ((file = readdir(d)) != NULL) { + if (is_dot_dir(file->d_name)) continue; + rm_file(file->d_name); + } + + closedir(d); + return true; +} + +static void rm_file(char* path) { + int save = push_path_buffer(path); + + struct stat s; + if (lstat(get_path_buffer(), &s) < 0) { + pop_path_buffer(save); + error_s("failed to stat '%s': %s\n", get_path_buffer(), strerror(errno)); + return; + } + + if (S_ISDIR(s.st_mode)) { + if (!flags.force) { + error_s("cannot delete '%s': Is a directory\n", get_path_buffer()); + pop_path_buffer(save); + return; + } + if (flags.recurse && !rm_dir()) { + pop_path_buffer(save); + return; + } + } + + if (flags.prompt) { + fprintf(stderr, "delete '%s'? ", get_path_buffer()); + fflush(stderr); + char c = getchar(); + if (c != 'y' && c != 'Y') { + fprintf(stderr, "Skipping...\n"); + pop_path_buffer(save); + return; + } + } + + if (remove(get_path_buffer()) < 0) { + error_s("failed to delete '%s': %s\n", get_path_buffer(), strerror(errno)); + } else if (flags.verbose) { + output("deleted '%s'\n", get_path_buffer()); + } + + pop_path_buffer(save); +} + +COMMAND(rm) { + if (argc < 1) { + global_help(help); + return EXIT_SUCCESS; + } + + flags.prompt = false; + flags.force = false; + flags.verbose = false; + flags.recurse = false; + + int start = parse_args(argc, argv, help, short_arg, NULL); + +#ifdef FRENCH + if (streql(argv[0], "-rf")) { + printf("\x1b[94mremoving \x1b[97mthe \x1b[91mfrench \x1b[93m(beguette noises)\x1b[0m\n"); + } +#endif + + for (int i = start; i < argc; i++) { + rm_file(argv[i]); + } + + return EXIT_SUCCESS; +} diff --git a/src/commands/tail.c b/src/commands/tail.c index a611842..4856bc0 100644 --- a/src/commands/tail.c +++ b/src/commands/tail.c @@ -131,7 +131,7 @@ static int short_arg(char c, char* next) { check_arg(next); long int bkm = get_blkm(next); if (bkm < 1) { - error("error: bkm cannot be less than 1"); + error("bkm cannot be less than 1"); } flags.count = bkm; return ARG_USED; @@ -141,7 +141,7 @@ static int short_arg(char c, char* next) { check_arg(next); long int bkm = get_blkm(next); if (bkm < 1) { - error("error: bkm cannot be less than 1"); + error("bkm cannot be less than 1"); } flags.count = bkm; return ARG_USED; @@ -159,14 +159,13 @@ static int short_arg(char c, char* next) { check_arg(next); long int sec = get_number(next); if (sec < 1) { - error("error: wait seconds cannot be less than 1"); + error("wait seconds cannot be less than 1"); } flags.grow_wait = sec; return ARG_USED; } - default: { - error("error: unknown option -%c", c); - } + default: + return ARG_INVALID; } return ARG_UNUSED; } diff --git a/src/commands/tee.c b/src/commands/tee.c index ced5b06..652e369 100644 --- a/src/commands/tee.c +++ b/src/commands/tee.c @@ -41,7 +41,7 @@ static int short_arg(char c, char* next) { flags.handle_sigint = true; break; default: - error("error: unkown option: %c", c); + return ARG_INVALID; } return ARG_UNUSED; } diff --git a/src/commands/wc.c b/src/commands/wc.c index 9acbf5c..4012b6c 100644 --- a/src/commands/wc.c +++ b/src/commands/wc.c @@ -111,7 +111,7 @@ static int short_arg(char c, char* next) { flags.longest_line = true; break; default: - error("error: invald option -%c", c); + return ARG_INVALID; } flags.has_flags = true; return ARG_UNUSED; diff --git a/src/commands/xargs.c b/src/commands/xargs.c index 01fb6ce..8f24441 100644 --- a/src/commands/xargs.c +++ b/src/commands/xargs.c @@ -45,10 +45,12 @@ static int short_arg(char c, char* next) { check_arg(next); long int n = get_number(next); if (n < 1) { - error("error: max arg count must be at least 1"); + error("max arg count must be at least 1"); } flags.max_args = n; return ARG_USED; + default: + return ARG_INVALID; } return ARG_UNUSED; } diff --git a/src/main.c b/src/main.c index 7bd931e..e23b915 100644 --- a/src/main.c +++ b/src/main.c @@ -8,9 +8,12 @@ #include #include +char* cmd; + int main (ARGUMENTS) { if (argc < 1) { - error("error: argument 0 missing"); + fprintf(stderr, "fatal: argument 0 missing"); + return EXIT_FAILURE; } #ifdef CHECK_LINK @@ -21,7 +24,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\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\n"); return EXIT_SUCCESS; } argc--; @@ -29,7 +32,6 @@ int main (ARGUMENTS) { } #endif - const char* cmd; if (strncmp("./", argv[0], 2) == 0) { cmd = argv[0] + 2; } else { @@ -52,6 +54,8 @@ int main (ARGUMENTS) { return user_id(); } else if (streql(cmd, "ls") || streql(cmd, "dir")) { return ls(NEXT_ARGS); + } else if (streql(cmd, "lsd")) { + printf("look at all the funny colors\n"); } else if (streql(cmd, "tail")) { return tail(NEXT_ARGS); } else if (streql(cmd, "head")) { @@ -72,8 +76,17 @@ int main (ARGUMENTS) { return xargs(NEXT_ARGS); } else if (streql(cmd, "tac")) { return tac(NEXT_ARGS); + } else if (streql(cmd, "rm")) { + return rm(NEXT_ARGS); + } else if (streql(cmd, "cp")) { + return cp(NEXT_ARGS); + } else if (streql(cmd, "mkdir")) { + return makedir(NEXT_ARGS); + } else if (streql(cmd, "mv")) { + return mv(NEXT_ARGS); } else { - error("error: invalid command %s", cmd); + fprintf(stderr, "lazysphere: invalid command %s", cmd); + return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/src/util/shared.c b/src/util/shared.c index 600a967..8f2fe93 100644 --- a/src/util/shared.c +++ b/src/util/shared.c @@ -1,5 +1,6 @@ #include "shared.h" +#include #include #include #include @@ -10,15 +11,36 @@ #include #include -void error(const char* format, ...) { +extern char* cmd; + +void error_s(const char *format, ...) { va_list list; va_start(list, format); + fprintf(stderr, "%s: ", cmd); + vfprintf(stderr, format, list); + fprintf(stderr, "\n"); +} + +void error(const char *format, ...) { + va_list list; + va_start(list, format); + + fprintf(stderr, "%s: ", cmd); vfprintf(stderr, format, list); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } +void output(const char *format, ...) { + va_list list; + va_start(list, format); + + fprintf(stdout, "%s: ", cmd); + vfprintf(stdout, format, list); + fprintf(stdout, "\n"); +} + FILE* get_file_s(const char* path, const char* type) { struct stat s; if (streql("-", path) && type[0] == 'r') { @@ -28,11 +50,11 @@ FILE* get_file_s(const char* path, const char* type) { } if (lstat(path, &s) < 0) { if (type[0] != 'r') goto read; - fprintf(stderr, "error: failed to read %s: %s\n", path, strerror(errno)); + error_s("failed to read %s: %s", path, strerror(errno)); return NULL; } if (S_ISDIR(s.st_mode)) { - fprintf(stderr, "error: %s is a directory\n", path); + error_s("%s is a directory", path); return NULL; } @@ -40,7 +62,7 @@ FILE* get_file_s(const char* path, const char* type) { read: file = fopen(path, type); if (file == NULL) { - fprintf(stderr, "error: failed to %s file %s: %s\n", type[0] == 'r' ? "read" : "write", path, strerror(errno)); + error_s("failed to %s file %s: %s", type[0] == 'r' ? "read" : "write", path, strerror(errno)); } return file; } @@ -55,7 +77,7 @@ long int get_number(const char* text) { char* end; long int n = strtol(text, &end, 10); if (text == end) { - error("error: %s is not a valid number", text); + error("%s is not a valid number", text); } return n; } @@ -64,7 +86,7 @@ long int get_blkm(const char* text) { char* end; long int n = strtol(text, &end, 10); if (text == end) { - error("error: %s is not a valid bkm", text); + error("%s is not a valid bkm", text); } if (*end == '\0') return n; switch (*end) { @@ -78,12 +100,23 @@ long int get_blkm(const char* text) { case 'm': return n * 1024 * 1204; default: - error("error: invalid bkm type %c", *end); + error("invalid bkm type %c", *end); } // shouldnt get here anyways return 0; } +mode_t get_mode(const char* next) { + char* end = NULL; + mode_t mode = (mode_t)strtol(next, &end, 8); + if (!end) return 0; + while(isspace(*end)) end++; + if (*end != '\0' || (unsigned) mode < 010000) { + error("invalid file mode: `%s`", next); + } + return mode; +} + bool streql(const char* a, const char* b) { if (*a != *b) return false; int n = 0; @@ -151,11 +184,11 @@ void print_date_time(time_t mills, char buf[13]) { void check_arg (char* arg) { if (arg == NULL) { - error("error: expected another argument after option"); + error("expected another argument after option"); } } -static void global_help(void (*help)(void)) { +void global_help(void (*help)(void)) { printf("LazySphere v%d.%d.%d multi-call binary.\n\n", MAJOR, MINOR, PATCH); help(); exit(EXIT_SUCCESS); @@ -196,6 +229,8 @@ int parse_args(int argc, char** argv, void (*help)(void), int (*short_arg)(char, start++; } else if (r == ARG_IGNORE) { goto exit; + } else if (r == ARG_INVALID) { + error("invalid argument %s", argv[current]); } } else { if (short_arg == NULL) { @@ -208,6 +243,8 @@ int parse_args(int argc, char** argv, void (*help)(void), int (*short_arg)(char, start++; } else if (r == ARG_IGNORE) { goto exit; + } else if (r == ARG_INVALID) { + error("invalid argument -%c", argv[current][j]); } } } @@ -220,7 +257,7 @@ exit: int get_tty() { int fd = open(_PATH_TTY, O_RDONLY); - if (fd < 0) error("error: failed to get tty: %s", strerror(errno)); + if (fd < 0) error("failed to get tty: %s", strerror(errno)); return fd; } @@ -228,11 +265,27 @@ FILE* get_tty_stream(char* type) { int fd = get_tty(); FILE* file = fdopen(fd, type); if (file == NULL) { - error("error: failed to open tty stream: %s", strerror(errno)); + error("failed to open tty stream: %s", strerror(errno)); } return file; } +static int push_path_buffer_b(char* buf, int* index, const char* string) { + int save = *index; + if (*index > 1 || (*index == 1 && buf[0] != '/')) { + buf[(*index)++] = '/'; + } + int string_len = strlen(string); + memcpy(buf + *index, string, string_len + 1); + *index += string_len; + return save; +} + +static void pop_path_buffer_b(char* buf, int* index, int i) { + *index = i; + buf[*index] = '\0'; +} + static char path_buffer[PATH_MAX + 1]; static int path_buffer_index = 0; @@ -241,17 +294,28 @@ char* get_path_buffer() { } int push_path_buffer(const char* string) { - int save = path_buffer_index; - if (path_buffer_index > 1 || (path_buffer_index == 1 && path_buffer[0] != '/')) { - path_buffer[path_buffer_index++] = '/'; - } - int string_len = strlen(string); - memcpy(path_buffer + path_buffer_index, string, string_len + 1); - path_buffer_index += string_len; - return save; + return push_path_buffer_b(path_buffer, &path_buffer_index, string); } void pop_path_buffer(int i) { - path_buffer_index = i; - path_buffer[path_buffer_index] = '\0'; + pop_path_buffer_b(path_buffer, &path_buffer_index, i); +} + +static char path_buffer_2[PATH_MAX + 1]; +static int path_buffer_index_2 = 0; + +char* get_path_buffer_2() { + return path_buffer_2; +} + +int push_path_buffer_2(const char* string) { + return push_path_buffer_b(path_buffer_2, &path_buffer_index_2, string); +} + +void pop_path_buffer_2(int i) { + pop_path_buffer_b(path_buffer_2, &path_buffer_index_2, i); +} + +bool is_dot_dir(const char* path) { + return streql(path, ".") || streql(path, ".."); } diff --git a/src/util/shared.h b/src/util/shared.h index 9bade52..26e27c3 100644 --- a/src/util/shared.h +++ b/src/util/shared.h @@ -32,13 +32,20 @@ enum When { AUTO }; +__attribute__ ((__format__(printf, 1, 2))) +void error_s(const char* format, ...); + __attribute__ ((__format__(printf, 1, 2))) void error(const char* format, ...); +__attribute__ ((__format__(printf, 1, 2))) +void output(const char* format, ...); + FILE* get_file_s(const char* path, const char* type); FILE* get_file(const char* path, const char* type); long int get_number(const char* text); long int get_blkm(const char* text); +mode_t get_mode(const char* next); bool streql(const char* a, const char* b); bool prefix(const char* pre, const char* str); @@ -50,8 +57,10 @@ void print_date_time(time_t mills, char buf[13]); #define ARG_UNUSED 0 #define ARG_USED 1 #define ARG_IGNORE 2 +#define ARG_INVALID 3 void check_arg (char* arg); +void global_help(void (*help)(void)); void parse_help (int argc, char** argv, void (*help)(void)); int parse_args (int argc, char** argv, void (*help)(void), int (*short_arg)(char, char*), int (*long_arg)(char*, char*)); @@ -62,3 +71,8 @@ char* get_path_buffer(); int push_path_buffer(const char* string); void pop_path_buffer(int i); +char* get_path_buffer_2(); +int push_path_buffer_2(const char* string); +void pop_path_buffer_2(int i); + +bool is_dot_dir(const char* path);