Last active
June 10, 2024 19:46
-
-
Save AdriDevelopsThings/747bd78ee29cffcc9de161d00aaaf294 to your computer and use it in GitHub Desktop.
This program will print the content of a file and then waits for a change of the file and prints the added content.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This program will print the content of a file and then waits for a change of the file and prints the added content. | |
* Author: AdriDoesThings <adri@adridoesthings.com> | |
*/ | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <sys/inotify.h> | |
#include <signal.h> | |
#define BUFFER_SIZE 255 | |
int fd; | |
int ifd; | |
int wfd; | |
typedef struct { | |
char* file; | |
int skip_n_line; // -1 if all lines should be skipped | |
} args_t; | |
void print_usage(FILE* file, char* exec) { | |
fprintf(file, "Usage: %s [OPTIONS] FILE\nPrint the output of a file and print the new content when new content is added to the file.\n\nOptions:\n\t-s, --skip-lines\tSkip the first n lines. Set this value to 'a' to skip all lines while first printing.\n\t-h, --help\tPrint this help.\n", exec); | |
} | |
/* | |
* Parse arguments (argc long string list in argv) to args | |
* Returns: | |
* - 1: success but the program should exit with EXIT_SUCCESS right now | |
* - 0: success, args contains the arguments | |
* - -1: error, program should exit with EXIT_FAILURE now | |
*/ | |
int parse_args(args_t *args, int argc, char **argv) { | |
args->file = NULL; | |
args->skip_n_line = 0; | |
for (size_t i=1; i < argc; i++) { // start at i=1, first argument is executable | |
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { | |
print_usage(stdout, argv[0]); | |
return 1; | |
} else { | |
// -s <N>, --skip-lines <N>, -s=<N> and --skip-lines=<N> syntax is allowed | |
// if N is 'a' then args->skip_n_lines should be -1 | |
int s_cmp = strncmp(argv[i], "-s", 2); | |
int sl_cmp = strncmp(argv[i], "--skip-lines", 12); | |
if (s_cmp == 0 && strlen(argv[i]) > 3) { // -s=<N> syntax | |
if (argv[i][2] != '=') { | |
print_usage(stderr, argv[0]); | |
return -1; | |
} | |
if (argv[i][3] == 'a') { | |
args->skip_n_line = -1; | |
continue; | |
} | |
args->skip_n_line = atoi(&argv[i][3]); | |
if (args->skip_n_line == 0) { | |
print_usage(stderr, argv[0]); | |
return -1; | |
} | |
} else if (sl_cmp == 0 && strlen(argv[i]) > 13) { // --skip-lines=<N> syntax | |
if (argv[i][12] != '=') { | |
print_usage(stderr, argv[0]); | |
return -1; | |
} | |
if (argv[i][13] == 'a') { | |
args->skip_n_line = -1; | |
continue; | |
} | |
args->skip_n_line = atoi(&argv[i][13]); | |
if (args->skip_n_line == 0) { | |
print_usage(stderr, argv[0]); | |
return -1; | |
} | |
} else if (s_cmp == 0 || sl_cmp == 0) { // not '=' syntax | |
i++; // go to next argument | |
if (i == argc) { // there isn't a next argument | |
print_usage(stderr, argv[0]); | |
return -1; | |
} | |
if (argv[i][0] == 'a') { | |
args->skip_n_line = -1; | |
continue; | |
} | |
args->skip_n_line = atoi(argv[i]); | |
if (args->skip_n_line == 0) { | |
print_usage(stderr, argv[0]); | |
return -1; | |
} | |
} else { | |
args->file = argv[i]; | |
} | |
} | |
} | |
// file is a required argument | |
if (args->file == NULL) { | |
print_usage(stderr, argv[0]); | |
return -1; | |
} | |
return 0; | |
} | |
/* | |
* print the content of the file of fd, but skip the first skip_n_line lines | |
*/ | |
void print_file_content(int skip_n_line) { | |
while (1) { // read in 255 byte chuns | |
u_int8_t buffer[sizeof(char) * BUFFER_SIZE]; | |
ssize_t n = read(fd, buffer, sizeof(char) * BUFFER_SIZE); | |
if (n == 0) { // there is nothing to read anymore | |
break; | |
} | |
if (n < 0) { | |
perror("Error while reading file"); | |
close(fd); | |
close(ifd); | |
exit(EXIT_FAILURE); | |
} | |
// pointer should be mutable | |
uint8_t *write_buffer = buffer; | |
// there are lines to skip and data to read (n > 0) | |
while (skip_n_line > 0 && n > 0) { | |
size_t i; | |
for (i=0; i < n; i++) { | |
if (((char*) write_buffer)[i] == '\n') { | |
break; | |
} | |
} | |
// i contains the index of the next '\n' or n if there is no newline | |
// there is no newline, but lines should be skipped (skip_n_line > 0) so the next chunk should be read | |
if (i == n) { | |
break; | |
} | |
// skipped i characters and 1 for the newline | |
n -= i + 1; | |
write_buffer = &write_buffer[i + 1]; | |
// skipped one line | |
skip_n_line--; | |
} | |
// lines should be skipped or no data available anymore, go to read of the next chunk | |
if (skip_n_line > 0 || n == 0) { | |
continue; | |
} | |
if (write(STDOUT_FILENO, write_buffer, n) <= 0) { | |
perror("Error while writing to stdout"); | |
close(fd); | |
close(ifd); | |
exit(EXIT_FAILURE); | |
} | |
} | |
} | |
void sighandler(int arg) { | |
close(fd); | |
close(wfd); | |
close(ifd); | |
printf("\n"); | |
exit(EXIT_SUCCESS); | |
} | |
int main(int argc, char **argv) { | |
args_t args; | |
int arg_r = parse_args(&args, argc, argv); | |
if (arg_r == 1) { | |
return EXIT_SUCCESS; | |
} else if (arg_r != 0) { | |
return EXIT_FAILURE; | |
} | |
fd = open(args.file, O_RDONLY); | |
if (fd < 0) { | |
perror("Error while opening file"); | |
return EXIT_FAILURE; | |
} | |
int ifd = inotify_init(); | |
if (ifd < 0) { | |
perror("Error while creating inotify"); | |
close(fd); | |
return EXIT_FAILURE; | |
} | |
wfd = inotify_add_watch(ifd, args.file, IN_MODIFY); | |
if (wfd < 0) { | |
perror("Error while adding inotify watcher"); | |
close(fd); | |
close(ifd); | |
return EXIT_FAILURE; | |
} | |
if (signal(SIGINT, &sighandler) == SIG_ERR) { | |
perror("Error while registering signal"); | |
close(fd); | |
close(wfd); | |
close(ifd); | |
return EXIT_FAILURE; | |
} | |
// if skip_n_lines < 0 (-1), the 'a' flag is set to all lines should be skipped | |
if (args.skip_n_line >= 0) { | |
print_file_content(args.skip_n_line); | |
} | |
for(;;) { | |
struct inotify_event event; | |
ssize_t n = read(ifd, &event, sizeof(struct inotify_event)); | |
if (n != sizeof(struct inotify_event)) { | |
fprintf(stderr, "Error while reading inotify event\n"); | |
close(fd); | |
close(wfd); | |
close(ifd); | |
return EXIT_FAILURE; | |
} | |
// 0 because no lines should be skipped | |
print_file_content(0); | |
} | |
// end of function is unreachable | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good install script:
curl https://gist.githubusercontent.com/AdriDevelopsThings/747bd78ee29cffcc9de161d00aaaf294/raw/0c2be94e39e3be792c8cf16adcfdeb62bd9e813d/follow.c | sudo gcc -xc - -o /usr/local/bin/follow