Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F137443485
D714.id1337.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
189 KB
Referenced Files
None
Subscribers
None
D714.id1337.diff
View Options
Index: lib/Makefile
===================================================================
--- lib/Makefile
+++ lib/Makefile
@@ -42,11 +42,13 @@
libcrypt \
libdevinfo \
libdevstat \
+ libdpv \
libdwarf \
libedit \
libexecinfo \
libexpat \
libfetch \
+ libfigpar \
libgeom \
${_libgpib} \
${_libgssapi} \
Index: lib/libdpv/Makefile
===================================================================
--- /dev/null
+++ lib/libdpv/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+LIB= dpv
+SHLIB_MAJOR= 1
+INCS= dpv.h
+MAN= dpv.3
+MLINKS= dpv.3 dpv_free.3
+
+CFLAGS+= -I${.CURDIR}
+LDFLAGS+= -ldialog -lfigpar -lncurses -lutil
+
+SRCS= dialog_util.c dialogrc.c dprompt.c dpv.c status.c util.c
+
+WARNS?= 6
+
+.include <bsd.lib.mk>
Index: lib/libdpv/dialog_util.h
===================================================================
--- /dev/null
+++ lib/libdpv/dialog_util.h
@@ -0,0 +1,77 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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 int dheight, dwidth;
+extern char *title, *backtitle;
+
+/* Function prototypes */
+int dialog_maxcols(void);
+int dialog_maxrows(void);
+char * dialog_prompt_lastline(char *prompt, uint8_t nlstate);
+unsigned int dialog_prompt_longestline(const char *prompt, uint8_t nlstate);
+unsigned int dialog_prompt_numlines(const char *prompt, uint8_t nlstate);
+uint8_t dialog_prompt_nlstate(const char *prompt);
+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()
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_DIALOG_UTIL_H_ */
Index: lib/libdpv/dialog_util.c
===================================================================
--- /dev/null
+++ lib/libdpv/dialog_util.c
@@ -0,0 +1,592 @@
+/*-
+ * 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 <ctype.h>
+#include <err.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <spawn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.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 */
+int dheight = 0;
+int dwidth = 0;
+char *title = NULL;
+char *backtitle = NULL;
+
+/* TTY/Screen characteristics */
+static struct winsize *maxsize = NULL;
+
+/* Function prototypes for private functions (see style(9)) */
+void tty_maxsize_update(void);
+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.
+ */
+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.
+ */
+void
+x11_maxsize_update(void)
+{
+ FILE *f = NULL;
+ char *cp, *rows, *cols;
+ char cmdbuf[LINE_MAX], 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)
+{
+ unsigned int n = 0;
+ int height, width, error, stdin_pipe[2] = { -1, -1 };
+ char *cp, *dargv[13], dummy_init[2] = "";
+ posix_spawn_file_actions_t action;
+
+ /* 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
+ {
+ unsigned int i;
+
+ 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 = 1; /* See declaration comment */
+ } else if (*cp == '\n') {
+ if (!nls) nlines++;
+ nls = 0; /* 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() */
+ int n = 0, longest = 0;
+ const char *p = prompt;
+
+ /* `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 = 0; /* 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 = 1; /* 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)
+{
+ char *lastline, *p;
+ uint8_t nls = nlstate; /* See dialog_prompt_nlstate() */
+
+ 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 = 0; /* See declaration comment */
+ }
+ }
+ /* dialog(3) does not expand escaped newlines */
+ if (use_libdialog) {
+ p++;
+ continue;
+ }
+ if (*p == '\\' && *(p + 1) != '\0' && *(++p) == 'n') {
+ nls = 1; /* 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() */
+ int wlines = 0, n = 0;
+ char *p = prompt;
+
+ /* `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 = 0; /* 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 = 1; /* 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).
+ */
+ char *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) */
+}
Index: lib/libdpv/dialogrc.h
===================================================================
--- /dev/null
+++ lib/libdpv/dialogrc.h
@@ -0,0 +1,60 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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[];
+
+/* Function prototypes */
+struct config * dialogrc_config_option(const char *directive);
+int parse_dialogrc(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_DIALOGRC_H_ */
Index: lib/libdpv/dialogrc.c
===================================================================
--- /dev/null
+++ lib/libdpv/dialogrc.c
@@ -0,0 +1,326 @@
+/*-
+ * 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 <errno.h>
+#include <figpar.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+#include <sys/types.h>
+
+#include "dialogrc.h"
+
+#define STR_BUFSIZE 255
+
+/* dialog(1) `.dialogrc' characteristics */
+char separator[STR_BUFSIZE] = "";
+uint8_t use_shadow = 1;
+uint8_t use_colors = 1;
+char gauge_color[STR_BUFSIZE] = "47b"; /* (BLUE,WHITE,ON) */
+
+/* Function prototypes for private functions (see style(9)) */
+int setattr(struct config *, uint32_t, char *, char *);
+int setbool(struct config *, uint32_t, char *, char *);
+int setnum(struct config *, uint32_t, char *, char *);
+int setstr(struct 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 config dialogrc_config[] = {
+ /* TYPE Directive DEFAULT HANDLER */
+ {TYPE_INT, "aspect", {(void *)0}, &setnum},
+ {TYPE_STR, "separate_widget", {separator}, &setstr},
+ {TYPE_INT, "tab_len", {(void *)0}, &setnum},
+ {TYPE_BOOL, "visit_items", {(void *)0}, &setbool},
+ {TYPE_BOOL, "use_shadow", {(void *)1}, &setbool},
+ {TYPE_BOOL, "use_colors", {(void *)1}, &setbool},
+ {TYPE_STR, "screen_color", {NULL}, &setattr},
+ {TYPE_STR, "shadow_color", {NULL}, &setattr},
+ {TYPE_STR, "dialog_color", {NULL}, &setattr},
+ {TYPE_STR, "title_color", {NULL}, &setattr},
+ {TYPE_STR, "border_color", {NULL}, &setattr},
+ {TYPE_STR, "button_active_color", {NULL}, &setattr},
+ {TYPE_STR, "button_inactive_color", {NULL}, &setattr},
+ {TYPE_STR, "button_key_active_color", {NULL}, &setattr},
+ {TYPE_STR, "button_key_inactive_color", {NULL}, &setattr},
+ {TYPE_STR, "button_label_active_color", {NULL}, &setattr},
+ {TYPE_STR, "button_label_inactive_color", {NULL}, &setattr},
+ {TYPE_STR, "inputbox_color", {NULL}, &setattr},
+ {TYPE_STR, "inputbox_border_color", {NULL}, &setattr},
+ {TYPE_STR, "searchbox_color", {NULL}, &setattr},
+ {TYPE_STR, "searchbox_title_color", {NULL}, &setattr},
+ {TYPE_STR, "searchbox_border_color", {NULL}, &setattr},
+ {TYPE_STR, "position_indicator_color", {NULL}, &setattr},
+ {TYPE_STR, "menubox_color", {NULL}, &setattr},
+ {TYPE_STR, "menubox_border_color", {NULL}, &setattr},
+ {TYPE_STR, "item_color", {NULL}, &setattr},
+ {TYPE_STR, "item_selected_color", {NULL}, &setattr},
+ {TYPE_STR, "tag_color", {NULL}, &setattr},
+ {TYPE_STR, "tag_selected_color", {NULL}, &setattr},
+ {TYPE_STR, "tag_key_color", {NULL}, &setattr},
+ {TYPE_STR, "tag_key_selected_color", {NULL}, &setattr},
+ {TYPE_STR, "check_color", {NULL}, &setattr},
+ {TYPE_STR, "check_selected_color", {NULL}, &setattr},
+ {TYPE_STR, "uarrow_color", {NULL}, &setattr},
+ {TYPE_STR, "darrow_color", {NULL}, &setattr},
+ {TYPE_STR, "itemhelp_color", {NULL}, &setattr},
+ {TYPE_STR, "form_active_text_color", {NULL}, &setattr},
+ {TYPE_STR, "form_text_color", {NULL}, &setattr},
+ {TYPE_STR, "form_item_readonly_color", {NULL}, &setattr},
+ {TYPE_STR, "gauge_color", {gauge_color}, &setattr},
+ {0, NULL, {0}, NULL}
+};
+
+/*
+ * figpar call-back for interpreting value as .dialogrc `Attribute'
+ */
+int
+setattr(struct config *option, uint32_t line __unused,
+ char *directive __unused, char *value)
+{
+ size_t len;
+ char *cp = value, 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 != '(')
+ {
+ char *val;
+
+ /* 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'
+ */
+int
+setbool(struct 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'
+ */
+int
+setnum(struct 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'
+ */
+int
+setstr(struct 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)
+{
+ int res;
+ char *cp, 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)
+ {
+ size_t len;
+
+ /* 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, 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 dummy_config (full of NULLs) if none found (see get_config_option(3);
+ * part of figpar(3)).
+ */
+struct config *
+dialogrc_config_option(const char *directive)
+{
+ return get_config_option(dialogrc_config, directive);
+}
Index: lib/libdpv/dprompt.h
===================================================================
--- /dev/null
+++ lib/libdpv/dprompt.h
@@ -0,0 +1,62 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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;
+
+/* Function prototypes */
+int dprompt_add(const char *format, ...);
+void dprompt_clear(void);
+void dprompt_dprint(int fd, const char *prefix, const char *append,
+ int overall);
+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_sprint(char * restrict str, const char *prefix,
+ const char *append);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_DPROMPT_H_ */
Index: lib/libdpv/dprompt.c
===================================================================
--- /dev/null
+++ lib/libdpv/dprompt.c
@@ -0,0 +1,731 @@
+/*-
+ * 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$");
+
+#define _BSD_SOURCE
+#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 <sys/types.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 */
+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 done_size, done_lsize, done_rsize;
+static int fail_size, fail_lsize, fail_rsize;
+static int msg_size, msg_lsize, msg_rsize;
+static int pend_size, pend_lsize, pend_rsize;
+static int pct_lsize, pct_rsize;
+static int gauge_percent = 0;
+static char *done = NULL;
+static char *fail = NULL;
+static char *pend = NULL;
+static char msg[PROMPT_MAX + 1];
+#define SPIN_SIZE 4
+static char spin[SPIN_SIZE + 1] = "/-\\|";
+static char *spin_cp = spin;
+static void *gauge = NULL;
+
+/* Function prototypes for private functions (see style(9)) */
+char spin_char(void);
+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.
+ */
+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 max_cols, max_rows, 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--)
+ {
+ int nthfile = 0, nlines = 0;
+
+ fheight = (int)dpv_nfiles > display_limit ?
+ (unsigned int)display_limit : dpv_nfiles;
+ for (curfile = file_list; curfile != NULL;
+ curfile = curfile->next)
+ {
+ nthfile++;
+ nlines += dialog_prompt_numlines(curfile->name, nls);
+ if ((nthfile % display_limit) == 0) {
+ if (nlines > fheight) fheight = nlines;
+ nlines = nthfile = 0;
+ }
+ }
+ if (nlines > fheight) fheight = nlines;
+ 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)
+ {
+ int len;
+
+ 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?!");
+ 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?!");
+ 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?!");
+ 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.
+ */
+int
+dprompt_add_files(struct dpv_file_node *file_list,
+ struct dpv_file_node *curfile, int pct)
+{
+ uint8_t after_curfile = ( curfile ? FALSE : TRUE );
+ uint8_t nls = 0;
+ int nlines = 0, nthfile = 0;
+ enum dprompt_state dstate;
+ char bold_code = 'b'; /* default: enabled */
+ char color_code = '4'; /* default: blue */
+ char flabel[FLABEL_MAX + 1];
+ char pbar[pbar_size + 16]; /* +15 for optional color */
+ struct dpv_file_node *fp;
+
+ /* 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)
+ {
+ int pwidth;
+ char pbar_cap[sizeof(pbar)];
+ char pbar_fill[sizeof(pbar)];
+
+ /* 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)
+ {
+ int flabel_size = label_size;
+ int estext_lsize, estext_size, estext_rsize;
+ char *lastline, *name = fp->name;
+ const char *format, *bg_code, *estext;
+
+
+ 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)
+ {
+ char 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)
+ {
+ const char *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)
+ {
+ char *cp;
+
+ /*
+ * 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) */
+ {
+ char human[32];
+ int lsize, rsize, hlen;
+
+ 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 < (msg_size = strlen(msg))) {
+ msg_lsize = msg_rsize = 0;
+ *(msg + pbar_size) = '\0';
+ msg_size = pbar_size;
+ } else {
+ msg_lsize = (pbar_size - msg_size) / 2;
+ msg_rsize = msg_lsize;
+ if ((msg_rsize + msg_size + msg_lsize)
+ != pbar_size) msg_rsize++;
+ }
+ if (use_color)
+ dprompt_add(format, bold_code, bg_code,
+ msg_lsize, "", msg, msg_rsize, "");
+ else
+ dprompt_add(format, msg_lsize, "", msg,
+ msg_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 */
+ 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)
+{
+ /*
+ * 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)
+ {
+ size_t len;
+
+ /* 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);
+}
Index: lib/libdpv/dpv.h
===================================================================
--- /dev/null
+++ lib/libdpv/dpv.h
@@ -0,0 +1,168 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+/* Data to process */
+extern long long 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"
+
+/* Function prototypes */
+int dpv(struct dpv_config *config, struct dpv_file_node *file_list);
+void dpv_free(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_DPV_H_ */
Index: lib/libdpv/dpv.3
===================================================================
--- /dev/null
+++ lib/libdpv/dpv.3
@@ -0,0 +1,483 @@
+.\" 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 Aug 30, 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 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 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 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 .
Index: lib/libdpv/dpv.c
===================================================================
--- /dev/null
+++ lib/libdpv/dpv.c
@@ -0,0 +1,701 @@
+/*-
+ * 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 <dialog.h>
+#include <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.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 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 *pprompt = NULL; /* dpv_config.pprompt */
+char *msg_done = NULL;
+char *msg_fail = NULL;
+char *msg_pending = NULL;
+
+/* Status-Line format for when using dialog(3) */
+char status_format_default[DPV_STATUS_FORMAT_MAX];
+const char *status_format_custom = NULL;
+
+/*
+ * 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)
+{
+ uint8_t no_overrun = FALSE;
+ uint8_t pprompt_nls = 0; /* See dialog_prompt_nlstate() */
+ uint8_t shrink_label_size = FALSE;
+ uint16_t options;
+ enum dpv_display display_type;
+ enum dpv_output output_type;
+ int (*action)(struct dpv_file_node *file, int out);
+ int dialog_out = STDOUT_FILENO;
+ int dialog_updates_per_second, dialog_update_usec = 0;
+ int files_left;
+ int max_cols;
+ int nthfile = 0, overall = 0;
+ int seconds, status_old_seconds = -1, dialog_old_seconds = -1;
+ int status_last_update = 0, dialog_last_update = 0;
+ int status_old_nthfile = 0, dialog_old_nthfile = 0;
+ int status_updates_per_second, status_update_usec = 0;
+ char *cp, *output;
+ const char *status_fmt, *path_fmt;
+ pid_t pid;
+ size_t len;
+ struct dpv_file_node *list_head, *curfile;
+ struct timeval start, now;
+
+ /* 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)
+ {
+ int nls = 0; /* See dialog_prompt_nlstate() */
+ char *last, *name = curfile->name;
+
+ if (curfile == file_list) nls = pprompt_nls;
+ last = (char *)dialog_prompt_lastline(name, nls);
+ if (use_dialog) {
+ char 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) {
+ int res;
+ 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)
+ {
+ int backslash = 0;
+ char *fc;
+
+ /* 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)
+ {
+ struct dpv_file_node *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)
+ {
+ char init_prompt[PROMPT_MAX + 1] = "";
+
+ /* 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 */
+ dprompt_sprint(init_prompt, pprompt, aprompt);
+ if (use_libdialog) {
+ status_printf("");
+ dprompt_libprint(pprompt, aprompt, 0);
+ } else {
+ 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) */
+ 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)
+ {
+ uint8_t keep_going = TRUE;
+ int output_out = -1;
+ int pct = 0;
+ pid_t output_pid;
+
+ 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)
+ {
+ mode_t 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)
+ {
+ enum dpv_status status;
+
+ if (dialog_test) {
+ usleep(50000);
+ pct += increment;
+ overall_read +=
+ (int)(random() / 512 / dpv_nfiles);
+ } 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, overall_read,
+ (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__, overall_read);
+
+ if (dpv_interrupt || dpv_abort)
+ return -1;
+ else
+ return 0;
+}
+
+/*
+ * Free allocated items initialized by dpv()
+ */
+void
+dpv_free(void)
+{
+ if (aprompt != NULL) free(aprompt);
+ if (pprompt != NULL) free(pprompt);
+}
Index: lib/libdpv/dpv_private.h
===================================================================
--- /dev/null
+++ lib/libdpv/dpv_private.h
@@ -0,0 +1,74 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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 */
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_DPV_PRIVATE_H_ */
Index: lib/libdpv/status.h
===================================================================
--- /dev/null
+++ lib/libdpv/status.h
@@ -0,0 +1,47 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 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 */
+
+/* Function prototypes */
+void status_printf(const char *format, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_STATUS_H_ */
Index: lib/libdpv/status.c
===================================================================
--- /dev/null
+++ lib/libdpv/status.c
@@ -0,0 +1,98 @@
+/*-
+ * 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;
+static int status_bufsize = -1;
+static int status_row, 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, ...)
+{
+ va_list args;
+ int n, attrs;
+ chtype color = dlg_color_pair(dlg_color_table[BUTTON_ACTIVE_ATTR].fg,
+ dlg_color_table[SCREEN_ATTR].bg) | A_BOLD;
+
+ 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();
+}
Index: lib/libdpv/util.h
===================================================================
--- /dev/null
+++ lib/libdpv/util.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 _UTIL_H_
+#define _UTIL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#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
+
+/* Function prototypes */
+int shell_spawn_pipecmd(const char *cmd, const char *label, pid_t *pid);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_UTIL_H_ */
Index: lib/libdpv/util.c
===================================================================
--- /dev/null
+++ lib/libdpv/util.c
@@ -0,0 +1,112 @@
+/*-
+ * 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 shellcmd[PATH_MAX] = PATH_SHELL;
+static char cmdbuf[CMDBUFMAX] = "";
+
+/*
+ * 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)
+{
+ unsigned int n = 0;
+ int len, error, stdin_pipe[2] = { -1, -1 };
+ char *dargv[6];
+ posix_spawn_file_actions_t action;
+
+ /* Populate argument array */
+ dargv[n++] = shellcmd;
+ if ((dargv[n] = malloc(3)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "-c");
+ 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;
+ }
+ dargv[n++] = cmdbuf;
+ if ((dargv[n] = malloc(3)) == NULL)
+ errx(EXIT_FAILURE, "Out of memory?!");
+ sprintf(dargv[n++], "--");
+ dargv[n++] = shellcmd;
+ dargv[n] = NULL;
+
+ /* 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
+ {
+ unsigned int i;
+
+ fprintf(stderr, "%s: spawning `", __func__);
+ for (i = 0; i < n; i++)
+ {
+ if (i == 0)
+ fprintf(stderr, "%s", dargv[i]);
+ else if (i == 2)
+ 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, shellcmd, &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];
+}
Index: lib/libfigpar/Makefile
===================================================================
--- /dev/null
+++ 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>
Index: lib/libfigpar/figpar.h
===================================================================
--- /dev/null
+++ lib/libfigpar/figpar.h
@@ -0,0 +1,105 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+
+/*
+ * Union for storing various types of data in a single common container.
+ */
+union 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 cfgtype {
+ TYPE_NONE = 0x0000, /* for directives with no value */
+ TYPE_BOOL = 0x0001, /* boolean */
+ TYPE_INT = 0x0002, /* signed 32 bit integer */
+ TYPE_UINT = 0x0004, /* unsigned 32 bit integer */
+ TYPE_STR = 0x0008, /* string pointer */
+ TYPE_STRARRAY = 0x0010, /* string array pointer */
+ TYPE_DATA1 = 0x0020, /* void data type-1 (for whatever) */
+ TYPE_DATA2 = 0x0040, /* void data type-2 (for whatever) */
+ TYPE_DATA3 = 0x0080, /* void data type-3 (for whatever) */
+ TYPE_RESERVED1 = 0x0100, /* reserved data type-1 (for future) */
+ TYPE_RESERVED2 = 0x0200, /* reserved data type-2 (for future) */
+ TYPE_RESERVED3 = 0x0400, /* reserved data type-3 (for future) */
+};
+
+/*
+ * Options to parse_config() for processing_options bitmask
+ */
+#define BREAK_ON_EQUALS 0x0001 /* stop reading directive at `=' */
+#define BREAK_ON_SEMICOLON 0x0002 /* `;' starts a new line */
+#define CASE_SENSITIVE 0x0004 /* directives are case sensitive */
+#define REQUIRE_EQUALS 0x0008 /* assignment directives only */
+#define STRICT_EQUALS 0x0010 /* `=' must be part of directive */
+
+/*
+ * Anatomy of a config file option
+ */
+struct config {
+ enum cfgtype type; /* Type of value that the option is */
+ const char *directive; /* Keyword to locate in config file */
+ union cfgvalue value; /* Default (overwrite by action) */
+
+ /*
+ * Function pointer; action to be taken when the directive is found
+ */
+ int (*action)(struct config *option, uint32_t line, char *directive,
+ char *value);
+};
+extern struct config dummy_config;
+
+/* Function prototypes */
+struct config * get_config_option(struct config options[],
+ const char *directive);
+int parse_config(struct config options[], const char *path,
+ int (*unknown)(struct config *option, uint32_t line,
+ char *directive, char *value),
+ uint16_t processing_options);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FIGPAR_H_ */
Index: lib/libfigpar/figpar.3
===================================================================
--- /dev/null
+++ 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 Aug 25, 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 config options[], const char *path"
+.Fa "int \*[lp]*unknown\*[rp]\*[lp]struct config *option, uint32_t line"
+.Fa "char *directive, char *value\*[rp], uint8_t processing_options"
+.Fc
+.Ft "struct config *"
+.Fo get_config_option
+.Fa "struct 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 config {
+ enum cfgtype type; /* value type */
+ const char *directive; /* keyword */
+ union cfgvalue value; /* value */
+
+ /* Pointer to function used when directive is found */
+ int (*action)(struct config *option, uint32_t line,
+ char *directive, char *value);
+};
+
+enum cfgtype {
+ TYPE_NONE = 0x0000, /* for directives with no value */
+ TYPE_BOOL = 0x0001, /* boolean */
+ TYPE_INT = 0x0002, /* signed 32 bit integer */
+ TYPE_UINT = 0x0004, /* unsigned 32 bit integer */
+ TYPE_STR = 0x0008, /* string pointer */
+ TYPE_STRARRAY = 0x0010, /* string array pointer */
+ TYPE_DATA1 = 0x0020, /* void data type-1 (for whatever) */
+ TYPE_DATA2 = 0x0040, /* void data type-2 (for whatever) */
+ TYPE_DATA3 = 0x0080, /* void data type-3 (for whatever) */
+ TYPE_RESERVED1 = 0x0100, /* reserved data type-1 (for future) */
+ TYPE_RESERVED2 = 0x0200, /* reserved data type-2 (for future) */
+ TYPE_RESERVED3 = 0x0400, /* reserved data type-3 (for future) */
+};
+
+union 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 BREAK_ON_SEMICOLON
+.It Dv 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 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 CASE_SENSITIVE
+Normally directives are matched case insensitively using
+.Xr fnmatch 3 .
+This flag enables directive matching to be case sensitive.
+.It Dv REQUIRE_EQUALS
+If a directive is not followed by an equals, processing is aborted.
+.It Dv 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 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 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 config"
+is entirely optional as-is the use of
+.Fa "enum cfgtype"
+or
+.Fa "union 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 still subject to refinement.
Index: lib/libfigpar/figpar.c
===================================================================
--- /dev/null
+++ lib/libfigpar/figpar.c
@@ -0,0 +1,403 @@
+/*-
+ * 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+#include "figpar.h"
+#include "string_m.h"
+
+struct config dummy_config = {0, NULL, {0}, NULL};
+
+/*
+ * Search for config option (struct 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 config *
+get_config_option(struct config options[], const char *directive)
+{
+ uint32_t n;
+
+ /* Check arguments */
+ if (options == NULL || directive == NULL) return &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 */
+ dummy_config.directive = NULL;
+ dummy_config.type = 0;
+ dummy_config.value.u_num = 0;
+ dummy_config.action = NULL;
+
+ return &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 config options[], const char *path,
+ int (*unknown)(struct config *option, uint32_t line, char *directive,
+ char *value), uint16_t processing_options)
+{
+ uint8_t bequals, bsemicolon, case_sensitive;
+ uint8_t require_equals, strict_equals;
+ uint8_t comment = 0, have_equals = 0, quote, end, found;
+ int fd, error;
+ off_t curpos, charpos;
+ ssize_t r = 1;
+ char rpath[PATH_MAX], p[2], *t;
+ char *directive, *value;
+ uint32_t dsize, vsize, n, x, line = 1;
+
+ /* Sanity check: if no options and no unknown function, return */
+ if (options == NULL && unknown == NULL) return -1;
+
+ /* Processing options */
+ bequals = ((processing_options & BREAK_ON_EQUALS) == 0 ? 0 : 1);
+ bsemicolon = ((processing_options & BREAK_ON_SEMICOLON) == 0 ? 0 : 1);
+ case_sensitive = ((processing_options & CASE_SENSITIVE) == 0 ? 0 : 1);
+ require_equals = ((processing_options & REQUIRE_EQUALS) == 0 ? 0 : 1);
+ strict_equals = ((processing_options & 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)
+ {
+ 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 = charpos - curpos;
+ if (r != 0) n--; /* more to read, but don't read ending key */
+
+ /* 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;
+}
Index: lib/libfigpar/string_m.h
===================================================================
--- /dev/null
+++ lib/libfigpar/string_m.h
@@ -0,0 +1,48 @@
+/*-
+ * 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_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Function prototypes */
+int replaceall(char *source, const char *find,
+ const char *replace);
+unsigned int strcount(const char *source, const char *find);
+void strexpand(char *source);
+void strexpandnl(char *source);
+void strtolower(char *source);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_STRING_M_H_ */
Index: lib/libfigpar/string_m.c
===================================================================
--- /dev/null
+++ lib/libfigpar/string_m.c
@@ -0,0 +1,292 @@
+/*-
+ * 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 <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.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)
+{
+ unsigned int n = 0;
+ size_t flen;
+ const char *p = source;
+
+ /* 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) {
+ p += flen; n++; /* found an instance */
+ } 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)
+{
+ size_t slen, flen, rlen;
+ uint32_t n = 0;
+ char *p, *t, *temp;
+
+ 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) return -1; /* could not allocate memory */
+ 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 *pos, *chr, 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, *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 ? '\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 */
+ }
+}
Index: share/mk/bsd.libnames.mk
===================================================================
--- share/mk/bsd.libnames.mk
+++ share/mk/bsd.libnames.mk
@@ -43,12 +43,14 @@
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
Index: usr.bin/Makefile
===================================================================
--- usr.bin/Makefile
+++ usr.bin/Makefile
@@ -36,6 +36,7 @@
ctlstat \
cut \
dirname \
+ dpv \
du \
ee \
elf2aout \
Index: usr.bin/dpv/Makefile
===================================================================
--- /dev/null
+++ usr.bin/dpv/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+PROG= dpv
+
+CFLAGS+= -I${.CURDIR}
+
+DPADD+= ${LIBDPV} ${LIBDIALOG} ${LIBFIGPAR} ${LIBNCURSES} ${LIBUTIL}
+LDADD+= -ldpv -ldialog -lfigpar -lncurses -lutil
+
+WARNS?= 6
+
+.include <bsd.prog.mk>
Index: usr.bin/dpv/dpv.1
===================================================================
--- /dev/null
+++ usr.bin/dpv/dpv.1
@@ -0,0 +1,399 @@
+.\" 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 Aug 30, 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 .
+.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
Index: usr.bin/dpv/dpv.c
===================================================================
--- /dev/null
+++ usr.bin/dpv/dpv.c
@@ -0,0 +1,549 @@
+/*-
+ * 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 <dialog.h>
+#include <dpv.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#define _BSD_SOURCE
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "dpv_util.h"
+
+/* Debugging */
+static uint8_t debug = FALSE;
+
+/* Data to process */
+static unsigned int nfiles = 0;
+static struct dpv_file_node *file_list = NULL;
+
+/* Data processing */
+static uint8_t no_overrun = FALSE;
+static uint8_t line_mode = FALSE;
+static int output_type = DPV_OUTPUT_NONE;
+static int fd = -1;
+static size_t bsize;
+static char *buf = NULL;
+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 for private functions (see style(9)) */
+void usage(void);
+void sig_int(int sig);
+int operate_common(struct dpv_file_node *file, int out);
+int operate_on_bytes(struct dpv_file_node *file, int out);
+int operate_on_lines(struct dpv_file_node *file, int out);
+
+int
+operate_common(struct dpv_file_node *file, int out)
+{
+ /* 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)
+ {
+ struct stat sb;
+
+ 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;
+}
+
+int
+operate_on_bytes(struct dpv_file_node *file, int out)
+{
+ 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);
+ }
+
+ overall_read += r;
+ file->read += r;
+
+ /* Calculate percentage of completion (if possible) */
+ if (file->length >= 0)
+ {
+ int 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;
+}
+
+int
+operate_on_lines(struct dpv_file_node *file, int out)
+{
+ char *p;
+ 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)
+ overall_read++, p++, file->read++;
+
+ /* Calculate percentage of completion (if possible) */
+ if (file->length >= 0)
+ {
+ int 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[])
+{
+ struct dpv_config *config;
+ struct dpv_file_node *curfile;
+ size_t config_size = sizeof(struct dpv_config);
+ size_t file_node_size = sizeof(struct dpv_file_node);
+ int ch;
+ int n = 0;
+ 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;
+ 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) */
+ default: /* unhandled argument (based on switch) */
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Process remaining arguments as list of names to display */
+ for (curfile = file_list; n < argc; n++)
+ {
+ char dummy;
+
+ 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];
+ }
+
+ if (!multiple) break;
+ }
+
+ /* Display usage and exit if not given at least one name */
+ if (nfiles == 0) {
+ warnx("no labels provided");
+ usage();
+ }
+
+ /*
+ * 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();
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * Interrupt handler to indicate we received a Ctrl-C interrupt.
+ */
+void
+sig_int(int sig __unused)
+{
+ dpv_interrupt = TRUE;
+}
+
+/*
+ * Print short usage statement to stderr and exit with error status.
+ */
+void
+usage(void)
+{
+
+ if (debug) exit(EXIT_FAILURE); /* No need for usage */
+
+ fprintf(stderr, "Usage: %s [options] lines:label\n", pgm);
+ fprintf(stderr, " %s [options] -m lines1:label1 path1 "
+ "[lines2: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);
+}
Index: usr.bin/dpv/dpv_util.h
===================================================================
--- /dev/null
+++ 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_ */
Index: usr.sbin/bsdinstall/distextract/Makefile
===================================================================
--- usr.sbin/bsdinstall/distextract/Makefile
+++ usr.sbin/bsdinstall/distextract/Makefile
@@ -2,8 +2,8 @@
BINDIR= /usr/libexec/bsdinstall
PROG= distextract
-DPADD= ${LIBARCHIVE} ${LIBNCURSESW} ${LIBDIALOG} ${LIBM}
-LDADD= -larchive -lncursesw -ldialog -lm
+DPADD= ${LIBARCHIVE} ${LIBDPV} ${LIBDIALOG} ${LIBFIGAR} ${LIBM} ${LIBNCURSESW}
+LDADD= -larchive -ldpv -ldialog -lfigpar -lncursesw -lm
WARNS?= 6
MAN=
Index: usr.sbin/bsdinstall/distextract/distextract.c
===================================================================
--- usr.sbin/bsdinstall/distextract/distextract.c
+++ usr.sbin/bsdinstall/distextract/distextract.c
@@ -1,5 +1,6 @@
/*-
* Copyright (c) 2011 Nathan Whitehorn
+ * Copyright (c) 2014 Devin Teske <dteske@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -27,215 +28,309 @@
*/
#include <sys/param.h>
-#include <stdio.h>
-#include <errno.h>
-#include <limits.h>
#include <archive.h>
+#include <ctype.h>
#include <dialog.h>
+#include <dpv.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Data to process */
+static char *distdir = NULL;
+static struct archive *archive = NULL;
+static struct dpv_file_node *file_list = NULL;
+
+/* Data processing */
+static int extract_flags = 0;
+
+/* Extra display information */
+static char backtitle[] = "FreeBSD Installer";
+static char title[] = "Archive Extraction";
+static char pprompt[] = "Extracting distribution files...\n";
+static char aprompt[] = "\n Overall Progress:";
+
+/* Function prototypes for private functions (see style(9)) */
+static int count_archive_files(const char *file);
+static int extract(struct dpv_file_node *file, int out);
+void sig_int(int sig);
+
+#if __FreeBSD_version <= 1000008 /* r232154: bump for libarchive update */
+#define archive_read_support_filter_all(x) \
+ archive_read_support_compression_all(x)
+#endif
-static int extract_files(int nfiles, const char **files);
+#define _errx(...) (end_dialog(), errx(__VA_ARGS__))
int
main(void)
{
- char *diststring;
- const char **dists;
- int i, retval, ndists = 0;
-
- if (getenv("DISTRIBUTIONS") == NULL) {
- fprintf(stderr, "DISTRIBUTIONS variable is not set\n");
- return (1);
- }
-
- diststring = strdup(getenv("DISTRIBUTIONS"));
- for (i = 0; diststring[i] != 0; i++)
- if (isspace(diststring[i]) && !isspace(diststring[i+1]))
- ndists++;
- ndists++; /* Last one */
-
- dists = calloc(ndists, sizeof(const char *));
- if (dists == NULL) {
- fprintf(stderr, "Out of memory!\n");
- free(diststring);
- return (1);
- }
-
- for (i = 0; i < ndists; i++)
- dists[i] = strsep(&diststring, " \t");
+ int ndists = 0;
+ size_t config_size = sizeof(struct dpv_config);
+ size_t file_node_size = sizeof(struct dpv_file_node);
+ char *distributions, *chrootdir;
+ struct dpv_config *config;
+ struct dpv_file_node *curfile = file_list;
+ struct sigaction act;
+
+ if ((distributions = getenv("DISTRIBUTIONS")) == NULL)
+ errx(EXIT_FAILURE, "DISTRIBUTIONS variable is not set");
+ if ((distdir = getenv("BSDINSTALL_DISTDIR")) == NULL)
+ distdir = __DECONST(char *, "");
+ /* Initialize dialog(3) */
init_dialog(stdin, stdout);
- dialog_vars.backtitle = __DECONST(char *, "FreeBSD Installer");
+ dialog_vars.backtitle = backtitle;
dlg_put_backtitle();
- if (chdir(getenv("BSDINSTALL_CHROOT")) != 0) {
- char error[512];
- sprintf(error, "Could could change to directory %s: %s\n",
- getenv("BSDINSTALL_DISTDIR"), strerror(errno));
+ dialog_msgbox("",
+ "Checking distribution archives.\nPlease wait...", 4, 35, FALSE);
+
+ /*
+ * Parse $DISTRIBUTIONS into dpv(3) linked-list
+ */
+ while (*distributions != '\0')
+ {
+ size_t span = strcspn(distributions, "\t\n\v\f\r ");
+
+ if (span < 1) { /* currently on whitespace */
+ distributions++;
+ continue;
+ }
+ ndists++;
+
+ /* Allocate a new struct for the distribution */
+ if (curfile == NULL) {
+ if ((curfile = calloc(1, file_node_size)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory?!");
+ file_list = curfile;
+ } else {
+ if ((curfile->next = calloc(1, file_node_size))
+ == NULL) _errx(EXIT_FAILURE, "Out of memory?!");
+ curfile = curfile->next;
+ }
+
+ /* Set path */
+ if ((curfile->path = malloc(span + 1)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory?!");
+ snprintf(curfile->path, span + 1, "%s", distributions);
+ curfile->path[span] = '\0';
+
+ /* Set display name */
+ curfile->name = strrchr(curfile->path, '/');
+ if (curfile->name == NULL) curfile->name = curfile->path;
+
+ /* Set initial length in bytes (-1 == unknown) */
+ curfile->length = count_archive_files(curfile->path);
+ if (curfile->length < 0) {
+ end_dialog();
+ return EXIT_FAILURE;
+ }
+
+ distributions += span;
+ }
+
+ /* Optionally chdir(2) into $BSDINSTALL_CHROOT */
+ chrootdir = getenv("BSDINSTALL_CHROOT");
+ if (chrootdir != NULL && chdir(chrootdir) != 0)
+ {
+ char error[PATH_MAX + 512];
+
+ snprintf(error, sizeof(error),
+ "Could could change to directory %s: %s\n",
+ chrootdir, strerror(errno));
dialog_msgbox("Error", error, 0, 0, TRUE);
+
end_dialog();
- return (1);
+ return EXIT_FAILURE;
}
- retval = extract_files(ndists, dists);
+ /*
+ * Set cleanup routine for Ctrl-C action
+ */
+ act.sa_handler = sig_int;
+ sigaction(SIGINT, &act, 0);
+
+ /*
+ * Hand off to dpv(3)
+ */
+
+ if ((config = calloc(1, config_size)) == NULL)
+ _errx(EXIT_FAILURE, "Out of memory?!");
+
+ config->backtitle = backtitle;
+ config->title = title;
+ config->pprompt = pprompt;
+ config->aprompt = aprompt;
+ config->options |= DPV_WIDE_MODE;
+ config->label_size = -1;
+ config->action = extract;
+ config->status_solo =
+ "%10lli files read @ %'9.1f files/sec.";
+ config->status_many =
+ "%10lli files read @ %'9.1f files/sec. [%i/%i busy/wait]";
+
+ extract_flags |= ARCHIVE_EXTRACT_TIME;
+ extract_flags |= ARCHIVE_EXTRACT_OWNER;
+ extract_flags |= ARCHIVE_EXTRACT_PERM;
+ extract_flags |= ARCHIVE_EXTRACT_ACL;
+ extract_flags |= ARCHIVE_EXTRACT_XATTR;
+ extract_flags |= ARCHIVE_EXTRACT_FFLAGS;
end_dialog();
+ return dpv(config, file_list);
+}
- free(diststring);
- free(dists);
-
- return (retval);
+void
+sig_int(int sig __unused)
+{
+ dpv_interrupt = TRUE;
}
+/*
+ * Returns number of files in archive file. Parses $BSDINSTALL_DISTDIR/MANIFEST
+ * if it exists, otherwise uses archive(3) to read the archive file.
+ */
static int
-count_files(const char *file)
+count_archive_files(const char *file)
{
- struct archive *archive;
- struct archive_entry *entry;
- static FILE *manifest = NULL;
- char path[MAXPATHLEN];
+ int count, rv;
+ char path[PATH_MAX];
char errormsg[512];
- int file_count, err;
+ static FILE *manifest = NULL;
+ struct archive_entry *entry;
if (manifest == NULL) {
- sprintf(path, "%s/MANIFEST", getenv("BSDINSTALL_DISTDIR"));
+ snprintf(path, PATH_MAX, "%s/MANIFEST", distdir);
manifest = fopen(path, "r");
}
- if (manifest != NULL) {
+ if (manifest != NULL)
+ {
char line[512];
- char *tok1, *tok2;
rewind(manifest);
- while (fgets(line, sizeof(line), manifest) != NULL) {
- tok2 = line;
- tok1 = strsep(&tok2, "\t");
- if (tok1 == NULL || strcmp(tok1, file) != 0)
+ while (fgets(line, sizeof(line), manifest) != NULL)
+ {
+ char *p = &line[0];
+ size_t span = strcspn(p, "\t") ;
+
+ if (span < 1 || strncmp(p, file, span) != 0)
continue;
/*
- * We're at the right manifest line. The file count is
- * in the third element
+ * We're at the right manifest line.
+ * The file count is in the third element
*/
- tok1 = strsep(&tok2, "\t");
- tok1 = strsep(&tok2, "\t");
- if (tok1 != NULL)
- return atoi(tok1);
+ span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
+ span = strcspn(p += span + (*p != '\0' ? 1 : 0), "\t");
+ if (span > 0) {
+ count = (int)strtol(p, (char **)NULL, 10);
+ if (count == 0 && errno == EINVAL)
+ continue;
+ return count;
+ }
}
}
- /* Either we didn't have a manifest, or this archive wasn't there */
- archive = archive_read_new();
+ /*
+ * Either no manifest, or manifest didn't mention this archive.
+ * Use archive(3) to read the archive, counting files within.
+ */
+ if ((archive = archive_read_new()) == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error: %s\n", archive_error_string(NULL));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ return -1;
+ }
archive_read_support_format_all(archive);
archive_read_support_filter_all(archive);
- sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), file);
- err = archive_read_open_filename(archive, path, 4096);
- if (err != ARCHIVE_OK) {
+ snprintf(path, PATH_MAX, "%s/%s", distdir, file);
+ rv = archive_read_open_filename(archive, path, 4096);
+ if (rv != ARCHIVE_OK) {
snprintf(errormsg, sizeof(errormsg),
"Error while extracting %s: %s\n", file,
archive_error_string(archive));
dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
- return (-1);
+ archive = NULL;
+ return -1;
}
- file_count = 0;
+ count = 0;
while (archive_read_next_header(archive, &entry) == ARCHIVE_OK)
- file_count++;
+ count++;
+
archive_read_free(archive);
+ archive = NULL;
- return (file_count);
+ return count;
}
static int
-extract_files(int nfiles, const char **files)
+extract(struct dpv_file_node *file, int out __unused)
{
- const char *items[nfiles*2];
char path[PATH_MAX];
- int archive_files[nfiles];
- int total_files, current_files, archive_file;
- struct archive *archive;
struct archive_entry *entry;
char errormsg[512];
- char status[8];
- int i, err, progress, last_progress;
-
- err = 0;
- progress = 0;
-
- /* Make the transfer list for dialog */
- for (i = 0; i < nfiles; i++) {
- items[i*2] = strrchr(files[i], '/');
- if (items[i*2] != NULL)
- items[i*2]++;
- else
- items[i*2] = files[i];
- items[i*2 + 1] = "Pending";
- }
-
- dialog_msgbox("",
- "Checking distribution archives.\nPlease wait...", 0, 0, FALSE);
+ int rv;
- /* Count all the files */
- total_files = 0;
- for (i = 0; i < nfiles; i++) {
- archive_files[i] = count_files(files[i]);
- if (archive_files[i] < 0)
- return (-1);
- total_files += archive_files[i];
- }
-
- current_files = 0;
-
- for (i = 0; i < nfiles; i++) {
- archive = archive_read_new();
+ /* Open the archive if necessary */
+ if (archive == NULL) {
+ if ((archive = archive_read_new()) == NULL) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error: %s\n", archive_error_string(NULL));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ dpv_abort = 1;
+ return -1;
+ }
archive_read_support_format_all(archive);
archive_read_support_filter_all(archive);
- sprintf(path, "%s/%s", getenv("BSDINSTALL_DISTDIR"), files[i]);
- err = archive_read_open_filename(archive, path, 4096);
-
- items[i*2 + 1] = "In Progress";
- archive_file = 0;
-
- while ((err = archive_read_next_header(archive, &entry)) ==
- ARCHIVE_OK) {
- last_progress = progress;
- progress = (current_files*100)/total_files;
-
- sprintf(status, "-%d",
- (archive_file*100)/archive_files[i]);
- items[i*2 + 1] = status;
-
- if (progress > last_progress)
- dialog_mixedgauge("Archive Extraction",
- "Extracting distribution files...", 0, 0,
- progress, nfiles,
- __DECONST(char **, items));
-
- err = archive_read_extract(archive, entry,
- ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_OWNER |
- ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL |
- ARCHIVE_EXTRACT_XATTR | ARCHIVE_EXTRACT_FFLAGS);
-
- if (err != ARCHIVE_OK)
- break;
-
- archive_file++;
- current_files++;
- }
-
- items[i*2 + 1] = "Done";
-
- if (err != ARCHIVE_EOF) {
+ snprintf(path, PATH_MAX, "%s/%s", distdir, file->path);
+ rv = archive_read_open_filename(archive, path, 4096);
+ if (rv != 0) {
snprintf(errormsg, sizeof(errormsg),
- "Error while extracting %s: %s\n", items[i*2],
+ "Error opening %s: %s\n", file->name,
archive_error_string(archive));
- items[i*2 + 1] = "Failed";
- dialog_msgbox("Extract Error", errormsg, 0, 0,
- TRUE);
- return (err);
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ file->status = DPV_STATUS_FAILED;
+ dpv_abort = 1;
+ return -1;
}
+ }
+
+ /* Read the next archive header */
+ rv = archive_read_next_header(archive, &entry);
+ /* If that went well, perform the extraction */
+ if (rv == ARCHIVE_OK)
+ rv = archive_read_extract(archive, entry, extract_flags);
+
+ /* Test for either EOF or error */
+ if (rv == ARCHIVE_EOF) {
archive_read_free(archive);
+ archive = NULL;
+ file->status = DPV_STATUS_DONE;
+ return 100;
+ } else if (rv != ARCHIVE_OK) {
+ snprintf(errormsg, sizeof(errormsg),
+ "Error while extracting %s: %s\n", file->name,
+ archive_error_string(archive));
+ dialog_msgbox("Extract Error", errormsg, 0, 0, TRUE);
+ file->status = DPV_STATUS_FAILED;
+ dpv_abort = 1;
+ return -1;
}
- return (0);
+ overall_read++;
+ file->read++;
+
+ /* Calculate [overall] percentage of completion (if possible) */
+ if (file->length >= 0)
+ return (file->read * 100 / file->length);
+ else
+ return -1;
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Nov 24, 12:43 PM (3 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
26060642
Default Alt Text
D714.id1337.diff (189 KB)
Attached To
Mode
D714: Installer Enhancement -- dpv
Attached
Detach File
Event Timeline
Log In to Comment