/*
 *
 *   Copyright (c) International Business Machines  Corp., 2002
 *   Copyright (c) Cyril Hrubis chrubis@suse.cz 2009
 *
 *   This program is free software;  you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 *   the GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program;  if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/*
 * NAME
 *	ftest06.c -- test inode things (ported from SPIE section2/filesuite/ftest7.c, by Airong Zhang)
 *
 * 	this is the same as ftest2, except that it uses lseek64
 *
 * CALLS
 *	open, close,  read, write, llseek,
 *	unlink, chdir
 *
 *
 * ALGORITHM
 *
 *	This was tino.c by rbk.  Moved to test suites by dale.
 *
 *	ftest06 [-f tmpdirname] nchild iterations [partition]
 *
 *	This forks some child processes, they do some random operations
 *	which use lots of directory operations.
 *
 * RESTRICTIONS
 *	Runs a long time with default args - can take others on input
 *	line.  Use with "term mode".
 *	If run on vax the ftruncate will not be random - will always go to
 *	start of file.  NOTE: produces a very high load average!!
 *
 */

#define _LARGEFILE64_SOURCE 1
#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include "test.h"
#include "libftest.h"

char *TCID = "ftest06";
int TST_TOTAL = 1;

#define PASSED 1
#define FAILED 0

static void crfile(int, int);
static void unlfile(int, int);
static void fussdir(int, int);
static void dotest(int, int);
static void dowarn(int, char *, char *);
static void term(int sig);
static void cleanup(void);

#define MAXCHILD	25
#define K_1		1024
#define K_2		2048
#define K_4		4096

static int local_flag;

#define M       (1024*1024)

static int iterations;
static int nchild;
static int parent_pid;
static int pidlist[MAXCHILD];

static char homedir[MAXPATHLEN];
static char dirname[MAXPATHLEN];
static int dirlen;
static int mnt = 0;
static char startdir[MAXPATHLEN], mntpoint[MAXPATHLEN];
static char *partition;
static char *cwd;
static char *fstyp;

int main(int ac, char *av[])
{
	int pid, child, status, count, k, j;
	char name[3];

	int lc;

	/*
	 * parse standard options
	 */
	tst_parse_opts(ac, av, NULL, NULL);

	/*
	 * Default values for run conditions.
	 */
	iterations = 50;
	nchild = 5;

	if (signal(SIGTERM, term) == SIG_ERR) {
		tst_resm(TBROK, "first signal failed");

	}

	/* use the default values for run conditions */
	for (lc = 0; TEST_LOOPING(lc); lc++) {

		local_flag = PASSED;
		/*
		 * Make a directory to do this in; ignore error if already exists.
		 */
		parent_pid = getpid();
		tst_tmpdir();

		if (!startdir[0]) {
			if (getcwd(startdir, MAXPATHLEN) == NULL) {
				tst_brkm(TFAIL | TERRNO, NULL, "getcwd failed");
			}
		}
		cwd = startdir;

		snprintf(dirname, ARRAY_SIZE(dirname),
			 "%s/ftest06.%d", cwd, getpid());
		snprintf(homedir, ARRAY_SIZE(homedir),
			 "%s/ftest06h.%d", cwd, getpid());

		mkdir(dirname, 0755);
		mkdir(homedir, 0755);

		if (chdir(dirname) < 0)
			tst_brkm(TFAIL | TERRNO, cleanup, "\tCan't chdir(%s)",
				 dirname);

		dirlen = strlen(dirname);

		if (chdir(homedir) < 0)
			tst_brkm(TFAIL | TERRNO, cleanup, "\tCan't chdir(%s)",
				 homedir);

		/* enter block */
		for (k = 0; k < nchild; k++) {
			if ((child = fork()) == 0) {
				dotest(k, iterations);
				tst_exit();
			}
			if (child < 0) {
				tst_brkm(TBROK | TERRNO, cleanup,
					 "fork failed");
			}
			pidlist[k] = child;
		}

		/*
		 * Wait for children to finish.
		 */
		count = 0;
		while ((child = wait(&status)) > 0) {
			//tst_resm(TINFO,"Test{%d} exited status = 0x%x", child, status);
			//fprintf(stdout, "status is %d",status);
			if (status) {
				tst_resm(TFAIL,
					 "Test{%d} failed, expected 0 exit.",
					 child);
				local_flag = FAILED;
			}
			++count;
		}

		/*
		 * Should have collected all children.
		 */
		if (count != nchild) {
			tst_resm(TFAIL,
				 "Wrong # children waited on, count = %d",
				 count);
			local_flag = FAILED;
		}

		if (local_flag == PASSED)
			tst_resm(TPASS, "Test passed.");
		else
			tst_resm(TFAIL, "Test failed.");

		if (iterations > 26)
			iterations = 26;

		for (k = 0; k < nchild; k++)
			for (j = 0; j < iterations + 1; j++) {
				ft_mkname(name, dirname, k, j);
				rmdir(name);
				unlink(name);
			}

		if (chdir(startdir) < 0)
			tst_brkm(TFAIL | TERRNO, cleanup, "Can't chdir(%s)",
				 startdir);

		pid = fork();
		if (pid < 0) {
			tst_brkm(TBROK | TERRNO, NULL, "fork failed");
		}

		if (pid == 0) {
			execl("/bin/rm", "rm", "-rf", homedir, NULL);

		} else
			wait(&status);

		if (status)
			tst_resm(TINFO,
				 "CAUTION - ftest06, '%s' may not have been removed.",
				 homedir);

		pid = fork();
		if (pid < 0) {
			tst_brkm(TBROK | TERRNO, NULL, "fork failed");
		}
		if (pid == 0) {
			execl("/bin/rm", "rm", "-rf", dirname, NULL);
			exit(1);
		} else
			wait(&status);
		if (status) {
			tst_resm(TWARN,
				 "CAUTION - ftest06, '%s' may not have been removed.",
				 dirname);
		}

		sync();

	}

	if (local_flag == FAILED)
		tst_resm(TFAIL, "Test failed.");
	else
		tst_resm(TPASS, "Test passed.");

	cleanup();
	tst_exit();
}

#define	warn(val,m1,m2)	if ((val) < 0) dowarn(me,m1,m2)

/*
 * crfile()
 *	Create a file and write something into it.
 */
static char crmsg[] = "Gee, let's write something in the file!\n";

static void crfile(int me, int count)
{
	int fd;
	off64_t seekval;
	int val;
	char fname[MAXPATHLEN];
	char buf[MAXPATHLEN];

	ft_mkname(fname, dirname, me, count);

	fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0666);
	if (fd < 0 && errno == EISDIR) {
		val = rmdir(fname);
		warn(val, "rmdir", fname);
		fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, 0666);
	}
	warn(fd, "creating", fname);

	seekval = lseek64(fd, (off64_t) (rand() % M), 0);
	warn(seekval, "lseek64", 0);

	val = write(fd, crmsg, sizeof(crmsg) - 1);
	warn(val, "write", 0);

	seekval = lseek(fd, -((off64_t) sizeof(crmsg) - 1), 1);
	warn(seekval, "lseek64", 0);

	val = read(fd, buf, sizeof(crmsg) - 1);
	warn(val, "read", 0);

	if (strncmp(crmsg, buf, sizeof(crmsg) - 1))
		dowarn(me, "compare", 0);

	val = close(fd);
	warn(val, "close", 0);
}

/*
 * unlfile()
 *	Unlink some of the files.
 */
static void unlfile(int me, int count)
{
	int val, i;
	char fname[MAXPATHLEN];

	i = count - 10;
	if (i < 0)
		i = 0;
	for (; i < count; i++) {
		ft_mkname(fname, dirname, me, i);
		val = rmdir(fname);
		if (val < 0)
			val = unlink(fname);
		if (val == 0 || errno == ENOENT)
			continue;
		dowarn(me, "unlink", fname);
	}
}

/*
 * fussdir()
 *	Make a directory, put stuff in it, remove it, and remove directory.
 *
 * Randomly leave the directory there.
 */
static void fussdir(int me, int count)
{
	int val;
	char dir[MAXPATHLEN], fname[MAXPATHLEN], savedir[MAXPATHLEN];

	ft_mkname(dir, dirname, me, count);
	rmdir(dir);
	unlink(dir);

	val = mkdir(dir, 0755);
	warn(val, "mkdir", dir);

	/*
	 * Arrange to create files in the directory.
	 */
	strcpy(savedir, dirname);
	strcpy(dirname, "");

	val = chdir(dir);
	warn(val, "chdir", dir);

	crfile(me, count);
	crfile(me, count + 1);

	val = chdir("..");
	warn(val, "chdir", "..");

	val = rmdir(dir);

	if (val >= 0) {
		tst_brkm(TFAIL, NULL,
			 "Test[%d]: rmdir of non-empty %s succeeds!", me,
			 dir);
	}

	val = chdir(dir);
	warn(val, "chdir", dir);

	ft_mkname(fname, dirname, me, count);
	val = unlink(fname);
	warn(val, "unlink", fname);

	ft_mkname(fname, dirname, me, count + 1);
	val = unlink(fname);
	warn(val, "unlink", fname);

	val = chdir(homedir);
	warn(val, "chdir", homedir);

	if (rand() & 0x01) {
		val = rmdir(dir);
		warn(val, "rmdir", dir);
	}

	strcpy(dirname, savedir);
}

/*
 * dotest()
 *	Children execute this.
 *
 * Randomly do an inode thing; loop for # iterations.
 */
#define	THING(p)	{p, "p"}

struct ino_thing {
	void (*it_proc) ();
	char *it_name;
} ino_thing[] = {
THING(crfile), THING(unlfile), THING(fussdir), THING(sync),};

#define	NTHING	ARRAY_SIZE(ino_thing)

int thing_cnt[NTHING];
int thing_last[NTHING];

static void dotest(int me, int count)
{
	int thing, i;

	//tst_resm(TINFO,"Test %d pid %d starting.", me, getpid());

	srand(getpid());

	for (i = 0; i < count; i++) {
		thing = (rand() >> 3) % NTHING;
		(*ino_thing[thing].it_proc) (me, i, ino_thing[thing].it_name);
		++thing_cnt[thing];
	}

	//tst_resm(TINFO,"Test %d pid %d exiting.", me, getpid());
}

static void dowarn(int me, char *m1, char *m2)
{
	int err = errno;

	tst_brkm(TFAIL, NULL, "Test[%d]: error %d on %s %s",
		 me, err, m1, (m2 ? m2 : ""));
}

static void term(int sig LTP_ATTRIBUTE_UNUSED)
{
	int i;

	tst_resm(TINFO, "\tterm -[%d]- got sig term.", getpid());

	if (parent_pid == getpid()) {
		for (i = 0; i < nchild; i++)
			if (pidlist[i])
				kill(pidlist[i], SIGTERM);
		return;
	}

	tst_brkm(TBROK, NULL, "Term: Child process exiting.");
}

static void cleanup(void)
{
	char mount_buffer[1024];

	if (mnt == 1) {
		if (chdir(startdir) < 0) {
			tst_resm(TINFO, "Could not change to %s ", startdir);
		}
		if (!strcmp(fstyp, "cfs")) {
			sprintf(mount_buffer, "/bin/umount %s", partition);
			if (system(mount_buffer) != 0) {
				tst_resm(TINFO, "Unable to unmount %s from %s ",
					 partition, mntpoint);
				if (umount(partition)) {
					tst_resm(TINFO,
						 "Unable to unmount %s from %s ",
						 partition, mntpoint);
				} else {
					tst_resm(TINFO,
						 "Forced umount for %s, /etc/mtab now dirty",
						 partition);
				}
			}
		} else {
			if (umount(partition)) {
				tst_resm(TINFO, "Unable to unmount %s from %s ",
					 partition, mntpoint);
			}
		}
		if (rmdir(mntpoint) != 0) {
			tst_resm(TINFO, "Unable to rmdir %s ", mntpoint);
		}
	}
	tst_rmdir();

}