X-Git-Url: https://git.xandkar.net/?a=blobdiff_plain;f=x5%2Fkhatus.c;h=37cbccca13a5d23fe703d2cb20083bd5e6fd6795;hb=b6316e94783d975f109c9901e4061ba041f0cb3f;hp=733f6863f85ff25e48517a1adf5b8b7a27fb42e3;hpb=3efcce25450defc72df93f907a918bf37891d568;p=khatus.git diff --git a/x5/khatus.c b/x5/khatus.c index 733f686..37cbccc 100644 --- a/x5/khatus.c +++ b/x5/khatus.c @@ -1,55 +1,76 @@ - -#include -#include #include #include #include #include #include +#include #include #include #include +#include +#include + +#include + +#include "bsdtimespec.h" -#define debug(args...) {fprintf(stderr, "[debug] " args);} -#define info( args...) {fprintf(stderr, "[info] " args);} -#define error(args...) {fprintf(stderr, "[error] " args);} +#define debug(args...) if (cfg->log_level >= Debug) {fprintf(stderr, "[debug] " args); fflush(stderr);} +#define info( args...) if (cfg->log_level >= Info ) {fprintf(stderr, "[info] " args); fflush(stderr);} +#define error(args...) if (cfg->log_level >= Error) {fprintf(stderr, "[error] " args); fflush(stderr);} #define fatal(args...) {fprintf(stderr, "[fatal] " args); exit(EXIT_FAILURE);} #define usage(args...) {print_usage(); fatal("[usage] " args);} +#define ERRMSG "ERROR" + +static const char errmsg[] = ERRMSG; +static const int errlen = sizeof(ERRMSG) - 1; + char *argv0; -typedef struct File File; -struct File { +typedef enum LogLevel { + Nothing, + Error, + Info, + Debug +} LogLevel; + +/* TODO: Convert fifo list to fifo array. */ +typedef struct Fifo Fifo; +struct Fifo { char *name; int fd; int width; int last_read; int ttl; int pos; /* Position on the output buffer. */ - File *next; + Fifo *next; }; typedef struct Config Config; struct Config { int interval; char * separator; - File * files; - int file_count; + Fifo * fifos; + int fifo_count; int total_width; + int output_to_x_root_window; + LogLevel log_level; } defaults = { .interval = 1, .separator = "|", - .files = NULL, - .file_count = 0, + .fifos = NULL, + .fifo_count = 0, .total_width = 0, + .output_to_x_root_window = 0, + .log_level = Info, }; void -file_print_one(File *f) +fifo_print_one(Fifo *f, Config *cfg) { - debug( - "File " + info( + "Fifo " "{" " name = %s," " fd = %d," @@ -70,31 +91,33 @@ file_print_one(File *f) } void -file_print_all(File *head) +fifo_print_all(Fifo *head, Config *cfg) { - for (File *f = head; f; f = f->next) { - file_print_one(f); + for (Fifo *f = head; f; f = f->next) { + fifo_print_one(f, cfg); } } void -config_print(Config *c) +config_print(Config *cfg) { - debug( + info( "Config " "{" " interval = %d," " separator = %s," - " file_count = %d," + " fifo_count = %d," " total_width = %d," - " files = ..." + " log_level = %d," + " fifos = ..." " }\n", - c->interval, - c->separator, - c->file_count, - c->total_width + cfg->interval, + cfg->separator, + cfg->fifo_count, + cfg->total_width, + cfg->log_level ); - file_print_all(c->files); + fifo_print_all(cfg->fifos, cfg); } int @@ -121,10 +144,15 @@ print_usage() " DATA_TTL = int (* (positive) number of seconds *)\n" " OPTION = -i INTERVAL\n" " | -s SEPARATOR\n" + " | -x (* Output to X root window *)\n" + " | -l LOG_LEVEL\n" " SEPARATOR = string\n" " INTERVAL = int (* (positive) number of seconds *)\n" + " LOG_LEVEL = int (* %d through %d *)\n" "\n", - argv0 + argv0, + Nothing, + Debug ); fprintf( stderr, @@ -165,13 +193,52 @@ parse_opts_opt_s(Config *cfg, int argc, char *argv[], int i) } } +void +parse_opts_opt_l(Config *cfg, int argc, char *argv[], int i) +{ + int log_level; + + if (i < argc) { + char *param = argv[i++]; + + if (is_pos_num(param)) { + log_level = atoi(param); + if (log_level <= Debug) { + cfg->log_level = log_level; + opts_parse_any(cfg, argc, argv, i); + } else { + usage("Option -l value (%d) exceeds maximum (%d)\n", log_level, Debug); + } + } else { + usage("Option -l parameter is invalid: \"%s\"\n", param); + } + } else { + usage("Option -l parameter is missing.\n"); + } +} + 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 */ - 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->output_to_x_root_window = 1; + opts_parse_any(cfg, argc, argv, ++i); + break; + case 'l': + /* TODO: Generic set_int */ + parse_opts_opt_l(cfg, argc, argv, ++i); + break; + default : + usage("Option \"%s\" is invalid\n", argv[i]); } } @@ -179,17 +246,17 @@ void parse_opts_spec(Config *cfg, int argc, char *argv[], int i) { 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++]; if (!is_pos_num(w)) - usage("[spec] Invalid width: \"%s\", for file \"%s\"\n", w, n); + usage("[spec] Invalid width: \"%s\", for fifo \"%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)); + usage("[spec] Invalid TTL: \"%s\", for fifo \"%s\"\n", t, n); + Fifo *f = calloc(1, sizeof(struct Fifo)); if (f) { f->name = n; f->fd = -1; @@ -197,11 +264,11 @@ parse_opts_spec(Config *cfg, int argc, char *argv[], int i) f->ttl = atoi(t); f->last_read = 0; f->pos = cfg->total_width; - f->next = cfg->files; + f->next = cfg->fifos; - cfg->files = f; + cfg->fifos = f; cfg->total_width += f->width; - cfg->file_count++; + cfg->fifo_count++; } else { fatal("[memory] Allocation failure."); } @@ -213,29 +280,48 @@ opts_parse_any(Config *cfg, int argc, char *argv[], int 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; + 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 -read_one(File *f, char *buf) +fifo_read_error(Fifo *f, char *buf) +{ + char *b; + int i; + + b = buf + f->pos; + /* Copy as much of the error message as possible. + * EXCLUDING the reminating \0. */ + for (i = 0; i < errlen && i < f->width; i++) + b[i] = errmsg[i]; + /* Any remaining slots: */ + for (; i < f->width; i++) + b[i] = '_'; +} + +void +fifo_read_one(Fifo *f, char *buf, Config *cfg) { ssize_t current; ssize_t total; @@ -246,38 +332,49 @@ read_one(File *f, char *buf) 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) + if (current == -1) { error("Failed to read: \"%s\". Error: %s\n", f->name, strerror(errno)); + fifo_read_error(f, buf); + } else + while (total++ < f->width) + *b++ = ' '; + /* TODO Record timestamp read */ close(f->fd); f->fd = -1; } void -read_all(Config *cfg, char *buf) +fifo_read_all(Config *cfg, char *buf) { fd_set fds; - int maxfd; + int maxfd = -1; int ready; struct stat st; FD_ZERO(&fds); - - /* TODO: Check TTL */ - for (File *f = cfg->files; f; f = f->next) { + 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) - 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); + 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; + } 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 == -1) { + /* TODO: Consider backing off retries for failed fifos. */ + error("Failed to open \"%s\"\n", f->name); + fifo_read_error(f, buf); + continue; + } if (f->fd > maxfd) maxfd = f->fd; FD_SET(f->fd, &fds); @@ -288,10 +385,33 @@ read_all(Config *cfg, char *buf) assert(ready != 0); if (ready < 0) fatal("%s", strerror(errno)); - for (File *f = cfg->files; f; f = f->next) { + for (Fifo *f = cfg->fifos; f; f = f->next) { if (FD_ISSET(f->fd, &fds)) { debug("reading: %s\n", f->name); - read_one(f, buf); + fifo_read_one(f, buf, cfg); + } + } +} + +void +snooze(struct timespec *t, Config *cfg) +{ + struct timespec remainder; + int result; + + result = nanosleep(t, &remainder); + + if (result < 0) { + if (errno == EINTR) { + info( + "nanosleep interrupted. Remainder: " + "{ tv_sec = %ld, tv_nsec = %ld }", + remainder.tv_sec, remainder.tv_nsec); + /* No big deal if we occasionally sleep less, + * so not attempting to correct after an interruption. + */ + } else { + fatal("nanosleep: %s\n", strerror(errno)); } } } @@ -299,48 +419,108 @@ read_all(Config *cfg, char *buf) int main(int argc, char *argv[]) { - int width; - int nfiles = 0; - int seplen; + int width = 0; + int nfifos = 0; + int seplen = 0; int prefix = 0; + int errors = 0; char *buf; - Config *cfg = &defaults; + 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 */ + 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, 1); + opts_parse(cfg, argc, argv); debug("argv0 = %s\n", argv0); config_print(cfg); - if (cfg->files == NULL) - usage("No file specs were given!\n"); + + /* TODO: Support interval < 1. i.e. implement timespec_of_float */ + ti.tv_sec = cfg->interval; + ti.tv_nsec = 0; + + 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); - /* 1st pass to make space for separators */ - for (File *f = cfg->files; f; f = f->next) { + /* 2nd pass to make space for separators */ + for (Fifo *f = cfg->fifos; f; f = f->next) { f->pos += prefix; prefix += seplen; - nfiles++; + nfifos++; } - width += (seplen * (nfiles - 1)); + 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'; - /* 2nd pass to set the separators */ - for (File *f = cfg->files; f; f = f->next) { + /* 3rd pass to set the separators */ + for (Fifo *f = cfg->fifos; 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); } } - printf("%s\n", buf); - /* TODO: nanosleep and nano time diff */ + if (cfg->output_to_x_root_window && !(display = XOpenDisplay(NULL))) + fatal("XOpenDisplay failed with: %p\n", display); + /* TODO: Handle signals */ for (;;) { - read_all(cfg, buf); - printf("%s\n", buf); + clock_gettime(CLOCK_MONOTONIC, &t0); // FIXME: check errors + /* TODO: Cache expiration. i.e. use the TTL */ + /* TODO: How to trigger TTL check? On select? Alarm signal? */ + /* TODO: Set timeout on fifo_read_all based on diff of last time of + * fifo_read_all and desired time of next TTL check. + */ + /* TODO: How long to wait on IO? Max TTL? */ + fifo_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); + } + 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); + if (timespeccmp(&td, &ti, <)) { + /* 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, cfg); + } else + debug("snooze NO\n"); } + + return EXIT_SUCCESS; }