io-rectitude.c
text/plain
Filename: io-rectitude.c
Type: text/plain
Part: 0
/*
* 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 <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/* 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 */
}