DF-3158 / hammer.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | /* * DF-3158 hammer — deget() duplicate-denode SMP race. * * msdosfs_denode.c deget(): msdosfs_hashins() publishes the denode in the * global hash (releasing dehash_token) at :308, but de_refcnt is set to 1 * only at :323, outside the token. A concurrent deget() for the same * (dev, dirclust, diroffset) key on another CPU sees the entry with * de_refcnt == 0, skips it (msdosfs_hashget :131), and inserts its own * second denode for the same dirent (msdosfs_hashins :174-183 only treats * refcnt>0 entries as collisions). Result: two vnodes/denodes for one * directory entry. * * Detection: both threads open the SAME file after a barrier (racing * degets). T1 then ftruncates to a per-round value and T2 fstats. With a * single denode the size always agrees. With duplicated denodes T2 sees a * stale size forever after -- no timing needed post-hit. * * build: cc -O2 -pthread -o hammer hammer.c * usage: ./hammer /fat/RACEFILE seconds */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/mman.h> #include <pthread.h> static volatile int stop; static volatile long flag; /* round# whose truncate has completed */ static volatile long hits, rounds; static const char *path; #define NT 6 static void *worker(void *arg) { int me = (int)(intptr_t)arg; long local_hits = 0, local_rounds = 0; while (!__atomic_load_n(&stop, __ATOMIC_SEQ_CST)) { int fd = open(path, O_RDWR); if (fd < 0) { perror("open"); break; } if (me == 0) { long sz = 100000 + (local_rounds & 1) * 4096; ftruncate(fd, sz); __atomic_store_n(&flag, local_rounds, __ATOMIC_SEQ_CST); } else { long fl1 = __atomic_load_n(&flag, __ATOMIC_SEQ_CST); struct stat st; if (fstat(fd, &st) == 0) { long fl2 = __atomic_load_n(&flag, __ATOMIC_SEQ_CST); if (fl1 == fl2) { long want = 100000 + (fl1 & 1) * 4096; if ((long)st.st_size != want) { /* candidate: confirm by persistent divergence */ int fd2 = open(path, O_RDWR); int ok = 0; if (fd2 >= 0) { long sz2 = (st.st_size == 100000) ? 104096 : 100000; if (ftruncate(fd2, sz2) == 0) { struct stat st2; if (fstat(fd, &st2) == 0 && (long)st2.st_size == (long)st.st_size && (long)st.st_size != sz2) ok = 1; /* never converged */ } close(fd2); } fprintf(stderr, "%s t%d round %ld: size %ld want %ld\n", ok ? "[CONFIRMED-DUP]" : "[transient]", me, local_rounds, (long)st.st_size, want); if (ok) local_hits++; } } } } close(fd); local_rounds++; __atomic_add_fetch(&rounds, 1, __ATOMIC_SEQ_CST); } __atomic_add_fetch(&hits, local_hits, __ATOMIC_SEQ_CST); return NULL; } int main(int argc, char **argv) { if (argc < 3) { fprintf(stderr, "usage: %s file seconds\n", argv[0]); return 1; } path = argv[1]; int secs = atoi(argv[2]); int fd = open(path, O_RDWR | O_CREAT, 0644); if (fd < 0) { perror("create"); return 1; } ftruncate(fd, 100000); close(fd); pthread_t a, b; pthread_create(&a, NULL, worker, (void*)0); pthread_create(&b, NULL, worker, (void*)1); sleep(secs); __atomic_store_n(&stop, 1, __ATOMIC_SEQ_CST); pthread_join(a, NULL); pthread_join(b, NULL); printf("rounds(thread0 approx)=%ld hits=%ld -> %s\n", __atomic_load_n(&rounds, __ATOMIC_SEQ_CST), __atomic_load_n(&hits, __ATOMIC_SEQ_CST), __atomic_load_n(&hits, __ATOMIC_SEQ_CST) ? "DUPLICATE DENODES OBSERVED" : "no hit"); return 0; } |