summaryrefslogtreecommitdiff
path: root/command
diff options
context:
space:
mode:
authorTyler Murphy <tylerm@tylerm.dev>2023-05-06 00:39:44 -0400
committerTyler Murphy <tylerm@tylerm.dev>2023-05-06 00:39:44 -0400
commitd8f2c10b7108fff6b7e437291093a1cadc15ab9f (patch)
tree3fc50a19d6fbb9c94a8fe147cd2a6c4ba7f59b8d /command
parentansii c (diff)
downloadlazysphere-d8f2c10b7108fff6b7e437291093a1cadc15ab9f.tar.gz
lazysphere-d8f2c10b7108fff6b7e437291093a1cadc15ab9f.tar.bz2
lazysphere-d8f2c10b7108fff6b7e437291093a1cadc15ab9f.zip
refactor
Diffstat (limited to 'command')
-rw-r--r--command/cat.c142
-rw-r--r--command/cp.c245
-rw-r--r--command/dd.c63
-rw-r--r--command/echo.c107
-rw-r--r--command/ed.c920
-rw-r--r--command/grep.c269
-rw-r--r--command/groups.c63
-rw-r--r--command/head.c141
-rw-r--r--command/id.c76
-rw-r--r--command/ls.c563
-rw-r--r--command/mkdir.c70
-rw-r--r--command/mv.c121
-rw-r--r--command/printf.c144
-rw-r--r--command/rm.c140
-rw-r--r--command/tac.c119
-rw-r--r--command/tail.c243
-rw-r--r--command/tee.c83
-rw-r--r--command/wc.c161
-rw-r--r--command/whoami.c30
-rw-r--r--command/xargs.c192
-rw-r--r--command/yes.c27
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, &regex, 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, &regex, 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, &current)) {
+ 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);
+ }
+}