xref: /freebsd/lib/libdpv/dpv.c (revision 80f7300d7b70c869f82fe9df0b35807703914f5f)
1041394f3SDevin Teske /*-
2041394f3SDevin Teske  * Copyright (c) 2013-2014 Devin Teske <dteske@FreeBSD.org>
3041394f3SDevin Teske  * All rights reserved.
4041394f3SDevin Teske  *
5041394f3SDevin Teske  * Redistribution and use in source and binary forms, with or without
6041394f3SDevin Teske  * modification, are permitted provided that the following conditions
7041394f3SDevin Teske  * are met:
8041394f3SDevin Teske  * 1. Redistributions of source code must retain the above copyright
9041394f3SDevin Teske  *    notice, this list of conditions and the following disclaimer.
10041394f3SDevin Teske  * 2. Redistributions in binary form must reproduce the above copyright
11041394f3SDevin Teske  *    notice, this list of conditions and the following disclaimer in the
12041394f3SDevin Teske  *    documentation and/or other materials provided with the distribution.
13041394f3SDevin Teske  *
14041394f3SDevin Teske  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15041394f3SDevin Teske  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16041394f3SDevin Teske  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17041394f3SDevin Teske  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18041394f3SDevin Teske  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19041394f3SDevin Teske  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20041394f3SDevin Teske  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21041394f3SDevin Teske  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22041394f3SDevin Teske  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23041394f3SDevin Teske  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24041394f3SDevin Teske  * SUCH DAMAGE.
25041394f3SDevin Teske  */
26041394f3SDevin Teske 
27041394f3SDevin Teske #include <sys/cdefs.h>
28041394f3SDevin Teske __FBSDID("$FreeBSD$");
29041394f3SDevin Teske 
30041394f3SDevin Teske #include <sys/stat.h>
31041394f3SDevin Teske #include <sys/time.h>
32041394f3SDevin Teske #include <sys/types.h>
33041394f3SDevin Teske #include <sys/wait.h>
34041394f3SDevin Teske 
35041394f3SDevin Teske #include <ctype.h>
36041394f3SDevin Teske #include <dialog.h>
37041394f3SDevin Teske #include <err.h>
38041394f3SDevin Teske #include <limits.h>
399e35cfceSDevin Teske #include <locale.h>
40041394f3SDevin Teske #include <stdio.h>
41041394f3SDevin Teske #include <stdlib.h>
42041394f3SDevin Teske #include <string.h>
43041394f3SDevin Teske #include <string_m.h>
44041394f3SDevin Teske #include <unistd.h>
45041394f3SDevin Teske 
46041394f3SDevin Teske #include "dialog_util.h"
47041394f3SDevin Teske #include "dialogrc.h"
48041394f3SDevin Teske #include "dprompt.h"
49041394f3SDevin Teske #include "dpv.h"
50041394f3SDevin Teske #include "dpv_private.h"
51041394f3SDevin Teske #include "status.h"
52041394f3SDevin Teske #include "util.h"
53041394f3SDevin Teske 
54041394f3SDevin Teske /* Test Mechanics (Only used when dpv_config.options |= DPV_TEST_MODE) */
55041394f3SDevin Teske #define INCREMENT		1	/* Increment % per-pass test-mode */
56041394f3SDevin Teske #define XDIALOG_INCREMENT	15	/* different for slower Xdialog(1) */
57041394f3SDevin Teske static uint8_t increment = INCREMENT;
58041394f3SDevin Teske 
59041394f3SDevin Teske /* Debugging */
60041394f3SDevin Teske uint8_t debug = FALSE;
61041394f3SDevin Teske 
62041394f3SDevin Teske /* Data to process */
63041394f3SDevin Teske int dpv_interrupt = FALSE;
64041394f3SDevin Teske int dpv_abort = FALSE;
65041394f3SDevin Teske unsigned int dpv_nfiles = 0;
66041394f3SDevin Teske 
67041394f3SDevin Teske /* Data processing */
68041394f3SDevin Teske long long dpv_overall_read = 0;
69041394f3SDevin Teske static char pathbuf[PATH_MAX];
70041394f3SDevin Teske 
71041394f3SDevin Teske /* Extra display information */
72*80f7300dSDevin Teske uint8_t keep_tite = FALSE;	/* dpv_config.keep_tite */
73041394f3SDevin Teske uint8_t no_labels = FALSE;	/* dpv_config.options & DPV_NO_LABELS */
74041394f3SDevin Teske uint8_t wide = FALSE;		/* dpv_config.options & DPV_WIDE_MODE */
75041394f3SDevin Teske char *aprompt = NULL;		/* dpv_config.aprompt */
76041394f3SDevin Teske char *msg_done = NULL;		/* dpv_config.msg_done */
77041394f3SDevin Teske char *msg_fail = NULL;		/* dpv_config.msg_fail */
78041394f3SDevin Teske char *msg_pending = NULL;	/* dpv_config.msg_pending */
79041394f3SDevin Teske char *pprompt = NULL;		/* dpv_config.pprompt */
80041394f3SDevin Teske 
81041394f3SDevin Teske /* Status-Line format for when using dialog(3) */
82492b8271SDevin Teske static const char *status_format_custom = NULL;
83492b8271SDevin Teske static char status_format_default[DPV_STATUS_FORMAT_MAX];
84041394f3SDevin Teske 
85041394f3SDevin Teske /*
86041394f3SDevin Teske  * Takes a pointer to a dpv_config structure containing layout details and
87041394f3SDevin Teske  * pointer to initial element in a linked-list of dpv_file_node structures,
88041394f3SDevin Teske  * each presenting a file to process. Executes the `action' function passed-in
89041394f3SDevin Teske  * as a member to the `config' structure argument.
90041394f3SDevin Teske  */
91041394f3SDevin Teske int
92041394f3SDevin Teske dpv(struct dpv_config *config, struct dpv_file_node *file_list)
93041394f3SDevin Teske {
94041394f3SDevin Teske 	char c;
95041394f3SDevin Teske 	uint8_t keep_going;
96041394f3SDevin Teske 	uint8_t nls = FALSE; /* See dialog_prompt_nlstate() */
97041394f3SDevin Teske 	uint8_t no_overrun = FALSE;
98041394f3SDevin Teske 	uint8_t pprompt_nls = FALSE; /* See dialog_prompt_nlstate() */
99041394f3SDevin Teske 	uint8_t shrink_label_size = FALSE;
100041394f3SDevin Teske 	mode_t mask;
101041394f3SDevin Teske 	uint16_t options;
102041394f3SDevin Teske 	char *cp;
103041394f3SDevin Teske 	char *fc;
104041394f3SDevin Teske 	char *last;
105041394f3SDevin Teske 	char *name;
106041394f3SDevin Teske 	char *output;
107041394f3SDevin Teske 	const char *status_fmt;
108041394f3SDevin Teske 	const char *path_fmt;
109041394f3SDevin Teske 	enum dpv_display display_type;
110041394f3SDevin Teske 	enum dpv_output output_type;
111041394f3SDevin Teske 	enum dpv_status status;
112041394f3SDevin Teske 	int (*action)(struct dpv_file_node *file, int out);
113041394f3SDevin Teske 	int backslash;
114041394f3SDevin Teske 	int dialog_last_update = 0;
115041394f3SDevin Teske 	int dialog_old_nthfile = 0;
116041394f3SDevin Teske 	int dialog_old_seconds = -1;
117041394f3SDevin Teske 	int dialog_out = STDOUT_FILENO;
118041394f3SDevin Teske 	int dialog_update_usec = 0;
119041394f3SDevin Teske 	int dialog_updates_per_second;
120041394f3SDevin Teske 	int files_left;
121041394f3SDevin Teske 	int max_cols;
122041394f3SDevin Teske 	int nthfile = 0;
123041394f3SDevin Teske 	int output_out;
124041394f3SDevin Teske 	int overall = 0;
125041394f3SDevin Teske 	int pct;
126041394f3SDevin Teske 	int res;
127041394f3SDevin Teske 	int seconds;
128041394f3SDevin Teske 	int status_last_update = 0;
129041394f3SDevin Teske 	int status_old_nthfile = 0;
130041394f3SDevin Teske 	int status_old_seconds = -1;
131041394f3SDevin Teske 	int status_update_usec = 0;
132041394f3SDevin Teske 	int status_updates_per_second;
133041394f3SDevin Teske 	pid_t output_pid;
134041394f3SDevin Teske 	pid_t pid;
135041394f3SDevin Teske 	size_t len;
136041394f3SDevin Teske 	struct dpv_file_node *curfile;
137041394f3SDevin Teske 	struct dpv_file_node *first_file;
138041394f3SDevin Teske 	struct dpv_file_node *list_head;
139041394f3SDevin Teske 	struct timeval now;
140041394f3SDevin Teske 	struct timeval start;
141041394f3SDevin Teske 	char init_prompt[PROMPT_MAX + 1] = "";
142041394f3SDevin Teske 
143041394f3SDevin Teske 	/* Initialize globals to default values */
144041394f3SDevin Teske 	aprompt		= NULL;
145041394f3SDevin Teske 	pprompt		= NULL;
146041394f3SDevin Teske 	options		= 0;
147041394f3SDevin Teske 	action		= NULL;
148041394f3SDevin Teske 	backtitle	= NULL;
149041394f3SDevin Teske 	debug		= FALSE;
150041394f3SDevin Teske 	dialog_test	= FALSE;
151041394f3SDevin Teske 	dialog_updates_per_second = DIALOG_UPDATES_PER_SEC;
152041394f3SDevin Teske 	display_limit	= DISPLAY_LIMIT_DEFAULT;
153041394f3SDevin Teske 	display_type	= DPV_DISPLAY_LIBDIALOG;
154*80f7300dSDevin Teske 	keep_tite	= FALSE;
155041394f3SDevin Teske 	label_size	= LABEL_SIZE_DEFAULT;
156041394f3SDevin Teske 	msg_done	= NULL;
157041394f3SDevin Teske 	msg_fail	= NULL;
158041394f3SDevin Teske 	msg_pending	= NULL;
159041394f3SDevin Teske 	no_labels	= FALSE;
160041394f3SDevin Teske 	output		= NULL;
161041394f3SDevin Teske 	output_type	= DPV_OUTPUT_NONE;
162041394f3SDevin Teske 	pbar_size	= PBAR_SIZE_DEFAULT;
163041394f3SDevin Teske 	status_format_custom = NULL;
164041394f3SDevin Teske 	status_updates_per_second = STATUS_UPDATES_PER_SEC;
165041394f3SDevin Teske 	title		= NULL;
166041394f3SDevin Teske 	wide		= FALSE;
167041394f3SDevin Teske 
168041394f3SDevin Teske 	/* Process config options (overriding defaults) */
169041394f3SDevin Teske 	if (config != NULL) {
170041394f3SDevin Teske 		if (config->aprompt != NULL) {
171041394f3SDevin Teske 			if (aprompt == NULL) {
172041394f3SDevin Teske 				aprompt = malloc(DPV_APROMPT_MAX);
173041394f3SDevin Teske 				if (aprompt == NULL)
174041394f3SDevin Teske 					return (-1);
175041394f3SDevin Teske 			}
176041394f3SDevin Teske 			snprintf(aprompt, DPV_APROMPT_MAX, "%s",
177041394f3SDevin Teske 			    config->aprompt);
178041394f3SDevin Teske 		}
179041394f3SDevin Teske 		if (config->pprompt != NULL) {
180041394f3SDevin Teske 			if (pprompt == NULL) {
181041394f3SDevin Teske 				pprompt = malloc(DPV_PPROMPT_MAX + 2);
182041394f3SDevin Teske 				/* +2 is for implicit "\n" appended later */
183041394f3SDevin Teske 				if (pprompt == NULL)
184041394f3SDevin Teske 					return (-1);
185041394f3SDevin Teske 			}
186041394f3SDevin Teske 			snprintf(pprompt, DPV_APROMPT_MAX, "%s",
187041394f3SDevin Teske 			    config->pprompt);
188041394f3SDevin Teske 		}
189041394f3SDevin Teske 
190041394f3SDevin Teske 		options		= config->options;
191041394f3SDevin Teske 		action		= config->action;
192041394f3SDevin Teske 		backtitle	= config->backtitle;
193041394f3SDevin Teske 		debug		= config->debug;
194041394f3SDevin Teske 		dialog_test	= ((options & DPV_TEST_MODE) != 0);
195041394f3SDevin Teske 		dialog_updates_per_second = config->dialog_updates_per_second;
196041394f3SDevin Teske 		display_limit	= config->display_limit;
197041394f3SDevin Teske 		display_type	= config->display_type;
198*80f7300dSDevin Teske 		keep_tite	= config->keep_tite;
199041394f3SDevin Teske 		label_size	= config->label_size;
200041394f3SDevin Teske 		msg_done	= (char *)config->msg_done;
201041394f3SDevin Teske 		msg_fail	= (char *)config->msg_fail;
202041394f3SDevin Teske 		msg_pending	= (char *)config->msg_pending;
203041394f3SDevin Teske 		no_labels	= ((options & DPV_NO_LABELS) != 0);
204041394f3SDevin Teske 		no_overrun	= ((options & DPV_NO_OVERRUN) != 0);
205041394f3SDevin Teske 		output          = config->output;
206041394f3SDevin Teske 		output_type	= config->output_type;
207041394f3SDevin Teske 		pbar_size	= config->pbar_size;
208041394f3SDevin Teske 		status_updates_per_second = config->status_updates_per_second;
209041394f3SDevin Teske 		title		= config->title;
210041394f3SDevin Teske 		wide		= ((options & DPV_WIDE_MODE) != 0);
211041394f3SDevin Teske 
212041394f3SDevin Teske 		/* Enforce some minimums (pedantic) */
213041394f3SDevin Teske 		if (display_limit < -1)
214041394f3SDevin Teske 			display_limit = -1;
215041394f3SDevin Teske 		if (label_size < -1)
216041394f3SDevin Teske 			label_size = -1;
217041394f3SDevin Teske 		if (pbar_size < -1)
218041394f3SDevin Teske 			pbar_size = -1;
219041394f3SDevin Teske 
220041394f3SDevin Teske 		/* For the mini-pbar, -1 means hide, zero is invalid unless
221041394f3SDevin Teske 		 * only one file is given */
222041394f3SDevin Teske 		if (pbar_size == 0) {
223041394f3SDevin Teske 			if (file_list == NULL || file_list->next == NULL)
224041394f3SDevin Teske 				pbar_size = -1;
225041394f3SDevin Teske 			else
226041394f3SDevin Teske 				pbar_size = PBAR_SIZE_DEFAULT;
227041394f3SDevin Teske 		}
228041394f3SDevin Teske 
229041394f3SDevin Teske 		/* For the label, -1 means auto-size, zero is invalid unless
230041394f3SDevin Teske 		 * specifically requested through the use of options flag */
231041394f3SDevin Teske 		if (label_size == 0 && no_labels == FALSE)
232041394f3SDevin Teske 			label_size = LABEL_SIZE_DEFAULT;
233041394f3SDevin Teske 
234041394f3SDevin Teske 		/* Status update should not be zero */
235041394f3SDevin Teske 		if (status_updates_per_second == 0)
236041394f3SDevin Teske 			status_updates_per_second = STATUS_UPDATES_PER_SEC;
237041394f3SDevin Teske 	} /* config != NULL */
238041394f3SDevin Teske 
239041394f3SDevin Teske 	/* Process the type of display we've been requested to produce */
240041394f3SDevin Teske 	switch (display_type) {
241041394f3SDevin Teske 	case DPV_DISPLAY_STDOUT:
242041394f3SDevin Teske 		debug		= TRUE;
243041394f3SDevin Teske 		use_color	= FALSE;
244041394f3SDevin Teske 		use_dialog	= FALSE;
245041394f3SDevin Teske 		use_libdialog	= FALSE;
246041394f3SDevin Teske 		use_xdialog	= FALSE;
247041394f3SDevin Teske 		break;
248041394f3SDevin Teske 	case DPV_DISPLAY_DIALOG:
249041394f3SDevin Teske 		use_color	= TRUE;
250041394f3SDevin Teske 		use_dialog	= TRUE;
251041394f3SDevin Teske 		use_libdialog	= FALSE;
252041394f3SDevin Teske 		use_xdialog	= FALSE;
253041394f3SDevin Teske 		break;
254041394f3SDevin Teske 	case DPV_DISPLAY_XDIALOG:
255041394f3SDevin Teske 		snprintf(dialog, PATH_MAX, XDIALOG);
256041394f3SDevin Teske 		use_color	= FALSE;
257041394f3SDevin Teske 		use_dialog	= FALSE;
258041394f3SDevin Teske 		use_libdialog	= FALSE;
259041394f3SDevin Teske 		use_xdialog	= TRUE;
260041394f3SDevin Teske 		break;
261041394f3SDevin Teske 	default:
262041394f3SDevin Teske 		use_color	= TRUE;
263041394f3SDevin Teske 		use_dialog	= FALSE;
264041394f3SDevin Teske 		use_libdialog	= TRUE;
265041394f3SDevin Teske 		use_xdialog	= FALSE;
266041394f3SDevin Teske 		break;
267041394f3SDevin Teske 	} /* display_type */
268041394f3SDevin Teske 
269041394f3SDevin Teske 	/* Enforce additional minimums that require knowing our display type */
270041394f3SDevin Teske 	if (dialog_updates_per_second == 0)
271041394f3SDevin Teske 		dialog_updates_per_second = use_xdialog ?
272041394f3SDevin Teske 			XDIALOG_UPDATES_PER_SEC : DIALOG_UPDATES_PER_SEC;
273041394f3SDevin Teske 
274041394f3SDevin Teske 	/* Allow forceful override of use_color */
275041394f3SDevin Teske 	if (config != NULL && (config->options & DPV_USE_COLOR) != 0)
276041394f3SDevin Teske 		use_color = TRUE;
277041394f3SDevin Teske 
278041394f3SDevin Teske 	/* Count the number of files in provided list of dpv_file_node's */
279041394f3SDevin Teske 	if (use_dialog && pprompt != NULL && *pprompt != '\0')
280041394f3SDevin Teske 		pprompt_nls = dialog_prompt_nlstate(pprompt);
281041394f3SDevin Teske 
282041394f3SDevin Teske 	max_cols = dialog_maxcols();
283041394f3SDevin Teske 	if (label_size == -1)
284041394f3SDevin Teske 		shrink_label_size = TRUE;
285041394f3SDevin Teske 
286041394f3SDevin Teske 	/* Process file arguments */
287041394f3SDevin Teske 	for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
288041394f3SDevin Teske 		dpv_nfiles++;
289041394f3SDevin Teske 
290041394f3SDevin Teske 		/* dialog(3) only expands literal newlines */
291041394f3SDevin Teske 		if (use_libdialog) strexpandnl(curfile->name);
292041394f3SDevin Teske 
293041394f3SDevin Teske 		/* Optionally calculate label size for file */
294041394f3SDevin Teske 		if (shrink_label_size) {
295041394f3SDevin Teske 			nls = FALSE;
296041394f3SDevin Teske 			name = curfile->name;
297041394f3SDevin Teske 			if (curfile == file_list)
298041394f3SDevin Teske 				nls = pprompt_nls;
299041394f3SDevin Teske 			last = (char *)dialog_prompt_lastline(name, nls);
300041394f3SDevin Teske 			if (use_dialog) {
301041394f3SDevin Teske 				c = *last;
302041394f3SDevin Teske 				*last = '\0';
303041394f3SDevin Teske 				nls = dialog_prompt_nlstate(name);
304041394f3SDevin Teske 				*last = c;
305041394f3SDevin Teske 			}
306041394f3SDevin Teske 			len = dialog_prompt_longestline(last, nls);
307041394f3SDevin Teske 			if ((int)len > (label_size - 3)) {
308041394f3SDevin Teske 				if (label_size > 0)
309041394f3SDevin Teske 					label_size += 3;
310041394f3SDevin Teske 				label_size = len;
311041394f3SDevin Teske 				/* Room for ellipsis (unless NULL) */
312041394f3SDevin Teske 				if (label_size > 0)
313041394f3SDevin Teske 					label_size += 3;
314041394f3SDevin Teske 			}
315041394f3SDevin Teske 
316041394f3SDevin Teske 			if (max_cols > 0 && label_size > (max_cols - pbar_size
317041394f3SDevin Teske 			    - 9))
318041394f3SDevin Teske 				label_size = max_cols - pbar_size - 9;
319041394f3SDevin Teske 		}
320041394f3SDevin Teske 
321041394f3SDevin Teske 		if (debug)
322041394f3SDevin Teske 			warnx("label=[%s] path=[%s] size=%lli",
323041394f3SDevin Teske 			    curfile->name, curfile->path, curfile->length);
324041394f3SDevin Teske 	} /* file_list */
325041394f3SDevin Teske 
326041394f3SDevin Teske 	/* Optionally process the contents of DIALOGRC (~/.dialogrc) */
327041394f3SDevin Teske 	if (use_dialog) {
328041394f3SDevin Teske 		res = parse_dialogrc();
329041394f3SDevin Teske 		if (debug && res == 0) {
330041394f3SDevin Teske 			warnx("Successfully read `%s' config file", DIALOGRC);
331041394f3SDevin Teske 			warnx("use_shadow = %i (Boolean)", use_shadow);
332041394f3SDevin Teske 			warnx("use_colors = %i (Boolean)", use_colors);
333041394f3SDevin Teske 			warnx("gauge_color=[%s] (FBH)", gauge_color);
334041394f3SDevin Teske 		}
335041394f3SDevin Teske 	} else if (use_libdialog) {
336041394f3SDevin Teske 		init_dialog(stdin, stdout);
337041394f3SDevin Teske 		use_shadow = dialog_state.use_shadow;
338041394f3SDevin Teske 		use_colors = dialog_state.use_colors;
339041394f3SDevin Teske 		gauge_color[0] = 48 + dlg_color_table[GAUGE_ATTR].fg;
340041394f3SDevin Teske 		gauge_color[1] = 48 + dlg_color_table[GAUGE_ATTR].bg;
341041394f3SDevin Teske 		gauge_color[2] = dlg_color_table[GAUGE_ATTR].hilite ?
342041394f3SDevin Teske 		    'b' : 'B';
343041394f3SDevin Teske 		gauge_color[3] = '\0';
344041394f3SDevin Teske 		end_dialog();
345041394f3SDevin Teske 		if (debug) {
346041394f3SDevin Teske 			warnx("Finished initializing dialog(3) library");
347041394f3SDevin Teske 			warnx("use_shadow = %i (Boolean)", use_shadow);
348041394f3SDevin Teske 			warnx("use_colors = %i (Boolean)", use_colors);
349041394f3SDevin Teske 			warnx("gauge_color=[%s] (FBH)", gauge_color);
350041394f3SDevin Teske 		}
351041394f3SDevin Teske 	}
352041394f3SDevin Teske 
353041394f3SDevin Teske 	/* Enable mini progress bar automatically for stdin streams if unable
354041394f3SDevin Teske 	 * to calculate progress (missing `lines:' syntax). */
355041394f3SDevin Teske 	if (dpv_nfiles <= 1 && file_list != NULL && file_list->length < 0 &&
356041394f3SDevin Teske 	    !dialog_test)
357041394f3SDevin Teske 		pbar_size = PBAR_SIZE_DEFAULT;
358041394f3SDevin Teske 
359041394f3SDevin Teske 	/* If $USE_COLOR is set and non-NULL enable color; otherwise disable */
360041394f3SDevin Teske 	if ((cp = getenv(ENV_USE_COLOR)) != 0)
361041394f3SDevin Teske 		use_color = *cp != '\0' ? 1 : 0;
362041394f3SDevin Teske 
363041394f3SDevin Teske 	/* Print error and return `-1' if not given at least one name */
364041394f3SDevin Teske 	if (dpv_nfiles == 0) {
365041394f3SDevin Teske 		warnx("%s: no labels provided", __func__);
366041394f3SDevin Teske 		return (-1);
367041394f3SDevin Teske 	} else if (debug)
368041394f3SDevin Teske 		warnx("%s: %u label%s provided", __func__, dpv_nfiles,
369041394f3SDevin Teske 		    dpv_nfiles == 1 ? "" : "s");
370041394f3SDevin Teske 
371041394f3SDevin Teske 	/* If only one file and pbar size is zero, default to `-1' */
372041394f3SDevin Teske 	if (dpv_nfiles <= 1 && pbar_size == 0)
373041394f3SDevin Teske 		pbar_size = -1;
374041394f3SDevin Teske 
375041394f3SDevin Teske 	/* Print some debugging information */
376041394f3SDevin Teske 	if (debug) {
377041394f3SDevin Teske 		warnx("%s: %s(%i) max rows x cols = %i x %i",
378041394f3SDevin Teske 		    __func__, use_xdialog ? XDIALOG : DIALOG,
379041394f3SDevin Teske 		    use_libdialog ? 3 : 1, dialog_maxrows(),
380041394f3SDevin Teske 		    dialog_maxcols());
381041394f3SDevin Teske 	}
382041394f3SDevin Teske 
383041394f3SDevin Teske 	/* Xdialog(1) updates a lot slower than dialog(1) */
384041394f3SDevin Teske 	if (dialog_test && use_xdialog)
385041394f3SDevin Teske 		increment = XDIALOG_INCREMENT;
386041394f3SDevin Teske 
387041394f3SDevin Teske 	/* Always add implicit newline to pprompt (when specified) */
388041394f3SDevin Teske 	if (pprompt != NULL && *pprompt != '\0') {
389041394f3SDevin Teske 		len = strlen(pprompt);
390041394f3SDevin Teske 		/*
391041394f3SDevin Teske 		 * NOTE: pprompt = malloc(PPROMPT_MAX + 2)
392041394f3SDevin Teske 		 * NOTE: (see getopt(2) section above for pprompt allocation)
393041394f3SDevin Teske 		 */
394041394f3SDevin Teske 		pprompt[len++] = '\\';
395041394f3SDevin Teske 		pprompt[len++] = 'n';
396041394f3SDevin Teske 		pprompt[len++] = '\0';
397041394f3SDevin Teske 	}
398041394f3SDevin Teske 
399041394f3SDevin Teske 	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
400041394f3SDevin Teske 	if (use_xdialog && pprompt != NULL) {
401041394f3SDevin Teske 		/* Replace `\n' with `\n\\n\n' in pprompt */
402041394f3SDevin Teske 		len = strlen(pprompt);
403041394f3SDevin Teske 		len += strcount(pprompt, "\\n") * 2;
404041394f3SDevin Teske 		if (len > DPV_PPROMPT_MAX)
405041394f3SDevin Teske 			errx(EXIT_FAILURE, "%s: Oops, pprompt buffer overflow "
406041394f3SDevin Teske 			    "(%zu > %i)", __func__, len, DPV_PPROMPT_MAX);
407041394f3SDevin Teske 		if (replaceall(pprompt, "\\n", "\n\\n\n") < 0)
408041394f3SDevin Teske 			err(EXIT_FAILURE, "%s: replaceall()", __func__);
409041394f3SDevin Teske 	}
410041394f3SDevin Teske 	/* libdialog requires literal newlines */
411041394f3SDevin Teske 	else if (use_libdialog && pprompt != NULL)
412041394f3SDevin Teske 		strexpandnl(pprompt);
413041394f3SDevin Teske 
414041394f3SDevin Teske 	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
415041394f3SDevin Teske 	if (use_xdialog && aprompt != NULL) {
416041394f3SDevin Teske 		/* Replace `\n' with `\n\\n\n' in aprompt */
417041394f3SDevin Teske 		len = strlen(aprompt);
418041394f3SDevin Teske 		len += strcount(aprompt, "\\n") * 2;
419041394f3SDevin Teske 		if (len > DPV_APROMPT_MAX)
420041394f3SDevin Teske 			errx(EXIT_FAILURE, "%s: Oops, aprompt buffer overflow "
421041394f3SDevin Teske 			    " (%zu > %i)", __func__, len, DPV_APROMPT_MAX);
422041394f3SDevin Teske 		if (replaceall(aprompt, "\\n", "\n\\n\n") < 0)
423041394f3SDevin Teske 			err(EXIT_FAILURE, "%s: replaceall()", __func__);
424041394f3SDevin Teske 	}
425041394f3SDevin Teske 	/* libdialog requires literal newlines */
426041394f3SDevin Teske 	else if (use_libdialog && aprompt != NULL)
427041394f3SDevin Teske 		strexpandnl(aprompt);
428041394f3SDevin Teske 
429041394f3SDevin Teske 	/*
430041394f3SDevin Teske 	 * Warn user about an obscure dialog(1) bug (neither Xdialog(1) nor
431041394f3SDevin Teske 	 * libdialog are affected) in the `--gauge' widget. If the first non-
432041394f3SDevin Teske 	 * whitespace letter of "{new_prompt}" in "XXX\n{new_prompt}\nXXX\n"
433041394f3SDevin Teske 	 * is a number, the number can sometimes be mistaken for a percentage
434041394f3SDevin Teske 	 * to the overall progressbar. Other nasty side-effects such as the
435041394f3SDevin Teske 	 * entire prompt not displaying or displaying improperly are caused by
436041394f3SDevin Teske 	 * this bug too.
437041394f3SDevin Teske 	 *
438041394f3SDevin Teske 	 * NOTE: When we can use color, we have a work-around... prefix the
439041394f3SDevin Teske 	 * output with `\Zn' (used to terminate ANSI and reset to normal).
440041394f3SDevin Teske 	 */
441041394f3SDevin Teske 	if (use_dialog && !use_color) {
442041394f3SDevin Teske 		backslash = 0;
443041394f3SDevin Teske 
444041394f3SDevin Teske 		/* First, check pprompt (falls through if NULL) */
445041394f3SDevin Teske 		fc = pprompt;
446041394f3SDevin Teske 		while (fc != NULL && *fc != '\0') {
447041394f3SDevin Teske 			if (*fc == '\n') /* leading literal newline OK */
448041394f3SDevin Teske 				break;
449041394f3SDevin Teske 			if (!isspace(*fc) && *fc != '\\' && backslash == 0)
450041394f3SDevin Teske 				break;
451041394f3SDevin Teske 			else if (backslash > 0 && *fc != 'n')
452041394f3SDevin Teske 				break;
453041394f3SDevin Teske 			else if (*fc == '\\') {
454041394f3SDevin Teske 				backslash++;
455041394f3SDevin Teske 				if (backslash > 2)
456041394f3SDevin Teske 					break; /* we're safe */
457041394f3SDevin Teske 			}
458041394f3SDevin Teske 			fc++;
459041394f3SDevin Teske 		}
460041394f3SDevin Teske 		/* First non-whitespace character that dialog(1) will see */
461041394f3SDevin Teske 		if (fc != NULL && *fc >= '0' && *fc <= '9')
462041394f3SDevin Teske 			warnx("%s: WARNING! text argument to `-p' begins with "
463041394f3SDevin Teske 			    "a number (not recommended)", __func__);
464041394f3SDevin Teske 		else if (fc > pprompt)
465041394f3SDevin Teske 			warnx("%s: WARNING! text argument to `-p' begins with "
466041394f3SDevin Teske 			    "whitespace (not recommended)", __func__);
467041394f3SDevin Teske 
468041394f3SDevin Teske 		/*
469041394f3SDevin Teske 		 * If no pprompt or pprompt is all whitespace, check the first
470041394f3SDevin Teske 		 * file name provided to make sure it is alright too.
471041394f3SDevin Teske 		 */
472041394f3SDevin Teske 		if ((pprompt == NULL || *fc == '\0') && file_list != NULL) {
473041394f3SDevin Teske 			first_file = file_list;
474041394f3SDevin Teske 			fc = first_file->name;
475041394f3SDevin Teske 			while (fc != NULL && *fc != '\0' && isspace(*fc))
476041394f3SDevin Teske 				fc++;
477041394f3SDevin Teske 			/* First non-whitespace char that dialog(1) will see */
478041394f3SDevin Teske 			if (fc != NULL && *fc >= '0' && *fc <= '9')
479041394f3SDevin Teske 				warnx("%s: WARNING! File name `%s' begins "
480041394f3SDevin Teske 				    "with a number (use `-p text' for safety)",
481041394f3SDevin Teske 				    __func__, first_file->name);
482041394f3SDevin Teske 		}
483041394f3SDevin Teske 	}
484041394f3SDevin Teske 
485041394f3SDevin Teske 	dprompt_init(file_list);
486041394f3SDevin Teske 		/* Reads: label_size pbar_size pprompt aprompt dpv_nfiles */
487041394f3SDevin Teske 		/* Inits: dheight and dwidth */
488041394f3SDevin Teske 
4899e35cfceSDevin Teske 	/* Default localeconv(3) settings for dialog(3) status */
4909e35cfceSDevin Teske 	setlocale(LC_NUMERIC,
4919e35cfceSDevin Teske 		getenv("LC_ALL") == NULL && getenv("LC_NUMERIC") == NULL ?
4929e35cfceSDevin Teske 		LC_NUMERIC_DEFAULT : "");
4939e35cfceSDevin Teske 
494041394f3SDevin Teske 	if (!debug) {
495041394f3SDevin Teske 		/* Internally create the initial `--gauge' prompt text */
496041394f3SDevin Teske 		dprompt_recreate(file_list, (struct dpv_file_node *)NULL, 0);
497041394f3SDevin Teske 
498041394f3SDevin Teske 		/* Spawn [X]dialog(1) `--gauge', returning pipe descriptor */
499041394f3SDevin Teske 		if (use_libdialog) {
500041394f3SDevin Teske 			status_printf("");
501041394f3SDevin Teske 			dprompt_libprint(pprompt, aprompt, 0);
502041394f3SDevin Teske 		} else {
503041394f3SDevin Teske 			dprompt_sprint(init_prompt, pprompt, aprompt);
504041394f3SDevin Teske 			dialog_out = dialog_spawn_gauge(init_prompt, &pid);
505041394f3SDevin Teske 			dprompt_dprint(dialog_out, pprompt, aprompt, 0);
506041394f3SDevin Teske 		}
507041394f3SDevin Teske 	} /* !debug */
508041394f3SDevin Teske 
509041394f3SDevin Teske 	/* Seed the random(3) generator */
510041394f3SDevin Teske 	if (dialog_test)
511041394f3SDevin Teske 		srandom(0xf1eeface);
512041394f3SDevin Teske 
513041394f3SDevin Teske 	/* Set default/custom status line format */
514041394f3SDevin Teske 	if (dpv_nfiles > 1) {
515041394f3SDevin Teske 		snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
516041394f3SDevin Teske 		    DPV_STATUS_MANY);
517041394f3SDevin Teske 		status_format_custom = config->status_many;
518041394f3SDevin Teske 	} else {
519041394f3SDevin Teske 		snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
520041394f3SDevin Teske 		    DPV_STATUS_SOLO);
521041394f3SDevin Teske 		status_format_custom = config->status_solo;
522041394f3SDevin Teske 	}
523041394f3SDevin Teske 
524041394f3SDevin Teske 	/* Add test mode identifier to default status line if enabled */
525041394f3SDevin Teske 	if (dialog_test && (strlen(status_format_default) + 12) <
526041394f3SDevin Teske 	    DPV_STATUS_FORMAT_MAX)
527041394f3SDevin Teske 		strcat(status_format_default, " [TEST MODE]");
528041394f3SDevin Teske 
529041394f3SDevin Teske 	/* Verify custom status format */
530041394f3SDevin Teske 	status_fmt = fmtcheck(status_format_custom, status_format_default);
531041394f3SDevin Teske 	if (status_format_custom != NULL &&
532041394f3SDevin Teske 	    status_fmt == status_format_default) {
533041394f3SDevin Teske 		warnx("WARNING! Invalid status_format configuration `%s'",
534041394f3SDevin Teske 		      status_format_custom);
535041394f3SDevin Teske 		warnx("Default status_format `%s'", status_format_default);
536041394f3SDevin Teske 	}
537041394f3SDevin Teske 
538041394f3SDevin Teske 	/* Record when we started (used to prevent updating too quickly) */
539041394f3SDevin Teske 	(void)gettimeofday(&start, (struct timezone *)NULL);
540041394f3SDevin Teske 
541041394f3SDevin Teske 	/* Calculate number of microseconds in-between sub-second updates */
542041394f3SDevin Teske 	if (status_updates_per_second != 0)
543041394f3SDevin Teske 		status_update_usec = 1000000 / status_updates_per_second;
544041394f3SDevin Teske 	if (dialog_updates_per_second != 0)
545041394f3SDevin Teske 		dialog_update_usec = 1000000 / dialog_updates_per_second;
546041394f3SDevin Teske 
547041394f3SDevin Teske 	/*
548041394f3SDevin Teske 	 * Process the file list [serially] (one for each argument passed)
549041394f3SDevin Teske 	 */
550041394f3SDevin Teske 	files_left = dpv_nfiles;
551041394f3SDevin Teske 	list_head = file_list;
552041394f3SDevin Teske 	for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
553041394f3SDevin Teske 		keep_going = TRUE;
554041394f3SDevin Teske 		output_out = -1;
555041394f3SDevin Teske 		pct = 0;
556041394f3SDevin Teske 		nthfile++;
557041394f3SDevin Teske 		files_left--;
558041394f3SDevin Teske 
559041394f3SDevin Teske 		if (dpv_interrupt)
560041394f3SDevin Teske 			break;
561041394f3SDevin Teske 		if (dialog_test)
562041394f3SDevin Teske 			pct = 0 - increment;
563041394f3SDevin Teske 
564041394f3SDevin Teske 		/* Attempt to spawn output program for this file */
565041394f3SDevin Teske 		if (!dialog_test && output != NULL) {
566041394f3SDevin Teske 			mask = umask(0022);
567041394f3SDevin Teske 			(void)umask(mask);
568041394f3SDevin Teske 
569041394f3SDevin Teske 			switch (output_type) {
570041394f3SDevin Teske 			case DPV_OUTPUT_SHELL:
571041394f3SDevin Teske 				output_out = shell_spawn_pipecmd(output,
572041394f3SDevin Teske 				    curfile->name, &output_pid);
573041394f3SDevin Teske 				break;
574041394f3SDevin Teske 			case DPV_OUTPUT_FILE:
575041394f3SDevin Teske 				path_fmt = fmtcheck(output, "%s");
576041394f3SDevin Teske 				if (path_fmt == output)
577041394f3SDevin Teske 					len = snprintf(pathbuf,
578041394f3SDevin Teske 					    PATH_MAX, output, curfile->name);
579041394f3SDevin Teske 				else
580041394f3SDevin Teske 					len = snprintf(pathbuf,
581041394f3SDevin Teske 					    PATH_MAX, "%s", output);
582041394f3SDevin Teske 				if (len >= PATH_MAX) {
583041394f3SDevin Teske 					warnx("%s:%d:%s: pathbuf[%u] too small"
584041394f3SDevin Teske 					    "to hold output argument",
585041394f3SDevin Teske 					    __FILE__, __LINE__, __func__,
586041394f3SDevin Teske 					    PATH_MAX);
587041394f3SDevin Teske 					return (-1);
588041394f3SDevin Teske 				}
589041394f3SDevin Teske 				if ((output_out = open(pathbuf,
590041394f3SDevin Teske 				    O_CREAT|O_WRONLY, DEFFILEMODE & ~mask))
591041394f3SDevin Teske 				    < 0) {
592041394f3SDevin Teske 					warn("%s", pathbuf);
593041394f3SDevin Teske 					return (-1);
594041394f3SDevin Teske 				}
595041394f3SDevin Teske 				break;
596041394f3SDevin Teske 			default:
597041394f3SDevin Teske 				break;
598041394f3SDevin Teske 			}
599041394f3SDevin Teske 		}
600041394f3SDevin Teske 
601041394f3SDevin Teske 		while (!dpv_interrupt && keep_going) {
602041394f3SDevin Teske 			if (dialog_test) {
603041394f3SDevin Teske 				usleep(50000);
604041394f3SDevin Teske 				pct += increment;
605041394f3SDevin Teske 				dpv_overall_read +=
606041394f3SDevin Teske 				    (int)(random() / 512 / dpv_nfiles);
607041394f3SDevin Teske 				    /* 512 limits fake readout to Megabytes */
608041394f3SDevin Teske 			} else if (action != NULL)
609041394f3SDevin Teske 				pct = action(curfile, output_out);
610041394f3SDevin Teske 
611041394f3SDevin Teske 			if (no_overrun || dialog_test)
612041394f3SDevin Teske 				keep_going = (pct < 100);
613041394f3SDevin Teske 			else {
614041394f3SDevin Teske 				status = curfile->status;
615041394f3SDevin Teske 				keep_going = (status == DPV_STATUS_RUNNING);
616041394f3SDevin Teske 			}
617041394f3SDevin Teske 
618041394f3SDevin Teske 			/* Get current time and calculate seconds elapsed */
619041394f3SDevin Teske 			gettimeofday(&now, (struct timezone *)NULL);
620041394f3SDevin Teske 			now.tv_sec = now.tv_sec - start.tv_sec;
621041394f3SDevin Teske 			now.tv_usec = now.tv_usec - start.tv_usec;
622041394f3SDevin Teske 			if (now.tv_usec < 0)
623041394f3SDevin Teske 				now.tv_sec--, now.tv_usec += 1000000;
624041394f3SDevin Teske 			seconds = now.tv_sec + (now.tv_usec / 1000000.0);
625041394f3SDevin Teske 
626041394f3SDevin Teske 			/* Update dialog (be it dialog(3), dialog(1), etc.) */
627041394f3SDevin Teske 			if ((dialog_updates_per_second != 0 &&
628041394f3SDevin Teske 			   (
629041394f3SDevin Teske 			    seconds != dialog_old_seconds ||
630041394f3SDevin Teske 			    now.tv_usec - dialog_last_update >=
631041394f3SDevin Teske 			        dialog_update_usec ||
632041394f3SDevin Teske 			    nthfile != dialog_old_nthfile
633041394f3SDevin Teske 			   )) || pct == 100
634041394f3SDevin Teske 			) {
635041394f3SDevin Teske 				/* Calculate overall progress (rounding up) */
636041394f3SDevin Teske 				overall = (100 * nthfile - 100 + pct) /
637041394f3SDevin Teske 				    dpv_nfiles;
638041394f3SDevin Teske 				if (((100 * nthfile - 100 + pct) * 10 /
639041394f3SDevin Teske 				    dpv_nfiles % 100) > 50)
640041394f3SDevin Teske 					overall++;
641041394f3SDevin Teske 
642041394f3SDevin Teske 				dprompt_recreate(list_head, curfile, pct);
643041394f3SDevin Teske 
644041394f3SDevin Teske 				if (use_libdialog && !debug) {
645041394f3SDevin Teske 					/* Update dialog(3) widget */
646041394f3SDevin Teske 					dprompt_libprint(pprompt, aprompt,
647041394f3SDevin Teske 					    overall);
648041394f3SDevin Teske 				} else {
649041394f3SDevin Teske 					/* stdout, dialog(1), or Xdialog(1) */
650041394f3SDevin Teske 					dprompt_dprint(dialog_out, pprompt,
651041394f3SDevin Teske 					    aprompt, overall);
652041394f3SDevin Teske 					fsync(dialog_out);
653041394f3SDevin Teske 				}
654041394f3SDevin Teske 				dialog_old_seconds = seconds;
655041394f3SDevin Teske 				dialog_old_nthfile = nthfile;
656041394f3SDevin Teske 				dialog_last_update = now.tv_usec;
657041394f3SDevin Teske 			}
658041394f3SDevin Teske 
659041394f3SDevin Teske 			/* Update the status line */
660041394f3SDevin Teske 			if ((use_libdialog && !debug) &&
661041394f3SDevin Teske 			    status_updates_per_second != 0 &&
662041394f3SDevin Teske 			   (
663041394f3SDevin Teske 			    keep_going != TRUE ||
664041394f3SDevin Teske 			    seconds != status_old_seconds ||
665041394f3SDevin Teske 			    now.tv_usec - status_last_update >=
666041394f3SDevin Teske 			        status_update_usec ||
667041394f3SDevin Teske 			    nthfile != status_old_nthfile
668041394f3SDevin Teske 			   )
669041394f3SDevin Teske 			) {
670041394f3SDevin Teske 				status_printf(status_fmt, dpv_overall_read,
671041394f3SDevin Teske 				    (dpv_overall_read / (seconds == 0 ? 1 :
672041394f3SDevin Teske 					seconds) * 1.0),
673041394f3SDevin Teske 				    1, /* XXX until we add parallelism XXX */
674041394f3SDevin Teske 				    files_left);
675041394f3SDevin Teske 				status_old_seconds = seconds;
676041394f3SDevin Teske 				status_old_nthfile = nthfile;
677041394f3SDevin Teske 				status_last_update = now.tv_usec;
678041394f3SDevin Teske 			}
679041394f3SDevin Teske 		}
680041394f3SDevin Teske 
681041394f3SDevin Teske 		if (!dialog_test && output_out >= 0) {
682041394f3SDevin Teske 			close(output_out);
683041394f3SDevin Teske 			waitpid(output_pid, (int *)NULL, 0);
684041394f3SDevin Teske 		}
685041394f3SDevin Teske 
686041394f3SDevin Teske 		if (dpv_abort)
687041394f3SDevin Teske 			break;
688041394f3SDevin Teske 
689041394f3SDevin Teske 		/* Advance head of list when we hit the max display lines */
690041394f3SDevin Teske 		if (display_limit > 0 && nthfile % display_limit == 0)
691041394f3SDevin Teske 			list_head = curfile->next;
692041394f3SDevin Teske 	}
693041394f3SDevin Teske 
694041394f3SDevin Teske 	if (!debug) {
695041394f3SDevin Teske 		if (use_libdialog)
696041394f3SDevin Teske 			end_dialog();
697041394f3SDevin Teske 		else {
698041394f3SDevin Teske 			close(dialog_out);
699041394f3SDevin Teske 			waitpid(pid, (int *)NULL, 0);
700041394f3SDevin Teske 		}
701*80f7300dSDevin Teske 		if (!keep_tite && !dpv_interrupt)
702041394f3SDevin Teske 			printf("\n");
703041394f3SDevin Teske 	} else
704324c11a1SDevin Teske 		warnx("%s: %lli overall read", __func__, dpv_overall_read);
705041394f3SDevin Teske 
706041394f3SDevin Teske 	if (dpv_interrupt || dpv_abort)
707041394f3SDevin Teske 		return (-1);
708041394f3SDevin Teske 	else
709041394f3SDevin Teske 		return (0);
710041394f3SDevin Teske }
711041394f3SDevin Teske 
712041394f3SDevin Teske /*
713041394f3SDevin Teske  * Free allocated items initialized by dpv()
714041394f3SDevin Teske  */
715041394f3SDevin Teske void
716041394f3SDevin Teske dpv_free(void)
717041394f3SDevin Teske {
718041394f3SDevin Teske 	dialogrc_free();
719041394f3SDevin Teske 	dprompt_free();
720041394f3SDevin Teske 	dialog_maxsize_free();
721041394f3SDevin Teske 	if (aprompt != NULL) {
722041394f3SDevin Teske 		free(aprompt);
723041394f3SDevin Teske 		aprompt = NULL;
724041394f3SDevin Teske 	}
725041394f3SDevin Teske 	if (pprompt != NULL) {
726041394f3SDevin Teske 		free(pprompt);
727041394f3SDevin Teske 		pprompt = NULL;
728041394f3SDevin Teske 	}
729041394f3SDevin Teske 	status_free();
730041394f3SDevin Teske }
731