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

#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#ifndef HAVE_SOCKLEN_T
typedef int socklen_t;
#endif

typedef enum
{
  DEAD, INITIALIZED,
  CONNECTINPROGRESS, CONNECTED,
  REQUESTINPROGRESS, REQUESTCOMPLETE,
  RESPONSEINPROGRESS, REQUESTRESPONSEINPROGRESS, RESPONSECOMPLETE,
  SLEEP, SLEEPING, SLEEPINGCOMPLETE,
  CLOSE, RETIRED,
  NUM_STATES
} ConnectState_t;

static const char *state_names[] =
{
  "DEAD",
  "INITIALIZED",
  "CONNECTINPROGRESS",
  "CONNECTED",
  "REQUESTINPROGRESS",
  "REQUESTCOMPLETE",
  "RESPONSEINPROGRESS",
  "REQUESTRESPONSEINPROGRESS",
  "RESPONSECOMPLETE",
  "SLEEP",
  "SLEEPING",
  "SLEEPINGCOMPLETE",
  "CLOSE",
  "RETIRED",
  "***BOGOSITY WITHOUT END***" /* NUM_STATES isn't a real state */
};

/*
 * return codes from processing routines
 */

#define ERROR         1
#define BLOCKED       2
#define DONE          3
#define ERR_USAGE     4

/*
 * defaults
 */

#define DEFAULT_REQUESTS     500
#define DEFAULT_CONCURRENCY  1

typedef struct
{
  int connecting;
  int writing;             /* not used for -1 */
  int reading;             /* not used for -1 */
  int reading_and_writing; /* used for -1 */
} Stats_t;

typedef struct
{
  int fd;
  int rcvbuf_size;
  int sndbuf_size;
  ConnectState_t state;
  struct sockaddr_in server;
  char *request;
  char *uri;
  size_t request_size;
  size_t request_offset;
  fd_set *read_set;
  int *read_count;
  fd_set *write_set;
  int *write_count;
  int *sleep_count;
  size_t response_bytes;
  Stats_t *stats;
  time_t end_of_sleep;
  /* neglect to call shutdown() so server sits in keepalive timeout? */
  int hang_keepalive;
  int readable;
  int writeable;
} Connection_t;

#define MAX_FDS 1024

/*
 * Use FAKE_ERRNO when we simulate a problem.
 */
#define FAKE_ERRNO ENXIO

static Connection_t *conn_by_fd[MAX_FDS];

int connections_finished;
int connections_to_finish;
int connection_errors;
int connections_attempted;
int highest_fd;
int show_timestamps;
/* how many aborted connections per hundred? */
int aborted_connections = 0;
/* how many goofy ranges per hundred? */
int goofy_ranges = 0;
/* how many keepalive timeouts per hundred? */
int hanging_keepalives = 0;
/* print verbose info on stalls? */
int verbose = 0;
/* issue all requests on the same TCP connection? */
int one_connection = 0;

char *singleURL;
FILE *urls;
time_t start_seconds;

#define SHOULD_WE_ABORT_CONNECTION() \
(aborted_connections && (1 + (rand() % 100)) <= aborted_connections)

#define SHOULD_WE_DO_RANGE() \
(goofy_ranges && (1 + (rand() % 100)) <= goofy_ranges)

#define SHOULD_WE_HANG_KEEPALIVE() \
(hanging_keepalives && (1 + (rand() % 100)) <= hanging_keepalives)

#define SETSTATE(c,newstate) \
((c)->state = (newstate))

static void put_timestamp(FILE *out)
{
  char timestamp[20];
  time_t now;

  if (show_timestamps)
  {
    time(&now);
    strftime(timestamp,sizeof timestamp,"%H:%M:%S",localtime(&now));
    fprintf(out,"%s ",timestamp);
  }
}

static void errmsg(const char *msg)
{
  static char lastmsg[1024] = {0};
  static int lastmsg_count = 0;
  static int lastmsg_limit = 10;

  if (!strcmp(msg,lastmsg))
  {
    ++lastmsg_count;
    if (lastmsg_count == lastmsg_limit)
    {
      put_timestamp(stderr);
      fprintf(stderr,
              "The last message occurred %d times.\n",
              lastmsg_count);
      lastmsg_count = 0;
      if (lastmsg_limit < 100)      /* yeah, I know that 100 isn't a 
                                     * power of two                  */
      {
        lastmsg_limit *= 2;
      }
    }
  }
  else
  {
    put_timestamp(stderr);
    fprintf(stderr,"%s\n",msg);
    strcpy(lastmsg,msg);
    lastmsg_count = 0;
    lastmsg_limit = 10;
  }
}

static int get_next_target(struct sockaddr_in *target,char **req,char **uri_copy)
{
  char *colon, *uri, *host, *request, *url, *blank, *postdata = NULL;
  char host_buffer[260];
  const char *method;
  char url_buffer[512];
  char post_content_length[100];

  if (singleURL)
  {
    strcpy(url_buffer,singleURL);
  }
  else
  {
    char *newline;

    /* read URL from file */
    if (!fgets(url_buffer,sizeof url_buffer,urls))
    {
      rewind(urls);
      if (!fgets(url_buffer,sizeof url_buffer,urls))
      {
        return 1;
      }
    }
    if ((newline = strchr(url_buffer,'\n')))
    {
      *newline = '\0';
    }
  }

  url = url_buffer;
  if (!memcmp(url,"GET ",4)) 
  {
    method = "GET";
    url += 4;
  }
  else if (!memcmp(url,"HEAD ",5))
  {
    method = "HEAD";
    url += 5;
  }
  else if (!memcmp(url, "OPTIONS ",8))
  {
    method = "OPTIONS";
    url += 8;
  }
  else if (!memcmp(url,"POST ",5))
  {
    method = "POST";
    url += 5;
    blank = url;
    while (!isspace(*blank))
      ++blank;
    *blank = '\0';
    postdata = blank + 1;
    sprintf(post_content_length,
            "Content-Length: %d\r\n",
            (int)strlen(postdata));
  }
  else
  {
    method = "GET";
  }

  memset(target,0,sizeof(*target));

  if (!memcmp(url,"http://",7))
  {
    url += 7;
  }

  host = url;

  uri = strchr(url,'/');
  if (!uri)
  {
    fprintf(stderr,"url must contain slash\n");
    fprintf(stderr,"bad url: %s\n",url);
    exit(ERR_USAGE);
  }
  colon = strchr(url,':');
  if (colon)
  {
    *colon = '\0';
    target->sin_port = htons(atoi(colon + 1));
  }
  else
  {
    size_t host_len;
    
    target->sin_port = htons(80);
    /* move host to different buffer so we can '\0'-terminate
     * it without clobbering the uri
     */
    host_len = uri - url;
    if (host_len >= sizeof host_buffer)
    {
      fprintf(stderr,"hostname too long in `%s'\n",
              url);
      exit(ERR_USAGE);
    }
    memcpy(host_buffer,url,host_len);
    host_buffer[host_len] = '\0';
    host = host_buffer;
  }

  request = (char *)malloc(strlen(uri) + 200);
  sprintf(request,"%s %s HTTP/1.1\r\n%s%sHost: %s\r\n\r\n%s",
          method,
          uri,
          SHOULD_WE_DO_RANGE() ? "Range: bytes=0-0,1-5\r\n" : "",
          postdata ? post_content_length : "",
          host,
          postdata ? postdata : "");
#if 'A' != 0x41
  __etoa(request);
#endif
  
  target->sin_family = AF_INET;
  target->sin_addr.s_addr = inet_addr(host);
  if (target->sin_addr.s_addr == INADDR_NONE)
  {
    struct hostent *he;

    he = gethostbyname(host);
    if (!he)
    {
      fprintf(stderr,"gethostbyname(%s) failed\n",
              host);
      exit(1);
    }
    memcpy(&target->sin_addr.s_addr,he->h_addr,he->h_length);
  }

  *req = request;
  *uri_copy = strdup(uri);
  return 0;
}

static void bad_syscall(const char *s)
{
  char msg[1024];

  ++connection_errors;
#ifdef HAVE_SNPRINTF
  snprintf(msg, sizeof msg, 
           "%s: %s",
           s, strerror(errno));
#else
  sprintf(msg,
          "%s: %s",
          s, strerror(errno));
  fprintf(stderr,
          "Your system really sucks.  I hope I just overlaid storage.\n");
#endif
  errmsg(msg);
}

static int nonblock_socket(Connection_t *c)
{
  int rc = 0;
  int flags;

  flags = fcntl(c->fd, F_GETFL, 0);
  if (flags < 0)
  {
    bad_syscall("fcntl(,F_GETFL,)");
    rc = ERROR;
  }

  if (!rc)
  {
    flags |= O_NONBLOCK;
    rc = fcntl(c->fd, F_SETFL, flags);
    if (rc)
    {
      bad_syscall("fcntl(,F_SETFL,)");
      rc = ERROR;
    }
  }

  return rc;
}

static int init_socket(Connection_t *c)
{
  int rc = 0;
  
  assert(c->state == DEAD);
  c->fd = socket(AF_INET,SOCK_STREAM,0);
  if (c->fd < 0)
  {
    bad_syscall("socket");
    rc = ERROR;
  }

  if (!rc)
  {
    if (c->fd > highest_fd)
    {
      highest_fd = c->fd;
      assert((sizeof(conn_by_fd)/sizeof(conn_by_fd[0])) > c->fd);
    }

    assert(conn_by_fd[c->fd] == NULL);
    conn_by_fd[c->fd] = c;
    rc = nonblock_socket(c);
  }

  if (!rc && c->rcvbuf_size)
  {
    rc = setsockopt(c->fd, SOL_SOCKET, SO_RCVBUF, &c->rcvbuf_size,
                    sizeof c->rcvbuf_size);
    if (rc < 0)
    {
      bad_syscall("setsockopt(SO_RCVBUF)");
      rc = ERROR;
    }
  }
  
  if (!rc && c->sndbuf_size)
  {
    rc = setsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, &c->sndbuf_size,
                    sizeof c->sndbuf_size);
    if (rc < 0)
    {
      bad_syscall("setsockopt(SO_SNDBUF)");
      rc = ERROR;
    }
  }
  
  if (!rc)
  {
    SETSTATE(c,INITIALIZED);
  }

  return rc;
}

static int connect_socket(Connection_t *c)
{
  int rc = 0;

  assert(c->state == INITIALIZED);

  if (connections_finished >= connections_to_finish)
  {
    printf("We're done\n");
    rc = DONE;
  }
  /* XXX handle -1 option */
  else if (connections_finished + *c->sleep_count + c->stats->connecting +
           c->stats->reading + c->stats->writing >= connections_to_finish) 
  {
    SETSTATE(c,RETIRED);
    rc = RETIRED;
  }

  if (!rc)
  {
    if (c->request)
      free(c->request);
    if (c->uri)
      free(c->uri);
    rc = get_next_target(&c->server,&c->request,&c->uri);
    if (rc) /* no more targets */
    {
      SETSTATE(c,RETIRED);
      rc = RETIRED;
    }
  }
    
  if (!rc)
  {
    static struct timeval tv = {0,100 /* microseconds */};
      
    c->request_size = strlen(c->request);

    ++connections_attempted;

    rc = connect(c->fd,(struct sockaddr *)&c->server,sizeof c->server);
    if (!rc)
    {
      int abort_connection = SHOULD_WE_ABORT_CONNECTION();
      if (abort_connection)
      {
        /* leave state alone */
        rc = ERROR;
        errno = FAKE_ERRNO;
      }
      else
        SETSTATE(c,CONNECTED);
      tv.tv_usec = 100;
    }
    else if (rc == -1 && errno == EINPROGRESS)
    {
      SETSTATE(c,CONNECTINPROGRESS);
      FD_SET(c->fd,c->write_set);
      ++*c->write_count;
      ++c->stats->connecting;
      rc = BLOCKED;
    }
    else if (rc == -1 && errno == EAGAIN)
    {
      bad_syscall("connect");
      errmsg("sleeping briefly before retrying...");
      rc = select(0,NULL,NULL,NULL,&tv);
      if (tv.tv_usec < 50000)
        tv.tv_usec *= 2;
      if (rc != 0)
      {
        bad_syscall("select");
      }
      rc = ERROR;
      SETSTATE(c,CLOSE);
    }
    else
    {
      bad_syscall("connect");
      rc = ERROR;
    }
  }

  return rc;
}

static int check_connected(Connection_t *c)
{
  int rc, error;
  socklen_t len;
  
  assert(c->state == CONNECTINPROGRESS);
  --c->stats->connecting;
  len = sizeof error;
  rc = getsockopt(c->fd,SOL_SOCKET,SO_ERROR,&error,&len);
  if (rc < 0)
  {
    rc = ERROR;
  }
  else if (error)
  {
    errno = error;
    rc = ERROR;
  }
  else
  {
    int abort_connection = SHOULD_WE_ABORT_CONNECTION();
    if (abort_connection)
    {
      /* leave state alone */
      rc = ERROR;
      errno = FAKE_ERRNO;
    }
    else
    {
      SETSTATE(c,CONNECTED);
      FD_CLR(c->fd,c->write_set);
      --*c->write_count;
    }
  }

  if (rc == ERROR)
  {
    bad_syscall("non-blocking connect");
    --*c->write_count;
    FD_CLR(c->fd,c->write_set);
    c->state = CLOSE;
  }

  return rc;
}

static int send_more_request(Connection_t *c)
{
  int rc = 0;
  int bytes_left = c->request_size - c->request_offset;

  rc = write(c->fd,
             (char *)c->request + c->request_offset,
             bytes_left);
  if (rc == bytes_left)
  {
    if (one_connection)
    {
      assert(c->state == REQUESTRESPONSEINPROGRESS);
      ++connections_finished; /* request isn't finished, but I don't parse the response to
                      * know when it has been; this is useful info anyway
                      */
      rc = 0;
      /* start a new request */
      if (c->request)
        free(c->request);
      if (c->uri)
        free(c->uri);
      rc = get_next_target(&c->server,&c->request,&c->uri);
      if (rc) /* no more targets */
      {
        SETSTATE(c,RETIRED);
        rc = RETIRED;
      }
      else
      {
        c->request_size = strlen(c->request);
        c->request_offset = 0;
      }
    }
    else
    {
      SETSTATE(c,REQUESTCOMPLETE);
      FD_CLR(c->fd,c->write_set);
      --*c->write_count;
      --c->stats->writing;
      rc = 0;
      if (!c->hang_keepalive)
      {
        shutdown(c->fd,1);
      }
    }
  }
  else if (rc > 0)
  {
    c->request_offset += rc;
    rc = BLOCKED;
  }
  else if (errno == EAGAIN || errno == EWOULDBLOCK)
  {
    rc = BLOCKED;
  }
  else
  {
    bad_syscall("write");
    if (one_connection)
      --c->stats->reading_and_writing;
    else
      --c->stats->writing;
    rc = ERROR;
  }
  
  return rc;
}

static int start_request(Connection_t *c)
{
  int rc = 0;

  assert(c->state == CONNECTED);
  if (one_connection)
  {
    SETSTATE(c,REQUESTRESPONSEINPROGRESS);
    FD_SET(c->fd,c->read_set);
    ++c->stats->reading_and_writing;
  }
  else
  {
    SETSTATE(c,REQUESTINPROGRESS);
    ++c->stats->writing;
  }
  FD_SET(c->fd,c->write_set);
  ++*c->write_count;
  c->request_offset = 0;
  rc = send_more_request(c);
  return rc;
}

static int read_more_response(Connection_t *c)
{
  int rc;
  char buf[8192];

  if (one_connection)
    assert(c->state == REQUESTRESPONSEINPROGRESS);
  else
    assert(c->state == RESPONSEINPROGRESS);
         
  do 
  {
    rc = read(c->fd,buf,sizeof buf);
    if (rc > 0)
    {
      c->response_bytes += rc;
    }
  } while (rc > 0);

  if (rc == 0)
  {
    SETSTATE(c,RESPONSECOMPLETE);
#ifdef DBG
    fprintf(stderr,"responsecomplete found\n");
#endif
    FD_CLR(c->fd,c->read_set);
    --*c->read_count;
    if (one_connection)
      --c->stats->reading_and_writing;
    else
      --c->stats->reading;
  }
  else if (errno == EAGAIN || errno == EWOULDBLOCK)
  {
    rc = BLOCKED;
  }
  else
  {
    char errmsg[1024];

    sprintf(errmsg,
            "for fd %d (after reading %ld bytes): read",
            c->fd,(long int)c->response_bytes);
    bad_syscall(errmsg);
    rc = ERROR;
    if (one_connection)
    {
      --c->stats->reading_and_writing;
      FD_CLR(c->fd,c->write_set);
    }
    else
      --c->stats->reading;
    FD_CLR(c->fd,c->read_set);
    --*c->read_count;
    SETSTATE(c,CLOSE);
  }

  return rc;
}

static int start_reading_response(Connection_t *c)
{
  int rc;

  if (one_connection)
  {
    assert(c->state == REQUESTRESPONSEINPROGRESS);
    ++c->stats->reading_and_writing;
    FD_SET(c->fd,c->read_set);
  }
  else
  {
    assert(c->state == REQUESTCOMPLETE);
    SETSTATE(c,RESPONSEINPROGRESS);
    ++c->stats->reading;
    FD_SET(c->fd,c->read_set);
  }
  ++*c->read_count;
  c->response_bytes = 0;
  rc = read_more_response(c);
  return rc;
}

static int find_next_state(Connection_t *c)
{
  int new_state;

  switch(c->state)
  {
    case RESPONSECOMPLETE:
#ifdef DBG
      printf("responsecomplete fns\n");
#endif
      /* 
       * choose randomly which state to pick next 
       * (CONNECTED, CLOSE, SLEEP) 
       */
      new_state = CLOSE;
      /* new_state = SLEEP; */
      break;
    case SLEEPINGCOMPLETE:
      new_state = CLOSE;
      break;
    default:
      assert(1 != 1);
  }

  switch(new_state)
  {
    case CLOSE:
      ++connections_finished;
      SETSTATE(c,CLOSE);
      break;
    case SLEEP:
      SETSTATE(c,SLEEP);
      break;
    default:
      assert(1 != 1);
  }

  return 0;
}

static int close_connection(Connection_t *c)
{
  int rc, fd;

  assert(c->state == CLOSE);
  fd = c->fd;
  c->fd = -1;
  SETSTATE(c,DEAD);
  conn_by_fd[fd] = NULL;
  assert(!FD_ISSET(fd,c->read_set));
  assert(!FD_ISSET(fd,c->write_set));
  /* printf("finished connection on socket %d read %u bytes\n",fd,c->response_bytes); */
  rc = close(fd);
  if (rc)
  {
    perror("close");
    rc = ERROR;
  }

  return rc;
}

static int start_idle_connection(Connection_t *c)
{
  time_t cur_time;

  assert(c->state == SLEEP);

  time(&cur_time);
  c->end_of_sleep = cur_time + 1;
  SETSTATE(c,SLEEPING);
  ++*c->sleep_count;

  return BLOCKED;
}

static int check_idle_connection(Connection_t *c)
{
  int rc;
  time_t cur_time;

  assert(c->state == SLEEPING);
  time(&cur_time);
  if (c->end_of_sleep >= cur_time)
  {
    rc = 0;
    SETSTATE(c,SLEEPINGCOMPLETE);
    --*c->sleep_count;
  }
  else
  {
    rc = BLOCKED;
  }

  return rc;
}

static int processConnection(Connection_t *c)
{
  int rc = 0;

  do {
    switch (c->state)
    {
      case DEAD:
        rc = init_socket(c);
        break;
      case INITIALIZED:
        rc = connect_socket(c);
        break;
      case CONNECTINPROGRESS:
        rc = check_connected(c);
        break;
      case CONNECTED:
        rc = start_request(c);
        break;
      case REQUESTINPROGRESS:
        rc = send_more_request(c);
        break;
      case REQUESTRESPONSEINPROGRESS:
        if (c->writeable)
        {
          rc = send_more_request(c);
        }
        if ((rc == 0 || rc == BLOCKED) && c->readable)
        {
          rc = read_more_response(c);
        }
        break;
      case REQUESTCOMPLETE:
        rc = start_reading_response(c);
        break;
      case RESPONSEINPROGRESS:
        rc = read_more_response(c);
        break;
      case RESPONSECOMPLETE:
#ifdef DBG
        printf("responsecomplete pc\n");
#endif
        rc = find_next_state(c);
        break;
      case SLEEP:
        rc = start_idle_connection(c);
        break;
      case SLEEPING:
        rc = check_idle_connection(c);
        break;
      case SLEEPINGCOMPLETE:
        rc = find_next_state(c);
        break;
      case CLOSE:
        rc = close_connection(c);
        break;
      default:
        fprintf(stderr,"How do I handle state %d?\n",c->state);
        exit(1);
    }
    if (rc == ERROR && c->state == CLOSE) /* do we need to clean up? */
    {
      rc = 0;                       /* clean up on next iteration    */
    }
  } while (!rc);
  
  return rc;
}

static void show_stats(Stats_t *s,
                       int read_count,fd_set *read_set,
                       int write_count,fd_set *write_set,
                       int sleep_count)
{
  time_t current_seconds;

  time(&current_seconds);

  if (current_seconds == start_seconds) /* avoid an extremely likely x/0 error */
    ++current_seconds;

  printf("finished ok: %d (%ld/sec) attempted: %d errors: %d connecting: %d ",
         connections_finished,
         (long int)(connections_finished / (current_seconds - start_seconds)),
         connections_attempted,connection_errors,
         s->connecting);
  if (one_connection)
  {
    printf("writing and reading: %d (%d/%d)",
           s->reading_and_writing,read_count,write_count);
  }
  else
  {
    printf("writing: %d (%d) reading: %d (%d)",
           s->writing,write_count,
           s->reading,read_count);
  }
  printf(" sleeping: %d\n",
         sleep_count);
#if 0
  {
    int i = 0;

    while (i <= highest_fd)
    {
      if (FD_ISSET(i,read_set))
      {
        printf("checking for readability on %d...\n",
               i);
      }
      if (FD_ISSET(i,write_set))
      {
        printf("checking for writability on %d...\n",
               i);
      }
      ++i;
    }
  }
#endif
}

static void show_states(void)
{
  int cur;
  int state_counts[NUM_STATES] = {0};
  int did_timestamp = 0;

  for (cur = 0; cur <= highest_fd; cur++)
  {
    if (conn_by_fd[cur])
    {
      state_counts[conn_by_fd[cur]->state] += 1;
      if (verbose &&
          conn_by_fd[cur]->state == RESPONSEINPROGRESS)
      {
        fprintf(stdout,
                "% 2d RESPONSEINPROGRESS % 5d bytes received for %s%s\n",
                cur,
                (int)conn_by_fd[cur]->response_bytes,
                conn_by_fd[cur]->uri,
                conn_by_fd[cur]->hang_keepalive ? " (keepalive?)" : "");
      }
    }
  }

  for (cur = 0; cur < NUM_STATES; cur++)
  {
    if (state_counts[cur])
    {
      if (show_timestamps && !did_timestamp)
      {
        put_timestamp(stdout);
        did_timestamp = 1;
      }        
      printf("%s: %d ",state_names[cur],state_counts[cur]);
    }
  }
  printf("\n");
}

static int start_connections(int concurrent,
                             fd_set *read_set,
                             int *read_count,
                             fd_set *write_set,
                             int *write_count,
                             int *sleep_count,
                             Stats_t *stats)
{
  int i, rc;
  Connection_t *c;

  for (i = 0; i < concurrent; i++)
  {
    const char *ch;
    
    c = calloc(sizeof(Connection_t),1);
    SETSTATE(c,DEAD);
    c->fd = -1;
    ch = getenv("B_RCVBUF");
    if (ch)
    {
      c->rcvbuf_size = atoi(ch);
    }
    ch = getenv("B_SNDBUF");
    if (ch)
    {
      c->sndbuf_size = atoi(ch);
    }
    c->read_set = read_set;
    c->read_count = read_count;
    c->write_set = write_set;
    c->write_count = write_count;
    c->sleep_count = sleep_count;
    c->stats = stats;
    c->hang_keepalive = SHOULD_WE_HANG_KEEPALIVE();

    /*
     * Get the new connection started
     */
    
    rc = processConnection(c);
    if (rc != BLOCKED)
    {
      printf("processConnection->%d\n",rc);
      rc = ERROR;
      break;
    }
  }

  if (rc == BLOCKED)
  {
    rc = 0;
  }

  return rc;
}

static int process_sleepers(time_t cur_time)
{
  int rc, cur;

  for (cur = 0; cur < highest_fd + 1; cur++)
  {
    if (conn_by_fd[cur] && conn_by_fd[cur]->state == SLEEPING)
    {
      rc = processConnection(conn_by_fd[cur]);
      if (rc != BLOCKED && rc != RETIRED && rc != DONE)
      {
        fprintf(stderr,
                "process_sleepers: processConnection()->%d\n",rc);
        break;
      }
    }
  }

  return rc;
}

static int process_connections(int *read_count,fd_set *read_set,
                               int *write_count,fd_set *write_set,
                               int *sleepers,
                               Stats_t *stats)
{
  int rc, src;
  time_t prev_time, cur_time;

  time(&prev_time);
  do
  {
    if (*read_count || *write_count || *sleepers)
    {
      /* select before continuing */
      fd_set tmp_read_set, *tr;
      fd_set tmp_write_set, *tw;
      struct timeval tv;
      
      tv.tv_sec = 1;
      tv.tv_usec = 0;
      
      if (read_count)
      {
        memcpy(&tmp_read_set,read_set,sizeof(fd_set));
        tr = &tmp_read_set;
      }
      else
        tr = NULL;

      if (write_count)
      {
        memcpy(&tmp_write_set,write_set,sizeof(fd_set));
        tw = &tmp_write_set;
      }
      else
        tw = NULL;
      
      src = select(highest_fd + 1,tr,tw,NULL,&tv);
      /* printf("select->%d\n",src); */
      if (src > 0)
      {
        int cur;

        if (FD_ISSET(0,tr))
        {
          show_stats(stats,*read_count,read_set,*write_count,write_set,*sleepers);
          getchar();
        }

        cur = 1;
        while (cur < highest_fd + 1)
        {
          if (conn_by_fd[cur])
          {
            conn_by_fd[cur]->readable = 0;
            conn_by_fd[cur]->writeable = 0;
            if (tr && FD_ISSET(cur,tr))
            {
              assert(conn_by_fd[cur]);
              conn_by_fd[cur]->readable = 1;
              rc = processConnection(conn_by_fd[cur]);
              if (rc != BLOCKED && rc != RETIRED)
                break;
            }
            else if (tw && FD_ISSET(cur,tw))
            {
              assert(conn_by_fd[cur]);
              conn_by_fd[cur]->writeable = 1;
              rc = processConnection(conn_by_fd[cur]);
              if (rc != BLOCKED && rc != RETIRED)
                break;
            }
          }
          ++cur;
        }

      }
      else if (src == 0)            /* nothing happened */
      {
        show_states();
      }
      else
      {
        perror("select");
        exit(1);
      }
      if (*sleepers)
      {
        time(&cur_time);
        if (cur_time != prev_time)
        {
          rc = process_sleepers(cur_time);
          prev_time = cur_time;
        }
      }
    }
    else
    {
      printf("I'm confused!\n");
    }             
  } while (rc == 0 || rc == BLOCKED || rc == RETIRED);

  return rc;
}

int main(int argc,char **argv)
{
  int concurrent, opt, rc;
  char *endptr;
  Stats_t stats = {0};
  fd_set read_set;
  int read_count = 0;
  fd_set write_set;
  int write_count = 0;
  int sleep_count = 0;
  struct timeval start_time, finish_time;
  int secs, usecs;
  long tmp;

  signal(SIGPIPE,SIG_IGN);

  srand(time(NULL));
  connections_to_finish = DEFAULT_REQUESTS;
  concurrent = DEFAULT_CONCURRENCY;

  rc = 0;
  while (!rc && (opt = getopt(argc,argv,"n:c:C:f:R:K:tv1")) != -1)
  {
    switch(opt)
    {
      case 'c':
        concurrent = strtol(optarg,&endptr,10);
        if (*endptr != '\0' || concurrent < 1)
        {
          fprintf(stderr,"bad parm: -c %s\n",optarg);
          exit(1);
        }
        break;
      case 'f':
        urls = fopen(optarg,"r");
        if (!urls)
        {
          perror(optarg);
          exit(1);
        }
        break;
      case 'n':
        connections_to_finish = strtol(optarg,&endptr,10);
        if (*endptr != '\0' || connections_to_finish < 1)
        {
          fprintf(stderr,"bad parm: -n %s\n",optarg);
          exit(1);
        }
        break;
      case 't':
        show_timestamps = 1;
        break;
      case 'C': /* how many times to drop the connection ASAP? */
      case 'R': /* how many goofy ranges per hundred? */
      case 'K': /* how many hanging keepalives per hundred? */
        tmp = strtol(optarg,&endptr,10);
        if (*endptr != '\0' || tmp < 1 || tmp > 100)
        {
          fprintf(stderr,"bad parm: -%c %s\n",opt,optarg);
          exit(1);
        }
        switch(opt)
        {
          case 'C':
            aborted_connections = tmp;
            break;
          case 'K':
            hanging_keepalives = tmp;
            break;
          case 'R':
            goofy_ranges = tmp;
            break;
          default:
            assert(1 != 1);
        }
        break;
      case 'v':
        verbose = 1;
        break;
      case '1':
        one_connection = 1;
        break;
      default:
        rc = ERR_USAGE;
    }
  }

  if (!urls)
  {
    if (optind + 1 != argc)
    {
      rc = ERR_USAGE;
    }

    if (!rc)
    {
      singleURL = strdup(argv[optind]);
    }
  }
  else
  {
    if (optind != argc)
    {
      rc = ERR_USAGE;
    }
  }

  if (concurrent > 1 && one_connection)
  {
    fprintf(stderr,
            "Concurrency must be 1 when -1 is used.\n\n");
    rc = ERR_USAGE;
  }

  if (!rc)
  {
    FD_ZERO(&read_set);
    FD_ZERO(&write_set);

    /* include stdin - file descriptor zero */
    if (isatty(0))
    {
      FD_SET(0,&read_set);
      ++read_count;
    }
    
    assert(!gettimeofday(&start_time,NULL));
    time(&start_seconds);

    rc = start_connections(concurrent,&read_set,&read_count,
                           &write_set,&write_count,&sleep_count,
                           &stats);
  }

  if (!rc)
  {
    rc = process_connections(&read_count,&read_set,
                             &write_count,&write_set,
                             &sleep_count,
                             &stats);
    if (rc == DONE)
    {
      rc = 0;
    }

    assert(!gettimeofday(&finish_time,NULL));

    show_stats(&stats,read_count,&read_set,write_count,&write_set,sleep_count);

    if (finish_time.tv_usec < start_time.tv_usec)
    {
      secs = finish_time.tv_sec - start_time.tv_sec - 1;
      usecs = 1000000 - (start_time.tv_usec - finish_time.tv_usec);
    }
    else
    {
      secs = finish_time.tv_sec - start_time.tv_sec;
      usecs = finish_time.tv_usec - start_time.tv_usec;
    }
    
    printf("time taken: %d.%03d seconds\n",
           secs,usecs / 1000);
  }

  switch(rc)
  {
    case 0:
      break;
    case ERR_USAGE:
      fprintf(stderr,
              "Usage: %s [-c concurrency] [-n number-of-requests] [-C dropped-connections-per-hundred] [-R goofy-ranges-per-hundred] [-K hanging-keepalives-per-hundred] [-f filename] [-t] [-v] [-1] [URL]\n"
              "Either [-f file-with-URLs] or [URL] must be specified.\n"
              "Defaults:\n"
              "\tconcurrency: %d\n"
              "\tnumber-of-requests: %d\n"
              "\n"
              "When -f is specified, -n must still be used to\n"
              "set the number of requests to be completed.\n"
              "-t: print timestamps on some messages\n"
              "-v: print verbose information on stalls\n"
              "-1: use one TCP connection (all requests to same server)\n"
              ,
              argv[0],DEFAULT_CONCURRENCY,DEFAULT_REQUESTS);
      break;
    default:
      fprintf(stderr,
              "Unexpected exit status: %d\n",
              rc);
  }

  return rc;
}

