2023-04-28 19:13:10 +00:00
|
|
|
#include "../command.h"
|
|
|
|
|
2023-05-01 22:43:32 +00:00
|
|
|
static struct {
|
2023-04-29 00:32:18 +00:00
|
|
|
bool lines;
|
|
|
|
int count;
|
|
|
|
bool print_headers;
|
|
|
|
bool dont_print_headers;
|
|
|
|
bool print_as_grow;
|
|
|
|
int grow_wait;
|
2023-05-01 22:43:32 +00:00
|
|
|
} flags;
|
2023-04-28 19:13:10 +00:00
|
|
|
|
2023-04-29 00:32:18 +00:00
|
|
|
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];
|
2023-04-28 19:13:10 +00:00
|
|
|
|
|
|
|
int index = 0;
|
2023-04-29 00:32:18 +00:00
|
|
|
unsigned int size = 0;
|
2023-04-28 19:13:10 +00:00
|
|
|
|
|
|
|
int read;
|
2023-04-29 00:32:18 +00:00
|
|
|
fseek(file, skip, SEEK_SET);
|
|
|
|
size_t len = skip;
|
2023-04-28 19:13:10 +00:00
|
|
|
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++;
|
2023-04-29 00:32:18 +00:00
|
|
|
index %= count;
|
|
|
|
if (size < count) size++;
|
2023-04-28 19:13:10 +00:00
|
|
|
|
|
|
|
line = NULL;
|
|
|
|
}
|
|
|
|
|
2023-04-29 00:32:18 +00:00
|
|
|
index += count - size;
|
|
|
|
index %= count;
|
2023-04-28 19:13:10 +00:00
|
|
|
|
2023-04-29 00:32:18 +00:00
|
|
|
for (unsigned int i = 0; i < size; i++) {
|
2023-04-28 19:13:10 +00:00
|
|
|
fwrite(ring[index], ring_len[index], 1, stdout);
|
|
|
|
free(ring[index]);
|
|
|
|
index += 1;
|
2023-04-29 00:32:18 +00:00
|
|
|
index %= count;
|
2023-04-28 19:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
free(line);
|
|
|
|
fclose(file);
|
2023-04-29 00:32:18 +00:00
|
|
|
|
|
|
|
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;
|
2023-04-28 19:13:10 +00:00
|
|
|
}
|
|
|
|
|
2023-05-01 22:43:32 +00:00
|
|
|
static void help(void) {
|
2023-04-29 00:32:18 +00:00
|
|
|
printf("Usage: tail [OPTIONS] [FILE]...\n\n");
|
2023-04-28 19:13:10 +00:00
|
|
|
printf("Print last 10 lines of FILEs (or stdin) to.\n");
|
2023-04-29 00:32:18 +00:00
|
|
|
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");
|
2023-04-28 19:13:10 +00:00
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2023-05-01 22:43:32 +00:00
|
|
|
static void print_header(char* path, bool many) {
|
|
|
|
if (flags.dont_print_headers) return;
|
|
|
|
if (!many && !flags.print_headers) return;
|
2023-04-29 00:32:18 +00:00
|
|
|
if (streql("-", path)) {
|
|
|
|
printf("\n==> standard input <==\n");
|
|
|
|
} else {
|
|
|
|
printf("\n=>> %s <==\n", path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-01 22:43:32 +00:00
|
|
|
static void tail_file(char* path, bool many) {
|
2023-04-29 00:32:18 +00:00
|
|
|
FILE* file = get_file(path, "r");
|
2023-05-01 22:43:32 +00:00
|
|
|
print_header(path, many);
|
2023-04-29 00:32:18 +00:00
|
|
|
|
|
|
|
size_t skip = 0;
|
|
|
|
while (true) {
|
2023-05-01 22:43:32 +00:00
|
|
|
if (flags.lines) {
|
|
|
|
skip = tail_file_lines(file, flags.count, skip);
|
2023-04-29 00:32:18 +00:00
|
|
|
} else {
|
2023-05-01 22:43:32 +00:00
|
|
|
skip = tail_file_chars(file, flags.count, skip);
|
2023-04-29 00:32:18 +00:00
|
|
|
}
|
2023-05-01 22:43:32 +00:00
|
|
|
if (!flags.print_as_grow) break;
|
|
|
|
sleep(flags.grow_wait);
|
2023-04-29 00:32:18 +00:00
|
|
|
get_file(path, "r");
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-05-01 22:43:32 +00:00
|
|
|
static int short_arg(char c, char* next) {
|
|
|
|
switch (c) {
|
|
|
|
case 'c': {
|
|
|
|
flags.lines = false;
|
|
|
|
check_arg(next);
|
|
|
|
long int bkm = get_blkm(next);
|
|
|
|
if (bkm < 1) {
|
2023-05-03 16:17:56 +00:00
|
|
|
error("bkm cannot be less than 1");
|
2023-05-01 22:43:32 +00:00
|
|
|
}
|
|
|
|
flags.count = bkm;
|
|
|
|
return ARG_USED;
|
|
|
|
}
|
|
|
|
case 'n': {
|
|
|
|
flags.lines = true;
|
|
|
|
check_arg(next);
|
|
|
|
long int bkm = get_blkm(next);
|
|
|
|
if (bkm < 1) {
|
2023-05-03 16:17:56 +00:00
|
|
|
error("bkm cannot be less than 1");
|
2023-05-01 22:43:32 +00:00
|
|
|
}
|
|
|
|
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': {
|
|
|
|
check_arg(next);
|
|
|
|
long int sec = get_number(next);
|
|
|
|
if (sec < 1) {
|
2023-05-03 16:17:56 +00:00
|
|
|
error("wait seconds cannot be less than 1");
|
2023-05-01 22:43:32 +00:00
|
|
|
}
|
|
|
|
flags.grow_wait = sec;
|
|
|
|
return ARG_USED;
|
|
|
|
}
|
2023-05-03 16:17:56 +00:00
|
|
|
default:
|
|
|
|
return ARG_INVALID;
|
2023-05-01 22:43:32 +00:00
|
|
|
}
|
|
|
|
return ARG_UNUSED;
|
|
|
|
}
|
|
|
|
|
2023-04-28 19:13:10 +00:00
|
|
|
COMMAND(tail) {
|
2023-04-29 00:32:18 +00:00
|
|
|
|
|
|
|
flags.count = 10;
|
|
|
|
flags.dont_print_headers = false;
|
|
|
|
flags.print_headers = false;
|
|
|
|
flags.lines = true;
|
|
|
|
flags.print_as_grow = false;
|
|
|
|
flags.grow_wait = 10;
|
|
|
|
|
2023-05-01 22:43:32 +00:00
|
|
|
int start = parse_args(argc, argv, help, short_arg, NULL);
|
2023-04-28 19:13:10 +00:00
|
|
|
|
2023-04-29 00:32:18 +00:00
|
|
|
int count = argc - start;
|
2023-04-28 19:13:10 +00:00
|
|
|
|
2023-04-29 00:32:18 +00:00
|
|
|
if (count < 1) {
|
|
|
|
tail_file_lines(stdin, 10, 0);
|
2023-04-28 19:13:10 +00:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2023-04-29 00:32:18 +00:00
|
|
|
if (count == 1) {
|
2023-05-01 22:43:32 +00:00
|
|
|
tail_file(argv[start], false);
|
2023-04-29 00:32:18 +00:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
2023-05-01 22:43:32 +00:00
|
|
|
tail_file(argv[start + i], true);
|
2023-04-28 19:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|