]> git.ozlabs.org Git - ccan/blobdiff - ccan/failtest/failtest.c
failtest: rely on the save/restore of files, don't use write cleanup.
[ccan] / ccan / failtest / failtest.c
index 09b92b601c6cb8f36c6b5f3a6b59a583cd66b7d8..56a1ad1872f9f584b9fda6bbf9f21d10e6f3c0b3 100644 (file)
@@ -34,17 +34,6 @@ enum info_type {
        UNEXPECTED
 };
 
-struct write_info_hdr {
-       size_t len;
-       off_t offset;
-       int fd;
-};
-
-struct write_info {
-       struct write_info_hdr hdr;
-       char *data;
-};
-
 struct lock_info {
        int fd;
        /* end is inclusive: you can't have a 0-byte lock. */
@@ -59,10 +48,7 @@ static unsigned int history_num = 0;
 static int control_fd = -1;
 static struct timeval start;
 
-static struct write_info *writes = NULL;
-static unsigned int writes_num = 0;
-
-static struct write_info *child_writes = NULL;
+static struct write_call *child_writes = NULL;
 static unsigned int child_writes_num = 0;
 
 static pid_t lock_owner;
@@ -106,16 +92,18 @@ bool failtest_default_hook(struct failtest_call *history, unsigned num)
 
 static bool read_write_info(int fd)
 {
-       struct write_info_hdr hdr;
-
-       if (!read_all(fd, &hdr, sizeof(hdr)))
-               return false;
+       struct write_call *w;
+       char *buf;
 
+       /* We don't need all of this, but it's simple. */
        child_writes = realloc(child_writes,
                               (child_writes_num+1) * sizeof(child_writes[0]));
-       child_writes[child_writes_num].hdr = hdr;
-       child_writes[child_writes_num].data = malloc(hdr.len);
-       if (!read_all(fd, child_writes[child_writes_num].data, hdr.len))
+       w = &child_writes[child_writes_num];
+       if (!read_all(fd, w, sizeof(*w)))
+               return false;
+
+       w->buf = buf = malloc(w->count);
+       if (!read_all(fd, buf, w->count))
                return false;
 
        child_writes_num++;
@@ -243,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;
@@ -250,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;
@@ -272,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)
@@ -383,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;
@@ -529,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;
@@ -564,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)
 {
@@ -586,78 +646,30 @@ 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 struct write_info *new_write(void)
-{
-       writes = realloc(writes, (writes_num + 1) * sizeof(*writes));
-       return &writes[writes_num++];
-}
-
-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) {
-               struct write_info *winfo = new_write();
                enum info_type type = WRITE;
 
-               winfo->hdr.len = count;
-               winfo->hdr.fd = fd;
-               winfo->data = malloc(count);
-               memcpy(winfo->data, buf, count);
-               winfo->hdr.offset = off;
                write_all(control_fd, &type, sizeof(type));
-               write_all(control_fd, &winfo->hdr, sizeof(winfo->hdr));
-               write_all(control_fd, winfo->data, count);
+               write_all(control_fd, &p->u.write, sizeof(p->u.write));
+               write_all(control_fd, buf, count);
        }
 
        /* FIXME: Try partial write returns. */
@@ -667,22 +679,22 @@ ssize_t failtest_pwrite(int fd, const void *buf, size_t count, off_t off,
        } else {
                /* FIXME: We assume same write order in parent and child */
                if (child_writes_num != 0) {
-                       if (child_writes[0].hdr.fd != fd)
+                       if (child_writes[0].fd != fd)
                                errx(1, "Child wrote to fd %u, not %u?",
-                                    child_writes[0].hdr.fd, fd);
-                       if (child_writes[0].hdr.offset != p->u.write.off)
+                                    child_writes[0].fd, fd);
+                       if (child_writes[0].off != p->u.write.off)
                                errx(1, "Child wrote to offset %zu, not %zu?",
-                                    (size_t)child_writes[0].hdr.offset,
+                                    (size_t)child_writes[0].off,
                                     (size_t)p->u.write.off);
-                       if (child_writes[0].hdr.len != count)
+                       if (child_writes[0].count != count)
                                errx(1, "Child wrote length %zu, not %zu?",
-                                    child_writes[0].hdr.len, count);
-                       if (memcmp(child_writes[0].data, buf, count)) {
+                                    child_writes[0].count, count);
+                       if (memcmp(child_writes[0].buf, buf, count)) {
                                child_fail(NULL, 0,
                                           "Child wrote differently to"
                                           " fd %u than we did!\n", fd);
                        }
-                       free(child_writes[0].data);
+                       free((char *)child_writes[0].buf);
                        child_writes_num--;
                        memmove(&child_writes[0], &child_writes[1],
                                sizeof(child_writes[0]) * child_writes_num);
@@ -778,49 +790,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;
@@ -929,11 +931,6 @@ static void free_everything(void)
 {
        unsigned int i;
 
-       for (i = 0; i < writes_num; i++) {
-               free(writes[i].data);
-       }
-       free(writes);
-
        /* We don't do this in cleanup: needed even for failed opens. */
        for (i = 0; i < history_num; i++) {
                if (history[i].type == FAILTEST_OPEN)