diff options
author | Tyler Murphy <tylerm@tylerm.dev> | 2023-05-06 00:39:44 -0400 |
---|---|---|
committer | Tyler Murphy <tylerm@tylerm.dev> | 2023-05-06 00:39:44 -0400 |
commit | d8f2c10b7108fff6b7e437291093a1cadc15ab9f (patch) | |
tree | 3fc50a19d6fbb9c94a8fe147cd2a6c4ba7f59b8d /command | |
parent | ansii c (diff) | |
download | lazysphere-d8f2c10b7108fff6b7e437291093a1cadc15ab9f.tar.gz lazysphere-d8f2c10b7108fff6b7e437291093a1cadc15ab9f.tar.bz2 lazysphere-d8f2c10b7108fff6b7e437291093a1cadc15ab9f.zip |
refactor
Diffstat (limited to 'command')
-rw-r--r-- | command/cat.c | 142 | ||||
-rw-r--r-- | command/cp.c | 245 | ||||
-rw-r--r-- | command/dd.c | 63 | ||||
-rw-r--r-- | command/echo.c | 107 | ||||
-rw-r--r-- | command/ed.c | 920 | ||||
-rw-r--r-- | command/grep.c | 269 | ||||
-rw-r--r-- | command/groups.c | 63 | ||||
-rw-r--r-- | command/head.c | 141 | ||||
-rw-r--r-- | command/id.c | 76 | ||||
-rw-r--r-- | command/ls.c | 563 | ||||
-rw-r--r-- | command/mkdir.c | 70 | ||||
-rw-r--r-- | command/mv.c | 121 | ||||
-rw-r--r-- | command/printf.c | 144 | ||||
-rw-r--r-- | command/rm.c | 140 | ||||
-rw-r--r-- | command/tac.c | 119 | ||||
-rw-r--r-- | command/tail.c | 243 | ||||
-rw-r--r-- | command/tee.c | 83 | ||||
-rw-r--r-- | command/wc.c | 161 | ||||
-rw-r--r-- | command/whoami.c | 30 | ||||
-rw-r--r-- | command/xargs.c | 192 | ||||
-rw-r--r-- | command/yes.c | 27 |
21 files changed, 3919 insertions, 0 deletions
diff --git a/command/cat.c b/command/cat.c new file mode 100644 index 0000000..c392a46 --- /dev/null +++ b/command/cat.c @@ -0,0 +1,142 @@ +#include "command.h" +#include "lslib.h" + +#include <ctype.h> +#include <stdlib.h> + +static struct { + bool number_lines; + bool number_non_empty; + bool change_non_print; + bool change_tabs; + bool end_lines_dollar; +} flags; + +static bool printable(char c) { + switch (c) { + case '\n': return true; + case '\r': return true; + case '\b': return true; + case '\t': return true; + default: return isprint(c) == 0; + } +} + +static void help(void) { + printf("Usage: cat [-nbvteA] [FILE]...\n\n"); + printf("Print FILEs to stdout\n\n"); + printf("\t-n Number output lines\n"); + printf("\t-b Number nonempty lines\n"); + printf("\t-v Show nonprinting characters as ^x or M-x\n"); + printf("\t-t ...and tabs as ^I\n"); + printf("\t-e ...and end lines with $\n"); + printf("\t-A Same as -vte\n"); +} + +static void cat_file(FILE* file) { + char c; + size_t read; + + size_t line = 1; + bool empty = true; + bool newline = true; + + while ((read = fread(&c, 1, 1, file)) != 0) { + if (c == '\n') { + if (empty && flags.number_lines) { + printf("\t%ld ", line); + } + if (flags.end_lines_dollar) { + printf("$"); + } + line++; + newline = true; + empty = true; + goto print; + } else { + empty = false; + } + + if (!newline) { + goto print; + } + + if (!empty && (flags.number_non_empty || flags.number_lines)) { + printf("\t%ld ", line); + newline = false; + } + print: + if (!flags.change_non_print || printable(c)) { + if (flags.change_tabs && c == '\t') { + fwrite("^I", 1, 2, stdout); + } else { + fwrite(&c, 1, 1, stdout); + } + } else { + c |= '@'; + fwrite(&c, 1, 1, stdout); + } + } +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'n': + flags.number_lines = true; + break; + case 'b': + flags.number_non_empty = true; + break; + case 'v': + flags.change_non_print = true; + break; + case 't': + flags.change_non_print = true; + flags.change_tabs = true; + break; + case 'e': + flags.change_non_print = true; + flags.end_lines_dollar = true; + break; + case 'A': + flags.change_non_print = true; + flags.change_tabs = true; + flags.end_lines_dollar = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +COMMAND(cat) { + + int start; + int arg_len; + int i; + + flags.number_lines = false; + flags.number_non_empty = false; + flags.change_non_print = false; + flags.change_tabs = false; + flags.end_lines_dollar = false; + + start = parse_args(argc, argv, help, short_arg, NULL); + + arg_len = argc - start; + + if (arg_len < 1) { + cat_file(stdin); + return EXIT_SUCCESS; + } + + for (i = start; i < argc; i++) { + FILE* in = get_file(argv[i], "r"); + cat_file(in); + if (in != stdin) + fclose(in); + } + + return EXIT_SUCCESS; +} diff --git a/command/cp.c b/command/cp.c new file mode 100644 index 0000000..ca80f69 --- /dev/null +++ b/command/cp.c @@ -0,0 +1,245 @@ +#include "command.h" +#include "lslib.h" + +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +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) { + #define BS 1024 + + FILE *from_f, *to_f; + char buf[BS]; + int read; + + from_f = get_file_s(from, "r"); + if (from_f == NULL) { return false; } + + to_f = get_file_s(to, "w"); + if (to_f == NULL) { fclose(from_f); return false; } + + + 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) { + + DIR* d; + struct dirent* file; + + 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; + } + + d = opendir(get_path_buffer()); + + if (d == NULL) { + error_s("cannot open directory '%s': %s", get_path_buffer(), strerror(errno)); + return; + } + + 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) { + int last = 0; + int i = 0; + + if (path[0] == '\0') return path; + + 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) { + + int start, i; + struct stat s; + + flags.hard_link = false; + flags.sym_link = false; + flags.preserve = false; + flags.recurse = false; + flags.verbose = false; + + 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]); + + if (lstat(get_path_buffer_2(), &s) < 0) { + error("target: '%s': %s", get_path_buffer_2(), strerror(errno)); + } + + for (i = start; i < argc - 1; i++) { + cp_file(argv[i]); + } + + return EXIT_SUCCESS; +} diff --git a/command/dd.c b/command/dd.c new file mode 100644 index 0000000..67f8be3 --- /dev/null +++ b/command/dd.c @@ -0,0 +1,63 @@ +#include "command.h" +#include "lslib.h" + +#include <stdlib.h> + +static void help(void) { + printf("Usage: dd [if=FILE] [of=FILE] [bs=N] [count=N]\n\n"); + printf("Copy a file with converting and formatting\n\n"); + printf("\tif=FILE\t\tRead from FILE instead of stdin\n"); + printf("\tof=FILE\t\tWrite to FILE instead of stdout\n"); + printf("\tbs=N\t\tRead and write N bytes at a time\n"); + printf("\tcount=N\t\tCopy only N input blocks\n"); +} + +COMMAND(dd) { + + FILE* in_file = stdin; + FILE* out_file = stdout; + int bs = 1024; + int count = -1; + int i; + char* buffer; + size_t read; + + parse_help(argc, argv, help); + + for (i = 0; i < argc; i++) { + if (prefix("if=", argv[i])) { + char* path = argv[i] + 3; + in_file = get_file(path, "rb"); + } else if (prefix("of=", argv[i])) { + char* path = argv[i] + 3; + out_file = get_file(path, "wb"); + } else if (prefix("bs=", argv[i])) { + char* str = argv[i] + 3; + bs = get_number(str); + if (bs < 1) { + 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("count must be greather than 0"); + } + } else { + error("unkown option %s", argv[i]); + } + } + + buffer = malloc(bs); + + while ((read = fread(buffer, 1, bs, in_file)) != 0) { + fwrite(buffer, 1, read, out_file); + if (--count, count == 0) break; + } + + free(buffer); + fclose(in_file); + fclose(out_file); + + return EXIT_SUCCESS; +} diff --git a/command/echo.c b/command/echo.c new file mode 100644 index 0000000..a0ab78d --- /dev/null +++ b/command/echo.c @@ -0,0 +1,107 @@ +#include "command.h" +#include "lslib.h" + +#include <stdlib.h> + +static struct { + bool escape_codes; + bool newline; +} flags; + +static void print_with_escape_codes(const char* str) { + + size_t index = 0; + char n; + + while (true) { + char c = str[index]; + index++; + + if (c == '\0') break; + if (c != '\\') { + putchar(c); + continue; + } + + n = str[index]; + index++; + + switch (n) { + case '\\': + putchar('\\'); + break; + case 'b': + putchar('\b'); + break; + case 'c': + exit(EXIT_SUCCESS); + case 'n': + putchar('\n'); + break; + case 'r': + putchar('\r'); + break; + case 't': + putchar('\t'); + break; + case 'v': + putchar('\v'); + break; + default: + putchar(c); + putchar(n); + } + } +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'e': + flags.escape_codes = true; + break; + case 'E': + flags.escape_codes = false; + break; + case 'n': + flags.newline = false; + break; + default: + flags.newline = true; + flags.escape_codes = false; + return ARG_IGNORE; + }; + return ARG_UNUSED; +} + +COMMAND(echo) { + + int start, i; + + if (argc < 1) { + return EXIT_SUCCESS; + } + + flags.escape_codes = false; + flags.newline = true; + + start = parse_args(argc, argv, NULL, short_arg, NULL); + + for (i = start; i < argc; i++) { + if (flags.escape_codes) { + print_with_escape_codes(argv[i]); + } else { + printf("%s", argv[i]); + } + + if (i + 1 != argc) { + putchar(' '); + } + } + + if (flags.newline) { + putchar('\n'); + } + + return EXIT_SUCCESS; +} diff --git a/command/ed.c b/command/ed.c new file mode 100644 index 0000000..472473f --- /dev/null +++ b/command/ed.c @@ -0,0 +1,920 @@ +#include "command.h" +#include "lslib.h" + +#include <stdlib.h> +#include <string.h> + +#define INPUT_LEN 1024 + +static char** lines = NULL; +static unsigned long line_capacity; +static unsigned long line_count; +static unsigned long line_current; +static bool pending_writes; +static char* default_filename = NULL; +static re_t last_regex = NULL; + +enum LineAddressType { + INDEX, + RANGE, + SET, + FREE +}; + +struct LineAddress { + enum LineAddressType type; + union { + struct { + long int i; + } index; + struct { + long int a; + long int b; + } range; + struct { + long int* b; + unsigned long c; + unsigned long s; + } set; + } data; + bool empty; +}; + +enum RegexDirection { + BEFORE, + AFTER, + ALL +}; + +static bool read_regex(char** end, re_t* regex, enum RegexDirection dir) { + char c; + char* index = *end; + char* regex_str = index; + while(true) { + c = *(index++); + if (c == '\0') { + error_s("missing regex after %c\n", dir == BEFORE ? '?' : '/'); + return false; + } + if (c == (dir == BEFORE ? '?' : '/')) { + *(index - 1) = '\0'; + break; + } + } + *regex = re_compile(regex_str); + last_regex = *regex; + *end = index; + return true; +} + +static bool parse_regex(char** end, struct LineAddress* address, enum RegexDirection dir) { + re_t regex; + unsigned long cap, siz, i, until; + long int* buf; + + if (!read_regex(end, ®ex, dir)) return false; + + cap = 8; + siz = 0; + buf = malloc(cap * sizeof(long int)); + + i = (dir == ALL ? 0 : line_current); + until = (dir == BEFORE ? 0 : line_count - 1); + for (; (dir == BEFORE ? i >= until : i < until); dir == BEFORE ? i-- : i++) { + int len; + if (re_matchp(regex, lines[i], &len) == -1) { + if (dir == BEFORE && i == 0) break; + continue; + } + if (cap == siz) { + cap *= 2; + buf = realloc(buf, cap * sizeof(long int)); + } + buf[siz] = i; + siz++; + if (dir == BEFORE && i == 0) break; + } + + address->empty = false; + address->type = SET; + address->data.set.s = siz; + address->data.set.c = cap; + address->data.set.b = buf; + return true; +} + +static void free_address (struct LineAddress address) { + if (address.type != SET) return; + address.type = FREE; + free(address.data.set.b); +} + +static void expand_buffer(long int** buf, unsigned long* cap, unsigned long* size) { + if (*cap == *size) { + *cap *= 2; + *buf = realloc(*buf, sizeof(long int) * *cap); + } +} + +static bool parse_regex_lines(char** end, struct LineAddress* address) { + re_t regex; + unsigned long cap, siz; + long int* buf; + int len; + struct LineAddress addr; + + if (!read_regex(end, ®ex, ALL)) return false; + + cap = 8; + siz = 0; + buf = malloc(cap * sizeof(long int)); + + addr = *address; + if (addr.type == INDEX) { + if (re_matchp(regex, lines[addr.data.index.i], &len) != -1) { + buf[0] = addr.data.index.i; + siz = 1; + } + } else if (addr.type == RANGE) { + long int i; + for (i = addr.data.range.a; i < addr.data.range.b; i++) { + if (re_matchp(regex, lines[i], &len) == -1) continue; + expand_buffer(&buf, &cap, &siz); + buf[siz] = i; + siz++; + } + } else if (addr.type == SET) { + unsigned long i; + for (i = 0; i < addr.data.set.s; i++) { + if (re_matchp(regex, lines[addr.data.set.b[i]], &len) == -1) continue; + expand_buffer(&buf, &cap, &siz); + buf[siz] = addr.data.set.b[i]; + siz++; + } + } + + free_address(*address); + address->empty = false; + address->type = SET; + address->data.set.s = siz; + address->data.set.c = cap; + address->data.set.b = buf; + return true; +} + +static bool read_address(char** command, bool whitespace, struct LineAddress* a) { + char* index = *command; + struct LineAddress address; + + char* end_pre; + long int n_pre; + char pre; + + memset(&address, 0, sizeof(struct LineAddress)); + + address.empty = false; + if (strlen(*command) < 1) { + address.type = INDEX; + address.data.index.i = line_current + 1; + if (line_current >= line_count) line_current = line_count - 1; + *a = address; + return true; + } + + n_pre = strtol(index, &end_pre, 10) - 1; + if (end_pre == index) { + n_pre = -1; + } else { + if (n_pre < 0) { + error_s("input cannot be negative\n"); + return false; + } + index = end_pre; + } + + pre = *(index++); + switch (pre) { + case '.': + address.type = INDEX; + address.data.index.i = line_current; + break; + case '$': + address.type = INDEX; + address.data.index.i = line_count - 1; + break; + case '-': + case '^': { + char* end; + long int n; + + address.type = INDEX; + n = strtol(index, &end, 10) - 1; + + if (n < 0) { + error_s("input cannot be negative\n"); + return false; + } + + if (index == end) { + address.data.index.i = line_current - 1; + } else { + address.data.index.i = line_current - n; + } + + if (address.data.index.i < 0) { + error_s("line number %ld does not exist\n", address.data.index.i + 1); + return false; + } + + break; + } + case '+': { + char* end; + long int n; + + address.type = INDEX; + n = strtol(index, &end, 10) - 1; + + if (n < 0) { + error_s("input cannot be negative\n"); + return false; + } + if (index == end) { + address.data.index.i = line_current + 1; + } else { + address.data.index.i = line_current + n; + } + if (address.data.index.i >= (long int) line_count) { + error_s("line number %ld does not exist\n", address.data.index.i + 1); + return false; + } + break; + } + case '%': + address.type = RANGE; + address.data.range.a = 0; + address.data.range.b = line_count - 1; + break; + case ';': + address.type = RANGE; + address.data.range.a = line_current; + address.data.range.b = line_count - 1; + break; + case '/': + if (!parse_regex(&index, &address, AFTER)) return false; + break; + case '?': + if (!parse_regex(&index, &address, BEFORE)) return false; + break; + default: { + index--; + if (n_pre == -1) { + address.type = INDEX; + address.data.index.i = line_current; + address.empty = true; + break; + } else if (whitespace) { + address.type = INDEX; + address.data.index.i = line_current + n_pre; + } else { + address.type = INDEX; + address.data.index.i = n_pre; + } + if (address.data.index.i < 0 || address.data.index.i >= (long int) line_count) { + error_s("line number %ld does not exist\n", address.data.index.i + 1); + return false; + } + } + } + *command = index; + *a = address; + return true; +} + +static void free_data(bool all) { + if (lines != NULL) { + unsigned long i; + for (i = 0; i < line_count; i++) { + free(lines[i]); + } + free(lines); + lines = NULL; + } + if (all && default_filename != NULL) { + free(default_filename); + default_filename = NULL; + } +} + +static void load_empty(void) { + free_data(false); + + line_capacity = 8; + lines = malloc(sizeof(char*) * line_capacity); + + line_count = 0; + line_current = 0; + pending_writes = false; +} + +static void get_input(FILE* file, char*** buffer, unsigned long* capacity, unsigned long* size) { + unsigned long cap = 8; + unsigned long siz = 0; + char** buf = malloc(sizeof(char*) * cap); + + char* line = NULL; + size_t offset = 0; + + clearerr(stdin); + while (getline(&line, &offset, file) != -1) { + if (cap == siz) { + cap *= 2; + buf = realloc(buf, sizeof(char*) * cap); + } + buf[siz] = line; + siz++; + line = NULL; + } + + free(line); + + *buffer = buf; + *capacity = cap; + *size = siz; +} + +int ed_getline(char *buf, size_t size) { + size_t i = 0; + int ch; + clearerr(stdin); + while ((ch = getchar()) != EOF) { /* Read until EOF ... */ + if (i + 1 < size) { + buf[i++] = ch; + } + if (ch == '\n') { /* ... or end of line */ + break; + } + } + buf[i] = '\0'; + if (i == 0) { + return EOF; + } + return i; +} + +static void load_file(FILE* file) { + free_data(false); + line_current = 0; + get_input(file, &lines, &line_capacity, &line_count); + if (file != stdin) + fclose(file); + +} + +static bool check_if_sure(char* prompt) { + char buf[INPUT_LEN]; + + if (!pending_writes) { + return true; + } + + printf("%s", prompt); + fflush(stdout); + + if (ed_getline(buf, INPUT_LEN) == EOF) { + putchar('\n'); + return false; + } + + return prefix("y", buf); +} + +static bool skip_whitespace(char** index) { + char c; + bool w = false; + while (c = **index, c == ' ' || c == '\t') { (*index)++; w = true; } + return w; +} + +static void expand(unsigned long count) { + if (count < line_capacity) return; + line_capacity *= 2; + if (count > line_capacity) line_capacity = count; + lines = realloc(lines, line_capacity * sizeof(char*)); +} + +static void append_lines(unsigned long index, char** new, unsigned long new_len) { + if (new_len < 1) return; + pending_writes = true; + expand(line_count + new_len); + if (index + 1 <= line_count) + memmove(&lines[index+new_len], &lines[index], sizeof(char*) * (line_count - index)); + memcpy(&lines[index], new, sizeof(char*) * new_len); + line_count += new_len; +} + +static void delete_lines(unsigned long a, unsigned long b) { + unsigned long i; + + if (b < a) return; + pending_writes = true; + + for (i = a; i <= b; i++) { + free(lines[i]); + } + if (b == line_count - 1) { + line_count = a; + return; + } + memmove(&lines[a], &lines[b+1], sizeof(char*) * (line_count - (b + 1))); + line_count -= (b - a) + 1; + line_current = a; + if (line_current >= line_count) line_current = line_count - 1; +} + +static bool handle_append(struct LineAddress* address) { + char** buf; + unsigned long cap, size; + + if (address->type != INDEX) { + error_s("append command requires index addressing\n"); + return false; + } + + if (line_count == 0) { + address->data.index.i = -1; + } + + get_input(stdin, &buf, &cap, &size); + + if (size > 0) { + append_lines(address->data.index.i + 1, buf, size); + printf("ed: appened %lu lines\n", size); + } + + line_current += size; + free(buf); + return true; +} + +static bool handle_delete(struct LineAddress* address) { + if (address->empty && address->data.index.i >= (long int) line_count) { + 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); + 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); + output("deleted lines %ld-%ld\n", address->data.range.a+1, address->data.range.b+1); + } else if (address->type == SET) { + unsigned long i; + for (i = 0; i < address->data.set.s; i++) { + delete_lines(address->data.set.b[i], address->data.set.b[i]); + } + output("deleted %lu lines\n", address->data.set.s); + } + + return true; +} + +static bool get_file_name(char** filename) { + size_t len = strlen(*filename); + + if (len < 1 || (len == 1 && **filename == '\n')) { + if (default_filename == NULL) { + error_s("no default filename specified\n"); + return false; + } + *filename = default_filename; + } else { + if ((*filename)[len - 1] == '\n') { + (*filename)[len - 1] = '\0'; + len--; + } + if (default_filename != NULL) { + default_filename = realloc(default_filename, len + 1); + } else { + default_filename = malloc(len + 1); + } + memcpy(default_filename, *filename, len + 1); + *filename = default_filename; + } + return true; +} + +static void write_file(char* filename, struct LineAddress* address, char* type) { + FILE* file; + int wrote; + + if (line_count < 1) { + error_s("cannot write empty file\n"); + return; + } + + if (!get_file_name(&filename)) return; + file = get_file_s(filename, type); + if (file == NULL) return; + + wrote = 0; + + if (address->empty) { + unsigned long i; + for (i = 0; i < line_count; i++) { + fprintf(file, "%s", lines[i]); + wrote++; + } + } else if (address->type == INDEX) { + long int i = address->data.index.i; + fprintf(file, "%s", lines[i]); + wrote++; + } else if (address->type == RANGE) { + long int i; + for (i = address->data.range.a; i < address->data.range.b; i++) { + fprintf(file, "%s", lines[i]); + wrote++; + } + } else if (address->type == SET) { + unsigned long i; + for (i = 0; i < address->data.set.s; i++) { + fprintf(file, "%s", lines[address->data.set.b[i]]); + wrote++; + } + } + + pending_writes = false; + fclose(file); + output("wrote %d lines from %s\n", wrote, filename); +} + +static void read_file(char* filename) { + FILE* file; + char** buf; + long int line; + unsigned long capacity, size; + + if (!get_file_name(&filename)) return; + file = get_file_s(filename, "r"); + if (file == NULL) return; + + capacity = 8; + size = 0; + buf = malloc(capacity * sizeof(char*)); + get_input(file, &buf, &capacity, &size); + + if (size < 1) { + free(buf); + error_s("attempted to read a empty file\n"); + return; + } + + line = -1; + if (line_count > 0) { + line = line_count - 1; + } + + append_lines(line, buf, size); + free(buf); + 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) { + if (*size + len >= *capacity) { + *capacity *= 2; + if (*capacity < *size + len) *capacity = *size + len; + *buf = realloc(*buf, *capacity); + } + memcpy(*buf + *size, text, len); + *size += len; +} + +static int substute_string(long int index, long int matches, re_t regex, char* sub, int sub_len) { + int capacity = 8; + int size = 0; + char* buf = malloc(sizeof(char) * capacity); + long int left; + + int offset = 0; + int matches_found = 0; + while(true) { + int distance, len; + + if (lines[index][offset] == '\0') break; + + if (matches_found >= matches && matches > 0) break; + + if ((distance = re_matchp(regex, &lines[index][offset], &len)) == -1) { + break; + } + + if (distance > 0) { + expand_string(&buf, &capacity, &size, &lines[index][offset], distance); + } + + expand_string(&buf, &capacity, &size, sub, sub_len); + offset += len + distance; + matches_found++; + } + + left = strlen(lines[index] + offset); + expand_string(&buf, &capacity, &size, &lines[index][offset], left + 1); + + free(lines[index]); + lines[index] = buf; + return matches_found; +} + +static void prompt(void) { + char buf[INPUT_LEN]; + char* index; + char cmd; + bool whitespace, linenumbers; + struct LineAddress address; + + printf(": "); + fflush(stdout); + + if (ed_getline(buf, INPUT_LEN) == EOF) { putchar('\n'); return; } + if (buf[0] == '\0') { putchar('\n'); return; } + + index = &buf[0]; + whitespace = skip_whitespace(&index); + + if (!read_address(&index, whitespace, &address)) return; + + cmd = *(index++); + + if (cmd == ',') { + struct LineAddress address2; + + if (address.type != INDEX) { + error_s("comma range addressing requires two index addresses\n"); + free_address(address); + return; + } + + whitespace = skip_whitespace(&index); + + if (!read_address(&index, whitespace, &address2)) { + free_address(address); + return; + } + + if (address2.type != INDEX) { + error_s("comma range addressing requires two index addresses\n"); + free_address(address); + free_address(address2); + return; + } + + address.type = RANGE; + address.data.range.a = address.data.index.i; /* cursed */ + address.data.range.b = address2.data.index.i; + + cmd = *(index++); + } + + if (address.type == RANGE && address.data.range.a > address.data.range.b) { + error_s("range addressing must be in ascending order\n"); + free_address(address); + return; + } + + linenumbers = false; + +test: + switch (cmd) { + case '\0': + case '\n': + if (address.empty) { + if (line_current == line_count) { + error_s("line number %ld does not exist\n", line_current + 1); + break; + } else if (line_current + 1 == line_count) { + error_s("line number %ld does not exist\n", line_current + 2); + break; + } else { + line_current++; + } + printf("%s", lines[line_current]); + break; + } + if (address.type == INDEX) { + line_current = address.data.index.i; + } else if (address.type == RANGE) { + line_current = address.data.range.b; + } else if (address.type == SET) { + error_s("unexpected range addressing\n"); + break; + } + printf("%s", lines[line_current]); + break; + case 'a': + handle_append(&address); + break; + case 'd': + handle_delete(&address); + break; + case 'c': + if (!handle_delete(&address)) { break; } + address.type = INDEX; + address.data.index.i = line_current - 1; + handle_append(&address); + break; + case 'n': + linenumbers = true; + __attribute__((fallthrough)); + case 'p': + if (address.empty && address.data.index.i >= (long int) line_count) { + error_s("line number %ld does not exist\n", address.data.index.i + 1); + break; + } + if (address.type == INDEX) { + if (linenumbers) printf("%ld\t", address.data.index.i + 1); + printf("%s", lines[address.data.index.i]); + } else if (address.type == RANGE) { + long int i; + for (i = address.data.range.a; i <= address.data.range.b; i++) { + if (linenumbers) printf("%ld\t", i + 1); + printf("%s", lines[i]); + } + } else if (address.type == SET) { + unsigned long i; + for (i = 0; i < address.data.set.s; i++) { + if (linenumbers) printf("%ld\t", address.data.set.b[i] +1); + printf("%s", lines[address.data.set.b[i]]); + } + } + break; + case 'q': + if (!check_if_sure("Quit for sure? ")) break; + __attribute__((fallthrough)); + case 'Q': + free_address(address); + free_data(true); + exit(EXIT_SUCCESS); + break; + case 'g': + skip_whitespace(&index); + free_address(address); + if (*(index++) != '/') { + error_s("unexpected character at start of regex\n"); + break; + } + if (!parse_regex(&index, &address, ALL)) { return; } + skip_whitespace(&index); + if (*index == '\n' || *index == '\0') { + cmd = 'p'; + } else { + cmd = *(index++); + } + goto test; + break; + case 's': { + char* replace = index; + long int matches, sub_len, matches_found; + unsigned long i; + + skip_whitespace(&index); + if (*(index++) != '/') { + error_s("unexpected character at start of regex\n"); + break; + } + if (!parse_regex_lines(&index, &address)) { return; } + while(*index != '\0' && *index != '/') index++; + if (*index != '/') { + error_s("/ missing after %c\n", *index); + break; + } + if (address.data.set.s < 1) { + error_s("no matches found\n"); + break; + } + *(index++) = '\0'; + if (*index == '\0' || *index == '\n') { + matches = 1; + } else if (*index == 'g') { + matches = -1; + } else { + char* end; + matches = strtol(index, &end, 10); + if (end == index) { + error_s("invalid number: %s\n", index); + break; + } + if (matches < 1) { + error_s("matches cannot be less than 1\n"); + break; + } + } + sub_len = strlen(replace); + matches_found = 0; + + for (i = 0; i < address.data.set.s; i++) { + matches_found += substute_string(address.data.set.b[i], matches, last_regex, replace, sub_len); + } + output("replaced %ld matches over %ld lines\n", matches_found, address.data.set.s); + pending_writes = true; + break; + } + case 'w': { + bool quit = false; + if (*index == 'q') { + index++; + quit = true; + } + skip_whitespace(&index); + write_file(index, &address, "w"); + if (quit) { + free_address(address); + free_data(true); + exit(EXIT_SUCCESS); + } + break; + } + case 'r': { + skip_whitespace(&index); + read_file(index); + break; + } + case 'e': + if (!check_if_sure("Load new file for sure? ")) break; + __attribute__((fallthrough)); + case 'E': { + char* filename; + FILE* file; + + skip_whitespace(&index); + + filename = index; + if(!get_file_name(&filename)) break; + + file = get_file_s(filename, "r"); + if (file == NULL) break; + + load_file(file); + break; + } + case 'W': + skip_whitespace(&index); + write_file(index, &address, "a"); + break; + case '=': + printf("%ld\n", line_current + 1); + break; + default: + error_s("unimplemented command\n"); + break; + } + + free_address(address); + +} + +static void prompt_loop(void) { + while (true) { + prompt(); + } +} + +static void help(void) { + printf("Usage: ed [FILE]\n\n"); + printf("Edit a given [FILE] or create a new text file\n\n"); + printf("\t(.,.)\tnewline\t\tgo to address line and print\n"); + printf("\t(.)\ta\t\tappend new data after given line\n"); + printf("\t(.,.)\tc\t\treplace given lines with new data\n"); + printf("\t(.,.)\td\t\tdelete given lines\n"); + printf("\t\te file\t\tdelete current buffer and edit file\n"); + printf("\t\tE file\t\tdelete current buffer and edit file unconditionally\n"); + printf("\t\tg/re/command\tgrep all lines with regex and run command on matches\n"); + printf("\t(.,.)\tn\t\tprint given lines along with their line numbers\n"); + printf("\t(.,.)\tp\t\tprint given lines\n"); + printf("\t\tq\t\tquit file\n"); + printf("\t\tQ\t\tquit file unconditionally\n"); + printf("\t($)\tr file\t\tread file and append to end of buffer\n"); + printf("\t(.,.)\ts/re/replace/\treplace the first match on each matching line\n"); + printf("\t(.,.)\ts/re/replace/g\treplace all matches on each matching line\n"); + printf("\t(.,.)\ts/re/replace/n\treplace n matches on each matching line\n"); + printf("\t(.,.)\tw file\t\twrite contents of lines to file\n"); + printf("\t(.,.)\twq file\t\twrite contents of lines to file then quit\n"); + printf("\t(.,.)\tW file\t\tappend contents of line to file\n"); + printf("\t\t=\t\tprint current line number\n"); +} + +COMMAND(ed) { + + parse_help(argc, argv, help); + + if (argc < 1) { + load_empty(); + prompt_loop(); + } else { + FILE* file = get_file(argv[0], "r"); + load_file(file); + get_file_name(&argv[0]); + prompt_loop(); + } + return EXIT_SUCCESS; +} diff --git a/command/grep.c b/command/grep.c new file mode 100644 index 0000000..0dad094 --- /dev/null +++ b/command/grep.c @@ -0,0 +1,269 @@ +#include "command.h" +#include "lslib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static struct { + bool filename_prefix; + bool never_file_prefix; + bool line_number; + bool only_matching_names; + bool only_non_matching_names; + bool only_line_count; + bool only_matching_part; + bool quiet; + bool inverse; + bool ignore_case; + bool is_regex; +} flags; + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'H': + flags.filename_prefix = true; + break; + case 'h': + flags.never_file_prefix = true; + break; + case 'n': + flags.line_number = true; + break; + case 'l': + flags.only_matching_names = true; + break; + case 'L': + flags.only_non_matching_names = true; + break; + case 'c': + flags.only_line_count = true; + break; + case 'o': + flags.only_matching_part = true; + break; + case 'q': + flags.quiet = true; + break; + case 'v': + flags.inverse = true; + break; + case 'i': + flags.ignore_case = true; + break; + case 'F': + flags.is_regex = false; + break; + case 'E': + flags.is_regex = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +static void help(void) { + printf("Usage: grep [-HhlLoqviFE] [-m N] PATTERN [FILE]...\n"); + printf("Search for PATTERN in FILEs (or stdin)\n"); + printf("\t-H\tAdd 'filename:' prefix\n"); + printf("\t-h\tDo not add 'filename:' prefix\n"); + printf("\t-n\tAdd 'line_no:' prefix\n"); + printf("\t-l\tShow only names of files that match\n"); + printf("\t-L\tShow only names of files that don't match\n"); + printf("\t-c\tShow only count of matching lines\n"); + printf("\t-o\tShow only the matching part of line\n"); + printf("\t-q\tQuiet. Return 0 if PATTERN is found, 1 otherwise\n"); + printf("\t-v\tSelect non-matching lines\n"); + printf("\t-i\tIgnore case\n"); + printf("\t-F\tPATTERN is a literal (not regexp)\n"); + printf("\t-E\tPATTERN is an extended regexp\n"); +} + +static bool match_regex(char** string, re_t pattern) { + int len; + int index; + if ((index = re_matchp(pattern, *string, &len)) < 0) return false; + if (flags.only_matching_part) { + (*string) += index; + (*string)[len] = '\0'; + } + return true; +} + +static bool match_literal(char** string, char* pattern) { + char* match = *string; + size_t match_len = strlen(match); + size_t pattern_len = strlen(pattern); + size_t i; + + if (match_len < pattern_len) return false; + + for (i = 0; i < match_len - pattern_len + 1; i++) { + if ( + (!flags.ignore_case && strncmp(match + i, pattern, pattern_len) == 0) || + (flags.ignore_case && strncasecmp(match + i, pattern, pattern_len) == 0) + ) { + if (flags.only_matching_part) { + *string = (*string) + i; + (*string)[pattern_len] = '\0'; + } + return true; + } + } + + return false; +} + +static bool match(char** string, void* pattern) { + bool result; + if (flags.is_regex) { + result = match_regex(string, (re_t) pattern); + } else { + result = match_literal(string, (char*) pattern); + } + return (flags.inverse ? !result : result); +} + +static bool match_any(char* path, void* pattern) { + FILE* file; + char* buf = NULL; + size_t offset; + bool matched = false; + int read; + + file = get_file_s(path, "r"); + if (file == NULL) return false; + + while ((read = getline(&buf, &offset, file)) > 0) { + char* save = buf; + if (buf[read-1] == '\n') buf[read-1] = '\0'; + if (match(&save, pattern)) { + matched = true; + break; + } + } + + if (buf != NULL) free(buf); + + return matched; +} + +static bool match_file(char* path, void* pattern, bool many) { + FILE* file; + char* buf = NULL; + size_t offset; + int num_matched = 0; + int line_num = 0; + int read; + + file = get_file_s(path, "r"); + if (file == NULL) return false; + + while((read = getline(&buf, &offset, file)) > 0) { + char* matched = buf; + + if (buf[read-1] == '\n') buf[read-1] = '\0'; + line_num++; + + if (!match(&matched, pattern)) { + continue; + } + + num_matched++; + + if (flags.only_line_count || flags.quiet) continue; + + if ((many || flags.filename_prefix) && !flags.never_file_prefix) { + print_file_path(path); + putchar(':'); + } + + if (flags.line_number) { + printf("%d:", line_num); + } + + if (flags.only_matching_part) { + printf("%s\n", matched); + } else { + printf("%s\n", buf); + } + } + + if (!flags.quiet && flags.only_line_count) { + if ((many || flags.filename_prefix) && !flags.never_file_prefix) { + print_file_path(path); + putchar(':'); + } + printf("%d\n", num_matched); + } + + if (buf != NULL) free(buf); + + return num_matched != 0; +} + +static void* compile(char* pattern) { + if (flags.is_regex) { + return re_compile(pattern); + } else { + return pattern; + } +} + +static bool run_match(char* path, void* pattern, bool many) { + bool result; + if (flags.only_matching_names || flags.only_non_matching_names) { + result = match_any(path, pattern); + if (flags.only_non_matching_names) result = !result; + if (result && !flags.quiet) { + print_file_path(path); + putchar('\n'); + } + return result; + } else { + return match_file(path, pattern, many); + } +} + +COMMAND(grep) { + + int start, i; + char* pattern; + bool many, ok; + void* compiled; + + flags.only_matching_part = false; + flags.only_non_matching_names = false; + flags.only_matching_names = false; + flags.only_line_count = false; + flags.quiet = false; + flags.is_regex = true; + flags.line_number = false; + flags.never_file_prefix = false; + flags.filename_prefix = false; + flags.inverse = false; + + start = parse_args(argc, argv, help, short_arg, NULL); + + if (argc - start < 1) global_help(help); + + pattern = argv[start++]; + + many = argc - start > 0; + ok = false; + + compiled = compile(pattern); + if (run_match("-", compiled, many)) ok = true; + + for (i = start; i < argc; i++) { + if (run_match(argv[i], compiled, many)) ok = true; + } + + if (flags.quiet) { + return ok ? EXIT_SUCCESS : EXIT_FAILURE; + } else { + return EXIT_SUCCESS; + } +} diff --git a/command/groups.c b/command/groups.c new file mode 100644 index 0000000..cb950be --- /dev/null +++ b/command/groups.c @@ -0,0 +1,63 @@ +#include "args.h" +#include "command.h" +#include "lslib.h" + +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static void help (void) { + printf("Usage: groups [USER]\n\n"); + printf("Print the groups USER is in\n"); +} + +COMMAND(groups) { + + uid_t uid; + int ngroups, i; + gid_t* groups; + struct passwd* pw; + + parse_help(argc, argv, help); + + if (argc < 1) { + uid = getuid(); + pw = getpwuid(uid); + } else { + pw = getpwnam(argv[0]); + } + + if(pw == NULL){ + if (errno == 0) { + error("user not found"); + } else { + error("failed to fetch groups: %s", strerror(errno)); + } + } + + ngroups = 0; + getgrouplist(pw->pw_name, pw->pw_gid, NULL, &ngroups); + + groups = malloc(sizeof(gid_t) * ngroups); + getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); + + for (i = 0; i < ngroups; i++){ + struct group* gr = getgrgid(groups[i]); + + if(gr == NULL) { + free(groups); + error("failed to fetch groups: %s", strerror(errno)); + } + + printf("%s ",gr->gr_name); + } + + printf("\n"); + + free(groups); + + return EXIT_SUCCESS; +} diff --git a/command/head.c b/command/head.c new file mode 100644 index 0000000..c28a82c --- /dev/null +++ b/command/head.c @@ -0,0 +1,141 @@ +#include "command.h" +#include "lslib.h" + +#include <stdlib.h> + +static struct { + int count; + bool lines; + bool print_headers; + bool dont_print_headers; +} flags; + +static void head_file_lines(FILE* file) { + size_t len = 0; + char* line = NULL; + + int count = flags.count; + while(count > 0 && getline(&line, &len, file) != -1) { + printf("%s", line); + count--; + } + + free(line); + fclose(file); +} + +static void head_file_chars(FILE* file) { + char c; + int count = flags.count; + while(count > 0 && (c = getc(file)) != EOF) { + putchar(c); + count--; + } + + fclose(file); +} + +static void help(void) { + printf("Usage: head [OPTIONS] [FILE]...\n\n"); + printf("Print first 10 lines of FILEs (or stdin)\n"); + printf("With more than one FILE, precede each with a filename header.\n\n"); + printf("\t-c [+]N[bkm]\tPrint first N bytes\n"); + printf("\t-n N[bkm]\tPrint first N lines\n"); + printf("\t\t\t(b:*512 k:*1024 m:*1024^2)\n"); + printf("\t-q\t\tNever print headers\n"); + printf("\t-v\t\tAlways print headers\n"); +} + +static void print_header(char* path, bool many) { + if (flags.dont_print_headers) return; + if (!many && !flags.print_headers) return; + if (streql("-", path)) { + printf("\n==> standard input <==\n"); + } else { + printf("\n=>> %s <==\n", path); + } +} + +static void head_file(char* path, bool many) { + FILE* file = get_file(path, "r"); + print_header(path, many); + if (flags.lines) { + head_file_lines(file); + } else { + head_file_chars(file); + } +} + +static int short_arg(char c, char* next) { + switch(c) { + case 'c': { + long int bkm; + + flags.lines = false; + + check_arg(next); + bkm = get_blkm(next); + + if (bkm < 1) { + error("bkm cannot be less than 1"); + } + + flags.count = bkm; + return ARG_USED; + } + case 'n': { + long int bkm; + + flags.lines = true; + + check_arg(next); + bkm = get_blkm(next); + + if (bkm < 1) { + error("bkm cannot be less than 1"); + } + + flags.count = bkm; + return ARG_USED; + } + case 'q': + flags.dont_print_headers = true; + break; + case 'v': + flags.print_headers = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +COMMAND(head) { + + int start, count, i; + + flags.count = 10; + flags.lines = true; + flags.print_headers = false; + flags.dont_print_headers = false; + + start = parse_args(argc, argv, help, short_arg, NULL); + + count = argc - start; + + if (count < 1) { + head_file_lines(stdin); + return EXIT_SUCCESS; + } + + if (count == 1) { + head_file(argv[start], false); + return EXIT_SUCCESS; + } + + for (i = 0; i < count; i++) { + head_file(argv[start + i], true); + } + + return EXIT_SUCCESS; +} diff --git a/command/id.c b/command/id.c new file mode 100644 index 0000000..3a63989 --- /dev/null +++ b/command/id.c @@ -0,0 +1,76 @@ +#include "args.h" +#include "command.h" +#include "lslib.h" + +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static void help (void) { + printf("Usage: id [USER]\n\n"); + printf("Print information about the USER\n"); +} + +COMMAND(user_id) { + + uid_t uid; + gid_t gid, *groups; + int ngroups, i; + struct passwd* pw; + struct group* ugr; + + parse_help(argc, argv, help); + + if (argc < 1) { + uid = getuid(); + pw = getpwuid(uid); + } else { + pw = getpwnam(argv[0]); + } + + if(pw == NULL){ + if (errno == 0) { + error("user not found"); + } else { + error("failed to fetch groups: %s", strerror(errno)); + } + } + + uid = pw->pw_uid; + + ngroups = 0; + getgrouplist(pw->pw_name, pw->pw_gid, NULL, &ngroups); + + groups = malloc(sizeof(gid_t) * ngroups); + getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); + + gid = pw->pw_gid; + ugr = getgrgid(gid); + printf("uid=%d(%s) gid=%d(%s) ", + uid, ugr->gr_name, gid, ugr->gr_name); + + if (ngroups > 0) { + printf("groups="); + } + + for (i = 0; i < ngroups; i++){ + struct group* gr = getgrgid(groups[i]); + if(gr == NULL) { + free(groups); + error("failed to fetch groups: %s", strerror(errno)); + } + + printf("%d(%s)", gr->gr_gid, gr->gr_name); + + if (i + 1 < ngroups) putchar(','); + } + + printf("\b\n"); + + free(groups); + + return EXIT_SUCCESS; +} diff --git a/command/ls.c b/command/ls.c new file mode 100644 index 0000000..8ed796f --- /dev/null +++ b/command/ls.c @@ -0,0 +1,563 @@ +#include "command.h" +#include "lslib.h" + +#include <grp.h> +#include <pwd.h> +#include <dirent.h> +#include <ftw.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#define FILE_COLOR ANSCII BLACK COLOR +#define DIR_COLOR ANSCII BOLD NEXT NORMAL BLUE COLOR +#define DIR_COLOR_EXEC ANSCII BACKGROUND GREEN NEXT NORMAL BLACK COLOR +#define LINK_COLOR ANSCII BOLD NEXT NORMAL TURQUOISE COLOR +#define SET_UID_COLOR ANSCII BACKGROUND RED NEXT NORMAL WHITE COLOR +#define SET_GID_COLOR ANSCII BACKGROUND YELLOW NEXT NORMAL BLACK COLOR +#define EXEC_COLOR ANSCII BOLD NEXT NORMAL GREEN COLOR +#define BLK_COLOR ANSCII BOLD NEXT NORMAL YELLOW COLOR +#define SOCK_COLOR ANSCII BOLD NEXT NORMAL MAGENTA COLOR + +enum When { + YES, + NO, + AUTO +}; + +static struct { + bool hidden; + bool hide_dot; + bool more_info; + bool one_column; + bool recurse; + enum When colored; +} flags; + +struct FileInfo { + struct passwd* usr; + struct group* grp; + char* name; + char date[13]; + char mode[11]; + char size[5]; + int links; + int bytes; + bool set_uid; + bool set_gid; + bool exec; + unsigned char type; +}; + +struct FileListInfo { + int max_link; + int max_usr; + int max_grp; + int max_size; + int max_name; + int total_len; + int total_size; +}; + +static DIR* get_directory(char* path) { + DIR* d = opendir(path); + if (d == NULL) { + if (errno == ENOTDIR) { + error_s("`%s` is a a file\n", path); + } else { + error_s("failed to open directory '%s': %s\n", path, strerror(errno)); + } + } + return d; +} + +static bool get_file_info(const char* file_name, struct FileInfo* info) { + + uid_t uid; + gid_t gid; + int save, ty; + struct stat s; + size_t file_len; + + uid = getuid(); + gid = getgid(); + + memset(&s, 0, sizeof(struct stat)); + + save = push_path_buffer(file_name); + + if (lstat(get_path_buffer(), &s) < 0) { + error_s("failed to read file '%s': %s\n", get_path_buffer(), strerror(errno)); + pop_path_buffer(save); + return false; + } + + ty = (s.st_mode & S_IFMT) >> 12; + + info->set_uid = false; + info->set_gid = false; + info->exec = false; + + switch (ty) { + case DT_BLK: + info->mode[0] = 'b'; + break; + case DT_CHR: + info->mode[0] = 'c'; + break; + case DT_DIR: + info->mode[0] = 'd'; + break; + case DT_FIFO: + info->mode[0] = 'f'; + break; + case DT_LNK: + info->mode[0] = 'l'; + break; + case DT_SOCK: + info->mode[0] = 's'; + break; + case DT_UNKNOWN: + info->mode[0] = 'u'; + break; + case DT_WHT: + info->mode[0] = 'w'; + break; + default: + info->mode[0] = '-'; + break; + } + + 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; + } 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; + } 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) { + info->mode[9] = 'x'; + info->exec = true; + } else { + info->mode[9] = '-'; + } + + info->mode[10] = '\0'; + + info->usr = getpwuid(s.st_uid); + if (info->usr == NULL) { + 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) { + error_s("failed to get user from `%s`\n", get_path_buffer()); + pop_path_buffer(save); + return false; + } + + info->links = s.st_nlink; + info->type = ty; + + file_len = strlen(file_name) + 1; + info->name = malloc(file_len); + memcpy(info->name, file_name, file_len); + + print_file_size(s.st_size, info->size); + print_date_time(s.st_mtim.tv_sec + s.st_mtim.tv_nsec / 1000000000, info->date); + + info->bytes = (s.st_size + s.st_blksize - 1) / s.st_blksize; + + pop_path_buffer(save); + return true; +} + +static char* get_file_color(struct FileInfo* info) { + char* color; + if (info->type == DT_DIR) { + if (info->mode[8] == 'w') { + color = DIR_COLOR_EXEC; + } else { + color = DIR_COLOR; + } + } else if (info->type == DT_LNK) { + color = LINK_COLOR; + } else if (info->type == DT_SOCK) { + color = SOCK_COLOR; + } else if ( + info->type == DT_CHR || + info->type == DT_BLK + ) { + color = BLK_COLOR; + } else { + if (info->set_uid) { + color = SET_UID_COLOR; + } else if (info->set_gid) { + color = SET_GID_COLOR; + } else if (info->exec) { + color = EXEC_COLOR; + } else { + color = FILE_COLOR; + } + } + return color; +} + +static void list_files(struct FileInfo* files, int file_len, struct FileListInfo info) { + + struct winsize w; + char* color; + int column_width, row_count, i; + + if (flags.more_info) { + char total[13]; + print_file_size(info.total_size, total); + printf("total %s\n", total); + } + + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + + if (!isatty(1)) { + flags.one_column = true; + if (flags.colored == AUTO) + flags.colored = NO; + } + + column_width = info.max_name + 1; + row_count = w.ws_col / column_width; + + for (i = 0; i < file_len; i++) { + struct FileInfo finfo = files[i]; + color = get_file_color(&finfo); + if (flags.more_info) { + printf("%s %*d %*s %*s %*s %s %s%s%s", + finfo.mode, + info.max_link, + finfo.links, + info.max_usr, + finfo.usr->pw_name, + info.max_grp, + finfo.grp->gr_name, + info.max_size, + finfo.size, + finfo.date, + flags.colored != NO ? color : "", + finfo.name, + flags.colored != NO ? "\x1b[0m" : "" + ); + if (finfo.type == DT_LNK) { + int save = push_path_buffer(finfo.name); + + char lnk[PATH_MAX]; + ssize_t n; + if ((n = readlink(get_path_buffer(), lnk, PATH_MAX)) != -1) { + printf(" -> %.*s\n", (int)n, lnk); + } else { + putchar('\n'); + } + + pop_path_buffer(save); + } else { + putchar('\n'); + } + } else if (flags.one_column) { + printf("%s%s%s\n", flags.colored != NO ? color : "", finfo.name, flags.colored != NO ? "\x1b[0m" : ""); + } else { + if (info.total_len > w.ws_col) { + if (i != 0 && i % row_count == 0) putchar('\n'); + printf("%s%*s%s", flags.colored != NO ? color : "", -column_width, + finfo.name, flags.colored != NO ? "\x1b[0m" : ""); + } else { + printf("%s%s%s ", flags.colored != NO ? color : "", finfo.name, + flags.colored != NO ? "\x1b[0m" : ""); + } + } + free(finfo.name); + } + + if (!flags.more_info) printf("\n"); +} + +static int num_places (int n) { + int r = 1; + if (n < 0) n = (n == INT_MIN) ? INT_MAX: -n; + while (n > 9) { + n /= 10; + r++; + } + return r; +} + +static void push_file( + struct FileInfo** files, + struct FileListInfo* info, + int* size, int* capacity, + const char* file_path +) { + struct FileInfo finfo; + int user_len, group_len, name_len, size_len, link_len; + + if (!get_file_info(file_path, &finfo)) return; + + if (*size == *capacity) { + *capacity *= 2; + *files = realloc(*files, sizeof(struct FileInfo) * *capacity); + } + + user_len = strlen(finfo.usr->pw_name); + if (user_len > info->max_usr) info->max_usr = user_len; + + group_len = strlen(finfo.grp->gr_name); + if (group_len > info->max_grp) info->max_grp = group_len; + + name_len = strlen(file_path); + if (name_len > info->max_name) info->max_name = name_len; + + size_len = strlen(finfo.size); + if (size_len > info->max_size) info->max_size = size_len; + + link_len = num_places(finfo.links); + if (link_len > info->max_link) info->max_link = link_len; + + info->total_len += name_len + 2; + info->total_size += finfo.bytes; + + (*files)[*size] = finfo; + (*size)++; +} + +static void recurse_directory(char* dir_name) { + DIR* d; + int capacity, size, save; + struct dirent* file; + struct FileInfo* files; + struct FileListInfo info; + + save = push_path_buffer(dir_name); + + d = get_directory(get_path_buffer()); + if (d == NULL) { + return; + } + + capacity = 8; + size = 0; + + files = malloc(sizeof(struct FileInfo) * capacity); + memset(&info, 0, sizeof(struct FileListInfo)); + + while((file = readdir(d)) != NULL) { + if (!flags.hidden && prefix(".", file->d_name)) continue; + if (flags.hide_dot && is_dot_dir(file->d_name)) continue; + if (file->d_type == DT_DIR && !is_dot_dir(file->d_name)) { + recurse_directory(file->d_name); + } else { + push_file(&files, &info, &size, &capacity, file->d_name); + } + } + + + if (flags.colored == NO) { + printf("\n%s:\n", get_path_buffer()); + } else { + printf("\n%s%s:%s\n", DIR_COLOR, get_path_buffer(), FILE_COLOR); + } + + list_files(files, size, info); + + free(files); + + if (!flags.more_info) printf("\n"); + + closedir(d); + + pop_path_buffer(save); +} + +static void list_directory(char* path) { + + DIR* d; + int capacity, size, save; + struct FileInfo* files; + struct FileListInfo info; + struct dirent* file; + + if (flags.recurse) { + recurse_directory(path); + return; + } + + d = get_directory(path); + if (d == NULL) return; + + save = push_path_buffer(path); + + capacity = 8; + size = 0; + + files = malloc(sizeof(struct FileInfo) * capacity); + memset(&info, 0, sizeof(struct FileListInfo)); + + while ((file = readdir(d)) != NULL) { + if (!flags.hidden && prefix(".", file->d_name)) continue; + if (flags.hide_dot && is_dot_dir(file->d_name)) continue; + push_file(&files, &info, &size, &capacity, file->d_name); + } + + if (size > 0) list_files(files, size, info); + free(files); + + pop_path_buffer(save); + + closedir(d); +} + +static bool is_dir(const char* path) { + struct stat s; + if (stat(path, &s) < 0) return false; + return S_ISDIR(s.st_mode); +} + +static void list_file_args(int start, int argc, char** argv) { + + int capacity, size, i; + struct FileInfo* files; + struct FileListInfo info; + + capacity = 8; + size = 0; + + files = malloc(sizeof(struct FileInfo) * capacity); + memset(&info, 0, sizeof(struct FileListInfo)); + + for (i = start; i < argc; i++) { + if (is_dir(argv[i])) continue; + push_file(&files, &info, &size, &capacity, argv[i]); + } + + if (size > 0) list_files(files, size, info); + + free(files); +} + +static void help(void) { + printf("Usage: ls [FILE]...\n\n"); + printf("List directory contents\n\n"); + printf("\t-1\tOne column output\n"); + printf("\t-l\tLong format\n"); + printf("\t-a\tInclude names starting with .\n"); + printf("\t-A\tLike -a but without . and ..\n"); + printf("\t-R\tRecurse\n"); +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'R': + flags.recurse = true; + break; + case '1': + flags.one_column = true; + break; + case 'A': + flags.hide_dot = true; + flags.hidden = true; + break; + case 'a': + flags.hidden = true; + break; + case 'l': + flags.more_info = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +static int long_arg(char* cur, char* next) { + UNUSED(next); + if (prefix("--color=", cur)) { + char* arg = cur + 8; + if (streql("yes", arg) || streql("always", arg)) { + flags.colored = YES; + } else if (streql("auto", arg)) { + flags.colored = AUTO; + } else if (streql("no", arg) || streql("never", arg)) { + flags.colored = NO; + } else { + error("invalid color options: %s", arg); + } + } else { + return ARG_IGNORE; + } + return ARG_UNUSED; +} + +COMMAND(ls) { + + int start, i; + bool titled; + + flags.hidden = false; + flags.more_info = false; + flags.hide_dot = false; + flags.one_column = false; + flags.recurse = false; + flags.colored = NO; + + start = parse_args(argc, argv, help, short_arg, long_arg); + + if (argc - start == 0) { + list_directory("."); + return EXIT_SUCCESS; + } + + list_file_args(start, argc, argv); + + titled = argc - start > 1; + for (i = start; i < argc; i++) { + + if (!is_dir(argv[i])) continue; + + if (titled && !flags.recurse) { + if (flags.colored != NO) { + printf("\n%s%s:%s\n", DIR_COLOR, argv[i], FILE_COLOR); + } else { + printf("\n%s:\n", argv[i]); + } + } + + list_directory(argv[i]); + } + + return EXIT_SUCCESS; +} diff --git a/command/mkdir.c b/command/mkdir.c new file mode 100644 index 0000000..0d3950d --- /dev/null +++ b/command/mkdir.c @@ -0,0 +1,70 @@ +#include "command.h" +#include "lslib.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.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) { + size_t i; + for (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) { + + int start, i; + + if (argc < 1) global_help(help); + + flags.make_parent = false; + flags.mode = 0755; + + start = parse_args(argc, argv, help, short_arg, NULL); + + for (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/command/mv.c b/command/mv.c new file mode 100644 index 0000000..adce2b7 --- /dev/null +++ b/command/mv.c @@ -0,0 +1,121 @@ +#include "command.h" +#include "lslib.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.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) { + + char c; + + 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); + + 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) { + + int start, dest, i; + struct stat s; + + flags.refuse_if_dir = false; + flags.dont_overwrite = false; + flags.prompt = false; + flags.verbose = false; + + start = parse_args(argc, argv, help, short_arg, NULL); + + if (argc - start < 2) { + global_help(help); + } + + push_path_buffer_2(argv[argc-1]); + + dest = true; + 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 (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/command/printf.c b/command/printf.c new file mode 100644 index 0000000..99139d0 --- /dev/null +++ b/command/printf.c @@ -0,0 +1,144 @@ +#include "command.h" +#include "lslib.h" + +#include <stdlib.h> + +static long cast_long(const char* arg) { + char* end; + long l = strtol(arg, &end, 10); + if (end == arg) { + return 0; + } else { + return l; + } +} + +static double cast_double(const char* arg) { + char* end; + double d = strtod(arg, &end); + if (end == arg) { + return 0.0; + } else { + return d; + } +} + +#define NUMBER(name, type, arg) \ + long l = cast_long(arg); \ + type* t = (type*) &l; \ + type name = *t; + +static void handle_percent(char n, const char* arg) { + switch (n) { + case 'd': + case 'z': { + NUMBER(i, int, arg) + printf("%d", i); + break; + } + case 'u': { + NUMBER(u, unsigned int, arg); + printf("%u", u); + break; + } + case 'f': { + double d = cast_double(arg); + printf("%f", d); + break; + } + case 'c': { + putchar(arg[0]); + break; + } + case 's': { + printf("%s", arg); + break; + } + default: { + putchar('%'); + putchar(n); + } + } +} + +static void handle_slash(char n) { + switch (n) { + case 'n': + putchar('\n'); + break; + case 't': + putchar('\t'); + break; + case 'v': + putchar('\v'); + break; + case 'b': + putchar('\b'); + break; + case 'f': + putchar('\f'); + break; + case 'a': + putchar('\a'); + break; + case '"': + putchar('"'); + break; + case 'c': + exit(EXIT_SUCCESS); + default: + putchar('\\'); + putchar(n); + } +} + +static void help(void) { + printf("Usage printf FORMAT [ARG]...\n\n"); + printf("Format and print ARG(s) according to FORMAT (a-la C prinf)\n"); +} + +COMMAND(print) { + + size_t index; + int arg_index; + char n, *arg; + + if (argc < 1) { + global_help(help); + return EXIT_SUCCESS; + } + + parse_help(argc, argv, help); + + index = 0; + arg_index = 0; + + while (true) { + char c = argv[0][index]; + index++; + + if (c == '\0') break; + if (c != '%' && c != '\\') { + putchar(c); + continue; + } + + n = argv[0][index]; + index++; + + arg = NULL; + if (arg_index < argc) { + arg = argv[arg_index + 1]; + } + + if (c == '%') { + handle_percent(n, arg); + } else { + handle_slash(n); + } + + arg_index++; + } + + return EXIT_SUCCESS; +} diff --git a/command/rm.c b/command/rm.c new file mode 100644 index 0000000..81a956a --- /dev/null +++ b/command/rm.c @@ -0,0 +1,140 @@ +#include "command.h" +#include "lslib.h" + +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +static struct { + bool force; + bool prompt; + bool verbose; + bool recurse; +} flags; + +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 (void) { + DIR* d; + struct dirent* file; + + d = opendir(get_path_buffer()); + if (d == NULL) { + error_s("failed to stat '%s': %s\n", get_path_buffer(), strerror(errno)); + return false; + } + + 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) { + char c; + + fprintf(stderr, "delete '%s'? ", get_path_buffer()); + fflush(stderr); + + 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) { + + int start, i; + + if (argc < 1) { + global_help(help); + return EXIT_SUCCESS; + } + + flags.prompt = false; + flags.force = false; + flags.verbose = false; + flags.recurse = false; + + start = parse_args(argc, argv, help, short_arg, NULL); + +#ifdef FRENCH + if (streql(argv[0], "-fr")) { + printf("\x1b[94mremoving \x1b[97mthe \x1b[91mfrench \x1b[93m(baguette noises)\x1b[0m\n"); + } +#endif + + for (i = start; i < argc; i++) { + rm_file(argv[i]); + } + + return EXIT_SUCCESS; +} diff --git a/command/tac.c b/command/tac.c new file mode 100644 index 0000000..9e9e48e --- /dev/null +++ b/command/tac.c @@ -0,0 +1,119 @@ +#include "command.h" +#include "lslib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <time.h> + +static void help(void) { + printf("Usage: tac [FILE]...\n\n"); + printf("Concatenate FILEs and print them in reverse\n"); +} + +static void print_range(FILE* file, int start, int end) { + int len, i; + + len = end - start; + fseek(file, start, SEEK_SET); + + for (i = 0; i < len; i++) { + putchar(getc(file)); + } + + fflush(stdout); +} + +static char stdin_path[PATH_MAX]; + +static FILE* read_stdin (void) { + static bool read; + static FILE* file; + int r; + char c; + + if (read) goto finished; + read = true; + + srand(time(NULL)); + + r = rand() % 1000000; + + sprintf(stdin_path, "/tmp/%d.tac", r); + file = get_file(stdin_path, "w"); + + while((c = getchar()) != EOF) putc(c, file); + fclose(file); + + file = get_file(stdin_path, "r"); + +finished: + return file; +} + +static void parse_file(FILE* file, struct Stack* stack) { + char buf[1024], c; + int read, i; + int total = 1; + + stack_push_int(stack, 0); + rewind(file); + while ((read = fread(buf, 1, 1024, file)) > 0) { + for (i = 0; i < read; i++) { + c = buf[i]; + if (c != '\n') continue; + stack_push_int(stack, total + i); + } + total += read; + } +} + +static void tac_file(FILE* file) { + struct Stack stack; + int last, current; + + stack_init(&stack, 80); + parse_file(file, &stack); + rewind(file); + + if (!stack_pop_int(&stack, &last)) goto cleanup; + + while(stack_pop_int(&stack, ¤t)) { + print_range(file, current, last); + last = current; + } + +cleanup: + + stack_free(&stack); +} + +COMMAND(tac) { + + FILE* in; + int i; + + parse_help(argc, argv, help); + + in = read_stdin(); + + if (argc < 1) { + tac_file(in); + return EXIT_SUCCESS; + } + + for (i = 0; i < argc; i++) { + FILE* file = get_file(argv[i], "r"); + if (file == stdin) { + tac_file(in); + } else { + tac_file(file); + fclose(file); + } + } + + fclose(in); + remove(stdin_path); + + return EXIT_SUCCESS; +} diff --git a/command/tail.c b/command/tail.c new file mode 100644 index 0000000..8137eca --- /dev/null +++ b/command/tail.c @@ -0,0 +1,243 @@ +#include "command.h" +#include "lslib.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static struct { + bool lines; + int count; + bool print_headers; + bool dont_print_headers; + bool print_as_grow; + int grow_wait; +} flags; + +static size_t tail_file_lines(FILE* file, unsigned int count, size_t skip) { + + char** ring; + int* ring_len; + int index, read; + unsigned int size, i; + size_t len; + char* line; + + ring = malloc(sizeof(char*) * count); + memset(ring, 0, sizeof(char*) * count); + + ring_len = malloc(sizeof(int) * count); + + index = 0; + size = 0; + + fseek(file, skip, SEEK_SET); + + len = skip; + line = NULL; + + while ((read = getline(&line, &len, file)) != -1) { + + if (ring[index] != NULL) free(ring[index]); + ring[index] = line; + ring_len[index] = read; + + index++; + index %= count; + if (size < count) size++; + + line = NULL; + } + + index += count - size; + index %= count; + + for (i = 0; i < size; i++) { + fwrite(ring[index], ring_len[index], 1, stdout); + free(ring[index]); + index += 1; + index %= count; + } + + free(line); + fclose(file); + free(ring); + free(ring_len); + + return len; +} + +static size_t tail_file_chars(FILE* file, unsigned int count, size_t skip) { + + char* ring; + int index; + unsigned int size, i; + int read, c; + + ring = malloc(sizeof(char) * count); + memset(ring, 0, count); + + index = 0; + size = 0; + + fseek(file, skip, SEEK_SET); + read = skip; + + while((c = getc(file)) != EOF) { + ring[index] = c; + index++; + read++; + index %= count; + if (size < count) size++; + } + + index += count - size; + index %= count; + + for (i = 0; i < size; i++) { + putchar(ring[index]); + index += 1; + index %= count; + } + + fclose(file); + + return read; +} + +static void help(void) { + printf("Usage: tail [OPTIONS] [FILE]...\n\n"); + printf("Print last 10 lines of FILEs (or stdin) to.\n"); + printf("With more than one FILE, precede each with a filename header.\n\n"); + printf("\t-c [+]N[bkm]\tPrint last N bytes\n"); + printf("\t-n N[bkm]\tPrint last N lines\n"); + printf("\t\t\t(b:*512 k:*1024 m:*1024^2)\n"); + printf("\t-q\t\tNever print headers\n"); + printf("\t-v\t\tAlways print headers\n"); + printf("\t-f\t\tPrint data as file grows\n"); + printf("\t-s SECONDS\tWait SECONDS between reads with -f\n"); + exit(EXIT_SUCCESS); +} + +static void print_header(char* path, bool many) { + if (flags.dont_print_headers) return; + if (!many && !flags.print_headers) return; + if (streql("-", path)) { + printf("\n==> standard input <==\n"); + } else { + printf("\n=>> %s <==\n", path); + } +} + +static void tail_file(char* path, bool many) { + + FILE* file; + size_t skip; + + file = get_file(path, "r"); + print_header(path, many); + + skip = 0; + while (true) { + if (flags.lines) { + skip = tail_file_lines(file, flags.count, skip); + } else { + skip = tail_file_chars(file, flags.count, skip); + } + if (!flags.print_as_grow) break; + sleep(flags.grow_wait); + get_file(path, "r"); + }; +} + +static int short_arg(char c, char* next) { + switch (c) { + case 'c': { + long int bkm; + + flags.lines = false; + + check_arg(next); + bkm = get_blkm(next); + + if (bkm < 1) { + error("bkm cannot be less than 1"); + } + + flags.count = bkm; + return ARG_USED; + } + case 'n': { + long int bkm; + + flags.lines = true; + + check_arg(next); + bkm = get_blkm(next); + + if (bkm < 1) { + error("bkm cannot be less than 1"); + } + + flags.count = bkm; + return ARG_USED; + } + case 'q': + flags.dont_print_headers = true; + break; + case 'v': + flags.print_headers = true; + break; + case 'f': + flags.print_as_grow = true; + break; + case 's': { + long int sec; + + check_arg(next); + sec = get_number(next); + + if (sec < 1) { + error("wait seconds cannot be less than 1"); + } + + flags.grow_wait = sec; + return ARG_USED; + } + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +COMMAND(tail) { + + int start, count, i; + + flags.count = 10; + flags.dont_print_headers = false; + flags.print_headers = false; + flags.lines = true; + flags.print_as_grow = false; + flags.grow_wait = 10; + + start = parse_args(argc, argv, help, short_arg, NULL); + + count = argc - start; + + if (count < 1) { + tail_file_lines(stdin, 10, 0); + return EXIT_SUCCESS; + } + + if (count == 1) { + tail_file(argv[start], false); + return EXIT_SUCCESS; + } + + for (i = 0; i < count; i++) { + tail_file(argv[start + i], true); + } + + return EXIT_SUCCESS; +} diff --git a/command/tee.c b/command/tee.c new file mode 100644 index 0000000..0462517 --- /dev/null +++ b/command/tee.c @@ -0,0 +1,83 @@ +#include "command.h" +#include "lslib.h" + +#include <signal.h> +#include <stdlib.h> + +static struct { + bool append; + bool handle_sigint; +} flags; + +static void help(void) { + printf("Usage: tee [-ai] [FILE]...\n\n"); + printf("Copy stdin to each FILE, and also to stdout\n\n"); + printf("\t-a Append to the given FILEs, don't overwrite\n"); + printf("\t-i Ignore interrupt signals (SIGINT)\n"); + exit(EXIT_SUCCESS); +} + +static void handle(int dummy){UNUSED(dummy);} + +static void run_tee(int file_count, FILE** files) { + char c; + int i; + + while((c = getchar()) != EOF) { + int i; + for (i = 0; i < file_count; i++) { + fwrite(&c, 1, 1, files[i]); + fflush(files[i]); + } + putchar(c); + } + + for (i = 0; i < file_count; i++) { + fclose(files[i]); + } +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'a': + flags.append = true; + break; + case 'i': + flags.handle_sigint = true; + break; + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +COMMAND(tee) { + + int start, i; + FILE** files; + + flags.append = false; + flags.handle_sigint = false; + + start = parse_args(argc, argv, help, short_arg, NULL); + + if (flags.handle_sigint) { + signal(SIGINT, handle); + } + + if (argc - start < 1) { + run_tee(0, NULL); + return EXIT_SUCCESS; + } + + files = malloc(sizeof(FILE*) * (argc - start)); + + for (i = start; i < argc; i++) { + FILE* file = get_file(argv[i], flags.append ? "a" : "w"); + files[i - start] = file; + } + + run_tee(argc - start, files); + return EXIT_SUCCESS; +} diff --git a/command/wc.c b/command/wc.c new file mode 100644 index 0000000..3150045 --- /dev/null +++ b/command/wc.c @@ -0,0 +1,161 @@ +#include "command.h" +#include "lslib.h" + +#include <ctype.h> +#include <stdlib.h> + +static struct { + bool newlines; + bool words; + bool characters; + bool bytes; + bool longest_line; + bool has_flags; +} flags; + +static int lines = 0; +static int words = 0; +static int chars = 0; +static int bytes = 0; +static int logst = 0; + +static void list(int l, int w, int c, int b, int lg) { + if (flags.newlines) { + printf("\t%d", l); + } + if (flags.words) { + printf("\t%d", w); + } + if (flags.characters) { + printf("\t%d", c); + } + if (flags.bytes) { + printf("\t%d", b); + } + if (flags.longest_line) { + printf("\t%d", lg); + } +} + +#define BS 1024 + +static bool is_delimiter(char c) { + return c == ' ' || c == '\t' || c == '\n'; +} + +static void run_wc(FILE* file) { + int l = 0, w = 0, c = 0, b = 0, lg = 0; + + bool in_word = false; + int current_length = 0; + + int read; + char buf[BS]; + while ((read = fread(buf, 1, 1024, file)) > 0) { + int i; + for (i = 0; i < read; i++) { + char ch = buf[i]; + b++; + if (ch == '\n') { + l++; + lg = lg > current_length ? lg : current_length; + current_length = 0; + } else { + current_length++; + } + if (isprint(ch) || is_delimiter(ch)) c++; + if (in_word && is_delimiter(ch)) { + in_word = false; + w++; + } else if (!in_word && !is_delimiter(ch) && isprint(ch)) { + in_word = true; + } + } + } + if (in_word) w++; + lg = lg > current_length ? lg : current_length; + list(l, w, c, b, lg); + lines += l; + words += w; + chars += c; + bytes += b; + logst += lg; + if (file != stdin) + fclose(file); +} + +static void help(void) { + printf("Usage: wc [-cmlwL] [FILE]...\n\n"); + printf("Count lines, words, and bytes for FILEs (or stdin)\n\n"); + printf("\t-c Count bytes\n"); + printf("\t-m Count characters\n"); + printf("\t-l Count newlines\n"); + printf("\t-w Count words\n"); + printf("\t-L Print longest line length\n"); + exit(EXIT_SUCCESS); +} + +static int short_arg(char c, char* next) { + UNUSED(next); + switch (c) { + case 'c': + flags.bytes = true; + break; + case 'm': + flags.characters = true; + break; + case 'l': + flags.newlines = true; + break; + case 'w': + flags.words = true; + break; + case 'L': + flags.longest_line = true; + break; + default: + return ARG_INVALID; + } + flags.has_flags = true; + return ARG_UNUSED; +} + +COMMAND(wc) { + + int start, i; + + flags.newlines = false; + flags.words = false; + flags.characters = false; + flags.bytes = false; + flags.longest_line = false; + flags.has_flags = false; + + + start = parse_args(argc, argv, help, short_arg, NULL); + + if (!flags.has_flags) { + flags.newlines = true; + flags.words = true; + flags.characters = true; + } + + if (argc - start < 1) { + run_wc(stdin); + printf("\n"); + return EXIT_SUCCESS; + } + + for (i = start; i < argc; i++) { + FILE* file = get_file(argv[i], "r"); + run_wc(file); + printf("\t%s\n", argv[i]); + } + + if (argc - start > 1) { + list(lines, words, chars, bytes, logst); + printf("\ttotal\n"); + } + + return EXIT_SUCCESS; +} diff --git a/command/whoami.c b/command/whoami.c new file mode 100644 index 0000000..5823b8f --- /dev/null +++ b/command/whoami.c @@ -0,0 +1,30 @@ +#include "command.h" +#include "lslib.h" + +#include <pwd.h> +#include <stdlib.h> +#include <unistd.h> + +static void help(void) { + printf("Usage: whoami\n\n"); + printf("Print the username associated with the current effective user id\n"); + exit(EXIT_SUCCESS); +} + +COMMAND(whoami) { + + uid_t usr; + struct passwd* passwd; + + parse_help(argc, argv, help); + + usr = getuid(); + passwd = getpwuid(usr); + + if (passwd == NULL) { + printf("\x1b[1;91myou do not exist.\n"); + } else { + printf("%s\n", passwd->pw_name); + } + return EXIT_SUCCESS; +} diff --git a/command/xargs.c b/command/xargs.c new file mode 100644 index 0000000..2a41460 --- /dev/null +++ b/command/xargs.c @@ -0,0 +1,192 @@ +#include "command.h" +#include "lslib.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static struct { + bool null_seperated; + bool ignore_empty; + bool print_command; + bool prompt_command; + int max_args; + FILE* file; +} flags; + +static void help(void) { + printf("Usage: xargs [OPTIONS] [PROG ARGS]\n\n"); + printf("Run PROG on every item given by stdin\n\n"); + printf("\t-0\tInput is separated by NULs\n"); + printf("\t-a FILE\tRead from FILE instead of stdin\n"); + printf("\t-r\tDon't run command if input is empty\n"); + printf("\t-t\tPrint the command on stderr before execution\n"); + printf("\t-p\tAsk user whether to run each command\n"); + printf("\t-n N\tPass no more than N args to PROG\n"); +} + +static int short_arg(char c, char* next) { + UNUSED(next); + + switch (c) { + case '0': + flags.null_seperated = true; + break; + case 'a': + check_arg(next); + flags.file = get_file(next, "r"); + return ARG_USED; + case 'r': + flags.ignore_empty = true; + break; + case 't': + flags.print_command = true; + break; + case 'p': + flags.prompt_command = true; + break; + case 'n': { + long int n; + + check_arg(next); + n = get_number(next); + + if (n < 1) { + error("max arg count must be at least 1"); + } + + flags.max_args = n; + return ARG_USED; + } + default: + return ARG_INVALID; + } + return ARG_UNUSED; +} + +char* read_next(FILE* file, int arg_count) { + + int size, capacity; + char* buf; + char c; + + if (flags.max_args != -1 && arg_count == flags.max_args) return NULL; + + size = 0; + capacity = 8; + buf = malloc(sizeof(char) * capacity); + + while(c = getc(file), true) { + if (c == EOF && size == 0) { + free(buf); + return NULL; + } + + if (size == capacity) { + capacity *= 2; + buf = realloc(buf, sizeof(char) * capacity); + } + + if (c == '\0' || c == EOF || (!flags.null_seperated && c == '\n')) { + buf[size++] = '\0'; + return buf; + } else { + buf[size++] = c; + } + } +} + +void read_args(FILE* file, char*** args, int* size, int* capacity) { + char* arg; + static int read = 0; + while (arg = read_next(file, read), true) { + if (*size == *capacity) { + *capacity *= 2; + *args = realloc(*args, sizeof(char*) * *capacity); + } + (*args)[(*size)++] = arg; + read++; + if (arg == NULL) break; + } +} + +COMMAND(xargs) { + + int start, arg_start, arg_on_stack_count; + int size, capacity, i; + char* command; + char** args; + + flags.null_seperated = false; + flags.ignore_empty = false; + flags.print_command = false; + flags.print_command = false; + flags.max_args = -1; + flags.file = stdin; + + start = parse_args(argc, argv, help, short_arg, NULL); + + if (start >= argc) { + command = "echo"; + } else { + command = argv[start]; + } + + arg_start = start + 1; + + if (arg_start >= argc) { + arg_on_stack_count = 0; + arg_start = argc - 1; + } else { + arg_on_stack_count = argc - arg_start; + } + + size = arg_on_stack_count + 1; + capacity = size + 8; + + args = malloc(sizeof(char*) * capacity); + args[0] = command; + memcpy(&args[1], &argv[arg_start], arg_on_stack_count * sizeof(char*)); + read_args(flags.file, &args, &size, &capacity); + + if (flags.ignore_empty && size < 2) goto cleanup; + + if (flags.prompt_command || flags.print_command) { + for (i = 0; i < size - 1; i++) { + fprintf(stderr, "%s ", args[i]); + } + fprintf(stderr, "\b\n"); + } + + if (flags.prompt_command) { + FILE* in; + char c; + + fprintf(stderr, "Run command? "); + fflush(stderr); + + in = get_tty_stream("r"); + c = getc(in); + fclose(in); + + if (c != 'y' && c != 'Y') { + fprintf(stderr, "Skipping...\n"); + goto cleanup; + } + } + + if (execvp(command, args) == -1) { + error("error: failed to execute command: %s", strerror(errno)); + } + +cleanup: + + for (i = arg_on_stack_count + 1; i < size - 1; i++) { + free(args[i]); + } + fclose(flags.file); + + return EXIT_SUCCESS; +} diff --git a/command/yes.c b/command/yes.c new file mode 100644 index 0000000..f979a9f --- /dev/null +++ b/command/yes.c @@ -0,0 +1,27 @@ +#include "command.h" +#include "lslib.h" + +static void help(void) { + printf("Usage: yes [STRING]\n\n"); + printf("Repeatedly output a line with all specified STRING(s), or 'y'.\n"); +} + +COMMAND(yes) { + const char* repeat; + int i; + + parse_help(argc, argv, help); + + if (argc == 0) { + repeat = "y"; + } else { + repeat = argv[0]; + for (i = 1; i < argc; i++) { + *(argv[i]-1) = ' '; + } + } + + while (true) { + printf("%s\n", repeat); + } +} |