1 /*- 2 * Copyright (c) 2013-2014 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:lL: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 'l': /* Line mode */ 311 line_mode = TRUE; 312 break; 313 case 'L': /* custom label size */ 314 config->label_size = 315 (int)strtol(optarg, (char **)NULL, 10); 316 if (config->label_size == 0 && errno == EINVAL) 317 errx(EXIT_FAILURE, 318 "`-L' argument must be numeric"); 319 else if (config->label_size < -1) 320 config->label_size = -1; 321 break; 322 case 'm': /* enable multiple file arguments */ 323 multiple = TRUE; 324 break; 325 case 'o': /* `-o path' for sending data-read to file */ 326 output_type = DPV_OUTPUT_FILE; 327 config->output_type = DPV_OUTPUT_FILE; 328 config->output = optarg; 329 break; 330 case 'n': /* custom number of files per `page' */ 331 config->display_limit = 332 (int)strtol(optarg, (char **)NULL, 10); 333 if (config->display_limit == 0 && errno == EINVAL) 334 errx(EXIT_FAILURE, 335 "`-n' argument must be numeric"); 336 else if (config->display_limit < 0) 337 config->display_limit = -1; 338 break; 339 case 'N': /* No overrun (truncate reads of known-length) */ 340 no_overrun = TRUE; 341 config->options |= DPV_NO_OVERRUN; 342 break; 343 case 'p': /* additional message text to use as prefix */ 344 if (config->pprompt == NULL) { 345 config->pprompt = malloc(DPV_PPROMPT_MAX + 2); 346 if (config->pprompt == NULL) 347 errx(EXIT_FAILURE, "Out of memory?!"); 348 /* +2 is for implicit "\n" appended later */ 349 } 350 snprintf(config->pprompt, DPV_PPROMPT_MAX, "%s", 351 optarg); 352 break; 353 case 'P': /* custom size for mini-progressbar */ 354 config->pbar_size = 355 (int)strtol(optarg, (char **)NULL, 10); 356 if (config->pbar_size == 0 && errno == EINVAL) 357 errx(EXIT_FAILURE, 358 "`-P' argument must be numeric"); 359 else if (config->pbar_size < -1) 360 config->pbar_size = -1; 361 break; 362 case 't': /* [X]dialog(1) title */ 363 if (config->title != NULL) 364 free(config->title); 365 config->title = malloc(strlen(optarg) + 1); 366 if (config->title == NULL) 367 errx(EXIT_FAILURE, "Out of memory?!"); 368 *(config->title) = '\0'; 369 strcat(config->title, optarg); 370 break; 371 case 'T': /* test mode (don't read data, fake it) */ 372 config->options |= DPV_TEST_MODE; 373 break; 374 case 'U': /* updates per second */ 375 config->status_updates_per_second = 376 (int)strtol(optarg, (char **)NULL, 10); 377 if (config->status_updates_per_second == 0 && 378 errno == EINVAL) 379 errx(EXIT_FAILURE, 380 "`-U' argument must be numeric"); 381 break; 382 case 'w': /* `-p' and `-a' widths bump [X]dialog(1) width */ 383 config->options |= DPV_WIDE_MODE; 384 break; 385 case 'x': /* `-x cmd' for sending data-read to sh(1) code */ 386 output_type = DPV_OUTPUT_SHELL; 387 config->output_type = DPV_OUTPUT_SHELL; 388 config->output = optarg; 389 break; 390 case 'X': /* X11 support through x11/xdialog */ 391 config->display_type = DPV_DISPLAY_XDIALOG; 392 break; 393 case '?': /* unknown argument (based on optstring) */ 394 /* FALLTHROUGH */ 395 default: /* unhandled argument (based on switch) */ 396 usage(); 397 /* NOTREACHED */ 398 } 399 } 400 argc -= optind; 401 argv += optind; 402 403 /* Process remaining arguments as list of names to display */ 404 for (curfile = file_list; n < argc; n++) { 405 nfiles++; 406 407 /* Allocate a new struct for the file argument */ 408 if (curfile == NULL) { 409 if ((curfile = malloc(file_node_size)) == NULL) 410 errx(EXIT_FAILURE, "Out of memory?!"); 411 memset((void *)(curfile), '\0', file_node_size); 412 file_list = curfile; 413 } else { 414 if ((curfile->next = malloc(file_node_size)) == NULL) 415 errx(EXIT_FAILURE, "Out of memory?!"); 416 memset((void *)(curfile->next), '\0', file_node_size); 417 curfile = curfile->next; 418 } 419 curfile->name = argv[n]; 420 421 /* Read possible `lines:' prefix from label syntax */ 422 if (sscanf(curfile->name, "%lli:%c", &(curfile->length), 423 &dummy) == 2) 424 curfile->name = strchr(curfile->name, ':') + 1; 425 else 426 curfile->length = -1; 427 428 /* Read path argument if enabled */ 429 if (multiple) { 430 if (++n >= argc) 431 errx(EXIT_FAILURE, "Missing path argument " 432 "for label number %i", nfiles); 433 curfile->path = argv[n]; 434 } else 435 break; 436 } 437 438 /* Display usage and exit if not given at least one name */ 439 if (nfiles == 0) { 440 warnx("no labels provided"); 441 usage(); 442 /* NOTREACHED */ 443 } 444 445 /* 446 * Set cleanup routine for Ctrl-C action 447 */ 448 if (config->display_type == DPV_DISPLAY_LIBDIALOG) { 449 act.sa_handler = sig_int; 450 sigaction(SIGINT, &act, 0); 451 } 452 453 /* Set status formats and action */ 454 if (line_mode) { 455 config->status_solo = LINE_STATUS_SOLO; 456 config->status_many = LINE_STATUS_SOLO; 457 config->action = operate_on_lines; 458 } else { 459 config->status_solo = BYTE_STATUS_SOLO; 460 config->status_many = BYTE_STATUS_SOLO; 461 config->action = operate_on_bytes; 462 } 463 464 /* 465 * Hand off to dpv(3)... 466 */ 467 if (dpv(config, file_list) != 0 && debug) 468 warnx("dpv(3) returned error!?"); 469 470 end_dialog(); 471 dpv_free(); 472 473 exit(EXIT_SUCCESS); 474 } 475 476 /* 477 * Interrupt handler to indicate we received a Ctrl-C interrupt. 478 */ 479 static void 480 sig_int(int sig __unused) 481 { 482 dpv_interrupt = TRUE; 483 } 484 485 /* 486 * Print short usage statement to stderr and exit with error status. 487 */ 488 static void 489 usage(void) 490 { 491 492 if (debug) /* No need for usage */ 493 exit(EXIT_FAILURE); 494 495 fprintf(stderr, "Usage: %s [options] bytes:label\n", pgm); 496 fprintf(stderr, " %s [options] -m bytes1:label1 path1 " 497 "[bytes2:label2 path2 ...]\n", pgm); 498 fprintf(stderr, "OPTIONS:\n"); 499 #define OPTFMT "\t%-14s %s\n" 500 fprintf(stderr, OPTFMT, "-a text", 501 "Append text. Displayed below file progress indicators."); 502 fprintf(stderr, OPTFMT, "-b backtitle", 503 "String to be displayed on the backdrop, at top-left."); 504 fprintf(stderr, OPTFMT, "-d", 505 "Debug. Write to standard output instead of dialog."); 506 fprintf(stderr, OPTFMT, "-D", 507 "Use dialog(1) instead of dialog(3) [default]."); 508 fprintf(stderr, OPTFMT, "-h", 509 "Produce this output on standard error and exit."); 510 fprintf(stderr, OPTFMT, "-i format", 511 "Customize status line format. See fdpv(1) for details."); 512 fprintf(stderr, OPTFMT, "-I format", 513 "Customize status line format. See fdpv(1) for details."); 514 fprintf(stderr, OPTFMT, "-L size", 515 "Label size. Must be a number greater than 0, or -1."); 516 fprintf(stderr, OPTFMT, "-m", 517 "Enable processing of multiple file argiments."); 518 fprintf(stderr, OPTFMT, "-n num", 519 "Display at-most num files per screen. Default is -1."); 520 fprintf(stderr, OPTFMT, "-N", 521 "No overrun. Stop reading input at stated length, if any."); 522 fprintf(stderr, OPTFMT, "-o file", 523 "Output data to file. First %s replaced with label text."); 524 fprintf(stderr, OPTFMT, "-p text", 525 "Prefix text. Displayed above file progress indicators."); 526 fprintf(stderr, OPTFMT, "-P size", 527 "Mini-progressbar size. Must be a number greater than 3."); 528 fprintf(stderr, OPTFMT, "-t title", 529 "Title string to be displayed at top of dialog(1) box."); 530 fprintf(stderr, OPTFMT, "-T", 531 "Test mode. Don't actually read any data, but fake it."); 532 fprintf(stderr, OPTFMT, "-U num", 533 "Update status line num times per-second. Default is 2."); 534 fprintf(stderr, OPTFMT, "-w", 535 "Wide. Width of `-p' and `-a' text bump dialog(1) width."); 536 fprintf(stderr, OPTFMT, "-x cmd", 537 "Send data to executed cmd. First %s replaced with label."); 538 fprintf(stderr, OPTFMT, "-X", 539 "X11. Use Xdialog(1) instead of dialog(1)."); 540 exit(EXIT_FAILURE); 541 } 542