/****************************************************************************
 *                                                                          *
 * The contents of this file are subject to the WebStone Public License     *
 * Version 1.0 (the "License"); you may not use this file except in         *
 * compliance with the License. You may obtain a copy of the License        *
 * at http://www.mindcraft.com/webstone/license10.html                      *
 *                                                                          *
 * Software distributed under the License is distributed on an "AS IS"      *
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See      *
 * the License for the specific language governing rights and limitations   *
 * under the License.                                                       *
 *                                                                          *
 * The Original Code is WebStone 2.5.                                       *
 *                                                                          *
 * The Initial Developer of the Original Code is Silicon Graphics, Inc.     *
 * and Mindcraft, Inc.. Portions created by Silicon Graphics. and           *
 * Mindcraft. are Copyright (C) 1995-1998 Silicon Graphics, Inc. and        *
 * Mindcraft, Inc. All Rights Reserved.                                     *
 *                                                                          *
 * Contributor(s): ______________________________________.                  *
 *                                                                          *
 * @(#) bench.c 2.4@(#)                                                     *
 ***************************************************************************/


#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

#ifndef WIN32
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#else /* WIN32 */
#define	FD_SETSIZE  1024 /* max size for select() - keep before <winsock.h> 
			  * and same size as MAXCLIENTS */
#include <windows.h>
#include <winsock.h>
#include <io.h>
#include <process.h>
#endif /* WIN32 */

#include "sysdep.h"
#include "bench.h"
#ifdef STONE_SSL
#include "random.h"
#endif

/* command line options/data */
int	    savefile = 0;
int	    debug = DEBUG_OFF;
int	    norexec = 0;
int	    haveproxyserver = 0;
char	    proxyserver[MAXHOSTNAMELEN];
char	    network_mask_str[30] = "255.255.255.0";
unsigned    network_mask = 0;
int	    servaddrin_config = 0;
int	    dumpall = 0;
int	    testtime = 0;
int	    havewebserver = 0;
int	    numloops = 0;
NETPORT	    portnum = 0;
int	    record_all_transactions = 0;
int		use_fixed_random_seed = 0;
int		verbose = 0;
char	webserver[MAXHOSTNAMELEN];
char	configfile[MAXPATHLEN];
char	webclient_path[MAXPATHLEN];
char	debug_filename[MAXPATHLEN];
int		uil_filelist_f = 0;
char	uil_filelist[MAXPATHLEN];
char	child_uil_filelist[MAXPATHLEN];
char	filelist[256][MAXPATHLEN];
int		randomize = 0;
fd_set	zerofdset;

#ifdef STONE_SSL
#define MAXSSLVERSIONLEN 8
#define MAXSSLCIPHERLEN 32
#define MAXSSLMIXLEN 8
#define MAXSSLHANDSHAKELEN 8

char ssl_version[MAXSSLVERSIONLEN];
int ssl_client_cache_mode = 0;
char ssl_cipher[MAXSSLCIPHERLEN];
char ssl_mix[MAXSSLMIXLEN];
char ssl_handshake[MAXSSLHANDSHAKELEN];
int ssl_portnum = 8443;

#endif

/* other key data */
long int    number_of_pages = 0;
long int    num_input_lines = 0;
int	    totalnumclients = 0;
int	    num_rexecs = 0;
SOCKET	    socknum[MAXCLIENTS];
SOCKET	    sockIO[MAXTOTALPROCS];
SOCKET	    sockErr[MAXTOTALPROCS];
THREAD  FILE *debugfile;	    

struct hostent  *master_phe;   /* IP addresses for webmaster */

static void
usage(const char *progname)
{
	fprintf(stderr, "Usage: %s [-a] [-d] -f config_file -C webclient_path\n",
		progname);
	fprintf(stderr, "          [-t run_time | -l num_loops] [-p port_num]\n");
	fprintf(stderr, "          [-r] [-s] [-v] [-w webserver_URL] \n");
	fprintf(stderr, "          [-u masterfilelist] [-U clientfilelist] [-W]\n");
	fprintf(stderr, "          [-P proxy_server] [-R] [-S]\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "-w webserver URL [URL ...]\n\n");
	fprintf(stderr, "-a print timing information for all clients\n");
	fprintf(stderr, "-d turn on debug statements\n");
	fprintf(stderr, "-f config_file\tfile specifying clients\n");
	fprintf(stderr, "-l number of iterations to retrieve uils\n");
	fprintf(stderr, "-p port number of web server if not 80\n");
	fprintf(stderr, "-s save client gets to /tmp/webstone.data.*\n");
	fprintf(stderr, "-t run_time\tduration of test in minutes\n");
	fprintf(stderr, "-u URL file\tfilelist of URLs for webmaster\n");
	fprintf(stderr, "-U URL file\tfilelist of URLs for webclient\n");
	fprintf(stderr, "-v verbose mode\n");
	fprintf(stderr, "-D debugfile\tFile to receive debug data on client\n");
	fprintf(stderr, "-P servername\tuse proxy server for transactions\n");
	fprintf(stderr, "-W webserver addresses are in the config file\n");
	fprintf(stderr, "-R record all transactions\n");
	fprintf(stderr, "-S used fixed seed for random number generator\n");
	fprintf(stderr, "-T turn on trace statements\n");
#ifdef STONE_SSL
	fprintf(stderr, "-V SSLv3|SSLv23|SSLv2\tSSL version\n");
	fprintf(stderr, "-c cipher\tpreferred SSL cipher\n");
	fprintf(stderr, "-m on|off\tturn on/off client cache mode\n");
#endif
	errexit("\n");
}

static SOCKET
passivesock(const NETPORT portnum, const char *protocol, const int qlen)
{
	struct protoent    *ppe; /* pointer to protocol info entry */
	struct sockaddr_in sin;  /* Internet endpoint address */
	SOCKET    s;             /* socket descriptor */

	D_PRINTF("Entering passivesock(): portnum=%d, protocol='%s', qlen=%d\n",
				portnum, protocol, qlen);

	memset((char *)&sin, 0, sizeof(sin));

	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
 
	errno = 0;
	sin.sin_port = htons(portnum);
 
	/* Map protocol name to number */
	CLEAR_SOCK_ERR;
	if ((ppe = getprotobyname(protocol)) == NULL)
		errexit("Can't get \"%s\" protocol entry\n", protocol);

	/* allocate a socket */
	CLEAR_SOCK_ERR;
	s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);
	if (BADSOCKET(s))
	{
		D_PRINTF("socket() returned %d. p_proto=%d, errno=%d (%s)",
				s, ppe->p_proto, s, GET_NET_ERR, neterrstr());
		errexit("Can't create socket: %s\n", neterrstr());
	}

	/* Bind the socket */
	CLEAR_SOCK_ERR;
	if (SOCK_ERR(bind(s, (struct sockaddr *)&sin, sizeof(sin))))
		errexit("Can't bind to port %d: %s\n", portnum, neterrstr());

	/* 
	 * If it's a stream, listen for connections.
	 * On NT and many UNIX systems, the backlog parameter is silently 
	 * limited to 5 connections.
	 */
	CLEAR_SOCK_ERR;
	if (SOCK_ERR(listen(s, qlen)))
		errexit("Can't listen on port %s: %s\n", portnum, neterrstr());

	return s;
}  


/* 
 * abort_clients() 
 *	Called by SIGINT handler as well as other error handlers.
 *	This just does an exit() which will close all socket connections.
 *	This doesn't explicitly kill the clients but they will get an error
 *	and die when they try to send results to the webmaster.  If any
 *	children are stuck then they'll have to be killed by hand.
 */	
static void 
abort_clients(void)
{
	exit(2);
}


/* signal handler for SIGINT */
static void 
sig_int(int sig) 
{
	/* its not safe to call fprintf() from a signal handler. */
	abort_clients();
}


#ifdef WIN32
/* echo stdout/stderr from clients */
void 
echo_client(void *arg)
{
	SOCKET	*sockarr;
	char 	buf[BUFSIZ];
	int	i, len, rv;
	fd_set	readfds;
	struct timeval timeout;
	int	is_err = (int) arg;
	int	fdout;
	int	anyleft;

	timeout.tv_sec = 5L;
	timeout.tv_usec = 0L;

	if (is_err) 
	{
		sockarr = sockErr;
		fdout = _fileno(stderr);
	} 
	else 
	{
		sockarr = sockIO;
		fdout = _fileno(stdout);
	}

	while (1) 
	{
		FD_ZERO(&readfds);
		for (i = anyleft = 0; i < num_rexecs; i++)
		{
			if (sockarr[i] != BADSOCKET_VALUE)
			{
				FD_SET(sockarr[i], &readfds);
				anyleft++;
			}
		}
		if (!anyleft)
			return;
		WSASetLastError(0);
		rv = select(num_rexecs, &readfds, NULL, NULL, &timeout);
		if (rv == 0)
			continue;
		if (rv < 0) 
		{
			if (WSAGetLastError() == WSANOTINITIALISED)
				return;
			errexit("echo_client(): select() returns %d: %s\n", 
					rv, neterrstr());
		}

		/* loop over the sockets that are ready with data */
		for (i = 0; i < num_rexecs; i++) 
		{
			if (sockarr[i] != BADSOCKET_VALUE && FD_ISSET(sockarr[i], &readfds))
			{
				len = NETREAD(sockarr[i], buf, sizeof(buf));
				if (len <= 0) /* mark connection closed */
					sockarr[i] = BADSOCKET_VALUE;
				if (len < 0) 
				{
					if (WSAGetLastError() == WSANOTINITIALISED)
						return;
					fprintf(stderr, 
							"Error in echo_client() after NETREAD(): %s\n", 
							neterrstr());
					continue;
				}
				write(fdout, buf, len);	/* copy to stdout or stderr */
			}
		}
	}
}

#else
static int
echo_client(const int fd, int is_err)
{
	/* 
	 * WRITE TEXT FROM FILE DESCRIPTOR INTO STDOUT 
	 */
	char buf[BUFSIZ];
	int  cc;

	D_PRINTF("entering echo_client() for %s\n", (is_err ? "stderr" : "stdout"));

	while (getppid() != 1)
	{
		cc = NETREAD(fd, buf, sizeof(buf));

		/*
		 * If this is the error stream (stderr of the clients) then only
		 * print it to our stdout if we are in debugging mode.
		 */
		if ((cc > 0) && is_err && DEBUGGING_ON)
				write(STDOUT_FILENO, buf, cc);
	}
	D_PRINTF("Exiting echo_client() for %s\n", (is_err ? "stderr" : "stdout"));
	NETCLOSE(fd);
}
#endif /* WIN32 */


/* Picks the appropriate webmaster IP address based on the address of the 
 * client.  This is significant only for hosts with multiple interfaces.
 * Return value is a string with the IP address or hostname (or NULL)
 */
char *
pick_webmaster_IP_address(char *client_hostname, struct hostent *master_phe,
							unsigned netmask) 
{
	static char buf[20];
	unsigned char addr[4];
	int client_addr;
	int i;

	/* Uncomment the following line to let clients do their own
	 * hostname lookup to find the webmaster system.
	 */
	/* return master_phe->h_name; */

    if (isdigit(client_hostname[0]))
	{
		/* we have an IP address */
		client_addr = inet_addr(client_hostname);
		if (client_addr == INADDR_NONE)
			return NULL;
    } 
	else 
	{
		/* we have a hostname, use the webmaster hostname */
		return master_phe->h_name;
    }

    for (i = 0; master_phe->h_addr_list[i] != NULL; i++) 
	{
		if ((*(int *)(master_phe->h_addr_list[i]) & netmask) == 
													(client_addr & netmask))
		{
			memcpy((char *)addr, master_phe->h_addr_list[i], sizeof(addr));
			sprintf(buf, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
			return buf;
		}
    }
	/* No common net - can't give a verified hostname to webclient
	 */
    return NULL;
}


/*
 * Command line parsing
 */
static void 
ParseCmdLine(int argc, char **argv )
{
	int		getoptch;
	int		currarg;
	extern char	*optarg;
	extern int	optind;
	FILE	*filelist_fp;

#ifdef STONE_SSL
	ssl_version[0] = '0';
	ssl_cipher[0] = '0';
	ssl_mix[0] = '0';
	ssl_handshake[0] = '0';
#endif

    /* 
     * PARSE THE COMMAND LINE OPTIONS
     */
#ifndef STONE_SSL
	while((getoptch = getopt(argc,argv,"aC:dD:f:l:M:n:p:P:R:sSt:Tu:U:vw:WX")) != EOF)
#else
	while((getoptch = getopt(argc,argv,"ac:C:dD:f:l:m:M:n:p:P:r:R:sSt:Tu:U:vV:w:Wx:Xy:")) != EOF)
#endif
	
    {
		switch(getoptch)
		{
		case 'C':
			strcpy(webclient_path, optarg);
			break;
		case 'D':
			strcpy(debug_filename, optarg);
			break;
		case 'M':
			strcpy(network_mask_str, optarg);
			break;
		case 'P':
			haveproxyserver = 1;
			strcpy(proxyserver, optarg); 
			break;
		case 'R':	
			record_all_transactions = 1;
			break;
		case 'S':	
			use_fixed_random_seed = 1;
			break;
		case 'T':	
			debug |= DEBUG_TRACE;
			break;
		case 'U':
			strcpy(child_uil_filelist, optarg);
			break;
#ifdef STONE_SSL
		case 'V':
			strcpy(ssl_version, optarg);
			break;
#endif
		case 'X':
			norexec = 1;
			break;
		case 'W':
			servaddrin_config = 1;
			break;
		case 'a':
			dumpall = 1;
			break;
#ifdef STONE_SSL
		case 'c':
			strcpy(ssl_cipher, optarg);
			break;
#endif
		case 'd':
			debug = DEBUG_ALL;
			break;
		case 'f':
			strcpy(configfile, optarg);
			break;
		case 'l':
			numloops = atoi(optarg);
			break;
#ifdef STONE_SSL
		case 'm':
			if(strcasecmp("off", optarg) == 0)
				ssl_client_cache_mode = 0;
			else if(strcasecmp("on", optarg) == 0)
				ssl_client_cache_mode = 1;
			else {
				fprintf(stderr, "unknown option -m %s\n", optarg);
				usage(argv[0]);
			}
			fprintf(stderr, "ssl_client_cache_mode = %d\n", ssl_client_cache_mode);
			break;
#endif
		case 'p':
			portnum = atoi(optarg);
			break;
#ifdef STONE_SSL
		case 'r':
			strcpy(ssl_handshake, optarg);
			break;
#endif
		case 's':
			savefile = 1;
			break;
		case 't':
			testtime = atoi(optarg);
			break;
		case 'u':
			uil_filelist_f = 1;
			strcpy(uil_filelist, optarg);
			break;
		case 'v':
			verbose = 1;
			break;
		case 'w':
			havewebserver = 1;
			strcpy(webserver, optarg);
			break;
#ifdef STONE_SSL
		case 'x':
			strcpy(ssl_mix, optarg);
			break;
		case 'y':
			ssl_portnum = atoi(optarg);
			break;
#endif
		default:
			fprintf(stderr,"-%c: Flag not recognized\n", (char)getoptch);
			usage(argv[0]);
		} /* end switch */
	} /* end while */

	if (numloops && testtime)
		errexit("Can't have both -l and -t\n");

	if (!havewebserver && !servaddrin_config)
	{
		/*
		 * THE SERVERS NAME MUST BE SPECIFIED
		 */
		fprintf(stderr,"No WWW Server specified\n");
		usage(argv[0]);
	}

	if (havewebserver && servaddrin_config)
	{
		/*
		 * CAN'T HAVE BOTH -w and -W
		 */
		fprintf(stderr, "Can't have both -w and -W options\n");
		usage(argv[0]);
	}

	network_mask = inet_addr(network_mask_str);
	if (network_mask == INADDR_NONE) 
	{
		fprintf(stderr, "Invalid network mask (-M %s)\n", network_mask_str);
		usage(argv[0]);
	}

	if (strlen(webclient_path) == 0)
	{
		fprintf(stderr,"No path specified for the webclient program\n");
		usage(argv[0]);
	}

	if (strlen(configfile) == 0)
	{
		/*
		 * THE MASTER MUST HAVE A CONFIGURATION FILE TO READ.
		 */
		fprintf(stderr,"No Configuration file specified\n");
		usage(argv[0]);
	}

	/*
	 * Get the list of UILs to retrieve from the server.  The
	 * page count here may include comment lines.  We'll get the
	 * actual count from the webclient program later.
	 */
	if (uil_filelist_f == 1)
	{
		D_PRINTF( "webmaster(): About to read filelist %s\n",
			uil_filelist );

		filelist_fp = fopen(uil_filelist, "r");
		if (filelist_fp == NULL)
		{
			D_PRINTF( "Error %d opening filelist %s: %s\n", 
					errno, uil_filelist, strerror(errno) );;
			errexit("Error %d opening filelist %s: %s\n", 
					errno, uil_filelist, strerror(errno));
		}
		while (fgets(filelist[num_input_lines], MAXPATHLEN, filelist_fp)
			!= (char *)NULL)
				num_input_lines++;
		fclose(filelist_fp);
	}
	else if (optind < argc)  /* UILs are on the command  line */
	{
		currarg = optind;
		while(currarg != argc)
		{

			/*
			 * GET THE UILS TO RETRIEVE.
			 */
			sscanf(argv[currarg],"%s",filelist[num_input_lines]);
			num_input_lines++;
			currarg++;
		}
	}
	else if (*child_uil_filelist)
	{
		num_input_lines=1;	/* Bogus value; trust that webclient has a file */
	}
	else	/* Get UILs from stdin */
	{
		while (fgets(filelist[num_input_lines], MAXPATHLEN, stdin)
			!= (char *)NULL)
				num_input_lines++;
	}


	if (num_input_lines == 0)
	{
		/*
		 * AT LEAST ONE FILE MUST BE SPECIFIED
		 */
		fprintf(stderr,"No URL resources specified\n");
		usage(argv[0]);
	}
}

/*
 * This function sets up the socket we will use to synchronize with the
 * clients.
 * Returns the socket number if successful, doesn't return if it fails
 */
SOCKET 
SetupSyncSocket(struct sockaddr_in *serveraddr)
{
	int sock, len;

	D_PRINTF("entering SetupSyncSocket()\n");

	/* 
	 * passivesock() will exit if there is any error so the returned 
	 * socket is always valid.
	 */
    sock = passivesock(0, "tcp", MAXCLIENTS);

    len = sizeof(struct sockaddr);  
	CLEAR_SOCK_ERR;
    if (SOCK_ERR(getsockname(sock, (struct sockaddr *)serveraddr, &len)))
		errexit("Could not get socket informaton\n");
  
	return sock;
}

/* Added by Rajesh Shah 5/18/96 */
int
HostEntCpy(struct hostent *dest, struct hostent *src)
{
	size_t count;
	char **sptr, **dptr;
	struct in_addr *iptr;
	
	dest->h_name = (char *)malloc(strlen(src->h_name)+1);
	strcpy(dest->h_name, src->h_name);
	printf("WebMaster name = %s\n", dest->h_name);
	dest->h_aliases = src->h_aliases;
	dest->h_addrtype = src->h_addrtype;
	dest->h_length = src->h_length;

	/*
	 * ADDED: by Greg Burrell of Mindcraft Inc. 10/22/96
	 * PROBLEM: we can't just do the assignment:
	 *
	 *              dest->h_addr_list = src->h_addr_list
	 *
	 *     because those are just pointers and the memory pointed to
	 *     may get overwritten during the next gethostbyname() call.  
	 *
	 * FIX: Make a copy of the h_addr_list of a hostent structure.
	 */
	for(count = 0, sptr = src->h_addr_list; *sptr != NULL; sptr++, count++);
	if ((dest->h_addr_list = malloc(count + 1)) == NULL)
		return 0;
	if ((iptr = malloc(count * sizeof(struct in_addr))) == NULL)
		return 0;
	for (sptr = src->h_addr_list, dptr = dest->h_addr_list; 
			*sptr != NULL; sptr++, dptr++, iptr++) 
	{
		*iptr = *((struct in_addr *) *sptr);
		*dptr = (char *) iptr;
	}
	*dptr = NULL;
	return 1;
}


/*
 * Function which generates a commandline for the webclients
 */
void 
MakeCmdLine(char *commandline)
{
    char tmpcommandline[NCCARGS];
    char hostname[MAXHOSTNAMELEN];
    struct hostent  *master_phe_tmp;

    /*
     * BUILD THE PORTIONS OF THE cmdline FOR EACH CLIENT THAT WE CAN BUILD NOW.
     * WE WILL FILL IN THE NUMBER OF CLIENTS LATER WITH AN sprintf.
     */
    D_PRINTF( "Calling gethostname\n" );

	CLEAR_SOCK_ERR;
    if(SOCK_ERR(gethostname(hostname,MAXHOSTNAMELEN)))
		errexit("Could not retrieve local host name");

	/* Convert hostname to address, to avoid DNS problems for webclients. */
	/* Copy the output of gethostbyname() to our own storage,
	 * so it isn't overwritten by a later call to gethostbyname(). */
	 
	master_phe_tmp = gethostbyname(hostname); 
	if (master_phe_tmp == NULL) 
		errexit("Unable to resolve webmaster hostname '%s'\n", hostname);
	master_phe = (struct hostent *)malloc(sizeof(struct hostent));
	HostEntCpy(master_phe, master_phe_tmp);

    sprintf(commandline,"%s", webclient_path);

    if(haveproxyserver)
    {
        sprintf(tmpcommandline, " -P %s", proxyserver);
        strcat(commandline, tmpcommandline);
    }

    if (DEBUGGING_ON)
        strcat(commandline, " -d");
	else if (TRACING_ON)
        strcat(commandline, " -T");

    if (*debug_filename)
    {
		strcat(commandline," -D ");
		strcat(commandline,debug_filename);
    }

    if (numloops != 0)
    {
        sprintf(tmpcommandline," -l %d", numloops);
        strcat(commandline,tmpcommandline);
    }

    if (portnum)
    {
        sprintf(tmpcommandline," -p %d", portnum);
        strcat(commandline,tmpcommandline);
    }

    if (savefile)
        strcat(commandline," -s");

    if (record_all_transactions)
		strcat(commandline, " -R");

    if (testtime != 0)
    {
        sprintf(tmpcommandline," -t %d", testtime);
        strcat(commandline,tmpcommandline);
    }

    if (*child_uil_filelist)
    {
        sprintf(tmpcommandline," -u %s", child_uil_filelist);
        strcat(commandline,tmpcommandline);
    }

#ifdef STONE_SSL
	if(strlen(ssl_version) != 0) 
	{
		sprintf(tmpcommandline, " -V %s", ssl_version);
		strcat(commandline, tmpcommandline);
	}

	if(strlen(ssl_cipher) != 0)
	{
		sprintf(tmpcommandline, " -C %s", ssl_cipher);
		strcat(commandline, tmpcommandline);
	}
	
	if(ssl_client_cache_mode > 0)
	{
		strcat(commandline, " -m on");
	}
	else
	{
		strcat(commandline, " -m off");
	}
	
	if(strlen(ssl_mix) > 0)
	{
		sprintf(tmpcommandline, " -x %s", ssl_mix);
		strcat(commandline, tmpcommandline);
	}

	if(strlen(ssl_handshake) > 0)
	{
		sprintf(tmpcommandline, " -r %s", ssl_handshake);
		strcat(commandline, tmpcommandline);
	}

	sprintf(tmpcommandline, " -y %d", ssl_portnum);
	strcat(commandline, tmpcommandline);
#endif

    /*
     * SET UP A SPACE FOR THE NUMBER OF CLIENTS ON THE commandline.
     */
	sprintf(tmpcommandline,
		(use_fixed_random_seed ? "%s -n %%d -w %%s -c %%s:%%d -S %%d" :
								 "%s -n %%d -w %%s -c %%s:%%d"), 
		commandline);
    strcpy(commandline,tmpcommandline);
}


/*
 * rexec to the client hosts and start the webclients
 */
int 
RexecClients(char *commandline, 
			char clienthostname[MAXCLIENTS][MAXHOSTNAMELEN],
			struct sockaddr_in *serveraddr)
{
	int		tmpfd;
	int		numclients = 0;
	char	tmpcommandline[NCCARGS];
	struct servent *inetport;
	int		cnt;
	char	buffer[NCCARGS];
	char	login[MAXUSERNAME];
	char	password[MAXPASSWD];
	FILE	*fp;
	int		returnval;
	char	*tmphostname;
	int		client_random_seed_base = 0;
	int		i;

	D_PRINTF( "Entering RexecClients(\"%s\", \"%s\", %lu)\n",
		commandline, clienthostname[0], (long)serveraddr);
	fflush(debugfile);

	/*
	 * OPEN UP THE CONFIG FILE. FOR EACH LINE IN THE CONFIG FILE, CHECK
	 * ITS VALIDITY AND THEN rexec A COMMAND ON THE CLIENT.
	 */
	errno = 0;
	D_PRINTF("Opening configfile: %s\n", configfile);
	if ((fp = fopen(configfile,"r")) == NULL)
		errexit("Could not open config file %s\n", configfile);

    CLEAR_SOCK_ERR;
	if ((inetport = getservbyname("exec","tcp")) == NULL)
		errexit("Could not get service name for exec/tcp\n");

	D_PRINTF( "getservbyname returned %d\n", ntohs(inetport->s_port) );

	cnt = 0;

	while(1)
	{
		char	webserver2[MAXHOSTNAMELEN];
		char	linebuf[150];
		int		num;
		char	*primename;

		if (NULL == fgets(linebuf, sizeof(linebuf), fp))
			break;
		num = sscanf(linebuf,"%s %s %s %d %s", clienthostname[cnt], login,
						password, &numclients, webserver2);
		D_PRINTF("	%s %s %s %d %s\n", clienthostname[cnt], login,
						password, numclients, webserver2);
		if (num < 4)
			break;
		if (servaddrin_config) 
		{
			if (num == 4) 
				errexit("No webserver specified in config file for %s\n", 
						clienthostname[cnt]);
			strcpy(webserver, webserver2);
		}

		if (numclients == 0)
			continue;			/* no work for this client system */
		if (numclients < 0)
			errexit("Number of clients must be >= 0\n");
		if (numclients > MAXPROCSPERNODE)
			errexit("Number of clients per node can't exceed %d\n", 
					MAXPROCSPERNODE);
		totalnumclients += numclients;

		primename = pick_webmaster_IP_address(clienthostname[cnt], master_phe, 
												network_mask);
		if (primename == NULL) 
			errexit("Bad client address %s for Client %d\n", 
					clienthostname[cnt], cnt); 

		fprintf(stdout, "Client %d: %s \t# Processes: %d\n    Webserver: %s\tWebmaster: %s:%d\n", cnt, clienthostname[cnt], numclients, webserver, primename,
				ntohs(serveraddr->sin_port));
		fflush(stdout);

		if (use_fixed_random_seed) {
			sprintf(tmpcommandline, commandline, numclients, webserver, 
					primename, ntohs(serveraddr->sin_port),
					client_random_seed_base);
			client_random_seed_base += numclients;
		}
		else
			sprintf(tmpcommandline, commandline, numclients, webserver, 
					primename, ntohs(serveraddr->sin_port));

		D_PRINTF( "%s rexec %s\n",clienthostname[cnt],tmpcommandline );
		if (norexec) 
			sleep(30);	/* gives some time to start clients for debugging */
		else
		{
			tmphostname = &(clienthostname[cnt][0]);
			D_PRINTF("rexec( %s, %d, %s, %s, \"%s,\" %d)\n",
				tmphostname, inetport->s_port, login, password,
				tmpcommandline, sockErr[cnt]);
			tmpfd = rexec(&tmphostname, inetport->s_port, login, password,
							 tmpcommandline, &sockErr[cnt]); 
			perror("rexec");
			if(BADSOCKET(sockIO[cnt] = tmpfd))
			{
				errexit("Could not rexec: rexec to client %s, cmdline %s failed\n", clienthostname[cnt],tmpcommandline);
			}

			/*
			 * Send the list of UILs to the webclient process, before it
			 * forks
			 */
			if (!(*child_uil_filelist))
			{
				for (i = 0; i < num_input_lines; i++)
				{
					D_PRINTF("webmaster: sending <%s> to client, socket %d.\n",
						filelist[i], sockIO[cnt]);
					NETWRITE(tmpfd, filelist[i], strlen(filelist[i]));
				}
			}
			D_PRINTF("webmaster: sending <%s> to client, socket %d.\n",
					ENDTOKEN, tmpfd);
			/* 
		 	 * write an extra newline in case the last line of the filelist
			 * didn't end in a newline.
			 */
			NETWRITE(tmpfd, "\n", 1);
			NETWRITE(tmpfd, ENDTOKEN, ENDTOKENSTRLEN);

			memset(buffer, 0, sizeof(buffer));

			if (NETREAD(tmpfd, buffer, 10) <= 0)
			{
				D_PRINTF( "Error reading number of pages: %s\n", neterrstr());
				fprintf(stderr, 
					"Error reading number of pages: %s\nSocket number %d\n",
								neterrstr(),tmpfd);
				abort_clients();
				errexit(""); 
			}
			sscanf(buffer, "%d", &number_of_pages);

			memset(buffer, 0, sizeof(buffer));

			if (NETREAD(tmpfd, buffer, 10) <= 0)
			{
				D_PRINTF( "Error reading randomize flag: %s\n", neterrstr());
				fprintf(stderr, 
					"Error reading randomize flag: %s\nSocket number %d\n",
								neterrstr(),tmpfd);
				abort_clients();
				errexit(""); 
			}
			sscanf(buffer, "%d", &randomize);
			D_PRINTF( "randomize set to %d.\n", randomize);

			memset(buffer, 0, sizeof(buffer));

			returnval = NETREAD(tmpfd, buffer, OKSTRLEN);
			if (returnval <= 0 || memcmp(buffer, OKSTR, OKSTRLEN) != 0)
			{
				errexit("rexec to client %s, cmdline %s received error %s\n",
						clienthostname[cnt],tmpcommandline, buffer);
			}
			D_PRINTF("Received OK from %s.\n", clienthostname[cnt] );
		}

#ifndef WIN32
		/* 
	     * Set up processes to read the stdout and stderr of the 
		 * rexec().  
		 */
		D_PRINTF( "Forking webclient stdout process\n" );
		switch (fork()) 
		{ 
			case -1:   /* ERROR */
					errexit("fork: %s\n", strerror(errno));
			case 0:    /* CHILD */
					exit(echo_client(sockIO[cnt], 0)); 
			default:   /* PARENT */
					break;
		} 
		D_PRINTF( "Forking webclient stderr process\n" );
		switch (fork()) 
		{ 
			case -1:   /* ERROR */
					errexit("fork: %s\n", strerror(errno));
			case 0:    /* CHILD */
					exit(echo_client(sockErr[cnt], 1)); 
			default:   /* PARENT */
					break;
		} 
#endif /* ! WIN32 */

		cnt++;
		if (cnt > MAXCLIENTS || cnt > FD_SETSIZE)
			errexit("Number of Clients can't exceed %d\n", MAXCLIENTS);
	}

	num_rexecs = cnt;
	if (totalnumclients > MAXTOTALPROCS)
		errexit("Total number of processes can't exceed  %d\n", MAXTOTALPROCS);

#ifdef WIN32
	/* start threads to echo stdout/stderr from clients */
	_beginthread(echo_client, 0, (void *)0);
	_beginthread(echo_client, 0, (void *)1);
#endif /* WIN32 */

	fprintf(stdout,"\n");
	fprintf(stdout,"\n");
	fclose(fp);

	return totalnumclients;
}


/*
 * GetReady() 
 *		When each client thread or process starts up it makes a connection
 *		back to the web master.  
 */
void 
GetReady(fd_set *fdset, int totalnumclients, int sock)
{
	int		cnt, len;
	char	buffer[NCCARGS];

	D_PRINTF( "Beginning accept loop\n" );
	for (cnt = 0; cnt < totalnumclients; cnt++)
	{
		fd_set readfds;
		struct timeval timeout;
		int rv;

		timeout.tv_sec = MAX_ACCEPT_SECS;
		timeout.tv_usec = 0;
		FD_ZERO(&readfds);
		FD_SET(sock, &readfds);

		/* Time out if any of the clients are unable to connect to us. */
		if (!(rv = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout)))  
		{
			fprintf(stdout, 
				"Listen timeout after %d seconds (%d clients so far)\n",
						MAX_ACCEPT_SECS, cnt);
			D_PRINTF("select() timed out after %d seconds\n", MAX_ACCEPT_SECS);
			errexit("Webmaster terminating\n");
		}

		CLEAR_SOCK_ERR;
		if (BADSOCKET(socknum[cnt] = accept(sock, NULL, 0)))
		{
			abort_clients();
			errexit("accept() error: %s", neterrstr());
		} 

		FD_SET(socknum[cnt], fdset); 
		D_PRINTF( "GetReady(): cnt=%d, socket=%d\n", cnt, socknum[cnt] );
	}

	/*
	 * WAIT FOR A READY.
	 */
	fprintf(stdout,"Waiting for READY from %d clients\n",totalnumclients);
	fflush(stdout);

	for (cnt = 0; cnt < totalnumclients; cnt++)
	{
		len = NETREAD(socknum[cnt], buffer, READYSTRLEN);
		if (len != READYSTRLEN)
		{
			abort_clients();
			errexit("Error reading from client #%d\n", cnt);
		}
		if (memcmp(buffer, READYSTR, READYSTRLEN))
		{
			abort_clients();
			errexit("Received bad READY string: len %d, value %s\n",
					len,buffer);
		}
		D_PRINTF("Got ready from client #%d\n", cnt);
	}
	fprintf(stdout,"All READYs received\n");
	fflush(stdout);  
}

/*
 * Start all the clients by sending them a GO message.
 * totalnumclients is the total number of clients
 * socknum is an array with the socket descriptors for all the client
 * connections.  We know they are all valid descriptors because we wouldn't
 * have gotten this far if they weren't.
 */
void 
SendGo(int totalnumclients, int *socknum)
{
	int cnt;

	fprintf(stdout, "Sending GO to all clients\n");
	for(cnt = 0; cnt < totalnumclients; cnt++)
	{
		if (NETWRITE(socknum[cnt], GOSTR, GOSTRLEN) != GOSTRLEN)
		{
			abort_clients();
			errexit("Error sending GO to client %d: %s\n", cnt, 
					neterrstr());
		}
	}
}


/*
 * This function gathers statistics from all the clients
 */
void 
GetResults(fd_set *fdset, page_stats_t **page_stats, time_t *endtime, 
			char *timestr, int totalnumclients, stats_t statarray[])
{
	fd_set	leftfdset;
	char	*stats_as_text;
	char	*page_stats_as_text;
	int		returnval;
	int		cnt,i;

  
	/* DOESN'T ACTUALLY PRINT UNTIL THE FIRST CLIENT REPORTS */
	fprintf(stdout,"Reading results ");
  
	/* 
	 * COPY THE FILE DESCRIPTORS TO A TMP LIST,
	 * ALLOCATE MEMORY FOR STATS, PAGESTATS IN TEXT FORM
	 */
	leftfdset = *fdset;
	stats_as_text = (char *)mymalloc(SIZEOF_STATSTEXT+1);
	page_stats_as_text = (char *)mymalloc(SIZEOF_PAGESTATSTEXT+1);


	/* 
	 * LOOP UNTIL ALL CLIENTS HAVE REPORTED
	 */
	for(cnt = 0; cnt < totalnumclients; cnt++)
	{
		/*
		 * Send "GO" to each socket and receive its data.
		 */
		if(!BADSOCKET(socknum[cnt]) && (FD_ISSET(socknum[cnt],&leftfdset)))
		{
			int len;

			D_PRINTF( "Sending GO to receive data from client %d\n", cnt );
			if (NETWRITE(socknum[cnt], GOSTR, GOSTRLEN) != GOSTRLEN)
			{
				abort_clients();
				errexit("Error sending GO to client %d: %s\n", cnt, 
						neterrstr());
			}

			len = SIZEOF_STATSTEXTBASE + number_of_pages*SIZEOF_DOUBLETEXT;
			returnval = recvdata(socknum[cnt], stats_as_text, len);
			if (returnval < 0)
			{
				D_PRINTF( "Error reading timing stats: %s\n", neterrstr() );
				fprintf(stderr, 
					"Error reading timing stats: %s\nSocket number %d\n",
								neterrstr(),socknum[cnt]);
				fprintf(stderr, "recvdata() returned %d\n", returnval);
				abort_clients();
				errexit(""); 
			}

			/* convert text to stats */
			stats_as_text[len] = 0;	/* add an end marker */
			statarray[cnt] = *text_to_stats(stats_as_text);

			fputc('.', stdout); /* PROGRESS MARKER */
			fflush(stdout);

			if (randomize)
			{
				for (i = 0; i < number_of_pages; i++) 
				{
					D_PRINTF( "On page_stats[%d][%d]\n", cnt, i );
					returnval = recvdata(socknum[cnt], page_stats_as_text,
										SIZEOF_PAGESTATSTEXT);
					if (returnval < 0)
					{
						D_PRINTF( "Error reading page_stats[%d][%d]: %s\n",
									cnt, i, neterrstr() );
						fprintf(stderr, 
								"Error reading page_stats[%d][%d]: %s\n",
										cnt, i, neterrstr()); 
						fprintf(stderr, "recvdata() returned %d\n", returnval);
						abort_clients();
						errexit("");
					}
					page_stats_as_text[returnval] = 0; /* add end marker */
					page_stats[cnt][i] = 
								*text_to_page_stats(page_stats_as_text);
				} /* end for */   
			}

			FD_CLR(socknum[cnt],&leftfdset);
			NETCLOSE(socknum[cnt]);
			D_PRINTF("Finished with socket %d\n", socknum[cnt]);
			socknum[cnt] = BADSOCKET_VALUE;
		} /* end if socknum */
	} /* end for cnt */

	/*
 	 * DONE READING RESULTS FROM CLIENTS
	 */
	*endtime = time(NULL);
	timestr = asctime(localtime(endtime));
	fprintf(stdout,"\nAll clients ended at %s\n",timestr);
	fflush(stdout);

	/* FREE MEMORY ALLOCATED FOR CLIENT STATS, PAGESTATS AS TEXT */
	free(stats_as_text);
	free(page_stats_as_text);
}

/*
 * Prints out all the results
 */
void 
PrintResults(page_stats_t **page_stats, time_t endtime, char *timestr, 
				int totalnumclients, stats_t statarray[],
				page_stats_t *page_stats_total)
{
	stats_t	masterstat;
	int		cnt, i, j;
	double	thruput;
	struct timeval	dtime;

    /*
     * Print everything out.  
	 */
	stats_init(&masterstat);
	masterstat.rs.totalconnects = 0;
	for(cnt = 0; cnt < totalnumclients; cnt++)
	{

		if(statarray[cnt].rs.totalconnects == 0)
		{
			fprintf(stdout,
			"No connects from client %d. Is the Web server running?\n", cnt);
		}
		else if(statarray[cnt].rs.totalconnects > 0)
		{
			if(dumpall)
			{
				fprintf(stdout,"----------------------------------\n");
				fprintf(stdout,
						"Total number of pages retrieved from server: %u\n", 
						statarray[cnt].totalpages);

				rqstat_fprint(stdout, &(statarray[cnt].rs));

				thruput = thruputpersec((double)(statarray[cnt].rs.totalbytes),
										&(statarray[cnt].rs.totalresponsetime));

				fprintf(stdout, 
						"Thruput average per connection: %.0lf bytes/sec\n",
						thruput);
			}
		
			D_PRINTF( "Summing stats for %d, with %ld total connections\n",
						cnt, statarray[cnt].rs.totalconnects );
			rqstat_sum(&masterstat.rs, &(statarray[cnt].rs));
		}
		else
			masterstat.rs.totalerrs += statarray[cnt].rs.totalerrs;
		fflush(stdout);
	}

	if(masterstat.rs.totalconnects == 0)
	{
		fprintf(stdout, "No connects at all. Is the Web server running?\n");
		return;
	}

	for (i=0; i < totalnumclients; i++)
	{
		for (j=0; j < number_of_pages; j++)
		{
			D_PRINTF( "Summing page stats for %d, page %d, with %d connects\n",
						i, j, statarray[i].page_numbers[j] );

			if (statarray[i].page_numbers[j] != 0)
			{
				rqst_stats_t *pst_rs;
				rqst_stats_t *ps_rs;

				pst_rs = &(page_stats_total[j].rs);
				ps_rs  = &(page_stats[i][j].rs);

				rqstat_sum(pst_rs, ps_rs);

				page_stats_total[j].totalpages += page_stats[i][j].totalpages;
				masterstat.totalpages += page_stats[i][j].totalpages;

				/* yes, this is assignment, not sum */
				page_stats_total[j].page_size = page_stats[i][j].page_size;
				page_stats_total[j].page_valid = 1;
			}
		}
	} 

	/* 
	 * If the -v (verbose) option was used then print even more.  Note that 
	 * using the -v option can cause a LOT of data to be output and may fill 
	 * up disks if you're saving all stdout and stderr to a log file.  It may 
	 * also greatly increase the run time while all the data is dumped to
	 * screen.
     */
	if (verbose) 
	{
		for (i = 0; i < number_of_pages; i++)
		{
			if (page_stats_total[i].page_valid == 1)
			{
				page_stats_t *pst;

				pst = &(page_stats_total[i]);

				printf ("===============================================================================\n");
				printf ("Page # %lu\n\n", i);
				printf ("Total number of times page was hit %u\n",
						pst->totalpages); 

				rqstat_print(&(pst->rs));

				printf ("Page size %u \n", pst->page_size);
				printf ("===============================================================================\n\n"); 
			}
		}
	}

	fprintf(stdout,"===============================================================================\n");

	/*
	 * Validate run.
	 */
	masterstat.total_num_of_files = statarray[0].total_num_of_files;
	for (i=1; i < totalnumclients; i++)
	{
		if ((statarray[i].rs.totalconnects > 0) &&
			(statarray[i].total_num_of_files != masterstat.total_num_of_files))
		{
			fprintf(stdout,"**********************************************************************\n");
			fprintf(stdout,"**** ERROR: number of files in each test configuration is not the same\n");
			fprintf(stdout,"**** ERROR: Check configuration file %s on each client\n", configfile);
			fprintf(stdout,"**********************************************************************\n");
			break;
		}
	}

	/*
	 * Print summary statistics
	 */
	fprintf(stdout, "WEBSTONE %s results:\n", VERSION);

	fprintf(stdout,
			"Total number of clients:         %5d\n", totalnumclients);
	if (testtime) 
	{ 
		fprintf(stdout,
			"Test time:                       %5d minutes\n", testtime);

		fprintf(stdout,
			"Server connection rate:        %10.2lf connections/sec\n",
				(double)(masterstat.rs.totalconnects)/(60*testtime));

		fprintf(stdout,
			"Server error rate:             %10.2lf err/sec\n",
				(double)(masterstat.rs.totalerrs)/(60*testtime));

		fprintf(stdout,
			"Server thruput:                %10.2lf Mbit/sec\n",
				(double)(8*masterstat.rs.totalbytes)/(60*testtime*1000000));

		fprintf(stdout,
			"Little's Load Factor:          %10.2lf \n", 
				(double)(masterstat.rs.totalresponsetime.tv_sec)/
					(1000*60*testtime));
	}

	avgtime(&masterstat.rs.totalresponsetime, masterstat.rs.totalconnects, 
			&dtime);

	fprintf(stdout,
			"Average response time:         %11.3lf sec\n",
			timevaldouble(&dtime) / 1000);

	if (masterstat.rs.totalconnects)
	{
		fprintf(stdout,
			"Error Level:                   %10.2lf %%\n",
		(double)(100 * masterstat.rs.totalerrs)/(masterstat.rs.totalconnects));
	}
	else
	{
		fprintf(stdout, 
			"Error Level:                   UNDEFINED\n");
		fprintf(stderr,
			"No connections during run; is the Web server running?\n");
	}

	/* so much for the key metrics */

	thruput = 8000 * thruputpersec((double)(masterstat.rs.totalbytes),
								&(masterstat.rs.totalresponsetime));

	fprintf(stdout,
			"Average client thruput:        %10.2lf Mbit/sec\n",
				thruput/1000000);

	fprintf(stdout,
			"Sum of client response times:  %10.2lf sec\n", 
			timevaldouble(&(masterstat.rs.totalresponsetime)) / 1000);

	fprintf(stdout,
			"Total number of pages read:      %5ld\n\n", 
			masterstat.totalpages);

	/* Remaining stats are the same as usual */
	rqstat_fprint(stdout, &(masterstat.rs));
	fflush(stdout);
}


#ifdef WIN32
/* close socket library */
void
sock_cleanup(void) 
{
	WSACleanup();
}
#endif /* WIN32 */

void
main(const int argc, char *argv[])
{

	/* 
	 * sync_sock is the socket that clients will use to let us know that
	 * they've started up and are ready to go. 
	 */
    int		sync_sock;

    int		i, j;
    char	buffer[NCCARGS];
    char 	commandline[NCCARGS];
    char 	*timestr;
    time_t 	starttime;
    time_t	endtime;
    fd_set	fdset;

    /* make the big arrays static to avoid stack overflow */
    static char		clienthostname[MAXCLIENTS][MAXHOSTNAMELEN];
    static stats_t	statarray[MAXCLIENTS];
    page_stats_t **page_stats;
    page_stats_t *page_stats_total;
    struct sockaddr_in 	serveraddr;

#ifdef WIN32
    WSADATA	WSAData;
    COORD	dwSize;

    if ((WSAStartup(MAKEWORD(1,1), &WSAData)) != 0) 
		errexit("Error in WSAStartup()\n");
    atexit(sock_cleanup);

    /* increase size of output window */
    dwSize.X = 80;
    dwSize.Y = 500;
    SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), dwSize);
#endif /* WIN32 */


    /* Initalization of variables. */
    debugfile = stderr;
    memset(buffer, 0, NCCARGS);
    memset(webserver, 0, MAXHOSTNAMELEN);
    memset(configfile, 0, MAXPATHLEN);
    FD_ZERO(&zerofdset);
    FD_ZERO(&fdset);

    for(i = 0; i < MAXCLIENTS; i++)
    {
        socknum[i] = BADSOCKET_VALUE;
        statarray[i].rs.totalconnects = 0;
    }  
    
    signal(SIGINT, sig_int);

    ParseCmdLine(argc, argv);
    sync_sock = SetupSyncSocket(&serveraddr);
    MakeCmdLine(commandline);

#ifdef STONE_SSL
   /* call to Eric Robert's lib */
   /* XXX: multithreaded performance? */
    Randomize();
#endif
    totalnumclients = RexecClients(commandline, clienthostname, &serveraddr);
	if(totalnumclients == 0)
	{
		errexit("No work to do!\n");
	}

    /* Initalization of variables. */
    page_stats = (page_stats_t **)
					mymalloc(totalnumclients*sizeof(page_stats_t *));
    for (i=0; i < totalnumclients; i++)
	{
		page_stats[i] = (page_stats_t *)
						mymalloc(number_of_pages*sizeof(page_stats_t));
	}
	page_stats_total = (page_stats_t *)
						mymalloc(number_of_pages*sizeof(page_stats_t));

	for (i=0; i < totalnumclients; i++) 
		stats_init(&(statarray[i]));

	for (i=0; i < totalnumclients; i++) 
	{
		for (j=0; j < number_of_pages; j++) 
			page_stats_init(&(page_stats[i][j]));
	}
	for (i=0; i < number_of_pages; i++) 
		page_stats_init(&(page_stats_total[i]));

	GetReady(&fdset, totalnumclients, sync_sock );
	NETCLOSE(sync_sock);       

	/*
	 * Send a "GO" message to all webclients so they will start their tests.
	 */
	SendGo(totalnumclients, socknum);

	/*
	 * Wait for all of the webclients to complete.  We have a socket 
	 * connection to each webclient process or thread so we should get 
 	 * a reply from each one.  The reply will be the timing information.
	 */
	starttime = time(NULL);
	timestr = asctime(localtime(&starttime));
	fprintf(stdout, "All clients started at %s\n", timestr);
	fprintf(stdout, "Waiting for clients completion...\n");
	fflush(stdout);

	/* 
	 * If this is a timed test, we might as well snooze.  The clients have
	 * their own timers and will contact us when they are finished with 
	 * their tests and ready to report their data. 
	 */
	if (testtime) 
		sleep(testtime * 60);


	GetResults(&fdset, page_stats, &endtime, timestr, totalnumclients,
				statarray);

	PrintResults(page_stats, endtime, timestr, totalnumclients, statarray,
				page_stats_total);

	exit(0);
}

