/* 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
 *
 * afretr.c - retrieve interface 
 *
 * Author: Andreas Lampen (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: afretr.c[7.0] Fri Jan 28 19:02:49 1994 andy@cs.tu-berlin.de frozen $
 */

#include "atfs.h"

extern int af_errno;

extern Af_hashent af_hashentry;

/*==============================
 * af_histories
 *==============================*/

EXPORT char **af_histories (path, pattern)
     char *path, *pattern;
{
  char **histList;
  int  histListSize, histNum = 0, secondDir = FALSE, i;
  DIR           *dirPtr;
  struct dirent *dirEntry;
  char *reMsg, *histSym, *archPath;

  if (!path || !(*path))
    path = ".";

  /* open named directory  */
  if ((dirPtr = opendir (path)) == NULL)
    FAIL ("histories", "opendir", AF_ESYSERR, NULL);

  /* compile pattern */
  if ((reMsg = re_comp (pattern)))
    af_wng ("histories", reMsg);

  /* allocate memory for result list */
  if ((histList = (char **)malloc ((unsigned) (AF_SEGLEN * sizeof (char *)))) == NULL)
    FAIL ("histories", "malloc", AF_ESYSERR, NULL);
  histListSize = AF_SEGLEN;

loop:
  /* lookup all files */
  while ((dirEntry = (struct dirent *)readdir (dirPtr))) {
    if (!re_exec (dirEntry->d_name))
      continue;

    /* see if name is already in "histLists" */
    histSym = af_entersym (dirEntry->d_name);
    for (i=0; i<histNum; i++) {
      if (histSym == histList[i])
	break;
    }

    /* ToDo: check if name comes from a bogus archive file */
    
    if (i == histNum)
      histList[histNum++] = histSym;
    if (histNum == histListSize) {
      if ((histList = (char **)realloc (histList, (unsigned) ((histListSize+AF_SEGLEN) * sizeof (char *)))) == NULL)
	FAIL ("histories", "realloc", AF_ESYSERR, NULL);
      histListSize += AF_SEGLEN;
    }
  }
  closedir (dirPtr);

  if (!secondDir) {
    archPath = afArchivePath (af_uniqpath (af_entersym (path)));
    archPath = afArchiveName (archPath, AF_ATTRDIR, NULL, NULL, AF_READ);
    if ((dirPtr = opendir (archPath))) {
      secondDir = TRUE;
      goto loop;
    }
  }
  histList[histNum] = NULL;
  return (histList);
}

/*==============================
 * af_cachenames
 *==============================*/

EXPORT char **af_cachenames (path, pattern)
     char *path, *pattern;
{
  char       *pathSym, **nameList, *reMsg, *nameSym;
  int        nameListSize, nameCount=0, i, j, maxindex;
  Af_revlist *objCacheList;

  /* allocate memory for result list */
  if ((nameList = (char **)malloc ((unsigned) (AF_SEGLEN * sizeof (char *)))) == NULL)
    FAIL ("cachenames", "malloc", AF_ESYSERR, NULL);
  nameListSize = AF_SEGLEN;
  nameList[0] = NULL;

  /* build pathname (default is current directory) */
  pathSym = af_uniqpath (af_entersym (path));

  if ((objCacheList = afInitObjCache (pathSym)) == NULL)
    if (af_errno == AF_ENOATFSDIR)
      return (nameList);
    else
      return (NULL);

  /* compile pattern */
  if ((reMsg = re_comp (pattern)))
    af_wng ("cachenames", reMsg);

  /* lookup all entries */
  maxindex = objCacheList->af_nrevs-1;
  for (i=0; i <= maxindex; i++) {
    /* skip invalid object cache files */
    if (!(objCacheList->af_list[i].af_class & AF_VALID)) {
      if (++maxindex == objCacheList->af_listlen)
	FATAL ("cachenames", "bad revision count", AF_EINCONSIST, NULL);
      continue;
    }

    /* see if name is already in "nameLists" */
    nameSym = af_gbusname (NULL, objCacheList->af_list[i].af_name, objCacheList->af_list[i].af_type);
    for (j=0; j<nameCount; j++) {
      if (nameSym == nameList[j])
	break;
    }
    if (!re_exec (nameSym))
      continue;
    if (j == nameCount)
      nameList[nameCount++] = nameSym;
    if (nameCount == nameListSize) {
      if ((nameList = (char **)realloc (nameList, (unsigned) ((nameListSize+AF_SEGLEN) * sizeof (char *)))) == NULL)
	FAIL ("cachenames", "realloc", AF_ESYSERR, NULL);
      nameListSize += AF_SEGLEN;
    }
  }
  nameList[nameCount] = NULL;
  return (nameList);
}

/*====================================================================
 *    af_abufcmp -- compare attrbuf with version attributes
 *                  returns TRUE if attrs do not match
 *====================================================================*/

LOCAL int af_abufcmp (attrbuf, key)
     Af_attrs *attrbuf;
     Af_key   *key;
{
  register int match, j;

  /* if (attribute is set && attributes does not match) -- return ERROR */
  
  /*** generation number ***/
  if ((attrbuf->af_gen != AF_NOVNUM) && (attrbuf->af_gen != VATTR(key).af_gen))
    return (ERROR);
  
  /*** revision number ***/
  if ((attrbuf->af_rev != AF_NOVNUM) && (attrbuf->af_rev != VATTR(key).af_rev))
    return (ERROR);

  /*** state ***/
  if ((attrbuf->af_state != AF_NOSTATE) &&
      (attrbuf->af_state != VATTR(key).af_state))
    return (ERROR);

  /*** owner ***/
  if ( (attrbuf->af_owner.af_username[0]) &&
       (strcmp (attrbuf->af_owner.af_username, CATTR(key).af_ownname)) )
    return (ERROR);
  if ( (attrbuf->af_owner.af_userhost[0]) &&
       (strcmp (attrbuf->af_owner.af_userhost, CATTR(key).af_ownhost)) )
    return (ERROR);
  if ( (attrbuf->af_owner.af_userdomain[0]) &&
       (strcmp (attrbuf->af_owner.af_userdomain, CATTR(key).af_owndomain)) )
    return (ERROR);
  
  /*** author ***/
  if ( (attrbuf->af_author.af_username[0]) &&
       (strcmp (attrbuf->af_author.af_username, VATTR(key).af_auname)) )
    return (ERROR);
  if ( (attrbuf->af_author.af_userhost[0]) &&
       (strcmp (attrbuf->af_author.af_userhost, VATTR(key).af_auhost)) )
    return (ERROR);
  if ( (attrbuf->af_author.af_userdomain[0]) &&
       (strcmp (attrbuf->af_author.af_userdomain, VATTR(key).af_audomain)) )
    return (ERROR);
  
  /*** size ***/
  if ((attrbuf->af_size != AF_NOSIZE) &&
      (attrbuf->af_size != VATTR(key).af_fsize) )
    return (ERROR);

  /*** mode ***/
  if ((attrbuf->af_mode != AF_NOMODE) &&
      ((attrbuf->af_mode & VATTR(key).af_mode) != attrbuf->af_mode))
    return (ERROR);

  /*** locker ***/
  if ( (attrbuf->af_locker.af_username[0] == '\01') &&
       (attrbuf->af_locker.af_username[1] == '$') &&
       VATTR(key).af_lckname)
    return (ERROR);

  if ( (attrbuf->af_locker.af_username[0]) &&
       (strcmp (attrbuf->af_locker.af_username,
		NOTNIL(VATTR(key).af_lckname))) )
    return (ERROR);
  if ( (attrbuf->af_locker.af_userhost[0]) &&
       (strcmp (attrbuf->af_locker.af_userhost,
		NOTNIL(VATTR(key).af_lckhost))) )
    return (ERROR);
  if ( (attrbuf->af_locker.af_userdomain[0]) &&
       (strcmp (attrbuf->af_locker.af_userdomain,
		NOTNIL(VATTR(key).af_lckdomain))) )
    return (ERROR);
  
  /*** date of last modification ***/
  if ((attrbuf->af_mtime != AF_NOTIME) &&
      (attrbuf->af_mtime != VATTR(key).af_mtime) )
    return (ERROR);

  /*** date of last access ***/
  if ((attrbuf->af_atime != AF_NOTIME) &&
      (attrbuf->af_atime != VATTR(key).af_atime) )
    return (ERROR);

  /*** date of last status change ***/
  if ((attrbuf->af_ctime != AF_NOTIME) &&
      (attrbuf->af_ctime != VATTR(key).af_ctime) )
    return (ERROR);

  /*** saving date ***/
  if ((attrbuf->af_stime != AF_NOTIME) &&
      (attrbuf->af_stime != VATTR(key).af_stime) )
    return (ERROR);
  
  /*** date of last lock change ***/
  if ((attrbuf->af_stime != AF_NOTIME) &&
      (attrbuf->af_stime != VATTR(key).af_stime) )
    return (ERROR);
  
  /*** user defined attributes ***/
  if (attrbuf->af_udattrs[0]) {
    /* if list of user defined attributes is not empty or there are attributes */
    match = TRUE;
    if ((attrbuf->af_udattrs[0][0] != '\0') || (VATTR(key).af_udanum != 0)) {
      /* test all given entries */
      j=0;
      while ((attrbuf->af_udattrs[j]) 
	     && (match = !afMatchUda (key, attrbuf->af_udattrs[j])))
	j++;
    } /* if */
    if (match == FALSE)
      return (ERROR);
  } /* if */
  return (AF_OK);
}


/*===================
 *    af_cachefind
 *===================*/

EXPORT int af_cachefind (attrbuf, set)
     Af_attrs *attrbuf;
     Af_set   *set;     /* out */
{
  register Af_revlist *objCacheList;
  register int i, maxindex;
  char         *pathsym;
  Af_key       key;

  /* init set */
  set->af_nkeys = 0;
  set->af_setlen = 0;
  set->af_klist = NULL;

  /* build pathname (default is current directory) */
  pathsym = af_uniqpath (af_entersym (attrbuf->af_syspath));

  if ((objCacheList = afInitObjCache (pathsym)) == NULL)
    if (af_errno == AF_ENOATFSDIR)
      return (0);
    else
      return (ERROR);

  /* alloacte memory for set */
  if(objCacheList->af_nrevs) {
    if ((set->af_klist = (Af_key *)malloc ((unsigned) (sizeof (Af_key) * objCacheList->af_nrevs))) == NULL)
      FAIL ("cachefind", "malloc", AF_ESYSERR, ERROR);
  }
  set->af_setlen = objCacheList->af_nrevs;

  /* add all desired object cache files to set */
  maxindex = objCacheList->af_nrevs-1;
  for (i=0; i <= maxindex; i++) {
    /* skip invalid object cache files */
    if (!(objCacheList->af_list[i].af_class & AF_VALID)) {
      if (++maxindex == objCacheList->af_listlen)
	FATAL ("cachefind", "bad revision count", AF_EINCONSIST, ERROR);
      continue;
    }
    if ((strcmp (attrbuf->af_name, AF_PATTERN_ALL) && strcmp (attrbuf->af_name, objCacheList->af_list[i].af_name)) ||
	(strcmp (attrbuf->af_type, AF_PATTERN_ALL) && strcmp (attrbuf->af_type, NOTNIL(objCacheList->af_list[i].af_type))))
      continue;

    key.af_ldes = objCacheList;
    key.af_lpos = i;
    if (af_abufcmp (attrbuf, &key))
      continue;
      
    /* else add object cache file to set */
    set->af_klist[set->af_nkeys].af_ldes = objCacheList;
    set->af_klist[set->af_nkeys].af_lpos = i;
    afIncreaseAccess (&(set->af_klist[set->af_nkeys]));
    set->af_nkeys++;
  }
  /* if set is empty */
  if ((set->af_nkeys == 0) && (set->af_klist)) {
    free ((char *)set->af_klist);
    set->af_setlen = 0;
  }

  return (set->af_nkeys);
}


/*==============
 *    af_find
 *==============*/

EXPORT int af_find (attrBuf, set)
     Af_attrs *attrBuf;
     Af_set   *set;     /* out */
{
  char          *pathSym, *nameSym, *typeSym;
  register int  i, maxIndex;
  int           nLists=0, listSize;
  Af_revlist    **revList;
  Af_key        key;

  /* init set */
  set->af_nkeys = 0;
  set->af_setlen = 0;
  set->af_klist = NULL;

  /* build pathname (default is current directory) */
  pathSym = af_uniqpath (af_entersym (attrBuf->af_syspath));
  nameSym = af_entersym (attrBuf->af_name);
  typeSym = af_entersym (attrBuf->af_type);

  /* alloc memory for revlist */
  if ((revList = (Af_revlist **)malloc ((unsigned) (sizeof(Af_revlist *) * AF_SEGLEN))) == NULL)
    FAIL ("find", "malloc", AF_ESYSERR, ERROR);
  listSize = AF_SEGLEN;

  if (!strcmp (attrBuf->af_name, AF_PATTERN_ALL) || !strcmp (attrBuf->af_type, AF_PATTERN_ALL)) {
    char **nameList = af_histories (pathSym, AF_PATTERN_ALL);
    char *histNameSym, *histTypeSym;
    i=0;
    if (nameList) {
      while (nameList[i]) {
	histNameSym = af_entersym (af_afname (nameList[i]));
	histTypeSym = af_entersym (af_aftype (nameList[i++]));

	/* IF name is 'PATTERN_ALL' OR matches history name AND 
	   type is 'PATTERN_ALL' OR matches history type
	   -> add history to revList
	   */
	if ( ((nameSym && !strcmp (nameSym, AF_PATTERN_ALL)) || (nameSym == histNameSym)) &&
	    ((typeSym && !strcmp (typeSym, AF_PATTERN_ALL)) || (typeSym == histNameSym)) ) {
	  /* realloc memory for revList if necessary*/
	  if (nLists == listSize) {
	    if ((revList = (Af_revlist **)realloc ((char *)revList, (unsigned) (sizeof(Af_revlist *) * (listSize + AF_SEGLEN)))) == NULL)
	      FAIL ("find","realloc", AF_ESYSERR, ERROR);
	    listSize += AF_SEGLEN;
	  }
	  if ((revList[nLists] = afInitList (pathSym, histNameSym, histTypeSym)) == NULL) {
	    if (af_errno == AF_ENOATFSFILE)
	      continue;
	    else
	      return (ERROR);
	  }
	  revList[nLists++]->af_extent |= AF_LISTBUSY;
	} /* IF ... */
      } /* while (nameList... */
    free (nameList);
    } /* if (nameList) */
  } /* if */
  else {
    /* impossible: empty name and non-empty type */
    if (!attrBuf->af_name[0] && attrBuf->af_type[0]) {
      free ((char *)revList);
      return (set->af_nkeys); /* no Af-files found */
    }

    /* too much hassle to handle this case */
    if (!strcmp (attrBuf->af_name, "..") && (!attrBuf->af_type[0])) {
      free ((char *)revList);
      FAIL ("find", "cannot handle name '..'", AF_EMISC, ERROR);
    }

    if ((!attrBuf->af_name[0] || !strcmp (attrBuf->af_name, ".")) && (!attrBuf->af_type[0]))
      revList[0] = afInitList (af_afpath (pathSym), af_afname (pathSym), af_aftype (pathSym));
    else
      revList[0] = afInitList (pathSym, nameSym, typeSym);

    if (revList[0] == NULL) {
      if (af_errno == AF_ENOATFSFILE)
	nLists = 0;
      else {
	free ((char *)revList);
	return (ERROR);
      }
    }
    else
      nLists = 1;
  }
  
  if (nLists == 0) {
    free ((char *)revList);
    return (set->af_nkeys); /* no Af-files found */
  }

  /* alloacte memory for set */
  if ((set->af_klist = (Af_key *)malloc ((unsigned) (sizeof (Af_key) * AF_SEGLEN))) == NULL)
    FAIL ("find", "malloc", AF_ESYSERR, ERROR);
  set->af_setlen = AF_SEGLEN;

  /* lookup all revisions in all lists */
  /* this part is implemented quite dull up to now */
  /*     -- the number of "if"'s should be reduced */
  for (;nLists > 0; nLists--) {
    maxIndex = revList[nLists-1]->af_nrevs-1;
    for (i = 0; i <= maxIndex; i++) {
      /* skip holes in the list */
      if (!(revList[nLists-1]->af_list[i].af_class & AF_VALID)) {
	maxIndex++;
	continue;
      }

      /* test all attributes -- returnes true if attrs do not match */
      key.af_ldes = revList[nLists-1];
      key.af_lpos = i;
      if (af_abufcmp (attrBuf, &key))
	continue;

      /********************************************/
      /********** put AF-file into set ************/
      /********************************************/
      
      /* if set is full, enlarge it */
      if (set->af_nkeys == set->af_setlen) {
	if ((set->af_klist = (Af_key *)realloc ((char *)set->af_klist, (unsigned) (sizeof(Af_key) * (set->af_setlen + AF_SEGLEN)))) == NULL)
	  FAIL ("find", "realloc", AF_ESYSERR, ERROR);
	set->af_setlen += AF_SEGLEN;
      }

      /* add revision to key-set */
      set->af_klist[set->af_nkeys].af_ldes = revList[nLists-1];
      set->af_klist[set->af_nkeys].af_lpos = i;
      afIncreaseAccess (&(set->af_klist[set->af_nkeys]));
      set->af_nkeys++;
    } /* for all revisions in archive */
    revList[nLists-1]->af_extent &= ~AF_LISTBUSY;
  } /* for all archives */

  /* if set is empty */
  if (set->af_nkeys == 0) {
    free ((char *)set->af_klist);
    set->af_setlen = 0;
  }

  free ((char *)revList);
  return (set->af_nkeys);
}

/*================
 *    af_getkey
 *================*/

EXPORT int af_getkey (syspath, name, type, gen, rev, key)
     char   *syspath, *name, *type;
     int    gen, rev;
     Af_key *key;
{
  char *uniqPath = af_uniqpath (af_entersym (syspath));

  /* empty name argument */
  if (!name || !(*name)) {
    if (type && *type)
      FAIL ("getkey", "cannot handle empty name and non-empty type", AF_EMISC, ERROR);
    name = ".";
  }

  /* too much hassle to handle this case */
  if (!strcmp (name, "..") && (!type || !(*type)))
    FAIL ("getkey", "cannot handle name '..'", AF_EMISC, ERROR);

  /* when referring the current directory, busyname is just the pathname */
  /*  -- with the exception of "/." */
  if (!strcmp (name, ".") && (!type || !(*type)) && strcmp (uniqPath, "/"))
    return (afGetAso (af_afpath (uniqPath), af_afname (uniqPath), af_aftype (uniqPath), gen, rev, key));
  else
    return (afGetAso (uniqPath, name, type, gen, rev, key));
}

/*==================
 *    af_predsucc
 *==================*/

EXPORT int af_predsucc (inkey, mode, outkey)
     Af_key *inkey, *outkey;
     int    mode;
{
  if (afAccessAso (inkey, AF_ATTRS))
    FAIL ("predsucc", "", AF_EINVKEY, ERROR);

  switch (mode) {
  case AF_LOGICAL_SUCC:
    FAIL ("predsucc", "not yet implemented", AF_EMISC, ERROR);
  case AF_LOGICAL_PRED:
    FAIL ("predsucc", "not yet implemented", AF_EMISC, ERROR);
  case AF_PHYSICAL_SUCC:
    if (VATTR(inkey).af_succgen == AF_NOVNUM)
      return (0);
    if (af_buildkey (inkey->af_ldes, VATTR(inkey).af_succgen, VATTR(inkey).af_succrev, outkey) == ERROR)
      FAIL ("predsucc", "successor not found", AF_EINTERNAL, ERROR);
    return (1);
  case AF_PHYSICAL_PRED:
    if (VATTR(inkey).af_predgen == AF_NOVNUM)
      return (0);
    if (af_buildkey (inkey->af_ldes, VATTR(inkey).af_predgen, VATTR(inkey).af_predrev, outkey) == ERROR)
      FAIL ("predsucc", "predecessor not found", AF_EINTERNAL, ERROR);
    return (1);
  }
  FAIL ("predsucc", "invalid mode argument", AF_EMISC, ERROR);
}

/*====================
 *    af_initattrs
 *====================*/

EXPORT void af_initattrs (attrs)
     Af_attrs *attrs;
{
  attrs->af_host[0] = '\0';
  attrs->af_syspath[0] = '\0';
  strcpy (attrs->af_name, AF_PATTERN_ALL);
  strcpy (attrs->af_type, AF_PATTERN_ALL);
  attrs->af_gen = AF_NOVNUM;
  attrs->af_rev = AF_NOVNUM;
  attrs->af_state = AF_NOSTATE;
  attrs->af_owner.af_username[0] = '\0';
  attrs->af_owner.af_userhost[0] = '\0';
  attrs->af_owner.af_userdomain[0] = '\0';
  attrs->af_author.af_username[0] = '\0';
  attrs->af_author.af_userhost[0] = '\0';
  attrs->af_author.af_userdomain[0] = '\0';
  attrs->af_size = AF_NOSIZE;
  attrs->af_mode = AF_NOMODE;
  attrs->af_locker.af_username[0] = '\0';
  attrs->af_locker.af_userhost[0] = '\0';
  attrs->af_locker.af_userdomain[0] = '\0';
  attrs->af_mtime = AF_NOTIME;
  attrs->af_atime = AF_NOTIME;
  attrs->af_ctime = AF_NOTIME;
  attrs->af_stime = AF_NOTIME;
  attrs->af_ltime = AF_NOTIME;
  attrs->af_udattrs[0] = NULL;
}

