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