Index: stable/10/lib/libdpv/dprompt.c =================================================================== --- stable/10/lib/libdpv/dprompt.c (revision 284713) +++ stable/10/lib/libdpv/dprompt.c (revision 284714) @@ -1,770 +1,767 @@ /*- * Copyright (c) 2013-2014 Devin Teske * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */ #include #include #include #include #include #include #include #include #include #include "dialog_util.h" #include "dialogrc.h" #include "dprompt.h" #include "dpv.h" #include "dpv_private.h" #define FLABEL_MAX 1024 static int fheight = 0; /* initialized by dprompt_init() */ static char dprompt[PROMPT_MAX + 1] = ""; static char *dprompt_pos = (char *)(0); /* treated numerically */ /* Display characteristics */ #define FM_DONE 0x01 #define FM_FAIL 0x02 #define FM_PEND 0x04 static uint8_t dprompt_free_mask; static char *done = NULL; static char *fail = NULL; static char *pend = NULL; int display_limit = DISPLAY_LIMIT_DEFAULT; /* Max entries to show */ int label_size = LABEL_SIZE_DEFAULT; /* Max width for labels */ int pbar_size = PBAR_SIZE_DEFAULT; /* Mini-progressbar size */ static int gauge_percent = 0; static int done_size, done_lsize, done_rsize; static int fail_size, fail_lsize, fail_rsize; static int mesg_size, mesg_lsize, mesg_rsize; static int pend_size, pend_lsize, pend_rsize; static int pct_lsize, pct_rsize; static void *gauge = NULL; #define SPIN_SIZE 4 static char spin[SPIN_SIZE + 1] = "/-\\|"; static char msg[PROMPT_MAX + 1]; static char *spin_cp = spin; /* Function prototypes */ static char spin_char(void); static int dprompt_add_files(struct dpv_file_node *file_list, struct dpv_file_node *curfile, int pct); /* * Returns a pointer to the current spin character in the spin string and * advances the global position to the next character for the next call. */ static char spin_char(void) { char ch; if (spin_cp == '\0') spin_cp = spin; ch = *spin_cp; /* Advance the spinner to the next char */ if (++spin_cp >= (spin + SPIN_SIZE)) spin_cp = spin; return (ch); } /* * Initialize heights and widths based on various strings and environment * variables (such as ENV_USE_COLOR). */ void dprompt_init(struct dpv_file_node *file_list) { uint8_t nls = 0; int len; int max_cols; int max_rows; int nthfile; int numlines; struct dpv_file_node *curfile; /* * Initialize dialog(3) `colors' support and draw backtitle */ if (use_libdialog && !debug) { init_dialog(stdin, stdout); dialog_vars.colors = 1; if (backtitle != NULL) { dialog_vars.backtitle = (char *)backtitle; dlg_put_backtitle(); } } /* Calculate width of dialog(3) or [X]dialog(1) --gauge box */ dwidth = label_size + pbar_size + 9; /* * Calculate height of dialog(3) or [X]dialog(1) --gauge box */ dheight = 5; max_rows = dialog_maxrows(); /* adjust max_rows for backtitle and/or dialog(3) statusLine */ if (backtitle != NULL) max_rows -= use_shadow ? 3 : 2; if (use_libdialog && use_shadow) max_rows -= 2; /* add lines for `-p text' */ numlines = dialog_prompt_numlines(pprompt, 0); if (debug) warnx("`-p text' is %i line%s long", numlines, numlines == 1 ? "" : "s"); dheight += numlines; /* adjust dheight for various implementations */ if (use_dialog) { dheight -= dialog_prompt_nlstate(pprompt); nls = dialog_prompt_nlstate(pprompt); } else if (use_xdialog) { if (pprompt == NULL || *pprompt == '\0') dheight++; } else if (use_libdialog) { if (pprompt != NULL && *pprompt != '\0') dheight--; } /* limit the number of display items (necessary per dialog(1,3)) */ if (display_limit == 0 || display_limit > DPV_DISPLAY_LIMIT) display_limit = DPV_DISPLAY_LIMIT; /* verify fheight will fit (stop if we hit 1) */ for (; display_limit > 0; display_limit--) { nthfile = numlines = 0; fheight = (int)dpv_nfiles > display_limit ? (unsigned int)display_limit : dpv_nfiles; for (curfile = file_list; curfile != NULL; curfile = curfile->next) { nthfile++; numlines += dialog_prompt_numlines(curfile->name, nls); if ((nthfile % display_limit) == 0) { if (numlines > fheight) fheight = numlines; numlines = nthfile = 0; } } if (numlines > fheight) fheight = numlines; if ((dheight + fheight + (int)dialog_prompt_numlines(aprompt, use_dialog) - (use_dialog ? (int)dialog_prompt_nlstate(aprompt) : 0)) <= max_rows) break; } /* don't show any items if we run the risk of hitting a blank set */ if ((max_rows - (use_shadow ? 5 : 4)) >= fheight) dheight += fheight; else fheight = 0; /* add lines for `-a text' */ numlines = dialog_prompt_numlines(aprompt, use_dialog); if (debug) warnx("`-a text' is %i line%s long", numlines, numlines == 1 ? "" : "s"); dheight += numlines; /* If using Xdialog(1), adjust accordingly (based on testing) */ if (use_xdialog) dheight += dheight / 4; /* For wide mode, long prefix (`pprompt') or append (`aprompt') * strings will bump width */ if (wide) { len = (int)dialog_prompt_longestline(pprompt, 0); /* !nls */ if ((len + 4) > dwidth) dwidth = len + 4; len = (int)dialog_prompt_longestline(aprompt, 1); /* nls */ if ((len + 4) > dwidth) dwidth = len + 4; } /* Enforce width constraints to maximum values */ max_cols = dialog_maxcols(); if (max_cols > 0 && dwidth > max_cols) dwidth = max_cols; /* Optimize widths to sane values*/ if (pbar_size > dwidth - 9) { pbar_size = dwidth - 9; label_size = 0; /* -9 = "| - [" ... "] |" */ } if (pbar_size < 0) label_size = dwidth - 8; /* -8 = "| " ... " - |" */ else if (label_size > (dwidth - pbar_size - 9) || wide) label_size = no_labels ? 0 : dwidth - pbar_size - 9; /* -9 = "| " ... " - [" ... "] |" */ /* Hide labels if requested */ if (no_labels) label_size = 0; /* Touch up the height (now that we know dwidth) */ dheight += dialog_prompt_wrappedlines(pprompt, dwidth - 4, 0); dheight += dialog_prompt_wrappedlines(aprompt, dwidth - 4, 1); if (debug) warnx("dheight = %i dwidth = %i fheight = %i", dheight, dwidth, fheight); /* Calculate left/right portions of % */ pct_lsize = (pbar_size - 4) / 2; /* -4 == printf("%-3s%%", pct) */ pct_rsize = pct_lsize; /* If not evenly divisible by 2, increment the right-side */ if ((pct_rsize + pct_rsize + 4) != pbar_size) pct_rsize++; /* Initialize "Done" text */ if (done == NULL && (done = msg_done) == NULL) { if ((done = getenv(ENV_MSG_DONE)) != NULL) done_size = strlen(done); else { done_size = strlen(DPV_DONE_DEFAULT); if ((done = malloc(done_size + 1)) == NULL) errx(EXIT_FAILURE, "Out of memory?!"); dprompt_free_mask |= FM_DONE; snprintf(done, done_size + 1, DPV_DONE_DEFAULT); } } if (pbar_size < done_size) { done_lsize = done_rsize = 0; *(done + pbar_size) = '\0'; done_size = pbar_size; } else { /* Calculate left/right portions for mini-progressbar */ done_lsize = (pbar_size - done_size) / 2; done_rsize = done_lsize; /* If not evenly divisible by 2, increment the right-side */ if ((done_rsize + done_size + done_lsize) != pbar_size) done_rsize++; } /* Initialize "Fail" text */ if (fail == NULL && (fail = msg_fail) == NULL) { if ((fail = getenv(ENV_MSG_FAIL)) != NULL) fail_size = strlen(fail); else { fail_size = strlen(DPV_FAIL_DEFAULT); if ((fail = malloc(fail_size + 1)) == NULL) errx(EXIT_FAILURE, "Out of memory?!"); dprompt_free_mask |= FM_FAIL; snprintf(fail, fail_size + 1, DPV_FAIL_DEFAULT); } } if (pbar_size < fail_size) { fail_lsize = fail_rsize = 0; *(fail + pbar_size) = '\0'; fail_size = pbar_size; } else { /* Calculate left/right portions for mini-progressbar */ fail_lsize = (pbar_size - fail_size) / 2; fail_rsize = fail_lsize; /* If not evenly divisible by 2, increment the right-side */ if ((fail_rsize + fail_size + fail_lsize) != pbar_size) fail_rsize++; } /* Initialize "Pending" text */ if (pend == NULL && (pend = msg_pending) == NULL) { if ((pend = getenv(ENV_MSG_PENDING)) != NULL) pend_size = strlen(pend); else { pend_size = strlen(DPV_PENDING_DEFAULT); if ((pend = malloc(pend_size + 1)) == NULL) errx(EXIT_FAILURE, "Out of memory?!"); dprompt_free_mask |= FM_PEND; snprintf(pend, pend_size + 1, DPV_PENDING_DEFAULT); } } if (pbar_size < pend_size) { pend_lsize = pend_rsize = 0; *(pend + pbar_size) = '\0'; pend_size = pbar_size; } else { /* Calculate left/right portions for mini-progressbar */ pend_lsize = (pbar_size - pend_size) / 2; pend_rsize = pend_lsize; /* If not evenly divisible by 2, increment the right-side */ if ((pend_rsize + pend_lsize + pend_size) != pbar_size) pend_rsize++; } if (debug) warnx("label_size = %i pbar_size = %i", label_size, pbar_size); dprompt_clear(); } /* * Clear the [X]dialog(1) `--gauge' prompt buffer. */ void dprompt_clear(void) { *dprompt = '\0'; dprompt_pos = dprompt; } /* * Append to the [X]dialog(1) `--gauge' prompt buffer. Syntax is like printf(3) * and returns the number of bytes appended to the buffer. */ int dprompt_add(const char *format, ...) { int len; va_list ap; if (dprompt_pos >= (dprompt + PROMPT_MAX)) return (0); va_start(ap, format); len = vsnprintf(dprompt_pos, (size_t)(PROMPT_MAX - (dprompt_pos - dprompt)), format, ap); va_end(ap); if (len == -1) errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow", __func__); if ((dprompt_pos + len) < (dprompt + PROMPT_MAX)) dprompt_pos += len; else dprompt_pos = dprompt + PROMPT_MAX; return (len); } /* * Append active files to the [X]dialog(1) `--gauge' prompt buffer. Syntax * requires a pointer to the head of the dpv_file_node linked-list. Returns the * number of files processed successfully. */ static int dprompt_add_files(struct dpv_file_node *file_list, struct dpv_file_node *curfile, int pct) { char c; char bold_code = 'b'; /* default: enabled */ char color_code = '4'; /* default: blue */ uint8_t after_curfile = curfile != NULL ? FALSE : TRUE; uint8_t nls = 0; char *cp; char *lastline; char *name; const char *bg_code; const char *estext; const char *format; enum dprompt_state dstate; int estext_lsize; int estext_rsize; - int estext_size; int flabel_size; int hlen; int lsize; int nlines = 0; int nthfile = 0; int pwidth; int rsize; struct dpv_file_node *fp; char flabel[FLABEL_MAX + 1]; char human[32]; char pbar[pbar_size + 16]; /* +15 for optional color */ char pbar_cap[sizeof(pbar)]; char pbar_fill[sizeof(pbar)]; /* Override color defaults with that of main progress bar */ if (use_colors || use_shadow) { /* NB: shadow enables color */ color_code = gauge_color[0]; /* NB: str[1] aka bg is unused */ bold_code = gauge_color[2]; } /* * Create mini-progressbar for current file (if applicable) */ *pbar = '\0'; if (pbar_size >= 0 && pct >= 0 && curfile != NULL && (curfile->length >= 0 || dialog_test)) { snprintf(pbar, pbar_size + 1, "%*s%3u%%%*s", pct_lsize, "", pct, pct_rsize, ""); if (use_color) { /* Calculate the fill-width of progressbar */ pwidth = pct * pbar_size / 100; /* Round up based on one-tenth of a percent */ if ((pct * pbar_size % 100) > 50) pwidth++; /* * Make two copies of pbar. Make one represent the fill * and the other the remainder (cap). We'll insert the * ANSI delimiter in between. */ *pbar_fill = '\0'; *pbar_cap = '\0'; strncat(pbar_fill, (const char *)(pbar), dwidth); *(pbar_fill + pwidth) = '\0'; strncat(pbar_cap, (const char *)(pbar+pwidth), dwidth); /* Finalize the mini [color] progressbar */ snprintf(pbar, sizeof(pbar), "\\Z%c\\Zr\\Z%c%s%s%s\\Zn", bold_code, color_code, pbar_fill, "\\ZR", pbar_cap); } } for (fp = file_list; fp != NULL; fp = fp->next) { flabel_size = label_size; name = fp->name; nthfile++; /* * Support multiline filenames (where the filename is taken as * the last line and the text leading up to the last line can * be used as (for example) a heading/separator between files. */ if (use_dialog) nls = dialog_prompt_nlstate(pprompt); nlines += dialog_prompt_numlines(name, nls); lastline = dialog_prompt_lastline(name, 1); if (name != lastline) { c = *lastline; *lastline = '\0'; dprompt_add("%s", name); *lastline = c; name = lastline; } /* Support color codes (for dialog(1,3)) in file names */ if ((use_dialog || use_libdialog) && use_color) { cp = name; while (*cp != '\0') { if (*cp == '\\' && *(cp + 1) != '\0' && *(++cp) == 'Z' && *(cp + 1) != '\0') { cp++; flabel_size += 3; } cp++; } if (flabel_size > FLABEL_MAX) flabel_size = FLABEL_MAX; } /* If no mini-progressbar, increase label width */ if (pbar_size < 0 && flabel_size <= FLABEL_MAX - 2 && no_labels == FALSE) flabel_size += 2; /* If name is too long, add an ellipsis */ if (snprintf(flabel, flabel_size + 1, "%s", name) > flabel_size) sprintf(flabel + flabel_size - 3, "..."); /* * Append the label (processing the current file differently) */ if (fp == curfile && pct < 100) { /* * Add an ellipsis to current file name if it will fit. * There may be an ellipsis already from truncating the * label (in which case, we already have one). */ cp = flabel + strlen(flabel); if (cp < (flabel + flabel_size)) snprintf(cp, flabel_size - (cp - flabel) + 1, "..."); /* Append label (with spinner and optional color) */ dprompt_add("%s%-*s%s %c", use_color ? "\\Zb" : "", flabel_size, flabel, use_color ? "\\Zn" : "", spin_char()); } else dprompt_add("%-*s%s %s", flabel_size, flabel, use_color ? "\\Zn" : "", " "); /* * Append pbar/status (processing the current file differently) */ dstate = DPROMPT_NONE; if (fp->msg != NULL) dstate = DPROMPT_CUSTOM_MSG; else if (pbar_size < 0) dstate = DPROMPT_NONE; else if (pbar_size < 4) dstate = DPROMPT_MINIMAL; else if (after_curfile) dstate = DPROMPT_PENDING; else if (fp == curfile) { if (*pbar == '\0') { if (fp->length < 0) dstate = DPROMPT_DETAILS; else if (fp->status == DPV_STATUS_RUNNING) dstate = DPROMPT_DETAILS; else dstate = DPROMPT_END_STATE; } else if (dialog_test) /* status/length ignored */ dstate = pct < 100 ? DPROMPT_PBAR : DPROMPT_END_STATE; else if (fp->status == DPV_STATUS_RUNNING) dstate = fp->length < 0 ? DPROMPT_DETAILS : DPROMPT_PBAR; else /* not running */ dstate = fp->length < 0 ? DPROMPT_DETAILS : DPROMPT_END_STATE; } else { /* before curfile */ if (dialog_test) dstate = DPROMPT_END_STATE; else dstate = fp->length < 0 ? DPROMPT_DETAILS : DPROMPT_END_STATE; } format = use_color ? " [\\Z%c%s%-*s%s%-*s\\Zn]\\n" : " [%-*s%s%-*s]\\n"; if (fp->status == DPV_STATUS_FAILED) { bg_code = "\\Zr\\Z1"; /* Red */ estext_lsize = fail_lsize; estext_rsize = fail_rsize; - estext_size = fail_size; estext = fail; } else { /* e.g., DPV_STATUS_DONE */ bg_code = "\\Zr\\Z2"; /* Green */ estext_lsize = done_lsize; estext_rsize = done_rsize; - estext_size = done_size; estext = done; } switch (dstate) { case DPROMPT_PENDING: /* Future file(s) */ dprompt_add(" [%-*s%s%-*s]\\n", pend_lsize, "", pend, pend_rsize, ""); break; case DPROMPT_PBAR: /* Current file */ dprompt_add(" [%s]\\n", pbar); break; case DPROMPT_END_STATE: /* Past/Current file(s) */ if (use_color) dprompt_add(format, bold_code, bg_code, estext_lsize, "", estext, estext_rsize, ""); else dprompt_add(format, estext_lsize, "", estext, estext_rsize, ""); break; case DPROMPT_DETAILS: /* Past/Current file(s) */ humanize_number(human, pbar_size + 2, fp->read, "", HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000); /* Calculate center alignment */ hlen = (int)strlen(human); lsize = (pbar_size - hlen) / 2; rsize = lsize; if ((lsize+hlen+rsize) != pbar_size) rsize++; if (use_color) dprompt_add(format, bold_code, bg_code, lsize, "", human, rsize, ""); else dprompt_add(format, lsize, "", human, rsize, ""); break; case DPROMPT_CUSTOM_MSG: /* File-specific message override */ snprintf(msg, PROMPT_MAX + 1, "%s", fp->msg); if (pbar_size < (mesg_size = strlen(msg))) { mesg_lsize = mesg_rsize = 0; *(msg + pbar_size) = '\0'; mesg_size = pbar_size; } else { mesg_lsize = (pbar_size - mesg_size) / 2; mesg_rsize = mesg_lsize; if ((mesg_rsize + mesg_size + mesg_lsize) != pbar_size) mesg_rsize++; } if (use_color) dprompt_add(format, bold_code, bg_code, mesg_lsize, "", msg, mesg_rsize, ""); else dprompt_add(format, mesg_lsize, "", msg, mesg_rsize, ""); break; case DPROMPT_MINIMAL: /* Short progress bar, minimal room */ if (use_color) dprompt_add(format, bold_code, bg_code, pbar_size, "", "", 0, ""); else dprompt_add(format, pbar_size, "", "", 0, ""); break; case DPROMPT_NONE: /* pbar_size < 0 */ /* FALLTHROUGH */ default: dprompt_add(" \\n"); /* * NB: Leading space required for the case when * spin_char() returns a single backslash [\] which * without the space, changes the meaning of `\n' */ } /* Stop building if we've hit the internal limit */ if (nthfile >= display_limit) break; /* If this is the current file, all others are pending */ if (fp == curfile) after_curfile = TRUE; } /* * Since we cannot change the height/width of the [X]dialog(1) widget * after spawn, to make things look nice let's pad the height so that * the `-a text' always appears in the same spot. * * NOTE: fheight is calculated in dprompt_init(). It represents the * maximum height required to display the set of items (broken up into * pieces of display_limit chunks) whose names contain the most * newlines for any given set. */ while (nlines < fheight) { dprompt_add("\n"); nlines++; } return (nthfile); } /* * Process the dpv_file_node linked-list of named files, re-generating the * [X]dialog(1) `--gauge' prompt text for the current state of transfers. */ void dprompt_recreate(struct dpv_file_node *file_list, struct dpv_file_node *curfile, int pct) { size_t len; /* * Re-Build the prompt text */ dprompt_clear(); if (display_limit > 0) dprompt_add_files(file_list, curfile, pct); /* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */ if (use_xdialog) { /* Replace `\n' with `\n\\n\n' in dprompt */ len = strlen(dprompt); len += strcount(dprompt, "\\n") * 5; /* +5 chars per count */ if (len > PROMPT_MAX) errx(EXIT_FAILURE, "%s: Oops, dprompt buffer overflow " "(%zu > %i)", __func__, len, PROMPT_MAX); if (replaceall(dprompt, "\\n", "\n\\n\n") < 0) err(EXIT_FAILURE, "%s: replaceall()", __func__); } else if (use_libdialog) strexpandnl(dprompt); } /* * Print the [X]dialog(1) `--gauge' prompt text to a buffer. */ int dprompt_sprint(char * restrict str, const char *prefix, const char *append) { return (snprintf(str, PROMPT_MAX, "%s%s%s%s", use_color ? "\\Zn" : "", prefix ? prefix : "", dprompt, append ? append : "")); } /* * Print the [X]dialog(1) `--gauge' prompt text to file descriptor fd (could * be STDOUT_FILENO or a pipe(2) file descriptor to actual [X]dialog(1)). */ void dprompt_dprint(int fd, const char *prefix, const char *append, int overall) { int percent = gauge_percent; if (overall >= 0 && overall <= 100) gauge_percent = percent = overall; dprintf(fd, "XXX\n%s%s%s%s\nXXX\n%i\n", use_color ? "\\Zn" : "", prefix ? prefix : "", dprompt, append ? append : "", percent); fsync(fd); } /* * Print the dialog(3) `gauge' prompt text using libdialog. */ void dprompt_libprint(const char *prefix, const char *append, int overall) { int percent = gauge_percent; char buf[DPV_PPROMPT_MAX + DPV_APROMPT_MAX + DPV_DISPLAY_LIMIT * 1024]; dprompt_sprint(buf, prefix, append); if (overall >= 0 && overall <= 100) gauge_percent = percent = overall; gauge = dlg_reallocate_gauge(gauge, title == NULL ? "" : title, buf, dheight, dwidth, percent); dlg_update_gauge(gauge, percent); } /* * Free allocated items initialized by dprompt_init() */ void dprompt_free(void) { if ((dprompt_free_mask & FM_DONE) != 0) { dprompt_free_mask ^= FM_DONE; free(done); done = NULL; } if ((dprompt_free_mask & FM_FAIL) != 0) { dprompt_free_mask ^= FM_FAIL; free(fail); fail = NULL; } if ((dprompt_free_mask & FM_PEND) != 0) { dprompt_free_mask ^= FM_PEND; free(pend); pend = NULL; } } Index: stable/10/lib/libdpv/dpv.c =================================================================== --- stable/10/lib/libdpv/dpv.c (revision 284713) +++ stable/10/lib/libdpv/dpv.c (revision 284714) @@ -1,721 +1,721 @@ /*- * Copyright (c) 2013-2014 Devin Teske * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dialog_util.h" #include "dialogrc.h" #include "dprompt.h" #include "dpv.h" #include "dpv_private.h" #include "status.h" #include "util.h" /* Test Mechanics (Only used when dpv_config.options |= DPV_TEST_MODE) */ #define INCREMENT 1 /* Increment % per-pass test-mode */ #define XDIALOG_INCREMENT 15 /* different for slower Xdialog(1) */ static uint8_t increment = INCREMENT; /* Debugging */ uint8_t debug = FALSE; /* Data to process */ int dpv_interrupt = FALSE; int dpv_abort = FALSE; unsigned int dpv_nfiles = 0; /* Data processing */ long long dpv_overall_read = 0; static char pathbuf[PATH_MAX]; /* Extra display information */ uint8_t no_labels = FALSE; /* dpv_config.options & DPV_NO_LABELS */ uint8_t wide = FALSE; /* dpv_config.options & DPV_WIDE_MODE */ char *aprompt = NULL; /* dpv_config.aprompt */ char *msg_done = NULL; /* dpv_config.msg_done */ char *msg_fail = NULL; /* dpv_config.msg_fail */ char *msg_pending = NULL; /* dpv_config.msg_pending */ char *pprompt = NULL; /* dpv_config.pprompt */ /* Status-Line format for when using dialog(3) */ static const char *status_format_custom = NULL; static char status_format_default[DPV_STATUS_FORMAT_MAX]; /* * Takes a pointer to a dpv_config structure containing layout details and * pointer to initial element in a linked-list of dpv_file_node structures, * each presenting a file to process. Executes the `action' function passed-in * as a member to the `config' structure argument. */ int dpv(struct dpv_config *config, struct dpv_file_node *file_list) { char c; uint8_t keep_going; uint8_t nls = FALSE; /* See dialog_prompt_nlstate() */ uint8_t no_overrun = FALSE; uint8_t pprompt_nls = FALSE; /* See dialog_prompt_nlstate() */ uint8_t shrink_label_size = FALSE; mode_t mask; uint16_t options; char *cp; char *fc; char *last; char *name; char *output; const char *status_fmt; const char *path_fmt; enum dpv_display display_type; enum dpv_output output_type; enum dpv_status status; int (*action)(struct dpv_file_node *file, int out); int backslash; int dialog_last_update = 0; int dialog_old_nthfile = 0; int dialog_old_seconds = -1; int dialog_out = STDOUT_FILENO; int dialog_update_usec = 0; int dialog_updates_per_second; int files_left; int max_cols; int nthfile = 0; int output_out; int overall = 0; int pct; int res; int seconds; int status_last_update = 0; int status_old_nthfile = 0; int status_old_seconds = -1; int status_update_usec = 0; int status_updates_per_second; pid_t output_pid; pid_t pid; size_t len; struct dpv_file_node *curfile; struct dpv_file_node *first_file; struct dpv_file_node *list_head; struct timeval now; struct timeval start; char init_prompt[PROMPT_MAX + 1] = ""; /* Initialize globals to default values */ aprompt = NULL; pprompt = NULL; options = 0; action = NULL; backtitle = NULL; debug = FALSE; dialog_test = FALSE; dialog_updates_per_second = DIALOG_UPDATES_PER_SEC; display_limit = DISPLAY_LIMIT_DEFAULT; display_type = DPV_DISPLAY_LIBDIALOG; label_size = LABEL_SIZE_DEFAULT; msg_done = NULL; msg_fail = NULL; msg_pending = NULL; no_labels = FALSE; output = NULL; output_type = DPV_OUTPUT_NONE; pbar_size = PBAR_SIZE_DEFAULT; status_format_custom = NULL; status_updates_per_second = STATUS_UPDATES_PER_SEC; title = NULL; wide = FALSE; /* Process config options (overriding defaults) */ if (config != NULL) { if (config->aprompt != NULL) { if (aprompt == NULL) { aprompt = malloc(DPV_APROMPT_MAX); if (aprompt == NULL) return (-1); } snprintf(aprompt, DPV_APROMPT_MAX, "%s", config->aprompt); } if (config->pprompt != NULL) { if (pprompt == NULL) { pprompt = malloc(DPV_PPROMPT_MAX + 2); /* +2 is for implicit "\n" appended later */ if (pprompt == NULL) return (-1); } snprintf(pprompt, DPV_APROMPT_MAX, "%s", config->pprompt); } options = config->options; action = config->action; backtitle = config->backtitle; debug = config->debug; dialog_test = ((options & DPV_TEST_MODE) != 0); dialog_updates_per_second = config->dialog_updates_per_second; display_limit = config->display_limit; display_type = config->display_type; label_size = config->label_size; msg_done = (char *)config->msg_done; msg_fail = (char *)config->msg_fail; msg_pending = (char *)config->msg_pending; no_labels = ((options & DPV_NO_LABELS) != 0); no_overrun = ((options & DPV_NO_OVERRUN) != 0); output = config->output; output_type = config->output_type; pbar_size = config->pbar_size; status_updates_per_second = config->status_updates_per_second; title = config->title; wide = ((options & DPV_WIDE_MODE) != 0); /* Enforce some minimums (pedantic) */ if (display_limit < -1) display_limit = -1; if (label_size < -1) label_size = -1; if (pbar_size < -1) pbar_size = -1; /* For the mini-pbar, -1 means hide, zero is invalid unless * only one file is given */ if (pbar_size == 0) { if (file_list == NULL || file_list->next == NULL) pbar_size = -1; else pbar_size = PBAR_SIZE_DEFAULT; } /* For the label, -1 means auto-size, zero is invalid unless * specifically requested through the use of options flag */ if (label_size == 0 && no_labels == FALSE) label_size = LABEL_SIZE_DEFAULT; /* Status update should not be zero */ if (status_updates_per_second == 0) status_updates_per_second = STATUS_UPDATES_PER_SEC; } /* config != NULL */ /* Process the type of display we've been requested to produce */ switch (display_type) { case DPV_DISPLAY_STDOUT: debug = TRUE; use_color = FALSE; use_dialog = FALSE; use_libdialog = FALSE; use_xdialog = FALSE; break; case DPV_DISPLAY_DIALOG: use_color = TRUE; use_dialog = TRUE; use_libdialog = FALSE; use_xdialog = FALSE; break; case DPV_DISPLAY_XDIALOG: snprintf(dialog, PATH_MAX, XDIALOG); use_color = FALSE; use_dialog = FALSE; use_libdialog = FALSE; use_xdialog = TRUE; break; default: use_color = TRUE; use_dialog = FALSE; use_libdialog = TRUE; use_xdialog = FALSE; break; } /* display_type */ /* Enforce additional minimums that require knowing our display type */ if (dialog_updates_per_second == 0) dialog_updates_per_second = use_xdialog ? XDIALOG_UPDATES_PER_SEC : DIALOG_UPDATES_PER_SEC; /* Allow forceful override of use_color */ if (config != NULL && (config->options & DPV_USE_COLOR) != 0) use_color = TRUE; /* Count the number of files in provided list of dpv_file_node's */ if (use_dialog && pprompt != NULL && *pprompt != '\0') pprompt_nls = dialog_prompt_nlstate(pprompt); max_cols = dialog_maxcols(); if (label_size == -1) shrink_label_size = TRUE; /* Process file arguments */ for (curfile = file_list; curfile != NULL; curfile = curfile->next) { dpv_nfiles++; /* dialog(3) only expands literal newlines */ if (use_libdialog) strexpandnl(curfile->name); /* Optionally calculate label size for file */ if (shrink_label_size) { nls = FALSE; name = curfile->name; if (curfile == file_list) nls = pprompt_nls; last = (char *)dialog_prompt_lastline(name, nls); if (use_dialog) { c = *last; *last = '\0'; nls = dialog_prompt_nlstate(name); *last = c; } len = dialog_prompt_longestline(last, nls); if ((int)len > (label_size - 3)) { if (label_size > 0) label_size += 3; label_size = len; /* Room for ellipsis (unless NULL) */ if (label_size > 0) label_size += 3; } if (max_cols > 0 && label_size > (max_cols - pbar_size - 9)) label_size = max_cols - pbar_size - 9; } if (debug) warnx("label=[%s] path=[%s] size=%lli", curfile->name, curfile->path, curfile->length); } /* file_list */ /* Optionally process the contents of DIALOGRC (~/.dialogrc) */ if (use_dialog) { res = parse_dialogrc(); if (debug && res == 0) { warnx("Successfully read `%s' config file", DIALOGRC); warnx("use_shadow = %i (Boolean)", use_shadow); warnx("use_colors = %i (Boolean)", use_colors); warnx("gauge_color=[%s] (FBH)", gauge_color); } } else if (use_libdialog) { init_dialog(stdin, stdout); use_shadow = dialog_state.use_shadow; use_colors = dialog_state.use_colors; gauge_color[0] = 48 + dlg_color_table[GAUGE_ATTR].fg; gauge_color[1] = 48 + dlg_color_table[GAUGE_ATTR].bg; gauge_color[2] = dlg_color_table[GAUGE_ATTR].hilite ? 'b' : 'B'; gauge_color[3] = '\0'; end_dialog(); if (debug) { warnx("Finished initializing dialog(3) library"); warnx("use_shadow = %i (Boolean)", use_shadow); warnx("use_colors = %i (Boolean)", use_colors); warnx("gauge_color=[%s] (FBH)", gauge_color); } } /* Enable mini progress bar automatically for stdin streams if unable * to calculate progress (missing `lines:' syntax). */ if (dpv_nfiles <= 1 && file_list != NULL && file_list->length < 0 && !dialog_test) pbar_size = PBAR_SIZE_DEFAULT; /* If $USE_COLOR is set and non-NULL enable color; otherwise disable */ if ((cp = getenv(ENV_USE_COLOR)) != 0) use_color = *cp != '\0' ? 1 : 0; /* Print error and return `-1' if not given at least one name */ if (dpv_nfiles == 0) { warnx("%s: no labels provided", __func__); return (-1); } else if (debug) warnx("%s: %u label%s provided", __func__, dpv_nfiles, dpv_nfiles == 1 ? "" : "s"); /* If only one file and pbar size is zero, default to `-1' */ if (dpv_nfiles <= 1 && pbar_size == 0) pbar_size = -1; /* Print some debugging information */ if (debug) { warnx("%s: %s(%i) max rows x cols = %i x %i", __func__, use_xdialog ? XDIALOG : DIALOG, use_libdialog ? 3 : 1, dialog_maxrows(), dialog_maxcols()); } /* Xdialog(1) updates a lot slower than dialog(1) */ if (dialog_test && use_xdialog) increment = XDIALOG_INCREMENT; /* Always add implicit newline to pprompt (when specified) */ if (pprompt != NULL && *pprompt != '\0') { len = strlen(pprompt); /* * NOTE: pprompt = malloc(PPROMPT_MAX + 2) * NOTE: (see getopt(2) section above for pprompt allocation) */ pprompt[len++] = '\\'; pprompt[len++] = 'n'; pprompt[len++] = '\0'; } /* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */ if (use_xdialog && pprompt != NULL) { /* Replace `\n' with `\n\\n\n' in pprompt */ len = strlen(pprompt); len += strcount(pprompt, "\\n") * 2; if (len > DPV_PPROMPT_MAX) errx(EXIT_FAILURE, "%s: Oops, pprompt buffer overflow " "(%zu > %i)", __func__, len, DPV_PPROMPT_MAX); if (replaceall(pprompt, "\\n", "\n\\n\n") < 0) err(EXIT_FAILURE, "%s: replaceall()", __func__); } /* libdialog requires literal newlines */ else if (use_libdialog && pprompt != NULL) strexpandnl(pprompt); /* Xdialog(1) requires newlines (a) escaped and (b) in triplicate */ if (use_xdialog && aprompt != NULL) { /* Replace `\n' with `\n\\n\n' in aprompt */ len = strlen(aprompt); len += strcount(aprompt, "\\n") * 2; if (len > DPV_APROMPT_MAX) errx(EXIT_FAILURE, "%s: Oops, aprompt buffer overflow " " (%zu > %i)", __func__, len, DPV_APROMPT_MAX); if (replaceall(aprompt, "\\n", "\n\\n\n") < 0) err(EXIT_FAILURE, "%s: replaceall()", __func__); } /* libdialog requires literal newlines */ else if (use_libdialog && aprompt != NULL) strexpandnl(aprompt); /* * Warn user about an obscure dialog(1) bug (neither Xdialog(1) nor * libdialog are affected) in the `--gauge' widget. If the first non- * whitespace letter of "{new_prompt}" in "XXX\n{new_prompt}\nXXX\n" * is a number, the number can sometimes be mistaken for a percentage * to the overall progressbar. Other nasty side-effects such as the * entire prompt not displaying or displaying improperly are caused by * this bug too. * * NOTE: When we can use color, we have a work-around... prefix the * output with `\Zn' (used to terminate ANSI and reset to normal). */ if (use_dialog && !use_color) { backslash = 0; /* First, check pprompt (falls through if NULL) */ fc = pprompt; while (fc != NULL && *fc != '\0') { if (*fc == '\n') /* leading literal newline OK */ break; if (!isspace(*fc) && *fc != '\\' && backslash == 0) break; else if (backslash > 0 && *fc != 'n') break; else if (*fc == '\\') { backslash++; if (backslash > 2) break; /* we're safe */ } fc++; } /* First non-whitespace character that dialog(1) will see */ if (fc != NULL && *fc >= '0' && *fc <= '9') warnx("%s: WARNING! text argument to `-p' begins with " "a number (not recommended)", __func__); else if (fc > pprompt) warnx("%s: WARNING! text argument to `-p' begins with " "whitespace (not recommended)", __func__); /* * If no pprompt or pprompt is all whitespace, check the first * file name provided to make sure it is alright too. */ if ((pprompt == NULL || *fc == '\0') && file_list != NULL) { first_file = file_list; fc = first_file->name; while (fc != NULL && *fc != '\0' && isspace(*fc)) fc++; /* First non-whitespace char that dialog(1) will see */ if (fc != NULL && *fc >= '0' && *fc <= '9') warnx("%s: WARNING! File name `%s' begins " "with a number (use `-p text' for safety)", __func__, first_file->name); } } dprompt_init(file_list); /* Reads: label_size pbar_size pprompt aprompt dpv_nfiles */ /* Inits: dheight and dwidth */ if (!debug) { /* Internally create the initial `--gauge' prompt text */ dprompt_recreate(file_list, (struct dpv_file_node *)NULL, 0); /* Spawn [X]dialog(1) `--gauge', returning pipe descriptor */ if (use_libdialog) { status_printf(""); dprompt_libprint(pprompt, aprompt, 0); } else { dprompt_sprint(init_prompt, pprompt, aprompt); dialog_out = dialog_spawn_gauge(init_prompt, &pid); dprompt_dprint(dialog_out, pprompt, aprompt, 0); } } /* !debug */ /* Seed the random(3) generator */ if (dialog_test) srandom(0xf1eeface); /* Set default/custom status line format */ if (dpv_nfiles > 1) { snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s", DPV_STATUS_MANY); status_format_custom = config->status_many; } else { snprintf(status_format_default, DPV_STATUS_FORMAT_MAX, "%s", DPV_STATUS_SOLO); status_format_custom = config->status_solo; } /* Add test mode identifier to default status line if enabled */ if (dialog_test && (strlen(status_format_default) + 12) < DPV_STATUS_FORMAT_MAX) strcat(status_format_default, " [TEST MODE]"); /* Verify custom status format */ status_fmt = fmtcheck(status_format_custom, status_format_default); if (status_format_custom != NULL && status_fmt == status_format_default) { warnx("WARNING! Invalid status_format configuration `%s'", status_format_custom); warnx("Default status_format `%s'", status_format_default); } /* Record when we started (used to prevent updating too quickly) */ (void)gettimeofday(&start, (struct timezone *)NULL); /* Calculate number of microseconds in-between sub-second updates */ if (status_updates_per_second != 0) status_update_usec = 1000000 / status_updates_per_second; if (dialog_updates_per_second != 0) dialog_update_usec = 1000000 / dialog_updates_per_second; /* * Process the file list [serially] (one for each argument passed) */ files_left = dpv_nfiles; list_head = file_list; for (curfile = file_list; curfile != NULL; curfile = curfile->next) { keep_going = TRUE; output_out = -1; pct = 0; nthfile++; files_left--; if (dpv_interrupt) break; if (dialog_test) pct = 0 - increment; /* Attempt to spawn output program for this file */ if (!dialog_test && output != NULL) { mask = umask(0022); (void)umask(mask); switch (output_type) { case DPV_OUTPUT_SHELL: output_out = shell_spawn_pipecmd(output, curfile->name, &output_pid); break; case DPV_OUTPUT_FILE: path_fmt = fmtcheck(output, "%s"); if (path_fmt == output) len = snprintf(pathbuf, PATH_MAX, output, curfile->name); else len = snprintf(pathbuf, PATH_MAX, "%s", output); if (len >= PATH_MAX) { warnx("%s:%d:%s: pathbuf[%u] too small" "to hold output argument", __FILE__, __LINE__, __func__, PATH_MAX); return (-1); } if ((output_out = open(pathbuf, O_CREAT|O_WRONLY, DEFFILEMODE & ~mask)) < 0) { warn("%s", pathbuf); return (-1); } break; default: break; } } while (!dpv_interrupt && keep_going) { if (dialog_test) { usleep(50000); pct += increment; dpv_overall_read += (int)(random() / 512 / dpv_nfiles); /* 512 limits fake readout to Megabytes */ } else if (action != NULL) pct = action(curfile, output_out); if (no_overrun || dialog_test) keep_going = (pct < 100); else { status = curfile->status; keep_going = (status == DPV_STATUS_RUNNING); } /* Get current time and calculate seconds elapsed */ gettimeofday(&now, (struct timezone *)NULL); now.tv_sec = now.tv_sec - start.tv_sec; now.tv_usec = now.tv_usec - start.tv_usec; if (now.tv_usec < 0) now.tv_sec--, now.tv_usec += 1000000; seconds = now.tv_sec + (now.tv_usec / 1000000.0); /* Update dialog (be it dialog(3), dialog(1), etc.) */ if ((dialog_updates_per_second != 0 && ( seconds != dialog_old_seconds || now.tv_usec - dialog_last_update >= dialog_update_usec || nthfile != dialog_old_nthfile )) || pct == 100 ) { /* Calculate overall progress (rounding up) */ overall = (100 * nthfile - 100 + pct) / dpv_nfiles; if (((100 * nthfile - 100 + pct) * 10 / dpv_nfiles % 100) > 50) overall++; dprompt_recreate(list_head, curfile, pct); if (use_libdialog && !debug) { /* Update dialog(3) widget */ dprompt_libprint(pprompt, aprompt, overall); } else { /* stdout, dialog(1), or Xdialog(1) */ dprompt_dprint(dialog_out, pprompt, aprompt, overall); fsync(dialog_out); } dialog_old_seconds = seconds; dialog_old_nthfile = nthfile; dialog_last_update = now.tv_usec; } /* Update the status line */ if ((use_libdialog && !debug) && status_updates_per_second != 0 && ( keep_going != TRUE || seconds != status_old_seconds || now.tv_usec - status_last_update >= status_update_usec || nthfile != status_old_nthfile ) ) { status_printf(status_fmt, dpv_overall_read, (dpv_overall_read / (seconds == 0 ? 1 : seconds) * 1.0), 1, /* XXX until we add parallelism XXX */ files_left); status_old_seconds = seconds; status_old_nthfile = nthfile; status_last_update = now.tv_usec; } } if (!dialog_test && output_out >= 0) { close(output_out); waitpid(output_pid, (int *)NULL, 0); } if (dpv_abort) break; /* Advance head of list when we hit the max display lines */ if (display_limit > 0 && nthfile % display_limit == 0) list_head = curfile->next; } if (!debug) { if (use_libdialog) end_dialog(); else { close(dialog_out); waitpid(pid, (int *)NULL, 0); } if (!dpv_interrupt) printf("\n"); } else - warnx("%s: %lli lines read", __func__, dpv_overall_read); + warnx("%s: %lli overall read", __func__, dpv_overall_read); if (dpv_interrupt || dpv_abort) return (-1); else return (0); } /* * Free allocated items initialized by dpv() */ void dpv_free(void) { dialogrc_free(); dprompt_free(); dialog_maxsize_free(); if (aprompt != NULL) { free(aprompt); aprompt = NULL; } if (pprompt != NULL) { free(pprompt); pprompt = NULL; } status_free(); } Index: stable/10 =================================================================== --- stable/10 (revision 284713) +++ stable/10 (revision 284714) Property changes on: stable/10 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r283863,283975