-#include <fcntl.h>
-#include <unistd.h>
#include <sys/select.h>
#include <sys/stat.h>
-#include <X11/Xlib.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
+#include <unistd.h>
-#define debug(args...) {fprintf(stderr, "[debug] " args);}
-#define info( args...) {fprintf(stderr, "[info] " args);}
-#define error(args...) {fprintf(stderr, "[error] " args);}
-#define fatal(args...) {fprintf(stderr, "[fatal] " args); exit(EXIT_FAILURE);}
-#define usage(args...) {print_usage(); fatal("[usage] " args);}
-
-char *argv0;
-
-/* TODO: Convert file list to file array. */
-typedef struct File File;
-struct File {
- char *name;
- int fd;
- int width;
- int last_read;
- int ttl;
- int pos; /* Position on the output buffer. */
- File *next;
+#include <X11/Xlib.h>
+
+#include "bsdtimespec.h"
+#include "khlib_log.h"
+#include "khlib_time.h"
+
+#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 = 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 {
- int interval;
+ double interval;
char * separator;
- File * files;
- int file_count;
- int total_width;
- int output_to_x_root_window;
-} defaults = {
- .interval = 1,
- .separator = "|",
- .files = NULL,
- .file_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 {
+ END_OF_FILE,
+ END_OF_MESSAGE,
+ RETRY,
+ 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
-file_print_one(File *f)
+slot_log(Slot *s)
{
- debug(
- "File "
- "{"
- " name = %s,"
- " fd = %d,"
- " width = %d,"
- " last_read = %d,"
- " ttl = %d,"
- " pos = %d,"
- " next = %p,"
- " }\n",
- f->name,
- f->fd,
- f->width,
- f->last_read,
- f->ttl,
- f->pos,
- f->next
+ khlib_info("Slot "
+ "{"
+ " 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",
+ 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
-file_print_all(File *head)
+slots_log(Slot *head)
+{
+ Slot *s = head;
+
+ for (; s; s = s->next) {
+ slot_log(s);
+ }
+}
+
+void
+slots_assert_fifos_exist(Slot *s)
{
- for (File *f = head; f; f = f->next) {
- file_print_one(f);
+ 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
-config_print(Config *c)
+slot_close(Slot *s)
{
- debug(
- "Config "
- "{"
- " interval = %d,"
- " separator = %s,"
- " file_count = %d,"
- " total_width = %d,"
- " files = ..."
- " }\n",
- c->interval,
- c->separator,
- c->file_count,
- c->total_width
+ 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)
+{
+ 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_log(Config *cfg)
+{
+ khlib_info(
+ "Config "
+ "{"
+ " interval = %f,"
+ " separator = %s,"
+ " slot_count = %d,"
+ " buf_width = %d,"
+ " slots = ..."
+ " }\n",
+ cfg->interval,
+ cfg->separator,
+ cfg->slot_count,
+ cfg->buf_width
);
- file_print_all(c->files);
+ 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 *str)
+{
+ char c;
+ int seen = 0;
+
+ while ((c = *(str++)) != '\0')
+ if (!isdigit(c)) {
+ if (c == '.' && !seen++)
+ continue;
+ else
+ return 0;
+ }
+ return 1;
+}
+
void
print_usage()
{
assert(argv0);
fprintf(
- stderr,
- "\n"
- "Usage: %s [OPTION ...] SPEC [SPEC ...]\n"
- "\n"
- " SPEC = FILE_PATH DATA_WIDTH DATA_TTL\n"
- " FILE_PATH = string\n"
- " DATA_WIDTH = int (* (positive) number of characters *)\n"
- " DATA_TTL = int (* (positive) number of seconds *)\n"
- " OPTION = -i INTERVAL\n"
- " | -s SEPARATOR\n"
- " SEPARATOR = string\n"
- " INTERVAL = int (* (positive) number of seconds *)\n"
- "\n",
- argv0
+ stderr,
+ "\n"
+ "Usage: %s [OPTION ...] SPEC [SPEC ...]\n"
+ "\n"
+ " SPEC = FILE_PATH DATA_WIDTH DATA_TTL\n"
+ " FILE_PATH = string\n"
+ " DATA_WIDTH = int (* (positive) number of characters *)\n"
+ " DATA_TTL = float (* (positive) number of seconds *)\n"
+ " OPTION = -i INTERVAL\n"
+ " | -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,
+ Debug
);
fprintf(
- stderr,
- "Example: %s -i 1 /dev/shm/khatus/khatus_sensor_x 4 10\n"
- "\n",
- argv0
+ stderr,
+ "Example: %s -i 1 /dev/shm/khatus/khatus_sensor_x 4 10\n"
+ "\n",
+ argv0
);
}
-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)
{
- if (i < argc) {
- char *param = argv[i++];
+ char *param;
- if (is_pos_num(param)) {
- cfg->interval = atoi(param);
- opts_parse_any(cfg, argc, argv, i);
- } else {
- usage("Option -i parameter is invalid: \"%s\"\n", param);
- }
- } else {
+ if (i >= argc)
usage("Option -i parameter is missing.\n");
- }
+ param = argv[i++];
+ if (!is_decimal(param))
+ usage("Option -i parameter is invalid: \"%s\"\n", param);
+ cfg->interval = atof(param);
+ opts_parse_any(cfg, argc, argv, i);
}
void
parse_opts_opt_s(Config *cfg, int argc, char *argv[], int i)
{
- if (i < argc) {
- cfg->separator = calloc((strlen(argv[i]) + 1), sizeof(char));
- strcpy(cfg->separator, argv[i]);
- opts_parse_any(cfg, argc, argv, ++i);
- } else {
+ if (i >= argc)
usage("Option -s parameter is missing.\n");
- }
+ cfg->separator = calloc((strlen(argv[i]) + 1), sizeof(char));
+ strcpy(cfg->separator, argv[i]);
+ opts_parse_any(cfg, argc, argv, ++i);
+}
+
+void
+parse_opts_opt_l(Config *cfg, int argc, char *argv[], int i)
+{
+ char *param;
+ int log_level;
+
+ if (i >= argc)
+ usage("Option -l parameter is missing.\n");
+ param = argv[i++];
+ if (!is_pos_num(param))
+ 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
+ );
+ _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);
}
void
parse_opts_opt(Config *cfg, int argc, char *argv[], int i)
{
switch (argv[i][1]) {
- case 'i': parse_opts_opt_i(cfg, argc, argv, ++i); break; /* TODO: Generic set_int */
- case 's': parse_opts_opt_s(cfg, argc, argv, ++i); break; /* TODO: Generic set_str */
- case 'x': {
- cfg->output_to_x_root_window = 1;
- opts_parse_any(cfg, argc, argv, ++i);
- break;
- }
- default : usage("Option \"%s\" is invalid\n", argv[i]);
+ case 'i':
+ /* TODO: Generic set_int */
+ parse_opts_opt_i(cfg, argc, argv, ++i);
+ break;
+ case 's':
+ /* TODO: Generic set_str */
+ parse_opts_opt_s(cfg, argc, argv, ++i);
+ break;
+ case 'x':
+ 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]);
}
}
void
parse_opts_spec(Config *cfg, int argc, char *argv[], int i)
{
+ char *n;
+ char *w;
+ char *t;
+ struct timespec in_last_read;
+ Slot *s;
+
if ((i + 3) > argc)
- usage("[spec] Parameter(s) missing for file \"%s\".\n", argv[i]);
+ usage(
+ "[spec] Parameter(s) missing for fifo \"%s\".\n",
+ argv[i]
+ );
- char *n = argv[i++];
- char *w = argv[i++];
- char *t = argv[i++];
+ n = argv[i++];
+ w = argv[i++];
+ t = argv[i++];
if (!is_pos_num(w))
- usage("[spec] Invalid width: \"%s\", for file \"%s\"\n", w, n);
- if (!is_pos_num(t))
- usage("[spec] Invalid TTL: \"%s\", for file \"%s\"\n", t, n);
- File *f = calloc(1, sizeof(struct File));
- if (f) {
- f->name = n;
- f->fd = -1;
- f->width = atoi(w);
- f->ttl = atoi(t);
- f->last_read = 0;
- f->pos = cfg->total_width;
- f->next = cfg->files;
-
- cfg->files = f;
- cfg->total_width += f->width;
- cfg->file_count++;
+ 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);
+
+ 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);
}
{
if (i < argc) {
switch (argv[i][0]) {
- case '-': parse_opts_opt(cfg, argc, argv, i); break;
- default : parse_opts_spec(cfg, argc, argv, i);
+ case '-':
+ parse_opts_opt(cfg, argc, argv, i);
+ break;
+ default :
+ parse_opts_spec(cfg, argc, argv, i);
}
}
}
void
-opts_parse(Config *cfg, int argc, char *argv[], int i)
+opts_parse(Config *cfg, int argc, char *argv[])
{
opts_parse_any(cfg, argc, argv, 1);
-
- File *last = cfg->files;
- cfg->files = NULL;
- for (File *f = last; f; ) {
- File *next = f->next;
- f->next = cfg->files;
- cfg->files = f;
- f = next;
- }
+ cfg->slots = slots_rev(cfg->slots);
+ config_log(cfg);
+ if (cfg->slots == NULL)
+ usage("No slot specs were given!\n");
}
void
-read_one(File *f, char *buf)
+loop(Config *cfg, char *buf, Display *d)
{
- ssize_t current;
- ssize_t total;
- char *b;
- char c;
-
- current = 0;
- total = 0;
- c = '\0';
- b = buf + f->pos;
- memset(b, ' ', f->width);
- while ((current = read(f->fd, &c, 1)) && c != '\n' && c != '\0' && total++ < f->width)
- *b++ = c;
- if (current == -1)
- error("Failed to read: \"%s\". Error: %s\n", f->name, strerror(errno));
- /* TODO Record timestamp read */
- close(f->fd);
- f->fd = -1;
+ struct timespec
+ 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 */
+
+ ti = khlib_timespec_of_float(cfg->interval);
+ while (running) {
+ clock_gettime(CLOCK_MONOTONIC, &t0); // FIXME: check errors
+ 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);
+ }
+ clock_gettime(CLOCK_MONOTONIC, &t1); // FIXME: check errors
+ timespecsub(&t1, &t0, &td);
+ 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
+ * pipe more frequently than the interval.
+ */
+ timespecsub(&ti, &td, &tc);
+ khlib_sleep(&tc);
+ }
+ }
}
void
-read_all(Config *cfg, char *buf)
+terminate(int s)
{
- fd_set fds;
- int maxfd;
- int ready;
- struct stat st;
-
- FD_ZERO(&fds);
- /* TODO: Check TTL */
- for (File *f = cfg->files; f; f = f->next) {
- /* TODO: Create the FIFO if it doesn't already exist. */
- if (lstat(f->name, &st) < 0)
- fatal("Cannot stat \"%s\". Error: %s\n", f->name, strerror(errno));
- if (!(st.st_mode & S_IFIFO))
- fatal("\"%s\" is not a FIFO\n", f->name);
- debug("opening: %s\n", f->name);
- if (f->fd < 0)
- f->fd = open(f->name, O_RDONLY | O_NONBLOCK);
- if (f->fd == -1)
- /* TODO: Consider backing off retries for failed files. */
- fatal("Failed to open \"%s\"\n", f->name);
- if (f->fd > maxfd)
- maxfd = f->fd;
- FD_SET(f->fd, &fds);
- }
- debug("selecting...\n");
- ready = select(maxfd + 1, &fds, NULL, NULL, NULL);
- debug("ready: %d\n", ready);
- assert(ready != 0);
- if (ready < 0)
- fatal("%s", strerror(errno));
- for (File *f = cfg->files; f; f = f->next) {
- if (FD_ISSET(f->fd, &fds)) {
- debug("reading: %s\n", f->name);
- read_one(f, buf);
- }
- }
+ khlib_debug("terminating due to signal %d\n", s);
+ running = 0;
}
int
main(int argc, char *argv[])
{
- int width;
- int nfiles = 0;
- int seplen;
- int prefix = 0;
- char *buf;
- Config *cfg = &defaults;
- Display *display;
-
argv0 = argv[0];
- opts_parse(cfg, argc, argv, 1);
- debug("argv0 = %s\n", argv0);
- config_print(cfg);
- if (cfg->files == NULL)
- usage("No file specs were given!\n");
-
- width = cfg->total_width;
- seplen = strlen(cfg->separator);
-
- /* 1st pass to make space for separators */
- for (File *f = cfg->files; f; f = f->next) {
- f->pos += prefix;
- prefix += seplen;
- nfiles++;
- }
- width += (seplen * (nfiles - 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';
- /* 2nd pass to set the separators */
- for (File *f = cfg->files; f; f = f->next) {
- if (f->pos) { /* Skip the first, left-most */
- /* Copying only seplen ensures we omit the '\0' byte. */
- strncpy(buf + (f->pos - seplen), cfg->separator, seplen);
- }
- }
-
- if (cfg->output_to_x_root_window && !(display = XOpenDisplay(NULL)))
- fatal("XOpenDisplay failed with: %p\n", display);
- /* TODO: nanosleep and nano time diff */
- for (;;) {
- /* TODO: Check TTL and maybe blank-out */
- /* TODO: How to trigger TTL check? On select? Alarm signal? */
- /* TODO: Set timeout on read_all based on diff of last time of
- * read_all and desired time of next TTL check.
- * */
- read_all(cfg, buf);
- if (cfg->output_to_x_root_window) {
- if (XStoreName(display, DefaultRootWindow(display), buf) < 0)
- fatal("XStoreName failed.\n");
- XFlush(display);
- } else {
- puts(buf);
- fflush(stdout);
- }
- }
+ 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;
}