/* * 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 */ #include #include #include #include #include #include #include #include #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 , --skip-lines , -s= and --skip-lines= 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= 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= 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 }