/* $Id$ * * Keep a cache of name/id mappings * * Copyright (C) 1989-1998 by the Massachusetts Institute of Technology * For copying and distribution information, please see the file * . */ #include #include "mr_server.h" #include "query.h" #include #include EXEC SQL INCLUDE sqlca; RCSID("$Header$"); EXEC SQL WHENEVER SQLERROR DO dbmserr(); extern char *whoami; /* Cache parameters: */ #define CACHESIZE 101 /* number of cache slots */ struct item { char name[MAX_FIELD_WIDTH]; enum tables type; 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; int hashname(char *name, enum tables type); /* Name hash function. */ int hashname(char *name, enum tables type) { 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. */ void flush_cache(void) { 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 = 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(char *name, enum tables type, int *id) { struct item *i, *t; EXEC SQL BEGIN DECLARE SECTION; char *iname; int j, h; EXEC SQL END DECLARE SECTION; h = hashname(name, type); for (i = cachehead.next; i != &cachehead; i = i->next) { if (i->nhash != h || strcmp(name, i->name) || 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; switch (type) { case USERS_TABLE: if (strchr(iname, '@') || (strlen(iname) > 8)) { sqlca.sqlcode = SQL_NO_MATCH; break; } EXEC SQL SELECT users_id INTO :j FROM users WHERE login = :iname; break; case LIST_TABLE: EXEC SQL SELECT list_id INTO :j FROM list WHERE name = :iname; break; case MACHINE_TABLE: EXEC SQL SELECT mach_id INTO :j FROM machine WHERE name = UPPER(:iname); break; case SUBNET_TABLE: EXEC SQL SELECT snet_id INTO :j FROM subnet WHERE name = UPPER(:iname); break; case CLUSTERS_TABLE: EXEC SQL SELECT clu_id INTO :j FROM clusters WHERE name = :iname; break; case FILESYS_TABLE: EXEC SQL SELECT filsys_id INTO :j FROM filesys WHERE label = :iname; break; case STRINGS_TABLE: if (!iname[0]) /* special-case empty string */ { *id = 0; return MR_SUCCESS; } EXEC SQL SELECT string_id INTO :j FROM strings WHERE string = :iname; break; default: return MR_INTERNAL; } if (sqlca.sqlcode == SQL_NO_MATCH) return MR_NO_MATCH; if (sqlca.sqlerrd[2] > 1) return MR_NOT_UNIQUE; if (sqlca.sqlcode) return MR_DBMS_ERR; *id = j; if (name[0] == '#' && type != USERS_TABLE) return MR_SUCCESS; if (cachesize < CACHESIZE) { i = malloc(sizeof(struct item)); if (!i) return MR_SUCCESS; cachesize++; } else { i = cachehead.prev; cachehead.prev = i->prev; i->prev->next = &cachehead; } strcpy(i->name, name); i->type = type; i->nhash = h; i->id = j; i->next = cachehead.next; i->prev = &cachehead; i->trans = NULL; 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; 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(int id, enum tables type, char **name) { struct item *i, *t; EXEC SQL BEGIN DECLARE SECTION; char iname[MAX_FIELD_WIDTH]; int j; EXEC SQL END DECLARE SECTION; for (i = cachehead.next; i != &cachehead; i = i->next) { if (i->id != id || type != i->type) continue; free(*name); *name = xstrdup(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; switch (type) { case USERS_TABLE: EXEC SQL SELECT login INTO :iname FROM users WHERE users_id = :j; break; case LIST_TABLE: EXEC SQL SELECT name INTO :iname FROM list WHERE list_id = :j; break; case MACHINE_TABLE: EXEC SQL SELECT name INTO :iname FROM machine WHERE mach_id = :j; break; case SUBNET_TABLE: EXEC SQL SELECT name INTO :iname FROM subnet WHERE snet_id = :j; break; case CLUSTERS_TABLE: EXEC SQL SELECT name INTO :iname FROM clusters WHERE clu_id = :j; break; case FILESYS_TABLE: EXEC SQL SELECT label INTO :iname FROM filesys WHERE filsys_id = :j; break; case STRINGS_TABLE: EXEC SQL SELECT string INTO :iname FROM strings WHERE string_id = :j; break; default: return MR_INTERNAL; } if (sqlca.sqlcode == SQL_NO_MATCH) { free(*name); sprintf(iname, "#%d", j); *name = xstrdup(iname); return MR_NO_MATCH; } if (sqlca.sqlerrd[2] > 1) return MR_INTERNAL; if (sqlca.sqlcode) return MR_DBMS_ERR; free(*name); *name = xstrdup(strtrim(iname)); if (**name == '#' && type != USERS_TABLE) return MR_SUCCESS; if (cachesize < CACHESIZE) { i = malloc(sizeof(struct item)); if (!i) return MR_SUCCESS; cachesize++; } else { i = cachehead.prev; cachehead.prev = i->prev; i->prev->next = &cachehead; } strcpy(i->name, *name); i->type = type; i->nhash = hashname(*name, type); i->id = id; i->next = cachehead.next; i->prev = &cachehead; i->trans = NULL; 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; return MR_SUCCESS; } /* Explicitly add something to the cache without doing a lookup in the * database. */ int cache_entry(char *name, enum tables type, int id) { struct item *i, *t; for (i = cachehead.next; i != &cachehead; i = i->next) { if (i->id == id && i->type == type) { if (strcmp(i->name, name)) { strcpy(i->name, name); i->nhash = hashname(name, type); } return MR_SUCCESS; } } if (cachesize < CACHESIZE) { i = malloc(sizeof(struct item)); if (!i) return MR_SUCCESS; cachesize++; } else { i = cachehead.prev; cachehead.prev = i->prev; i->prev->next = &cachehead; } strcpy(i->name, name); i->type = type; i->nhash = hashname(name, type); i->id = id; i->next = cachehead.next; i->prev = &cachehead; i->trans = NULL; 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; return MR_SUCCESS; } /* Flush something that may or may not already be in the cache. */ void flush_name(char *name, enum tables type) { struct item *i; /* first clear it out of the transaction chain */ for (i = &cachehead; i && i->trans; i = i->trans) { if (!strcmp(name, i->trans->name) && type == i->trans->type) i->trans = i->trans->trans; } /* now remove it */ for (i = cachehead.next; i != &cachehead; i = i->next) { if (!strcmp(name, i->name) && type == i->type) { cachesize--; i->next->prev = i->prev; i->prev->next = i->next; free(i); break; } } } /* Called when a transaction is committed to also commit any cache changes. * Just throws away the list of cache changes for this transaction. */ void cache_commit(void) { cachehead.trans = NULL; } /* Called whan a transaction is aborted to throw away any cache changes * from that transaction. */ void cache_abort(void) { struct item *i, *t; for (i = cachehead.trans; i; i = t) { t = i->trans; flush_name(i->name, i->type); } cachehead.trans = NULL; }