Last active 1719877040

This program will print the content of a file and then waits for a change of the file and prints the added content.

follow.c Raw
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
17int fd;
18int ifd;
19int wfd;
20
21typedef struct {
22 char* file;
23 int skip_n_line; // -1 if all lines should be skipped
24} args_t;
25
26void 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 */
37int 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 */
111void 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
170void sighandler(int arg) {
171 close(fd);
172 close(wfd);
173 close(ifd);
174 printf("\n");
175 exit(EXIT_SUCCESS);
176}
177
178int 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}