summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.inc16
-rw-r--r--gnu/lib/libdialog/Makefile3
-rw-r--r--lib/Makefile4
-rw-r--r--lib/libdpv/Makefile18
-rw-r--r--lib/libdpv/dialog_util.c633
-rw-r--r--lib/libdpv/dialog_util.h73
-rw-r--r--lib/libdpv/dialogrc.c359
-rw-r--r--lib/libdpv/dialogrc.h56
-rw-r--r--lib/libdpv/dprompt.c770
-rw-r--r--lib/libdpv/dprompt.h59
-rw-r--r--lib/libdpv/dpv.3510
-rw-r--r--lib/libdpv/dpv.c721
-rw-r--r--lib/libdpv/dpv.h161
-rw-r--r--lib/libdpv/dpv_private.h66
-rw-r--r--lib/libdpv/status.c111
-rw-r--r--lib/libdpv/status.h43
-rw-r--r--lib/libdpv/util.c107
-rw-r--r--lib/libdpv/util.h50
-rw-r--r--lib/libfigpar/Makefile21
-rw-r--r--lib/libfigpar/figpar.3251
-rw-r--r--lib/libfigpar/figpar.c469
-rw-r--r--lib/libfigpar/figpar.h99
-rw-r--r--lib/libfigpar/string_m.c309
-rw-r--r--lib/libfigpar/string_m.h43
-rw-r--r--share/mk/bsd.libnames.mk2
-rw-r--r--sys/sys/param.h2
-rw-r--r--usr.bin/Makefile1
-rw-r--r--usr.bin/dpv/Makefile12
-rw-r--r--usr.bin/dpv/dpv.1430
-rw-r--r--usr.bin/dpv/dpv.c541
-rw-r--r--usr.bin/dpv/dpv_util.h68
31 files changed, 5995 insertions, 3 deletions
diff --git a/Makefile.inc1 b/Makefile.inc1
index 79c9b59..1a13c88 100644
--- a/Makefile.inc1
+++ b/Makefile.inc1
@@ -1510,6 +1510,7 @@ _prebuild_libs= ${_kerberos5_lib_libasn1} \
${_lib_atf} \
lib/libbz2 ${_libcom_err} lib/libcrypt \
lib/libelf lib/libexpat \
+ lib/libfigpar \
${_lib_libgssapi} ${_lib_libipx} \
lib/libkiconv lib/libkvm lib/liblzma lib/libmd \
lib/ncurses/ncurses lib/ncurses/ncursesw \
@@ -1520,7 +1521,8 @@ _prebuild_libs= ${_kerberos5_lib_libasn1} \
${_cddl_lib_libzfs_core} \
lib/libutil ${_lib_libypclnt} lib/libz lib/msun \
${_secure_lib_libcrypto} ${_lib_libldns} \
- ${_secure_lib_libssh} ${_secure_lib_libssl}
+ ${_secure_lib_libssh} ${_secure_lib_libssl} \
+ gnu/lib/libdialog
.if ${MK_GNUCXX} != no
_prebuild_libs+= gnu/lib/libstdc++ gnu/lib/libsupc++
gnu/lib/libstdc++__L: lib/msun__L
@@ -1637,6 +1639,8 @@ _lib_libypclnt= lib/libypclnt
lib/libradius__L: lib/libmd__L
.endif
+gnu/lib/libdialog__L: lib/msun__L lib/ncurses/ncursesw__L
+
.for _lib in ${_prereq_libs}
${_lib}__PL: .PHONY .MAKE
.if exists(${.CURDIR}/${_lib})
diff --git a/gnu/lib/libdialog/Makefile b/gnu/lib/libdialog/Makefile
index 395b02d..3945cc0 100644
--- a/gnu/lib/libdialog/Makefile
+++ b/gnu/lib/libdialog/Makefile
@@ -13,6 +13,9 @@ SRCS= argv.c arrows.c buildlist.c buttons.c calendar.c checklist.c \
INCS= dialog.h dlg_colors.h dlg_config.h dlg_keys.h
MAN= dialog.3
+DPADD= ${LIBNCURSESW} ${LIBM}
+LDADD= -lncursesw -lm
+
CFLAGS+= -I${.CURDIR} -I${DIALOG} -D_XOPEN_SOURCE_EXTENDED -DGCC_UNUSED=__unused
.PATH: ${DIALOG}
WARNS?= 1
diff --git a/lib/Makefile b/lib/Makefile
index 0f8f9da..cd844db 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -39,12 +39,14 @@ SUBDIR= ${SUBDIR_ORDERED} \
libcrypt \
libdevinfo \
libdevstat \
+ libdpv \
libdwarf \
libedit \
${_libefi} \
libexecinfo \
libexpat \
libfetch \
+ libfigpar \
libgeom \
${_libgpib} \
${_libgssapi} \
@@ -121,7 +123,7 @@ SUBDIR_DEPEND_libc++= libcxxrt
SUBDIR_DEPEND_libc= libcompiler_rt
SUBDIR_DEPEND_libcam= libsbuf
SUBDIR_DEPEND_libdevstat= libkvm
-SUBDIR_DEPEND_libdiaglog= ncurses
+SUBDIR_DEPEND_libdpv= libfigpar ncurses libutil
SUBDIR_DEPEND_libedit= ncurses
SUBDIR_DEPEND_libg++= msun
SUBDIR_DEPEND_libgeom= libexpat libsbuf
diff --git a/lib/libdpv/Makefile b/lib/libdpv/Makefile
new file mode 100644
index 0000000..8096cc9
--- /dev/null
+++ b/lib/libdpv/Makefile
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+LIB= dpv
+SHLIB_MAJOR= 1
+INCS= dpv.h
+MAN= dpv.3
+MLINKS= dpv.3 dpv_free.3
+
+DPADD= ${LIBDIALOG} ${LIBFIGPAR} ${LIBNCURSESW} ${LIBUTIL}
+LDADD= -ldialog -lfigpar -lncursesw -lutil
+
+SRCS= dialog_util.c dialogrc.c dprompt.c dpv.c status.c util.c
+
+CFLAGS+= -I${.CURDIR}
+
+WARNS?= 6
+
+.include <bsd.lib.mk>
diff --git a/lib/libdpv/dialog_util.c b/lib/libdpv/dialog_util.c
new file mode 100644
index 0000000..d047a25
--- /dev/null
+++ b/lib/libdpv/dialog_util.c
@@ -0,0 +1,633 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "dialog_util.h"
+#include "dpv.h"
+#include "dpv_private.h"
+
+extern char **environ;
+
+#define TTY_DEFAULT_ROWS 24
+#define TTY_DEFAULT_COLS 80
+
+/* [X]dialog(1) characteristics */
+uint8_t dialog_test = 0;
+uint8_t use_dialog = 0;
+uint8_t use_libdialog = 1;
+uint8_t use_xdialog = 0;
+uint8_t use_color = 1;
+char dialog[PATH_MAX] = DIALOG;
+
+/* [X]dialog(1) functionality */
+char *title = NULL;
+char *backtitle = NULL;
+int dheight = 0;
+int dwidth = 0;
+static char *dargv[64] = { NULL };
+
+/* TTY/Screen characteristics */
+static struct winsize *maxsize = NULL;
+
+/* Function prototypes */
+static void tty_maxsize_update(void);
+static void x11_maxsize_update(void);
+
+/*
+ * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
+ * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
+ * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
+ * maximum height and width (respectively) for a dialog(1) widget based on the
+ * active TTY size.
+ *
+ * This function is called automatically by dialog_maxrows/cols() to reflect
+ * changes in terminal size in-between calls.
+ */
+static void
+tty_maxsize_update(void)
+{
+ int fd = STDIN_FILENO;
+ struct termios t;
+
+ if (maxsize == NULL) {
+ if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ memset((void *)maxsize, '\0', sizeof(struct winsize));
+ }
+
+ if (!isatty(fd))
+ fd = open("/dev/tty", O_RDONLY);
+ if ((tcgetattr(fd, &t) < 0) || (ioctl(fd, TIOCGWINSZ, maxsize) < 0)) {
+ maxsize->ws_row = TTY_DEFAULT_ROWS;
+ maxsize->ws_col = TTY_DEFAULT_COLS;
+ }
+}
+
+/*
+ * Update row/column fields of `maxsize' global (used by dialog_maxrows() and
+ * dialog_maxcols()). If the `maxsize' pointer is NULL, it will be initialized.
+ * The `ws_row' and `ws_col' fields of `maxsize' are updated to hold current
+ * maximum height and width (respectively) for an Xdialog(1) widget based on
+ * the active video resolution of the X11 environment.
+ *
+ * This function is called automatically by dialog_maxrows/cols() to initialize
+ * `maxsize'. Since video resolution changes are less common and more obtrusive
+ * than changes to terminal size, the dialog_maxrows/cols() functions only call
+ * this function when `maxsize' is set to NULL.
+ */
+static void
+x11_maxsize_update(void)
+{
+ FILE *f = NULL;
+ char *cols;
+ char *cp;
+ char *rows;
+ char cmdbuf[LINE_MAX];
+ char rbuf[LINE_MAX];
+
+ if (maxsize == NULL) {
+ if ((maxsize = malloc(sizeof(struct winsize))) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ memset((void *)maxsize, '\0', sizeof(struct winsize));
+ }
+
+ /* Assemble the command necessary to get X11 sizes */
+ snprintf(cmdbuf, LINE_MAX, "%s --print-maxsize 2>&1", dialog);
+
+ fflush(STDIN_FILENO); /* prevent popen(3) from seeking on stdin */
+
+ if ((f = popen(cmdbuf, "r")) == NULL) {
+ if (debug)
+ warnx("WARNING! Command `%s' failed", cmdbuf);
+ return;
+ }
+
+ /* Read in the line returned from Xdialog(1) */
+ if ((fgets(rbuf, LINE_MAX, f) == NULL) || (pclose(f) < 0))
+ return;
+
+ /* Check for X11-related errors */
+ if (strncmp(rbuf, "Xdialog: Error", 14) == 0)
+ return;
+
+ /* Parse expected output: MaxSize: YY, XXX */
+ if ((rows = strchr(rbuf, ' ')) == NULL)
+ return;
+ if ((cols = strchr(rows, ',')) != NULL) {
+ /* strtonum(3) doesn't like trailing junk */
+ *(cols++) = '\0';
+ if ((cp = strchr(cols, '\n')) != NULL)
+ *cp = '\0';
+ }
+
+ /* Convert to unsigned short */
+ maxsize->ws_row = (unsigned short)strtonum(
+ rows, 0, USHRT_MAX, (const char **)NULL);
+ maxsize->ws_col = (unsigned short)strtonum(
+ cols, 0, USHRT_MAX, (const char **)NULL);
+}
+
+/*
+ * Return the current maximum height (rows) for an [X]dialog(1) widget.
+ */
+int
+dialog_maxrows(void)
+{
+
+ if (use_xdialog && maxsize == NULL)
+ x11_maxsize_update(); /* initialize maxsize for GUI */
+ else if (!use_xdialog)
+ tty_maxsize_update(); /* update maxsize for TTY */
+ return (maxsize->ws_row);
+}
+
+/*
+ * Return the current maximum width (cols) for an [X]dialog(1) widget.
+ */
+int
+dialog_maxcols(void)
+{
+
+ if (use_xdialog && maxsize == NULL)
+ x11_maxsize_update(); /* initialize maxsize for GUI */
+ else if (!use_xdialog)
+ tty_maxsize_update(); /* update maxsize for TTY */
+
+ if (use_dialog || use_libdialog) {
+ if (use_shadow)
+ return (maxsize->ws_col - 2);
+ else
+ return (maxsize->ws_col);
+ } else
+ return (maxsize->ws_col);
+}
+
+/*
+ * Return the current maximum width (cols) for the terminal.
+ */
+int
+tty_maxcols(void)
+{
+
+ if (use_xdialog && maxsize == NULL)
+ x11_maxsize_update(); /* initialize maxsize for GUI */
+ else if (!use_xdialog)
+ tty_maxsize_update(); /* update maxsize for TTY */
+
+ return (maxsize->ws_col);
+}
+
+/*
+ * Spawn an [X]dialog(1) `--gauge' box with a `--prompt' value of init_prompt.
+ * Writes the resulting process ID to the pid_t pointed at by `pid'. Returns a
+ * file descriptor (int) suitable for writing data to the [X]dialog(1) instance
+ * (data written to the file descriptor is seen as standard-in by the spawned
+ * [X]dialog(1) process).
+ */
+int
+dialog_spawn_gauge(char *init_prompt, pid_t *pid)
+{
+ char dummy_init[2] = "";
+ char *cp;
+ int height;
+ int width;
+ int error;
+ posix_spawn_file_actions_t action;
+#if DIALOG_SPAWN_DEBUG
+ unsigned int i;
+#endif
+ unsigned int n = 0;
+ int stdin_pipe[2] = { -1, -1 };
+
+ /* Override `dialog' with a path from ENV_DIALOG if provided */
+ if ((cp = getenv(ENV_DIALOG)) != NULL)
+ snprintf(dialog, PATH_MAX, "%s", cp);
+
+ /* For Xdialog(1), set ENV_XDIALOG_HIGH_DIALOG_COMPAT */
+ setenv(ENV_XDIALOG_HIGH_DIALOG_COMPAT, "1", 1);
+
+ /* Constrain the height/width */
+ height = dialog_maxrows();
+ if (backtitle != NULL)
+ height -= use_shadow ? 5 : 4;
+ if (dheight < height)
+ height = dheight;
+ width = dialog_maxcols();
+ if (dwidth < width)
+ width = dwidth;
+
+ /* Populate argument array */
+ dargv[n++] = dialog;
+ if (title != NULL) {
+ if ((dargv[n] = malloc(8)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "--title");
+ dargv[n++] = title;
+ }
+ if (backtitle != NULL) {
+ if ((dargv[n] = malloc(12)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "--backtitle");
+ dargv[n++] = backtitle;
+ }
+ if (use_color) {
+ if ((dargv[n] = malloc(11)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "--colors");
+ }
+ if (use_xdialog) {
+ if ((dargv[n] = malloc(7)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "--left");
+
+ /*
+ * NOTE: Xdialog(1)'s `--wrap' appears to be broken for the
+ * `--gauge' widget prompt-updates. Add it anyway (in-case it
+ * gets fixed in some later release).
+ */
+ if ((dargv[n] = malloc(7)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "--wrap");
+ }
+ if ((dargv[n] = malloc(8)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "--gauge");
+ dargv[n++] = use_xdialog ? dummy_init : init_prompt;
+ if ((dargv[n] = malloc(40)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ snprintf(dargv[n++], 40, "%u", height);
+ if ((dargv[n] = malloc(40)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ snprintf(dargv[n++], 40, "%u", width);
+ dargv[n] = NULL;
+
+ /* Open a pipe(2) to communicate with [X]dialog(1) */
+ if (pipe(stdin_pipe) < 0)
+ err(EXIT_FAILURE, "%s: pipe(2)", __func__);
+
+ /* Fork [X]dialog(1) process */
+#if DIALOG_SPAWN_DEBUG
+ fprintf(stderr, "%s: spawning `", __func__);
+ for (i = 0; i < n; i++) {
+ if (i == 0)
+ fprintf(stderr, "%s", dargv[i]);
+ else if (*dargv[i] == '-' && *(dargv[i] + 1) == '-')
+ fprintf(stderr, " %s", dargv[i]);
+ else
+ fprintf(stderr, " \"%s\"", dargv[i]);
+ }
+ fprintf(stderr, "'\n");
+#endif
+ posix_spawn_file_actions_init(&action);
+ posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
+ posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
+ error = posix_spawnp(pid, dialog, &action,
+ (const posix_spawnattr_t *)NULL, dargv, environ);
+ if (error != 0)
+ err(EXIT_FAILURE, "%s: posix_spawnp(3)", __func__);
+
+ /* NB: Do not free(3) *dargv[], else SIGSEGV */
+
+ return (stdin_pipe[1]);
+}
+
+/*
+ * Returns the number of lines in buffer pointed to by `prompt'. Takes both
+ * newlines and escaped-newlines into account.
+ */
+unsigned int
+dialog_prompt_numlines(const char *prompt, uint8_t nlstate)
+{
+ uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
+ const char *cp = prompt;
+ unsigned int nlines = 1;
+
+ if (prompt == NULL || *prompt == '\0')
+ return (0);
+
+ while (*cp != '\0') {
+ if (use_dialog) {
+ if (strncmp(cp, "\\n", 2) == 0) {
+ cp++;
+ nlines++;
+ nls = TRUE; /* See declaration comment */
+ } else if (*cp == '\n') {
+ if (!nls)
+ nlines++;
+ nls = FALSE; /* See declaration comment */
+ }
+ } else if (use_libdialog) {
+ if (*cp == '\n')
+ nlines++;
+ } else if (strncmp(cp, "\\n", 2) == 0) {
+ cp++;
+ nlines++;
+ }
+ cp++;
+ }
+
+ return (nlines);
+}
+
+/*
+ * Returns the length in bytes of the longest line in buffer pointed to by
+ * `prompt'. Takes newlines and escaped newlines into account. Also discounts
+ * dialog(1) color escape codes if enabled (via `use_color' global).
+ */
+unsigned int
+dialog_prompt_longestline(const char *prompt, uint8_t nlstate)
+{
+ uint8_t backslash = 0;
+ uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
+ const char *p = prompt;
+ int longest = 0;
+ int n = 0;
+
+ /* `prompt' parameter is required */
+ if (prompt == NULL)
+ return (0);
+ if (*prompt == '\0')
+ return (0); /* shortcut */
+
+ /* Loop until the end of the string */
+ while (*p != '\0') {
+ /* dialog(1) and dialog(3) will render literal newlines */
+ if (use_dialog || use_libdialog) {
+ if (*p == '\n') {
+ if (!use_libdialog && nls)
+ n++;
+ else {
+ if (n > longest)
+ longest = n;
+ n = 0;
+ }
+ nls = FALSE; /* See declaration comment */
+ p++;
+ continue;
+ }
+ }
+
+ /* Check for backslash character */
+ if (*p == '\\') {
+ /* If second backslash, count as a single-char */
+ if ((backslash ^= 1) == 0)
+ n++;
+ } else if (backslash) {
+ if (*p == 'n' && !use_libdialog) { /* new line */
+ /* NB: dialog(3) ignores escaped newlines */
+ nls = TRUE; /* See declaration comment */
+ if (n > longest)
+ longest = n;
+ n = 0;
+ } else if (use_color && *p == 'Z') {
+ if (*++p != '\0')
+ p++;
+ backslash = 0;
+ continue;
+ } else /* [X]dialog(1)/dialog(3) only expand those */
+ n += 2;
+
+ backslash = 0;
+ } else
+ n++;
+ p++;
+ }
+ if (n > longest)
+ longest = n;
+
+ return (longest);
+}
+
+/*
+ * Returns a pointer to the last line in buffer pointed to by `prompt'. Takes
+ * both newlines (if using dialog(1) versus Xdialog(1)) and escaped newlines
+ * into account. If no newlines (escaped or otherwise) appear in the buffer,
+ * `prompt' is returned. If passed a NULL pointer, returns NULL.
+ */
+char *
+dialog_prompt_lastline(char *prompt, uint8_t nlstate)
+{
+ uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
+ char *lastline;
+ char *p;
+
+ if (prompt == NULL)
+ return (NULL);
+ if (*prompt == '\0')
+ return (prompt); /* shortcut */
+
+ lastline = p = prompt;
+ while (*p != '\0') {
+ /* dialog(1) and dialog(3) will render literal newlines */
+ if (use_dialog || use_libdialog) {
+ if (*p == '\n') {
+ if (use_libdialog || !nls)
+ lastline = p + 1;
+ nls = FALSE; /* See declaration comment */
+ }
+ }
+ /* dialog(3) does not expand escaped newlines */
+ if (use_libdialog) {
+ p++;
+ continue;
+ }
+ if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') {
+ nls = TRUE; /* See declaration comment */
+ lastline = p + 1;
+ }
+ p++;
+ }
+
+ return (lastline);
+}
+
+/*
+ * Returns the number of extra lines generated by wrapping the text in buffer
+ * pointed to by `prompt' within `ncols' columns (for prompts, this should be
+ * dwidth - 4). Also discounts dialog(1) color escape codes if enabled (via
+ * `use_color' global).
+ */
+int
+dialog_prompt_wrappedlines(char *prompt, int ncols, uint8_t nlstate)
+{
+ uint8_t backslash = 0;
+ uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
+ char *cp;
+ char *p = prompt;
+ int n = 0;
+ int wlines = 0;
+
+ /* `prompt' parameter is required */
+ if (p == NULL)
+ return (0);
+ if (*p == '\0')
+ return (0); /* shortcut */
+
+ /* Loop until the end of the string */
+ while (*p != '\0') {
+ /* dialog(1) and dialog(3) will render literal newlines */
+ if (use_dialog || use_libdialog) {
+ if (*p == '\n') {
+ if (use_dialog || !nls)
+ n = 0;
+ nls = FALSE; /* See declaration comment */
+ }
+ }
+
+ /* Check for backslash character */
+ if (*p == '\\') {
+ /* If second backslash, count as a single-char */
+ if ((backslash ^= 1) == 0)
+ n++;
+ } else if (backslash) {
+ if (*p == 'n' && !use_libdialog) { /* new line */
+ /* NB: dialog(3) ignores escaped newlines */
+ nls = TRUE; /* See declaration comment */
+ n = 0;
+ } else if (use_color && *p == 'Z') {
+ if (*++p != '\0')
+ p++;
+ backslash = 0;
+ continue;
+ } else /* [X]dialog(1)/dialog(3) only expand those */
+ n += 2;
+
+ backslash = 0;
+ } else
+ n++;
+
+ /* Did we pass the width barrier? */
+ if (n > ncols) {
+ /*
+ * Work backward to find the first whitespace on-which
+ * dialog(1) will wrap the line (but don't go before
+ * the start of this line).
+ */
+ cp = p;
+ while (n > 1 && !isspace(*cp)) {
+ cp--;
+ n--;
+ }
+ if (n > 0 && isspace(*cp))
+ p = cp;
+ wlines++;
+ n = 1;
+ }
+
+ p++;
+ }
+
+ return (wlines);
+}
+
+/*
+ * Returns zero if the buffer pointed to by `prompt' contains an escaped
+ * newline but only if appearing after any/all literal newlines. This is
+ * specific to dialog(1) and does not apply to Xdialog(1).
+ *
+ * As an attempt to make shell scripts easier to read, dialog(1) will "eat"
+ * the first literal newline after an escaped newline. This however has a bug
+ * in its implementation in that rather than allowing `\\n\n' to be treated
+ * similar to `\\n' or `\n', dialog(1) expands the `\\n' and then translates
+ * the following literal newline (with or without characters between [!]) into
+ * a single space.
+ *
+ * If you want to be compatible with Xdialog(1), it is suggested that you not
+ * use literal newlines (they aren't supported); but if you have to use them,
+ * go right ahead. But be forewarned... if you set $DIALOG in your environment
+ * to something other than `cdialog' (our current dialog(1)), then it should
+ * do the same thing w/respect to how to handle a literal newline after an
+ * escaped newline (you could do no wrong by translating every literal newline
+ * into a space but only when you've previously encountered an escaped one;
+ * this is what dialog(1) is doing).
+ *
+ * The ``newline state'' (or nlstate for short; as I'm calling it) is helpful
+ * if you plan to combine multiple strings into a single prompt text. In lead-
+ * up to this procedure, a common task is to calculate and utilize the widths
+ * and heights of each piece of prompt text to later be combined. However, if
+ * (for example) the first string ends in a positive newline state (has an
+ * escaped newline without trailing literal), the first literal newline in the
+ * second string will be mangled.
+ *
+ * The return value of this function should be used as the `nlstate' argument
+ * to dialog_*() functions that require it to allow accurate calculations in
+ * the event such information is needed.
+ */
+uint8_t
+dialog_prompt_nlstate(const char *prompt)
+{
+ const char *cp;
+
+ if (prompt == NULL)
+ return 0;
+
+ /*
+ * Work our way backward from the end of the string for efficiency.
+ */
+ cp = prompt + strlen(prompt);
+ while (--cp >= prompt) {
+ /*
+ * If we get to a literal newline first, this prompt ends in a
+ * clean state for rendering with dialog(1). Otherwise, if we
+ * get to an escaped newline first, this prompt ends in an un-
+ * clean state (following literal will be mangled; see above).
+ */
+ if (*cp == '\n')
+ return (0);
+ else if (*cp == 'n' && --cp > prompt && *cp == '\\')
+ return (1);
+ }
+
+ return (0); /* no newlines (escaped or otherwise) */
+}
+
+/*
+ * Free allocated items initialized by tty_maxsize_update() and
+ * x11_maxsize_update()
+ */
+void
+dialog_maxsize_free(void)
+{
+ if (maxsize != NULL) {
+ free(maxsize);
+ maxsize = NULL;
+ }
+}
diff --git a/lib/libdpv/dialog_util.h b/lib/libdpv/dialog_util.h
new file mode 100644
index 0000000..e279c3f
--- /dev/null
+++ b/lib/libdpv/dialog_util.h
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DIALOG_UTIL_H_
+#define _DIALOG_UTIL_H_
+
+#include <sys/types.h>
+
+#include "dialogrc.h"
+
+#define DIALOG_SPAWN_DEBUG 0 /* Debug spawning of [X]dialog(1) */
+
+/* dialog(3) and [X]dialog(1) characteristics */
+#define DIALOG "dialog"
+#define XDIALOG "Xdialog"
+#define PROMPT_MAX 16384
+#define ENV_DIALOG "DIALOG"
+#define ENV_USE_COLOR "USE_COLOR"
+#define ENV_XDIALOG_HIGH_DIALOG_COMPAT "XDIALOG_HIGH_DIALOG_COMPAT"
+extern uint8_t dialog_test;
+extern uint8_t use_libdialog;
+extern uint8_t use_dialog;
+extern uint8_t use_xdialog;
+extern uint8_t use_color;
+extern char dialog[];
+
+/* dialog(3) and [X]dialog(1) functionality */
+extern char *title, *backtitle;
+extern int dheight, dwidth;
+
+__BEGIN_DECLS
+uint8_t dialog_prompt_nlstate(const char *_prompt);
+void dialog_gauge_free(void);
+void dialog_maxsize_free(void);
+char *dialog_prompt_lastline(char *_prompt, uint8_t _nlstate);
+int dialog_maxcols(void);
+int dialog_maxrows(void);
+int dialog_prompt_wrappedlines(char *_prompt, int _ncols,
+ uint8_t _nlstate);
+int dialog_spawn_gauge(char *_init_prompt, pid_t *_pid);
+int tty_maxcols(void);
+#define tty_maxrows() dialog_maxrows()
+unsigned int dialog_prompt_longestline(const char *_prompt,
+ uint8_t _nlstate);
+unsigned int dialog_prompt_numlines(const char *_prompt, uint8_t _nlstate);
+__END_DECLS
+
+#endif /* !_DIALOG_UTIL_H_ */
diff --git a/lib/libdpv/dialogrc.c b/lib/libdpv/dialogrc.c
new file mode 100644
index 0000000..eb4a536
--- /dev/null
+++ b/lib/libdpv/dialogrc.c
@@ -0,0 +1,359 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <figpar.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+
+#include "dialogrc.h"
+
+#define STR_BUFSIZE 255
+
+/* dialog(1) `.dialogrc' characteristics */
+uint8_t use_colors = 1;
+uint8_t use_shadow = 1;
+char gauge_color[STR_BUFSIZE] = "47b"; /* (BLUE,WHITE,ON) */
+char separator[STR_BUFSIZE] = "";
+
+/* Function prototypes */
+static int setattr(struct fp_config *, uint32_t, char *, char *);
+static int setbool(struct fp_config *, uint32_t, char *, char *);
+static int setnum(struct fp_config *, uint32_t, char *, char *);
+static int setstr(struct fp_config *, uint32_t, char *, char *);
+
+/*
+ * Anatomy of DIALOGRC (~/.dialogrc by default)
+ * NOTE: Must appear after private function prototypes (above)
+ * NB: Brace-initialization of union requires cast to *first* member of union
+ */
+static struct fp_config dialogrc_config[] = {
+ /* TYPE Directive DEFAULT HANDLER */
+ {FP_TYPE_INT, "aspect", {(void *)0}, &setnum},
+ {FP_TYPE_STR, "separate_widget", {separator}, &setstr},
+ {FP_TYPE_INT, "tab_len", {(void *)0}, &setnum},
+ {FP_TYPE_BOOL, "visit_items", {(void *)0}, &setbool},
+ {FP_TYPE_BOOL, "use_shadow", {(void *)1}, &setbool},
+ {FP_TYPE_BOOL, "use_colors", {(void *)1}, &setbool},
+ {FP_TYPE_STR, "screen_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "shadow_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "dialog_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "title_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "border_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "button_active_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "button_inactive_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "button_key_active_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "button_key_inactive_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "button_label_active_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "button_label_inactive_color",{NULL}, &setattr},
+ {FP_TYPE_STR, "inputbox_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "inputbox_border_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "searchbox_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "searchbox_title_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "searchbox_border_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "position_indicator_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "menubox_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "menubox_border_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "item_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "item_selected_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "tag_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "tag_selected_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "tag_key_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "tag_key_selected_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "check_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "check_selected_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "uarrow_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "darrow_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "itemhelp_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "form_active_text_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "form_text_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "form_item_readonly_color", {NULL}, &setattr},
+ {FP_TYPE_STR, "gauge_color", {gauge_color}, &setattr},
+ {0, NULL, {0}, NULL}
+};
+
+/*
+ * figpar call-back for interpreting value as .dialogrc `Attribute'
+ */
+static int
+setattr(struct fp_config *option, uint32_t line __unused,
+ char *directive __unused, char *value)
+{
+ char *cp = value;
+ char *val;
+ size_t len;
+ char attrbuf[4];
+
+ if (option == NULL) {
+ warnx("%s:%d:%s: Missing callback parameter", __FILE__,
+ __LINE__, __func__);
+ return (-1); /* Abort processing */
+ }
+
+ /* Allocate memory for the data if not already done */
+ if (option->value.str == NULL) {
+ if ((option->value.str = malloc(STR_BUFSIZE)) == NULL)
+ return (-1);
+ }
+
+ /*
+ * If the first character is left-parenthesis, the format is
+ * `(background,foreground,highlight)' otherwise, we should take it
+ * as a reference to another color.
+ */
+ if (*cp != '(') {
+ /* Copy the [current] value from the referenced color */
+ val = dialogrc_config_option(cp)->value.str;
+ if (val != NULL)
+ snprintf(option->value.str, STR_BUFSIZE, "%s", val);
+
+ return (0);
+ } else
+ cp++;
+
+ strtolower(cp);
+
+ /* Initialize the attrbuf (fg,bg,hi,NUL) */
+ attrbuf[0] = '0';
+ attrbuf[1] = '0';
+ attrbuf[2] = 'B'; /* \ZB = disable; \Zb = enable (see dialog(1)) */
+ attrbuf[3] = '\0';
+
+ /* Interpret the foreground color */
+ if (strncmp(cp, "red,", 4) == 0) attrbuf[0] = '1';
+ else if (strncmp(cp, "green,", 6) == 0) attrbuf[0] = '2';
+ else if (strncmp(cp, "yellow,", 7) == 0) attrbuf[0] = '3';
+ else if (strncmp(cp, "blue,", 5) == 0) attrbuf[0] = '4';
+ else if (strncmp(cp, "magenta,", 8) == 0) attrbuf[0] = '5';
+ else if (strncmp(cp, "cyan,", 5) == 0) attrbuf[0] = '6';
+ else if (strncmp(cp, "white,", 6) == 0) attrbuf[0] = '7';
+ else if (strncmp(cp, "black,", 6) == 0) attrbuf[0] = '8';
+
+ /* Advance to the background color */
+ cp = strchr(cp, ',');
+ if (cp == NULL)
+ goto write_attrbuf;
+ else
+ cp++;
+
+ /* Interpret the background color */
+ if (strncmp(cp, "red,", 4) == 0) attrbuf[1] = '1';
+ else if (strncmp(cp, "green,", 6) == 0) attrbuf[1] = '2';
+ else if (strncmp(cp, "yellow,", 7) == 0) attrbuf[1] = '3';
+ else if (strncmp(cp, "blue,", 5) == 0) attrbuf[1] = '4';
+ else if (strncmp(cp, "magenta,", 8) == 0) attrbuf[1] = '5';
+ else if (strncmp(cp, "cyan,", 5) == 0) attrbuf[1] = '6';
+ else if (strncmp(cp, "white,", 6) == 0) attrbuf[1] = '7';
+ else if (strncmp(cp, "black,", 6) == 0) attrbuf[1] = '8';
+
+ /* Advance to the highlight */
+ cp = strchr(cp, ',');
+ if (cp == NULL)
+ goto write_attrbuf;
+ else
+ cp++;
+
+ /* Trim trailing parenthesis */
+ len = strlen(cp);
+ if (cp[len - 1] == ')')
+ cp[len - 1] = '\0';
+
+ /* Interpret the highlight (initialized to off above) */
+ if (strcmp(cp, "on") == 0 || strncmp(cp, "on,", 3) == 0)
+ attrbuf[2] = 'b'; /* \Zb = enable bold (see dialog(1)) */
+
+write_attrbuf:
+ sprintf(option->value.str, "%s", attrbuf);
+
+ return (0);
+}
+
+/*
+ * figpar call-back for interpreting value as .dialogrc `Boolean'
+ */
+static int
+setbool(struct fp_config *option, uint32_t line __unused,
+ char *directive __unused, char *value)
+{
+
+ if (option == NULL) {
+ warnx("%s:%d:%s: Missing callback parameter", __FILE__,
+ __LINE__, __func__);
+ return (-1); /* Abort processing */
+ }
+
+ /* Assume ON, check for OFF (case-insensitive) */
+ option->value.boolean = 1;
+ strtolower(value);
+ if (strcmp(value, "off") == 0)
+ option->value.boolean = 0;
+
+ return (0);
+}
+
+/*
+ * figpar call-back for interpreting value as .dialogrc `Number'
+ */
+static int
+setnum(struct fp_config *option, uint32_t line __unused,
+ char *directive __unused, char *value)
+{
+
+ if (option == NULL) {
+ warnx("%s:%d:%s: Missing callback parameter", __FILE__,
+ __LINE__, __func__);
+ return (-1); /* Abort processing */
+ }
+
+ /* Convert the string to a 32-bit signed integer */
+ option->value.num = (int32_t)strtol(value, (char **)NULL, 10);
+
+ return (0);
+}
+
+/*
+ * figpar call-back for interpreting value as .dialogrc `String'
+ */
+static int
+setstr(struct fp_config *option, uint32_t line __unused,
+ char *directive __unused, char *value)
+{
+ size_t len;
+
+ if (option == NULL) {
+ warnx("%s:%d:%s: Missing callback parameter", __FILE__,
+ __LINE__, __func__);
+ return (-1); /* Abort processing */
+ }
+
+ /* Allocate memory for the data if not already done */
+ if (option->value.str == NULL) {
+ if ((option->value.str = malloc(STR_BUFSIZE)) == NULL)
+ return (-1);
+ }
+
+ /* Trim leading quote */
+ if (*value == '"')
+ value++;
+
+ /* Write the data into the buffer */
+ snprintf(option->value.str, STR_BUFSIZE, "%s", value);
+
+ /* Trim trailing quote */
+ len = strlen(option->value.str);
+ if (option->value.str[len - 1] == '"')
+ option->value.str[len - 1] = '\0';
+
+ return (0);
+}
+
+/*
+ * Parse (in order of preference) $DIALOGRC or `$HOME/.dialogrc'. Returns zero
+ * on success, -1 on failure (and errno should be consulted).
+ */
+int
+parse_dialogrc(void)
+{
+ char *cp;
+ int res;
+ size_t len;
+ char path[PATH_MAX];
+
+ /* Allow $DIALOGRC to override `$HOME/.dialogrc' default */
+ if ((cp = getenv(ENV_DIALOGRC)) != NULL && *cp != '\0')
+ snprintf(path, PATH_MAX, "%s", cp);
+ else if ((cp = getenv(ENV_HOME)) != NULL) {
+ /* Copy $HOME into buffer and append trailing `/' if missing */
+ snprintf(path, PATH_MAX, "%s", cp);
+ len = strlen(path);
+ cp = path + len;
+ if (len > 0 && len < (PATH_MAX - 1) && *(cp - 1) != '/') {
+ *cp++ = '/';
+ *cp = '\0';
+ len++;
+ }
+
+ /* If we still have room, shove in the name of rc file */
+ if (len < (PATH_MAX - 1))
+ snprintf(cp, PATH_MAX - len, "%s", DIALOGRC);
+ } else {
+ /* Like dialog(1), don't process a file if $HOME is unset */
+ errno = ENOENT;
+ return (-1);
+ }
+
+ /* Process file (either $DIALOGRC if set, or `$HOME/.dialogrc') */
+ res = parse_config(dialogrc_config, path, NULL, FP_BREAK_ON_EQUALS);
+
+ /* Set some globals based on what we parsed */
+ use_shadow = dialogrc_config_option("use_shadow")->value.boolean;
+ use_colors = dialogrc_config_option("use_colors")->value.boolean;
+ snprintf(gauge_color, STR_BUFSIZE, "%s",
+ dialogrc_config_option("gauge_color")->value.str);
+
+ return (res);
+}
+
+/*
+ * Return a pointer to the `.dialogrc' config option specific to `directive' or
+ * static fp_dummy_config (full of NULLs) if none found (see
+ * get_config_option(3); part of figpar(3)).
+ */
+struct fp_config *
+dialogrc_config_option(const char *directive)
+{
+ return (get_config_option(dialogrc_config, directive));
+}
+
+/*
+ * Free allocated items initialized by setattr() (via parse_config() callback
+ * matrix [dialogrc_config] used in parse_dialogrc() above).
+ */
+void
+dialogrc_free(void)
+{
+ char *value;
+ uint32_t n;
+
+ for (n = 0; dialogrc_config[n].directive != NULL; n++) {
+ if (dialogrc_config[n].action != &setattr)
+ continue;
+ value = dialogrc_config[n].value.str;
+ if (value != NULL && value != gauge_color) {
+ free(dialogrc_config[n].value.str);
+ dialogrc_config[n].value.str = NULL;
+ }
+ }
+}
diff --git a/lib/libdpv/dialogrc.h b/lib/libdpv/dialogrc.h
new file mode 100644
index 0000000..7d17235
--- /dev/null
+++ b/lib/libdpv/dialogrc.h
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DIALOGRC_H_
+#define _DIALOGRC_H_
+
+#include <sys/types.h>
+
+#include <figpar.h>
+
+/* dialog(3) dlg_color_table[] attributes */
+#define GAUGE_ATTR 33 /* entry used for gauge_color */
+
+/* dialog(1) characteristics */
+#define DIALOGRC ".dialogrc"
+#define ENV_DIALOGRC "DIALOGRC"
+#define ENV_HOME "HOME"
+
+/* dialog(1) `.dialogrc' characteristics */
+extern uint8_t use_colors;
+extern uint8_t use_shadow;
+extern char gauge_color[];
+extern char separator[];
+
+__BEGIN_DECLS
+void dialogrc_free(void);
+int parse_dialogrc(void);
+struct fp_config *dialogrc_config_option(const char *_directive);
+__END_DECLS
+
+#endif /* !_DIALOGRC_H_ */
diff --git a/lib/libdpv/dprompt.c b/lib/libdpv/dprompt.c
new file mode 100644
index 0000000..031f550
--- /dev/null
+++ b/lib/libdpv/dprompt.c
@@ -0,0 +1,770 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */
+#include <dialog.h>
+#include <err.h>
+#include <libutil.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+#include <unistd.h>
+
+#include "dialog_util.h"
+#include "dialogrc.h"
+#include "dprompt.h"
+#include "dpv.h"
+#include "dpv_private.h"
+
+#define FLABEL_MAX 1024
+
+static int fheight = 0; /* initialized by dprompt_init() */
+static char dprompt[PROMPT_MAX + 1] = "";
+static char *dprompt_pos = (char *)(0); /* treated numerically */
+
+/* Display characteristics */
+#define FM_DONE 0x01
+#define FM_FAIL 0x02
+#define FM_PEND 0x04
+static uint8_t dprompt_free_mask;
+static char *done = NULL;
+static char *fail = NULL;
+static char *pend = NULL;
+int display_limit = DISPLAY_LIMIT_DEFAULT; /* Max entries to show */
+int label_size = LABEL_SIZE_DEFAULT; /* Max width for labels */
+int pbar_size = PBAR_SIZE_DEFAULT; /* Mini-progressbar size */
+static int gauge_percent = 0;
+static int done_size, done_lsize, done_rsize;
+static int fail_size, fail_lsize, fail_rsize;
+static int mesg_size, mesg_lsize, mesg_rsize;
+static int pend_size, pend_lsize, pend_rsize;
+static int pct_lsize, pct_rsize;
+static void *gauge = NULL;
+#define SPIN_SIZE 4
+static char spin[SPIN_SIZE + 1] = "/-\\|";
+static char msg[PROMPT_MAX + 1];
+static char *spin_cp = spin;
+
+/* Function prototypes */
+static char spin_char(void);
+static int dprompt_add_files(struct dpv_file_node *file_list,
+ struct dpv_file_node *curfile, int pct);
+
+/*
+ * Returns a pointer to the current spin character in the spin string and
+ * advances the global position to the next character for the next call.
+ */
+static char
+spin_char(void)
+{
+ char ch;
+
+ if (spin_cp == '\0')
+ spin_cp = spin;
+ ch = *spin_cp;
+
+ /* Advance the spinner to the next char */
+ if (++spin_cp >= (spin + SPIN_SIZE))
+ spin_cp = spin;
+
+ return (ch);
+}
+
+/*
+ * Initialize heights and widths based on various strings and environment
+ * variables (such as ENV_USE_COLOR).
+ */
+void
+dprompt_init(struct dpv_file_node *file_list)
+{
+ uint8_t nls = 0;
+ int len;
+ int max_cols;
+ int max_rows;
+ int nthfile;
+ int numlines;
+ struct dpv_file_node *curfile;
+
+ /*
+ * Initialize dialog(3) `colors' support and draw backtitle
+ */
+ if (use_libdialog && !debug) {
+ init_dialog(stdin, stdout);
+ dialog_vars.colors = 1;
+ if (backtitle != NULL) {
+ dialog_vars.backtitle = (char *)backtitle;
+ dlg_put_backtitle();
+ }
+ }
+
+ /* Calculate width of dialog(3) or [X]dialog(1) --gauge box */
+ dwidth = label_size + pbar_size + 9;
+
+ /*
+ * Calculate height of dialog(3) or [X]dialog(1) --gauge box
+ */
+ dheight = 5;
+ max_rows = dialog_maxrows();
+ /* adjust max_rows for backtitle and/or dialog(3) statusLine */
+ if (backtitle != NULL)
+ max_rows -= use_shadow ? 3 : 2;
+ if (use_libdialog && use_shadow)
+ max_rows -= 2;
+ /* add lines for `-p text' */
+ numlines = dialog_prompt_numlines(pprompt, 0);
+ if (debug)
+ warnx("`-p text' is %i line%s long", numlines,
+ numlines == 1 ? "" : "s");
+ dheight += numlines;
+ /* adjust dheight for various implementations */
+ if (use_dialog) {
+ dheight -= dialog_prompt_nlstate(pprompt);
+ nls = dialog_prompt_nlstate(pprompt);
+ } else if (use_xdialog) {
+ if (pprompt == NULL || *pprompt == '\0')
+ dheight++;
+ } else if (use_libdialog) {
+ if (pprompt != NULL && *pprompt != '\0')
+ dheight--;
+ }
+ /* limit the number of display items (necessary per dialog(1,3)) */
+ if (display_limit == 0 || display_limit > DPV_DISPLAY_LIMIT)
+ display_limit = DPV_DISPLAY_LIMIT;
+ /* verify fheight will fit (stop if we hit 1) */
+ for (; display_limit > 0; display_limit--) {
+ nthfile = numlines = 0;
+ fheight = (int)dpv_nfiles > display_limit ?
+ (unsigned int)display_limit : dpv_nfiles;
+ for (curfile = file_list; curfile != NULL;
+ curfile = curfile->next) {
+ nthfile++;
+ numlines += dialog_prompt_numlines(curfile->name, nls);
+ if ((nthfile % display_limit) == 0) {
+ if (numlines > fheight)
+ fheight = numlines;
+ numlines = nthfile = 0;
+ }
+ }
+ if (numlines > fheight)
+ fheight = numlines;
+ if ((dheight + fheight +
+ (int)dialog_prompt_numlines(aprompt, use_dialog) -
+ (use_dialog ? (int)dialog_prompt_nlstate(aprompt) : 0))
+ <= max_rows)
+ break;
+ }
+ /* don't show any items if we run the risk of hitting a blank set */
+ if ((max_rows - (use_shadow ? 5 : 4)) >= fheight)
+ dheight += fheight;
+ else
+ fheight = 0;
+ /* add lines for `-a text' */
+ numlines = dialog_prompt_numlines(aprompt, use_dialog);
+ if (debug)
+ warnx("`-a text' is %i line%s long", numlines,
+ numlines == 1 ? "" : "s");
+ dheight += numlines;
+
+ /* If using Xdialog(1), adjust accordingly (based on testing) */
+ if (use_xdialog)
+ dheight += dheight / 4;
+
+ /* For wide mode, long prefix (`pprompt') or append (`aprompt')
+ * strings will bump width */
+ if (wide) {
+ len = (int)dialog_prompt_longestline(pprompt, 0); /* !nls */
+ if ((len + 4) > dwidth)
+ dwidth = len + 4;
+ len = (int)dialog_prompt_longestline(aprompt, 1); /* nls */
+ if ((len + 4) > dwidth)
+ dwidth = len + 4;
+ }
+
+ /* Enforce width constraints to maximum values */
+ max_cols = dialog_maxcols();
+ if (max_cols > 0 && dwidth > max_cols)
+ dwidth = max_cols;
+
+ /* Optimize widths to sane values*/
+ if (pbar_size > dwidth - 9) {
+ pbar_size = dwidth - 9;
+ label_size = 0;
+ /* -9 = "| - [" ... "] |" */
+ }
+ if (pbar_size < 0)
+ label_size = dwidth - 8;
+ /* -8 = "| " ... " - |" */
+ else if (label_size > (dwidth - pbar_size - 9) || wide)
+ label_size = no_labels ? 0 : dwidth - pbar_size - 9;
+ /* -9 = "| " ... " - [" ... "] |" */
+
+ /* Hide labels if requested */
+ if (no_labels)
+ label_size = 0;
+
+ /* Touch up the height (now that we know dwidth) */
+ dheight += dialog_prompt_wrappedlines(pprompt, dwidth - 4, 0);
+ dheight += dialog_prompt_wrappedlines(aprompt, dwidth - 4, 1);
+
+ if (debug)
+ warnx("dheight = %i dwidth = %i fheight = %i",
+ dheight, dwidth, fheight);
+
+ /* Calculate left/right portions of % */
+ pct_lsize = (pbar_size - 4) / 2; /* -4 == printf("%-3s%%", pct) */
+ pct_rsize = pct_lsize;
+ /* If not evenly divisible by 2, increment the right-side */
+ if ((pct_rsize + pct_rsize + 4) != pbar_size)
+ pct_rsize++;
+
+ /* Initialize "Done" text */
+ if (done == NULL && (done = msg_done) == NULL) {
+ if ((done = getenv(ENV_MSG_DONE)) != NULL)
+ done_size = strlen(done);
+ else {
+ done_size = strlen(DPV_DONE_DEFAULT);
+ if ((done = malloc(done_size + 1)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ dprompt_free_mask |= FM_DONE;
+ snprintf(done, done_size + 1, DPV_DONE_DEFAULT);
+ }
+ }
+ if (pbar_size < done_size) {
+ done_lsize = done_rsize = 0;
+ *(done + pbar_size) = '\0';
+ done_size = pbar_size;
+ } else {
+ /* Calculate left/right portions for mini-progressbar */
+ done_lsize = (pbar_size - done_size) / 2;
+ done_rsize = done_lsize;
+ /* If not evenly divisible by 2, increment the right-side */
+ if ((done_rsize + done_size + done_lsize) != pbar_size)
+ done_rsize++;
+ }
+
+ /* Initialize "Fail" text */
+ if (fail == NULL && (fail = msg_fail) == NULL) {
+ if ((fail = getenv(ENV_MSG_FAIL)) != NULL)
+ fail_size = strlen(fail);
+ else {
+ fail_size = strlen(DPV_FAIL_DEFAULT);
+ if ((fail = malloc(fail_size + 1)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ dprompt_free_mask |= FM_FAIL;
+ snprintf(fail, fail_size + 1, DPV_FAIL_DEFAULT);
+ }
+ }
+ if (pbar_size < fail_size) {
+ fail_lsize = fail_rsize = 0;
+ *(fail + pbar_size) = '\0';
+ fail_size = pbar_size;
+ } else {
+ /* Calculate left/right portions for mini-progressbar */
+ fail_lsize = (pbar_size - fail_size) / 2;
+ fail_rsize = fail_lsize;
+ /* If not evenly divisible by 2, increment the right-side */
+ if ((fail_rsize + fail_size + fail_lsize) != pbar_size)
+ fail_rsize++;
+ }
+
+ /* Initialize "Pending" text */
+ if (pend == NULL && (pend = msg_pending) == NULL) {
+ if ((pend = getenv(ENV_MSG_PENDING)) != NULL)
+ pend_size = strlen(pend);
+ else {
+ pend_size = strlen(DPV_PENDING_DEFAULT);
+ if ((pend = malloc(pend_size + 1)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ dprompt_free_mask |= FM_PEND;
+ snprintf(pend, pend_size + 1, DPV_PENDING_DEFAULT);
+ }
+ }
+ if (pbar_size < pend_size) {
+ pend_lsize = pend_rsize = 0;
+ *(pend + pbar_size) = '\0';
+ pend_size = pbar_size;
+ } else {
+ /* Calculate left/right portions for mini-progressbar */
+ pend_lsize = (pbar_size - pend_size) / 2;
+ pend_rsize = pend_lsize;
+ /* If not evenly divisible by 2, increment the right-side */
+ if ((pend_rsize + pend_lsize + pend_size) != pbar_size)
+ pend_rsize++;
+ }
+
+ if (debug)
+ warnx("label_size = %i pbar_size = %i", label_size, pbar_size);
+
+ dprompt_clear();
+}
+
+/*
+ * Clear the [X]dialog(1) `--gauge' prompt buffer.
+ */
+void
+dprompt_clear(void)
+{
+
+ *dprompt = '\0';
+ dprompt_pos = dprompt;
+}
+
+/*
+ * Append to the [X]dialog(1) `--gauge' prompt buffer. Syntax is like printf(3)
+ * and returns the number of bytes appended to the buffer.
+ */
+int
+dprompt_add(const char *format, ...)
+{
+ int len;
+ va_list ap;
+
+ if (dprompt_pos >= (dprompt + PROMPT_MAX))
+ return (0);
+
+ va_start(ap, format);
+ len = vsnprintf(dprompt_pos, (size_t)(PROMPT_MAX -
+ (dprompt_pos - dprompt)), format, ap);
+ va_end(ap);
+ if (len == -1)
+ errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow",
+ __func__);
+
+ if ((dprompt_pos + len) < (dprompt + PROMPT_MAX))
+ dprompt_pos += len;
+ else
+ dprompt_pos = dprompt + PROMPT_MAX;
+
+ return (len);
+}
+
+/*
+ * Append active files to the [X]dialog(1) `--gauge' prompt buffer. Syntax
+ * requires a pointer to the head of the dpv_file_node linked-list. Returns the
+ * number of files processed successfully.
+ */
+static int
+dprompt_add_files(struct dpv_file_node *file_list,
+ struct dpv_file_node *curfile, int pct)
+{
+ char c;
+ char bold_code = 'b'; /* default: enabled */
+ char color_code = '4'; /* default: blue */
+ uint8_t after_curfile = curfile != NULL ? FALSE : TRUE;
+ uint8_t nls = 0;
+ char *cp;
+ char *lastline;
+ char *name;
+ const char *bg_code;
+ const char *estext;
+ const char *format;
+ enum dprompt_state dstate;
+ int estext_lsize;
+ int estext_rsize;
+ int estext_size;
+ int flabel_size;
+ int hlen;
+ int lsize;
+ int nlines = 0;
+ int nthfile = 0;
+ int pwidth;
+ int rsize;
+ struct dpv_file_node *fp;
+ char flabel[FLABEL_MAX + 1];
+ char human[32];
+ char pbar[pbar_size + 16]; /* +15 for optional color */
+ char pbar_cap[sizeof(pbar)];
+ char pbar_fill[sizeof(pbar)];
+
+
+ /* Override color defaults with that of main progress bar */
+ if (use_colors || use_shadow) { /* NB: shadow enables color */
+ color_code = gauge_color[0];
+ /* NB: str[1] aka bg is unused */
+ bold_code = gauge_color[2];
+ }
+
+ /*
+ * Create mini-progressbar for current file (if applicable)
+ */
+ *pbar = '\0';
+ if (pbar_size >= 0 && pct >= 0 && curfile != NULL &&
+ (curfile->length >= 0 || dialog_test)) {
+ snprintf(pbar, pbar_size + 1, "%*s%3u%%%*s", pct_lsize, "",
+ pct, pct_rsize, "");
+ if (use_color) {
+ /* Calculate the fill-width of progressbar */
+ pwidth = pct * pbar_size / 100;
+ /* Round up based on one-tenth of a percent */
+ if ((pct * pbar_size % 100) > 50)
+ pwidth++;
+
+ /*
+ * Make two copies of pbar. Make one represent the fill
+ * and the other the remainder (cap). We'll insert the
+ * ANSI delimiter in between.
+ */
+ *pbar_fill = '\0';
+ *pbar_cap = '\0';
+ strncat(pbar_fill, (const char *)(pbar), dwidth);
+ *(pbar_fill + pwidth) = '\0';
+ strncat(pbar_cap, (const char *)(pbar+pwidth), dwidth);
+
+ /* Finalize the mini [color] progressbar */
+ snprintf(pbar, sizeof(pbar),
+ "\\Z%c\\Zr\\Z%c%s%s%s\\Zn", bold_code, color_code,
+ pbar_fill, "\\ZR", pbar_cap);
+ }
+ }
+
+ for (fp = file_list; fp != NULL; fp = fp->next) {
+ flabel_size = label_size;
+ name = fp->name;
+ nthfile++;
+
+ /*
+ * Support multiline filenames (where the filename is taken as
+ * the last line and the text leading up to the last line can
+ * be used as (for example) a heading/separator between files.
+ */
+ if (use_dialog)
+ nls = dialog_prompt_nlstate(pprompt);
+ nlines += dialog_prompt_numlines(name, nls);
+ lastline = dialog_prompt_lastline(name, 1);
+ if (name != lastline) {
+ c = *lastline;
+ *lastline = '\0';
+ dprompt_add("%s", name);
+ *lastline = c;
+ name = lastline;
+ }
+
+ /* Support color codes (for dialog(1,3)) in file names */
+ if ((use_dialog || use_libdialog) && use_color) {
+ cp = name;
+ while (*cp != '\0') {
+ if (*cp == '\\' && *(cp + 1) != '\0' &&
+ *(++cp) == 'Z' && *(cp + 1) != '\0') {
+ cp++;
+ flabel_size += 3;
+ }
+ cp++;
+ }
+ if (flabel_size > FLABEL_MAX)
+ flabel_size = FLABEL_MAX;
+ }
+
+ /* If no mini-progressbar, increase label width */
+ if (pbar_size < 0 && flabel_size <= FLABEL_MAX - 2 &&
+ no_labels == FALSE)
+ flabel_size += 2;
+
+ /* If name is too long, add an ellipsis */
+ if (snprintf(flabel, flabel_size + 1, "%s", name) >
+ flabel_size) sprintf(flabel + flabel_size - 3, "...");
+
+ /*
+ * Append the label (processing the current file differently)
+ */
+ if (fp == curfile && pct < 100) {
+ /*
+ * Add an ellipsis to current file name if it will fit.
+ * There may be an ellipsis already from truncating the
+ * label (in which case, we already have one).
+ */
+ cp = flabel + strlen(flabel);
+ if (cp < (flabel + flabel_size))
+ snprintf(cp, flabel_size -
+ (cp - flabel) + 1, "...");
+
+ /* Append label (with spinner and optional color) */
+ dprompt_add("%s%-*s%s %c", use_color ? "\\Zb" : "",
+ flabel_size, flabel, use_color ? "\\Zn" : "",
+ spin_char());
+ } else
+ dprompt_add("%-*s%s %s", flabel_size,
+ flabel, use_color ? "\\Zn" : "", " ");
+
+ /*
+ * Append pbar/status (processing the current file differently)
+ */
+ dstate = DPROMPT_NONE;
+ if (fp->msg != NULL)
+ dstate = DPROMPT_CUSTOM_MSG;
+ else if (pbar_size < 0)
+ dstate = DPROMPT_NONE;
+ else if (pbar_size < 4)
+ dstate = DPROMPT_MINIMAL;
+ else if (after_curfile)
+ dstate = DPROMPT_PENDING;
+ else if (fp == curfile) {
+ if (*pbar == '\0') {
+ if (fp->length < 0)
+ dstate = DPROMPT_DETAILS;
+ else if (fp->status == DPV_STATUS_RUNNING)
+ dstate = DPROMPT_DETAILS;
+ else
+ dstate = DPROMPT_END_STATE;
+ }
+ else if (dialog_test) /* status/length ignored */
+ dstate = pct < 100 ?
+ DPROMPT_PBAR : DPROMPT_END_STATE;
+ else if (fp->status == DPV_STATUS_RUNNING)
+ dstate = fp->length < 0 ?
+ DPROMPT_DETAILS : DPROMPT_PBAR;
+ else /* not running */
+ dstate = fp->length < 0 ?
+ DPROMPT_DETAILS : DPROMPT_END_STATE;
+ } else { /* before curfile */
+ if (dialog_test)
+ dstate = DPROMPT_END_STATE;
+ else
+ dstate = fp->length < 0 ?
+ DPROMPT_DETAILS : DPROMPT_END_STATE;
+ }
+ format = use_color ?
+ " [\\Z%c%s%-*s%s%-*s\\Zn]\\n" :
+ " [%-*s%s%-*s]\\n";
+ if (fp->status == DPV_STATUS_FAILED) {
+ bg_code = "\\Zr\\Z1"; /* Red */
+ estext_lsize = fail_lsize;
+ estext_rsize = fail_rsize;
+ estext_size = fail_size;
+ estext = fail;
+ } else { /* e.g., DPV_STATUS_DONE */
+ bg_code = "\\Zr\\Z2"; /* Green */
+ estext_lsize = done_lsize;
+ estext_rsize = done_rsize;
+ estext_size = done_size;
+ estext = done;
+ }
+ switch (dstate) {
+ case DPROMPT_PENDING: /* Future file(s) */
+ dprompt_add(" [%-*s%s%-*s]\\n",
+ pend_lsize, "", pend, pend_rsize, "");
+ break;
+ case DPROMPT_PBAR: /* Current file */
+ dprompt_add(" [%s]\\n", pbar);
+ break;
+ case DPROMPT_END_STATE: /* Past/Current file(s) */
+ if (use_color)
+ dprompt_add(format, bold_code, bg_code,
+ estext_lsize, "", estext,
+ estext_rsize, "");
+ else
+ dprompt_add(format,
+ estext_lsize, "", estext,
+ estext_rsize, "");
+ break;
+ case DPROMPT_DETAILS: /* Past/Current file(s) */
+ humanize_number(human, pbar_size + 2, fp->read, "",
+ HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000);
+
+ /* Calculate center alignment */
+ hlen = (int)strlen(human);
+ lsize = (pbar_size - hlen) / 2;
+ rsize = lsize;
+ if ((lsize+hlen+rsize) != pbar_size)
+ rsize++;
+
+ if (use_color)
+ dprompt_add(format, bold_code, bg_code,
+ lsize, "", human, rsize, "");
+ else
+ dprompt_add(format,
+ lsize, "", human, rsize, "");
+ break;
+ case DPROMPT_CUSTOM_MSG: /* File-specific message override */
+ snprintf(msg, PROMPT_MAX + 1, "%s", fp->msg);
+ if (pbar_size < (mesg_size = strlen(msg))) {
+ mesg_lsize = mesg_rsize = 0;
+ *(msg + pbar_size) = '\0';
+ mesg_size = pbar_size;
+ } else {
+ mesg_lsize = (pbar_size - mesg_size) / 2;
+ mesg_rsize = mesg_lsize;
+ if ((mesg_rsize + mesg_size + mesg_lsize)
+ != pbar_size)
+ mesg_rsize++;
+ }
+ if (use_color)
+ dprompt_add(format, bold_code, bg_code,
+ mesg_lsize, "", msg, mesg_rsize, "");
+ else
+ dprompt_add(format, mesg_lsize, "", msg,
+ mesg_rsize, "");
+ break;
+ case DPROMPT_MINIMAL: /* Short progress bar, minimal room */
+ if (use_color)
+ dprompt_add(format, bold_code, bg_code,
+ pbar_size, "", "", 0, "");
+ else
+ dprompt_add(format, pbar_size, "", "", 0, "");
+ break;
+ case DPROMPT_NONE: /* pbar_size < 0 */
+ /* FALLTHROUGH */
+ default:
+ dprompt_add(" \\n");
+ /*
+ * NB: Leading space required for the case when
+ * spin_char() returns a single backslash [\] which
+ * without the space, changes the meaning of `\n'
+ */
+ }
+
+ /* Stop building if we've hit the internal limit */
+ if (nthfile >= display_limit)
+ break;
+
+ /* If this is the current file, all others are pending */
+ if (fp == curfile)
+ after_curfile = TRUE;
+ }
+
+ /*
+ * Since we cannot change the height/width of the [X]dialog(1) widget
+ * after spawn, to make things look nice let's pad the height so that
+ * the `-a text' always appears in the same spot.
+ *
+ * NOTE: fheight is calculated in dprompt_init(). It represents the
+ * maximum height required to display the set of items (broken up into
+ * pieces of display_limit chunks) whose names contain the most
+ * newlines for any given set.
+ */
+ while (nlines < fheight) {
+ dprompt_add("\n");
+ nlines++;
+ }
+
+ return (nthfile);
+}
+
+/*
+ * Process the dpv_file_node linked-list of named files, re-generating the
+ * [X]dialog(1) `--gauge' prompt text for the current state of transfers.
+ */
+void
+dprompt_recreate(struct dpv_file_node *file_list,
+ struct dpv_file_node *curfile, int pct)
+{
+ size_t len;
+
+ /*
+ * Re-Build the prompt text
+ */
+ dprompt_clear();
+ if (display_limit > 0)
+ dprompt_add_files(file_list, curfile, pct);
+
+ /* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
+ if (use_xdialog) {
+ /* Replace `\n' with `\n\\n\n' in dprompt */
+ len = strlen(dprompt);
+ len += strcount(dprompt, "\\n") * 5; /* +5 chars per count */
+ if (len > PROMPT_MAX)
+ errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow "
+ "(%zu > %i)", __func__, len, PROMPT_MAX);
+ if (replaceall(dprompt, "\\n", "\n\\n\n") < 0)
+ err(EXIT_FAILURE, "%s: replaceall()", __func__);
+ }
+ else if (use_libdialog)
+ strexpandnl(dprompt);
+}
+
+/*
+ * Print the [X]dialog(1) `--gauge' prompt text to a buffer.
+ */
+int
+dprompt_sprint(char * restrict str, const char *prefix, const char *append)
+{
+
+ return (snprintf(str, PROMPT_MAX, "%s%s%s%s", use_color ? "\\Zn" : "",
+ prefix ? prefix : "", dprompt, append ? append : ""));
+}
+
+/*
+ * Print the [X]dialog(1) `--gauge' prompt text to file descriptor fd (could
+ * be STDOUT_FILENO or a pipe(2) file descriptor to actual [X]dialog(1)).
+ */
+void
+dprompt_dprint(int fd, const char *prefix, const char *append, int overall)
+{
+ int percent = gauge_percent;
+
+ if (overall >= 0 && overall <= 100)
+ gauge_percent = percent = overall;
+ dprintf(fd, "XXX\n%s%s%s%s\nXXX\n%i\n", use_color ? "\\Zn" : "",
+ prefix ? prefix : "", dprompt, append ? append : "", percent);
+ fsync(fd);
+}
+
+/*
+ * Print the dialog(3) `gauge' prompt text using libdialog.
+ */
+void
+dprompt_libprint(const char *prefix, const char *append, int overall)
+{
+ int percent = gauge_percent;
+ char buf[DPV_PPROMPT_MAX + DPV_APROMPT_MAX + DPV_DISPLAY_LIMIT * 1024];
+
+ dprompt_sprint(buf, prefix, append);
+
+ if (overall >= 0 && overall <= 100)
+ gauge_percent = percent = overall;
+ gauge = dlg_reallocate_gauge(gauge, title == NULL ? "" : title,
+ buf, dheight, dwidth, percent);
+ dlg_update_gauge(gauge, percent);
+}
+
+/*
+ * Free allocated items initialized by dprompt_init()
+ */
+void
+dprompt_free(void)
+{
+ if ((dprompt_free_mask & FM_DONE) != 0) {
+ dprompt_free_mask ^= FM_DONE;
+ free(done);
+ done = NULL;
+ }
+ if ((dprompt_free_mask & FM_FAIL) != 0) {
+ dprompt_free_mask ^= FM_FAIL;
+ free(fail);
+ fail = NULL;
+ }
+ if ((dprompt_free_mask & FM_PEND) != 0) {
+ dprompt_free_mask ^= FM_PEND;
+ free(pend);
+ pend = NULL;
+ }
+}
diff --git a/lib/libdpv/dprompt.h b/lib/libdpv/dprompt.h
new file mode 100644
index 0000000..a082364
--- /dev/null
+++ b/lib/libdpv/dprompt.h
@@ -0,0 +1,59 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DPROMPT_H_
+#define _DPROMPT_H_
+
+#include <sys/cdefs.h>
+
+#include "dpv.h"
+
+/* Display characteristics */
+#define ENV_MSG_DONE "msg_done"
+#define ENV_MSG_FAIL "msg_fail"
+#define ENV_MSG_PENDING "msg_pending"
+extern int display_limit;
+extern int label_size;
+extern int pbar_size;
+
+__BEGIN_DECLS
+void dprompt_clear(void);
+void dprompt_dprint(int _fd, const char *_prefix, const char *_append,
+ int _overall);
+void dprompt_free(void);
+void dprompt_init(struct dpv_file_node *_file_list);
+void dprompt_libprint(const char *_prefix, const char *_append,
+ int _overall);
+void dprompt_recreate(struct dpv_file_node *_file_list,
+ struct dpv_file_node *_curfile, int _pct);
+int dprompt_add(const char *_format, ...);
+int dprompt_sprint(char * restrict _str, const char *_prefix,
+ const char *_append);
+__END_DECLS
+
+#endif /* !_DPROMPT_H_ */
diff --git a/lib/libdpv/dpv.3 b/lib/libdpv/dpv.3
new file mode 100644
index 0000000..8c04ac3
--- /dev/null
+++ b/lib/libdpv/dpv.3
@@ -0,0 +1,510 @@
+.\" Copyright (c) 2013-2014 Devin Teske
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd Oct 24, 2014
+.Dt DPV 3
+.Os
+.Sh NAME
+.Nm dpv
+.Nd dialog progress view library
+.Sh LIBRARY
+.Lb libdpv
+.Sh SYNOPSIS
+.In dpv.h
+.Ft int
+.Fo dpv
+.Fa "struct dpv_config *config, struct dpv_file_node *file_list"
+.Fc
+.Ft void
+.Fo dpv_free
+.Fa "void"
+.Fc
+.Sh DESCRIPTION
+The
+.Nm
+library provides an interface for creating complex
+.Dq gauge
+widgets for displaying progress on various actions.
+The
+.Nm
+library can display progress with one of
+.Xr dialog 3 ,
+.Xr dialog 1 ,
+or
+.Xr Xdialog 1
+.Pq x11/xdialog from the ports tree .
+.Pp
+The
+.Fn dpv
+.Fa config
+argument contains the following properties for configuring global display
+features:
+.Bd -literal -offset indent
+struct dpv_config {
+ enum dpv_display display_type; /* Def. DPV_DISPLAY_LIBDIALOG */
+ enum dpv_output output_type; /* Default DPV_OUTPUT_NONE */
+ int debug; /* Enable debug on stderr */
+ int display_limit; /* Files/page. Default -1 */
+ int label_size; /* Label size. Default 28 */
+ int pbar_size; /* Mini-progress size */
+ int dialog_updates_per_second; /* Default 16 */
+ int status_updates_per_second; /* Default 2 */
+ uint16_t options; /* Default 0 (none) */
+ char *title; /* Widget title */
+ char *backtitle; /* Widget backtitle */
+ char *aprompt; /* Append. Default NULL */
+ char *pprompt; /* Prefix. Default NULL */
+ char *msg_done; /* Default `Done' */
+ char *msg_fail; /* Default `Fail' */
+ char *msg_pending; /* Default `Pending' */
+ char *output; /* Output format string */
+ const char *status_solo; /* dialog(3) solo-status format.
+ * Default DPV_STATUS_SOLO */
+ const char *status_many; /* dialog(3) many-status format.
+ * Default DPV_STATUS_MANY */
+
+ /*
+ * Function pointer; action to perform data transfer
+ */
+ int (*action)(struct dpv_file_node *file, int out);
+};
+
+enum dpv_display {
+ DPV_DISPLAY_LIBDIALOG = 0, /* Use dialog(3) (default) */
+ DPV_DISPLAY_STDOUT, /* Use stdout */
+ DPV_DISPLAY_DIALOG, /* Use spawned dialog(1) */
+ DPV_DISPLAY_XDIALOG, /* Use spawned Xdialog(1) */
+};
+
+enum dpv_output {
+ DPV_OUTPUT_NONE = 0, /* No output (default) */
+ DPV_OUTPUT_FILE, /* Read `output' member as file path */
+ DPV_OUTPUT_SHELL, /* Read `output' member as shell cmd */
+};
+.Ed
+.Pp
+The
+.Va options
+member of the
+.Fn dpv
+.Fa config
+argument is a mask of bit fields indicating various processing options.
+Possible flags are as follows:
+.Bl -tag -width DPV_NO_OVERRUN
+.It Dv DPV_TEST_MODE
+Enable test mode.
+In test mode, the
+.Fn action
+callback of the
+.Fa config
+argument is not called but instead simulated-data is used to drive progress.
+Appends
+.Dq [TEST MODE]
+to the status line
+.Po
+to override, set the
+.Va status_format
+member of the
+.Fn dpv
+.Fa config
+argument;
+e.g., to
+.Dv DPV_STATUS_DEFAULT
+.Pc .
+.It Dv DPV_WIDE_MODE
+Enable wide mode.
+In wide mode, the length of the
+.Va aprompt
+and
+.Va pprompt
+members of the
+.Fn dpv
+.Fa config
+argument will bump the width of the gauge widget.
+Prompts wider than the maximum width will wrap
+.Po
+unless using
+.Xr Xdialog 1 ;
+see BUGS section below
+.Pc .
+.It Dv DPV_NO_LABELS
+Disables the display of labels associated with each transfer
+.Po
+.Va label_size
+member of
+.Fn dpv
+.Fa config
+argument is ignored
+.Pc .
+.It Dv DPV_USE_COLOR
+Force the use of color even if the
+.Va display_type
+does not support color
+.Po
+.Ev USE_COLOR
+environment variable is ignored
+.Pc .
+.It Dv DPV_NO_OVERRUN
+When enabled, callbacks for the current
+.Vt dpv_file_node
+are terminated when
+.Fn action
+returns 100 or greater
+.Po
+alleviates the need to change the
+.Va status
+of the current
+.Vt dpv_file_node
+but may also cause file truncation if the stream exceeds expected length
+.Pc .
+.El
+.Pp
+The
+.Fa file_list
+argument to
+.Fn dpv
+is a pointer to a
+.Dq linked-list ,
+described as follows in
+.In dpv.h :
+.Bd -literal -offset indent
+struct dpv_file_node {
+ enum dpv_status status; /* status of read operation */
+ char *msg; /* display instead of "Done/Fail" */
+ char *name; /* name of file to read */
+ char *path; /* path to file */
+ long long length; /* expected size */
+ long long read; /* number units read (e.g., bytes) */
+ struct dpv_file_node *next;/* pointer to next (end with NULL) */
+};
+.Ed
+.Pp
+For each of the items in the
+.Fa file_list
+.Dq linked-list
+argument, the
+.Fn action
+callback member of the
+.Fn dpv
+.Fa config
+argument is called.
+The
+.Fn action
+function should perform a
+.Dq nominal
+action on the file and return.
+The return value of
+.Vt int
+represents the current progress percentage
+.Pq 0-100
+for the current file.
+.Pp
+The
+.Fn action
+callback provides two variables for each call.
+.Fa file
+provides a reference to the current
+.Vt dpv_file_node
+being processed.
+.Fa out
+provides a file descriptor where the data should go.
+.Pp
+If the
+.Va output
+member of the
+.Fn dpv
+.Fa config
+argument was set to DPV_OUTPUT_NONE
+.Pq default ; when invoking Fn dpv ,
+the
+.Fa out
+file descriptor of
+.Fn action
+will be zero and should be ignored.
+If
+.Fa output
+was set to DPV_OUTPUT_FILE,
+.Fa out
+will be an open file descriptor to a file.
+If
+.Fa output
+was set to DPV_OUTPUT_SHELL,
+.Fa out
+will be an open file descriptor to a pipe for a spawned shell program.
+When
+.Fa out
+is greater than zero, you should write any data you have read back to
+.Fa out .
+.Pp
+To abort
+.Fn dpv ,
+either from the
+.Fn action
+callback or asynchronously from a signal handler, two globals are provided via
+.In dpv.h :
+.Bd -literal -offset indent
+extern int dpv_interrupt; /* Set to TRUE in interrupt handler */
+extern int dpv_abort; /* Set to true in callback to abort */
+.Ed
+.Pp
+These globals are not automatically reset and must be manually maintained.
+Don't forget to reset these globals before subsequent invocations of
+.Fn dpv
+when making multiple calls from the same program.
+.Pp
+In addition, the
+.Va status
+member of the
+.Fn action
+.Fa file
+argument can be used to control callbacks for the current file.
+The
+.Va status
+member can be set to any of the following from
+.In dpv.h :
+.Bd -literal -offset indent
+enum dpv_status {
+ DPV_STATUS_RUNNING = 0, /* Running (default) */
+ DPV_STATUS_DONE, /* Completed */
+ DPV_STATUS_FAILED, /* Oops, something went wrong */
+};
+.Ed
+.Pp
+The default
+.Fa status
+is zero, DPV_STATUS_RUNING, which keeps the callbacks coming for the current
+.Fn file .
+Setting
+.Ql file->status
+to anything other than DPV_STATUS_RUNNING will cause
+.Fn dpv
+to loop to the next file, effecting the next callback, if any.
+.Pp
+The
+.Fn action
+callback is responsible for calculating percentages and
+.Pq recommended
+maintaining a
+.Nm
+global counter so
+.Fn dpv
+can display throughput statistics.
+Percentages are reported through the
+.Vt int
+return value of the
+.Fn action
+callback.
+Throughput statistics are calculated from the following global
+.Vt int
+in
+.In dpv.h :
+.Bd -literal -offset indent
+extern int dpv_overall_read;
+.Ed
+.Pp
+This should be set to the number of bytes that have been read for all files.
+Throughput information is displayed in the status line
+.Pq only available when using Xr dialog 3
+at the bottom of the screen.
+See DPV_DISPLAY_LIBDIALOG above.
+.Pp
+Note that
+.Va dpv_overall_read
+does not have to represent bytes.
+For example, you can change the
+.Va status_format
+to display something other than
+.Dq Li bytes
+and increment
+.Va dpv_overall_read
+accordingly
+.Pq e.g., counting lines .
+.Pp
+When
+.Fn dpv
+is processing the current file, the
+.Va length
+and
+.Va read
+members of the
+.Fn action
+.Fa file
+argument are used for calculating the display of mini progress bars
+.Po
+if enabled; see
+.Va pbar_size
+above
+.Pc .
+If the
+.Va length
+member of the current
+.Fa file
+is less than zero
+.Pq indicating an unknown file length ,
+a
+.Xr humanize_number 3
+version of the
+.Va read
+member is used instead of a traditional progress bar.
+Otherwise a progress bar is calculated as percentage read to file length.
+.Fn action
+callback must maintain these member values for mini-progress bars.
+.Pp
+The
+.Fn dpv_free
+function performs
+.Xr free 3
+on private global variables initialized by
+.Fn dpv .
+.Sh ENVIRONMENT
+The following environment variables are referenced by
+.Nm :
+.Bl -tag -width ".Ev USE_COLOR"
+.It Ev DIALOG
+Override command string used to launch
+.Xr dialog 1
+.Pq requires Dv DPV_DISPLAY_DIALOG
+or
+.Xr Xdialog 1
+.Pq requires Dv DPV_DISPLAY_XDIALOG ;
+default is either
+.Ql dialog
+.Pq for Dv DPV_DISPLAY_DIALOG
+or
+.Ql Xdialog
+.Pq for Dv DPV_DISPLAY_XDIALOG .
+.It Ev DIALOGRC
+If set and non-NULL, path to
+.Ql .dialogrc
+file.
+.It Ev HOME
+If
+.Ql Ev $DIALOGRC
+is either not set or NULL, used as a prefix to
+.Ql .dialogrc
+.Pq i.e., Ql $HOME/.dialogrc .
+.It Ev USE_COLOR
+If set and NULL, disables the use of color when using
+.Xr dialog 1
+.Pq does not apply to Xr Xdialog 1 .
+.It Ev msg_done Ev msg_fail Ev msg_pending
+Internationalization strings for overriding the default English strings
+.Ql Done ,
+.Ql Fail ,
+and
+.Ql Pending
+respectively.
+To prevent their usage, explicitly set the
+.Va msg_done ,
+.Va msg_fail ,
+and
+.Va msg_pending
+members of
+.Fn dpv
+.Fa config
+argument to default macros
+.Pq DPV_DONE_DEFAULT, DPV_FAIL_DEFAULT, and DPV_PENDING_DEFAULT
+or desired values.
+.El
+.Sh FILES
+.Bl -tag -width ".Pa $HOME/.dialogrc" -compact
+.It Pa $HOME/.dialogrc
+.El
+.Sh SEE ALSO
+.Xr dialog 1 ,
+.Xr dialog 3 ,
+.Xr Xdialog 1
+.Sh HISTORY
+The
+.Nm
+library first appeared in
+.Fx 11.0 .
+.Sh AUTHORS
+.An Devin Teske Aq dteske@FreeBSD.org
+.Sh BUGS
+.Xr Xdialog 1 ,
+when given both
+.Ql Fl -title Ar title
+.Po
+see above
+.Ql Va title
+member of
+.Va struct dpv_config
+.Pc
+and
+.Ql Fl -backtitle Ar backtitle
+.Po
+see above
+.Ql Va backtitle
+member of
+.Va struct dpv_config
+.Pc ,
+displays the backtitle in place of the title and vice-versa.
+.Pp
+.Xr Xdialog 1
+does not wrap long prompt texts received after initial launch.
+This is a known issue with the
+.Ql --gauge
+widget in
+.Xr Xdialog 1 .
+Embed escaped newlines within prompt text(s) to force line breaks.
+.Pp
+.Xr dialog 1
+does not display the first character after a series of escaped escape-sequences
+(e.g., ``\\\\n'' produces ``\\'' instead of ``\\n'').
+This is a known issue with
+.Xr dialog 1
+and does not affect
+.Xr dialog 3
+or
+.Xr Xdialog 1 .
+.Pp
+If your application ignores
+.Ev USE_COLOR
+when set and NULL before calling
+.Xr dpv 3
+with color escape sequences anyway,
+.Xr dialog 3
+and
+.Xr dialog 1
+may not render properly.
+Workaround is to detect when
+.Ev USE_COLOR
+is set and NULL and either not use color escape sequences at that time or use
+.Xr unsetenv 3
+to unset
+.Ev USE_COLOR ,
+forcing interpretation of color sequences.
+This does not effect
+.Xr Xdialog 1 ,
+which renders the color escape sequences as plain text.
+See
+.Do Li
+embedded "\\Z" sequences
+.Dc
+in
+.Xr dialog 1
+for additional information.
diff --git a/lib/libdpv/dpv.c b/lib/libdpv/dpv.c
new file mode 100644
index 0000000..fd44360
--- /dev/null
+++ b/lib/libdpv/dpv.c
@@ -0,0 +1,721 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <dialog.h>
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+#include <unistd.h>
+
+#include "dialog_util.h"
+#include "dialogrc.h"
+#include "dprompt.h"
+#include "dpv.h"
+#include "dpv_private.h"
+#include "status.h"
+#include "util.h"
+
+/* Test Mechanics (Only used when dpv_config.options |= DPV_TEST_MODE) */
+#define INCREMENT 1 /* Increment % per-pass test-mode */
+#define XDIALOG_INCREMENT 15 /* different for slower Xdialog(1) */
+static uint8_t increment = INCREMENT;
+
+/* Debugging */
+uint8_t debug = FALSE;
+
+/* Data to process */
+int dpv_interrupt = FALSE;
+int dpv_abort = FALSE;
+unsigned int dpv_nfiles = 0;
+
+/* Data processing */
+long long dpv_overall_read = 0;
+static char pathbuf[PATH_MAX];
+
+/* Extra display information */
+uint8_t no_labels = FALSE; /* dpv_config.options & DPV_NO_LABELS */
+uint8_t wide = FALSE; /* dpv_config.options & DPV_WIDE_MODE */
+char *aprompt = NULL; /* dpv_config.aprompt */
+char *msg_done = NULL; /* dpv_config.msg_done */
+char *msg_fail = NULL; /* dpv_config.msg_fail */
+char *msg_pending = NULL; /* dpv_config.msg_pending */
+char *pprompt = NULL; /* dpv_config.pprompt */
+
+/* Status-Line format for when using dialog(3) */
+static const char *status_format_custom = NULL;
+static char status_format_default[DPV_STATUS_FORMAT_MAX];
+
+/*
+ * Takes a pointer to a dpv_config structure containing layout details and
+ * pointer to initial element in a linked-list of dpv_file_node structures,
+ * each presenting a file to process. Executes the `action' function passed-in
+ * as a member to the `config' structure argument.
+ */
+int
+dpv(struct dpv_config *config, struct dpv_file_node *file_list)
+{
+ char c;
+ uint8_t keep_going;
+ uint8_t nls = FALSE; /* See dialog_prompt_nlstate() */
+ uint8_t no_overrun = FALSE;
+ uint8_t pprompt_nls = FALSE; /* See dialog_prompt_nlstate() */
+ uint8_t shrink_label_size = FALSE;
+ mode_t mask;
+ uint16_t options;
+ char *cp;
+ char *fc;
+ char *last;
+ char *name;
+ char *output;
+ const char *status_fmt;
+ const char *path_fmt;
+ enum dpv_display display_type;
+ enum dpv_output output_type;
+ enum dpv_status status;
+ int (*action)(struct dpv_file_node *file, int out);
+ int backslash;
+ int dialog_last_update = 0;
+ int dialog_old_nthfile = 0;
+ int dialog_old_seconds = -1;
+ int dialog_out = STDOUT_FILENO;
+ int dialog_update_usec = 0;
+ int dialog_updates_per_second;
+ int files_left;
+ int max_cols;
+ int nthfile = 0;
+ int output_out;
+ int overall = 0;
+ int pct;
+ int res;
+ int seconds;
+ int status_last_update = 0;
+ int status_old_nthfile = 0;
+ int status_old_seconds = -1;
+ int status_update_usec = 0;
+ int status_updates_per_second;
+ pid_t output_pid;
+ pid_t pid;
+ size_t len;
+ struct dpv_file_node *curfile;
+ struct dpv_file_node *first_file;
+ struct dpv_file_node *list_head;
+ struct timeval now;
+ struct timeval start;
+ char init_prompt[PROMPT_MAX + 1] = "";
+
+ /* Initialize globals to default values */
+ aprompt = NULL;
+ pprompt = NULL;
+ options = 0;
+ action = NULL;
+ backtitle = NULL;
+ debug = FALSE;
+ dialog_test = FALSE;
+ dialog_updates_per_second = DIALOG_UPDATES_PER_SEC;
+ display_limit = DISPLAY_LIMIT_DEFAULT;
+ display_type = DPV_DISPLAY_LIBDIALOG;
+ label_size = LABEL_SIZE_DEFAULT;
+ msg_done = NULL;
+ msg_fail = NULL;
+ msg_pending = NULL;
+ no_labels = FALSE;
+ output = NULL;
+ output_type = DPV_OUTPUT_NONE;
+ pbar_size = PBAR_SIZE_DEFAULT;
+ status_format_custom = NULL;
+ status_updates_per_second = STATUS_UPDATES_PER_SEC;
+ title = NULL;
+ wide = FALSE;
+
+ /* Process config options (overriding defaults) */
+ if (config != NULL) {
+ if (config->aprompt != NULL) {
+ if (aprompt == NULL) {
+ aprompt = malloc(DPV_APROMPT_MAX);
+ if (aprompt == NULL)
+ return (-1);
+ }
+ snprintf(aprompt, DPV_APROMPT_MAX, "%s",
+ config->aprompt);
+ }
+ if (config->pprompt != NULL) {
+ if (pprompt == NULL) {
+ pprompt = malloc(DPV_PPROMPT_MAX + 2);
+ /* +2 is for implicit "\n" appended later */
+ if (pprompt == NULL)
+ return (-1);
+ }
+ snprintf(pprompt, DPV_APROMPT_MAX, "%s",
+ config->pprompt);
+ }
+
+ options = config->options;
+ action = config->action;
+ backtitle = config->backtitle;
+ debug = config->debug;
+ dialog_test = ((options & DPV_TEST_MODE) != 0);
+ dialog_updates_per_second = config->dialog_updates_per_second;
+ display_limit = config->display_limit;
+ display_type = config->display_type;
+ label_size = config->label_size;
+ msg_done = (char *)config->msg_done;
+ msg_fail = (char *)config->msg_fail;
+ msg_pending = (char *)config->msg_pending;
+ no_labels = ((options & DPV_NO_LABELS) != 0);
+ no_overrun = ((options & DPV_NO_OVERRUN) != 0);
+ output = config->output;
+ output_type = config->output_type;
+ pbar_size = config->pbar_size;
+ status_updates_per_second = config->status_updates_per_second;
+ title = config->title;
+ wide = ((options & DPV_WIDE_MODE) != 0);
+
+ /* Enforce some minimums (pedantic) */
+ if (display_limit < -1)
+ display_limit = -1;
+ if (label_size < -1)
+ label_size = -1;
+ if (pbar_size < -1)
+ pbar_size = -1;
+
+ /* For the mini-pbar, -1 means hide, zero is invalid unless
+ * only one file is given */
+ if (pbar_size == 0) {
+ if (file_list == NULL || file_list->next == NULL)
+ pbar_size = -1;
+ else
+ pbar_size = PBAR_SIZE_DEFAULT;
+ }
+
+ /* For the label, -1 means auto-size, zero is invalid unless
+ * specifically requested through the use of options flag */
+ if (label_size == 0 && no_labels == FALSE)
+ label_size = LABEL_SIZE_DEFAULT;
+
+ /* Status update should not be zero */
+ if (status_updates_per_second == 0)
+ status_updates_per_second = STATUS_UPDATES_PER_SEC;
+ } /* config != NULL */
+
+ /* Process the type of display we've been requested to produce */
+ switch (display_type) {
+ case DPV_DISPLAY_STDOUT:
+ debug = TRUE;
+ use_color = FALSE;
+ use_dialog = FALSE;
+ use_libdialog = FALSE;
+ use_xdialog = FALSE;
+ break;
+ case DPV_DISPLAY_DIALOG:
+ use_color = TRUE;
+ use_dialog = TRUE;
+ use_libdialog = FALSE;
+ use_xdialog = FALSE;
+ break;
+ case DPV_DISPLAY_XDIALOG:
+ snprintf(dialog, PATH_MAX, XDIALOG);
+ use_color = FALSE;
+ use_dialog = FALSE;
+ use_libdialog = FALSE;
+ use_xdialog = TRUE;
+ break;
+ default:
+ use_color = TRUE;
+ use_dialog = FALSE;
+ use_libdialog = TRUE;
+ use_xdialog = FALSE;
+ break;
+ } /* display_type */
+
+ /* Enforce additional minimums that require knowing our display type */
+ if (dialog_updates_per_second == 0)
+ dialog_updates_per_second = use_xdialog ?
+ XDIALOG_UPDATES_PER_SEC : DIALOG_UPDATES_PER_SEC;
+
+ /* Allow forceful override of use_color */
+ if (config != NULL && (config->options & DPV_USE_COLOR) != 0)
+ use_color = TRUE;
+
+ /* Count the number of files in provided list of dpv_file_node's */
+ if (use_dialog && pprompt != NULL && *pprompt != '\0')
+ pprompt_nls = dialog_prompt_nlstate(pprompt);
+
+ max_cols = dialog_maxcols();
+ if (label_size == -1)
+ shrink_label_size = TRUE;
+
+ /* Process file arguments */
+ for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
+ dpv_nfiles++;
+
+ /* dialog(3) only expands literal newlines */
+ if (use_libdialog) strexpandnl(curfile->name);
+
+ /* Optionally calculate label size for file */
+ if (shrink_label_size) {
+ nls = FALSE;
+ name = curfile->name;
+ if (curfile == file_list)
+ nls = pprompt_nls;
+ last = (char *)dialog_prompt_lastline(name, nls);
+ if (use_dialog) {
+ c = *last;
+ *last = '\0';
+ nls = dialog_prompt_nlstate(name);
+ *last = c;
+ }
+ len = dialog_prompt_longestline(last, nls);
+ if ((int)len > (label_size - 3)) {
+ if (label_size > 0)
+ label_size += 3;
+ label_size = len;
+ /* Room for ellipsis (unless NULL) */
+ if (label_size > 0)
+ label_size += 3;
+ }
+
+ if (max_cols > 0 && label_size > (max_cols - pbar_size
+ - 9))
+ label_size = max_cols - pbar_size - 9;
+ }
+
+ if (debug)
+ warnx("label=[%s] path=[%s] size=%lli",
+ curfile->name, curfile->path, curfile->length);
+ } /* file_list */
+
+ /* Optionally process the contents of DIALOGRC (~/.dialogrc) */
+ if (use_dialog) {
+ res = parse_dialogrc();
+ if (debug && res == 0) {
+ warnx("Successfully read `%s' config file", DIALOGRC);
+ warnx("use_shadow = %i (Boolean)", use_shadow);
+ warnx("use_colors = %i (Boolean)", use_colors);
+ warnx("gauge_color=[%s] (FBH)", gauge_color);
+ }
+ } else if (use_libdialog) {
+ init_dialog(stdin, stdout);
+ use_shadow = dialog_state.use_shadow;
+ use_colors = dialog_state.use_colors;
+ gauge_color[0] = 48 + dlg_color_table[GAUGE_ATTR].fg;
+ gauge_color[1] = 48 + dlg_color_table[GAUGE_ATTR].bg;
+ gauge_color[2] = dlg_color_table[GAUGE_ATTR].hilite ?
+ 'b' : 'B';
+ gauge_color[3] = '\0';
+ end_dialog();
+ if (debug) {
+ warnx("Finished initializing dialog(3) library");
+ warnx("use_shadow = %i (Boolean)", use_shadow);
+ warnx("use_colors = %i (Boolean)", use_colors);
+ warnx("gauge_color=[%s] (FBH)", gauge_color);
+ }
+ }
+
+ /* Enable mini progress bar automatically for stdin streams if unable
+ * to calculate progress (missing `lines:' syntax). */
+ if (dpv_nfiles <= 1 && file_list != NULL && file_list->length < 0 &&
+ !dialog_test)
+ pbar_size = PBAR_SIZE_DEFAULT;
+
+ /* If $USE_COLOR is set and non-NULL enable color; otherwise disable */
+ if ((cp = getenv(ENV_USE_COLOR)) != 0)
+ use_color = *cp != '\0' ? 1 : 0;
+
+ /* Print error and return `-1' if not given at least one name */
+ if (dpv_nfiles == 0) {
+ warnx("%s: no labels provided", __func__);
+ return (-1);
+ } else if (debug)
+ warnx("%s: %u label%s provided", __func__, dpv_nfiles,
+ dpv_nfiles == 1 ? "" : "s");
+
+ /* If only one file and pbar size is zero, default to `-1' */
+ if (dpv_nfiles <= 1 && pbar_size == 0)
+ pbar_size = -1;
+
+ /* Print some debugging information */
+ if (debug) {
+ warnx("%s: %s(%i) max rows x cols = %i x %i",
+ __func__, use_xdialog ? XDIALOG : DIALOG,
+ use_libdialog ? 3 : 1, dialog_maxrows(),
+ dialog_maxcols());
+ }
+
+ /* Xdialog(1) updates a lot slower than dialog(1) */
+ if (dialog_test && use_xdialog)
+ increment = XDIALOG_INCREMENT;
+
+ /* Always add implicit newline to pprompt (when specified) */
+ if (pprompt != NULL && *pprompt != '\0') {
+ len = strlen(pprompt);
+ /*
+ * NOTE: pprompt = malloc(PPROMPT_MAX + 2)
+ * NOTE: (see getopt(2) section above for pprompt allocation)
+ */
+ pprompt[len++] = '\\';
+ pprompt[len++] = 'n';
+ pprompt[len++] = '\0';
+ }
+
+ /* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
+ if (use_xdialog && pprompt != NULL) {
+ /* Replace `\n' with `\n\\n\n' in pprompt */
+ len = strlen(pprompt);
+ len += strcount(pprompt, "\\n") * 2;
+ if (len > DPV_PPROMPT_MAX)
+ errx(EXIT_FAILURE, "%s: Oops, pprompt buffer overflow "
+ "(%zu > %i)", __func__, len, DPV_PPROMPT_MAX);
+ if (replaceall(pprompt, "\\n", "\n\\n\n") < 0)
+ err(EXIT_FAILURE, "%s: replaceall()", __func__);
+ }
+ /* libdialog requires literal newlines */
+ else if (use_libdialog && pprompt != NULL)
+ strexpandnl(pprompt);
+
+ /* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
+ if (use_xdialog && aprompt != NULL) {
+ /* Replace `\n' with `\n\\n\n' in aprompt */
+ len = strlen(aprompt);
+ len += strcount(aprompt, "\\n") * 2;
+ if (len > DPV_APROMPT_MAX)
+ errx(EXIT_FAILURE, "%s: Oops, aprompt buffer overflow "
+ " (%zu > %i)", __func__, len, DPV_APROMPT_MAX);
+ if (replaceall(aprompt, "\\n", "\n\\n\n") < 0)
+ err(EXIT_FAILURE, "%s: replaceall()", __func__);
+ }
+ /* libdialog requires literal newlines */
+ else if (use_libdialog && aprompt != NULL)
+ strexpandnl(aprompt);
+
+ /*
+ * Warn user about an obscure dialog(1) bug (neither Xdialog(1) nor
+ * libdialog are affected) in the `--gauge' widget. If the first non-
+ * whitespace letter of "{new_prompt}" in "XXX\n{new_prompt}\nXXX\n"
+ * is a number, the number can sometimes be mistaken for a percentage
+ * to the overall progressbar. Other nasty side-effects such as the
+ * entire prompt not displaying or displaying improperly are caused by
+ * this bug too.
+ *
+ * NOTE: When we can use color, we have a work-around... prefix the
+ * output with `\Zn' (used to terminate ANSI and reset to normal).
+ */
+ if (use_dialog && !use_color) {
+ backslash = 0;
+
+ /* First, check pprompt (falls through if NULL) */
+ fc = pprompt;
+ while (fc != NULL && *fc != '\0') {
+ if (*fc == '\n') /* leading literal newline OK */
+ break;
+ if (!isspace(*fc) && *fc != '\\' && backslash == 0)
+ break;
+ else if (backslash > 0 && *fc != 'n')
+ break;
+ else if (*fc == '\\') {
+ backslash++;
+ if (backslash > 2)
+ break; /* we're safe */
+ }
+ fc++;
+ }
+ /* First non-whitespace character that dialog(1) will see */
+ if (fc != NULL && *fc >= '0' && *fc <= '9')
+ warnx("%s: WARNING! text argument to `-p' begins with "
+ "a number (not recommended)", __func__);
+ else if (fc > pprompt)
+ warnx("%s: WARNING! text argument to `-p' begins with "
+ "whitespace (not recommended)", __func__);
+
+ /*
+ * If no pprompt or pprompt is all whitespace, check the first
+ * file name provided to make sure it is alright too.
+ */
+ if ((pprompt == NULL || *fc == '\0') && file_list != NULL) {
+ first_file = file_list;
+ fc = first_file->name;
+ while (fc != NULL && *fc != '\0' && isspace(*fc))
+ fc++;
+ /* First non-whitespace char that dialog(1) will see */
+ if (fc != NULL && *fc >= '0' && *fc <= '9')
+ warnx("%s: WARNING! File name `%s' begins "
+ "with a number (use `-p text' for safety)",
+ __func__, first_file->name);
+ }
+ }
+
+ dprompt_init(file_list);
+ /* Reads: label_size pbar_size pprompt aprompt dpv_nfiles */
+ /* Inits: dheight and dwidth */
+
+ if (!debug) {
+ /* Internally create the initial `--gauge' prompt text */
+ dprompt_recreate(file_list, (struct dpv_file_node *)NULL, 0);
+
+ /* Spawn [X]dialog(1) `--gauge', returning pipe descriptor */
+ if (use_libdialog) {
+ status_printf("");
+ dprompt_libprint(pprompt, aprompt, 0);
+ } else {
+ dprompt_sprint(init_prompt, pprompt, aprompt);
+ dialog_out = dialog_spawn_gauge(init_prompt, &pid);
+ dprompt_dprint(dialog_out, pprompt, aprompt, 0);
+ }
+ } /* !debug */
+
+ /* Seed the random(3) generator */
+ if (dialog_test)
+ srandom(0xf1eeface);
+
+ /* Set default/custom status line format */
+ if (dpv_nfiles > 1) {
+ snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
+ DPV_STATUS_MANY);
+ status_format_custom = config->status_many;
+ } else {
+ snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
+ DPV_STATUS_SOLO);
+ status_format_custom = config->status_solo;
+ }
+
+ /* Add test mode identifier to default status line if enabled */
+ if (dialog_test && (strlen(status_format_default) + 12) <
+ DPV_STATUS_FORMAT_MAX)
+ strcat(status_format_default, " [TEST MODE]");
+
+ /* Verify custom status format */
+ status_fmt = fmtcheck(status_format_custom, status_format_default);
+ if (status_format_custom != NULL &&
+ status_fmt == status_format_default) {
+ warnx("WARNING! Invalid status_format configuration `%s'",
+ status_format_custom);
+ warnx("Default status_format `%s'", status_format_default);
+ }
+
+ /* Record when we started (used to prevent updating too quickly) */
+ (void)gettimeofday(&start, (struct timezone *)NULL);
+
+ /* Calculate number of microseconds in-between sub-second updates */
+ if (status_updates_per_second != 0)
+ status_update_usec = 1000000 / status_updates_per_second;
+ if (dialog_updates_per_second != 0)
+ dialog_update_usec = 1000000 / dialog_updates_per_second;
+
+ /*
+ * Process the file list [serially] (one for each argument passed)
+ */
+ files_left = dpv_nfiles;
+ list_head = file_list;
+ for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
+ keep_going = TRUE;
+ output_out = -1;
+ pct = 0;
+ nthfile++;
+ files_left--;
+
+ if (dpv_interrupt)
+ break;
+ if (dialog_test)
+ pct = 0 - increment;
+
+ /* Attempt to spawn output program for this file */
+ if (!dialog_test && output != NULL) {
+ mask = umask(0022);
+ (void)umask(mask);
+
+ switch (output_type) {
+ case DPV_OUTPUT_SHELL:
+ output_out = shell_spawn_pipecmd(output,
+ curfile->name, &output_pid);
+ break;
+ case DPV_OUTPUT_FILE:
+ path_fmt = fmtcheck(output, "%s");
+ if (path_fmt == output)
+ len = snprintf(pathbuf,
+ PATH_MAX, output, curfile->name);
+ else
+ len = snprintf(pathbuf,
+ PATH_MAX, "%s", output);
+ if (len >= PATH_MAX) {
+ warnx("%s:%d:%s: pathbuf[%u] too small"
+ "to hold output argument",
+ __FILE__, __LINE__, __func__,
+ PATH_MAX);
+ return (-1);
+ }
+ if ((output_out = open(pathbuf,
+ O_CREAT|O_WRONLY, DEFFILEMODE & ~mask))
+ < 0) {
+ warn("%s", pathbuf);
+ return (-1);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ while (!dpv_interrupt && keep_going) {
+ if (dialog_test) {
+ usleep(50000);
+ pct += increment;
+ dpv_overall_read +=
+ (int)(random() / 512 / dpv_nfiles);
+ /* 512 limits fake readout to Megabytes */
+ } else if (action != NULL)
+ pct = action(curfile, output_out);
+
+ if (no_overrun || dialog_test)
+ keep_going = (pct < 100);
+ else {
+ status = curfile->status;
+ keep_going = (status == DPV_STATUS_RUNNING);
+ }
+
+ /* Get current time and calculate seconds elapsed */
+ gettimeofday(&now, (struct timezone *)NULL);
+ now.tv_sec = now.tv_sec - start.tv_sec;
+ now.tv_usec = now.tv_usec - start.tv_usec;
+ if (now.tv_usec < 0)
+ now.tv_sec--, now.tv_usec += 1000000;
+ seconds = now.tv_sec + (now.tv_usec / 1000000.0);
+
+ /* Update dialog (be it dialog(3), dialog(1), etc.) */
+ if ((dialog_updates_per_second != 0 &&
+ (
+ seconds != dialog_old_seconds ||
+ now.tv_usec - dialog_last_update >=
+ dialog_update_usec ||
+ nthfile != dialog_old_nthfile
+ )) || pct == 100
+ ) {
+ /* Calculate overall progress (rounding up) */
+ overall = (100 * nthfile - 100 + pct) /
+ dpv_nfiles;
+ if (((100 * nthfile - 100 + pct) * 10 /
+ dpv_nfiles % 100) > 50)
+ overall++;
+
+ dprompt_recreate(list_head, curfile, pct);
+
+ if (use_libdialog && !debug) {
+ /* Update dialog(3) widget */
+ dprompt_libprint(pprompt, aprompt,
+ overall);
+ } else {
+ /* stdout, dialog(1), or Xdialog(1) */
+ dprompt_dprint(dialog_out, pprompt,
+ aprompt, overall);
+ fsync(dialog_out);
+ }
+ dialog_old_seconds = seconds;
+ dialog_old_nthfile = nthfile;
+ dialog_last_update = now.tv_usec;
+ }
+
+ /* Update the status line */
+ if ((use_libdialog && !debug) &&
+ status_updates_per_second != 0 &&
+ (
+ keep_going != TRUE ||
+ seconds != status_old_seconds ||
+ now.tv_usec - status_last_update >=
+ status_update_usec ||
+ nthfile != status_old_nthfile
+ )
+ ) {
+ status_printf(status_fmt, dpv_overall_read,
+ (dpv_overall_read / (seconds == 0 ? 1 :
+ seconds) * 1.0),
+ 1, /* XXX until we add parallelism XXX */
+ files_left);
+ status_old_seconds = seconds;
+ status_old_nthfile = nthfile;
+ status_last_update = now.tv_usec;
+ }
+ }
+
+ if (!dialog_test && output_out >= 0) {
+ close(output_out);
+ waitpid(output_pid, (int *)NULL, 0);
+ }
+
+ if (dpv_abort)
+ break;
+
+ /* Advance head of list when we hit the max display lines */
+ if (display_limit > 0 && nthfile % display_limit == 0)
+ list_head = curfile->next;
+ }
+
+ if (!debug) {
+ if (use_libdialog)
+ end_dialog();
+ else {
+ close(dialog_out);
+ waitpid(pid, (int *)NULL, 0);
+ }
+ if (!dpv_interrupt)
+ printf("\n");
+ } else
+ warnx("%s: %lli lines read", __func__, dpv_overall_read);
+
+ if (dpv_interrupt || dpv_abort)
+ return (-1);
+ else
+ return (0);
+}
+
+/*
+ * Free allocated items initialized by dpv()
+ */
+void
+dpv_free(void)
+{
+ dialogrc_free();
+ dprompt_free();
+ dialog_maxsize_free();
+ if (aprompt != NULL) {
+ free(aprompt);
+ aprompt = NULL;
+ }
+ if (pprompt != NULL) {
+ free(pprompt);
+ pprompt = NULL;
+ }
+ status_free();
+}
diff --git a/lib/libdpv/dpv.h b/lib/libdpv/dpv.h
new file mode 100644
index 0000000..dbcd59b
--- /dev/null
+++ b/lib/libdpv/dpv.h
@@ -0,0 +1,161 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DPV_H_
+#define _DPV_H_
+
+#include <sys/types.h>
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/* Data to process */
+extern long long dpv_overall_read;
+
+/* Interrupt flag */
+extern int dpv_interrupt; /* Set to TRUE in interrupt handler */
+extern int dpv_abort; /* Set to true in callback to abort */
+
+/*
+ * Display types for use with display_type member of dpv_config structure
+ */
+enum dpv_display {
+ DPV_DISPLAY_LIBDIALOG = 0, /* Display using dialog(3) (default) */
+ DPV_DISPLAY_STDOUT, /* Display on stdout */
+ DPV_DISPLAY_DIALOG, /* Display using spawned dialog(1) */
+ DPV_DISPLAY_XDIALOG, /* Display using spawned Xdialog(1) */
+};
+
+/*
+ * Output types for use with output_type member of dpv_config structure
+ */
+enum dpv_output {
+ DPV_OUTPUT_NONE = 0, /* No output (default) */
+ DPV_OUTPUT_FILE, /* Read `output' member as file path */
+ DPV_OUTPUT_SHELL, /* Read `output' member as shell cmd */
+};
+
+/*
+ * Activity types for use with status member of dpv_file_node structure.
+ * If you set a status other than DPV_STATUS_RUNNING on the current file in the
+ * action callback of dpv_config structure, you'll end callbacks for that
+ * dpv_file_node.
+ */
+enum dpv_status {
+ DPV_STATUS_RUNNING = 0, /* Running (default) */
+ DPV_STATUS_DONE, /* Completed */
+ DPV_STATUS_FAILED, /* Oops, something went wrong */
+};
+
+/*
+ * Anatomy of file option; pass an array of these as dpv() file_list argument
+ * terminated with a NULL pointer.
+ */
+struct dpv_file_node {
+ enum dpv_status status; /* status of read operation */
+ char *msg; /* display instead of "Done/Fail" */
+ char *name; /* name of file to read */
+ char *path; /* path to file */
+ long long length; /* expected size */
+ long long read; /* number units read (e.g., bytes) */
+ struct dpv_file_node *next; /* pointer to next (end with NULL) */
+};
+
+/*
+ * Anatomy of config option to pass as dpv() config argument
+ */
+struct dpv_config {
+ enum dpv_display display_type; /* Display (default TYPE_LIBDIALOG) */
+ enum dpv_output output_type; /* Output (default TYPE_NONE) */
+ int debug; /* Enable debugging output on stderr */
+ int display_limit; /* Files per `page'. Default -1 */
+ int label_size; /* Label size. Default 28 */
+ int pbar_size; /* Mini-progress size. See dpv(3) */
+ int dialog_updates_per_second; /* Progress updates/s. Default 16 */
+ int status_updates_per_second; /* dialog(3) status updates/second.
+ * Default 2 */
+ uint16_t options; /* Special options. Default 0 */
+ char *title; /* widget title */
+ char *backtitle; /* Widget backtitle */
+ char *aprompt; /* Prompt append. Default NULL */
+ char *pprompt; /* Prompt prefix. Default NULL */
+ char *msg_done; /* Progress text. Default `Done' */
+ char *msg_fail; /* Progress text. Default `Fail' */
+ char *msg_pending; /* Progress text. Default `Pending' */
+ char *output; /* Output format string; see dpv(3) */
+ const char *status_solo; /* dialog(3) solo-status format.
+ * Default DPV_STATUS_SOLO */
+ const char *status_many; /* dialog(3) many-status format.
+ * Default DPV_STATUS_MANY */
+
+ /*
+ * Function pointer; action to perform data transfer
+ */
+ int (*action)(struct dpv_file_node *file, int out);
+};
+
+/*
+ * Macros for dpv() options bitmask argument
+ */
+#define DPV_TEST_MODE 0x0001 /* Test mode (fake reading data) */
+#define DPV_WIDE_MODE 0x0002 /* prefix/append bump dialog width */
+#define DPV_NO_LABELS 0x0004 /* Hide file_node.name labels */
+#define DPV_USE_COLOR 0x0008 /* Override to force color output */
+#define DPV_NO_OVERRUN 0x0010 /* Stop transfers when they hit 100% */
+
+/*
+ * Limits (modify with extreme care)
+ */
+#define DPV_APROMPT_MAX 4096 /* Buffer size for `-a text' */
+#define DPV_DISPLAY_LIMIT 10 /* Max file progress lines */
+#define DPV_PPROMPT_MAX 4096 /* Buffer size for `-p text' */
+#define DPV_STATUS_FORMAT_MAX 80 /* Buffer size for `-u format' */
+
+/*
+ * Extra display information
+ */
+#define DPV_STATUS_SOLO "%'10lli bytes read @ %'9.1f bytes/sec."
+#define DPV_STATUS_MANY (DPV_STATUS_SOLO " [%i/%i busy/wait]")
+
+/*
+ * Strings
+ */
+#define DPV_DONE_DEFAULT "Done"
+#define DPV_FAIL_DEFAULT "Fail"
+#define DPV_PENDING_DEFAULT "Pending"
+
+__BEGIN_DECLS
+void dpv_free(void);
+int dpv(struct dpv_config *_config, struct dpv_file_node *_file_list);
+__END_DECLS
+
+#endif /* !_DPV_H_ */
diff --git a/lib/libdpv/dpv_private.h b/lib/libdpv/dpv_private.h
new file mode 100644
index 0000000..5164eb3
--- /dev/null
+++ b/lib/libdpv/dpv_private.h
@@ -0,0 +1,66 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DPV_PRIVATE_H_
+#define _DPV_PRIVATE_H_
+
+#include <sys/types.h>
+
+/* Debugging */
+extern uint8_t debug;
+
+/* Data to process */
+extern unsigned int dpv_nfiles;
+
+/* Extra display information */
+extern uint8_t no_labels;
+extern uint8_t wide;
+extern char *msg_done, *msg_fail, *msg_pending;
+extern char *pprompt, *aprompt;
+extern const char status_format[];
+
+/* Defaults */
+#define DIALOG_UPDATES_PER_SEC 16
+#define XDIALOG_UPDATES_PER_SEC 4
+#define DISPLAY_LIMIT_DEFAULT 0 /* Auto-calculate */
+#define LABEL_SIZE_DEFAULT 28
+#define PBAR_SIZE_DEFAULT 17
+#define STATUS_UPDATES_PER_SEC 2
+
+/* states for dprompt_add_files() of dprompt.c */
+enum dprompt_state {
+ DPROMPT_NONE = 0, /* Default */
+ DPROMPT_PENDING, /* Pending */
+ DPROMPT_PBAR, /* Progress bar */
+ DPROMPT_END_STATE, /* Done/Fail */
+ DPROMPT_DETAILS, /* dpv_file_node->read */
+ DPROMPT_CUSTOM_MSG, /* dpv_file_node->msg */
+ DPROMPT_MINIMAL, /* whitespace */
+};
+
+#endif /* !_DPV_PRIVATE_H_ */
diff --git a/lib/libdpv/status.c b/lib/libdpv/status.c
new file mode 100644
index 0000000..4bf4773
--- /dev/null
+++ b/lib/libdpv/status.c
@@ -0,0 +1,111 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <curses.h>
+#include <dialog.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dialog_util.h"
+#include "status.h"
+
+/* static globals */
+static char *status_buf = NULL;
+static int status_bufsize = -1;
+static int status_row;
+static int status_width;
+
+/*
+ * Print a `one-liner' status message at the bottom of the screen. Messages are
+ * trimmed to fit within the console length (ANSI coloring not accounted for).
+ */
+void
+status_printf(const char *fmt, ...)
+{
+ int n, attrs;
+ chtype color = dlg_color_pair(dlg_color_table[BUTTON_ACTIVE_ATTR].fg,
+ dlg_color_table[SCREEN_ATTR].bg) | A_BOLD;
+ va_list args;
+
+ status_row = tty_maxrows() - 1;
+ status_width = tty_maxcols();
+
+ /* NULL is a special convention meaning "erase the old stuff" */
+ if (fmt == NULL) {
+ move(status_row, 0);
+ clrtoeol();
+ return;
+ }
+
+ /* Resize buffer if terminal width is greater */
+ if ((status_width + 1) > status_bufsize) {
+ status_buf = realloc(status_buf, status_width + 1);
+ if (status_buf == NULL) {
+ status_bufsize = -1;
+ return;
+ }
+ status_bufsize = status_width + 1;
+ }
+
+ /* Print the message within a space-filled buffer */
+ memset(status_buf, ' ', status_width);
+ va_start(args, fmt);
+ n = vsnprintf(status_buf, status_width + 1, fmt, args);
+ va_end(args);
+
+ /* If vsnprintf(3) produced less bytes than the maximum, change the
+ * implicitly-added NUL-terminator into a space and terminate at max */
+ if (n < status_width) {
+ status_buf[n] = ' ';
+ status_buf[status_width] = '\0';
+ }
+
+ /* Print text in screen bg, button active fg, and bold */
+ attrs = getattrs(stdscr);
+ attrset(color);
+ mvaddstr(status_row, 0, status_buf);
+ attrset(attrs);
+
+ /* Seat the cursor over the last character at absolute lower-right */
+ move(status_row, status_width - 1);
+ refresh();
+}
+
+/*
+ * Free allocated items initialized by status_printf()
+ */
+void
+status_free(void)
+{
+ if (status_buf != NULL) {
+ free(status_buf);
+ status_buf = NULL;
+ }
+}
diff --git a/lib/libdpv/status.h b/lib/libdpv/status.h
new file mode 100644
index 0000000..002f7f1
--- /dev/null
+++ b/lib/libdpv/status.h
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _STATUS_H_
+#define _STATUS_H_
+
+#include <sys/cdefs.h>
+
+/* dialog(3) dlg_color_table[] attributes */
+#define SCREEN_ATTR 0 /* entry used for status line bg */
+#define BUTTON_ACTIVE_ATTR 5 /* entry used for status line fg */
+
+__BEGIN_DECLS
+void status_free(void);
+void status_printf(const char *_format, ...);
+__END_DECLS
+
+#endif /* !_STATUS_H_ */
diff --git a/lib/libdpv/util.c b/lib/libdpv/util.c
new file mode 100644
index 0000000..25fc1cb
--- /dev/null
+++ b/lib/libdpv/util.c
@@ -0,0 +1,107 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <limits.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "util.h"
+
+extern char **environ;
+static char cmdbuf[CMDBUFMAX] = "";
+static char shellcmd[PATH_MAX] = PATH_SHELL;
+static char *shellcmd_argv[6] = {
+ shellcmd,
+ __DECONST(char *, "-c"),
+ cmdbuf,
+ __DECONST(char *, "--"),
+ shellcmd,
+ NULL,
+};
+
+/*
+ * Spawn a sh(1) command. Writes the resulting process ID to the pid_t pointed
+ * at by `pid'. Returns a file descriptor (int) suitable for writing data to
+ * the spawned command (data written to file descriptor is seen as standard-in
+ * by the spawned sh(1) command). Returns `-1' if unable to spawn command.
+ *
+ * If cmd contains a single "%s" sequence, replace it with label if non-NULL.
+ */
+int
+shell_spawn_pipecmd(const char *cmd, const char *label, pid_t *pid)
+{
+ int error;
+ int len;
+ posix_spawn_file_actions_t action;
+#if SHELL_SPAWN_DEBUG
+ unsigned int i;
+#endif
+ int stdin_pipe[2] = { -1, -1 };
+
+ /* Populate argument array */
+ if (label != NULL && fmtcheck(cmd, "%s") == cmd)
+ len = snprintf(cmdbuf, CMDBUFMAX, cmd, label);
+ else
+ len = snprintf(cmdbuf, CMDBUFMAX, "%s", cmd);
+ if (len >= CMDBUFMAX) {
+ warnx("%s:%d:%s: cmdbuf[%u] too small to hold cmd argument",
+ __FILE__, __LINE__, __func__, CMDBUFMAX);
+ return (-1);
+ }
+
+ /* Open a pipe to communicate with [X]dialog(1) */
+ if (pipe(stdin_pipe) < 0)
+ err(EXIT_FAILURE, "%s: pipe(2)", __func__);
+
+ /* Fork sh(1) process */
+#if SHELL_SPAWN_DEBUG
+ fprintf(stderr, "%s: spawning `", __func__);
+ for (i = 0; shellcmd_argv[i] != NULL; i++) {
+ if (i == 0)
+ fprintf(stderr, "%s", shellcmd_argv[i]);
+ else if (i == 2)
+ fprintf(stderr, " '%s'", shellcmd_argv[i]);
+ else
+ fprintf(stderr, " %s", shellcmd_argv[i]);
+ }
+ fprintf(stderr, "'\n");
+#endif
+ posix_spawn_file_actions_init(&action);
+ posix_spawn_file_actions_adddup2(&action, stdin_pipe[0], STDIN_FILENO);
+ posix_spawn_file_actions_addclose(&action, stdin_pipe[1]);
+ error = posix_spawnp(pid, shellcmd, &action,
+ (const posix_spawnattr_t *)NULL, shellcmd_argv, environ);
+ if (error != 0)
+ err(EXIT_FAILURE, "%s: posix_spawnp(3)", __func__);
+
+ return stdin_pipe[1];
+}
diff --git a/lib/libdpv/util.h b/lib/libdpv/util.h
new file mode 100644
index 0000000..7bb3b18
--- /dev/null
+++ b/lib/libdpv/util.h
@@ -0,0 +1,50 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _UTIL_H_
+#define _UTIL_H_
+
+#include <sys/types.h>
+
+#include <paths.h>
+
+#define SHELL_SPAWN_DEBUG 0 /* Debug spawning of sh(1) commands */
+
+#ifdef _PATH_BSHELL
+#define PATH_SHELL _PATH_BSHELL
+#else
+#define PATH_SHELL "/bin/sh"
+#endif
+
+#define CMDBUFMAX 4096
+
+__BEGIN_DECLS
+int shell_spawn_pipecmd(const char *_cmd, const char *_label, pid_t *_pid);
+__END_DECLS
+
+#endif /* !_UTIL_H_ */
diff --git a/lib/libfigpar/Makefile b/lib/libfigpar/Makefile
new file mode 100644
index 0000000..6f01966
--- /dev/null
+++ b/lib/libfigpar/Makefile
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+LIB= figpar
+SHLIB_MAJOR= 0
+INCS= figpar.h string_m.h
+MAN= figpar.3
+MLINKS= figpar.3 get_config_option.3 \
+ figpar.3 parse_config.3 \
+ figpar.3 replaceall.3 \
+ figpar.3 strcount.3 \
+ figpar.3 strexpand.3 \
+ figpar.3 strexpandnl.3 \
+ figpar.3 strtolower.3
+
+CFLAGS+= -I${.CURDIR}
+
+SRCS= figpar.c string_m.c
+
+WARNS?= 6
+
+.include <bsd.lib.mk>
diff --git a/lib/libfigpar/figpar.3 b/lib/libfigpar/figpar.3
new file mode 100644
index 0000000..549808e
--- /dev/null
+++ b/lib/libfigpar/figpar.3
@@ -0,0 +1,251 @@
+.\" Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd Oct 24, 2014
+.Dt FIGPAR 3
+.Os
+.Sh NAME
+.Nm figpar ,
+.Nm parse_config ,
+.Nm get_config_option
+.Nd configuration file parsing library
+.Sh LIBRARY
+.Lb libfigpar
+.Sh SYNOPSIS
+.In figpar.h
+.Ft int
+.Fo parse_config
+.Fa "struct fp_config options[], const char *path"
+.Fa "int \*[lp]*unknown\*[rp]\*[lp]struct fp_config *option, uint32_t line"
+.Fa "char *directive, char *value\*[rp], uint8_t processing_options"
+.Fc
+.Ft "struct fp_config *"
+.Fo get_config_option
+.Fa "struct fp_config options[], const char *directive"
+.Fc
+.In string_m.h
+.Ft int
+.Fo replaceall
+.Fa "char *source, const char *find, const char *replace"
+.Fc
+.Ft unsigned int
+.Fo strcount
+.Fa "const char *source, const char *find"
+.Fc
+.Ft void
+.Fo strexpand
+.Fa "char *source"
+.Fc
+.Ft void
+.Fo strexpandnl
+.Fa "char *source"
+.Fc
+.Ft void
+.Fo strtolower
+.Fa "char *source"
+.Fc
+.Sh DESCRIPTION
+The
+.Nm
+library provides a light-weight, portable framework for parsing configuration
+files.
+The library uses
+.Xr open 2 ,
+.Xr read 2 ,
+and
+.Xr lseek 2
+within the file pointed to by
+.Fa path
+to find directives and values which are then made available to the application.
+.Pp
+Due to the fact that configuration files may have basic syntax differences,
+the library does not attempt to impose any structure on the data but instead
+provides raw data to a set of callback functions.
+These callback functions can in-turn initiate abort through their return value,
+allowing custom syntax validation during parsing.
+.Pp
+Configuration directives, types, and callback functions are provided through
+data structures defined in
+.In figpar.h :
+.Bd -literal -offset indent
+struct fp_config {
+ enum fp_cfgtype type; /* value type */
+ const char *directive; /* keyword */
+ union fp_cfgvalue value; /* value */
+
+ /* Pointer to function used when directive is found */
+ int (*action)(struct fp_config *option, uint32_t line,
+ char *directive, char *value);
+};
+
+enum fp_cfgtype {
+ FP_TYPE_NONE = 0x0000, /* for directives with no value */
+ FP_TYPE_BOOL = 0x0001, /* boolean */
+ FP_TYPE_INT = 0x0002, /* signed 32 bit integer */
+ FP_TYPE_UINT = 0x0004, /* unsigned 32 bit integer */
+ FP_TYPE_STR = 0x0008, /* string pointer */
+ FP_TYPE_STRARRAY = 0x0010, /* string array pointer */
+ FP_TYPE_DATA1 = 0x0020, /* void data type-1 (whatever) */
+ FP_TYPE_DATA2 = 0x0040, /* void data type-2 (whatever) */
+ FP_TYPE_DATA3 = 0x0080, /* void data type-3 (whatever) */
+ FP_TYPE_RESERVED1 = 0x0100, /* reserved data type-1 (future) */
+ FP_TYPE_RESERVED2 = 0x0200, /* reserved data type-2 (future) */
+ FP_TYPE_RESERVED3 = 0x0400, /* reserved data type-3 (future) */
+};
+
+union fp_cfgvalue {
+ void *data; /* Pointer to NUL-terminated string */
+ char *str; /* Pointer to NUL-terminated string */
+ char **strarray; /* Pointer to an array of strings */
+ int32_t num; /* Signed 32-bit integer value */
+ uint32_t u_num; /* Unsigned 32-bit integer value */
+ uint32_t boolean:1; /* Boolean integer value (0 or 1) */
+};
+.Ed
+.Pp
+The
+.Fa processing_options
+argument to
+.Fn parse_config
+is a mask of bit fields which indicate various
+processing options.
+The possible flags are as follows:
+.Bl -tag -width FP_BREAK_ON_SEMICOLON
+.It Dv FP_BREAK_ON_EQUALS
+An equals sign
+.Pq Ql Li =
+is normally considered part of the directive.
+This flag enables terminating the directive at the equals sign.
+Also makes equals sign optional and transient.
+.It Dv FP_BREAK_ON_SEMICOLON
+A semicolon
+.Pq Ql Li \;
+is normally considered part of the value.
+This flag enables terminating the value at the semicolon.
+Also allows multiple statements on a single line separated by semicolon.
+.It Dv FP_CASE_SENSITIVE
+Normally directives are matched case insensitively using
+.Xr fnmatch 3 .
+This flag enables directive matching to be case sensitive.
+.It Dv FP_REQUIRE_EQUALS
+If a directive is not followed by an equals, processing is aborted.
+.It Dv FP_STRICT_EQUALS
+Equals must be part of the directive to be considered a delimiter between
+directive and value.
+.El
+.Pp
+The
+.Fa options
+struct array pointer can be NULL and every directive will invoke the
+.Fn unknown
+function argument.
+.Pp
+The directive for each fp_config item in the
+.Fn parse_config
+options argument is matched against each parsed directive using
+.Xr fnmatch 3
+until a match is found.
+If a match is found, the
+.Fn action
+function for that fp_config directive is invoked with the line number,
+directive, and value.
+Otherwise if no match, the
+.Fn unknown
+function is invoked
+.Pq with the same arguments .
+.Pp
+If either
+.Fa action
+or
+.Fa unknown
+return non-zero,
+.Fn parse_config
+aborts reading the file and returns the error value to its caller.
+.Pp
+.Fn get_config_option
+traverses the options-array and returns the option that matches via
+.Xr strcmp 3 ,
+or if no match a pointer to a static dummy struct is returned
+.Pq whose values are all zero or NULL .
+.Pp
+The use of
+.Fa "struct fp_config"
+is entirely optional as-is the use of
+.Fa "enum fp_cfgtype"
+or
+.Fa "union fp_cfgvalue" .
+For example, you could choose to pass a NULL pointer to
+.Fn parse_config
+for the first argument and then provide a simple
+.Fa unknown
+function based on
+.Xr queue 3
+that populates a singly-linked list of your own struct containing the
+.Fa directive
+and
+.Fa value .
+.Pp
+In addition, the following miscellaneous string manipulation routines are
+provided by
+.In string_m.h :
+.Bl -tag -width strexpandnl()
+.It Fn replaceall
+Replace all occurrences of
+.Fa find
+in
+.Fa source
+with
+.Fa replace .
+.It Fn strcount
+Count the number of occurrences of one string that appear in the
+.Fa source
+string.
+Return value is the total count.
+An example use would be if you need to know how large a block of memory needs
+to be for a
+.Fn replaceall
+series.
+.It Fn strexpand
+Expand escape sequences in a buffer pointed to by
+.Fa source .
+.It Fn strexpandnl
+Expand only the escaped newlines in a buffer pointed to by
+.Fa source .
+.It Fn strtolower
+Convert a string to lower case.
+.El
+.Sh SEE ALSO
+.Xr queue 3
+.Sh HISTORY
+The
+.Nm
+library first appeared in
+.Fx 11.0 .
+.Sh AUTHORS
+.An Devin Teske Aq dteske@FreeBSD.org
+.Sh BUGS
+This is the first implementation of the library,
+and the interface may be subject to refinement.
diff --git a/lib/libfigpar/figpar.c b/lib/libfigpar/figpar.c
new file mode 100644
index 0000000..a97fc85
--- /dev/null
+++ b/lib/libfigpar/figpar.c
@@ -0,0 +1,469 @@
+/*-
+ * Copyright (c) 2002-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "figpar.h"
+#include "string_m.h"
+
+struct fp_config fp_dummy_config = {0, NULL, {0}, NULL};
+
+/*
+ * Search for config option (struct fp_config) in the array of config options,
+ * returning the struct whose directive matches the given parameter. If no
+ * match is found, a pointer to the static dummy array (above) is returned.
+ *
+ * This is to eliminate dependency on the index position of an item in the
+ * array, since the index position is more apt to be changed as code grows.
+ */
+struct fp_config *
+get_config_option(struct fp_config options[], const char *directive)
+{
+ uint32_t n;
+
+ /* Check arguments */
+ if (options == NULL || directive == NULL)
+ return (&fp_dummy_config);
+
+ /* Loop through the array, return the index of the first match */
+ for (n = 0; options[n].directive != NULL; n++)
+ if (strcmp(options[n].directive, directive) == 0)
+ return (&(options[n]));
+
+ /* Re-initialize the dummy variable in case it was written to */
+ fp_dummy_config.directive = NULL;
+ fp_dummy_config.type = 0;
+ fp_dummy_config.action = NULL;
+ fp_dummy_config.value.u_num = 0;
+
+ return (&fp_dummy_config);
+}
+
+/*
+ * Parse the configuration file at `path' and execute the `action' call-back
+ * functions for any directives defined by the array of config options (first
+ * argument).
+ *
+ * For unknown directives that are encountered, you can optionally pass a
+ * call-back function for the third argument to be called for unknowns.
+ *
+ * Returns zero on success; otherwise returns -1 and errno should be consulted.
+*/
+int
+parse_config(struct fp_config options[], const char *path,
+ int (*unknown)(struct fp_config *option, uint32_t line, char *directive,
+ char *value), uint16_t processing_options)
+{
+ uint8_t bequals;
+ uint8_t bsemicolon;
+ uint8_t case_sensitive;
+ uint8_t comment = 0;
+ uint8_t end;
+ uint8_t found;
+ uint8_t have_equals = 0;
+ uint8_t quote;
+ uint8_t require_equals;
+ uint8_t strict_equals;
+ char p[2];
+ char *directive;
+ char *t;
+ char *value;
+ int error;
+ int fd;
+ ssize_t r = 1;
+ uint32_t dsize;
+ uint32_t line = 1;
+ uint32_t n;
+ uint32_t vsize;
+ uint32_t x;
+ off_t charpos;
+ off_t curpos;
+ char rpath[PATH_MAX];
+
+ /* Sanity check: if no options and no unknown function, return */
+ if (options == NULL && unknown == NULL)
+ return (-1);
+
+ /* Processing options */
+ bequals = (processing_options & FP_BREAK_ON_EQUALS) == 0 ? 0 : 1;
+ bsemicolon = (processing_options & FP_BREAK_ON_SEMICOLON) == 0 ? 0 : 1;
+ case_sensitive = (processing_options & FP_CASE_SENSITIVE) == 0 ? 0 : 1;
+ require_equals = (processing_options & FP_REQUIRE_EQUALS) == 0 ? 0 : 1;
+ strict_equals = (processing_options & FP_STRICT_EQUALS) == 0 ? 0 : 1;
+
+ /* Initialize strings */
+ directive = value = 0;
+ vsize = dsize = 0;
+
+ /* Resolve the file path */
+ if (realpath(path, rpath) == 0)
+ return (-1);
+
+ /* Open the file */
+ if ((fd = open(rpath, O_RDONLY)) < 0)
+ return (-1);
+
+ /* Read the file until EOF */
+ while (r != 0) {
+ r = read(fd, p, 1);
+
+ /* skip to the beginning of a directive */
+ while (r != 0 && (isspace(*p) || *p == '#' || comment ||
+ (bsemicolon && *p == ';'))) {
+ if (*p == '#')
+ comment = 1;
+ else if (*p == '\n') {
+ comment = 0;
+ line++;
+ }
+ r = read(fd, p, 1);
+ }
+ /* Test for EOF; if EOF then no directive was found */
+ if (r == 0) {
+ close(fd);
+ return (0);
+ }
+
+ /* Get the current offset */
+ curpos = lseek(fd, 0, SEEK_CUR) - 1;
+ if (curpos == -1) {
+ close(fd);
+ return (-1);
+ }
+
+ /* Find the length of the directive */
+ for (n = 0; r != 0; n++) {
+ if (isspace(*p))
+ break;
+ if (bequals && *p == '=') {
+ have_equals = 1;
+ break;
+ }
+ if (bsemicolon && *p == ';')
+ break;
+ r = read(fd, p, 1);
+ }
+
+ /* Test for EOF, if EOF then no directive was found */
+ if (n == 0 && r == 0) {
+ close(fd);
+ return (0);
+ }
+
+ /* Go back to the beginning of the directive */
+ error = (int)lseek(fd, curpos, SEEK_SET);
+ if (error == (curpos - 1)) {
+ close(fd);
+ return (-1);
+ }
+
+ /* Allocate and read the directive into memory */
+ if (n > dsize) {
+ if ((directive = realloc(directive, n + 1)) == NULL) {
+ close(fd);
+ return (-1);
+ }
+ dsize = n;
+ }
+ r = read(fd, directive, n);
+
+ /* Advance beyond the equals sign if appropriate/desired */
+ if (bequals && *p == '=') {
+ if (lseek(fd, 1, SEEK_CUR) != -1)
+ r = read(fd, p, 1);
+ if (strict_equals && isspace(*p))
+ *p = '\n';
+ }
+
+ /* Terminate the string */
+ directive[n] = '\0';
+
+ /* Convert directive to lower case before comparison */
+ if (!case_sensitive)
+ strtolower(directive);
+
+ /* Move to what may be the start of the value */
+ if (!(bsemicolon && *p == ';') &&
+ !(strict_equals && *p == '=')) {
+ while (r != 0 && isspace(*p) && *p != '\n')
+ r = read(fd, p, 1);
+ }
+
+ /* An equals sign may have stopped us, should we eat it? */
+ if (r != 0 && bequals && *p == '=' && !strict_equals) {
+ have_equals = 1;
+ r = read(fd, p, 1);
+ while (r != 0 && isspace(*p) && *p != '\n')
+ r = read(fd, p, 1);
+ }
+
+ /* If no value, allocate a dummy value and jump to action */
+ if (r == 0 || *p == '\n' || *p == '#' ||
+ (bsemicolon && *p == ';')) {
+ /* Initialize the value if not already done */
+ if (value == NULL && (value = malloc(1)) == NULL) {
+ close(fd);
+ return (-1);
+ }
+ value[0] = '\0';
+ goto call_function;
+ }
+
+ /* Get the current offset */
+ curpos = lseek(fd, 0, SEEK_CUR) - 1;
+ if (curpos == -1) {
+ close(fd);
+ return (-1);
+ }
+
+ /* Find the end of the value */
+ quote = 0;
+ end = 0;
+ while (r != 0 && end == 0) {
+ /* Advance to the next character if we know we can */
+ if (*p != '\"' && *p != '#' && *p != '\n' &&
+ (!bsemicolon || *p != ';')) {
+ r = read(fd, p, 1);
+ continue;
+ }
+
+ /*
+ * If we get this far, we've hit an end-key
+ */
+
+ /* Get the current offset */
+ charpos = lseek(fd, 0, SEEK_CUR) - 1;
+ if (charpos == -1) {
+ close(fd);
+ return (-1);
+ }
+
+ /*
+ * Go back so we can read the character before the key
+ * to check if the character is escaped (which means we
+ * should continue).
+ */
+ error = (int)lseek(fd, -2, SEEK_CUR);
+ if (error == -3) {
+ close(fd);
+ return (-1);
+ }
+ r = read(fd, p, 1);
+
+ /*
+ * Count how many backslashes there are (an odd number
+ * means the key is escaped, even means otherwise).
+ */
+ for (n = 1; *p == '\\'; n++) {
+ /* Move back another offset to read */
+ error = (int)lseek(fd, -2, SEEK_CUR);
+ if (error == -3) {
+ close(fd);
+ return (-1);
+ }
+ r = read(fd, p, 1);
+ }
+
+ /* Move offset back to the key and read it */
+ error = (int)lseek(fd, charpos, SEEK_SET);
+ if (error == (charpos - 1)) {
+ close(fd);
+ return (-1);
+ }
+ r = read(fd, p, 1);
+
+ /*
+ * If an even number of backslashes was counted meaning
+ * key is not escaped, we should evaluate what to do.
+ */
+ if ((n & 1) == 1) {
+ switch (*p) {
+ case '\"':
+ /*
+ * Flag current sequence of characters
+ * to follow as being quoted (hashes
+ * are not considered comments).
+ */
+ quote = !quote;
+ break;
+ case '#':
+ /*
+ * If we aren't in a quoted series, we
+ * just hit an inline comment and have
+ * found the end of the value.
+ */
+ if (!quote)
+ end = 1;
+ break;
+ case '\n':
+ /*
+ * Newline characters must always be
+ * escaped, whether inside a quoted
+ * series or not, otherwise they
+ * terminate the value.
+ */
+ end = 1;
+ case ';':
+ if (!quote && bsemicolon)
+ end = 1;
+ break;
+ }
+ } else if (*p == '\n')
+ /* Escaped newline character. increment */
+ line++;
+
+ /* Advance to the next character */
+ r = read(fd, p, 1);
+ }
+
+ /* Get the current offset */
+ charpos = lseek(fd, 0, SEEK_CUR) - 1;
+ if (charpos == -1) {
+ close(fd);
+ return (-1);
+ }
+
+ /* Get the length of the value */
+ n = (uint32_t)(charpos - curpos);
+ if (r != 0) /* more to read, but don't read ending key */
+ n--;
+
+ /* Move offset back to the beginning of the value */
+ error = (int)lseek(fd, curpos, SEEK_SET);
+ if (error == (curpos - 1)) {
+ close(fd);
+ return (-1);
+ }
+
+ /* Allocate and read the value into memory */
+ if (n > vsize) {
+ if ((value = realloc(value, n + 1)) == NULL) {
+ close(fd);
+ return (-1);
+ }
+ vsize = n;
+ }
+ r = read(fd, value, n);
+
+ /* Terminate the string */
+ value[n] = '\0';
+
+ /* Cut trailing whitespace off by termination */
+ t = value + n;
+ while (isspace(*--t))
+ *t = '\0';
+
+ /* Escape the escaped quotes (replaceall is in string_m.c) */
+ x = strcount(value, "\\\""); /* in string_m.c */
+ if (x != 0 && (n + x) > vsize) {
+ if ((value = realloc(value, n + x + 1)) == NULL) {
+ close(fd);
+ return (-1);
+ }
+ vsize = n + x;
+ }
+ if (replaceall(value, "\\\"", "\\\\\"") < 0) {
+ /* Replace operation failed for some unknown reason */
+ close(fd);
+ return (-1);
+ }
+
+ /* Remove all new line characters */
+ if (replaceall(value, "\\\n", "") < 0) {
+ /* Replace operation failed for some unknown reason */
+ close(fd);
+ return (-1);
+ }
+
+ /* Resolve escape sequences */
+ strexpand(value); /* in string_m.c */
+
+call_function:
+ /* Abort if we're seeking only assignments */
+ if (require_equals && !have_equals)
+ return (-1);
+
+ found = have_equals = 0; /* reset */
+
+ /* If there are no options defined, call unknown and loop */
+ if (options == NULL && unknown != NULL) {
+ error = unknown(NULL, line, directive, value);
+ if (error != 0) {
+ close(fd);
+ return (error);
+ }
+ continue;
+ }
+
+ /* Loop through the array looking for a match for the value */
+ for (n = 0; options[n].directive != NULL; n++) {
+ error = fnmatch(options[n].directive, directive,
+ FNM_NOESCAPE);
+ if (error == 0) {
+ found = 1;
+ /* Call function for array index item */
+ if (options[n].action != NULL) {
+ error = options[n].action(
+ &options[n],
+ line, directive, value);
+ if (error != 0) {
+ close(fd);
+ return (error);
+ }
+ }
+ } else if (error != FNM_NOMATCH) {
+ /* An error has occurred */
+ close(fd);
+ return (-1);
+ }
+ }
+ if (!found && unknown != NULL) {
+ /*
+ * No match was found for the value we read from the
+ * file; call function designated for unknown values.
+ */
+ error = unknown(NULL, line, directive, value);
+ if (error != 0) {
+ close(fd);
+ return (error);
+ }
+ }
+ }
+
+ close(fd);
+ return (0);
+}
diff --git a/lib/libfigpar/figpar.h b/lib/libfigpar/figpar.h
new file mode 100644
index 0000000..16f825a
--- /dev/null
+++ b/lib/libfigpar/figpar.h
@@ -0,0 +1,99 @@
+/*-
+ * Copyright (c) 2002-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _FIGPAR_H_
+#define _FIGPAR_H_
+
+#include <sys/types.h>
+
+/*
+ * Union for storing various types of data in a single common container.
+ */
+union fp_cfgvalue {
+ void *data; /* Pointer to NUL-terminated string */
+ char *str; /* Pointer to NUL-terminated string */
+ char **strarray; /* Pointer to an array of strings */
+ int32_t num; /* Signed 32-bit integer value */
+ uint32_t u_num; /* Unsigned 32-bit integer value */
+ uint32_t boolean:1; /* Boolean integer value (0 or 1) */
+};
+
+/*
+ * Option types (based on above cfgvalue union)
+ */
+enum fp_cfgtype {
+ FP_TYPE_NONE = 0x0000, /* for directives with no value */
+ FP_TYPE_BOOL = 0x0001, /* boolean */
+ FP_TYPE_INT = 0x0002, /* signed 32 bit integer */
+ FP_TYPE_UINT = 0x0004, /* unsigned 32 bit integer */
+ FP_TYPE_STR = 0x0008, /* string pointer */
+ FP_TYPE_STRARRAY = 0x0010, /* string array pointer */
+ FP_TYPE_DATA1 = 0x0020, /* void data type-1 (whatever) */
+ FP_TYPE_DATA2 = 0x0040, /* void data type-2 (whatever) */
+ FP_TYPE_DATA3 = 0x0080, /* void data type-3 (whatever) */
+ FP_TYPE_RESERVED1 = 0x0100, /* reserved data type-1 (future) */
+ FP_TYPE_RESERVED2 = 0x0200, /* reserved data type-2 (future) */
+ FP_TYPE_RESERVED3 = 0x0400, /* reserved data type-3 (future) */
+};
+
+/*
+ * Options to parse_config() for processing_options bitmask
+ */
+#define FP_BREAK_ON_EQUALS 0x0001 /* stop reading directive at `=' */
+#define FP_BREAK_ON_SEMICOLON 0x0002 /* `;' starts a new line */
+#define FP_CASE_SENSITIVE 0x0004 /* directives are case sensitive */
+#define FP_REQUIRE_EQUALS 0x0008 /* assignment directives only */
+#define FP_STRICT_EQUALS 0x0010 /* `=' must be part of directive */
+
+/*
+ * Anatomy of a config file option
+ */
+struct fp_config {
+ enum fp_cfgtype type; /* Option value type */
+ const char *directive; /* config file keyword */
+ union fp_cfgvalue value; /* NB: set by action */
+
+ /*
+ * Function pointer; action to be taken when the directive is found
+ */
+ int (*action)(struct fp_config *option, uint32_t line, char *directive,
+ char *value);
+};
+extern struct fp_config fp_dummy_config;
+
+__BEGIN_DECLS
+int parse_config(struct fp_config _options[],
+ const char *_path,
+ int (*_unknown)(struct fp_config *_option,
+ uint32_t _line, char *_directive, char *_value),
+ uint16_t _processing_options);
+struct fp_config *get_config_option(struct fp_config _options[],
+ const char *_directive);
+__END_DECLS
+
+#endif /* _FIGPAR_H_ */
diff --git a/lib/libfigpar/string_m.c b/lib/libfigpar/string_m.c
new file mode 100644
index 0000000..d358e90
--- /dev/null
+++ b/lib/libfigpar/string_m.c
@@ -0,0 +1,309 @@
+/*-
+ * Copyright (c) 2001-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "string_m.h"
+
+/*
+ * Counts the number of occurrences of one string that appear in the source
+ * string. Return value is the total count.
+ *
+ * An example use would be if you need to know how large a block of memory
+ * needs to be for a replaceall() series.
+ */
+unsigned int
+strcount(const char *source, const char *find)
+{
+ const char *p = source;
+ size_t flen;
+ unsigned int n = 0;
+
+ /* Both parameters are required */
+ if (source == NULL || find == NULL)
+ return (0);
+
+ /* Cache the length of find element */
+ flen = strlen(find);
+ if (strlen(source) == 0 || flen == 0)
+ return (0);
+
+ /* Loop until the end of the string */
+ while (*p != '\0') {
+ if (strncmp(p, find, flen) == 0) { /* found an instance */
+ p += flen;
+ n++;
+ } else
+ p++;
+ }
+
+ return (n);
+}
+
+/*
+ * Replaces all occurrences of `find' in `source' with `replace'.
+ *
+ * You should not pass a string constant as the first parameter, it needs to be
+ * a pointer to an allocated block of memory. The block of memory that source
+ * points to should be large enough to hold the result. If the length of the
+ * replacement string is greater than the length of the find string, the result
+ * will be larger than the original source string. To allocate enough space for
+ * the result, use the function strcount() declared above to determine the
+ * number of occurrences and how much larger the block size needs to be.
+ *
+ * If source is not large enough, the application will crash. The return value
+ * is the length (in bytes) of the result.
+ *
+ * When an error occurs, -1 is returned and the global variable errno is set
+ * accordingly. Returns zero on success.
+ */
+int
+replaceall(char *source, const char *find, const char *replace)
+{
+ char *p;
+ char *t;
+ char *temp;
+ size_t flen;
+ size_t rlen;
+ size_t slen;
+ uint32_t n = 0;
+
+ errno = 0; /* reset global error number */
+
+ /* Check that we have non-null parameters */
+ if (source == NULL)
+ return (0);
+ if (find == NULL)
+ return (strlen(source));
+
+ /* Cache the length of the strings */
+ slen = strlen(source);
+ flen = strlen(find);
+ rlen = replace ? strlen(replace) : 0;
+
+ /* Cases where no replacements need to be made */
+ if (slen == 0 || flen == 0 || slen < flen)
+ return (slen);
+
+ /* If replace is longer than find, we'll need to create a temp copy */
+ if (rlen > flen) {
+ temp = malloc(slen + 1);
+ if (errno != 0) /* could not allocate memory */
+ return (-1);
+ strcpy(temp, source);
+ } else
+ temp = source;
+
+ /* Reconstruct the string with the replacements */
+ p = source; t = temp; /* position elements */
+
+ while (*t != '\0') {
+ if (strncmp(t, find, flen) == 0) {
+ /* found an occurrence */
+ for (n = 0; replace && replace[n]; n++)
+ *p++ = replace[n];
+ t += flen;
+ } else
+ *p++ = *t++; /* copy character and increment */
+ }
+
+ /* Terminate the string */
+ *p = '\0';
+
+ /* Free the temporary allocated memory */
+ if (temp != source)
+ free(temp);
+
+ /* Return the length of the completed string */
+ return (strlen(source));
+}
+
+/*
+ * Expands escape sequences in a buffer pointed to by `source'. This function
+ * steps through each character, and converts escape sequences such as "\n",
+ * "\r", "\t" and others into their respective meanings.
+ *
+ * You should not pass a string constant or literal to this function or the
+ * program will likely segmentation fault when it tries to modify the data.
+ *
+ * The string length will either shorten or stay the same depending on whether
+ * any escape sequences were converted but the amount of memory allocated does
+ * not change.
+ *
+ * Interpreted sequences are:
+ *
+ * \0NNN character with octal value NNN (0 to 3 digits)
+ * \N character with octal value N (0 thru 7)
+ * \a alert (BEL)
+ * \b backslash
+ * \f form feed
+ * \n new line
+ * \r carriage return
+ * \t horizontal tab
+ * \v vertical tab
+ * \xNN byte with hexadecimal value NN (1 to 2 digits)
+ *
+ * All other sequences are unescaped (ie. '\"' and '\#').
+ */
+void strexpand(char *source)
+{
+ uint8_t c;
+ char *chr;
+ char *pos;
+ char d[4];
+
+ /* Initialize position elements */
+ pos = chr = source;
+
+ /* Loop until we hit the end of the string */
+ while (*pos != '\0') {
+ if (*chr != '\\') {
+ *pos = *chr; /* copy character to current offset */
+ pos++;
+ chr++;
+ continue;
+ }
+
+ /* Replace the backslash with the correct character */
+ switch (*++chr) {
+ case 'a': *pos = '\a'; break; /* bell/alert (BEL) */
+ case 'b': *pos = '\b'; break; /* backspace */
+ case 'f': *pos = '\f'; break; /* form feed */
+ case 'n': *pos = '\n'; break; /* new line */
+ case 'r': *pos = '\r'; break; /* carriage return */
+ case 't': *pos = '\t'; break; /* horizontal tab */
+ case 'v': *pos = '\v'; break; /* vertical tab */
+ case 'x': /* hex value (1 to 2 digits)(\xNN) */
+ d[2] = '\0'; /* pre-terminate the string */
+
+ /* verify next two characters are hex */
+ d[0] = isxdigit(*(chr+1)) ? *++chr : '\0';
+ if (d[0] != '\0')
+ d[1] = isxdigit(*(chr+1)) ? *++chr : '\0';
+
+ /* convert the characters to decimal */
+ c = (uint8_t)strtoul(d, 0, 16);
+
+ /* assign the converted value */
+ *pos = (c != 0 || d[0] == '0') ? c : *++chr;
+ break;
+ case '0': /* octal value (0 to 3 digits)(\0NNN) */
+ d[3] = '\0'; /* pre-terminate the string */
+
+ /* verify next three characters are octal */
+ d[0] = (isdigit(*(chr+1)) && *(chr+1) < '8') ?
+ *++chr : '\0';
+ if (d[0] != '\0')
+ d[1] = (isdigit(*(chr+1)) && *(chr+1) < '8') ?
+ *++chr : '\0';
+ if (d[1] != '\0')
+ d[2] = (isdigit(*(chr+1)) && *(chr+1) < '8') ?
+ *++chr : '\0';
+
+ /* convert the characters to decimal */
+ c = (uint8_t)strtoul(d, 0, 8);
+
+ /* assign the converted value */
+ *pos = c;
+ break;
+ default: /* single octal (\0..7) or unknown sequence */
+ if (isdigit(*chr) && *chr < '8') {
+ d[0] = *chr;
+ d[1] = '\0';
+ *pos = (uint8_t)strtoul(d, 0, 8);
+ } else
+ *pos = *chr;
+ }
+
+ /* Increment to next offset, possible next escape sequence */
+ pos++;
+ chr++;
+ }
+}
+
+/*
+ * Expand only the escaped newlines in a buffer pointed to by `source'. This
+ * function steps through each character, and converts the "\n" sequence into
+ * a literal newline and the "\\n" sequence into "\n".
+ *
+ * You should not pass a string constant or literal to this function or the
+ * program will likely segmentation fault when it tries to modify the data.
+ *
+ * The string length will either shorten or stay the same depending on whether
+ * any escaped newlines were converted but the amount of memory allocated does
+ * not change.
+ */
+void strexpandnl(char *source)
+{
+ uint8_t backslash = 0;
+ char *cp1;
+ char *cp2;
+
+ /* Replace '\n' with literal in dprompt */
+ cp1 = cp2 = source;
+ while (*cp2 != '\0') {
+ *cp1 = *cp2;
+ if (*cp2 == '\\')
+ backslash++;
+ else if (*cp2 != 'n')
+ backslash = 0;
+ else if (backslash > 0) {
+ *(--cp1) = (backslash & 1) == 1 ? '\n' : 'n';
+ backslash = 0;
+ }
+ cp1++;
+ cp2++;
+ }
+ *cp1 = *cp2;
+}
+
+/*
+ * Convert a string to lower case. You should not pass a string constant to
+ * this function. Only pass pointers to allocated memory with null terminated
+ * string data.
+ */
+void
+strtolower(char *source)
+{
+ char *p = source;
+
+ if (source == NULL)
+ return;
+
+ while (*p != '\0') {
+ *p = tolower(*p);
+ p++; /* would have just used `*p++' but gcc 3.x warns */
+ }
+}
diff --git a/lib/libfigpar/string_m.h b/lib/libfigpar/string_m.h
new file mode 100644
index 0000000..18a3fcc
--- /dev/null
+++ b/lib/libfigpar/string_m.h
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2001-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _STRING_M_H_
+#define _STRING_M_H_
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+void strexpand(char *_source);
+void strexpandnl(char *_source);
+void strtolower(char *_source);
+int replaceall(char *_source, const char *_find,
+ const char *_replace);
+unsigned int strcount(const char *_source, const char *_find);
+__END_DECLS
+
+#endif /* !_STRING_M_H_ */
diff --git a/share/mk/bsd.libnames.mk b/share/mk/bsd.libnames.mk
index 47ec226..e07b611 100644
--- a/share/mk/bsd.libnames.mk
+++ b/share/mk/bsd.libnames.mk
@@ -42,12 +42,14 @@ LIBDEVINFO?= ${DESTDIR}${LIBDIR}/libdevinfo.a
LIBDEVSTAT?= ${DESTDIR}${LIBDIR}/libdevstat.a
LIBDIALOG?= ${DESTDIR}${LIBDIR}/libdialog.a
LIBDNS?= ${DESTDIR}${LIBDIR}/libdns.a
+LIBDPV?= ${DESTDIR}${LIBDIR}/libdpv.a
LIBDTRACE?= ${DESTDIR}${LIBDIR}/libdtrace.a
LIBDWARF?= ${DESTDIR}${LIBDIR}/libdwarf.a
LIBEDIT?= ${DESTDIR}${LIBDIR}/libedit.a
LIBELF?= ${DESTDIR}${LIBDIR}/libelf.a
LIBEXECINFO?= ${DESTDIR}${LIBDIR}/libexecinfo.a
LIBFETCH?= ${DESTDIR}${LIBDIR}/libfetch.a
+LIBFIGPAR?= ${DESTDIR}${LIBDIR}/libfigpar.a
LIBFL?= "don't use LIBFL, use LIBL"
LIBFORM?= ${DESTDIR}${LIBDIR}/libform.a
LIBG2C?= ${DESTDIR}${LIBDIR}/libg2c.a
diff --git a/sys/sys/param.h b/sys/sys/param.h
index c9436c4..cc20497 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -58,7 +58,7 @@
* in the range 5 to 9.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 1001502 /* Master, propagated to newvers */
+#define __FreeBSD_version 1001503 /* Master, propagated to newvers */
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,
diff --git a/usr.bin/Makefile b/usr.bin/Makefile
index 785d17b..12855e1 100644
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -36,6 +36,7 @@ SUBDIR= alias \
ctlstat \
cut \
dirname \
+ dpv \
du \
ee \
elf2aout \
diff --git a/usr.bin/dpv/Makefile b/usr.bin/dpv/Makefile
new file mode 100644
index 0000000..6f89c0f
--- /dev/null
+++ b/usr.bin/dpv/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PROG= dpv
+
+CFLAGS+= -I${.CURDIR}
+
+DPADD= ${LIBDPV} ${LIBDIALOG} ${LIBFIGPAR} ${LIBNCURSESW} ${LIBUTIL} ${LIBM}
+LDADD= -ldpv -ldialog -lfigpar -lncursesw -lutil -lm
+
+WARNS?= 6
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/dpv/dpv.1 b/usr.bin/dpv/dpv.1
new file mode 100644
index 0000000..c8d321b
--- /dev/null
+++ b/usr.bin/dpv/dpv.1
@@ -0,0 +1,430 @@
+.\" Copyright (c) 2013-2014 Devin Teske
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd Sep 7, 2014
+.Dt DPV 1
+.Os
+.Sh NAME
+.Nm dpv
+.Nd stream data from stdin or multiple paths with dialog progress view
+.Sh SYNOPSIS
+.Nm
+.Op options
+.Ar [bytes:]label
+.Nm
+.Op options
+.Fl m
+.Ar [bytes1:]label1
+.Ar path1
+.Op Ar [bytes2:]label2 path2 ...
+.Sh DESCRIPTION
+.Nm
+provides a dialog progress view, allowing a user to see current throughput rate
+and total data transferred for one or more streams.
+.Pp
+The
+.Nm
+utility has two main modes for processing input.
+.Pp
+The default input mode, without
+.Ql Fl m ,
+.Nm
+reads bytes from standard input.
+A label for the data must be provided.
+.Pp
+The secondary input mode, with
+.Ql Fl m ,
+.Nm
+reads multiple paths
+.Pq up to 2047 or Dq ARG_MAX/2-1 ,
+sequentially.
+.Pp
+Data read in either mode is either thrown away
+.Pq default ,
+sent to a spawned instance of the program specified via
+.Ql Fl x Ar cmd ,
+or sent to a unique file specified by
+.Ql Fl o Ar file .
+.Pp
+With or without
+.Ql Fl m ,
+progress is displayed using one of
+.Xr dialog 3
+.Pq default ,
+.Xr dialog 1
+.Pq see Ql Fl D ,
+or instead
+.Xr Xdialog 1
+.Pq see Ql Fl X .
+.Pp
+The following options are available:
+.Bl -tag -width ".Fl b Ar backtitle"
+.It Fl a Ar text
+Display
+.Ar text
+below the file progress indicator(s).
+.It Fl b Ar backtitle
+Display
+.Ar backtitle
+on the backdrop, at top-left, behind the dialog widget.
+When using
+.Xr Xdialog 1 ,
+this is displayed inside the window
+.Pq at the top
+followed by a separator line.
+.It Fl d
+Debug mode.
+Print dialog prompt data to standard out and provide additional debugging on
+standard error.
+.It Fl D
+Do not use the default interface of
+.Xr dialog 3 ,
+but instead spawn an instance of
+.Xr dialog 1 .
+The path to
+.Xr dialog 1
+is taken from the
+.Ev DIALOG
+environment variable or simply
+.Dq Li dialog
+if unset or NULL.
+.It Fl h
+Produce a short syntax usage with brief option descriptions and exit.
+Output is produced on standard error.
+.It Fl i Ar format
+Customize the single-file format string used to update the status line.
+Ignored when using either
+.Ql Fl D
+or
+.Ql Fl X
+which lack the ability to display the status line
+.Pq containing bytes/rate/thread information .
+Default value
+is
+.Dq Li %'10lli bytes read @ %'9.1f bytes/sec. .
+This format is used when handling one file.
+.It Fl I Ar format
+Customize the multi-file format string used to update the status line.
+Ignored when using either
+.Ql Fl D
+or
+.Ql Fl X
+which lack the ability to display the status line
+.Pq containing bytes/rate/thread information .
+Default value
+is
+.Dq Li %'10lli bytes read @ %'9.1f bytes/sec. [%i/%i busy/wait] .
+This format is used when handling more than one file.
+.It Fl l
+Line mode. Read lines from input instead of bytes.
+.It Fl L Ar size
+Label size.
+If negative, shrink to longest label width.
+.It Fl m
+Multi-input mode.
+Instead of reading bytes from standard input, read from a set of paths
+.Pq one for each label .
+By default, each path is processed sequentially in the order given.
+.It Fl n Ar num
+Display at-most
+.Ar num
+progress indicators per screen.
+If zero, display as many as possible.
+If negative, only display the main progress indicator.
+Default is 0.
+Maximum value is 10.
+.It Fl N
+No overrun.
+If enabled, stop reading known-length inputs when input reaches stated length.
+.It Fl o Ar file
+Output data to
+.Ar file .
+The first occurrence of
+.Ql %s
+.Pq if any
+in
+.Ql Ar file
+will be replaced with the
+.Ar label
+text.
+.It Fl p Ar text
+Display
+.Ar text
+above the file progress indicator(s).
+.It Fl P Ar size
+Mini-progressbar size.
+If negative, don't display mini-progressbars
+.Pq only the large overall progress indicator is shown .
+If zero, auto-adjust based on number of files to read.
+When zero and only one file to read, defaults to -1.
+When zero and more than one file to read, defaults to 17.
+.It Fl t Ar title
+Display
+.Ar title
+atop the dialog box.
+Note that if you use this option at the same time as
+.Ql Fl X
+and
+.Ql Fl b Ar backtitle ,
+the
+.Ar backtitle
+and
+.Ar title
+are effectively switched
+.Pq see BUGS section below .
+.It Fl T
+Test mode.
+Simulate reading a number of bytes, divided evenly across the number of files,
+while stepping through each percent value of each file to process.
+Appends
+.Dq Li [TEST MODE]
+to the status line
+.Pq to override, use Ql Fl u Ar format .
+No data is actually read.
+.It Fl U Ar num
+Update status line
+.Ar num
+times per-second.
+Default value is
+.Ql Li 2 .
+A value of
+.Ql Li 0
+disables status line updates.
+If negative, update the status line as fast as possible.
+Ignored when using either
+.Ql Fl D
+or
+.Ql Fl X
+which lack the ability to display the status line
+.Pq containing bytes/rate/thread information .
+.It Fl w
+Wide mode.
+Allows long
+.Ar text
+arguments used with
+.Ql Fl p
+and
+.Ql Fl a
+to bump the dialog width.
+Prompts wider than the maximum width will wrap
+.Pq unless using Xr Xdialog 1 ; see BUGS section below .
+.It Fl x Ar cmd
+Execute
+.Ar cmd
+.Pq via Xr sh 1
+and send it data that has been read.
+Data is available to
+.Ar cmd
+on standard input.
+With
+.Ql Fl m ,
+.Ar cmd
+is executed once for each
+.Ar path
+argument.
+The first occurrence of
+.Ql %s
+.Pq if any
+in
+.Ql Ar cmd
+will be replaced with the
+.Ar label
+text.
+.It Fl X
+Enable X11 mode by using
+.Xr Xdialog 1
+instead of
+.Xr dialog 1
+or
+.Xr dialog 3 .
+.El
+.Sh ENVIRONMENT
+The following environment variables are referenced by
+.Nm :
+.Bl -tag -width ".Ev USE_COLOR"
+.It Ev DIALOG
+Override command string used to launch
+.Xr dialog 1
+.Pq requires Ql Fl D
+or
+.Xr Xdialog 1
+.Pq requires Ql Fl X ;
+default is either
+.Ql dialog
+.Pq for Ql Fl D
+or
+.Ql Xdialog
+.Pq for Ql Fl X .
+.It Ev DIALOGRC
+If set and non-NULL, path to
+.Ql .dialogrc
+file.
+.It Ev HOME
+If
+.Ql Ev $DIALOGRC
+is either not set or NULL, used as a prefix to
+.Ql .dialogrc
+.Pq i.e., Ql $HOME/.dialogrc .
+.It Ev USE_COLOR
+If set and NULL, disables the use of color when using
+.Xr dialog 1
+.Pq does not apply to Xr Xdialog 1 .
+.El
+.Sh DEPENDENCIES
+If using
+.Ql Fl D ,
+.Xr dialog 1
+is required.
+.Pp
+If using
+.Ql Fl X ,
+.Xr Xdialog 1
+is required.
+.Sh FILES
+.Bl -tag -width ".Pa $HOME/.dialogrc" -compact
+.It Pa $HOME/.dialogrc
+.El
+.Sh EXAMPLES
+.Pp
+Simple example to show how fast
+.Xr yes 1
+produces lines
+.Pq usually about ten-million per-second; your results may vary :
+.Bd -literal -offset indent
+yes | dpv -l yes
+.Ed
+.Pp
+Display progress while timing how long it takes
+.Xr yes 1
+to produce a half-billion lines
+.Pq usually under one minute; your results may vary :
+.Bd -literal -offset indent
+time yes | dpv -Nl 500000000:yes
+.Ed
+.Pp
+An example to watch how quickly a file is transferred using
+.Xr nc 1 :
+.Bd -literal -offset indent
+dpv -x "nc -w 1 somewhere.com 3000" -m label file
+.Ed
+.Pp
+A similar example, transferring a file from another process and passing the
+expected size to
+.Nm :
+.Bd -literal -offset indent
+cat file | dpv -x "nc -w 1 somewhere.com 3000" 12345:label
+.Ed
+.Pp
+A more complicated example:
+.Bd -literal -offset indent
+tar cf - . | dpv -x "gzip -9 > out.tgz" \\
+ $( du -s . | awk '{print $1 * 1024}' ):label
+.Ed
+.Pp
+Taking an image of a disk:
+.Bd -literal -offset indent
+dpv -o disk-image.img -m label /dev/ada0
+.Ed
+.Pp
+Writing an image back to a disk:
+.Bd -literal -offset indent
+dpv -o /dev/ada0 -m label disk-image.img
+.Ed
+.Pp
+Zeroing a disk:
+.Bd -literal -offset indent
+dpv -o /dev/md42 < /dev/zero
+.Ed
+.Pp
+.Sh BUGS
+.Xr Xdialog 1 ,
+when given both
+.Ql Fl -title Ar title
+.Pq see above Ql Fl t Ar title
+and
+.Ql Fl -backtitle Ar backtitle
+.Pq see above Ql Fl b Ar backtitle ,
+displays the backtitle in place of the title and vice-versa.
+.Pp
+.Xr Xdialog 1
+does not wrap long prompt texts received after initial launch.
+This is a known issue with the
+.Ql --gauge
+widget in
+.Xr Xdialog 1 .
+.Pp
+.Xr dialog 1
+does not display the first character after a series of escaped escape-sequences
+(e.g., ``\\\\n'' produces ``\\'' instead of ``\\n'').
+This is a known issue with
+.Xr dialog 1
+and does not affect
+.Xr dialog 3
+or
+.Xr Xdialog 1 .
+.Pp
+If your application ignores
+.Ev USE_COLOR
+when set and NULL before calling
+.Xr dpv 1
+with color escape sequences anyway,
+.Xr dialog 3
+and
+.Xr dialog 1
+may not render properly.
+Workaround is to detect when
+.Ev USE_COLOR
+is set and NULL and either not use color escape sequences at that time or use
+.Xr unset 1
+.Xr [ sh 1 ]
+or
+.Xr unsetenv 1
+.Xr [ csh 1 ]
+to unset
+.Ev USE_COLOR ,
+forcing interpretation of color sequences.
+This does not effect
+.Xr Xdialog 1 ,
+which renders the color escape sequences as plain text.
+See
+.Do Li
+embedded "\\Z" sequences
+.Dc
+in
+.Xr dialog 1
+for additional information.
+.Sh SEE ALSO
+.Xr dialog 1 ,
+.Xr dialog 3 ,
+.Xr sh 1 ,
+.Xr Xdialog 1
+.Sh HISTORY
+A
+.Nm
+utility first appeared in
+.Fx 11.0 .
+.Sh AUTHORS
+.An Devin Teske Aq dteske@FreeBSD.org
diff --git a/usr.bin/dpv/dpv.c b/usr.bin/dpv/dpv.c
new file mode 100644
index 0000000..4e006f6
--- /dev/null
+++ b/usr.bin/dpv/dpv.c
@@ -0,0 +1,541 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */
+#include <dialog.h>
+#include <dpv.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+#include <unistd.h>
+
+#include "dpv_util.h"
+
+/* Debugging */
+static uint8_t debug = FALSE;
+
+/* Data to process */
+static struct dpv_file_node *file_list = NULL;
+static unsigned int nfiles = 0;
+
+/* Data processing */
+static uint8_t line_mode = FALSE;
+static uint8_t no_overrun = FALSE;
+static char *buf = NULL;
+static int fd = -1;
+static int output_type = DPV_OUTPUT_NONE;
+static size_t bsize;
+static char rpath[PATH_MAX];
+
+/* Extra display information */
+static uint8_t multiple = FALSE; /* `-m' */
+static char *pgm; /* set to argv[0] by main() */
+
+/* Function prototypes */
+static void sig_int(int sig);
+static void usage(void);
+int main(int argc, char *argv[]);
+static int operate_common(struct dpv_file_node *file, int out);
+static int operate_on_bytes(struct dpv_file_node *file, int out);
+static int operate_on_lines(struct dpv_file_node *file, int out);
+
+static int
+operate_common(struct dpv_file_node *file, int out)
+{
+ struct stat sb;
+
+ /* Open the file if necessary */
+ if (fd < 0) {
+ if (multiple) {
+ /* Resolve the file path and attempt to open it */
+ if (realpath(file->path, rpath) == 0 ||
+ (fd = open(rpath, O_RDONLY)) < 0) {
+ warn("%s", file->path);
+ file->status = DPV_STATUS_FAILED;
+ return (-1);
+ }
+ } else {
+ /* Assume stdin, but if that's a TTY instead use the
+ * highest numbered file descriptor (obtained by
+ * generating new fd and then decrementing).
+ *
+ * NB: /dev/stdin should always be open(2)'able
+ */
+ fd = STDIN_FILENO;
+ if (isatty(fd)) {
+ fd = open("/dev/stdin", O_RDONLY);
+ close(fd--);
+ }
+
+ /* This answer might be wrong, if dpv(3) has (by
+ * request) opened an output file or pipe. If we
+ * told dpv(3) to open a file, subtract one from
+ * previous answer. If instead we told dpv(3) to
+ * prepare a pipe output, subtract two.
+ */
+ switch(output_type) {
+ case DPV_OUTPUT_FILE:
+ fd -= 1;
+ break;
+ case DPV_OUTPUT_SHELL:
+ fd -= 2;
+ break;
+ }
+ }
+ }
+
+ /* Allocate buffer if necessary */
+ if (buf == NULL) {
+ /* Use output block size as buffer size if available */
+ if (out >= 0) {
+ if (fstat(out, &sb) != 0) {
+ warn("%i", out);
+ file->status = DPV_STATUS_FAILED;
+ return (-1);
+ }
+ if (S_ISREG(sb.st_mode)) {
+ if (sysconf(_SC_PHYS_PAGES) >
+ PHYSPAGES_THRESHOLD)
+ bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
+ else
+ bsize = BUFSIZE_SMALL;
+ } else
+ bsize = MAX(sb.st_blksize,
+ (blksize_t)sysconf(_SC_PAGESIZE));
+ } else
+ bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
+
+ /* Attempt to allocate */
+ if ((buf = malloc(bsize+1)) == NULL) {
+ end_dialog();
+ err(EXIT_FAILURE, "Out of memory?!");
+ }
+ }
+
+ return (0);
+}
+
+static int
+operate_on_bytes(struct dpv_file_node *file, int out)
+{
+ int progress;
+ ssize_t r, w;
+
+ if (operate_common(file, out) < 0)
+ return (-1);
+
+ /* [Re-]Fill the buffer */
+ if ((r = read(fd, buf, bsize)) <= 0) {
+ if (fd != STDIN_FILENO)
+ close(fd);
+ fd = -1;
+ file->status = DPV_STATUS_DONE;
+ return (100);
+ }
+
+ /* [Re-]Dump the buffer */
+ if (out >= 0) {
+ if ((w = write(out, buf, r)) < 0) {
+ end_dialog();
+ err(EXIT_FAILURE, "output");
+ }
+ fsync(out);
+ }
+
+ dpv_overall_read += r;
+ file->read += r;
+
+ /* Calculate percentage of completion (if possible) */
+ if (file->length >= 0) {
+ progress = (file->read * 100 / (file->length > 0 ?
+ file->length : 1));
+
+ /* If no_overrun, do not return 100% until read >= length */
+ if (no_overrun && progress == 100 && file->read < file->length)
+ progress--;
+
+ return (progress);
+ } else
+ return (-1);
+}
+
+static int
+operate_on_lines(struct dpv_file_node *file, int out)
+{
+ char *p;
+ int progress;
+ ssize_t r, w;
+
+ if (operate_common(file, out) < 0)
+ return (-1);
+
+ /* [Re-]Fill the buffer */
+ if ((r = read(fd, buf, bsize)) <= 0) {
+ if (fd != STDIN_FILENO)
+ close(fd);
+ fd = -1;
+ file->status = DPV_STATUS_DONE;
+ return (100);
+ }
+ buf[r] = '\0';
+
+ /* [Re-]Dump the buffer */
+ if (out >= 0) {
+ if ((w = write(out, buf, r)) < 0) {
+ end_dialog();
+ err(EXIT_FAILURE, "output");
+ }
+ fsync(out);
+ }
+
+ /* Process the buffer for number of lines */
+ for (p = buf; p != NULL && *p != '\0';)
+ if ((p = strchr(p, '\n')) != NULL)
+ dpv_overall_read++, p++, file->read++;
+
+ /* Calculate percentage of completion (if possible) */
+ if (file->length >= 0) {
+ progress = (file->read * 100 / file->length);
+
+ /* If no_overrun, do not return 100% until read >= length */
+ if (no_overrun && progress == 100 && file->read < file->length)
+ progress--;
+
+ return (progress);
+ } else
+ return (-1);
+}
+
+/*
+ * Takes a list of names that are to correspond to input streams coming from
+ * stdin or fifos and produces necessary config to drive dpv(3) `--gauge'
+ * widget. If the `-d' flag is used, output is instead send to terminal
+ * standard output (and the output can then be saved to a file, piped into
+ * custom [X]dialog(1) invocation, or whatever.
+ */
+int
+main(int argc, char *argv[])
+{
+ char dummy;
+ int ch;
+ int n = 0;
+ size_t config_size = sizeof(struct dpv_config);
+ size_t file_node_size = sizeof(struct dpv_file_node);
+ struct dpv_config *config;
+ struct dpv_file_node *curfile;
+ struct sigaction act;
+
+ pgm = argv[0]; /* store a copy of invocation name */
+
+ /* Allocate config structure */
+ if ((config = malloc(config_size)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ memset((void *)(config), '\0', config_size);
+
+ /*
+ * Process command-line options
+ */
+ while ((ch = getopt(argc, argv,
+ "a:b:dDhi:I:lL:mn:No:p:P:t:TU:wx:X")) != -1) {
+ switch(ch) {
+ case 'a': /* additional message text to append */
+ if (config->aprompt == NULL) {
+ config->aprompt = malloc(DPV_APROMPT_MAX);
+ if (config->aprompt == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ }
+ snprintf(config->aprompt, DPV_APROMPT_MAX, "%s",
+ optarg);
+ break;
+ case 'b': /* [X]dialog(1) backtitle */
+ if (config->backtitle != NULL)
+ free((char *)config->backtitle);
+ config->backtitle = malloc(strlen(optarg) + 1);
+ if (config->backtitle == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ *(config->backtitle) = '\0';
+ strcat(config->backtitle, optarg);
+ break;
+ case 'd': /* debugging */
+ debug = TRUE;
+ config->debug = debug;
+ break;
+ case 'D': /* use dialog(1) instead of libdialog */
+ config->display_type = DPV_DISPLAY_DIALOG;
+ break;
+ case 'h': /* help/usage */
+ usage();
+ break; /* NOTREACHED */
+ case 'i': /* status line format string for single-file */
+ config->status_solo = optarg;
+ break;
+ case 'I': /* status line format string for many-files */
+ config->status_many = optarg;
+ break;
+ case 'l': /* Line mode */
+ line_mode = TRUE;
+ break;
+ case 'L': /* custom label size */
+ config->label_size =
+ (int)strtol(optarg, (char **)NULL, 10);
+ if (config->label_size == 0 && errno == EINVAL)
+ errx(EXIT_FAILURE,
+ "`-L' argument must be numeric");
+ else if (config->label_size < -1)
+ config->label_size = -1;
+ break;
+ case 'm': /* enable multiple file arguments */
+ multiple = TRUE;
+ break;
+ case 'o': /* `-o path' for sending data-read to file */
+ output_type = DPV_OUTPUT_FILE;
+ config->output_type = DPV_OUTPUT_FILE;
+ config->output = optarg;
+ break;
+ case 'n': /* custom number of files per `page' */
+ config->display_limit =
+ (int)strtol(optarg, (char **)NULL, 10);
+ if (config->display_limit == 0 && errno == EINVAL)
+ errx(EXIT_FAILURE,
+ "`-n' argument must be numeric");
+ else if (config->display_limit < 0)
+ config->display_limit = -1;
+ break;
+ case 'N': /* No overrun (truncate reads of known-length) */
+ no_overrun = TRUE;
+ config->options |= DPV_NO_OVERRUN;
+ break;
+ case 'p': /* additional message text to use as prefix */
+ if (config->pprompt == NULL) {
+ config->pprompt = malloc(DPV_PPROMPT_MAX + 2);
+ if (config->pprompt == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ /* +2 is for implicit "\n" appended later */
+ }
+ snprintf(config->pprompt, DPV_PPROMPT_MAX, "%s",
+ optarg);
+ break;
+ case 'P': /* custom size for mini-progressbar */
+ config->pbar_size =
+ (int)strtol(optarg, (char **)NULL, 10);
+ if (config->pbar_size == 0 && errno == EINVAL)
+ errx(EXIT_FAILURE,
+ "`-P' argument must be numeric");
+ else if (config->pbar_size < -1)
+ config->pbar_size = -1;
+ break;
+ case 't': /* [X]dialog(1) title */
+ if (config->title != NULL)
+ free(config->title);
+ config->title = malloc(strlen(optarg) + 1);
+ if (config->title == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ *(config->title) = '\0';
+ strcat(config->title, optarg);
+ break;
+ case 'T': /* test mode (don't read data, fake it) */
+ config->options |= DPV_TEST_MODE;
+ break;
+ case 'U': /* updates per second */
+ config->status_updates_per_second =
+ (int)strtol(optarg, (char **)NULL, 10);
+ if (config->status_updates_per_second == 0 &&
+ errno == EINVAL)
+ errx(EXIT_FAILURE,
+ "`-U' argument must be numeric");
+ break;
+ case 'w': /* `-p' and `-a' widths bump [X]dialog(1) width */
+ config->options |= DPV_WIDE_MODE;
+ break;
+ case 'x': /* `-x cmd' for sending data-read to sh(1) code */
+ output_type = DPV_OUTPUT_SHELL;
+ config->output_type = DPV_OUTPUT_SHELL;
+ config->output = optarg;
+ break;
+ case 'X': /* X11 support through x11/xdialog */
+ config->display_type = DPV_DISPLAY_XDIALOG;
+ break;
+ case '?': /* unknown argument (based on optstring) */
+ /* FALLTHROUGH */
+ default: /* unhandled argument (based on switch) */
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Process remaining arguments as list of names to display */
+ for (curfile = file_list; n < argc; n++) {
+ nfiles++;
+
+ /* Allocate a new struct for the file argument */
+ if (curfile == NULL) {
+ if ((curfile = malloc(file_node_size)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ memset((void *)(curfile), '\0', file_node_size);
+ file_list = curfile;
+ } else {
+ if ((curfile->next = malloc(file_node_size)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ memset((void *)(curfile->next), '\0', file_node_size);
+ curfile = curfile->next;
+ }
+ curfile->name = argv[n];
+
+ /* Read possible `lines:' prefix from label syntax */
+ if (sscanf(curfile->name, "%lli:%c", &(curfile->length),
+ &dummy) == 2)
+ curfile->name = strchr(curfile->name, ':') + 1;
+ else
+ curfile->length = -1;
+
+ /* Read path argument if enabled */
+ if (multiple) {
+ if (++n >= argc)
+ errx(EXIT_FAILURE, "Missing path argument "
+ "for label number %i", nfiles);
+ curfile->path = argv[n];
+ } else
+ break;
+ }
+
+ /* Display usage and exit if not given at least one name */
+ if (nfiles == 0) {
+ warnx("no labels provided");
+ usage();
+ /* NOTREACHED */
+ }
+
+ /*
+ * Set cleanup routine for Ctrl-C action
+ */
+ if (config->display_type == DPV_DISPLAY_LIBDIALOG) {
+ act.sa_handler = sig_int;
+ sigaction(SIGINT, &act, 0);
+ }
+
+ /* Set status formats and action */
+ if (line_mode) {
+ config->status_solo = LINE_STATUS_SOLO;
+ config->status_many = LINE_STATUS_SOLO;
+ config->action = operate_on_lines;
+ } else {
+ config->status_solo = BYTE_STATUS_SOLO;
+ config->status_many = BYTE_STATUS_SOLO;
+ config->action = operate_on_bytes;
+ }
+
+ /*
+ * Hand off to dpv(3)...
+ */
+ if (dpv(config, file_list) != 0 && debug)
+ warnx("dpv(3) returned error!?");
+
+ end_dialog();
+ dpv_free();
+
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * Interrupt handler to indicate we received a Ctrl-C interrupt.
+ */
+static void
+sig_int(int sig __unused)
+{
+ dpv_interrupt = TRUE;
+}
+
+/*
+ * Print short usage statement to stderr and exit with error status.
+ */
+static void
+usage(void)
+{
+
+ if (debug) /* No need for usage */
+ exit(EXIT_FAILURE);
+
+ fprintf(stderr, "Usage: %s [options] bytes:label\n", pgm);
+ fprintf(stderr, " %s [options] -m bytes1:label1 path1 "
+ "[bytes2:label2 path2 ...]\n", pgm);
+ fprintf(stderr, "OPTIONS:\n");
+#define OPTFMT "\t%-14s %s\n"
+ fprintf(stderr, OPTFMT, "-a text",
+ "Append text. Displayed below file progress indicators.");
+ fprintf(stderr, OPTFMT, "-b backtitle",
+ "String to be displayed on the backdrop, at top-left.");
+ fprintf(stderr, OPTFMT, "-d",
+ "Debug. Write to standard output instead of dialog.");
+ fprintf(stderr, OPTFMT, "-D",
+ "Use dialog(1) instead of dialog(3) [default].");
+ fprintf(stderr, OPTFMT, "-h",
+ "Produce this output on standard error and exit.");
+ fprintf(stderr, OPTFMT, "-i format",
+ "Customize status line format. See fdpv(1) for details.");
+ fprintf(stderr, OPTFMT, "-I format",
+ "Customize status line format. See fdpv(1) for details.");
+ fprintf(stderr, OPTFMT, "-L size",
+ "Label size. Must be a number greater than 0, or -1.");
+ fprintf(stderr, OPTFMT, "-m",
+ "Enable processing of multiple file argiments.");
+ fprintf(stderr, OPTFMT, "-n num",
+ "Display at-most num files per screen. Default is -1.");
+ fprintf(stderr, OPTFMT, "-N",
+ "No overrun. Stop reading input at stated length, if any.");
+ fprintf(stderr, OPTFMT, "-o file",
+ "Output data to file. First %s replaced with label text.");
+ fprintf(stderr, OPTFMT, "-p text",
+ "Prefix text. Displayed above file progress indicators.");
+ fprintf(stderr, OPTFMT, "-P size",
+ "Mini-progressbar size. Must be a number greater than 3.");
+ fprintf(stderr, OPTFMT, "-t title",
+ "Title string to be displayed at top of dialog(1) box.");
+ fprintf(stderr, OPTFMT, "-T",
+ "Test mode. Don't actually read any data, but fake it.");
+ fprintf(stderr, OPTFMT, "-U num",
+ "Update status line num times per-second. Default is 2.");
+ fprintf(stderr, OPTFMT, "-w",
+ "Wide. Width of `-p' and `-a' text bump dialog(1) width.");
+ fprintf(stderr, OPTFMT, "-x cmd",
+ "Send data to executed cmd. First %s replaced with label.");
+ fprintf(stderr, OPTFMT, "-X",
+ "X11. Use Xdialog(1) instead of dialog(1).");
+ exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/dpv/dpv_util.h b/usr.bin/dpv/dpv_util.h
new file mode 100644
index 0000000..36990be
--- /dev/null
+++ b/usr.bin/dpv/dpv_util.h
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _DPV_UTIL_H_
+#define _DPV_UTIL_H_
+
+/* Limits */
+#define BUFSIZE_MAX (2 * 1024 * 1024)
+ /* Buffer size for read(2) input */
+#ifndef MAXPHYS
+#define MAXPHYS (128 * 1024)
+ /* max raw I/O transfer size */
+#endif
+
+/*
+ * Memory strategry threshold, in pages: if physmem is larger than this,
+ * use a large buffer.
+ */
+#define PHYSPAGES_THRESHOLD (32 * 1024)
+
+/*
+ * Small (default) buffer size in bytes. It's inefficient for this to be
+ * smaller than MAXPHYS.
+ */
+#define BUFSIZE_SMALL (MAXPHYS)
+
+/*
+ * Math macros
+ */
+#undef MIN
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+#undef MAX
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+
+/*
+ * Extra display information
+ */
+#define BYTE_STATUS_SOLO "%'10lli bytes read @ %'9.1f bytes/sec."
+#define BYTE_STATUS_MANY (BYTE_STATUS_SOLO " [%i/%i busy/wait]")
+#define LINE_STATUS_SOLO "%'10lli lines read @ %'9.1f lines/sec."
+#define LINE_STATUS_MANY (LINE_STATUS_SOLO " [%i/%i busy/wait]")
+
+#endif /* !_DPV_UTIL_H_ */
OpenPOWER on IntegriCloud