xref: /freebsd/lib/libdpv/dpv.c (revision a0ee8cc636cd5c2374ec44ca71226564ea0bca95)
1 /*-
2  * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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
24  * SUCH DAMAGE.
25  */
26 
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include <sys/stat.h>
31 #include <sys/time.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 
35 #include <ctype.h>
36 #include <dialog.h>
37 #include <err.h>
38 #include <limits.h>
39 #include <locale.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <string_m.h>
44 #include <unistd.h>
45 
46 #include "dialog_util.h"
47 #include "dialogrc.h"
48 #include "dprompt.h"
49 #include "dpv.h"
50 #include "dpv_private.h"
51 #include "status.h"
52 #include "util.h"
53 
54 /* Test Mechanics (Only used when dpv_config.options |= DPV_TEST_MODE) */
55 #define INCREMENT		1	/* Increment % per-pass test-mode */
56 #define XDIALOG_INCREMENT	15	/* different for slower Xdialog(1) */
57 static uint8_t increment = INCREMENT;
58 
59 /* Debugging */
60 uint8_t debug = FALSE;
61 
62 /* Data to process */
63 int dpv_interrupt = FALSE;
64 int dpv_abort = FALSE;
65 unsigned int dpv_nfiles = 0;
66 
67 /* Data processing */
68 long long dpv_overall_read = 0;
69 static char pathbuf[PATH_MAX];
70 
71 /* Extra display information */
72 uint8_t no_labels = FALSE;	/* dpv_config.options & DPV_NO_LABELS */
73 uint8_t wide = FALSE;		/* dpv_config.options & DPV_WIDE_MODE */
74 char *aprompt = NULL;		/* dpv_config.aprompt */
75 char *msg_done = NULL;		/* dpv_config.msg_done */
76 char *msg_fail = NULL;		/* dpv_config.msg_fail */
77 char *msg_pending = NULL;	/* dpv_config.msg_pending */
78 char *pprompt = NULL;		/* dpv_config.pprompt */
79 
80 /* Status-Line format for when using dialog(3) */
81 static const char *status_format_custom = NULL;
82 static char status_format_default[DPV_STATUS_FORMAT_MAX];
83 
84 /*
85  * Takes a pointer to a dpv_config structure containing layout details and
86  * pointer to initial element in a linked-list of dpv_file_node structures,
87  * each presenting a file to process. Executes the `action' function passed-in
88  * as a member to the `config' structure argument.
89  */
90 int
91 dpv(struct dpv_config *config, struct dpv_file_node *file_list)
92 {
93 	char c;
94 	uint8_t keep_going;
95 	uint8_t nls = FALSE; /* See dialog_prompt_nlstate() */
96 	uint8_t no_overrun = FALSE;
97 	uint8_t pprompt_nls = FALSE; /* See dialog_prompt_nlstate() */
98 	uint8_t shrink_label_size = FALSE;
99 	mode_t mask;
100 	uint16_t options;
101 	char *cp;
102 	char *fc;
103 	char *last;
104 	char *name;
105 	char *output;
106 	const char *status_fmt;
107 	const char *path_fmt;
108 	enum dpv_display display_type;
109 	enum dpv_output output_type;
110 	enum dpv_status status;
111 	int (*action)(struct dpv_file_node *file, int out);
112 	int backslash;
113 	int dialog_last_update = 0;
114 	int dialog_old_nthfile = 0;
115 	int dialog_old_seconds = -1;
116 	int dialog_out = STDOUT_FILENO;
117 	int dialog_update_usec = 0;
118 	int dialog_updates_per_second;
119 	int files_left;
120 	int max_cols;
121 	int nthfile = 0;
122 	int output_out;
123 	int overall = 0;
124 	int pct;
125 	int res;
126 	int seconds;
127 	int status_last_update = 0;
128 	int status_old_nthfile = 0;
129 	int status_old_seconds = -1;
130 	int status_update_usec = 0;
131 	int status_updates_per_second;
132 	pid_t output_pid;
133 	pid_t pid;
134 	size_t len;
135 	struct dpv_file_node *curfile;
136 	struct dpv_file_node *first_file;
137 	struct dpv_file_node *list_head;
138 	struct timeval now;
139 	struct timeval start;
140 	char init_prompt[PROMPT_MAX + 1] = "";
141 
142 	/* Initialize globals to default values */
143 	aprompt		= NULL;
144 	pprompt		= NULL;
145 	options		= 0;
146 	action		= NULL;
147 	backtitle	= NULL;
148 	debug		= FALSE;
149 	dialog_test	= FALSE;
150 	dialog_updates_per_second = DIALOG_UPDATES_PER_SEC;
151 	display_limit	= DISPLAY_LIMIT_DEFAULT;
152 	display_type	= DPV_DISPLAY_LIBDIALOG;
153 	label_size	= LABEL_SIZE_DEFAULT;
154 	msg_done	= NULL;
155 	msg_fail	= NULL;
156 	msg_pending	= NULL;
157 	no_labels	= FALSE;
158 	output		= NULL;
159 	output_type	= DPV_OUTPUT_NONE;
160 	pbar_size	= PBAR_SIZE_DEFAULT;
161 	status_format_custom = NULL;
162 	status_updates_per_second = STATUS_UPDATES_PER_SEC;
163 	title		= NULL;
164 	wide		= FALSE;
165 
166 	/* Process config options (overriding defaults) */
167 	if (config != NULL) {
168 		if (config->aprompt != NULL) {
169 			if (aprompt == NULL) {
170 				aprompt = malloc(DPV_APROMPT_MAX);
171 				if (aprompt == NULL)
172 					return (-1);
173 			}
174 			snprintf(aprompt, DPV_APROMPT_MAX, "%s",
175 			    config->aprompt);
176 		}
177 		if (config->pprompt != NULL) {
178 			if (pprompt == NULL) {
179 				pprompt = malloc(DPV_PPROMPT_MAX + 2);
180 				/* +2 is for implicit "\n" appended later */
181 				if (pprompt == NULL)
182 					return (-1);
183 			}
184 			snprintf(pprompt, DPV_APROMPT_MAX, "%s",
185 			    config->pprompt);
186 		}
187 
188 		options		= config->options;
189 		action		= config->action;
190 		backtitle	= config->backtitle;
191 		debug		= config->debug;
192 		dialog_test	= ((options & DPV_TEST_MODE) != 0);
193 		dialog_updates_per_second = config->dialog_updates_per_second;
194 		display_limit	= config->display_limit;
195 		display_type	= config->display_type;
196 		label_size	= config->label_size;
197 		msg_done	= (char *)config->msg_done;
198 		msg_fail	= (char *)config->msg_fail;
199 		msg_pending	= (char *)config->msg_pending;
200 		no_labels	= ((options & DPV_NO_LABELS) != 0);
201 		no_overrun	= ((options & DPV_NO_OVERRUN) != 0);
202 		output          = config->output;
203 		output_type	= config->output_type;
204 		pbar_size	= config->pbar_size;
205 		status_updates_per_second = config->status_updates_per_second;
206 		title		= config->title;
207 		wide		= ((options & DPV_WIDE_MODE) != 0);
208 
209 		/* Enforce some minimums (pedantic) */
210 		if (display_limit < -1)
211 			display_limit = -1;
212 		if (label_size < -1)
213 			label_size = -1;
214 		if (pbar_size < -1)
215 			pbar_size = -1;
216 
217 		/* For the mini-pbar, -1 means hide, zero is invalid unless
218 		 * only one file is given */
219 		if (pbar_size == 0) {
220 			if (file_list == NULL || file_list->next == NULL)
221 				pbar_size = -1;
222 			else
223 				pbar_size = PBAR_SIZE_DEFAULT;
224 		}
225 
226 		/* For the label, -1 means auto-size, zero is invalid unless
227 		 * specifically requested through the use of options flag */
228 		if (label_size == 0 && no_labels == FALSE)
229 			label_size = LABEL_SIZE_DEFAULT;
230 
231 		/* Status update should not be zero */
232 		if (status_updates_per_second == 0)
233 			status_updates_per_second = STATUS_UPDATES_PER_SEC;
234 	} /* config != NULL */
235 
236 	/* Process the type of display we've been requested to produce */
237 	switch (display_type) {
238 	case DPV_DISPLAY_STDOUT:
239 		debug		= TRUE;
240 		use_color	= FALSE;
241 		use_dialog	= FALSE;
242 		use_libdialog	= FALSE;
243 		use_xdialog	= FALSE;
244 		break;
245 	case DPV_DISPLAY_DIALOG:
246 		use_color	= TRUE;
247 		use_dialog	= TRUE;
248 		use_libdialog	= FALSE;
249 		use_xdialog	= FALSE;
250 		break;
251 	case DPV_DISPLAY_XDIALOG:
252 		snprintf(dialog, PATH_MAX, XDIALOG);
253 		use_color	= FALSE;
254 		use_dialog	= FALSE;
255 		use_libdialog	= FALSE;
256 		use_xdialog	= TRUE;
257 		break;
258 	default:
259 		use_color	= TRUE;
260 		use_dialog	= FALSE;
261 		use_libdialog	= TRUE;
262 		use_xdialog	= FALSE;
263 		break;
264 	} /* display_type */
265 
266 	/* Enforce additional minimums that require knowing our display type */
267 	if (dialog_updates_per_second == 0)
268 		dialog_updates_per_second = use_xdialog ?
269 			XDIALOG_UPDATES_PER_SEC : DIALOG_UPDATES_PER_SEC;
270 
271 	/* Allow forceful override of use_color */
272 	if (config != NULL && (config->options & DPV_USE_COLOR) != 0)
273 		use_color = TRUE;
274 
275 	/* Count the number of files in provided list of dpv_file_node's */
276 	if (use_dialog && pprompt != NULL && *pprompt != '\0')
277 		pprompt_nls = dialog_prompt_nlstate(pprompt);
278 
279 	max_cols = dialog_maxcols();
280 	if (label_size == -1)
281 		shrink_label_size = TRUE;
282 
283 	/* Process file arguments */
284 	for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
285 		dpv_nfiles++;
286 
287 		/* dialog(3) only expands literal newlines */
288 		if (use_libdialog) strexpandnl(curfile->name);
289 
290 		/* Optionally calculate label size for file */
291 		if (shrink_label_size) {
292 			nls = FALSE;
293 			name = curfile->name;
294 			if (curfile == file_list)
295 				nls = pprompt_nls;
296 			last = (char *)dialog_prompt_lastline(name, nls);
297 			if (use_dialog) {
298 				c = *last;
299 				*last = '\0';
300 				nls = dialog_prompt_nlstate(name);
301 				*last = c;
302 			}
303 			len = dialog_prompt_longestline(last, nls);
304 			if ((int)len > (label_size - 3)) {
305 				if (label_size > 0)
306 					label_size += 3;
307 				label_size = len;
308 				/* Room for ellipsis (unless NULL) */
309 				if (label_size > 0)
310 					label_size += 3;
311 			}
312 
313 			if (max_cols > 0 && label_size > (max_cols - pbar_size
314 			    - 9))
315 				label_size = max_cols - pbar_size - 9;
316 		}
317 
318 		if (debug)
319 			warnx("label=[%s] path=[%s] size=%lli",
320 			    curfile->name, curfile->path, curfile->length);
321 	} /* file_list */
322 
323 	/* Optionally process the contents of DIALOGRC (~/.dialogrc) */
324 	if (use_dialog) {
325 		res = parse_dialogrc();
326 		if (debug && res == 0) {
327 			warnx("Successfully read `%s' config file", DIALOGRC);
328 			warnx("use_shadow = %i (Boolean)", use_shadow);
329 			warnx("use_colors = %i (Boolean)", use_colors);
330 			warnx("gauge_color=[%s] (FBH)", gauge_color);
331 		}
332 	} else if (use_libdialog) {
333 		init_dialog(stdin, stdout);
334 		use_shadow = dialog_state.use_shadow;
335 		use_colors = dialog_state.use_colors;
336 		gauge_color[0] = 48 + dlg_color_table[GAUGE_ATTR].fg;
337 		gauge_color[1] = 48 + dlg_color_table[GAUGE_ATTR].bg;
338 		gauge_color[2] = dlg_color_table[GAUGE_ATTR].hilite ?
339 		    'b' : 'B';
340 		gauge_color[3] = '\0';
341 		end_dialog();
342 		if (debug) {
343 			warnx("Finished initializing dialog(3) library");
344 			warnx("use_shadow = %i (Boolean)", use_shadow);
345 			warnx("use_colors = %i (Boolean)", use_colors);
346 			warnx("gauge_color=[%s] (FBH)", gauge_color);
347 		}
348 	}
349 
350 	/* Enable mini progress bar automatically for stdin streams if unable
351 	 * to calculate progress (missing `lines:' syntax). */
352 	if (dpv_nfiles <= 1 && file_list != NULL && file_list->length < 0 &&
353 	    !dialog_test)
354 		pbar_size = PBAR_SIZE_DEFAULT;
355 
356 	/* If $USE_COLOR is set and non-NULL enable color; otherwise disable */
357 	if ((cp = getenv(ENV_USE_COLOR)) != 0)
358 		use_color = *cp != '\0' ? 1 : 0;
359 
360 	/* Print error and return `-1' if not given at least one name */
361 	if (dpv_nfiles == 0) {
362 		warnx("%s: no labels provided", __func__);
363 		return (-1);
364 	} else if (debug)
365 		warnx("%s: %u label%s provided", __func__, dpv_nfiles,
366 		    dpv_nfiles == 1 ? "" : "s");
367 
368 	/* If only one file and pbar size is zero, default to `-1' */
369 	if (dpv_nfiles <= 1 && pbar_size == 0)
370 		pbar_size = -1;
371 
372 	/* Print some debugging information */
373 	if (debug) {
374 		warnx("%s: %s(%i) max rows x cols = %i x %i",
375 		    __func__, use_xdialog ? XDIALOG : DIALOG,
376 		    use_libdialog ? 3 : 1, dialog_maxrows(),
377 		    dialog_maxcols());
378 	}
379 
380 	/* Xdialog(1) updates a lot slower than dialog(1) */
381 	if (dialog_test && use_xdialog)
382 		increment = XDIALOG_INCREMENT;
383 
384 	/* Always add implicit newline to pprompt (when specified) */
385 	if (pprompt != NULL && *pprompt != '\0') {
386 		len = strlen(pprompt);
387 		/*
388 		 * NOTE: pprompt = malloc(PPROMPT_MAX + 2)
389 		 * NOTE: (see getopt(2) section above for pprompt allocation)
390 		 */
391 		pprompt[len++] = '\\';
392 		pprompt[len++] = 'n';
393 		pprompt[len++] = '\0';
394 	}
395 
396 	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
397 	if (use_xdialog && pprompt != NULL) {
398 		/* Replace `\n' with `\n\\n\n' in pprompt */
399 		len = strlen(pprompt);
400 		len += strcount(pprompt, "\\n") * 2;
401 		if (len > DPV_PPROMPT_MAX)
402 			errx(EXIT_FAILURE, "%s: Oops, pprompt buffer overflow "
403 			    "(%zu > %i)", __func__, len, DPV_PPROMPT_MAX);
404 		if (replaceall(pprompt, "\\n", "\n\\n\n") < 0)
405 			err(EXIT_FAILURE, "%s: replaceall()", __func__);
406 	}
407 	/* libdialog requires literal newlines */
408 	else if (use_libdialog && pprompt != NULL)
409 		strexpandnl(pprompt);
410 
411 	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
412 	if (use_xdialog && aprompt != NULL) {
413 		/* Replace `\n' with `\n\\n\n' in aprompt */
414 		len = strlen(aprompt);
415 		len += strcount(aprompt, "\\n") * 2;
416 		if (len > DPV_APROMPT_MAX)
417 			errx(EXIT_FAILURE, "%s: Oops, aprompt buffer overflow "
418 			    " (%zu > %i)", __func__, len, DPV_APROMPT_MAX);
419 		if (replaceall(aprompt, "\\n", "\n\\n\n") < 0)
420 			err(EXIT_FAILURE, "%s: replaceall()", __func__);
421 	}
422 	/* libdialog requires literal newlines */
423 	else if (use_libdialog && aprompt != NULL)
424 		strexpandnl(aprompt);
425 
426 	/*
427 	 * Warn user about an obscure dialog(1) bug (neither Xdialog(1) nor
428 	 * libdialog are affected) in the `--gauge' widget. If the first non-
429 	 * whitespace letter of "{new_prompt}" in "XXX\n{new_prompt}\nXXX\n"
430 	 * is a number, the number can sometimes be mistaken for a percentage
431 	 * to the overall progressbar. Other nasty side-effects such as the
432 	 * entire prompt not displaying or displaying improperly are caused by
433 	 * this bug too.
434 	 *
435 	 * NOTE: When we can use color, we have a work-around... prefix the
436 	 * output with `\Zn' (used to terminate ANSI and reset to normal).
437 	 */
438 	if (use_dialog && !use_color) {
439 		backslash = 0;
440 
441 		/* First, check pprompt (falls through if NULL) */
442 		fc = pprompt;
443 		while (fc != NULL && *fc != '\0') {
444 			if (*fc == '\n') /* leading literal newline OK */
445 				break;
446 			if (!isspace(*fc) && *fc != '\\' && backslash == 0)
447 				break;
448 			else if (backslash > 0 && *fc != 'n')
449 				break;
450 			else if (*fc == '\\') {
451 				backslash++;
452 				if (backslash > 2)
453 					break; /* we're safe */
454 			}
455 			fc++;
456 		}
457 		/* First non-whitespace character that dialog(1) will see */
458 		if (fc != NULL && *fc >= '0' && *fc <= '9')
459 			warnx("%s: WARNING! text argument to `-p' begins with "
460 			    "a number (not recommended)", __func__);
461 		else if (fc > pprompt)
462 			warnx("%s: WARNING! text argument to `-p' begins with "
463 			    "whitespace (not recommended)", __func__);
464 
465 		/*
466 		 * If no pprompt or pprompt is all whitespace, check the first
467 		 * file name provided to make sure it is alright too.
468 		 */
469 		if ((pprompt == NULL || *fc == '\0') && file_list != NULL) {
470 			first_file = file_list;
471 			fc = first_file->name;
472 			while (fc != NULL && *fc != '\0' && isspace(*fc))
473 				fc++;
474 			/* First non-whitespace char that dialog(1) will see */
475 			if (fc != NULL && *fc >= '0' && *fc <= '9')
476 				warnx("%s: WARNING! File name `%s' begins "
477 				    "with a number (use `-p text' for safety)",
478 				    __func__, first_file->name);
479 		}
480 	}
481 
482 	dprompt_init(file_list);
483 		/* Reads: label_size pbar_size pprompt aprompt dpv_nfiles */
484 		/* Inits: dheight and dwidth */
485 
486 	/* Default localeconv(3) settings for dialog(3) status */
487 	setlocale(LC_NUMERIC,
488 		getenv("LC_ALL") == NULL && getenv("LC_NUMERIC") == NULL ?
489 		LC_NUMERIC_DEFAULT : "");
490 
491 	if (!debug) {
492 		/* Internally create the initial `--gauge' prompt text */
493 		dprompt_recreate(file_list, (struct dpv_file_node *)NULL, 0);
494 
495 		/* Spawn [X]dialog(1) `--gauge', returning pipe descriptor */
496 		if (use_libdialog) {
497 			status_printf("");
498 			dprompt_libprint(pprompt, aprompt, 0);
499 		} else {
500 			dprompt_sprint(init_prompt, pprompt, aprompt);
501 			dialog_out = dialog_spawn_gauge(init_prompt, &pid);
502 			dprompt_dprint(dialog_out, pprompt, aprompt, 0);
503 		}
504 	} /* !debug */
505 
506 	/* Seed the random(3) generator */
507 	if (dialog_test)
508 		srandom(0xf1eeface);
509 
510 	/* Set default/custom status line format */
511 	if (dpv_nfiles > 1) {
512 		snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
513 		    DPV_STATUS_MANY);
514 		status_format_custom = config->status_many;
515 	} else {
516 		snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
517 		    DPV_STATUS_SOLO);
518 		status_format_custom = config->status_solo;
519 	}
520 
521 	/* Add test mode identifier to default status line if enabled */
522 	if (dialog_test && (strlen(status_format_default) + 12) <
523 	    DPV_STATUS_FORMAT_MAX)
524 		strcat(status_format_default, " [TEST MODE]");
525 
526 	/* Verify custom status format */
527 	status_fmt = fmtcheck(status_format_custom, status_format_default);
528 	if (status_format_custom != NULL &&
529 	    status_fmt == status_format_default) {
530 		warnx("WARNING! Invalid status_format configuration `%s'",
531 		      status_format_custom);
532 		warnx("Default status_format `%s'", status_format_default);
533 	}
534 
535 	/* Record when we started (used to prevent updating too quickly) */
536 	(void)gettimeofday(&start, (struct timezone *)NULL);
537 
538 	/* Calculate number of microseconds in-between sub-second updates */
539 	if (status_updates_per_second != 0)
540 		status_update_usec = 1000000 / status_updates_per_second;
541 	if (dialog_updates_per_second != 0)
542 		dialog_update_usec = 1000000 / dialog_updates_per_second;
543 
544 	/*
545 	 * Process the file list [serially] (one for each argument passed)
546 	 */
547 	files_left = dpv_nfiles;
548 	list_head = file_list;
549 	for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
550 		keep_going = TRUE;
551 		output_out = -1;
552 		pct = 0;
553 		nthfile++;
554 		files_left--;
555 
556 		if (dpv_interrupt)
557 			break;
558 		if (dialog_test)
559 			pct = 0 - increment;
560 
561 		/* Attempt to spawn output program for this file */
562 		if (!dialog_test && output != NULL) {
563 			mask = umask(0022);
564 			(void)umask(mask);
565 
566 			switch (output_type) {
567 			case DPV_OUTPUT_SHELL:
568 				output_out = shell_spawn_pipecmd(output,
569 				    curfile->name, &output_pid);
570 				break;
571 			case DPV_OUTPUT_FILE:
572 				path_fmt = fmtcheck(output, "%s");
573 				if (path_fmt == output)
574 					len = snprintf(pathbuf,
575 					    PATH_MAX, output, curfile->name);
576 				else
577 					len = snprintf(pathbuf,
578 					    PATH_MAX, "%s", output);
579 				if (len >= PATH_MAX) {
580 					warnx("%s:%d:%s: pathbuf[%u] too small"
581 					    "to hold output argument",
582 					    __FILE__, __LINE__, __func__,
583 					    PATH_MAX);
584 					return (-1);
585 				}
586 				if ((output_out = open(pathbuf,
587 				    O_CREAT|O_WRONLY, DEFFILEMODE & ~mask))
588 				    < 0) {
589 					warn("%s", pathbuf);
590 					return (-1);
591 				}
592 				break;
593 			default:
594 				break;
595 			}
596 		}
597 
598 		while (!dpv_interrupt && keep_going) {
599 			if (dialog_test) {
600 				usleep(50000);
601 				pct += increment;
602 				dpv_overall_read +=
603 				    (int)(random() / 512 / dpv_nfiles);
604 				    /* 512 limits fake readout to Megabytes */
605 			} else if (action != NULL)
606 				pct = action(curfile, output_out);
607 
608 			if (no_overrun || dialog_test)
609 				keep_going = (pct < 100);
610 			else {
611 				status = curfile->status;
612 				keep_going = (status == DPV_STATUS_RUNNING);
613 			}
614 
615 			/* Get current time and calculate seconds elapsed */
616 			gettimeofday(&now, (struct timezone *)NULL);
617 			now.tv_sec = now.tv_sec - start.tv_sec;
618 			now.tv_usec = now.tv_usec - start.tv_usec;
619 			if (now.tv_usec < 0)
620 				now.tv_sec--, now.tv_usec += 1000000;
621 			seconds = now.tv_sec + (now.tv_usec / 1000000.0);
622 
623 			/* Update dialog (be it dialog(3), dialog(1), etc.) */
624 			if ((dialog_updates_per_second != 0 &&
625 			   (
626 			    seconds != dialog_old_seconds ||
627 			    now.tv_usec - dialog_last_update >=
628 			        dialog_update_usec ||
629 			    nthfile != dialog_old_nthfile
630 			   )) || pct == 100
631 			) {
632 				/* Calculate overall progress (rounding up) */
633 				overall = (100 * nthfile - 100 + pct) /
634 				    dpv_nfiles;
635 				if (((100 * nthfile - 100 + pct) * 10 /
636 				    dpv_nfiles % 100) > 50)
637 					overall++;
638 
639 				dprompt_recreate(list_head, curfile, pct);
640 
641 				if (use_libdialog && !debug) {
642 					/* Update dialog(3) widget */
643 					dprompt_libprint(pprompt, aprompt,
644 					    overall);
645 				} else {
646 					/* stdout, dialog(1), or Xdialog(1) */
647 					dprompt_dprint(dialog_out, pprompt,
648 					    aprompt, overall);
649 					fsync(dialog_out);
650 				}
651 				dialog_old_seconds = seconds;
652 				dialog_old_nthfile = nthfile;
653 				dialog_last_update = now.tv_usec;
654 			}
655 
656 			/* Update the status line */
657 			if ((use_libdialog && !debug) &&
658 			    status_updates_per_second != 0 &&
659 			   (
660 			    keep_going != TRUE ||
661 			    seconds != status_old_seconds ||
662 			    now.tv_usec - status_last_update >=
663 			        status_update_usec ||
664 			    nthfile != status_old_nthfile
665 			   )
666 			) {
667 				status_printf(status_fmt, dpv_overall_read,
668 				    (dpv_overall_read / (seconds == 0 ? 1 :
669 					seconds) * 1.0),
670 				    1, /* XXX until we add parallelism XXX */
671 				    files_left);
672 				status_old_seconds = seconds;
673 				status_old_nthfile = nthfile;
674 				status_last_update = now.tv_usec;
675 			}
676 		}
677 
678 		if (!dialog_test && output_out >= 0) {
679 			close(output_out);
680 			waitpid(output_pid, (int *)NULL, 0);
681 		}
682 
683 		if (dpv_abort)
684 			break;
685 
686 		/* Advance head of list when we hit the max display lines */
687 		if (display_limit > 0 && nthfile % display_limit == 0)
688 			list_head = curfile->next;
689 	}
690 
691 	if (!debug) {
692 		if (use_libdialog)
693 			end_dialog();
694 		else {
695 			close(dialog_out);
696 			waitpid(pid, (int *)NULL, 0);
697 		}
698 		if (!dpv_interrupt)
699 			printf("\n");
700 	} else
701 		warnx("%s: %lli overall read", __func__, dpv_overall_read);
702 
703 	if (dpv_interrupt || dpv_abort)
704 		return (-1);
705 	else
706 		return (0);
707 }
708 
709 /*
710  * Free allocated items initialized by dpv()
711  */
712 void
713 dpv_free(void)
714 {
715 	dialogrc_free();
716 	dprompt_free();
717 	dialog_maxsize_free();
718 	if (aprompt != NULL) {
719 		free(aprompt);
720 		aprompt = NULL;
721 	}
722 	if (pprompt != NULL) {
723 		free(pprompt);
724 		pprompt = NULL;
725 	}
726 	status_free();
727 }
728