xref: /netbsd-src/tests/lib/libc/stdio/h_intr.c (revision bdb5d51c43e0698030f2d2eaf8207053047a3dbb)
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