2 * Copyright (c) 2013-2016 Devin Teske <dteske@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
31 #include <sys/types.h>
33 #define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */
50 static uint8_t debug = FALSE;
53 static struct dpv_file_node *file_list = NULL;
54 static unsigned int nfiles = 0;
57 static uint8_t line_mode = FALSE;
58 static uint8_t no_overrun = FALSE;
59 static char *buf = NULL;
61 static int output_type = DPV_OUTPUT_NONE;
63 static char rpath[PATH_MAX];
65 /* Extra display information */
66 static uint8_t multiple = FALSE; /* `-m' */
67 static char *pgm; /* set to argv[0] by main() */
69 /* Function prototypes */
70 static void sig_int(int sig);
71 static void usage(void);
72 int main(int argc, char *argv[]);
73 static int operate_common(struct dpv_file_node *file, int out);
74 static int operate_on_bytes(struct dpv_file_node *file, int out);
75 static int operate_on_lines(struct dpv_file_node *file, int out);
78 operate_common(struct dpv_file_node *file, int out)
82 /* Open the file if necessary */
85 /* Resolve the file path and attempt to open it */
86 if (realpath(file->path, rpath) == 0 ||
87 (fd = open(rpath, O_RDONLY)) < 0) {
88 warn("%s", file->path);
89 file->status = DPV_STATUS_FAILED;
93 /* Assume stdin, but if that's a TTY instead use the
94 * highest numbered file descriptor (obtained by
95 * generating new fd and then decrementing).
97 * NB: /dev/stdin should always be open(2)'able
101 fd = open("/dev/stdin", O_RDONLY);
105 /* This answer might be wrong, if dpv(3) has (by
106 * request) opened an output file or pipe. If we
107 * told dpv(3) to open a file, subtract one from
108 * previous answer. If instead we told dpv(3) to
109 * prepare a pipe output, subtract two.
111 switch(output_type) {
112 case DPV_OUTPUT_FILE:
115 case DPV_OUTPUT_SHELL:
122 /* Allocate buffer if necessary */
124 /* Use output block size as buffer size if available */
126 if (fstat(out, &sb) != 0) {
128 file->status = DPV_STATUS_FAILED;
131 if (S_ISREG(sb.st_mode)) {
132 if (sysconf(_SC_PHYS_PAGES) >
134 bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
136 bsize = BUFSIZE_SMALL;
138 bsize = MAX(sb.st_blksize,
139 (blksize_t)sysconf(_SC_PAGESIZE));
141 bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
143 /* Attempt to allocate */
144 if ((buf = malloc(bsize+1)) == NULL) {
146 err(EXIT_FAILURE, "Out of memory?!");
154 operate_on_bytes(struct dpv_file_node *file, int out)
159 if (operate_common(file, out) < 0)
162 /* [Re-]Fill the buffer */
163 if ((r = read(fd, buf, bsize)) <= 0) {
164 if (fd != STDIN_FILENO)
167 file->status = DPV_STATUS_DONE;
171 /* [Re-]Dump the buffer */
173 if ((w = write(out, buf, r)) < 0) {
175 err(EXIT_FAILURE, "output");
180 dpv_overall_read += r;
183 /* Calculate percentage of completion (if possible) */
184 if (file->length >= 0) {
185 progress = (file->read * 100 / (file->length > 0 ?
188 /* If no_overrun, do not return 100% until read >= length */
189 if (no_overrun && progress == 100 && file->read < file->length)
198 operate_on_lines(struct dpv_file_node *file, int out)
204 if (operate_common(file, out) < 0)
207 /* [Re-]Fill the buffer */
208 if ((r = read(fd, buf, bsize)) <= 0) {
209 if (fd != STDIN_FILENO)
212 file->status = DPV_STATUS_DONE;
217 /* [Re-]Dump the buffer */
219 if ((w = write(out, buf, r)) < 0) {
221 err(EXIT_FAILURE, "output");
226 /* Process the buffer for number of lines */
227 for (p = buf; p != NULL && *p != '\0';)
228 if ((p = strchr(p, '\n')) != NULL)
229 dpv_overall_read++, p++, file->read++;
231 /* Calculate percentage of completion (if possible) */
232 if (file->length >= 0) {
233 progress = (file->read * 100 / file->length);
235 /* If no_overrun, do not return 100% until read >= length */
236 if (no_overrun && progress == 100 && file->read < file->length)
245 * Takes a list of names that are to correspond to input streams coming from
246 * stdin or fifos and produces necessary config to drive dpv(3) `--gauge'
247 * widget. If the `-d' flag is used, output is instead send to terminal
248 * standard output (and the output can then be saved to a file, piped into
249 * custom [X]dialog(1) invocation, or whatever.
252 main(int argc, char *argv[])
257 size_t config_size = sizeof(struct dpv_config);
258 size_t file_node_size = sizeof(struct dpv_file_node);
259 struct dpv_config *config;
260 struct dpv_file_node *curfile;
261 struct sigaction act;
263 pgm = argv[0]; /* store a copy of invocation name */
265 /* Allocate config structure */
266 if ((config = malloc(config_size)) == NULL)
267 errx(EXIT_FAILURE, "Out of memory?!");
268 memset((void *)(config), '\0', config_size);
271 * Process command-line options
273 while ((ch = getopt(argc, argv,
274 "a:b:dDhi:I:klL:mn:No:p:P:t:TU:wx:X")) != -1) {
276 case 'a': /* additional message text to append */
277 if (config->aprompt == NULL) {
278 config->aprompt = malloc(DPV_APROMPT_MAX);
279 if (config->aprompt == NULL)
280 errx(EXIT_FAILURE, "Out of memory?!");
282 snprintf(config->aprompt, DPV_APROMPT_MAX, "%s",
285 case 'b': /* [X]dialog(1) backtitle */
286 if (config->backtitle != NULL)
287 free((char *)config->backtitle);
288 config->backtitle = malloc(strlen(optarg) + 1);
289 if (config->backtitle == NULL)
290 errx(EXIT_FAILURE, "Out of memory?!");
291 *(config->backtitle) = '\0';
292 strcat(config->backtitle, optarg);
294 case 'd': /* debugging */
296 config->debug = debug;
298 case 'D': /* use dialog(1) instead of libdialog */
299 config->display_type = DPV_DISPLAY_DIALOG;
301 case 'h': /* help/usage */
303 break; /* NOTREACHED */
304 case 'i': /* status line format string for single-file */
305 config->status_solo = optarg;
307 case 'I': /* status line format string for many-files */
308 config->status_many = optarg;
310 case 'k': /* keep tite */
311 config->keep_tite = TRUE;
313 case 'l': /* Line mode */
316 case 'L': /* custom label size */
318 (int)strtol(optarg, (char **)NULL, 10);
319 if (config->label_size == 0 && errno == EINVAL)
321 "`-L' argument must be numeric");
322 else if (config->label_size < -1)
323 config->label_size = -1;
325 case 'm': /* enable multiple file arguments */
328 case 'o': /* `-o path' for sending data-read to file */
329 output_type = DPV_OUTPUT_FILE;
330 config->output_type = DPV_OUTPUT_FILE;
331 config->output = optarg;
333 case 'n': /* custom number of files per `page' */
334 config->display_limit =
335 (int)strtol(optarg, (char **)NULL, 10);
336 if (config->display_limit == 0 && errno == EINVAL)
338 "`-n' argument must be numeric");
339 else if (config->display_limit < 0)
340 config->display_limit = -1;
342 case 'N': /* No overrun (truncate reads of known-length) */
344 config->options |= DPV_NO_OVERRUN;
346 case 'p': /* additional message text to use as prefix */
347 if (config->pprompt == NULL) {
348 config->pprompt = malloc(DPV_PPROMPT_MAX + 2);
349 if (config->pprompt == NULL)
350 errx(EXIT_FAILURE, "Out of memory?!");
351 /* +2 is for implicit "\n" appended later */
353 snprintf(config->pprompt, DPV_PPROMPT_MAX, "%s",
356 case 'P': /* custom size for mini-progressbar */
358 (int)strtol(optarg, (char **)NULL, 10);
359 if (config->pbar_size == 0 && errno == EINVAL)
361 "`-P' argument must be numeric");
362 else if (config->pbar_size < -1)
363 config->pbar_size = -1;
365 case 't': /* [X]dialog(1) title */
366 if (config->title != NULL)
368 config->title = malloc(strlen(optarg) + 1);
369 if (config->title == NULL)
370 errx(EXIT_FAILURE, "Out of memory?!");
371 *(config->title) = '\0';
372 strcat(config->title, optarg);
374 case 'T': /* test mode (don't read data, fake it) */
375 config->options |= DPV_TEST_MODE;
377 case 'U': /* updates per second */
378 config->status_updates_per_second =
379 (int)strtol(optarg, (char **)NULL, 10);
380 if (config->status_updates_per_second == 0 &&
383 "`-U' argument must be numeric");
385 case 'w': /* `-p' and `-a' widths bump [X]dialog(1) width */
386 config->options |= DPV_WIDE_MODE;
388 case 'x': /* `-x cmd' for sending data-read to sh(1) code */
389 output_type = DPV_OUTPUT_SHELL;
390 config->output_type = DPV_OUTPUT_SHELL;
391 config->output = optarg;
393 case 'X': /* X11 support through x11/xdialog */
394 config->display_type = DPV_DISPLAY_XDIALOG;
396 case '?': /* unknown argument (based on optstring) */
398 default: /* unhandled argument (based on switch) */
406 /* Process remaining arguments as list of names to display */
407 for (curfile = file_list; n < argc; n++) {
410 /* Allocate a new struct for the file argument */
411 if (curfile == NULL) {
412 if ((curfile = malloc(file_node_size)) == NULL)
413 errx(EXIT_FAILURE, "Out of memory?!");
414 memset((void *)(curfile), '\0', file_node_size);
417 if ((curfile->next = malloc(file_node_size)) == NULL)
418 errx(EXIT_FAILURE, "Out of memory?!");
419 memset((void *)(curfile->next), '\0', file_node_size);
420 curfile = curfile->next;
422 curfile->name = argv[n];
424 /* Read possible `lines:' prefix from label syntax */
425 if (sscanf(curfile->name, "%lli:%c", &(curfile->length),
427 curfile->name = strchr(curfile->name, ':') + 1;
429 curfile->length = -1;
431 /* Read path argument if enabled */
434 errx(EXIT_FAILURE, "Missing path argument "
435 "for label number %i", nfiles);
436 curfile->path = argv[n];
441 /* Display usage and exit if not given at least one name */
443 warnx("no labels provided");
449 * Set cleanup routine for Ctrl-C action
451 if (config->display_type == DPV_DISPLAY_LIBDIALOG) {
452 act.sa_handler = sig_int;
453 sigaction(SIGINT, &act, 0);
456 /* Set status formats and action */
458 config->status_solo = LINE_STATUS_SOLO;
459 config->status_many = LINE_STATUS_SOLO;
460 config->action = operate_on_lines;
462 config->status_solo = BYTE_STATUS_SOLO;
463 config->status_many = BYTE_STATUS_SOLO;
464 config->action = operate_on_bytes;
468 * Hand off to dpv(3)...
470 if (dpv(config, file_list) != 0 && debug)
471 warnx("dpv(3) returned error!?");
473 if (!config->keep_tite)
481 * Interrupt handler to indicate we received a Ctrl-C interrupt.
484 sig_int(int sig __unused)
486 dpv_interrupt = TRUE;
490 * Print short usage statement to stderr and exit with error status.
496 if (debug) /* No need for usage */
499 fprintf(stderr, "Usage: %s [options] bytes:label\n", pgm);
500 fprintf(stderr, " %s [options] -m bytes1:label1 path1 "
501 "[bytes2:label2 path2 ...]\n", pgm);
502 fprintf(stderr, "OPTIONS:\n");
503 #define OPTFMT "\t%-14s %s\n"
504 fprintf(stderr, OPTFMT, "-a text",
505 "Append text. Displayed below file progress indicators.");
506 fprintf(stderr, OPTFMT, "-b backtitle",
507 "String to be displayed on the backdrop, at top-left.");
508 fprintf(stderr, OPTFMT, "-d",
509 "Debug. Write to standard output instead of dialog.");
510 fprintf(stderr, OPTFMT, "-D",
511 "Use dialog(1) instead of dialog(3) [default].");
512 fprintf(stderr, OPTFMT, "-h",
513 "Produce this output on standard error and exit.");
514 fprintf(stderr, OPTFMT, "-i format",
515 "Customize status line format. See fdpv(1) for details.");
516 fprintf(stderr, OPTFMT, "-I format",
517 "Customize status line format. See fdpv(1) for details.");
518 fprintf(stderr, OPTFMT, "-L size",
519 "Label size. Must be a number greater than 0, or -1.");
520 fprintf(stderr, OPTFMT, "-m",
521 "Enable processing of multiple file argiments.");
522 fprintf(stderr, OPTFMT, "-n num",
523 "Display at-most num files per screen. Default is -1.");
524 fprintf(stderr, OPTFMT, "-N",
525 "No overrun. Stop reading input at stated length, if any.");
526 fprintf(stderr, OPTFMT, "-o file",
527 "Output data to file. First %s replaced with label text.");
528 fprintf(stderr, OPTFMT, "-p text",
529 "Prefix text. Displayed above file progress indicators.");
530 fprintf(stderr, OPTFMT, "-P size",
531 "Mini-progressbar size. Must be a number greater than 3.");
532 fprintf(stderr, OPTFMT, "-t title",
533 "Title string to be displayed at top of dialog(1) box.");
534 fprintf(stderr, OPTFMT, "-T",
535 "Test mode. Don't actually read any data, but fake it.");
536 fprintf(stderr, OPTFMT, "-U num",
537 "Update status line num times per-second. Default is 2.");
538 fprintf(stderr, OPTFMT, "-w",
539 "Wide. Width of `-p' and `-a' text bump dialog(1) width.");
540 fprintf(stderr, OPTFMT, "-x cmd",
541 "Send data to executed cmd. First %s replaced with label.");
542 fprintf(stderr, OPTFMT, "-X",
543 "X11. Use Xdialog(1) instead of dialog(1).");