/*
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009
 *	The President and Fellows of Harvard College.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * fstest - filesystem test code
 *
 * Writes a file (in small chunks) and then reads it back again
 * (also in small chunks) and complains if what it reads back is
 * not the same.
 *
 * The length of SLOGAN is intentionally a prime number and 
 * specifically *not* a power of two.
 */

#include <types.h>
#include <kern/errno.h>
#include <kern/fcntl.h>
#include <lib.h>
#include <uio.h>
#include <thread.h>
#include <synch.h>
#include <vfs.h>
#include <fs.h>
#include <vnode.h>
#include <test.h>

#define SLOGAN   "HODIE MIHI - CRAS TIBI\n"
#define FILENAME "fstest.tmp"
#define NCHUNKS  720
#define NTHREADS 12
#define NCREATES 32

static struct semaphore *threadsem = NULL;

static
void
init_threadsem(void)
{
	if (threadsem==NULL) {
		threadsem = sem_create("fstestsem", 0);
		if (threadsem == NULL) {
			panic("fstest: sem_create failed\n");
		}
	}
}

/*
 * Vary each line of the test file in a way that's predictable but
 * unlikely to mask bugs in the filesystem.
 */
static
void
rotate(char *str, int amt)
{
	int i, ch;

	amt = (amt+2600)%26;
	KASSERT(amt>=0);

	for (i=0; str[i]; i++) {
		ch = str[i];
		if (ch>='A' && ch<='Z') {
			ch = ch - 'A';
			ch += amt;
			ch %= 26;
			ch = ch + 'A';
			KASSERT(ch>='A' && ch<='Z');
		}
		str[i] = ch;
	}
}

////////////////////////////////////////////////////////////

static
void
fstest_makename(char *buf, size_t buflen, 
		const char *fs, const char *namesuffix)
{
	snprintf(buf, buflen, "%s:%s%s", fs, FILENAME, namesuffix);
	KASSERT(strlen(buf) < buflen);
}

#define MAKENAME() fstest_makename(name, sizeof(name), fs, namesuffix)

static
int
fstest_remove(const char *fs, const char *namesuffix)
{
	char name[32];
	char buf[32];
	int err;

	MAKENAME();

	strcpy(buf, name);
	err = vfs_remove(buf);
	if (err) {
		kprintf("Could not remove %s: %s\n", name, strerror(err));
		return -1;
	}
	
	return 0;
}

static
int
fstest_write(const char *fs, const char *namesuffix,
	     int stridesize, int stridepos)
{
	struct vnode *vn;
	int err;
	int i;
	size_t shouldbytes=0;
	size_t bytes=0;
	off_t pos=0;
	char name[32];
	char buf[32];
	struct iovec iov;
	struct uio ku;
	int flags;

	KASSERT(sizeof(buf) > strlen(SLOGAN));

	MAKENAME();

	flags = O_WRONLY|O_CREAT;
	if (stridesize == 1) {
		flags |= O_TRUNC;
	}

	/* vfs_open destroys the string it's passed */
	strcpy(buf, name);
	err = vfs_open(buf, flags, 0664, &vn);
	if (err) {
		kprintf("Could not open %s for write: %s\n", 
			name, strerror(err));
		return -1;
	}

	for (i=0; i<NCHUNKS; i++) {
		if (i % stridesize != stridepos) {
			pos += strlen(SLOGAN);
			continue;
		}
		strcpy(buf, SLOGAN);
		rotate(buf, i);
		uio_kinit(&iov, &ku, buf, strlen(SLOGAN), pos, UIO_WRITE);
		err = VOP_WRITE(vn, &ku);
		if (err) {
			kprintf("%s: Write error: %s\n", name, strerror(err));
			vfs_close(vn);
			vfs_remove(name);
			return -1;
		}

		if (ku.uio_resid > 0) {
			kprintf("%s: Short write: %lu bytes left over\n",
				name, (unsigned long) ku.uio_resid);
			vfs_close(vn);
			vfs_remove(name);
			return -1;
		}

		bytes += (ku.uio_offset - pos);
		shouldbytes += strlen(SLOGAN);
		pos = ku.uio_offset;
	}

	vfs_close(vn);

	if (bytes != shouldbytes) {
		kprintf("%s: %lu bytes written, should have been %lu!\n",
			name, (unsigned long) bytes, 
			(unsigned long) (NCHUNKS*strlen(SLOGAN)));
		vfs_remove(name);
		return -1;
	}
	kprintf("%s: %lu bytes written\n", name, (unsigned long) bytes);

	return 0;
}

static
int
fstest_read(const char *fs, const char *namesuffix)
{
	struct vnode *vn;
	int err;
	int i;
	size_t bytes=0;
	char name[32];
	char buf[32];
	struct iovec iov;
	struct uio ku;

	MAKENAME();

	/* vfs_open destroys the string it's passed */
	strcpy(buf, name);
	err = vfs_open(buf, O_RDONLY, 0664, &vn);
	if (err) {
		kprintf("Could not open test file for read: %s\n",
			strerror(err));
		return -1;
	}

	for (i=0; i<NCHUNKS; i++) {
		uio_kinit(&iov, &ku, buf, strlen(SLOGAN), bytes, UIO_READ);
		err = VOP_READ(vn, &ku);
		if (err) {
			kprintf("%s: Read error: %s\n", name, strerror(err));
			vfs_close(vn);
			return -1;
		}

		if (ku.uio_resid > 0) {
			kprintf("%s: Short read: %lu bytes left over\n", name,
				(unsigned long) ku.uio_resid);
			vfs_close(vn);
			return -1;
		}
		buf[strlen(SLOGAN)] = 0;
		rotate(buf, -i);
		if (strcmp(buf, SLOGAN)) {
			kprintf("%s: Test failed: line %d mismatched: %s\n",
				name, i+1, buf);
			vfs_close(vn);
			return -1;
		}

		bytes = ku.uio_offset;
	}

	vfs_close(vn);

	if (bytes != NCHUNKS*strlen(SLOGAN)) {
		kprintf("%s: %lu bytes read, should have been %lu!\n",
			name, (unsigned long) bytes, 
			(unsigned long) (NCHUNKS*strlen(SLOGAN)));
		return -1;
	}
	kprintf("%s: %lu bytes read\n", name, (unsigned long) bytes);
	return 0;
}

////////////////////////////////////////////////////////////

static
void
dofstest(const char *filesys)
{
	kprintf("*** Starting filesystem test on %s:\n", filesys);

	if (fstest_write(filesys, "", 1, 0)) {
		kprintf("*** Test failed\n");
		return;
	}
	
	if (fstest_read(filesys, "")) {
		kprintf("*** Test failed\n");
		return;
	}

	if (fstest_remove(filesys, "")) {
		kprintf("*** Test failed\n");
		return;
	}

	kprintf("*** Filesystem test done\n");
}

////////////////////////////////////////////////////////////

static
void
readstress_thread(void *fs, unsigned long num)
{
	const char *filesys = fs;
	if (fstest_read(filesys, "")) {
		kprintf("*** Thread %lu: failed\n", num);
	}
	V(threadsem);
}

static
void
doreadstress(const char *filesys)
{
	int i, err;

	init_threadsem();

	kprintf("*** Starting fs read stress test on %s:\n", filesys);

	if (fstest_write(filesys, "", 1, 0)) {
		kprintf("*** Test failed\n");
		return;
	}

	for (i=0; i<NTHREADS; i++) {
		err = thread_fork("readstress", NULL,
				  readstress_thread, (char *)filesys, i);
		if (err) {
			panic("readstress: thread_fork failed: %s\n",
			      strerror(err));
		}
	}

	for (i=0; i<NTHREADS; i++) {
		P(threadsem);
	}

	if (fstest_remove(filesys, "")) {
		kprintf("*** Test failed\n");
		return;
	}
	
	kprintf("*** fs read stress test done\n");
}

////////////////////////////////////////////////////////////

static
void
writestress_thread(void *fs, unsigned long num)
{
	const char *filesys = fs;
	char numstr[8];
	snprintf(numstr, sizeof(numstr), "%lu", num);

	if (fstest_write(filesys, numstr, 1, 0)) {
		kprintf("*** Thread %lu: failed\n", num);
		V(threadsem);
		return;
	}

	if (fstest_read(filesys, numstr)) {
		kprintf("*** Thread %lu: failed\n", num);
		V(threadsem);
		return;
	}

	if (fstest_remove(filesys, numstr)) {
		kprintf("*** Thread %lu: failed\n", num);
	}

	kprintf("*** Thread %lu: done\n", num);

	V(threadsem);
}

static
void
dowritestress(const char *filesys)
{
	int i, err;

	init_threadsem();

	kprintf("*** Starting fs write stress test on %s:\n", filesys);

	for (i=0; i<NTHREADS; i++) {
		err = thread_fork("writestress", NULL,
				  writestress_thread, (char *)filesys, i);
		if (err) {
			panic("thread_fork failed %s\n", strerror(err));
		}
	}

	for (i=0; i<NTHREADS; i++) {
		P(threadsem);
	}

	kprintf("*** fs write stress test done\n");
}

////////////////////////////////////////////////////////////

static
void
writestress2_thread(void *fs, unsigned long num)
{
	const char *filesys = fs;

	if (fstest_write(filesys, "", NTHREADS, num)) {
		kprintf("*** Thread %lu: failed\n", num);
		V(threadsem);
		return;
	}

	V(threadsem);
}

static
void
dowritestress2(const char *filesys)
{
	int i, err;
	char name[32];
	struct vnode *vn;

	init_threadsem();

	kprintf("*** Starting fs write stress test 2 on %s:\n", filesys);

	/* Create and truncate test file */
	fstest_makename(name, sizeof(name), filesys, "");
	err = vfs_open(name, O_WRONLY|O_CREAT|O_TRUNC, 0664, &vn);
	if (err) {
		kprintf("Could not create test file: %s\n", strerror(err));
		kprintf("*** Test failed\n");
		return;
	}
	vfs_close(vn);

	for (i=0; i<NTHREADS; i++) {
		err = thread_fork("writestress2", NULL,
				  writestress2_thread, (char *)filesys, i);
		if (err) {
			panic("writestress2: thread_fork failed: %s\n",
			      strerror(err));
		}
	}

	for (i=0; i<NTHREADS; i++) {
		P(threadsem);
	}

	if (fstest_read(filesys, "")) {
		kprintf("*** Test failed\n");
		return;
	}

	if (fstest_remove(filesys, "")) {
		kprintf("*** Test failed\n");
	}


	kprintf("*** fs write stress test 2 done\n");
}

////////////////////////////////////////////////////////////

static
void
createstress_thread(void *fs, unsigned long num)
{
	const char *filesys = fs;
	int i;
	char numstr[16];

	for (i=0; i<NCREATES; i++) {

		snprintf(numstr, sizeof(numstr), "%lu-%d", num, i);

		if (fstest_write(filesys, numstr, 1, 0)) {
			kprintf("*** Thread %lu: file %d: failed\n", num, i);
			V(threadsem);
			return;
		}
		
		if (fstest_read(filesys, numstr)) {
			kprintf("*** Thread %lu: file %d: failed\n", num, i);
			V(threadsem);
			return;
		}

		if (fstest_remove(filesys, numstr)) {
			kprintf("*** Thread %lu: file %d: failed\n", num, i);
			V(threadsem);
			return;
		}

	}

	V(threadsem);
}

static
void
docreatestress(const char *filesys)
{
	int i, err;

	init_threadsem();

	kprintf("*** Starting fs create stress test on %s:\n", filesys);

	for (i=0; i<NTHREADS; i++) {
#ifdef UW
		err = thread_fork("createstress", NULL,
				  createstress_thread, (char *)filesys, i);
#else
		err = thread_fork("createstress",
				  createstress_thread, (char *)filesys, i,
				  NULL);
#endif
		if (err) {
			panic("createstress: thread_fork failed %s\n",
			      strerror(err));
		}
	}

	for (i=0; i<NTHREADS; i++) {
		P(threadsem);
	}

	kprintf("*** fs create stress test done\n");
}

////////////////////////////////////////////////////////////

static
int
checkfilesystem(int nargs, char **args)
{
	char *device;

	if (nargs != 2) {
		kprintf("Usage: fs[12345] filesystem:\n");
		return EINVAL;
	}

	device = args[1];

	/* Allow (but do not require) colon after device name */
	if (device[strlen(device)-1]==':') {
		device[strlen(device)-1] = 0;
	}

	return 0;
}

#define DEFTEST(testname)                         \
  int                                             \
  testname(int nargs, char **args)                \
  {                                               \
	int result;                               \
	result = checkfilesystem(nargs, args);    \
	if (result) {                             \
		return result;                    \
	}                                         \
	do##testname(args[1]);                    \
	return 0;                                 \
  }

DEFTEST(fstest);
DEFTEST(readstress);
DEFTEST(writestress);
DEFTEST(writestress2);
DEFTEST(createstress);

////////////////////////////////////////////////////////////

int
printfile(int nargs, char **args)
{
	struct vnode *rv, *wv;
	struct iovec iov;
	struct uio ku;
	off_t rpos=0, wpos=0;
	char buf[128];
	char outfile[16];
	int result;
	int done=0;

	if (nargs != 2) {
		kprintf("Usage: pf filename\n");
		return EINVAL;
	}

	/* vfs_open destroys the string it's passed; make a copy */
	strcpy(outfile, "con:");

	result = vfs_open(args[1], O_RDONLY, 0664, &rv);
	if (result) {
		kprintf("printfile: %s\n", strerror(result));
		return result;
	}

	result = vfs_open(outfile, O_WRONLY, 0664, &wv);
	if (result) {
		kprintf("printfile: output: %s\n", strerror(result));
		vfs_close(rv);
		return result;
	}

	while (!done) {
		uio_kinit(&iov, &ku, buf, sizeof(buf), rpos, UIO_READ);
		result = VOP_READ(rv, &ku);
		if (result) {
			kprintf("Read error: %s\n", strerror(result));
			break;
		}
		rpos = ku.uio_offset;

		if (ku.uio_resid > 0) {
			done = 1;
		}

		uio_kinit(&iov, &ku, buf, sizeof(buf)-ku.uio_resid, wpos,
			  UIO_WRITE);
		result = VOP_WRITE(wv, &ku);
		if (result) {
			kprintf("Write error: %s\n", strerror(result));
			break;
		}
		wpos = ku.uio_offset;

		if (ku.uio_resid > 0) {
			kprintf("Warning: short write\n");
		}
	}

	vfs_close(wv);
	vfs_close(rv);

	return 0;
}
