Пример #1
0
/* Add a new event for a given IOD. msevents are stored in separate event lists
 * (in the nsock pool) and are grouped by IOD within each list.
 *
 * This function appends the event _before_ the first similar event we have for
 * the given IOD, or append it to the end of the list if no similar event is
 * already present.
 *
 * Note that adding the event before the similar ones is important for
 * reentrancy, as it will prevent the new event to be processed in the event
 * loop just after its addition.
 */
static int iod_add_event(msiod *iod, msevent *nse) {
  switch(nse->type) {
    case NSE_TYPE_CONNECT:
    case NSE_TYPE_CONNECT_SSL:
      if (iod->first_connect)
        iod->first_connect = gh_list_insert_before(&iod->nsp->connect_events, iod->first_connect, nse);
      else
        iod->first_connect = gh_list_append(&iod->nsp->connect_events, nse);
      break;

    case NSE_TYPE_READ:
      if (iod->first_read)
        iod->first_read = gh_list_insert_before(&iod->nsp->read_events, iod->first_read, nse);
      else
        iod->first_read = gh_list_append(&iod->nsp->read_events, nse);
      break;

    case NSE_TYPE_WRITE:
      if (iod->first_write)
        iod->first_write = gh_list_insert_before(&iod->nsp->write_events, iod->first_write, nse);
      else
        iod->first_write = gh_list_append(&iod->nsp->write_events, nse);
      break;

#if HAVE_PCAP
    case NSE_TYPE_PCAP_READ: {
      char add_read = 0, add_pcap_read = 0;

#if PCAP_BSD_SELECT_HACK
      /* BSD hack mode: add event to both read and pcap_read lists */
      add_read = add_pcap_read = 1;
#else
      if (((mspcap *)iod->pcap)->pcap_desc >= 0) {
        add_read = 1;
      } else {
        add_pcap_read = 1;
      }
#endif
      if (add_read) {
        if (iod->first_read)
          iod->first_read = gh_list_insert_before(&iod->nsp->read_events, iod->first_read, nse);
        else
          iod->first_read = gh_list_append(&iod->nsp->read_events, nse);
      }
      if (add_pcap_read) {
        if (iod->first_pcap_read)
          iod->first_pcap_read = gh_list_insert_before(&iod->nsp->pcap_read_events, iod->first_pcap_read, nse);
        else
          iod->first_pcap_read = gh_list_append(&iod->nsp->pcap_read_events, nse);
      }
      break;
    }
#endif

    default:
      fatal("Unknown event type (%d) for IOD #%d\n", nse->type, iod->id);
  }
  return 0;
}
Пример #2
0
/* A proxy chain is a comma-separated list of proxy specification strings:
 * proto://[user:pass@]host[:port] */
int nsock_proxychain_new(const char *proxystr, nsock_proxychain *chain, nsock_pool nspool) {
  struct npool *nsp = (struct npool *)nspool;
  struct proxy_chain *pxc, **pchain = (struct proxy_chain **)chain;

  *pchain = NULL;

  pxc = (struct proxy_chain *)safe_malloc(sizeof(struct proxy_chain));
  gh_list_init(&pxc->nodes);

  if (proxystr) {
    struct proxy_parser *parser;

    parser = proxy_parser_new(proxystr);
    while (!parser->done) {
      gh_list_append(&pxc->nodes, &parser->value->nodeq);
      proxy_parser_next(parser);
    }
    proxy_parser_delete(parser);
  }

  if (nsp) {
    if (nsock_pool_set_proxychain(nspool, pxc) < 0) {
      nsock_proxychain_delete(pxc);
      return -1;
    }
  }

  *pchain = pxc;
  return 1;
}
Пример #3
0
static int nevent_unref(struct npool *nsp, struct nevent *nse) {
  switch (nse->type) {
    case NSE_TYPE_CONNECT:
    case NSE_TYPE_CONNECT_SSL:
      gh_list_remove(&nsp->connect_events, &nse->nodeq_io);
      break;

    case NSE_TYPE_READ:
      gh_list_remove(&nsp->read_events, &nse->nodeq_io);
      break;

    case NSE_TYPE_WRITE:
      gh_list_remove(&nsp->write_events, &nse->nodeq_io);
      break;

#if HAVE_PCAP
    case NSE_TYPE_PCAP_READ: {
      char read = 0;
      char pcap = 0;

#if PCAP_BSD_SELECT_HACK
      read = pcap = 1;
#else
      if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0)
        read = 1;
      else
        pcap = 1;
#endif /* PCAP_BSD_SELECT_HACK */

      if (read)
        gh_list_remove(&nsp->read_events, &nse->nodeq_io);
      if (pcap)
        gh_list_remove(&nsp->pcap_read_events, &nse->nodeq_pcap);

      break;
    }
#endif /* HAVE_PCAP */

    case NSE_TYPE_TIMER:
      /* Nothing to do */
      break;

    default:
      fatal("Unknown event type %d", nse->type);
  }
  gh_list_append(&nsp->free_events, &nse->nodeq_io);
  return 0;
}
Пример #4
0
/* This version allows you to associate an existing sd with the msi
   so that you can read/write it using the nsock infrastructure.  For example,
   you may want to watch for data from STDIN_FILENO at the same time as you
   read/wrtie various sockets. Ths sd is dup()ed, so you may close or
   otherwise manipulate your copy.  The duped copy will be destroyed when the
   nsi is destroyed 
*/
nsock_iod nsi_new2(nsock_pool nsockp, int sd, void *userdata) {
  mspool *nsp = (mspool *) nsockp;
  msiod *nsi;

  nsi = (msiod *) gh_list_pop(&nsp->free_iods);
  if (!nsi) nsi = (msiod * ) safe_malloc(sizeof(msiod));

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

  if (sd == -1) {  
    nsi->sd = -1;
    nsi->state = NSIOD_STATE_INITIAL;
  } else {
    nsi->sd = dup(sd);
    if (nsi->sd == -1) {
      free(nsi);
      return NULL;
    }
    nsock_unblock_socket(nsi->sd);
    nsi->state = NSIOD_STATE_UNKNOWN;
  }
    
  nsi->userdata = userdata;
  nsi->nsp = (mspool *) nsockp;
  nsi->events_pending = 0;

#if HAVE_OPENSSL
  nsi->ssl_session = NULL;
#endif

  nsi->id = nsp->next_iod_serial++;
  if (nsi->id == 0) nsi->id = nsp->next_iod_serial++;

  /* The nsp keeps track of active msiods so it can delete them 
     if it is deleted */
  nsi->entry_in_nsp_active_iods = gh_list_append(&nsi->nsp->active_iods, nsi);

  return (nsock_iod) nsi;
}
Пример #5
0
void process_iod_events(struct npool *nsp, struct niod *nsi, int ev) {
  int i = 0;
  /* store addresses of the pointers to the first elements of each kind instead
   * of storing the values, as a connect can add a read for instance */
  gh_lnode_t **start_elems[] = {
    &nsi->first_connect,
    &nsi->first_read,
    &nsi->first_write,
#if HAVE_PCAP
    &nsi->first_pcap_read,
#endif
    NULL
  };
  gh_list_t *evlists[] = {
    &nsp->connect_events,
    &nsp->read_events,
    &nsp->write_events,
#if HAVE_PCAP
    &nsp->pcap_read_events,
#endif
    NULL
  };

  assert(nsp == nsi->nsp);
  nsock_log_debug_all("Processing events on IOD %lu (ev=%d)", nsi->id, ev);

  /* We keep the events separate because we want to handle them in the
   * order: connect => read => write => timer for several reasons:
   *
   *  1) Makes sure we have gone through all the net i/o events before
   *     a timer expires (would be a shame to timeout after the data was
   *     available but before we delivered the events
   *
   *  2) The connect() results often lead to a read or write that can be
   *     processed in the same cycle.  In the same way, read() often
   *     leads to write().
   */
  for (i = 0; evlists[i] != NULL; i++) {
    gh_lnode_t *current, *next, *last;

    /* for each list, get the last event and don't look past it as an event
     * could add another event in the same list and so on... */
    last = gh_list_last_elem(evlists[i]);

    for (current = *start_elems[i];
         current != NULL && gh_lnode_prev(current) != last;
         current = next) {
      struct nevent *nse;

#if HAVE_PCAP
      if (evlists[i] == &nsi->nsp->pcap_read_events)
        nse = lnode_nevent2(current);
      else
#endif
        nse = lnode_nevent(current);

      /* events are grouped by IOD. Break if we're done with the events for the
       * current IOD */
      if (nse->iod != nsi)
        break;

      process_event(nsp, evlists[i], nse, ev);
      next = gh_lnode_next(current);

      if (nse->event_done) {
        /* event is done, remove it from the event list and update IOD pointers
         * to the first events of each kind */
        update_first_events(nse);
        gh_list_remove(evlists[i], current);
        gh_list_append(&nsp->free_events, &nse->nodeq_io);

        if (nse->timeout.tv_sec)
          gh_heap_remove(&nsp->expirables, &nse->expire);
      }
    }
  }
}
Пример #6
0
/* Adds an event to the appropriate nsp event list, handles housekeeping such as
 * adjusting the descriptor select/poll lists, registering the timeout value,
 * etc. */
void nsp_add_event(mspool *nsp, msevent *nse) {
  if (nsp->tracelevel > 5)
    nsock_trace(nsp, "NSE #%lu: Adding event", nse->id);

  /* First lets do the event-type independent stuff, starting with timeouts */
  if (nse->event_done) {
    nsp->next_ev = nsock_tod;
  } else {
    if (nse->timeout.tv_sec != 0) {
      if (nsp->next_ev.tv_sec == 0)
        nsp->next_ev = nse->timeout;
      else if (TIMEVAL_AFTER(nsp->next_ev, nse->timeout))
        nsp->next_ev = nse->timeout;
    }
  }

  nsp->events_pending++;

  /* Now we do the event type specific actions */
  switch(nse->type) {
    case NSE_TYPE_CONNECT:
    case NSE_TYPE_CONNECT_SSL:
      if (!nse->event_done) {
        assert(nse->iod->sd >= 0);
        socket_count_read_inc(nse->iod);
        socket_count_write_inc(nse->iod);
        update_events(nse->iod, nsp, EV_READ|EV_WRITE|EV_EXCEPT, EV_NONE);
      }
      iod_add_event(nse->iod, nse);
      break;

    case NSE_TYPE_READ:
      if (!nse->event_done) {
        assert(nse->iod->sd >= 0);
        socket_count_read_inc(nse->iod);
        update_events(nse->iod, nsp, EV_READ, EV_NONE);
#if HAVE_OPENSSL
        if (nse->iod->ssl)
          nse->sslinfo.ssl_desire = SSL_ERROR_WANT_READ;
#endif
      }
      iod_add_event(nse->iod, nse);
      break;

    case NSE_TYPE_WRITE:
      if (!nse->event_done) {
        assert(nse->iod->sd >= 0);
        socket_count_write_inc(nse->iod);
        update_events(nse->iod, nsp, EV_WRITE, EV_NONE);
#if HAVE_OPENSSL
        if (nse->iod->ssl)
          nse->sslinfo.ssl_desire = SSL_ERROR_WANT_WRITE;
#endif
      }
      iod_add_event(nse->iod, nse);
      break;

    case NSE_TYPE_TIMER:
      gh_list_append(&nsp->timer_events, nse);
      break;

#if HAVE_PCAP
    case NSE_TYPE_PCAP_READ: {
      mspcap *mp = (mspcap *)nse->iod->pcap;

      assert(mp);
      if (mp->pcap_desc >= 0) { /* pcap descriptor present */
        if (!nse->event_done) {
          socket_count_readpcap_inc(nse->iod);
          update_events(nse->iod, nsp, EV_READ, EV_NONE);
        }
        if (nsp->tracelevel > 8)
          nsock_trace(nsp, "PCAP NSE #%lu: Adding event to READ_EVENTS", nse->id);

        #if PCAP_BSD_SELECT_HACK
        /* when using BSD hack we must do pcap_next() after select().
         * Let's insert this pcap to bot queues, to selectable and nonselectable.
         * This will result in doing pcap_next_ex() just before select() */
        if (nsp->tracelevel > 8)
          nsock_trace(nsp, "PCAP NSE #%lu: Adding event to PCAP_READ_EVENTS", nse->id);
        #endif
      } else {
        /* pcap isn't selectable. Add it to pcap-specific queue. */
        if (nsp->tracelevel > 8)
          nsock_trace(nsp, "PCAP NSE #%lu: Adding event to PCAP_READ_EVENTS", nse->id);
      }
      iod_add_event(nse->iod, nse);
      break;
    }
#endif

    default:
      assert(0);
      break; /* unreached */
  }
}
Пример #7
0
/* This version allows you to associate an existing sd with the msi so that you
 * can read/write it using the nsock infrastructure.  For example, you may want
 * to watch for data from STDIN_FILENO at the same time as you read/write
 * various sockets.  STDIN_FILENO is a special case, however. Any other sd is
 * dup()ed, so you may close or otherwise manipulate your copy.  The duped copy
 * will be destroyed when the nsi is destroyed. */
nsock_iod nsock_iod_new2(nsock_pool nsockp, int sd, void *userdata) {
  struct npool *nsp = (struct npool *)nsockp;
  gh_lnode_t *lnode;
  struct niod *nsi;

  lnode = gh_list_pop(&nsp->free_iods);
  if (!lnode) {
    nsi = (struct niod *)safe_malloc(sizeof(*nsi));
    memset(nsi, 0, sizeof(*nsi));
  } else {
    nsi = container_of(lnode, struct niod, nodeq);
  }

  if (sd == -1) {
    nsi->sd = -1;
    nsi->state = NSIOD_STATE_INITIAL;
  } else if (sd == STDIN_FILENO) {
    nsi->sd = STDIN_FILENO;
    nsi->state = NSIOD_STATE_UNKNOWN;
  } else {
    nsi->sd = dup_socket(sd);
    if (nsi->sd == -1) {
      free(nsi);
      return NULL;
    }
    unblock_socket(nsi->sd);
    nsi->state = NSIOD_STATE_UNKNOWN;
  }

  nsi->first_connect = NULL;
  nsi->first_read = NULL;
  nsi->first_write = NULL;
#if HAVE_PCAP
  nsi->first_pcap_read = NULL;
  nsi->readpcapsd_count = 0;
#endif
  nsi->readsd_count = 0;
  nsi->write_count = 0;

  nsi->userdata = userdata;
  nsi->nsp = (struct npool *)nsockp;

  nsi->_flags = 0;

  nsi->read_count = 0;
  nsi->write_count = 0;

  nsi->hostname = NULL;

  nsi->ipopts = NULL;
  nsi->ipoptslen = 0;

#if HAVE_OPENSSL
  nsi->ssl_session = NULL;
#endif

  if (nsp->px_chain) {
    nsi->px_ctx = proxy_chain_context_new(nsp);
  } else {
    nsi->px_ctx = NULL;
  }

  nsi->id = nsp->next_iod_serial++;
  if (nsi->id == 0)
    nsi->id = nsp->next_iod_serial++;

  /* The nsp keeps track of active iods so it can delete them if it is deleted */
  gh_list_append(&nsp->active_iods, &nsi->nodeq);

  nsock_log_info("nsock_iod_new (IOD #%lu)", nsi->id);

  return (nsock_iod)nsi;
}
Пример #8
0
/* An internal function for cancelling an event when you already have a pointer
 * to the msevent (use nsock_event_cancel if you just have an ID). The
 * event_list passed in should correspond to the type of the event. For example,
 * with NSE_TYPE_READ, you would pass in &nsp->read_events;. elem is the list
 * element in event_list which holds the event.  Pass a nonzero for notify if
 * you want the program owning the event to be notified that it has been
 * cancelled */
int msevent_cancel(mspool *nsp, msevent *nse, gh_list_t *event_list,
                   gh_lnode_t *elem, int notify) {
  if (nse->event_done) {
    /* This event has already been marked for death somewhere else -- it will be
     * gone soon (and if we try to kill it now all hell will break loose due to
     * reentrancy. */
    return 0;
  }

  nsock_log_info(nsp, "msevent_cancel on event #%li (type %s)",
                 nse->id, nse_type2str(nse->type));

  /* Now that we found the event... we go through the motions of cleanly
   * cancelling it */
  switch (nse->type) {
    case NSE_TYPE_CONNECT:
    case NSE_TYPE_CONNECT_SSL:
      handle_connect_result(nsp, nse, NSE_STATUS_CANCELLED);
      break;

    case NSE_TYPE_READ:
      handle_read_result(nsp, nse, NSE_STATUS_CANCELLED);
      break;

    case NSE_TYPE_WRITE:
      handle_write_result(nsp, nse, NSE_STATUS_CANCELLED);
      break;

    case NSE_TYPE_TIMER:
      handle_timer_result(nsp, nse, NSE_STATUS_CANCELLED);
      break;

#if HAVE_PCAP
    case NSE_TYPE_PCAP_READ:
      handle_pcap_read_result(nsp, nse, NSE_STATUS_CANCELLED);
      break;
#endif

    default:
      fatal("Invalid nsock event type (%d)", nse->type);
  }

  assert(nse->event_done);

  if (nse->timeout.tv_sec)
    gh_heap_remove(&nsp->expirables, &nse->expire);

  if (event_list) {
    update_first_events(nse);
    gh_list_remove(event_list, elem);
  }

  gh_list_append(&nsp->free_events, &nse->nodeq_io);

  nsock_log_debug_all(nsp, "NSE #%lu: Removing event from list", nse->id);

#if HAVE_PCAP
#if PCAP_BSD_SELECT_HACK
  if (nse->type == NSE_TYPE_PCAP_READ) {
    nsock_log_debug_all(nsp, "PCAP NSE #%lu: CANCEL TEST pcap=%p read=%p curr=%p sd=%i",
                        nse->id, &nsp->pcap_read_events, &nsp->read_events,
                        event_list,((mspcap *)nse->iod->pcap)->pcap_desc);

    /* If event occurred, and we're in BSD_HACK mode, then this event was added to
     * two queues. read_event and pcap_read_event Of course we should
     * destroy it only once.  I assume we're now in read_event, so just unlink
     * this event from pcap_read_event */
    if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0 && event_list == &nsp->read_events) {
      /* event is done, list is read_events and we're in BSD_HACK mode. So unlink
       * event from pcap_read_events */
      gh_list_remove(&nsp->pcap_read_events, &nse->nodeq_pcap);
      nsock_log_debug_all(nsp, "PCAP NSE #%lu: Removing event from PCAP_READ_EVENTS", nse->id);
    }

    if (((mspcap *)nse->iod->pcap)->pcap_desc >= 0 && event_list == &nsp->pcap_read_events) {
      /* event is done, list is read_events and we're in BSD_HACK mode.
       * So unlink event from read_events */
      gh_list_remove(&nsp->read_events, &nse->nodeq_io);

      nsock_log_debug_all(nsp, "PCAP NSE #%lu: Removing event from READ_EVENTS", nse->id);
    }
  }
#endif
#endif
  msevent_dispatch_and_delete(nsp, nse, notify);
  return 1;
}
/* If nsp_new returned success, you must free the nsp when you are done with it
 * to conserve memory (and in some cases, sockets).  After this call, nsp may no
 * longer be used.  Any pending events are sent an NSE_STATUS_KILL callback and
 * all outstanding iods are deleted. */
void nsp_delete(nsock_pool ms_pool) {
  mspool *nsp = (mspool *)ms_pool;
  msevent *nse;
  msiod *nsi;
  int i;
  gh_lnode_t *current, *next;
  gh_list_t *event_lists[] = {
    &nsp->connect_events,
    &nsp->read_events,
    &nsp->write_events,
#if HAVE_PCAP
    &nsp->pcap_read_events,
#endif
    NULL
  };

  assert(nsp);

  /* First I go through all the events sending NSE_STATUS_KILL */
  for (i = 0; event_lists[i] != NULL; i++) {
    while (gh_list_count(event_lists[i]) > 0) {
      gh_lnode_t *lnode = gh_list_pop(event_lists[i]);

      assert(lnode);

#if HAVE_PCAP
      if (event_lists[i] == &nsp->pcap_read_events)
        nse = lnode_msevent2(lnode);
      else
#endif
        nse = lnode_msevent(lnode);

      assert(nse);

      nse->status = NSE_STATUS_KILL;
      nsock_trace_handler_callback(nsp, nse);
      nse->handler(nsp, nse, nse->userdata);

      if (nse->iod) {
        nse->iod->events_pending--;
        assert(nse->iod->events_pending >= 0);
      }
      msevent_delete(nsp, nse);
    }
    gh_list_free(event_lists[i]);
  }

  /* Kill timers too, they're not in event lists */
  while (gh_heap_count(&nsp->expirables) > 0) {
    gh_hnode_t *hnode;

    hnode = gh_heap_pop(&nsp->expirables);
    nse = container_of(hnode, msevent, expire);

    if (nse->type == NSE_TYPE_TIMER) {
      nse->status = NSE_STATUS_KILL;
      nsock_trace_handler_callback(nsp, nse);
      nse->handler(nsp, nse, nse->userdata);
      msevent_delete(nsp, nse);
      gh_list_append(&nsp->free_events, &nse->nodeq_io);
    }
  }

  gh_heap_free(&nsp->expirables);

  /* foreach msiod */
  for (current = gh_list_first_elem(&nsp->active_iods);
       current != NULL;
       current = next) {
    next = gh_lnode_next(current);
    nsi = container_of(current, msiod, nodeq);

    nsi_delete(nsi, NSOCK_PENDING_ERROR);

    gh_list_remove(&nsp->active_iods, current);
    gh_list_prepend(&nsp->free_iods, &nsi->nodeq);
  }

  /* Now we free all the memory in the free iod list */
  while ((current = gh_list_pop(&nsp->free_iods))) {
    nsi = container_of(current, msiod, nodeq);
    free(nsi);
  }

  while ((current = gh_list_pop(&nsp->free_events))) {
    nse = lnode_msevent(current);
    free(nse);
  }

  gh_list_free(&nsp->active_iods);
  gh_list_free(&nsp->free_iods);
  gh_list_free(&nsp->free_events);

  nsock_engine_destroy(nsp);

#if HAVE_OPENSSL
  if (nsp->sslctx != NULL)
    SSL_CTX_free(nsp->sslctx);
#endif

  free(nsp);
}
Пример #10
0
int main(int argc, char *argv[]) {
  gh_list lists[16];
  gh_list_elem *current, *next;
  int num = 0;
  int ret;
  int i;

  for(i=0; i < 16; i++)
    gh_list_init(&lists[i]);

  for(num=25000; num < 50000; num++) {
    for(i=0; i < 16; i++) {
      gh_list_append(&lists[i], (void *)num);
    }
  }

  for(num=24999; num >= 0; num--) {
    for(i=0; i < 16; i++) {
      gh_list_prepend(&lists[i], (void *)num);
    }
  }

  for(num=0; num < 50000; num++) {
    for(i=0; i < 16; i++) {
      ret = (int) gh_list_pop(&lists[i]);
      if (ret != num)
	fatal("prepend_test: Bogus return value %d when expected %d\n", ret, num);
    }
  }
  for(i=0; i < 16; i++) {
    ret = (int) gh_list_pop(&lists[i]);
    if (ret != 0)
      fatal("Ret is bogus for list %d", i);
  }

  printf("Done with first set\n");

  for(num=24999; num >= 0; num--) {
    for(i=0; i < 16; i++) {
      gh_list_prepend(&lists[i], (void *)num);
    }
  }

  for(num=25000; num < 50000; num++) {
    for(i=0; i < 16; i++) {
      gh_list_append(&lists[i], (void *)num);
    }
  }

  for(num=0; num < 50000; num++) {
    for(i=0; i < 16; i++) {
      ret = (int) gh_list_pop(&lists[i]);
      if (ret != num)
	fatal("prepend_test: Bogus return value %d when expected %d\n", ret, num);
    }
  }

  printf("Done with second set\n");
  for(num=25000; num < 50000; num++) {
    for(i=0; i < 16; i++) {
      gh_list_append(&lists[i], (void *)num);
    }
  }

  for(num=24999; num >= 0; num--) {
    for(i=0; i < 16; i++) {
      gh_list_prepend(&lists[i], (void *)num);
    }
  }

  for(num=0; num < 50000; num++) {
    for(i=0; i < 16; i++) {
      ret = (int) gh_list_pop(&lists[i]);
      if (ret != num)
	fatal("prepend_test: Bogus return value %d when expected %d\n", ret, num);
    }
  }

  printf("Done with third set ...\n");

  for(num=24999; num >= 0; num--) {
    for(i=0; i < 16; i++) {
      gh_list_prepend(&lists[i], (void *)num);
    }
  }

  for(num=25000; num < 50000; num++) {
    for(i=0; i < 16; i++) {
      gh_list_append(&lists[i], (void *)num);
    }
  }

  for(i=0; i < 16; i++) {
    num=0;
    for(current = GH_LIST_FIRST_ELEM(&lists[i]); current;
	current = next) {
      next = GH_LIST_ELEM_NEXT(current);
      if ((int)GH_LIST_ELEM_DATA(current) != num)
	fatal("Got %d when I expected %d\n", (int)GH_LIST_ELEM_DATA(current), num);
      gh_list_remove_elem(&lists[i], current);
      num++;
    }
    if (num != 50000)
      fatal("Number is %d, even though %d was expected", num, 50000);

    if (GH_LIST_COUNT(&lists[i]) != 0) {
      fatal("List should be empty, but instead it has %d members!\n", GH_LIST_COUNT(&lists[i]));
    }
  }

  printf("Done with fourth set, freeing buffers\n");
  for(i=0; i < 16; i++) {
    gh_list_free(&lists[i]);
  }
}