#include <sys/types.h>
#ifndef __NetBSD__
extern char *__progname;
# define getprogname() __progname
# ifdef __GNUC__
#  define atomic_cas_32(ptr, expected, new) \
    __sync_val_compare_and_swap(ptr, expected, new)
#  define atomic_add_32(ptr, incr) \
    __sync_fetch_and_add(ptr, incr)
# else
#  include <sys/atomic.h>
# endif
#endif
#include <sys/mman.h>

#include <stdio.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>

struct state {
	uint32_t pid;
	uint32_t count;
	uint32_t owner;
	uint32_t todo;
};

static int debug = 0;

static uint32_t batch = 100;		/* How many files per turn */
static uint32_t workers	= 10;		/* How many processes */
static uint32_t repetitions = 10;	/* How many iterations */

static void
makefiles(struct state *s)
{
	uint32_t i, e;
	uint32_t pid, spid, rpid;
	int fd;
	char buf[1024];

	pid = spid = s->pid;

	if (debug)
		fprintf(stderr, "%s: enter pid %d %d %d\n", __func__, spid,
		    s->owner, s->count);
	if ((rpid = atomic_cas_32(&s->owner, 0, spid)) != 0)
		goto out;

	if (debug)
		fprintf(stderr, "%s: work pid %d %d\n", __func__,
		    spid, s->count);

	for (i = s->count, e = i + batch; i < e; i++) {
		snprintf(buf, sizeof(buf), "%d.%d", pid, i);
		if ((fd = open(buf, O_CREAT|O_TRUNC, 0600)) == -1)
			err(EXIT_FAILURE, "%s: can't create pid %d `%s'",
			    __func__, spid, buf);
		close(fd);
	}
	s->count += batch;
	s->todo = 1;
	if (atomic_cas_32(&s->owner, spid, 0) != spid)
		errx(EXIT_FAILURE, "%s: can't unlock pid %d `%s'",
		    __func__, spid, buf);
out:
	if (debug)
		fprintf(stderr, "%s: exit pid %d %d %d\n", __func__, spid,
		    rpid, s->count);
}

static int
removefiles(struct state *s, uint32_t spid)
{
	uint32_t i, e, rpid;
	uint32_t pid = s->pid;
	char buf[1024];

	if (debug)
		fprintf(stderr, "%s: enter pid %d[%d] %d %d\n", __func__, spid,
		    s->pid, s->owner, s->count);

	if ((rpid = atomic_cas_32(&s->owner, 0, spid)) != 0)
		goto out;

	if (debug)
		fprintf(stderr, "%s: work pid %d %d\n", __func__, spid,
		    s->count);

	if (s->todo) {
		for (e = s->count, i = e - batch; i < e; i++) {
			snprintf(buf, sizeof(buf), "%d.%d", pid, i);
			if (unlink(buf) == -1)
				err(EXIT_FAILURE,
				    "%s: can't unlink pid %d `%s'",
				    __func__, spid, buf);
		}
		s->todo = 0;
		rpid = spid;
	}
	if (atomic_cas_32(&s->owner, spid, 0) != spid)
		errx(EXIT_FAILURE, "%s: can't unlock pid %d `%s'",
		    __func__, spid, buf);
out:
	if (debug)
		fprintf(stderr, "%s: exit pid %d[%d] %d %d\n", __func__, spid,
		    s->pid, rpid, s->count);
	return rpid != 0;
}

static void
makeworker(struct state *s, size_t i)
{
	pid_t pid;
	size_t j, r;
	switch (pid = fork()) {
	case 0:
		s[i].pid = getpid();
		for (r = 0; r < repetitions; r++) {
			int got;
			makefiles(&s[i]);
			do {
				got = 0;
				for (j = 0; j < workers; j++)
					got |= removefiles(&s[j], s[i].pid);
			} while (got);
		}
		exit(EXIT_SUCCESS);
	case -1:
		err(EXIT_FAILURE, "fork");
	default:
		return;
	}
}

static void
usage(void)
{
	fprintf(stderr, "Usage: %s [-d] [-b <batch>] [-r <repetitions>] "
	    "[-w <workers>]\n", getprogname());
	exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
	uint32_t i;
	int s;
	void *p;

	while ((s = getopt(argc, argv, "b:dr:w:")) != -1)
		switch (s) {
		case 'd':
			debug++;
			break;
		case 'b':
			batch = atoi(optarg);
			break;
		case 'r':
			repetitions = atoi(optarg);
			break;
		case 'w':
			workers = atoi(optarg);
			break;
		default:
			usage();
		}

	p = mmap(NULL, 8192, PROT_READ|PROT_WRITE,
	    MAP_ANON|MAP_SHARED, -1, 0);
	if (p == MAP_FAILED)
		err(EXIT_FAILURE, "mmap");

	for (i = 0; i < workers; i++)
		makeworker(p, i);

	for (i = 0; i < workers; i++)
		if (wait(&s) == -1)
			err(EXIT_FAILURE, "wait");
	return 0;
}