progress.c revision 1.25
11.25Sgson/*	$NetBSD: progress.c,v 1.25 2021/08/17 07:18:43 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.25Sgson__RCSID("$NetBSD: progress.c,v 1.25 2021/08/17 07:18:43 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.25Sgson		if (nr < 0) {
2351.25Sgson			progressmeter(1);
2361.25Sgson			err(1, "reading input");
2371.25Sgson		}
2381.25Sgson		if (nr == 0)
2391.21Sgson			break;
2401.2Senami		for (off = 0; nr; nr -= nw, off += nw, bytes += nw)
2411.2Senami			if ((nw = write(outpipe[1], fb_buf + off,
2421.17Sdholland			    (size_t) nr)) < 0) {
2431.23Slukem				if (errno == EINTR) {
2441.23Slukem					nw = 0;
2451.23Slukem					continue;
2461.23Slukem				}
2471.17Sdholland				progressmeter(1);
2481.5Sagc				err(1, "writing %u bytes to output pipe",
2491.5Sagc							(unsigned) nr);
2501.17Sdholland			}
2511.21Sgson	}
2521.1Sjhawk	close(outpipe[1]);
2531.1Sjhawk
2541.10Sdsl	gzipstat = 0;
2551.10Sdsl	cmdstat = 0;
2561.7Sross	while (pid || gzippid) {
2571.10Sdsl		deadpid = wait(&ws);
2581.10Sdsl		/*
2591.10Sdsl		 * We need to exit with an error if the command (or gzip)
2601.10Sdsl		 * exited abnormally.
2611.10Sdsl		 * Unfortunately we can't generate a true 'exited by signal'
2621.10Sdsl		 * error without sending the signal to ourselves :-(
2631.10Sdsl		 */
2641.10Sdsl		ws = WIFSIGNALED(ws) ? WTERMSIG(ws) : WEXITSTATUS(ws);
2651.7Sross
2661.24Sgson		if (deadpid == -1 && errno == EINTR)
2671.10Sdsl			continue;
2681.10Sdsl		if (deadpid == pid) {
2691.7Sross			pid = 0;
2701.10Sdsl			cmdstat = ws;
2711.10Sdsl			continue;
2721.10Sdsl		}
2731.10Sdsl		if (deadpid == gzippid) {
2741.7Sross			gzippid = 0;
2751.10Sdsl			gzipstat = ws;
2761.7Sross			continue;
2771.10Sdsl		}
2781.10Sdsl		break;
2791.7Sross	}
2801.1Sjhawk
2811.1Sjhawk	progressmeter(1);
2821.17Sdholland	signal(SIGPIPE, SIG_DFL);
2831.15Sbriggs
2841.15Sbriggs	free(fb_buf);
2851.15Sbriggs
2861.10Sdsl	exit(cmdstat ? cmdstat : gzipstat);
2871.1Sjhawk}
288