/* pbcache.c -- Copyright 1989, 1993, 1994, 1996 Liam R. E. Quin. * All Rights Reserved. * This code is NOT in the public domain. * See the file COPYRIGHT for full details. */ #ifndef LINT static char *RcsId = "@(#) $Id: pbcache.c,v 1.33 1996/08/14 16:55:27 lee Exp lee $"; #endif /* Block cache for lq-text */ #include "globals.h" /* defines and declarations for database filenames */ #include "error.h" #include /* stderr, also for fileinfo.h */ #include /* for fileinfo.h, which uses time_t */ #ifdef HAVE_FCNTL_H # ifdef HAVE_SYSV_FCNTL_H # include # endif # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #include "fileinfo.h" /* for wordinfo.h */ #include "wordinfo.h" /* for t_WID */ #include "pblock.h" #include "blkheader.h" #include "block0.h" #include "emalloc.h" #include "liblqtext.h" #include "lqutil.h" #include "lqtrace.h" /** Unix system calls that need to be declared: **/ /** C library functions that need to be declared: **/ /** lqtext library functions that need to be declared: **/ /** Functions within this file that need to be declared: **/ PRIVATE void OpenDataBase( #ifdef HAVE_PROTO t_LQTEXT_Database *db #endif ); PRIVATE void OpenFreeFile( #ifdef HAVE_PROTO t_LQTEXT_Database *db #endif ); PRIVATE void WriteFreeBlock( #ifdef HAVE_PROTO t_LQTEXT_Database *db #endif ); #ifdef HAVE_MPOOL LIBRARY unsigned long ReclaimFreeBlocks( #ifdef HAVE_PROTO t_LQTEXT_Database *db, unsigned long AmountNeeded #endif ); PRIVATE void AddMemoryReclaimers( #ifdef HAVE_PROTO t_LQTEXT_Database *db #endif ); #endif #define SetBitInByte(Which) (01 << (Which)) /** **/ static int DataFile = -1; static char BlockCacheIsDirty = 0; static char FreeBitBlockIsDirty = 0; #define BLOCKS_PER_CACHE_SEGMENT 128 #define CACHELEN (BLOCKSIZE * BLOCKS_PER_CACHE_SEGMENT) /* with 64 byte blocks this gives 8 KBytes. * That's a good size to read/write on a BSD or SunOS file system, * as that's the underlying block size. On System V, a smaller value * might be better, but it won't hurt too much. For NFS, 4K is * probably best, although with NFS 3 it won't make much difference, * assuming the write-through cache is working OK! * You can't have MAX_CONTIGUOUS_BLOCKS bigger than 255, however big * you make BLOCKS_PER_CACHE_SEGMENT, because there's only one byte * available to store it. */ #if (BLOCKS_PER_CACHE_SEGMENT<256) # define MAX_CONTIGUOUS_BLOCKS (BLOCKS_PER_CACHE_SEGMENT - 1) #else # define MAX_CONTIGUOUS_BLOCKS 255 #endif static unsigned long BlockCacheTimer = 0; typedef struct s_CacheEntry { unsigned long CacheStart; long CacheLen; int TimeLastUsed; unsigned char ReadAheadBuffer[CACHELEN]; short IsDirty; } t_CacheEntry; /* CACHEDBLOCKS *must* be at least 2, or mayhem will result. */ #define CACHEDBLOCKS 200 static t_CacheEntry **BlockCache = 0; static t_CacheEntry *CurrentBlock = 0; static unsigned long FreeBlockStart = 3L; /* i.e. not a multiple of 2, and hence not a valid value */ #define FREEBITLEN 1024 #define CACHEDFREEBLOCKS 200 #define BYTESPERFREEBIT (BLOCKSIZE*8) static unsigned char *FreeBitBlock = (unsigned char *) 0; static unsigned int LowestFreeBlock = 0L; PRIVATE INLINE int FindUnsetBitInByte(Value) unsigned int Value; { register int WhichBit = 0; while (Value & 01) { Value >>= 1; WhichBit++; } return WhichBit; } PRIVATE void printcache(db) t_LQTEXT_Database *db; { register int i; fprintf(stderr, "**** cache at %ld is [", BlockCacheTimer); for (i = 0; i < CACHEDBLOCKS; i++) { if (BlockCache[i]) { fprintf(stderr, " %ld-%ld@%d", BlockCache[i]->CacheStart, BlockCache[i]->CacheStart + BlockCache[i]->CacheLen, BlockCache[i]->TimeLastUsed ); } } fprintf(stderr, "]\n"); } PRIVATE void LQTpMoveCacheDatesBack(db) t_LQTEXT_Database *db; { if (BlockCacheTimer < 1) { t_CacheEntry **cpp; unsigned long Smallest = CurrentBlock->TimeLastUsed; /* we went round the clock. * So we'll go round and reduce all the others */ /* First, work out what to subtract */ for (cpp = BlockCache; cpp - BlockCache < CACHEDBLOCKS; cpp++) { if (*cpp) { register t_CacheEntry *cp2 = *cpp; if (!Smallest || cp2->TimeLastUsed < Smallest) { Smallest = cp2->TimeLastUsed; } } } /* now do the subtraction */ if (Smallest < CACHEDBLOCKS) { Error(E_FATAL|E_INTERNAL|E_BUG, "%s: %d; There's a block stuck in the cache [%ul]", __FILE__, __LINE__, Smallest ); } --Smallest; for (cpp = BlockCache; cpp - BlockCache < CACHEDBLOCKS; cpp++) { if (*cpp) { register t_CacheEntry *cp2 = *cpp; cp2->TimeLastUsed -= Smallest; if (cp2->TimeLastUsed > BlockCacheTimer) { BlockCacheTimer = cp2->TimeLastUsed; } } } /* This won't overflow, because smallest was >= CACHEDBLOCKS, * which must be at least 2. */ ++BlockCacheTimer; } } /* * LQT_BlockIsCached * Database/Files, Database/Physical * *

Determine whether the block at a given offset in the data file is * in the block buffer cache or not. * Since LQT_ReadBlock returns a pointer into the cache, it is a * fatal error (E_BUG) if LQT_WriteBlock is called for a block that * is not cached.

*

The cache is always large enough to hold at least the last two * blocks returned by LQT_ReadBlock. * This is just enough to ensure that the NextOffset field in a * block's header can be filled in after allocating the next block * in a chain.

* * Non-zero if the block is cached, and zero otherwise. * * Fatal error if the main data file can't be opened or created. * * As a side-effect, the CurrentBlock variable in pbcache.c is set to * point to the cached block; this is used internally by the library * routines in that file. * * LQT_ReadBlock * LQT_WriteBlock *
*/ API int LQT_BlockIsCached(db, Block) t_LQTEXT_Database *db; unsigned long Block; { /* see if the requested block is cached, and, if so, make * CurrentBlock point to it. */ register t_CacheEntry **cpp; if (DataFile < 0) { /* clearly it isn't cached if we haven't read or written * anything yet... */ return 0; } for (cpp = BlockCache; cpp - BlockCache < CACHEDBLOCKS; cpp++) { if (*cpp) { register t_CacheEntry *cp = *cpp; if (cp->CacheStart <= Block && cp->CacheStart + cp->CacheLen >= Block + BLOCKSIZE ) { /* Note: use CacheLen, not CACHELEN, in case the cache entry * isn't valid, in which case CacheLen is zero. * * Once we've found a block, we move it to the start, * and move the previous first block to place 2, * so that we find them quickly next time. This is * because WriteBlock typically refers to the previous * block and then the current one. */ if (cpp - BlockCache > 3) { /* we remember the current entry in "cp" */ /* cp = *cpp (already done) */ /* move BlockCache[2] to the current entry */ *cpp = BlockCache[2]; /* move BlockCache[1] to BlockCache[2] */ BlockCache[2] = BlockCache[1]; /* move BlockCache[0] to BlockCache[1] */ BlockCache[1] = BlockCache[0]; /* put the current entry (saved in cp) to the front */ CurrentBlock = BlockCache[0] = cp; } else { CurrentBlock = cp; } if (++BlockCacheTimer < 1) { LQTpMoveCacheDatesBack(db); } CurrentBlock->TimeLastUsed = BlockCacheTimer; return 1; } } } #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_BLOCKCACHE)) { fprintf(stderr, "** IsCached Miss for %ld\n", Block); printcache(db); } #endif return 0; } /* Returns the approximate amount of memory freed */ PRIVATE unsigned long ReclaimCleanBlocks(db, SpaceNeeded) t_LQTEXT_Database *db; unsigned long SpaceNeeded; { t_CacheEntry **Oldestp = 0; t_CacheEntry **cpp; int BlocksInCache = 0; int FlushThisMany = 0; unsigned long AmountSaved = 0; if (DataFile <= 0) { if (BlockCacheIsDirty) { Error(E_BUG|E_MEMORY, "ReclaimCleanBlocks: DataFile closed, cache is dirty, help!" ); BlockCacheIsDirty = 0; } return 0; } /* Count blocks to flush and identify the oldest clean block */ for (cpp = BlockCache; cpp - BlockCache < CACHEDBLOCKS; cpp++) { if (*cpp) { register t_CacheEntry *cp = *cpp; /* We are only interested in blocks that are not dirty. * Furthermore, we cannot flush the youngest two blocks, * because they may be in use, even though they are not * locked. Well, there's no locking code right now anyway. */ if (!cp->IsDirty) { if (!Oldestp || cp->TimeLastUsed < (*Oldestp)->TimeLastUsed) { Oldestp = cpp; } ++BlocksInCache; } } } if (BlocksInCache <= 2 || !Oldestp) { /* We can't save anything safely */ return 0L; } for (FlushThisMany = BlocksInCache - 2; FlushThisMany; FlushThisMany--) { /* get rid of the oldest entry that we found last time round */ efree((unsigned char *) *Oldestp); *Oldestp = (t_CacheEntry *) 0; AmountSaved += sizeof(t_CacheEntry); if (AmountSaved >= SpaceNeeded) { return AmountSaved; } /* find the next oldest */ Oldestp = 0; for (cpp = BlockCache; cpp - BlockCache < CACHEDBLOCKS; cpp++) { if (!(*cpp)->IsDirty) { if (!Oldestp||(*cpp)->TimeLastUsed < (*Oldestp)->TimeLastUsed) { Oldestp = cpp; } } } } return AmountSaved; } PRIVATE void FlushOneBlock(db, theEntry) t_LQTEXT_Database *db; t_CacheEntry *theEntry; { int i; if (DataFile <= 0) { if (BlockCacheIsDirty) { Error(E_BUG, "FlushOneBlock: dirty cache %ld len %ld, fd %d=%s", theEntry->CacheStart, theEntry->CacheLen, DataFile, db->DataBase ); BlockCacheIsDirty = 0; } return; } if (!theEntry->IsDirty) { return; } (void) LQU_Elseek(E_FATAL, db->DataBase, "main data file", DataFile, (long) theEntry->CacheStart, SEEK_SET /* = 0 */ ); i = write( DataFile, (char *) theEntry->ReadAheadBuffer, theEntry->CacheLen ); if (i != theEntry->CacheLen) { Error(E_FATAL|E_SYS, "FlushOneBlock: write(%d=%s, 0x%s, %d) failed, returned %d", DataFile, db->DataBase, theEntry->ReadAheadBuffer, theEntry->CacheLen, i ); } #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_READBLOCK)) { LQT_Trace(LQTRACE_BLOCKCACHE, "WRITE entry %d: %ld %d bytes", i, theEntry->CacheStart, theEntry->CacheLen ); } #endif theEntry->IsDirty = 0; /* Don't change CacheLen, so that we will still find the block * if we want it */ } PRIVATE int CompareCacheStartPoints(e1p, e2p) t_CacheEntry **e1p, **e2p; { /* This is an unstable sort if sizeof(int) < sizeof(long), * but neighbouring blocks will generally sort together, * which is all that matters here. */ if (!*e1p) { return 100000; } if (!*e2p) { return -100000; } return (int) (*e1p)->CacheStart - (*e2p)->CacheStart; } PRIVATE void SortCache() { qsort( (char *) BlockCache, CACHEDBLOCKS, sizeof(t_CacheEntry *), CompareCacheStartPoints ); } /* * LQT_FlushBlockCache * Database/Files, Database/Physical * *

Writes any pending dirty blocks to the disk. * Copies of the blocks are retained in memory, however, until * LQT_CloseDatabase is called, and will be found by LQT_BlockIsCached * and hence by LQT_ReadBlock if an attempt is made to read * them again.

*

When a database is opened, LQT_OpenDatabase adds LQT_FlushBlockCache * as an action to be performed automatically whenever the database * is flushed or closed. It should not be necessary to call this code * directly from outside the library, and it is made available primarily * to aid in debugging.

* * Fatal error (E_BUG) if the cache is dirty in read-only mode. * * LQT_AddActionOnClose * LQT_OpenDatabase * LQT_SyncDatabase * LQT_CloseDatabase *
*/ LIBRARY int LQT_FlushBlockCache(db) t_LQTEXT_Database *db; { t_CacheEntry *Oldest = 0; t_CacheEntry **cpp; #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_READBLOCK)) { LQT_Trace(LQTRACE_BLOCKCACHE, "cache flush:"); } #endif if (DataFile < 0) { if (BlockCacheIsDirty) { Error(E_WARN|E_INTERNAL, "\"data\" isn't open but cache is dirty %ld len %ld, fd %d=%s", CurrentBlock->CacheStart, CurrentBlock->CacheLen, DataFile, db->DataBase ); BlockCacheIsDirty = 0; } return 0; } /* If we sort the cache by block number first, the writes * would be in ascending order, minimising disk seeks. */ SortCache(); for (cpp = BlockCache; cpp - BlockCache < CACHEDBLOCKS; cpp++) { if (*cpp) { register t_CacheEntry *cp = *cpp; if (!Oldest || cp->TimeLastUsed < Oldest->TimeLastUsed) { Oldest = cp; } if (cp->CacheLen && cp->IsDirty) { FlushOneBlock(db, cp); } } } BlockCacheIsDirty = 0; CurrentBlock = Oldest; if (FreeBitBlockIsDirty) { WriteFreeBlock(db); } #ifdef ASCIITRACE /* check the result! */ if (LQT_TraceFlagsSet(LQTRACE_READAFTERWRITE|LQTRACE_WIDCACHE)) { (void) LQT_GetMaxWID(db); } if (LQT_TraceFlagsSet(LQTRACE_READBLOCK)) { LQT_Trace(LQTRACE_BLOCKCACHE, "cache flushed"); } #endif return 0; } /* * LQT_FlushOneBlockFromCache * Database/Files, Database/Physical * *

If there any data blocks that are waiting to be written out * to disk, LQT_FlushOneBlockFromCache will write one of them out.

*

This function is internal to lq-text and users of the Physical * layer of the database.

* * Fatal error (E_BUG) if the cache is dirty in read-only mode. * * LQT_FlushBlockCache *
*/ LIBRARY int LQT_FlushOneBlockFromCache(db) t_LQTEXT_Database *db; { t_CacheEntry *Oldest = 0; t_CacheEntry **cpp; #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_READBLOCK)) { LQT_Trace(LQTRACE_BLOCKCACHE, "(flush one block from cache)"); } #endif if (DataFile < 0) { if (BlockCacheIsDirty) { Error(E_BUG|E_WARN, "\"data\" is not open but cache is dirty %ld len %ld, fd %d=%s", CurrentBlock->CacheStart, CurrentBlock->CacheLen, DataFile, db->DataBase ); BlockCacheIsDirty = 0; } return 0; } /* check that the cache is in fact full: * if so, find the oldest block and free it; * if not, allocate a new block that we can use. */ for (cpp = BlockCache; cpp - BlockCache < CACHEDBLOCKS; cpp++) { if (*cpp) { register t_CacheEntry *cp = *cpp; if (!Oldest || cp->TimeLastUsed < Oldest->TimeLastUsed) { Oldest = *cpp; } } else { /* a virgin block -- * this only happens the first few times! * (it's wearing white socks...) */ /* allocate a nw cache entry */ *cpp = (t_CacheEntry *) emalloc("CacheEntry", sizeof(t_CacheEntry)); /*INITACHE*/ (*cpp)->CacheStart = 0; (*cpp)->CacheLen = 0; (*cpp)->TimeLastUsed = 0; (*cpp)->IsDirty = 0; Oldest = *cpp; break; } } CurrentBlock = Oldest; if (CurrentBlock->CacheLen && CurrentBlock->IsDirty) { FlushOneBlock(db, CurrentBlock); } return 0; } #ifdef WIDINBLOCK static int WIDDebugFile = -1; static t_WID *WIDDebugArray; #define WID_DEBUG_LENGTH 1500000 static unsigned long WIDLargestOffset; PRIVATE void LQTpEndWIDDebugging(db) t_LQTEXT_Database *db; { if (WIDDebugFile > 0) { int i; (void) LQU_Elseek(E_FATAL, "widfile.dbg", "WID debug file", WIDDebugFile, 0L, SEEK_SET /* = 0 */ ); i = write( WIDDebugFile, WIDDebugArray, (WIDLargestOffset / BLOCKSIZE) * sizeof(t_WID) ); if (i != sizeof(t_WID) * WID_DEBUG_LENGTH) { Error(E_WARN|E_SYS, "write: widfile.dbg: wrote %d bytes, not %d", i, sizeof(t_WID) * WID_DEBUG_LENGTH ); } (void) close(WIDDebugFile); WIDDebugFile = 0; } } PRIVATE void LQTpStartWIDDebugging(db) t_LQTEXT_Database *db; { int Flags, Modes; char *theFile = LQU_joinstr3(db->DatabaseDir, "/", "widfile.dbg"); LQT_GetFileModes(db, &Flags, &Modes); WIDDebugFile = LQU_Eopen(E_FATAL, theFile, "WID debug file", Flags, Modes); { int i; WIDDebugArray = (t_WID *) emalloc( "WID debug array", sizeof(t_WID) * WID_DEBUG_LENGTH ); i = read(WIDDebugFile, WIDDebugArray, sizeof(t_WID)*WID_DEBUG_LENGTH); if (i < 0) { Error(E_FATAL|E_SYS, "can't read %ld bytes from wid debug file %s", sizeof(t_WID) * WID_DEBUG_LENGTH, theFile ); } WIDLargestOffset = (i / sizeof(t_WID)) * BLOCKSIZE; LQT_AddActionOnClose( db, "Save WID debugging data", LQTpEndWIDDebugging, LQT_ON_CLOSE|LQT_ON_SYNC ); } } LIBRARY t_WID LQTpGetWIDofBlock(db, Offset) t_LQTEXT_Database *db; unsigned long Offset; { if (WIDDebugFile < 0) { LQTpStartWIDDebugging(db); } if (Offset > WIDLargestOffset) { return (t_WID) 0; } else { return WIDDebugArray[Offset / BLOCKSIZE]; } } LIBRARY void LQTpSetWIDofBlock(db, Offset, WID) t_LQTEXT_Database *db; unsigned long Offset; t_WID WID; { if (WIDDebugFile < 0) { LQTpStartWIDDebugging(db); } if (Offset >= WID_DEBUG_LENGTH * BLOCKSIZE) { return; } if (Offset > WIDLargestOffset) { WIDLargestOffset = Offset; } WIDDebugArray[Offset / BLOCKSIZE] = WID; } #endif /* WIDINBLOCK */ /* * LQT_WriteBlock * Database/Files, Database/Physical *

* Writes the given block to the database. Actually the block is * saved in the cache, and if it was originally obtained with * LQT_ReadBlock it's already in the cache, so LQT_WriteBlock simply * marks it as dirty, needing to be saved. If you change data in a * block without calling LQT_WriteBlock, the changes usually won't be * written to disk (unless an adjacent block in the cache is written). *

*

The block must have a valid header; if the block's length field is * larger than the Length argument, the extra blocks are marked as free. * The header is described in blkheader.h. *

* * Format or consistency errors are generally fatal. * Attempting to write a block not in the cache will produce a warning. * * LQT_FindFreeBlock * LQT_ReadBlock * LQT_WriteBlock *
*/ API void #ifdef ASCIITRACE LQTp_WriteBlock(db, theFile, theLine, Block, Data, Length, theWID) t_LQTEXT_Database *db; char *theFile; int theLine; #else LQT_WriteBlock(db, Block, Data, Length, theWID) t_LQTEXT_Database *db; #endif unsigned long Block; unsigned char *Data; int Length; t_WID theWID; { t_BlockHeader *H; int MaxLength; unsigned OffsetInBlock; if (DataFile < 0) { OpenDataBase(db); } if (!Data) { Error(E_BUG, #ifdef ASCIITRACE "%s: %d: %s: %d: LQT_WriteBlock %ld, 0x0: second argument (Data) is invalid", theFile, theLine, __FILE__, __LINE__, #else "LQT_WriteBlock %ld, 0x0: second argument (Data) is invalid", #endif Block ); } H = (t_BlockHeader *) Data; MaxLength = H->NumberOfBlocks; #ifdef WIDINBLOCK # ifndef ASCIITRACE how did this happen? check Makefile includes either both -DWIDINBLOCK and -DASCIITRACE or neither # endif if (H->WID != theWID) { Error(E_INTERNAL|E_WARN, "%s: %d: %s: %d: LQT_WriteBlock 0x%x: WID %ld != BH->WID %ld (next %ld)", theFile, theLine, __FILE__, __LINE__, Block, theWID, H->WID, H->NextOffset ); } #endif /* WIDINBLOCK */ if (!MaxLength) { Error(E_INTERNAL|((MaxLength > 0) ? E_WARN : (E_BUG|E_FATAL)), #ifdef ASCIITRACE "%s: %d: %s: %d: LQT_WriteBlock 0%o: WID=%ld: Length=%d, BH->l=0", theFile, theLine, #else "%s: %d: LQT_WriteBlock 0%o: WID=%ld: Length=%d, BH->l 0", #endif __FILE__, __LINE__, theWID, Length ); Length = H->NumberOfBlocks; } if (!Length) { Error(E_INTERNAL|((MaxLength > 0) ? E_WARN : (E_BUG|E_FATAL)), #ifdef ASCIITRACE "%s: %d: %s: %d: LQT_WriteBlock 0%o: WID %ld: Length 0, BH->l %d", theFile, theLine, #else "%s: %d: LQT_WriteBlock 0%o: WID %ld: Length 0, BH->l %d", #endif __FILE__, __LINE__, theWID, MaxLength ); Length = H->NumberOfBlocks; } { unsigned long unwantedOffset = Block + (MaxLength * BLOCKSIZE); while (MaxLength > Length) { --MaxLength; /* e.g. if MaxLength is 2 and Length is 1, we must free * block number "Block + 1" */ unwantedOffset -= BLOCKSIZE; LQT_SetBlockStatus(db, unwantedOffset, SET_BLOCK_AS_FREE); } } H->NumberOfBlocks = Length; if (theWID != (t_WID) 0 && H->NextOffset == 0L) { LQT_SetLastBlockInChain( db, theWID, &Block, &Data[(Length * BLOCKSIZE) - 1], Data ); } if (!LQT_BlockIsCached(db, Block)) { /* With an in-place cache this is a distaster, it means that * the block got swapped out while it was in use! */ Error(E_WARN|E_INTERNAL, #ifdef ASCIITRACE "%s: %d: %s: %d: LQT_WriteBlock %ld not in cache!", theFile, theLine, #else "%s: %d: LQT_WriteBlock %ld not in cache!", #endif __FILE__, __LINE__, Block ); printcache(db); (void) LQT_ReadBlock(db, Block, theWID); /* so that the cache gets full; * this isn't so bad as we're likely to want the other blocks * in the cache... and I/O is in 8K chunks anyway. We ought to * check that this block isn't 8K long, though... */ } OffsetInBlock = Block - CurrentBlock->CacheStart; if (Length > 1) { if (OffsetInBlock + Length*BLOCKSIZE > CACHELEN) { #ifdef ASCIITRACE Error(E_BUG, "%s: %d: %s: %d: LQT_WriteBlock: offset %ld, length %ld overran cache at %ld by %d bytes", __FILE__, __LINE__, theFile, theLine, Block, Length, CurrentBlock->CacheStart, (OffsetInBlock + Length*BLOCKSIZE) - CACHELEN ); #else Error(E_BUG, "LQT_WriteBlock: offset %ld, length %ld overran cache at %ld by %d bytes", Block, Length, CurrentBlock->CacheStart, (OffsetInBlock + Length*BLOCKSIZE) - CACHELEN ); #endif } } if (Data != &CurrentBlock->ReadAheadBuffer[OffsetInBlock]) { Error(E_INTERNAL|E_WARN, #ifdef ASCIITRACE "%s: %d: %s: %d: LQT_WriteBlock %ld: WID %ld: data at 0x%x not returned by LQT_ReadBlock 0x%x", theFile, theLine, __FILE__, __LINE__, #else "LQT_WriteBlock %ld: WID %ld: data at 0x%x not returned by LQT_ReadBlock 0x%x", #endif Block, theWID, Data, &CurrentBlock->ReadAheadBuffer[OffsetInBlock] ); bcopy( Data, (char *) &CurrentBlock->ReadAheadBuffer[OffsetInBlock], BLOCKSIZE * Length ); } if (++BlockCacheTimer < 1) { LQTpMoveCacheDatesBack(db); } CurrentBlock->TimeLastUsed = BlockCacheTimer; CurrentBlock->IsDirty = 1; BlockCacheIsDirty = 1; #ifdef WIDINBLOCK LQTpSetWIDofBlock(Block, theWID); #endif #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_WRITEBLOCK)) { LQT_Trace(LQTRACE_WRITEBLOCK, "WROTE %ld len %d Next %ld", Block, Length, H->NextOffset ); } #endif return; } PRIVATE void OpenDataBase(thedb) t_LQTEXT_Database *thedb; { int Flags, Modes; LQT_GetFileModes(thedb, &Flags, &Modes); DataFile = LQU_Eopen(E_FATAL, thedb->DataBase, "match data", Flags, Modes); if (!BlockCache) { BlockCache = (t_CacheEntry **) ecalloc( "block buffer cache", sizeof(t_CacheEntry *), CACHEDBLOCKS ); } /* LQT_WriteVersionToDatabase and LQT_CheckDatabaseVersion() MUST do a * LQT_ReadBlock(0) immediately, * so that the cache gets initialised properly. */ LQT_CheckDatabaseVersion(thedb); (void) LQT_GetMaxWID(thedb); #ifdef HAVE_MPOOL AddMemoryReclaimers(thedb); #endif } static int FreeFile = -1; /* Pages in the cache are FREEBITLEN bytes long, and each represent * FREEBITLEN * 8 blocks (one per bit in each of FREEBITLEN 8-bit bytes). */ #define PAGENO_TO_BLOCK(pageNumber) ((pageNumber) * BYTESPERFREEBIT * FREEBITLEN) /* The reverse transformation is harder, as we have to round down to * get the start of the block. */ #define BLOCK_TO_PAGENO(blockNumber) \ ((unsigned long) (blockNumber / (FREEBITLEN * BYTESPERFREEBIT))) #ifdef bsddb # define HAVE_MPOOL probably_ok #endif #ifdef HAVE_MPOOL /* In this case we have the mpool(3) routines, and can use them to * get a considerable performance improvement with the free block code. * Except, this code is broken for some reason. */ # include # include static MPOOL *FreePool = 0; PRIVATE void OpenFreePool(FileName) char *FileName; { if (FreePool) { Error(E_FATAL|E_INTERNAL|E_BUG, "%s: -- OpenFreePool: already open", FileName ); } FreePool = mpool_open((DBT *) NULL, FreeFile, (pgno_t) FREEBITLEN, (pgno_t) CACHEDBLOCKS ); if (!FreePool) { Error(E_FATAL|E_SYS, "couldn't open free bit cache \"%s\"", FileName); } } PRIVATE void SyncFreePool(db) t_LQTEXT_Database *db; { if (!FreePool) return; if (mpool_sync(FreePool) < 0) { Error(E_SYS|E_WARN, "Problem sync'ing freelist pool"); } FreeBitBlockIsDirty = 0; } PRIVATE void CloseFreePool(db) t_LQTEXT_Database *db; { if (!FreePool) { Error(E_WARN|E_INTERNAL|E_BUG, "%s: %d: FreePool NULL in CloseFreePool", __FILE__, __LINE__ ); } SyncFreePool(db); if (mpool_close(FreePool) < 0) { Error(E_SYS|E_WARN, "Problem closing freelist pool"); } FreePool = (MPOOL *) 0; close(FreeFile); FreeFile = -1; FreeBitBlockIsDirty = 0; } LIBRARY unsigned long ReclaimFreeBlocks(db, AmountNeeded) t_LQTEXT_Database *db; unsigned long AmountNeeded; { if (!FreePool) { return 0; } SyncFreePool(db); CloseFreePool(db); return AmountNeeded; /* guess */ } PRIVATE void AddMemoryReclaimers(db) t_LQTEXT_Database *db; { /* If we run low on space, we can shrink the cache */ LQU_AddMemoryReclaimFunction(thedb, ReclaimCleanBlocks); /* If that doesn't work, we can shrink the free block cache */ LQU_AddMemoryReclaimFunction(thedb, ReclaimFreeBlocks); } static unsigned char *currentPoolPage = 0; PRIVATE void ReadFreeBitBlock( #ifdef HAVE_PROTO t_LQTEXT_Database *db, unsigned long BlockNumber #endif ); PRIVATE void WriteFreeBlock(db) t_LQTEXT_Database *db; { if (!FreePool) { Error(E_FATAL|E_BUG|E_INTERNAL, "%s: %d: WriteFreeBlock/FreePool Null for %lu", __FILE__, __LINE__, FreeBlockStart ); } if (!currentPoolPage) { return; } if (mpool_put( FreePool, currentPoolPage, FreeBitBlockIsDirty ? MPOOL_DIRTY : 0 ) < 0 ) { Error(E_WARN|E_SYS, "WriteFreeBlock(%lu): mpool_put: ", FreeBlockStart ); } FreeBitBlockIsDirty = 0; } PRIVATE void ReadFreeBitBlock(db, BlockNumber) t_LQTEXT_Database *db; unsigned long BlockNumber; { unsigned long PageNumber; #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_FREEBLOCKS, "", BlockNumber ); } #endif if (FreeFile < 0) { OpenFreeFile(db); } /* Read the free bitmap for the block containing the bit that tells us * whether the given Block Number is free or used... */ if (!FreePool) { Error(E_FATAL|E_BUG|E_INTERNAL, "%s: %d: FetchFreeBitPage/FreePool Null for %lu", __FILE__, __LINE__, BlockNumber ); } if (FreeBitBlockIsDirty) { WriteFreeBlock(db); } PageNumber = BLOCK_TO_PAGENO(BlockNumber); currentPoolPage = mpool_get(FreePool, PageNumber, (u_int) 0); if (!currentPoolPage) { pgno_t returnedPageNumber; /* allocate a new page */ do { currentPoolPage = mpool_new( FreePool, &returnedPageNumber ); if (!currentPoolPage) { Error(E_FATAL|E_SYS, "mpool_new returned NULL for %lu", PageNumber ); } bzero(currentPoolPage, FREEBITLEN); } while (returnedPageNumber != PageNumber); } FreeBitBlock = (unsigned char *) currentPoolPage; FreeBitBlockIsDirty = 0; /* The free block now contains the single bit representing the * given block number, but it might not start with that bit: */ FreeBlockStart = PAGENO_TO_BLOCK(PageNumber); } #else PRIVATE void WriteFreeBlock(db) t_LQTEXT_Database *db; { int i; unsigned long OffsetInFile; if (FreeFile < 0 || !FreeBitBlockIsDirty) { return; } OffsetInFile = FreeBlockStart / (BLOCKSIZE * 8); (void) LQU_Elseek(E_FATAL, db->FreeFileName, "free block bitmap", FreeFile, (long) OffsetInFile, SEEK_SET /* = 0 */ ); i = write(FreeFile, FreeBitBlock, FREEBITLEN); if (i != FREEBITLEN) { Error(E_FATAL|E_SYS, "WriteFreeBlock: write(%d=\"%s\", 0x%x, %d) returned %d", FreeFile, db->FreeFileName, FreeBitBlock, FREEBITLEN, i); } FreeBitBlockIsDirty = 0; } PRIVATE void ReadFreeBitBlock(db, Where) t_LQTEXT_Database *db; long Where; { long AmountRead; unsigned long OffsetInFile; #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_FREEBLOCKS, "", Where ); } #endif if (FreeFile < 0) { OpenFreeFile(db); } OffsetInFile = Where / BYTESPERFREEBIT; /* Round FREEBITLEN down to the start of the block */ OffsetInFile /= FREEBITLEN; OffsetInFile *= FREEBITLEN; if (OffsetInFile * BYTESPERFREEBIT == FreeBlockStart) { return; } if (FreeBitBlockIsDirty) { WriteFreeBlock(db); } (void) LQU_Elseek(E_FATAL, db->FreeFileName, "block free/used bitmap", FreeFile, (long) OffsetInFile, SEEK_SET /* = 0 */ ); AmountRead = read(FreeFile, FreeBitBlock, FREEBITLEN); if (AmountRead < 0) { Error(E_FATAL|E_SYS, "ReadFreeBlock: read(%d=\"%s\", ...) returned %d, not %d", FreeFile, db->FreeFileName, AmountRead, FREEBITLEN ); } FreeBlockStart = OffsetInFile * BYTESPERFREEBIT; /* If we have gone past the end of the file, set the rest of the * block to zeros. */ if (AmountRead < FREEBITLEN) { (void) bzero(&FreeBitBlock[AmountRead], FREEBITLEN - AmountRead); } #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_FREEBLOCKS, "", FreeBlockStart ); } #endif } #endif /* HAVE_MPOOL */ /* For efficiency, we keep a bitmap in memory: each bit refers to * a single block in the freeblocks bitmap. Each bit in that freeblock in * turn refers to a data block. * Hence, for a maximum 4Gbyte data file, we need * 4G / (DATABLOCKSIZE * 8) --> max size of freeblocks * freeblocks.maxsize / (FREEBITLEN * 8) --> number of bytes we need. * When DATABLOCKSIZE is 64 and FREEBITLEN is also 64, we need * 16384 bytes for the free block bitmap. * In practice, FREEBITLEN is usually 1024 or 8192 bytes, and that means * we only need 1KByte. * * On a 64-bit computer, you could configure lq-text to make much larger * databases, and then you might need more than would fit in memory. * Hence, we use 2K and assume it doesn't all fit into memory. */ #ifdef BROKEN unsigned long FreeBitBlockBitmapStart; PRIVATE int FreeBitBlockBitmapFile = -1; #define FREE_BIT_BIT_LENGTH 2048 PRIVATE unsigned char *FreeBitBlockBitmapData; PRIVATE char FreeBitBitBlockIsDirty = 0; PRIVATE void WriteFreeBitBlockToDisk(db) t_LQTEXT_Database *db; { int nbytes; if (!FreeBitBitBlockIsDirty) { return; } (void) LQU_Elseek(E_FATAL, "freelist.bit", "bit map of free block bitmap", FreeBitBlockBitmapFile, FreeBitBlockBitmapStart, SEEK_SET /* = 0 */ ); nbytes = write( FreeBitBlockBitmapFile, FreeBitBlockBitmapData, FREE_BIT_BIT_LENGTH ); if (nbytes == -1) { Error(E_FATAL|E_SYS, "%s: couldn't save bitbit data", "freelist.bit"); } else if (nbytes != FREE_BIT_BIT_LENGTH) { Error(E_FATAL|E_SYS, "%s: wrote %d bytes of freelist.bit, not %d", nbytes, FREE_BIT_BIT_LENGTH ); } FreeBitBitBlockIsDirty = 0; } PRIVATE void LoadFreeBitmapBitmap(db) t_LQTEXT_Database *db; { int Flags, Modes; char *tmp; tmp = emalloc("free bit bit name", strlen(db->FreeFileName) + 4); (void) sprintf(tmp, "%s.bit", db->FreeFileName); LQT_GetFileModes(db, &Flags, &Modes); FreeBitBlockBitmapFile = LQU_Eopen(E_FATAL, tmp, "free block bitmap bitmap [sic]", Flags, Modes ); FreeBitBlockBitmapData = (unsigned char *) emalloc("free bitbit data", FREE_BIT_BIT_LENGTH); FreeBitBlockBitmapStart = 3L; /* so we read the real one */ LoadFreeBitBitBlock(db, 0L); efree(tmp); } PRIVATE void LoadFreeBitBitBlock(db, BlockInBitBitmap) t_LQTEXT_Database *db; unsigned long BlockInBitBitmap; { int nbytes; if (!FreeBitBlockBitmapData) { LoadFreeBitmapBitmap(db); } if (FreeBitBitBlockIsDirty) { WriteFreeBitBlockToDisk(db); } if (BlockInBitBitmap == FreeBitBlockBitmapStart) { return; /* it's already there */ } (void) LQU_Elseek(E_FATAL, "freelist.bit", "bit map of free block bitmap", FreeBitBlockBitmapFile, BlockInBitBitmap, SEEK_SET /* = 0 */ ); FreeBitBlockBitmapStart = BlockInBitBitmap; nbytes = read( FreeBitBlockBitmapFile, FreeBitBlockBitmapData, FREE_BIT_BIT_LENGTH ); if (nbytes == -1) { Error(E_FATAL|E_SYS, "%s: couldn't read bitbit data", "freelist.bit"); } if (nbytes == 0) { (void) bzero(FreeBitBlockBitmapData, FREE_BIT_BIT_LENGTH); } else if (nbytes != FREE_BIT_BIT_LENGTH) { Error(E_FATAL|E_INTERNAL, "%s: read %d bytes, expected %d", "freelist.bit", nbytes ); } FreeBitBitBlockIsDirty = 0; } PRIVATE void SetFreeBitBitStatusForFreeBlock(db, FreeBlock, Status) t_LQTEXT_Database *db; unsigned long FreeBlock; int Status; { unsigned long ByteInBitBitmap; unsigned long BlockInBitBitmap; register unsigned char *p; ByteInBitBitmap = FreeBlock / (8 * FREEBITLEN); BlockInBitBitmap = ByteInBitBitmap / FREE_BIT_BIT_LENGTH; if (ByteInBitBitmap < FreeBitBlockBitmapStart) { LoadFreeBitBitBlock(db, BlockInBitBitmap); } p = &FreeBitBlockBitmapData[ByteInBitBitmap - FreeBitBlockBitmapStart]; if (*p == 0xFF && (Status == SET_BLOCK_AS_USED)) { /* no change */ return; } else if (*p == 0 && (Status == SET_BLOCK_AS_FREE)) { return; } else { /* Find if our bit is set to zero */ unsigned int ui; unsigned char OldValue = (*p); /* We have: * The byte offset of the free block we're interested in, * measured from the start of the Free Bitmap. * The byte offset of the byte containing the single bit in * the free bitmap bitmap corresponding to that block. * We need: * The actual bit address within the byte. */ ui = (FreeBlock / FREEBITLEN); if (Status == SET_BLOCK_AS_FREE) { *p &= (unsigned char)~(unsigned char) SetBitInByte(ui & 07); } else { *p |= (unsigned char) SetBitInByte(ui & 07); } if (*p != OldValue) { FreeBitBitBlockIsDirty = 1; } } } PRIVATE void ABlockMightBeFreeInFreeBlock(db, FreeBlock) t_LQTEXT_Database *db; unsigned long FreeBlock; { unsigned long ByteInBitBitmap; unsigned long BlockInBitBitmap; register unsigned char *p; ByteInBitBitmap = FreeBlock / (8 * FREEBITLEN); BlockInBitBitmap = ByteInBitBitmap / FREE_BIT_BIT_LENGTH; if (ByteInBitBitmap < FreeBitBlockBitmapStart) { LoadFreeBitBitBlock(db, BlockInBitBitmap); } p = &FreeBitBlockBitmapData[ByteInBitBitmap - FreeBitBlockBitmapStart]; if (*p == 0xFF) { /* the whole block of blocks is full, so no point looking further */ return; } else { /* Find if our bit is set to zero */ unsigned int ui; /* We have: * The byte offset of the free block we're interested in, * measured from the start of the Free Bitmap. * The byte offset of the byte containing the single bit in * the free bitmap bitmap corresponding to that block. * We need: * The actual bit address within the byte. */ ui = (FreeBlock / FREEBITLEN); if ((*p) & SetBitInByte(ui & 07)) { return; /* nope */ } } return; } PRIVATE unsigned long FindLowestCandidateForFreeBlock(db, NoLowerThan) t_LQTEXT_Database *db; unsigned long NoLowerThan; { unsigned long BlockInFreeList; unsigned long BlockInFreeBitBitList; /* NoLowerThan is a byte offset in the main data file. * Our job is to find an approximate place where there's a free block * in the data file no lower than NoLowerThan. * We do this by returning an address that's at the start of a * block in the free block bitmap, where that free block bitmap block * contains at least one free block. The caller than has to find the * free block by scanning the bitmap. */ /* First, round NoLowerThan down to a multiple of the free * bit block size to find the starting block number in the * free block bitmap: */ BlockInFreeList = NoLowerThan / (BYTESPERFREEBIT * FREEBITLEN); NoLowerThan = BlockInFreeList * (BYTESPERFREEBIT * FREEBITLEN); /* next, find the FreeBitBit block containing the bit representing * the freelist block which contains a bit representing NoLowerThan */ BlockInFreeBitBitList = BlockInFreeList / (FREEBITLEN * 8 * FREE_BIT_BIT_LENGTH); /* Actually, we work with byte offsets and not block numbers: */ BlockInFreeBitBitList *= FREE_BIT_BIT_LENGTH; /* now, find the first zero bit in that FreeBitBit block, or in a higher * block */ for (;;) { register unsigned char *p; /* load the block (TODO: check if it's already loaded!) * note that eventually this loop will end up allocating a new * block, because we'll call LoadFreeBitBitBlock with a 2nd * argument that's larger than the current free list. This block * will be initialised by LoadFreeBitBitBlock to contain all zeros, * and hene we'll eventually find a free block. */ LoadFreeBitBitBlock(db, BlockInFreeBitBitList); for (p = FreeBitBlockBitmapData; p - FreeBitBlockBitmapData < FREE_BIT_BIT_LENGTH; p++ ) { if (*p != 0xFF) { /* found a zero --> return the block number */ /* the byte in the free block bitmap bitmap file: */ LowestFreeBlock = (p - FreeBitBlockBitmapData) + FreeBitBlockBitmapStart; /* convert to the exact bit offset: */ LowestFreeBlock *= 8; LowestFreeBlock += FindUnsetBitInByte( (unsigned int) *p); /* each bit in this file represents an entire free block */ LowestFreeBlock *= FREEBITLEN; /* now we have a byte address in the free block bitmap file */ LowestFreeBlock *= BYTESPERFREEBIT; return LowestFreeBlock; } } /* nope, that block was all used! */ BlockInFreeBitBitList += FREE_BIT_BIT_LENGTH; } } #endif /*BROKEN*/ PRIVATE void OpenFreeFile(db) t_LQTEXT_Database *db; { int Flags, Modes; LQT_GetFileModes(db, &Flags, &Modes); FreeFile = LQU_Eopen(E_FATAL, db->FreeFileName, "free block bitmap", Flags, Modes ); #ifdef HAVE_MPOOL OpenFreePool(db->FreeFileName); FreeBitBlock = 0; #else /* !HAVE_MPOOL */ if (FreeBitBlock) { efree((char *) FreeBitBlock); } FreeBitBlock = (unsigned char *) emalloc("Free Bit Block", FREEBITLEN); #endif /* HAVE_MPOOL */ LQT_AddActionOnClose( db, "Write out cached freelist blocks", #ifdef HAVE_MPOOL (int (*)()) CloseFreePool, #else (int (*)()) WriteFreeBlock, #endif LQT_ON_CLOSE|LQT_ON_SYNC ); ReadFreeBitBlock(db, 0L); if (LQT_BlockIsFree(db, 0L)) { /* A new database */ unsigned long Reserved = 0L; do { LQT_SetBlockStatus(db, Reserved, SET_BLOCK_AS_USED); Reserved += BLOCKSIZE; } while (Reserved + BLOCKSIZE <= BLOCK_ZERO_RESERVEDBYTES); LowestFreeBlock = Reserved; } else { /* an existing database; guess the lowest free block * Actually if this was stored in block0 we might get a * really big win, but I would need to improve the * way I keep track of its value to get maximum benefit. */ LowestFreeBlock = BLOCK_ZERO_RESERVEDBYTES; } } #define ReadNextFreeBitBlock(db) \ ReadFreeBitBlock(db, FreeBlockStart + (BYTESPERFREEBIT * FREEBITLEN + 1)) /* * LQT_FindFreeBlock * Database/Physical * * Allocates a block from the free list, and marks it as in use. * The block is at least BLOCKSIZE bytes long, and may be longer, * as contiguous free blocks are combined to make a single longer * block as long as will fit in a single cache entry. * If the BytesWanted argument is non-zero, the block will not * be more than BLOCKSIZE bytes longer than than BytesWanted bytes. * Since LQT_FindFreeBlock does not actually read the data from the * disk (or cache), it is up to the caller to ensure that LQT_ReadBlock * is called, and that the resulting block's header is filled in with * NumberOfBlocks equal to the value that LQT_FindFreeBlock stored in * *BlockLengthp. * * the byte offset in the data file of the block allocated, and also * the number of blocks allocated (in BlockLengthp). * * LQT_BlockIsCached * LQT_BlockIsFree * LQT_SetBlockStatus * */ API unsigned long LQT_FindFreeBlock(db, WID, BlockLengthp, BytesWanted) t_LQTEXT_Database *db; t_WID WID; unsigned int *BlockLengthp; unsigned long BytesWanted; { register unsigned char *p; int GotOne = 0; unsigned long Here = 0; if (FreeFile < 0) { OpenFreeFile(db); /* OpenFreeFile() calls ReadFreeBitBlock(0L) */ } if (LowestFreeBlock && LQT_BlockIsFree(db, LowestFreeBlock)) { Here = LowestFreeBlock; GotOne = 1; } /* search the free bit bitmap to find a possible freelist block to search * for a free block. The idea is to avoid looking at every block in * the freelist, as that's too slow. */ if (!GotOne) { do { #ifdef BROKEN LowestFreeBlock = FindLowestCandidateForFreeBlock(db, LowestFreeBlock); #endif ReadFreeBitBlock(db, LowestFreeBlock); /* start at the right place: */ #ifdef BROKEN p = &FreeBitBlock[ (LowestFreeBlock - FreeBlockStart) / BYTESPERFREEBIT ]; #else p = FreeBitBlock; #endif for (; p - FreeBitBlock < FREEBITLEN; p++) { if (*p != (unsigned char) 0xff) { GotOne++; break; } } if (GotOne) break; #ifdef BROKEN /* it's all full, as we didn't find anything */ SetFreeBitBitStatusForFreeBlock( db, LowestFreeBlock, SET_BLOCK_AS_USED ); #else LowestFreeBlock += BYTESPERFREEBIT * FREEBITLEN; #endif } while (!GotOne); /* Now we've found a byte `containing' a free block... ( a zero bit) * so we have to identify the block. */ Here = FreeBlockStart + (p - FreeBitBlock) * BYTESPERFREEBIT + (FindUnsetBitInByte((unsigned int)*p) * BLOCKSIZE); } if (!GotOne || !Here) { Error(E_FATAL|E_INTERNAL|E_BUG, "%s: %d: GotOne %d here %lu; failed to find free block", __FILE__, __LINE__, GotOne, Here ); } LQT_SetBlockStatus(db, Here, SET_BLOCK_AS_USED); /* Now make the block as long as we can: */ *BlockLengthp = 1; if (BytesWanted == 0 || BytesWanted > BLOCKSIZE) { LQT_ExtendBlock(db, Here, BlockLengthp, BytesWanted); } *BlockLengthp *= BLOCKSIZE; /* we have found the lowest free block and used it... */ LowestFreeBlock = Here + (*BlockLengthp); return Here; } /* * LQT_ExtendBlock * Database/Physical * *

LQT_ExtendBlock marks as many blocks as possible following the * given byte Offset as being used, and increments the unsigned int * pointed to by BlockCountp by the number of blocks added. * The number of blocks added is such that a single contiguous stretch * of data starting at the given Offset, and continuing for the * number of blocks in *BlockCountp, does not cross an LQT_ReadBlock * cache boundary.

*

If the BytesWanted argument is non-zero, the total number of * blocks in BlockCountp when LQT_ExtendBlock returns will not be * more than one block greater than BytesWanted bytes.

* * *BlockCountp must be greater than zero on entry to LQT_ExtendBlock. * * The total number of blocks in *BlockCountp * * LQT_FindFreeBlock * LQT_SetBlockStatus *
*/ API void LQT_ExtendBlock(db, Offset, BlockCountp, BytesWanted) t_LQTEXT_Database *db; unsigned long Offset; unsigned int *BlockCountp; unsigned long BytesWanted; { unsigned long Candidate; int MaxLength; Candidate = Offset + (*BlockCountp * BLOCKSIZE); /** Work out the maximum possible length, based on the ** fact that blocks can't span bigblock boundaries: */ /* We only have one byte in which to store the length... * see blockhdr.h * Note: Offset and CACHELEN are multiples of BLOCKSIZE. */ { unsigned long LastUsedBlock = (Candidate - BLOCKSIZE) & (CACHELEN - 1); MaxLength = (CACHELEN - LastUsedBlock) / BLOCKSIZE; if (BytesWanted != 0) { int MaxWanted = (BytesWanted / BLOCKSIZE) + 1; if (MaxLength > MaxWanted) { MaxLength = MaxWanted; } } } /* we only have one byte available... * the limit here is arbitrary. */ if (MaxLength > MAX_CONTIGUOUS_BLOCKS) { MaxLength = MAX_CONTIGUOUS_BLOCKS; } while (*BlockCountp < MaxLength && LQT_BlockIsFree(db, Candidate)) { LQT_SetBlockStatus(db, Candidate, SET_BLOCK_AS_USED); ++*BlockCountp; Candidate += BLOCKSIZE; } #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_PUTPLACES|LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_PUTPLACES|LQTRACE_FREEBLOCKS, "Extend %ld --> %d\n", Offset, *BlockCountp ); } #endif } /* * LQT_BlockIsFree * Database/Physical * *

Determine the status of the block at a given byte offset from the * start of the data overflow file (data). * An external file, freelist, is kept in the database directory; this * file uses a single bit to represent the status of each block, either * in use or free. If the freelist file is removed, subsequent * attempts to write to the database will fail. Read-only * access will still work unless LQTRACE_READAFTERWRITE is set, * whereupon LQT_ReadBlock checks the status of each * block before returning it; * it is an error to attempt to read an unallocated block, * although this not normally checked, for performance reasons.

* * Non-zero if the block is available, zero if it is free * *

The first few blocks are reserved for storing information about * the database; they are marked as used automatically whenever a * database is created.

*

The freelist file can be rebuilt by the lqmkfreelist program.

*

The test program `free' contains examples of using the Block * Status functions LQT_BlockIsFree and LQT_SetBlockStatus. * It can also be used to edit the contents of the freelist file.

* * Fatal error if the freelist file could not be opened * * LQT_SetBlockStatus * LQT_FindFreeBlock * LQT_ReadBlock *
*/ API int LQT_BlockIsFree(db, Offset) t_LQTEXT_Database *db; unsigned long Offset; { if (FreeFile < 0) { OpenFreeFile(db); } /* First make sure that the necessary bitmap block is loaded: */ if ( Offset < FreeBlockStart || Offset >= (FreeBlockStart + (BYTESPERFREEBIT * FREEBITLEN)) ) { ReadFreeBitBlock(db, Offset); } { /* Declare p and ui now, so that they weren't around for the * ReadFreeBitBlock() call above. This may save a register dump on * some systems. LQT_BlockIsFree is called a *lot*. */ register unsigned char *p; register unsigned int ui; ui = (Offset - FreeBlockStart) / BLOCKSIZE; /* the bit address */ p = &FreeBitBlock[ui >> 3]; /* i.e. ui/8, i.e. the right byte */ if ((*p) & SetBitInByte(ui & 07)) { #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_FREEBLOCKS, "", Offset); } #endif return 0; /* bit is set --> block is in use */ } else { /* We could update LowestFreeBlock here, but the chances are * that we're about to allocate the block, and then we'd end * up with LowestFreeBlock being used immediately, and lost * the advantage. * So we only set it if it isn't already set. */ if (!LowestFreeBlock) { LowestFreeBlock = Offset; } #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_FREEBLOCKS, "", Offset); } #endif return 1; /* bit not set --> block is free */ } } } /* * LQT_SetBlockStatus * Database/Physical * *

Set the status of the block at a given byte offset in the * data file.

*

Status must be either SET_BLOCK_AS_USED or SET_BLOCK_AS_FREE. * In the former (USED) case, the block is marked as being in use, * and can be brought into the cache with LQT_ReadBlock. * In the latter case (FREE), the block is marked as being available * for reuse. Since LQT_SetBlockStatus does not access the actual * data, it does not have access to the block's length. It is * therefore the caller's responsibility to call LQT_SetBlockstatus * for each contiguous block when a block header's NumberOfBlock * field is greater than one.

* * This routine was called 2,785,338 times when indexing Shakespeare's * complete works. To try and speed things up, LQT_SetBlockstatus * performs as few checks as possible. * * LQT_BlockIsFree *
*/ API void LQT_SetBlockStatus(db, Offset, Status) t_LQTEXT_Database *db; unsigned long Offset; int Status; { if (FreeFile < 0) { OpenFreeFile(db); } /* First make sure that the necessary bitmap block is loaded: */ if (Offset >= (FreeBlockStart + (BYTESPERFREEBIT * FREEBITLEN)) || (Offset < FreeBlockStart)) { ReadFreeBitBlock(db, Offset); } { /* Declare p and ui now, so that they weren't around for * ReadFreeBitBlock(). This may save a register dump on * some systems. */ register unsigned char *p; register unsigned int ui; ui = (Offset - FreeBlockStart) / BLOCKSIZE; /* the bit address */ p = &FreeBitBlock[ui >> 3]; /* i.e. ui/8, i.e. the right byte */ if (Status == SET_BLOCK_AS_FREE) { /* ui & 07 is ui % 8 is the bit within the byte: */ *p &= (unsigned char)~(unsigned char) SetBitInByte(ui & 07); if (!LowestFreeBlock || Offset < LowestFreeBlock) { LowestFreeBlock = Offset; } #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_FREEBLOCKS, "", Offset); } #endif } else { #ifdef PARANOID if (Status != SET_BLOCK_AS_USED) { Error(E_BUG, "LQT_SetBlockStatus(%ld, %d)", Offset, Status); } #endif /* no point in setting LowestFreeBlock, as we don't know * what to set it to. But there is no free block below it. */ #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_FREEBLOCKS)) { LQT_Trace(LQTRACE_FREEBLOCKS, "", Offset); } #endif *p |= (unsigned char) SetBitInByte(ui & 07); } FreeBitBlockIsDirty = 1; } } /* * LQT_ReadBlock * Database/Files, Database/Physical * * Reads the block at the given byte offset, and returns a pointer to * the data. The data is stored in a cache, so it is important not * to try and write beyond the end of the block or group of blocks as * determined by LQT_ExtendBlock or LQT_FindFreeBlock. * The block must be written out with LQT_WriteBlock if it has changed. * In addition, the block is not locked in memory, but LQT_ReadBlock * ensures that it is safe to read at least one other block before * writing this one out with LQT_WriteBlock. * * A pointer to the data * * Attempts to read beyond the end of the data file will extend * the database automatically. * The data will be initialised to zero, except for the block headers, * whose NumberOfBlocks field will all be set to one. * * Fatal error (E_BUG) if the database can't be opened or created. * * LQT_ExtendBlock * LQT_FindFreeBlock * LQT_WriteBlock * */ API unsigned char * LQT_ReadBlock(db, Offset, WID) t_LQTEXT_Database *db; unsigned long Offset; t_WID WID; { t_BlockHeader *BH; if (DataFile < 0) { OpenDataBase(db); } #ifdef WIDINBLOCK { t_WID otherWID = LQTpGetWIDofBlock(db, Offset); if (WID && otherWID && otherWID != WID) { Error(E_WARN|E_BUG, "Reading block %ld, WID %ld, was last written for WID %ld", Offset, WID, otherWID ); } } #endif if (LQT_BlockIsCached(db, Offset)) { if (++BlockCacheTimer < 1) { LQTpMoveCacheDatesBack(db); } CurrentBlock->TimeLastUsed = BlockCacheTimer; #ifdef ASCIITRACE /* Block 0 is used to store other data: */ if (WID && Offset > 0L) { if (LQT_TraceFlagsSet(LQTRACE_READBLOCK) #ifdef WIDINBLOCK || 1 #endif ) { BH = (t_BlockHeader *) &(CurrentBlock->ReadAheadBuffer[ Offset - CurrentBlock->CacheStart ]); LQT_Trace(LQTRACE_READBLOCK, "READ %ld len %d Next %ld", Offset, BH->NumberOfBlocks, BH->NextOffset ); #ifdef WIDINBLOCK if (BH->WID != WID) { Error(E_WARN|E_INTERNAL, "%s: %d: LQT_ReadBlock(%ld, WID %ld) Block WID is %ld (next %ld)", __FILE__, __LINE__, Offset, WID, BH->WID, BH->NextOffset ); BH->WID = WID; } #endif /* WIDINBLOCK */ } } #endif /*ASCIITRACE */ return &(CurrentBlock->ReadAheadBuffer[Offset - CurrentBlock->CacheStart]); } /* So it's not cached... * Which means we have to read a new cache. * So let's make some room: */ LQT_FlushOneBlockFromCache(db); /* cache only on CACHELEN boundaries (typically 8K) as this helps the * file system, especially over NFS. */ CurrentBlock->CacheStart = Offset / CACHELEN; CurrentBlock->CacheStart *= CACHELEN; (void) LQU_Elseek(E_FATAL, db->DataBase, "Main data file", DataFile, CurrentBlock->CacheStart, SEEK_SET /* = 0 */ ); /* Now we are in the right place */ CurrentBlock->CacheLen = read( DataFile, (char *) CurrentBlock->ReadAheadBuffer, CACHELEN ); #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_READBLOCK)) { LQT_Trace(LQTRACE_READBLOCK, "READ %ld len %d -> %d Cached", Offset, CACHELEN, CurrentBlock->CacheLen ); } #endif if (CurrentBlock->CacheLen < 0) { Error(E_FATAL|E_SYS, "LQT_ReadBlock: read(%d=\"%s\"...) returned %d, not %d", DataFile, db->DataBase, CurrentBlock->CacheLen, CACHELEN ); } BH = (t_BlockHeader *) &(CurrentBlock->ReadAheadBuffer[Offset-CurrentBlock->CacheStart]); #ifdef WIDINBLOCK if (WID) { if (BH->WID != WID) { Error(E_WARN|E_INTERNAL, "%s: %d: LQT_ReadBlock(%ld, WID %ld) WID in block is %ld", __FILE__, __LINE__, Offset, WID, BH->WID ); BH->WID = WID; } } #endif if (CurrentBlock->CacheLen < CACHELEN) { /* If CacheLen was less than CACHELN this can only mean that we * have tried to read beyond the end of the file. In this case, * we might as well consider that we have the extra bytes cached * as well. The disadvantage is that the file will always grow to * a multiple of CACHELEN, but the advantage is fewer reads when * the file is growing. */ /* First, zero out the block if it's near the start. * This is so that we can check for things stored in block zero. */ bzero( (char *) &CurrentBlock->ReadAheadBuffer[CurrentBlock->CacheLen], CACHELEN - CurrentBlock->CacheLen ); { register long Block = CurrentBlock->CacheLen / BLOCKSIZE; for (Block *= BLOCKSIZE; Block < CACHELEN; Block += BLOCKSIZE) { BH = (t_BlockHeader *) &(CurrentBlock->ReadAheadBuffer[Block]); if (BH->NumberOfBlocks == 0) { BH->NumberOfBlocks = 1; } } } BH = (t_BlockHeader *) &(CurrentBlock->ReadAheadBuffer[Offset-CurrentBlock->CacheStart]); if (BH->NumberOfBlocks == 0) { BH->NumberOfBlocks = 1; } CurrentBlock->CacheLen = CACHELEN; } if (++BlockCacheTimer < 1) { LQTpMoveCacheDatesBack(db); } CurrentBlock->TimeLastUsed = BlockCacheTimer; #ifdef ASCIITRACE if (LQT_TraceFlagsSet(LQTRACE_READBLOCK)) { printcache(db); } #endif return &(CurrentBlock->ReadAheadBuffer[Offset - CurrentBlock->CacheStart]); } /* * LQT_DeleteWordPlaces * Database/Update, Database/Words * *

Deletes the word places from disk for a given WID, marking the * corresponding data blocks as unused.

*

The given FirstBlock argument is the first block in the chain of * the linked list of blocks for the given WID. If the data is * contained entirely in the WID index block, LQT_DeleteWordPlaces * should not be called, and this is a fatal error.

*

LQT_DeleteWordPlaces does not remove the WID <--> Word mapping * from the wordlist Key Value Database, and does not zero out the * information in the widindex block.

* * Fatal (E_BUG) error if FirstBlock or WID are zero. * * LQT_Deletepblock *
*/ API void LQT_DeleteWordPlaces(db, FirstBlock, WID) t_LQTEXT_Database *db; unsigned long FirstBlock; t_WID WID; { t_BlockHeader *H; unsigned long Block = FirstBlock; if (!FirstBlock || !WID) { Error(E_BUG, "LQT_DeleteWordPlaces(%ld, %ld) invalid", FirstBlock, WID); } if (DataFile < 0) { OpenDataBase(db); } while (Block != 0L) { int NumberOfBlocks; H = (t_BlockHeader *) LQT_ReadBlock(db, Block, WID); NumberOfBlocks = H->NumberOfBlocks; #ifdef WIDINBLOCK H->WID = 0; /* help debugging: */ (void) sprintf(&H->Data[0], "Deleted -- was %ld\n", WID); /* only need to write the block if debugging... */ LQT_WriteBlock( db, Block, (unsigned char *) H H->NumberOfBlocks, (t_WID) 0 ); #endif while (NumberOfBlocks > 0) { --NumberOfBlocks; LQT_SetBlockStatus(db, Block + (NumberOfBlocks * BLOCKSIZE), SET_BLOCK_AS_FREE); } Block = H->NextOffset; } return; } /* * LQT_Deletepblock * Database/Words, Database/Update * *

Deletes the word places for a given pblock, using * LQT_DeleteWordPlaces.

*

Like LQT_DeleteWordPlaces, LQT_Deletepblock does not remove * the WID <--> Word mapping * from the wordlist Key Value Database, and does not zero out the * information in the widindex block.

*

In other words, the word is not removed from the database * vocabulary, and subsequent calls to LQT_WordToWID will return * the same WID value as before the call to LQT_Deletepblock.

* * LQT_Getpblock * LQT_DestroyWordInfo * */ API void LQT_Deletepblock(db, pblock) t_LQTEXT_Database *db; t_pblock *pblock; { if (!pblock) { Error(E_BUG, "Attempt to delete null pblock (address 0x%x)", pblock); } else if (!pblock->ChainStart || !pblock->WID) { Error(E_BUG, "Attempt to delete pblock 0x%x with WID %d, ChainStart %ld", pblock, pblock->WID, pblock->ChainStart ); } LQT_DeleteWordPlaces(db, pblock->ChainStart, pblock->WID); return; }