1 /* $Id: article.c 7285 2005-06-07 06:38:24Z eagle $
3 ** The Article class for innfeed.
5 ** Written by James Brister <brister@vix.com>
7 ** The implementation of the Article class. Articles are the abstraction for
8 ** the actual news articles. They are a reference counted object because they
9 ** need to be shared by multiple Host and Connection objects. When an Article
10 ** is created it verifies that the actual file exists. When a Connection
11 ** wants the article's contents for transmission the Article reads the data
12 ** off disk and returns a set of Buffer objects. The Article holds onto these
13 ** Buffers so that the next Connection that wants to transmit it won't have
14 ** to wait for a disk read to be done again.
20 #include "portable/mmap.h"
31 #include "inn/messages.h"
40 #define VALIDATE_HASH_TABLE() (void (0))
42 #define VALIDATE_HASH_TABLE() hashValidateTable ()
52 struct map_info_s *next;
55 typedef struct map_info_s *MapInfo;
59 int refCount ; /* the reference count on this article */
60 char *fname ; /* the file name of the article */
61 char *msgid ; /* the msgid of the article (INN tells us) */
62 Buffer contents ; /* the buffer of the actual on disk stuff */
63 Buffer *nntpBuffers ; /* list of buffers for transmisson */
64 MapInfo mapInfo; /* arthandle and mMapping */
65 bool loggedMissing ; /* true if article is missing and we logged */
66 bool articleOk ; /* true until we know otherwise. */
67 bool inWireFormat ; /* true if ->contents is \r\n/dot-escaped */
71 struct hash_entry_s *next ;
72 struct hash_entry_s *prev ;
73 struct hash_entry_s *nextTime ;
74 struct hash_entry_s *prevTime ;
79 typedef struct hash_entry_s *HashEntry ;
85 static Buffer artGetContents (Article article) ; /* Return the buffer that
89 /* Log statistics on article memory usage. */
90 static void logArticleStats (TimeoutId id, void *data) ;
92 static bool fillContents (Article article) ; /* Read the article's bits
95 /* Append buffer B to the buffer array BUFFS. */
96 static void appendBuffer (Buffer b, Buffer **buffs, int *newSpot, int *curLen);
98 static bool prepareArticleForNNTP (Article article) ; /* Do the necessary
101 static bool artFreeContents (Article art) ; /* Tell the Article to release
102 its contents buffer if
105 static void artUnmap (Article art) ; /* munmap an mmap()ed
110 * Hash table routine declarations.
113 /* Returns a has value for the given string */
114 static unsigned int hashString (const char *string) ;
116 /* Locates the article with the given message ID, in the has table. */
117 static Article hashFindArticle (const char *msgid) ;
119 /* Puts the given article in the hash table. */
120 static void hashAddArticle (Article article) ;
122 /* Removes the given article from the has table */
123 static bool hashRemoveArticle (Article article) ;
125 /* Does some simple-minded hash table validation */
126 static void hashValidateTable (void) ;
135 static unsigned int missingArticleCount ; /* Number of articles that were missing */
137 static bool logMissingArticles ; /* if true then we log the details on a
140 static unsigned int preparedBytes ; /* The number of areticle bytes read in so
141 far of disk (will wrap around) */
143 static unsigned int preparedNewlines ; /* The number of newlines found in all the
146 static unsigned int avgCharsPerLine ; /* The average number of characters per
147 line over all articles. */
149 static bool rolledOver ; /* true if preparedBytes wrapped around */
151 static unsigned int bytesInUse ; /* count of article contents bytes in use--just
152 the amount read from the article files, not
153 all memory involved in. */
155 static unsigned int maxBytesInUse ; /* the limit we want to stay under for total
156 bytes resident in memory dedicated to
159 static unsigned int articlesInUse ; /* number of articles currently allocated. */
161 static unsigned int byteTotal ; /* number of bytes for article contents
162 allocated totally since last log. */
164 static unsigned int articleTotal ; /* number of articles alloced since last log. */
166 static TimeoutId articleStatsId ; /* The timer callback id. */
168 static MapInfo mapInfo;
174 static HashEntry *hashTable ; /* the has table itself */
175 static HashEntry chronList ; /* chronologically ordered. Points at newest */
177 #define TABLE_SIZE 2048 /* MUST be a power of 2 */
178 #define HASH_MASK (TABLE_SIZE - 1)
179 #define TABLE_ENTRY(hash) ((hash) & HASH_MASK)
183 /*******************************************************************/
184 /** PUBLIC FUNCTIONS **/
185 /*******************************************************************/
187 /* Create a new article object. First looks to see if one with the given
188 message id already exists in the hash table and if so returns that
189 (after incrementing the reference count). */
190 Article newArticle (const char *filename, const char *msgid)
192 Article newArt = NULL ;
194 TMRstart(TMR_NEWARTICLE);
195 if (hashTable == NULL)
196 { /* first-time through initialization. */
199 ASSERT ((TABLE_SIZE & HASH_MASK) == 0) ;
200 hashTable = xmalloc (sizeof(HashEntry) * TABLE_SIZE) ;
202 addPointerFreedOnExit ((char *)hashTable) ;
204 for (i = 0 ; i < TABLE_SIZE ; i++)
205 hashTable [i] = NULL ;
207 if (ARTICLE_STATS_PERIOD > 0)
208 articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0);
211 /* now look for it in the hash table. We presume the disk file is still
213 if ((newArt = hashFindArticle (msgid)) == NULL)
215 newArt = xcalloc (1, sizeof(struct article_s)) ;
217 newArt->fname = xstrdup (filename) ;
218 newArt->msgid = xstrdup (msgid) ;
220 newArt->contents = NULL ;
221 newArt->mapInfo = NULL ;
222 newArt->refCount = 1 ;
223 newArt->loggedMissing = false ;
224 newArt->articleOk = true ;
225 newArt->inWireFormat = false ;
227 d_printf (3,"Adding a new article(%p): %s\n", (void *)newArt, msgid) ;
232 hashAddArticle (newArt) ;
236 if (strcmp (filename,newArt->fname) != 0)
237 warn ("ME two filenames for same article: %s, %s", filename,
241 d_printf (2,"Reusing existing article for %s\nx",msgid) ;
243 TMRstop(TMR_NEWARTICLE);
248 /* Decrement the reference count on the article and free up its memory if
249 the ref count gets to 0. */
250 void delArticle (Article article)
255 ASSERT (article->refCount > 0) ;
257 if (--(article->refCount) == 0)
259 bool removed = hashRemoveArticle (article) ;
261 ASSERT (removed == true) ;
263 d_printf (2,"Cleaning up article (%p): %s\n",
264 (void *)article, article->msgid) ;
266 if (article->contents != NULL)
268 if (article->mapInfo)
271 bytesInUse -= bufferDataSize (article->contents) ;
273 if (article->nntpBuffers != NULL)
274 freeBufferArray (article->nntpBuffers) ;
276 delBuffer (article->contents) ;
281 free (article->fname) ;
282 free (article->msgid) ;
286 VALIDATE_HASH_TABLE () ;
290 void gPrintArticleInfo (FILE *fp, unsigned int indentAmt)
292 char indent [INDENT_BUFFER_SIZE] ;
295 for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
299 fprintf (fp,"%sGlobal Article information : (count %d) {\n",
300 indent, articlesInUse) ;
302 fprintf (fp,"%s missingArticleCount : %d\n",indent,missingArticleCount) ;
303 fprintf (fp,"%s logMissingArticles : %d\n",indent,logMissingArticles) ;
304 fprintf (fp,"%s preparedBytes : %d\n",indent,preparedBytes) ;
305 fprintf (fp,"%s preparedNewlines : %d\n",indent,preparedNewlines) ;
306 fprintf (fp,"%s avgCharsPerLine : %d\n",indent,avgCharsPerLine) ;
307 fprintf (fp,"%s rolledOver : %s\n",indent,boolToString (rolledOver)) ;
308 fprintf (fp,"%s bytesInUse : %d\n",indent,bytesInUse) ;
309 fprintf (fp,"%s maxBytesInUse : %d\n",indent,maxBytesInUse) ;
310 fprintf (fp,"%s articlesInUse : %d\n",indent,articlesInUse) ;
311 fprintf (fp,"%s byteTotal : %d\n",indent,byteTotal) ;
312 fprintf (fp,"%s articleTotal : %d\n",indent,articleTotal) ;
313 fprintf (fp,"%s articleStatsId : %d\n",indent,articleStatsId) ;
318 for (he = chronList ; he != NULL ; he = he->nextTime)
319 printArticleInfo (he->article,fp,indentAmt + INDENT_INCR) ;
322 fprintf (fp,"%s}\n",indent) ;
326 void printArticleInfo (Article art, FILE *fp, unsigned int indentAmt)
329 char indent [INDENT_BUFFER_SIZE] ;
332 for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
336 fprintf (fp,"%sArticle : %p {\n",indent,(void *) art) ;
337 fprintf (fp,"%s article ok : %s\n",indent,boolToString (art->articleOk)) ;
338 fprintf (fp,"%s refcount : %d\n",indent,art->refCount) ;
339 fprintf (fp,"%s filename : %s\n",indent,art->fname) ;
340 fprintf (fp,"%s msgid : %s\n",indent,art->msgid) ;
342 fprintf (fp,"%s contents buffer : {\n",indent) ;
344 printBufferInfo (art->contents,fp,indentAmt + INDENT_INCR) ;
346 fprintf (fp,"%s %p\n",indent,(void *) art->contents) ;
349 fprintf (fp,"%s }\n",indent) ;
351 fprintf (fp,"%s nntp buffers : {\n",indent) ;
352 for (b = art->nntpBuffers ; b != NULL && *b != NULL ; b++)
354 printBufferInfo (*b,fp,indentAmt + INDENT_INCR) ;
356 fprintf (fp,"%s %p\n",indent,(void *) *b) ;
359 fprintf (fp,"%s }\n", indent) ;
361 fprintf (fp,"%s logged missing : %s\n",
362 indent,boolToString (art->loggedMissing));
363 fprintf (fp,"%s}\n", indent) ;
367 /* return true if we have or are able to get the contents off the disk */
368 bool artContentsOk (Article article)
372 if ( prepareArticleForNNTP (article) )
379 /* bump reference count on the article. */
380 Article artTakeRef (Article article)
383 article->refCount++ ;
389 /* return the filename of the article */
390 const char *artFileName (Article article)
395 return article->fname ;
399 /* Get a NULL terminated array of Buffers that is ready for sending via NNTP */
401 Buffer *artGetNntpBuffers (Article article)
403 if ( !prepareArticleForNNTP (article) )
406 return dupBufferArray (article->nntpBuffers) ;
410 /* return the message id of the article */
411 const char *artMsgId (Article article)
413 return article->msgid ;
416 /* return size of the article */
417 int artSize (Article article)
419 if (article == NULL || article->contents == NULL)
421 return (int)bufferDataSize(article->contents);
425 /* return how many NNTP-ready buffers the article contains */
426 unsigned int artNntpBufferCount (Article article)
428 if ( !prepareArticleForNNTP (article) )
431 return bufferArrayLen (article->nntpBuffers) ;
435 /* if VAL is true then all missing articles will be logged. */
436 void artLogMissingArticles (bool val)
438 logMissingArticles = val ;
442 /* set the limit we want to stay under. */
443 void artSetMaxBytesInUse (unsigned int val)
445 ASSERT (maxBytesInUse > 0) ; /* can only set one time. */
448 maxBytesInUse = val ;
452 /**********************************************************************/
453 /** STATIC FUNCTIONS **/
454 /**********************************************************************/
457 /* return a single buffer that contains the disk image of the article (i.e.
458 not fixed up for NNTP). */
459 static Buffer artGetContents (Article article)
463 if (article->articleOk)
465 if (article->contents == NULL)
466 fillContents (article) ;
468 if (article->contents != NULL)
469 rval = bufferTakeRef (article->contents) ;
475 /* arthandle/mMapping needs to be refcounted since a buffer
476 may exist referencing it after delArticle if the remote
477 host sends the return status too soon (diablo, 439). */
479 static MapInfo getMapInfo(ARTHANDLE *arthandle,
480 const void *mMapping, size_t size)
484 for (m = mapInfo; m; m = m->next) {
485 if (m->arthandle == arthandle &&
486 m->mMapping == mMapping) {
492 m = xmalloc(sizeof(struct map_info_s));
494 m->arthandle = arthandle;
495 m->mMapping = mMapping;
503 static void delMapInfo(void *vm)
506 MapInfo m = (MapInfo)vm;
511 if (--(m->refCount) > 0)
515 SMfreearticle(m->arthandle);
517 if (munmap(m->mMapping, m->size) < 0)
518 syslog (LOG_NOTICE, "munmap article: %m");
521 for (i = mapInfo; i != m; i = i->next)
525 prev->next = m->next;
532 static void artUnmap (Article article) {
534 delMapInfo(article->mapInfo);
535 article->mapInfo = NULL;
540 static void logArticleStats (TimeoutId id, void *data UNUSED)
542 ASSERT (id == articleStatsId) ;
544 notice ("ME articles active %d bytes %d", articlesInUse, bytesInUse) ;
545 notice ("ME articles total %d bytes %d", articleTotal, byteTotal) ;
550 articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0) ;
554 /* do the actual read of the article off disk into a Buffer that is stored
555 in the Article object. The Article will end up with its contents field
556 having a buffer with the article data in it. This buffer may be
557 holding a mmapped pointer, or it may be simply a regular buffer with
558 the data read off disk into it. In the regular buffer case the
559 contents may be copied around after reading to insert a carriage
560 return before each newline. */
562 static bool fillContents (Article article)
566 static bool maxLimitNotified ;
568 size_t articlesize = 0;
569 char *buffer = NULL ;
571 size_t idx = 0, amtToRead ;
572 size_t newBufferSize ;
574 ARTHANDLE *arthandle = NULL;
575 const void *mMapping = NULL;
577 ASSERT (article->contents == NULL) ;
579 TMRstart(TMR_READART);
580 if (maxBytesInUse == 0)
581 maxBytesInUse = SOFT_ARTICLE_BYTE_LIMIT ;
583 if (avgCharsPerLine == 0)
584 avgCharsPerLine = 75 ; /* roughly number of characters per line */
586 if (IsToken(article->fname)) {
587 opened = ((arthandle = SMretrieve(TextToToken(article->fname), RETR_ALL)) != NULL) ? true : false;
589 articlesize = arthandle->len;
591 if (SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT) {
592 syslog(LOG_ERR, "Could not retrieve %s: %s",
593 article->fname, SMerrorstr);
594 article->articleOk = false;
595 TMRstop(TMR_READART);
602 opened = ((fd = open (article->fname,O_RDONLY,0)) >= 0) ? true : false;
605 if (fstat (fd, &sb) < 0) {
606 article->articleOk = false ;
607 syswarn ("ME oserr fstat %s", article->fname) ;
608 TMRstop(TMR_READART);
611 if (!S_ISREG (sb.st_mode)) {
612 article->articleOk = false ;
613 warn ("ME article file-type: %s", article->fname) ;
614 TMRstop(TMR_READART);
617 if (sb.st_size == 0) {
618 article->articleOk = false ;
619 warn ("ME article 0 bytes: %s", article->fname) ;
620 TMRstop(TMR_READART);
623 articlesize = sb.st_size;
628 article->articleOk = false ;
629 missingArticleCount++ ;
631 if (logMissingArticles && !article->loggedMissing)
633 notice ("ME article missing: %s, %s", article->msgid,
635 article->loggedMissing = true ;
637 TMRstop(TMR_READART);
640 amtToRead = articlesize ;
641 newBufferSize = articlesize ;
643 if (arthandle || useMMap) {
645 mMapping = arthandle->data;
647 mMapping = mmap(NULL, articlesize, PROT_READ,
650 if (mMapping == MAP_FAILED) {
651 /* dunno, but revert to plain reading */
653 syswarn ("ME mmap failure %s (%s)",
657 article->contents = newBufferByCharP(mMapping,
660 article->mapInfo = getMapInfo(arthandle, mMapping, articlesize);
661 article->mapInfo->refCount++; /* one more for the buffer */
662 bufferSetDeletedCbk(article->contents, delMapInfo, article->mapInfo);
664 buffer = bufferBase (article->contents) ;
665 if ((p = strchr(buffer, '\n')) == NULL) {
666 article->articleOk = false;
667 delBuffer (article->contents) ;
668 article->contents = NULL ;
669 warn ("ME munged article %s", article->fname) ;
672 article->inWireFormat = true ;
674 /* we need to copy the contents into a buffer below */
675 delBuffer (article->contents) ;
676 article->contents = NULL ;
682 if (article->contents == NULL && article->articleOk) {
683 /* an estimate to give some room for nntpPrepareBuffer to use. */
684 newBufferSize *= (1.0 + (1.0 / avgCharsPerLine)) ;
687 /* if we're going over the limit try to free up some older article's
689 if (amtToRead + bytesInUse > maxBytesInUse)
691 for (h = chronList ; h != NULL ; h = h->nextTime)
693 if (artFreeContents (h->article))
694 if (amtToRead + bytesInUse <= maxBytesInUse)
699 /* we we couldn't get below, then log it (one time only) */
700 if ((amtToRead + bytesInUse) > maxBytesInUse && maxLimitNotified == false) {
701 maxLimitNotified = true ;
702 notice ("ME exceeding maximum article byte limit: %d (max),"
703 " %lu (cur)", maxBytesInUse,
704 (unsigned long) (amtToRead + bytesInUse)) ;
707 if ((article->contents = newBuffer (newBufferSize)) == NULL)
710 buffer = bufferBase (article->contents) ;
711 bytesInUse += articlesize ;
712 byteTotal += articlesize ;
715 if (mMapping && buffer != NULL) {
716 memcpy(buffer, mMapping, articlesize);
721 while (amtToRead > 0) {
722 if ((amt = read (fd, buffer + idx,amtToRead)) <= 0) {
723 syswarn ("ME article read error: %s", article->fname) ;
724 bytesInUse -= articlesize ;
725 byteTotal -= articlesize ;
728 delBuffer (article->contents) ;
729 article->contents = NULL ;
737 if (article->contents != NULL) {
738 bufferSetDataSize (article->contents, articlesize) ;
740 if ((p = strchr(buffer, '\n')) == NULL) {
741 article->articleOk = false;
742 warn ("ME munged article %s", article->fname) ;
744 else if (p[-1] == '\r') {
745 article->inWireFormat = true ;
748 if ( nntpPrepareBuffer (article->contents) ) {
750 (bufferDataSize (article->contents) - articlesize) ;
752 if (((unsigned int) UINT_MAX) - diff <= preparedBytes) {
753 d_printf (2,"Newline ratio so far: %02.2f\n",
754 ((double) preparedBytes / preparedNewlines)) ;
755 notice ("ME newline to file size ratio: %0.2f (%d/%d)",
756 ((double) preparedBytes)/preparedNewlines,
757 preparedBytes,preparedNewlines) ;
759 preparedNewlines = 0 ;
763 preparedBytes += articlesize ;
764 preparedNewlines += diff ;
768 if (preparedBytes > (1024 * 1024)) {
770 ((double) preparedBytes) / preparedNewlines ;
773 article->inWireFormat = true ;
775 warn ("ME internal failed to prepare buffer for NNTP") ;
776 bytesInUse -= articlesize ;
777 byteTotal -= articlesize ;
779 delBuffer (article->contents) ;
780 article->contents = NULL ;
787 /* If we're not useing storage api, we should close a valid file descriptor */
788 if (!arthandle && (fd >= 0))
791 TMRstop(TMR_READART);
792 return (article->contents != NULL ? true : false) ;
797 /* stick the buffer B into the Buffer array pointed at by BUFFS *BUFFS is
798 reallocated if necessary. NEWSPOT points at the index where B should be
799 put (presumably the end). CURLEN points at the length of the BUFFS and
800 it will get updated if BUFFS is reallocated. */
801 static void appendBuffer (Buffer b, Buffer **buffs, int *newSpot, int *curLen)
803 if (*newSpot == *curLen)
806 *buffs = xrealloc (*buffs, sizeof(Buffer) * *curLen) ;
808 (*buffs) [(*newSpot)++] = b ;
813 /* Takes the articles contents buffer and overlays a set of new buffers on
814 top of it. These buffers insert the required carriage return and dot
815 characters as needed */
816 static bool prepareArticleForNNTP (Article article)
818 static Buffer dotFirstBuffer ;
819 static Buffer dotBuffer ;
820 static Buffer crlfBuffer ;
821 Buffer *nntpBuffs = NULL ;
827 contents = artGetContents (article) ; /* returns a reference */
829 TMRstart(TMR_PREPART);
830 if (contents == NULL) {
831 TMRstop(TMR_PREPART);
834 else if (article->nntpBuffers != NULL)
836 delBuffer (contents) ;
837 TMRstop(TMR_PREPART);
838 return true ; /* already done */
841 if (dotBuffer == NULL)
843 dotBuffer = newBufferByCharP (".\r\n",3,3) ;
844 dotFirstBuffer = newBufferByCharP ("\r\n.",3,3) ;
845 crlfBuffer = newBufferByCharP ("\r\n",2,2) ;
848 /* overlay a set of buffers on top of the articles contents buffer. This
849 is a real speed loss at the moment, so by default it's disabled (by
850 calling artBitFiddleContents(true) in main(). */
851 if (article->inWireFormat == false)
853 end = bufferBase (contents) ;
858 while (*end && *end != '\n')
861 appendBuffer (newBufferByCharP (start, (size_t) (end - start),
862 (size_t) (end - start)),
863 &nntpBuffs,&buffIdx,&buffLen) ;
869 while (*end != '\0') ;
871 appendBuffer (bufferTakeRef (dotBuffer), &nntpBuffs,&buffIdx,&buffLen) ;
872 appendBuffer (NULL,&nntpBuffs,&buffIdx,&buffLen) ;
876 /* we already fixed the contents up when we read in the article */
877 nntpBuffs = xmalloc (sizeof(Buffer) * 3) ;
878 nntpBuffs [0] = newBufferByCharP (bufferBase (contents),
879 bufferDataSize (contents),
880 bufferDataSize (contents)) ;
881 if (article->mapInfo) {
882 article->mapInfo->refCount++;
883 bufferSetDeletedCbk(nntpBuffs[0], delMapInfo, article->mapInfo);
885 nntpBuffs [1] = NULL ;
889 delBuffer (contents) ; /* the article is still holding a reference */
890 article->nntpBuffers = nntpBuffs ;
891 TMRstop(TMR_PREPART);
896 /* free the contents of the buffers if article is the only thing holding a
897 reference. Returns true if it could, false if it couldn't */
898 static bool artFreeContents (Article art)
900 if (art->contents == NULL)
903 if (art->nntpBuffers != NULL)
905 if (bufferRefCount (art->nntpBuffers[0]) > 1)
909 freeBufferArray (art->nntpBuffers) ;
910 art->nntpBuffers = NULL ;
914 ASSERT (bufferRefCount (art->contents) == 1) ;
919 bytesInUse -= bufferDataSize (art->contents) ;
921 delBuffer (art->contents) ;
923 art->contents = NULL ;
935 /**********************************************************************/
936 /* Private hash table and routines for storing articles */
937 /**********************************************************************/
942 /* Hash function lifted from perl 5 */
943 static unsigned int hashString (const char *string)
947 for (i = 0 ; string && *string ; string++)
948 i = 33 * i + (u_char) *string ;
954 /* find the article in the has table and return it. */
955 static Article hashFindArticle (const char *msgid)
957 unsigned int hash = hashString (msgid) ;
960 for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
961 if (hash == h->hash && strcmp (msgid,h->article->msgid) == 0)
964 return (h == NULL ? NULL : h->article) ;
968 /* add the article to the hash table. */
969 static void hashAddArticle (Article article)
971 unsigned int hash = hashString (article->msgid) ;
975 h = hashTable [TABLE_ENTRY(hash)] ;
977 ne = xmalloc (sizeof(struct hash_entry_s));
979 ne->article = article ;
981 ne->next = hashTable [TABLE_ENTRY(hash)] ;
987 hashTable [TABLE_ENTRY(hash)] = ne ;
989 ne->nextTime = chronList ;
990 ne->prevTime = NULL ;
992 if (chronList != NULL)
993 chronList->prevTime = ne ;
999 /* remove the article from the hash table and chronological list.
1000 Does not delete the article itself. */
1001 static bool hashRemoveArticle (Article article)
1003 unsigned int hash = hashString (article->msgid) ;
1006 for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
1007 if (hash == h->hash && strcmp (article->msgid,h->article->msgid) == 0)
1013 if (h == hashTable [TABLE_ENTRY(hash)])
1015 hashTable [TABLE_ENTRY(hash)] = h->next ;
1016 if (h->next != NULL)
1017 h->next->prev = NULL ;
1021 h->prev->next = h->next ;
1022 if (h->next != NULL)
1023 h->next->prev = h->prev ;
1028 chronList = h->nextTime ;
1029 if (chronList != NULL)
1030 chronList->prevTime = NULL ;
1034 h->prevTime->nextTime = h->nextTime ;
1035 if (h->nextTime != NULL)
1036 h->nextTime->prevTime = h->prevTime ;
1044 #define HASH_VALIDATE_BUCKET_COUNT 1 /* hash buckets to check per call */
1046 static void hashValidateTable (void)
1048 static int hbn = 0 ;
1052 #if ! defined (NDEBUG)
1053 for (i = 0 ; i < HASH_VALIDATE_BUCKET_COUNT ; i++)
1055 for (he = hashTable [hbn] ; he != NULL ; he = he->next)
1056 ASSERT (he->article->refCount > 0) ;
1057 if (++hbn >= TABLE_SIZE)