mirror of
https://github.com/open-simh/simtools.git
synced 2026-01-24 11:22:09 +00:00
Reorganize commands into separate modules - ods2.c was way too big. This reduces it from around 3000 LOC to less than 1K LOC. Reorder I/O so PHYVIRT mediates all physical access. Fix memory leaks of device structures. Reorganize Files-11 vs. internal structures. Allow more granular control of DEBUG from the makefile. Show VOLUME handles free space and tries to match geometry from SCB to known disk types. Fix many compilation warnings. Add code to allow dumping disk data for debug. Automatically generate descrip.mms from dependencies. Correctly handle directory default version limit; previously confused with file version limit. Teach help to sort tables for presentation; manually keeping them sorted is problematic, and code maintenance prefers functional groupings. Add the ability to initialize a Files-11 volume. (Not quite complete.) Add dependency generation to makefiles. Excludes system headers so makedepends isn't required to build. Simplify makefiles to use more recipes. Teach makefiles to list all sources and headers so it's easier to keep git and MSVC up-to-date. Add support for accessing images of disks with interleave/skew/offsets (e.g. RX02). Add VHD support, including ability to create a snapshot with mount/write. Teach RMS to handle NOSPAN variable length records correctly. Fix RMS GET handling of variable length records with VFC that span block boundaries. Fix delete file (a day-one incomplete). Still some cases to validate. Purge cache of modified blocks at rundown and at the end of each command. Do not allow deletion of reserved files. Move revision history to version.h Correct various permissions in git.
424 lines
15 KiB
C
424 lines
15 KiB
C
/* Cache.c Caching control routines */
|
|
|
|
/*
|
|
This is part of ODS2 written by Paul Nankervis,
|
|
email address: Paulnank@au1.ibm.com
|
|
|
|
ODS2 is distributed freely for all members of the
|
|
VMS community to use. However all derived works
|
|
must maintain comments in their source to acknowledge
|
|
the contibution of the original author.
|
|
*/
|
|
|
|
/*
|
|
The theory is that all cachable objects share a common cache pool.
|
|
Any object with a reference count of zero is a candidate for
|
|
destruction. All cacheable objects have a 'struct CACHE' as the
|
|
first item of the object so that cache pointers and object pointers
|
|
are 'interchangeable'. All cache objects are also part of a binary
|
|
tree so that they can be located. There are many instances of these
|
|
binary trees: for files on a volume, file windows, file chunks
|
|
(segments) etc. Also each object can have an object manager: a
|
|
routine to handle special object deletion requirements (for example
|
|
to remove all file chunks before removing a file), or to flush
|
|
objects (write modified chunks to disk).
|
|
|
|
The main routines is cache_find() which is used to search for and
|
|
place objects in a binary tree which is located by a tree pointer.
|
|
cache_touch() and cache_untouch() are used to bump and decrement
|
|
reference counts. Any object with a reference count of zero is a
|
|
candidate for destruction - but if it requires special action
|
|
before it is destroyed, such as deleting a subtree, flushing data
|
|
to disk, etc, then it should have an 'object manager' function
|
|
assigned which will be called at deletion time to take care of these
|
|
needs.
|
|
|
|
This version of the cache routines attempts to maintain binary tree
|
|
balance by dynamically changing tree shape during search functions.
|
|
All objects remain in the binary tree until destruction so that they
|
|
can be re-used at any time. Objects with a zero reference count are
|
|
special in that they are kept in a 'least recently used' linked list.
|
|
When the reference count is decemented a flag is used to indicate
|
|
whether the object is likely to be referenced again so that the object
|
|
can be put in the 'correct' end of this list.
|
|
|
|
Note: These routines are 'general' in that do not know anything
|
|
about ODS2 objects or structures....
|
|
*/
|
|
|
|
#if !defined( DEBUG ) && defined( DEBUG_CACHE )
|
|
#define DEBUG DEBUG_CACHE
|
|
#else
|
|
#ifndef DEBUG
|
|
#define DEBUG 0
|
|
#endif
|
|
#endif
|
|
|
|
/* *** TEMP TODO ***/
|
|
#undef DEBUG
|
|
#define DEBUG 1
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "cache.h"
|
|
#include "ods2.h"
|
|
#include "ssdef.h"
|
|
|
|
#define IMBALANCE 5 /* Tree imbalance limit */
|
|
#define CACHELIM 256 /* Free object limit */
|
|
#define CACHEGOAL 128 /* Free object goal */
|
|
|
|
static int cachefinds = 0;
|
|
static int cachecreated = 0;
|
|
static int cachepurges = 0;
|
|
static int cachepeak = 0;
|
|
static int cachecount = 0;
|
|
static int cachefreecount = 0;
|
|
static int cachedeletes = 0;
|
|
|
|
static int cachedeleteing = FALSE; /* Cache deletion in progress... */
|
|
|
|
struct CACHE lrulist = {&lrulist,&lrulist,NULL,NULL,NULL,NULL,0,0,1,0};
|
|
|
|
static void cache_deleter( struct CACHE *cacheobj, struct CACHE **actualobj );
|
|
|
|
/*************************************************************** cache_show() */
|
|
|
|
/* cache_show() - to print cache statistics */
|
|
|
|
void cache_show(void)
|
|
{
|
|
printf("CACHE_SHOW Find %d Create %d Purge %d Peak %d Count %d Free %d\n",
|
|
cachefinds,cachecreated,cachepurges,cachepeak,cachecount,
|
|
cachefreecount);
|
|
if (cachecreated - cachedeletes != cachecount) {
|
|
printf(" - Deleted %d\n",cachedeletes);
|
|
}
|
|
}
|
|
|
|
/*********************************************************** cache_refcount() */
|
|
|
|
/* cache_refcount() - compute reference count for cache subtree... */
|
|
|
|
int cache_refcount(struct CACHE *cacheobj)
|
|
{
|
|
register int refcount = 0;
|
|
if (cacheobj != NULL) {
|
|
refcount = cacheobj->refcount;
|
|
if (cacheobj->left != NULL) {
|
|
refcount += cache_refcount(cacheobj->left);
|
|
}
|
|
if (cacheobj->right != NULL) {
|
|
refcount += cache_refcount(cacheobj->right);
|
|
}
|
|
}
|
|
return refcount;
|
|
}
|
|
|
|
/************************************************************* cache_delete() */
|
|
|
|
/* cache_delete() - blow away item from cache - allow item to select a proxy (!)
|
|
and adjust 'the tree' containing the item. */
|
|
|
|
void cache_delete( struct CACHE *cacheobj )
|
|
{
|
|
cache_deleter( cacheobj, NULL );
|
|
}
|
|
|
|
/* Internal routine that does the work. Optionally returns the address of the
|
|
* object actually deleted, which allows pointers to be adjusted correctly.
|
|
*/
|
|
static void cache_deleter( struct CACHE *cacheobj, struct CACHE **actualobj ) {
|
|
while (cacheobj->objmanager != NULL) {
|
|
register struct CACHE *proxyobj;
|
|
cachedeleteing = TRUE;
|
|
proxyobj = (*cacheobj->objmanager) (cacheobj,FALSE);
|
|
cachedeleteing = FALSE;
|
|
if( proxyobj == NULL ) {
|
|
if( actualobj != NULL )
|
|
*actualobj = NULL;
|
|
return;
|
|
}
|
|
if (proxyobj == cacheobj) break;
|
|
cacheobj = proxyobj;
|
|
}
|
|
#if DEBUG
|
|
if (cachedeleteing) printf("CACHE deletion while delete in progress\n");
|
|
#endif
|
|
if (cacheobj->refcount != 0) {
|
|
#if DEBUG
|
|
printf("CACHE attempt to delete referenced object %d:%d\n",
|
|
cacheobj->objtype,cacheobj->hashval);
|
|
#endif
|
|
abort();
|
|
if( actualobj != NULL )
|
|
*actualobj = NULL;
|
|
return;
|
|
}
|
|
cacheobj->lastlru->nextlru = cacheobj->nextlru;
|
|
cacheobj->nextlru->lastlru = cacheobj->lastlru;
|
|
if (cacheobj->left == NULL) {
|
|
if (cacheobj->right == NULL) {
|
|
*(cacheobj->parent) = NULL;
|
|
} else {
|
|
cacheobj->right->parent = cacheobj->parent;
|
|
*(cacheobj->parent) = cacheobj->right;
|
|
}
|
|
} else {
|
|
if (cacheobj->right == NULL) {
|
|
cacheobj->left->parent = cacheobj->parent;
|
|
*(cacheobj->parent) = cacheobj->left;
|
|
} else {
|
|
register struct CACHE *path = cacheobj->right;
|
|
if (path->left != NULL) {
|
|
do {
|
|
path = path->left;
|
|
} while (path->left != NULL);
|
|
*(path->parent) = path->right;
|
|
if (path->right != NULL) path->right->parent = path->parent;
|
|
path->right = cacheobj->right;
|
|
path->right->parent = &path->right;
|
|
}
|
|
path->left = cacheobj->left;
|
|
path->left->parent = &path->left;
|
|
path->parent = cacheobj->parent;
|
|
*(cacheobj->parent) = path;
|
|
path->balance = 0;
|
|
}
|
|
}
|
|
cachecount--;
|
|
cachefreecount--;
|
|
cachedeletes++;
|
|
#if DEBUG
|
|
cacheobj->nextlru = NULL;
|
|
cacheobj->lastlru = NULL;
|
|
cacheobj->left = NULL;
|
|
cacheobj->right = NULL;
|
|
cacheobj->parent = NULL;
|
|
cacheobj->objmanager = NULL;
|
|
cacheobj->hashval = 0;
|
|
cacheobj->balance = 0;
|
|
cacheobj->refcount = 0;
|
|
#endif
|
|
if( actualobj != NULL ) /* The returned value is used as a flag. It will not be dereferenced. */
|
|
*actualobj = cacheobj;
|
|
|
|
free(cacheobj); /* This may be the supplied object, or it may have been replaced by a proxy. */
|
|
return;
|
|
}
|
|
|
|
/************************************************************** cache_purge() */
|
|
|
|
/* cache_purge() - trim size of free list */
|
|
|
|
void cache_purge(int all)
|
|
{
|
|
if (!cachedeleteing) {
|
|
register struct CACHE *cacheobj = lrulist.lastlru;
|
|
cachepurges++;
|
|
while ( (all || cachefreecount > CACHEGOAL) && cacheobj != &lrulist) {
|
|
register struct CACHE *lastobj = cacheobj->lastlru;
|
|
#if DEBUG
|
|
if (cacheobj->lastlru->nextlru != cacheobj ||
|
|
cacheobj->nextlru->lastlru != cacheobj ||
|
|
*(cacheobj->parent) != cacheobj) {
|
|
printf("CACHE pointers in bad shape!\n");
|
|
}
|
|
#endif
|
|
if (cacheobj->refcount == 0) {
|
|
struct CACHE *actualobj;
|
|
cache_deleter( cacheobj, &actualobj );
|
|
if (actualobj != lastobj) cacheobj = lastobj;
|
|
} else {
|
|
cacheobj = lastobj;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************** cache_flush() */
|
|
|
|
/* cache_flush() - flush modified entries in cache */
|
|
|
|
void cache_flush(void)
|
|
{
|
|
register struct CACHE *cacheobj = lrulist.lastlru;
|
|
while (cacheobj != &lrulist) {
|
|
if (cacheobj->objmanager != NULL) {
|
|
(*cacheobj->objmanager) (cacheobj,TRUE);
|
|
}
|
|
cacheobj = cacheobj->lastlru;
|
|
}
|
|
}
|
|
|
|
/************************************************************* cache_remove() */
|
|
|
|
/* cache_remove() - delete all possible objects from cache subtree */
|
|
|
|
void cache_remove(struct CACHE *cacheobj)
|
|
{
|
|
if (cacheobj != NULL) {
|
|
if (cacheobj->left != NULL) cache_remove(cacheobj->left);
|
|
if (cacheobj->right != NULL) cache_remove(cacheobj->right);
|
|
if (cacheobj->refcount == 0) {
|
|
struct CACHE *delobj;
|
|
do {
|
|
cache_deleter(cacheobj, &delobj);
|
|
} while (delobj != NULL && delobj != cacheobj);
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************** cache_touch() */
|
|
|
|
/* cache_touch() - to increase the access count on an object... */
|
|
|
|
void cache_touch(struct CACHE *cacheobj)
|
|
{
|
|
if (cacheobj->refcount++ == 0) {
|
|
#if DEBUG
|
|
if (cacheobj->nextlru == NULL || cacheobj->lastlru == NULL) {
|
|
printf("CACHE LRU pointers corrupt!\n");
|
|
abort();
|
|
}
|
|
#endif
|
|
cacheobj->nextlru->lastlru = cacheobj->lastlru;
|
|
cacheobj->lastlru->nextlru = cacheobj->nextlru;
|
|
cacheobj->nextlru = NULL;
|
|
cacheobj->lastlru = NULL;
|
|
cachefreecount--;
|
|
}
|
|
}
|
|
|
|
/************************************************************ cache_untouch() */
|
|
|
|
/* cache_untouch() - to deaccess an object... */
|
|
|
|
void cache_untouch(struct CACHE *cacheobj,int recycle)
|
|
{
|
|
if (cacheobj->refcount > 0) {
|
|
if (--cacheobj->refcount == 0) {
|
|
if (++cachefreecount >= CACHELIM) cache_purge(FALSE);
|
|
#if DEBUG
|
|
if (cacheobj->nextlru != NULL || cacheobj->lastlru != NULL) {
|
|
printf("CACHE LRU pointers corrupt\n");
|
|
}
|
|
#endif
|
|
if (recycle) {
|
|
cacheobj->nextlru = lrulist.nextlru;
|
|
cacheobj->lastlru = &lrulist;
|
|
cacheobj->nextlru->lastlru = cacheobj;
|
|
lrulist.nextlru = cacheobj;
|
|
} else {
|
|
cacheobj->lastlru = lrulist.lastlru;
|
|
cacheobj->nextlru = &lrulist;
|
|
cacheobj->lastlru->nextlru = cacheobj;
|
|
lrulist.lastlru = cacheobj;
|
|
}
|
|
}
|
|
} else {
|
|
#if DEBUG
|
|
printf("CACHE untouch limit exceeded\n");
|
|
#endif
|
|
abort();
|
|
}
|
|
}
|
|
|
|
/*************************************************************** cache_find() */
|
|
|
|
/* cache_find() - to find or create cache entries...
|
|
|
|
The grand plan here was to use a hash code as a quick key
|
|
and call a compare function for duplicates. So far no data
|
|
type actually works like this - they either have a unique binary
|
|
key, or all records rely on the compare function - sigh!
|
|
Never mind, the potential is there! :-)
|
|
|
|
This version will call a creation function to allocate and
|
|
initialize an object if it is not found.
|
|
*/
|
|
|
|
void *cache_find( void **root, unsigned hashval, void *keyval, unsigned *retsts,
|
|
int (*compare_func) ( unsigned hashval, void *keyval,
|
|
void *node ),
|
|
void *(*create_func) ( unsigned hashval, void *keyval,
|
|
unsigned *retsts ) ) {
|
|
|
|
register struct CACHE *cacheobj,**parent = (struct CACHE **) root;
|
|
cachefinds++;
|
|
while ((cacheobj = *parent) != NULL) {
|
|
register int cmp = hashval - cacheobj->hashval;
|
|
#if DEBUG
|
|
if (cacheobj->parent != parent) {
|
|
printf("CACHE Parent pointer is corrupt\n");
|
|
}
|
|
#endif
|
|
if (cmp == 0 && compare_func != NULL) {
|
|
cmp = (*compare_func) (hashval,keyval,cacheobj);
|
|
}
|
|
if (cmp == 0) {
|
|
cache_touch(cacheobj);
|
|
if (retsts != NULL) *retsts = SS$_NORMAL;
|
|
return cacheobj;
|
|
}
|
|
if (cmp < 0) {
|
|
#ifdef IMBALANCE
|
|
register struct CACHE *left_path = cacheobj->left;
|
|
if (left_path != NULL && cacheobj->balance-- < -IMBALANCE) {
|
|
cacheobj->left = left_path->right;
|
|
if (cacheobj->left != NULL) {
|
|
cacheobj->left->parent = &cacheobj->left;
|
|
}
|
|
left_path->right = cacheobj;
|
|
cacheobj->parent = &left_path->right;
|
|
*parent = left_path;
|
|
left_path->parent = parent;
|
|
cacheobj->balance = 0;
|
|
} else {
|
|
parent = &cacheobj->left;
|
|
}
|
|
} else {
|
|
register struct CACHE *right_path = cacheobj->right;
|
|
if (right_path != NULL && cacheobj->balance++ > IMBALANCE) {
|
|
cacheobj->right = right_path->left;
|
|
if (cacheobj->right != NULL) {
|
|
cacheobj->right->parent = &cacheobj->right;
|
|
}
|
|
right_path->left = cacheobj;
|
|
cacheobj->parent = &right_path->left;
|
|
*parent = right_path;
|
|
right_path->parent = parent;
|
|
cacheobj->balance = 0;
|
|
} else {
|
|
parent = &cacheobj->right;
|
|
}
|
|
#else
|
|
parent = &cacheobj->left;
|
|
} else {
|
|
parent = &cacheobj->right;
|
|
#endif
|
|
}
|
|
}
|
|
if (create_func == NULL) {
|
|
if (retsts != NULL) *retsts = SS$_ITEMNOTFOUND;
|
|
} else {
|
|
cacheobj = (*create_func) (hashval,keyval,retsts);
|
|
if (cacheobj != NULL) {
|
|
cacheobj->nextlru = NULL;
|
|
cacheobj->lastlru = NULL;
|
|
cacheobj->left = NULL;
|
|
cacheobj->right = NULL;
|
|
cacheobj->parent = parent;
|
|
cacheobj->hashval = hashval;
|
|
cacheobj->refcount = 1;
|
|
cacheobj->balance = 0;
|
|
*parent = cacheobj;
|
|
cachecreated++;
|
|
if (cachecount++ >= cachepeak) cachepeak = cachecount;
|
|
}
|
|
}
|
|
return cacheobj;
|
|
}
|