aboutsummaryrefslogtreecommitdiff
path: root/src/rc/einfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rc/einfo.c')
-rw-r--r--src/rc/einfo.c1016
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();
+}