X-Git-Url: https://git.xandkar.net/?p=khatus.git;a=blobdiff_plain;f=x5%2Fkhatus.c;h=5407acde0b145131f038515d773e0a8d17bf2526;hp=5caae33d698edc1d4d4abf1e1a1035c7629ef46f;hb=HEAD;hpb=4bfac488ba99787df53cf6b8627b0192921309cf diff --git a/x5/khatus.c b/x5/khatus.c index 5caae33..5407acd 100644 --- a/x5/khatus.c +++ b/x5/khatus.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,8 @@ static const char errmsg[] = ERRMSG; static const int errlen = sizeof(ERRMSG) - 1; -char *argv0; +char *argv0 = NULL; +int running = 1; /* TODO: Convert slot list to slot array. */ typedef struct Slot Slot; @@ -48,10 +50,11 @@ typedef struct Config Config; struct Config { double interval; char * separator; + char expiry_character; Slot * slots; int slot_count; - int total_width; - int output_to_x_root_window; + int buf_width; + int to_x_root; }; enum read_status { @@ -61,6 +64,37 @@ 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) { @@ -77,7 +111,7 @@ slots_rev(Slot *old) } void -slot_print(Slot *s) +slot_log(Slot *s) { khlib_info("Slot " "{" @@ -106,44 +140,94 @@ slot_print(Slot *s) } void -slots_print(Slot *head) +slots_log(Slot *head) { - for (Slot *s = head; s; s = s->next) { - slot_print(s); + Slot *s = head; + + for (; s; s = s->next) { + slot_log(s); } } void -slot_expire(Slot *s, struct timespec t, char *buf) +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), >=)) { - /* TODO: Maybe configurable expiry character. */ - memset(buf + s->out_pos_lo, '_', s->out_pos_hi - s->out_pos_lo); + memset( + buf + s->out_pos_lo, + expiry_character, + s->out_width + ); khlib_warn("Slot expired: \"%s\"\n", s->in_fifo); } } void -slot_read_error(Slot *s, char *buf) +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: */ - for (; i < s->out_width; i++) - b[i] = '_'; + memset(b + i, '_', s->out_width - i); } enum read_status -slot_read(Slot *s, struct timespec t, char *buf) +slot_read(Slot *s, char *buf) { char c; /* Character read. */ int r; /* Remaining unused positions in buffer range. */ @@ -171,21 +255,21 @@ slot_read(Slot *s, struct timespec t, char *buf) 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; + r = (s->out_pos_hi - s->out_pos_cur) + 1; if (r > 0) memset(buf + s->out_pos_cur, ' ', r); - s->out_pos_cur = s->out_pos_lo; - s->in_last_read = t; return END_OF_MESSAGE; } else { if (s->out_pos_cur <= s->out_pos_hi) buf[s->out_pos_cur++] = c; - /* Drop beyond available range. */ - /* - * TODO Define max after which we stop reading. - * To ensure that a rogue large message - * doesn't trap us here. - */ + 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: @@ -213,12 +297,12 @@ slots_read(Config *cfg, struct timespec *ti, char *buf) s->in_fifo, strerror(errno) ); - slot_read_error(s, buf); + slot_set_error(s, buf); continue; } if (!(st.st_mode & S_IFIFO)) { khlib_error("\"%s\" is not a FIFO\n", s->in_fifo); - slot_read_error(s, buf); + slot_set_error(s, buf); continue; } if (s->in_fd < 0) { @@ -238,7 +322,7 @@ slots_read(Config *cfg, struct timespec *ti, char *buf) if (s->in_fd == -1) { /* TODO Consider backing off retries for failed slots */ khlib_error("Failed to open \"%s\"\n", s->in_fifo); - slot_read_error(s, buf); + slot_set_error(s, buf); continue; } khlib_debug("%s: open. in_fd: %d\n", s->in_fifo, s->in_fd); @@ -254,7 +338,7 @@ slots_read(Config *cfg, struct timespec *ti, char *buf) switch (errno) { case EINTR: khlib_error( - "pselect temp failure: %d, errno: %d, msg: %s\n", + "pselect interrupted: %d, errno: %d, msg: %s\n", ready, errno, strerror(errno) @@ -273,9 +357,11 @@ slots_read(Config *cfg, struct timespec *ti, char *buf) /* 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, t, buf)) { + switch (slot_read(s, buf)) { /* * ### MESSAGE LOSS ### * is introduced by closing at EOM in addition @@ -289,12 +375,25 @@ slots_read(Config *cfg, struct timespec *ti, char *buf) * 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: - close(s->in_fd); - s->in_fd = -1; + slot_close(s); + s->in_last_read = t; ready--; break; case RETRY: @@ -303,7 +402,7 @@ slots_read(Config *cfg, struct timespec *ti, char *buf) assert(0); } } else { - slot_expire(s, t, buf); + slot_expire(s, t, cfg->expiry_character, buf); } } } while (ready); @@ -311,7 +410,7 @@ slots_read(Config *cfg, struct timespec *ti, char *buf) } void -config_print(Config *cfg) +config_log(Config *cfg) { khlib_info( "Config " @@ -319,15 +418,34 @@ config_print(Config *cfg) " interval = %f," " separator = %s," " slot_count = %d," - " total_width = %d," + " buf_width = %d," " slots = ..." " }\n", cfg->interval, cfg->separator, cfg->slot_count, - cfg->total_width + cfg->buf_width ); - slots_print(cfg->slots); + 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 @@ -372,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, @@ -437,6 +558,15 @@ parse_opts_opt_l(Config *cfg, int argc, char *argv[], int i) 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) { @@ -450,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]); } @@ -496,13 +630,13 @@ parse_opts_spec(Config *cfg, int argc, char *argv[], int i) 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->total_width; + 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->total_width += s->out_width; + cfg->buf_width += s->out_width; cfg->slot_count++; } else { khlib_fatal("[memory] Allocation failure."); @@ -529,108 +663,26 @@ opts_parse(Config *cfg, int argc, char *argv[]) { opts_parse_any(cfg, argc, argv, 1); cfg->slots = slots_rev(cfg->slots); + config_log(cfg); + if (cfg->slots == NULL) + usage("No slot specs were given!\n"); } -int -main(int argc, char *argv[]) +void +loop(Config *cfg, char *buf, Display *d) { - Config cfg = { - .interval = 1.0, - .separator = "|", - .slots = NULL, - .slot_count = 0, - .total_width = 0, - .output_to_x_root_window = 0, - }; - - int width = 0; - int nslots = 0; - int seplen = 0; - int prefix = 0; - int errors = 0; - char *buf; - Display *d = NULL; - struct stat st; 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 */ - Slot *s; - - argv0 = argv[0]; - - opts_parse(&cfg, argc, argv); - khlib_debug("argv0 = %s\n", argv0); - config_print(&cfg); - - ti = khlib_timespec_of_float(cfg.interval); - - if (cfg.slots == NULL) - usage("No slot specs were given!\n"); - - /* 1st pass to check file existence and type */ - for (s = cfg.slots; 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" - ); - width = cfg.total_width; - seplen = strlen(cfg.separator); - - /* 2nd pass to make space for separators */ - for (s = cfg.slots; s; s = s->next) { - s->out_pos_lo += prefix; - s->out_pos_hi += prefix; - s->out_pos_cur = s->out_pos_lo; - prefix += seplen; - nslots++; - } - width += (seplen * (nslots - 1)); - buf = calloc(1, width + 1); - if (buf == NULL) - khlib_fatal( - "[memory] Failed to allocate buffer of %d bytes", - width - ); - memset(buf, ' ', width); - buf[width] = '\0'; - /* 3rd pass to set the separators */ - for (s = cfg.slots; s; s = s->next) { - if (s->out_pos_lo) { /* Skip the first, left-most */ - /* Copying only seplen ensures we omit the '\0' byte. */ - strncpy( - buf + (s->out_pos_lo - seplen), - cfg.separator, - seplen - ); - } - } - - if (cfg.output_to_x_root_window && !(d = XOpenDisplay(NULL))) - khlib_fatal("XOpenDisplay failed with: %p\n", d); - /* TODO: Handle signals */ - for (;;) { + ti = khlib_timespec_of_float(cfg->interval); + while (running) { clock_gettime(CLOCK_MONOTONIC, &t0); // FIXME: check errors - slots_read(&cfg, &ti, buf); - if (cfg.output_to_x_root_window) { + slots_read(cfg, &ti, buf); + if (cfg->to_x_root) { if (XStoreName(d, DefaultRootWindow(d), buf) < 0) khlib_fatal("XStoreName failed.\n"); XFlush(d); @@ -654,5 +706,45 @@ main(int argc, char *argv[]) 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; }