/* * $Header$ * * Copyright (C) 1989, 1990 by the Massachusetts Institute of Technology * For copying and distribution information, please see the file * . */ #ifndef lint static char *rcsid_cache_dc = "$Header$"; #endif lint #include #include "query.h" #include "mr_server.h" EXEC SQL INCLUDE sqlca; EXEC SQL WHENEVER SQLERROR CALL ingerr; extern char *whoami, *strsave(); extern int ingres_errno, mr_errcode; /*** NOTE ************************************************************** * * This code depends on each type starting with a unique letter. If * any new types are added to the system that begin with the same * letter as one of the existing types: * User * List * String * Machine * Subnet * Cluster * Filesystem * then we will have to rework the code that only looks at the first * letter of the types. * *********************************************************************** */ /* Cache parameters: */ #define CACHESIZE 101 /* number of cache slots */ #define NAMESZ 257 /* max size of a name */ struct item { char name[NAMESZ]; char type[9]; int nhash; int id; struct item *next; struct item *prev; struct item *trans; /* keep track of transactions */ }; static struct item cachehead; static int cachesize; /* statistics counters */ int cachehits = 0, cachemisses = 0; /* Name hash function. */ int hashname(name, type) register char *name; char *type; { register int val = *type; while (*name) val = val<<5 - val + *name++ - '`'; return(val); } /* Initialize the cache, flushing any old data, and report the statistics * if the cache was previously in use. */ flush_cache() { register struct item *i; if (cachehits + cachemisses != 0) com_err(whoami, 0, "Flushing cache; %d hits, %d misses, %d%% hit rate", cachehits, cachemisses, (100 * cachehits) / (cachehits + cachemisses)); else cachehead.next = cachehead.prev = &cachehead; cachehits = cachemisses = cachesize = 0; for (i = cachehead.next; i != &cachehead; i = i->next) { if (i->prev != &cachehead) free(i->prev); } if (cachehead.prev != &cachehead) free(cachehead.prev); cachehead.next = cachehead.prev = &cachehead; cachehead.trans = (struct item *)NULL; } /* Do a name to ID translation. id will be updated with the answer if * it is available, and as a side effect the cache is updated. */ int name_to_id(name, type, id) char *name; char *type; int *id; { register struct item *i, *t; EXEC SQL BEGIN DECLARE SECTION; char *iname; int j, rowcount; EXEC SQL END DECLARE SECTION; char key; int h, ctr; h = hashname(name, type); for (i = cachehead.next; i != &cachehead; i = i->next) { if (i->nhash != h || strcmp(name, i->name) || strcasecmp(type, i->type)) continue; *id = i->id; cachehits++; i->next->prev = i->prev; i->prev->next = i->next; i->next = cachehead.next; i->prev = &cachehead; cachehead.next->prev = i; cachehead.next = i; return(MR_SUCCESS); } cachemisses++; iname = name; key = *type; if (!strcasecmp(type, "subnet")) key = 'N'; switch (key) { case 'U': case 'u': if (index(iname, '@') || (strlen(iname) > 8)) { sqlca.sqlcode = 100; break; } EXEC SQL SELECT users_id INTO :j FROM users WHERE login=:iname; break; case 'L': case 'l': EXEC SQL SELECT list_id INTO :j FROM list WHERE name=:iname; break; case 'M': case 'm': EXEC SQL SELECT mach_id INTO :j FROM machine WHERE name=UPPERCASE(:iname); break; case 'N': case 'n': EXEC SQL SELECT snet_id INTO :j FROM subnet WHERE name=UPPERCASE(:iname); break; case 'C': case 'c': EXEC SQL SELECT clu_id INTO :j FROM cluster WHERE name=:iname; break; case 'F': case 'f': EXEC SQL SELECT filsys_id INTO :j FROM filesys WHERE label=:iname; break; case 'S': case 's': EXEC SQL SELECT string_id INTO :j FROM strings WHERE string=:iname; break; default: return(MR_INTERNAL); } if (sqlca.sqlcode == 100) return(MR_NO_MATCH); if (sqlca.sqlerrd[2] > 1) return(MR_NOT_UNIQUE); if (sqlca.sqlcode != 0) return(MR_INGRES_ERR); *id = j; if (name[0] == '#' && !strcasecmp(type, "USER")) return(MR_SUCCESS); if (cachesize < CACHESIZE) { i = (struct item *) malloc(sizeof(struct item)); cachesize++; } else { i = cachehead.prev; cachehead.prev = i->prev; i->prev->next = &cachehead; } strcpy(i->name, name); strcpy(i->type, type); i->nhash = h; i->id = j; i->next = cachehead.next; i->prev = &cachehead; cachehead.next->prev = i; cachehead.next = i; /* find the end of the transaction chain & add this item */ for (t = &cachehead; t->trans && t != i; t = t->trans); if (t != i) { t->trans = i; i->trans = (struct item *)NULL; } return(MR_SUCCESS); } /* Perform an ID to name mapping. name should be a pointer to a pointer to * malloc'ed data. The buffer it refers to will be freed, and a new buffer * allocated with the answer. */ int id_to_name(id, type, name) int id; char *type; char **name; { register struct item *i, *t; EXEC SQL BEGIN DECLARE SECTION; char iname[NAMESZ]; int j, rowcount; EXEC SQL END DECLARE SECTION; char key; int ctr; for (i = cachehead.next; i != &cachehead; i = i->next) { if (i->id != id || strcasecmp(type, i->type)) continue; free(*name); *name = strsave(i->name); cachehits++; i->next->prev = i->prev; i->prev->next = i->next; i->next = cachehead.next; i->prev = &cachehead; cachehead.next->prev = i; cachehead.next = i; return(MR_SUCCESS); } cachemisses++; j = id; key = *type; if (!strcasecmp(type, "subnet")) key = 'N'; switch (key) { case 'U': case 'u': EXEC SQL SELECT CHAR(login) INTO :iname FROM users WHERE users_id=:j; break; case 'L': case 'l': EXEC SQL SELECT CHAR(name) INTO :iname FROM list WHERE list_id=:j; break; case 'M': case 'm': EXEC SQL SELECT CHAR(name) INTO :iname FROM machine WHERE mach_id=:j; break; case 'N': case 'n': EXEC SQL SELECT CHAR(name) INTO :iname FROM subnet WHERE snet_id=:j; break; case 'C': case 'c': EXEC SQL SELECT CHAR(name) INTO :iname FROM cluster WHERE clu_id=:j; break; case 'F': case 'f': EXEC SQL SELECT CHAR(label) INTO :iname FROM filesys WHERE filsys_id=:j; break; case 'S': case 's': EXEC SQL SELECT CHAR(string) INTO :iname FROM strings WHERE string_id=:j; break; default: return(MR_INTERNAL); } if (sqlca.sqlcode == 100) { free(*name); sprintf(iname, "#%d", j); *name = strsave(iname); return(MR_NO_MATCH); } if (sqlca.sqlerrd[2] > 1) return(MR_INTERNAL); if (sqlca.sqlcode != 0) return(MR_INGRES_ERR); free(*name); *name = strsave(strtrim(iname)); if (**name == '#' && !strcasecmp(type, "USER")) return(MR_SUCCESS); if (cachesize < CACHESIZE) { i = (struct item *) malloc(sizeof(struct item)); cachesize++; } else { i = cachehead.prev; cachehead.prev = i->prev; i->prev->next = &cachehead; } strcpy(i->name, *name); strcpy(i->type, type); i->nhash = hashname(*name, type); i->id = id; i->next = cachehead.next; i->prev = &cachehead; cachehead.next->prev = i; cachehead.next = i; /* find the end of the transaction chain & add this item */ for (t = &cachehead; t->trans && t != i; t = t->trans); if (t != i) { t->trans = i; i->trans = (struct item *)NULL; } return(MR_SUCCESS); } /* Explicitly add something to the cache without doing a lookup in the * database. */ cache_entry(name, type, id) char *name; char *type; int id; { register struct item *i, *t; for (i = cachehead.next; i != &cachehead; i = i->next) if (i->id == id && !strcmp(i->type, type)) { if (strcmp(i->name, name)) { strcpy(i->name, name); i->nhash = hashname(name, type); } return(MR_SUCCESS); } if (cachesize < CACHESIZE) { i = (struct item *) malloc(sizeof(struct item)); cachesize++; } else { i = cachehead.prev; cachehead.prev = i->prev; i->prev->next = &cachehead; } strcpy(i->name, name); strcpy(i->type, type); i->nhash = hashname(name, type); i->id = id; i->next = cachehead.next; i->prev = &cachehead; cachehead.next->prev = i; cachehead.next = i; /* find the end of the transaction chain & add this item */ for (t = &cachehead; t->trans && t != i; t = t->trans); if (t != i) { t->trans = i; i->trans = (struct item *)NULL; } return(MR_SUCCESS); } /* Flush something that may or may not already be in the cache. */ flush_name(name, type) char *name; char *type; { int h; register struct item *i; h = hashname(name, type); for (i = cachehead.next; i != &cachehead; i = i->next) { if (!strcmp(name, i->name) && !strcasecmp(type, i->type)) { cachesize--; i->next->prev = i->prev; i->prev->next = i->next; free(i); return(MR_SUCCESS); } } } /* Called when a transaction is committed to also commit any cache changes. * Just throws away the list of cache changes for this transaction. */ cache_commit() { cachehead.trans = (struct item *)NULL; } /* Called whan a transaction is aborted to throw away any cache changes * from that transaction. */ cache_abort() { register struct item *i, *t; for (i = cachehead.trans; i; i = t) { t = i->trans; flush_name(i->name, i->type); } cachehead.trans = (struct item *)NULL; }