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