/* $Id: fsperf1.c,v 1.6 2004/07/15 18:21:19 ca Exp $ */

#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sysexits.h>

#define PERLEVEL	100
#define FNAMEFMT0	"%06d"
#define FNAMEFMT1	"%02d/%06d"
#define FNAMEFMT2	"%02d/%02d/%06d"
#define DNAMEFMT1	"%02d"
#define DNAMEFMT2	"%02d/%02d"

#if BUFSIZ < 16
# error BUFSIZ too small
#endif

#define FSP_SIZE	2
#define FSP_LINES	400
#define FSP_LEVEL	0

static void
fatal(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	va_end(ap);
	exit(1);
}

static void
filename(char *fn, int n, int level)
{
	switch(level)
	{
	  case 0:
		sprintf(fn, FNAMEFMT0, n);
		break;
	  case 1:
		sprintf(fn, FNAMEFMT1, n % PERLEVEL, n);
		break;
	  case 2:
		sprintf(fn, FNAMEFMT2, (n / PERLEVEL) % PERLEVEL, n % PERLEVEL,
			n);
		break;
	  default:
		fatal("filename: wrong level %d", level);
		/* NOTREACHED */
	}
}

/* rename_file - rename a file */
static void
rename_file(int old, int new, int level)
{
	char new_path[BUFSIZ];
	char old_path[BUFSIZ];

	filename(new_path, new, level);
	filename(old_path, old, level);
/* printf("/: %s -> %s\n", old_path, new_path); */
	if (rename(old_path, new_path))
		fatal("rename %s to %s: %d", old_path, new_path, errno);
}

/* make_file - create a little file and use it */
static void make_file(int seqno, int size, int level, int syncit)
{
	char path[BUFSIZ];
	char buf[1024];
	FILE *fp;
	int i;

	filename(path, seqno, level);
/* printf("+: %s\n", path); */
	if ((fp = fopen(path, "w")) == 0)
		fatal("open(%s): %d", path, errno);
	memset(buf, 'x', sizeof(buf));
	for (i = 0; i < size; i++)
		if (fwrite(buf, sizeof(buf), 1, fp) != 1)
			fatal("fwrite: %d", errno);
	if (syncit && fsync(fileno(fp)))
		fatal("fsync(%s): %d", path, errno);
	if (fclose(fp))
		fatal("fclose(%s): %d", path, errno);
	if ((fp = fopen(path, "r")) == 0)
		fatal("open(%s): %d", path, errno);
	while (fgets(path, sizeof(path), fp))
		/* void */ ;
	if (fclose(fp))
		fatal("fclose(%s): %d", path, errno);
}

/* use_file - use existing file */
static void
use_file(int seqno, int size, int level)
{
	char path[BUFSIZ];
	FILE *fp;
	int i;

	filename(path, seqno, level);
	if ((fp = fopen(path, "w")) == NULL)
		fatal("open %s: %d", path, errno);
	for (i = 0; i < size; i++)
		fprintf(fp, "hello %d", i);
	if (fsync(fileno(fp)))
		fatal("fsync: %d", errno);
	if (fclose(fp))
		fatal("fclose: %d", errno);
	if ((fp = fopen(path, "r+")) == NULL)
		fatal("open %s: %d", path, errno);
	while (fgets(path, sizeof(path), fp) != NULL)
		/* void */ ;
	if (ftruncate(fileno(fp), (off_t) 0))
		fatal("ftruncate: %d", errno);
	if (fclose(fp))
		fatal("fclose: %d", errno);
}

/* remove_file - delete specified file */

static void remove_file(int n, int level)
{
	char path[BUFSIZ];

	filename(path, n, level);
/* printf("-: %s\n", path); */
	if (remove(path))
		fatal("remove %s: %d", path, errno);
}

/* remove_silent - delete specified file, silently */
static void remove_silent(int n, int level)
{
	char path[BUFSIZ];

	filename(path, n, level);
	(void) remove(path);
}

/* usage - explain */

static void usage(char *myname)
{
	fprintf(stderr, "usage: %s [-cr] [-C concurrency] [-h hash] [-l lines] [-s size] messages directory_entries\n",
		myname);
	fprintf(stderr, " -C n           keep n messages open concurrently\n");
	fprintf(stderr, " -c             create/delete file\n");
	fprintf(stderr, " -h hashlevel   0, 1, 2: number of directory levels\n");
	fprintf(stderr, "                (default: %d)\n", FSP_LEVEL);
	fprintf(stderr, " -l lines       lines for file to use (default: %d)\n",
			FSP_LINES);
	fprintf(stderr, " -p             populate directory with files before start\n");
	fprintf(stderr, " -r             rename file twice\n");
	fprintf(stderr, " -s size        size of file [KB] (default: %d)\n",
			FSP_SIZE);
	fprintf(stderr, " -S             do not use fscync(2)\n");
#ifdef EX_USAGE
	exit(EX_USAGE);
#else
	exit(1);
#endif
}

int
main(int argc, char **argv)
{
	int op_count;
	int max_file;
	time_t start;
	int do_rename = 0;
	int do_create = 0;
	int syncit = 1;
	int populate = 0;
	int seq, h;
	int o, n;
	int ch;
	int size = FSP_SIZE;
	int lines = FSP_LINES;
	int level = FSP_LEVEL;
	int concurrent = 0;
	char path[BUFSIZ];

	while ((ch = getopt(argc, argv, "C:ch:l:prs:S")) != EOF)
	{
		switch (ch)
		{
		  case 'C':
			if ((concurrent = atoi(optarg)) < 0)
				usage(argv[0]);
			break;
		  case 'c':
			do_create = 1;
			break;
		  case 'h':
			if ((level = atoi(optarg)) < 0)
				usage(argv[0]);
			break;
		  case 'l':
			if ((lines = atoi(optarg)) <= 0)
				usage(argv[0]);
			break;
		  case 'p':
			populate = 1;
			break;
		  case 'r':
			do_rename = 1;
			break;
		  case 'S':
			syncit = 0;
			break;
		  case 's':
			if ((size = atoi(optarg)) <= 0)
				usage(argv[0]);
			break;
		  default:
			usage(argv[0]);
		}
	}

	if (argc != optind + 2 || (do_rename && !do_create))
		usage(argv[0]);
	if ((op_count = atoi(argv[optind])) <= 0)
		usage(argv[0]);
	if ((max_file = atoi(argv[optind + 1])) <= 0)
		usage(argv[0]);
	if (concurrent >= max_file)
		usage(argv[0]);
	if (op_count < max_file)
		usage(argv[0]);

	if (level > 0)
	{
		for (seq = 0; seq < PERLEVEL; seq++)
		{
			sprintf(path, DNAMEFMT1, seq);
			if (mkdir(path, 0750))
				fatal("mkdir(%s): %d", path, errno);
		}
	}

	if (level > 1)
	{
		for (h = 0; h < PERLEVEL; h++)
		{
			for (seq = 0; seq < PERLEVEL; seq++)
			{
				sprintf(path, DNAMEFMT2, h, seq);
				if (mkdir(path, 0750))
					fatal("mkdir(%s): %d", path, errno);
			}
		}
	}

	/* Populate the directory with little files. */
	if (populate)
		for (seq = 0; seq < max_file; seq++)
			make_file(seq, size, level, syncit);

	/* Simulate arrival and delivery of mail messages. */
	seq = 0;
	start = time((time_t *) 0);
	if (concurrent > 0 && do_create)
	{
		for (h = 0; h < concurrent; h++)
		{
			seq %= max_file;
			make_file(seq, size, level, syncit);
			seq++;
		}
	}
	while (op_count > 0)
	{
		seq %= max_file;
		if (do_create)
		{
			make_file(seq, size, level, syncit);
			o = seq - concurrent;
			if (o < 0)
				o += max_file;
			if (do_rename)
			{
				n = o + max_file;
				rename_file(o, n, level);
				rename_file(n, o, level);
			}
			remove_file(o, level);
		}
		else
		{
			use_file(seq, lines, level);
		}
		seq++;
		op_count--;
	}
	if (concurrent > 0 && do_create)
	{
		for (h = 0; h < concurrent; h++)
		{
			o = seq - concurrent;
			if (o < 0)
				o += max_file;
			remove_file(o, level);
			seq++;
		}
	}
	printf("elapsed time: %ld\n", (long) time((time_t *) 0) - start);

	/* Clean up directory fillers.  */
	if (populate || !do_create)
		for (seq = 0; seq < max_file; seq++)
			remove_silent(seq, level);

	if (level > 1)
	{
		for (h = 0; h < PERLEVEL; h++)
		{
			for (seq = 0; seq < PERLEVEL; seq++)
			{
				sprintf(path, DNAMEFMT2, h, seq);
				(void) rmdir(path);
			}
		}
	}

	if (level > 0)
	{
		for (seq = 0; seq < PERLEVEL; seq++)
		{
			sprintf(path, DNAMEFMT1, seq);
			(void) rmdir(path);
		}
	}

	return (0);
}


syntax highlighted by Code2HTML, v. 0.9.1