DragonFlyBSD Kernel Audit
DF-2633 / write2633.c
← back to finding ↓ download raw
/*
 * DF-2633 — quantify silent write() loss on a full hammer2 PFS.
 *
 * All results printed as KEY[=VALUE] lines on stdout (unbuffered).
 *
 * Phases:
 *   FILL   - append 64KB incompressible blocks to <mp>/fill.bin until
 *            statfs shows <= 8MB free for 3 consecutive polls (the
 *            exhaustion regime), or a write fails, or block cap reached.
 *            fsync(fill) rc recorded in-regime.
 *   REGIME - 1024 more 64KB appends (count rc), fsync rc; then 500
 *            O_CREAT file creates (rc counts + immediate-exists count)
 *            which drive the backend inode-insert path whose failure
 *            is currently dropped on the floor.
 *   TAIL   - write tail.bin (512KB deterministic pattern), record per-
 *            write rc, fsync rc; the same 512KB is written to
 *            <ctrl>/tail.control on a healthy filesystem for an md5
 *            control after remount.
 *   MMAP   - mmap-write 1MB mm.bin (deterministic pattern), msync rc;
 *            control copy at <ctrl>/mm.control.
 *
 * usage: write2633 <mountpoint> <controldir> [blockcap]
 */
#include <sys/mount.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static uint64_t
xs64(uint64_t *s)
{
	uint64_t x = *s;
	x ^= x << 13; x ^= x >> 7; x ^= x << 17;
	*s = x;
	return x;
}

/* deterministic incompressible content from a fixed seed */
static void
fillbuf(uint64_t seed, uint8_t *buf, size_t n)
{
	size_t i;
	for (i = 0; i < n; i += 8) {
		uint64_t r = xs64(&seed);
		memcpy(buf + i, &r, (n - i >= 8) ? 8 : n - i);
	}
}

static sigjmp_buf bus_jmp;
static volatile sig_atomic_t got_bus;
static void
bus_handler(int sig)
{
	(void)sig;
	got_bus = 1;
	siglongjmp(bus_jmp, 1);
}

#define BLSZ  65536
#define NREG  1024
#define NCREATE 500
#define TAILSZ (128 * 4096)
#define MMSZ (1 << 20)

static int
writen(int fd, const uint8_t *buf, size_t n)
{
	size_t off = 0;
	while (off < n) {
		ssize_t r = write(fd, buf + off, n - off);
		if (r < 0)
			return -1;
		off += r;
	}
	return 0;
}

int
main(int argc, char **argv)
{
	const char *mp, *ctrl;
	char path[1024];
	uint8_t *blk;
	uint64_t seed = 0xC0FFEE123456789ULL;
	struct statfs st;
	long blocks = 0, cap = 6000;
	long ok, fail, firstfail;
	long lowrun = 0;
	int fd, rc, i;
	int regime = 0;
	off_t off_ok = 0, fill_pre_regime = 0;
	uint8_t *tbuf;

	if (argc < 3 || argc > 4) {
		fprintf(stderr, "usage: %s <mountpoint> <controldir> [cap]\n",
		    argv[0]);
		return 2;
	}
	mp = argv[1];
	ctrl = argv[2];
	if (argc == 4)
		cap = atol(argv[3]);

	setvbuf(stdout, NULL, _IONBF, 0);
	blk = malloc(BLSZ);
	tbuf = malloc(TAILSZ > MMSZ ? TAILSZ : MMSZ);
	if (!blk || !tbuf) { perror("malloc"); return 2; }

	snprintf(path, sizeof(path), "%s/fill.bin", mp);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd < 0) { perror("open fill.bin"); return 2; }

	/* PHASE 1: FILL */
	while (blocks < cap) {
		fillbuf(seed + blocks, blk, BLSZ);
		rc = write(fd, blk, BLSZ);
		if (rc < 0) {
			printf("FILL_WRITE_FAIL block=%ld errno=%d (%s)\n",
			    blocks, errno, strerror(errno));
			break;
		}
		++blocks;
		off_ok += rc;
		if ((blocks & 63) == 0) {
			if (statfs(mp, &st) == 0) {
				printf("FILL_PROG blocks=%ld off=%jd free_kb=%ld\n",
				    blocks, (intmax_t)off_ok,
				    (long)(st.f_bavail * st.f_bsize / 1024));
				if ((int64_t)st.f_bavail * st.f_bsize <=
				    (8LL << 20)) {
					if (++lowrun >= 3) {
						regime = 1;
						fill_pre_regime = off_ok;
						printf("REGIME_START free_kb=%ld fill_off=%jd\n",
						    (long)(st.f_bavail*st.f_bsize/1024),
						    (intmax_t)fill_pre_regime);
						break;
					}
				} else {
					lowrun = 0;
				}
			}
		}
	}
	printf("FILL_DONE blocks=%ld off_ok=%jd regime=%d\n",
	    blocks, (intmax_t)off_ok, regime);

	errno = 0;
	rc = fsync(fd);
	printf("FILL_FSYNC rc=%d errno=%d (%s)\n", rc, errno,
	    rc ? strerror(errno) : "ok");
	rc = close(fd);
	printf("FILL_CLOSE rc=%d\n", rc);

	if (!regime) {
		printf("NOTE exhaustion regime not reached\n");
		return 0;
	}

	/* PHASE 2: REGIME appends */
	fd = open(path, O_WRONLY | O_APPEND);
	if (fd < 0) {
		printf("REGIME_OPEN_FAIL errno=%d (%s)\n", errno, strerror(errno));
	} else {
		ok = fail = 0;
		firstfail = -1;
		for (i = 0; i < NREG; ++i) {
			fillbuf(seed + blocks + i, blk, BLSZ);
			rc = write(fd, blk, BLSZ);
			if (rc < 0) {
				++fail;
				if (firstfail < 0) {
					firstfail = i;
					printf("REGIME_WRITE_FAIL i=%d errno=%d (%s)\n",
					    i, errno, strerror(errno));
				}
			} else {
				++ok;
				off_ok += rc;
			}
		}
		printf("REGIME_WRITES ok=%ld fail=%ld firstfail=%ld "
		    "regime_bytes=%ld\n", ok, fail, firstfail, ok * BLSZ);
		errno = 0;
		rc = fsync(fd);
		printf("REGIME_FSYNC rc=%d errno=%d (%s)\n", rc, errno,
		    rc ? strerror(errno) : "ok");
		close(fd);
	}

	/* PHASE 2b: REGIME creates */
	ok = fail = 0;
	for (i = 0; i < NCREATE; ++i) {
		snprintf(path, sizeof(path), "%s/c%04d", mp, i);
		rc = open(path, O_CREAT | O_EXCL | O_WRONLY, 0644);
		if (rc < 0) {
			++fail;
		} else {
			++ok;
			close(rc);
		}
	}
	{
		struct stat sb;
		long exist = 0;
		for (i = 0; i < NCREATE; ++i) {
			snprintf(path, sizeof(path), "%s/c%04d", mp, i);
			if (stat(path, &sb) == 0)
				++exist;
		}
		printf("REGIME_CREATES rc_ok=%ld rc_fail=%ld exist_now=%ld\n",
		    ok, fail, exist);
	}

	/* PHASE 3: TAIL + control */
	fillbuf(0x5EEDAB1E, tbuf, TAILSZ);
	snprintf(path, sizeof(path), "%s/tail.control", ctrl);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd >= 0) {
		rc = writen(fd, tbuf, TAILSZ);
		printf("TAIL_CONTROL rc=%d\n", rc);
		fsync(fd);
		close(fd);
	}
	snprintf(path, sizeof(path), "%s/tail.bin", mp);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd < 0) {
		printf("TAIL_OPEN_FAIL errno=%d (%s)\n", errno, strerror(errno));
	} else {
		ok = fail = 0;
		for (i = 0; i < TAILSZ / 4096; ++i) {
			rc = write(fd, tbuf + i * 4096, 4096);
			if (rc < 0) ++fail; else ++ok;
		}
		printf("TAIL_WRITES ok=%ld fail=%ld\n", ok, fail);
		errno = 0;
		rc = fsync(fd);
		printf("TAIL_FSYNC rc=%d errno=%d (%s)\n", rc, errno,
		    rc ? strerror(errno) : "ok");
		errno = 0;
		rc = close(fd);
		printf("TAIL_CLOSE rc=%d errno=%d\n", rc, errno);
	}

	/* PHASE 4: MMAP + control */
	fillbuf(0xAB1E5EED, tbuf, MMSZ);
	snprintf(path, sizeof(path), "%s/mm.control", ctrl);
	fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
	if (fd >= 0) {
		rc = writen(fd, tbuf, MMSZ);
		printf("MMAP_CONTROL rc=%d\n", rc);
		fsync(fd);
		close(fd);
	}
	snprintf(path, sizeof(path), "%s/mm.bin", mp);
	fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644);
	if (fd < 0) {
		printf("MMAP_OPEN_FAIL errno=%d (%s)\n", errno, strerror(errno));
	} else {
		if (ftruncate(fd, MMSZ) < 0) {
			printf("MMAP_FTRUNCATE_FAIL errno=%d (%s)\n",
			    errno, strerror(errno));
		} else {
			uint8_t *m = mmap(NULL, MMSZ, PROT_READ | PROT_WRITE,
			    MAP_SHARED, fd, 0);
			if (m == MAP_FAILED) {
				printf("MMAP_MAP_FAIL errno=%d (%s)\n",
				    errno, strerror(errno));
			} else {
				got_bus = 0;
				signal(SIGBUS, bus_handler);
				if (sigsetjmp(bus_jmp, 1) == 0) {
					memcpy(m, tbuf, MMSZ);
					printf("MMAP_STORES ok=1\n");
				} else {
					printf("MMAP_STORES sigbus=1\n");
				}
				errno = 0;
				rc = msync(m, MMSZ, MS_SYNC);
				printf("MMAP_MSYNC rc=%d errno=%d (%s) sigbus=%d\n",
				    rc, errno, rc ? strerror(errno) : "ok",
				    got_bus);
				munmap(m, MMSZ);
				signal(SIGBUS, SIG_DFL);
			}
		}
		errno = 0;
		rc = close(fd);
		printf("MMAP_CLOSE rc=%d errno=%d\n", rc, errno);
	}

	printf("WRITE2633_DONE off_ok=%jd fill_pre_regime=%jd\n",
	    (intmax_t)off_ok, (intmax_t)fill_pre_regime);
	return 0;
}