follow.c
· 8.6 KiB · C
Eredeti
/*
* 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
}
| 1 | /* |
| 2 | * This program will print the content of a file and then waits for a change of the file and prints the added content. |
| 3 | * Author: AdriDoesThings <adri@adridoesthings.com> |
| 4 | */ |
| 5 | |
| 6 | #include <stdint.h> |
| 7 | #include <stdio.h> |
| 8 | #include <stdlib.h> |
| 9 | #include <string.h> |
| 10 | #include <unistd.h> |
| 11 | #include <fcntl.h> |
| 12 | #include <sys/inotify.h> |
| 13 | #include <signal.h> |
| 14 | |
| 15 | #define BUFFER_SIZE 255 |
| 16 | |
| 17 | int fd; |
| 18 | int ifd; |
| 19 | int wfd; |
| 20 | |
| 21 | typedef struct { |
| 22 | char* file; |
| 23 | int skip_n_line; // -1 if all lines should be skipped |
| 24 | } args_t; |
| 25 | |
| 26 | void print_usage(FILE* file, char* exec) { |
| 27 | 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); |
| 28 | } |
| 29 | |
| 30 | /* |
| 31 | * Parse arguments (argc long string list in argv) to args |
| 32 | * Returns: |
| 33 | * - 1: success but the program should exit with EXIT_SUCCESS right now |
| 34 | * - 0: success, args contains the arguments |
| 35 | * - -1: error, program should exit with EXIT_FAILURE now |
| 36 | */ |
| 37 | int parse_args(args_t *args, int argc, char **argv) { |
| 38 | args->file = NULL; |
| 39 | args->skip_n_line = 0; |
| 40 | |
| 41 | for (size_t i=1; i < argc; i++) { // start at i=1, first argument is executable |
| 42 | if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { |
| 43 | print_usage(stdout, argv[0]); |
| 44 | return 1; |
| 45 | } else { |
| 46 | // -s <N>, --skip-lines <N>, -s=<N> and --skip-lines=<N> syntax is allowed |
| 47 | // if N is 'a' then args->skip_n_lines should be -1 |
| 48 | int s_cmp = strncmp(argv[i], "-s", 2); |
| 49 | int sl_cmp = strncmp(argv[i], "--skip-lines", 12); |
| 50 | if (s_cmp == 0 && strlen(argv[i]) > 3) { // -s=<N> syntax |
| 51 | if (argv[i][2] != '=') { |
| 52 | print_usage(stderr, argv[0]); |
| 53 | return -1; |
| 54 | } |
| 55 | if (argv[i][3] == 'a') { |
| 56 | args->skip_n_line = -1; |
| 57 | continue; |
| 58 | } |
| 59 | args->skip_n_line = atoi(&argv[i][3]); |
| 60 | if (args->skip_n_line == 0) { |
| 61 | print_usage(stderr, argv[0]); |
| 62 | return -1; |
| 63 | } |
| 64 | } else if (sl_cmp == 0 && strlen(argv[i]) > 13) { // --skip-lines=<N> syntax |
| 65 | if (argv[i][12] != '=') { |
| 66 | print_usage(stderr, argv[0]); |
| 67 | return -1; |
| 68 | } |
| 69 | if (argv[i][13] == 'a') { |
| 70 | args->skip_n_line = -1; |
| 71 | continue; |
| 72 | } |
| 73 | args->skip_n_line = atoi(&argv[i][13]); |
| 74 | if (args->skip_n_line == 0) { |
| 75 | print_usage(stderr, argv[0]); |
| 76 | return -1; |
| 77 | } |
| 78 | } else if (s_cmp == 0 || sl_cmp == 0) { // not '=' syntax |
| 79 | i++; // go to next argument |
| 80 | if (i == argc) { // there isn't a next argument |
| 81 | print_usage(stderr, argv[0]); |
| 82 | return -1; |
| 83 | } |
| 84 | if (argv[i][0] == 'a') { |
| 85 | args->skip_n_line = -1; |
| 86 | continue; |
| 87 | } |
| 88 | args->skip_n_line = atoi(argv[i]); |
| 89 | if (args->skip_n_line == 0) { |
| 90 | print_usage(stderr, argv[0]); |
| 91 | return -1; |
| 92 | } |
| 93 | } else { |
| 94 | args->file = argv[i]; |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | // file is a required argument |
| 100 | if (args->file == NULL) { |
| 101 | print_usage(stderr, argv[0]); |
| 102 | return -1; |
| 103 | } |
| 104 | |
| 105 | return 0; |
| 106 | } |
| 107 | |
| 108 | /* |
| 109 | * print the content of the file of fd, but skip the first skip_n_line lines |
| 110 | */ |
| 111 | void print_file_content(int skip_n_line) { |
| 112 | while (1) { // read in 255 byte chuns |
| 113 | u_int8_t buffer[sizeof(char) * BUFFER_SIZE]; |
| 114 | ssize_t n = read(fd, buffer, sizeof(char) * BUFFER_SIZE); |
| 115 | |
| 116 | if (n == 0) { // there is nothing to read anymore |
| 117 | break; |
| 118 | } |
| 119 | |
| 120 | if (n < 0) { |
| 121 | perror("Error while reading file"); |
| 122 | close(fd); |
| 123 | close(ifd); |
| 124 | exit(EXIT_FAILURE); |
| 125 | } |
| 126 | |
| 127 | // pointer should be mutable |
| 128 | uint8_t *write_buffer = buffer; |
| 129 | |
| 130 | // there are lines to skip and data to read (n > 0) |
| 131 | while (skip_n_line > 0 && n > 0) { |
| 132 | size_t i; |
| 133 | for (i=0; i < n; i++) { |
| 134 | if (((char*) write_buffer)[i] == '\n') { |
| 135 | break; |
| 136 | } |
| 137 | } |
| 138 | // i contains the index of the next '\n' or n if there is no newline |
| 139 | |
| 140 | // there is no newline, but lines should be skipped (skip_n_line > 0) so the next chunk should be read |
| 141 | if (i == n) { |
| 142 | break; |
| 143 | } |
| 144 | |
| 145 | // skipped i characters and 1 for the newline |
| 146 | n -= i + 1; |
| 147 | write_buffer = &write_buffer[i + 1]; |
| 148 | |
| 149 | // skipped one line |
| 150 | skip_n_line--; |
| 151 | |
| 152 | } |
| 153 | |
| 154 | // lines should be skipped or no data available anymore, go to read of the next chunk |
| 155 | if (skip_n_line > 0 || n == 0) { |
| 156 | continue; |
| 157 | } |
| 158 | |
| 159 | |
| 160 | if (write(STDOUT_FILENO, write_buffer, n) <= 0) { |
| 161 | perror("Error while writing to stdout"); |
| 162 | close(fd); |
| 163 | close(ifd); |
| 164 | exit(EXIT_FAILURE); |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | |
| 170 | void sighandler(int arg) { |
| 171 | close(fd); |
| 172 | close(wfd); |
| 173 | close(ifd); |
| 174 | printf("\n"); |
| 175 | exit(EXIT_SUCCESS); |
| 176 | } |
| 177 | |
| 178 | int main(int argc, char **argv) { |
| 179 | args_t args; |
| 180 | int arg_r = parse_args(&args, argc, argv); |
| 181 | if (arg_r == 1) { |
| 182 | return EXIT_SUCCESS; |
| 183 | } else if (arg_r != 0) { |
| 184 | return EXIT_FAILURE; |
| 185 | } |
| 186 | |
| 187 | fd = open(args.file, O_RDONLY); |
| 188 | if (fd < 0) { |
| 189 | perror("Error while opening file"); |
| 190 | return EXIT_FAILURE; |
| 191 | } |
| 192 | |
| 193 | int ifd = inotify_init(); |
| 194 | if (ifd < 0) { |
| 195 | perror("Error while creating inotify"); |
| 196 | close(fd); |
| 197 | return EXIT_FAILURE; |
| 198 | } |
| 199 | |
| 200 | wfd = inotify_add_watch(ifd, args.file, IN_MODIFY); |
| 201 | if (wfd < 0) { |
| 202 | perror("Error while adding inotify watcher"); |
| 203 | close(fd); |
| 204 | close(ifd); |
| 205 | return EXIT_FAILURE; |
| 206 | } |
| 207 | |
| 208 | if (signal(SIGINT, &sighandler) == SIG_ERR) { |
| 209 | perror("Error while registering signal"); |
| 210 | close(fd); |
| 211 | close(wfd); |
| 212 | close(ifd); |
| 213 | return EXIT_FAILURE; |
| 214 | } |
| 215 | |
| 216 | // if skip_n_lines < 0 (-1), the 'a' flag is set to all lines should be skipped |
| 217 | if (args.skip_n_line >= 0) { |
| 218 | print_file_content(args.skip_n_line); |
| 219 | } |
| 220 | |
| 221 | |
| 222 | for(;;) { |
| 223 | struct inotify_event event; |
| 224 | ssize_t n = read(ifd, &event, sizeof(struct inotify_event)); |
| 225 | if (n != sizeof(struct inotify_event)) { |
| 226 | fprintf(stderr, "Error while reading inotify event\n"); |
| 227 | close(fd); |
| 228 | close(wfd); |
| 229 | close(ifd); |
| 230 | return EXIT_FAILURE; |
| 231 | } |
| 232 | |
| 233 | // 0 because no lines should be skipped |
| 234 | print_file_content(0); |
| 235 | } |
| 236 | |
| 237 | // end of function is unreachable |
| 238 | |
| 239 | } |