Refactor main
[khatus.git] / x5 / khatus.c
CommitLineData
77c76070 1#include <sys/select.h>
bec93767 2#include <sys/stat.h>
4d66492f 3
9b5ebc12
SK
4#include <assert.h>
5#include <ctype.h>
77c76070 6#include <errno.h>
c5d1af8c 7#include <fcntl.h>
9b5ebc12
SK
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
b7487ec5 11#include <time.h>
c5d1af8c
SK
12#include <unistd.h>
13
14#include <X11/Xlib.h>
9b5ebc12 15
b7487ec5 16#include "bsdtimespec.h"
1084633a
SK
17#include "khlib_log.h"
18#include "khlib_time.h"
b7487ec5 19
544b0835
SK
20#define usage(...) { \
21 print_usage(); \
22 fprintf(stderr, "Error:\n " __VA_ARGS__); \
23 exit(EXIT_FAILURE); \
24}
e6c523cd
SK
25#define ERRMSG "ERROR"
26
ca7a3d6c 27
e6c523cd
SK
28static const char errmsg[] = ERRMSG;
29static const int errlen = sizeof(ERRMSG) - 1;
30
4d66492f 31char *argv0;
9b5ebc12 32
900b9a6b
SK
33/* TODO: Convert slot list to slot array. */
34typedef struct Slot Slot;
35struct Slot {
36 char *in_fifo;
37 int in_fd;
38 struct timespec in_last_read;
39 struct timespec out_ttl;
40 int out_width;
41 int out_pos_lo; /* Lowest position on the output buffer. */
42 int out_pos_cur; /* Current position on the output buffer. */
43 int out_pos_hi; /* Highest position on the output buffer. */
44 Slot *next;
9b5ebc12
SK
45};
46
4d66492f
SK
47typedef struct Config Config;
48struct Config {
348d5f36 49 double interval;
4d66492f 50 char * separator;
900b9a6b
SK
51 Slot * slots;
52 int slot_count;
457f091d
SK
53 int buf_width;
54 int to_x_root;
9b5ebc12
SK
55};
56
efa97b71 57enum read_status {
e6441710
SK
58 END_OF_FILE,
59 END_OF_MESSAGE,
60 RETRY,
61 FAILURE
efa97b71
SK
62};
63
457f091d
SK
64char *
65buf_create(Config *cfg)
66{
67 int seplen;
68 char *buf;
69 Slot *s;
70
71 buf = calloc(1, cfg->buf_width + 1);
72 if (buf == NULL)
73 khlib_fatal(
74 "[memory] Failed to allocate buffer of %d bytes",
75 cfg->buf_width
76 );
77 memset(buf, ' ', cfg->buf_width);
78 buf[cfg->buf_width] = '\0';
79 seplen = strlen(cfg->separator);
80 /* Set the separators */
81 for (s = cfg->slots; s; s = s->next) {
82 /* Skip the first, left-most */
83 if (s->out_pos_lo) {
84 /* Copying only seplen ensures we omit the '\0' byte. */
85 strncpy(
86 buf + (s->out_pos_lo - seplen),
87 cfg->separator,
88 seplen
89 );
90 }
91 }
92 return buf;
93}
94
ca7a3d6c
SK
95Slot *
96slots_rev(Slot *old)
97{
98 Slot *tmp = NULL;
99 Slot *new = NULL;
100
101 while (old) {
102 tmp = old->next;
103 old->next = new;
104 new = old;
105 old = tmp;
106 }
107 return new;
108}
109
77c76070 110void
457f091d 111slot_log(Slot *s)
77c76070 112{
900b9a6b 113 khlib_info("Slot "
a6b13fa2 114 "{"
900b9a6b
SK
115 " in_fifo = %s,"
116 " in_fd = %d,"
117 " out_width = %d,"
118 " in_last_read = {tv_sec = %ld, tv_nsec = %ld}"
119 " out_ttl = {tv_sec = %ld, tv_nsec = %ld},"
120 " out_pos_lo = %d,"
121 " out_pos_cur = %d,"
122 " out_pos_hi = %d,"
a6b13fa2
SK
123 " next = %p,"
124 " }\n",
900b9a6b
SK
125 s->in_fifo,
126 s->in_fd,
127 s->out_width,
128 s->in_last_read.tv_sec,
129 s->in_last_read.tv_nsec,
130 s->out_ttl.tv_sec,
131 s->out_ttl.tv_nsec,
132 s->out_pos_lo,
133 s->out_pos_cur,
134 s->out_pos_hi,
135 s->next
77c76070
SK
136 );
137}
138
139void
457f091d 140slots_log(Slot *head)
77c76070 141{
900b9a6b 142 for (Slot *s = head; s; s = s->next) {
457f091d 143 slot_log(s);
77c76070
SK
144 }
145}
146
457f091d
SK
147void
148slots_assert_fifos_exist(Slot *s)
149{
150 struct stat st;
151 int errors = 0;
152
153 for (; s; s = s->next) {
154 if (lstat(s->in_fifo, &st) < 0) {
155 khlib_error(
156 "Cannot stat \"%s\". Error: %s\n",
157 s->in_fifo,
158 strerror(errno)
159 );
160 errors++;
161 continue;
162 }
163 if (!(st.st_mode & S_IFIFO)) {
164 khlib_error("\"%s\" is not a FIFO\n", s->in_fifo);
165 errors++;
166 continue;
167 }
168 }
169 if (errors)
170 khlib_fatal(
171 "Encountered errors with given file paths. See log.\n"
172 );
173}
174
4bfac488
SK
175void
176slot_expire(Slot *s, struct timespec t, char *buf)
177{
178 struct timespec td;
179
180 timespecsub(&t, &(s->in_last_read), &td);
181 if (timespeccmp(&td, &(s->out_ttl), >=)) {
182 /* TODO: Maybe configurable expiry character. */
4e3d71e8 183 memset(buf + s->out_pos_lo, '_', s->out_width);
4bfac488
SK
184 khlib_warn("Slot expired: \"%s\"\n", s->in_fifo);
185 }
186}
187
188void
4e3d71e8 189slot_set_error(Slot *s, char *buf)
4bfac488
SK
190{
191 char *b;
192 int i;
193
4e3d71e8 194 s->in_fd = -1;
4bfac488
SK
195 b = buf + s->out_pos_lo;
196 /* Copy as much of the error message as possible.
197 * EXCLUDING the terminating \0. */
198 for (i = 0; i < errlen && i < s->out_width; i++)
199 b[i] = errmsg[i];
200 /* Any remaining positions: */
4e3d71e8 201 memset(b + i, '_', s->out_width - i);
4bfac488
SK
202}
203
204enum read_status
51e63a6f 205slot_read(Slot *s, char *buf)
4bfac488
SK
206{
207 char c; /* Character read. */
208 int r; /* Remaining unused positions in buffer range. */
209
210 for (;;) {
211 switch (read(s->in_fd, &c, 1)) {
212 case -1:
213 khlib_error(
214 "Failed to read: \"%s\". errno: %d, msg: %s\n",
215 s->in_fifo,
216 errno,
217 strerror(errno)
218 );
219 switch (errno) {
220 case EINTR:
221 case EAGAIN:
222 return RETRY;
223 default:
224 return FAILURE;
225 }
226 case 0:
227 khlib_debug("%s: End of FILE\n", s->in_fifo);
228 s->out_pos_cur = s->out_pos_lo;
229 return END_OF_FILE;
230 case 1:
231 /* TODO: Consider making msg term char a CLI option */
232 if (c == '\n' || c == '\0') {
4e3d71e8 233 r = (s->out_pos_hi - s->out_pos_cur) + 1;
4bfac488
SK
234 if (r > 0)
235 memset(buf + s->out_pos_cur, ' ', r);
4bfac488
SK
236 return END_OF_MESSAGE;
237 } else {
238 if (s->out_pos_cur <= s->out_pos_hi)
239 buf[s->out_pos_cur++] = c;
51e63a6f
SK
240 else
241 /*
242 * Force EOM beyond available range.
243 * To ensure that a rogue large message
244 * doesn't trap us here needlessly
245 * long.
246 */
247 return END_OF_MESSAGE;
4bfac488
SK
248 }
249 break;
250 default:
251 assert(0);
252 }
253 }
254}
255
256void
257slots_read(Config *cfg, struct timespec *ti, char *buf)
258{
259 fd_set fds;
260 int maxfd = -1;
261 int ready = 0;
262 struct stat st;
263 struct timespec t;
264 Slot *s;
265
266 FD_ZERO(&fds);
267 for (s = cfg->slots; s; s = s->next) {
268 /* TODO: Create the FIFO if it doesn't already exist. */
269 if (lstat(s->in_fifo, &st) < 0) {
270 khlib_error(
271 "Cannot stat \"%s\". Error: %s\n",
272 s->in_fifo,
273 strerror(errno)
274 );
4e3d71e8 275 slot_set_error(s, buf);
4bfac488
SK
276 continue;
277 }
278 if (!(st.st_mode & S_IFIFO)) {
279 khlib_error("\"%s\" is not a FIFO\n", s->in_fifo);
4e3d71e8 280 slot_set_error(s, buf);
4bfac488
SK
281 continue;
282 }
283 if (s->in_fd < 0) {
284 khlib_debug(
285 "%s: closed. opening. in_fd: %d\n",
286 s->in_fifo,
287 s->in_fd
288 );
289 s->in_fd = open(s->in_fifo, O_RDONLY | O_NONBLOCK);
290 } else {
291 khlib_debug(
292 "%s: already openned. in_fd: %d\n",
293 s->in_fifo,
294 s->in_fd
295 );
296 }
297 if (s->in_fd == -1) {
298 /* TODO Consider backing off retries for failed slots */
299 khlib_error("Failed to open \"%s\"\n", s->in_fifo);
4e3d71e8 300 slot_set_error(s, buf);
4bfac488
SK
301 continue;
302 }
303 khlib_debug("%s: open. in_fd: %d\n", s->in_fifo, s->in_fd);
304 if (s->in_fd > maxfd)
305 maxfd = s->in_fd;
306 FD_SET(s->in_fd, &fds);
307 }
308 khlib_debug("selecting...\n");
309 ready = pselect(maxfd + 1, &fds, NULL, NULL, ti, NULL);
310 khlib_debug("ready: %d\n", ready);
311 clock_gettime(CLOCK_MONOTONIC, &t);
312 if (ready == -1) {
313 switch (errno) {
314 case EINTR:
315 khlib_error(
316 "pselect temp failure: %d, errno: %d, msg: %s\n",
317 ready,
318 errno,
319 strerror(errno)
320 );
321 /* TODO: Reconsider what to do here. */
322 return;
323 default:
324 khlib_fatal(
325 "pselect failed: %d, errno: %d, msg: %s\n",
326 ready,
327 errno,
328 strerror(errno)
329 );
330 }
331 }
332 /* At-least-once ensures that expiries are still checked on timeouts. */
333 do {
334 for (s = cfg->slots; s; s = s->next) {
4e3d71e8
SK
335 if (s->in_fd < 0)
336 continue;
4bfac488
SK
337 if (FD_ISSET(s->in_fd, &fds)) {
338 khlib_debug("reading: %s\n", s->in_fifo);
51e63a6f 339 switch (slot_read(s, buf)) {
4bfac488
SK
340 /*
341 * ### MESSAGE LOSS ###
342 * is introduced by closing at EOM in addition
343 * to EOF, since there may be unread messages
344 * remaining in the pipe. However,
345 *
346 * ### INTER-MESSAGE PUSHBACK ###
347 * is also gained, since pipes block at the
348 * "open" call.
349 *
350 * This is an acceptable trade-off because we
351 * are a stateless reporter of a _most-recent_
352 * status, not a stateful accumulator.
c21cacfe
SK
353 *
354 * ### LOSSLESS ALTERNATIVES ###
355 * - Read each pipe until EOF before reading
356 * another.
357 * PROBLEM: a fast writer can trap us in the
358 * read loop.
359 *
360 * - Read each pipe until EOM, but close only
361 * at EOF.
362 * PROBLEM: a fast writer can fill the pipe
363 * faster than we can read it and we end-up
364 * displaying stale data.
365 *
4bfac488
SK
366 */
367 case END_OF_MESSAGE:
368 case END_OF_FILE:
369 case FAILURE:
370 close(s->in_fd);
51e63a6f
SK
371 s->in_fd = -1;
372 s->in_last_read = t;
373 s->out_pos_cur = s->out_pos_lo;
4bfac488
SK
374 ready--;
375 break;
376 case RETRY:
377 break;
378 default:
379 assert(0);
380 }
381 } else {
382 slot_expire(s, t, buf);
383 }
384 }
385 } while (ready);
386 assert(ready == 0);
387}
388
77c76070 389void
457f091d 390config_log(Config *cfg)
77c76070 391{
1084633a 392 khlib_info(
a6b13fa2
SK
393 "Config "
394 "{"
348d5f36 395 " interval = %f,"
a6b13fa2 396 " separator = %s,"
900b9a6b 397 " slot_count = %d,"
457f091d 398 " buf_width = %d,"
900b9a6b 399 " slots = ..."
a6b13fa2
SK
400 " }\n",
401 cfg->interval,
402 cfg->separator,
900b9a6b 403 cfg->slot_count,
457f091d 404 cfg->buf_width
77c76070 405 );
457f091d
SK
406 slots_log(cfg->slots);
407}
408
409void
410config_stretch_for_separators(Config *cfg)
411{
412 int seplen = strlen(cfg->separator);
413 int prefix = 0;
414 int nslots = 0;
415 Slot *s = cfg->slots;
416
417 while (s) {
418 s->out_pos_lo += prefix;
419 s->out_pos_hi += prefix;
420 s->out_pos_cur = s->out_pos_lo;
421 prefix += seplen;
422 nslots++;
423 s = s->next;
424 }
425 cfg->buf_width += (seplen * (nslots - 1));
77c76070 426}
9b5ebc12
SK
427
428int
900b9a6b 429is_pos_num(char *str)
9b5ebc12 430{
900b9a6b
SK
431 while (*str != '\0')
432 if (!isdigit(*(str++)))
9b5ebc12
SK
433 return 0;
434 return 1;
435}
436
348d5f36 437int
900b9a6b 438is_decimal(char *str)
348d5f36
SK
439{
440 char c;
441 int seen = 0;
442
900b9a6b 443 while ((c = *(str++)) != '\0')
348d5f36
SK
444 if (!isdigit(c)) {
445 if (c == '.' && !seen++)
446 continue;
447 else
448 return 0;
449 }
450 return 1;
451}
452
9b5ebc12
SK
453void
454print_usage()
455{
4d66492f 456 assert(argv0);
9b5ebc12 457 fprintf(
a6b13fa2
SK
458 stderr,
459 "\n"
460 "Usage: %s [OPTION ...] SPEC [SPEC ...]\n"
461 "\n"
462 " SPEC = FILE_PATH DATA_WIDTH DATA_TTL\n"
463 " FILE_PATH = string\n"
464 " DATA_WIDTH = int (* (positive) number of characters *)\n"
0a01172a 465 " DATA_TTL = float (* (positive) number of seconds *)\n"
a6b13fa2
SK
466 " OPTION = -i INTERVAL\n"
467 " | -s SEPARATOR\n"
468 " | -x (* Output to X root window *)\n"
469 " | -l LOG_LEVEL\n"
470 " SEPARATOR = string\n"
0a01172a 471 " INTERVAL = float (* (positive) number of seconds *)\n"
a6b13fa2
SK
472 " LOG_LEVEL = int (* %d through %d *)\n"
473 "\n",
474 argv0,
475 Nothing,
476 Debug
9b5ebc12
SK
477 );
478 fprintf(
a6b13fa2
SK
479 stderr,
480 "Example: %s -i 1 /dev/shm/khatus/khatus_sensor_x 4 10\n"
481 "\n",
482 argv0
9b5ebc12
SK
483 );
484}
485
544b0835
SK
486/* For mutually-recursive calls. */
487void opts_parse_any(Config *, int, char *[], int);
9b5ebc12
SK
488
489void
4d66492f 490parse_opts_opt_i(Config *cfg, int argc, char *argv[], int i)
9b5ebc12 491{
4c438cef
SK
492 char *param;
493
494 if (i >= argc)
9b5ebc12 495 usage("Option -i parameter is missing.\n");
4c438cef 496 param = argv[i++];
348d5f36 497 if (!is_decimal(param))
4c438cef 498 usage("Option -i parameter is invalid: \"%s\"\n", param);
348d5f36 499 cfg->interval = atof(param);
4c438cef 500 opts_parse_any(cfg, argc, argv, i);
9b5ebc12
SK
501}
502
503void
4d66492f
SK
504parse_opts_opt_s(Config *cfg, int argc, char *argv[], int i)
505{
4c438cef 506 if (i >= argc)
4d66492f 507 usage("Option -s parameter is missing.\n");
4c438cef
SK
508 cfg->separator = calloc((strlen(argv[i]) + 1), sizeof(char));
509 strcpy(cfg->separator, argv[i]);
510 opts_parse_any(cfg, argc, argv, ++i);
4d66492f
SK
511}
512
b6316e94
SK
513void
514parse_opts_opt_l(Config *cfg, int argc, char *argv[], int i)
515{
4c438cef 516 char *param;
b6316e94
SK
517 int log_level;
518
4c438cef 519 if (i >= argc)
b6316e94 520 usage("Option -l parameter is missing.\n");
4c438cef
SK
521 param = argv[i++];
522 if (!is_pos_num(param))
523 usage("Option -l parameter is invalid: \"%s\"\n", param);
524 log_level = atoi(param);
525 if (log_level > Debug)
544b0835
SK
526 usage(
527 "Option -l value (%d) exceeds maximum (%d)\n",
528 log_level,
529 Debug
530 );
1084633a 531 _khlib_log_level = log_level;
4c438cef 532 opts_parse_any(cfg, argc, argv, i);
b6316e94
SK
533}
534
4d66492f
SK
535void
536parse_opts_opt(Config *cfg, int argc, char *argv[], int i)
9b5ebc12
SK
537{
538 switch (argv[i][1]) {
ce552549
SK
539 case 'i':
540 /* TODO: Generic set_int */
541 parse_opts_opt_i(cfg, argc, argv, ++i);
542 break;
543 case 's':
544 /* TODO: Generic set_str */
545 parse_opts_opt_s(cfg, argc, argv, ++i);
546 break;
547 case 'x':
457f091d 548 cfg->to_x_root = 1;
ce552549
SK
549 opts_parse_any(cfg, argc, argv, ++i);
550 break;
551 case 'l':
552 /* TODO: Generic set_int */
553 parse_opts_opt_l(cfg, argc, argv, ++i);
554 break;
555 default :
556 usage("Option \"%s\" is invalid\n", argv[i]);
9b5ebc12
SK
557 }
558}
559
560void
4d66492f 561parse_opts_spec(Config *cfg, int argc, char *argv[], int i)
9b5ebc12 562{
ca7a3d6c
SK
563 char *n;
564 char *w;
565 char *t;
566 struct timespec in_last_read;
567 Slot *s;
568
9b5ebc12 569 if ((i + 3) > argc)
544b0835
SK
570 usage(
571 "[spec] Parameter(s) missing for fifo \"%s\".\n",
572 argv[i]
573 );
9b5ebc12 574
ca7a3d6c
SK
575 n = argv[i++];
576 w = argv[i++];
577 t = argv[i++];
0a01172a 578
9b5ebc12 579 if (!is_pos_num(w))
1872c5c1 580 usage("[spec] Invalid width: \"%s\", for fifo \"%s\"\n", w, n);
0a01172a 581 if (!is_decimal(t))
1872c5c1 582 usage("[spec] Invalid TTL: \"%s\", for fifo \"%s\"\n", t, n);
ca7a3d6c 583
900b9a6b
SK
584 in_last_read.tv_sec = 0;
585 in_last_read.tv_nsec = 0;
ca7a3d6c
SK
586 s = calloc(1, sizeof(struct Slot));
587
900b9a6b
SK
588 if (s) {
589 s->in_fifo = n;
590 s->in_fd = -1;
ca7a3d6c
SK
591 s->out_width = atoi(w);
592 s->out_ttl = khlib_timespec_of_float(atof(t));
900b9a6b 593 s->in_last_read = in_last_read;
457f091d 594 s->out_pos_lo = cfg->buf_width;
900b9a6b 595 s->out_pos_cur = s->out_pos_lo;
ca7a3d6c
SK
596 s->out_pos_hi = s->out_pos_lo + s->out_width - 1;
597 s->next = cfg->slots;
900b9a6b
SK
598
599 cfg->slots = s;
457f091d 600 cfg->buf_width += s->out_width;
900b9a6b 601 cfg->slot_count++;
9b5ebc12 602 } else {
1084633a 603 khlib_fatal("[memory] Allocation failure.");
9b5ebc12 604 }
4d66492f 605 opts_parse_any(cfg, argc, argv, i);
9b5ebc12
SK
606}
607
608void
4d66492f 609opts_parse_any(Config *cfg, int argc, char *argv[], int i)
9b5ebc12
SK
610{
611 if (i < argc) {
612 switch (argv[i][0]) {
ce552549
SK
613 case '-':
614 parse_opts_opt(cfg, argc, argv, i);
615 break;
616 default :
617 parse_opts_spec(cfg, argc, argv, i);
4d66492f
SK
618 }
619 }
620}
621
622void
5400b86f 623opts_parse(Config *cfg, int argc, char *argv[])
4d66492f
SK
624{
625 opts_parse_any(cfg, argc, argv, 1);
ca7a3d6c 626 cfg->slots = slots_rev(cfg->slots);
457f091d
SK
627 config_log(cfg);
628 if (cfg->slots == NULL)
629 usage("No slot specs were given!\n");
4d66492f
SK
630}
631
457f091d
SK
632void
633loop(Config *cfg, char *buf, Display *d)
9b5ebc12 634{
b7487ec5 635 struct timespec
900b9a6b
SK
636 t0, /* time stamp. before reading slots */
637 t1, /* time stamp. after reading slots */
b7487ec5
SK
638 ti, /* time interval desired (t1 - t0) */
639 td, /* time interval measured (t1 - t0) */
640 tc; /* time interval correction (ti - td) when td < ti */
4d66492f 641
457f091d 642 ti = khlib_timespec_of_float(cfg->interval);
4d66492f 643 for (;;) {
b7487ec5 644 clock_gettime(CLOCK_MONOTONIC, &t0); // FIXME: check errors
457f091d
SK
645 slots_read(cfg, &ti, buf);
646 if (cfg->to_x_root) {
544b0835 647 if (XStoreName(d, DefaultRootWindow(d), buf) < 0)
1084633a 648 khlib_fatal("XStoreName failed.\n");
544b0835 649 XFlush(d);
fabb8771
SK
650 } else {
651 puts(buf);
652 fflush(stdout);
653 }
b7487ec5
SK
654 clock_gettime(CLOCK_MONOTONIC, &t1); // FIXME: check errors
655 timespecsub(&t1, &t0, &td);
1084633a 656 khlib_debug(
544b0835
SK
657 "td {tv_sec = %ld, tv_nsec = %ld}\n",
658 td.tv_sec,
659 td.tv_nsec
660 );
a415999c 661 if (timespeccmp(&td, &ti, <)) {
ca7a3d6c
SK
662 /*
663 * Pushback on data producers by refusing to read the
b7487ec5
SK
664 * pipe more frequently than the interval.
665 */
666 timespecsub(&ti, &td, &tc);
1084633a 667 khlib_sleep(&tc);
574a4bff 668 }
4d66492f 669 }
457f091d
SK
670}
671
672int
673main(int argc, char *argv[])
674{
675 Config cfg = {
676 .interval = 1.0,
677 .separator = "|",
678 .slots = NULL,
679 .slot_count = 0,
680 .buf_width = 0,
681 .to_x_root = 0,
682 };
683 char *buf;
684 Display *d = NULL;
685
686 /* TODO: Handle signals */
687
688 argv0 = argv[0];
689 opts_parse(&cfg, argc, argv);
690 slots_assert_fifos_exist(cfg.slots);
691 config_stretch_for_separators(&cfg);
692 buf = buf_create(&cfg);
693 if (cfg.to_x_root && !(d = XOpenDisplay(NULL)))
694 khlib_fatal("XOpenDisplay failed with: %p\n", d);
695 loop(&cfg, buf, d);
3d7e82a8 696 return EXIT_SUCCESS;
9b5ebc12 697}
This page took 0.147785 seconds and 4 git commands to generate.