// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2000-2003 Silicon Graphics, Inc.
 * All Rights Reserved.
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <linux/param.h>

typedef	void	*(*fpi_t)(void);
typedef	void	(*fpt_t)(int, void *);
typedef	void	(*fpd_t)(void *);
typedef struct	tdesc
{
	char	*name;
	fpi_t	init;
	fpt_t	test;
	fpd_t	done;
} tdesc_t;

static void	d_readdir(void *);
static void	*i_readdir(void);
static void	t_readdir(int, void *);
static void	crfiles(char **, int, char *);
static void	d_chown(void *);
static void	d_create(void *);
static void	d_linkun(void *);
static void	d_open(void *);
static void	d_rename(void *);
static void	d_stat(void *);
static void	delflist(char **);
static void	dotest(tdesc_t *);
static void	*i_chown(void);
static void	*i_create(void);
static void	*i_linkun(void);
static void	*i_open(void);
static void	*i_rename(void);
static void	*i_stat(void);
static char	**mkflist(int, int, char);
static double	now(void);
static void	prtime(char *, int, double);
static void	rmfiles(char **);
static void	t_chown(int, void *);
static void	t_create(int, void *);
static void	t_crunlink(int, void *);
static void	t_linkun(int, void *);
static void	t_open(int, void *);
static void	t_rename(int, void *);
static void	t_stat(int, void *);
static void	usage(void);

tdesc_t	tests[] = {
	{ "chown",	i_chown, t_chown, d_chown },
	{ "create",	i_create, t_create, d_create },
	{ "crunlink",	(fpi_t)0, t_crunlink, (fpd_t)0 },
	{ "readdir",	i_readdir, t_readdir, d_readdir },
	{ "linkun",	i_linkun, t_linkun, d_linkun },
	{ "open",	i_open, t_open, d_open },
	{ "rename",	i_rename, t_rename, d_rename },
	{ "stat",	i_stat, t_stat, d_stat },
	{ NULL }
};

char		*buffer;
int		compact = 0;
int		files_bg = 0;
int		files_op = 1;
char		**flist_bg;
char		**flist_op;
int		fnlen_bg = 5;
int		fnlen_op = 5;
int		fsize = 0;
int		iters = 0;
double		time_end;
double		time_start;
int		totsec = 0;
int		verbose = 0;

int
main(int argc, char **argv)
{
	int		c;
	char		*testdir;
	tdesc_t		*tp;

	testdir = getenv("TMPDIR");
	if (testdir == NULL)
		testdir = ".";
	while ((c = getopt(argc, argv, "cd:i:l:L:n:N:s:t:v")) != -1) {
		switch (c) {
		case 'c':
			compact = 1;
			break;
		case 'd':
			testdir = optarg;
			break;
		case 'i':
			iters = atoi(optarg);	
			break;
		case 'l':
			fnlen_op = atoi(optarg);
			break;
		case 'L':
			fnlen_bg = atoi(optarg);
			break;
		case 'n':
			files_op = atoi(optarg);
			break;
		case 'N':
			files_bg = atoi(optarg);
			break;
		case 's':
			fsize = atoi(optarg);
			break;
		case 't':
			totsec = atoi(optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		case '?':
			fprintf(stderr, "bad option\n");
			usage();
		}
	}
	if (!iters && !totsec)
		iters = 1;
	if (chdir(testdir) < 0) {
		perror(testdir);
		return 1;
	}
	if (mkdir("metaperf", 0777) < 0 || chdir("metaperf") < 0) {
		perror("metaperf");
		return 1;
	}
	for (; optind < argc; optind++) {
		for (tp = tests; tp->name; tp++) {
			if (strcmp(argv[optind], tp->name) == 0) {
				dotest(tp);
				break;
			}
		}
	}
	chdir("..");
	rmdir("metaperf");
	return 0;
}

static void
crfiles(char **flist, int fsize, char *buf)
{
	int	fd;
	char	**fnp;

	for (fnp = flist; *fnp; fnp++) {
		fd = creat(*fnp, 0666);
		if (fsize)
			write(fd, buf, fsize);
		close(fd);
	}
}

/* ARGSUSED */
static void
d_chown(void *v)
{
	rmfiles(flist_op);
}

/* ARGSUSED */
static void
d_create(void *v)
{
	rmfiles(flist_op);
}

static void
d_readdir(void *v)
{
	rmfiles(flist_op);
	closedir((DIR *)v);
}

/* ARGSUSED */
static void
d_linkun(void *v)
{
	unlink("a");
}

/* ARGSUSED */
static void
d_open(void *v)
{
	rmfiles(flist_op);
}

static void
d_rename(void *v)
{
	rmfiles(flist_op);
	rmfiles((char **)v);
	delflist((char **)v);
}

/* ARGSUSED */
static void
d_stat(void *v)
{
	rmfiles(flist_op);
}

static void
delflist(char **flist)
{
	char	**fnp;

	for (fnp = flist; *fnp; fnp++)
		free(*fnp);
	free(flist);
}

static void
dotest(tdesc_t *tp)
{
	double	dn;
	double	gotsec;
	int	n;
	void	*v;

	flist_bg = mkflist(files_bg, fnlen_bg, 'b');
	flist_op = mkflist(files_op, fnlen_op, 'o');
	if (fsize)
		buffer = calloc(fsize, 1);
	else
		buffer = NULL;
	crfiles(flist_bg, 0, (char *)0);
	n = iters ? iters : 1;
	v = (void *)0;
	for (;;) {
		if (tp->init)
			v = (tp->init)();
		sync();
		sleep(1);
		time_start = now();
		(tp->test)(n, v);
		time_end = now();
		if (tp->done)
			(tp->done)(v);
		gotsec = time_end - time_start;
		if (!totsec || gotsec >= 0.9 * totsec)
			break;
		if (verbose)
			prtime(tp->name, n, gotsec);
		if (!gotsec)
			gotsec = 1.0 / (2 * HZ);
		if (gotsec < 0.001 * totsec)
			dn = n * (0.01 * totsec / gotsec);
		else if (gotsec < 0.01 * totsec)
			dn = n * (0.1 * totsec / gotsec);
		else
			dn = n * (totsec / gotsec);
		if ((int)dn <= n)
			n++;
		else
			n = (int)dn;
	}
	prtime(tp->name, n, gotsec);
	rmfiles(flist_bg);
	delflist(flist_bg);
	delflist(flist_op);
	if (fsize)
		free(buffer);
}

static void *
i_chown(void)
{
	char	**fnp;

	crfiles(flist_op, 0, (char *)0);
	for (fnp = flist_op; *fnp; fnp++)
		chown(*fnp, 1, -1);
	return (void *)0;
}

static void *
i_create(void)
{
	crfiles(flist_op, fsize, buffer);
	return (void *)0;
}

static void *
i_readdir(void)
{
	crfiles(flist_op, 0, (char *)0);
	return opendir(".");
}

static void *
i_linkun(void)
{
	close(creat("a", 0666));
	return (void *)0;
}

static void *
i_open(void)
{
	crfiles(flist_op, 0, (char *)0);
	return (void *)0;
}

static void *
i_rename(void)
{
	crfiles(flist_op, 0, (char *)0);
	return (void *)mkflist(files_op, fnlen_op, 'r');
}

static void *
i_stat(void)
{
	crfiles(flist_op, 0, (char *)0);
	return (void *)0;
}

static char **
mkflist(int files, int fnlen, char start)
{
	int	i;
	char	**rval;

	rval = calloc(files + 1, sizeof(char *));
	for (i = 0; i < files; i++) {
		rval[i] = malloc(fnlen + 1);
		sprintf(rval[i], "%0*d%c", fnlen - 1, i, start);
	}
	return rval;
}

static double
now(void)
{
	struct timeval	t;

	gettimeofday(&t, (void *)0);
	return (double)t.tv_sec + 1.0e-6 * (double)t.tv_usec;
}

static void
prtime(char *name, int n, double t)
{
	double	ops_per_sec;
	double	usec_per_op;

	ops_per_sec = (double)n * (double)files_op / t;
	usec_per_op = t * 1.0e6 / ((double)n * (double)files_op);
	if (compact)
		printf("%s %d %d %d %d %d %d %f %f %f\n",
			name, n, files_op, fnlen_op, fsize, files_bg, fnlen_bg,
			t, ops_per_sec, usec_per_op);
	else {
		printf("%s: %d times, %d file(s) namelen %d",
			name, n, files_op, fnlen_op);
		if (fsize)
			printf(" size %d", fsize);
		if (files_bg)
			printf(", bg %d file(s) namelen %d",
				files_bg, fnlen_bg);
		printf(", time = %f sec, ops/sec=%f, usec/op = %f\n",
			t, ops_per_sec, usec_per_op);
	}
}

static void
rmfiles(char **flist)
{
	char	**fnp;

	for (fnp = flist; *fnp; fnp++)
		unlink(*fnp);
}

/* ARGSUSED */
static void
t_chown(int n, void *v)
{
	char	**fnp;
	int	i;

	for (i = 0; i < n; i++) {
		for (fnp = flist_op; *fnp; fnp++) {
			if ((i & 1) == 0)
				chown(*fnp, 2, -1);
			else
				chown(*fnp, 1, -1);
		}
	}
}

/* ARGSUSED */
static void
t_create(int n, void *v)
{
	int	i;

	for (i = 0; i < n; i++)
		crfiles(flist_op, fsize, buffer);
}

/* ARGSUSED */
static void
t_crunlink(int n, void *v)
{
	int	i;

	for (i = 0; i < n; i++) {
		crfiles(flist_op, fsize, buffer);
		rmfiles(flist_op);
	}
}

static void
t_readdir(int n, void *v)
{
	DIR	*dir;
	int	i;

	for (dir = (DIR *)v, i = 0; i < n; i++) {
		rewinddir(dir);
		while ((readdir(dir)) != NULL);
	}
}

/* ARGSUSED */
static void
t_linkun(int n, void *v)
{
	char	**fnp;
	int	i;

	for (i = 0; i < n; i++) {
		for (fnp = flist_op; *fnp; fnp++)
			link("a", *fnp);
		rmfiles(flist_op);
	}
}

/* ARGSUSED */
static void
t_open(int n, void *v)
{
	char		**fnp;
	int		i;

	for (i = 0; i < n; i++) {
		for (fnp = flist_op; *fnp; fnp++)
			close(open(*fnp, O_RDWR));
	}
}

static void
t_rename(int n, void *v)
{
	char	**fnp;
	int	i;
	char	**rflist;
	char	**rfp;

	for (rflist = (char **)v, i = 0; i < n; i++) {
		for (fnp = flist_op, rfp = rflist; *fnp; fnp++, rfp++) {
			if ((i & 1) == 0)
				rename(*fnp, *rfp);
			else
				rename(*rfp, *fnp);
		}
	}
}

/* ARGSUSED */
static void
t_stat(int n, void *v)
{
	char		**fnp;
	int		i;
	struct stat	stb;

	for (i = 0; i < n; i++) {
		for (fnp = flist_op; *fnp; fnp++)
			stat(*fnp, &stb);
	}
}

static void
usage(void)
{
	fprintf(stderr,
		"Usage: metaperf [-d dname] [-i iters|-t seconds] [-s fsize]\n"
		"\t[-l opfnamelen] [-L bgfnamelen]\n"
		"\t[-n opfcount] [-N bgfcount] test...\n");
	fprintf(stderr,
		"Tests: chown create crunlink linkun open rename stat readdir\n");
	exit(1);
}
