1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
|
/* session.c */
/* Copyright 1995 by Steve Kirkendall */
char id_session[] = "$Id: session.c,v 2.21 1998/09/20 18:33:25 steve Exp $";
#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
#ifdef DEBUG_SESSION
static char *blktypename[] =
{
"SES_NEW","" "SES_SUPER","" "SES_SUPER2","" "SES_BUFINFO","" "SES_BLKLIST","" "SES_CHARS"
};
#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);
o_blkwrite++;
}
/* 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)
{
o_blkhit++;
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);
o_blkhit++;
return scan;
}
o_blkmiss++;
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;
/* if session file was never opened, then we don't need to close it */
if (nblocks == 0)
return;
/* 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;
#ifdef DEBUG_SESSION
fprintf(stderr, "%s:%d: seslock(%d, %s, %s)...\n", file, line,
blkno, forwrite ? "True" : "False", blktypename[blktype]);
#endif
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, blktype);
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 */
#ifdef DEBUG_SESSION
fprintf(stderr, "%s:%d: seslock(...) returning %d\n",
file, line, blkno);
#endif
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);
if (forwrite)
bc->dirty = True;
bc->locks--;
#ifdef DEBUG_SESSION
if (forwrite)
{
blkwrite(bc->buf, bc->blkno);
o_blkwrite++;
}
#endif
}
/*----------------------------------------------------------------------------*/
static void flushblock(bc)
CACHEENTRY *bc; /* cache item to be flushed */
{
blkwrite(bc->buf, bc->blkno);
o_blkwrite++;
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.
*/
#ifdef DEBUG_SESSION
BLKNO _sesalloc(file, line, blkwant, blktype)
char *file; /* name of source file that called this func */
int line; /* line number of source file */
_BLKNO_ blkwant; /* 0 usually, else BLKNO to use */
BLKTYPE blktype; /* type of data in this block */
#else
BLKNO sesalloc(blkwant, blktype)
_BLKNO_ blkwant; /* 0 usually, else BLKNO to use */
BLKTYPE blktype; /* type of data in this block */
#endif
{
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);
o_blkwrite++;
}
safefree(tmp);
}
/* increment the allocation counter for the chosen block */
alloccnt[blkno]++;
#ifdef DEBUG_SESSION
fprintf(stderr, "%s:%d: sesalloc(%d, %s), new alloccnt[%d] = %d, nblocks=%d\n",
file, line, blkwant, blktypename[blktype],
blkno, alloccnt[blkno], nblocks);
#endif
}
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, blktype);
tmp = sesblk(blkno);
memset((char *)tmp, 0, (size_t)o_blksize);
sesunlock(blkno, True);
}
#ifdef DEBUG_SESSION
fprintf(stderr, "%s:%d: sesalloc(%d, %s), recycled alloccnt[%d] = %d\n",
file, line, blkwant, blktypename[blktype],
blkno, alloccnt[blkno]);
#endif
}
assert(alloccnt[blkno] > 0);
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.
*/
#ifdef DEBUG_SESSION
void _sesfree(file, line, blkno)
char *file; /* source file of call */
int line; /* line number of call */
_BLKNO_ blkno; /* BLKNO of a block to be returned to free pool */
#else
void sesfree(blkno)
_BLKNO_ blkno; /* BLKNO of a block to be returned to free pool */
#endif
{
assert((int)blkno < nblocks && alloccnt[blkno] > 0);
alloccnt[blkno]--;
#ifdef DEBUG_SESSION
fprintf(stderr, "%s:%d: sesfree(%d), alloccnt[%d]=%d\n",
file, line, blkno, blkno, alloccnt[blkno]);
#endif
}
|