/* einfo.c Informational functions */ /* * Copyright 2007-2008 Roy Marples * All rights reserved * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_TERMCAP # include #endif #include #include "einfo.h" #include "hidden-visibility.h" hidden_proto(ecolor) hidden_proto(ebegin) hidden_proto(ebeginv) hidden_proto(ebracket) hidden_proto(eend) hidden_proto(eendv) hidden_proto(eerror) hidden_proto(eerrorn) hidden_proto(eerrorx) hidden_proto(eindent) hidden_proto(eindentv) hidden_proto(einfo) hidden_proto(einfon) hidden_proto(einfov) hidden_proto(einfovn) hidden_proto(elog) hidden_proto(eoutdent) hidden_proto(eoutdentv) hidden_proto(eprefix) hidden_proto(ewarn) hidden_proto(ewarnn) hidden_proto(ewarnv) hidden_proto(ewarnvn) hidden_proto(ewarnx) hidden_proto(ewend) hidden_proto(ewendv) /* 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) { \ _v = p; \ p += strlcpy (p, tmp, sizeof (ebuffer) - (p - ebuffer)) + 1; \ } /* 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 { einfo_color_t 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[sizeof(ecolors)/sizeof(ecolors[0])]; 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 */ static const char *const color_terms[] = { "Eterm", "ansi", "color-xterm", "con132x25", "con132x30", "con132x43", "con132x60", "con80x25", "con80x28", "con80x30", "con80x43", "con80x50", "con80x60", "cons25", "console", "cygwin", "dtterm", "gnome", "konsole", "kterm", "linux", "linux-c", "mach-color", "mlterm", "putty", "rxvt", "rxvt-cygwin", "rxvt-cygwin-native", "rxvt-unicode", "screen", "screen-bce", "screen-w", "screen.linux", "vt100", "vt220", "wsvt25", "xterm", "xterm-256color", "xterm-color", "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)); } static size_t strlcpy (char *dst, const char *src, size_t size) { const char *s = src; size_t n = size; if (n && --n) do { if (! (*dst++ = *src++)) break; } while (--n); if (! n) { if (size) *dst = '\0'; while (*src++); } return (src - s - 1); } # 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() { return (yesno (getenv ("EINFO_QUIET"))); } static bool is_verbose() { 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 a, int b) { static char buf[20]; snprintf (buf, sizeof (buf), cap, b, a); return (buf); } #endif static bool colour_terminal (FILE * __EINFO_RESTRICT f) { static int in_colour = -1; char *e; int c; const char *_af = NULL; const char *_ce = NULL; const char *_ch = NULL; const char *_md = NULL; const char *_me = NULL; const char *_up = NULL; char tmp[100]; char *p; unsigned int i = 0; 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) { char *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 while (color_terms[i]) { 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 < sizeof (ecolors) / sizeof (ecolors[0]); i++) { tmp[0] = '\0'; if (ecolors[i].name) { const char *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"))) { char *ee = strstr (e, ecolors[i].name); if (ee) ee += strlen (ecolors[i].name); if (ee && *ee == '=') { char *d = strdup (ee + 1); if (d) { char *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 * __EINFO_RESTRICT 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 *__EINFO_RESTRICT prefix) { _eprefix = prefix; } hidden_def(eprefix) static void elogv (int level, const char *__EINFO_RESTRICT 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 *__EINFO_RESTRICT fmt, ...) { va_list ap; va_start (ap, fmt); elogv (level, fmt, ap); va_end (ap); } hidden_def(elog) static int _eindent (FILE * __EINFO_RESTRICT 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 * __EINFO_RESTRICT f, einfo_color_t color) { unsigned int i; if (! colour_terminal (f)) return (""); for (i = 0; i < sizeof (ecolors) / sizeof (ecolors[0]); i++) { if (ecolors[i].color == color) return (ecolors_str[i]); } return (""); } hidden_def(ecolor) const char *ecolor (einfo_color_t 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); \ } #define EINFOVN(_file, _color) \ { \ char *_e = getenv ("EINFO_LASTCMD"); \ if (_e && ! colour_terminal (_file) && strcmp (_e, "ewarn") != 0 && \ _e[strlen (_e) - 1] == 'n') \ fprintf (_file, "\n"); \ if (_eprefix) \ fprintf (_file, "%s%s%s|", _ecolor (_file, _color), _eprefix, _ecolor (_file, ECOLOR_NORMAL)); \ fprintf (_file, " %s*%s ", _ecolor (_file, _color), _ecolor (_file, ECOLOR_NORMAL)); \ retval += _eindent (_file); \ { \ va_list _ap; \ va_copy (_ap, ap); \ retval += vfprintf (_file, fmt, _ap) + 3; \ va_end (_ap); \ } \ if (colour_terminal (_file)) \ fprintf (_file, "%s", flush); \ } static int _einfovn (const char *__EINFO_RESTRICT fmt, va_list ap) { int retval = 0; EINFOVN (stdout, ECOLOR_GOOD); return (retval); } static int _ewarnvn (const char *__EINFO_RESTRICT fmt, va_list ap) { int retval = 0; EINFOVN (stderr, ECOLOR_WARN); return (retval); } static int _eerrorvn (const char *__EINFO_RESTRICT fmt, va_list ap) { int retval = 0; EINFOVN (stderr, ECOLOR_BAD); return (retval); } int einfon (const char *__EINFO_RESTRICT 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); } hidden_def(einfon) int ewarnn (const char *__EINFO_RESTRICT 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); } hidden_def(ewarnn) int eerrorn (const char *__EINFO_RESTRICT fmt, ...) { int retval; va_list ap; va_start (ap, fmt); retval = _eerrorvn (fmt, ap); va_end (ap); LASTCMD ("errorn"); return (retval); } hidden_def(eerrorn) int einfo (const char *__EINFO_RESTRICT 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); } hidden_def(einfo) int ewarn (const char *__EINFO_RESTRICT 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); } hidden_def(ewarn) void ewarnx (const char *__EINFO_RESTRICT 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); } hidden_def(ewarnx) int eerror (const char *__EINFO_RESTRICT fmt, ...) { int retval; va_list ap; if (! fmt) 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); } hidden_def(eerror) void eerrorx (const char *__EINFO_RESTRICT fmt, ...) { va_list ap; if (fmt) { va_start (ap, fmt); elogv (LOG_ERR, fmt, ap); _eerrorvn (fmt, ap); va_end (ap); fprintf (stderr, "\n"); } exit (EXIT_FAILURE); } hidden_def(eerrorx) int ebegin (const char *__EINFO_RESTRICT 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); } hidden_def(ebegin) static void _eend (FILE * __EINFO_RESTRICT fp, int col, einfo_color_t color, const char *msg) { int i; int cols; if (! msg) return; cols = get_term_columns (fp) - (strlen (msg) + 3); /* 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 _do_eend (const char *cmd, int retval, const char *__EINFO_RESTRICT 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 *__EINFO_RESTRICT 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); } hidden_def(eend) int ewend (int retval, const char *__EINFO_RESTRICT 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); } hidden_def(ewend) void ebracket (int col, einfo_color_t color, const char *msg) { _eend (stdout, col, color, msg); } hidden_def(ebracket) 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); } hidden_def(eindent) 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; } hidden_def(eoutdent) int einfovn (const char *__EINFO_RESTRICT 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); } hidden_def(einfovn) int ewarnvn (const char *__EINFO_RESTRICT 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); } hidden_def(ewarnvn) int einfov (const char *__EINFO_RESTRICT 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); } hidden_def(einfov) int ewarnv (const char *__EINFO_RESTRICT 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); } hidden_def(ewarnv) int ebeginv (const char *__EINFO_RESTRICT 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); } hidden_def(ebeginv) int eendv (int retval, const char *__EINFO_RESTRICT 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); } hidden_def(eendv) int ewendv (int retval, const char *__EINFO_RESTRICT 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); } hidden_def(ewendv) void eindentv (void) { if (is_verbose ()) eindent (); } hidden_def(eindentv) void eoutdentv (void) { if (is_verbose ()) eoutdent (); } hidden_def(eoutdentv)