/*
 *
 *   Copyright (c) International Business Machines  Corp., 2002
 *
 *   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
 */

/* 11/01/2002	Port to LTP	robbiew@us.ibm.com */
/* 06/30/2001	Port to Linux	nsharoff@us.ibm.com */

			   /*inode02.c */
/*======================================================================
	=================== TESTPLAN SEGMENT ===================
CALLS:	mkdir, stat, open

	Run with TERM mode.

>KEYS:  < file system and I/O management, system resource constraints.
>WHAT:  < Can the system handle a heavy load on the file system I/O
	< functions?
>HOW:   < Create several identical process that call inode02.c.  This
	< will simulate the multi-user environment, and hopefully uncover
	< conflicts that might occur in "real life" use.
>BUGS:  <
======================================================================*/

#define PATH_STRING_LENGTH  1024
#define NAME_LENGTH  8
#define MAX_PATH_STRING_LENGTH  (PATH_STRING_LENGTH - NAME_LENGTH - 40)
#define DIRECTORY_MODE  00777
#define FILE_MODE       00777

#define MKDIR_STRING_LENGTH  (MAX_PATH_STRING_LENGTH + 7)

/* #define DEBUG	 you can watch the generation with this flag */

#define TRUE  1
#define FALSE 0
#define READ  0
#define WRITE 1

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <wait.h>

#ifdef LINUX
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#endif

#define MAXCHILD	25
int allchild[MAXCHILD + 1];

char name[NAME_LENGTH + 1];
char path_string[PATH_STRING_LENGTH + 1];
char read_string[PATH_STRING_LENGTH + 1];
char write_string[PATH_STRING_LENGTH + 1];
char remove_string[PATH_STRING_LENGTH + 10];
int parent_pid;
int nchild;

FILE *list_stream = NULL;
int list_id;
int file_id;

int increment_name(), get_next_name(), mode(), escrivez(), massmurder();
int max_depth, max_breadth, file_length;
int bd_arg(char *);

#ifdef LINUX
void (*sigset(int, void (*)(int))) (int);
#endif

/** LTP Port **/
#include "test.h"
#include "usctest.h"

void setup(void);
void fail_exit(void);
void anyfail(void);
void ok_exit(void);
void forkfail(void);
void terror(char *);
int instress(void);

#define FAILED 0
#define PASSED 1

int local_flag = PASSED;
FILE *temp;

char *TCID = "inode02";		/* Test program identifier.    */
int TST_TOTAL = 1;		/* Total number of test cases. */
/**************/

int main(argc, argv)
int argc;
char *argv[];
{
	int pid, tree(), p, status;
	int count, child;
	register int i;
	int term();

	setup();

	parent_pid = getpid();

	if (sigset(SIGTERM, (void (*)())term) == SIG_ERR) {
		tst_resm(TBROK, "\tSIGTERM sigset set failed, errno=%d\n",
			 errno);
		exit(1);
	}

	/************************************************/
	/*                                              */
	/*  Input the parameters for the directory---   */
	/*  file trees which are to be generated        */
	/*                                              */
	/************************************************/

	if (argc < 2) {
		max_depth = 6;
		max_breadth = 5;
		file_length = 8;
		nchild = 5;
	} else if (argc < 5) {
		tst_resm(TCONF, "Bad argument count.\n");
		printf
		    ("\tinode02 max_depth max_breadth file_length #children\n\tdefault: inode02 6 5 8 5\n");
		exit(1);
	} else {
		i = 1;
		if (sscanf(argv[i++], "%d", &max_depth) != 1)
			bd_arg(argv[i - 1]);
		if (sscanf(argv[i++], "%d", &max_breadth) != 1)
			bd_arg(argv[i - 1]);
		if (sscanf(argv[i++], "%d", &file_length) != 1)
			bd_arg(argv[i - 1]);
		if (sscanf(argv[i++], "%d", &nchild) != 1)
			bd_arg(argv[i - 1]);
		if (nchild > MAXCHILD) {
			fprintf(temp, "too many children - max is %d\n",
				MAXCHILD);
			exit(1);
		}
	}

	/************************************************/
	/*                                              */
	/*  Generate and check nchild trees             */
	/*                                              */
	/************************************************/

	for (p = 0; p < nchild; p++) {
		pid = fork();
		if (pid == 0) {
			tree();
		} else {
			if (pid < 1) {
				terror
				    ("Fork failed (may be OK if under stress)");
				massmurder();
				if (instress()) {
					ok_exit();
				}
				forkfail();
			}
		}
	}

	count = 0;
	while ((child = wait(&status)) > 0) {
#ifdef DEBUG
		tst_resm(TINFO, "Test %d exited status = %d\n", child, status);
#endif
		if (status) {
			fprintf(temp, "Test %d failed - expected 0 exit.\n",
				child);
			local_flag = FAILED;
		}
		count++;
	}

	if (count != nchild) {
		tst_resm(TFAIL, "Wrong number of children waited on!\n");
		tst_resm(TFAIL, "Saw %d, expected %d\n", count, nchild);
		local_flag = FAILED;
	}

	/************************************************/
	/*                                              */
	/*  And report the results..........            */
	/*                                              */
	/************************************************/

	anyfail();
	/** NOT REACHED **/
	tst_exit();
}

int bd_arg(str)
char *str;
{
	fprintf(temp,
		"Bad argument - %s - could not parse as number.\n\tinode02 [max_depth] [max_breadth] [file_length] [#children]\n\tdefault: inode02 6 5 8 5\n",
		str);
	exit(1);
}

int tree()

/************************************************/
/*						*/
/*  		      TREE			*/
/*						*/
/*   generate a tree of directories and files   */
/*   and save the path names in the path_list	*/
/*   file 					*/
/*						*/
/*   then, read the path names and attempt to   */
/*   access the corresponding directories and	*/
/*   files					*/
/*						*/
/************************************************/
{
	int gen_ret_val, ch_ret_val, exit_val, level;
	int ret_val;
	int generate(), check();
	char path_list_string[PATH_STRING_LENGTH + 10];
	int len;
	int status;
	int snp_ret;

	/********************************/
	/*                              */
	/*  make the root directory for */
	/*  the tree                    */
	/*                              */
	/********************************/

	sprintf(path_string, "inode02.%d", getpid());

	ret_val = mkdir(path_string, DIRECTORY_MODE);

	if (ret_val == -1) {
		tst_resm(TBROK,
			 "Reason: Impossible to create directory %s, errno=%d\n",
			 path_string, errno);
		exit(-5);
	}

	strcpy(remove_string, "rm -rf ");
	strcat(remove_string, path_string);

#ifdef DEBUG
	tst_resm(TINFO, "\n%s\n", path_string);
#endif

	/****************************************/
	/*                                      */
	/*  create the "path_list" file, in     */
	/*  which the list of generated paths   */
	/*  will be stored so that they later   */
	/*  may be checked                      */
	/*                                      */
	/****************************************/

	snp_ret = snprintf(path_list_string, sizeof(path_list_string),
		"%s/path_list",	path_string);
	if (snp_ret < 0 || snp_ret >= sizeof(path_list_string)) {
		tst_resm(TBROK, "snprintf(path_list_string,..) returned %d",
			snp_ret);
		exit(-1);
	}
	list_id = creat(path_list_string, FILE_MODE);
	if (list_id == -1) {
		fprintf(temp,
			"\nThe path_list file '%s' cannot be created, errno=%d\n",
			path_list_string, errno);
		exit(-7);
	}

	/****************************************/
	/*                                      */
	/*   and store its name in path_list    */
	/*                                      */
	/****************************************/

	strcpy(write_string, path_string);
	len = strlen(write_string);
	write_string[len++] = 'D';
	write_string[len] = '\0';
	escrivez(write_string);

	/****************************************/
	/*                                      */
	/*   generate the directory-file tree   */
	/*                                      */
	/****************************************/

	level = 0;

#ifdef DEBUG
	tst_resm(TINFO, "\n\t%s\n\n", "GENERATING:");
#endif

	gen_ret_val = generate(path_string, level);
	close(list_id);
	list_id = open(path_list_string, READ);
	if (list_id == -1) {
		fprintf(temp,
			"\nThe path_list file cannot be opened for reading, errno=%d\n",
			errno);
		exit(-8);
	}
	list_stream = fdopen(list_id, "r");

	/****************************************/
	/*                                      */
	/*   check the directory-file tree      */
	/*      for correctness                 */
	/*                                      */
	/****************************************/

#ifdef DEBUG
	tst_resm(TINFO, "\n\t%s\n\n", "CHECKING:");
#endif

	ch_ret_val = check();

	if (gen_ret_val > ch_ret_val)
		exit_val = ch_ret_val;
	else
		exit_val = gen_ret_val;

	status = fclose(list_stream);
	if (status != 0) {
		fprintf(temp,
			"Failed to close list_stream: ret=%d errno=%d (%s)\n",
			status, errno, strerror(errno));
		exit(-8);
	}

	/*
	 * Remove file.
	 */

	status = system(remove_string);
	if (status) {
		fprintf(temp, "Caution - `%s' failed.\n", remove_string);
		fprintf(temp, "Status returned %d.\n", status);
	}

	/****************************************/
	/*                                      */
	/*         .....and exit main           */
	/*                                      */
	/****************************************/

	exit(exit_val);
}

int generate(string, level)

/****************************************/
/*					*/
/*   generate recursively a tree of	*/
/*   directories and files:  within   	*/
/*   created directory, an alternating	*/
/*   series of files and directories 	*/
/*   are constructed---until tree	*/
/*   breadth and depth limits are	*/
/*   reached or an error occurs		*/
/*					*/
/****************************************/
		/***************************/
		/*                         */
char string[];			/*  the directory path     */
		/*  string below which a   */
		/*  tree is generated      */
		/*                         */
		/***************************/

		/***************************/
		/*                         */
int level;			/* the tree depth variable */
		/*                         */
		/***************************/
{
	int switch_flag;
	int ret_val = 0;
	int new_ret_val, len, ret_len;
	char new_string[PATH_STRING_LENGTH + 1];
	int new_level;
	int i, j;		/* iteration counters */
	int snp_ret;

	switch_flag = level & TRUE;
	if (strlen(string) >= MAX_PATH_STRING_LENGTH) {

		/********************************/
		/*                              */
		/*   Maximum path name length   */
		/*          reached             */
		/*                              */
		/********************************/

		fprintf(temp, "\nMaximum path_name length reached\n");
		return (-1);
	} else if (level < max_depth) {
		for (i = 0; i <= max_breadth; i++) {
			get_next_name();
			snp_ret = snprintf(new_string, sizeof(new_string),
				"%s/%s", string, name);
			if (snp_ret < 0 || snp_ret >= sizeof(new_string)) {
				tst_resm(TBROK, "snprintf(new_string,..) "
					"returned %d", snp_ret);
				exit(-1);
			}

			/****************************************/
			/*                                      */
			/*    switch between creating files     */
			/*    and making directories            */
			/*                                      */
			/****************************************/

			if (switch_flag) {
				switch_flag = FALSE;

				/****************************************/
				/*                                      */
				/*        create a new file             */
				/*                                      */
				/****************************************/

				file_id = creat(new_string, FILE_MODE);
				if (file_id == -1) {
					fprintf(temp,
						"\nImpossible to create file %s, errno=%d\n",
						new_string, errno);
					return (-2);
				}
#ifdef DEBUG
				tst_resm(TINFO, "%d  %s F\n", level,
					 new_string);
#endif

				/****************************************/
				/*                                      */
				/*            write to it               */
				/*                                      */
				/****************************************/

				len = strlen(new_string);
				for (j = 1; j <= file_length; j++) {
					ret_len =
					    write(file_id, new_string, len);
					if (ret_len != len) {
						fprintf(temp,
							"\nUnsuccessful write to file %s, errno=%d\n",
							new_string, errno);
						return (-3);
					}
				}
				close(file_id);

				/****************************************/
				/*                                      */
				/*   and store its name in path_list    */
				/*                                      */
				/****************************************/

				strcpy(write_string, new_string);
				len = strlen(write_string);
				write_string[len++] = 'F';
				write_string[len] = '\0';
				escrivez(write_string);
			} else {
				switch_flag = TRUE;

				/****************************************/
				/*                                      */
				/*       or make a directory            */
				/*                                      */
				/*  (mknod can only be called when in   */
				/*   super user mode)                   */
				/*                                      */
				/****************************************/

				ret_val = mkdir(new_string, DIRECTORY_MODE);

				if (ret_val != 0) {
					fprintf(temp,
						"\nImpossible to create directory %s, errno=%d\n",
						new_string, errno);
					return (-5);
				}
#ifdef DEBUG
				tst_resm(TINFO, "%d  %s D\n", level,
					 new_string);
#endif

				/****************************************/
				/*                                      */
				/*     store its name in path_list      */
				/*                                      */
				/****************************************/

				strcpy(write_string, new_string);
				len = strlen(write_string);
				write_string[len++] = 'D';
				write_string[len] = '\0';
				escrivez(write_string);

				/****************************************/
				/*                                      */
				/*      and generate a new level        */
				/*                                      */
				/****************************************/

				new_level = level + 1;
				new_ret_val = generate(new_string, new_level);
				if (new_ret_val < ret_val)
					ret_val = new_ret_val;
			}
		}

		/********************************/
		/*                              */
		/*    Maximum breadth reached   */
		/*                              */
		/********************************/

		return (ret_val);
	} else
		    /********************************/
		/*                             */
		/*    Maximum depth reached    */
		/*                             */
 /********************************/
		return 0;
}

int check()

/****************************************/
/*					*/
/*   check for file and directory	*/
/*   correctness by reading records	*/
/*   from the path_list and attempting	*/
/*   to determine if the corresponding	*/
/*   files or directories are as 	*/
/*   created 				*/
/*					*/
/****************************************/
{
	int len, path_mode, val, ret_len, j;

	for (;;) {

		/****************************************/
		/*                                      */
		/*  read a path string from path_list   */
		/*                                      */
		/****************************************/

		if (fscanf(list_stream, "%s", path_string) == EOF) {

#ifdef DEBUG
			tst_resm(TINFO, "\nEnd of path_list file reached \n");
#endif

			return 0;
		}
#ifdef DEBUG
		tst_resm(TINFO, "%s\n", path_string);
#endif

		len = strlen(path_string);
		len--;
		if (path_string[len] == 'F') {

		/********************************/
			/*                              */
			/*    this should be a file     */
			/*                              */
		/********************************/

			path_string[len] = '\0';
			file_id = open(path_string, READ);
			if (file_id <= 0) {
				fprintf(temp,
					"\nImpossible to open file %s, errno=%d\n",
					path_string, errno);
				return (-1);
			}

			else {
				/********************************/
				/*                              */
				/*    check its contents        */
				/*                              */
				/********************************/

				ret_len = 0;
				len = strlen(path_string);
				for (j = 1; j <= file_length; j++) {
					ret_len =
					    read(file_id, read_string, len);
					if (len != ret_len) {
						fprintf(temp,
							"\nFile read error for file %s, errno=%d\n",
							path_string, errno);
						return (-3);
					}
					read_string[len] = '\0';
					val = strcmp(read_string, path_string);
					if (val != 0) {
						fprintf(temp,
							"\nContents of file %s are different than expected: %s\n",
							path_string,
							read_string);
						return (-4);
					}
				}
				close(file_id);
			}	/* else for */
			if (ret_len <= 0) {
				fprintf(temp,
					"\nImpossible to read file %s, errno=%d\n",
					path_string, errno);
				return (-2);
			}
		} else {

		/********************************/
			/*                              */
			/*  otherwise..........         */
			/*  it should be a directory    */
			/*                              */
		/********************************/

			path_string[len] = '\0';
			path_mode = mode(path_string);
			if (path_mode == -1) {
				fprintf(temp,
					"\nPreviously created directory path %s was not open\n",
					path_string);
				return (-4);
			}
			if ((040000 & path_mode) != 040000) {
				fprintf(temp,
					"\nPath %s was not recognized to be a directory\n",
					path_string);
				fprintf(temp, "Its mode is %o\n", path_mode);
				return (-5);
			}
		}
	}			/* while */
}

int get_next_name()

/****************************************/
/*					*/
/*   get the next---in a dictionary	*/
/*   sense---file or directory name	*/
/*					*/
/****************************************/
{
	static int k;
	int i;
	int last_position;

	last_position = NAME_LENGTH - 1;
	if (k == 0) {

		/************************/
		/*                      */
		/*   initialize name    */
		/*                      */
		/************************/

		for (i = 0; i < NAME_LENGTH; i++)
			name[i] = 'a';
		name[NAME_LENGTH] = '\0';
		k++;
	}
					    /********************************/
	/*                              */
	else
		increment_name(last_position);	/* i.e., beginning at the last  */
	/* position                     */
	/*                              */
					    /********************************/
	return 0;
}

int increment_name(position)

/****************************************/
/*					*/
/*  recursively revise the letters in 	*/
/*  a name to get the lexiographically	*/
/*  next name				*/
/*					*/
/****************************************/
 int position;
{
	int next_position;

	if (name[position] == 'z')
		if (position == 0) {
			fprintf(temp,
				"ERROR: There are no more available names\n");
			exit(-1);
		} else {
			name[position] = 'a';	       /**********************/
			next_position = --position;	/*                    */
			increment_name(next_position);	/*  increment the     */
			/*  previous letter   */
			/*                    */
						       /**********************/
		}
				  /*********************************/
	/*                               */
	else
		name[position]++;	/* otherwise, increment this one */
	return 0;		/*                               */
				  /*********************************/
}

int mode(path_string)

/****************************************/
/*					*/
/*   determine and return the mode of	*/
/*   the file named by path_string 	*/
/*					*/
/****************************************/
 char path_string[];
{
	struct stat buf;
	int ret_val, mod;

	ret_val = stat(path_string, &buf);
	if (ret_val == -1)
		return (-1);
	else {
		mod = buf.st_mode;
		return (mod);
	}
}

int escrivez(string)

char string[];
{
	char write_string[PATH_STRING_LENGTH + 1];
	int len, ret_len;

	strcpy(write_string, string);
	len = strlen(write_string);
	write_string[len] = '\n';
	len++;
	ret_len = write(list_id, write_string, len);
	if (len != ret_len) {
		fprintf(temp,
			"A string of deviant length %d written to path_list, errno=%d\n",
			ret_len, errno);
		exit(-2);
	}
	return 0;
}

int term()
{
	int status;

	fflush(temp);
	if (parent_pid == getpid()) {
		massmurder();	/* kill kids */
		fprintf(temp, "\term1 - SIGTERM received by parent.\n");
		fflush(temp);
	} else {
		fprintf(temp, "\tchild - got SIGTERM signal.\n");
		if (list_stream != NULL)
			fclose(list_stream);
		close(list_id);
		close(file_id);
		status = system(remove_string);
		if (status) {
			fprintf(temp, "Caution - ``%s'' returned status %d\n",
				remove_string, status);
		}
		exit(0);
	}
	return 0;
}

int massmurder()
{
	int i;
	for (i = 0; i < MAXCHILD; i++) {
		if (allchild[i]) {
			kill(allchild[i], SIGTERM);
		}
	}
	return 0;
}

/** LTP Port **/
/*
 * setup
 *
 * Do set up - here its a dummy function
 */
void setup()
{
	tst_tmpdir();
	temp = stderr;
}

/*
 * fail_exit()
 *
 * Exit on failure
 */
void fail_exit()
{
	tst_resm(TFAIL, "Test failed\n");
	tst_rmdir();
	tst_exit();
}

/*
 *
 * Function: anyfail()
 *
 * Description: Exit a test.
 */
void anyfail()
{
	(local_flag == FAILED) ? tst_resm(TFAIL, "Test failed")
	    : tst_resm(TPASS, "Test passed");
	tst_rmdir();
	tst_exit();
}

/*
 * ok_exit
 *
 * Calling block passed the test
 */
void ok_exit()
{
	local_flag = PASSED;
	return;
}

/*
 * forkfail()
 *
 * exit on failure
 */
void forkfail()
{
	tst_resm(TBROK, "Reason: %s\n", strerror(errno));
	tst_rmdir();
	tst_exit();
}

/*
 * Function: terror
 *
 * Description: prints error message this may not be because some part of the
 *              test case failed, for example fork() failed. We will log this
 *              failure as TBROK instead of TFAIL.
 */
void terror(char *message)
{
	tst_resm(TBROK, "Reason: %s:%s\n", message, strerror(errno));
	return;
}

/*
 * instress
 *
 * Assume that we are always running under stress, so this function will
 * return > 0 value always.
 */
int instress()
{
	tst_resm(TINFO, "System resource may be too low, fork() malloc()"
		 " etc are likely to fail.\n");
	return 1;
}
