diff options
Diffstat (limited to 'tests/bugs/write-behind')
| -rw-r--r-- | tests/bugs/write-behind/bug-1058663.c | 158 | ||||
| -rw-r--r-- | tests/bugs/write-behind/bug-1279730.c | 149 | ||||
| -rwxr-xr-x | tests/bugs/write-behind/bug-1279730.t | 37 | ||||
| -rw-r--r-- | tests/bugs/write-behind/issue-884.c | 267 | ||||
| -rwxr-xr-x | tests/bugs/write-behind/issue-884.t | 40 |
5 files changed, 574 insertions, 77 deletions
diff --git a/tests/bugs/write-behind/bug-1058663.c b/tests/bugs/write-behind/bug-1058663.c index 5e522e98048..aedf97d7487 100644 --- a/tests/bugs/write-behind/bug-1058663.c +++ b/tests/bugs/write-behind/bug-1058663.c @@ -19,101 +19,105 @@ static int sigbus_received; /* test for truncate()/seek()/write()/mmap() * There should ne no SIGBUS triggered. */ -void seek_write(char *filename) +void +seek_write(char *filename) { - int fd; - uint8_t *map; - int i; - - fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0600); - lseek(fd, FILE_SIZE - 1, SEEK_SET); - write(fd, "\xff", 1); - - map = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0); - for (i = 0; i < (FILE_SIZE - 1); i++) { - if (map[i] != 0) /* should never be true */ - abort(); - } - munmap(map, FILE_SIZE); - - close(fd); + int fd; + uint8_t *map; + int i; + + fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0600); + lseek(fd, FILE_SIZE - 1, SEEK_SET); + write(fd, "\xff", 1); + + map = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0); + for (i = 0; i < (FILE_SIZE - 1); i++) { + if (map[i] != 0) /* should never be true */ + abort(); + } + munmap(map, FILE_SIZE); + + close(fd); } -int read_after_eof(char *filename) +int +read_after_eof(char *filename) { - int ret = 0; - int fd; - char *data; - uint8_t *map; - - fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0600); - lseek(fd, FILE_SIZE - 1, SEEK_SET); - write(fd, "\xff", 1); - - /* trigger verify that reading after EOF fails */ - ret = read(fd, data, FILE_SIZE / 2); - if (ret != 0) - return 1; - - /* map an area of 1 byte after FILE_SIZE */ - map = mmap(NULL, 1, PROT_READ, MAP_PRIVATE, fd, FILE_SIZE); - /* map[0] is an access after EOF, it should trigger SIGBUS */ - if (map[0] != 0) - /* it is expected that we exit before we get here */ - if (!sigbus_received) - return 1; - munmap(map, FILE_SIZE); - - close(fd); - - return ret; + int ret = 0; + int fd; + char *data; + uint8_t *map; + + fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0600); + lseek(fd, FILE_SIZE - 1, SEEK_SET); + write(fd, "\xff", 1); + + /* trigger verify that reading after EOF fails */ + ret = read(fd, data, FILE_SIZE / 2); + if (ret != 0) + return 1; + + /* map an area of 1 byte after FILE_SIZE */ + map = mmap(NULL, 1, PROT_READ, MAP_PRIVATE, fd, FILE_SIZE); + /* map[0] is an access after EOF, it should trigger SIGBUS */ + if (map[0] != 0) + /* it is expected that we exit before we get here */ + if (!sigbus_received) + return 1; + munmap(map, FILE_SIZE); + + close(fd); + + return ret; } /* signal handler for SIGBUS */ -void catch_sigbus(int signum) +void +catch_sigbus(int signum) { - switch (signum) { + switch (signum) { #ifdef __NetBSD__ - /* Depending on architecture, we can get SIGSEGV */ - case SIGSEGV: /* FALLTHROUGH */ + /* Depending on architecture, we can get SIGSEGV */ + case SIGSEGV: /* FALLTHROUGH */ #endif - case SIGBUS: - sigbus_received++; - if (!expect_sigbus) - exit(EXIT_FAILURE); - if (sigbus_received >= MAX_SIGBUS) - exit(EXIT_SUCCESS); - break; - default: - printf("Unexpected signal received: %d\n", signum); - } + case SIGBUS: + sigbus_received++; + if (!expect_sigbus) + exit(EXIT_FAILURE); + if (sigbus_received >= MAX_SIGBUS) + exit(EXIT_SUCCESS); + break; + default: + printf("Unexpected signal received: %d\n", signum); + } } -int main(int argc, char **argv) +int +main(int argc, char **argv) { - int i = 0; + int i = 0; - if (argc == 1) { - printf("Usage: %s <filename>\n", argv[0]); - return EXIT_FAILURE; - } + if (argc == 1) { + printf("Usage: %s <filename>\n", argv[0]); + return EXIT_FAILURE; + } #ifdef __NetBSD__ - /* Depending on architecture, we can get SIGSEGV */ - signal(SIGSEGV, catch_sigbus); + /* Depending on architecture, we can get SIGSEGV */ + signal(SIGSEGV, catch_sigbus); #endif - signal(SIGBUS, catch_sigbus); + signal(SIGBUS, catch_sigbus); - /* the next test should not trigger SIGBUS */ - expect_sigbus = 0; - for (i = 0; i < RUN_LOOP; i++) { - seek_write(argv[1]); - } + /* the next test should not trigger SIGBUS */ + expect_sigbus = 0; + for (i = 0; i < RUN_LOOP; i++) { + seek_write(argv[1]); + } - /* the next test should trigger SIGBUS */ - expect_sigbus = 1; - if (read_after_eof(argv[1])) - return EXIT_FAILURE; + /* the next test should trigger SIGBUS */ + expect_sigbus = 1; + if (read_after_eof(argv[1])) + return EXIT_FAILURE; - return EXIT_SUCCESS; + return EXIT_SUCCESS; } diff --git a/tests/bugs/write-behind/bug-1279730.c b/tests/bugs/write-behind/bug-1279730.c new file mode 100644 index 00000000000..706ae67b102 --- /dev/null +++ b/tests/bugs/write-behind/bug-1279730.c @@ -0,0 +1,149 @@ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <assert.h> + +int +main(int argc, char *argv[]) +{ + int fd = -1, ret = -1, len = 0; + char *path = NULL, + buf[128] = + { + 0, + }, + *cmd = NULL; + struct stat stbuf = { + 0, + }; + int write_to_child[2] = + { + 0, + }, + write_to_parent[2] = { + 0, + }; + + path = argv[1]; + cmd = argv[2]; + + assert(argc == 3); + + ret = pipe(write_to_child); + if (ret < 0) { + fprintf(stderr, + "creation of write-to-child pipe failed " + "(%s)\n", + strerror(errno)); + goto out; + } + + ret = pipe(write_to_parent); + if (ret < 0) { + fprintf(stderr, + "creation of write-to-parent pipe failed " + "(%s)\n", + strerror(errno)); + goto out; + } + + ret = fork(); + switch (ret) { + case 0: + close(write_to_child[1]); + close(write_to_parent[0]); + + /* child, wait for instructions to execute command */ + ret = read(write_to_child[0], buf, 128); + if (ret < 0) { + fprintf(stderr, "child: read on pipe failed (%s)\n", + strerror(errno)); + goto out; + } + + system(cmd); + + ret = write(write_to_parent[1], "1", 2); + if (ret < 0) { + fprintf(stderr, "child: write to pipe failed (%s)\n", + strerror(errno)); + goto out; + } + break; + + case -1: + fprintf(stderr, "fork failed (%s)\n", strerror(errno)); + goto out; + + default: + close(write_to_parent[1]); + close(write_to_child[0]); + + fd = open(path, O_CREAT | O_RDWR | O_APPEND, S_IRWXU); + if (fd < 0) { + fprintf(stderr, "open failed (%s)\n", strerror(errno)); + goto out; + } + + len = strlen("test-content") + 1; + ret = write(fd, "test-content", len); + + if (ret < len) { + fprintf(stderr, "write failed %d (%s)\n", ret, strerror(errno)); + } + + ret = pread(fd, buf, 128, 0); + if ((ret == len) && (strcmp(buf, "test-content") == 0)) { + fprintf(stderr, + "read should've failed as previous " + "write would've failed with EDQUOT, but its " + "successful"); + ret = -1; + goto out; + } + + ret = write(write_to_child[1], "1", 2); + if (ret < 0) { + fprintf(stderr, "parent: write to pipe failed (%s)\n", + strerror(errno)); + goto out; + } + + ret = read(write_to_parent[0], buf, 128); + if (ret < 0) { + fprintf(stderr, "parent: read from pipe failed (%s)\n", + strerror(errno)); + goto out; + } + + /* this will force a sync on cached-write and now that quota + limit is increased, sync will be successful. ignore return + value as fstat would fail with EDQUOT (picked up from + cached-write because of previous sync failure. + */ + fstat(fd, &stbuf); + + ret = pread(fd, buf, 128, 0); + if (ret != len) { + fprintf(stderr, + "post cmd read failed %d (data:%s) " + "(error:%s)\n", + ret, buf, strerror(errno)); + goto out; + } + + if (strcmp(buf, "test-content")) { + fprintf(stderr, "wrong data (%s)\n", buf); + goto out; + } + } + + ret = 0; + +out: + return ret; +} diff --git a/tests/bugs/write-behind/bug-1279730.t b/tests/bugs/write-behind/bug-1279730.t new file mode 100755 index 00000000000..20447c349d5 --- /dev/null +++ b/tests/bugs/write-behind/bug-1279730.t @@ -0,0 +1,37 @@ +#!/bin/bash + +. $(dirname $0)/../../include.rc +. $(dirname $0)/../../volume.rc +. $(dirname $0)/../../fileio.rc + +cleanup; + +## Start and create a volume +TEST glusterd +TEST pidof glusterd; +TEST $CLI volume info; + +TEST $CLI volume create $V0 $H0:$B0/$V0; +TEST $CLI volume start $V0; +TEST $CLI volume quota $V0 enable +TEST $CLI volume quota $V0 limit-usage / 4 +TEST $CLI volume quota $V0 hard-timeout 0 +TEST $CLI volume quota $V0 soft-timeout 0 + +TEST glusterfs --entry-timeout=0 --attribute-timeout=0 -s $H0 --volfile-id $V0 $M0 + +# compile the test program and run it +TEST $CC -O0 -g3 $(dirname $0)/bug-1279730.c -o $(dirname $0)/bug-1279730 + +TEST $(dirname $0)/bug-1279730 $M0/file "\"$CLI volume quota $V0 limit-usage / 1024\"" + +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" force_umount $M0 + +TEST $CLI volume stop $V0; +TEST $CLI volume delete $V0; + +cleanup; + +#G_TESTDEF_TEST_STATUS_CENTOS6=BAD_TEST,BUG=1279730 +#G_TESTDEF_TEST_STATUS_NETBSD7=BAD_TEST,BUG=1279730 + diff --git a/tests/bugs/write-behind/issue-884.c b/tests/bugs/write-behind/issue-884.c new file mode 100644 index 00000000000..e9c33b351ad --- /dev/null +++ b/tests/bugs/write-behind/issue-884.c @@ -0,0 +1,267 @@ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <pthread.h> + +#include <glusterfs/api/glfs.h> + +/* Based on a reproducer by Stefan Ring. It seems to be quite sensible to any + * timing modification, so the code has been maintained as is, only with minor + * changes. */ + +struct glfs *glfs; + +pthread_mutex_t the_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t the_cond = PTHREAD_COND_INITIALIZER; + +typedef struct _my_aiocb { + int64_t size; + volatile int64_t seq; + int which; +} my_aiocb; + +typedef struct _worker_data { + my_aiocb cb; + struct iovec iov; + int64_t offset; +} worker_data; + +typedef struct { + worker_data wdata[2]; + + volatile unsigned busy; +} all_data_t; + +all_data_t all_data; + +static void +completion_fnc(struct glfs_fd *fd, ssize_t ret, struct glfs_stat *pre, + struct glfs_stat *post, void *arg) +{ + void *the_thread; + my_aiocb *cb = (my_aiocb *)arg; + long seq = cb->seq; + + assert(ret == cb->size); + + pthread_mutex_lock(&the_mutex); + pthread_cond_broadcast(&the_cond); + + all_data.busy &= ~(1 << cb->which); + cb->seq = -1; + + the_thread = (void *)pthread_self(); + printf("worker %d is done from thread %p, seq %ld!\n", cb->which, + the_thread, seq); + + pthread_mutex_unlock(&the_mutex); +} + +static void +init_wdata(worker_data *data, int which) +{ + data->cb.which = which; + data->cb.seq = -1; + + data->iov.iov_base = malloc(1024 * 1024); + memset(data->iov.iov_base, 6, + 1024 * 1024); /* tail part never overwritten */ +} + +static void +init() +{ + all_data.busy = 0; + + init_wdata(&all_data.wdata[0], 0); + init_wdata(&all_data.wdata[1], 1); +} + +static void +do_write(struct glfs_fd *fd, int content, int size, int64_t seq, + worker_data *wdata, const char *name) +{ + int ret; + + wdata->cb.size = size; + wdata->cb.seq = seq; + + if (content >= 0) + memset(wdata->iov.iov_base, content, size); + wdata->iov.iov_len = size; + + pthread_mutex_lock(&the_mutex); + printf("(%d) dispatching write \"%s\", offset %lx, len %x, seq %ld\n", + wdata->cb.which, name, (long)wdata->offset, size, (long)seq); + pthread_mutex_unlock(&the_mutex); + ret = glfs_pwritev_async(fd, &wdata->iov, 1, wdata->offset, 0, + completion_fnc, &wdata->cb); + assert(ret >= 0); +} + +#define IDLE 0 // both workers must be idle +#define ANY 1 // use any worker, other one may be busy + +int +get_worker(int waitfor, int64_t excl_seq) +{ + int which; + + pthread_mutex_lock(&the_mutex); + + while (waitfor == IDLE && (all_data.busy & 3) != 0 || + waitfor == ANY && + ((all_data.busy & 3) == 3 || + excl_seq >= 0 && (all_data.wdata[0].cb.seq == excl_seq || + all_data.wdata[1].cb.seq == excl_seq))) + pthread_cond_wait(&the_cond, &the_mutex); + + if (!(all_data.busy & 1)) + which = 0; + else + which = 1; + + all_data.busy |= (1 << which); + + pthread_mutex_unlock(&the_mutex); + + return which; +} + +static int +doit(struct glfs_fd *fd) +{ + int ret; + int64_t seq = 0; + int64_t offset = 0; // position in file, in blocks + int64_t base = 0x1000; // where to place the data, in blocks + + int async_mode = ANY; + + init(); + + for (;;) { + int which; + worker_data *wdata; + + // for growing to the first offset + for (;;) { + int gap = base + 0x42 - offset; + if (!gap) + break; + if (gap > 80) + gap = 80; + + which = get_worker(IDLE, -1); + wdata = &all_data.wdata[which]; + + wdata->offset = offset << 9; + do_write(fd, 0, gap << 9, seq++, wdata, "gap-filling"); + + offset += gap; + } + + // 8700 + which = get_worker(IDLE, -1); + wdata = &all_data.wdata[which]; + + wdata->offset = (base + 0x42) << 9; + do_write(fd, 1, 62 << 9, seq++, wdata, "!8700"); + + // 8701 + which = get_worker(IDLE, -1); + wdata = &all_data.wdata[which]; + + wdata->offset = (base + 0x42) << 9; + do_write(fd, 2, 55 << 9, seq++, wdata, "!8701"); + + // 8702 + which = get_worker(async_mode, -1); + wdata = &all_data.wdata[which]; + + wdata->offset = (base + 0x79) << 9; + do_write(fd, 3, 54 << 9, seq++, wdata, "!8702"); + + // 8703 + which = get_worker(async_mode, -1); + wdata = &all_data.wdata[which]; + + wdata->offset = (base + 0xaf) << 9; + do_write(fd, 4, 81 << 9, seq++, wdata, "!8703"); + + // 8704 + // this writes both 5s and 6s + // the range of 5s is the one that overwrites 8703 + + which = get_worker(async_mode, seq - 1); + wdata = &all_data.wdata[which]; + + memset(wdata->iov.iov_base, 5, 81 << 9); + wdata->offset = (base + 0xaf) << 9; + do_write(fd, -1, 1623 << 9, seq++, wdata, "!8704"); + + offset = base + 0x706; + base += 0x1000; + if (base >= 0x100000) + break; + } + + printf("done!\n"); + fflush(stdout); + + pthread_mutex_lock(&the_mutex); + + while ((all_data.busy & 3) != 0) + pthread_cond_wait(&the_cond, &the_mutex); + + pthread_mutex_unlock(&the_mutex); + + ret = glfs_close(fd); + assert(ret >= 0); + /* + ret = glfs_fini(glfs); + assert(ret >= 0); + */ + return 0; +} + +int +main(int argc, char *argv[]) +{ + int ret; + int open_flags = O_RDWR | O_DIRECT | O_TRUNC; + struct glfs_fd *fd; + + glfs = glfs_new(argv[1]); + if (!glfs) { + printf("glfs_new!\n"); + goto out; + } + ret = glfs_set_volfile_server(glfs, "tcp", "localhost", 24007); + if (ret < 0) { + printf("set_volfile!\n"); + goto out; + } + ret = glfs_init(glfs); + if (ret) { + printf("init!\n"); + goto out; + } + fd = glfs_open(glfs, argv[2], open_flags); + if (!fd) { + printf("open!\n"); + goto out; + } + srand(time(NULL)); + return doit(fd); +out: + return 1; +} diff --git a/tests/bugs/write-behind/issue-884.t b/tests/bugs/write-behind/issue-884.t new file mode 100755 index 00000000000..2bcf7d15265 --- /dev/null +++ b/tests/bugs/write-behind/issue-884.t @@ -0,0 +1,40 @@ +#!/bin/bash + +. $(dirname $0)/../../include.rc +. $(dirname $0)/../../volume.rc + +# This test tries to detect a race condition in write-behind. It's based on a +# reproducer written by Stefan Ring that is able to hit it sometimes. On my +# system, it happened around 10% of the runs. This means that if this bug +# appears again, this test will fail once every 10 runs. Most probably this +# failure will be hidden by the automatic test retry of the testing framework. +# +# Please, if this test fails, it needs to be analyzed in detail. + +function run() { + "${@}" >/dev/null +} + +cleanup + +TEST glusterd +TEST pidof glusterd + +TEST $CLI volume create $V0 $H0:$B0/$V0 +# This makes it easier to hit the issue +TEST $CLI volume set $V0 client-log-level TRACE +TEST $CLI volume start $V0 + +TEST $GFS --volfile-server=$H0 --volfile-id=$V0 $M0 + +build_tester $(dirname $0)/issue-884.c -lgfapi + +TEST touch $M0/testfile + +# This program generates a file of 535694336 bytes with a fixed pattern +TEST run $(dirname $0)/issue-884 $V0 testfile + +# This is the md5sum of the expected pattern without corruption +EXPECT "ad105f9349345a70fc697632cbb5eec8" echo "$(md5sum $B0/$V0/testfile | awk '{ print $1; }')" + +cleanup |
