Thu Jan 7 12:02:52 2021 UTC ()
progress: handle EINTR in writes. PR/55914


(lukem)
diff -r1.22 -r1.23 src/usr.bin/progress/progress.c

cvs diff -r1.22 -r1.23 src/usr.bin/progress/progress.c (switch to unified diff)

--- src/usr.bin/progress/progress.c 2020/04/25 11:12:39 1.22
+++ src/usr.bin/progress/progress.c 2021/01/07 12:02:52 1.23
@@ -1,279 +1,283 @@ @@ -1,279 +1,283 @@
1/* $NetBSD: progress.c,v 1.22 2020/04/25 11:12:39 simonb Exp $ */ 1/* $NetBSD: progress.c,v 1.23 2021/01/07 12:02:52 lukem Exp $ */
2 2
3/*- 3/*-
4 * Copyright (c) 2003 The NetBSD Foundation, Inc. 4 * Copyright (c) 2003 The NetBSD Foundation, Inc.
5 * All rights reserved. 5 * All rights reserved.
6 * 6 *
7 * This code is derived from software contributed to The NetBSD Foundation 7 * This code is derived from software contributed to The NetBSD Foundation
8 * by John Hawkinson. 8 * by John Hawkinson.
9 * 9 *
10 * Redistribution and use in source and binary forms, with or without 10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions 11 * modification, are permitted provided that the following conditions
12 * are met: 12 * are met:
13 * 1. Redistributions of source code must retain the above copyright 13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer. 14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright 15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the 16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution. 17 * documentation and/or other materials provided with the distribution.
18 * 18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE. 29 * POSSIBILITY OF SUCH DAMAGE.
30 */ 30 */
31 31
32#include <sys/cdefs.h> 32#include <sys/cdefs.h>
33#ifndef lint 33#ifndef lint
34__RCSID("$NetBSD: progress.c,v 1.22 2020/04/25 11:12:39 simonb Exp $"); 34__RCSID("$NetBSD: progress.c,v 1.23 2021/01/07 12:02:52 lukem Exp $");
35#endif /* not lint */ 35#endif /* not lint */
36 36
37#include <sys/types.h> 37#include <sys/types.h>
38#include <sys/ioctl.h> 38#include <sys/ioctl.h>
39#include <sys/stat.h> 39#include <sys/stat.h>
40#include <sys/wait.h> 40#include <sys/wait.h>
41 41
42#include <err.h> 42#include <err.h>
43#include <errno.h> 43#include <errno.h>
44#include <fcntl.h> 44#include <fcntl.h>
45#include <inttypes.h> 45#include <inttypes.h>
46#include <limits.h> 46#include <limits.h>
47#include <signal.h> 47#include <signal.h>
48#include <stdio.h> 48#include <stdio.h>
49#include <stdlib.h> 49#include <stdlib.h>
50#include <string.h> 50#include <string.h>
51#include <unistd.h> 51#include <unistd.h>
52 52
53#define GLOBAL /* force GLOBAL decls in progressbar.h to be 53#define GLOBAL /* force GLOBAL decls in progressbar.h to be
54 * declared */ 54 * declared */
55#include "progressbar.h" 55#include "progressbar.h"
56 56
57static void broken_pipe(int unused); 57static void broken_pipe(int unused);
58__dead static void usage(void); 58__dead static void usage(void);
59 59
60static void 60static void
61broken_pipe(int unused) 61broken_pipe(int unused)
62{ 62{
63 signal(SIGPIPE, SIG_DFL); 63 signal(SIGPIPE, SIG_DFL);
64 progressmeter(1); 64 progressmeter(1);
65 kill(getpid(), SIGPIPE); 65 kill(getpid(), SIGPIPE);
66} 66}
67 67
68static void 68static void
69usage(void) 69usage(void)
70{ 70{
71 fprintf(stderr, 71 fprintf(stderr,
72 "usage: %s [-ez] [-b buffersize] [-f file] [-l length]\n" 72 "usage: %s [-ez] [-b buffersize] [-f file] [-l length]\n"
73 " %*.s [-p prefix] cmd [args...]\n", 73 " %*.s [-p prefix] cmd [args...]\n",
74 getprogname(), (int) strlen(getprogname()), ""); 74 getprogname(), (int) strlen(getprogname()), "");
75 exit(EXIT_FAILURE); 75 exit(EXIT_FAILURE);
76} 76}
77 77
78int 78int
79main(int argc, char *argv[]) 79main(int argc, char *argv[])
80{ 80{
81 char *fb_buf; 81 char *fb_buf;
82 char *infile = NULL; 82 char *infile = NULL;
83 pid_t pid = 0, gzippid = 0, deadpid; 83 pid_t pid = 0, gzippid = 0, deadpid;
84 int ch, fd, outpipe[2]; 84 int ch, fd, outpipe[2];
85 int ws, gzipstat, cmdstat; 85 int ws, gzipstat, cmdstat;
86 int eflag = 0, lflag = 0, zflag = 0; 86 int eflag = 0, lflag = 0, zflag = 0;
87 ssize_t nr, nw, off; 87 ssize_t nr, nw, off;
88 size_t buffersize; 88 size_t buffersize;
89 struct stat statb; 89 struct stat statb;
90 struct ttysize ts; 90 struct ttysize ts;
91 91
92 setprogname(argv[0]); 92 setprogname(argv[0]);
93 93
94 /* defaults: Read from stdin, 0 filesize (no completion estimate) */ 94 /* defaults: Read from stdin, 0 filesize (no completion estimate) */
95 fd = STDIN_FILENO; 95 fd = STDIN_FILENO;
96 filesize = 0; 96 filesize = 0;
97 buffersize = 64 * 1024; 97 buffersize = 64 * 1024;
98 prefix = NULL; 98 prefix = NULL;
99 99
100 while ((ch = getopt(argc, argv, "b:ef:l:p:z")) != -1) 100 while ((ch = getopt(argc, argv, "b:ef:l:p:z")) != -1)
101 switch (ch) { 101 switch (ch) {
102 case 'b': 102 case 'b':
103 buffersize = (size_t) strsuftoll("buffer size", optarg, 103 buffersize = (size_t) strsuftoll("buffer size", optarg,
104 0, SSIZE_MAX); 104 0, SSIZE_MAX);
105 break; 105 break;
106 case 'e': 106 case 'e':
107 eflag++; 107 eflag++;
108 break; 108 break;
109 case 'f': 109 case 'f':
110 infile = optarg; 110 infile = optarg;
111 break; 111 break;
112 case 'l': 112 case 'l':
113 lflag++; 113 lflag++;
114 filesize = strsuftoll("input size", optarg, 0, 114 filesize = strsuftoll("input size", optarg, 0,
115 LLONG_MAX); 115 LLONG_MAX);
116 break; 116 break;
117 case 'p': 117 case 'p':
118 prefix = optarg; 118 prefix = optarg;
119 break; 119 break;
120 case 'z': 120 case 'z':
121 zflag++; 121 zflag++;
122 break; 122 break;
123 case '?': 123 case '?':
124 default: 124 default:
125 usage(); 125 usage();
126 /* NOTREACHED */ 126 /* NOTREACHED */
127 } 127 }
128 argc -= optind; 128 argc -= optind;
129 argv += optind; 129 argv += optind;
130 130
131 if (argc < 1) 131 if (argc < 1)
132 usage(); 132 usage();
133 133
134 if (infile && (fd = open(infile, O_RDONLY, 0)) < 0) 134 if (infile && (fd = open(infile, O_RDONLY, 0)) < 0)
135 err(1, "%s", infile); 135 err(1, "%s", infile);
136 136
137 /* stat() to get the filesize unless overridden, or -z */ 137 /* stat() to get the filesize unless overridden, or -z */
138 if (!zflag && !lflag && (fstat(fd, &statb) == 0)) { 138 if (!zflag && !lflag && (fstat(fd, &statb) == 0)) {
139 if (S_ISFIFO(statb.st_mode)) { 139 if (S_ISFIFO(statb.st_mode)) {
140 /* stat(2) on pipe may return only the 140 /* stat(2) on pipe may return only the
141 * first few bytes with more coming. 141 * first few bytes with more coming.
142 * Don't trust! 142 * Don't trust!
143 */ 143 */
144 } else { 144 } else {
145 filesize = statb.st_size; 145 filesize = statb.st_size;
146 } 146 }
147 } 147 }
148 148
149 /* gzip -l the file if we have the name and -z is given */ 149 /* gzip -l the file if we have the name and -z is given */
150 if (zflag && !lflag && infile != NULL) { 150 if (zflag && !lflag && infile != NULL) {
151 FILE *gzipsizepipe; 151 FILE *gzipsizepipe;
152 char buf[256], *cp, *cmd; 152 char buf[256], *cp, *cmd;
153 153
154 /* 154 /*
155 * Read second word of last line of gzip -l output. Looks like: 155 * Read second word of last line of gzip -l output. Looks like:
156 * % gzip -l ../etc.tgz  156 * % gzip -l ../etc.tgz
157 * compressed uncompressed ratio uncompressed_name 157 * compressed uncompressed ratio uncompressed_name
158 * 119737 696320 82.8% ../etc.tar 158 * 119737 696320 82.8% ../etc.tar
159 */ 159 */
160 160
161 asprintf(&cmd, "gzip -l %s", infile); 161 asprintf(&cmd, "gzip -l %s", infile);
162 if ((gzipsizepipe = popen(cmd, "r")) == NULL) 162 if ((gzipsizepipe = popen(cmd, "r")) == NULL)
163 err(1, "reading compressed file length"); 163 err(1, "reading compressed file length");
164 for (; fgets(buf, 256, gzipsizepipe) != NULL;) 164 for (; fgets(buf, 256, gzipsizepipe) != NULL;)
165 continue; 165 continue;
166 strtoimax(buf, &cp, 10); 166 strtoimax(buf, &cp, 10);
167 filesize = strtoimax(cp, NULL, 10); 167 filesize = strtoimax(cp, NULL, 10);
168 if (pclose(gzipsizepipe) < 0) 168 if (pclose(gzipsizepipe) < 0)
169 err(1, "closing compressed file length pipe"); 169 err(1, "closing compressed file length pipe");
170 free(cmd); 170 free(cmd);
171 } 171 }
172 /* Pipe input through gzip -dc if -z is given */ 172 /* Pipe input through gzip -dc if -z is given */
173 if (zflag) { 173 if (zflag) {
174 int gzippipe[2]; 174 int gzippipe[2];
175 175
176 if (pipe(gzippipe) < 0) 176 if (pipe(gzippipe) < 0)
177 err(1, "gzip pipe"); 177 err(1, "gzip pipe");
178 gzippid = fork(); 178 gzippid = fork();
179 if (gzippid < 0) 179 if (gzippid < 0)
180 err(1, "fork for gzip"); 180 err(1, "fork for gzip");
181 181
182 if (gzippid) { 182 if (gzippid) {
183 /* parent */ 183 /* parent */
184 dup2(gzippipe[0], fd); 184 dup2(gzippipe[0], fd);
185 close(gzippipe[0]); 185 close(gzippipe[0]);
186 close(gzippipe[1]); 186 close(gzippipe[1]);
187 } else { 187 } else {
188 dup2(gzippipe[1], STDOUT_FILENO); 188 dup2(gzippipe[1], STDOUT_FILENO);
189 dup2(fd, STDIN_FILENO); 189 dup2(fd, STDIN_FILENO);
190 close(gzippipe[0]); 190 close(gzippipe[0]);
191 close(gzippipe[1]); 191 close(gzippipe[1]);
192 if (execlp("gzip", "gzip", "-dc", NULL)) 192 if (execlp("gzip", "gzip", "-dc", NULL))
193 err(1, "exec()ing gzip"); 193 err(1, "exec()ing gzip");
194 } 194 }
195 } 195 }
196 196
197 /* Initialize progressbar.c's global state */ 197 /* Initialize progressbar.c's global state */
198 bytes = 0; 198 bytes = 0;
199 progress = 1; 199 progress = 1;
200 ttyout = eflag ? stderr : stdout; 200 ttyout = eflag ? stderr : stdout;
201 201
202 if (ioctl(fileno(ttyout), TIOCGSIZE, &ts) == -1) 202 if (ioctl(fileno(ttyout), TIOCGSIZE, &ts) == -1)
203 ttywidth = 80; 203 ttywidth = 80;
204 else 204 else
205 ttywidth = ts.ts_cols; 205 ttywidth = ts.ts_cols;
206 206
207 fb_buf = malloc(buffersize); 207 fb_buf = malloc(buffersize);
208 if (fb_buf == NULL) 208 if (fb_buf == NULL)
209 err(1, "malloc for buffersize"); 209 err(1, "malloc for buffersize");
210 210
211 if (pipe(outpipe) < 0) 211 if (pipe(outpipe) < 0)
212 err(1, "output pipe"); 212 err(1, "output pipe");
213 pid = fork(); 213 pid = fork();
214 if (pid < 0) 214 if (pid < 0)
215 err(1, "fork for output pipe"); 215 err(1, "fork for output pipe");
216 216
217 if (pid == 0) { 217 if (pid == 0) {
218 /* child */ 218 /* child */
219 dup2(outpipe[0], STDIN_FILENO); 219 dup2(outpipe[0], STDIN_FILENO);
220 close(outpipe[0]); 220 close(outpipe[0]);
221 close(outpipe[1]); 221 close(outpipe[1]);
222 execvp(argv[0], argv); 222 execvp(argv[0], argv);
223 err(1, "could not exec %s", argv[0]); 223 err(1, "could not exec %s", argv[0]);
224 } 224 }
225 close(outpipe[0]); 225 close(outpipe[0]);
226 226
227 signal(SIGPIPE, broken_pipe); 227 signal(SIGPIPE, broken_pipe);
228 progressmeter(-1); 228 progressmeter(-1);
229 229
230 while (1) { 230 while (1) {
231 do { 231 do {
232 nr = read(fd, fb_buf, buffersize); 232 nr = read(fd, fb_buf, buffersize);
233 } while (nr < 0 && errno == EINTR); 233 } while (nr < 0 && errno == EINTR);
234 if (nr <= 0) 234 if (nr <= 0)
235 break; 235 break;
236 for (off = 0; nr; nr -= nw, off += nw, bytes += nw) 236 for (off = 0; nr; nr -= nw, off += nw, bytes += nw)
237 if ((nw = write(outpipe[1], fb_buf + off, 237 if ((nw = write(outpipe[1], fb_buf + off,
238 (size_t) nr)) < 0) { 238 (size_t) nr)) < 0) {
 239 if (errno == EINTR) {
 240 nw = 0;
 241 continue;
 242 }
239 progressmeter(1); 243 progressmeter(1);
240 err(1, "writing %u bytes to output pipe", 244 err(1, "writing %u bytes to output pipe",
241 (unsigned) nr); 245 (unsigned) nr);
242 } 246 }
243 } 247 }
244 close(outpipe[1]); 248 close(outpipe[1]);
245 249
246 gzipstat = 0; 250 gzipstat = 0;
247 cmdstat = 0; 251 cmdstat = 0;
248 while (pid || gzippid) { 252 while (pid || gzippid) {
249 deadpid = wait(&ws); 253 deadpid = wait(&ws);
250 /* 254 /*
251 * We need to exit with an error if the command (or gzip) 255 * We need to exit with an error if the command (or gzip)
252 * exited abnormally. 256 * exited abnormally.
253 * Unfortunately we can't generate a true 'exited by signal' 257 * Unfortunately we can't generate a true 'exited by signal'
254 * error without sending the signal to ourselves :-( 258 * error without sending the signal to ourselves :-(
255 */ 259 */
256 ws = WIFSIGNALED(ws) ? WTERMSIG(ws) : WEXITSTATUS(ws); 260 ws = WIFSIGNALED(ws) ? WTERMSIG(ws) : WEXITSTATUS(ws);
257 261
258 if (deadpid != -1 && errno == EINTR) 262 if (deadpid != -1 && errno == EINTR)
259 continue; 263 continue;
260 if (deadpid == pid) { 264 if (deadpid == pid) {
261 pid = 0; 265 pid = 0;
262 cmdstat = ws; 266 cmdstat = ws;
263 continue; 267 continue;
264 } 268 }
265 if (deadpid == gzippid) { 269 if (deadpid == gzippid) {
266 gzippid = 0; 270 gzippid = 0;
267 gzipstat = ws; 271 gzipstat = ws;
268 continue; 272 continue;
269 } 273 }
270 break; 274 break;
271 } 275 }
272 276
273 progressmeter(1); 277 progressmeter(1);
274 signal(SIGPIPE, SIG_DFL); 278 signal(SIGPIPE, SIG_DFL);
275 279
276 free(fb_buf); 280 free(fb_buf);
277 281
278 exit(cmdstat ? cmdstat : gzipstat); 282 exit(cmdstat ? cmdstat : gzipstat);
279} 283}