Commit | Line | Data |
---|---|---|
4d66492f SK |
1 | /* |
2 | ||
3 | - [ ] Switch from regular files to named pipes | |
4 | - [ ] poll with select | |
5 | ||
6 | */ | |
7 | ||
9b5ebc12 SK |
8 | #include <assert.h> |
9 | #include <ctype.h> | |
10 | #include <stdio.h> | |
11 | #include <stdlib.h> | |
12 | #include <string.h> | |
13 | ||
4d66492f SK |
14 | #include <fcntl.h> |
15 | #include <unistd.h> | |
9b5ebc12 | 16 | |
4d66492f SK |
17 | #define debug(args...) {fprintf(stderr, "[debug] " args);} |
18 | #define info( args...) {fprintf(stderr, "[info] " args);} | |
19 | #define error(args...) {fprintf(stderr, "[error] " args);} | |
20 | #define fatal(args...) {fprintf(stderr, "[fatal] " args); exit(EXIT_FAILURE);} | |
21 | #define usage(args...) {print_usage(); fatal("[usage] " args);} | |
9b5ebc12 | 22 | |
4d66492f | 23 | char *argv0; |
9b5ebc12 | 24 | |
4d66492f SK |
25 | typedef struct File File; |
26 | struct File { | |
27 | char *name; | |
28 | int fd; | |
9b5ebc12 | 29 | int width; |
4d66492f | 30 | int last_read; |
9b5ebc12 | 31 | int ttl; |
4d66492f SK |
32 | int pos; /* Position on the output buffer. */ |
33 | File *next; | |
9b5ebc12 SK |
34 | }; |
35 | ||
4d66492f SK |
36 | typedef struct Config Config; |
37 | struct Config { | |
9b5ebc12 | 38 | int interval; |
4d66492f SK |
39 | char * separator; |
40 | char * timefmt; | |
41 | File * files; | |
42 | int file_count; | |
43 | int total_width; | |
44 | } defaults = { | |
45 | .interval = 1, | |
46 | .separator = "|", | |
47 | .timefmt = "%Y-%m-%d %H:%M:%S", | |
48 | .files = NULL, | |
49 | .file_count = 0, | |
50 | .total_width = 0, | |
9b5ebc12 SK |
51 | }; |
52 | ||
9b5ebc12 SK |
53 | |
54 | int | |
55 | is_pos_num(char *s) | |
56 | { | |
57 | while (*s != '\0') | |
58 | if (!isdigit(*(s++))) | |
59 | return 0; | |
60 | return 1; | |
61 | } | |
62 | ||
63 | void | |
64 | print_usage() | |
65 | { | |
4d66492f | 66 | assert(argv0); |
9b5ebc12 SK |
67 | fprintf( |
68 | stderr, | |
69 | "\n" | |
4d66492f | 70 | "Usage: %s [OPTION ...] SPEC [SPEC ...]\n" |
9b5ebc12 | 71 | "\n" |
4d66492f SK |
72 | " SPEC = FILE_PATH DATA_WIDTH DATA_TTL\n" |
73 | " FILE_PATH = string\n" | |
74 | " DATA_WIDTH = int (* (positive) number of characters *)\n" | |
75 | " DATA_TTL = int (* (positive) number of seconds *)\n" | |
76 | " OPTION = -i INTERVAL\n" | |
77 | " | -s SEPARATOR\n" | |
78 | " SEPARATOR = string\n" | |
79 | " INTERVAL = int (* (positive) number of seconds *)\n" | |
9b5ebc12 | 80 | "\n", |
4d66492f | 81 | argv0 |
9b5ebc12 SK |
82 | ); |
83 | fprintf( | |
84 | stderr, | |
85 | "Example: %s -i 1 /dev/shm/khatus/khatus_sensor_x 4 10\n" | |
86 | "\n", | |
4d66492f | 87 | argv0 |
9b5ebc12 SK |
88 | ); |
89 | } | |
90 | ||
4d66492f | 91 | void opts_parse_any(Config *, int, char *[], int); /* For mutually-recursive calls. */ |
9b5ebc12 SK |
92 | |
93 | void | |
4d66492f | 94 | parse_opts_opt_i(Config *cfg, int argc, char *argv[], int i) |
9b5ebc12 SK |
95 | { |
96 | if (i < argc) { | |
97 | char *param = argv[i++]; | |
98 | ||
99 | if (is_pos_num(param)) { | |
4d66492f SK |
100 | cfg->interval = atoi(param); |
101 | opts_parse_any(cfg, argc, argv, i); | |
9b5ebc12 SK |
102 | } else { |
103 | usage("Option -i parameter is invalid: \"%s\"\n", param); | |
104 | } | |
105 | } else { | |
106 | usage("Option -i parameter is missing.\n"); | |
107 | } | |
108 | } | |
109 | ||
110 | void | |
4d66492f SK |
111 | parse_opts_opt_s(Config *cfg, int argc, char *argv[], int i) |
112 | { | |
113 | if (i < argc) { | |
3c836bfd | 114 | cfg->separator = calloc((strlen(argv[i]) + 1), sizeof(char)); |
4d66492f SK |
115 | strcpy(cfg->separator, argv[i]); |
116 | opts_parse_any(cfg, argc, argv, ++i); | |
117 | } else { | |
118 | usage("Option -s parameter is missing.\n"); | |
119 | } | |
120 | } | |
121 | ||
122 | void | |
123 | parse_opts_opt_t(Config *cfg, int argc, char *argv[], int i) | |
124 | { | |
125 | if (i < argc) { | |
3c836bfd | 126 | cfg->timefmt = calloc((strlen(argv[i]) + 1), sizeof(char)); |
4d66492f SK |
127 | strcpy(cfg->timefmt, argv[i]); |
128 | opts_parse_any(cfg, argc, argv, ++i); | |
129 | } else { | |
130 | usage("Option -t parameter is missing.\n"); | |
131 | } | |
132 | } | |
133 | ||
134 | void | |
135 | parse_opts_opt(Config *cfg, int argc, char *argv[], int i) | |
9b5ebc12 SK |
136 | { |
137 | switch (argv[i][1]) { | |
4d66492f SK |
138 | case 'i': parse_opts_opt_i(cfg, argc, argv, ++i); break; /* TODO: Generic set_int */ |
139 | case 's': parse_opts_opt_s(cfg, argc, argv, ++i); break; /* TODO: Generic set_str */ | |
140 | case 't': parse_opts_opt_t(cfg, argc, argv, ++i); break; /* TODO: Generic set_str */ | |
9b5ebc12 SK |
141 | default : usage("Option \"%s\" is invalid\n", argv[i]); |
142 | } | |
143 | } | |
144 | ||
145 | void | |
4d66492f | 146 | parse_opts_spec(Config *cfg, int argc, char *argv[], int i) |
9b5ebc12 SK |
147 | { |
148 | if ((i + 3) > argc) | |
149 | usage("[spec] Parameter(s) missing for file \"%s\".\n", argv[i]); | |
150 | ||
151 | char *n = argv[i++]; | |
152 | char *w = argv[i++]; | |
153 | char *t = argv[i++]; | |
154 | ||
155 | if (!is_pos_num(w)) | |
156 | usage("[spec] Invalid width: \"%s\", for file \"%s\"\n", w, n); | |
157 | if (!is_pos_num(t)) | |
158 | usage("[spec] Invalid TTL: \"%s\", for file \"%s\"\n", t, n); | |
3c836bfd | 159 | File *f = calloc(1, sizeof(struct File)); |
9b5ebc12 | 160 | if (f) { |
4d66492f SK |
161 | f->name = n; |
162 | f->fd = -1; | |
163 | f->width = atoi(w); | |
164 | f->ttl = atoi(t); | |
165 | f->last_read = 0; | |
166 | f->pos = cfg->total_width; | |
167 | f->next = cfg->files; | |
168 | ||
169 | cfg->files = f; | |
170 | cfg->total_width += f->width; | |
171 | cfg->file_count++; | |
9b5ebc12 | 172 | } else { |
4d66492f | 173 | fatal("[memory] Allocation failure."); |
9b5ebc12 | 174 | } |
4d66492f | 175 | opts_parse_any(cfg, argc, argv, i); |
9b5ebc12 SK |
176 | } |
177 | ||
178 | void | |
4d66492f | 179 | opts_parse_any(Config *cfg, int argc, char *argv[], int i) |
9b5ebc12 SK |
180 | { |
181 | if (i < argc) { | |
182 | switch (argv[i][0]) { | |
4d66492f SK |
183 | case '-': parse_opts_opt(cfg, argc, argv, i); break; |
184 | default : parse_opts_spec(cfg, argc, argv, i); | |
185 | } | |
186 | } | |
187 | } | |
188 | ||
189 | void | |
190 | opts_parse(Config *cfg, int argc, char *argv[], int i) | |
191 | { | |
192 | opts_parse_any(cfg, argc, argv, 1); | |
193 | ||
194 | File *last = cfg->files; | |
195 | cfg->files = NULL; | |
196 | for (File *f = last; f; ) { | |
197 | File *next = f->next; | |
198 | f->next = cfg->files; | |
199 | cfg->files = f; | |
200 | f = next; | |
201 | } | |
202 | } | |
203 | ||
204 | void | |
205 | read_all(Config *cfg, char *buf) | |
206 | { | |
207 | /* TODO: stat then check TTL */ | |
208 | for (File *f = cfg->files; f; f = f->next) { | |
209 | if (f->fd < 0) | |
210 | f->fd = open(f->name, O_RDONLY); | |
211 | if (f->fd == -1) { | |
212 | /* TODO: Consider backing off retries for failed files. */ | |
213 | fatal("Failed to open \"%s\"\n", f->name); | |
214 | } else { | |
215 | lseek(f->fd, 0, 0); | |
216 | ssize_t n = read(f->fd, buf + f->pos, f->width); | |
217 | int lasti = n + f->pos - 1; | |
218 | char lastc = buf[lasti]; | |
219 | if (lastc == '\n') | |
220 | buf[lasti] = ' '; | |
9b5ebc12 SK |
221 | } |
222 | } | |
223 | } | |
224 | ||
225 | int | |
4d66492f | 226 | main(int argc, char *argv[]) |
9b5ebc12 | 227 | { |
4d66492f SK |
228 | int width; |
229 | int nfiles = 0; | |
230 | int seplen; | |
231 | int prefix = 0; | |
232 | char *buf; | |
233 | Config *cfg = &defaults; | |
234 | ||
235 | argv0 = argv[0]; | |
236 | ||
237 | opts_parse(cfg, argc, argv, 1); | |
238 | debug("argv0 = %s\n", argv0); | |
239 | debug("[config] interval = %d\n", cfg->interval); | |
240 | debug("[config] separator = %s\n", cfg->separator); | |
241 | debug("[config] file_count = %d\n", cfg->file_count); | |
242 | debug("[config] total_width = %d\n", cfg->total_width); | |
243 | if (cfg->files == NULL) | |
9b5ebc12 | 244 | usage("No file specs were given!\n"); |
4d66492f | 245 | for (File *f = cfg->files; f; f = f->next) { |
9b5ebc12 | 246 | debug( |
4d66492f SK |
247 | "[config] file = " |
248 | "{" | |
249 | " name = %s," | |
250 | " pos = %d," | |
251 | " width = %d," | |
252 | " ttl = %d," | |
253 | " last_read = %d," | |
254 | " }\n", | |
9b5ebc12 | 255 | f->name, |
4d66492f | 256 | f->pos, |
9b5ebc12 | 257 | f->width, |
4d66492f SK |
258 | f->ttl, |
259 | f->last_read | |
9b5ebc12 SK |
260 | ); |
261 | } | |
4d66492f SK |
262 | |
263 | width = cfg->total_width; | |
264 | seplen = strlen(cfg->separator); | |
265 | ||
266 | /* 1st pass to make space for separators */ | |
267 | for (File *f = cfg->files; f; f = f->next) { | |
268 | f->pos += prefix; | |
269 | prefix += seplen; | |
270 | nfiles++; | |
271 | } | |
272 | width += (seplen * (nfiles - 1)); | |
3c836bfd | 273 | buf = calloc(1, width + 1); |
4d66492f SK |
274 | if (buf == NULL) |
275 | fatal("[memory] Failed to allocate buffer of %d bytes", width); | |
276 | memset(buf, ' ', width); | |
277 | buf[width] = '\0'; | |
278 | /* 2nd pass to set the separators */ | |
279 | for (File *f = cfg->files; f; f = f->next) { | |
280 | if (f->pos) { /* Skip the first, left-most */ | |
281 | strcpy(buf + (f->pos - seplen), cfg->separator); | |
282 | } | |
283 | } | |
284 | ||
285 | /* TODO: nanosleep and nano time diff */ | |
286 | for (;;) { | |
287 | read_all(cfg, buf); | |
288 | printf("%s\n", buf); | |
289 | sleep(cfg->interval); | |
290 | } | |
9b5ebc12 | 291 | } |