#include "../command.h" struct Flags { bool lines; int count; bool print_headers; bool dont_print_headers; bool print_as_grow; int grow_wait; }; 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; unsigned int size = 0; int read; fseek(file, skip, SEEK_SET); size_t len = skip; char* 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 (unsigned int i = 0; i < size; i++) { fwrite(ring[index], ring_len[index], 1, stdout); free(ring[index]); index += 1; 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 [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 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 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 { skip = tail_file_chars(file, flags->count, skip); } 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; }