X-Git-Url: https://git.xandkar.net/?p=khatus.git;a=blobdiff_plain;f=x5%2Fkhatus.c;h=5407acde0b145131f038515d773e0a8d17bf2526;hp=ce7db950a1355212c614d9554f1975f1e5fb2e24;hb=HEAD;hpb=eb6dbe7a1ae8ed5ff871a4a98a4e95793155d5e0 diff --git a/x5/khatus.c b/x5/khatus.c index ce7db95..5407acd 100644 --- a/x5/khatus.c +++ b/x5/khatus.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -14,46 +15,46 @@ #include #include "bsdtimespec.h" -#include "khatus_lib_log.h" -#include "khatus_lib_time.h" +#include "khlib_log.h" +#include "khlib_time.h" -#define usage(...) {print_usage(); fprintf(stderr, "Error:\n " __VA_ARGS__); exit(EXIT_FAILURE);} +#define usage(...) { \ + print_usage(); \ + fprintf(stderr, "Error:\n " __VA_ARGS__); \ + exit(EXIT_FAILURE); \ +} #define ERRMSG "ERROR" + static const char errmsg[] = ERRMSG; static const int errlen = sizeof(ERRMSG) - 1; -char *argv0; - -/* TODO: Convert fifo list to fifo array. */ -typedef struct Fifo Fifo; -struct Fifo { - char *name; - int fd; - int width; - struct timespec last_read; - struct timespec ttl; - int pos_init; /* Initial position on the output buffer. */ - int pos_curr; /* Current position on the output buffer. */ - int pos_final; /* Final position on the output buffer. */ - Fifo *next; +char *argv0 = NULL; +int running = 1; + +/* TODO: Convert slot list to slot array. */ +typedef struct Slot Slot; +struct Slot { + char *in_fifo; + int in_fd; + struct timespec in_last_read; + struct timespec out_ttl; + int out_width; + int out_pos_lo; /* Lowest position on the output buffer. */ + int out_pos_cur; /* Current position on the output buffer. */ + int out_pos_hi; /* Highest position on the output buffer. */ + Slot *next; }; typedef struct Config Config; struct Config { double interval; char * separator; - Fifo * fifos; - int fifo_count; - int total_width; - int output_to_x_root_window; -} defaults = { - .interval = 1.0, - .separator = "|", - .fifos = NULL, - .fifo_count = 0, - .total_width = 0, - .output_to_x_root_window = 0, + char expiry_character; + Slot * slots; + int slot_count; + int buf_width; + int to_x_root; }; enum read_status { @@ -63,79 +64,406 @@ enum read_status { FAILURE }; +char * +buf_create(Config *cfg) +{ + int seplen; + char *buf; + Slot *s; + + buf = calloc(1, cfg->buf_width + 1); + if (buf == NULL) + khlib_fatal( + "[memory] Failed to allocate buffer of %d bytes", + cfg->buf_width + ); + memset(buf, ' ', cfg->buf_width); + buf[cfg->buf_width] = '\0'; + seplen = strlen(cfg->separator); + /* Set the separators */ + for (s = cfg->slots; s; s = s->next) { + /* Skip the first, left-most */ + if (s->out_pos_lo) { + /* Copying only seplen ensures we omit the '\0' byte. */ + strncpy( + buf + (s->out_pos_lo - seplen), + cfg->separator, + seplen + ); + } + } + return buf; +} + +Slot * +slots_rev(Slot *old) +{ + Slot *tmp = NULL; + Slot *new = NULL; + + while (old) { + tmp = old->next; + old->next = new; + new = old; + old = tmp; + } + return new; +} + void -fifo_print_one(Fifo *f) +slot_log(Slot *s) { - info("Fifo " + khlib_info("Slot " "{" - " name = %s," - " fd = %d," - " width = %d," - " last_read = {tv_sec = %ld, tv_nsec = %ld}" - " ttl = {tv_sec = %ld, tv_nsec = %ld}," - " pos_init = %d," - " pos_curr = %d," - " pos_final = %d," + " in_fifo = %s," + " in_fd = %d," + " out_width = %d," + " in_last_read = {tv_sec = %ld, tv_nsec = %ld}" + " out_ttl = {tv_sec = %ld, tv_nsec = %ld}," + " out_pos_lo = %d," + " out_pos_cur = %d," + " out_pos_hi = %d," " next = %p," " }\n", - f->name, - f->fd, - f->width, - f->last_read.tv_sec, - f->last_read.tv_nsec, - f->ttl.tv_sec, - f->ttl.tv_nsec, - f->pos_init, - f->pos_curr, - f->pos_final, - f->next + s->in_fifo, + s->in_fd, + s->out_width, + s->in_last_read.tv_sec, + s->in_last_read.tv_nsec, + s->out_ttl.tv_sec, + s->out_ttl.tv_nsec, + s->out_pos_lo, + s->out_pos_cur, + s->out_pos_hi, + s->next ); } void -fifo_print_all(Fifo *head) +slots_log(Slot *head) +{ + Slot *s = head; + + for (; s; s = s->next) { + slot_log(s); + } +} + +void +slots_assert_fifos_exist(Slot *s) +{ + struct stat st; + int errors = 0; + + for (; s; s = s->next) { + if (lstat(s->in_fifo, &st) < 0) { + khlib_error( + "Cannot stat \"%s\". Error: %s\n", + s->in_fifo, + strerror(errno) + ); + errors++; + continue; + } + if (!(st.st_mode & S_IFIFO)) { + khlib_error("\"%s\" is not a FIFO\n", s->in_fifo); + errors++; + continue; + } + } + if (errors) + khlib_fatal( + "Encountered errors with given file paths. See log.\n" + ); +} + +void +slot_close(Slot *s) +{ + close(s->in_fd); + s->in_fd = -1; + s->out_pos_cur = s->out_pos_lo; +} + +void +slots_close(Slot *s) +{ + for (; s; s = s->next) + if (s->in_fd > -1) + slot_close(s); +} + + +void +slot_expire(Slot *s, struct timespec t, char expiry_character, char *buf) +{ + struct timespec td; + + timespecsub(&t, &(s->in_last_read), &td); + if (timespeccmp(&td, &(s->out_ttl), >=)) { + memset( + buf + s->out_pos_lo, + expiry_character, + s->out_width + ); + khlib_warn("Slot expired: \"%s\"\n", s->in_fifo); + } +} + +void +slot_set_error(Slot *s, char *buf) +{ + char *b; + int i; + + s->in_fd = -1; + b = buf + s->out_pos_lo; + /* Copy as much of the error message as possible. + * EXCLUDING the terminating \0. */ + for (i = 0; i < errlen && i < s->out_width; i++) + b[i] = errmsg[i]; + /* Any remaining positions: */ + memset(b + i, '_', s->out_width - i); +} + +enum read_status +slot_read(Slot *s, char *buf) +{ + char c; /* Character read. */ + int r; /* Remaining unused positions in buffer range. */ + + for (;;) { + switch (read(s->in_fd, &c, 1)) { + case -1: + khlib_error( + "Failed to read: \"%s\". errno: %d, msg: %s\n", + s->in_fifo, + errno, + strerror(errno) + ); + switch (errno) { + case EINTR: + case EAGAIN: + return RETRY; + default: + return FAILURE; + } + case 0: + khlib_debug("%s: End of FILE\n", s->in_fifo); + s->out_pos_cur = s->out_pos_lo; + return END_OF_FILE; + case 1: + /* TODO: Consider making msg term char a CLI option */ + if (c == '\n' || c == '\0') { + r = (s->out_pos_hi - s->out_pos_cur) + 1; + if (r > 0) + memset(buf + s->out_pos_cur, ' ', r); + return END_OF_MESSAGE; + } else { + if (s->out_pos_cur <= s->out_pos_hi) + buf[s->out_pos_cur++] = c; + else + /* + * Force EOM beyond available range. + * To ensure that a rogue large message + * doesn't trap us here needlessly + * long. + */ + return END_OF_MESSAGE; + } + break; + default: + assert(0); + } + } +} + +void +slots_read(Config *cfg, struct timespec *ti, char *buf) { - for (Fifo *f = head; f; f = f->next) { - fifo_print_one(f); + fd_set fds; + int maxfd = -1; + int ready = 0; + struct stat st; + struct timespec t; + Slot *s; + + FD_ZERO(&fds); + for (s = cfg->slots; s; s = s->next) { + /* TODO: Create the FIFO if it doesn't already exist. */ + if (lstat(s->in_fifo, &st) < 0) { + khlib_error( + "Cannot stat \"%s\". Error: %s\n", + s->in_fifo, + strerror(errno) + ); + slot_set_error(s, buf); + continue; + } + if (!(st.st_mode & S_IFIFO)) { + khlib_error("\"%s\" is not a FIFO\n", s->in_fifo); + slot_set_error(s, buf); + continue; + } + if (s->in_fd < 0) { + khlib_debug( + "%s: closed. opening. in_fd: %d\n", + s->in_fifo, + s->in_fd + ); + s->in_fd = open(s->in_fifo, O_RDONLY | O_NONBLOCK); + } else { + khlib_debug( + "%s: already openned. in_fd: %d\n", + s->in_fifo, + s->in_fd + ); + } + if (s->in_fd == -1) { + /* TODO Consider backing off retries for failed slots */ + khlib_error("Failed to open \"%s\"\n", s->in_fifo); + slot_set_error(s, buf); + continue; + } + khlib_debug("%s: open. in_fd: %d\n", s->in_fifo, s->in_fd); + if (s->in_fd > maxfd) + maxfd = s->in_fd; + FD_SET(s->in_fd, &fds); } + khlib_debug("selecting...\n"); + ready = pselect(maxfd + 1, &fds, NULL, NULL, ti, NULL); + khlib_debug("ready: %d\n", ready); + clock_gettime(CLOCK_MONOTONIC, &t); + if (ready == -1) { + switch (errno) { + case EINTR: + khlib_error( + "pselect interrupted: %d, errno: %d, msg: %s\n", + ready, + errno, + strerror(errno) + ); + /* TODO: Reconsider what to do here. */ + return; + default: + khlib_fatal( + "pselect failed: %d, errno: %d, msg: %s\n", + ready, + errno, + strerror(errno) + ); + } + } + /* At-least-once ensures that expiries are still checked on timeouts. */ + do { + for (s = cfg->slots; s; s = s->next) { + if (s->in_fd < 0) + continue; + if (FD_ISSET(s->in_fd, &fds)) { + khlib_debug("reading: %s\n", s->in_fifo); + switch (slot_read(s, buf)) { + /* + * ### MESSAGE LOSS ### + * is introduced by closing at EOM in addition + * to EOF, since there may be unread messages + * remaining in the pipe. However, + * + * ### INTER-MESSAGE PUSHBACK ### + * is also gained, since pipes block at the + * "open" call. + * + * This is an acceptable trade-off because we + * are a stateless reporter of a _most-recent_ + * status, not a stateful accumulator. + * + * ### LOSSLESS ALTERNATIVES ### + * - Read each pipe until EOF before reading + * another. + * PROBLEM: a fast writer can trap us in the + * read loop. + * + * - Read each pipe until EOM, but close only + * at EOF. + * PROBLEM: a fast writer can fill the pipe + * faster than we can read it and we end-up + * displaying stale data. + * + */ + case END_OF_MESSAGE: + case END_OF_FILE: + case FAILURE: + slot_close(s); + s->in_last_read = t; + ready--; + break; + case RETRY: + break; + default: + assert(0); + } + } else { + slot_expire(s, t, cfg->expiry_character, buf); + } + } + } while (ready); + assert(ready == 0); } void -config_print(Config *cfg) +config_log(Config *cfg) { - info( + khlib_info( "Config " "{" " interval = %f," " separator = %s," - " fifo_count = %d," - " total_width = %d," - " fifos = ..." + " slot_count = %d," + " buf_width = %d," + " slots = ..." " }\n", cfg->interval, cfg->separator, - cfg->fifo_count, - cfg->total_width + cfg->slot_count, + cfg->buf_width ); - fifo_print_all(cfg->fifos); + slots_log(cfg->slots); +} + +void +config_stretch_for_separators(Config *cfg) +{ + int seplen = strlen(cfg->separator); + int prefix = 0; + int nslots = 0; + Slot *s = cfg->slots; + + while (s) { + s->out_pos_lo += prefix; + s->out_pos_hi += prefix; + s->out_pos_cur = s->out_pos_lo; + prefix += seplen; + nslots++; + s = s->next; + } + cfg->buf_width += (seplen * (nslots - 1)); } int -is_pos_num(char *s) +is_pos_num(char *str) { - while (*s != '\0') - if (!isdigit(*(s++))) + while (*str != '\0') + if (!isdigit(*(str++))) return 0; return 1; } int -is_decimal(char *s) +is_decimal(char *str) { char c; int seen = 0; - while ((c = *(s++)) != '\0') + while ((c = *(str++)) != '\0') if (!isdigit(c)) { if (c == '.' && !seen++) continue; @@ -162,9 +490,12 @@ print_usage() " | -s SEPARATOR\n" " | -x (* Output to X root window *)\n" " | -l LOG_LEVEL\n" + " | -e EXPIRY_CHARACTER\n" " SEPARATOR = string\n" " INTERVAL = float (* (positive) number of seconds *)\n" " LOG_LEVEL = int (* %d through %d *)\n" + " EXPIRY_CHARACTER = string " + "(* Character with which to fill the slot upon expiration. *)\n" "\n", argv0, Nothing, @@ -178,7 +509,8 @@ print_usage() ); } -void opts_parse_any(Config *, int, char *[], int); /* For mutually-recursive calls. */ +/* For mutually-recursive calls. */ +void opts_parse_any(Config *, int, char *[], int); void parse_opts_opt_i(Config *cfg, int argc, char *argv[], int i) @@ -217,8 +549,21 @@ parse_opts_opt_l(Config *cfg, int argc, char *argv[], int i) usage("Option -l parameter is invalid: \"%s\"\n", param); log_level = atoi(param); if (log_level > Debug) - usage("Option -l value (%d) exceeds maximum (%d)\n", log_level, Debug); - _khatus_lib_log_level = log_level; + usage( + "Option -l value (%d) exceeds maximum (%d)\n", + log_level, + Debug + ); + _khlib_log_level = log_level; + opts_parse_any(cfg, argc, argv, i); +} + +void +parse_opts_opt_e(Config *cfg, int argc, char *argv[], int i) +{ + if (i >= argc) + usage("Option -e parameter is missing.\n"); + cfg->expiry_character = argv[i++][0]; opts_parse_any(cfg, argc, argv, i); } @@ -235,13 +580,17 @@ parse_opts_opt(Config *cfg, int argc, char *argv[], int i) parse_opts_opt_s(cfg, argc, argv, ++i); break; case 'x': - cfg->output_to_x_root_window = 1; + cfg->to_x_root = 1; opts_parse_any(cfg, argc, argv, ++i); break; case 'l': /* TODO: Generic set_int */ parse_opts_opt_l(cfg, argc, argv, ++i); break; + case 'e': + /* TODO: Generic set_str */ + parse_opts_opt_e(cfg, argc, argv, ++i); + break; default : usage("Option \"%s\" is invalid\n", argv[i]); } @@ -250,38 +599,47 @@ parse_opts_opt(Config *cfg, int argc, char *argv[], int i) void parse_opts_spec(Config *cfg, int argc, char *argv[], int i) { - if ((i + 3) > argc) - usage("[spec] Parameter(s) missing for fifo \"%s\".\n", argv[i]); + char *n; + char *w; + char *t; + struct timespec in_last_read; + Slot *s; - char *n = argv[i++]; - char *w = argv[i++]; - char *t = argv[i++]; + if ((i + 3) > argc) + usage( + "[spec] Parameter(s) missing for fifo \"%s\".\n", + argv[i] + ); - struct timespec last_read; + n = argv[i++]; + w = argv[i++]; + t = argv[i++]; if (!is_pos_num(w)) usage("[spec] Invalid width: \"%s\", for fifo \"%s\"\n", w, n); if (!is_decimal(t)) usage("[spec] Invalid TTL: \"%s\", for fifo \"%s\"\n", t, n); - last_read.tv_sec = 0; - last_read.tv_nsec = 0; - Fifo *f = calloc(1, sizeof(struct Fifo)); - if (f) { - f->name = n; - f->fd = -1; - f->width = atoi(w); - f->ttl = timespec_of_float(atof(t)); - f->last_read = last_read; - f->pos_init = cfg->total_width; - f->pos_curr = f->pos_init; - f->pos_final = f->pos_init + f->width - 1; - f->next = cfg->fifos; - - cfg->fifos = f; - cfg->total_width += f->width; - cfg->fifo_count++; + + in_last_read.tv_sec = 0; + in_last_read.tv_nsec = 0; + s = calloc(1, sizeof(struct Slot)); + + if (s) { + s->in_fifo = n; + s->in_fd = -1; + s->out_width = atoi(w); + s->out_ttl = khlib_timespec_of_float(atof(t)); + s->in_last_read = in_last_read; + s->out_pos_lo = cfg->buf_width; + s->out_pos_cur = s->out_pos_lo; + s->out_pos_hi = s->out_pos_lo + s->out_width - 1; + s->next = cfg->slots; + + cfg->slots = s; + cfg->buf_width += s->out_width; + cfg->slot_count++; } else { - fatal("[memory] Allocation failure."); + khlib_fatal("[memory] Allocation failure."); } opts_parse_any(cfg, argc, argv, i); } @@ -304,287 +662,89 @@ void opts_parse(Config *cfg, int argc, char *argv[]) { opts_parse_any(cfg, argc, argv, 1); - - Fifo *last = cfg->fifos; - cfg->fifos = NULL; - for (Fifo *f = last; f; ) { - Fifo *next = f->next; - f->next = cfg->fifos; - cfg->fifos = f; - f = next; - } -} - -void -fifo_expire_one(Fifo *f, struct timespec t, char *buf) -{ - struct timespec td; - - timespecsub(&t, &(f->last_read), &td); - if (timespeccmp(&td, &(f->ttl), >=)) { - /* TODO: Maybe configurable expiry character. */ - memset(buf + f->pos_init, '_', f->pos_final - f->pos_init); - warn("Data source expired: \"%s\"\n", f->name); - } -} - -void -fifo_expire_all(Config *cfg, struct timespec t, char *buf) -{ - Fifo *f; - - for (f = cfg->fifos; f; f = f->next) - fifo_expire_one(f, t, buf); -} - -void -fifo_read_error(Fifo *f, char *buf) -{ - char *b; - int i; - - b = buf + f->pos_init; - /* Copy as much of the error message as possible. - * EXCLUDING the terminating \0. */ - for (i = 0; i < errlen && i < f->width; i++) - b[i] = errmsg[i]; - /* Any remaining slots: */ - for (; i < f->width; i++) - b[i] = '_'; -} - -enum read_status -fifo_read_one(Fifo *f, struct timespec t, char *buf) -{ - char c; /* Character read. */ - int r; /* Remaining unused slots in buffer range. */ - - for (;;) { - switch (read(f->fd, &c, 1)) { - case -1: - error("Failed to read: \"%s\". errno: %d, msg: %s\n", - f->name, errno, strerror(errno)); - switch (errno) { - case EINTR: - case EAGAIN: - return RETRY; - default: - return FAILURE; - } - case 0: - debug("%s: End of FILE\n", f->name); - f->pos_curr = f->pos_init; - return END_OF_FILE; - case 1: - /* TODO: Consider making msg term char a CLI option */ - if (c == '\n' || c == '\0') { - r = f->pos_final - f->pos_curr; - if (r > 0) - memset(buf + f->pos_curr, ' ', r); - f->pos_curr = f->pos_init; - f->last_read = t; - return END_OF_MESSAGE; - } else { - if (f->pos_curr <= f->pos_final) - buf[f->pos_curr++] = c; - /* Drop beyond available range. */ - } - break; - default: - assert(0); - } - } + cfg->slots = slots_rev(cfg->slots); + config_log(cfg); + if (cfg->slots == NULL) + usage("No slot specs were given!\n"); } void -fifo_read_all(Config *cfg, struct timespec *ti, char *buf) -{ - fd_set fds; - int maxfd = -1; - int ready = 0; - struct stat st; - struct timespec t; - - FD_ZERO(&fds); - for (Fifo *f = cfg->fifos; f; f = f->next) { - /* TODO: Create the FIFO if it doesn't already exist. */ - if (lstat(f->name, &st) < 0) { - error("Cannot stat \"%s\". Error: %s\n", f->name, strerror(errno)); - fifo_read_error(f, buf); - continue; - } - if (!(st.st_mode & S_IFIFO)) { - error("\"%s\" is not a FIFO\n", f->name); - fifo_read_error(f, buf); - continue; - } - if (f->fd < 0) { - debug("%s: closed. opening. fd: %d\n", f->name, f->fd); - f->fd = open(f->name, O_RDONLY | O_NONBLOCK); - } else { - debug("%s: already openned. fd: %d\n", f->name, f->fd); - } - if (f->fd == -1) { - /* TODO: Consider backing off retries for failed fifos. */ - error("Failed to open \"%s\"\n", f->name); - fifo_read_error(f, buf); - continue; - } - debug("%s: open. fd: %d\n", f->name, f->fd); - if (f->fd > maxfd) - maxfd = f->fd; - FD_SET(f->fd, &fds); - } - debug("selecting...\n"); - ready = pselect(maxfd + 1, &fds, NULL, NULL, ti, NULL); - debug("ready: %d\n", ready); - assert(ready >= 0); - clock_gettime(CLOCK_MONOTONIC, &t); - while (ready) { - for (Fifo *f = cfg->fifos; f; f = f->next) { - if (FD_ISSET(f->fd, &fds)) { - debug("reading: %s\n", f->name); - switch (fifo_read_one(f, t, buf)) { - /* - * ### MESSAGE LOSS ### - * is introduced by closing at EOM in addition - * to EOF, since there may be unread messages - * remaining in the pipe. However, - * - * ### INTER-MESSAGE PUSHBACK ### - * is also gained, since pipes block at the - * "open" call. - * - * This is an acceptable trade-off because we - * are a stateless reporter of a _most-recent_ - * status, not a stateful accumulator. - */ - case END_OF_MESSAGE: - case END_OF_FILE: - case FAILURE: - close(f->fd); - f->fd = -1; - ready--; - break; - case RETRY: - break; - default: - assert(0); - } - } - } - } - assert(ready == 0); -} - -int -main(int argc, char *argv[]) +loop(Config *cfg, char *buf, Display *d) { - int width = 0; - int nfifos = 0; - int seplen = 0; - int prefix = 0; - int errors = 0; - char *buf; - Config cfg0 = defaults; - Config *cfg = &cfg0; - Display *display = NULL; - struct stat st; struct timespec - t0, /* time stamp. before reading fifos */ - t1, /* time stamp. after reading fifos */ + t0, /* time stamp. before reading slots */ + t1, /* time stamp. after reading slots */ ti, /* time interval desired (t1 - t0) */ td, /* time interval measured (t1 - t0) */ tc; /* time interval correction (ti - td) when td < ti */ - argv0 = argv[0]; - - opts_parse(cfg, argc, argv); - debug("argv0 = %s\n", argv0); - config_print(cfg); - - ti = timespec_of_float(cfg->interval); - - if (cfg->fifos == NULL) - usage("No fifo specs were given!\n"); - - /* 1st pass to check file existence and type */ - for (Fifo *f = cfg->fifos; f; f = f->next) { - if (lstat(f->name, &st) < 0) { - error("Cannot stat \"%s\". Error: %s\n", f->name, strerror(errno)); - errors++; - continue; - } - if (!(st.st_mode & S_IFIFO)) { - error("\"%s\" is not a FIFO\n", f->name); - errors++; - continue; - } - } - if (errors) - fatal("Encountered errors with the given file paths. See log.\n"); - - width = cfg->total_width; - seplen = strlen(cfg->separator); - - /* 2nd pass to make space for separators */ - for (Fifo *f = cfg->fifos; f; f = f->next) { - f->pos_init += prefix; - f->pos_final += prefix; - f->pos_curr = f->pos_init; - prefix += seplen; - nfifos++; - } - width += (seplen * (nfifos - 1)); - buf = calloc(1, width + 1); - if (buf == NULL) - fatal("[memory] Failed to allocate buffer of %d bytes", width); - memset(buf, ' ', width); - buf[width] = '\0'; - /* 3rd pass to set the separators */ - for (Fifo *f = cfg->fifos; f; f = f->next) { - if (f->pos_init) { /* Skip the first, left-most */ - /* Copying only seplen ensures we omit the '\0' byte. */ - strncpy(buf + (f->pos_init - seplen), cfg->separator, seplen); - } - } - - if (cfg->output_to_x_root_window && !(display = XOpenDisplay(NULL))) - fatal("XOpenDisplay failed with: %p\n", display); - /* TODO: Handle signals */ - for (;;) { + ti = khlib_timespec_of_float(cfg->interval); + while (running) { clock_gettime(CLOCK_MONOTONIC, &t0); // FIXME: check errors - fifo_read_all(cfg, &ti, buf); - if (cfg->output_to_x_root_window) { - if (XStoreName(display, DefaultRootWindow(display), buf) < 0) - fatal("XStoreName failed.\n"); - XFlush(display); + slots_read(cfg, &ti, buf); + if (cfg->to_x_root) { + if (XStoreName(d, DefaultRootWindow(d), buf) < 0) + khlib_fatal("XStoreName failed.\n"); + XFlush(d); } else { puts(buf); fflush(stdout); } - - /* - * This is a good place for expiry check, since we're about to - * sleep anyway and the time taken by the check will be - * subtracted from the sleep period. - */ - fifo_expire_all(cfg, t0, buf); - clock_gettime(CLOCK_MONOTONIC, &t1); // FIXME: check errors timespecsub(&t1, &t0, &td); - debug("td {tv_sec = %ld, tv_nsec = %ld}\n", td.tv_sec, td.tv_nsec); + khlib_debug( + "td {tv_sec = %ld, tv_nsec = %ld}\n", + td.tv_sec, + td.tv_nsec + ); if (timespeccmp(&td, &ti, <)) { - /* Pushback on data producers by refusing to read the + /* + * Pushback on data producers by refusing to read the * pipe more frequently than the interval. */ timespecsub(&ti, &td, &tc); - debug("snooze YES\n"); - snooze(&tc); - } else { - debug("snooze NO\n"); + khlib_sleep(&tc); } } +} +void +terminate(int s) +{ + khlib_debug("terminating due to signal %d\n", s); + running = 0; +} + +int +main(int argc, char *argv[]) +{ + argv0 = argv[0]; + + Config cfg = { + .interval = 1.0, + .separator = "|", + .expiry_character = '_', + .slots = NULL, + .slot_count = 0, + .buf_width = 0, + .to_x_root = 0, + }; + char *buf; + Display *d = NULL; + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = terminate; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT , &sa, NULL); + + opts_parse(&cfg, argc, argv); + slots_assert_fifos_exist(cfg.slots); + config_stretch_for_separators(&cfg); + buf = buf_create(&cfg); + if (cfg.to_x_root && !(d = XOpenDisplay(NULL))) + khlib_fatal("XOpenDisplay failed with: %p\n", d); + loop(&cfg, buf, d); + slots_close(cfg.slots); return EXIT_SUCCESS; }