]> git.ozlabs.org Git - ccan/blob - ccan/failtest/failtest.c
failtest: failtest_restore.h as an antidote to function overload.
[ccan] / ccan / failtest / failtest.c
1 #include <stdarg.h>
2 #include <string.h>
3 #include <stdio.h>
4 #include <stdarg.h>
5 #include <ctype.h>
6 #include <err.h>
7 #include <unistd.h>
8 #include <poll.h>
9 #include <errno.h>
10 #include <sys/types.h>
11 #include <sys/wait.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <ccan/read_write_all/read_write_all.h>
15 #include <ccan/failtest/failtest_proto.h>
16 #include <ccan/failtest/failtest.h>
17
18 bool (*failtest_hook)(struct failtest_call *history, unsigned num)
19 = failtest_default_hook;
20
21 unsigned int failtest_timeout_ms = 20000;
22
23 const char *failpath;
24
25 enum info_type {
26         WRITE,
27         FAILURE,
28         SUCCESS,
29         UNEXPECTED
30 };
31
32 struct write_info_hdr {
33         size_t len;
34         off_t offset;
35         int fd;
36 };
37
38 struct fd_orig {
39         int fd;
40         off_t offset;
41         size_t size;
42         bool dupped;
43 };
44
45 struct write_info {
46         struct write_info_hdr hdr;
47         char *data;
48         size_t oldlen;
49         char *olddata;
50 };
51
52 bool (*failtest_exit_check)(struct failtest_call *history, unsigned num);
53
54 static struct failtest_call *history = NULL;
55 static unsigned int history_num = 0;
56 static int control_fd = -1;
57
58 static struct write_info *writes = NULL;
59 static unsigned int writes_num = 0;
60
61 static struct write_info *child_writes = NULL;
62 static unsigned int child_writes_num = 0;
63
64 static struct fd_orig *fd_orig = NULL;
65 static unsigned int fd_orig_num = 0;
66
67 static const char info_to_arg[] = "mceoprw";
68
69 /* Dummy call used for failtest_undo wrappers. */
70 static struct failtest_call unrecorded_call;
71
72 static struct failtest_call *add_history_(enum failtest_call_type type,
73                                           const char *file,
74                                           unsigned int line,
75                                           const void *elem,
76                                           size_t elem_size)
77 {
78         /* NULL file is how we suppress failure. */
79         if (!file)
80                 return &unrecorded_call;
81
82         history = realloc(history, (history_num + 1) * sizeof(*history));
83         history[history_num].type = type;
84         history[history_num].file = file;
85         history[history_num].line = line;
86         memcpy(&history[history_num].u, elem, elem_size);
87         return &history[history_num++];
88 }
89
90 #define add_history(type, file, line, elem) \
91         add_history_((type), (file), (line), (elem), sizeof(*(elem)))
92
93 static void save_fd_orig(int fd)
94 {
95         unsigned int i;
96
97         for (i = 0; i < fd_orig_num; i++)
98                 if (fd_orig[i].fd == fd)
99                         return;
100
101         fd_orig = realloc(fd_orig, (fd_orig_num + 1) * sizeof(*fd_orig));
102         fd_orig[fd_orig_num].fd = fd;
103         fd_orig[fd_orig_num].dupped = false;
104         fd_orig[fd_orig_num].offset = lseek(fd, 0, SEEK_CUR);
105         fd_orig[fd_orig_num].size = lseek(fd, 0, SEEK_END);
106         lseek(fd, fd_orig[fd_orig_num].offset, SEEK_SET);
107         fd_orig_num++;
108 }
109
110 bool failtest_default_hook(struct failtest_call *history, unsigned num)
111 {
112         return true;
113 }
114
115 static bool read_write_info(int fd)
116 {
117         struct write_info_hdr hdr;
118
119         if (!read_all(fd, &hdr, sizeof(hdr)))
120                 return false;
121
122         child_writes = realloc(child_writes,
123                                (child_writes_num+1) * sizeof(child_writes[0]));
124         child_writes[child_writes_num].hdr = hdr;
125         child_writes[child_writes_num].data = malloc(hdr.len);
126         if (!read_all(fd, child_writes[child_writes_num].data, hdr.len))
127                 return false;
128
129         child_writes_num++;
130         return true;
131 }
132
133 static void print_reproduce(void)
134 {
135         unsigned int i;
136
137         printf("To reproduce: --failpath=");
138         for (i = 0; i < history_num; i++) {
139                 if (history[i].fail)
140                         printf("%c", toupper(info_to_arg[history[i].type]));
141                 else
142                         printf("%c", info_to_arg[history[i].type]);
143         }
144         printf("\n");
145 }
146
147 static void tell_parent(enum info_type type)
148 {
149         if (control_fd != -1)
150                 write_all(control_fd, &type, sizeof(type));
151 }
152
153 static void child_fail(const char *out, size_t outlen, const char *fmt, ...)
154 {
155         va_list ap;
156
157         va_start(ap, fmt);
158         vfprintf(stderr, fmt, ap);
159         va_end(ap);
160
161         fprintf(stderr, "%.*s", (int)outlen, out);
162         print_reproduce();
163         tell_parent(FAILURE);
164         exit(1);
165 }
166
167 static pid_t child;
168
169 static void hand_down(int signal)
170 {
171         kill(child, signal);
172 }
173
174 static bool should_fail(struct failtest_call *call)
175 {
176         int status;
177         int control[2], output[2];
178         enum info_type type = UNEXPECTED;
179         char *out = NULL;
180         size_t outlen = 0;
181
182         if (call == &unrecorded_call)
183                 return false;
184
185         if (failpath) {
186                 if (tolower(*failpath) != info_to_arg[call->type])
187                         errx(1, "Failpath expected '%c' got '%c'\n",
188                              info_to_arg[call->type], *failpath);
189                 return isupper(*(failpath++));
190         }
191
192         if (!failtest_hook(history, history_num)) {
193                 call->fail = false;
194                 return false;
195         }
196
197         /* We're going to fail in the child. */
198         call->fail = true;
199         if (pipe(control) != 0 || pipe(output) != 0)
200                 err(1, "opening pipe");
201
202         /* Prevent double-printing (in child and parent) */
203         fflush(stdout);
204         child = fork();
205         if (child == -1)
206                 err(1, "forking failed");
207
208         if (child == 0) {
209                 close(control[0]);
210                 close(output[0]);
211                 dup2(output[1], STDOUT_FILENO);
212                 dup2(output[1], STDERR_FILENO);
213                 if (output[1] != STDOUT_FILENO && output[1] != STDERR_FILENO)
214                         close(output[1]);
215                 control_fd = control[1];
216                 return true;
217         }
218
219         signal(SIGUSR1, hand_down);
220
221         close(control[1]);
222         close(output[1]);
223
224         /* We grab output so we can display it; we grab writes so we
225          * can compare. */
226         do {
227                 struct pollfd pfd[2];
228                 int ret;
229
230                 pfd[0].fd = output[0];
231                 pfd[0].events = POLLIN|POLLHUP;
232                 pfd[1].fd = control[0];
233                 pfd[1].events = POLLIN|POLLHUP;
234
235                 if (type == SUCCESS)
236                         ret = poll(pfd, 1, failtest_timeout_ms);
237                 else
238                         ret = poll(pfd, 2, failtest_timeout_ms);
239
240                 if (ret <= 0)
241                         hand_down(SIGUSR1);
242
243                 if (pfd[0].revents & POLLIN) {
244                         ssize_t len;
245
246                         out = realloc(out, outlen + 8192);
247                         len = read(output[0], out + outlen, 8192);
248                         outlen += len;
249                 } else if (type != SUCCESS && (pfd[1].revents & POLLIN)) {
250                         if (read_all(control[0], &type, sizeof(type))) {
251                                 if (type == WRITE) {
252                                         if (!read_write_info(control[0]))
253                                                 break;
254                                 }
255                         }
256                 } else if (pfd[0].revents & POLLHUP) {
257                         break;
258                 }
259         } while (type != FAILURE);
260
261         close(output[0]);
262         close(control[0]);
263         waitpid(child, &status, 0);
264         if (!WIFEXITED(status))
265                 child_fail(out, outlen, "Killed by signal %u: ",
266                            WTERMSIG(status));
267         /* Child printed failure already, just pass up exit code. */
268         if (type == FAILURE) {
269                 fprintf(stderr, "%.*s", (int)outlen, out);
270                 tell_parent(type);
271                 exit(WEXITSTATUS(status) ? WEXITSTATUS(status) : 1);
272         }
273         if (WEXITSTATUS(status) != 0)
274                 child_fail(out, outlen, "Exited with status %i: ",
275                            WEXITSTATUS(status));
276
277         free(out);
278         signal(SIGUSR1, SIG_DFL);
279
280         /* We continue onwards without failing. */
281         call->fail = false;
282         return false;
283 }
284
285 void *failtest_calloc(size_t nmemb, size_t size,
286                       const char *file, unsigned line)
287 {
288         struct failtest_call *p;
289         struct calloc_call call;
290         call.nmemb = nmemb;
291         call.size = size;
292         p = add_history(FAILTEST_CALLOC, file, line, &call);
293
294         if (should_fail(p)) {
295                 p->u.calloc.ret = NULL;
296                 p->error = ENOMEM;
297         } else {
298                 p->u.calloc.ret = calloc(nmemb, size);
299         }
300         errno = p->error;
301         return p->u.calloc.ret;
302 }
303
304 void *failtest_malloc(size_t size, const char *file, unsigned line)
305 {
306         struct failtest_call *p;
307         struct malloc_call call;
308         call.size = size;
309
310         p = add_history(FAILTEST_MALLOC, file, line, &call);
311         if (should_fail(p)) {
312                 p->u.calloc.ret = NULL;
313                 p->error = ENOMEM;
314         } else {
315                 p->u.calloc.ret = malloc(size);
316         }
317         errno = p->error;
318         return p->u.calloc.ret;
319 }
320
321 void *failtest_realloc(void *ptr, size_t size, const char *file, unsigned line)
322 {
323         struct failtest_call *p;
324         struct realloc_call call;
325         call.size = size;
326         p = add_history(FAILTEST_REALLOC, file, line, &call);
327
328         /* FIXME: Try one child moving allocation, one not. */
329         if (should_fail(p)) {
330                 p->u.realloc.ret = NULL;
331                 p->error = ENOMEM;
332         } else {
333                 p->u.realloc.ret = realloc(ptr, size);
334         }
335         errno = p->error;
336         return p->u.realloc.ret;
337 }
338
339 int failtest_open(const char *pathname, int flags,
340                   const char *file, unsigned line, ...)
341 {
342         struct failtest_call *p;
343         struct open_call call;
344
345         call.pathname = strdup(pathname);
346         call.flags = flags;
347         if (flags & O_CREAT) {
348                 va_list ap;
349                 va_start(ap, line);
350                 call.mode = va_arg(ap, mode_t);
351                 va_end(ap);
352         }
353         p = add_history(FAILTEST_OPEN, file, line, &call);
354         /* Avoid memory leak! */
355         if (p == &unrecorded_call)
356                 free((char *)call.pathname);
357         if (should_fail(p)) {
358                 p->u.open.ret = -1;
359                 /* FIXME: Play with error codes? */
360                 p->error = EACCES;
361         } else {
362                 p->u.open.ret = open(pathname, flags, call.mode);
363         }
364         errno = p->error;
365         return p->u.open.ret;
366 }
367
368 int failtest_pipe(int pipefd[2], const char *file, unsigned line)
369 {
370         struct failtest_call *p;
371         struct pipe_call call;
372
373         p = add_history(FAILTEST_PIPE, file, line, &call);
374         if (should_fail(p)) {
375                 p->u.open.ret = -1;
376                 /* FIXME: Play with error codes? */
377                 p->error = EMFILE;
378         } else {
379                 p->u.pipe.ret = pipe(p->u.pipe.fds);
380         }
381         /* This causes valgrind to notice if they use pipefd[] after failure */
382         memcpy(pipefd, p->u.pipe.fds, sizeof(p->u.pipe.fds));
383         errno = p->error;
384         return p->u.pipe.ret;
385 }
386
387 ssize_t failtest_read(int fd, void *buf, size_t count,
388                       const char *file, unsigned line)
389 {
390         struct failtest_call *p;
391         struct read_call call;
392         call.fd = fd;
393         call.buf = buf;
394         call.count = count;
395         p = add_history(FAILTEST_READ, file, line, &call);
396
397         /* This is going to change seek offset, so save it. */
398         if (control_fd != -1)
399                 save_fd_orig(fd);
400
401         /* FIXME: Try partial read returns. */
402         if (should_fail(p)) {
403                 p->u.read.ret = -1;
404                 p->error = EIO;
405         } else {
406                 p->u.read.ret = read(fd, buf, count);
407         }
408         errno = p->error;
409         return p->u.read.ret;
410 }
411
412 static struct write_info *new_write(void)
413 {
414         writes = realloc(writes, (writes_num + 1) * sizeof(*writes));
415         return &writes[writes_num++];
416 }
417
418 ssize_t failtest_write(int fd, const void *buf, size_t count,
419                        const char *file, unsigned line)
420 {
421         struct failtest_call *p;
422         struct write_call call;
423         off_t offset;
424
425         call.fd = fd;
426         call.buf = buf;
427         call.count = count;
428         p = add_history(FAILTEST_WRITE, file, line, &call);
429
430         offset = lseek(fd, 0, SEEK_CUR);
431
432         /* If we're a child, save contents and tell parent about write. */
433         if (control_fd != -1) {
434                 struct write_info *winfo = new_write();
435                 enum info_type type = WRITE;
436
437                 save_fd_orig(fd);
438
439                 winfo->hdr.len = count;
440                 winfo->hdr.fd = fd;
441                 winfo->data = malloc(count);
442                 memcpy(winfo->data, buf, count);
443                 winfo->hdr.offset = offset;
444                 if (winfo->hdr.offset != (off_t)-1) {
445                         lseek(fd, offset, SEEK_SET);
446                         winfo->olddata = malloc(count);
447                         winfo->oldlen = read(fd, winfo->olddata, count);
448                         if (winfo->oldlen == -1)
449                                 winfo->oldlen = 0;
450                 }
451                 write_all(control_fd, &type, sizeof(type));
452                 write_all(control_fd, &winfo->hdr, sizeof(winfo->hdr));
453                 write_all(control_fd, winfo->data, count);
454         }
455
456         /* FIXME: Try partial write returns. */
457         if (should_fail(p)) {
458                 p->u.write.ret = -1;
459                 p->error = EIO;
460         } else {
461                 /* FIXME: We assume same write order in parent and child */
462                 if (child_writes_num != 0) {
463                         if (child_writes[0].hdr.fd != fd)
464                                 errx(1, "Child wrote to fd %u, not %u?",
465                                      child_writes[0].hdr.fd, fd);
466                         if (child_writes[0].hdr.offset != offset)
467                                 errx(1, "Child wrote to offset %zu, not %zu?",
468                                      (size_t)child_writes[0].hdr.offset,
469                                      (size_t)offset);
470                         if (child_writes[0].hdr.len != count)
471                                 errx(1, "Child wrote length %zu, not %zu?",
472                                      child_writes[0].hdr.len, count);
473                         if (memcmp(child_writes[0].data, buf, count)) {
474                                 child_fail(NULL, 0,
475                                            "Child wrote differently to"
476                                            " fd %u than we did!\n", fd);
477                         }
478                         free(child_writes[0].data);
479                         child_writes_num--;
480                         memmove(&child_writes[0], &child_writes[1],
481                                 sizeof(child_writes[0]) * child_writes_num);
482
483                         /* Is this is a socket or pipe, child wrote it
484                            already. */
485                         if (offset == (off_t)-1) {
486                                 p->u.write.ret = count;
487                                 errno = p->error;
488                                 return p->u.write.ret;
489                         }
490                 }
491                 p->u.write.ret = write(fd, buf, count);
492         }
493         errno = p->error;
494         return p->u.write.ret;
495 }
496
497 /* We only trap this so we can dup fds in case we need to restore. */
498 int failtest_close(int fd)
499 {
500         unsigned int i;
501         int newfd = -1;
502
503         for (i = 0; i < fd_orig_num; i++) {
504                 if (fd_orig[i].fd == fd) {
505                         fd_orig[i].fd = newfd = dup(fd);
506                         fd_orig[i].dupped = true;
507                 }
508         }
509
510         for (i = 0; i < writes_num; i++) {
511                 if (writes[i].hdr.fd == fd)
512                         writes[i].hdr.fd = newfd;
513         }
514         return close(fd);
515 }
516
517 void failtest_init(int argc, char *argv[])
518 {
519         if (argc == 2
520             && strncmp(argv[1], "--failpath=", strlen("--failpath=")) == 0) {
521                 failpath = argv[1] + strlen("--failpath=");
522         }
523 }
524
525 void failtest_exit(int status)
526 {
527         unsigned int i;
528
529         if (control_fd == -1)
530                 exit(status);
531
532         if (failtest_exit_check) {
533                 if (!failtest_exit_check(history, history_num))
534                         child_fail(NULL, 0, "failtest_exit_check failed\n");
535         }
536
537         /* Restore any stuff we overwrote. */
538         for (i = 0; i < writes_num; i++) {
539                 if (writes[i].hdr.offset == (off_t)-1)
540                         continue;
541                 if (writes[i].oldlen != 0) {
542                         lseek(writes[i].hdr.fd, writes[i].hdr.offset,
543                               SEEK_SET);
544                         write(writes[i].hdr.fd, writes[i].olddata,
545                               writes[i].oldlen);
546                 }
547         }
548
549         /* Fix up fd offsets, restore sizes. */
550         for (i = 0; i < fd_orig_num; i++) {
551                 lseek(fd_orig[i].fd, fd_orig[i].offset, SEEK_SET);
552                 ftruncate(fd_orig[i].fd, fd_orig[i].size);
553                 /* Free up any file descriptors we dup'ed. */
554                 if (fd_orig[i].dupped)
555                         close(fd_orig[i].fd);
556         }
557
558         tell_parent(SUCCESS);
559         exit(0);
560 }