]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.bin/dpv/dpv.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / usr.bin / dpv / dpv.c
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 }