/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * AtFS -- Attribute Filesystem
 *
 * afcache.c -- realize caching for archives in core
 *
 * Author: Andreas Lampen (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: afcache.c[7.0] Tue Jul 20 19:42:23 1993 andy@cs.tu-berlin.de frozen $
 */

#include "atfs.h"
#include "afarchive.h"

extern int af_errno;

/*==========================================================================
 * List of list-descriptors + hash table for faster access
 *==========================================================================*/

EXPORT Af_revlist *af_lists = NULL;     /* base address of all list descriptors */
EXPORT Af_revlist *archFreelist = NULL; /* beginning of freelist */

EXPORT int af_listmem = 0;              /* # of bytes used for list */

LOCAL int archtabSize;

/*=========================================================================
 *     Hash stuff
 *=========================================================================*/

LOCAL bool hashInit = FALSE; /* indicate if hashtable is yet initialized */

typedef struct AfArnt AfArchent;

struct AfArnt { char       *symbol;
		Af_revlist *list;
		AfArchent  *next;
	      };

LOCAL AfArchent *afArchhash;

LOCAL char *hashSym;

/*=====================================================================
 * afHasharch -- put archive into hashtable
 *=====================================================================*/ 

LOCAL int afHasharch (symbol, list)
     char       *symbol;
     Af_revlist *list;
{
  register int where;
  AfArchent    *new, *curptr;

  where = afHashval (symbol, AF_MAXLISTS);
  if (!afArchhash[where].symbol) { /* entry is not used yet */
    afArchhash[where].symbol = symbol;
    afArchhash[where].list = list;
    return (AF_OK);
  }
  else { /* hash collision! */
    if ((new = (AfArchent *)malloc ((unsigned) sizeof (AfArchent))) == NULL)
      FAIL ("Hasharch", "malloc", AF_ESYSERR, ERROR);
    archtabSize += sizeof (AfArchent);
    new->symbol = symbol;
    new->list = list;
    new->next = NULL;
    curptr = &afArchhash[where];
    while (curptr->next)
      curptr = curptr->next;
    curptr->next = new;
    return (AF_OK);
  }
}

/*=====================================================================
 * afLookuparch -- search archive in hashtable
 *=====================================================================*/ 

LOCAL Af_revlist *afLookuparch (symbol, path)
     char *symbol, *path;
{
  int where;
  AfArchent *entry;

  /* lookup symbol */
  where = afHashval (symbol, AF_MAXLISTS);
  if (afArchhash[where].symbol) { /* found something */
    if ((afArchhash[where].symbol == symbol) &&
	(afArchhash[where].list->af_cattrs.af_syspath == path))
      return (afArchhash[where].list);
    else {
      entry = &afArchhash[where];
      while (entry->next) {
	entry = entry->next;
	if ((entry->symbol == symbol) && (entry->list->af_cattrs.af_syspath == path))
	  return (entry->list);
      }
    }
  }
  /* nothing found */
  return (NULL);
}

/*=========================================================================
 *     afInitList
 *=========================================================================*/

EXPORT Af_revlist *afInitList (pathSym, nameSym, typeSym)
     char *pathSym, *nameSym, *typeSym;
{
  register   int      i;
  register Af_revlist *list, *oldlist;

  /* init hashtable if this is not yet done */
  if (!hashInit) {
    archtabSize = AF_MAXLISTS * sizeof (AfArchent);
    if ((afArchhash = (AfArchent *)malloc ((unsigned) archtabSize)) == NULL)
      FAIL ("InitList", "malloc", AF_ESYSERR, NULL);
    /* set hashlist all zeros */
    memset ((char *)afArchhash, 0, archtabSize);
    hashInit = TRUE;
  }

  /* if list is already loaded */
  if ((oldlist = afTestList (pathSym, nameSym, typeSym))) {
    oldlist->af_extent |= AF_LISTBUSY;
    if (afReadAttrs (oldlist) == ERROR) {
      oldlist->af_extent &= ~AF_LISTBUSY;
      return (NULL);
    }
    oldlist->af_extent &= ~AF_LISTBUSY;
    return (oldlist);
  }

  /* if there are no more descriptors available - allocate new space */
  if (archFreelist == NULL) {
    if ((archFreelist = (Af_revlist *) malloc ((unsigned) (AF_LISTSEG * sizeof (Af_revlist)))) == NULL)
      FAIL ("InitList", "malloc(i)", AF_ESYSERR, NULL);
    archtabSize += AF_LISTSEG * sizeof (Af_revlist);
    /* build up new freelist */
    for (i=1; i<AF_LISTSEG; i++)
      archFreelist[i-1].af_next = &(archFreelist[i]);
    archFreelist[AF_LISTSEG-1].af_next = NULL;
  }

  list = archFreelist;
  archFreelist = archFreelist->af_next;
  memset ((char *)list, 0, sizeof (*list));

  list->af_extent |= AF_LISTBUSY;
  list->af_mem = NULL;
  list->af_cattrs.af_host = af_gethostname ();
  list->af_cattrs.af_syspath = pathSym;
  list->af_cattrs.af_globname = nameSym;
  list->af_cattrs.af_globtype = typeSym;
  list->af_lastmod = (time_t) 0;

  if (afReadAttrs (list) == ERROR) {
    list->af_extent &= ~AF_LISTBUSY;
    list->af_next = archFreelist;
    archFreelist = list;
    return (NULL);
  }
  list->af_extent &= ~AF_LISTBUSY;

  /* add list to chain */
  list->af_next = af_lists;
  af_lists = list;

  /* add list to hashtable */
  afHasharch (hashSym, list);

  return (list);
}

/*=========================================================================
 *     afInitObjCache
 *=========================================================================*/

LOCAL Af_revlist *objectCaches = NULL;  

EXPORT Af_revlist *afInitObjCache (pathSym)
     char *pathSym;
{
  register    int i;
  Af_revlist  *list, *oldlist;
  Af_user     *owner;
  bool        writeOk;
  struct stat iBuf;

  /* check if path exists and is a directory */
  if (stat (pathSym, &iBuf) == -1) {
    char msgBuf[PATH_MAX+64];
    sprintf (msgBuf, "directory %s does not exist", pathSym);
    FAIL ("InitObjCache", msgBuf, AF_EMISC, NULL);
  }
  else if (!S_ISDIR (iBuf.st_mode)) {
    char msgBuf[PATH_MAX+64];
    sprintf (msgBuf, "%s is not a directory", pathSym);
    FAIL ("InitObjCache", msgBuf, AF_EMISC, NULL);
  }

  /* init hashtable if it is not yet done */
  if (!hashInit) {
    archtabSize = AF_MAXLISTS * sizeof (AfArchent);
    if ((afArchhash = (AfArchent *)malloc ((unsigned) archtabSize)) == NULL)
      FAIL ("InitObjCache", "malloc", AF_ESYSERR, NULL);
    /* set hashlist all zeros */
    memset ((char *)afArchhash, 0, archtabSize);
    hashInit = TRUE;
  }

  /* if there are open archives check if desired archive is loaded yet */
  if (objectCaches) {
    if ((oldlist = afLookuparch (pathSym, pathSym))) {
      oldlist->af_extent |= AF_LISTBUSY;
      if (afObjCacheRead (oldlist) == ERROR) {
	oldlist->af_extent &= ~AF_LISTBUSY;
	return (NULL);
      }
      oldlist->af_extent &= ~AF_LISTBUSY;
      return (oldlist);
    }
  }
  
  /* if there are no more descriptors available - allocate new space */
  if (archFreelist == NULL) {
    if ((archFreelist = (Af_revlist *) malloc ((unsigned) (AF_LISTSEG * sizeof (Af_revlist)))) == NULL)
      FAIL ("InitObjCache", "malloc(i)", AF_ESYSERR, NULL);
    archtabSize += AF_LISTSEG * sizeof (Af_revlist);
    /* build up new freelist */
    for (i=1; i<AF_LISTSEG; i++)
      archFreelist[i-1].af_next = &(archFreelist[i]);
    archFreelist[AF_LISTSEG-1].af_next = NULL;
  }
  list = archFreelist;
  archFreelist = archFreelist->af_next;
  memset ((char *)list, 0, sizeof (*list));

  list->af_arpath = afArchivePath (pathSym);
  if ((owner = afArchiveOwner (list->af_arpath, &writeOk, &list->af_owngid)) == NULL) {
    list->af_owngid = getegid();
    owner = af_afuser ((uid_t) geteuid());
  }
  list->af_lastmod = (time_t)0;
  list->af_busyfilename = NULL;
  list->af_datasize = 0;
  if (writeOk)
    list->af_extent |= (AF_CACHE | AF_LISTBUSY | AF_UXWRITE);
  else
    list->af_extent |= (AF_CACHE | AF_LISTBUSY);
  list->af_mem = NULL;
  list->af_cattrs.af_host = af_gethostname ();
  list->af_cattrs.af_syspath = pathSym;
  list->af_cattrs.af_ownname = af_entersym (owner->af_username);
  list->af_cattrs.af_ownhost = af_enterhost (owner->af_userhost);
  list->af_cattrs.af_owndomain = af_enterdomain (owner->af_userdomain);

  if (afObjCacheRead (list) == ERROR) {
    list->af_extent &= ~AF_LISTBUSY;
    list->af_next = archFreelist;
    archFreelist = list;
    return (NULL);
  }
  list->af_extent &= ~AF_LISTBUSY;

  /* add list to chain */
  list->af_next = objectCaches;
  objectCaches = list;

  /* add list to hashtable */
  afHasharch (pathSym, list);

  return (list);
}

/*==========================================================================
 *	afTestList -- see if list is in core
 *==========================================================================*/

EXPORT Af_revlist *afTestList (pathSym, nameSym, typeSym)
     char *pathSym, *nameSym, *typeSym;
{
  if (!hashInit)
    return (NULL);

  hashSym = af_gbusname (NULL, nameSym, typeSym);

  /* if there are open archives check if desired archive is loaded yet */
  if (af_lists)
    return (afLookuparch (hashSym, pathSym));

  return (NULL);
}

/*==========================================================================
 *	afRefreshList -- re-read list (if necessary)
 *==========================================================================*/

EXPORT int afRefreshList (list, extent)
     Af_revlist *list;
     int extent;
{
  int retcode = AF_OK;

  /* this shortcut is for sake of efficiency */
  if ((list->af_access) && (extent == AF_ATTRS))
    return (retcode);

  list->af_extent |= AF_LISTBUSY;

  /* if list is a derived object cache */
  if ((list->af_extent & AF_CACHE) == AF_CACHE)
    retcode = afObjCacheRead (list);
  else {
    retcode = afReadAttrs (list);
    if (extent & AF_DATA)
      retcode = afReadData (list);
  }

  list->af_extent &= ~AF_LISTBUSY;
  return (retcode);
}

/*==========================================================================
 *	afDetachList -- detach list data
 *==========================================================================*/

EXPORT int afDetachList (list)
     Af_revlist *list;
{
  /* free all allocated memory */
  af_frmemlist (list);

  list->af_listlen = 0;
  list->af_nrevs = 0;
  list->af_datasize = 0;
  list->af_extent &= ~AF_SEGMASK;
  list->af_list = NULL;

  return (AF_OK);
}

/*==========================================================================
 *	afListSpace -- get space for new list
 *==========================================================================*/

EXPORT int afListSpace (size)
     int size;
{
  register Af_revlist *listptr;

  if (af_listmem + size < AF_MAXMEM)
    return (AF_OK);

  /* search oldest unused revision list */ /* not yet implemented */
  /* search first unused revision list and free memory */

  listptr = af_lists;
  while (listptr) {
    if (!(listptr->af_extent & AF_LISTBUSY) && (listptr->af_access < 1) && (listptr->af_listlen > 0))
      afDetachList (listptr);
    if (af_listmem + size < AF_MAXMEM)
      return (AF_OK);
    else
      listptr = listptr->af_next;
  }

  listptr = objectCaches;
  while (listptr) {
    if (!(listptr->af_extent & AF_LISTBUSY) && (listptr->af_access < 1) && (listptr->af_listlen > 0))
      afDetachList (listptr);
    if (af_listmem + size < AF_MAXMEM)
      return (AF_OK);
    else
      listptr = listptr->af_next;
  }

  /* unable to free enough memory */
  /* return (ERROR); ignore this */
  return (AF_OK);
}
