
/*
 * OpenCache()
 *
 *	return +1	if a valid cache file was found, set *pcfd, *psize
 *
 *	return 0	if a valid cache file was not found, set *pcfd to >= 0
 *			if we were able to open a temporary file for a new
 *			cache object, < 0 if we could not.
 *
 *	return -1	If we found a negatively cached entry.
 *
 * (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 AbortCache(int fd, const char *msgid, int closefd);
Prototype void CommitCache(int fd, const  char *msgid, int closefd);
Prototype void DumpArticleFromCache(Connection *conn, const char *map, int size);

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) {	/* negatively cached */
		close(fd);
		return(-1);
	    }
	    *pcfd = fd;			/* positively cached */
	    *psize = st.st_size;
	    return(1);
	}
	close(fd);			/* error	     */
	return(0);
    }

    strcat(path, ".tmp");

    if ((fd = open(path, O_RDWR|O_CREAT, 0644)) < 0) {
	return(0);			/* error	     */
    }

    if (hflock(fd, 0, XLOCK_EX|XLOCK_NB) < 0) {
	close(fd);			/* someone else owns it */
	return(0);
    }
    {
	struct stat st;
	struct stat st2;
	if (fstat(fd, &st) < 0 || st.st_nlink == 0) {
	    close(fd);			/* delete race		*/
	    return(0);
	}
	if (stat(path, &st2) < 0 || st.st_ino != st2.st_ino) {
	    close(fd);			/* rename race		*/
	    return(0);
	}
	if (st.st_size != 0)		/* truncate if partial left over */
	    ftruncate(fd, 0);
    }

    /*
     * created new cache file.
     */
    *pcfd = fd;
    return(0);
}

/*
 * 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(int fd, const  char *msgid, int closefd)
{
    char path1[PATH_MAX];
    char path2[PATH_MAX];
    hash_t hv = hhash(msgid);

    cacheFile(hv, path2, 0);
    strcpy(path1, path2);
    strcat(path1, ".tmp");
    if (rename(path1, path2) < 0)
	remove(path1);
    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 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) {
		    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);
}

