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