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
operate_common(struct dpv_file_node * file,int out)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
operate_on_bytes(struct dpv_file_node * file,int out)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
operate_on_lines(struct dpv_file_node * file,int out)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
main(int argc,char * argv[])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
sig_int(int sig __unused)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
usage(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