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