X-Git-Url: https://git.cameronkatri.com/bsd-progress.git/blobdiff_plain/dd44b80a2e041b2df81a7f869c2ab5d9c6eae632..HEAD:/progress.c diff --git a/progress.c b/progress.c index 2eee724..a921ac9 100644 --- a/progress.c +++ b/progress.c @@ -1,4 +1,4 @@ -/* $NetBSD: progress.c,v 1.1 2003/01/22 00:14:12 jhawk Exp $ */ +/* $NetBSD: progress.c,v 1.23 2021/01/07 12:02:52 lukem Exp $ */ /*- * Copyright (c) 2003 The NetBSD Foundation, Inc. @@ -15,9 +15,6 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of The NetBSD Foundation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED @@ -32,84 +29,107 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#define _GNU_SOURCE + #include #ifndef lint -__RCSID("$NetBSD: progress.c,v 1.1 2003/01/22 00:14:12 jhawk Exp $"); +__RCSID("$NetBSD: progress.c,v 1.23 2021/01/07 12:02:52 lukem Exp $"); #endif /* not lint */ #include -#include -#include #include -#include +#include #include -#include -#include -#include #include #include #include -#include -#include +#include #include -#include +#include #include #include #include -#include -#include -#include #include + /* LONGLONG */ +long long strsuftoll(const char *, const char *, long long, long long); + /* LONGLONG */ +long long strsuftollx(const char *, const char *, long long, long long, + char *, size_t); + #define GLOBAL /* force GLOBAL decls in progressbar.h to be * declared */ #include "progressbar.h" +static void broken_pipe(int unused); static void usage(void); -int main(int, char *[]); + +static void +broken_pipe(int unused) +{ + signal(SIGPIPE, SIG_DFL); + progressmeter(1); + kill(getpid(), SIGPIPE); +} static void usage(void) { fprintf(stderr, - "usage: %s [-z] [-f file] [-l length] cmd [args...]\n", - getprogname()); - exit(1); + "usage: %s [-ez] [-b buffersize] [-f file] [-l length]\n" + " %*.s [-p prefix] cmd [args...]\n", + getprogname(), (int) strlen(getprogname()), ""); + exit(EXIT_FAILURE); } - int main(int argc, char *argv[]) { - static char fb_buf[BUFSIZ]; + char *fb_buf; char *infile = NULL; - pid_t pid; - int ch, fd, outpipe[2], waitstat; - int lflag = 0, zflag = 0; + pid_t pid = 0, gzippid = 0, deadpid; + int ch, fd, outpipe[2]; + int ws, gzipstat, cmdstat; + int eflag = 0, lflag = 0, zflag = 0; ssize_t nr, nw, off; + size_t buffersize; struct stat statb; + struct winsize wins; setprogname(argv[0]); /* defaults: Read from stdin, 0 filesize (no completion estimate) */ fd = STDIN_FILENO; filesize = 0; + buffersize = 64 * 1024; + prefix = NULL; - while ((ch = getopt(argc, argv, "f:l:z")) != -1) + while ((ch = getopt(argc, argv, "b:ef:l:p:z")) != -1) switch (ch) { + case 'b': + buffersize = (size_t) strsuftoll("buffer size", optarg, + 0, SSIZE_MAX); + break; + case 'e': + eflag++; + break; case 'f': infile = optarg; break; case 'l': lflag++; - filesize = strtoull(optarg, NULL, 0); + filesize = strsuftoll("input size", optarg, 0, + LLONG_MAX); + break; + case 'p': + prefix = optarg; break; case 'z': zflag++; break; - default: case '?': + default: usage(); /* NOTREACHED */ } @@ -118,18 +138,26 @@ main(int argc, char *argv[]) if (argc < 1) usage(); + if (infile && (fd = open(infile, O_RDONLY, 0)) < 0) err(1, "%s", infile); /* stat() to get the filesize unless overridden, or -z */ - if (!zflag && !lflag && (fstat(fd, &statb) == 0)) - filesize = statb.st_size; + if (!zflag && !lflag && (fstat(fd, &statb) == 0)) { + if (S_ISFIFO(statb.st_mode)) { + /* stat(2) on pipe may return only the + * first few bytes with more coming. + * Don't trust! + */ + } else { + filesize = statb.st_size; + } + } /* gzip -l the file if we have the name and -z is given */ if (zflag && !lflag && infile != NULL) { FILE *gzipsizepipe; - char buf[256], *cmd; - long size; + char buf[256], *cp, *cmd; /* * Read second word of last line of gzip -l output. Looks like: @@ -143,15 +171,14 @@ main(int argc, char *argv[]) err(1, "reading compressed file length"); for (; fgets(buf, 256, gzipsizepipe) != NULL;) continue; - sscanf(buf, "%*d %ld", &size); - filesize = size; + strtoimax(buf, &cp, 10); + filesize = strtoimax(cp, NULL, 10); if (pclose(gzipsizepipe) < 0) err(1, "closing compressed file length pipe"); free(cmd); } /* Pipe input through gzip -dc if -z is given */ if (zflag) { - pid_t gzippid; int gzippipe[2]; if (pipe(gzippipe) < 0) @@ -178,8 +205,16 @@ main(int argc, char *argv[]) /* Initialize progressbar.c's global state */ bytes = 0; progress = 1; - ttyout = stdout; - ttywidth = 80; + ttyout = eflag ? stderr : stdout; + + if (ioctl(fileno(ttyout), TIOCGWINSZ, &wins) == -1) + ttywidth = 80; + else + ttywidth = wins.ws_col; + + fb_buf = malloc(buffersize); + if (fb_buf == NULL) + err(1, "malloc for buffersize"); if (pipe(outpipe) < 0) err(1, "output pipe"); @@ -197,15 +232,60 @@ main(int argc, char *argv[]) } close(outpipe[0]); + signal(SIGPIPE, broken_pipe); progressmeter(-1); - while ((nr = read(fd, fb_buf, BUFSIZ)) > 0) - for (off = 0; nr; nr -= nw, off += nw, bytes += nw) - if ((nw = write(outpipe[1], &fb_buf + off, (size_t) nr)) < 0) - err(1, "writing %d bytes to output pipe", nr); + + while (1) { + do { + nr = read(fd, fb_buf, buffersize); + } while (nr < 0 && errno == EINTR); + if (nr <= 0) + break; + for (off = 0; nr; nr -= nw, off += nw, bytes += nw) + if ((nw = write(outpipe[1], fb_buf + off, + (size_t) nr)) < 0) { + if (errno == EINTR) { + nw = 0; + continue; + } + progressmeter(1); + err(1, "writing %u bytes to output pipe", + (unsigned) nr); + } + } close(outpipe[1]); - wait(&waitstat); + gzipstat = 0; + cmdstat = 0; + while (pid || gzippid) { + deadpid = wait(&ws); + /* + * We need to exit with an error if the command (or gzip) + * exited abnormally. + * Unfortunately we can't generate a true 'exited by signal' + * error without sending the signal to ourselves :-( + */ + ws = WIFSIGNALED(ws) ? WTERMSIG(ws) : WEXITSTATUS(ws); + + if (deadpid != -1 && errno == EINTR) + continue; + if (deadpid == pid) { + pid = 0; + cmdstat = ws; + continue; + } + if (deadpid == gzippid) { + gzippid = 0; + gzipstat = ws; + continue; + } + break; + } progressmeter(1); - return 0; + signal(SIGPIPE, SIG_DFL); + + free(fb_buf); + + exit(cmdstat ? cmdstat : gzipstat); }