diff --git a/readme.md b/readme.md index 3440299..8308f19 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ A terrible busybox/gnu coreutils clone. Currently the only supported commands are: -`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail` +`dd`, `cat`, `yes`, `echo`, `printf`, `id`, `groups`, `ls`, `tail`, `head` ## How to diff --git a/src/command.h b/src/command.h index 939886d..0b7b8ad 100644 --- a/src/command.h +++ b/src/command.h @@ -18,3 +18,4 @@ COMMAND_EMPTY(groups); COMMAND_EMPTY(user_id); COMMAND(ls); COMMAND(tail); +COMMAND(head); diff --git a/src/commands/head.c b/src/commands/head.c new file mode 100644 index 0000000..446a9e6 --- /dev/null +++ b/src/commands/head.c @@ -0,0 +1,145 @@ +#include "../command.h" + +#include +#include + +struct Flags { + int count; + bool lines; + bool print_headers; + bool dont_print_headers; +}; + +static void head_file_lines(FILE* file, struct Flags* flags) { + 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, struct Flags* flags) { + char c; + int count = flags->count; + while(count > 0 && (c = getc(file)) != EOF) { + putchar(c); + count--; + } + + fclose(file); +} + +static void help() { + 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"); + exit(EXIT_SUCCESS); +} + +static char* next_arg(int argc, char** argv, int index) { + if (index >= argc) { + error("error: expected another argument after option"); + } + return argv[index]; +} + +static void print_header(char* path, struct Flags* flags, 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, struct Flags* flags, bool many) { + FILE* file = get_file(path, "r"); + print_header(path, flags, many); + if (flags->lines) { + head_file_lines(file, flags); + } else { + head_file_chars(file, flags); + } +} + +COMMAND(head) { + struct Flags flags; + flags.count = 10; + flags.lines = true; + flags.print_headers = false; + flags.dont_print_headers = false; + + int start = 0; + for (int i = 0; i < argc; i++) { + if (!prefix("-", argv[i])) break; + if (streql(argv[0], "--help")) help(); + start++; + int i_s = i; + for (size_t j = 1; j < strlen(argv[i_s]); j++) { + char c = argv[i_s][j]; + switch(c) { + case 'c': { + start++; + flags.lines = false; + char* arg = next_arg(argc, argv, ++i); + long int bkm = get_blkm(arg); + if (bkm < 1) { + error("error: bkm cannot be less than 1"); + } + flags.count = bkm; + break; + } + case 'n': { + start++; + flags.lines = true; + char* arg = next_arg(argc, argv, ++i); + long int bkm = get_blkm(arg); + if (bkm < 1) { + error("error: bkm cannot be less than 1"); + } + flags.count = bkm; + break; + } + case 'q': + flags.dont_print_headers = true; + break; + case 'v': + flags.print_headers = true; + break; + default: { + error("error: unknown option -%c", c); + } + } + } + } + + int count = argc - start; + + if (count < 1) { + head_file_lines(stdin, &flags); + return EXIT_SUCCESS; + } + + if (count == 1) { + head_file(argv[start], &flags, false); + return EXIT_SUCCESS; + } + + for (int i = 0; i < count; i++) { + head_file(argv[start + i], &flags, true); + } + + return EXIT_SUCCESS; +} diff --git a/src/commands/ls.c b/src/commands/ls.c index d31c143..b0b00a5 100644 --- a/src/commands/ls.c +++ b/src/commands/ls.c @@ -25,6 +25,7 @@ struct Flags { bool more_info; bool one_column; bool recurse; + bool colored; }; struct FileInfo { @@ -34,10 +35,11 @@ struct FileInfo { struct group* grp; char size[5]; char date[13]; - struct dirent* file; + char name[PATH_MAX]; bool set_uid; bool set_gid; bool exec; + unsigned char type; }; struct FileListInfo { @@ -61,14 +63,17 @@ static DIR* get_directory(char* path) { return d; } -static void append_path(char buf[PATH_MAX], const char* dir_path, const struct dirent* file) { +static void append_path(char buf[PATH_MAX], const char* dir_path, const char* file_path) { size_t dir_len = strlen(dir_path); + size_t file_len = strlen(file_path); + memcpy(buf, dir_path, dir_len); if (buf[dir_len-1] != '/') { buf[dir_len] = '/'; dir_len++; } - memcpy(buf + dir_len, file->d_name, strlen(file->d_name) + 1); + memcpy(buf + dir_len, file_path, file_len); + buf[dir_len + file_len] = '\0'; } static bool get_file_info(struct dirent* file, const char* dir_path, struct FileInfo* info) { @@ -77,17 +82,20 @@ static bool get_file_info(struct dirent* file, const char* dir_path, struct File gid_t gid = getgid(); char buf[PATH_MAX]; - append_path(buf, dir_path, file); + append_path(buf, dir_path, file->d_name); struct stat s; + memset(&s, 0, sizeof(struct stat)); + if (stat(buf, &s) < 0) { - printf("\x1b[0merror: failed to read file '%s': %s\n", buf, strerror(errno)); + // printf("\x1b[0merror: failed to read file '%s': %s\n", buf, strerror(errno)); return false; } info->set_uid = false; info->set_gid = false; info->exec = false; + info->name[0] = '\0'; if (file->d_type == DT_DIR) info->mode[0] = 'd'; @@ -137,8 +145,10 @@ static bool get_file_info(struct dirent* file, const char* dir_path, struct File info->usr = getpwuid(s.st_uid); info->grp = getgrgid(s.st_gid); - info->file = file; info->links = s.st_nlink; + info->type = file->d_type; + + strcpy(info->name, file->d_name); print_file_size(s.st_size, info->size); print_date_time(s.st_mtim.tv_sec + s.st_mtim.tv_nsec / 1000000, info->date); @@ -147,9 +157,9 @@ static bool get_file_info(struct dirent* file, const char* dir_path, struct File static char* get_file_color(struct FileInfo* info) { char* color; - if (info->file->d_type == DT_DIR) { + if (info->type == DT_DIR) { color = DIR_COLOR; - } else if (info->file->d_type == DT_LNK) { + } else if (info->type == DT_LNK) { color = LINK_COLOR; } else { if (info->set_uid) { @@ -171,6 +181,7 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo if (!isatty(1)) { flags->one_column = true; + flags->colored = false; } char* color; @@ -179,7 +190,6 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo for (int i = 0; i < file_len; i++) { struct FileInfo finfo = files[i]; - if (strlen(finfo.file->d_name) < 1) continue; color = get_file_color(&finfo); if (flags->more_info) { printf("%s %*d %*s %*s %*s %s %s%s\x1b[0m", @@ -193,12 +203,12 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo info.max_size, finfo.size, finfo.date, - color, - finfo.file->d_name + flags->colored ? color : FILE_COLOR, + finfo.name ); - if (finfo.file->d_type == DT_LNK) { + if (finfo.type == DT_LNK) { char path[PATH_MAX]; - append_path(path, dir_path, finfo.file); + append_path(path, dir_path, finfo.name); char lnk[PATH_MAX]; if (readlink(path, lnk, PATH_MAX) < 0) { @@ -211,13 +221,13 @@ static void list_files(struct FileInfo* files, int file_len, struct FileListInfo putchar('\n'); } } else if (flags->one_column) { - printf("%s%s\x1b[0m\n", color, finfo.file->d_name); + printf("%s%s\x1b[0m\n", color, finfo.name); } else { if (info.total_len > w.ws_col) { if (i != 0 && i % row_count == 0) putchar('\n'); - printf("%s%*s\x1b[0m", color, column_width, finfo.file->d_name); + printf("%s%*s\x1b[0m", flags->colored ? color : FILE_COLOR, -column_width, finfo.name); } else { - printf("%s%s\x1b[0m ", color, finfo.file->d_name); + printf("%s%s\x1b[0m ", flags->colored ? color : FILE_COLOR, finfo.name); } } } @@ -318,7 +328,7 @@ static bool recurse_directory(char* path, struct Flags* flags) { if (!flags->hidden && prefix(".", file->d_name)) continue; if (is_dot_dir(file->d_name)) continue; char buf[PATH_MAX]; - append_path(buf, path, file); + append_path(buf, path, file->d_name); recurse_directory(buf, flags); } @@ -374,11 +384,17 @@ COMMAND(ls) { flags.hide_dot = false; flags.one_column = false; flags.recurse = false; + flags.colored = false; + int start = 0; for (int i = 0; i < argc; i++) { if (!prefix("-", argv[i])) break; if (streql("--help", argv[i])) help(); start++; + if (streql("--color=auto", argv[i]) || streql("--color=yes", argv[i])) { + flags.colored = true; + continue; + } for (size_t j = 1; j < strlen(argv[i]); j++) { char c = argv[i][j]; switch (c) { diff --git a/src/commands/tail.c b/src/commands/tail.c index 04f7f42..24d7b1f 100644 --- a/src/commands/tail.c +++ b/src/commands/tail.c @@ -2,19 +2,31 @@ #include #include +#include #include +#include -static void tail_file(FILE* file) { - char* ring[10]; - memset(ring, 0, sizeof(char*) * 10); +struct Flags { + bool lines; + int count; + bool print_headers; + bool dont_print_headers; + bool print_as_grow; + int grow_wait; +}; - int ring_len[10]; +static size_t tail_file_lines(FILE* file, unsigned int count, size_t skip) { + char* ring[count]; + memset(ring, 0, sizeof(char*) * count); + + int ring_len[count]; int index = 0; - int size = 0; + unsigned int size = 0; int read; - size_t len = 0; + fseek(file, skip, SEEK_SET); + size_t len = skip; char* line = NULL; while ((read = getline(&line, &len, file)) != -1) { @@ -23,57 +35,189 @@ static void tail_file(FILE* file) { ring_len[index] = read; index++; - index %= 10; - if (size < 10) size++; + index %= count; + if (size < count) size++; line = NULL; } - index += 10 - size; - index %= 10; + index += count - size; + index %= count; - for (int i = 0; i < size; i++) { + for (unsigned int i = 0; i < size; i++) { fwrite(ring[index], ring_len[index], 1, stdout); free(ring[index]); index += 1; - index %= 10; + index %= count; } free(line); fclose(file); + + return len; +} + +static size_t tail_file_chars(FILE* file, unsigned int count, size_t skip) { + char ring[count]; + memset(ring, 0, count); + + int index = 0; + unsigned int size = 0; + + fseek(file, skip, SEEK_SET); + int read = skip; + int c; + while((c = getc(file)) != EOF) { + ring[index] = c; + index++; + read++; + index %= count; + if (size < count) size++; + } + + index += count - size; + index %= count; + + for (unsigned int i = 0; i < size; i++) { + putchar(ring[index]); + index += 1; + index %= count; + } + + fclose(file); + + return read; } static void help() { - printf("Usage: tail [FILE]...\n\n"); + 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"); + 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); } -COMMAND(tail) { - if (argc < 1) { - tail_file(stdin); - return EXIT_SUCCESS; +static char* next_arg(int argc, char** argv, int index) { + if (index >= argc) { + error("error: expected another argument after option"); } + return argv[index]; +} - if (streql(argv[0], "--help")) help(); - - if (argc == 1) { - FILE* file = get_file(argv[0], "r"); - tail_file(file); - return EXIT_SUCCESS; +static void print_header(char* path, struct Flags* flags, 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); } +} - for (int i = 0; i < argc; i++) { - FILE* file; - if (streql("-", argv[i])) { - file = stdin; +static void tail_file(char* path, struct Flags* flags, bool many) { + FILE* file = get_file(path, "r"); + print_header(path, flags, many); + + size_t skip = 0; + while (true) { + if (flags->lines) { + skip = tail_file_lines(file, flags->count, skip); } else { - file = get_file_s(argv[i], "r"); - if (file == NULL) continue; + skip = tail_file_chars(file, flags->count, skip); } - printf("\n==> %s <==\n", argv[i]); - tail_file(file); + if (!flags->print_as_grow) break; + sleep(flags->grow_wait); + get_file(path, "r"); + }; +} + +COMMAND(tail) { + + struct Flags flags; + flags.count = 10; + flags.dont_print_headers = false; + flags.print_headers = false; + flags.lines = true; + flags.print_as_grow = false; + flags.grow_wait = 10; + + int start = 0; + for (int i = 0; i < argc; i++) { + if (!prefix("-", argv[i])) break; + if (streql(argv[0], "--help")) help(); + start++; + int i_s = i; + for (size_t j = 1; j < strlen(argv[i_s]); j++) { + char c = argv[i_s][j]; + switch(c) { + case 'c': { + start++; + flags.lines = false; + char* arg = next_arg(argc, argv, ++i); + long int bkm = get_blkm(arg); + if (bkm < 1) { + error("error: bkm cannot be less than 1"); + } + flags.count = bkm; + break; + } + case 'n': { + start++; + flags.lines = true; + char* arg = next_arg(argc, argv, ++i); + long int bkm = get_blkm(arg); + if (bkm < 1) { + error("error: bkm cannot be less than 1"); + } + flags.count = bkm; + break; + } + 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': { + start++; + char* arg = next_arg(argc, argv, ++i); + long int sec = get_number(arg); + if (sec < 1) { + error("error: wait seconds cannot be less than 1"); + } + flags.grow_wait = sec; + break; + } + default: { + error("error: unknown option -%c", c); + } + } + } + } + + int count = argc - start; + + if (count < 1) { + tail_file_lines(stdin, 10, 0); + return EXIT_SUCCESS; + } + + if (count == 1) { + tail_file(argv[start], &flags, false); + return EXIT_SUCCESS; + } + + for (int i = 0; i < count; i++) { + tail_file(argv[start + i], &flags, true); } return EXIT_SUCCESS; diff --git a/src/main.c b/src/main.c index ce89210..72e1531 100644 --- a/src/main.c +++ b/src/main.c @@ -20,7 +20,7 @@ int main (ARGUMENTS) { if (argc < 2) { printf("usage: lazysphere [function [arguments]...]\n\n"); printf("currently defined functions:\n"); - printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail\n"); + printf("\tdd, cat, yes, echo, printf, id, groups, ls, tail, head\n"); return EXIT_SUCCESS; } argc--; @@ -52,6 +52,8 @@ int main (ARGUMENTS) { return ls(NEXT_ARGS); } else if (streql(cmd, "tail")) { return tail(NEXT_ARGS); + } else if (streql(cmd, "head")) { + return head(NEXT_ARGS); } else { error("error: invalid command %s", cmd); } diff --git a/src/shared.c b/src/shared.c index 8d0dcca..240e364 100644 --- a/src/shared.c +++ b/src/shared.c @@ -34,6 +34,7 @@ FILE* get_file_s(const char* path, const char* type) { } FILE* get_file(const char* path, const char* type) { + if (streql("-", path)) return stdin; FILE* file = get_file_s(path, type); if (file == NULL) exit(EXIT_FAILURE); return file; @@ -48,6 +49,30 @@ long int get_number(const char* text) { return n; } +long int get_blkm(const char* text) { + char* end; + long int n = strtol(text, &end, 10); + if (text == end) { + error("error: %s is not a valid bkm", text); + } + if (*end == '\0') return n; + switch (*end) { + case 'K': + case 'k': + return n * 1024; + case 'B': + case 'b': + return n * 512; + case 'M': + case 'm': + return n * 1024 * 1204; + default: + error("error: invalid bkm type %c", *end); + } + // shouldnt get here anyways + return 0; +} + bool streql(const char* a, const char* b) { if (*a != *b) return false; int n = 0; diff --git a/src/shared.h b/src/shared.h index 251a993..a542ba6 100644 --- a/src/shared.h +++ b/src/shared.h @@ -10,6 +10,7 @@ void error(const char* format, ...); FILE* get_file_s(const char* path, const char* type); FILE* get_file(const char* path, const char* type); long int get_number(const char* text); +long int get_blkm(const char* text); bool streql(const char* a, const char* b); bool prefix(const char* pre, const char* str);