/*
 * LIB/PRECOMMIT.C	- Precommit caching
 *
 * (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 void InitPreCommit(void);
Prototype int PreCommit(const char *msgid, int flags);
Prototype void SetPCommitExpire(int pret, int post, int hsize);
Prototype int GetPCommit(int which);

#ifndef PC_HSIZE
#define PC_HSIZE	16384	/* times sizeof(pchash_t) */
#endif
#ifndef PC_EXPIRE
#define PC_EXPIRE	30	/* 30 second expiration	  */
#endif
#ifndef POC_EXPIRE
#define POC_EXPIRE	45*60	/* 45 minute expiration	  */
#endif
#define PC_HMASK	(PC_HSIZE - 1)

typedef struct pchash_t {
    hash_t	pc_Hash;	/* hash code		*/
    time_t	pc_Time;	/* time of entry	*/
    pid_t	pc_Pid;		/* process id, -1 if post commit */
} pchash_t;

pchash_t *PCHAry;
int	PCFd = -1;
int	PCExpire = PC_EXPIRE;		/* 30 second  default	*/
int	PCPostExpire = POC_EXPIRE;	/* 30 minute default	*/
int	PCHSize = PC_HSIZE;		/* History cache size	*/
int	PCHMask = PC_HMASK;		/* History cache hash mask */
int	PCPid = -1;

void 
SetPCommitExpire(int pret, int post, int hsize)
{
    if (pret >= 0)
	PCExpire = pret;
    if (post >= 0)
	PCPostExpire = post;
    if (hsize >= 0) {
	PCHSize = hsize;
	PCHMask = hsize - 1;
    }
}

/*
 * InitPreCommit() is called by the master diablo to initialize any 
 *		   server-private (but inherited on fork) shared memory.
 *
 *		   We generate a private shared memory segment which is
 *		   mapped, then immediately removed.  The map is inherited
 *		   on fork.  If we don't remove it now, shm segments may
 *		   build up on the machine.
 *
 *		   NOTE: InitPreCommit() may not open() any descriptors because
 *		   our code is not designed to deal with the shared lseek
 *		   for the descriptor on fork.
 */

void
InitPreCommit(void)
{
#if USE_PCOMMIT_SHM
    int sid = shmget(IPC_PRIVATE, PCHSize * sizeof(pchash_t), SHM_R|SHM_W);
    struct shmid_ds ds;

    if (sid < 0) {
	logit(LOG_CRIT, "sysv shared memory alloc of %d failed, is your machine configured with a high enough maximum segment size?",
	    PCHSize * sizeof(pchash_t)
	);
	exit(1);
    }

    PCHAry = (pchash_t *)shmat(sid, NULL, SHM_R|SHM_W);

    if (shmctl(sid, IPC_STAT, &ds) < 0 || shmctl(sid, IPC_RMID, &ds) < 0) {
	logit(LOG_CRIT, "sysv shmctl stat/rmid failed");
	exit(1);
    }
    if (PCHAry == (pchash_t *)-1) {
	PCHAry = NULL;
	logit(LOG_CRIT, "sysv shared memory map failed");
	exit(1);
    }
#endif
}

int
PreCommit(const char *msgid, int flags)
{
    int r = 0;

#if DO_PCOMMIT_POSTCACHE == 0
    /*
     * This option is turned on by default.  If it is 
     * off, do not write the precommit cache to post-cache
     * history lookup hits.
     */
    if (flags & PC_POSTCOMM)
	return(0);
#endif

    if (PCExpire == 0)
	return(0);

    if (PCPid == (pid_t)-1)
	PCPid = (int)getpid();

    if (PCHAry == NULL) {
#if USE_PCOMMIT_SHM
	logit(LOG_CRIT, "unable to initialize precommit cache");
	exit(1);
#else
	struct stat st;

	PCFd = open(PatDbExpand(PCommitCachePat), O_RDWR|O_CREAT, 0644);

	if (PCFd >= 0 && fstat(PCFd, &st) == 0) {
	    int prot = PROT_READ | (USE_PCOMMIT_RW_MAP * PROT_WRITE);
	    if (st.st_size < PCHSize * sizeof(pchash_t))
		ftruncate(PCFd, PCHSize * sizeof(pchash_t));
	    PCHAry = xmap(NULL, PCHSize * sizeof(pchash_t), prot, MAP_SHARED, PCFd, 0);
	}
#endif
    }
    if (PCHAry == NULL && PCFd >= 0) {
	close(PCFd);
	PCFd = -1;
    }
    if (PCHAry) {
	hash_t hv = hhash(msgid);
	int i = (hv.h1 ^ hv.h2) & PCHMask;
	pchash_t *pc = &PCHAry[i];
	time_t t = time(NULL);
	int32 dt = t - pc->pc_Time;

	if (pc->pc_Hash.h1 == hv.h1 && 
	    pc->pc_Hash.h2 == hv.h2 &&
	    dt >= 0 && 
	    ((dt < PCExpire && pc->pc_Pid != PCPid) ||
	    (dt < PCPostExpire && pc->pc_Pid == (pid_t)-1))
	) {
	    /*
	     * collision.  Return -1 if postcommit or -2 if precommit.
	     * If we are posting a history cache
	     * hit/commit, change the pid to -1, which lengthens the expire
	     * time.  We can do this because the message-id is already in
	     * the history file.
	     */
	    if (pc->pc_Pid == (pid_t)-1)
		r = -1;
	    else
		r = -2;
	    /*
	     * If we are removing from precommit, then set pid to 0
	     * This stops any future cache hits for the Message-ID
	     * We don't care about the return code
	     */
	    if (flags & PC_DELCOMM) {
		pc->pc_Time = 0;
		pc->pc_Pid = 0;
		r = 0;
	    }
#if USE_PCOMMIT_RW_MAP && DO_PCOMMIT_POSTCACHE
	    if ((flags & PC_POSTCOMM) && pc->pc_Pid != (pid_t)-1) {
		pc->pc_Pid = (pid_t)-1;
	    }
#endif
	} else {
	    pchash_t npc;
	    /*
	     * enter new info, we don't care about collisions
	     *
	     * PC_POSTCOMM is set only when we are using the precommit
	     * cache as a post-commit 'in the history file' cache.
	     */
	    npc.pc_Hash = hv;
	    npc.pc_Time = t - 1;
	    npc.pc_Pid = (flags & PC_POSTCOMM) ? (pid_t)-1 : PCPid;
	    if (flags & PC_DELCOMM) {
		npc.pc_Time = 0;
		npc.pc_Pid = 0;
		r = 0;
	    }
#if USE_PCOMMIT_RW_MAP
	    PCHAry[i] = npc;
#else
	    lseek(PCFd, i * sizeof(pchash_t), 0);
	    write(PCFd, &npc, sizeof(npc));
#endif
	}
    }
    return(r);
}

int
GetPCommit(int which)
{
    switch (which) {
	case 1: return(PCExpire);
	case 2: return(PCPostExpire);
	case 3: return(PCHSize);
    }
    return(0);
}



syntax highlighted by Code2HTML, v. 0.9.1