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