]> git.ozlabs.org Git - ccan/blobdiff - ccan/failtest/failtest.c
failtest: don't insist parents and children write the same thing to files.
[ccan] / ccan / failtest / failtest.c
index 2df1ae668e01e06318f535a2fa5f3a75466968cf..9102ba0a88b884591ef766bdfa9582e4de72d424 100644 (file)
@@ -231,6 +231,79 @@ static void trace_str(const char *str)
        err(1, "Writing trace.");
 }
 
+struct saved_file {
+       struct saved_file *next;
+       int fd;
+       void *contents;
+       off_t off, len;
+};
+
+static struct saved_file *save_file(struct saved_file *next, int fd)
+{
+       struct saved_file *s = malloc(sizeof(*s));
+
+       s->next = next;
+       s->fd = fd;
+       s->off = lseek(fd, 0, SEEK_CUR);
+       /* Special file?  Erk... */
+       assert(s->off != -1);
+       s->len = lseek(fd, 0, SEEK_END);
+       lseek(fd, 0, SEEK_SET);
+       s->contents = malloc(s->len);
+       read(fd, s->contents, s->len);
+       lseek(fd, s->off, SEEK_SET);
+       return s;
+}
+       
+/* We have little choice but to save and restore open files: mmap means we
+ * can really intercept changes in the child.
+ *
+ * We could do non-mmap'ed files on demand, however. */
+static struct saved_file *save_files(void)
+{
+       struct saved_file *files = NULL;
+       int i;
+
+       /* Figure out the set of live fds. */
+       for (i = history_num - 2; i >= 0; i--) {
+               if (history[i].type == FAILTEST_OPEN) {
+                       int fd = history[i].u.open.ret;
+                       /* Only do successful, writable fds. */
+                       if (fd < 0)
+                               continue;
+
+                       /* If it was closed, cleanup == NULL. */
+                       if (!history[i].cleanup)
+                               continue;
+
+                       if ((history[i].u.open.flags & O_RDWR) == O_RDWR) {
+                               files = save_file(files, fd);
+                       } else if ((history[i].u.open.flags & O_WRONLY)
+                                  == O_WRONLY) {
+                               /* FIXME: Handle O_WRONLY.  Open with O_RDWR? */
+                               abort();
+                       }
+               }
+       }
+
+       return files;
+}
+
+static void restore_files(struct saved_file *s)
+{
+       while (s) {
+               struct saved_file *next = s->next;
+
+               lseek(s->fd, 0, SEEK_SET);
+               write(s->fd, s->contents, s->len);
+               ftruncate(s->fd, s->len);
+               free(s->contents);
+               lseek(s->fd, s->off, SEEK_SET);
+               free(s);
+               s = next;
+       }
+}
+
 static bool should_fail(struct failtest_call *call)
 {
        int status;
@@ -238,6 +311,7 @@ static bool should_fail(struct failtest_call *call)
        enum info_type type = UNEXPECTED;
        char *out = NULL;
        size_t outlen = 0;
+       struct saved_file *files;
 
        if (call == &unrecorded_call)
                return false;
@@ -260,6 +334,8 @@ static bool should_fail(struct failtest_call *call)
                return false;
        }
 
+       files = save_files();
+
        /* We're going to fail in the child. */
        call->fail = true;
        if (pipe(control) != 0 || pipe(output) != 0)
@@ -371,6 +447,8 @@ static bool should_fail(struct failtest_call *call)
        free(out);
        signal(SIGUSR1, SIG_DFL);
 
+       restore_files(files);
+
        /* We continue onwards without failing. */
        call->fail = false;
        return false;
@@ -517,7 +595,6 @@ int failtest_open(const char *pathname,
        } else {
                p->u.open.ret = open(pathname, call.flags, call.mode);
                set_cleanup(p, cleanup_open, struct open_call);
-               p->u.open.dup_fd = p->u.open.ret;
        }
        errno = p->error;
        return p->u.open.ret;
@@ -552,11 +629,6 @@ int failtest_pipe(int pipefd[2], const char *file, unsigned line)
        return p->u.pipe.ret;
 }
 
-static void cleanup_read(struct read_call *call)
-{
-       lseek(call->fd, call->off, SEEK_SET);
-}
-
 ssize_t failtest_pread(int fd, void *buf, size_t count, off_t off,
                       const char *file, unsigned line)
 {
@@ -574,61 +646,26 @@ ssize_t failtest_pread(int fd, void *buf, size_t count, off_t off,
                p->error = EIO;
        } else {
                p->u.read.ret = pread(fd, buf, count, off);
-               set_cleanup(p, cleanup_read, struct read_call);
        }
        errno = p->error;
        return p->u.read.ret;
 }
 
-static void cleanup_write(struct write_call *call)
-{
-       lseek(call->dup_fd, call->off, SEEK_SET);
-       write(call->dup_fd, call->saved_contents, call->saved_len);
-       lseek(call->dup_fd, call->off, SEEK_SET);
-       ftruncate(call->dup_fd, call->old_filelen);
-       free(call->saved_contents);
-}
-
 ssize_t failtest_pwrite(int fd, const void *buf, size_t count, off_t off,
                        const char *file, unsigned line)
 {
        struct failtest_call *p;
        struct write_call call;
 
-       call.fd = call.dup_fd = fd;
+       call.fd = fd;
        call.buf = buf;
        call.count = count;
        call.off = off;
        p = add_history(FAILTEST_WRITE, file, line, &call);
 
-       /* Save old contents if we can */
-       if (p->u.write.off != -1) {
-               ssize_t ret;
-               p->u.write.old_filelen = lseek(fd, 0, SEEK_END);
-
-               /* Write past end of file?  Nothing to save.*/
-               if (p->u.write.old_filelen <= p->u.write.off)
-                       p->u.write.saved_len = 0;
-               /* Write which goes over end of file?  Partial save. */
-               else if (p->u.write.off + count > p->u.write.old_filelen)
-                       p->u.write.saved_len = p->u.write.old_filelen
-                               - p->u.write.off;
-               /* Full save. */
-               else
-                       p->u.write.saved_len = count;
-
-               p->u.write.saved_contents = malloc(p->u.write.saved_len);
-               lseek(fd, p->u.write.off, SEEK_SET);
-               ret = read(fd, p->u.write.saved_contents, p->u.write.saved_len);
-               if (ret != p->u.write.saved_len)
-                       err(1, "Expected %i bytes, got %i",
-                           (int)p->u.write.saved_len, (int)ret);
-               lseek(fd, p->u.write.off, SEEK_SET);
-               set_cleanup(p, cleanup_write, struct write_call);
-       }
-
-       /* If we're a child, tell parent about write. */
-       if (control_fd != -1) {
+       /* If we're a child, we need to make sure we write the same thing
+        * to non-files as the parent does, so tell it. */
+       if (control_fd != -1 && off == (off_t)-1) {
                enum info_type type = WRITE;
 
                write_all(control_fd, &type, sizeof(type));
@@ -642,7 +679,7 @@ ssize_t failtest_pwrite(int fd, const void *buf, size_t count, off_t off,
                p->error = EIO;
        } else {
                /* FIXME: We assume same write order in parent and child */
-               if (child_writes_num != 0) {
+               if (off == (off_t)-1 && child_writes_num != 0) {
                        if (child_writes[0].fd != fd)
                                errx(1, "Child wrote to fd %u, not %u?",
                                     child_writes[0].fd, fd);
@@ -754,49 +791,39 @@ add_lock(struct lock_info *locks, int fd, off_t start, off_t end, int type)
        return locks;
 }
 
-/* We only trap this so we can dup fds in case we need to restore. */
+/* We trap this so we can record it: we don't fail it. */
 int failtest_close(int fd)
 {
-       int new_fd = -1, i;
+       int i;
 
        if (fd < 0)
                return close(fd);
 
-       /* Trace history to find source of fd, and if we need to cleanup writes. */
+       /* Trace history to find source of fd. */
        for (i = history_num-1; i >= 0; i--) {
                switch (history[i].type) {
-               case FAILTEST_WRITE:
-                       if (history[i].u.write.fd != fd)
-                               break;
-                       if (!history[i].cleanup)
-                               break;
-                       /* We need to save fd so we can restore file. */
-                       if (new_fd == -1)
-                               new_fd = dup(fd);
-                       history[i].u.write.dup_fd = new_fd;
-                       break;
-               case FAILTEST_READ:
-                       /* We don't need to cleanup reads on closed fds. */
-                       if (history[i].u.read.fd != fd)
-                               break;
-                       history[i].cleanup = NULL;
-                       break;
                case FAILTEST_PIPE:
-                       /* From a pipe?  We don't ever restore pipes... */
+                       /* From a pipe? */
                        if (history[i].u.pipe.fds[0] == fd) {
-                               assert(new_fd == -1);
+                               assert(!history[i].u.pipe.closed[0]);
                                history[i].u.pipe.closed[0] = true;
+                               if (history[i].u.pipe.closed[1])
+                                       history[i].cleanup = NULL;
                                goto out;
                        }
                        if (history[i].u.pipe.fds[1] == fd) {
-                               assert(new_fd == -1);
+                               assert(!history[i].u.pipe.closed[1]);
                                history[i].u.pipe.closed[1] = true;
+                               if (history[i].u.pipe.closed[0])
+                                       history[i].cleanup = NULL;
                                goto out;
                        }
                        break;
                case FAILTEST_OPEN:
                        if (history[i].u.open.ret == fd) {
-                               history[i].u.open.dup_fd = new_fd;
+                               assert((void *)history[i].cleanup
+                                      == (void *)cleanup_open);
+                               history[i].cleanup = NULL;
                                goto out;
                        }
                        break;