/*
* OpenCache()
*
* return +1 if a valid cache file was found, set *pcfd, *psize
* return 0 if a valid cache file was not found or if an
* empty file is found (uncacheable article)
*
*
* (c)Copyright 1998, Matthew Dillon, All Rights Reserved. Refer to
* the COPYRIGHT file in the base directory of this distribution
* for specific rights granted.
*
*/
#include "defs.h"
Prototype int OpenCache(const char *msgid, int *pcfd, int *psize);
Prototype void CreateCache(Connection *conn);
Prototype void AbortCache(int fd, const char *msgid, int closefd);
Prototype void CommitCache(Connection *conn, int closefd);
Prototype void DumpArticleFromCache(Connection *conn, const char *map, int size, int grpIter, int endNo);
Prototype void OpenCacheHits(void);
struct CacheHitEntry* FindCacheHitEntry(struct CacheHash_t *ch);
struct CacheHitEntry* CreateCacheHitEntry(struct CacheHash_t *ch, int endNo);
struct CacheHitEntry* UpdateCacheHits(char *groupName, int grpIter, int endNo, int cachehit);
int XPageMask=0;
int CacheHitsFD=-1;
void *CacheHits=NULL;
uint32 CacheHitsEnd=0;
unsigned int
cacheHashNum(char *st)
{
hash_t h = hhash(st);
return(abs(h.h1 + h.h2));
}
void
cacheFile(hash_t hv, char *path, int makedir)
{
int blen = 0;
char fstr[32];
int i;
sprintf(fstr, "%08x%08x", (int)hv.h1, (int)hv.h2);
strcpy(path, PatExpand(CacheHomePat));
for (i = 0; i < DOpts.ReaderCacheDirs.dt_dirlvl; i++) {
int c = DOpts.ReaderCacheDirs.dt_dirinfo[i];
int formsize = 0;
char format[32];
while (c > 0) {
formsize++;
c = (c - 1) / 16;
}
sprintf(format, "/%%0%dx", formsize);
blen = strlen(path);
sprintf(path + blen, format,
cacheHashNum(&fstr[i]) % DOpts.ReaderCacheDirs.dt_dirinfo[i]);
if (makedir) {
struct stat st;
if (stat(path, &st) < 0) {
mkdir(path, 0755);
}
}
}
blen = strlen(path);
sprintf(path + blen, "/%08x.%08x", (int)hv.h1, (int)hv.h2);
}
int
OpenCache(const char *msgid, int *pcfd, int *psize)
{
int fd;
hash_t hv = hhash(msgid);
char path[PATH_MAX];
*pcfd = -1;
*psize = 0;
/*
* open cache file
*/
cacheFile(hv, path, 1);
if ((fd = open(path, O_RDWR)) >= 0) {
struct stat st;
if (fstat(fd, &st) == 0) {
if (st.st_size == 0) { /* uncachable */
close(fd);
return(0);
}
*pcfd = fd; /* positively cached */
*psize = st.st_size;
#if MMAP_DOES_NOT_UPDATE_ATIME
{
char t[1];
read(fd, &t, 1);
}
#endif
return(1);
}
close(fd); /* error */
return(0);
}
return(0);
}
void
CreateCache(Connection *conn)
{
int fd;
hash_t hv;
char path[PATH_MAX], tmp[PATH_MAX];
/* do not create new cache files for old articles */
if (conn->co_Desc->d_CacheableTime > 0 &&
(conn->co_Desc->d_CacheableTime < (time(NULL) - conn->co_SReq->sr_TimeRcvd))) {
return;
}
hv = hhash(conn->co_SReq->sr_MsgId);
/*
* open cache file
*/
cacheFile(hv, path, 1);
{
struct stat st;
if (!stat(path, &st)) { /* uncacheable article */
return;
}
}
strcpy(tmp, path);
strcat(tmp, ".tmp");
if (conn->co_Desc->d_Cache == CACHE_LAZY) {
/*
* Lazy cache, if tmp file does not exit, we just create it and
* return
*/
if ((fd = open(tmp, O_RDWR, 0644)) < 0) {
close(creat(tmp, 0644));
return;
}
} else if (conn->co_Desc->d_Cache == CACHE_SCOREBOARD) {
struct CacheHitEntry *che;
int new;
double read;
/* Check cache hits ratio */
che = UpdateCacheHits(conn->co_SReq->sr_Group, conn->co_SReq->sr_GrpIter, conn->co_SReq->sr_endNo, 0);
if (che==NULL) return;
read = che->che_ReadArt+che->che_Hits;
new = che->che_NewArt+conn->co_SReq->sr_endNo-che->che_LastHi;
if (new < 1) new=1;
if ( (read / new) > conn->co_Desc->d_ReadNewRatio) {
if ((che->che_Hits/che->che_ReadArt) > conn->co_Desc->d_CacheReadRatio) {
/* cache on */
if ((fd = open(tmp, O_RDWR|O_CREAT, 0644)) < 0) {
return; /* error */
}
} else {
/* partly cached */
if ((fd = open(tmp, O_RDWR, 0644)) < 0) {
close(creat(tmp, 0644));
return;
} else {
/* correcting stat */
che->che_ReadArt--;
che->che_Hits++;
/* lazy cache in scoring mode, no return */
}
}
} else {
return;
}
} else {
if ((fd = open(tmp, O_RDWR|O_CREAT, 0644)) < 0) {
return; /* error */
}
}
if (hflock(fd, 0, XLOCK_EX|XLOCK_NB) < 0) {
close(fd); /* someone else owns it */
return;
}
{
struct stat st;
struct stat st2;
if (fstat(fd, &st) < 0 || st.st_nlink == 0) {
close(fd); /* delete race */
return;
}
if (stat(tmp, &st2) < 0 || st.st_ino != st2.st_ino) {
close(fd); /* rename race */
return;
}
if (st.st_size != 0) /* truncate if partial left over */
ftruncate(fd, 0);
}
conn->co_SReq->sr_Cache = fdopen(fd, "w");
if (!conn->co_SReq->sr_Cache) {
close(fd);
}
}
/*
* AbortCache() - cache not successfully written, destroy
*/
void
AbortCache(int fd, const char *msgid, int closefd)
{
char path[PATH_MAX];
hash_t hv = hhash(msgid);
cacheFile(hv, path, 0);
strcat(path, ".tmp");
remove(path);
ftruncate(fd, 0);
hflock(fd, 0, XLOCK_UN);
if (closefd)
close(fd);
}
/*
* CommitCache() - cache successfully written, commit to disk
*/
void
CommitCache(Connection *conn, int closefd)
{
char path1[PATH_MAX];
char path2[PATH_MAX];
char *msgid = conn->co_SReq->sr_MsgId;
hash_t hv = hhash(msgid);
int fd = fileno(conn->co_SReq->sr_Cache);
cacheFile(hv, path2, 0);
strcpy(path1, path2);
strcat(path1, ".tmp");
if (rename(path1, path2) < 0)
remove(path1);
{
struct stat st;
if (stat(path2, &st) == 0 && ((conn->co_Desc->d_CacheMax > 0 &&
st.st_size > conn->co_Desc->d_CacheMax)
|| (conn->co_Desc->d_CacheMin > 0 &&
st.st_size < conn->co_Desc->d_CacheMin)))
ftruncate(fd, 0);
}
hflock(fd, 0, XLOCK_UN);
if (closefd)
close(fd);
}
/*
* DUMPARTICLEFROMCACHE() - article buffer is passed as an argument. The
* buffer is already '.' escaped (but has no
* terminating '.\r\n'), and \r\n terminated.
*
* if (conn->co_ArtMode == COM_BODYNOSTAT), just
* do the body. Otherwise do the whole thing.
*/
void
DumpArticleFromCache(Connection *conn, const char *map, int size, int grpIter, int endNo)
{
int b = 0;
int inHeader = 1;
int nonl = 0;
char line[8192];
char *vserver;
char *buf;
char ch;
if (conn->co_Auth.dr_VServerDef)
vserver = conn->co_Auth.dr_VServerDef->vs_ClusterName;
else
vserver = "";
while (b < size) {
int i;
int yes = 0;
for (i = b; i < size && map[i] != '\n'; ++i)
;
if (i < size)
++i;
else
nonl = 1;
switch(conn->co_ArtMode) {
case COM_STAT:
break;
case COM_HEAD:
if (inHeader == 1) {
yes = 1;
if (i == b + 2 && map[b] == '\r' && map[b+1] == '\n')
yes = 0;
}
break;
case COM_ARTICLEWVF:
{
const char *ovdata;
int ovlen;
if ((ovdata = NNRetrieveHead(conn, &ovlen, NULL, NULL, NULL, NULL)) != NULL) {
DumpOVHeaders(conn, ovdata, ovlen);
MBPrintf(&conn->co_TMBuf, "\r\n");
conn->co_ArtMode = COM_BODYNOSTAT;
} else {
yes = 1;
}
}
break;
case COM_ARTICLE:
yes = 1;
break;
case COM_BODY:
case COM_BODYWVF:
case COM_BODYNOSTAT:
if (inHeader == 0)
yes = 1;
break;
}
/*
* Do some header rewriting for virtual servers
*/
*line = '\0';
buf = (char *)&map[b];
ch = tolower(*buf);
if (inHeader && *vserver &&
!conn->co_Auth.dr_VServerDef->vs_NoXrefHostUpdate &&
ch == 'x' && strncasecmp(buf, "Xref:", 5) == 0) {
char *ptr;
ptr = (char *)buf + 5;
while (isspace((int)*ptr))
ptr++;
while (!isspace((int)*ptr))
ptr++;
while (isspace((int)*ptr))
ptr++;
/* ptr should point to first group name */
if (*ptr) {
int len;
int e = i - 1;
while (map[e] == '\r' || map[e] == '\n')
e--;
len = (e - b + 1) - (ptr - buf);
if (len > sizeof(line) - 100)
len = sizeof(line) - 100;
sprintf(line, "Xref: %s ", vserver);
e = strlen(line);
memcpy(&line[e], ptr, len);
line[e + len] = '\0';
strcat(line, "\r\n");
}
}
if (inHeader && *vserver && ch == 'p' &&
!conn->co_Auth.dr_VServerDef->vs_NoReadPath &&
strncasecmp(buf, "Path:", 5) == 0) {
char *ptr;
int vsl = strlen(vserver);
ptr = (char *)buf + 5;
while (isspace((int)*ptr))
ptr++;
if (ptr && *ptr && (strncmp(vserver, ptr, vsl) ||
((ptr[vsl] != '\0') &&
(ptr[vsl] != '!')))) {
int len;
int e = i - 1;
while (map[e] == '\r' || map[e] == '\n')
e--;
len = (e - b + 1) - (ptr - buf);
if (len > sizeof(line) - 100)
len = sizeof(line) - 100;
sprintf(line, "Path: %s!", vserver);
e = strlen(line);
memcpy(&line[e], ptr, len);
line[e + len] = '\0';
strcat(line, "\r\n");
}
}
if (yes) {
if (*line)
MBWrite(&conn->co_TMBuf, line, strlen(line));
else
MBWrite(&conn->co_TMBuf, map + b, i - b);
}
if (inHeader && i == b + 2 && map[b] == '\r' && map[b+1] == '\n')
inHeader = 0;
b = i;
}
if (nonl && conn->co_ArtMode != COM_STAT)
MBPrintf(&conn->co_TMBuf, "\r\n", 2);
/* update the cache hits */
if (CacheHits) {
UpdateCacheHits(conn->co_GroupName, grpIter, endNo, 1);
}
}
struct CacheHitEntry*
FindCacheHitEntry(struct CacheHash_t *ch) {
uint32 *i=NULL;
uint32 max=CacheHitsEnd-sizeof(struct CacheHitEntry);
struct CacheHitEntry *che=NULL;
struct CacheHitHead *chh=(struct CacheHitHead *)CacheHits;
i = (uint32*) (CacheHits+sizeof(struct CacheHitHead)+((ch->h1^ch->h2)%chh->chh_hashSize)*sizeof(uint32));
while ((*i != 0) && (*i < max)) {
che = (struct CacheHitEntry*) (CacheHits + (*i));
if (memcmp(ch, &(che->che_hash), sizeof(struct CacheHash_t))==0) {
return che;
}
i = &(che->che_Next);
}
return NULL;
}
/*** Any call to CreateCacheHitEntry may change CacheHits value ***
*
* There is no hurry to re-mmap CacheHitsFD, we may only check just before
* creating a new entry
*/
struct CacheHitEntry*
CreateCacheHitEntry(struct CacheHash_t *ch, int endNo) {
uint32 *i=NULL;
uint32 ne=0;
struct CacheHitEntry *che=NULL;
struct CacheHitHead *chh=(struct CacheHitHead *)CacheHits;
/* Get an exclusive lock */
hflock(CacheHitsFD, 0, XLOCK_EX);
/* has CacheHitsFD size changed ? */
if (chh->chh_end != CacheHitsEnd) {
int end;
logit(LOG_ERR, "CreateCacheHitEntry - cache.hits size has changed");
end = CacheHitsEnd;
CacheHitsEnd = chh->chh_end;
munmap(CacheHits, end);
CacheHits = mmap(NULL, CacheHitsEnd, PROT_READ|PROT_WRITE, MAP_SHARED, CacheHitsFD, 0);
if (CacheHits == NULL) {
CacheHitsEnd = 0;
logit(LOG_ERR, "Error on cache hits remaping (%s)", strerror(errno));
hflock(CacheHitsFD, 0, XLOCK_UN);
close(CacheHitsFD);
return NULL;
}
chh=(struct CacheHitHead *)CacheHits;
}
/* check if someone else have created the entry while waiting for the lock */
i = (uint32*) (CacheHits+sizeof(struct CacheHitHead)+((ch->h1^ch->h2)%chh->chh_hashSize)*sizeof(uint32));
while ((*i) != 0) {
che = (struct CacheHitEntry*) (CacheHits + (*i));
if (memcmp(ch, &(che->che_hash), sizeof(struct CacheHash_t))==0) {
hflock(CacheHitsFD, 0, XLOCK_UN);
return che;
}
i = &(che->che_Next);
}
/* Adding an entry */
(*i) = ne = chh->chh_newEntry;
chh->chh_newEntry += sizeof(struct CacheHitEntry);
if (chh->chh_newEntry > chh->chh_end) {
char buf[512];
int len;
uint32 j, end;
j = chh->chh_end;
lseek(CacheHitsFD, chh->chh_end, SEEK_SET);
end = (chh->chh_newEntry + XPageMask) & ~XPageMask;
logit(LOG_ERR, "CreateCacheHitEntry - cache.hits is being increased (%x/%x)", j, end);
bzero(buf, sizeof(buf));
len = sizeof(buf);
while(j<end) {
if (j+len>end) len=end-j;
write(CacheHitsFD, buf, len);
j += len;
}
fsync(CacheHitsFD);
chh->chh_end = end;
munmap(CacheHits, CacheHitsEnd);
CacheHitsEnd = end;
CacheHits = mmap(NULL, end, PROT_READ|PROT_WRITE, MAP_SHARED, CacheHitsFD, 0);
if (CacheHits == NULL) {
CacheHitsEnd = 0;
logit(LOG_ERR, "Error on cache hits remaping (%s)", strerror(errno));
hflock(CacheHitsFD, 0, XLOCK_UN);
close(CacheHitsFD);
return NULL;
}
chh=(struct CacheHitHead *)CacheHits;
}
/* beware, values may have changed */
che = (struct CacheHitEntry*) (CacheHits + ne);
memcpy(&(che->che_hash) , ch, sizeof(struct CacheHash_t));
che->che_Next = 0;
che->che_ReadArt = 0;
che->che_Hits = 0;
che->che_LastHi = endNo;
che->che_NewArt = 0;
hflock(CacheHitsFD, 0, XLOCK_UN);
return che;
}
struct CacheHitEntry*
UpdateCacheHits(char *groupName, int grpIter, int endNo, int cachehit) {
struct CacheHash_t ch;
struct CacheHitEntry *che=NULL;
if ((groupName==NULL) || (grpIter<0)) {
return NULL;
}
SetCacheHash(&ch, groupName, grpIter, &DOpts.ReaderGroupHashMethod);
che = FindCacheHitEntry(&ch);
if (che==NULL) {
che = CreateCacheHitEntry(&ch, endNo);
if (che==NULL) {
return NULL;
}
}
if (cachehit) {
che->che_Hits++;
} else {
che->che_ReadArt++;
}
return che;
}
void
OpenCacheHits(void) {
struct CacheHitHead chh;
int r;
if (CacheHitsFD >= 0) {
return;
}
if (XPageMask==0) {
XPageMask = getpagesize()-1;
}
CacheHitsFD = open(PatDbExpand(CacheHitsPat), O_RDWR|O_CREAT, 0644);
if (CacheHitsFD<0) {
logit(LOG_ERR, "Can not open cache hits file (%s)", PatDbExpand(CacheHitsPat));
return;
}
r = read(CacheHitsFD, &chh, sizeof(struct CacheHitHead));
if ( (r < sizeof(struct CacheHitHead)) || (chh.chh_magic != CHMAGIC) || (chh.chh_version != CHVERSION) ) {
/* unusable cache, recreating it */
hflock(CacheHitsFD, 0, XLOCK_EX);
lseek(CacheHitsFD, 0L, 0);
r = read(CacheHitsFD, &chh, sizeof(struct CacheHitHead));
if ( (r < sizeof(struct CacheHitHead)) || (chh.chh_magic != CHMAGIC) || (chh.chh_version != CHVERSION) ) {
/* clean the cache only if no one had the lock before */
char buf[512];
int i,len;
ftruncate(CacheHitsFD, sizeof(struct CacheHitHead));
fsync(CacheHitsFD);
bzero(&chh, sizeof(struct CacheHitHead));
chh.chh_magic = CHMAGIC;
chh.chh_version = CHVERSION;
chh.chh_hashSize = DOpts.ReaderCacheHashSize;
chh.chh_newEntry = sizeof(struct CacheHitHead)+DOpts.ReaderCacheHashSize*sizeof(uint32);
chh.chh_end = (chh.chh_newEntry+XPageMask) & ~XPageMask;
time(&(chh.chh_lastExpired));
lseek(CacheHitsFD, 0L, 0);
write(CacheHitsFD, &chh, sizeof(struct CacheHitHead));
i = sizeof(struct CacheHitHead);
bzero(buf, sizeof(buf));
len = sizeof(buf);
while(i<chh.chh_end) {
if (i+len>chh.chh_end) len=chh.chh_end-i;
write(CacheHitsFD, buf, len);
i += len;
}
fsync(CacheHitsFD);
}
hflock(CacheHitsFD, 0, XLOCK_UN);
}
CacheHitsEnd = chh.chh_end;
CacheHits = mmap(NULL, chh.chh_end, PROT_READ|PROT_WRITE, MAP_SHARED, CacheHitsFD, 0);
if (CacheHits == NULL) {
CacheHitsEnd = 0;
logit(LOG_ERR, "Error on cache hits mmap (%s)", strerror(errno));
close(CacheHitsFD);
return;
}
}
syntax highlighted by Code2HTML, v. 0.9.1