/*
 *	$Header: /afs/sipb.mit.edu/project/sipb-athena/repository/src/moira/server/cache.dc,v 2.11 1996/06/02 07:44:46 ghudson Exp $
 *
 *	Copyright (C) 1989, 1990 by the Massachusetts Institute of Technology
 *	For copying and distribution information, please see the file
 *	<mit-copyright.h>.
 */

#ifndef lint
static char *rcsid_cache_dc = "$Header: /afs/sipb.mit.edu/project/sipb-athena/repository/src/moira/server/cache.dc,v 2.11 1996/06/02 07:44:46 ghudson Exp $";
#endif lint

#include <mit-copyright.h>
#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;
}
