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