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