1 1.6 rillig /* $NetBSD: h_intr.c,v 1.6 2021/09/11 18:18:28 rillig 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.6 rillig * 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.6 rillig __RCSID("$NetBSD: h_intr.c,v 1.6 2021/09/11 18:18:28 rillig 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.5 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.5 christos const 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.5 christos static ssize_t (*rd_fn)(FILE *, void *, size_t); /* read fn. */ 78 1.5 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.5 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.5 christos smaxread(FILE* fp, void *buf, size_t size) 209 1.1 christos { 210 1.5 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.5 christos maxread(FILE* fp, void *buf, size_t size) 235 1.1 christos { 236 1.5 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.5 christos smaxwrite(FILE* fp, const void *buf, size_t size) 266 1.1 christos { 267 1.5 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.5 christos maxwrite(FILE* fp, const void *buf, size_t size) 291 1.1 christos { 292 1.5 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.6 rillig { 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.6 rillig 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.5 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