1 /* $NetBSD: h_intr.c,v 1.6 2021/09/11 18:18:28 rillig Exp $ */
2
3 /**
4 * Test of interrupted I/O to popen()ed commands.
5 *
6 * Example 1:
7 * ./h_intr -c "gzip -t" *.gz
8 *
9 * Example 2:
10 * while :; do ./h_intr -b $((12*1024)) -t 10 -c "bzip2 -t" *.bz2; sleep 2; done
11 *
12 * Example 3:
13 * Create checksum file:
14 * find /mnt -type f -exec sha512 -n {} + >SHA512
15 *
16 * Check program:
17 * find /mnt -type f -exec ./h_intr -b 512 -c run.sh {} +
18 *
19 * ./run.sh:
20 #!/bin/sh
21 set -eu
22 grep -q "^$(sha512 -q)" SHA512
23 *
24 * Author: RVP at sdf.org
25 */
26
27 #include <sys/cdefs.h>
28 __RCSID("$NetBSD: h_intr.c,v 1.6 2021/09/11 18:18:28 rillig Exp $");
29
30 #include <time.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <stdbool.h>
34 #include <libgen.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40
41 static bool process(const char *fn);
42 ssize_t maxread(FILE *fp, void *buf, size_t size);
43 ssize_t smaxread(FILE *fp, void *buf, size_t size);
44 ssize_t maxwrite(FILE *fp, const void *buf, size_t size);
45 ssize_t smaxwrite(FILE *fp, const void *buf, size_t size);
46 static int rndbuf(void);
47 static int rndmode(void);
48 static sig_t xsignal(int signo, sig_t handler);
49 static void alarmtimer(int wait);
50 static void pr_star(int signo);
51 static int do_opts(int argc, char *argv[]);
52 static void usage(FILE *fp);
53
54 /* Globals */
55 static struct options {
56 const char *cmd; /* cmd to run (which must read from stdin) */
57 size_t bsize; /* block size to use */
58 size_t asize; /* alt. stdio buffer size */
59 int btype; /* buffering type: _IONBF, ... */
60 int tmout; /* alarm timeout */
61 int flush; /* call fflush() after write if 1 */
62 int rndbuf; /* switch buffer randomly if 1 */
63 int rndmod; /* switch buffering modes randomly if 1 */
64 } opts;
65
66 static const struct {
67 const char *name;
68 int value;
69 } btypes[] = {
70 { "IONBF", _IONBF },
71 { "IOLBF", _IOLBF },
72 { "IOFBF", _IOFBF },
73 };
74
75 static void (*alarm_fn)(int); /* real/dummy alarm fn. */
76 static int (*sintr_fn)(int, int); /* " siginterrupt fn. */
77 static ssize_t (*rd_fn)(FILE *, void *, size_t); /* read fn. */
78 static ssize_t (*wr_fn)(FILE *, const void *, size_t); /* write fn. */
79
80 enum {
81 MB = 1024 * 1024, /* a megabyte */
82 BSIZE = 16 * 1024, /* default RW buffer size */
83 DEF_MS = 100, /* interrupt 10x a second */
84 MS = 1000, /* msecs. in a second */
85 };
86
87
88
89
90 /**
91 * M A I N
92 */
93 int
main(int argc,char * argv[])94 main(int argc, char *argv[])
95 {
96 int i, rc = EXIT_SUCCESS;
97
98 i = do_opts(argc, argv);
99 argc -= i;
100 argv += i;
101
102 if (argc == 0) {
103 usage(stderr);
104 return rc;
105 }
106
107 xsignal(SIGPIPE, SIG_IGN);
108 for (i = 0; i < argc; i++) {
109 char *s = strdup(argv[i]);
110 printf("%s...", basename(s));
111 fflush(stdout);
112 free(s);
113
114 sig_t osig = xsignal(SIGALRM, pr_star);
115
116 if (process(argv[i]) == true)
117 printf(" OK\n");
118 else
119 rc = EXIT_FAILURE;
120
121 xsignal(SIGALRM, osig);
122 }
123
124 return rc;
125 }
126
127 static bool
process(const char * fn)128 process(const char *fn)
129 {
130 FILE *ifp, *ofp;
131 char *buf, *abuf;
132 int rc = false;
133 size_t nw = 0;
134 ssize_t n;
135
136 abuf = NULL;
137
138 if ((buf = malloc(opts.bsize)) == NULL) {
139 warn("buffer alloc failed");
140 return rc;
141 }
142
143 if ((abuf = malloc(opts.asize)) == NULL) {
144 warn("alt. buffer alloc failed");
145 goto fail;
146 }
147
148 if ((ifp = fopen(fn, "r")) == NULL) {
149 warn("fopen failed: %s", fn);
150 goto fail;
151 }
152
153 if ((ofp = popen(opts.cmd, "w")) == NULL) {
154 warn("popen failed `%s'", opts.cmd);
155 goto fail;
156 }
157
158 setvbuf(ofp, NULL, opts.btype, opts.asize);
159 setvbuf(ifp, NULL, opts.btype, opts.asize);
160
161 alarm_fn(opts.tmout);
162
163 while ((n = rd_fn(ifp, buf, opts.bsize)) > 0) {
164 ssize_t i;
165
166 if (opts.rndbuf || opts.rndmod) {
167 int r = rndbuf();
168 setvbuf(ofp, r ? abuf : NULL,
169 rndmode(), r ? opts.asize : 0);
170 }
171
172 sintr_fn(SIGALRM, 0);
173
174 if ((i = wr_fn(ofp, buf, n)) == -1) {
175 sintr_fn(SIGALRM, 1);
176 warn("write failed");
177 break;
178 }
179
180 if (opts.flush)
181 if (fflush(ofp))
182 warn("fflush failed");
183
184 sintr_fn(SIGALRM, 1);
185 nw += i;
186 }
187
188 alarm_fn(0);
189 // printf("%zu\n", nw);
190
191 fclose(ifp);
192 if (pclose(ofp) != 0)
193 warn("command failed `%s'", opts.cmd);
194 else
195 rc = true;
196
197 fail:
198 free(abuf);
199 free(buf);
200
201 return rc;
202 }
203
204 /**
205 * maxread - syscall version
206 */
207 ssize_t
smaxread(FILE * fp,void * buf,size_t size)208 smaxread(FILE* fp, void *buf, size_t size)
209 {
210 char *p = buf;
211 ssize_t nrd = 0;
212 ssize_t n;
213
214 while (size > 0) {
215 n = read(fileno(fp), p, size);
216 if (n < 0) {
217 if (errno == EINTR)
218 continue;
219 else
220 return -1;
221 } else if (n == 0)
222 break;
223 p += n;
224 nrd += n;
225 size -= n;
226 }
227 return nrd;
228 }
229
230 /**
231 * maxread - stdio version
232 */
233 ssize_t
maxread(FILE * fp,void * buf,size_t size)234 maxread(FILE* fp, void *buf, size_t size)
235 {
236 char *p = buf;
237 ssize_t nrd = 0;
238 size_t n;
239
240 while (size > 0) {
241 errno = 0;
242 n = fread(p, 1, size, fp);
243 if (n == 0) {
244 printf("ir.");
245 fflush(stdout);
246 if (errno == EINTR)
247 continue;
248 if (feof(fp) || nrd > 0)
249 break;
250 return -1;
251 }
252 if (n != size)
253 clearerr(fp);
254 p += n;
255 nrd += n;
256 size -= n;
257 }
258 return nrd;
259 }
260
261 /**
262 * maxwrite - syscall version
263 */
264 ssize_t
smaxwrite(FILE * fp,const void * buf,size_t size)265 smaxwrite(FILE* fp, const void *buf, size_t size)
266 {
267 const char *p = buf;
268 ssize_t nwr = 0;
269 ssize_t n;
270
271 while (size > 0) {
272 n = write(fileno(fp), p, size);
273 if (n <= 0) {
274 if (errno == EINTR)
275 n = 0;
276 else
277 return -1;
278 }
279 p += n;
280 nwr += n;
281 size -= n;
282 }
283 return nwr;
284 }
285
286 /**
287 * maxwrite - stdio version (warning: substrate may be buggy)
288 */
289 ssize_t
maxwrite(FILE * fp,const void * buf,size_t size)290 maxwrite(FILE* fp, const void *buf, size_t size)
291 {
292 const char *p = buf;
293 ssize_t nwr = 0;
294 size_t n;
295
296 while (size > 0) {
297 errno = 0;
298 n = fwrite(p, 1, size, fp);
299 if (n == 0) {
300 printf("iw.");
301 fflush(stdout);
302 if (errno == EINTR)
303 continue;
304 if (nwr > 0)
305 break;
306 return -1;
307 }
308 if (n != size)
309 clearerr(fp);
310 p += n;
311 nwr += n;
312 size -= n;
313 }
314 return nwr;
315 }
316
317 static int
rndbuf(void)318 rndbuf(void)
319 {
320 if (opts.rndbuf == 0)
321 return 0;
322 return arc4random_uniform(2);
323 }
324
325 static int
rndmode(void)326 rndmode(void)
327 {
328 if (opts.rndmod == 0)
329 return opts.btype;
330
331 switch (arc4random_uniform(3)) {
332 case 0: return _IONBF;
333 case 1: return _IOLBF;
334 case 2: return _IOFBF;
335 default: errx(EXIT_FAILURE, "programmer error!");
336 }
337 }
338
339 /**
340 * wrapper around sigaction() because we want POSIX semantics:
341 * no auto-restarting of interrupted slow syscalls.
342 */
343 static sig_t
xsignal(int signo,sig_t handler)344 xsignal(int signo, sig_t handler)
345 {
346 struct sigaction sa, osa;
347
348 sa.sa_handler = handler;
349 sa.sa_flags = 0;
350 sigemptyset(&sa.sa_mask);
351 if (sigaction(signo, &sa, &osa) < 0)
352 return SIG_ERR;
353 return osa.sa_handler;
354 }
355
356 static void
alarmtimer(int wait)357 alarmtimer(int wait)
358 {
359 struct itimerval itv;
360
361 itv.it_value.tv_sec = wait / MS;
362 itv.it_value.tv_usec = (wait - itv.it_value.tv_sec * MS) * MS;
363 itv.it_interval = itv.it_value;
364 setitimer(ITIMER_REAL, &itv, NULL);
365 }
366
367 static void
dummytimer(int dummy)368 dummytimer(int dummy)
369 {
370 (void)dummy;
371 }
372
373 static int
dummysintr(int dum1,int dum2)374 dummysintr(int dum1, int dum2)
375 {
376 (void)dum1;
377 (void)dum2;
378 return 0; /* OK */
379 }
380
381 /**
382 * Print a `*' each time an alarm signal occurs.
383 */
384 static void
pr_star(int signo)385 pr_star(int signo)
386 {
387 int oe = errno;
388 (void)signo;
389
390 #if 0
391 write(1, "*", 1);
392 #endif
393 errno = oe;
394 }
395
396 /**
397 * return true if not empty or blank; false otherwise.
398 */
399 static bool
isvalid(const char * s)400 isvalid(const char *s)
401 {
402 return strspn(s, " \t") != strlen(s);
403 }
404
405 static const char *
btype2str(int val)406 btype2str(int val)
407 {
408 for (size_t i = 0; i < __arraycount(btypes); i++)
409 if (btypes[i].value == val)
410 return btypes[i].name;
411 return "*invalid*";
412 }
413
414 static int
str2btype(const char * s)415 str2btype(const char *s)
416 {
417 for (size_t i = 0; i < __arraycount(btypes); i++)
418 if (strcmp(btypes[i].name, s) == 0)
419 return btypes[i].value;
420 return EOF;
421 }
422
423 /**
424 * Print usage information.
425 */
426 static void
usage(FILE * fp)427 usage(FILE* fp)
428 {
429 fprintf(fp, "Usage: %s [-a SIZE] [-b SIZE] [-fihmnrsw]"
430 " [-p TYPE] [-t TMOUT] -c CMD FILE...\n",
431 getprogname());
432 fprintf(fp, "%s: Test interrupted writes to popen()ed CMD.\n",
433 getprogname());
434 fprintf(fp, "\n");
435 fprintf(fp, "Usual options:\n");
436 fprintf(fp, " -a SIZE Alt. stdio buffer size (%zu)\n", opts.asize);
437 fprintf(fp, " -b SIZE Program buffer size (%zu)\n", opts.bsize);
438 fprintf(fp, " -c CMD Command to run on each FILE\n");
439 fprintf(fp, " -h This message\n");
440 fprintf(fp, " -p TYPE Buffering type (%s)\n", btype2str(opts.btype));
441 fprintf(fp, " -t TMOUT Interrupt writing to CMD every (%d) ms\n",
442 opts.tmout);
443 fprintf(fp, "Debug options:\n");
444 fprintf(fp, " -f Do fflush() after writing each block\n");
445 fprintf(fp, " -i Use siginterrupt to block interrupts\n");
446 fprintf(fp, " -m Use random buffering modes\n");
447 fprintf(fp, " -n No interruptions (turns off -i)\n");
448 fprintf(fp, " -r Use read() instead of fread()\n");
449 fprintf(fp, " -s Switch between own/stdio buffers at random\n");
450 fprintf(fp, " -w Use write() instead of fwrite()\n");
451 }
452
453 /**
454 * Process program options.
455 */
456 static int
do_opts(int argc,char * argv[])457 do_opts(int argc, char *argv[])
458 {
459 int opt, i;
460
461 /* defaults */
462 opts.cmd = "";
463 opts.btype = _IONBF;
464 opts.asize = BSIZE; /* 16K */
465 opts.bsize = BSIZE; /* 16K */
466 opts.tmout = DEF_MS; /* 100ms */
467 opts.flush = 0; /* no fflush() after each write */
468 opts.rndbuf = 0; /* no random buffer switching */
469 opts.rndmod = 0; /* no random mode " */
470 alarm_fn = alarmtimer;
471 sintr_fn = dummysintr; /* don't protect writes with siginterrupt() */
472 rd_fn = maxread; /* read using stdio funcs. */
473 wr_fn = maxwrite; /* write " */
474
475 while ((opt = getopt(argc, argv, "a:b:c:fhimnp:rst:w")) != -1) {
476 switch (opt) {
477 case 'a':
478 i = atoi(optarg);
479 if (i <= 0 || i > MB)
480 errx(EXIT_FAILURE,
481 "alt. buffer size not in range (1 - %d): %d",
482 MB, i);
483 opts.asize = i;
484 break;
485 case 'b':
486 i = atoi(optarg);
487 if (i <= 0 || i > MB)
488 errx(EXIT_FAILURE,
489 "buffer size not in range (1 - %d): %d",
490 MB, i);
491 opts.bsize = i;
492 break;
493 case 'c':
494 opts.cmd = optarg;
495 break;
496 case 'f':
497 opts.flush = 1;
498 break;
499 case 'i':
500 sintr_fn = siginterrupt;
501 break;
502 case 'm':
503 opts.rndmod = 1;
504 break;
505 case 'n':
506 alarm_fn = dummytimer;
507 break;
508 case 'p':
509 i = str2btype(optarg);
510 if (i == EOF)
511 errx(EXIT_FAILURE,
512 "unknown buffering type: `%s'", optarg);
513 opts.btype = i;
514 break;
515 case 'r':
516 rd_fn = smaxread;
517 break;
518 case 'w':
519 wr_fn = smaxwrite;
520 break;
521 case 's':
522 opts.rndbuf = 1;
523 break;
524 case 't':
525 i = atoi(optarg);
526 if ((i < 10 || i > 10000) && i != 0)
527 errx(EXIT_FAILURE,
528 "timeout not in range (10ms - 10s): %d", i);
529 opts.tmout = i;
530 break;
531 case 'h':
532 usage(stdout);
533 exit(EXIT_SUCCESS);
534 default:
535 usage(stderr);
536 exit(EXIT_FAILURE);
537 }
538 }
539
540 if (!isvalid(opts.cmd))
541 errx(EXIT_FAILURE, "Please specify a valid command with -c");
542
543 /* don't call siginterrupt() if not interrupting */
544 if (alarm_fn == dummytimer)
545 sintr_fn = dummysintr;
546
547 return optind;
548 }
549