Track in-progress LISTs by next hash bucket to send, rather than a
[ircu2.10.12-pk.git] / ircd / hash.c
1 /*
2  * IRC - Internet Relay Chat, ircd/hash.c
3  * Copyright (C) 1998 Andrea Cocito, completely rewritten version.
4  * Previous version was Copyright (C) 1991 Darren Reed, the concept
5  * of linked lists for each hash bucket and the move-to-head 
6  * optimization has been borrowed from there.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 1, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  */
22 #include "config.h"
23
24 #include "hash.h"
25 #include "client.h"
26 #include "channel.h"
27 #include "ircd_alloc.h"
28 #include "ircd_chattr.h"
29 #include "ircd_reply.h"
30 #include "ircd_string.h"
31 #include "ircd.h"
32 #include "msg.h"
33 #include "numeric.h"
34 #include "random.h"
35 #include "send.h"
36 #include "struct.h"
37 #include "sys.h"
38
39 #include <assert.h>
40 #include <limits.h>
41 #include <stdlib.h>
42 #include <string.h>
43
44
45 /** @file
46  * @brief Hash table management.
47  * @version $Id$
48  *
49  * This file used to use some very complicated hash function.  Now it
50  * uses CRC-32, but effectively remaps each input byte according to a
51  * table initialized at startup.
52  */
53
54 /** Hash table for clients. */
55 static struct Client *clientTable[HASHSIZE];
56 /** Hash table for channels. */
57 static struct Channel *channelTable[HASHSIZE];
58 /** CRC-32 update table. */
59 static uint32_t crc32hash[256];
60
61 /** Initialize the map used by the hash function. */
62 void init_hash(void)
63 {
64   unsigned int ii, jj, rand, poly;
65
66   /* First calculate a normal CRC-32 table. */
67   for (ii = 0, poly = 0xedb88320; ii < 256; ii++)
68   {
69     rand = ii;
70     for (jj = 0; jj < 8; jj++)
71       rand = (rand & 1) ? poly ^ (rand >> 1) : rand >> 1;
72     crc32hash[ii] = rand;
73   }
74
75   /* Now reorder the hash table. */
76   for (ii = 0, rand = 0; ii < 256; ii++)
77   {
78     if (!rand)
79       rand = ircrandom();
80     poly = ii + rand % (256 - ii);
81     jj = crc32hash[ii];
82     crc32hash[ii] = crc32hash[poly];
83     crc32hash[poly] = jj;
84     rand >>= 8;
85   }
86 }
87
88 /** Output type of hash function. */
89 typedef unsigned int HASHREGS;
90
91 /** Calculate hash value for a string.
92  * @param[in] n String to hash.
93  * @return Hash value for string.
94  */
95 static HASHREGS strhash(const char *n)
96 {
97   HASHREGS hash = crc32hash[ToLower(*n++) & 255];
98   while (*n)
99     hash = (hash >> 8) ^ crc32hash[(hash ^ ToLower(*n++)) & 255];
100   return hash % HASHSIZE;
101 }
102
103 /************************** Externally visible functions ********************/
104
105 /* Optimization note: in these functions I supposed that the CSE optimization
106  * (Common Subexpression Elimination) does its work decently, this means that
107  * I avoided introducing new variables to do the work myself and I did let
108  * the optimizer play with more free registers, actual tests proved this
109  * solution to be faster than doing things like tmp2=tmp->hnext... and then
110  * use tmp2 myself wich would have given less freedom to the optimizer.
111  */
112
113 /** Prepend a client to the appropriate hash bucket.
114  * @param[in] cptr Client to add to hash table.
115  * @return Zero.
116  */
117 int hAddClient(struct Client *cptr)
118 {
119   HASHREGS hashv = strhash(cli_name(cptr));
120
121   cli_hnext(cptr) = clientTable[hashv];
122   clientTable[hashv] = cptr;
123
124   return 0;
125 }
126
127 /** Prepend a channel to the appropriate hash bucket.
128  * @param[in] chptr Channel to add to hash table.
129  * @return Zero.
130  */
131 int hAddChannel(struct Channel *chptr)
132 {
133   HASHREGS hashv = strhash(chptr->chname);
134
135   chptr->hnext = channelTable[hashv];
136   channelTable[hashv] = chptr;
137
138   return 0;
139 }
140
141 /** Remove a client from its hash bucket.
142  * @param[in] cptr Client to remove from hash table.
143  * @return Zero if the client is found and removed, -1 if not found.
144  */
145 int hRemClient(struct Client *cptr)
146 {
147   HASHREGS hashv = strhash(cli_name(cptr));
148   struct Client *tmp = clientTable[hashv];
149
150   if (tmp == cptr) {
151     clientTable[hashv] = cli_hnext(cptr);
152     cli_hnext(cptr) = cptr;
153     return 0;
154   }
155
156   while (tmp) {
157     if (cli_hnext(tmp) == cptr) {
158       cli_hnext(tmp) = cli_hnext(cli_hnext(tmp));
159       cli_hnext(cptr) = cptr;
160       return 0;
161     }
162     tmp = cli_hnext(tmp);
163   }
164   return -1;
165 }
166
167 /** Rename a client in the hash table.
168  * @param[in] cptr Client whose nickname is changing.
169  * @param[in] newname New nickname for client.
170  * @return Zero.
171  */
172 int hChangeClient(struct Client *cptr, const char *newname)
173 {
174   HASHREGS newhash = strhash(newname);
175
176   assert(0 != cptr);
177   hRemClient(cptr);
178
179   cli_hnext(cptr) = clientTable[newhash];
180   clientTable[newhash] = cptr;
181   return 0;
182 }
183
184 /** Remove a channel from its hash bucket.
185  * @param[in] chptr Channel to remove from hash table.
186  * @return Zero if the channel is found and removed, -1 if not found.
187  */
188 int hRemChannel(struct Channel *chptr)
189 {
190   HASHREGS hashv = strhash(chptr->chname);
191   struct Channel *tmp = channelTable[hashv];
192
193   if (tmp == chptr) {
194     channelTable[hashv] = chptr->hnext;
195     chptr->hnext = chptr;
196     return 0;
197   }
198
199   while (tmp) {
200     if (tmp->hnext == chptr) {
201       tmp->hnext = tmp->hnext->hnext;
202       chptr->hnext = chptr;
203       return 0;
204     }
205     tmp = tmp->hnext;
206   }
207
208   return -1;
209 }
210
211 /** Find a client by name, filtered by status mask.
212  * If a client is found, it is moved to the top of its hash bucket.
213  * @param[in] name Client name to search for.
214  * @param[in] TMask Bitmask of status bits, any of which are needed to match.
215  * @return Matching client, or NULL if none.
216  */
217 struct Client* hSeekClient(const char *name, int TMask)
218 {
219   HASHREGS hashv      = strhash(name);
220   struct Client *cptr = clientTable[hashv];
221
222   if (cptr) {
223     if (0 == (cli_status(cptr) & TMask) || 0 != ircd_strcmp(name, cli_name(cptr))) {
224       struct Client* prev;
225       while (prev = cptr, cptr = cli_hnext(cptr)) {
226         if ((cli_status(cptr) & TMask) && (0 == ircd_strcmp(name, cli_name(cptr)))) {
227           cli_hnext(prev) = cli_hnext(cptr);
228           cli_hnext(cptr) = clientTable[hashv];
229           clientTable[hashv] = cptr;
230           break;
231         }
232       }
233     }
234   }
235   return cptr;
236 }
237
238 /** Find a channel by name.
239  * If a channel is found, it is moved to the top of its hash bucket.
240  * @param[in] name Channel name to search for.
241  * @return Matching channel, or NULL if none.
242  */
243 struct Channel* hSeekChannel(const char *name)
244 {
245   HASHREGS hashv = strhash(name);
246   struct Channel *chptr = channelTable[hashv];
247
248   if (chptr) {
249     if (0 != ircd_strcmp(name, chptr->chname)) {
250       struct Channel* prev;
251       while (prev = chptr, chptr = chptr->hnext) {
252         if (0 == ircd_strcmp(name, chptr->chname)) {
253           prev->hnext = chptr->hnext;
254           chptr->hnext = channelTable[hashv];
255           channelTable[hashv] = chptr;
256           break;
257         }
258       }
259     }
260   }
261   return chptr;
262
263 }
264
265 /* I will add some useful(?) statistics here one of these days,
266    but not for DEBUGMODE: just to let the admins play with it,
267    coders are able to SIGCORE the server and look into what goes
268    on themselves :-) */
269
270 /** Report hash table statistics to a client.
271  * @param[in] cptr Client that sent us this message.
272  * @param[in] sptr Client that originated the message.
273  * @param[in] parc Number of arguments.
274  * @param[in] parv Argument array.
275  * @return Zero.
276  */
277 int m_hash(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
278 {
279   int max_chain = 0;
280   int buckets   = 0;
281   int count     = 0;
282   struct Client*  cl;
283   struct Channel* ch;
284   int i;
285   
286   sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Hash Table Statistics", sptr);
287
288   for (i = 0; i < HASHSIZE; ++i) {
289     if ((cl = clientTable[i])) {
290       int len = 0;
291       ++buckets;
292       for ( ; cl; cl = cli_hnext(cl))
293         ++len; 
294       if (len > max_chain)
295         max_chain = len;
296       count += len;
297     }
298   } 
299
300   sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Client: entries: %d buckets: %d "
301                 "max chain: %d", sptr, count, buckets, max_chain);
302
303   buckets = 0;
304   count   = 0;
305   max_chain = 0;
306
307   for (i = 0; i < HASHSIZE; ++i) {
308     if ((ch = channelTable[i])) {
309       int len = 0;
310       ++buckets;
311       for ( ; ch; ch = ch->hnext)
312         ++len; 
313       if (len > max_chain)
314         max_chain = len;
315       count += len;
316     }
317   } 
318
319   sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Channel: entries: %d buckets: %d "
320                 "max chain: %d", sptr, count, buckets, max_chain);
321   return 0;
322 }
323
324 /* Nick jupe utilities, these are in a static hash table with entry/bucket
325    ratio of one, collision shift up and roll in a circular fashion, the 
326    lowest 12 bits of the hash value are used, deletion is not supported,
327    only addition, test for existence and cleanup of the table are.. */
328
329 /** Number of bits in jupe hash value. */
330 #define JUPEHASHBITS 12         /* 4096 entries, 64 nick jupes allowed */
331 /** Size of jupe hash table. */
332 #define JUPEHASHSIZE (1<<JUPEHASHBITS)
333 /** Bitmask to select into jupe hash table. */
334 #define JUPEHASHMASK (JUPEHASHSIZE-1)
335 /** Maximum number of jupes allowed. */
336 #define JUPEMAX      (1<<(JUPEHASHBITS-6))
337
338 /** Hash table for jupes. */
339 static char jupeTable[JUPEHASHSIZE][NICKLEN + 1];       /* About 40k */
340 /** Count of jupes. */
341 static int jupesCount;
342
343 /** Check whether a nickname is juped.
344  * @param[in] nick Nickname to check.
345  * @return Non-zero of the nickname is juped, zero if not.
346  */
347 int isNickJuped(const char *nick)
348 {
349   int pos;
350
351   if (nick && *nick) {
352     for (pos = strhash(nick); (pos &= JUPEHASHMASK), jupeTable[pos][0]; pos++) {
353       if (0 == ircd_strcmp(nick, jupeTable[pos]))
354         return 1;
355     }
356   }
357   return 0;                     /* A bogus pointer is NOT a juped nick, right ? :) */
358 }
359
360 /** Add a comma-separated list of nick jupes.
361  * @param[in] nicks List of nicks to jupe, separated by commas.
362  * @return Zero on success, non-zero on error.
363  */
364 int addNickJupes(const char *nicks)
365 {
366   static char temp[BUFSIZE + 1];
367   char* one;
368   char* p;
369   int   pos;
370
371   if (nicks && *nicks)
372   {
373     ircd_strncpy(temp, nicks, BUFSIZE);
374     temp[BUFSIZE] = '\0';
375     p = NULL;
376     for (one = ircd_strtok(&p, temp, ","); one; one = ircd_strtok(&p, NULL, ","))
377     {
378       if (!*one)
379         continue;
380       pos = strhash(one);
381 loop:
382       pos &= JUPEHASHMASK;
383       if (!jupeTable[pos][0])
384       {
385         if (jupesCount == JUPEMAX)
386           return 1;             /* Error: Jupe table is full ! */
387         jupesCount++;
388         ircd_strncpy(jupeTable[pos], one, NICKLEN);
389         jupeTable[pos][NICKLEN] = '\000';       /* Better safe than sorry :) */
390         continue;
391       }
392       if (0 == ircd_strcmp(one, jupeTable[pos]))
393         continue;
394       ++pos;
395       goto loop;
396     }
397   }
398   return 0;
399 }
400
401 /** Empty the table of juped nicknames. */
402 void clearNickJupes(void)
403 {
404   int i;
405   jupesCount = 0;
406   for (i = 0; i < JUPEHASHSIZE; i++)
407     jupeTable[i][0] = '\000';
408 }
409
410 /** Send more channels to a client in mid-LIST.
411  * @param[in] cptr Client to send the list to.
412  */
413 void list_next_channels(struct Client *cptr)
414 {
415   struct ListingArgs *args;
416   struct Channel *chptr;
417
418   /* Walk consecutive buckets until we hit the end. */
419   for (args = cli_listing(cptr); args->bucket < HASHSIZE; args->bucket++)
420   {
421     /* Send all the matching channels in the bucket. */
422     for (chptr = channelTable[args->bucket]; chptr; chptr = chptr->hnext)
423     {
424       if (chptr->users > args->min_users
425           && chptr->users < args->max_users
426           && chptr->creationtime > args->min_time
427           && chptr->creationtime < args->max_time
428           && (!(args->flags & LISTARG_TOPICLIMITS)
429               || (chptr->topic[0]
430                   && chptr->topic_time > args->min_topic_time
431                   && chptr->topic_time < args->max_topic_time))
432           && ((args->flags & LISTARG_SHOWSECRET)
433               || ShowChannel(cptr, chptr)))
434       {
435         send_reply(cptr, RPL_LIST, chptr->chname, chptr->users, chptr->topic);
436       }
437     }
438     /* If, at the end of the bucket, client sendq is more than half
439      * full, stop. */
440     if (MsgQLength(&cli_sendQ(cptr)) > cli_max_sendq(cptr) / 2)
441       break;
442   }
443
444   /* If we did all buckets, clean the client and send RPL_LISTEND. */
445   if (args->bucket >= HASHSIZE)
446   {
447     MyFree(cli_listing(cptr));
448     cli_listing(cptr) = NULL;
449     send_reply(cptr, RPL_LISTEND);
450   }
451 }