diff options
Diffstat (limited to 'src/rc/einfo.c')
-rw-r--r-- | src/rc/einfo.c | 1016 |
1 files changed, 1016 insertions, 0 deletions
diff --git a/src/rc/einfo.c b/src/rc/einfo.c new file mode 100644 index 00000000..306e7885 --- /dev/null +++ b/src/rc/einfo.c @@ -0,0 +1,1016 @@ +/* + einfo.c + Informational functions +*/ + +/* + * Copyright (c) 2007-2008 Roy Marples <roy@marples.name> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char libeinfo_copyright[] = "Copyright (c) 2007-2008 Roy Marples"; + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <syslog.h> +#ifdef HAVE_TERMCAP +# include <termcap.h> +#endif +#include <unistd.h> + +#include "einfo.h" +#include "helpers.h" + +/* Incase we cannot work out how many columns from ioctl, supply a default */ +#define DEFAULT_COLS 80 + +#define OK "ok" +#define NOT_OK "!!" + +/* Number of spaces for an indent */ +#define INDENT_WIDTH 2 + +/* How wide can the indent go? */ +#define INDENT_MAX 40 + +/* Default colours */ +#define GOOD 2 +#define WARN 3 +#define BAD 1 +#define HILITE 6 +#define BRACKET 4 + +/* We fallback to these escape codes if termcap isn't available + * like say /usr isn't mounted */ +#define AF "\033[3%dm" +#define CE "\033[K" +#define CH "\033[%dC" +#define MD "\033[1m" +#define ME "\033[m" +#define UP "\033[A" + +#define _GET_CAP(_d, _c) strlcpy(_d, tgoto(_c, 0, 0), sizeof(_d)); +#define _ASSIGN_CAP(_v) do { \ + _v = p; \ + p += strlcpy(p, tmp, sizeof(ebuffer) - (p - ebuffer)) + 1; \ + } while (0) + +/* A pointer to a string to prefix to einfo/ewarn/eerror messages */ +static const char *_eprefix = NULL; + +/* Buffers and structures to hold the final colours */ +static char ebuffer[100]; +struct ecolor { + ECOLOR color; + int def; + const char *name; +}; +static char nullstr = '\0'; + +static const struct ecolor ecolors[] = { + { ECOLOR_GOOD, GOOD, "good" }, + { ECOLOR_WARN, WARN, "warn" }, + { ECOLOR_BAD, BAD, "bad" }, + { ECOLOR_HILITE, HILITE, "hilite" }, + { ECOLOR_BRACKET, BRACKET, "bracket" }, + { ECOLOR_NORMAL, 0, NULL }, +}; +static const char *ecolors_str[ARRAY_SIZE(ecolors)]; + +static char *flush = NULL; +static char *up = NULL; +static char *goto_column = NULL; + +static const char *term = NULL; +static bool term_is_cons25 = false; + +/* Termcap buffers and pointers + * Static buffers suck hard, but some termcap implementations require them */ +#ifdef HAVE_TERMCAP +static char termcapbuf[2048]; +static char tcapbuf[512]; +#else +/* No curses support, so we hardcode a list of colour capable terms + * Only terminals without "color" in the name need to be explicitly listed */ +static const char *const color_terms[] = { + "Eterm", + "ansi", + "con132x25", + "con132x30", + "con132x43", + "con132x60", + "con80x25", + "con80x28", + "con80x30", + "con80x43", + "con80x50", + "con80x60", + "cons25", + "console", + "cygwin", + "dtterm", + "gnome", + "konsole", + "kterm", + "linux", + "linux-c", + "mlterm", + "putty", + "rxvt", + "rxvt-cygwin", + "rxvt-cygwin-native", + "rxvt-unicode", + "screen", + "screen-bce", + "screen-w", + "screen.linux", + "vt100", + "vt220", + "wsvt25", + "xterm", + "xterm-debian", + NULL +}; +#endif + +/* strlcat and strlcpy are nice, shame glibc does not define them */ +#ifdef __GLIBC__ +# if ! defined (__UCLIBC__) && ! defined (__dietlibc__) +static size_t +strlcat(char *dst, const char *src, size_t size) +{ + char *d = dst; + const char *s = src; + size_t src_n = size; + size_t dst_n; + + while (src_n-- != 0 && *d != '\0') + d++; + dst_n = d - dst; + src_n = size - dst_n; + + if (src_n == 0) + return dst_n + strlen(src); + + while (*s != '\0') { + if (src_n != 1) { + *d++ = *s; + src_n--; + } + s++; + } + *d = '\0'; + + return dst_n + (s - src); +} +# endif +#endif + +static bool +yesno(const char *value) +{ + if (!value) { + errno = ENOENT; + return false; + } + + if (strcasecmp(value, "yes") == 0 || + strcasecmp(value, "y") == 0 || + strcasecmp(value, "true") == 0 || + strcasecmp(value, "on") == 0 || + strcasecmp(value, "1") == 0) + return true; + + if (strcasecmp(value, "no") != 0 && + strcasecmp(value, "n") != 0 && + strcasecmp(value, "false") != 0 && + strcasecmp(value, "off") != 0 && + strcasecmp(value, "0") != 0) + errno = EINVAL; + + return false; +} + +static bool +noyes(const char *value) +{ + int serrno = errno; + bool retval; + + errno = 0; + retval = yesno(value); + if (errno == 0) { + retval = !retval; + errno = serrno; + } + + return retval; +} + +static bool +is_quiet(void) +{ + return yesno(getenv("EINFO_QUIET")); +} + +static bool +is_really_quiet(void) +{ + return yesno(getenv("EERROR_QUIET")); +} + +static bool +is_verbose(void) +{ + return yesno(getenv ("EINFO_VERBOSE")); +} + +/* Fake tgoto call - very crapy, but works for our needs */ +#ifndef HAVE_TERMCAP +static char * +tgoto(const char *cap, int col, int line) +{ + static char buf[20]; + char *p, *e, c, dbuf[6]; + int oncol = 0, which = line, i; + + p = buf; + e = p + sizeof(buf); + while ((c = *cap++)) { + if (c != '%' || ((c = *cap++) == '%')) { + *p++ = c; + if (p >= e) { + errno = E2BIG; + return NULL; + } + continue; + } + switch (c) { + case '3': + case '2': + case 'd': + i = 0; + do + dbuf[i++] = which % 10 | '0'; + while ((which /= 10)); + if (c != 'd') { + c -= '0'; + if (i > c) { + errno = EINVAL; + return NULL; + } + while (i < c) + dbuf[i++] = '0'; + } + if (p + i >= e) { + errno = E2BIG; + return NULL; + } + do + *p++ = dbuf[--i]; + while (i); + break; + case 'r': + oncol = 0; + break; + case 'i': + col++; + line++; + which++; + continue; + default: + errno = EINVAL; + return NULL; + } + + oncol = 1 - oncol; + which = oncol ? col : line; + } + *p = '\0'; + return buf; +} +#endif + +static bool +colour_terminal(FILE *f) +{ + static int in_colour = -1; + char *e, *ee, *end, *d, *p; + int c; + const char *_af = NULL, *_ce = NULL, *_ch = NULL; + const char *_md = NULL, *_me = NULL, *_up = NULL; + const char *bold; + char tmp[100]; + unsigned int i = 0; +#ifdef HAVE_TERMCAP + char *bp; +#endif + + if (f && !isatty(fileno(f))) + return false; + + if (noyes(getenv("EINFO_COLOR"))) + return false; + + if (in_colour == 0) + return false; + if (in_colour == 1) + return true; + + term_is_cons25 = false; + if (!term) { + term = getenv("TERM"); + if (!term) + return false; + } + if (strcmp(term, "cons25") == 0) + term_is_cons25 = true; + +#ifdef HAVE_TERMCAP + /* Check termcap to see if we can do colour or not */ + if (tgetent(termcapbuf, term) == 1) { + bp = tcapbuf; + _af = tgetstr("AF", &bp); + _ce = tgetstr("ce", &bp); + _ch = tgetstr("ch", &bp); + /* Our ch use also works with RI .... for now */ + if (!_ch) + _ch = tgetstr("RI", &bp); + _md = tgetstr("md", &bp); + _me = tgetstr("me", &bp); + _up = tgetstr("up", &bp); + } + + /* Cheat here as vanilla BSD has the whole termcap info in /usr + * which is not available to us when we boot */ + if (term_is_cons25 || strcmp(term, "wsvt25") == 0) { +#else + if (strstr(term, "color")) + in_colour = 1; + + while (color_terms[i] && in_colour != 1) { + if (strcmp(color_terms[i], term) == 0) { + in_colour = 1; + } + i++; + } + + if (in_colour != 1) { + in_colour = 0; + return false; + } +#endif + if (!_af) + _af = AF; + if (!_ce) + _ce = CE; + if (!_ch) + _ch = CH; + if (!_md) + _md = MD; + if (!_me) + _me = ME; + if (!_up) + _up = UP; +#ifdef HAVE_TERMCAP + } + + if (!_af || !_ce || !_me || !_md || !_up) { + in_colour = 0; + return false; + } + + /* Many termcap databases don't have ch or RI even though they + * do work */ + if (!_ch) + _ch = CH; +#endif + + /* Now setup our colours */ + p = ebuffer; + for (i = 0; i < ARRAY_SIZE(ecolors); ++i) { + tmp[0] = '\0'; + if (ecolors[i].name) { + bold = _md; + c = ecolors[i].def; + + /* See if the user wants to override the colour + * We use a :col;bold: format like 2;1: for bold green + * and 1;0: for a normal red */ + if ((e = getenv("EINFO_COLOR"))) { + ee = strstr(e, ecolors[i].name); + if (ee) + ee += strlen(ecolors[i].name); + + if (ee && *ee == '=') { + d = strdup(ee + 1); + if (d) { + end = strchr(d, ':'); + if (end) + *end = '\0'; + c = atoi(d); + end = strchr(d, ';'); + if (end && *++end == '0') + bold = _me; + free(d); + } + } + } + strlcpy(tmp, tgoto(bold, 0, 0), sizeof(tmp)); + strlcat(tmp, tgoto(_af, 0, c & 0x07), sizeof(tmp)); + } else + _GET_CAP(tmp, _me); + + if (tmp[0]) + _ASSIGN_CAP(ecolors_str[i]); + else + ecolors_str[i] = &nullstr; + } + + _GET_CAP(tmp, _ce); + _ASSIGN_CAP(flush); + _GET_CAP(tmp, _up); + _ASSIGN_CAP(up); + strlcpy(tmp, _ch, sizeof(tmp)); + _ASSIGN_CAP(goto_column); + + in_colour = 1; + return true; +} + +static int +get_term_columns(FILE *stream) +{ + struct winsize ws; + char *env = getenv("COLUMNS"); + char *p; + int i; + + if (env) { + i = strtoimax(env, &p, 10); + if (!*p) + return i; + } + + if (ioctl(fileno(stream), TIOCGWINSZ, &ws) == 0) + return ws.ws_col; + + return DEFAULT_COLS; +} + +void +eprefix(const char *prefix) +{ + _eprefix = prefix; +} + +static void EINFO_PRINTF(2, 0) +elogv(int level, const char *fmt, va_list ap) +{ + char *e = getenv("EINFO_LOG"); + va_list apc; + + if (fmt && e) { + closelog(); + openlog(e, LOG_PID, LOG_DAEMON); + va_copy(apc, ap); + vsyslog(level, fmt, apc); + va_end(apc); + closelog(); + } +} + +void +elog(int level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + elogv(level, fmt, ap); + va_end(ap); +} + +static int +_eindent(FILE *stream) +{ + char *env = getenv("EINFO_INDENT"); + int amount = 0; + char indent[INDENT_MAX]; + + if (env) { + errno = 0; + amount = strtoimax(env, NULL, 0); + if (errno != 0 || amount < 0) + amount = 0; + else if (amount > INDENT_MAX) + amount = INDENT_MAX; + + if (amount > 0) + memset(indent, ' ', (size_t)amount); + } + + /* Terminate it */ + memset(indent + amount, 0, 1); + return fprintf(stream, "%s", indent); +} + +static const char * +_ecolor(FILE *f, ECOLOR color) +{ + unsigned int i; + + if (!colour_terminal(f)) + return ""; + + for (i = 0; i < ARRAY_SIZE(ecolors); ++i) + if (ecolors[i].color == color) + return ecolors_str[i]; + return ""; +} + +const char * +ecolor(ECOLOR color) +{ + FILE *f = stdout; + + /* Try and guess a valid tty */ + if (!isatty(fileno(f))) { + f = stderr; + if (!isatty(fileno(f))) { + f = stdin; + if (!isatty(fileno(f))) + f = NULL; + } + } + + return _ecolor(f, color); +} + +#define LASTCMD(_cmd) { \ + unsetenv("EINFO_LASTCMD"); \ + setenv("EINFO_LASTCMD", _cmd, 1); \ + } + +static int EINFO_PRINTF(3, 0) + _einfo(FILE *f, ECOLOR color, const char *fmt, va_list va) +{ + int retval = 0; + char *last = getenv("EINFO_LASTCMD"); + va_list ap; + + if (last && + !colour_terminal(f) && + strcmp(last, "ewarn") != 0 && + last[strlen(last) - 1] == 'n') + fprintf(f, "\n"); + if (_eprefix) + fprintf(f, "%s%s%s|", _ecolor(f, color), _eprefix, _ecolor(f, ECOLOR_NORMAL)); + fprintf(f, " %s*%s ", _ecolor(f, color), _ecolor(f, ECOLOR_NORMAL)); + retval += _eindent(f); + va_copy(ap, va); + retval += vfprintf(f, fmt, ap) + 3; + va_end(ap); \ + if (colour_terminal(f)) + fprintf(f, "%s", flush); + return retval; +} + +#define _einfovn(fmt, ap) _einfo(stdout, ECOLOR_GOOD, fmt, ap) +#define _ewarnvn(fmt, ap) _einfo(stderr, ECOLOR_WARN, fmt, ap) +#define _eerrorvn(fmt, ap) _einfo(stderr, ECOLOR_BAD, fmt, ap) + +int +einfon(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || is_quiet()) + return 0; + va_start(ap, fmt); + retval = _einfovn(fmt, ap); + va_end(ap); + LASTCMD("einfon"); + return retval; +} + +int +ewarnn(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || is_quiet()) + return 0; + va_start(ap, fmt); + retval = _ewarnvn(fmt, ap); + va_end(ap); + LASTCMD("ewarnn"); + return retval; +} + +int +eerrorn(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || is_really_quiet()) + return 0; + va_start(ap, fmt); + retval = _eerrorvn(fmt, ap); + va_end(ap); + LASTCMD("errorn"); + return retval; +} + +int +einfo(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || is_quiet()) + return 0; + va_start(ap, fmt); + retval = _einfovn(fmt, ap); + retval += printf("\n"); + va_end(ap); + LASTCMD("einfo"); + return retval; +} + +int +ewarn(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || is_quiet()) + return 0; + va_start(ap, fmt); + elogv(LOG_WARNING, fmt, ap); + retval = _ewarnvn(fmt, ap); + retval += fprintf(stderr, "\n"); + va_end(ap); + LASTCMD("ewarn"); + return retval; +} + +void +ewarnx(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (fmt && !is_quiet()) { + va_start(ap, fmt); + elogv(LOG_WARNING, fmt, ap); + retval = _ewarnvn(fmt, ap); + va_end(ap); + retval += fprintf(stderr, "\n"); + } + exit(EXIT_FAILURE); +} + +int +eerror(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || is_really_quiet()) + return 0; + va_start(ap, fmt); + elogv(LOG_ERR, fmt, ap); + retval = _eerrorvn(fmt, ap); + va_end(ap); + retval += fprintf(stderr, "\n"); + LASTCMD("eerror"); + return retval; +} + +void +eerrorx(const char *fmt, ...) +{ + va_list ap; + + if (fmt && !is_really_quiet()) { + va_start(ap, fmt); + elogv(LOG_ERR, fmt, ap); + _eerrorvn(fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + } + exit(EXIT_FAILURE); +} + +int +ebegin(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || is_quiet()) + return 0; + va_start(ap, fmt); + retval = _einfovn(fmt, ap); + va_end(ap); + retval += printf(" ..."); + if (colour_terminal(stdout)) + retval += printf("\n"); + LASTCMD("ebegin"); + return retval; +} + +static void +_eend(FILE *fp, int col, ECOLOR color, const char *msg) +{ + int i; + int cols; + + if (!msg) + return; + + cols = get_term_columns(fp) - (strlen(msg) + 5); + + /* cons25 is special - we need to remove one char, otherwise things + * do not align properly at all. */ + if (!term) { + term = getenv("TERM"); + if (term && strcmp(term, "cons25") == 0) + term_is_cons25 = true; + else + term_is_cons25 = false; + } + if (term_is_cons25) + cols--; + + if (cols > 0 && colour_terminal(fp)) { + fprintf(fp, "%s%s %s[%s %s %s]%s\n", up, tgoto(goto_column, 0, cols), + ecolor(ECOLOR_BRACKET), ecolor(color), msg, + ecolor(ECOLOR_BRACKET), ecolor(ECOLOR_NORMAL)); + } else { + if (col > 0) + for (i = 0; i < cols - col; i++) + fprintf(fp, " "); + fprintf(fp, " [ %s ]\n", msg); + } +} + +static int EINFO_PRINTF(3, 0) +_do_eend(const char *cmd, int retval, + const char *fmt, va_list ap) +{ + int col = 0; + FILE *fp = stdout; + va_list apc; + + if (fmt && *fmt != '\0' && retval != 0) { + fp = stderr; + va_copy(apc, ap); + if (strcmp(cmd, "ewend") == 0) + col = _ewarnvn(fmt, apc); + else + col = _eerrorvn(fmt, apc); + col += fprintf(fp, "\n"); + va_end(apc); + } + _eend(fp, col, + retval == 0 ? ECOLOR_GOOD : ECOLOR_BAD, + retval == 0 ? OK : NOT_OK); + return retval; +} + +int +eend(int retval, const char *fmt, ...) +{ + va_list ap; + + if (is_quiet()) + return retval; + va_start(ap, fmt); + _do_eend("eend", retval, fmt, ap); + va_end(ap); + LASTCMD("eend"); + return retval; +} + +int +ewend(int retval, const char *fmt, ...) +{ + va_list ap; + + if (is_quiet()) + return retval; + va_start(ap, fmt); + _do_eend("ewend", retval, fmt, ap); + va_end(ap); + LASTCMD("ewend"); + return retval; +} + +void +ebracket(int col, ECOLOR color, const char *msg) +{ + _eend(stdout, col, color, msg); +} + +void +eindent(void) +{ + char *env = getenv("EINFO_INDENT"); + int amount = 0; + char num[10]; + + if (env) { + errno = 0; + amount = strtoimax(env, NULL, 0); + if (errno != 0) + amount = 0; + } + amount += INDENT_WIDTH; + if (amount > INDENT_MAX) + amount = INDENT_MAX; + snprintf(num, 10, "%08d", amount); + setenv("EINFO_INDENT", num, 1); +} + +void eoutdent(void) +{ + char *env = getenv("EINFO_INDENT"); + int amount = 0; + char num[10]; + int serrno = errno; + + if (!env) + return; + errno = 0; + amount = strtoimax(env, NULL, 0); + if (errno != 0) + amount = 0; + else + amount -= INDENT_WIDTH; + if (amount <= 0) + unsetenv("EINFO_INDENT"); + else { + snprintf(num, 10, "%08d", amount); + setenv("EINFO_INDENT", num, 1); + } + errno = serrno; +} + +int +einfovn(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || !is_verbose()) + return 0; + va_start(ap, fmt); + retval = _einfovn(fmt, ap); + va_end(ap); + LASTCMD("einfovn"); + return retval; +} + +int +ewarnvn(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || !is_verbose()) + return 0; + va_start(ap, fmt); + retval = _ewarnvn(fmt, ap); + va_end(ap); + LASTCMD("ewarnvn"); + return retval; +} + +int +einfov(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || !is_verbose()) + return 0; + va_start(ap, fmt); + retval = _einfovn(fmt, ap); + retval += printf("\n"); + va_end(ap); + LASTCMD("einfov"); + return retval; +} + +int +ewarnv(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || !is_verbose()) + return 0; + va_start(ap, fmt); + retval = _ewarnvn(fmt, ap); + retval += printf("\n"); + va_end(ap); + LASTCMD("ewarnv"); + return retval; +} + +int +ebeginv(const char *fmt, ...) +{ + int retval; + va_list ap; + + if (!fmt || !is_verbose()) + return 0; + + va_start(ap, fmt); + retval = _einfovn(fmt, ap); + retval += printf(" ..."); + if (colour_terminal(stdout)) + retval += printf("\n"); + va_end(ap); + LASTCMD("ebeginv"); + return retval; +} + +int +eendv(int retval, const char *fmt, ...) +{ + va_list ap; + + if (!is_verbose()) + return 0; + va_start(ap, fmt); + _do_eend("eendv", retval, fmt, ap); + va_end(ap); + LASTCMD("eendv"); + return retval; +} + +int +ewendv(int retval, const char *fmt, ...) +{ + va_list ap; + + if (!is_verbose()) + return 0; + va_start(ap, fmt); + _do_eend("ewendv", retval, fmt, ap); + va_end(ap); + LASTCMD("ewendv"); + return retval; +} + +void +eindentv(void) +{ + if (is_verbose()) + eindent(); +} + +void +eoutdentv(void) +{ + if (is_verbose()) + eoutdent(); +} |