progress.c revision 1.24
11.24Sgson/* $NetBSD: progress.c,v 1.24 2021/08/09 10:46:39 gson Exp $ */ 21.1Sjhawk 31.1Sjhawk/*- 41.1Sjhawk * Copyright (c) 2003 The NetBSD Foundation, Inc. 51.1Sjhawk * All rights reserved. 61.1Sjhawk * 71.1Sjhawk * This code is derived from software contributed to The NetBSD Foundation 81.1Sjhawk * by John Hawkinson. 91.1Sjhawk * 101.1Sjhawk * Redistribution and use in source and binary forms, with or without 111.1Sjhawk * modification, are permitted provided that the following conditions 121.1Sjhawk * are met: 131.1Sjhawk * 1. Redistributions of source code must retain the above copyright 141.1Sjhawk * notice, this list of conditions and the following disclaimer. 151.1Sjhawk * 2. Redistributions in binary form must reproduce the above copyright 161.1Sjhawk * notice, this list of conditions and the following disclaimer in the 171.1Sjhawk * documentation and/or other materials provided with the distribution. 181.1Sjhawk * 191.1Sjhawk * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 201.1Sjhawk * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 211.1Sjhawk * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 221.1Sjhawk * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 231.1Sjhawk * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 241.1Sjhawk * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 251.1Sjhawk * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 261.1Sjhawk * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 271.1Sjhawk * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 281.1Sjhawk * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 291.1Sjhawk * POSSIBILITY OF SUCH DAMAGE. 301.1Sjhawk */ 311.1Sjhawk 321.1Sjhawk#include <sys/cdefs.h> 331.1Sjhawk#ifndef lint 341.24Sgson__RCSID("$NetBSD: progress.c,v 1.24 2021/08/09 10:46:39 gson Exp $"); 351.1Sjhawk#endif /* not lint */ 361.1Sjhawk 371.1Sjhawk#include <sys/types.h> 381.1Sjhawk#include <sys/ioctl.h> 391.20Sriastrad#include <sys/stat.h> 401.1Sjhawk#include <sys/wait.h> 411.1Sjhawk 421.1Sjhawk#include <err.h> 431.1Sjhawk#include <errno.h> 441.1Sjhawk#include <fcntl.h> 451.14Shubertf#include <inttypes.h> 461.1Sjhawk#include <limits.h> 471.20Sriastrad#include <signal.h> 481.1Sjhawk#include <stdio.h> 491.1Sjhawk#include <stdlib.h> 501.1Sjhawk#include <string.h> 511.1Sjhawk#include <unistd.h> 521.1Sjhawk 531.1Sjhawk#define GLOBAL /* force GLOBAL decls in progressbar.h to be 541.1Sjhawk * declared */ 551.1Sjhawk#include "progressbar.h" 561.1Sjhawk 571.17Sdhollandstatic void broken_pipe(int unused); 581.19Sjoerg__dead static void usage(void); 591.1Sjhawk 601.1Sjhawkstatic void 611.17Sdhollandbroken_pipe(int unused) 621.17Sdholland{ 631.17Sdholland signal(SIGPIPE, SIG_DFL); 641.17Sdholland progressmeter(1); 651.17Sdholland kill(getpid(), SIGPIPE); 661.17Sdholland} 671.17Sdholland 681.17Sdhollandstatic void 691.1Sjhawkusage(void) 701.1Sjhawk{ 711.1Sjhawk fprintf(stderr, 721.15Sbriggs "usage: %s [-ez] [-b buffersize] [-f file] [-l length]\n" 731.15Sbriggs " %*.s [-p prefix] cmd [args...]\n", 741.15Sbriggs getprogname(), (int) strlen(getprogname()), ""); 751.13Shubertf exit(EXIT_FAILURE); 761.1Sjhawk} 771.1Sjhawk 781.1Sjhawkint 791.1Sjhawkmain(int argc, char *argv[]) 801.1Sjhawk{ 811.15Sbriggs char *fb_buf; 821.1Sjhawk char *infile = NULL; 831.10Sdsl pid_t pid = 0, gzippid = 0, deadpid; 841.10Sdsl int ch, fd, outpipe[2]; 851.10Sdsl int ws, gzipstat, cmdstat; 861.11Sgarbled int eflag = 0, lflag = 0, zflag = 0; 871.1Sjhawk ssize_t nr, nw, off; 881.15Sbriggs size_t buffersize; 891.1Sjhawk struct stat statb; 901.3Schristos struct ttysize ts; 911.1Sjhawk 921.1Sjhawk setprogname(argv[0]); 931.1Sjhawk 941.1Sjhawk /* defaults: Read from stdin, 0 filesize (no completion estimate) */ 951.1Sjhawk fd = STDIN_FILENO; 961.1Sjhawk filesize = 0; 971.15Sbriggs buffersize = 64 * 1024; 981.13Shubertf prefix = NULL; 991.1Sjhawk 1001.15Sbriggs while ((ch = getopt(argc, argv, "b:ef:l:p:z")) != -1) 1011.1Sjhawk switch (ch) { 1021.15Sbriggs case 'b': 1031.15Sbriggs buffersize = (size_t) strsuftoll("buffer size", optarg, 1041.18Stron 0, SSIZE_MAX); 1051.15Sbriggs break; 1061.11Sgarbled case 'e': 1071.11Sgarbled eflag++; 1081.11Sgarbled break; 1091.1Sjhawk case 'f': 1101.1Sjhawk infile = optarg; 1111.1Sjhawk break; 1121.1Sjhawk case 'l': 1131.1Sjhawk lflag++; 1141.9Slukem filesize = strsuftoll("input size", optarg, 0, 1151.9Slukem LLONG_MAX); 1161.1Sjhawk break; 1171.8Shubertf case 'p': 1181.8Shubertf prefix = optarg; 1191.8Shubertf break; 1201.1Sjhawk case 'z': 1211.1Sjhawk zflag++; 1221.1Sjhawk break; 1231.13Shubertf case '?': 1241.1Sjhawk default: 1251.1Sjhawk usage(); 1261.1Sjhawk /* NOTREACHED */ 1271.1Sjhawk } 1281.1Sjhawk argc -= optind; 1291.1Sjhawk argv += optind; 1301.1Sjhawk 1311.1Sjhawk if (argc < 1) 1321.1Sjhawk usage(); 1331.13Shubertf 1341.1Sjhawk if (infile && (fd = open(infile, O_RDONLY, 0)) < 0) 1351.1Sjhawk err(1, "%s", infile); 1361.1Sjhawk 1371.1Sjhawk /* stat() to get the filesize unless overridden, or -z */ 1381.12Shubertf if (!zflag && !lflag && (fstat(fd, &statb) == 0)) { 1391.12Shubertf if (S_ISFIFO(statb.st_mode)) { 1401.12Shubertf /* stat(2) on pipe may return only the 1411.12Shubertf * first few bytes with more coming. 1421.12Shubertf * Don't trust! 1431.12Shubertf */ 1441.12Shubertf } else { 1451.12Shubertf filesize = statb.st_size; 1461.12Shubertf } 1471.12Shubertf } 1481.1Sjhawk 1491.1Sjhawk /* gzip -l the file if we have the name and -z is given */ 1501.1Sjhawk if (zflag && !lflag && infile != NULL) { 1511.1Sjhawk FILE *gzipsizepipe; 1521.10Sdsl char buf[256], *cp, *cmd; 1531.1Sjhawk 1541.1Sjhawk /* 1551.1Sjhawk * Read second word of last line of gzip -l output. Looks like: 1561.1Sjhawk * % gzip -l ../etc.tgz 1571.1Sjhawk * compressed uncompressed ratio uncompressed_name 1581.1Sjhawk * 119737 696320 82.8% ../etc.tar 1591.1Sjhawk */ 1601.1Sjhawk 1611.1Sjhawk asprintf(&cmd, "gzip -l %s", infile); 1621.1Sjhawk if ((gzipsizepipe = popen(cmd, "r")) == NULL) 1631.1Sjhawk err(1, "reading compressed file length"); 1641.1Sjhawk for (; fgets(buf, 256, gzipsizepipe) != NULL;) 1651.1Sjhawk continue; 1661.10Sdsl strtoimax(buf, &cp, 10); 1671.10Sdsl filesize = strtoimax(cp, NULL, 10); 1681.1Sjhawk if (pclose(gzipsizepipe) < 0) 1691.1Sjhawk err(1, "closing compressed file length pipe"); 1701.1Sjhawk free(cmd); 1711.1Sjhawk } 1721.1Sjhawk /* Pipe input through gzip -dc if -z is given */ 1731.1Sjhawk if (zflag) { 1741.1Sjhawk int gzippipe[2]; 1751.1Sjhawk 1761.1Sjhawk if (pipe(gzippipe) < 0) 1771.1Sjhawk err(1, "gzip pipe"); 1781.1Sjhawk gzippid = fork(); 1791.1Sjhawk if (gzippid < 0) 1801.1Sjhawk err(1, "fork for gzip"); 1811.1Sjhawk 1821.1Sjhawk if (gzippid) { 1831.1Sjhawk /* parent */ 1841.1Sjhawk dup2(gzippipe[0], fd); 1851.1Sjhawk close(gzippipe[0]); 1861.1Sjhawk close(gzippipe[1]); 1871.1Sjhawk } else { 1881.1Sjhawk dup2(gzippipe[1], STDOUT_FILENO); 1891.1Sjhawk dup2(fd, STDIN_FILENO); 1901.1Sjhawk close(gzippipe[0]); 1911.1Sjhawk close(gzippipe[1]); 1921.1Sjhawk if (execlp("gzip", "gzip", "-dc", NULL)) 1931.1Sjhawk err(1, "exec()ing gzip"); 1941.1Sjhawk } 1951.1Sjhawk } 1961.1Sjhawk 1971.1Sjhawk /* Initialize progressbar.c's global state */ 1981.1Sjhawk bytes = 0; 1991.1Sjhawk progress = 1; 2001.11Sgarbled ttyout = eflag ? stderr : stdout; 2011.3Schristos 2021.13Shubertf if (ioctl(fileno(ttyout), TIOCGSIZE, &ts) == -1) 2031.3Schristos ttywidth = 80; 2041.13Shubertf else 2051.3Schristos ttywidth = ts.ts_cols; 2061.1Sjhawk 2071.15Sbriggs fb_buf = malloc(buffersize); 2081.15Sbriggs if (fb_buf == NULL) 2091.15Sbriggs err(1, "malloc for buffersize"); 2101.15Sbriggs 2111.1Sjhawk if (pipe(outpipe) < 0) 2121.1Sjhawk err(1, "output pipe"); 2131.1Sjhawk pid = fork(); 2141.1Sjhawk if (pid < 0) 2151.1Sjhawk err(1, "fork for output pipe"); 2161.1Sjhawk 2171.1Sjhawk if (pid == 0) { 2181.1Sjhawk /* child */ 2191.1Sjhawk dup2(outpipe[0], STDIN_FILENO); 2201.1Sjhawk close(outpipe[0]); 2211.1Sjhawk close(outpipe[1]); 2221.1Sjhawk execvp(argv[0], argv); 2231.1Sjhawk err(1, "could not exec %s", argv[0]); 2241.1Sjhawk } 2251.1Sjhawk close(outpipe[0]); 2261.1Sjhawk 2271.17Sdholland signal(SIGPIPE, broken_pipe); 2281.1Sjhawk progressmeter(-1); 2291.17Sdholland 2301.21Sgson while (1) { 2311.21Sgson do { 2321.21Sgson nr = read(fd, fb_buf, buffersize); 2331.21Sgson } while (nr < 0 && errno == EINTR); 2341.21Sgson if (nr <= 0) 2351.21Sgson break; 2361.2Senami for (off = 0; nr; nr -= nw, off += nw, bytes += nw) 2371.2Senami if ((nw = write(outpipe[1], fb_buf + off, 2381.17Sdholland (size_t) nr)) < 0) { 2391.23Slukem if (errno == EINTR) { 2401.23Slukem nw = 0; 2411.23Slukem continue; 2421.23Slukem } 2431.17Sdholland progressmeter(1); 2441.5Sagc err(1, "writing %u bytes to output pipe", 2451.5Sagc (unsigned) nr); 2461.17Sdholland } 2471.21Sgson } 2481.1Sjhawk close(outpipe[1]); 2491.1Sjhawk 2501.10Sdsl gzipstat = 0; 2511.10Sdsl cmdstat = 0; 2521.7Sross while (pid || gzippid) { 2531.10Sdsl deadpid = wait(&ws); 2541.10Sdsl /* 2551.10Sdsl * We need to exit with an error if the command (or gzip) 2561.10Sdsl * exited abnormally. 2571.10Sdsl * Unfortunately we can't generate a true 'exited by signal' 2581.10Sdsl * error without sending the signal to ourselves :-( 2591.10Sdsl */ 2601.10Sdsl ws = WIFSIGNALED(ws) ? WTERMSIG(ws) : WEXITSTATUS(ws); 2611.7Sross 2621.24Sgson if (deadpid == -1 && errno == EINTR) 2631.10Sdsl continue; 2641.10Sdsl if (deadpid == pid) { 2651.7Sross pid = 0; 2661.10Sdsl cmdstat = ws; 2671.10Sdsl continue; 2681.10Sdsl } 2691.10Sdsl if (deadpid == gzippid) { 2701.7Sross gzippid = 0; 2711.10Sdsl gzipstat = ws; 2721.7Sross continue; 2731.10Sdsl } 2741.10Sdsl break; 2751.7Sross } 2761.1Sjhawk 2771.1Sjhawk progressmeter(1); 2781.17Sdholland signal(SIGPIPE, SIG_DFL); 2791.15Sbriggs 2801.15Sbriggs free(fb_buf); 2811.15Sbriggs 2821.10Sdsl exit(cmdstat ? cmdstat : gzipstat); 2831.1Sjhawk} 284