DragonFlyBSD Kernel Audit
DF-2645 / stress2645.c
← back to finding ↓ download raw
/*
 * DF-2645 stress: race a concurrent inode-chain modification against the
 * flusher's indirect-block collapse (hammer2_chain_indirect_maintenance
 * reached from hammer2_flush_core, hammer2_flush.c:1040).
 *
 * Mechanism under test:
 *   - flush_core(B) scan flushes B's children (incl. file inode chains F*)
 *   - a concurrent write to F* modifies F*'s inode chain AFTER the scan
 *     visited it (COW -> new bref.data_off), while B is still locked in
 *     the bottom-up phase
 *   - maintenance(A,B) collapse loop hits the skip guard
 *     (bcmp(&bsave,&sub->bref) mismatch, hammer2_chain.c:4202-4209)
 *   - chain left un-moved -> hammer2_chain_repchange KKASSERT
 *     (live_count==0 && RB_EMPTY) fires at hammer2_chain.c:2314
 *     (KKASSERT is unconditional on DragonFly)
 *
 * Usage: stress2645 <dir> <nwriter> <mode>
 *   mode 1 = plain 1-byte appends, rotate over surviving files
 *   mode 2 = append + fsync per iteration
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

int
main(int argc, char **argv)
{
	const char *dir = argv[1];
	int nwriter = atoi(argv[2]);
	int mode = atoi(argv[3]);
	char **paths;
	char name[256];
	char buf[256];
	int nfiles = 0, i, fd;
	long iter = 0;
	FILE *lsp;
	int pct;

	if (argc < 4) {
		fprintf(stderr, "usage: %s dir nwriter mode\n", argv[0]);
		exit(1);
	}

	/* collect surviving files */
	snprintf(buf, sizeof(buf), "find %s -type f 2>/dev/null | sort", dir);
	lsp = popen(buf, "r");
	if (!lsp) { perror("popen"); exit(1); }
	paths = calloc(65536, sizeof(char *));
	while (fgets(name, sizeof(name), lsp) && nfiles < 65536) {
		name[strlen(name)-1] = 0;
		if (name[0] == 0)
			continue;
		asprintf(&paths[nfiles], "%s", name);
		++nfiles;
	}
	pclose(lsp);
	if (nfiles == 0) {
		fprintf(stderr, "no files found under %s\n", dir);
		exit(1);
	}
	fprintf(stderr, "stress2645: %d surviving files, %d writers, mode %d\n",
		nfiles, nwriter, mode);
	setvbuf(stdout, NULL, _IOLBF, 0);

	for (i = 0; i < nwriter; ++i) {
		pid_t pid = fork();
		if (pid == 0) {
			unsigned long seed = i * 7919 + 13;
			long mine = 0;
			for (;;) {
				seed = seed * 1103515245 + 12345;
				pct = (seed >> 16) % nfiles;
				fd = open(paths[pct], O_WRONLY|O_APPEND);
				if (fd < 0) {
					usleep(1000);
					continue;
				}
				write(fd, "x", 1);
				if (mode == 2 && fsync(fd) < 0) {
					/* ignore */
				}
				close(fd);
				++mine;
				if ((mine & 0xFFFF) == 0) {
					fprintf(stdout,
						"w%d: %ld appends\n", i, mine);
				}
			}
			_exit(0);
		}
	}

	/* churn children: continuously create+delete randomly-named files
	 * across the whole dirhash key space so the flusher's
	 * indirect-maintenance always has freshly-created indirects that
	 * are becoming sparse (pending deletes) -- i.e. live collapse work
	 * overlapping the writers' inode-chain modifications */
	for (i = 0; i < 2; ++i) {
		pid_t pid = fork();
		if (pid == 0) {
			unsigned long seed = 4242 + i;
			long mine = 0;
			char nm[32], path[1024];
			int j, mode2 = i;
			for (;;) {
				seed = seed * 6364136223846793005UL +
					1442695040888963407UL;
				for (j = 0; j < 24; ++j) {
					nm[j] = 'a' + ((seed >> ((j % 21) + 3)) % 26);
					seed = seed * 6364136223846793005UL +
						1442695040888963407UL;
				}
				nm[24] = 0;
				snprintf(path, sizeof(path), "%s/%s", dir, nm);
				if (mode2 == 0) {
					int fd2 = open(path,
						       O_CREAT|O_RDWR|O_TRUNC,
						       0644);
					if (fd2 >= 0) {
						write(fd2, "c", 1);
						close(fd2);
					}
				} else {
					unlink(path);
				}
				++mine;
				if ((mine & 0x3FFF) == 0)
					fprintf(stdout, "churn%d: %ld\n",
						mode2, mine);
			}
			_exit(0);
		}
	}

	/* parent: progress heartbeat */
	for (;;) {
		sleep(30);
		fprintf(stdout, "parent alive, files=%d\n", nfiles);
	}
	return 0;
}