This is session.c in view mode; [Download] [Up]
/* session.c */
/* Copyright 1995 by Steve Kirkendall */
char id_session[] = "$Id: session.c,v 2.15 1995/10/19 21:35:10 steve Exp steve $";
#include "elvis.h"
/* This file contains the session functions, which implement a cache and
* block-locking semantics on the session file.
*/
/*----------------------------------------------------------------------------*/
/* session block cache */
typedef struct blkcache_s
{
struct blkcache_s *next; /* another block with same hash value */
struct blkcache_s *older,*newer;/* the next-older block in the cache */
COUNT locks; /* lock counter */
BOOLEAN dirty; /* does the block need to be rewritten? */
BLKNO blkno; /* block number of this block */
BLKTYPE blktype; /* type of data in this block */
BLK *buf; /* contents of the block */
#ifdef DEBUG_SESSION
char *lockfile[5]; /* name of source file that locked block */
int lockline[5]; /* line number in source file */
#endif
} CACHEENTRY;
#if USE_PROTOTYPES
static void delcache(CACHEENTRY *item, BOOLEAN thenfree);
static void addcache(CACHEENTRY *item);
static CACHEENTRY *findblock(_BLKNO_ blkno);
static void flushblock(CACHEENTRY *bc);
#endif
static long oldblkhash; /* previous value of o_blkhash option */
static CACHEENTRY **hashed; /* hash table */
static CACHEENTRY *newest; /* pointer to newest item in cache */
static CACHEENTRY *oldest; /* pointer to oldest item in cache */
static int ncached; /* number of items in cache */
static COUNT *alloccnt; /* array of allocation counts per block */
static int nblocks; /* size of alloccnt array */
/* This function deletes an item from the block cache. Optionally, it will
* also free the item.
*/
static void delcache(item, thenfree)
CACHEENTRY *item; /* cache item to be removed from cache */
BOOLEAN thenfree; /* if True, item is freed after removal */
{
CACHEENTRY *scan, *lag;
int i;
assert(item != NULL && (item->locks == 0 || !thenfree));
/* if the item is dirty & we're completely deleting it, then flush it */
if (thenfree && item->dirty)
{
blkwrite(item->buf, item->blkno);
}
/* delete the item from the newest/oldest list */
if (item == newest)
{
newest = item->older;
}
else
{
item->newer->older = item->older;
}
if (item == oldest)
{
oldest = item->newer;
}
else
{
item->older->newer = item->newer;
}
/* delete the item from the hashed list */
i = item->blkno % o_blkhash;
if (hashed[i] == item)
{
hashed[i] = item->next;
}
else
{
for (lag = hashed[i], scan = lag->next; scan != item; lag = scan, scan = scan->next)
{
assert(scan != NULL);
}
lag->next = scan->next;
}
/* if we're supposed to free it, do that */
if (thenfree)
{
safefree(item->buf);
safefree(item);
}
else
{
item->older = item->newer = NULL;
}
/* and count it */
ncached--;
}
/* This function adds an item to the "newest" end of the block cache, and also
* the hash list. Before it does this, it checks the overall size of the cache
* and if it has reached the maximum, it tries to delete the oldest unlocked
* item.
*/
static void addcache(item)
CACHEENTRY *item; /* item to be added to cache */
{
CACHEENTRY *scan;
int i;
/* if this would push the cache past its limit, then try to delete
* the oldest unlocked block.
*/
while (ncached >= o_blkcache)
{
for (i = 0, scan = oldest; scan && scan->locks > 0; scan = scan->newer, i++)
{
}
if (scan)
{
delcache(scan, True);
}
else
{
/* cache size will exceed blkcache... no big deal */
#ifdef DEBUG_SESSION
fprintf(stderr, "%d blocks locked\n", i);
#endif
break;
}
}
/* if this is the first item in the cache, then this is "oldest" */
if (!oldest)
{
oldest = item;
}
/* insert this item at the "newest" end of the cache list */
item->older = newest;
if (newest) newest->newer = item;
newest = item;
/* also insert it into the hash table list */
i = item->blkno % o_blkhash;
item->next = hashed[i];
hashed[i] = item;
/* also count it */
ncached++;
}
/* Find a block in the cache. If the block isn't in the cache, return NULL */
static CACHEENTRY *findblock(blkno)
_BLKNO_ blkno; /* physical block numbe of block to find */
{
CACHEENTRY *scan;
int i;
/* if newest item in cache, return it in a hurry! */
if (newest && newest->blkno == blkno)
{
return newest;
}
/* reallocate the hash table if first call or blkhash has changed */
if (!hashed || o_blkhash != oldblkhash)
{
/* free the old hash table, if any */
if (hashed)
{
safefree(hashed);
}
/* allocate a new hash table */
hashed = (CACHEENTRY **)safekept((int)o_blkhash, sizeof(CACHEENTRY *));
/* put each cached block into the appropriate hash slot */
for (scan = oldest; scan; scan = scan->newer)
{
i = scan->blkno % o_blkhash;
scan->next = hashed[i];
hashed[i] = scan;
}
oldblkhash = o_blkhash;
}
/* search for the block */
for (scan = hashed[blkno % o_blkhash];
scan && scan->blkno != blkno;
scan = scan->next)
{
}
/* if found, move it to the newest end of the cache list */
if (scan)
{
delcache(scan, False);
addcache(scan);
return scan;
}
return NULL;
}
/*----------------------------------------------------------------------------*/
/* Open a session file. For any error, issue an error message and
* exit without ever returning.
*/
void sesopen(force)
BOOLEAN force; /* if True, open even if "in use" flag is set */
{
BLK *tmp;
/* allocate a temporary buffer for the superblock */
tmp = (BLK *)safealloc((int)o_blksize, sizeof(char));
tmp->super.magic = SESSION_MAGIC;
tmp->super.blksize = (COUNT)o_blksize;
/* open the session file */
if (!blkopen(force, tmp))
{
msg(MSG_FATAL, "already in use");
}
/* use the block size denoted in the superblock */
o_blksize = tmp->super.blksize;
o_blkfill = SES_MAXCHARS * 9/10;
/* ToDo: limit range of blkfill values to "1:SES_MAXCHARS" */
/* free the superblock buffer */
safefree(tmp);
/* allocate an initial allocation count table. It'll grow later. */
alloccnt = (COUNT *)safealloc(1, sizeof(COUNT));
nblocks = 1;
alloccnt[0] = 1; /* so superblock is always allocated */
}
/* Flush all dirty blocks in the cache, and then close the session
* file. Elvis calls this just before exiting.
*/
void sesclose()
{
BLK *tmp;
/* flush any dirty blocks */
sessync();
/* close the session file */
tmp = (BLK *)safealloc((int)o_blksize, sizeof(char));
blkclose(tmp);
safefree(tmp);
}
/*----------------------------------------------------------------------------*/
/* Read the requested block into the cache, and lock it. Return
* a pointer to the block's data in the cache.
*/
#ifdef DEBUG_SESSION
BLKNO _seslock(file, line, blkno, forwrite, blktype)
char *file; /* name of source file that called this func */
int line; /* line number of source file */
#else
BLKNO seslock(blkno, forwrite, blktype)
#endif
_BLKNO_ blkno; /* BLKNO of block to be locked */
BOOLEAN forwrite; /* if True, lock it for writing */
BLKTYPE blktype; /* type of data in the block */
{
CACHEENTRY *bc, *newp;
assert((int)blkno < nblocks && alloccnt[blkno] > 0);
/* try to find the block in the cache */
bc = findblock(blkno);
/* if not in the cache, then read it into a new cache item */
if (!bc)
{
bc = (CACHEENTRY *)safekept(1, sizeof(CACHEENTRY));
bc->buf = (BLK *)safekept((int)o_blksize, sizeof(char));
bc->blkno = blkno;
bc->blktype = blktype;
blkread(bc->buf, bc->blkno);
addcache(bc);
}
/* if for write, and its allocation count is greater than 1, then
* "copy on write" means we have to copy this block right now.
*/
if (forwrite && alloccnt[blkno] > 1)
{
/* copy-on-write should only be necessary for CHARS blocks */
assert(blktype == SES_CHARS);
/* decrement the allocation count of the old block */
alloccnt[blkno]--;
/* allocate a new block */
blkno = sesalloc(0);
newp = findblock(blkno);
if (!newp)
{
newp = (CACHEENTRY *)safealloc(1, sizeof(CACHEENTRY));
newp->buf = (BLK *)safealloc((int)o_blksize, sizeof(char));
newp->blkno = blkno;
newp->blktype = blktype;
addcache(newp);
}
/* copy the old block's contents into the new block */
memcpy(newp->buf, bc->buf, (size_t)o_blksize);
newp->dirty = True;
bc = newp;
}
/* mark it as being locked */
#ifdef DEBUG_SESSION
bc->lockfile[bc->locks] = file;
bc->lockline[bc->locks] = line;
#endif
bc->locks++;
/* NOTE: I'd like to trap cases where a single block is locked
* repeatedly. Unfortunately, the recursive nature of the regexp
* matcher causes a scanned block to be locked once for each
* metacharacter so I can't put a hard limit on it. I'll pretend
* I can, and I'll make it a large limit so nobody is likely to
* trip it up with a complex regexp.
*/
assert(bc->locks <= 30);
/* return the data */
return blkno;
}
/* Return a pointer to the start of a block's data in the cache */
BLK *sesblk(blkno)
_BLKNO_ blkno; /* BLKNO of desired block */
{
CACHEENTRY *bc;
bc = findblock(blkno);
assert(bc != NULL && bc->locks > 0);
return bc->buf;
}
/* Release the lock on a block, so that it can be flushed out to the
* session file.
*/
void sesunlock(blkno, forwrite)
_BLKNO_ blkno; /* BLKNO of block to be unlocked */
BOOLEAN forwrite; /* if True, set the block's "dirty" flag */
{
CACHEENTRY *bc;
bc = findblock(blkno);
assert(bc != NULL && bc->locks > 0 && bc->blkno == blkno);
bc->dirty |= forwrite;
bc->locks--;
#ifdef DEBUG_SESSION
if (forwrite)
{
blkwrite(bc->buf, bc->blkno);
}
#endif
}
/*----------------------------------------------------------------------------*/
static void flushblock(bc)
CACHEENTRY *bc; /* cache item to be flushed */
{
blkwrite(bc->buf, bc->blkno);
bc->dirty = False;
}
/* If the block is dirty, write it out to the session file. */
void sesflush(blkno)
_BLKNO_ blkno; /* BLKNO of a block to be flushed */
{
CACHEENTRY *bc;
/* find the block */
bc = findblock(blkno);
assert(bc != NULL && bc->locks == 0);
/* if BYTES or BLKLIST, and not dirty, then don't bother */
if ((bc->blktype == SES_CHARS || bc->blktype == SES_BLKLIST)
&& !bc->dirty)
{
return;
}
flushblock(bc);
}
/* flush every dirty block in the cache */
void sessync()
{
CACHEENTRY *bc;
safeinspect();
/* for each block... */
for (bc = oldest; bc; bc = bc->newer)
{
assert(bc->locks == 0);
/* if dirty, flush it */
if (bc->dirty)
{
flushblock(bc);
}
}
/* maybe force the changes out to disk */
if (o_sync)
{
blksync();
}
}
/*----------------------------------------------------------------------------*/
/* Allocate a new block (if blkno is 0) or increment the allocation
* count on an existing block (if blkno is not 0). Returns its BLKNO.
*/
BLKNO sesalloc(blkwant)
_BLKNO_ blkwant; /* 0 usually, else BLKNO to use */
{
BLKNO blkno;
int newsize;
COUNT *newarray;
BLK *tmp;
int i;
/* if we're supposed to choose a block, then choose one */
if (blkwant == 0)
{
for (blkno = 1; blkno < nblocks && alloccnt[blkno] > 0; blkno++)
{
}
}
else
{
blkno = blkwant;
}
/* if past the end of the current alloccnt array, then grow */
if (blkno >= nblocks)
{
/* reallocate the alloccnt array */
newsize = blkno + o_blkgrow - (blkno % o_blkgrow);
assert(newsize > blkno);
newarray = (COUNT *)safekept(newsize, sizeof(COUNT));
for (i = 0; i < nblocks; i++)
{
newarray[i] = alloccnt[i];
}
safefree(alloccnt);
alloccnt = newarray;
nblocks = newsize;
/* if new block requested, write dummy data into the session file */
if (blkwant == 0)
{
tmp = (BLK *)safealloc((int)o_blksize, sizeof(char));
for (i = blkno; i < nblocks; i++)
{
blkwrite(tmp, (BLKNO)i);
}
safefree(tmp);
}
/* increment the allocation counter for the chosen block */
alloccnt[blkno]++;
}
else /* recycling an old block */
{
/* increment the allocation counter for the chosen block */
alloccnt[blkno]++;
/* if block is supposed to be new, then zero it */
if (blkwant == 0)
{
(void)seslock(blkno, True, SES_NEW);
tmp = sesblk(blkno);
memset((char *)tmp, 0, (size_t)o_blksize);
sesunlock(blkno, True);
}
}
return blkno;
}
/* Decrement the allocation count of a block. If the block's count
* is decremented to 0, it becomes available for reuse by sesalloc.
*/
void sesfree(blkno)
_BLKNO_ blkno; /* BLKNO of a block to be returned to free pool */
{
assert((int)blkno < nblocks && alloccnt[blkno] > 0);
alloccnt[blkno]--;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.