/* * Stress-test pread(), pwrite(), read(), and write() to detect a few problems * with their handling of regular files: * * - Lack of atomicity. * https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_07 * requires atomicity. (An implementation may always return <= 1; if it * chooses to return higher values, it must maintain atomicity.) * * - Transiently making readers see zeros when they read concurrently with a * write, even if the file had no zero at that offset before or after the * write. * * By default, this program will print "mismatch" messages if pwrite() is * non-atomic. Build with other options to test other behaviors: * -DCHANGE_CONTENT=0 tests the zeros bug instead of plain atomicity * -DUSE_SEEK=1 tests write()/read() instead of pwrite()/pread() * -DREOPEN=0 reuses the same file descriptor across iterations * -DXLOG_BLCKSZ=32 tests a different byte count * high values may require "ulimit -Ss" changes * * * Observed behaviors: * * Linux 3.10.0-1160.49.1.el7.x86_64 (CentOS 7.9.2009): * pwrite/pread is non-atomic if count>16 (no -D switches) * write/read is atomic (-DUSE_SEEK -DXLOG_BLCKSZ=8192000) * pwrite/pread is free from zeros bug (-DCHANGE_CONTENT=0) * write/read is free from zeros bug (-DUSE_SEEK -DCHANGE_CONTENT=0) * * Linux 4.9.0-13-sparc64-smp (Debian): * pwrite/pread is non-atomic if count>4 (no -D switches) * write/read is non-atomic if count>4 (-DUSE_SEEK) * write/read IS atomic w/o REOPEN (-DUSE_SEEK -DREOPEN=0 -DXLOG_BLCKSZ=8192000) * pwrite/pread has zeros bug for count>127 (-DCHANGE_CONTENT=0) * pwrite/pread w/o REOPEN also has zeros bug for count>127 (-DCHANGE_CONTENT=0 -DREOPEN=0) * write/read has zeros bug for count>127 (-DUSE_SEEK -DCHANGE_CONTENT=0) * write/read w/o REOPEN is free from zeros bug (-DUSE_SEEK -DCHANGE_CONTENT=0 -DREOPEN=0) * * Linux 5.15.0-2-sparc64-smp (Debian bookworm/sid): * pwrite/pread is atomic (-DXLOG_BLCKSZ=8192000) * write/read is atomic (-DUSE_SEEK -DXLOG_BLCKSZ=8192000) * pwrite/pread is free from zeros bug (-DUSE_SEEK -DCHANGE_CONTENT=0) * write/read is free from zeros bug (-DCHANGE_CONTENT=0) */ #include #include #include #include #include #include #include #include /* Size of block written in a loop and read in another process's loop. */ #ifndef XLOG_BLCKSZ #define XLOG_BLCKSZ 8192 #endif /* * Offset of that block within file. Probably not important. This was a * value seen in a PostgreSQL failure. */ #ifndef OFFSET #define OFFSET 0x72E0A0 #endif /* Replace pwrite() w/ lseek()+write()? Likewise pread(). */ #ifndef USE_SEEK #define USE_SEEK 0 #endif /* * Write a different payload to the block each time? Disable this to detect * the transient-zeros bug. Keep enabled to detect mere lack of atomicity. */ #ifndef CHANGE_CONTENT #define CHANGE_CONTENT 1 #endif /* Close and reopen the file before each read or write operation? */ #ifndef REOPEN #define REOPEN 1 #endif #if USE_SEEK static ssize_t my_pread(int fd, void *buf, size_t count, off_t offset) { if (lseek(fd, offset, SEEK_SET) == (off_t) -1) perror("lseek"); return read(fd, buf, count); } #else static ssize_t my_pread(int fd, void *buf, size_t count, off_t offset) { return pread(fd, buf, count, offset); } #endif #if USE_SEEK static ssize_t my_pwrite(int fd, const void *buf, size_t count, off_t offset) { if (lseek(fd, offset, SEEK_SET) == (off_t) -1) perror("lseek"); return write(fd, buf, count); } #else static ssize_t my_pwrite(int fd, const void *buf, size_t count, off_t offset) { return pwrite(fd, buf, count, offset); } #endif #if REOPEN static int reopen(void) { int fd = open("io-rectitude.tmp", O_RDWR); if (fd == -1) perror("open"); return fd; } #endif static void writer(int fd) { unsigned char byte_val = 1; for (;;) { char buf[XLOG_BLCKSZ]; memset(buf, byte_val, sizeof(buf)); #if REOPEN close(fd); fd = reopen(); #endif errno = 0; if (my_pwrite(fd, buf, sizeof(buf), OFFSET) != sizeof(buf)) perror("pwrite"); /* not a defect */ #if CHANGE_CONTENT byte_val++; #endif } } static void reader(int fd) { for (;;) { unsigned char buf[XLOG_BLCKSZ]; int i; #if REOPEN close(fd); fd = reopen(); #endif errno = 0; if (my_pread(fd, buf, sizeof(buf), OFFSET) != sizeof(buf)) perror("pread"); /* not a defect */ else { /* writer() installs the same value in every position. */ for (i = 0; i < sizeof(buf); ++i) { if (buf[i] != buf[0]) { fprintf(stderr, "mismatch at %d: %d vs. %d\n", i, buf[i], buf[0]); break; } } } } } int main(int argc, char **argv) { int fd; unsigned char buf[OFFSET + XLOG_BLCKSZ]; pid_t pid; fd = open("io-rectitude.tmp", O_RDWR | O_CREAT | O_TRUNC, 0666); if (fd == -1) { perror("open"); return 1; } /* initialize file contents */ memset(buf, 1, sizeof(buf)); if (write(fd, buf, sizeof(buf)) != sizeof(buf)) { perror("write"); return 1; } pid = fork(); if (pid == -1) { perror("fork"); return 1; } else if (pid == 0) writer(fd); else reader(fd); return 0; /* unreachable */ }