chiark / gitweb /
Commit 2.4.5-5 as unpacked
[inn-innduct.git] / innfeed / article.c
1 /*  $Id: article.c 7285 2005-06-07 06:38:24Z eagle $
2 **
3 **  The Article class for innfeed.
4 **
5 **  Written by James Brister <brister@vix.com>
6 **
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.
15 */
16
17 #include "innfeed.h"
18 #include "config.h"
19 #include "clibrary.h"
20 #include "portable/mmap.h"
21
22 #include <assert.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #if HAVE_LIMITS_H
26 # include <limits.h>
27 #endif
28 #include <sys/stat.h>
29 #include <syslog.h>
30
31 #include "inn/messages.h"
32 #include "libinn.h"
33 #include "storage.h"
34
35 #include "article.h"
36 #include "buffer.h"
37 #include "endpoint.h"
38
39 #if defined (NDEBUG)
40 #define VALIDATE_HASH_TABLE() (void (0))
41 #else
42 #define VALIDATE_HASH_TABLE() hashValidateTable ()
43 #endif
44
45 extern bool useMMap ;
46
47 struct map_info_s {
48     ARTHANDLE *arthandle;
49     const void *mMapping;
50     size_t size;
51     int refCount;
52     struct map_info_s *next;
53 };
54
55 typedef struct map_info_s *MapInfo;
56
57 struct article_s 
58 {
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 */
68 } ;
69
70 struct hash_entry_s {
71     struct hash_entry_s *next ;
72     struct hash_entry_s *prev ;
73     struct hash_entry_s *nextTime ;
74     struct hash_entry_s *prevTime ;
75     unsigned int hash ;
76     Article article ;
77 };
78
79 typedef struct hash_entry_s *HashEntry ;
80
81   /*
82    * Private functions
83    */
84
85 static Buffer artGetContents (Article article) ;  /* Return the buffer that
86                                                      fillContents() filled
87                                                      up. */
88
89   /* Log statistics on article memory usage. */
90 static void logArticleStats (TimeoutId id, void *data) ;
91
92 static bool fillContents (Article article) ;  /* Read the article's bits
93                                                  off the disk. */
94
95   /* Append buffer B to the buffer array BUFFS. */
96 static void appendBuffer (Buffer b, Buffer **buffs, int *newSpot, int *curLen);
97
98 static bool prepareArticleForNNTP (Article article) ;  /* Do the necessary
99                                                           CR-LF stuff */
100
101 static bool artFreeContents (Article art) ;  /* Tell the Article to release
102                                                 its contents buffer if
103                                                 possible. */
104
105 static void artUnmap (Article art) ; /* munmap an mmap()ed
106                                                      article */
107
108
109   /*
110    * Hash table routine declarations.
111    */
112
113   /* Returns a has value for the given string */
114 static unsigned int hashString (const char *string) ;
115
116   /* Locates the article with the given message ID, in the has table. */
117 static Article hashFindArticle (const char *msgid) ;
118
119   /* Puts the given article in the hash table. */
120 static void hashAddArticle (Article article) ;
121
122   /* Removes the given article from the has table */
123 static bool hashRemoveArticle (Article article) ;
124
125   /* Does some simple-minded hash table validation */
126 static void hashValidateTable (void) ;
127
128
129
130   /*
131    * Private data
132    */
133
134
135 static unsigned int missingArticleCount ;  /* Number of articles that were missing */
136
137 static bool logMissingArticles ;  /* if true then we log the details on a
138                                      missing article. */ 
139
140 static unsigned int preparedBytes ;  /* The number of areticle bytes read in so
141                                  far of disk (will wrap around) */
142
143 static unsigned int preparedNewlines ; /* The number of newlines found in all the
144                                    preparedBytes */
145
146 static unsigned int avgCharsPerLine ;  /* The average number of characters per
147                                    line over all articles. */
148
149 static bool rolledOver ;  /* true if preparedBytes wrapped around */
150
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. */
154
155 static unsigned int maxBytesInUse ;   /* the limit we want to stay under for total
156                                   bytes resident in memory dedicated to
157                                   article contents. */
158
159 static unsigned int articlesInUse ;  /* number of articles currently allocated. */
160
161 static unsigned int byteTotal ;        /* number of bytes for article contents
162                                    allocated totally since last log. */
163
164 static unsigned int articleTotal ; /* number of articles alloced since last log. */
165
166 static TimeoutId articleStatsId ; /* The timer callback id. */
167
168 static MapInfo mapInfo;
169
170   /*
171    * Hash Table data
172    */
173
174 static HashEntry *hashTable ;   /* the has table itself */
175 static HashEntry chronList ;    /* chronologically ordered. Points at newest */
176
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)
180
181
182
183   /*******************************************************************/
184   /**                       PUBLIC FUNCTIONS                        **/
185   /*******************************************************************/
186
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)
191 {
192   Article newArt = NULL ;
193   
194   TMRstart(TMR_NEWARTICLE);
195   if (hashTable == NULL)
196     {                           /* first-time through initialization. */
197       int i ;
198       
199       ASSERT ((TABLE_SIZE & HASH_MASK) == 0) ;
200       hashTable = xmalloc (sizeof(HashEntry) * TABLE_SIZE) ;
201       
202       addPointerFreedOnExit ((char *)hashTable) ;
203
204       for (i = 0 ; i < TABLE_SIZE ; i++)
205         hashTable [i] = NULL ;
206
207       if (ARTICLE_STATS_PERIOD > 0)
208         articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0);
209     }
210
211     /* now look for it in the hash table. We presume the disk file is still
212        ok */
213   if ((newArt = hashFindArticle (msgid)) == NULL)
214     {
215       newArt = xcalloc (1, sizeof(struct article_s)) ;
216
217       newArt->fname = xstrdup (filename) ;
218       newArt->msgid = xstrdup (msgid) ;
219       
220       newArt->contents = NULL ;
221       newArt->mapInfo = NULL ;
222       newArt->refCount = 1 ;
223       newArt->loggedMissing = false ;
224       newArt->articleOk = true ;
225       newArt->inWireFormat = false ;
226       
227       d_printf (3,"Adding a new article(%p): %s\n", (void *)newArt, msgid) ;
228       
229       articlesInUse++ ;
230       articleTotal++ ;
231       
232       hashAddArticle (newArt) ;
233     }
234   else
235     {
236       if (strcmp (filename,newArt->fname) != 0)
237         warn ("ME two filenames for same article: %s, %s", filename,
238               newArt->fname) ;
239       
240       newArt->refCount++ ;
241       d_printf (2,"Reusing existing article for %s\nx",msgid) ;
242     }
243   TMRstop(TMR_NEWARTICLE);
244   return newArt ;
245 }
246
247
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)
251 {
252   if (article == NULL)
253     return ;
254
255   ASSERT (article->refCount > 0) ;
256
257   if (--(article->refCount) == 0)
258     {
259       bool removed = hashRemoveArticle (article) ;
260
261       ASSERT (removed == true) ;
262
263       d_printf (2,"Cleaning up article (%p): %s\n", 
264           (void *)article, article->msgid) ;
265
266       if (article->contents != NULL)
267         {
268           if (article->mapInfo)
269             artUnmap(article);
270           else
271             bytesInUse -= bufferDataSize (article->contents) ;
272       
273           if (article->nntpBuffers != NULL)
274             freeBufferArray (article->nntpBuffers) ;
275
276           delBuffer (article->contents) ;
277         }
278
279       articlesInUse-- ;
280
281       free (article->fname) ;
282       free (article->msgid) ;
283       free (article) ;
284     }
285
286   VALIDATE_HASH_TABLE () ;
287 }
288
289
290 void gPrintArticleInfo (FILE *fp, unsigned int indentAmt)
291 {
292   char indent [INDENT_BUFFER_SIZE] ;
293   unsigned int i ;
294
295   for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
296     indent [i] = ' ' ;
297   indent [i] = '\0' ;
298
299   fprintf (fp,"%sGlobal Article information : (count %d) {\n",
300            indent, articlesInUse) ;
301
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) ;
314   
315   {
316     HashEntry he ;
317
318     for (he = chronList ; he != NULL ; he = he->nextTime)
319       printArticleInfo (he->article,fp,indentAmt + INDENT_INCR) ;
320   }
321
322   fprintf (fp,"%s}\n",indent) ;
323 }
324
325
326 void printArticleInfo (Article art, FILE *fp, unsigned int indentAmt)
327 {
328   Buffer *b ;
329   char indent [INDENT_BUFFER_SIZE] ;
330   unsigned int i ;
331
332   for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
333     indent [i] = ' ' ;
334   indent [i] = '\0' ;
335   
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) ;
341
342   fprintf (fp,"%s    contents buffer : {\n",indent) ;
343 #if 0
344   printBufferInfo (art->contents,fp,indentAmt + INDENT_INCR) ;
345 #else
346   fprintf (fp,"%s    %p\n",indent,(void *) art->contents) ;
347 #endif
348
349   fprintf (fp,"%s    }\n",indent) ;
350
351   fprintf (fp,"%s    nntp buffers : {\n",indent) ;
352   for (b = art->nntpBuffers ; b != NULL && *b != NULL ; b++)
353 #if 0
354     printBufferInfo (*b,fp,indentAmt + INDENT_INCR) ;
355 #else
356   fprintf (fp,"%s    %p\n",indent,(void *) *b) ;
357 #endif
358   
359   fprintf (fp,"%s    }\n", indent) ;
360
361   fprintf (fp,"%s    logged missing : %s\n",
362            indent,boolToString (art->loggedMissing));
363   fprintf (fp,"%s}\n", indent) ;
364   
365 }
366
367   /* return true if we have or are able to get the contents off the disk */
368 bool artContentsOk (Article article)
369 {
370   bool rval = false ;
371
372   if ( prepareArticleForNNTP (article) )
373     rval = true ;
374
375   return rval ;
376 }
377
378
379   /* bump reference count on the article. */
380 Article artTakeRef (Article article) 
381 {
382   if (article != NULL)
383     article->refCount++ ;
384
385   return article ;
386 }
387
388
389   /* return the filename of the article */
390 const char *artFileName (Article article)
391 {
392   if (article == NULL)
393     return NULL ;
394   else
395     return article->fname ;
396 }
397
398
399   /* Get a NULL terminated array of Buffers that is ready for sending via NNTP */
400
401 Buffer *artGetNntpBuffers (Article article)
402 {
403   if ( !prepareArticleForNNTP (article) )
404     return NULL ;
405
406   return dupBufferArray (article->nntpBuffers) ;
407 }
408
409
410   /* return the message id of the article */
411 const char *artMsgId (Article article)
412 {
413   return article->msgid ;
414 }
415
416   /* return size of the article */
417 int artSize (Article article)
418 {
419   if (article == NULL || article->contents == NULL)
420     return (int)0 ;
421   return (int)bufferDataSize(article->contents);
422 }
423
424
425   /* return how many NNTP-ready buffers the article contains */
426 unsigned int artNntpBufferCount (Article article)
427 {
428   if ( !prepareArticleForNNTP (article) )
429     return 0 ;
430   
431   return bufferArrayLen (article->nntpBuffers) ;
432 }
433
434
435   /* if VAL is true then all missing articles will be logged. */
436 void artLogMissingArticles (bool val)
437 {
438   logMissingArticles = val ;
439 }
440
441
442   /* set the limit we want to stay under. */
443 void artSetMaxBytesInUse (unsigned int val)
444 {
445   ASSERT (maxBytesInUse > 0) ;  /* can only set one time. */
446   ASSERT (val > 0) ;
447   
448   maxBytesInUse = val ;
449 }
450
451
452   /**********************************************************************/
453   /**                            STATIC FUNCTIONS                      **/
454   /**********************************************************************/
455
456
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)
460 {
461   Buffer rval = NULL ;
462
463   if (article->articleOk)
464     {
465       if (article->contents == NULL)
466         fillContents (article) ;
467
468       if (article->contents != NULL)
469         rval = bufferTakeRef (article->contents) ;
470     }
471
472   return rval ;
473 }
474
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). */
478
479 static MapInfo getMapInfo(ARTHANDLE *arthandle,
480                 const void *mMapping, size_t size)
481 {
482   MapInfo m;
483
484   for (m = mapInfo; m; m = m->next) {
485     if (m->arthandle == arthandle &&
486         m->mMapping == mMapping) {
487       m->refCount++;
488       return m;
489     }
490   }
491
492   m = xmalloc(sizeof(struct map_info_s));
493   m->refCount = 1;
494   m->arthandle = arthandle;
495   m->mMapping = mMapping;
496   m->size = size;
497   m->next = mapInfo;
498   mapInfo = m;
499
500   return m;
501 }
502
503 static void delMapInfo(void *vm)
504 {
505     MapInfo i, prev;
506     MapInfo m = (MapInfo)vm;
507
508     if (m == NULL)
509       return;
510
511     if (--(m->refCount) > 0)
512       return;
513
514     if (m->arthandle)
515       SMfreearticle(m->arthandle);
516     else
517       if (munmap(m->mMapping, m->size) < 0)
518         syslog (LOG_NOTICE, "munmap article: %m");
519
520     prev = NULL;
521     for (i = mapInfo; i != m; i = i->next)
522       prev = i;
523
524     if (prev)
525       prev->next = m->next;
526     else
527       mapInfo = m->next;
528
529     free(m);
530 }
531
532 static void artUnmap (Article article) {
533
534     delMapInfo(article->mapInfo);
535     article->mapInfo = NULL;
536 }
537
538
539
540 static void logArticleStats (TimeoutId id, void *data UNUSED)
541 {
542   ASSERT (id == articleStatsId) ;
543
544   notice ("ME articles active %d bytes %d", articlesInUse, bytesInUse) ;
545   notice ("ME articles total %d bytes %d", articleTotal, byteTotal) ;
546   
547   byteTotal = 0 ;
548   articleTotal = 0 ;
549
550   articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0) ;
551 }
552
553
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. */
561
562 static bool fillContents (Article article)
563 {
564     int fd = -1;
565     char *p;
566     static bool maxLimitNotified ;
567     bool opened;
568     size_t articlesize = 0;
569     char *buffer = NULL ;
570     int amt = 0 ;
571     size_t idx = 0, amtToRead ;
572     size_t newBufferSize ;
573     HashEntry h ;
574     ARTHANDLE *arthandle = NULL;
575     const void *mMapping = NULL;
576
577     ASSERT (article->contents == NULL) ;
578     
579     TMRstart(TMR_READART);
580     if (maxBytesInUse == 0)
581         maxBytesInUse = SOFT_ARTICLE_BYTE_LIMIT ;
582     
583     if (avgCharsPerLine == 0)
584         avgCharsPerLine = 75 ;      /* roughly number of characters per line */
585     
586     if (IsToken(article->fname)) {
587         opened = ((arthandle = SMretrieve(TextToToken(article->fname), RETR_ALL)) != NULL) ? true : false;
588         if (opened)
589             articlesize = arthandle->len;
590         else {
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); 
596                 return false;
597             }
598         }
599     } else {
600         struct stat sb ;
601         
602         opened = ((fd = open (article->fname,O_RDONLY,0)) >= 0) ? true : false;
603         arthandle = NULL;
604         if (opened) {
605             if (fstat (fd, &sb) < 0) {
606                 article->articleOk = false ;
607                 syswarn ("ME oserr fstat %s", article->fname) ;
608                 TMRstop(TMR_READART);
609                 return false;
610             }
611             if (!S_ISREG (sb.st_mode)) {
612                 article->articleOk = false ;
613                 warn ("ME article file-type: %s", article->fname) ;
614                 TMRstop(TMR_READART);
615                 return false;
616             }
617             if (sb.st_size == 0) {
618                 article->articleOk = false ;
619                 warn ("ME article 0 bytes: %s", article->fname) ;
620                 TMRstop(TMR_READART);
621                 return false;
622             }
623             articlesize = sb.st_size;
624         }
625     }
626     
627     if (!opened) {
628         article->articleOk = false ;
629         missingArticleCount++ ;
630         
631         if (logMissingArticles && !article->loggedMissing)
632         {
633             notice ("ME article missing: %s, %s", article->msgid,
634                     article->fname) ;
635             article->loggedMissing = true ;
636         }
637         TMRstop(TMR_READART);
638         return false;
639     }
640     amtToRead = articlesize ;
641     newBufferSize = articlesize ;
642     
643     if (arthandle || useMMap) {
644         if (arthandle)
645             mMapping = arthandle->data;
646         else 
647             mMapping = mmap(NULL, articlesize, PROT_READ,
648                                      MAP_SHARED, fd, 0);
649         
650         if (mMapping == MAP_FAILED) {
651             /* dunno, but revert to plain reading */
652             mMapping = NULL ;
653             syswarn ("ME mmap failure %s (%s)",
654                      article->fname,
655                      strerror(errno)) ;
656         } else {
657             article->contents = newBufferByCharP(mMapping,
658                                                  articlesize,
659                                                  articlesize);
660             article->mapInfo = getMapInfo(arthandle, mMapping, articlesize);
661             article->mapInfo->refCount++; /* one more for the buffer */
662             bufferSetDeletedCbk(article->contents, delMapInfo, article->mapInfo);
663
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) ;
670             } else {
671                 if (p[-1] == '\r') {
672                     article->inWireFormat = true ;
673                 } else {
674                     /* we need to copy the contents into a buffer below */
675                     delBuffer (article->contents) ;
676                     article->contents = NULL ;
677                 }
678             }
679         }
680     }
681         
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)) ;
685         newBufferSize ++ ;
686         
687         /* if we're going over the limit try to free up some older article's
688            contents.  */
689         if (amtToRead + bytesInUse > maxBytesInUse)
690         {
691             for (h = chronList ; h != NULL ; h = h->nextTime)
692             {
693                 if (artFreeContents (h->article))
694                     if (amtToRead + bytesInUse <= maxBytesInUse)
695                         break ;
696             }
697         }
698         
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)) ;
705         }
706         
707         if ((article->contents = newBuffer (newBufferSize)) == NULL)
708             amtToRead = 0 ;
709         else {
710             buffer = bufferBase (article->contents) ;
711             bytesInUse += articlesize ;
712             byteTotal += articlesize ;
713         }
714         
715         if (mMapping && buffer != NULL) {               
716             memcpy(buffer, mMapping, articlesize);
717             artUnmap(article) ;
718             amtToRead = 0;
719         }
720         
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 ;
726                 amtToRead = 0 ;
727                 
728                 delBuffer (article->contents) ;
729                 article->contents = NULL ;
730             }
731             else {
732                 idx += amt ;
733                 amtToRead -= amt ;
734             }
735         }
736         
737         if (article->contents != NULL) {
738             bufferSetDataSize (article->contents, articlesize) ;
739             
740             if ((p = strchr(buffer, '\n')) == NULL) {                  
741                 article->articleOk = false;
742                 warn ("ME munged article %s", article->fname) ;
743             }
744             else if (p[-1] == '\r') {
745                 article->inWireFormat = true ;
746             }
747             else {
748                 if ( nntpPrepareBuffer (article->contents) ) {
749                     size_t diff =
750                         (bufferDataSize (article->contents) - articlesize) ;
751                     
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) ;
758                         preparedBytes = 0 ;
759                         preparedNewlines = 0 ;
760                         rolledOver = true ;
761                     }
762                     
763                     preparedBytes += articlesize ;
764                     preparedNewlines += diff ;
765                     bytesInUse += diff ;
766                     byteTotal += diff ;
767                     
768                     if (preparedBytes > (1024 * 1024)) {
769                         avgCharsPerLine =
770                             ((double) preparedBytes) / preparedNewlines ;
771                         avgCharsPerLine++ ;
772                     }
773                     article->inWireFormat = true ;
774                 } else {
775                     warn ("ME internal failed to prepare buffer for NNTP") ;
776                     bytesInUse -= articlesize ;
777                     byteTotal -= articlesize ;
778                     
779                     delBuffer (article->contents) ;
780                     article->contents = NULL ;
781                 }
782             }
783         }
784     }
785
786     
787     /* If we're not useing storage api, we should close a valid file descriptor */
788     if (!arthandle && (fd >= 0))
789         close (fd) ;
790
791     TMRstop(TMR_READART);
792     return (article->contents != NULL ? true : false) ;
793 }
794
795
796
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)
802 {
803   if (*newSpot == *curLen)
804     {
805       *curLen += 10 ;
806       *buffs = xrealloc (*buffs, sizeof(Buffer) * *curLen) ;
807     }
808   (*buffs) [(*newSpot)++] = b ;
809 }
810
811
812
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)
817 {
818   static Buffer dotFirstBuffer ;
819   static Buffer dotBuffer ;
820   static Buffer crlfBuffer ;
821   Buffer *nntpBuffs = NULL ;
822   int buffLen = 0 ;
823   int buffIdx = 0 ;
824   char *start, *end ;
825   Buffer contents ;
826
827   contents = artGetContents (article) ; /* returns a reference */
828
829   TMRstart(TMR_PREPART);
830   if (contents == NULL) {
831     TMRstop(TMR_PREPART);
832     return false ;
833   }
834   else if (article->nntpBuffers != NULL)
835     {
836       delBuffer (contents) ;
837       TMRstop(TMR_PREPART);
838       return true ;               /* already done */
839     }
840
841   if (dotBuffer == NULL)
842     {
843       dotBuffer = newBufferByCharP (".\r\n",3,3) ;
844       dotFirstBuffer = newBufferByCharP ("\r\n.",3,3) ;
845       crlfBuffer = newBufferByCharP ("\r\n",2,2) ;
846     }
847
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)
852     {
853       end = bufferBase (contents) ;
854       do 
855         {
856           start = end ;
857           
858           while (*end && *end != '\n')
859             end++ ;
860           
861           appendBuffer (newBufferByCharP (start, (size_t) (end - start),
862                                           (size_t) (end - start)),
863                         &nntpBuffs,&buffIdx,&buffLen) ;
864           
865           if (*end != '\0')
866             end++ ;
867           
868         }
869       while (*end != '\0') ;
870       
871       appendBuffer (bufferTakeRef (dotBuffer), &nntpBuffs,&buffIdx,&buffLen) ;
872       appendBuffer (NULL,&nntpBuffs,&buffIdx,&buffLen) ;
873     }
874   else
875     {
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);
884       }
885       nntpBuffs [1] = NULL ;
886     }
887   
888       
889   delBuffer (contents) ;    /* the article is still holding a reference */
890   article->nntpBuffers = nntpBuffs ;
891   TMRstop(TMR_PREPART);
892   return true ;
893 }
894
895
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)
899 {
900   if (art->contents == NULL)
901     return false ;
902
903   if (art->nntpBuffers != NULL)
904     {
905     if (bufferRefCount (art->nntpBuffers[0]) > 1)
906       return false ;
907     else
908       {
909         freeBufferArray (art->nntpBuffers) ;    
910         art->nntpBuffers = NULL ;
911       }
912     }
913
914   ASSERT (bufferRefCount (art->contents) == 1) ;
915
916   if (art->mapInfo)
917     artUnmap(art);
918   else
919     bytesInUse -= bufferDataSize (art->contents) ;
920   
921   delBuffer (art->contents) ;
922
923   art->contents = NULL ;
924
925   return true ;
926 }
927
928
929
930
931
932
933
934
935   /**********************************************************************/
936   /*         Private hash table and routines for storing articles       */
937   /**********************************************************************/
938
939
940
941
942   /* Hash function lifted from perl 5 */
943 static unsigned int hashString (const char *string)
944 {
945   unsigned int i ;
946
947   for (i = 0 ; string && *string ; string++)
948     i = 33 * i + (u_char) *string ;
949
950   return i ;
951 }
952
953
954   /* find the article in the has table and return it. */
955 static Article hashFindArticle (const char *msgid)
956 {
957   unsigned int hash = hashString (msgid) ;
958   HashEntry h ;
959
960   for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
961     if (hash == h->hash && strcmp (msgid,h->article->msgid) == 0)
962       break ;
963
964   return (h == NULL ? NULL : h->article) ;
965 }
966
967
968   /* add the article to the hash table. */
969 static void hashAddArticle (Article article)
970 {
971   unsigned int hash = hashString (article->msgid) ;
972   HashEntry h ;
973   HashEntry ne ;
974
975   h = hashTable [TABLE_ENTRY(hash)] ;
976
977   ne = xmalloc (sizeof(struct hash_entry_s));
978
979   ne->article = article ;
980   ne->hash = hash ;
981   ne->next = hashTable [TABLE_ENTRY(hash)] ;
982   ne->prev = NULL ;
983
984   if (h != NULL)
985     h->prev = ne ;
986
987   hashTable [TABLE_ENTRY(hash)] = ne ;
988
989   ne->nextTime = chronList ;
990   ne->prevTime = NULL ;
991
992   if (chronList != NULL)
993     chronList->prevTime = ne ;
994
995   chronList = ne ;
996 }
997
998
999   /* remove the article from the hash table and chronological list.
1000      Does not delete the article itself. */
1001 static bool hashRemoveArticle (Article article)
1002 {
1003   unsigned int hash = hashString (article->msgid) ;
1004   HashEntry h ;
1005
1006   for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
1007     if (hash == h->hash && strcmp (article->msgid,h->article->msgid) == 0)
1008       break ;
1009
1010   if (h == NULL)
1011     return false ;
1012
1013   if (h == hashTable [TABLE_ENTRY(hash)])
1014     {
1015       hashTable [TABLE_ENTRY(hash)] = h->next ;
1016       if (h->next != NULL)
1017         h->next->prev = NULL ;
1018     }
1019   else
1020     {
1021       h->prev->next = h->next ;
1022       if (h->next != NULL)
1023         h->next->prev = h->prev ;
1024     }
1025
1026   if (chronList == h)
1027     {
1028       chronList = h->nextTime ;
1029       if (chronList != NULL)
1030         chronList->prevTime = NULL ;
1031     }
1032   else
1033     {
1034       h->prevTime->nextTime = h->nextTime ;
1035       if (h->nextTime != NULL)
1036         h->nextTime->prevTime = h->prevTime ;
1037     }
1038
1039   free (h) ;
1040   
1041   return true ;
1042 }
1043
1044 #define HASH_VALIDATE_BUCKET_COUNT 1 /* hash buckets to check per call */
1045
1046 static void hashValidateTable (void)
1047 {
1048   static int hbn = 0 ;
1049   int i ;
1050   HashEntry he ;
1051
1052 #if ! defined (NDEBUG)
1053   for (i = 0 ; i < HASH_VALIDATE_BUCKET_COUNT ; i++)
1054     {
1055       for (he = hashTable [hbn] ; he != NULL ; he = he->next)
1056         ASSERT (he->article->refCount > 0) ;
1057       if (++hbn >= TABLE_SIZE)
1058         hbn = 0 ;
1059     }
1060 #endif
1061 }