follow.c
· 8.6 KiB · C
Raw
/*
* 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 | } |