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 #include <sys/stat.h> 29 #include <sys/types.h> 30 31 #define _BSD_SOURCE /* to get dprintf() prototype in stdio.h below */ 32 #include <dialog.h> 33 #include <dpv.h> 34 #include <err.h> 35 #include <errno.h> 36 #include <fcntl.h> 37 #include <limits.h> 38 #include <signal.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 #include <string.h> 42 #include <string_m.h> 43 #include <unistd.h> 44 45 #include "dpv_util.h" 46 47 /* Debugging */ 48 static uint8_t debug = FALSE; 49 50 /* Data to process */ 51 static struct dpv_file_node *file_list = NULL; 52 static unsigned int nfiles = 0; 53 54 /* Data processing */ 55 static uint8_t line_mode = FALSE; 56 static uint8_t no_overrun = FALSE; 57 static char *buf = NULL; 58 static int fd = -1; 59 static int output_type = DPV_OUTPUT_NONE; 60 static size_t bsize; 61 static char rpath[PATH_MAX]; 62 63 /* Extra display information */ 64 static uint8_t multiple = FALSE; /* `-m' */ 65 static char *pgm; /* set to argv[0] by main() */ 66 67 /* Function prototypes */ 68 static void sig_int(int sig); 69 static void usage(void); 70 int main(int argc, char *argv[]); 71 static int operate_common(struct dpv_file_node *file, int out); 72 static int operate_on_bytes(struct dpv_file_node *file, int out); 73 static int operate_on_lines(struct dpv_file_node *file, int out); 74 75 static int 76 operate_common(struct dpv_file_node *file, int out) 77 { 78 struct stat sb; 79 80 /* Open the file if necessary */ 81 if (fd < 0) { 82 if (multiple) { 83 /* Resolve the file path and attempt to open it */ 84 if (realpath(file->path, rpath) == 0 || 85 (fd = open(rpath, O_RDONLY)) < 0) { 86 warn("%s", file->path); 87 file->status = DPV_STATUS_FAILED; 88 return (-1); 89 } 90 } else { 91 /* Assume stdin, but if that's a TTY instead use the 92 * highest numbered file descriptor (obtained by 93 * generating new fd and then decrementing). 94 * 95 * NB: /dev/stdin should always be open(2)'able 96 */ 97 fd = STDIN_FILENO; 98 if (isatty(fd)) { 99 fd = open("/dev/stdin", O_RDONLY); 100 close(fd--); 101 } 102 103 /* This answer might be wrong, if dpv(3) has (by 104 * request) opened an output file or pipe. If we 105 * told dpv(3) to open a file, subtract one from 106 * previous answer. If instead we told dpv(3) to 107 * prepare a pipe output, subtract two. 108 */ 109 switch(output_type) { 110 case DPV_OUTPUT_FILE: 111 fd -= 1; 112 break; 113 case DPV_OUTPUT_SHELL: 114 fd -= 2; 115 break; 116 } 117 } 118 } 119 120 /* Allocate buffer if necessary */ 121 if (buf == NULL) { 122 /* Use output block size as buffer size if available */ 123 if (out >= 0) { 124 if (fstat(out, &sb) != 0) { 125 warn("%i", out); 126 file->status = DPV_STATUS_FAILED; 127 return (-1); 128 } 129 if (S_ISREG(sb.st_mode)) { 130 if (sysconf(_SC_PHYS_PAGES) > 131 PHYSPAGES_THRESHOLD) 132 bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8); 133 else 134 bsize = BUFSIZE_SMALL; 135 } else 136 bsize = MAX(sb.st_blksize, 137 (blksize_t)sysconf(_SC_PAGESIZE)); 138 } else 139 bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8); 140 141 /* Attempt to allocate */ 142 if ((buf = malloc(bsize+1)) == NULL) { 143 end_dialog(); 144 err(EXIT_FAILURE, "Out of memory?!"); 145 } 146 } 147 148 return (0); 149 } 150 151 static int 152 operate_on_bytes(struct dpv_file_node *file, int out) 153 { 154 int progress; 155 ssize_t r, w; 156 157 if (operate_common(file, out) < 0) 158 return (-1); 159 160 /* [Re-]Fill the buffer */ 161 if ((r = read(fd, buf, bsize)) <= 0) { 162 if (fd != STDIN_FILENO) 163 close(fd); 164 fd = -1; 165 file->status = DPV_STATUS_DONE; 166 return (100); 167 } 168 169 /* [Re-]Dump the buffer */ 170 if (out >= 0) { 171 if ((w = write(out, buf, r)) < 0) { 172 end_dialog(); 173 err(EXIT_FAILURE, "output"); 174 } 175 fsync(out); 176 } 177 178 dpv_overall_read += r; 179 file->read += r; 180 181 /* Calculate percentage of completion (if possible) */ 182 if (file->length >= 0) { 183 progress = (file->read * 100 / (file->length > 0 ? 184 file->length : 1)); 185 186 /* If no_overrun, do not return 100% until read >= length */ 187 if (no_overrun && progress == 100 && file->read < file->length) 188 progress--; 189 190 return (progress); 191 } else 192 return (-1); 193 } 194 195 static int 196 operate_on_lines(struct dpv_file_node *file, int out) 197 { 198 char *p; 199 int progress; 200 ssize_t r, w; 201 202 if (operate_common(file, out) < 0) 203 return (-1); 204 205 /* [Re-]Fill the buffer */ 206 if ((r = read(fd, buf, bsize)) <= 0) { 207 if (fd != STDIN_FILENO) 208 close(fd); 209 fd = -1; 210 file->status = DPV_STATUS_DONE; 211 return (100); 212 } 213 buf[r] = '\0'; 214 215 /* [Re-]Dump the buffer */ 216 if (out >= 0) { 217 if ((w = write(out, buf, r)) < 0) { 218 end_dialog(); 219 err(EXIT_FAILURE, "output"); 220 } 221 fsync(out); 222 } 223 224 /* Process the buffer for number of lines */ 225 for (p = buf; p != NULL && *p != '\0';) 226 if ((p = strchr(p, '\n')) != NULL) 227 dpv_overall_read++, p++, file->read++; 228 229 /* Calculate percentage of completion (if possible) */ 230 if (file->length >= 0) { 231 progress = (file->read * 100 / file->length); 232 233 /* If no_overrun, do not return 100% until read >= length */ 234 if (no_overrun && progress == 100 && file->read < file->length) 235 progress--; 236 237 return (progress); 238 } else 239 return (-1); 240 } 241 242 /* 243 * Takes a list of names that are to correspond to input streams coming from 244 * stdin or fifos and produces necessary config to drive dpv(3) `--gauge' 245 * widget. If the `-d' flag is used, output is instead send to terminal 246 * standard output (and the output can then be saved to a file, piped into 247 * custom [X]dialog(1) invocation, or whatever. 248 */ 249 int 250 main(int argc, char *argv[]) 251 { 252 char dummy; 253 int ch; 254 int n = 0; 255 size_t config_size = sizeof(struct dpv_config); 256 size_t file_node_size = sizeof(struct dpv_file_node); 257 struct dpv_config *config; 258 struct dpv_file_node *curfile; 259 struct sigaction act; 260 261 pgm = argv[0]; /* store a copy of invocation name */ 262 263 /* Allocate config structure */ 264 if ((config = malloc(config_size)) == NULL) 265 errx(EXIT_FAILURE, "Out of memory?!"); 266 memset((void *)(config), '\0', config_size); 267 268 /* 269 * Process command-line options 270 */ 271 while ((ch = getopt(argc, argv, 272 "a:b:dDhi:I:klL:mn:No:p:P:t:TU:wx:X")) != -1) { 273 switch(ch) { 274 case 'a': /* additional message text to append */ 275 if (config->aprompt == NULL) { 276 config->aprompt = malloc(DPV_APROMPT_MAX); 277 if (config->aprompt == NULL) 278 errx(EXIT_FAILURE, "Out of memory?!"); 279 } 280 snprintf(config->aprompt, DPV_APROMPT_MAX, "%s", 281 optarg); 282 break; 283 case 'b': /* [X]dialog(1) backtitle */ 284 if (config->backtitle != NULL) 285 free((char *)config->backtitle); 286 config->backtitle = malloc(strlen(optarg) + 1); 287 if (config->backtitle == NULL) 288 errx(EXIT_FAILURE, "Out of memory?!"); 289 *(config->backtitle) = '\0'; 290 strcat(config->backtitle, optarg); 291 break; 292 case 'd': /* debugging */ 293 debug = TRUE; 294 config->debug = debug; 295 break; 296 case 'D': /* use dialog(1) instead of libdialog */ 297 config->display_type = DPV_DISPLAY_DIALOG; 298 break; 299 case 'h': /* help/usage */ 300 usage(); 301 break; /* NOTREACHED */ 302 case 'i': /* status line format string for single-file */ 303 config->status_solo = optarg; 304 break; 305 case 'I': /* status line format string for many-files */ 306 config->status_many = optarg; 307 break; 308 case 'k': /* keep tite */ 309 config->keep_tite = TRUE; 310 break; 311 case 'l': /* Line mode */ 312 line_mode = TRUE; 313 break; 314 case 'L': /* custom label size */ 315 config->label_size = 316 (int)strtol(optarg, (char **)NULL, 10); 317 if (config->label_size == 0 && errno == EINVAL) 318 errx(EXIT_FAILURE, 319 "`-L' argument must be numeric"); 320 else if (config->label_size < -1) 321 config->label_size = -1; 322 break; 323 case 'm': /* enable multiple file arguments */ 324 multiple = TRUE; 325 break; 326 case 'o': /* `-o path' for sending data-read to file */ 327 output_type = DPV_OUTPUT_FILE; 328 config->output_type = DPV_OUTPUT_FILE; 329 config->output = optarg; 330 break; 331 case 'n': /* custom number of files per `page' */ 332 config->display_limit = 333 (int)strtol(optarg, (char **)NULL, 10); 334 if (config->display_limit == 0 && errno == EINVAL) 335 errx(EXIT_FAILURE, 336 "`-n' argument must be numeric"); 337 else if (config->display_limit < 0) 338 config->display_limit = -1; 339 break; 340 case 'N': /* No overrun (truncate reads of known-length) */ 341 no_overrun = TRUE; 342 config->options |= DPV_NO_OVERRUN; 343 break; 344 case 'p': /* additional message text to use as prefix */ 345 if (config->pprompt == NULL) { 346 config->pprompt = malloc(DPV_PPROMPT_MAX + 2); 347 if (config->pprompt == NULL) 348 errx(EXIT_FAILURE, "Out of memory?!"); 349 /* +2 is for implicit "\n" appended later */ 350 } 351 snprintf(config->pprompt, DPV_PPROMPT_MAX, "%s", 352 optarg); 353 break; 354 case 'P': /* custom size for mini-progressbar */ 355 config->pbar_size = 356 (int)strtol(optarg, (char **)NULL, 10); 357 if (config->pbar_size == 0 && errno == EINVAL) 358 errx(EXIT_FAILURE, 359 "`-P' argument must be numeric"); 360 else if (config->pbar_size < -1) 361 config->pbar_size = -1; 362 break; 363 case 't': /* [X]dialog(1) title */ 364 if (config->title != NULL) 365 free(config->title); 366 config->title = malloc(strlen(optarg) + 1); 367 if (config->title == NULL) 368 errx(EXIT_FAILURE, "Out of memory?!"); 369 *(config->title) = '\0'; 370 strcat(config->title, optarg); 371 break; 372 case 'T': /* test mode (don't read data, fake it) */ 373 config->options |= DPV_TEST_MODE; 374 break; 375 case 'U': /* updates per second */ 376 config->status_updates_per_second = 377 (int)strtol(optarg, (char **)NULL, 10); 378 if (config->status_updates_per_second == 0 && 379 errno == EINVAL) 380 errx(EXIT_FAILURE, 381 "`-U' argument must be numeric"); 382 break; 383 case 'w': /* `-p' and `-a' widths bump [X]dialog(1) width */ 384 config->options |= DPV_WIDE_MODE; 385 break; 386 case 'x': /* `-x cmd' for sending data-read to sh(1) code */ 387 output_type = DPV_OUTPUT_SHELL; 388 config->output_type = DPV_OUTPUT_SHELL; 389 config->output = optarg; 390 break; 391 case 'X': /* X11 support through x11/xdialog */ 392 config->display_type = DPV_DISPLAY_XDIALOG; 393 break; 394 case '?': /* unknown argument (based on optstring) */ 395 /* FALLTHROUGH */ 396 default: /* unhandled argument (based on switch) */ 397 usage(); 398 /* NOTREACHED */ 399 } 400 } 401 argc -= optind; 402 argv += optind; 403 404 /* Process remaining arguments as list of names to display */ 405 for (curfile = file_list; n < argc; n++) { 406 nfiles++; 407 408 /* Allocate a new struct for the file argument */ 409 if (curfile == NULL) { 410 if ((curfile = malloc(file_node_size)) == NULL) 411 errx(EXIT_FAILURE, "Out of memory?!"); 412 memset((void *)(curfile), '\0', file_node_size); 413 file_list = curfile; 414 } else { 415 if ((curfile->next = malloc(file_node_size)) == NULL) 416 errx(EXIT_FAILURE, "Out of memory?!"); 417 memset((void *)(curfile->next), '\0', file_node_size); 418 curfile = curfile->next; 419 } 420 curfile->name = argv[n]; 421 422 /* Read possible `lines:' prefix from label syntax */ 423 if (sscanf(curfile->name, "%lli:%c", &(curfile->length), 424 &dummy) == 2) 425 curfile->name = strchr(curfile->name, ':') + 1; 426 else 427 curfile->length = -1; 428 429 /* Read path argument if enabled */ 430 if (multiple) { 431 if (++n >= argc) 432 errx(EXIT_FAILURE, "Missing path argument " 433 "for label number %i", nfiles); 434 curfile->path = argv[n]; 435 } else 436 break; 437 } 438 439 /* Display usage and exit if not given at least one name */ 440 if (nfiles == 0) { 441 warnx("no labels provided"); 442 usage(); 443 /* NOTREACHED */ 444 } 445 446 /* 447 * Set cleanup routine for Ctrl-C action 448 */ 449 if (config->display_type == DPV_DISPLAY_LIBDIALOG) { 450 act.sa_handler = sig_int; 451 sigaction(SIGINT, &act, 0); 452 } 453 454 /* Set status formats and action */ 455 if (line_mode) { 456 config->status_solo = LINE_STATUS_SOLO; 457 config->status_many = LINE_STATUS_SOLO; 458 config->action = operate_on_lines; 459 } else { 460 config->status_solo = BYTE_STATUS_SOLO; 461 config->status_many = BYTE_STATUS_SOLO; 462 config->action = operate_on_bytes; 463 } 464 465 /* 466 * Hand off to dpv(3)... 467 */ 468 if (dpv(config, file_list) != 0 && debug) 469 warnx("dpv(3) returned error!?"); 470 471 if (!config->keep_tite) 472 end_dialog(); 473 dpv_free(); 474 475 exit(EXIT_SUCCESS); 476 } 477 478 /* 479 * Interrupt handler to indicate we received a Ctrl-C interrupt. 480 */ 481 static void 482 sig_int(int sig __unused) 483 { 484 dpv_interrupt = TRUE; 485 } 486 487 /* 488 * Print short usage statement to stderr and exit with error status. 489 */ 490 static void 491 usage(void) 492 { 493 494 if (debug) /* No need for usage */ 495 exit(EXIT_FAILURE); 496 497 fprintf(stderr, "Usage: %s [options] [bytes:]label\n", pgm); 498 fprintf(stderr, " %s [options] -m [bytes1:]label1 path1 " 499 "[[bytes2:]label2 path2 ...]\n", pgm); 500 fprintf(stderr, "OPTIONS:\n"); 501 #define OPTFMT "\t%-14s %s\n" 502 fprintf(stderr, OPTFMT, "-a text", 503 "Append text. Displayed below file progress indicators."); 504 fprintf(stderr, OPTFMT, "-b backtitle", 505 "String to be displayed on the backdrop, at top-left."); 506 fprintf(stderr, OPTFMT, "-D", 507 "Use dialog(1) instead of dialog(3) [default]."); 508 fprintf(stderr, OPTFMT, "-d", 509 "Debug. Write to standard output instead of dialog."); 510 fprintf(stderr, OPTFMT, "-h", 511 "Produce this output on standard error and exit."); 512 fprintf(stderr, OPTFMT, "-I format", 513 "Customize status line format. See fdpv(1) for details."); 514 fprintf(stderr, OPTFMT, "-i format", 515 "Customize status line format. See fdpv(1) for details."); 516 fprintf(stderr, OPTFMT, "-L size", 517 "Label size. Must be a number greater than 0, or -1."); 518 fprintf(stderr, OPTFMT, "-m", 519 "Enable processing of multiple file argiments."); 520 fprintf(stderr, OPTFMT, "-N", 521 "No overrun. Stop reading input at stated length, if any."); 522 fprintf(stderr, OPTFMT, "-n num", 523 "Display at-most num files per screen. Default is -1."); 524 fprintf(stderr, OPTFMT, "-o file", 525 "Output data to file. First %s replaced with label text."); 526 fprintf(stderr, OPTFMT, "-P size", 527 "Mini-progressbar size. Must be a number greater than 3."); 528 fprintf(stderr, OPTFMT, "-p text", 529 "Prefix text. Displayed above file progress indicators."); 530 fprintf(stderr, OPTFMT, "-T", 531 "Test mode. Don't actually read any data, but fake it."); 532 fprintf(stderr, OPTFMT, "-t title", 533 "Title string to be displayed at top of dialog(1) box."); 534 fprintf(stderr, OPTFMT, "-U num", 535 "Update status line num times per-second. Default is 2."); 536 fprintf(stderr, OPTFMT, "-w", 537 "Wide. Width of `-p' and `-a' text bump dialog(1) width."); 538 fprintf(stderr, OPTFMT, "-X", 539 "X11. Use Xdialog(1) instead of dialog(1)."); 540 fprintf(stderr, OPTFMT, "-x cmd", 541 "Send data to executed cmd. First %s replaced with label."); 542 exit(EXIT_FAILURE); 543 } 544