/* * IRC - Internet Relay Chat, ircd/hash.c * Copyright (C) 1998 Andrea Cocito, completely rewritten version. * Previous version was Copyright (C) 1991 Darren Reed, the concept * of linked lists for each hash bucket and the move-to-head * optimization has been borrowed from there. * * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "config.h" #include "hash.h" #include "client.h" #include "channel.h" #include "ircd_alloc.h" #include "ircd_chattr.h" #include "ircd_features.h" #include "ircd_log.h" #include "ircd_reply.h" #include "ircd_string.h" #include "ircd.h" #include "match.h" #include "msg.h" #include "numeric.h" #include "random.h" #include "send.h" #include "struct.h" #include "sys.h" #include "watch.h" /* #include -- Now using assert in ircd_log.h */ #include #include #include /** @file * @brief Hash table management. * @version $Id: hash.c 1841 2007-11-05 03:01:34Z entrope $ * * This file used to use some very complicated hash function. Now it * uses CRC-32, but effectively remaps each input byte according to a * table initialized at startup. */ /** Hash table for clients. */ static struct Client *clientTable[HASHSIZE]; /** Hash table for channels. */ static struct Channel *channelTable[HASHSIZE]; /** Hash table for watches. */ static struct Watch *watchTable[HASHSIZE]; /** CRC-32 update table. */ static uint32_t crc32hash[256]; /** Initialize the map used by the hash function. */ void init_hash(void) { unsigned int ii, jj, rand, poly; /* First calculate a normal CRC-32 table. */ for (ii = 0, poly = 0xedb88320; ii < 256; ii++) { rand = ii; for (jj = 0; jj < 8; jj++) rand = (rand & 1) ? poly ^ (rand >> 1) : rand >> 1; crc32hash[ii] = rand; } /* Now reorder the hash table. */ for (ii = 0, rand = 0; ii < 256; ii++) { if (!rand) rand = ircrandom(); poly = ii + rand % (256 - ii); jj = crc32hash[ii]; crc32hash[ii] = crc32hash[poly]; crc32hash[poly] = jj; rand >>= 8; } } /** Output type of hash function. */ typedef unsigned int HASHREGS; /** Calculate hash value for a string. * @param[in] n String to hash. * @return Hash value for string. */ static HASHREGS strhash(const char *n) { HASHREGS hash = crc32hash[ToLower(*n++) & 255]; while (*n) hash = (hash >> 8) ^ crc32hash[(hash ^ ToLower(*n++)) & 255]; return hash % HASHSIZE; } /************************** 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. */ /** Prepend a client to the appropriate hash bucket. * @param[in] cptr Client to add to hash table. * @return Zero. */ int hAddClient(struct Client *cptr) { HASHREGS hashv = strhash(cli_name(cptr)); cli_hnext(cptr) = clientTable[hashv]; clientTable[hashv] = cptr; return 0; } /** Prepend a channel to the appropriate hash bucket. * @param[in] chptr Channel to add to hash table. * @return Zero. */ int hAddChannel(struct Channel *chptr) { HASHREGS hashv = strhash(chptr->chname); chptr->hnext = channelTable[hashv]; channelTable[hashv] = chptr; return 0; } /** Remove a client from its hash bucket. * @param[in] cptr Client to remove from hash table. * @return Zero if the client is found and removed, -1 if not found. */ int hRemClient(struct Client *cptr) { HASHREGS hashv = strhash(cli_name(cptr)); struct Client *tmp = clientTable[hashv]; if (tmp == cptr) { clientTable[hashv] = cli_hnext(cptr); cli_hnext(cptr) = cptr; return 0; } while (tmp) { if (cli_hnext(tmp) == cptr) { cli_hnext(tmp) = cli_hnext(cli_hnext(tmp)); cli_hnext(cptr) = cptr; return 0; } tmp = cli_hnext(tmp); } return -1; } /** Rename a client in the hash table. * @param[in] cptr Client whose nickname is changing. * @param[in] newname New nickname for client. * @return Zero. */ int hChangeClient(struct Client *cptr, const char *newname) { HASHREGS newhash = strhash(newname); assert(0 != cptr); hRemClient(cptr); cli_hnext(cptr) = clientTable[newhash]; clientTable[newhash] = cptr; return 0; } /** Remove a channel from its hash bucket. * @param[in] chptr Channel to remove from hash table. * @return Zero if the channel is found and removed, -1 if not found. */ int hRemChannel(struct Channel *chptr) { HASHREGS hashv = strhash(chptr->chname); struct Channel *tmp = channelTable[hashv]; if (tmp == chptr) { channelTable[hashv] = chptr->hnext; chptr->hnext = chptr; return 0; } while (tmp) { if (tmp->hnext == chptr) { tmp->hnext = tmp->hnext->hnext; chptr->hnext = chptr; return 0; } tmp = tmp->hnext; } return -1; } /** Find a client by name, filtered by status mask. * If a client is found, it is moved to the top of its hash bucket. * @param[in] name Client name to search for. * @param[in] TMask Bitmask of status bits, any of which are needed to match. * @return Matching client, or NULL if none. */ struct Client* hSeekClient(const char *name, int TMask) { HASHREGS hashv = strhash(name); struct Client *cptr = clientTable[hashv]; if (cptr) { if (0 == (cli_status(cptr) & TMask) || 0 != ircd_strcmp(name, cli_name(cptr))) { struct Client* prev; while (prev = cptr, cptr = cli_hnext(cptr)) { if ((cli_status(cptr) & TMask) && (0 == ircd_strcmp(name, cli_name(cptr)))) { cli_hnext(prev) = cli_hnext(cptr); cli_hnext(cptr) = clientTable[hashv]; clientTable[hashv] = cptr; break; } } } } return cptr; } /** Find a channel by name. * If a channel is found, it is moved to the top of its hash bucket. * @param[in] name Channel name to search for. * @return Matching channel, or NULL if none. */ struct Channel* hSeekChannel(const char *name) { HASHREGS hashv = strhash(name); struct Channel *chptr = channelTable[hashv]; if (chptr) { if (0 != ircd_strcmp(name, chptr->chname)) { struct Channel* prev; while (prev = chptr, chptr = chptr->hnext) { if (0 == ircd_strcmp(name, chptr->chname)) { prev->hnext = chptr->hnext; chptr->hnext = channelTable[hashv]; channelTable[hashv] = chptr; break; } } } } return chptr; } /* I will add some useful(?) statistics here one of these days, but not for DEBUGMODE: just to let the admins play with it, coders are able to SIGCORE the server and look into what goes on themselves :-) */ /** Report hash table statistics to a client. * @param[in] cptr Client that sent us this message. * @param[in] sptr Client that originated the message. * @param[in] parc Number of arguments. * @param[in] parv Argument array. * @return Zero. */ int m_hash(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) { int max_chain = 0; int buckets = 0; int count = 0; struct Client* cl; struct Channel* ch; int i; sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Hash Table Statistics", sptr); for (i = 0; i < HASHSIZE; ++i) { if ((cl = clientTable[i])) { int len = 0; ++buckets; for ( ; cl; cl = cli_hnext(cl)) ++len; if (len > max_chain) max_chain = len; count += len; } } sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Client: entries: %d buckets: %d " "max chain: %d", sptr, count, buckets, max_chain); buckets = 0; count = 0; max_chain = 0; for (i = 0; i < HASHSIZE; ++i) { if ((ch = channelTable[i])) { int len = 0; ++buckets; for ( ; ch; ch = ch->hnext) ++len; if (len > max_chain) max_chain = len; count += len; } } sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Channel: entries: %d buckets: %d " "max chain: %d", sptr, count, buckets, max_chain); return 0; } /* Nick jupe utilities, these are in a static hash table with entry/bucket ratio of one, collision shift up and roll in a circular fashion, the lowest 12 bits of the hash value are used, deletion is not supported, only addition, test for existence and cleanup of the table are.. */ /** Number of bits in jupe hash value. */ #define JUPEHASHBITS 12 /* 4096 entries, 64 nick jupes allowed */ /** Size of jupe hash table. */ #define JUPEHASHSIZE (1<bucket < HASHSIZE; args->bucket++) { /* Send all the matching channels in the bucket. */ for (chptr = channelTable[args->bucket]; chptr; chptr = chptr->hnext) { if (chptr->users >= args->min_users && chptr->users <= args->max_users && chptr->creationtime >= args->min_time && chptr->creationtime <= args->max_time && (!args->wildcard[0] || (args->flags & LISTARG_NEGATEWILDCARD) || (!match(args->wildcard, chptr->chname))) && (!(args->flags & LISTARG_NEGATEWILDCARD) || match(args->wildcard, chptr->chname)) && (!(args->flags & LISTARG_TOPICLIMITS) || (chptr->topic[0] && chptr->topic_time >= args->min_topic_time && chptr->topic_time <= args->max_topic_time)) && ((args->flags & LISTARG_SHOWSECRET) || (ShowChannel(cptr, chptr) || IsInvited(cptr, chptr) || (!EmptyString(feature_str(FEAT_LIST_PRIVATE_CHANNELS)) && HiddenChannel(chptr))))) { if (!ShowChannel(cptr, chptr) && !IsInvited(cptr, chptr) && !(args->flags & LISTARG_SHOWSECRET)) { send_reply(cptr, RPL_LIST, feature_str(FEAT_LIST_PRIVATE_CHANNELS), chptr->users, ""); } else if (args->flags & LISTARG_SHOWMODES) { char modebuf[MODEBUFLEN]; char parabuf[MODEBUFLEN]; modebuf[0] = modebuf[1] = parabuf[0] = '\0'; channel_modes(cptr, modebuf, parabuf, sizeof(parabuf), chptr, NULL); send_reply(cptr, RPL_LIST | SND_EXPLICIT, "%s %u :[%s%s%s] %s", chptr->chname, chptr->users, modebuf, (parabuf[0] ? " " : ""), parabuf, chptr->topic); } else { send_reply(cptr, RPL_LIST, chptr->chname, chptr->users, chptr->topic); } } } /* If, at the end of the bucket, client sendq is more than half * full, stop. */ if (MsgQLength(&cli_sendQ(cptr)) > cli_max_sendq(cptr) / 2) break; } /* If we did all buckets, clean the client and send RPL_LISTEND. */ if (args->bucket >= HASHSIZE) { MyFree(cli_listing(cptr)); cli_listing(cptr) = NULL; send_reply(cptr, RPL_LISTEND); } } /** Prepend a watch's nick to the appropriate hash bucket. * @param[in] wptr Watch to add to hash table. * @return Zero. */ int hAddWatch(struct Watch *wptr) { register HASHREGS hashv = strhash(wt_nick(wptr)); wt_next(wptr) = watchTable[hashv]; watchTable[hashv] = wptr; return 0; } /** Remove a watch from its hash bucket. * @param[in] wptr Watch to remove from hash table. * @return Zero if the watch is found and removed, -1 if not found. */ int hRemWatch(struct Watch *wptr) { HASHREGS hashv = strhash(wt_nick(wptr)); struct Watch *tmp = watchTable[hashv]; if (tmp == wptr) { watchTable[hashv] = wt_next(wptr); wt_next(wptr) = wptr; return 0; } while (tmp) { if (wt_next(tmp) == wptr) { wt_next(tmp) = wt_next(wt_next(tmp)); wt_next(wptr) = wptr; return 0; } tmp = wt_next(tmp); } return -1; } /** Find a watch by nick. * If a watch's nick is found, it is moved to the top of its hash bucket. * @param[in] nick Watch nick to search for. * @return Matching watch, or NULL if none. */ struct Watch *hSeekWatch(const char *nick) { HASHREGS hashv = strhash(nick); struct Watch *wptr = watchTable[hashv]; if (wptr) { if (0 != ircd_strcmp(nick, wt_nick(wptr))) { struct Watch* prev; while (prev = wptr, wptr = wt_next(wptr)) { if (0 == ircd_strcmp(nick, wt_nick(wptr))) { wt_next(prev) = wt_next(wptr); wt_next(wptr) = watchTable[hashv]; watchTable[hashv] = wptr; break; } } } } return wptr; }