Home | History | Annotate | Line # | Download | only in stdio
      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
     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
    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
    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
    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
    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
    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
    318 rndbuf(void)
    319 {
    320 	if (opts.rndbuf == 0)
    321 		return 0;
    322 	return arc4random_uniform(2);
    323 }
    324 
    325 static int
    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
    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
    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
    368 dummytimer(int dummy)
    369 {
    370 	(void)dummy;
    371 }
    372 
    373 static int
    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
    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
    400 isvalid(const char *s)
    401 {
    402 	return strspn(s, " \t") != strlen(s);
    403 }
    404 
    405 static const char *
    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
    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
    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
    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