aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/dpv/dpv.c
diff options
context:
space:
mode:
authorDevin Teske <dteske@FreeBSD.org>2014-11-04 23:46:01 +0000
committerDevin Teske <dteske@FreeBSD.org>2014-11-04 23:46:01 +0000
commit041394f38a59889f0e14ace3306df5310cd5aeac (patch)
tree98a67555867e474c5f716bf62b6c1120a5cfc536 /usr.bin/dpv/dpv.c
parentbccb6d5aa14a143f583903dbd28512ed3e93237f (diff)
downloadsrc-041394f38a59889f0e14ace3306df5310cd5aeac.tar.gz
src-041394f38a59889f0e14ace3306df5310cd5aeac.zip
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
Notes
Notes: svn path=/head/; revision=274116
Diffstat (limited to 'usr.bin/dpv/dpv.c')
-rw-r--r--usr.bin/dpv/dpv.c541
1 files changed, 541 insertions, 0 deletions
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 <dteske@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */
+#include <dialog.h>
+#include <dpv.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string_m.h>
+#include <unistd.h>
+
+#include "dpv_util.h"
+
+/* Debugging */
+static uint8_t debug = FALSE;
+
+/* Data to process */
+static struct dpv_file_node *file_list = NULL;
+static unsigned int nfiles = 0;
+
+/* Data processing */
+static uint8_t line_mode = FALSE;
+static uint8_t no_overrun = FALSE;
+static char *buf = NULL;
+static int fd = -1;
+static int output_type = DPV_OUTPUT_NONE;
+static size_t bsize;
+static char rpath[PATH_MAX];
+
+/* Extra display information */
+static uint8_t multiple = FALSE; /* `-m' */
+static char *pgm; /* set to argv[0] by main() */
+
+/* Function prototypes */
+static void sig_int(int sig);
+static void usage(void);
+int main(int argc, char *argv[]);
+static int operate_common(struct dpv_file_node *file, int out);
+static int operate_on_bytes(struct dpv_file_node *file, int out);
+static int operate_on_lines(struct dpv_file_node *file, int out);
+
+static int
+operate_common(struct dpv_file_node *file, int out)
+{
+ struct stat sb;
+
+ /* Open the file if necessary */
+ if (fd < 0) {
+ if (multiple) {
+ /* Resolve the file path and attempt to open it */
+ if (realpath(file->path, rpath) == 0 ||
+ (fd = open(rpath, O_RDONLY)) < 0) {
+ warn("%s", file->path);
+ file->status = DPV_STATUS_FAILED;
+ return (-1);
+ }
+ } else {
+ /* Assume stdin, but if that's a TTY instead use the
+ * highest numbered file descriptor (obtained by
+ * generating new fd and then decrementing).
+ *
+ * NB: /dev/stdin should always be open(2)'able
+ */
+ fd = STDIN_FILENO;
+ if (isatty(fd)) {
+ fd = open("/dev/stdin", O_RDONLY);
+ close(fd--);
+ }
+
+ /* This answer might be wrong, if dpv(3) has (by
+ * request) opened an output file or pipe. If we
+ * told dpv(3) to open a file, subtract one from
+ * previous answer. If instead we told dpv(3) to
+ * prepare a pipe output, subtract two.
+ */
+ switch(output_type) {
+ case DPV_OUTPUT_FILE:
+ fd -= 1;
+ break;
+ case DPV_OUTPUT_SHELL:
+ fd -= 2;
+ break;
+ }
+ }
+ }
+
+ /* Allocate buffer if necessary */
+ if (buf == NULL) {
+ /* Use output block size as buffer size if available */
+ if (out >= 0) {
+ if (fstat(out, &sb) != 0) {
+ warn("%i", out);
+ file->status = DPV_STATUS_FAILED;
+ return (-1);
+ }
+ if (S_ISREG(sb.st_mode)) {
+ if (sysconf(_SC_PHYS_PAGES) >
+ PHYSPAGES_THRESHOLD)
+ bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
+ else
+ bsize = BUFSIZE_SMALL;
+ } else
+ bsize = MAX(sb.st_blksize,
+ (blksize_t)sysconf(_SC_PAGESIZE));
+ } else
+ bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
+
+ /* Attempt to allocate */
+ if ((buf = malloc(bsize+1)) == NULL) {
+ end_dialog();
+ err(EXIT_FAILURE, "Out of memory?!");
+ }
+ }
+
+ return (0);
+}
+
+static int
+operate_on_bytes(struct dpv_file_node *file, int out)
+{
+ int progress;
+ ssize_t r, w;
+
+ if (operate_common(file, out) < 0)
+ return (-1);
+
+ /* [Re-]Fill the buffer */
+ if ((r = read(fd, buf, bsize)) <= 0) {
+ if (fd != STDIN_FILENO)
+ close(fd);
+ fd = -1;
+ file->status = DPV_STATUS_DONE;
+ return (100);
+ }
+
+ /* [Re-]Dump the buffer */
+ if (out >= 0) {
+ if ((w = write(out, buf, r)) < 0) {
+ end_dialog();
+ err(EXIT_FAILURE, "output");
+ }
+ fsync(out);
+ }
+
+ 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);
+}