/*$__copyright$*/
/*
 * AtFStk -- Attribute Filesystem Toolkit Library
 *
 * uda.c -- AtFS toolkit library
 *
 * Authors: Andreas.Lampen@cs.tu-berlin.de
 *          Axel Mahler (Axel.Mahler@cs.tu-berlin.de)
 *
 * $Header: uda.c[7.0] Mon May 16 15:50:33 1994 andy@cs.tu-berlin.de frozen $
 */

#include "atfs.h"
#include "sttk.h"
#include "atfstk.h"

static char logMem[8192];
static char headerStr[PATH_MAX*2];
static char *valueXpon="xpon", *valueXpoff="xpoff", *valueNa="n/a";

/*===============================
 * standard attribute names
 *===============================*/

EXPORT struct atAttrTab atStdAttrTab[] = {
  { AT_CODE_DESCRIPTION,"Description",	NULL },
  { AT_CODE_HEADER,	"Header",	NULL },
  { AT_CODE_INTENT,	"Intent",	NULL },
  { AT_CODE_LOG,	"Log",		NULL },
  { AT_CODE_ALIAS,	"alias",	NULL },
  { AT_CODE_ATIME,	"atime",	AF_ATTATIME },
  { AT_CODE_AUTHOR,	"author",	AF_ATTAUTHOR },
  { AT_CODE_CACHEKEY,	"cachekey",	NULL },
  { AT_CODE_CLEAD,	"clead",	NULL },
  { AT_CODE_CTIME,	"ctime",	AF_ATTCTIME },
  { AT_CODE_DSIZE,	"dsize",	AF_ATTDSIZE },
  { AT_CODE_GENERATION,	"generation",	AF_ATTGEN },
  { AT_CODE_HOST,	"host",		AF_ATTHOST },
  { AT_CODE_LOCK,	"lock",		AF_ATTLOCKER },
  { AT_CODE_LOCK,	"locker",	AF_ATTLOCKER },
  { AT_CODE_LTIME,	"ltime",	AF_ATTLTIME },
  { AT_CODE_MODE,	"mode",		AF_ATTMODE },
  { AT_CODE_MTIME,	"mtime",	AF_ATTMTIME },
  { AT_CODE_NAME,	"name",		AF_ATTNAME },
  { AT_CODE_NOTE,	"note",		NULL },
  { AT_CODE_OWNER,	"owner",	AF_ATTOWNER },
  { AT_CODE_PRED,	"pred",		NULL },
  { AT_CODE_REVISION,	"revision",	AF_ATTREV },
  { AT_CODE_RTIME,	"rtime",	NULL },
  { AT_CODE_SELF,	"self",		AF_ATTBOUND },
  { AT_CODE_SELFPATH,	"selfpath",	AF_ATTBOUNDPATH },
  { AT_CODE_SIZE,	"size",		AF_ATTSIZE },
  { AT_CODE_STATE,	"state",	AF_ATTSTATE },
  { AT_CODE_STATE,	"status",	AF_ATTSTATE },
  { AT_CODE_STIME,	"stime",	AF_ATTSTIME },
  { AT_CODE_SUCC,	"succ",		NULL },
  { AT_CODE_SYSPATH,	"syspath",	AF_ATTSPATH },
  { AT_CODE_TYPE,	"type",		AF_ATTTYPE },
  { AT_CODE_UNIXNAME,	"unixname",	AF_ATTUNIXNAME },
  { AT_CODE_UNIXPATH,	"unixpath",	AF_ATTUNIXPATH },
  { AT_CODE_VERSION,	"version",	AF_ATTVERSION },
  { AT_CODE_VTIME,	"vtime",	NULL },
  { AT_CODE_XPOFF,	"xpoff",	NULL },
  { AT_CODE_XPON,	"xpon",		NULL },
  { -1,                 NULL,   	NULL }
};

LOCAL int prevExpand = TRUE;

LOCAL char *logAttr (aso)
     Af_key *aso;
{
  Af_attrs attrBuf;
  Af_set   versionSet;
  Af_key   tmpAso;
  int      curGen, curRev, tmpGen, versionCount, i;
  char     *valPtr, *notePtr, *tmpPtr, commentLeader[32];
  int      headerPrinted = FALSE, noNewline;
  char     *logPtr;

  af_initattrs (&attrBuf);    
  strcpy (attrBuf.af_syspath, af_retattr (aso, AF_ATTSPATH));
  strcpy (attrBuf.af_name, af_retattr (aso, AF_ATTNAME));
  strcpy (attrBuf.af_type, af_retattr (aso, AF_ATTTYPE));
  af_find (&attrBuf, &versionSet);
  af_sortset (&versionSet, AF_ATTVERSION);
  curGen = af_retnumattr (aso, AF_ATTGEN);
  curRev = af_retnumattr (aso, AF_ATTREV);
  versionCount = af_nrofkeys (&versionSet);
	   
  /* determine comment leader sym to prepend it to loglines */
  if ((valPtr = af_retattr (aso, AT_ATTCLEAD))) {
    strcpy (commentLeader, valPtr);
    free (valPtr);
  }
  else
    commentLeader[0] = '\0';

  /* ToDo: allocate memory for version log */
  logPtr = logMem;

  /* write log for each version up to current */
  for (i = 0 ; i < versionCount; i++) {
    af_setgkey (&versionSet, i, &tmpAso);
    if (af_retnumattr (&tmpAso, AF_ATTSTATE) == AF_BUSY)
      continue; /* don't consider busy version */
    if (curGen != AF_BUSYVERS) {
      if ((tmpGen = af_retnumattr (&tmpAso, AF_ATTGEN)) > curGen)
	break;
      if ((tmpGen == curGen) && (af_retnumattr (&tmpAso, AF_ATTREV) > curRev))
	break;
    }
    if (!headerPrinted) {
      strcpy (logPtr, commentLeader);
      logPtr += strlen (logPtr);
      strcpy (logPtr, "Log for ");
      logPtr += strlen (logPtr);
      strcpy (logPtr, af_retattr (aso, AF_ATTBOUNDPATH));
      logPtr += strlen (logPtr);
      *logPtr++ = ':';
      headerPrinted = TRUE;
    }
    else {    /* precede log by version header */
      strcpy (logPtr, commentLeader);
      logPtr += strlen (logPtr);
      *logPtr++ = '[';
      strcpy (logPtr, af_retattr (&tmpAso, AF_ATTVERSION));
      logPtr += strlen (logPtr);
      *logPtr++ = ']';
      *logPtr++ = ' ';
      strcpy (logPtr, af_retattr (&tmpAso, AF_ATTSTIME));
      logPtr += strlen (logPtr);
      *logPtr++ = ' ';
      strcpy (logPtr, af_retattr (&tmpAso, AF_ATTAUTHOR));
      logPtr += strlen (logPtr);
      *logPtr++ = ' ';
      strcpy (logPtr, af_retattr (&tmpAso, AF_ATTSTATE));
      logPtr += strlen (logPtr);
    }
    *logPtr++ = '\n';
    valPtr = af_rnote (&tmpAso);
    if (valPtr && !strcmp (valPtr, AT_EMPTYLOG))
      notePtr = "";
    else
      notePtr = valPtr; 
    /* break log text into separate strings */
    noNewline = FALSE;
    while (notePtr) {
      tmpPtr = notePtr;
      while (*tmpPtr && (*tmpPtr != '\n'))
	tmpPtr++;
      strcpy (logPtr, commentLeader);
      logPtr += strlen (logPtr);
      *logPtr++ = ' ';
      *logPtr++ = ' ';
      if (*tmpPtr == '\n') {
	if (tmpPtr[1]) {
	  *tmpPtr++ = '\0'; /* make it end of an ordinary string */
	}
	else {
	  /* this is the case when the last comment line ends with
	     a newline character. Do not add extra newline then.
	   */
	  tmpPtr = NULL; /* we're ready */
	  noNewline = TRUE;
	}
      }
      else {
	tmpPtr = NULL; /* we're ready */
      }
      strcpy (logPtr, notePtr);
      logPtr += strlen (logPtr);
      if (!noNewline)
	*logPtr++ = '\n';
      notePtr = tmpPtr;
    }
    if (valPtr)
      free (valPtr);
  }
  /* chop off very last newline character */
  if (logPtr > logMem)
    *(logPtr-1) = '\0';
  af_dropset (&versionSet);

  return (logMem);
}

LOCAL char *retStdAttr (aso, attrName) 
     Af_key *aso;
     char *attrName;
{
  register int i;
  char         *attrVal;
  Af_key       tmpAso;

  for (i = 0; atStdAttrTab[i].name; i++) {
    if (!(strcmp (attrName, atStdAttrTab[i].name)))
      break;
  }

  switch (atStdAttrTab[i].code) {
  case AT_CODE_ATIME:
  case AT_CODE_AUTHOR:
  case AT_CODE_CTIME:
  case AT_CODE_DSIZE:
  case AT_CODE_GENERATION:
  case AT_CODE_HOST:
  case AT_CODE_LOCK:
  case AT_CODE_LTIME:
  case AT_CODE_MTIME:
  case AT_CODE_NAME:
  case AT_CODE_OWNER:
  case AT_CODE_REVISION:
  case AT_CODE_SELF:
  case AT_CODE_SELFPATH:
  case AT_CODE_SIZE:
  case AT_CODE_STATE:
  case AT_CODE_STIME:
  case AT_CODE_SYSPATH:
  case AT_CODE_TYPE:
  case AT_CODE_UNIXNAME:
  case AT_CODE_UNIXPATH:
  case AT_CODE_VERSION:
    return (af_retattr (aso, atStdAttrTab[i].atfsName));
  case AT_CODE_ALIAS:
    return (af_retattr (aso, AT_ATTALIAS));
  case AT_CODE_CACHEKEY:
    return (af_retattr (aso, AT_ATTCACHEKEY));
  case AT_CODE_CLEAD:
    return (af_retattr (aso, AT_ATTCLEAD));
  case AT_CODE_DESCRIPTION: {
    /* search first version */
    char tmpPath[PATH_MAX], tmpName[NAME_MAX], tmpType[TYPE_MAX], *retPtr;
    Af_key firstAso;
    strcpy (tmpPath, af_retattr (aso, AF_ATTSPATH));
    strcpy (tmpName, af_retattr (aso, AF_ATTNAME));
    strcpy (tmpType, af_retattr (aso, AF_ATTTYPE));
    if (af_getkey (tmpPath, tmpName, tmpType, AF_FIRSTVERS, AF_FIRSTVERS, &firstAso) < 0)
      return (NULL);
    retPtr = af_rnote (&firstAso);
    af_dropkey (&firstAso);
    return (retPtr);
  }
  case AT_CODE_HEADER: {
    strcpy (headerStr, "$Header: ");
    strcat (headerStr, af_retattr (aso, AF_ATTBOUND));
    strcat (headerStr, " ");
    if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
      strcat (headerStr, af_retattr (aso, AF_ATTMTIME));
    else
      strcat (headerStr, af_retattr (aso, AF_ATTSTIME));
    strcat (headerStr, " ");
    strcat (headerStr, af_retattr (aso, AF_ATTAUTHOR));
    strcat (headerStr, " ");
    strcat (headerStr, af_retattr (aso, AF_ATTSTATE));
    strcat (headerStr, " $");
    return (headerStr);
  }
  case AT_CODE_INTENT:
    return (af_retattr (aso, AT_ATTINTENT));
  case AT_CODE_LOG:
    return (logAttr (aso));
  case AT_CODE_MODE:
    return (atWriteMode (aso));
  case AT_CODE_NOTE:
    return (af_rnote (aso));
  case AT_CODE_PRED:
    switch (af_predsucc (aso, AF_PHYSICAL_PRED, &tmpAso))
      {
      case -1:
	return (NULL);
      case 0:
	return (valueNa);
      default:
	attrVal = af_retattr (&tmpAso, AF_ATTBOUND);
	af_dropkey (&tmpAso);
	return (attrVal);
      }
  case AT_CODE_RTIME:
    return (af_retattr (aso, atStdAttrTab[i].name));
  case AT_CODE_SUCC:
    switch (af_predsucc (aso, AF_PHYSICAL_SUCC, &tmpAso))
      {
      case -1:
	return (NULL);
      case 0:
	return (valueNa);
      default:
	attrVal = af_retattr (&tmpAso, AF_ATTBOUND);
	af_dropkey (&tmpAso);
	return (attrVal);
      }
  case AT_CODE_VTIME:
    if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
      return (af_retattr (aso, AF_ATTMTIME));
    return (af_retattr (aso, AF_ATTSTIME));
  case AT_CODE_XPON:
    atExpand = prevExpand;
    return (valueXpon);
  case AT_CODE_XPOFF:
    prevExpand = atExpand;
    atExpand = FALSE;
    return (valueXpoff);
  }
  return (NULL);
}

LOCAL int setStdAttr (aso, setMode, attrName, attrValue)
     Af_key *aso;
     int setMode;
     char *attrName, *attrValue;
{
  register int i;
  Af_user      user;
  int          mode, state, gen, rev;
  time_t       date;
  char         *strPtr;

  for (i = 0; atStdAttrTab[i].name; i++) {
    if (!(strcmp (attrName, atStdAttrTab[i].name)))
      break;
    if (atStdAttrTab[i].atfsName && !(strcmp (attrName, atStdAttrTab[i].atfsName)))
      break;
  }

  switch (atStdAttrTab[i].code) {
  case AT_CODE_ATIME:
  case AT_CODE_CACHEKEY:
  case AT_CODE_CTIME:
  case AT_CODE_DESCRIPTION:
  case AT_CODE_DSIZE:
  case AT_CODE_HEADER:
  case AT_CODE_HOST:
  case AT_CODE_INTENT:
  case AT_CODE_LOCK:
  case AT_CODE_LOG:
  case AT_CODE_LTIME:
  case AT_CODE_MTIME:
  case AT_CODE_NAME:
  case AT_CODE_PRED:
  case AT_CODE_RTIME:
  case AT_CODE_SELF:
  case AT_CODE_SELFPATH:
  case AT_CODE_SIZE:
  case AT_CODE_STIME:
  case AT_CODE_SUCC:
  case AT_CODE_SYSPATH:
  case AT_CODE_TYPE:
  case AT_CODE_UNIXNAME:
  case AT_CODE_UNIXPATH:
  case AT_CODE_VTIME:
  case AT_CODE_XPOFF:
  case AT_CODE_XPON:
    atError (AT_WARNING, "cannot set attribute with reserved name (no effect)");
    return (0);
  case AT_CODE_ALIAS:
    switch (setMode) {
    case AF_REMOVE:
      if (atDelVersAlias (aso, attrValue))
	return (1);
    default:
      if (atSetVersAlias (aso, attrValue))
	return (1);
    }
    return (0);
  case AT_CODE_AUTHOR:
    atScanUser (attrValue, &user);
    if (af_chauthor (aso, &user) == -1) {
      atError (AT_WARNING, af_errmsg ("chauthor"));
      return (0);
    }
    return (1);
  case AT_CODE_CLEAD: {
    char tmpAttr[AT_CLEADMAXLEN+32];
    strcpy (tmpAttr, AT_ATTCLEAD);
    strcat (tmpAttr, "=");
    strncat (tmpAttr, attrValue, AT_CLEADMAXLEN);
    tmpAttr[AT_CLEADMAXLEN+31] = '\0';
    if (af_setattr (aso, setMode, tmpAttr) == -1) {
      atError (AT_WARNING, af_errmsg ("setattr"));
      return (0);
    }
    return (1);
  }
  case AT_CODE_GENERATION:
    gen = atoi (attrValue);
    rev = af_retnumattr (aso, AF_ATTREV);
    if (af_svnum (aso, gen, rev) == -1) {
      atError (AT_WARNING, af_errmsg ("svnum"));
      return (0);
    }
    return (1);
  case AT_CODE_MODE:
    mode = atoi (attrValue);
    if (af_chmod (aso, mode) == -1) {
      atError (AT_WARNING, af_errmsg ("chmod"));
      return (0);
    }
    return (1);
  case AT_CODE_OWNER:
    atScanUser (attrValue, &user);
    if (af_chowner (aso, &user) == -1) {
      atError (AT_WARNING, af_errmsg ("chowner"));
      return (0);
    }
    return (1);
  case AT_CODE_REVISION:
    gen = af_retnumattr (aso, AF_ATTGEN);
    rev = atoi (attrValue);
    if (af_svnum (aso, gen, rev) == -1) {
      atError (AT_WARNING, af_errmsg ("svnum"));
      return (0);
    }
    return (1);
  case AT_CODE_VERSION:
    if (atScanBinding (attrValue, &strPtr, &gen, &rev, &date) != AT_BIND_VNUM) {
      atError (AT_WARNING, "cannot decipher version number");
      return (0);
    }
    if (af_svnum (aso, gen, rev) == -1) {
      atError (AT_WARNING, af_errmsg ("svnum"));
      return (0);
    }
    return (1);
  case AT_CODE_STATE:
    state = atScanStatus (attrValue);
    if (af_sstate (aso, state) == -1) {
      atError (AT_WARNING, af_errmsg ("sstate"));
      return (0);
    }
    return (1);
  case AT_CODE_NOTE:
    if (af_snote (aso, attrValue) == -1) {
      atError (AT_WARNING, af_errmsg ("snote"));
      return (0);
    }
    return (1);
  }
  return (-1);
}

/*=================================
 *  return user defined attribute
 *=================================*/

static int recursionDepth = 0;

EXPORT char *atRetAttr (aso, attr)
     Af_key *aso;
     char   *attr;
{
  char *attrValue, *attrName, attrMem[AT_MAXATTRSIZE];
  int  attrSize;

  if (recursionDepth >= 10) {
    atError (AT_ERROR, "infinite citation loop");
    return (NULL);
  }

  if ((attr == NULL) || (attr[0] == '\0')) {
    atError (AT_ERROR, "no attribute name given");
    return (NULL);
  }

  attrName = atAttrName (attr);

  if ((attrValue = retStdAttr (aso, attrName)) == NULL)
    if ((attrValue = af_retattr (aso, attrName)) == NULL)
      return (NULL);

  /* If attribute is a standard attribute, don't try to expand */
  if (af_isstdval (attrValue))
    return (attrValue);

  /* ToDo: there should be no limit for attribute length */
  attrMem[0] = '\0';
  recursionDepth++;
  if ((attrSize = atExpandAttrs (aso, attrValue, strlen(attrValue), attrMem,
				 AT_MAXATTRSIZE, AT_EXPAND_STRING)) == -1) {
    recursionDepth--;
    atError (AT_ERROR, "attribute expansion too long");
    return (NULL);
  }
  recursionDepth--;

  atFreeAttr (attrValue);
  if ((attrValue = malloc (attrSize+1)) == NULL) {
    atError (AT_ERROR, "not enough memory");
    return (NULL);
  }
  strcpy (attrValue, attrMem);

  /* file reference attribute */
  if (attrValue[0] == '^') {
    char   *aFileContents, *tmpFileName;
    FILE   *aFile, *tmpFile;
    Af_key *aFileKey;
    size_t  aFileSize;

    /* ToDo: handle multiple values */
    if (!(aFileKey = atBindVersion (attrValue+1, "[]")))
      return (attrValue); /* or return (NULL); ??? */

    if ((aFile = af_open (aFileKey, "r")) == NULL) {
      af_dropkey (aFileKey);
      return (attrValue); /* or return (NULL); ??? */
    }
    aFileSize = af_retnumattr (aFileKey, AF_ATTSIZE);
    if ((aFileContents = malloc (aFileSize+1)) == NULL) {
      af_close (aFile);
      af_dropkey (aFileKey);
      return (NULL);
    }
    fread (aFileContents, sizeof (char), aFileSize, aFile);
    af_close (aFile);
    aFileContents[aFileSize] = '\0';

    tmpFileName = stTmpFile(NULL);
    if ((tmpFile = fopen (tmpFileName, "w")) == NULL) {
      free (aFileContents);
      af_dropkey (aFileKey);
      atError (AT_ERROR, "cannot open temporary file");
      return (NULL);
    }
    recursionDepth++;
    attrSize = atExpandAttrs (aFileKey, aFileContents, aFileSize, tmpFile, 0, AT_EXPAND_FILE);
    recursionDepth--;
    fclose (tmpFile);
    af_dropkey (aFileKey);
    free (aFileContents);

    if ((attrValue = realloc (attrValue, attrSize+1)) == NULL) {
      fclose (tmpFile);
      atError (AT_ERROR, "not enough memory");
      return (NULL);
    }
    tmpFile = fopen (tmpFileName, "r");
    fread (attrValue, sizeof (char), attrSize, tmpFile);
    attrValue[attrSize] = '\0';
    fclose (tmpFile);
    unlink (tmpFileName);
    stUnRegisterFile (tmpFileName);
  }

  /* execution attribute */
  if (attrValue[0] == '!') {
#define BUFLEN 1024
    FILE *cPipe;
    char *cResultBuf, *cResultPtr;
    int  cByteCount;
    size_t cBufSize;

    /* ToDo: handle multiple values */
    if ((cPipe = popen (attrValue+1, "r")) == (FILE *)NULL)
      return (attrValue); /* or return (NULL); ??? */

    cBufSize = BUFLEN;
    if ((cResultBuf = malloc (cBufSize)) == NULL) {
      atError (AT_ERROR, "not enough memory");
      pclose (cPipe);
      return (NULL);
    }
    cResultPtr = cResultBuf;
    while (1) {
      cByteCount = fread (cResultPtr, sizeof (char), BUFLEN, cPipe);
      if (cByteCount != BUFLEN) {
	cBufSize = (cBufSize - BUFLEN) + cByteCount;
	break;
      }
      cBufSize += BUFLEN;
      if ((cResultBuf = realloc (cResultBuf, cBufSize)) == NULL) {
	atError (AT_ERROR, "not enough memory");
	pclose (cPipe);
	return (NULL);
      }
      cResultPtr += BUFLEN;
    } 
    pclose (cPipe);

    /* ToDo: there should be no limit for attribute length */
    attrMem[0] = '\0';
    recursionDepth++;
    if ((attrSize = atExpandAttrs (aso, cResultBuf, cBufSize, attrMem,
				   AT_MAXATTRSIZE, AT_EXPAND_STRING)) == -1) {
      recursionDepth--;
      atError (AT_ERROR, "attribute expansion too long");
      return (NULL);
    }
    recursionDepth--;
    atFreeAttr (attrValue);
    if ((attrValue = malloc (attrSize+1)) == NULL) {
      atError (AT_ERROR, "not enough memory");
      return (NULL);
    }
    strcpy (attrValue, attrMem);
  }

  /* ASO reference attribute */
  if (attrValue[0] == '*') {
    /* ToDo: there should be no limit for attribute length (attrMem) */
    char *curVal = &attrValue[1], *nextVal, *resultPtr = attrMem;

    /* handle multiple values */
    nextVal = strchr (curVal, '\n');
    while (nextVal) {
      *nextVal = '\0';
      strcpy (resultPtr, atLocalPath (curVal));
      resultPtr = strchr (resultPtr, '\0');
      *resultPtr = '\n';
      resultPtr++;
      curVal = nextVal+1;
      if (*curVal == '*')
	curVal++;
      else
	atError (AT_WARNING, "unrecognized value in pointer attribute");
      nextVal = strchr (curVal, '\n');
    }
    strcpy (resultPtr, atLocalPath (curVal));

    atFreeAttr (attrValue);
    if ((attrValue = malloc (strlen (attrMem+1))) == NULL) {
      atError (AT_ERROR, "not enough memory");
      return (NULL);
    }
    strcpy (attrValue, attrMem);
  }
  return (attrValue);
}

/*==============================
 *  set user defined attribute
 *==============================*/

EXPORT int atSetAttr (aso, attr, mode)
     Af_key *aso;
     char   *attr;
     int    mode;
{
  char         attrFileName[PATH_MAX], msgBuf[PATH_MAX+64];
  char         *attrMem, *attrName, *valPtr, *valId = "";
  unsigned int attrMemLen = 0;
  int          attrNameLen, retCode;
  FILE         *attrFile;

  if ((attr == NULL) || (attr[0] == '\0')) {
    atError (AT_ERROR, "no attribute given");
    return (FALSE);
  }

  attrFileName[0] = '\0';
  attrFileName[PATH_MAX-1] = '\0';

  if ((valPtr = strchr (attr, AF_UDANAMDEL)) == NULL) { /* no value */
    /* cut off + or - at the end of the name */
    attrNameLen = strlen (attr);
    if ((attr[attrNameLen-1] == '+') || (attr[attrNameLen-1] == '-'))
      attr[attrNameLen-1] = '\0';
    if (strlen (attr) > AF_UDANAMLEN) {
      atError (AT_ERROR, "attribute name too long");
      return (FALSE);
    }
    if ((retCode = setStdAttr (aso, mode, attr, NULL)) == 0)
      return (FALSE);
    else if (retCode == -1)
      if (af_setattr (aso, mode, attr) == -1) {
	atError (AT_ERROR, af_errmsg ("atSetAttr"));
	return (FALSE);
      }
    return (TRUE);
  }

  if (attr == valPtr) { /* No attribute name */
    atError (AT_ERROR, "no attribute name given");
    return (FALSE);
  }
  if ((valPtr-attr) > AF_UDANAMLEN) {
    atError (AT_ERROR, "attribute name too long");
    return (FALSE);
  }

  attrName = atAttrName (attr);
  if (*(valPtr-1) == '+') {
    mode = AF_ADD;
    attrMemLen = strlen (attr);
  }
  if (*(valPtr-1) == '-') {
    /*
     * mode = AF_REMOVE;
     * attrMemLen = strlen (attr);
     */
    atError (AT_ERROR, "Cannot delete single value from attribute (yet)");
    return (FALSE);
  }
  valPtr++;

  if (*valPtr != '@') { /* take attribute value literally */
    int i;
    for (i=0; valPtr[i]; i++) {
      if (valPtr[i] == 1) { /* Ctl-A not allowed */
	atError (AT_ERROR, "invalid attribute value (contains Ctl-A character)");
	return (FALSE);
      }
    }
    if (*valPtr == '*') { /* ASO reference attribute */
      Af_key *destAso;
      /* perform version binding */
      if ((destAso = atBindVersion (valPtr+1, "[]")) == NULL) {
	atError (AT_ERROR, "invalid reference attribute value (cannot be uniquely bound)");
	return (FALSE);
      }
      valPtr = atNetworkPath (destAso);
      valId = "*";
      attrMemLen = PATH_MAX+HOST_MAX+AF_UDANAMLEN+16;
    }
    if (attrMemLen) {
      /* copy attribute and eliminate + and - char */
      if ((attrMem = malloc (attrMemLen)) == NULL) {
	atError (AT_ERROR, "not enough memory");
	return (FALSE);
      }
      sprintf (attrMem, "%s%c%s%s", attrName, AF_UDANAMDEL, valId, valPtr);
    }
    else
      attrMem = attr;

    if ((retCode = setStdAttr (aso, mode, attrName, valPtr)) == 0)
      return (FALSE);
    else if (retCode == -1)
      if (af_setattr (aso, mode, attrMem) == -1) {
	if (attrMem != attr)
	  free (attrMem);
	atError (AT_ERROR, af_errmsg ("atSetAttr"));
	return (FALSE);
      }
    if (attrMem != attr)
      free (attrMem);
    return (TRUE);
  }

  /* take attribute value from file */
  strncpy (attrFileName, valPtr+1, PATH_MAX-1);

  if (strcmp (attrFileName, "-")) { /* read from a regular file */
    struct stat attrIbuf;

    if ((attrFile = fopen (attrFileName, "r")) == NULL) {
      sprintf (msgBuf, "Can't open attribute value file %s.", attrFileName);
      atError (AT_ERROR, msgBuf);
      return (FALSE);
    }
    if (fstat (fileno (attrFile), &attrIbuf) < 0) {
      sprintf (msgBuf, "Can't open attribute value file %s.", attrFileName);
      atError (AT_ERROR, msgBuf);
      return (FALSE);
    }
    attrMemLen = strlen (attrName) + attrIbuf.st_size + 2;
    if ((attrMem = malloc (attrMemLen)) == NULL) {
      atError (AT_ERROR, "not enough memory");
      return (FALSE);
    }
    sprintf (attrMem, "%s%d", attrName, AF_UDANAMDEL);
    fread (&attrMem[strlen(attrName)+1], sizeof (char), (size_t)attrIbuf.st_size, attrFile); 
    fclose (attrFile);
    attrMem[attrMemLen-1] = '\0';
  }
  else { /* read from standard input */
    char *memPtr;
    int  charNum;

    attrMemLen = AT_MAXATTRSIZE;
    if ((attrMem = malloc (attrMemLen)) == NULL) {
      atError (AT_ERROR, "not enough memory");
      return (FALSE);
    }
    sprintf (attrMem, "%s%d", attrName, AF_UDANAMDEL);
    memPtr = &attrMem[strlen(attrName)+1];
    charNum = fread (memPtr, sizeof (char), (size_t)(attrMemLen-2)-strlen(attrName), stdin);
    memPtr[charNum] = '\0';
  }

  if ((retCode = setStdAttr (aso, mode, attrName, &attrMem[strlen(attrName)+1])) == 0) {
    free (attrMem);
    return (FALSE);
  }
  else if (retCode == -1)
    if (af_setattr (aso, mode, attrMem) == -1) {
      free (attrMem);
      atError (AT_ERROR, af_errmsg ("atSetAttr"));
      return (FALSE);
    }
  free (attrMem);
  return (TRUE);
}

/*==================================================
 *  read user defined attributes from file and set
 *==================================================*/

EXPORT int atSetAttrFile (aso, fileName)
     Af_key *aso;
     char   *fileName;
{
  FILE *attrFile;
  struct stat iBuf;
  char msgBuf[PATH_MAX+64], *attrBuf, *attrPtr;
  int i, retCode = TRUE;

  if ((attrFile = fopen (fileName, "r")) == NULL) {
    sprintf (msgBuf, "Cannot open attribute file %s.", fileName);
    atError (AT_ERROR, msgBuf);
    return (FALSE);
  }

  if (fstat (fileno (attrFile), &iBuf) < 0) {
    sprintf (msgBuf, "Couldn't stat attribute file %s.", fileName);
    atError (AT_ERROR, msgBuf);
    return (FALSE);
  }

  if ((attrBuf = malloc ((unsigned)iBuf.st_size+1)) == NULL) {
    atError (AT_ERROR, "Not enough memory for attribute file.");
    return (FALSE);
  }

  fread (attrBuf, sizeof (char), (size_t)iBuf.st_size, attrFile);
  attrBuf[iBuf.st_size] = '\0';

  attrPtr = attrBuf;
  for (i=0; i < iBuf.st_size; i++) {
    if (attrBuf[i] == '\n') {
      attrBuf[i] = '\0';
      retCode = atSetAttr (aso, attrPtr, AF_REPLACE);
      attrPtr = &attrBuf[i+1];
      }
  }
  /* When the last line of the file has no newline, one attribute remains */
  if (*attrPtr)
    retCode = atSetAttr (aso, attrPtr, AF_REPLACE);

  free (attrBuf);
  return (retCode);
}

/*=================================
 *  return attr name and value
 *=================================*/

EXPORT char *atAttrName (attr)
     char *attr;
{
  static char attrName[AF_UDANAMLEN];
  char        *origPtr = attr, *newPtr = attrName;

  while (*origPtr && (*origPtr != AF_UDANAMDEL) && (*origPtr != '+') &&
	 (newPtr < &attrName[AF_UDANAMLEN])) {
    *newPtr++ = *origPtr++;
    if (newPtr == &attrName[AF_UDANAMLEN-1])
      break;
  }
  
  *newPtr = '\0';
  return (attrName);
}

EXPORT char *atAttrValue (attr)
     char *attr;
{
  char *valPtr = attr;

  while (*valPtr) {
    if (*valPtr == AF_UDANAMDEL) {
      valPtr++;
      if (*valPtr)
	return (valPtr);
      else
	return (NULL);
    }
    valPtr++;
  }
  return (NULL);
}

/*=================================
 *  other stuff
 *=================================*/

EXPORT int atMatchAttr (aso, attr)
     Af_key *aso;
     char   *attr;
     /* check if 'aso' has the given 'attr'. Result values are TRUE or FALSE.
	If just an attribute name is given, we return a positive result if
	the attribute exists or, in the case that it is a standard attribute,
	if it's value is non null.
	A full attribute (name and value) must match (value may be a pattern).
      */
{
  char *asoAttr, *valuePattern, *cp;

  /* attribute not found */
  if ((asoAttr = atRetAttr (aso, attr)) == NULL)
    return (FALSE);

  /* standard attribute with empty value */
  if (af_isstdval (asoAttr) && !*asoAttr)
    return (FALSE);

  valuePattern = stConvertPattern (atAttrValue (attr));

  /* no value given and attribute exists */
  if (!*valuePattern) {
    atFreeAttr (asoAttr);
    return (TRUE);
  }

  if ((cp = re_comp (valuePattern))) {
    atError (AT_ERROR, cp);
    atFreeAttr (asoAttr);
    return (FALSE);
  }

  /* ToDo: check each single value */
  if (re_exec (asoAttr)) {
    atFreeAttr (asoAttr);
    return (TRUE);
  }
  atFreeAttr (asoAttr);
  return (FALSE);
}

EXPORT void atFreeAttr (attr)
     char *attr;
{
  if ((attr != logMem) && (attr != headerStr) &&
      (attr != valueXpon) && (attr != valueXpoff) &&
      (attr != valueNa))
    af_freeattr (attr);
}
