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