h_intr.c revision 1.2 1 1.2 christos /* $NetBSD: h_intr.c,v 1.2 2021/07/08 15:21:40 christos Exp $ */
2 1.1 christos
3 1.1 christos /**
4 1.1 christos * Test of interrupted writes to popen()'ed commands.
5 1.1 christos *
6 1.1 christos * Example 1:
7 1.1 christos * ./h_fwrite -c "gzip -t" *.gz
8 1.1 christos *
9 1.1 christos * Example 2:
10 1.1 christos * while :; do ./h_fwrite -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.1 christos * find /mnt -type f -exec ./h_fwrite -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.2 christos __RCSID("$NetBSD: h_intr.c,v 1.2 2021/07/08 15:21:40 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.1 christos static int 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.1 christos static sig_t xsignal(int signo, sig_t handler);
47 1.1 christos static void alarmtimer(int wait);
48 1.1 christos static void pr_star(int signo);
49 1.1 christos static bool isvalid(const char *s);
50 1.1 christos static int do_opts(int argc, char* argv[]);
51 1.1 christos static void usage(FILE* fp);
52 1.1 christos
53 1.1 christos /* Globals */
54 1.1 christos static struct options {
55 1.1 christos size_t bsize;
56 1.1 christos size_t ssize;
57 1.1 christos int btype;
58 1.1 christos int tmout;
59 1.1 christos const char *cmd;
60 1.1 christos } opts;
61 1.1 christos
62 1.1 christos static const struct {
63 1.1 christos const char *name;
64 1.1 christos int value;
65 1.1 christos } btypes[] = {
66 1.1 christos { "IONBF", _IONBF },
67 1.1 christos { "IOLBF", _IOLBF },
68 1.1 christos { "IOFBF", _IOFBF },
69 1.1 christos };
70 1.1 christos
71 1.1 christos enum {
72 1.1 christos MB = 1024 * 1024,
73 1.1 christos BSIZE = 16 * 1024,
74 1.1 christos DEF_MS = 100,
75 1.1 christos MS = 1000,
76 1.1 christos };
77 1.1 christos
78 1.1 christos
79 1.1 christos
80 1.1 christos int
81 1.1 christos main(int argc, char* argv[])
82 1.1 christos {
83 1.1 christos int i, rc = EXIT_SUCCESS;
84 1.1 christos
85 1.1 christos i = do_opts(argc, argv);
86 1.1 christos argc -= i;
87 1.1 christos argv += i;
88 1.1 christos
89 1.1 christos if (argc == 0) {
90 1.1 christos usage(stderr);
91 1.1 christos return rc;
92 1.1 christos }
93 1.1 christos
94 1.1 christos xsignal(SIGPIPE, SIG_IGN);
95 1.1 christos for (i = 0; i < argc; i++) {
96 1.1 christos char *s = strdup(argv[i]);
97 1.1 christos printf("%s...", basename(s));
98 1.1 christos fflush(stdout);
99 1.1 christos free(s);
100 1.1 christos
101 1.1 christos sig_t osig = xsignal(SIGALRM, pr_star);
102 1.1 christos
103 1.1 christos if (process(argv[i]) == 0)
104 1.1 christos printf("ok\n");
105 1.1 christos else
106 1.1 christos rc = EXIT_FAILURE;
107 1.1 christos
108 1.1 christos xsignal(SIGALRM, osig);
109 1.1 christos }
110 1.1 christos
111 1.1 christos return rc;
112 1.1 christos }
113 1.1 christos
114 1.1 christos static int
115 1.1 christos process(const char *fn)
116 1.1 christos {
117 1.1 christos FILE *ifp, *ofp;
118 1.1 christos char *buf;
119 1.1 christos size_t nw = 0;
120 1.1 christos int rc = EXIT_FAILURE;
121 1.1 christos ssize_t n;
122 1.1 christos
123 1.1 christos if ((buf = malloc(opts.bsize)) == NULL)
124 1.1 christos err(rc, "buffer alloc failed");
125 1.1 christos
126 1.1 christos if ((ifp = fopen(fn, "r")) == NULL) {
127 1.1 christos warn("fopen failed: %s", fn);
128 1.1 christos return rc;
129 1.1 christos }
130 1.1 christos
131 1.1 christos if ((ofp = popen(opts.cmd, "w")) == NULL)
132 1.1 christos err(rc, "popen failed `%s'", opts.cmd);
133 1.1 christos
134 1.1 christos setvbuf(ofp, NULL, opts.btype, opts.ssize);
135 1.1 christos setvbuf(ifp, NULL, opts.btype, opts.ssize);
136 1.1 christos
137 1.1 christos alarmtimer(opts.tmout);
138 1.1 christos while ((n = maxread(ifp, buf, opts.bsize)) > 0) {
139 1.1 christos ssize_t i;
140 1.1 christos if ((i = maxwrite(ofp, buf, n)) == -1) {
141 1.1 christos warn("write failed");
142 1.1 christos break;
143 1.1 christos }
144 1.1 christos nw += i;
145 1.1 christos }
146 1.1 christos alarmtimer(0);
147 1.2 christos // printf("%zu\n", nw);
148 1.1 christos
149 1.1 christos fclose(ifp);
150 1.1 christos if (pclose(ofp) != 0)
151 1.1 christos warn("command failed `%s'", opts.cmd);
152 1.1 christos else
153 1.1 christos rc = EXIT_SUCCESS;
154 1.1 christos
155 1.1 christos return rc;
156 1.1 christos }
157 1.1 christos
158 1.1 christos /**
159 1.1 christos * maxread - syscall version
160 1.1 christos */
161 1.1 christos ssize_t
162 1.1 christos smaxread(FILE* fp, void* buf, size_t size)
163 1.1 christos {
164 1.1 christos char* p = buf;
165 1.1 christos ssize_t nrd = 0;
166 1.1 christos ssize_t n;
167 1.1 christos
168 1.1 christos while (size > 0) {
169 1.1 christos n = read(fileno(fp), p, size);
170 1.1 christos if (n < 0) {
171 1.1 christos if (errno == EINTR)
172 1.1 christos continue;
173 1.1 christos else
174 1.1 christos return -1;
175 1.1 christos } else if (n == 0)
176 1.1 christos break;
177 1.1 christos p += n;
178 1.1 christos nrd += n;
179 1.1 christos size -= n;
180 1.1 christos }
181 1.1 christos return nrd;
182 1.1 christos }
183 1.1 christos
184 1.1 christos /**
185 1.1 christos * maxread - stdio version
186 1.1 christos */
187 1.1 christos ssize_t
188 1.1 christos maxread(FILE* fp, void* buf, size_t size)
189 1.1 christos {
190 1.1 christos char* p = buf;
191 1.1 christos ssize_t nrd = 0;
192 1.1 christos size_t n;
193 1.1 christos
194 1.1 christos while (size > 0) {
195 1.1 christos errno = 0;
196 1.1 christos n = fread(p, 1, size, fp);
197 1.1 christos if (n == 0) {
198 1.1 christos printf("ir.");
199 1.1 christos fflush(stdout);
200 1.1 christos if (errno == EINTR)
201 1.1 christos continue;
202 1.1 christos if (feof(fp) || nrd > 0)
203 1.1 christos break;
204 1.1 christos return -1;
205 1.1 christos }
206 1.1 christos if (n != size)
207 1.1 christos clearerr(fp);
208 1.1 christos p += n;
209 1.1 christos nrd += n;
210 1.1 christos size -= n;
211 1.1 christos }
212 1.1 christos return nrd;
213 1.1 christos }
214 1.1 christos
215 1.1 christos /**
216 1.1 christos * maxwrite - syscall version
217 1.1 christos */
218 1.1 christos ssize_t
219 1.1 christos smaxwrite(FILE* fp, const void* buf, size_t size)
220 1.1 christos {
221 1.1 christos const char* p = buf;
222 1.1 christos ssize_t nwr = 0;
223 1.1 christos ssize_t n;
224 1.1 christos
225 1.1 christos while (size > 0) {
226 1.1 christos n = write(fileno(fp), p, size);
227 1.1 christos if (n <= 0) {
228 1.1 christos if (errno == EINTR)
229 1.1 christos n = 0;
230 1.1 christos else
231 1.1 christos return -1;
232 1.1 christos }
233 1.1 christos p += n;
234 1.1 christos nwr += n;
235 1.1 christos size -= n;
236 1.1 christos }
237 1.1 christos return nwr;
238 1.1 christos }
239 1.1 christos
240 1.1 christos /**
241 1.1 christos * maxwrite - stdio version (substrate is buggy)
242 1.1 christos */
243 1.1 christos ssize_t
244 1.1 christos maxwrite(FILE* fp, const void* buf, size_t size)
245 1.1 christos {
246 1.1 christos const char* p = buf;
247 1.1 christos ssize_t nwr = 0;
248 1.1 christos size_t n;
249 1.1 christos
250 1.1 christos while (size > 0) {
251 1.1 christos errno = 0;
252 1.1 christos n = fwrite(p, 1, size, fp);
253 1.1 christos if (n == 0) {
254 1.1 christos printf("iw.");
255 1.1 christos fflush(stdout);
256 1.1 christos if (errno == EINTR)
257 1.1 christos continue;
258 1.1 christos if (nwr > 0)
259 1.1 christos break;
260 1.1 christos return -1;
261 1.1 christos }
262 1.1 christos if (n != size)
263 1.1 christos clearerr(fp);
264 1.1 christos p += n;
265 1.1 christos nwr += n;
266 1.1 christos size -= n;
267 1.1 christos }
268 1.1 christos return nwr;
269 1.1 christos }
270 1.1 christos
271 1.1 christos /**
272 1.1 christos * wrapper around sigaction() because we want POSIX semantics:
273 1.1 christos * no auto-restarting of interrupted slow syscalls.
274 1.1 christos */
275 1.1 christos static sig_t
276 1.1 christos xsignal(int signo, sig_t handler)
277 1.1 christos {
278 1.1 christos struct sigaction sa, osa;
279 1.1 christos
280 1.1 christos sa.sa_handler = handler;
281 1.1 christos sa.sa_flags = 0;
282 1.1 christos sigemptyset(&sa.sa_mask);
283 1.1 christos if (sigaction(signo, &sa, &osa) < 0)
284 1.1 christos return SIG_ERR;
285 1.1 christos return osa.sa_handler;
286 1.1 christos }
287 1.1 christos
288 1.1 christos static void
289 1.1 christos alarmtimer(int wait)
290 1.1 christos {
291 1.1 christos struct itimerval itv;
292 1.1 christos
293 1.1 christos itv.it_value.tv_sec = wait / MS;
294 1.1 christos itv.it_value.tv_usec = (wait - itv.it_value.tv_sec * MS) * MS;
295 1.1 christos itv.it_interval = itv.it_value;
296 1.1 christos setitimer(ITIMER_REAL, &itv, NULL);
297 1.1 christos }
298 1.1 christos
299 1.1 christos /**
300 1.1 christos * Print a `*' each time an alarm signal occurs.
301 1.1 christos */
302 1.1 christos static void
303 1.1 christos pr_star(int signo)
304 1.1 christos {
305 1.1 christos int oe = errno;
306 1.1 christos (void)signo;
307 1.1 christos
308 1.1 christos #if 0
309 1.1 christos write(1, "*", 1);
310 1.1 christos #endif
311 1.1 christos errno = oe;
312 1.1 christos }
313 1.1 christos
314 1.1 christos /**
315 1.1 christos * return true if not empty or blank; FAIL otherwise.
316 1.1 christos */
317 1.1 christos static bool
318 1.1 christos isvalid(const char *s)
319 1.1 christos {
320 1.1 christos if (*s == '\0')
321 1.1 christos return false;
322 1.1 christos return strspn(s, " \t") != strlen(s);
323 1.1 christos }
324 1.1 christos
325 1.1 christos static const char *
326 1.1 christos getbtype(int val) {
327 1.1 christos for (size_t i = 0; i < __arraycount(btypes); i++)
328 1.1 christos if (btypes[i].value == val)
329 1.1 christos return btypes[i].name;
330 1.1 christos return "*invalid*";
331 1.1 christos }
332 1.1 christos
333 1.1 christos /**
334 1.1 christos * Print usage information.
335 1.1 christos */
336 1.1 christos static void
337 1.1 christos usage(FILE* fp)
338 1.1 christos {
339 1.1 christos fprintf(fp, "Usage: %s [-b SIZE] [-h] [-t TMOUT] -c CMD FILE...\n",
340 1.1 christos getprogname());
341 1.1 christos fprintf(fp, "%s: Test interrupted writes to popen()ed CMD.\n",
342 1.1 christos getprogname());
343 1.1 christos fprintf(fp, "\n");
344 1.2 christos fprintf(fp, " -b SIZE Buffer size (%zu)\n", opts.bsize);
345 1.1 christos fprintf(fp, " -c CMD Command to run on each FILE.\n");
346 1.1 christos fprintf(fp, " -h This message.\n");
347 1.1 christos fprintf(fp, " -p Buffering type %s.\n", getbtype(opts.btype));
348 1.2 christos fprintf(fp, " -s SIZE stdio buffer size (%zu)\n", opts.ssize);
349 1.1 christos fprintf(fp, " -t TMOUT Interrupt writing to CMD every (%d) ms\n",
350 1.1 christos opts.tmout);
351 1.1 christos }
352 1.1 christos
353 1.1 christos /**
354 1.1 christos * Process program options.
355 1.1 christos */
356 1.1 christos static int
357 1.1 christos do_opts(int argc, char *argv[])
358 1.1 christos {
359 1.1 christos int opt;
360 1.1 christos int i;
361 1.1 christos size_t j;
362 1.1 christos
363 1.1 christos /* defaults */
364 1.1 christos opts.btype = _IONBF;
365 1.1 christos opts.ssize = BSIZE; /* 16K */
366 1.1 christos opts.bsize = BSIZE; /* 16K */
367 1.1 christos opts.tmout = DEF_MS; /* 100ms */
368 1.1 christos opts.cmd = "";
369 1.1 christos
370 1.1 christos while ((opt = getopt(argc, argv, "b:c:hp:s:t:")) != -1) {
371 1.1 christos switch (opt) {
372 1.1 christos case 'b':
373 1.1 christos i = atoi(optarg);
374 1.1 christos if (i <= 0 || i > MB)
375 1.1 christos errx(EXIT_FAILURE,
376 1.1 christos "buffer size not in range (1 - %d): %d",
377 1.1 christos MB, i);
378 1.1 christos opts.bsize = i;
379 1.1 christos break;
380 1.1 christos case 'c':
381 1.1 christos opts.cmd = optarg;
382 1.1 christos break;
383 1.1 christos case 'h':
384 1.1 christos usage(stdout);
385 1.1 christos exit(EXIT_SUCCESS);
386 1.1 christos case 'p':
387 1.1 christos for (j = 0; j < __arraycount(btypes); j++)
388 1.1 christos if (strcmp(btypes[j].name, optarg) == 0) {
389 1.1 christos opts.btype = btypes[j].value;
390 1.1 christos break;
391 1.1 christos }
392 1.1 christos if (j == __arraycount(btypes))
393 1.1 christos errx(EXIT_FAILURE,
394 1.1 christos "unknown buffering type: `%s'", optarg);
395 1.1 christos break;
396 1.1 christos case 's':
397 1.1 christos i = atoi(optarg);
398 1.1 christos if (i <= 0 || i > MB)
399 1.1 christos errx(EXIT_FAILURE,
400 1.1 christos "buffer size not in range (1 - %d): %d",
401 1.1 christos MB, i);
402 1.1 christos opts.ssize = i;
403 1.1 christos break;
404 1.1 christos case 't':
405 1.1 christos i = atoi(optarg);
406 1.1 christos if ((i < 10 || i > 10000) && i != 0)
407 1.1 christos errx(EXIT_FAILURE,
408 1.1 christos "timeout not in range (10ms - 10s): %d", i);
409 1.1 christos opts.tmout = i;
410 1.1 christos break;
411 1.1 christos default:
412 1.1 christos usage(stderr);
413 1.1 christos exit(EXIT_FAILURE);
414 1.1 christos }
415 1.1 christos }
416 1.1 christos
417 1.1 christos if (!isvalid(opts.cmd))
418 1.1 christos errx(EXIT_FAILURE, "Please specify a valid command with -c");
419 1.1 christos
420 1.1 christos return optind;
421 1.1 christos }
422