10 #include <sys/types.h>
14 #include <ccan/read_write_all/read_write_all.h>
15 #include <ccan/failtest/failtest_proto.h>
16 #include <ccan/failtest/failtest.h>
18 bool (*failtest_hook)(struct failtest_call *history, unsigned num)
19 = failtest_default_hook;
21 unsigned int failtest_timeout_ms = 20000;
32 struct write_info_hdr {
46 struct write_info_hdr hdr;
52 bool (*failtest_exit_check)(struct failtest_call *history, unsigned num);
54 static struct failtest_call *history = NULL;
55 static unsigned int history_num = 0;
56 static int control_fd = -1;
58 static struct write_info *writes = NULL;
59 static unsigned int writes_num = 0;
61 static struct write_info *child_writes = NULL;
62 static unsigned int child_writes_num = 0;
64 static struct fd_orig *fd_orig = NULL;
65 static unsigned int fd_orig_num = 0;
67 static const char info_to_arg[] = "mceoprw";
69 static struct failtest_call *add_history_(enum failtest_call_type type,
75 history = realloc(history, (history_num + 1) * sizeof(*history));
76 history[history_num].type = type;
77 history[history_num].file = file;
78 history[history_num].line = line;
79 memcpy(&history[history_num].u, elem, elem_size);
80 return &history[history_num++];
83 #define add_history(type, file, line, elem) \
84 add_history_((type), (file), (line), (elem), sizeof(*(elem)))
86 static void save_fd_orig(int fd)
90 for (i = 0; i < fd_orig_num; i++)
91 if (fd_orig[i].fd == fd)
94 fd_orig = realloc(fd_orig, (fd_orig_num + 1) * sizeof(*fd_orig));
95 fd_orig[fd_orig_num].fd = fd;
96 fd_orig[fd_orig_num].dupped = false;
97 fd_orig[fd_orig_num].offset = lseek(fd, 0, SEEK_CUR);
98 fd_orig[fd_orig_num].size = lseek(fd, 0, SEEK_END);
99 lseek(fd, fd_orig[fd_orig_num].offset, SEEK_SET);
103 bool failtest_default_hook(struct failtest_call *history, unsigned num)
108 static bool read_write_info(int fd)
110 struct write_info_hdr hdr;
112 if (!read_all(fd, &hdr, sizeof(hdr)))
115 child_writes = realloc(child_writes,
116 (child_writes_num+1) * sizeof(child_writes[0]));
117 child_writes[child_writes_num].hdr = hdr;
118 child_writes[child_writes_num].data = malloc(hdr.len);
119 if (!read_all(fd, child_writes[child_writes_num].data, hdr.len))
126 static void print_reproduce(void)
130 printf("To reproduce: --failpath=");
131 for (i = 0; i < history_num; i++) {
133 printf("%c", toupper(info_to_arg[history[i].type]));
135 printf("%c", info_to_arg[history[i].type]);
140 static void tell_parent(enum info_type type)
142 if (control_fd != -1)
143 write_all(control_fd, &type, sizeof(type));
146 static void child_fail(const char *out, size_t outlen, const char *fmt, ...)
151 vfprintf(stderr, fmt, ap);
154 fprintf(stderr, "%.*s", (int)outlen, out);
156 tell_parent(FAILURE);
162 static void hand_down(int signal)
167 static bool should_fail(struct failtest_call *call)
170 int control[2], output[2];
171 enum info_type type = UNEXPECTED;
176 if (tolower(*failpath) != info_to_arg[call->type])
177 errx(1, "Failpath expected '%c' got '%c'\n",
178 info_to_arg[call->type], *failpath);
179 return isupper(*(failpath++));
182 if (!failtest_hook(history, history_num)) {
187 /* We're going to fail in the child. */
189 if (pipe(control) != 0 || pipe(output) != 0)
190 err(1, "opening pipe");
192 /* Prevent double-printing (in child and parent) */
196 err(1, "forking failed");
201 dup2(output[1], STDOUT_FILENO);
202 dup2(output[1], STDERR_FILENO);
203 if (output[1] != STDOUT_FILENO && output[1] != STDERR_FILENO)
205 control_fd = control[1];
209 signal(SIGUSR1, hand_down);
214 /* We grab output so we can display it; we grab writes so we
217 struct pollfd pfd[2];
220 pfd[0].fd = output[0];
221 pfd[0].events = POLLIN|POLLHUP;
222 pfd[1].fd = control[0];
223 pfd[1].events = POLLIN|POLLHUP;
226 ret = poll(pfd, 1, failtest_timeout_ms);
228 ret = poll(pfd, 2, failtest_timeout_ms);
233 if (pfd[0].revents & POLLIN) {
236 out = realloc(out, outlen + 8192);
237 len = read(output[0], out + outlen, 8192);
239 } else if (type != SUCCESS && (pfd[1].revents & POLLIN)) {
240 if (read_all(control[0], &type, sizeof(type))) {
242 if (!read_write_info(control[0]))
246 } else if (pfd[0].revents & POLLHUP) {
249 } while (type != FAILURE);
253 waitpid(child, &status, 0);
254 if (!WIFEXITED(status))
255 child_fail(out, outlen, "Killed by signal %u: ",
257 /* Child printed failure already, just pass up exit code. */
258 if (type == FAILURE) {
259 fprintf(stderr, "%.*s", (int)outlen, out);
261 exit(WEXITSTATUS(status) ? WEXITSTATUS(status) : 1);
263 if (WEXITSTATUS(status) != 0)
264 child_fail(out, outlen, "Exited with status %i: ",
265 WEXITSTATUS(status));
268 signal(SIGUSR1, SIG_DFL);
270 /* We continue onwards without failing. */
275 void *failtest_calloc(size_t nmemb, size_t size,
276 const char *file, unsigned line)
278 struct failtest_call *p;
279 struct calloc_call call;
282 p = add_history(FAILTEST_CALLOC, file, line, &call);
284 if (should_fail(p)) {
285 p->u.calloc.ret = NULL;
288 p->u.calloc.ret = calloc(nmemb, size);
291 return p->u.calloc.ret;
294 void *failtest_malloc(size_t size, const char *file, unsigned line)
296 struct failtest_call *p;
297 struct malloc_call call;
300 p = add_history(FAILTEST_MALLOC, file, line, &call);
301 if (should_fail(p)) {
302 p->u.calloc.ret = NULL;
305 p->u.calloc.ret = malloc(size);
308 return p->u.calloc.ret;
311 void *failtest_realloc(void *ptr, size_t size, const char *file, unsigned line)
313 struct failtest_call *p;
314 struct realloc_call call;
316 p = add_history(FAILTEST_REALLOC, file, line, &call);
318 /* FIXME: Try one child moving allocation, one not. */
319 if (should_fail(p)) {
320 p->u.realloc.ret = NULL;
323 p->u.realloc.ret = realloc(ptr, size);
326 return p->u.realloc.ret;
329 int failtest_open(const char *pathname, int flags,
330 const char *file, unsigned line, ...)
332 struct failtest_call *p;
333 struct open_call call;
335 call.pathname = strdup(pathname);
337 if (flags & O_CREAT) {
340 call.mode = va_arg(ap, mode_t);
343 p = add_history(FAILTEST_OPEN, file, line, &call);
344 if (should_fail(p)) {
346 /* FIXME: Play with error codes? */
349 p->u.open.ret = open(pathname, flags, call.mode);
352 return p->u.open.ret;
355 int failtest_pipe(int pipefd[2], const char *file, unsigned line)
357 struct failtest_call *p;
358 struct pipe_call call;
360 p = add_history(FAILTEST_PIPE, file, line, &call);
361 if (should_fail(p)) {
363 /* FIXME: Play with error codes? */
366 p->u.pipe.ret = pipe(p->u.pipe.fds);
368 /* This causes valgrind to notice if they use pipefd[] after failure */
369 memcpy(pipefd, p->u.pipe.fds, sizeof(p->u.pipe.fds));
371 return p->u.pipe.ret;
374 ssize_t failtest_read(int fd, void *buf, size_t count,
375 const char *file, unsigned line)
377 struct failtest_call *p;
378 struct read_call call;
382 p = add_history(FAILTEST_READ, file, line, &call);
384 /* This is going to change seek offset, so save it. */
385 if (control_fd != -1)
388 /* FIXME: Try partial read returns. */
389 if (should_fail(p)) {
393 p->u.read.ret = read(fd, buf, count);
396 return p->u.read.ret;
399 static struct write_info *new_write(void)
401 writes = realloc(writes, (writes_num + 1) * sizeof(*writes));
402 return &writes[writes_num++];
405 ssize_t failtest_write(int fd, const void *buf, size_t count,
406 const char *file, unsigned line)
408 struct failtest_call *p;
409 struct write_call call;
415 p = add_history(FAILTEST_WRITE, file, line, &call);
417 offset = lseek(fd, 0, SEEK_CUR);
419 /* If we're a child, save contents and tell parent about write. */
420 if (control_fd != -1) {
421 struct write_info *winfo = new_write();
422 enum info_type type = WRITE;
426 winfo->hdr.len = count;
428 winfo->data = malloc(count);
429 memcpy(winfo->data, buf, count);
430 winfo->hdr.offset = offset;
431 if (winfo->hdr.offset != (off_t)-1) {
432 lseek(fd, offset, SEEK_SET);
433 winfo->olddata = malloc(count);
434 winfo->oldlen = read(fd, winfo->olddata, count);
435 if (winfo->oldlen == -1)
438 write_all(control_fd, &type, sizeof(type));
439 write_all(control_fd, &winfo->hdr, sizeof(winfo->hdr));
440 write_all(control_fd, winfo->data, count);
443 /* FIXME: Try partial write returns. */
444 if (should_fail(p)) {
448 /* FIXME: We assume same write order in parent and child */
449 if (child_writes_num != 0) {
450 if (child_writes[0].hdr.fd != fd)
451 errx(1, "Child wrote to fd %u, not %u?",
452 child_writes[0].hdr.fd, fd);
453 if (child_writes[0].hdr.offset != offset)
454 errx(1, "Child wrote to offset %zu, not %zu?",
455 (size_t)child_writes[0].hdr.offset,
457 if (child_writes[0].hdr.len != count)
458 errx(1, "Child wrote length %zu, not %zu?",
459 child_writes[0].hdr.len, count);
460 if (memcmp(child_writes[0].data, buf, count)) {
462 "Child wrote differently to"
463 " fd %u than we did!\n", fd);
465 free(child_writes[0].data);
467 memmove(&child_writes[0], &child_writes[1],
468 sizeof(child_writes[0]) * child_writes_num);
470 /* Is this is a socket or pipe, child wrote it
472 if (offset == (off_t)-1) {
473 p->u.write.ret = count;
475 return p->u.write.ret;
478 p->u.write.ret = write(fd, buf, count);
481 return p->u.write.ret;
484 /* We only trap this so we can dup fds in case we need to restore. */
485 int failtest_close(int fd)
490 for (i = 0; i < fd_orig_num; i++) {
491 if (fd_orig[i].fd == fd) {
492 fd_orig[i].fd = newfd = dup(fd);
493 fd_orig[i].dupped = true;
497 for (i = 0; i < writes_num; i++) {
498 if (writes[i].hdr.fd == fd)
499 writes[i].hdr.fd = newfd;
504 void failtest_init(int argc, char *argv[])
507 && strncmp(argv[1], "--failpath=", strlen("--failpath=")) == 0) {
508 failpath = argv[1] + strlen("--failpath=");
512 void failtest_exit(int status)
516 if (control_fd == -1)
519 if (failtest_exit_check) {
520 if (!failtest_exit_check(history, history_num))
521 child_fail(NULL, 0, "failtest_exit_check failed\n");
524 /* Restore any stuff we overwrote. */
525 for (i = 0; i < writes_num; i++) {
526 if (writes[i].hdr.offset == (off_t)-1)
528 if (writes[i].oldlen != 0) {
529 lseek(writes[i].hdr.fd, writes[i].hdr.offset,
531 write(writes[i].hdr.fd, writes[i].olddata,
536 /* Fix up fd offsets, restore sizes. */
537 for (i = 0; i < fd_orig_num; i++) {
538 lseek(fd_orig[i].fd, fd_orig[i].offset, SEEK_SET);
539 ftruncate(fd_orig[i].fd, fd_orig[i].size);
540 /* Free up any file descriptors we dup'ed. */
541 if (fd_orig[i].dupped)
542 close(fd_orig[i].fd);
545 tell_parent(SUCCESS);