summaryrefslogtreecommitdiff
path: root/src/hash.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hash.c')
-rw-r--r--src/hash.c907
1 files changed, 907 insertions, 0 deletions
diff --git a/src/hash.c b/src/hash.c
new file mode 100644
index 0000000..d0b0a55
--- /dev/null
+++ b/src/hash.c
@@ -0,0 +1,907 @@
+/*
+ * ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd).
+ * hash.c: Maintains hashtables.
+ *
+ * Copyright (C) 2002 by the past and present ircd coders, and others.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * $Id$
+ */
+
+#include "stdinc.h"
+#include "list.h"
+#include "balloc.h"
+#include "conf.h"
+#include "channel.h"
+#include "channel_mode.h"
+#include "client.h"
+#include "modules.h"
+#include "hash.h"
+#include "resv.h"
+#include "rng_mt.h"
+#include "userhost.h"
+#include "irc_string.h"
+#include "ircd.h"
+#include "numeric.h"
+#include "send.h"
+#include "memory.h"
+#include "dbuf.h"
+#include "s_user.h"
+
+
+static BlockHeap *userhost_heap = NULL;
+static BlockHeap *namehost_heap = NULL;
+
+static unsigned int hashf_xor_key = 0;
+
+/* The actual hash tables, They MUST be of the same HASHSIZE, variable
+ * size tables could be supported but the rehash routine should also
+ * rebuild the transformation maps, I kept the tables of equal size
+ * so that I can use one hash function.
+ */
+static struct Client *idTable[HASHSIZE];
+static struct Client *clientTable[HASHSIZE];
+static struct Channel *channelTable[HASHSIZE];
+static struct UserHost *userhostTable[HASHSIZE];
+static struct ResvChannel *resvchannelTable[HASHSIZE];
+
+
+/* init_hash()
+ *
+ * inputs - NONE
+ * output - NONE
+ * side effects - Initialize the maps used by hash
+ * functions and clear the tables
+ */
+void
+init_hash(void)
+{
+ /* Default the userhost/namehost sizes to CLIENT_HEAP_SIZE for now,
+ * should be a good close approximation anyway
+ * - Dianora
+ */
+ userhost_heap = BlockHeapCreate("userhost", sizeof(struct UserHost), CLIENT_HEAP_SIZE);
+ namehost_heap = BlockHeapCreate("namehost", sizeof(struct NameHost), CLIENT_HEAP_SIZE);
+
+ hashf_xor_key = genrand_int32() % 256; /* better than nothing --adx */
+}
+
+/*
+ * New hash function based on the Fowler/Noll/Vo (FNV) algorithm from
+ * http://www.isthe.com/chongo/tech/comp/fnv/
+ *
+ * Here, we use the FNV-1 method, which gives slightly better results
+ * than FNV-1a. -Michael
+ */
+unsigned int
+strhash(const char *name)
+{
+ const unsigned char *p = (const unsigned char *)name;
+ unsigned int hval = FNV1_32_INIT;
+
+ if (*p == '\0')
+ return 0;
+ for (; *p != '\0'; ++p)
+ {
+ hval += (hval << 1) + (hval << 4) + (hval << 7) +
+ (hval << 8) + (hval << 24);
+ hval ^= (ToLower(*p) ^ hashf_xor_key);
+ }
+
+ return (hval >> FNV1_32_BITS) ^ (hval & ((1 << FNV1_32_BITS) - 1));
+}
+
+/************************** Externally visible functions ********************/
+
+/* Optimization note: in these functions I supposed that the CSE optimization
+ * (Common Subexpression Elimination) does its work decently, this means that
+ * I avoided introducing new variables to do the work myself and I did let
+ * the optimizer play with more free registers, actual tests proved this
+ * solution to be faster than doing things like tmp2=tmp->hnext... and then
+ * use tmp2 myself which would have given less freedom to the optimizer.
+ */
+
+/* hash_add_client()
+ *
+ * inputs - pointer to client
+ * output - NONE
+ * side effects - Adds a client's name in the proper hash linked
+ * list, can't fail, client_p must have a non-null
+ * name or expect a coredump, the name is infact
+ * taken from client_p->name
+ */
+void
+hash_add_client(struct Client *client_p)
+{
+ unsigned int hashv = strhash(client_p->name);
+
+ client_p->hnext = clientTable[hashv];
+ clientTable[hashv] = client_p;
+}
+
+/* hash_add_channel()
+ *
+ * inputs - pointer to channel
+ * output - NONE
+ * side effects - Adds a channel's name in the proper hash linked
+ * list, can't fail. chptr must have a non-null name
+ * or expect a coredump. As before the name is taken
+ * from chptr->name, we do hash its entire lenght
+ * since this proved to be statistically faster
+ */
+void
+hash_add_channel(struct Channel *chptr)
+{
+ unsigned int hashv = strhash(chptr->chname);
+
+ chptr->hnextch = channelTable[hashv];
+ channelTable[hashv] = chptr;
+}
+
+void
+hash_add_resv(struct ResvChannel *chptr)
+{
+ unsigned int hashv = strhash(chptr->name);
+
+ chptr->hnext = resvchannelTable[hashv];
+ resvchannelTable[hashv] = chptr;
+}
+
+void
+hash_add_userhost(struct UserHost *userhost)
+{
+ unsigned int hashv = strhash(userhost->host);
+
+ userhost->next = userhostTable[hashv];
+ userhostTable[hashv] = userhost;
+}
+
+void
+hash_add_id(struct Client *client_p)
+{
+ unsigned int hashv = strhash(client_p->id);
+
+ client_p->idhnext = idTable[hashv];
+ idTable[hashv] = client_p;
+}
+
+/* hash_del_id()
+ *
+ * inputs - pointer to client
+ * output - NONE
+ * side effects - Removes an ID from the hash linked list
+ */
+void
+hash_del_id(struct Client *client_p)
+{
+ unsigned int hashv = strhash(client_p->id);
+ struct Client *tmp = idTable[hashv];
+
+ if (tmp != NULL)
+ {
+ if (tmp == client_p)
+ {
+ idTable[hashv] = client_p->idhnext;
+ client_p->idhnext = client_p;
+ }
+ else
+ {
+ while (tmp->idhnext != client_p)
+ if ((tmp = tmp->idhnext) == NULL)
+ return;
+
+ tmp->idhnext = tmp->idhnext->idhnext;
+ client_p->idhnext = client_p;
+ }
+ }
+}
+
+/* hash_del_client()
+ *
+ * inputs - pointer to client
+ * output - NONE
+ * side effects - Removes a Client's name from the hash linked list
+ */
+void
+hash_del_client(struct Client *client_p)
+{
+ unsigned int hashv = strhash(client_p->name);
+ struct Client *tmp = clientTable[hashv];
+
+ if (tmp != NULL)
+ {
+ if (tmp == client_p)
+ {
+ clientTable[hashv] = client_p->hnext;
+ client_p->hnext = client_p;
+ }
+ else
+ {
+ while (tmp->hnext != client_p)
+ if ((tmp = tmp->hnext) == NULL)
+ return;
+
+ tmp->hnext = tmp->hnext->hnext;
+ client_p->hnext = client_p;
+ }
+ }
+}
+
+/* hash_del_userhost()
+ *
+ * inputs - pointer to userhost
+ * output - NONE
+ * side effects - Removes a userhost from the hash linked list
+ */
+void
+hash_del_userhost(struct UserHost *userhost)
+{
+ unsigned int hashv = strhash(userhost->host);
+ struct UserHost *tmp = userhostTable[hashv];
+
+ if (tmp != NULL)
+ {
+ if (tmp == userhost)
+ {
+ userhostTable[hashv] = userhost->next;
+ userhost->next = userhost;
+ }
+ else
+ {
+ while (tmp->next != userhost)
+ if ((tmp = tmp->next) == NULL)
+ return;
+
+ tmp->next = tmp->next->next;
+ userhost->next = userhost;
+ }
+ }
+}
+
+/* hash_del_channel()
+ *
+ * inputs - pointer to client
+ * output - NONE
+ * side effects - Removes the channel's name from the corresponding
+ * hash linked list
+ */
+void
+hash_del_channel(struct Channel *chptr)
+{
+ unsigned int hashv = strhash(chptr->chname);
+ struct Channel *tmp = channelTable[hashv];
+
+ if (tmp != NULL)
+ {
+ if (tmp == chptr)
+ {
+ channelTable[hashv] = chptr->hnextch;
+ chptr->hnextch = chptr;
+ }
+ else
+ {
+ while (tmp->hnextch != chptr)
+ if ((tmp = tmp->hnextch) == NULL)
+ return;
+
+ tmp->hnextch = tmp->hnextch->hnextch;
+ chptr->hnextch = chptr;
+ }
+ }
+}
+
+void
+hash_del_resv(struct ResvChannel *chptr)
+{
+ unsigned int hashv = strhash(chptr->name);
+ struct ResvChannel *tmp = resvchannelTable[hashv];
+
+ if (tmp != NULL)
+ {
+ if (tmp == chptr)
+ {
+ resvchannelTable[hashv] = chptr->hnext;
+ chptr->hnext = chptr;
+ }
+ else
+ {
+ while (tmp->hnext != chptr)
+ if ((tmp = tmp->hnext) == NULL)
+ return;
+
+ tmp->hnext = tmp->hnext->hnext;
+ chptr->hnext = chptr;
+ }
+ }
+}
+
+/* hash_find_client()
+ *
+ * inputs - pointer to name
+ * output - NONE
+ * side effects - New semantics: finds a client whose name is 'name'
+ * if can't find one returns NULL. If it finds one moves
+ * it to the top of the list and returns it.
+ */
+struct Client *
+hash_find_client(const char *name)
+{
+ unsigned int hashv = strhash(name);
+ struct Client *client_p;
+
+ if ((client_p = clientTable[hashv]) != NULL)
+ {
+ if (irccmp(name, client_p->name))
+ {
+ struct Client *prev;
+
+ while (prev = client_p, (client_p = client_p->hnext) != NULL)
+ {
+ if (!irccmp(name, client_p->name))
+ {
+ prev->hnext = client_p->hnext;
+ client_p->hnext = clientTable[hashv];
+ clientTable[hashv] = client_p;
+ break;
+ }
+ }
+ }
+ }
+
+ return client_p;
+}
+
+struct Client *
+hash_find_id(const char *name)
+{
+ unsigned int hashv = strhash(name);
+ struct Client *client_p;
+
+ if ((client_p = idTable[hashv]) != NULL)
+ {
+ if (strcmp(name, client_p->id))
+ {
+ struct Client *prev;
+
+ while (prev = client_p, (client_p = client_p->idhnext) != NULL)
+ {
+ if (!strcmp(name, client_p->id))
+ {
+ prev->idhnext = client_p->idhnext;
+ client_p->idhnext = idTable[hashv];
+ idTable[hashv] = client_p;
+ break;
+ }
+ }
+ }
+ }
+
+ return client_p;
+}
+
+struct Client *
+hash_find_server(const char *name)
+{
+ unsigned int hashv = strhash(name);
+ struct Client *client_p = NULL;
+
+ if (IsDigit(*name) && strlen(name) == IRC_MAXSID)
+ return hash_find_id(name);
+
+ if ((client_p = clientTable[hashv]) != NULL)
+ {
+ if ((!IsServer(client_p) && !IsMe(client_p)) ||
+ irccmp(name, client_p->name))
+ {
+ struct Client *prev;
+
+ while (prev = client_p, (client_p = client_p->hnext) != NULL)
+ {
+ if ((IsServer(client_p) || IsMe(client_p)) &&
+ !irccmp(name, client_p->name))
+ {
+ prev->hnext = client_p->hnext;
+ client_p->hnext = clientTable[hashv];
+ clientTable[hashv] = client_p;
+ break;
+ }
+ }
+ }
+ }
+
+ return client_p;
+}
+
+/* hash_find_channel()
+ *
+ * inputs - pointer to name
+ * output - NONE
+ * side effects - New semantics: finds a channel whose name is 'name',
+ * if can't find one returns NULL, if can find it moves
+ * it to the top of the list and returns it.
+ */
+struct Channel *
+hash_find_channel(const char *name)
+{
+ unsigned int hashv = strhash(name);
+ struct Channel *chptr = NULL;
+
+ if ((chptr = channelTable[hashv]) != NULL)
+ {
+ if (irccmp(name, chptr->chname))
+ {
+ struct Channel *prev;
+
+ while (prev = chptr, (chptr = chptr->hnextch) != NULL)
+ {
+ if (!irccmp(name, chptr->chname))
+ {
+ prev->hnextch = chptr->hnextch;
+ chptr->hnextch = channelTable[hashv];
+ channelTable[hashv] = chptr;
+ break;
+ }
+ }
+ }
+ }
+
+ return chptr;
+}
+
+/* hash_get_bucket(int type, unsigned int hashv)
+ *
+ * inputs - hash value (must be between 0 and HASHSIZE - 1)
+ * output - NONE
+ * returns - pointer to first channel in channelTable[hashv]
+ * if that exists;
+ * NULL if there is no channel in that place;
+ * NULL if hashv is an invalid number.
+ * side effects - NONE
+ */
+void *
+hash_get_bucket(int type, unsigned int hashv)
+{
+ assert(hashv < HASHSIZE);
+ if (hashv >= HASHSIZE)
+ return NULL;
+
+ switch (type)
+ {
+ case HASH_TYPE_ID:
+ return idTable[hashv];
+ break;
+ case HASH_TYPE_CHANNEL:
+ return channelTable[hashv];
+ break;
+ case HASH_TYPE_CLIENT:
+ return clientTable[hashv];
+ break;
+ case HASH_TYPE_USERHOST:
+ return userhostTable[hashv];
+ break;
+ case HASH_TYPE_RESERVED:
+ return resvchannelTable[hashv];
+ break;
+ default:
+ assert(0);
+ }
+
+ return NULL;
+}
+
+/* hash_find_resv()
+ *
+ * inputs - pointer to name
+ * output - NONE
+ * side effects - New semantics: finds a reserved channel whose name is 'name',
+ * if can't find one returns NULL, if can find it moves
+ * it to the top of the list and returns it.
+ */
+struct ResvChannel *
+hash_find_resv(const char *name)
+{
+ unsigned int hashv = strhash(name);
+ struct ResvChannel *chptr;
+
+ if ((chptr = resvchannelTable[hashv]) != NULL)
+ {
+ if (irccmp(name, chptr->name))
+ {
+ struct ResvChannel *prev;
+
+ while (prev = chptr, (chptr = chptr->hnext) != NULL)
+ {
+ if (!irccmp(name, chptr->name))
+ {
+ prev->hnext = chptr->hnext;
+ chptr->hnext = resvchannelTable[hashv];
+ resvchannelTable[hashv] = chptr;
+ break;
+ }
+ }
+ }
+ }
+
+ return chptr;
+}
+
+struct UserHost *
+hash_find_userhost(const char *host)
+{
+ unsigned int hashv = strhash(host);
+ struct UserHost *userhost;
+
+ if ((userhost = userhostTable[hashv]))
+ {
+ if (irccmp(host, userhost->host))
+ {
+ struct UserHost *prev;
+
+ while (prev = userhost, (userhost = userhost->next) != NULL)
+ {
+ if (!irccmp(host, userhost->host))
+ {
+ prev->next = userhost->next;
+ userhost->next = userhostTable[hashv];
+ userhostTable[hashv] = userhost;
+ break;
+ }
+ }
+ }
+ }
+
+ return userhost;
+}
+
+/* count_user_host()
+ *
+ * inputs - user name
+ * - hostname
+ * - int flag 1 if global, 0 if local
+ * - pointer to where global count should go
+ * - pointer to where local count should go
+ * - pointer to where identd count should go (local clients only)
+ * output - none
+ * side effects -
+ */
+void
+count_user_host(const char *user, const char *host, int *global_p,
+ int *local_p, int *icount_p)
+{
+ dlink_node *ptr;
+ struct UserHost *found_userhost;
+ struct NameHost *nameh;
+
+ if ((found_userhost = hash_find_userhost(host)) == NULL)
+ return;
+
+ DLINK_FOREACH(ptr, found_userhost->list.head)
+ {
+ nameh = ptr->data;
+
+ if (!irccmp(user, nameh->name))
+ {
+ if (global_p != NULL)
+ *global_p = nameh->gcount;
+ if (local_p != NULL)
+ *local_p = nameh->lcount;
+ if (icount_p != NULL)
+ *icount_p = nameh->icount;
+ return;
+ }
+ }
+}
+
+/* find_or_add_userhost()
+ *
+ * inputs - host name
+ * output - none
+ * side effects - find UserHost * for given host name
+ */
+static struct UserHost *
+find_or_add_userhost(const char *host)
+{
+ struct UserHost *userhost;
+
+ if ((userhost = hash_find_userhost(host)) != NULL)
+ return userhost;
+
+ userhost = BlockHeapAlloc(userhost_heap);
+ strlcpy(userhost->host, host, sizeof(userhost->host));
+ hash_add_userhost(userhost);
+
+ return userhost;
+}
+
+/* add_user_host()
+ *
+ * inputs - user name
+ * - hostname
+ * - int flag 1 if global, 0 if local
+ * output - none
+ * side effects - add given user@host to hash tables
+ */
+void
+add_user_host(const char *user, const char *host, int global)
+{
+ dlink_node *ptr;
+ struct UserHost *found_userhost;
+ struct NameHost *nameh;
+ int hasident = 1;
+
+ if (*user == '~')
+ {
+ hasident = 0;
+ ++user;
+ }
+
+ if ((found_userhost = find_or_add_userhost(host)) == NULL)
+ return;
+
+ DLINK_FOREACH(ptr, found_userhost->list.head)
+ {
+ nameh = ptr->data;
+
+ if (!irccmp(user, nameh->name))
+ {
+ nameh->gcount++;
+
+ if (!global)
+ {
+ if (hasident)
+ nameh->icount++;
+ nameh->lcount++;
+ }
+
+ return;
+ }
+ }
+
+ nameh = BlockHeapAlloc(namehost_heap);
+ strlcpy(nameh->name, user, sizeof(nameh->name));
+
+ nameh->gcount = 1;
+
+ if (!global)
+ {
+ if (hasident)
+ nameh->icount = 1;
+ nameh->lcount = 1;
+ }
+
+ dlinkAdd(nameh, &nameh->node, &found_userhost->list);
+}
+
+/* delete_user_host()
+ *
+ * inputs - user name
+ * - hostname
+ * - int flag 1 if global, 0 if local
+ * output - none
+ * side effects - delete given user@host to hash tables
+ */
+void
+delete_user_host(const char *user, const char *host, int global)
+{
+ dlink_node *ptr = NULL, *next_ptr = NULL;
+ struct UserHost *found_userhost;
+ struct NameHost *nameh;
+ int hasident = 1;
+
+ if (*user == '~')
+ {
+ hasident = 0;
+ ++user;
+ }
+
+ if ((found_userhost = hash_find_userhost(host)) == NULL)
+ return;
+
+ DLINK_FOREACH_SAFE(ptr, next_ptr, found_userhost->list.head)
+ {
+ nameh = ptr->data;
+
+ if (!irccmp(user, nameh->name))
+ {
+ if (nameh->gcount > 0)
+ nameh->gcount--;
+ if (!global)
+ {
+ if (nameh->lcount > 0)
+ nameh->lcount--;
+ if (hasident && nameh->icount > 0)
+ nameh->icount--;
+ }
+
+ if (nameh->gcount == 0 && nameh->lcount == 0)
+ {
+ dlinkDelete(&nameh->node, &found_userhost->list);
+ BlockHeapFree(namehost_heap, nameh);
+ }
+
+ if (dlink_list_length(&found_userhost->list) == 0)
+ {
+ hash_del_userhost(found_userhost);
+ BlockHeapFree(userhost_heap, found_userhost);
+ }
+
+ return;
+ }
+ }
+}
+
+/*
+ * Safe list code.
+ *
+ * The idea is really quite simple. As the link lists pointed to in
+ * each "bucket" of the channel hash table are traversed atomically
+ * there is no locking needed. Overall, yes, inconsistent reported
+ * state can still happen, but normally this isn't a big deal.
+ * I don't like sticking the code into hash.c but oh well. Moreover,
+ * if a hash isn't used in future, oops.
+ *
+ * - Dianora
+ */
+
+/* exceeding_sendq()
+ *
+ * inputs - pointer to client to check
+ * output - 1 if client is in danger of blowing its sendq
+ * 0 if it is not.
+ * side effects -
+ *
+ * Sendq limit is fairly conservative at 1/2 (In original anyway)
+ */
+static int
+exceeding_sendq(struct Client *to)
+{
+ if (dbuf_length(&to->localClient->buf_sendq) > (get_sendq(to) / 2))
+ return 1;
+ else
+ return 0;
+}
+
+void
+free_list_task(struct ListTask *lt, struct Client *source_p)
+{
+ dlink_node *dl, *dln;
+
+ if ((dl = dlinkFindDelete(&listing_client_list, source_p)) != NULL)
+ free_dlink_node(dl);
+
+ DLINK_FOREACH_SAFE(dl, dln, lt->show_mask.head)
+ {
+ MyFree(dl->data);
+ free_dlink_node(dl);
+ }
+
+ DLINK_FOREACH_SAFE(dl, dln, lt->hide_mask.head)
+ {
+ MyFree(dl->data);
+ free_dlink_node(dl);
+ }
+
+ MyFree(lt);
+
+ if (MyConnect(source_p))
+ source_p->localClient->list_task = NULL;
+}
+
+/* list_allow_channel()
+ *
+ * inputs - channel name
+ * - pointer to a list task
+ * output - 1 if the channel is to be displayed
+ * 0 otherwise
+ * side effects -
+ */
+static int
+list_allow_channel(const char *chname, struct ListTask *lt)
+{
+ dlink_node *dl = NULL;
+
+ DLINK_FOREACH(dl, lt->show_mask.head)
+ if (!match_chan(dl->data, chname))
+ return 0;
+
+ DLINK_FOREACH(dl, lt->hide_mask.head)
+ if (match_chan(dl->data, chname))
+ return 0;
+
+ return 1;
+}
+
+/* list_one_channel()
+ *
+ * inputs - client pointer to return result to
+ * - pointer to channel to list
+ * - pointer to ListTask structure
+ * output - none
+ * side effects -
+ */
+static void
+list_one_channel(struct Client *source_p, struct Channel *chptr,
+ struct ListTask *list_task)
+{
+ if (SecretChannel(chptr) && !IsMember(source_p, chptr))
+ return;
+ if (dlink_list_length(&chptr->members) < list_task->users_min ||
+ dlink_list_length(&chptr->members) > list_task->users_max ||
+ (chptr->channelts != 0 &&
+ ((unsigned int)chptr->channelts < list_task->created_min ||
+ (unsigned int)chptr->channelts > list_task->created_max)) ||
+ (unsigned int)chptr->topic_time < list_task->topicts_min ||
+ (chptr->topic_time ? (unsigned int)chptr->topic_time : UINT_MAX) >
+ list_task->topicts_max)
+ return;
+
+ if (!list_allow_channel(chptr->chname, list_task))
+ return;
+ sendto_one(source_p, form_str(RPL_LIST), me.name, source_p->name,
+ chptr->chname, dlink_list_length(&chptr->members),
+ chptr->topic);
+}
+
+/* safe_list_channels()
+ *
+ * inputs - pointer to client requesting list
+ * output - 0/1
+ * side effects - safely list all channels to source_p
+ *
+ * Walk the channel buckets, ensure all pointers in a bucket are
+ * traversed before blocking on a sendq. This means, no locking is needed.
+ *
+ * N.B. This code is "remote" safe, but is not currently used for
+ * remote clients.
+ *
+ * - Dianora
+ */
+void
+safe_list_channels(struct Client *source_p, struct ListTask *list_task,
+ int only_unmasked_channels)
+{
+ struct Channel *chptr = NULL;
+
+ if (!only_unmasked_channels)
+ {
+ unsigned int i;
+
+ for (i = list_task->hash_index; i < HASHSIZE; ++i)
+ {
+ if (exceeding_sendq(source_p->from))
+ {
+ list_task->hash_index = i;
+ return; /* still more to do */
+ }
+
+ for (chptr = channelTable[i]; chptr; chptr = chptr->hnextch)
+ list_one_channel(source_p, chptr, list_task);
+ }
+ }
+ else
+ {
+ dlink_node *dl;
+
+ DLINK_FOREACH(dl, list_task->show_mask.head)
+ if ((chptr = hash_find_channel(dl->data)) != NULL)
+ list_one_channel(source_p, chptr, list_task);
+ }
+
+ free_list_task(list_task, source_p);
+ sendto_one(source_p, form_str(RPL_LISTEND),
+ me.name, source_p->name);
+}