/* $Id: article.c 7285 2005-06-07 06:38:24Z eagle $
**
** The Article class for innfeed.
**
** Written by James Brister <brister@vix.com>
**
** The implementation of the Article class. Articles are the abstraction for
** the actual news articles. They are a reference counted object because they
** need to be shared by multiple Host and Connection objects. When an Article
** is created it verifies that the actual file exists. When a Connection
** wants the article's contents for transmission the Article reads the data
** off disk and returns a set of Buffer objects. The Article holds onto these
** Buffers so that the next Connection that wants to transmit it won't have
** to wait for a disk read to be done again.
*/
#include "innfeed.h"
#include "config.h"
#include "clibrary.h"
#include "portable/mmap.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#if HAVE_LIMITS_H
# include <limits.h>
#endif
#include <sys/stat.h>
#include <syslog.h>
#include "inn/messages.h"
#include "libinn.h"
#include "storage.h"
#include "article.h"
#include "buffer.h"
#include "endpoint.h"
#if defined (NDEBUG)
#define VALIDATE_HASH_TABLE() (void (0))
#else
#define VALIDATE_HASH_TABLE() hashValidateTable ()
#endif
extern bool useMMap ;
struct map_info_s {
ARTHANDLE *arthandle;
const void *mMapping;
size_t size;
int refCount;
struct map_info_s *next;
};
typedef struct map_info_s *MapInfo;
struct article_s
{
int refCount ; /* the reference count on this article */
char *fname ; /* the file name of the article */
char *msgid ; /* the msgid of the article (INN tells us) */
Buffer contents ; /* the buffer of the actual on disk stuff */
Buffer *nntpBuffers ; /* list of buffers for transmisson */
MapInfo mapInfo; /* arthandle and mMapping */
bool loggedMissing ; /* true if article is missing and we logged */
bool articleOk ; /* true until we know otherwise. */
bool inWireFormat ; /* true if ->contents is \r\n/dot-escaped */
} ;
struct hash_entry_s {
struct hash_entry_s *next ;
struct hash_entry_s *prev ;
struct hash_entry_s *nextTime ;
struct hash_entry_s *prevTime ;
unsigned int hash ;
Article article ;
};
typedef struct hash_entry_s *HashEntry ;
/*
* Private functions
*/
static Buffer artGetContents (Article article) ; /* Return the buffer that
fillContents() filled
up. */
/* Log statistics on article memory usage. */
static void logArticleStats (TimeoutId id, void *data) ;
static bool fillContents (Article article) ; /* Read the article's bits
off the disk. */
/* Append buffer B to the buffer array BUFFS. */
static void appendBuffer (Buffer b, Buffer **buffs, int *newSpot, int *curLen);
static bool prepareArticleForNNTP (Article article) ; /* Do the necessary
CR-LF stuff */
static bool artFreeContents (Article art) ; /* Tell the Article to release
its contents buffer if
possible. */
static void artUnmap (Article art) ; /* munmap an mmap()ed
article */
/*
* Hash table routine declarations.
*/
/* Returns a has value for the given string */
static unsigned int hashString (const char *string) ;
/* Locates the article with the given message ID, in the has table. */
static Article hashFindArticle (const char *msgid) ;
/* Puts the given article in the hash table. */
static void hashAddArticle (Article article) ;
/* Removes the given article from the has table */
static bool hashRemoveArticle (Article article) ;
/* Does some simple-minded hash table validation */
static void hashValidateTable (void) ;
/*
* Private data
*/
static unsigned int missingArticleCount ; /* Number of articles that were missing */
static bool logMissingArticles ; /* if true then we log the details on a
missing article. */
static unsigned int preparedBytes ; /* The number of areticle bytes read in so
far of disk (will wrap around) */
static unsigned int preparedNewlines ; /* The number of newlines found in all the
preparedBytes */
static unsigned int avgCharsPerLine ; /* The average number of characters per
line over all articles. */
static bool rolledOver ; /* true if preparedBytes wrapped around */
static unsigned int bytesInUse ; /* count of article contents bytes in use--just
the amount read from the article files, not
all memory involved in. */
static unsigned int maxBytesInUse ; /* the limit we want to stay under for total
bytes resident in memory dedicated to
article contents. */
static unsigned int articlesInUse ; /* number of articles currently allocated. */
static unsigned int byteTotal ; /* number of bytes for article contents
allocated totally since last log. */
static unsigned int articleTotal ; /* number of articles alloced since last log. */
static TimeoutId articleStatsId ; /* The timer callback id. */
static MapInfo mapInfo;
/*
* Hash Table data
*/
static HashEntry *hashTable ; /* the has table itself */
static HashEntry chronList ; /* chronologically ordered. Points at newest */
#define TABLE_SIZE 2048 /* MUST be a power of 2 */
#define HASH_MASK (TABLE_SIZE - 1)
#define TABLE_ENTRY(hash) ((hash) & HASH_MASK)
/*******************************************************************/
/** PUBLIC FUNCTIONS **/
/*******************************************************************/
/* Create a new article object. First looks to see if one with the given
message id already exists in the hash table and if so returns that
(after incrementing the reference count). */
Article newArticle (const char *filename, const char *msgid)
{
Article newArt = NULL ;
TMRstart(TMR_NEWARTICLE);
if (hashTable == NULL)
{ /* first-time through initialization. */
int i ;
ASSERT ((TABLE_SIZE & HASH_MASK) == 0) ;
hashTable = xmalloc (sizeof(HashEntry) * TABLE_SIZE) ;
addPointerFreedOnExit ((char *)hashTable) ;
for (i = 0 ; i < TABLE_SIZE ; i++)
hashTable [i] = NULL ;
if (ARTICLE_STATS_PERIOD > 0)
articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0);
}
/* now look for it in the hash table. We presume the disk file is still
ok */
if ((newArt = hashFindArticle (msgid)) == NULL)
{
newArt = xcalloc (1, sizeof(struct article_s)) ;
newArt->fname = xstrdup (filename) ;
newArt->msgid = xstrdup (msgid) ;
newArt->contents = NULL ;
newArt->mapInfo = NULL ;
newArt->refCount = 1 ;
newArt->loggedMissing = false ;
newArt->articleOk = true ;
newArt->inWireFormat = false ;
d_printf (3,"Adding a new article(%p): %s\n", (void *)newArt, msgid) ;
articlesInUse++ ;
articleTotal++ ;
hashAddArticle (newArt) ;
}
else
{
if (strcmp (filename,newArt->fname) != 0)
warn ("ME two filenames for same article: %s, %s", filename,
newArt->fname) ;
newArt->refCount++ ;
d_printf (2,"Reusing existing article for %s\nx",msgid) ;
}
TMRstop(TMR_NEWARTICLE);
return newArt ;
}
/* Decrement the reference count on the article and free up its memory if
the ref count gets to 0. */
void delArticle (Article article)
{
if (article == NULL)
return ;
ASSERT (article->refCount > 0) ;
if (--(article->refCount) == 0)
{
bool removed = hashRemoveArticle (article) ;
ASSERT (removed == true) ;
d_printf (2,"Cleaning up article (%p): %s\n",
(void *)article, article->msgid) ;
if (article->contents != NULL)
{
if (article->mapInfo)
artUnmap(article);
else
bytesInUse -= bufferDataSize (article->contents) ;
if (article->nntpBuffers != NULL)
freeBufferArray (article->nntpBuffers) ;
delBuffer (article->contents) ;
}
articlesInUse-- ;
free (article->fname) ;
free (article->msgid) ;
free (article) ;
}
VALIDATE_HASH_TABLE () ;
}
void gPrintArticleInfo (FILE *fp, unsigned int indentAmt)
{
char indent [INDENT_BUFFER_SIZE] ;
unsigned int i ;
for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
indent [i] = ' ' ;
indent [i] = '\0' ;
fprintf (fp,"%sGlobal Article information : (count %d) {\n",
indent, articlesInUse) ;
fprintf (fp,"%s missingArticleCount : %d\n",indent,missingArticleCount) ;
fprintf (fp,"%s logMissingArticles : %d\n",indent,logMissingArticles) ;
fprintf (fp,"%s preparedBytes : %d\n",indent,preparedBytes) ;
fprintf (fp,"%s preparedNewlines : %d\n",indent,preparedNewlines) ;
fprintf (fp,"%s avgCharsPerLine : %d\n",indent,avgCharsPerLine) ;
fprintf (fp,"%s rolledOver : %s\n",indent,boolToString (rolledOver)) ;
fprintf (fp,"%s bytesInUse : %d\n",indent,bytesInUse) ;
fprintf (fp,"%s maxBytesInUse : %d\n",indent,maxBytesInUse) ;
fprintf (fp,"%s articlesInUse : %d\n",indent,articlesInUse) ;
fprintf (fp,"%s byteTotal : %d\n",indent,byteTotal) ;
fprintf (fp,"%s articleTotal : %d\n",indent,articleTotal) ;
fprintf (fp,"%s articleStatsId : %d\n",indent,articleStatsId) ;
{
HashEntry he ;
for (he = chronList ; he != NULL ; he = he->nextTime)
printArticleInfo (he->article,fp,indentAmt + INDENT_INCR) ;
}
fprintf (fp,"%s}\n",indent) ;
}
void printArticleInfo (Article art, FILE *fp, unsigned int indentAmt)
{
Buffer *b ;
char indent [INDENT_BUFFER_SIZE] ;
unsigned int i ;
for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
indent [i] = ' ' ;
indent [i] = '\0' ;
fprintf (fp,"%sArticle : %p {\n",indent,(void *) art) ;
fprintf (fp,"%s article ok : %s\n",indent,boolToString (art->articleOk)) ;
fprintf (fp,"%s refcount : %d\n",indent,art->refCount) ;
fprintf (fp,"%s filename : %s\n",indent,art->fname) ;
fprintf (fp,"%s msgid : %s\n",indent,art->msgid) ;
fprintf (fp,"%s contents buffer : {\n",indent) ;
#if 0
printBufferInfo (art->contents,fp,indentAmt + INDENT_INCR) ;
#else
fprintf (fp,"%s %p\n",indent,(void *) art->contents) ;
#endif
fprintf (fp,"%s }\n",indent) ;
fprintf (fp,"%s nntp buffers : {\n",indent) ;
for (b = art->nntpBuffers ; b != NULL && *b != NULL ; b++)
#if 0
printBufferInfo (*b,fp,indentAmt + INDENT_INCR) ;
#else
fprintf (fp,"%s %p\n",indent,(void *) *b) ;
#endif
fprintf (fp,"%s }\n", indent) ;
fprintf (fp,"%s logged missing : %s\n",
indent,boolToString (art->loggedMissing));
fprintf (fp,"%s}\n", indent) ;
}
/* return true if we have or are able to get the contents off the disk */
bool artContentsOk (Article article)
{
bool rval = false ;
if ( prepareArticleForNNTP (article) )
rval = true ;
return rval ;
}
/* bump reference count on the article. */
Article artTakeRef (Article article)
{
if (article != NULL)
article->refCount++ ;
return article ;
}
/* return the filename of the article */
const char *artFileName (Article article)
{
if (article == NULL)
return NULL ;
else
return article->fname ;
}
/* Get a NULL terminated array of Buffers that is ready for sending via NNTP */
Buffer *artGetNntpBuffers (Article article)
{
if ( !prepareArticleForNNTP (article) )
return NULL ;
return dupBufferArray (article->nntpBuffers) ;
}
/* return the message id of the article */
const char *artMsgId (Article article)
{
return article->msgid ;
}
/* return size of the article */
int artSize (Article article)
{
if (article == NULL || article->contents == NULL)
return (int)0 ;
return (int)bufferDataSize(article->contents);
}
/* return how many NNTP-ready buffers the article contains */
unsigned int artNntpBufferCount (Article article)
{
if ( !prepareArticleForNNTP (article) )
return 0 ;
return bufferArrayLen (article->nntpBuffers) ;
}
/* if VAL is true then all missing articles will be logged. */
void artLogMissingArticles (bool val)
{
logMissingArticles = val ;
}
/* set the limit we want to stay under. */
void artSetMaxBytesInUse (unsigned int val)
{
ASSERT (maxBytesInUse > 0) ; /* can only set one time. */
ASSERT (val > 0) ;
maxBytesInUse = val ;
}
/**********************************************************************/
/** STATIC FUNCTIONS **/
/**********************************************************************/
/* return a single buffer that contains the disk image of the article (i.e.
not fixed up for NNTP). */
static Buffer artGetContents (Article article)
{
Buffer rval = NULL ;
if (article->articleOk)
{
if (article->contents == NULL)
fillContents (article) ;
if (article->contents != NULL)
rval = bufferTakeRef (article->contents) ;
}
return rval ;
}
/* arthandle/mMapping needs to be refcounted since a buffer
may exist referencing it after delArticle if the remote
host sends the return status too soon (diablo, 439). */
static MapInfo getMapInfo(ARTHANDLE *arthandle,
const void *mMapping, size_t size)
{
MapInfo m;
for (m = mapInfo; m; m = m->next) {
if (m->arthandle == arthandle &&
m->mMapping == mMapping) {
m->refCount++;
return m;
}
}
m = xmalloc(sizeof(struct map_info_s));
m->refCount = 1;
m->arthandle = arthandle;
m->mMapping = mMapping;
m->size = size;
m->next = mapInfo;
mapInfo = m;
return m;
}
static void delMapInfo(void *vm)
{
MapInfo i, prev;
MapInfo m = (MapInfo)vm;
if (m == NULL)
return;
if (--(m->refCount) > 0)
return;
if (m->arthandle)
SMfreearticle(m->arthandle);
else
if (munmap(m->mMapping, m->size) < 0)
syslog (LOG_NOTICE, "munmap article: %m");
prev = NULL;
for (i = mapInfo; i != m; i = i->next)
prev = i;
if (prev)
prev->next = m->next;
else
mapInfo = m->next;
free(m);
}
static void artUnmap (Article article) {
delMapInfo(article->mapInfo);
article->mapInfo = NULL;
}
static void logArticleStats (TimeoutId id, void *data UNUSED)
{
ASSERT (id == articleStatsId) ;
notice ("ME articles active %d bytes %d", articlesInUse, bytesInUse) ;
notice ("ME articles total %d bytes %d", articleTotal, byteTotal) ;
byteTotal = 0 ;
articleTotal = 0 ;
articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0) ;
}
/* do the actual read of the article off disk into a Buffer that is stored
in the Article object. The Article will end up with its contents field
having a buffer with the article data in it. This buffer may be
holding a mmapped pointer, or it may be simply a regular buffer with
the data read off disk into it. In the regular buffer case the
contents may be copied around after reading to insert a carriage
return before each newline. */
static bool fillContents (Article article)
{
int fd = -1;
char *p;
static bool maxLimitNotified ;
bool opened;
size_t articlesize = 0;
char *buffer = NULL ;
int amt = 0 ;
size_t idx = 0, amtToRead ;
size_t newBufferSize ;
HashEntry h ;
ARTHANDLE *arthandle = NULL;
const void *mMapping = NULL;
ASSERT (article->contents == NULL) ;
TMRstart(TMR_READART);
if (maxBytesInUse == 0)
maxBytesInUse = SOFT_ARTICLE_BYTE_LIMIT ;
if (avgCharsPerLine == 0)
avgCharsPerLine = 75 ; /* roughly number of characters per line */
if (IsToken(article->fname)) {
opened = ((arthandle = SMretrieve(TextToToken(article->fname), RETR_ALL)) != NULL) ? true : false;
if (opened)
articlesize = arthandle->len;
else {
if (SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT) {
syslog(LOG_ERR, "Could not retrieve %s: %s",
article->fname, SMerrorstr);
article->articleOk = false;
TMRstop(TMR_READART);
return false;
}
}
} else {
struct stat sb ;
opened = ((fd = open (article->fname,O_RDONLY,0)) >= 0) ? true : false;
arthandle = NULL;
if (opened) {
if (fstat (fd, &sb) < 0) {
article->articleOk = false ;
syswarn ("ME oserr fstat %s", article->fname) ;
TMRstop(TMR_READART);
return false;
}
if (!S_ISREG (sb.st_mode)) {
article->articleOk = false ;
warn ("ME article file-type: %s", article->fname) ;
TMRstop(TMR_READART);
return false;
}
if (sb.st_size == 0) {
article->articleOk = false ;
warn ("ME article 0 bytes: %s", article->fname) ;
TMRstop(TMR_READART);
return false;
}
articlesize = sb.st_size;
}
}
if (!opened) {
article->articleOk = false ;
missingArticleCount++ ;
if (logMissingArticles && !article->loggedMissing)
{
notice ("ME article missing: %s, %s", article->msgid,
article->fname) ;
article->loggedMissing = true ;
}
TMRstop(TMR_READART);
return false;
}
amtToRead = articlesize ;
newBufferSize = articlesize ;
if (arthandle || useMMap) {
if (arthandle)
mMapping = arthandle->data;
else
mMapping = mmap(NULL, articlesize, PROT_READ,
MAP_SHARED, fd, 0);
if (mMapping == MAP_FAILED) {
/* dunno, but revert to plain reading */
mMapping = NULL ;
syswarn ("ME mmap failure %s (%s)",
article->fname,
strerror(errno)) ;
} else {
article->contents = newBufferByCharP(mMapping,
articlesize,
articlesize);
article->mapInfo = getMapInfo(arthandle, mMapping, articlesize);
article->mapInfo->refCount++; /* one more for the buffer */
bufferSetDeletedCbk(article->contents, delMapInfo, article->mapInfo);
buffer = bufferBase (article->contents) ;
if ((p = strchr(buffer, '\n')) == NULL) {
article->articleOk = false;
delBuffer (article->contents) ;
article->contents = NULL ;
warn ("ME munged article %s", article->fname) ;
} else {
if (p[-1] == '\r') {
article->inWireFormat = true ;
} else {
/* we need to copy the contents into a buffer below */
delBuffer (article->contents) ;
article->contents = NULL ;
}
}
}
}
if (article->contents == NULL && article->articleOk) {
/* an estimate to give some room for nntpPrepareBuffer to use. */
newBufferSize *= (1.0 + (1.0 / avgCharsPerLine)) ;
newBufferSize ++ ;
/* if we're going over the limit try to free up some older article's
contents. */
if (amtToRead + bytesInUse > maxBytesInUse)
{
for (h = chronList ; h != NULL ; h = h->nextTime)
{
if (artFreeContents (h->article))
if (amtToRead + bytesInUse <= maxBytesInUse)
break ;
}
}
/* we we couldn't get below, then log it (one time only) */
if ((amtToRead + bytesInUse) > maxBytesInUse && maxLimitNotified == false) {
maxLimitNotified = true ;
notice ("ME exceeding maximum article byte limit: %d (max),"
" %lu (cur)", maxBytesInUse,
(unsigned long) (amtToRead + bytesInUse)) ;
}
if ((article->contents = newBuffer (newBufferSize)) == NULL)
amtToRead = 0 ;
else {
buffer = bufferBase (article->contents) ;
bytesInUse += articlesize ;
byteTotal += articlesize ;
}
if (mMapping && buffer != NULL) {
memcpy(buffer, mMapping, articlesize);
artUnmap(article) ;
amtToRead = 0;
}
while (amtToRead > 0) {
if ((amt = read (fd, buffer + idx,amtToRead)) <= 0) {
syswarn ("ME article read error: %s", article->fname) ;
bytesInUse -= articlesize ;
byteTotal -= articlesize ;
amtToRead = 0 ;
delBuffer (article->contents) ;
article->contents = NULL ;
}
else {
idx += amt ;
amtToRead -= amt ;
}
}
if (article->contents != NULL) {
bufferSetDataSize (article->contents, articlesize) ;
if ((p = strchr(buffer, '\n')) == NULL) {
article->articleOk = false;
warn ("ME munged article %s", article->fname) ;
}
else if (p[-1] == '\r') {
article->inWireFormat = true ;
}
else {
if ( nntpPrepareBuffer (article->contents) ) {
size_t diff =
(bufferDataSize (article->contents) - articlesize) ;
if (((unsigned int) UINT_MAX) - diff <= preparedBytes) {
d_printf (2,"Newline ratio so far: %02.2f\n",
((double) preparedBytes / preparedNewlines)) ;
notice ("ME newline to file size ratio: %0.2f (%d/%d)",
((double) preparedBytes)/preparedNewlines,
preparedBytes,preparedNewlines) ;
preparedBytes = 0 ;
preparedNewlines = 0 ;
rolledOver = true ;
}
preparedBytes += articlesize ;
preparedNewlines += diff ;
bytesInUse += diff ;
byteTotal += diff ;
if (preparedBytes > (1024 * 1024)) {
avgCharsPerLine =
((double) preparedBytes) / preparedNewlines ;
avgCharsPerLine++ ;
}
article->inWireFormat = true ;
} else {
warn ("ME internal failed to prepare buffer for NNTP") ;
bytesInUse -= articlesize ;
byteTotal -= articlesize ;
delBuffer (article->contents) ;
article->contents = NULL ;
}
}
}
}
/* If we're not useing storage api, we should close a valid file descriptor */
if (!arthandle && (fd >= 0))
close (fd) ;
TMRstop(TMR_READART);
return (article->contents != NULL ? true : false) ;
}
/* stick the buffer B into the Buffer array pointed at by BUFFS *BUFFS is
reallocated if necessary. NEWSPOT points at the index where B should be
put (presumably the end). CURLEN points at the length of the BUFFS and
it will get updated if BUFFS is reallocated. */
static void appendBuffer (Buffer b, Buffer **buffs, int *newSpot, int *curLen)
{
if (*newSpot == *curLen)
{
*curLen += 10 ;
*buffs = xrealloc (*buffs, sizeof(Buffer) * *curLen) ;
}
(*buffs) [(*newSpot)++] = b ;
}
/* Takes the articles contents buffer and overlays a set of new buffers on
top of it. These buffers insert the required carriage return and dot
characters as needed */
static bool prepareArticleForNNTP (Article article)
{
static Buffer dotFirstBuffer ;
static Buffer dotBuffer ;
static Buffer crlfBuffer ;
Buffer *nntpBuffs = NULL ;
int buffLen = 0 ;
int buffIdx = 0 ;
char *start, *end ;
Buffer contents ;
contents = artGetContents (article) ; /* returns a reference */
TMRstart(TMR_PREPART);
if (contents == NULL) {
TMRstop(TMR_PREPART);
return false ;
}
else if (article->nntpBuffers != NULL)
{
delBuffer (contents) ;
TMRstop(TMR_PREPART);
return true ; /* already done */
}
if (dotBuffer == NULL)
{
dotBuffer = newBufferByCharP (".\r\n",3,3) ;
dotFirstBuffer = newBufferByCharP ("\r\n.",3,3) ;
crlfBuffer = newBufferByCharP ("\r\n",2,2) ;
}
/* overlay a set of buffers on top of the articles contents buffer. This
is a real speed loss at the moment, so by default it's disabled (by
calling artBitFiddleContents(true) in main(). */
if (article->inWireFormat == false)
{
end = bufferBase (contents) ;
do
{
start = end ;
while (*end && *end != '\n')
end++ ;
appendBuffer (newBufferByCharP (start, (size_t) (end - start),
(size_t) (end - start)),
&nntpBuffs,&buffIdx,&buffLen) ;
if (*end != '\0')
end++ ;
}
while (*end != '\0') ;
appendBuffer (bufferTakeRef (dotBuffer), &nntpBuffs,&buffIdx,&buffLen) ;
appendBuffer (NULL,&nntpBuffs,&buffIdx,&buffLen) ;
}
else
{
/* we already fixed the contents up when we read in the article */
nntpBuffs = xmalloc (sizeof(Buffer) * 3) ;
nntpBuffs [0] = newBufferByCharP (bufferBase (contents),
bufferDataSize (contents),
bufferDataSize (contents)) ;
if (article->mapInfo) {
article->mapInfo->refCount++;
bufferSetDeletedCbk(nntpBuffs[0], delMapInfo, article->mapInfo);
}
nntpBuffs [1] = NULL ;
}
delBuffer (contents) ; /* the article is still holding a reference */
article->nntpBuffers = nntpBuffs ;
TMRstop(TMR_PREPART);
return true ;
}
/* free the contents of the buffers if article is the only thing holding a
reference. Returns true if it could, false if it couldn't */
static bool artFreeContents (Article art)
{
if (art->contents == NULL)
return false ;
if (art->nntpBuffers != NULL)
{
if (bufferRefCount (art->nntpBuffers[0]) > 1)
return false ;
else
{
freeBufferArray (art->nntpBuffers) ;
art->nntpBuffers = NULL ;
}
}
ASSERT (bufferRefCount (art->contents) == 1) ;
if (art->mapInfo)
artUnmap(art);
else
bytesInUse -= bufferDataSize (art->contents) ;
delBuffer (art->contents) ;
art->contents = NULL ;
return true ;
}
/**********************************************************************/
/* Private hash table and routines for storing articles */
/**********************************************************************/
/* Hash function lifted from perl 5 */
static unsigned int hashString (const char *string)
{
unsigned int i ;
for (i = 0 ; string && *string ; string++)
i = 33 * i + (u_char) *string ;
return i ;
}
/* find the article in the has table and return it. */
static Article hashFindArticle (const char *msgid)
{
unsigned int hash = hashString (msgid) ;
HashEntry h ;
for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
if (hash == h->hash && strcmp (msgid,h->article->msgid) == 0)
break ;
return (h == NULL ? NULL : h->article) ;
}
/* add the article to the hash table. */
static void hashAddArticle (Article article)
{
unsigned int hash = hashString (article->msgid) ;
HashEntry h ;
HashEntry ne ;
h = hashTable [TABLE_ENTRY(hash)] ;
ne = xmalloc (sizeof(struct hash_entry_s));
ne->article = article ;
ne->hash = hash ;
ne->next = hashTable [TABLE_ENTRY(hash)] ;
ne->prev = NULL ;
if (h != NULL)
h->prev = ne ;
hashTable [TABLE_ENTRY(hash)] = ne ;
ne->nextTime = chronList ;
ne->prevTime = NULL ;
if (chronList != NULL)
chronList->prevTime = ne ;
chronList = ne ;
}
/* remove the article from the hash table and chronological list.
Does not delete the article itself. */
static bool hashRemoveArticle (Article article)
{
unsigned int hash = hashString (article->msgid) ;
HashEntry h ;
for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
if (hash == h->hash && strcmp (article->msgid,h->article->msgid) == 0)
break ;
if (h == NULL)
return false ;
if (h == hashTable [TABLE_ENTRY(hash)])
{
hashTable [TABLE_ENTRY(hash)] = h->next ;
if (h->next != NULL)
h->next->prev = NULL ;
}
else
{
h->prev->next = h->next ;
if (h->next != NULL)
h->next->prev = h->prev ;
}
if (chronList == h)
{
chronList = h->nextTime ;
if (chronList != NULL)
chronList->prevTime = NULL ;
}
else
{
h->prevTime->nextTime = h->nextTime ;
if (h->nextTime != NULL)
h->nextTime->prevTime = h->prevTime ;
}
free (h) ;
return true ;
}
#define HASH_VALIDATE_BUCKET_COUNT 1 /* hash buckets to check per call */
static void hashValidateTable (void)
{
static int hbn = 0 ;
int i ;
HashEntry he ;
#if ! defined (NDEBUG)
for (i = 0 ; i < HASH_VALIDATE_BUCKET_COUNT ; i++)
{
for (he = hashTable [hbn] ; he != NULL ; he = he->next)
ASSERT (he->article->refCount > 0) ;
if (++hbn >= TABLE_SIZE)
hbn = 0 ;
}
#endif
}
syntax highlighted by Code2HTML, v. 0.9.1