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