Skip to content

Instantly share code, notes, and snippets.

@AdriDevelopsThings
Last active June 10, 2024 19:46
Show Gist options
  • Save AdriDevelopsThings/747bd78ee29cffcc9de161d00aaaf294 to your computer and use it in GitHub Desktop.
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 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
}
@AdriDevelopsThings
Copy link
Author

Good install script: curl https://gist.githubusercontent.com/AdriDevelopsThings/747bd78ee29cffcc9de161d00aaaf294/raw/0c2be94e39e3be792c8cf16adcfdeb62bd9e813d/follow.c | sudo gcc -xc - -o /usr/local/bin/follow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment