/* webmaster.c,v 1.5 2000/09/22 19:30:37 brunsch Exp */
/**************************************************************************
 *									  *
 * 		 Copyright (C) 1995 Silicon Graphics, Inc.		  *
 *									  *
 *  These coded instructions, statements, and computer programs were	  *
 *  developed by SGI for public use.  If any changes are made to this code*
 *  please try to get the changes back to the author.  Feel free to make  *
 *  modifications and changes to the code and release it.		  *
 *									  *
 **************************************************************************/

/* FUZZ: disable check_for_math_include */
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifndef WIN32
#include <unistd.h>
#endif /* WIN32 */

#include <math.h>

#ifndef WIN32
#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
#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"

/* command line options/data */
int	    savefile = 0;
int	    debug = 0;
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	    redirect = 0;
int	    record_all_transactions = 0;
int	    uil_filelist_f = 0;
int	    verbose = 0;
char 	    webserver[MAXHOSTNAMELEN];
char	    configfile[MAXPATHLEN];
char	    uil_filelist[NCCARGS];

char 	    filelist[256][MAXPATHLEN];
fd_set	    zerofdset;

/* other key data */
long int    number_of_pages = 0;
int	    totalnumclients = 0;
int	    num_rexecs = 0;
SOCKET	    socknum[MAXCLIENTS];
SOCKET	    sockIO[MAXTOTALPROCS];
SOCKET	    sockErr[MAXTOTALPROCS];
THREAD FILE *debugfile = stderr;	    
struct hostent  *master_phe;   /* IP addresses for webmaster */
struct timeval sumedh_start, sumedh_end;

void HostEntCpy(struct hostent *dest, struct hostent *src);

static void
usage(const char *progname)
{

	fprintf(stderr, "Usage: %s [-a] [-d] -f config_file [-l numloops]\n",
		progname);
	fprintf(stderr, "          [-p port_num] [-r] [-s] [-t run_time] \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, "-r redirect stdout of clients to /tmp/webstone.xxx\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, "-w webserver\tname of webserver host to contact\n");
	fprintf(stderr, "-u URL file\tfilelist of URLs\n");
	fprintf(stderr, "-v verbose mode\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");
	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 */
  int    type;             /* socket type */

  D_PRINTF( "Beginning passivesock with errno %d\n",errno );

  D_PRINTF( "Zeroing address structure\n" ); 
  memset((char *)&sin, 0, sizeof(sin));

  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = INADDR_ANY;
 
  /* NOT USED: Map service name to portnumber */
  D_PRINTF( "Mapping portnum errno %d\n",errno );
  sin.sin_port = htons(portnum);
 
  /* Map protocol name to number */
  D_PRINTF( "Mapping protocol name errno %d\n",errno );
  if ((ppe = getprotobyname(protocol)) == 0)
    {
    errexit("Can't get \"%s\" protocol entry\n", protocol);
    }
  errno = 0;

  /* use protocol to choose socket type */
  D_PRINTF( "Changing socket type, errno %d\n",errno );
  if (strcmp(protocol, "udp") == 0)
    {
      type = SOCK_DGRAM;
	D_PRINTF( "Choosing SOCK_DGRAM\n" );
    }
  else
    {
      type = SOCK_STREAM;
	D_PRINTF( "Choosing SOCK_STREAM, errno %d\n",errno );
    }

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

  /* Bind the socket */
  if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
  {
    errexit("Can't bind to port %d: %s\n", portnum, neterrstr());
  }
  D_PRINTF( "Bind succeeded\n" );

  /* If it's a stream, listen for connections */
  /* NOTE: ON NT, the listen() backlog parm is silently limited to 5 conns */
  if ((type == SOCK_STREAM) && BADSOCKET(listen(s, qlen)))
  {
      errexit("Can't listen on port %s: %s\n", portnum, neterrstr());
  }
  D_PRINTF( "Listen succeeded\n" );    

  /* all done, return socket descriptor */
  return(s);
}  


/* abort clients -- called by SIGINT handler */
static void abort_clients(void)
{
    /* Not supposed to have fprintf in a signal handler, but... */
    fprintf(stdout, "Webmaster received SIGINT.  Terminating.\n");
    /* exit will close all open connections */
    exit(2);
}

/* signal handler for SIGINT */
static void sig_int(int sig) {

    abort_clients();
}

#ifdef WIN32

/* echo stdout/stderr from clients */
void echo_client(void *stream)
{
    SOCKET *sockarr;
    FILE *outfile;
    int which_stream = (int) stream;
    char buf[BUFSIZ];
    int i, len, rv;
    fd_set readfds;

    /* This code which handles the timeout may need
       to be ifdef'ed for WIN32 */
    struct timeval timeout;

    timeout.tv_sec = (long)5;
    timeout.tv_usec = (long)0;

    if (which_stream) {
	sockarr = sockIO;
	outfile = stdout;
    } else {
	sockarr = sockErr;
	outfile = stderr;
    }

    D_PRINTF( "echo_client running\n" );
    signal( SIGINT, SIG_DFL); /* restore default behavior
				 for SIGINT */

    while (1) {
	FD_ZERO(&readfds);
	for (i = 0; i < num_rexecs; i++)
	    if (sockarr[i] != BADSOCKET_VALUE)
		FD_SET(sockarr[i], &readfds);
	rv = select(num_rexecs, &readfds, NULL, NULL, &timeout);
	if ( rv == 0)
	  continue;
	if (rv < 0 && WSAGetLastError() == WSANOTINITIALISED)
	    return;
	if (rv < 0)
	    errexit("Error in 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 && WSAGetLastError() == WSANOTINITIALISED)
			return;
		    if (len < 0)
			fprintf(stderr, "Error in echo_client() after NETREAD(): %s\n", neterrstr());
		    continue;
		}
  
		/* copy to stdout or stderr */
		fwrite(buf, sizeof(char), len, outfile);
	    }
	}
    }
    D_PRINTF( "Exiting echo_client\n" );
}

#else
static int
echo_client(char *hostname, const int fd)
{
  /* 
   * WRITE TEXT FROM FILE DESCRIPTOR INTO STDOUT 
   */
  char buf[BUFSIZ];
  int  cc;
  D_PRINTF( "echo_client running\n" );

  while (getppid() != 1)
    {
      cc = NETREAD(fd, buf, sizeof(buf));
      if (cc > 0)
      {
	  write(STDOUT_FILENO, buf, cc);
      }
  }
  D_PRINTF( "Exiting echo_client\n" );
  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;

    if (client_hostname[0] >= '0' && client_hostname[0] <= '9') {
	/* 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 webserver 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))
	    goto gotit;
    }
    i = 0;  /* give up */

gotit:
    memcpy((char *)addr, master_phe->h_addr_list[i], sizeof(addr));	 /* Internet specific */
    sprintf(buf, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]);
    return buf;
}

/*
  Command line parsing
*/

void ParseCmdLine(int argc, char **argv )
{
char		getoptch;
int		currarg;
extern char	*optarg;
extern int	optind;

    /* 
     * PARSE THE COMMAND LINE OPTIONS
     */
    while((getoptch = getopt(argc,argv,"P:f:t:l:p:u:R:w:n:M:adrsvWX")) != (const char)EOF)
      {
        switch(getoptch)
	  {
	  case 'M':
	    strcpy(network_mask_str, optarg);
	    break;
	  case 'P':
	    haveproxyserver = 1;
	    strcpy(proxyserver, optarg); 
	    break;
	  case 'R':	
	    record_all_transactions = 1;
	    break;
	  case 'X':
	    norexec = 1;
	    break;
	  case 'W':
	    servaddrin_config = 1;
	    break;
	  case 'a':
	    dumpall = 1;
	    break;
	  case 'd':
	    debug = 1;
	    break;
	  case 'f':
	    strcpy(configfile, optarg);
	    break;
	  case 'l':
	    numloops = atoi(optarg);
	    break;
	  case 'p':
	    portnum = atoi(optarg);
	    break;
	  case 'r':
	    redirect = 1;
	    break;
	  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;
	  default:
	    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(configfile) == 0)
    {
        /*
         * THE MASTER MUST HAVE A CONFIGURATION FILE TO READ.
         */
        fprintf(stderr,"No Configuration file specified\n");
        usage(argv[0]);
    }
    /* IF WE DO NOT HAVE A FILE LIST THEN THERE ARE UIL'S AT THE END OF THE
     * COMMAND LINE SO GRAB THEM.
     */
    if (uil_filelist_f == 0)
    {
	currarg = optind;
	number_of_pages = 0;
	while(currarg != argc)
	{
	   /*
	    * GET THE UILS TO RETRIEVE.
	    */

	    sscanf(argv[currarg],"%s",filelist[number_of_pages]);
	    number_of_pages++;
	    currarg++;
	}
    }
    else
    {
	/* have filelist; take a stab at the number of valid URLs */
	D_PRINTF( "About to parse filelist %s\n", uil_filelist );
	number_of_pages = count_file_list(uil_filelist);
    }
    if (number_of_pages == 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( serveraddr )
struct sockaddr_in *serveraddr;
{
  int sock,len;

    /*
     * SET UP THE SOCKET WE ARE GOING TO USE TO SYNCHRONIZE WITH THE CLIENTS.
     */
    D_PRINTF( "About to call sock %d %d\n", portnum, MAXCLIENTS );

    sock = passivesock(0, "tcp", MAXCLIENTS);

    if (BADSOCKET(sock))
    {
        errexit("Couldn't open socket %d: %s\n", sock, neterrstr());
    }
	D_PRINTF( "The passivesock call succeeded\n" );

    D_PRINTF( "calling getsockname\n" );

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

/*
  Function which generates a commandline for the webclients
*/
void MakeCmdLine( commandline)
char *commandline;
{
    char tmpcommandline[NCCARGS];
    char hostname[MAXHOSTNAMELEN];
    char *webclient_path, *temp;
    int  cnt;
    struct hostent  *master_phe_tmp; /* temp. variable added by - Rajesh Shah*/

    /*
     * 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" );

    if(gethostname(hostname,MAXHOSTNAMELEN) != 0)
    {
	errexit("Could not retrieve local host name");
    } else {
	/* convert hostname to address (to avoid DNS problems for webclients) */
	/* The following lines are add to copy the system 
	   buffer (output of gethostbyname()) into user area.
           This is because, there are very good chances that later
	   on system buffer might be overwritten by some calls and
	   still if your pointer is pointing to same addr. nothing
	   but only trouble and trouble! Infact this is what 
           happening when I tried to run webstone benchmark for more
           then one clients. It used to over write the webmaster name
 	   with the first client name and so remaining on client(s)
	   the webclient process(es) were invoked with wrong webmaster
	   name! This behaviour is observed Solaris 2.4 this bug 
	   can be hit in any OS.    - Rajesh Shah 5/18/96 */
	     
	/* master_phe = gethostbyname(hostname); */
	master_phe_tmp = gethostbyname(hostname); 
	master_phe = (struct hostent *)malloc(sizeof(struct hostent));
	HostEntCpy(master_phe, master_phe_tmp);
    }

    /* set up executable pathname */
#ifndef WIN32
    temp = getenv("TMPDIR");

    if ( temp && *temp ) {
      webclient_path = (char *)mymalloc( strlen(temp) + strlen("/webclient")
				      + 1);
      strcpy(webclient_path, temp);
      strcat(webclient_path, "/webclient");
      
    } else
#else
    temp = temp;
#endif /* WIN32 */
      webclient_path = PROGPATH;


    D_PRINTF( "Path to webclient is: %s\n", webclient_path );

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

    if(haveproxyserver)
    {
        sprintf(tmpcommandline, " -P %s", proxyserver);
        strcat(commandline, tmpcommandline);
    }
    if (debug)
    {
       strcat(commandline," -d");
    }
    if (numloops != 0)
    {
        sprintf(tmpcommandline," -l %d", numloops);
        strcat(commandline,tmpcommandline);
    }
    if (portnum)
    {
        sprintf(tmpcommandline," -p %d", portnum);
        strcat(commandline,tmpcommandline);
    }
    if (redirect)
    {
        strcat(commandline," -r");
    }
    if (savefile)
    {
        strcat(commandline," -s");
    }
    if (uil_filelist_f)
    {
        strcat(commandline," -u ");
	strcat(commandline,uil_filelist);
    }
    if (record_all_transactions)
    {
	strcat(commandline," -R");
    }
    if (testtime != 0)
    {
        sprintf(tmpcommandline," -t %d", testtime);
        strcat(commandline,tmpcommandline);
    }

    /*
     * SET UP A SPACE FOR THE NUMBER OF CLIENTS ON THE commandline.
     */
    sprintf(tmpcommandline,"%s -n %%d -w %%s -c %%s:%%d", commandline);
    strcpy(commandline,tmpcommandline);
    
    if (uil_filelist_f == 0)
    {
	cnt = 0;
	while(cnt < number_of_pages)
	{
	   /*
	    * PUT THE FILES AT THE END OF THE LIST.
	    */
	    strcat(commandline," ");
	    strcat(commandline,filelist[cnt]);
	    cnt++;
	}
    }
    puts(commandline);
}

/*
  rexec to the client hosts and start the webclients
*/
int RexecClients( commandline, clienthostname, serveraddr)
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;

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

    /*
     * OPEN UP THE CONFIG FILE. FOR EACH LINE IN THE CONFIG FILE, CHECK
     * ITS VALIDITY AND THEN rexec A COMMAND ON THE CLIENT.
     */

    if ((fp = fopen(configfile,"r")) == NULL)
    {
        errexit("Could not open config file %s\n", configfile);
    }
   
    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;

    D_PRINTF( "rexec loop\n" );
    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);
	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)
	    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);
        sprintf(tmpcommandline, commandline, numclients, webserver, primename,
	    ntohs(serveraddr->sin_port));

        fprintf(stderr, "tmpcommandline: %s\n", tmpcommandline);

	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]);
	    tmpfd = rexec(&tmphostname, inetport->s_port, login, password,
						tmpcommandline, &sockErr[cnt]);
	    if((sockIO[cnt] = tmpfd) < 0)
	    {
		errexit("Could not rexec: rexec to client %s, cmdline %s failed\n",
			clienthostname[cnt],tmpcommandline);
	    }
	}
	    

	returnval = NETREAD(tmpfd, buffer, OKSTRLEN);
	D_PRINTF( "read returns %d, %s\n", returnval, buffer );

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


	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);
    }

#ifndef WIN32
      /* NOW WE NEED TO HANDLE THE OUTPUT FROM THE REXEC.
       * TO DO THIS, WE FORK, THEN HAVE ONE PROCESS READ FROM TMPFD.
       * THE OTHER PROCESS CONTINUES WITH THE PROGRAM
       */
      D_PRINTF( "Forking webclient stderr/stdout processes\n" );
      switch (fork()) 
	{ 
	case -1:   /* ERROR */
	  errexit("fork: %s\n", strerror(errno));
	case 0:    /* CHILD */
	  exit(echo_client(clienthostname[cnt], tmpfd)); 
	default:   /* PARENT */
	  break;
	} 
#else
    /* 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;
}

void GetReady( fdset, totalnumclients, sock )
fd_set *fdset;
int totalnumclients;
int sock;
{
  int cnt,len;
  fd_set tmpfdset, leftfdset;
  char	buffer[NCCARGS];

    /*
     * NOW WE NEED TO ACCEPT ALL THE CONNECTIONS FROM THE CLIENTS,
     * ACCEPT ALL THE READY MESSAGES
     */

    D_PRINTF( "Beginning accept loop\n" );
    for (cnt = 0; cnt < totalnumclients; cnt++)
    {
	D_PRINTF( "Client %d:\t", 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);

	    /* if we're hung, quit */
	    D_PRINTF("Before select() on listen() socket\n");
	    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");
	    }
	}

        if(BADSOCKET(socknum[cnt] = accept(sock, NULL, 0)))
        {
            /*
             * ERROR accepting FROM THE CLIENTS. WE NEED TO ISSUE AN
             * ABORT TO ALL.
             */
            abort_clients();
            errexit("Error accepting from one of the clients: %s", neterrstr());
        } else
        {
            /*
             * SET THE FD IN THE MASK
             */
            FD_SET(socknum[cnt],fdset); 
        }
	D_PRINTF( "on socket %d\n",socknum[cnt] );
    }
    D_PRINTF( "\n" );

    /*
     * WAIT FOR A READY.
     */
    sleep(1);
    fprintf(stdout,"Waiting for READY from %d clients\n",totalnumclients);
    fflush(stdout);
    leftfdset = *fdset;
#ifndef WIN32
    while(memcmp(&leftfdset,&zerofdset,sizeof(fd_set)))
    {
        tmpfdset = leftfdset;

        if(select(FD_SETSIZE,&tmpfdset,NULL,NULL,NULL) < 0)
        {
            /*
             * ERROR SELECTING. ABORT ALL.
             */
            abort_clients();
            errexit("Error accepting from one of the clients: %s\n",
			neterrstr());
            break;
        }
#else
    /* I don't see why a select is needed at all--all clients must respond
     * and there is no synchronization/timing issue.
     */
    tmpfdset = leftfdset;
    {
#endif /* WIN32 */

        for (cnt = 0; cnt < totalnumclients; cnt++)
        {
            /*
             * SEE WHICH SOCKETS HAVE A INPUT ON THEM PENDING
             * AND RECEIVE IT.
             */
            if(!BADSOCKET(socknum[cnt]) && (FD_ISSET(socknum[cnt],&tmpfdset)))
            {
                /*
                 * GET THE READY FROM THIS GUY. 
                 * DON'T FORGET TO CLEAR HIS BIT IN THE tmpfdset
                 */
		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();
                     fprintf(stdout,"Received bad READY string: len %d, value %s\n",
				    len,buffer);
                }
                FD_CLR(socknum[cnt],&leftfdset);
            }
        }
    }
    sleep(1);
    fprintf(stdout,"All READYs received\n");
    fflush(stdout);  
}

/*
  Start all the clients by sending them a GO signal
  totalnumclients is the total number of clients
  socknum is an int array with the filedescriptors for all the
     client connections
*/
void SendGo( totalnumclients, socknum)
int totalnumclients;
int *socknum;
{
  int cnt;
  fprintf(stdout,"Sending GO to all clients\n");
  for(cnt = 0; cnt < totalnumclients; cnt++)
    {
      if(socknum[cnt] > 0)
        {
	  /*
	   * SEND A GO 
	   */
	  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(fdset, page_stats, endtime, timestr, totalnumclients,
		statarray)
fd_set *fdset;
page_stats_t **page_stats;
time_t *endtime;
char *timestr;
int totalnumclients;
stats_t statarray[MAXCLIENTS];
{
  fd_set leftfdset,tmpfdset;
  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);
  
    /* 
    * COPY THE FILE DESCRIPTORS TO A TMP LIST,
    * PLUS A LIST OF REMAINING FDs
    */
    leftfdset = *fdset;
    /* 
     * LOOP UNTIL ALL CLIENTS HAVE REPORTED
     * AND tmpfdset IS EMPTY
     */
#ifndef WIN32    
    while(memcmp(&leftfdset,&zerofdset,sizeof(fd_set)))
    {
	tmpfdset = leftfdset;
	sleep(1);
	returnval = select(FD_SETSIZE,&tmpfdset,NULL,NULL,NULL);
	D_PRINTF( "Call to select returned %d, errno %d\n",
	   		 returnval, errno );
        if(returnval < 0)
        {
            /*
             * ERROR SELECTING. ABORT ALL.
             */
	  D_PRINTF( "select() error %s\n", neterrstr() );
            abort_clients();
            errexit("Error selecting from one of the clients\n");
        }
#else
    /* I don't see why a select is needed at all */
    tmpfdset = leftfdset;
    {
#endif /* WIN32 */
	for(cnt = 0; cnt < totalnumclients; cnt++)
	{
	    /*
	     * SEE WHICH SOCKETS HAVE A INPUT ON THEM PENDING AND
	     * RECEIVE IT.  
	     */

	    /* IS THIS A VALID SOCKET? IS IT READY TO READ? */
	    if(!BADSOCKET(socknum[cnt]) && (FD_ISSET(socknum[cnt],&tmpfdset)))
	    {
		int len;

		/*
		 * GET THE TIMING DATA FROM THIS GUY
		 * THEN REMOVE HIM FROM THE tmpfdset
		 */
		  /* 
		   * READ TIME STATS
		   * DOES READ() RETURN THE CORRECT LENGTH? 
		   */
		  D_PRINTF( "About to read timestats, count %d, errno %d\n",
			cnt, errno );
		  len = SIZEOF_STATSTEXTBASE + number_of_pages*SIZEOF_DOUBLETEXT;
		  returnval = recvdata(socknum[cnt], stats_as_text,
				    len);
		  D_PRINTF( "Read time stats %d\n", returnval );
		  if (returnval != len) /* <= 0) */
		    {
		      D_PRINTF( "Error reading timing stats: %s\n",
			    neterrstr() );
		      fprintf(stderr, "Error reading timing stats: %s\nSocket number %d\n",
			      neterrstr(),socknum[cnt]);
		      abort_clients();
		      errexit(""); 
		    } /* end if */
		  
		  /* convert text to stats */
		  stats_as_text[returnval] = 0;	/* add an end marker */
		  statarray[cnt] = *text_to_stats(stats_as_text);

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

		if(uil_filelist_f) /* READ PAGE STATS */
		{
		  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);
		      D_PRINTF( "Read page stats %d\n", returnval );
		      
		      if (returnval != SIZEOF_PAGESTATSTEXT) /* <= 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()); 
			  abort_clients();
			  errexit("");
			}
		      D_PRINTF( "Page stats: read %d bytes\n", 
			  returnval );
		      
		      page_stats_as_text[returnval] = 0;    /* add an end marker */
		      D_PRINTF("strlen(page_stats_as_text) = %d\n", 
			    strlen(page_stats_as_text));
		      page_stats[cnt][i] = 
			*text_to_page_stats(page_stats_as_text);
		      
		    } /* end for */   
		} /* end if filelist */

		FD_CLR(socknum[cnt],&leftfdset);
		NETCLOSE(socknum[cnt]);
		socknum[cnt] = BADSOCKET_VALUE;
	    } /* end if socknum */
	} /* end for cnt */
    } /* end while memcmp fd */

    /*
     * 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, endtime, timestr, totalnumclients, statarray,
		  page_stats_total)
page_stats_t **page_stats;
time_t endtime;
char *timestr;
int totalnumclients;
stats_t statarray[MAXCLIENTS];
page_stats_t *page_stats_total;
{
  stats_t	masterstat;
  int cnt,i,j;
  double	thruput;
  struct timeval	dtime;

    /*
     * PRINT EVERYTHING OUT
     */
    stats_init(&masterstat);
    for(cnt = 0; cnt < totalnumclients; cnt++)
    {
        if((statarray[cnt].rs.totalconnects > 0) && (dumpall))
        {
            fprintf(stdout,"----------------------------------\n");
            /* fprintf(stdout,"Test for host: %s\n",statarray[cnt].hostname); */
            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: %.0f bytes/sec\n",
			thruput);
        }
        if(statarray[cnt].rs.totalconnects > 0)
        {
	    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;
	}
    }

    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;
	    }
	}
    } 
   
    /* print page statistics */
    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 # %d\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 2.0 results:\n");

    fprintf(stdout, "Total number of clients: \t%d\n", totalnumclients);
    testtime = sumedh_end.tv_sec - sumedh_start.tv_sec;
    fprintf(stdout,"Test time: \t\t\t%d seconds\n", testtime);
    
    fprintf(stdout, "Server connection rate: \t%3.2f connections/sec\n",
            (double)(masterstat.rs.totalconnects)/(testtime));
    
    fprintf(stdout, "Server error rate: \t\t%4.4f err/sec\n",
            (double)(masterstat.rs.totalerrs)/(testtime));
    
    fprintf(stdout, "Server thruput: \t\t%2.2f Mbit/sec\n",
            (double)(8*masterstat.rs.totalbytes)/(testtime*1024*1024));
    
    fprintf(stdout, "Little's Load Factor: \t\t%3.2f \n", 
            (double)(masterstat.rs.totalresponsetime.tv_sec)
            /(testtime));
    avgtime(&masterstat.rs.totalresponsetime,
		      masterstat.rs.totalconnects, &dtime);

    fprintf(stdout, "Average response time: \t\t%4.4f millisec\n",
	(double)1000*(dtime.tv_sec + (double)dtime.tv_usec / 1000000));

    fprintf(stdout, "Error Level:\t\t\t%4.4f %%\n",
	    (double)(100 * masterstat.rs.totalerrs)/(masterstat.rs.totalconnects));

    /* so much for the key metrics */

    thruput = 8 * thruputpersec((double)(masterstat.rs.totalbytes),
			&(masterstat.rs.totalresponsetime));
	
    fprintf(stdout, "Average client thruput: \t%4.4f Mbit/sec\n",
			thruput/(1024*1024));

    fprintf(stdout,"Sum of client response times:\t%u.%u sec\n", 
	    masterstat.rs.totalresponsetime.tv_sec,
	    masterstat.rs.totalresponsetime.tv_usec);

    fprintf(stdout,"Total number of pages read:\t%u\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[])
{

    int		sync_sock;
    int		i;
    int		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 = stdout;
    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);

    totalnumclients = RexecClients( commandline, clienthostname, &serveraddr);

    /* 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]));
    }
    
    for(i = 0; i < totalnumclients; i++)
    {
        socknum[i] = BADSOCKET_VALUE;
        statarray[i].rs.totalconnects = 0;
    }    

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

    /*
     * START ALL OF THE CLIENTS BY SENDING THEM A GO SIGNAL.
     */


    gettimeofday (&sumedh_start, NULL);
    SendGo( totalnumclients, socknum);

    /*
     * WAIT FOR ALL OF THE CLIENTS TO COMPLETE.  WE SHOULD GET A REPLY
     * FOR EACH SOCKET WE HAVE OPEN.  THE REPLY WILL BE THE TIMING
     * INFORMATION WE USE.
     */

    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 */
    if (testtime) {
      sleep(testtime * 60);
    }

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

    gettimeofday (&sumedh_end, NULL);
    PrintResults( page_stats, endtime, timestr, totalnumclients, statarray,
		 page_stats_total);

    /* free memory */
    for (i = 0; i < totalnumclients; i++)
      {
	free(page_stats[i]);
      }
    free(page_stats);
    free(page_stats_total);

    exit(0);
}

/* Added by Rajesh Shah 5/18/96 */
void
HostEntCpy(struct hostent *dest, struct hostent *src)
{
	
	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;
	dest->h_addr_list = src->h_addr_list;
}

