1274116Sdteske/*-
2294862Sdteske * Copyright (c) 2013-2016 Devin Teske <dteske@FreeBSD.org>
3274116Sdteske * All rights reserved.
4274116Sdteske *
5274116Sdteske * Redistribution and use in source and binary forms, with or without
6274116Sdteske * modification, are permitted provided that the following conditions
7274116Sdteske * are met:
8274116Sdteske * 1. Redistributions of source code must retain the above copyright
9274116Sdteske *    notice, this list of conditions and the following disclaimer.
10274116Sdteske * 2. Redistributions in binary form must reproduce the above copyright
11274116Sdteske *    notice, this list of conditions and the following disclaimer in the
12274116Sdteske *    documentation and/or other materials provided with the distribution.
13274116Sdteske *
14274116Sdteske * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15274116Sdteske * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16274116Sdteske * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17274116Sdteske * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18274116Sdteske * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19274116Sdteske * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20274116Sdteske * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21274116Sdteske * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22274116Sdteske * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23274116Sdteske * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24274116Sdteske * SUCH DAMAGE.
25274116Sdteske */
26274116Sdteske
27274116Sdteske#include <sys/cdefs.h>
28274116Sdteske__FBSDID("$FreeBSD$");
29274116Sdteske
30274116Sdteske#include <sys/stat.h>
31274116Sdteske#include <sys/time.h>
32274116Sdteske#include <sys/types.h>
33274116Sdteske#include <sys/wait.h>
34274116Sdteske
35274116Sdteske#include <ctype.h>
36274116Sdteske#include <dialog.h>
37274116Sdteske#include <err.h>
38274116Sdteske#include <limits.h>
39293868Sdteske#include <locale.h>
40274116Sdteske#include <stdio.h>
41274116Sdteske#include <stdlib.h>
42274116Sdteske#include <string.h>
43274116Sdteske#include <string_m.h>
44274116Sdteske#include <unistd.h>
45274116Sdteske
46274116Sdteske#include "dialog_util.h"
47274116Sdteske#include "dialogrc.h"
48274116Sdteske#include "dprompt.h"
49274116Sdteske#include "dpv.h"
50274116Sdteske#include "dpv_private.h"
51274116Sdteske#include "status.h"
52274116Sdteske#include "util.h"
53274116Sdteske
54274116Sdteske/* Test Mechanics (Only used when dpv_config.options |= DPV_TEST_MODE) */
55274116Sdteske#define INCREMENT		1	/* Increment % per-pass test-mode */
56274116Sdteske#define XDIALOG_INCREMENT	15	/* different for slower Xdialog(1) */
57274116Sdteskestatic uint8_t increment = INCREMENT;
58274116Sdteske
59274116Sdteske/* Debugging */
60274116Sdteskeuint8_t debug = FALSE;
61274116Sdteske
62274116Sdteske/* Data to process */
63274116Sdteskeint dpv_interrupt = FALSE;
64274116Sdteskeint dpv_abort = FALSE;
65274116Sdteskeunsigned int dpv_nfiles = 0;
66274116Sdteske
67274116Sdteske/* Data processing */
68274116Sdteskelong long dpv_overall_read = 0;
69274116Sdteskestatic char pathbuf[PATH_MAX];
70274116Sdteske
71274116Sdteske/* Extra display information */
72294860Sdteskeuint8_t keep_tite = FALSE;	/* dpv_config.keep_tite */
73274116Sdteskeuint8_t no_labels = FALSE;	/* dpv_config.options & DPV_NO_LABELS */
74274116Sdteskeuint8_t wide = FALSE;		/* dpv_config.options & DPV_WIDE_MODE */
75274116Sdteskechar *aprompt = NULL;		/* dpv_config.aprompt */
76274116Sdteskechar *msg_done = NULL;		/* dpv_config.msg_done */
77274116Sdteskechar *msg_fail = NULL;		/* dpv_config.msg_fail */
78274116Sdteskechar *msg_pending = NULL;	/* dpv_config.msg_pending */
79274116Sdteskechar *pprompt = NULL;		/* dpv_config.pprompt */
80274116Sdteske
81274116Sdteske/* Status-Line format for when using dialog(3) */
82274121Sdteskestatic const char *status_format_custom = NULL;
83274121Sdteskestatic char status_format_default[DPV_STATUS_FORMAT_MAX];
84274116Sdteske
85274116Sdteske/*
86274116Sdteske * Takes a pointer to a dpv_config structure containing layout details and
87274116Sdteske * pointer to initial element in a linked-list of dpv_file_node structures,
88274116Sdteske * each presenting a file to process. Executes the `action' function passed-in
89274116Sdteske * as a member to the `config' structure argument.
90274116Sdteske */
91274116Sdteskeint
92274116Sdteskedpv(struct dpv_config *config, struct dpv_file_node *file_list)
93274116Sdteske{
94274116Sdteske	char c;
95274116Sdteske	uint8_t keep_going;
96274116Sdteske	uint8_t nls = FALSE; /* See dialog_prompt_nlstate() */
97274116Sdteske	uint8_t no_overrun = FALSE;
98274116Sdteske	uint8_t pprompt_nls = FALSE; /* See dialog_prompt_nlstate() */
99274116Sdteske	uint8_t shrink_label_size = FALSE;
100274116Sdteske	mode_t mask;
101274116Sdteske	uint16_t options;
102274116Sdteske	char *cp;
103274116Sdteske	char *fc;
104274116Sdteske	char *last;
105274116Sdteske	char *name;
106274116Sdteske	char *output;
107274116Sdteske	const char *status_fmt;
108274116Sdteske	const char *path_fmt;
109274116Sdteske	enum dpv_display display_type;
110274116Sdteske	enum dpv_output output_type;
111274116Sdteske	enum dpv_status status;
112274116Sdteske	int (*action)(struct dpv_file_node *file, int out);
113274116Sdteske	int backslash;
114274116Sdteske	int dialog_last_update = 0;
115274116Sdteske	int dialog_old_nthfile = 0;
116274116Sdteske	int dialog_old_seconds = -1;
117274116Sdteske	int dialog_out = STDOUT_FILENO;
118274116Sdteske	int dialog_update_usec = 0;
119274116Sdteske	int dialog_updates_per_second;
120274116Sdteske	int files_left;
121274116Sdteske	int max_cols;
122274116Sdteske	int nthfile = 0;
123274116Sdteske	int output_out;
124274116Sdteske	int overall = 0;
125274116Sdteske	int pct;
126274116Sdteske	int res;
127274116Sdteske	int seconds;
128274116Sdteske	int status_last_update = 0;
129274116Sdteske	int status_old_nthfile = 0;
130274116Sdteske	int status_old_seconds = -1;
131274116Sdteske	int status_update_usec = 0;
132274116Sdteske	int status_updates_per_second;
133274116Sdteske	pid_t output_pid;
134274116Sdteske	pid_t pid;
135274116Sdteske	size_t len;
136274116Sdteske	struct dpv_file_node *curfile;
137274116Sdteske	struct dpv_file_node *first_file;
138274116Sdteske	struct dpv_file_node *list_head;
139274116Sdteske	struct timeval now;
140274116Sdteske	struct timeval start;
141274116Sdteske	char init_prompt[PROMPT_MAX + 1] = "";
142274116Sdteske
143274116Sdteske	/* Initialize globals to default values */
144274116Sdteske	aprompt		= NULL;
145274116Sdteske	pprompt		= NULL;
146274116Sdteske	options		= 0;
147274116Sdteske	action		= NULL;
148274116Sdteske	backtitle	= NULL;
149274116Sdteske	debug		= FALSE;
150274116Sdteske	dialog_test	= FALSE;
151274116Sdteske	dialog_updates_per_second = DIALOG_UPDATES_PER_SEC;
152274116Sdteske	display_limit	= DISPLAY_LIMIT_DEFAULT;
153274116Sdteske	display_type	= DPV_DISPLAY_LIBDIALOG;
154294860Sdteske	keep_tite	= FALSE;
155274116Sdteske	label_size	= LABEL_SIZE_DEFAULT;
156274116Sdteske	msg_done	= NULL;
157274116Sdteske	msg_fail	= NULL;
158274116Sdteske	msg_pending	= NULL;
159274116Sdteske	no_labels	= FALSE;
160274116Sdteske	output		= NULL;
161274116Sdteske	output_type	= DPV_OUTPUT_NONE;
162274116Sdteske	pbar_size	= PBAR_SIZE_DEFAULT;
163274116Sdteske	status_format_custom = NULL;
164274116Sdteske	status_updates_per_second = STATUS_UPDATES_PER_SEC;
165274116Sdteske	title		= NULL;
166274116Sdteske	wide		= FALSE;
167274116Sdteske
168274116Sdteske	/* Process config options (overriding defaults) */
169274116Sdteske	if (config != NULL) {
170274116Sdteske		if (config->aprompt != NULL) {
171274116Sdteske			if (aprompt == NULL) {
172274116Sdteske				aprompt = malloc(DPV_APROMPT_MAX);
173274116Sdteske				if (aprompt == NULL)
174274116Sdteske					return (-1);
175274116Sdteske			}
176274116Sdteske			snprintf(aprompt, DPV_APROMPT_MAX, "%s",
177274116Sdteske			    config->aprompt);
178274116Sdteske		}
179274116Sdteske		if (config->pprompt != NULL) {
180274116Sdteske			if (pprompt == NULL) {
181274116Sdteske				pprompt = malloc(DPV_PPROMPT_MAX + 2);
182274116Sdteske				/* +2 is for implicit "\n" appended later */
183274116Sdteske				if (pprompt == NULL)
184274116Sdteske					return (-1);
185274116Sdteske			}
186274116Sdteske			snprintf(pprompt, DPV_APROMPT_MAX, "%s",
187274116Sdteske			    config->pprompt);
188274116Sdteske		}
189274116Sdteske
190274116Sdteske		options		= config->options;
191274116Sdteske		action		= config->action;
192274116Sdteske		backtitle	= config->backtitle;
193274116Sdteske		debug		= config->debug;
194274116Sdteske		dialog_test	= ((options & DPV_TEST_MODE) != 0);
195274116Sdteske		dialog_updates_per_second = config->dialog_updates_per_second;
196274116Sdteske		display_limit	= config->display_limit;
197274116Sdteske		display_type	= config->display_type;
198294860Sdteske		keep_tite	= config->keep_tite;
199274116Sdteske		label_size	= config->label_size;
200274116Sdteske		msg_done	= (char *)config->msg_done;
201274116Sdteske		msg_fail	= (char *)config->msg_fail;
202274116Sdteske		msg_pending	= (char *)config->msg_pending;
203274116Sdteske		no_labels	= ((options & DPV_NO_LABELS) != 0);
204274116Sdteske		no_overrun	= ((options & DPV_NO_OVERRUN) != 0);
205274116Sdteske		output          = config->output;
206274116Sdteske		output_type	= config->output_type;
207274116Sdteske		pbar_size	= config->pbar_size;
208274116Sdteske		status_updates_per_second = config->status_updates_per_second;
209274116Sdteske		title		= config->title;
210274116Sdteske		wide		= ((options & DPV_WIDE_MODE) != 0);
211274116Sdteske
212274116Sdteske		/* Enforce some minimums (pedantic) */
213274116Sdteske		if (display_limit < -1)
214274116Sdteske			display_limit = -1;
215274116Sdteske		if (label_size < -1)
216274116Sdteske			label_size = -1;
217274116Sdteske		if (pbar_size < -1)
218274116Sdteske			pbar_size = -1;
219274116Sdteske
220274116Sdteske		/* For the mini-pbar, -1 means hide, zero is invalid unless
221274116Sdteske		 * only one file is given */
222274116Sdteske		if (pbar_size == 0) {
223274116Sdteske			if (file_list == NULL || file_list->next == NULL)
224274116Sdteske				pbar_size = -1;
225274116Sdteske			else
226274116Sdteske				pbar_size = PBAR_SIZE_DEFAULT;
227274116Sdteske		}
228274116Sdteske
229274116Sdteske		/* For the label, -1 means auto-size, zero is invalid unless
230274116Sdteske		 * specifically requested through the use of options flag */
231274116Sdteske		if (label_size == 0 && no_labels == FALSE)
232274116Sdteske			label_size = LABEL_SIZE_DEFAULT;
233274116Sdteske
234274116Sdteske		/* Status update should not be zero */
235274116Sdteske		if (status_updates_per_second == 0)
236274116Sdteske			status_updates_per_second = STATUS_UPDATES_PER_SEC;
237274116Sdteske	} /* config != NULL */
238274116Sdteske
239274116Sdteske	/* Process the type of display we've been requested to produce */
240274116Sdteske	switch (display_type) {
241274116Sdteske	case DPV_DISPLAY_STDOUT:
242274116Sdteske		debug		= TRUE;
243274116Sdteske		use_color	= FALSE;
244274116Sdteske		use_dialog	= FALSE;
245274116Sdteske		use_libdialog	= FALSE;
246274116Sdteske		use_xdialog	= FALSE;
247274116Sdteske		break;
248274116Sdteske	case DPV_DISPLAY_DIALOG:
249274116Sdteske		use_color	= TRUE;
250274116Sdteske		use_dialog	= TRUE;
251274116Sdteske		use_libdialog	= FALSE;
252274116Sdteske		use_xdialog	= FALSE;
253274116Sdteske		break;
254274116Sdteske	case DPV_DISPLAY_XDIALOG:
255274116Sdteske		snprintf(dialog, PATH_MAX, XDIALOG);
256274116Sdteske		use_color	= FALSE;
257274116Sdteske		use_dialog	= FALSE;
258274116Sdteske		use_libdialog	= FALSE;
259274116Sdteske		use_xdialog	= TRUE;
260274116Sdteske		break;
261274116Sdteske	default:
262274116Sdteske		use_color	= TRUE;
263274116Sdteske		use_dialog	= FALSE;
264274116Sdteske		use_libdialog	= TRUE;
265274116Sdteske		use_xdialog	= FALSE;
266274116Sdteske		break;
267274116Sdteske	} /* display_type */
268274116Sdteske
269274116Sdteske	/* Enforce additional minimums that require knowing our display type */
270274116Sdteske	if (dialog_updates_per_second == 0)
271274116Sdteske		dialog_updates_per_second = use_xdialog ?
272274116Sdteske			XDIALOG_UPDATES_PER_SEC : DIALOG_UPDATES_PER_SEC;
273274116Sdteske
274274116Sdteske	/* Allow forceful override of use_color */
275274116Sdteske	if (config != NULL && (config->options & DPV_USE_COLOR) != 0)
276274116Sdteske		use_color = TRUE;
277274116Sdteske
278274116Sdteske	/* Count the number of files in provided list of dpv_file_node's */
279274116Sdteske	if (use_dialog && pprompt != NULL && *pprompt != '\0')
280274116Sdteske		pprompt_nls = dialog_prompt_nlstate(pprompt);
281274116Sdteske
282274116Sdteske	max_cols = dialog_maxcols();
283274116Sdteske	if (label_size == -1)
284274116Sdteske		shrink_label_size = TRUE;
285274116Sdteske
286274116Sdteske	/* Process file arguments */
287274116Sdteske	for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
288274116Sdteske		dpv_nfiles++;
289274116Sdteske
290274116Sdteske		/* dialog(3) only expands literal newlines */
291274116Sdteske		if (use_libdialog) strexpandnl(curfile->name);
292274116Sdteske
293274116Sdteske		/* Optionally calculate label size for file */
294274116Sdteske		if (shrink_label_size) {
295274116Sdteske			nls = FALSE;
296274116Sdteske			name = curfile->name;
297274116Sdteske			if (curfile == file_list)
298274116Sdteske				nls = pprompt_nls;
299274116Sdteske			last = (char *)dialog_prompt_lastline(name, nls);
300274116Sdteske			if (use_dialog) {
301274116Sdteske				c = *last;
302274116Sdteske				*last = '\0';
303274116Sdteske				nls = dialog_prompt_nlstate(name);
304274116Sdteske				*last = c;
305274116Sdteske			}
306274116Sdteske			len = dialog_prompt_longestline(last, nls);
307274116Sdteske			if ((int)len > (label_size - 3)) {
308274116Sdteske				if (label_size > 0)
309274116Sdteske					label_size += 3;
310274116Sdteske				label_size = len;
311274116Sdteske				/* Room for ellipsis (unless NULL) */
312274116Sdteske				if (label_size > 0)
313274116Sdteske					label_size += 3;
314274116Sdteske			}
315274116Sdteske
316274116Sdteske			if (max_cols > 0 && label_size > (max_cols - pbar_size
317274116Sdteske			    - 9))
318274116Sdteske				label_size = max_cols - pbar_size - 9;
319274116Sdteske		}
320274116Sdteske
321274116Sdteske		if (debug)
322274116Sdteske			warnx("label=[%s] path=[%s] size=%lli",
323274116Sdteske			    curfile->name, curfile->path, curfile->length);
324274116Sdteske	} /* file_list */
325274116Sdteske
326274116Sdteske	/* Optionally process the contents of DIALOGRC (~/.dialogrc) */
327274116Sdteske	if (use_dialog) {
328274116Sdteske		res = parse_dialogrc();
329274116Sdteske		if (debug && res == 0) {
330274116Sdteske			warnx("Successfully read `%s' config file", DIALOGRC);
331274116Sdteske			warnx("use_shadow = %i (Boolean)", use_shadow);
332274116Sdteske			warnx("use_colors = %i (Boolean)", use_colors);
333274116Sdteske			warnx("gauge_color=[%s] (FBH)", gauge_color);
334274116Sdteske		}
335274116Sdteske	} else if (use_libdialog) {
336274116Sdteske		init_dialog(stdin, stdout);
337274116Sdteske		use_shadow = dialog_state.use_shadow;
338274116Sdteske		use_colors = dialog_state.use_colors;
339274116Sdteske		gauge_color[0] = 48 + dlg_color_table[GAUGE_ATTR].fg;
340274116Sdteske		gauge_color[1] = 48 + dlg_color_table[GAUGE_ATTR].bg;
341274116Sdteske		gauge_color[2] = dlg_color_table[GAUGE_ATTR].hilite ?
342274116Sdteske		    'b' : 'B';
343274116Sdteske		gauge_color[3] = '\0';
344274116Sdteske		end_dialog();
345274116Sdteske		if (debug) {
346274116Sdteske			warnx("Finished initializing dialog(3) library");
347274116Sdteske			warnx("use_shadow = %i (Boolean)", use_shadow);
348274116Sdteske			warnx("use_colors = %i (Boolean)", use_colors);
349274116Sdteske			warnx("gauge_color=[%s] (FBH)", gauge_color);
350274116Sdteske		}
351274116Sdteske	}
352274116Sdteske
353274116Sdteske	/* Enable mini progress bar automatically for stdin streams if unable
354274116Sdteske	 * to calculate progress (missing `lines:' syntax). */
355274116Sdteske	if (dpv_nfiles <= 1 && file_list != NULL && file_list->length < 0 &&
356274116Sdteske	    !dialog_test)
357274116Sdteske		pbar_size = PBAR_SIZE_DEFAULT;
358274116Sdteske
359274116Sdteske	/* If $USE_COLOR is set and non-NULL enable color; otherwise disable */
360274116Sdteske	if ((cp = getenv(ENV_USE_COLOR)) != 0)
361274116Sdteske		use_color = *cp != '\0' ? 1 : 0;
362274116Sdteske
363274116Sdteske	/* Print error and return `-1' if not given at least one name */
364274116Sdteske	if (dpv_nfiles == 0) {
365274116Sdteske		warnx("%s: no labels provided", __func__);
366274116Sdteske		return (-1);
367274116Sdteske	} else if (debug)
368274116Sdteske		warnx("%s: %u label%s provided", __func__, dpv_nfiles,
369274116Sdteske		    dpv_nfiles == 1 ? "" : "s");
370274116Sdteske
371274116Sdteske	/* If only one file and pbar size is zero, default to `-1' */
372274116Sdteske	if (dpv_nfiles <= 1 && pbar_size == 0)
373274116Sdteske		pbar_size = -1;
374274116Sdteske
375274116Sdteske	/* Print some debugging information */
376274116Sdteske	if (debug) {
377274116Sdteske		warnx("%s: %s(%i) max rows x cols = %i x %i",
378274116Sdteske		    __func__, use_xdialog ? XDIALOG : DIALOG,
379274116Sdteske		    use_libdialog ? 3 : 1, dialog_maxrows(),
380274116Sdteske		    dialog_maxcols());
381274116Sdteske	}
382274116Sdteske
383274116Sdteske	/* Xdialog(1) updates a lot slower than dialog(1) */
384274116Sdteske	if (dialog_test && use_xdialog)
385274116Sdteske		increment = XDIALOG_INCREMENT;
386274116Sdteske
387274116Sdteske	/* Always add implicit newline to pprompt (when specified) */
388274116Sdteske	if (pprompt != NULL && *pprompt != '\0') {
389274116Sdteske		len = strlen(pprompt);
390274116Sdteske		/*
391274116Sdteske		 * NOTE: pprompt = malloc(PPROMPT_MAX + 2)
392274116Sdteske		 * NOTE: (see getopt(2) section above for pprompt allocation)
393274116Sdteske		 */
394274116Sdteske		pprompt[len++] = '\\';
395274116Sdteske		pprompt[len++] = 'n';
396274116Sdteske		pprompt[len++] = '\0';
397274116Sdteske	}
398274116Sdteske
399274116Sdteske	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
400274116Sdteske	if (use_xdialog && pprompt != NULL) {
401274116Sdteske		/* Replace `\n' with `\n\\n\n' in pprompt */
402274116Sdteske		len = strlen(pprompt);
403274116Sdteske		len += strcount(pprompt, "\\n") * 2;
404274116Sdteske		if (len > DPV_PPROMPT_MAX)
405274116Sdteske			errx(EXIT_FAILURE, "%s: Oops, pprompt buffer overflow "
406274116Sdteske			    "(%zu > %i)", __func__, len, DPV_PPROMPT_MAX);
407274116Sdteske		if (replaceall(pprompt, "\\n", "\n\\n\n") < 0)
408274116Sdteske			err(EXIT_FAILURE, "%s: replaceall()", __func__);
409274116Sdteske	}
410274116Sdteske	/* libdialog requires literal newlines */
411274116Sdteske	else if (use_libdialog && pprompt != NULL)
412274116Sdteske		strexpandnl(pprompt);
413274116Sdteske
414274116Sdteske	/* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */
415274116Sdteske	if (use_xdialog && aprompt != NULL) {
416274116Sdteske		/* Replace `\n' with `\n\\n\n' in aprompt */
417274116Sdteske		len = strlen(aprompt);
418274116Sdteske		len += strcount(aprompt, "\\n") * 2;
419274116Sdteske		if (len > DPV_APROMPT_MAX)
420274116Sdteske			errx(EXIT_FAILURE, "%s: Oops, aprompt buffer overflow "
421274116Sdteske			    " (%zu > %i)", __func__, len, DPV_APROMPT_MAX);
422274116Sdteske		if (replaceall(aprompt, "\\n", "\n\\n\n") < 0)
423274116Sdteske			err(EXIT_FAILURE, "%s: replaceall()", __func__);
424274116Sdteske	}
425274116Sdteske	/* libdialog requires literal newlines */
426274116Sdteske	else if (use_libdialog && aprompt != NULL)
427274116Sdteske		strexpandnl(aprompt);
428274116Sdteske
429274116Sdteske	/*
430274116Sdteske	 * Warn user about an obscure dialog(1) bug (neither Xdialog(1) nor
431274116Sdteske	 * libdialog are affected) in the `--gauge' widget. If the first non-
432274116Sdteske	 * whitespace letter of "{new_prompt}" in "XXX\n{new_prompt}\nXXX\n"
433274116Sdteske	 * is a number, the number can sometimes be mistaken for a percentage
434274116Sdteske	 * to the overall progressbar. Other nasty side-effects such as the
435274116Sdteske	 * entire prompt not displaying or displaying improperly are caused by
436274116Sdteske	 * this bug too.
437274116Sdteske	 *
438274116Sdteske	 * NOTE: When we can use color, we have a work-around... prefix the
439274116Sdteske	 * output with `\Zn' (used to terminate ANSI and reset to normal).
440274116Sdteske	 */
441274116Sdteske	if (use_dialog && !use_color) {
442274116Sdteske		backslash = 0;
443274116Sdteske
444274116Sdteske		/* First, check pprompt (falls through if NULL) */
445274116Sdteske		fc = pprompt;
446274116Sdteske		while (fc != NULL && *fc != '\0') {
447274116Sdteske			if (*fc == '\n') /* leading literal newline OK */
448274116Sdteske				break;
449274116Sdteske			if (!isspace(*fc) && *fc != '\\' && backslash == 0)
450274116Sdteske				break;
451274116Sdteske			else if (backslash > 0 && *fc != 'n')
452274116Sdteske				break;
453274116Sdteske			else if (*fc == '\\') {
454274116Sdteske				backslash++;
455274116Sdteske				if (backslash > 2)
456274116Sdteske					break; /* we're safe */
457274116Sdteske			}
458274116Sdteske			fc++;
459274116Sdteske		}
460274116Sdteske		/* First non-whitespace character that dialog(1) will see */
461274116Sdteske		if (fc != NULL && *fc >= '0' && *fc <= '9')
462274116Sdteske			warnx("%s: WARNING! text argument to `-p' begins with "
463274116Sdteske			    "a number (not recommended)", __func__);
464274116Sdteske		else if (fc > pprompt)
465274116Sdteske			warnx("%s: WARNING! text argument to `-p' begins with "
466274116Sdteske			    "whitespace (not recommended)", __func__);
467274116Sdteske
468274116Sdteske		/*
469274116Sdteske		 * If no pprompt or pprompt is all whitespace, check the first
470274116Sdteske		 * file name provided to make sure it is alright too.
471274116Sdteske		 */
472274116Sdteske		if ((pprompt == NULL || *fc == '\0') && file_list != NULL) {
473274116Sdteske			first_file = file_list;
474274116Sdteske			fc = first_file->name;
475274116Sdteske			while (fc != NULL && *fc != '\0' && isspace(*fc))
476274116Sdteske				fc++;
477274116Sdteske			/* First non-whitespace char that dialog(1) will see */
478274116Sdteske			if (fc != NULL && *fc >= '0' && *fc <= '9')
479274116Sdteske				warnx("%s: WARNING! File name `%s' begins "
480274116Sdteske				    "with a number (use `-p text' for safety)",
481274116Sdteske				    __func__, first_file->name);
482274116Sdteske		}
483274116Sdteske	}
484274116Sdteske
485274116Sdteske	dprompt_init(file_list);
486274116Sdteske		/* Reads: label_size pbar_size pprompt aprompt dpv_nfiles */
487274116Sdteske		/* Inits: dheight and dwidth */
488274116Sdteske
489293868Sdteske	/* Default localeconv(3) settings for dialog(3) status */
490293868Sdteske	setlocale(LC_NUMERIC,
491293868Sdteske		getenv("LC_ALL") == NULL && getenv("LC_NUMERIC") == NULL ?
492293868Sdteske		LC_NUMERIC_DEFAULT : "");
493293868Sdteske
494274116Sdteske	if (!debug) {
495274116Sdteske		/* Internally create the initial `--gauge' prompt text */
496274116Sdteske		dprompt_recreate(file_list, (struct dpv_file_node *)NULL, 0);
497274116Sdteske
498274116Sdteske		/* Spawn [X]dialog(1) `--gauge', returning pipe descriptor */
499274116Sdteske		if (use_libdialog) {
500274116Sdteske			status_printf("");
501274116Sdteske			dprompt_libprint(pprompt, aprompt, 0);
502274116Sdteske		} else {
503274116Sdteske			dprompt_sprint(init_prompt, pprompt, aprompt);
504274116Sdteske			dialog_out = dialog_spawn_gauge(init_prompt, &pid);
505274116Sdteske			dprompt_dprint(dialog_out, pprompt, aprompt, 0);
506274116Sdteske		}
507274116Sdteske	} /* !debug */
508274116Sdteske
509274116Sdteske	/* Seed the random(3) generator */
510274116Sdteske	if (dialog_test)
511274116Sdteske		srandom(0xf1eeface);
512274116Sdteske
513274116Sdteske	/* Set default/custom status line format */
514274116Sdteske	if (dpv_nfiles > 1) {
515274116Sdteske		snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
516274116Sdteske		    DPV_STATUS_MANY);
517274116Sdteske		status_format_custom = config->status_many;
518274116Sdteske	} else {
519274116Sdteske		snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s",
520274116Sdteske		    DPV_STATUS_SOLO);
521274116Sdteske		status_format_custom = config->status_solo;
522274116Sdteske	}
523274116Sdteske
524274116Sdteske	/* Add test mode identifier to default status line if enabled */
525274116Sdteske	if (dialog_test && (strlen(status_format_default) + 12) <
526274116Sdteske	    DPV_STATUS_FORMAT_MAX)
527274116Sdteske		strcat(status_format_default, " [TEST MODE]");
528274116Sdteske
529274116Sdteske	/* Verify custom status format */
530274116Sdteske	status_fmt = fmtcheck(status_format_custom, status_format_default);
531274116Sdteske	if (status_format_custom != NULL &&
532274116Sdteske	    status_fmt == status_format_default) {
533274116Sdteske		warnx("WARNING! Invalid status_format configuration `%s'",
534274116Sdteske		      status_format_custom);
535274116Sdteske		warnx("Default status_format `%s'", status_format_default);
536274116Sdteske	}
537274116Sdteske
538274116Sdteske	/* Record when we started (used to prevent updating too quickly) */
539274116Sdteske	(void)gettimeofday(&start, (struct timezone *)NULL);
540274116Sdteske
541274116Sdteske	/* Calculate number of microseconds in-between sub-second updates */
542274116Sdteske	if (status_updates_per_second != 0)
543274116Sdteske		status_update_usec = 1000000 / status_updates_per_second;
544274116Sdteske	if (dialog_updates_per_second != 0)
545274116Sdteske		dialog_update_usec = 1000000 / dialog_updates_per_second;
546274116Sdteske
547274116Sdteske	/*
548274116Sdteske	 * Process the file list [serially] (one for each argument passed)
549274116Sdteske	 */
550274116Sdteske	files_left = dpv_nfiles;
551274116Sdteske	list_head = file_list;
552274116Sdteske	for (curfile = file_list; curfile != NULL; curfile = curfile->next) {
553274116Sdteske		keep_going = TRUE;
554274116Sdteske		output_out = -1;
555274116Sdteske		pct = 0;
556274116Sdteske		nthfile++;
557274116Sdteske		files_left--;
558274116Sdteske
559274116Sdteske		if (dpv_interrupt)
560274116Sdteske			break;
561274116Sdteske		if (dialog_test)
562274116Sdteske			pct = 0 - increment;
563274116Sdteske
564274116Sdteske		/* Attempt to spawn output program for this file */
565274116Sdteske		if (!dialog_test && output != NULL) {
566274116Sdteske			mask = umask(0022);
567274116Sdteske			(void)umask(mask);
568274116Sdteske
569274116Sdteske			switch (output_type) {
570274116Sdteske			case DPV_OUTPUT_SHELL:
571274116Sdteske				output_out = shell_spawn_pipecmd(output,
572274116Sdteske				    curfile->name, &output_pid);
573274116Sdteske				break;
574274116Sdteske			case DPV_OUTPUT_FILE:
575274116Sdteske				path_fmt = fmtcheck(output, "%s");
576274116Sdteske				if (path_fmt == output)
577274116Sdteske					len = snprintf(pathbuf,
578274116Sdteske					    PATH_MAX, output, curfile->name);
579274116Sdteske				else
580274116Sdteske					len = snprintf(pathbuf,
581274116Sdteske					    PATH_MAX, "%s", output);
582274116Sdteske				if (len >= PATH_MAX) {
583274116Sdteske					warnx("%s:%d:%s: pathbuf[%u] too small"
584274116Sdteske					    "to hold output argument",
585274116Sdteske					    __FILE__, __LINE__, __func__,
586274116Sdteske					    PATH_MAX);
587274116Sdteske					return (-1);
588274116Sdteske				}
589274116Sdteske				if ((output_out = open(pathbuf,
590274116Sdteske				    O_CREAT|O_WRONLY, DEFFILEMODE & ~mask))
591274116Sdteske				    < 0) {
592274116Sdteske					warn("%s", pathbuf);
593274116Sdteske					return (-1);
594274116Sdteske				}
595274116Sdteske				break;
596274116Sdteske			default:
597274116Sdteske				break;
598274116Sdteske			}
599274116Sdteske		}
600274116Sdteske
601274116Sdteske		while (!dpv_interrupt && keep_going) {
602274116Sdteske			if (dialog_test) {
603274116Sdteske				usleep(50000);
604274116Sdteske				pct += increment;
605274116Sdteske				dpv_overall_read +=
606274116Sdteske				    (int)(random() / 512 / dpv_nfiles);
607274116Sdteske				    /* 512 limits fake readout to Megabytes */
608274116Sdteske			} else if (action != NULL)
609274116Sdteske				pct = action(curfile, output_out);
610274116Sdteske
611274116Sdteske			if (no_overrun || dialog_test)
612274116Sdteske				keep_going = (pct < 100);
613274116Sdteske			else {
614274116Sdteske				status = curfile->status;
615274116Sdteske				keep_going = (status == DPV_STATUS_RUNNING);
616274116Sdteske			}
617274116Sdteske
618274116Sdteske			/* Get current time and calculate seconds elapsed */
619274116Sdteske			gettimeofday(&now, (struct timezone *)NULL);
620274116Sdteske			now.tv_sec = now.tv_sec - start.tv_sec;
621274116Sdteske			now.tv_usec = now.tv_usec - start.tv_usec;
622274116Sdteske			if (now.tv_usec < 0)
623274116Sdteske				now.tv_sec--, now.tv_usec += 1000000;
624274116Sdteske			seconds = now.tv_sec + (now.tv_usec / 1000000.0);
625274116Sdteske
626274116Sdteske			/* Update dialog (be it dialog(3), dialog(1), etc.) */
627274116Sdteske			if ((dialog_updates_per_second != 0 &&
628274116Sdteske			   (
629274116Sdteske			    seconds != dialog_old_seconds ||
630274116Sdteske			    now.tv_usec - dialog_last_update >=
631274116Sdteske			        dialog_update_usec ||
632274116Sdteske			    nthfile != dialog_old_nthfile
633274116Sdteske			   )) || pct == 100
634274116Sdteske			) {
635274116Sdteske				/* Calculate overall progress (rounding up) */
636274116Sdteske				overall = (100 * nthfile - 100 + pct) /
637274116Sdteske				    dpv_nfiles;
638274116Sdteske				if (((100 * nthfile - 100 + pct) * 10 /
639274116Sdteske				    dpv_nfiles % 100) > 50)
640274116Sdteske					overall++;
641274116Sdteske
642274116Sdteske				dprompt_recreate(list_head, curfile, pct);
643274116Sdteske
644274116Sdteske				if (use_libdialog && !debug) {
645274116Sdteske					/* Update dialog(3) widget */
646274116Sdteske					dprompt_libprint(pprompt, aprompt,
647274116Sdteske					    overall);
648274116Sdteske				} else {
649274116Sdteske					/* stdout, dialog(1), or Xdialog(1) */
650274116Sdteske					dprompt_dprint(dialog_out, pprompt,
651274116Sdteske					    aprompt, overall);
652274116Sdteske					fsync(dialog_out);
653274116Sdteske				}
654274116Sdteske				dialog_old_seconds = seconds;
655274116Sdteske				dialog_old_nthfile = nthfile;
656274116Sdteske				dialog_last_update = now.tv_usec;
657274116Sdteske			}
658274116Sdteske
659274116Sdteske			/* Update the status line */
660274116Sdteske			if ((use_libdialog && !debug) &&
661274116Sdteske			    status_updates_per_second != 0 &&
662274116Sdteske			   (
663274116Sdteske			    keep_going != TRUE ||
664274116Sdteske			    seconds != status_old_seconds ||
665274116Sdteske			    now.tv_usec - status_last_update >=
666274116Sdteske			        status_update_usec ||
667274116Sdteske			    nthfile != status_old_nthfile
668274116Sdteske			   )
669274116Sdteske			) {
670274116Sdteske				status_printf(status_fmt, dpv_overall_read,
671274116Sdteske				    (dpv_overall_read / (seconds == 0 ? 1 :
672274116Sdteske					seconds) * 1.0),
673274116Sdteske				    1, /* XXX until we add parallelism XXX */
674274116Sdteske				    files_left);
675274116Sdteske				status_old_seconds = seconds;
676274116Sdteske				status_old_nthfile = nthfile;
677274116Sdteske				status_last_update = now.tv_usec;
678274116Sdteske			}
679274116Sdteske		}
680274116Sdteske
681274116Sdteske		if (!dialog_test && output_out >= 0) {
682274116Sdteske			close(output_out);
683274116Sdteske			waitpid(output_pid, (int *)NULL, 0);
684274116Sdteske		}
685274116Sdteske
686274116Sdteske		if (dpv_abort)
687274116Sdteske			break;
688274116Sdteske
689274116Sdteske		/* Advance head of list when we hit the max display lines */
690274116Sdteske		if (display_limit > 0 && nthfile % display_limit == 0)
691274116Sdteske			list_head = curfile->next;
692274116Sdteske	}
693274116Sdteske
694274116Sdteske	if (!debug) {
695274116Sdteske		if (use_libdialog)
696274116Sdteske			end_dialog();
697274116Sdteske		else {
698274116Sdteske			close(dialog_out);
699274116Sdteske			waitpid(pid, (int *)NULL, 0);
700274116Sdteske		}
701294860Sdteske		if (!keep_tite && !dpv_interrupt)
702274116Sdteske			printf("\n");
703274116Sdteske	} else
704283863Sdteske		warnx("%s: %lli overall read", __func__, dpv_overall_read);
705274116Sdteske
706274116Sdteske	if (dpv_interrupt || dpv_abort)
707274116Sdteske		return (-1);
708274116Sdteske	else
709274116Sdteske		return (0);
710274116Sdteske}
711274116Sdteske
712274116Sdteske/*
713274116Sdteske * Free allocated items initialized by dpv()
714274116Sdteske */
715274116Sdteskevoid
716274116Sdteskedpv_free(void)
717274116Sdteske{
718274116Sdteske	dialogrc_free();
719274116Sdteske	dprompt_free();
720274116Sdteske	dialog_maxsize_free();
721274116Sdteske	if (aprompt != NULL) {
722274116Sdteske		free(aprompt);
723274116Sdteske		aprompt = NULL;
724274116Sdteske	}
725274116Sdteske	if (pprompt != NULL) {
726274116Sdteske		free(pprompt);
727274116Sdteske		pprompt = NULL;
728274116Sdteske	}
729274116Sdteske	status_free();
730274116Sdteske}
731