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

#include "global.h"
#include <ctype.h>

/*
 * nametest.c
 *
 * Run a fully automatic, random test of the directory routines.
 *
 * Given an input file of a list of filenames (one per line)
 * It does a number of iterations of operations
 * chosen pseudo-randomly in certain percentages:
 *   creating (open),
 *   deleting (unlink) and
 *   looking up (stat)
 * on a pseudo-randomly chosen filename (from input file).
 *
 * The percentage thresholds for operation selection change
 * every <number-of-names> iterations.
 * e.g.
 * If had 100 names then:
 * iterations:
 * 1-100:      pct_remove = 33; pct_create = 33;
 * 101-200:    pct_remove = 60; pct_create = 20;
 * 201-300:    pct_remove = 20; pct_create = 60;
 * 301-400:    pct_remove = 33; pct_create = 33;
 * 401-500:    pct_remove = 60; pct_create = 20;
 * 501-600:    pct_remove = 20; pct_create = 60;
 * etc...
 *
 * op > (pct_remove + pct_create) => auto_lookup(ip);
 * op > pct_remove                => auto_create(ip);
 * t                              => auto_remove(ip);
 *
 * Each iteration an op is chosen as shown above
 * and a filename is randomly chosen.
 *
 * The operation is done and any error codes are
 * verified considering whether file exists (info.exists)
 * or not. The stat(3) call also compares inode number.
 */


#define	DOT_COUNT	100	/* print a '.' every X operations */

struct info {
	ino64_t	inumber;
	char	*name;
	short	namelen;
	short	exists;
} *table;

char *table_data;	/* char string storage for info table */

int good_adds, good_rms, good_looks, good_tot;	/* ops that suceeded */
int bad_adds, bad_rms, bad_looks, bad_tot;	/* ops that failed */

int verbose;
int mixcase;

int	auto_lookup(struct info *);
int	auto_create(struct info *);
int	auto_remove(struct info *);

void	usage(void);

void
usage(void)
{
	printf("usage: nametest [-l srcfile] [-i iterations] [-s seed] [-z] [-v] [-c]\n");
	exit(1);
}

int
main(int argc, char *argv[])
{
	char *sourcefile, *c;
	int totalnames, iterations, zeroout;
	int zone, op, pct_remove=0, pct_create=0, ch, i, retval, fd;
	struct stat64 statb;
	struct info *ip;
	int seed, linedots;

	linedots = zeroout = verbose = mixcase = 0;
	seed = (int)time(NULL) % 1000;
	iterations = 100000;
	sourcefile = "input";
	while ((ch = getopt(argc, argv, "l:i:s:zvc")) != EOF) {
		switch (ch) {
		case 'l':	sourcefile = optarg;		break;
		case 's':	seed = atoi(optarg);		break;
		case 'i':	iterations = atoi(optarg);	break;
		case 'z':	zeroout++;			break;
		case 'v':	verbose++;			break;
		case 'c':	mixcase++;			break;
		default:	usage();			break;
		}
	}

	/*
	 * Read in the source file.
	 */
	if (stat64(sourcefile, &statb) < 0) {
		perror(sourcefile);
		usage();
		return 1;
	}
	if ((table_data = malloc(statb.st_size)) == NULL) {
		perror("calloc");
		return 1;
	}
	if ((fd = open(sourcefile, O_RDONLY)) < 0) {
		perror(sourcefile);
		return 1;
	}
	if (read(fd, table_data, statb.st_size) < 0) {
		perror(sourcefile);
		return 1;
	}
	close(fd);

	/*
	 * Allocate space for the info table and fill it in.
	 */

        /*
         * Add up number of lines in file
         * and replace '\n' by '\0'
         */
	totalnames = 0;
	for (c = table_data, i = 0; i < statb.st_size; c++, i++) {
		if (*c == '\n') {
			*c = 0;
			totalnames++;
		}
	}
        if (!totalnames) {
                printf("no names found in input file\n");
                return 1;
        }

	table = (struct info *)calloc(totalnames+1, sizeof(struct info));
	if (table == NULL) {
		perror("calloc");
		return 1;
	}
        /*
         * Copy over names from file (in <table_data>) into name fields
         * of info structures in <table>.
         */
        ip = table;
	ip->name = c = table_data;
	for (i = 0; i < totalnames;  ) {
		if (*c++ == 0) {
			ip++;
			ip->name = c;
			i++;
		} else {
			ip->namelen++;
		}
	}
        /*
         * Check table of names.
         * Name are of files and not commands.
         *
         * ??? I guess use of an input file with commands
         *     has been done before ???
         * "touch fred" => "fred"
         * "rm fred" => error
         * "ls fred" => error
         */
 	for (ip = table, i = 0; i < totalnames; ip++, i++) {
		if (strncmp(ip->name, "touch ", strlen("touch ")) == 0) {
                        /* make name skip over "touch " string */
			ip->name += strlen("touch ");
			ip->namelen -= strlen("touch ");
		} else if (strncmp(ip->name, "rm ", strlen("rm ")) == 0) {
			printf("bad input file, \"rm\" cmds not allowed\n");
			return 1;
		} else if (strncmp(ip->name, "ls ", strlen("ls ")) == 0) {
			printf("bad input file, \"ls\" cmds not allowed\n");
			return 1;
		}
	}

	/*
	 * Run random transactions against the directory.
	 */
	zone = -1;
	printf("Seed = %d (use \"-s %d\" to re-execute this test)\n", seed, seed);
        srandom(seed);

	for (i = 0; i < iterations; i++) {
		/*
		 * The distribution of transaction types changes over time.
		 * At first we have an equal distribution which gives us
		 * a steady state directory of 50% total size.
		 * Later, we have an unequal distribution which gives us
		 * more creates than removes, growing the directory.
		 * Later still, we have an unequal distribution which gives
		 * us more removes than creates, shrinking the directory.
		 */
		if ((i % totalnames) == 0) {
			zone++;
			switch(zone % 3) {
			case 0: pct_remove = 20; pct_create = 60; break;
			case 1: pct_remove = 33; pct_create = 33; break;
			case 2: pct_remove = 60; pct_create = 20; break;
			}
		}

		/*
		 * Choose an operation based on the current distribution.
		 */
		ip = &table[ random() % totalnames ];
		op = random() % 100;
		if (op > (pct_remove + pct_create)) {
			retval = auto_lookup(ip);
		} else if (op > pct_remove) {
			retval = auto_create(ip);
		} else {
			retval = auto_remove(ip);
		}

                /* output '.' every DOT_COUNT ops
                 * and output '\n" every 72 dots
                 */
		if ((i % DOT_COUNT) == 0) {
			if (linedots++ == 72) {
				linedots = 0;
				write(1, "\n", 1);
			}
			write(1, ".", 1);
			fflush(stdout);
		}
	}
	printf("\n");

	printf("creates: %6d OK, %6d EEXIST  (%6d total, %2d%% EEXIST)\n",
			 good_adds, bad_adds, good_adds + bad_adds,
			 (good_adds+bad_adds)
				 ? (bad_adds*100) / (good_adds+bad_adds)
				 : 0);
	printf("removes: %6d OK, %6d ENOENT  (%6d total, %2d%% ENOENT)\n",
			 good_rms, bad_rms, good_rms + bad_rms,
			 (good_rms+bad_rms)
				 ? (bad_rms*100) / (good_rms+bad_rms)
				 : 0);
	printf("lookups: %6d OK, %6d ENOENT  (%6d total, %2d%% ENOENT)\n",
			 good_looks, bad_looks, good_looks + bad_looks,
			 (good_looks+bad_looks)
				 ? (bad_looks*100) / (good_looks+bad_looks)
				 : 0);
	good_tot = good_looks + good_adds + good_rms;
	bad_tot = bad_looks + bad_adds + bad_rms;
	printf("total  : %6d OK, %6d w/error (%6d total, %2d%% w/error)\n",
			 good_tot, bad_tot, good_tot + bad_tot,
			 (good_tot + bad_tot)
				 ? (bad_tot*100) / (good_tot+bad_tot)
				 : 0);

	/*
	 * If asked to clear the directory out after the run,
	 * remove everything that is left.
	 */
	if (zeroout) {
		good_rms = 0;
		for (ip = table, i = 0; i < totalnames; ip++, i++) {
			if (!ip->exists)
				continue;
			good_rms++;
			retval = unlink(ip->name);
			if (retval < 0) {
				if (errno == ENOENT) {
					printf("\"%s\"(%llu) not removed, should have existed\n", ip->name, (unsigned long long)ip->inumber);
				} else {
					printf("\"%s\"(%llu) on remove: ", ip->name, (unsigned long long)ip->inumber);
					perror("unlink");
				}
			}

			if ((good_rms % DOT_COUNT) == 0) {
				write(1, ".", 1);
				fflush(stdout);
			}
		}
		printf("\ncleanup: %6d removes\n", good_rms);
	}
	return 0;
}

char *get_name(struct info *ip)
{
	static char path[PATH_MAX];
	char *p;

	if (!mixcase)
		return ip->name;

	/* pick a random character to change case in path */
	strcpy(path, ip->name);
	p = strrchr(path, '/');
	if (!p)
		p = path;
	p += random() % strlen(p);
	if (islower(*p))
		*p = toupper(*p);
	else
		*p = tolower(*p);
	return path;
}

int
auto_lookup(struct info *ip)
{
	struct stat64 statb;
	int retval;

	retval = stat64(get_name(ip), &statb);
	if (retval >= 0) {
		good_looks++;
		retval = 0;
		if (ip->exists == 0) {
			printf("\"%s\"(%llu) lookup, should not exist\n",
				ip->name, (unsigned long long)statb.st_ino);
			retval = 1;
		} else if (ip->inumber != statb.st_ino) {
			printf("\"%s\"(%llu) lookup, should be inumber %llu\n",
				ip->name, (unsigned long long)statb.st_ino,
				(unsigned long long)ip->inumber);
			retval = 1;
		} else if (verbose) {
			printf("\"%s\"(%llu) lookup ok\n",
				ip->name, (unsigned long long)statb.st_ino);
		}
	} else if (errno == ENOENT) {
		bad_looks++;
		retval = 0;
		if (ip->exists == 1) {
			printf("\"%s\"(%llu) lookup, should exist\n",
				ip->name, (unsigned long long)ip->inumber);
			retval = 1;
		} else if (verbose) {
			printf("\"%s\"(%llu) lookup ENOENT ok\n",
				ip->name, (unsigned long long)ip->inumber);
		}
	} else {
		retval = errno;
		printf("\"%s\"(%llu) on lookup: ",
			ip->name, (unsigned long long)ip->inumber);
		perror("stat64");
	}
	return(retval);
}

int
auto_create(struct info *ip)
{
	struct stat64 statb;
	int retval;

	retval = open(get_name(ip), O_RDWR|O_EXCL|O_CREAT, 0666);
	if (retval >= 0) {
		close(retval);
		good_adds++;
		retval = 0;
		if (stat64(ip->name, &statb) < 0) {
			perror("stat64");
			exit(1);
		}
		if (ip->exists == 1) {
			printf("\"%s\"(%llu) created, but already existed as inumber %llu\n", ip->name, (unsigned long long)statb.st_ino, (unsigned long long)ip->inumber);
			retval = 1;
		} else if (verbose) {
			printf("\"%s\"(%llu) create new ok\n",
				ip->name, (unsigned long long)statb.st_ino);
		}
		ip->exists = 1;
		ip->inumber = statb.st_ino;
	} else if (errno == EEXIST) {
		bad_adds++;
		retval = 0;
		if (ip->exists == 0) {
			if (stat64(ip->name, &statb) < 0) {
				perror("stat64");
				exit(1);
			}
			printf("\"%s\"(%llu) not created, should not exist\n",
				ip->name, (unsigned long long)statb.st_ino);
			retval = 1;
		} else if (verbose) {
			printf("\"%s\"(%llu) not created ok\n",
				ip->name, (unsigned long long)ip->inumber);
		}
		ip->exists = 1;
	} else {
		retval = errno;
		printf("\"%s\"(%llu) on create: ",
			ip->name, (unsigned long long)ip->inumber);
		perror("creat");
	}
	return(retval);
}

int
auto_remove(struct info *ip)
{
	int retval;

	retval = unlink(get_name(ip));
	if (retval >= 0) {
		good_rms++;
		retval = 0;
		if (ip->exists == 0) {
			printf("\"%s\"(%llu) removed, should not have existed\n",
				ip->name, (unsigned long long)ip->inumber);
			retval = 1;
		} else if (verbose) {
			printf("\"%s\"(%llu) remove ok\n",
				ip->name, (unsigned long long)ip->inumber);
		}
		ip->exists = 0;
		ip->inumber = 0;
	} else if (errno == ENOENT) {
		bad_rms++;
		retval = 0;
		if (ip->exists == 1) {
			printf("\"%s\"(%llu) not removed, should have existed\n",
				ip->name, (unsigned long long)ip->inumber);
			retval = 1;
		} else if (verbose) {
			printf("\"%s\"(%llu) not removed ok\n",
				ip->name, (unsigned long long)ip->inumber);
		}
		ip->exists = 0;
	} else {
		retval = errno;
		printf("\"%s\"(%llu) on remove: ",
			ip->name, (unsigned long long)ip->inumber);
		perror("unlink");
	}
	return(retval);
}
