#define _XOPEN_SOURCE 500 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include /* * A testcase for a race-condition in dm-snapshots. * * Change "VG" symbol to a volume group name that you are using. * The group should be on real disk with NCQ disabled. * No virtualization, no network disks, no ramdisk, no pagecache-backed loopback * (dm-loop is OK) * Use a CFQ disk scheduler on the physical device. * * You may tune the parameters with this #defines, if you have too fast disk, * try increasing number of threads. * * How does it work: * It creates 10MB logical volume, fills it with a pattern and creates * snapshot. * Then, it spawns 10 threads, and synchronizes them so that the main * thread simultaneously starts writing to the main volume and 10 helper * threads start reading the snapshot. * All the threads run in lockstep, operating on the same block * simultaneously (note the unusual use of 3 rw-locks for maintaining the * lockstep). * The reader threads have much lower I/O priority than the main writing * thread, so that their reads will be submitted, but will eventually wait * until the main thread does its relocate and write. The readers will then * read from the old overwritten area and signal an error. */ #define VG "vg1" #define LV "test_lv" #define LV_SNAP "test_snap" #define MEGABYTES "10" #define SNAP_MEGABYTES "11" #define THREADS 10 #define BS 4096 #define ORIG_PATTERN 'p' #define NEW_PATTERN 'n' enum { IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE, }; enum { IOPRIO_WHO_PROCESS = 1, IOPRIO_WHO_PGRP, IOPRIO_WHO_USER, }; #define IOPRIO_CLASS_SHIFT 13 static inline int ioprio_set(int which, int who, int ioprio) { return syscall(__NR_ioprio_set, which, who, ioprio); } static inline int ioprio_get(int which, int who) { return syscall(__NR_ioprio_get, which, who); } #define PRIO_READER ((IOPRIO_CLASS_IDLE << IOPRIO_CLASS_SHIFT) | 0xff) #define PRIO_WRITER (IOPRIO_CLASS_RT << IOPRIO_CLASS_SHIFT) static void do_cmd(char *cmd, int ign_err) { int r; fprintf(stderr, "* %s\n", cmd); r = system(cmd); if (r) { if (r == -1) { perror("system"); } else { if (ign_err) return; fprintf(stderr, "return code %x\n", r); } exit(1); } } static char pattern[BS]; static int h_orig, h_snap; static int n; static long long test_of; static pthread_rwlock_t rw_lock_1; static pthread_rwlock_t rw_lock_2; static pthread_rwlock_t rw_lock_3; static volatile int started = 0; static void pthread_error(int r) { fprintf(stderr, "pthread_error: %s\n", strerror(r)); exit(1); } static void *test_read(void *of) { int r; char *t = memalign(BS, BS); if (!t) perror("memalign"), exit(1); if ((r = pread(h_snap, t, BS, *(long long *)of)) != BS) { fprintf(stderr, "can't read (%d): %s\n", r, strerror(errno)); exit(1); } if (memcmp(pattern, t, BS)) { int i; for (i = 0; i < BS; i++) if (t[i] != pattern[i]) break; fprintf(stderr, "!!!! SNAPSHOT VOLUME DAMAGE AT BLOCK OFFSET %llX, BYTE OFFSET %X: %02x != %02x\n", *(long long *)of, i, (unsigned char)t[i], (unsigned char)pattern[i]); exit(2); } free(t); return NULL; } static void *test_thread(void *_) { int r; //fprintf(stderr, "start\n"); if ((r = ioprio_set(IOPRIO_WHO_PROCESS, 0, PRIO_READER))) perror("ioprio_set"), exit(1); if ((r = pthread_rwlock_rdlock(&rw_lock_2))) pthread_error(r); started = 1; if ((r = ioprio_get(IOPRIO_WHO_PROCESS, 0)) != PRIO_READER) { if (r == -1) perror("ioprio_get"); else fprintf(stderr, "reader priority not set: %x\n", r); exit(1); } again: if ((r = pthread_rwlock_rdlock(&rw_lock_1))) pthread_error(r); if ((r = pthread_rwlock_unlock(&rw_lock_2))) pthread_error(r); if (test_of == -1) { if ((r = pthread_rwlock_unlock(&rw_lock_1))) pthread_error(r); //fprintf(stderr, "return\n"); return NULL; } //fprintf(stderr, "test(%lld)\n", test_of); test_read(&test_of); if ((r = pthread_rwlock_rdlock(&rw_lock_3))) pthread_error(r); if ((r = pthread_rwlock_unlock(&rw_lock_1))) pthread_error(r); if ((r = pthread_rwlock_rdlock(&rw_lock_2))) pthread_error(r); if ((r = pthread_rwlock_unlock(&rw_lock_3))) pthread_error(r); goto again; } int main(void) { int i, j, r; char *np; pthread_t thr[THREADS]; memset(pattern, ORIG_PATTERN, sizeof pattern); do_cmd("lvremove -f "VG"/"LV_SNAP"", 1); do_cmd("lvremove -f "VG"/"LV"", 1); do_cmd("lvcreate -L "MEGABYTES" -n "LV" "VG"", 0); h_orig = open("/dev/mapper/"VG"-"LV"", O_RDWR); if (h_orig < 0) perror("open orig"), exit(1); n = 0; while (write(h_orig, pattern, BS) == BS) { n++; fprintf(stderr, "creating %llx...\r", (long long)n * BS); } if (fsync(h_orig)) perror("fsync"), exit(1); fprintf(stderr,"\n"); lseek(h_orig, 0, SEEK_SET); close(h_orig); do_cmd("lvcreate -L "SNAP_MEGABYTES" -n "LV_SNAP" -s "VG"/"LV"", 0); h_orig = open("/dev/mapper/"VG"-"LV"", O_RDWR | O_DIRECT); if (h_orig < 0) perror("open orig"), exit(1); h_snap = open("/dev/mapper/"VG"-"LV_SNAP"", O_RDONLY | O_DIRECT); if (h_snap < 0) perror("open snap"), exit(1); if ((r = pthread_rwlock_init(&rw_lock_1, NULL))) pthread_error(r); if ((r = pthread_rwlock_init(&rw_lock_2, NULL))) pthread_error(r); if ((r = pthread_rwlock_init(&rw_lock_3, NULL))) pthread_error(r); if ((r = pthread_rwlock_wrlock(&rw_lock_1))) pthread_error(r); if ((r = pthread_rwlock_wrlock(&rw_lock_3))) pthread_error(r); if ((r = ioprio_set(IOPRIO_WHO_PROCESS, 0, PRIO_WRITER))) perror("ioprio_set"), exit(1); for (j = 0; j < THREADS; j++) { if ((r = pthread_create(&thr[j], NULL, test_thread, NULL))) pthread_error(r); } while (!started) usleep(1000); if ((r = ioprio_get(IOPRIO_WHO_PROCESS, 0)) != PRIO_WRITER) { if (r == -1) perror("ioprio_get"); else fprintf(stderr, "writer priority not set: %x\n", r); exit(1); } np = memalign(BS, BS); if (!np) perror("memalign"), exit(1); memset(np, NEW_PATTERN, BS); for (i = 0; i < n; i++) { test_of = (off_t)i * BS; fprintf(stderr, "testing %llx...\r", test_of); if ((r = pthread_rwlock_unlock(&rw_lock_1))) pthread_error(r); if (pwrite(h_orig, np, BS, test_of) != BS) { fprintf(stderr, "can't write (%d): %s\n", r, strerror(errno)); exit(1); } if ((r = pthread_rwlock_wrlock(&rw_lock_2))) pthread_error(r); if ((r = pthread_rwlock_unlock(&rw_lock_3))) pthread_error(r); if ((r = pthread_rwlock_wrlock(&rw_lock_1))) pthread_error(r); if ((r = pthread_rwlock_unlock(&rw_lock_2))) pthread_error(r); if ((r = pthread_rwlock_wrlock(&rw_lock_3))) pthread_error(r); } fprintf(stderr,"\n"); test_of = -1; if ((r = pthread_rwlock_unlock(&rw_lock_1))) pthread_error(r); for (j = 0; j < THREADS; j++) { if ((r = pthread_join(thr[j], NULL))) pthread_error(r); } fprintf(stderr, "TEST PASSED OK.\n"); return 0; }