Commit 67bbee53 authored by Rusty Russell's avatar Rusty Russell

failtest: save and restore file state inside child (on-demand)

We currently save all files in the parent, and restore them once
the child is gone.  That doesn't work in a case where the child
manipulates a file the parent doesn't currently have open, so
switch to a model where the child cleans itself up, using the
already-existing cleanup callbacks.

This means that we need to undo much more, especially restoring
file offsets.  We also need to handle the case where we've already
closed the file, and now we're cleaning up.  As a bonus, we now
handle open() with O_TRUNC properly.

The cleanup function now has two modes: one simply frees (so valgrind
doesn't complain about failtest leaking so the user can see real leaks
in their programs), the other restores things so the parent sees no
changes.
parent 4f2f6f1e
This diff is collapsed.
......@@ -48,6 +48,7 @@ enum failtest_call_type {
FAILTEST_WRITE,
FAILTEST_FCNTL,
FAILTEST_MMAP,
FAILTEST_LSEEK
};
struct calloc_call {
......@@ -72,6 +73,10 @@ struct open_call {
const char *pathname;
int flags;
mode_t mode;
bool always_save;
bool closed;
/* This is used for O_TRUNC opens on existing files. */
struct contents_saved *saved;
};
struct close_call {
......@@ -98,6 +103,9 @@ struct write_call {
const void *buf;
size_t count;
off_t off;
bool is_pwrite;
struct failtest_call *opener;
struct contents_saved *saved;
};
struct fcntl_call {
......@@ -119,6 +127,16 @@ struct mmap_call {
int flags;
int fd;
off_t offset;
struct failtest_call *opener;
struct contents_saved *saved;
};
struct lseek_call {
ssize_t ret;
int fd;
off_t offset;
int whence;
off_t old_off;
};
/**
......@@ -147,7 +165,9 @@ struct failtest_call {
/* What we set errno to. */
int error;
/* How do we clean this up? */
void (*cleanup)(void *u);
void (*cleanup)(void *u, bool restore);
/* Should their program have cleaned up? */
bool can_leak;
/* Backtrace of call chain. */
void **backtrace;
unsigned int backtrace_num;
......@@ -163,6 +183,7 @@ struct failtest_call {
struct write_call write;
struct fcntl_call fcntl;
struct mmap_call mmap;
struct lseek_call lseek;
} u;
};
......
......@@ -68,6 +68,10 @@
failtest_mmap((addr), (length), (prot), (flags), (fd), (offset), \
__FILE__, __LINE__)
#undef lseek
#define lseek(fd, offset, whence) \
failtest_lseek((fd), (offset), (whence), __FILE__, __LINE__)
/* Replacement of getpid (since failtest will fork). */
#undef getpid
#define getpid() failtest_getpid(__FILE__, __LINE__)
......
......@@ -23,6 +23,8 @@ ssize_t failtest_pwrite(int fd, const void *buf, size_t count, off_t offset,
const char *file, unsigned line);
void *failtest_mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset, const char *file, unsigned line);
off_t failtest_lseek(int fd, off_t offset, int whence,
const char *file, unsigned line);
int failtest_close(int fd, const char *file, unsigned line);
int failtest_fcntl(int fd, const char *file, unsigned line, int cmd, ...);
pid_t failtest_getpid(const char *file, unsigned line);
......
......@@ -35,6 +35,10 @@
#define mmap(addr, length, prot, flags, fd, offset) \
failtest_mmap((addr), (length), (prot), (flags), (fd), (offset), NULL, 0)
#undef lseek
#define lseek(fd, off, whence) \
failtest_lseek((fd), (off), (whence), NULL, 0)
#undef close
#define close(fd) failtest_close(fd)
......
#include <ccan/failtest/failtest.c>
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <ccan/tap/tap.h>
#include <ccan/failtest/failtest.c>
int main(void)
{
......
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <ccan/tap/tap.h>
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
int main(void)
{
......@@ -15,18 +15,23 @@ int main(void)
struct pipe_call pipe_call;
struct read_call read_call;
struct write_call write_call;
struct mmap_call mmap_call;
char buf[20];
unsigned int i;
char *path;
/* This is how many tests you plan to run */
plan_tests(47);
plan_tests(69);
calloc_call.ret = calloc(1, 2);
calloc_call.nmemb = 1;
calloc_call.size = 2;
call = add_history(FAILTEST_CALLOC, "run-history.c", 1, &calloc_call);
call = add_history(FAILTEST_CALLOC, true,
"run-history.c", 1, &calloc_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_CALLOC);
ok1(call->can_leak == true);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->line == 1);
ok1(call->u.calloc.ret == calloc_call.ret);
......@@ -35,8 +40,12 @@ int main(void)
malloc_call.ret = malloc(2);
malloc_call.size = 2;
call = add_history(FAILTEST_MALLOC, "run-history.c", 2, &malloc_call);
call = add_history(FAILTEST_MALLOC, true,
"run-history.c", 2, &malloc_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_MALLOC);
ok1(call->can_leak == true);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->line == 2);
ok1(call->u.malloc.ret == malloc_call.ret);
......@@ -45,9 +54,12 @@ int main(void)
realloc_call.ret = realloc(malloc_call.ret, 3);
realloc_call.ptr = malloc_call.ret;
realloc_call.size = 3;
call = add_history(FAILTEST_REALLOC, "run-history.c", 3,
call = add_history(FAILTEST_REALLOC, true, "run-history.c", 3,
&realloc_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_REALLOC);
ok1(call->can_leak == true);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->line == 3);
ok1(call->u.realloc.ret == realloc_call.ret);
......@@ -58,8 +70,12 @@ int main(void)
open_call.pathname = "test/run-history.c";
open_call.flags = O_RDONLY;
open_call.mode = 0;
call = add_history(FAILTEST_OPEN, "run-history.c", 4, &open_call);
open_call.closed = false;
call = add_history(FAILTEST_OPEN, true, "run-history.c", 4, &open_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_OPEN);
ok1(call->can_leak == true);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->line == 4);
ok1(call->u.open.ret == open_call.ret);
......@@ -68,9 +84,12 @@ int main(void)
ok1(call->u.open.mode == open_call.mode);
pipe_call.ret = pipe(pipe_call.fds);
call = add_history(FAILTEST_PIPE, "run-history.c", 5, &pipe_call);
call = add_history(FAILTEST_PIPE, true, "run-history.c", 5, &pipe_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_PIPE);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->can_leak == true);
ok1(call->line == 5);
ok1(call->u.pipe.ret == pipe_call.ret);
ok1(call->u.pipe.fds[0] == pipe_call.fds[0]);
......@@ -80,8 +99,11 @@ int main(void)
read_call.buf = buf;
read_call.fd = open_call.ret;
read_call.count = 20;
call = add_history(FAILTEST_READ, "run-history.c", 6, &read_call);
call = add_history(FAILTEST_READ, false, "run-history.c", 6, &read_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_READ);
ok1(call->can_leak == false);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->line == 6);
ok1(call->u.read.ret == read_call.ret);
......@@ -93,33 +115,68 @@ int main(void)
write_call.buf = buf;
write_call.fd = open_call.ret;
write_call.count = 20;
call = add_history(FAILTEST_WRITE, "run-history.c", 7, &write_call);
write_call.opener = NULL;
call = add_history(FAILTEST_WRITE, false, "run-history.c", 7,
&write_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_WRITE);
ok1(call->can_leak == false);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->line == 7);
ok1(call->u.write.ret == write_call.ret);
ok1(call->u.write.buf == write_call.buf);
ok1(call->u.write.fd == write_call.fd);
ok1(call->u.write.count == write_call.count);
ok1(call->u.write.opener == write_call.opener);
mmap_call.ret = &mmap_call;
mmap_call.addr = NULL;
mmap_call.length = 4096;
mmap_call.prot = PROT_READ;
mmap_call.flags = 0;
mmap_call.fd = open_call.ret;
mmap_call.offset = 0;
mmap_call.opener = opener_of(open_call.ret);
ok1(mmap_call.opener->type == FAILTEST_OPEN);
mmap_call.saved = NULL;
call = add_history(FAILTEST_MMAP, false, "run-history.c", 8,
&mmap_call);
/* Normally should_fail would set this. */
call->fail = false;
ok1(call->type == FAILTEST_MMAP);
ok1(call->can_leak == false);
ok1(strcmp(call->file, "run-history.c") == 0);
ok1(call->line == 8);
ok1(call->u.mmap.ret == mmap_call.ret);
ok1(call->u.mmap.addr == mmap_call.addr);
ok1(call->u.mmap.length == mmap_call.length);
ok1(call->u.mmap.prot == mmap_call.prot);
ok1(call->u.mmap.flags == mmap_call.flags);
ok1(call->u.mmap.fd == mmap_call.fd);
ok1(call->u.mmap.offset == mmap_call.offset);
ok1(call->u.mmap.opener == mmap_call.opener);
ok1(call->u.mmap.saved == mmap_call.saved);
i = 0;
tlist_for_each(&history, call, list)
i++;
ok1(i == 7);
ok1(i == 8);
tlist_for_each(&history, call, list)
call->fail = false;
path = failpath_string();
ok1(streq(path, "cmeoprw"));
ok1(streq(path, "cmeoprwa"));
free(path);
tlist_for_each(&history, call, list)
call->fail = true;
path = failpath_string();
ok1(streq(path, "CMEOPRW"));
ok1(streq(path, "CMEOPRWA"));
free(path);
return exit_status();
......
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ccan/tap/tap.h>
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
#define SIZE 8
......
#include "config.h"
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
......
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ccan/tap/tap.h>
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
int main(void)
{
......
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ccan/tap/tap.h>
/* Include the C files directly. */
#include <ccan/failtest/failtest.c>
int main(int argc, char *argv[])
{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment