/* 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
 *
 * afarchive.c -- read/write archives of attribute filesystem
 *
 * Author: Andreas Lampen (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: afarchive.c[7.1] Thu Aug  4 16:02:46 1994 andy@cs.tu-berlin.de frozen $
 */

#include <string.h>

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

extern int af_errno;
extern int nameConflict;

EXPORT int af_suppressUdas = FALSE;

LOCAL short arVersion;

/*==========================================================================
 *	afFirstItem, afNextItem -- isolate items in input line
 *==========================================================================*/

EXPORT char *afFirstItem (line)
     char *line;
{
  register char *sptr;

  /* skip leading blank */
  if ((sptr = strchr (&line[1], ' ')) == NULL)
    sptr = strchr (&line[1], '\n');

  *sptr = '\0';
  return (&line[1]);
}

EXPORT char *afNextItem (line)
     char *line;
{
  register char *sptr, *finptr;

  sptr = &line[strlen(line)]+1; /* move to next entry */
  if ((finptr = strchr (sptr, ' ')) == NULL)
    finptr = strchr (sptr, '\n');
  *finptr = '\0';

  return (sptr);
}
  
/*======================= rddata ====================================*/

LOCAL int af_rddata (file, dbuf, list)
     FILE   *file;
     char   *dbuf;
     Af_revlist *list;
{
  register char   *itemptr;
  char	          idstr[AF_IDSTRLEN+1], line[AF_LINESIZ];
  register int    i, maxindex;
  int             gen, rev;
  register size_t size, dpos = 0;

  /* if there is a valid busy version */
  if (list->af_list[0].af_class & AF_VALID)
    maxindex = list->af_nrevs;
  else
    maxindex = list->af_nrevs+1;

  idstr[AF_IDSTRLEN] = '\0';
  /* skip busy version */
  for (i=1; i < maxindex; i++) {
    fgets (idstr, AF_IDSTRLEN+1, file);
    if (strcmp (idstr, AF_NOTEID))
      FATAL ("rddata", "wrong note-ID in datafile", AF_EINCONSIST, ERROR);
    fgets (line, AF_LINESIZ, file);
    itemptr = afFirstItem (line);
    gen = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    rev = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    size = (size_t) atoi (itemptr);

    if ((list->af_list[i].af_gen != gen) || (list->af_list[i].af_rev != rev))
      FATAL ("rddata", "wrong version in datafile", AF_EINCONSIST, ERROR);

    /* read note */
    list->af_list[i].af_notesize = size;
    if (size > 0) {
      fread (&(dbuf[dpos]), sizeof(char), (size_t) size, file);
      list->af_list[i].af_note = &(dbuf[dpos]);
      /* replace newline by nullbyte */
      list->af_list[i].af_note[size-sizeof(char)] = '\0';
      dpos = dpos+size;
    }
    else {
      /* skip newline */
      fread (&(dbuf[dpos]), sizeof(char), (size_t) 1, file);
      list->af_list[i].af_note = NULL;
    }
    
    fgets (idstr, AF_IDSTRLEN+1, file);
    if (strcmp (idstr, AF_DATAID))
      FATAL ("rddata", "wrong data-ID in datafile", AF_EINCONSIST, ERROR);
    fgets (line, AF_LINESIZ, file);
    itemptr = afFirstItem (line);
    gen = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    rev = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    itemptr = afNextItem (itemptr);
    size = (size_t) atoi (itemptr);

    if ((list->af_list[i].af_gen != gen) || (list->af_list[i].af_rev != rev))
      FATAL ("rddata", "wrong version in datafile", AF_EINCONSIST, ERROR);

    /* read data */
    if (size > 0) {
      fread (&(dbuf[dpos]), sizeof(char), (size_t) size, file);
      list->af_list[i].af_data = &(dbuf[dpos]);
      dpos = dpos+size;
    }
    else {
      list->af_list[i].af_data = NULL;
    }
  }
  return (AF_OK);
} /* af_rddata */

/*==========================================================================
 *	afReadData -- read notes and data section of archive file
 *==========================================================================*/

EXPORT int afReadData (list)
     Af_revlist *list;
{
  register char *data;
  char          idstr[AF_SEGSTRLEN+1], *archiveName, line[AF_LINESIZ];
  register FILE *attrArchFile, *dataArchFile;

  /* if data are already loaded */
  if ((list->af_extent & AF_DATA) == AF_DATA)
    return (AF_OK);

  /* set lock on old attr archive file -- tmp attr archive file *is* still locked */
  archiveName = afArchiveName (list->af_arpath, AF_ATTRDIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_READ);
  if ((attrArchFile = afOpenLock (archiveName, AF_READ, list)) == NULL)
    return (ERROR);

  archiveName = afArchiveName (list->af_arpath, AF_DATADIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_READ);
  if ((dataArchFile = fopen (archiveName, "r")) == NULL) {
    afCloseUnlock (attrArchFile, AF_READ, list);
    FATAL ("ReadData", "", AF_ENOATFSFILE, ERROR);
  }

  idstr[AF_SEGSTRLEN] = '\0';
  fgets (idstr, AF_SEGSTRLEN+1, dataArchFile);
  if (strncmp (idstr, AF_DATAHEADER, AF_SEGSTRLEN)) {
    fclose (dataArchFile);
    afCloseUnlock (attrArchFile, AF_READ, list);
    FATAL ("ReadData", "wrong header in datafile", AF_EINCONSIST, ERROR);
  }

  fgets (line, AF_LINESIZ, dataArchFile);
  arVersion = atoi (line);
  if (arVersion != AF_ARCURVERS) {
    fclose (dataArchFile);
    afCloseUnlock (attrArchFile, AF_READ, list);
    FATAL ("ReadData", "unknown archive format version", AF_EINCONSIST, ERROR);
  }

  /* if there are data */
  if (list->af_datasize) {
    /* allocate memory for data */
    if ((data = af_malloc (list, (unsigned) list->af_datasize * sizeof (char))) == NULL) {
      fclose (dataArchFile);
      afCloseUnlock (attrArchFile, AF_READ, list);
      return (ERROR);
    }

    if (af_rddata (dataArchFile, data, list) != AF_OK) {
      fclose (dataArchFile);
      afCloseUnlock (attrArchFile, AF_READ, list);
      return (ERROR);
    }
  }

  list->af_extent |= AF_DATA;
  fclose (dataArchFile);
  afCloseUnlock (attrArchFile, AF_READ, list);
  return (AF_OK);
}

/*======================= rdattrs ====================================*/

LOCAL int af_rdattrs (file, list, bibufptr)
     FILE	 *file;
     Af_revlist  *list;
     struct stat *bibufptr;
{
  register char *itemptr;
  char          idstr[AF_IDSTRLEN+1], line[AF_LINESIZ];
  register int  i;
  int           tmpMode;
  bool          writeok;
  Af_user       *user, *owner;

  /* skip idstring */
  idstr[AF_IDSTRLEN] = '\0';
  fgets (idstr, AF_IDSTRLEN+1, file);
  
  /* read constant attributes of busy version */
  fgets (line, AF_LINESIZ, file);

  itemptr = afFirstItem (line);   /* host - ignored ... */
  itemptr = afNextItem (itemptr); /* path - ignored ... */

  /* skip name and type */
  itemptr = afNextItem (itemptr); /* name - ignored ... */
  itemptr = afNextItem (itemptr); /* type - ignored ... */
  itemptr = afNextItem (itemptr); /* variant (ignored) */

  /* get owner of archive directory */
  if ((owner = afArchiveOwner (list->af_arpath, &writeok, &list->af_owngid)) == NULL)
    FATAL ("rdattrs", "cannot get owner of archive", AF_EINTERNAL, ERROR);
  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 (writeok)
    list->af_extent |= AF_UXWRITE;
  
  /* read owner from archive */
  fgets (idstr, AF_IDSTRLEN+1, file);
  fgets (line, AF_LINESIZ, file); /* name, host and domain of owner */
  /* plausibility of owner should be checked here */

  fgets (idstr, AF_IDSTRLEN+1, file);
  fgets (line, AF_LINESIZ, file); /* predecessor of busy object */
  itemptr = afFirstItem (line);
  list->af_list[0].af_predgen = atoi (itemptr);
  itemptr = afNextItem (itemptr);
  list->af_list[0].af_predrev = atoi (itemptr);

  fgets (idstr, AF_IDSTRLEN+1, file);
  fgets (line, AF_LINESIZ, file); /* locker */
  itemptr = afFirstItem (line);
  if (strcmp (itemptr, AF_NOSTRING)) {
    list->af_list[0].af_lckname = af_entersym (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[0].af_lckhost = af_enterhost (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[0].af_lckdomain = af_enterdomain (itemptr);
  }
  else {
    list->af_list[0].af_lckname = NULL;
    itemptr = afNextItem (itemptr);
    list->af_list[0].af_lckhost = NULL;
    itemptr = afNextItem (itemptr);
    list->af_list[0].af_lckdomain = NULL;
  }
  itemptr = afNextItem (itemptr);
  list->af_list[0].af_ltime = (time_t)atoi(itemptr);

  /* initialize and attributes for busy version */
  list->af_list[0].af_gen = AF_BUSYVERS;
  list->af_list[0].af_rev = AF_BUSYVERS;
  list->af_list[0].af_state = AF_BUSY;
  list->af_list[0].af_stime = AF_NOTIME;
  list->af_list[0].af_repr = AF_FILE;
  list->af_list[0].af_dsize = (size_t) 0;
  list->af_list[0].af_data = NULL;
  list->af_list[0].af_hashname = NULL;
  list->af_list[0].af_nrefs = 0;
  list->af_list[0].af_succgen = AF_NOVNUM;
  list->af_list[0].af_succrev = AF_NOVNUM;

  if (bibufptr->st_ino) { /* if there is a busy version */
    list->af_list[0].af_class = AF_VALID;
    if ((user = af_afuser (bibufptr->st_uid)) == NULL) {
      af_wng ("rdattrs", "invalid userID in inode of busy file");
      user = af_afuser ((uid_t) geteuid());
    }
    list->af_list[0].af_auname = af_entersym (user->af_username);
    list->af_list[0].af_auhost = af_enterhost (user->af_userhost);
    list->af_list[0].af_audomain = af_enterdomain (user->af_userdomain);
    list->af_list[0].af_mode = bibufptr->st_mode;
    list->af_list[0].af_mtime = bibufptr->st_mtime;
    list->af_list[0].af_atime = bibufptr->st_atime;
    list->af_list[0].af_ctime = bibufptr->st_ctime;
    list->af_list[0].af_fsize = (size_t) bibufptr->st_size;
  }
  else {
    list->af_list[0].af_class = 0;
    list->af_list[0].af_auname = NULL;
    list->af_list[0].af_auhost = NULL;
    list->af_list[0].af_audomain = NULL;
    list->af_list[0].af_mode = AF_NOMODE;
    list->af_list[0].af_mtime = AF_NOTIME;
    list->af_list[0].af_atime = AF_NOTIME;
    list->af_list[0].af_ctime = AF_NOTIME;
    list->af_list[0].af_fsize = 0;
  }
      
  /* read list */
  for (i=1; i < list->af_nrevs; i++) {
    /* do initializations */
    list->af_list[i].af_class = AF_VALID;
    list->af_list[i].af_notesize = 0;
    list->af_list[i].af_note = NULL;
    list->af_list[i].af_data = NULL;
    list->af_list[0].af_nrefs = 0;
    list->af_list[i].af_hashname = NULL;
    
    /* enter name (set a pointer to the name-field of af_list[0]) */
    /* skip position 0 */
    if (i != 0) {
      list->af_list[i].af_name = list->af_list[0].af_name;
      list->af_list[i].af_type = list->af_list[0].af_type;
    }

    /* read revision ID */
    fgets (idstr, AF_IDSTRLEN+1, file);
    if (strcmp (idstr, AF_REVID)) /* could be done for every field */
      FATAL ("rdattrs", "wrong revision-ID in archive file", AF_EINCONSIST, ERROR);
    fgets (line, AF_LINESIZ, file);
    itemptr = afFirstItem (line);
    list->af_list[i].af_gen = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_rev = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_state = (short) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    sscanf (itemptr, "%o", &tmpMode);
    list->af_list[i].af_mode = (mode_t) tmpMode;
    itemptr = afNextItem (itemptr); /* variant (ignored) */

    /* read author*/
    fgetc (file); /* skip tab */
    fgets (idstr, AF_IDSTRLEN+1, file);
    fgets (line, AF_LINESIZ, file); /* predecessor of busy object */
    itemptr = afFirstItem (line);
    list->af_list[i].af_auname = af_entersym (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_auhost = af_enterhost (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_audomain = af_enterdomain (itemptr);
    itemptr = afNextItem (itemptr);
    if (strcmp (itemptr, AF_NOSTRING)) {
      list->af_list[i].af_lckname = af_entersym (itemptr);
      itemptr = afNextItem (itemptr);
      list->af_list[i].af_lckhost = af_enterhost (itemptr);
      itemptr = afNextItem (itemptr);
      list->af_list[i].af_lckdomain = af_enterdomain (itemptr);
    }
    else {
      list->af_list[i].af_lckname = NULL;
      itemptr = afNextItem (itemptr);
      list->af_list[i].af_lckhost = NULL;
      itemptr = afNextItem (itemptr);
      list->af_list[i].af_lckdomain = NULL;
    }

    /* read dates */
    fgetc (file); /* skip tab */
    fgets (idstr, AF_IDSTRLEN+1, file);
    fgets (line, AF_LINESIZ, file);
    itemptr = afFirstItem (line);
    list->af_list[i].af_mtime = (time_t) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_atime = (time_t) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_ctime = (time_t) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_stime = (time_t) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_ltime = (time_t) atoi (itemptr);
    
    /* read kind of representation */
    fgetc (file); /* skip tab */
    fgets (idstr, AF_IDSTRLEN+1, file);
    fgets (line, AF_LINESIZ, file);
    itemptr = afFirstItem (line);
    list->af_list[i].af_repr = (short) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_fsize = (size_t) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_dsize = (size_t) atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_succgen = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_succrev = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_predgen = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    list->af_list[i].af_predrev = atoi (itemptr);
  }

  if (!(bibufptr->st_ino)) /* if there is no busy version */
    list->af_nrevs--;
  
  return (AF_OK);
} /* af_rdattrs */

/*======================= rdudas ====================================*/

LOCAL int af_rdudas (file, list)
     FILE       *file;
     Af_revlist *list;
{
  char	        idstr[AF_IDSTRLEN+1], line[AF_LINESIZ];
  register char *udabuf = NULL;
  char          *itemptr;
  register int	c, i, j, maxindex;
  int      	gen, rev;
  Af_key        tmpkey;

  tmpkey.af_ldes = list;

  /* if there is a valid busy version */
  if (list->af_list[0].af_class & AF_VALID)
    maxindex = list->af_nrevs;
  else
    maxindex = list->af_nrevs+1;

  getc (file); /* skip newline */
  idstr[AF_IDSTRLEN] = '\0';
  for (i=0; i < maxindex; i++) {
    fgets (idstr, AF_IDSTRLEN+1, file);
    if (strcmp (idstr, AF_UDAID))
      FATAL ("rdudas", "wrong uda-ID in archive file", AF_EINCONSIST, ERROR);
    fgets (line, AF_LINESIZ, file);
    itemptr = afFirstItem (line);
    gen = atoi (itemptr);
    itemptr = afNextItem (itemptr);
    rev = atoi (itemptr);

    if ((list->af_list[i].af_gen != gen) || (list->af_list[i].af_rev != rev))
      FATAL ("rdudas", "wrong version in archive file", AF_EINCONSIST, ERROR);
      
    /* build up hashlist and read user defined attributes */
    tmpkey.af_lpos = i;
    afInitUdas (&tmpkey);

    if (i == 0) { /* initalize only once */
      if ((udabuf = malloc ((unsigned) (AF_UDASEGSIZ * sizeof (char)))) == NULL)
	FAIL ("rdudas", "malloc", AF_ESYSERR, ERROR);
    }

    /* if there is *no* valid busy version, skip the user defined */
    /* attributes for the busy version */
    if ((i==0) && !(list->af_list[0].af_class & AF_VALID)) {
      while (TRUE) {
	if ((c = getc (file)) == '\0') {
	  if ((c = getc (file)) == '\0')
	    break;
	}
      }
      getc (file); /* skip trailing newline char */
      continue;
    }

    j = 0;
    while (TRUE) {
      if ((udabuf[j] = getc (file)) == '\0') {
	if (j != 0) {
	  tmpkey.af_lpos = i;
	  afEnterUda (&tmpkey, udabuf);
	}
	/* a second nullbyte indicates the end of the list of udas */
	if ((c = getc (file)) == '\0')
	  break;
	udabuf[0] = c;
	j = 1;
      }
      else {
	j++;
	if ((j % AF_UDASEGSIZ) == 0) { /* if segment is full */
	  if ((udabuf = realloc (udabuf, (unsigned) ((j + AF_UDASEGSIZ) * sizeof (char)))) == NULL) 
	    FAIL ("rdudas", "realloc", AF_ESYSERR, ERROR);
	}
      }
    }
    getc (file); /* skip trailing newline char */
  }
  free (udabuf);
  return (AF_OK);
} /* af_rdudas */

/*==========================================================================
 *	afReadAttrs -- read attributes from archive file
 *==========================================================================*/

EXPORT int afReadAttrs (list)
     Af_revlist *list;
{
  char	        idstr[AF_SEGSTRLEN+1], line[AF_LINESIZ];
  char          *archiveName;
  register char *itemptr;
  register FILE	*archfile;
  struct stat   bibuf, aibuf;
  bool          writeok;
  register Af_user *user, *owner;
  Af_key        tmpkey;

  tmpkey.af_ldes = list;

  if (!list->af_arpath) {
    list->af_arpath = afArchivePath (list->af_cattrs.af_syspath);
    list->af_extent |= AF_ARCHIVE;
  }
  
  if ((list->af_cattrs.af_globtype == NULL) &&
      (!strcmp (list->af_cattrs.af_globname, ".") || !strcmp (list->af_cattrs.af_globname, "..")))
    archiveName = NULL;
  else
    archiveName = afArchiveName (list->af_arpath, AF_ATTRDIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_READ);

  /* if archive file has been modified */
  if (stat (NOTNIL (archiveName), &aibuf) == -1)
    aibuf.st_mtime = (time_t) 0;
  if (list->af_lastmod != aibuf.st_mtime) {
    if (list->af_access > 0) {
      af_wng ("ReadAttrs", "archive file has changed");
      list->af_access = 0;
    }
    afDetachList (list); /* invalidate data */
  }

  /* if attributes are already loaded */
  if ((list->af_extent & AF_ATTRS) == AF_ATTRS) {
    /* see if busy version has changed */
    if (stat (list->af_busyfilename, &bibuf) == ERROR) {
      /* no busy version */
      if (list->af_list[0].af_class & AF_VALID) {
	list->af_nrevs--;
	list->af_list[0].af_class &= ~AF_VALID;
      }
    }
    else {
      if (bibuf.st_ctime != list->af_list[0].af_ctime) {
	/* update busy version */
	if (!(list->af_list[0].af_class & AF_VALID)) {
	  list->af_nrevs++;
	  tmpkey.af_lpos = 0;
	  afInitUdas (&tmpkey);
	  list->af_list[0].af_class = AF_VALID;
	}
	if ((user = af_afuser (bibuf.st_uid)) == NULL) {
	  af_wng ("ReadAttrs", "invalid userID in inode of busy file");
	  user = af_afuser ((uid_t) geteuid());
	}
	list->af_list[0].af_auname = af_entersym (user->af_username);
	list->af_list[0].af_auhost = af_enterhost (user->af_userhost);
	list->af_list[0].af_audomain = af_enterdomain (user->af_userdomain);
	list->af_list[0].af_mode = bibuf.st_mode;
	list->af_list[0].af_mtime = bibuf.st_mtime;
	list->af_list[0].af_atime = bibuf.st_atime;
	list->af_list[0].af_ctime = bibuf.st_ctime;
	list->af_list[0].af_fsize = (size_t) bibuf.st_size;
      }
    }
    return (AF_OK);
  }

  if (list->af_list)
    afDetachList (list);

  if (!list->af_busyfilename) {
    if ((list->af_busyfilename = af_gbusname (list->af_cattrs.af_syspath, list->af_cattrs.af_globname, list->af_cattrs.af_globtype)) == NULL)
      return (ERROR);
  }

  if (stat (list->af_busyfilename, &bibuf) == ERROR)
    bibuf.st_ino = 0;

  /* open archive with read lock */
  if ((archiveName == NULL) || ((archfile = afOpenLock (archiveName, AF_READ, list)) == NULL)) {
    if (bibuf.st_ino == 0) /* no busy file */ {
      list->af_nrevs = 0;
      afDetachList (list);
      FAIL ("ReadAttrs", "", AF_ENOATFSFILE, ERROR);
    }
    list->af_nrevs = 1;
    list->af_listlen = AF_NEWREVS;
    list->af_extent |= AF_SEGMASK;
    list->af_datasize = 0;
    
    /* determine author of busy file */
    if ((user = af_afuser (bibuf.st_uid)) == NULL) {
      af_wng ("ReadAttrs", "invalid userID in inode of busy file");
      user = af_afuser ((uid_t)geteuid());
    }

    /* if an archive-directory exists, get its owner */
    if ((owner = afArchiveOwner (list->af_arpath, &writeok, &list->af_owngid)) == NULL) {
      list->af_cattrs.af_ownname = af_entersym (user->af_username);
      list->af_cattrs.af_ownhost = af_enterhost (user->af_userhost);
      list->af_cattrs.af_owndomain = af_enterdomain (user->af_userdomain);
    }
    else {
      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 (writeok)
      list->af_extent |= AF_UXWRITE;
      
    if ((list->af_list = (Af_vattrs *)af_malloc (list, (unsigned) (list->af_listlen * sizeof(Af_vattrs)))) == NULL) {
      return (ERROR);
    }
      
    memset ((char *)list->af_list, 0, list->af_listlen * sizeof (Af_vattrs));
    /* init attrbuf for busy version  (relevant attrs only) */
    list->af_list[0].af_name = list->af_cattrs.af_globname;
    list->af_list[0].af_type = list->af_cattrs.af_globtype;
    list->af_list[0].af_gen = AF_BUSYVERS;
    list->af_list[0].af_rev = AF_BUSYVERS;
    list->af_list[0].af_state = AF_BUSY;
    list->af_list[0].af_class = AF_VALID;
    list->af_list[0].af_auname = af_entersym (user->af_username);
    list->af_list[0].af_auhost = af_enterhost (user->af_userhost);
    list->af_list[0].af_audomain = af_enterdomain (user->af_userdomain);
    list->af_list[0].af_mode = bibuf.st_mode;
    list->af_list[0].af_lckname = NULL;
    list->af_list[0].af_lckhost = NULL;
    list->af_list[0].af_lckdomain = NULL;
    list->af_list[0].af_mtime = bibuf.st_mtime;
    list->af_list[0].af_atime = bibuf.st_atime;
    list->af_list[0].af_ctime = bibuf.st_ctime;
    list->af_list[0].af_stime = AF_NOTIME;
    list->af_list[0].af_ltime = AF_NOTIME;
    list->af_list[0].af_notesize = 0;
    list->af_list[0].af_note = NULL;
    tmpkey.af_lpos = 0;
    afInitUdas (&tmpkey);
    list->af_list[0].af_repr = AF_FILE;
    list->af_list[0].af_fsize = (size_t) bibuf.st_size;
    list->af_list[0].af_dsize = 0;
    list->af_list[0].af_data = NULL;
    list->af_list[0].af_hashname = NULL;
    list->af_list[0].af_nrefs = 0;
    list->af_list[0].af_succgen = AF_NOVNUM;
    list->af_list[0].af_succrev = AF_NOVNUM;
    list->af_list[0].af_predgen = AF_NOVNUM;
    list->af_list[0].af_predrev = AF_NOVNUM;
    return (AF_OK);
  }
  
  /* record date of last modification */
  list->af_lastmod = aibuf.st_mtime;

  /* archive file ? */
  idstr[AF_SEGSTRLEN] = '\0';
  fgets (idstr, AF_SEGSTRLEN+1, archfile);
  if (strncmp (idstr, AF_ARHEADER, AF_SEGSTRLEN)) {
    afCloseUnlock (archfile, AF_READ, list);
    FATAL ("ReadAttrs", "wrong header in archive file", AF_EINCONSIST, ERROR);
  }

  /* read header */
  fgets (line, AF_LINESIZ, archfile);
  itemptr = afFirstItem (line);
  arVersion = atoi (itemptr);
  if (arVersion != AF_ARCURVERS) {
    afCloseUnlock (archfile, AF_READ, list);
    FATAL ("ReadAttrs", "unknown archive format version", AF_EINCONSIST, ERROR);
  }
  itemptr = afNextItem (itemptr);
  list->af_nrevs = atoi (itemptr);
  itemptr = afNextItem (itemptr);
  list->af_datasize = atoi (itemptr);

  /* alloc memory for revision list (plus space for new revs) */
  list->af_listlen = list->af_nrevs + AF_NEWREVS;
  if ((list->af_list = (Af_vattrs *)af_malloc (list, (unsigned) (list->af_listlen * sizeof(Af_vattrs)))) == NULL) {
    afCloseUnlock (archfile, AF_READ, list);
    return (ERROR);
  }

  memset ((char *) list->af_list, 0, list->af_listlen * sizeof (Af_vattrs));

  /* enter name and type */
  list->af_list[0].af_name = list->af_cattrs.af_globname;
  list->af_list[0].af_type = list->af_cattrs.af_globtype;

  if (af_rdattrs (archfile, list, &bibuf) != AF_OK) {
    list->af_nrevs = 0;
    afDetachList (list);
    afCloseUnlock (archfile, AF_READ, list);
    return (ERROR);
  }

  if (!af_suppressUdas) {
    /* read id string for user defined attributes section*/
    fgets (idstr, AF_SEGSTRLEN+1, archfile);
    if (strncmp (idstr, AF_UDASEG, AF_SEGSTRLEN)) {
      afCloseUnlock (archfile, AF_READ, list);
      FATAL ("ReadAttrs", "wrong udaseg-ID in archive file", AF_EINCONSIST,ERROR);
    }
    if (af_rdudas (archfile, list) != AF_OK) {
      list->af_nrevs = 0;
      afDetachList (list);
      afCloseUnlock (archfile, AF_READ, list);
      return (ERROR);
    }
  }

  list->af_extent |= AF_ATTRS;
  afCloseUnlock (archfile, AF_READ, list);
  return (AF_OK);
} /* afReadAttrs */

/*==========================================================================
 *	WriteArchive -- write archive file
 *==========================================================================*/

EXPORT int afWriteArchive (list)
     Af_revlist *list;
{
  register int  i, j, maxindex;
  register FILE	*attrTmpFile, *dataTmpFile, *attrArchFile;
  int           err;
  bool          writeok;
  size_t        siz;
  char          *archiveName, *attrArchSym, *dataArchSym = NULL;
  char          *attrTmpName, *dataTmpName = NULL, *ptrlist[AF_MAXUDAS+1], buf[BUFSIZ];
  size_t	datasize;
  Af_key        tmpkey, *busyptr;
  Af_user       *owner;
  struct stat   ibuf;

  tmpkey.af_ldes = list;

  archiveName = afArchiveName (list->af_arpath, AF_ATTRDIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_WRITE);
  attrArchSym = af_entersym (archiveName);
  archiveName = afArchiveName (list->af_arpath, AF_DATADIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_WRITE);
  dataArchSym = af_entersym (archiveName);

  /* if AtFS directory was newly created by afArchiveName */
  if ((owner = afArchiveOwner (list->af_arpath, &writeok, &list->af_owngid))) {
    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 (writeok)
      list->af_extent |= AF_UXWRITE;
  }

  /* if all revisions have been removed */
  if (list->af_nrevs == 0) {
    if ((attrArchFile = afOpenLock (attrArchSym, AF_WRITE, list)) == NULL)
      return (ERROR);
    disableSig (SIGINT);
    disableSig (SIGQUIT);
    unlink (attrArchSym);
    unlink (dataArchSym);
    enableSig();
    afCloseUnlock (attrArchFile, AF_WRITE, list);
    return (AF_OK);
  }

  attrTmpName = af_gtmpname (list->af_arpath);
  af_regtmpfile (attrTmpName);
  /* open attrTmpFile */
  if ((attrTmpFile = fopen (attrTmpName, "w")) == NULL)
    FAIL ("WriteArchive", "cannot open archive file for writing", AF_EMISC, ERROR);

  /* if there is no busy version - increase "nrevs" temporarily */
  busyptr = af_gbuskey (list);
  if (!(VATTR(busyptr).af_class & AF_VALID))
    list->af_nrevs++;

  /* write header */
  fprintf (attrTmpFile, "%s %d %d %lu\n", AF_ARHEADER, AF_ARCURVERS,
	   list->af_nrevs, list->af_datasize);

  /* write constant attributes */
  fprintf (attrTmpFile, "%s %s %s %s %s %s\n", AF_NAMEID, 
	   list->af_cattrs.af_host,
	   list->af_cattrs.af_syspath,
	   list->af_list[0].af_name,
	   NOTMT (list->af_list[0].af_type),
	   AF_NOSTRING); /* former variant string */

  /* write owner */
  fprintf (attrTmpFile, "%s %s %s %s\n", AF_OWNID,
	   list->af_cattrs.af_ownname,
	   list->af_cattrs.af_ownhost,
	   list->af_cattrs.af_owndomain);


  /* write predecessor and locker of busy version */
  fprintf (attrTmpFile, "%s %d %d\n%s %s %s %s %ld\n", AF_PRDID,
	   VATTR(busyptr).af_predgen,
	   VATTR(busyptr).af_predrev, AF_LOCKID,
	   NOTMT (VATTR(busyptr).af_lckname),
	   NOTMT (VATTR(busyptr).af_lckhost),
	   NOTMT (VATTR(busyptr).af_lckdomain),
	   VATTR(busyptr).af_ltime);

  /* write list of version attributes */
  maxindex = list->af_nrevs-1;
  for (i=1; i <= maxindex; i++) {
    /* skip deleted versions */
    if (!(list->af_list[i].af_class & AF_VALID)) {
      if (++maxindex == list->af_listlen) {
	fclose (attrTmpFile);
	af_unregtmpfile (attrTmpName);
	unlink (attrTmpName);
	FATAL ("WriteArchive", "revision count", AF_EINCONSIST, ERROR);
      }
      continue;
    }
    
    /* write revision ID */
    fprintf (attrTmpFile, "%s %d %d %d %o %s\n", AF_REVID, 
	     list->af_list[i].af_gen,
	     list->af_list[i].af_rev,
	     list->af_list[i].af_state,
	     list->af_list[i].af_mode,
	     AF_NOSTRING); /* former variant string */
    
    /* write author */
    fprintf (attrTmpFile, "\t%s %s %s %s %s %s %s\n", AF_AUTHORID,
	     list->af_list[i].af_auname,
	     list->af_list[i].af_auhost,
	     list->af_list[i].af_audomain,
	     NOTMT (list->af_list[i].af_lckname),
	     NOTMT (list->af_list[i].af_lckhost),
	     NOTMT (list->af_list[i].af_lckdomain));
    
    /* write dates */
    fprintf (attrTmpFile, "\t%s %ld %ld %ld %ld %ld\n", AF_DATEID,
	     list->af_list[i].af_mtime,
	     list->af_list[i].af_atime,
	     list->af_list[i].af_ctime,
	     list->af_list[i].af_stime,
	     list->af_list[i].af_ltime);
    
    /* write kind of representation and tree connects */
    fprintf (attrTmpFile, "\t%s %d %lu %lu %d %d %d %d\n", AF_REPRID,
	     list->af_list[i].af_repr,
	     list->af_list[i].af_fsize,
	     list->af_list[i].af_dsize,
	     list->af_list[i].af_succgen,
	     list->af_list[i].af_succrev,
	     list->af_list[i].af_predgen,
	     list->af_list[i].af_predrev);
  }
  
  /* write user defined attributes */
  fprintf (attrTmpFile, "%s\n", AF_UDASEG);
  maxindex = list->af_nrevs-1;
  for (i=0; i <= maxindex; i++) {
    /* skip deleted versions but not the busy version */
    if (!(list->af_list[i].af_class & AF_VALID) && (list->af_list[i].af_state != AF_BUSY))
      { maxindex++; continue; }

    fprintf (attrTmpFile, "%s %d %d\n", AF_UDAID,
	     list->af_list[i].af_gen,
	     list->af_list[i].af_rev);
    tmpkey.af_lpos = i;
    afListUdas (&tmpkey, ptrlist);
    j=0;
    while (ptrlist[j]) {
      fprintf (attrTmpFile, "%s%c", ptrlist[j++], '\0');
    }
    if (j==0) /* if no user defined attribute has been written */
      fputc ('\0', attrTmpFile);
    fputc ('\0', attrTmpFile);
    fputc ('\n', attrTmpFile);
    fflush (attrTmpFile);
  }
  if (fclose (attrTmpFile) < 0) {
    if (!(VATTR(busyptr).af_class & AF_VALID))
      list->af_nrevs--;
    af_unregtmpfile (attrTmpName);
    unlink (attrTmpName);
    FAIL ("WriteArchive", "fclose", AF_ESYSERR, ERROR);
  }

  /* if data have been manipulated - write data file */
  if ((list->af_extent & AF_DATA) == AF_DATA) {
    dataTmpName = af_gtmpname (list->af_arpath);
    af_regtmpfile (dataTmpName);
    /* open tmpfile for data */
    if ((dataTmpFile = fopen (dataTmpName, "w")) == NULL) {
      if (!(VATTR(busyptr).af_class & AF_VALID))
	list->af_nrevs--;
      af_unregtmpfile (attrTmpName);
      unlink (attrTmpName);
      FAIL ("WriteArchive", "fopen", AF_ESYSERR, ERROR);
    }

    /* write notes and data */
    fprintf (dataTmpFile, "%s %d\n", AF_DATAHEADER, AF_ARCURVERS);
      
    maxindex = list->af_nrevs-1;
    for (i=1; i <= maxindex; i++) {
      /* skip deleted versions */
      if (!(list->af_list[i].af_class & AF_VALID))
	{ maxindex++; continue; }
	  
      fprintf (dataTmpFile, "%s %d %d %lu\n", AF_NOTEID,
	       list->af_list[i].af_gen,
	       list->af_list[i].af_rev,
	       list->af_list[i].af_notesize);
      if (list->af_list[i].af_notesize > 1) {
	if (fwrite (list->af_list[i].af_note, sizeof(char), (size_t) list->af_list[i].af_notesize - sizeof(char), dataTmpFile) == 0) {
	  if (!(VATTR(busyptr).af_class & AF_VALID))
	    list->af_nrevs--;
	  fclose (dataTmpFile);
	  af_unregtmpfile (dataTmpName);
	  unlink (dataTmpName);
	  af_unregtmpfile (attrTmpName);
	  unlink (attrTmpName);
	  FAIL ("WriteArchive", "fwrite", AF_ESYSERR, ERROR);
	}
      }
      fputc ('\n', dataTmpFile);

      if (list->af_list[i].af_repr == AF_CHUNK)
	datasize = list->af_list[i].af_fsize;
      else datasize = list->af_list[i].af_dsize;
      fprintf (dataTmpFile, "%s %d %d %d %lu\n", AF_DATAID,
	       list->af_list[i].af_gen,
	       list->af_list[i].af_rev,
	       list->af_list[i].af_repr, datasize);
      
      if (datasize > 0) {
	if (fwrite (list->af_list[i].af_data, sizeof(char), (size_t) datasize, dataTmpFile) == 0) {
	  if (!(VATTR(busyptr).af_class & AF_VALID))
	    list->af_nrevs--;
	  fclose (dataTmpFile);
	  af_unregtmpfile (dataTmpName);
	  unlink (dataTmpName);
	  af_unregtmpfile (attrTmpName);
	  unlink (attrTmpName);
	  FAIL ("WriteArchive", "fwrite", AF_ESYSERR, ERROR);
	}
      }
    }
    if (fclose (dataTmpFile) < 0) {
      if (!(VATTR(busyptr).af_class & AF_VALID))
	list->af_nrevs--;
      af_unregtmpfile (dataTmpName);
      unlink (dataTmpName);
      FAIL ("WriteArchive", "fclose", AF_ESYSERR, ERROR);
    }
  }

  /* decrease "nrevs" again (see beginning of procedure */
  if (!(VATTR(busyptr).af_class & AF_VALID))
    list->af_nrevs--;

  /* check if temporary archive files can replace old archiche files */
  /* -> if archive file was modified since last read */
  if (stat (attrArchSym, &ibuf) == ERROR) {
    /* Ooops, lost it ? */
    if (list->af_lastmod != (time_t) 0) {
      if ((list->af_extent & AF_DATA) == AF_DATA) {
	af_unregtmpfile (dataTmpName);
	unlink (dataTmpName);
      }
      af_unregtmpfile (attrTmpName);
      unlink (attrTmpName);
      FATAL ("WriteArchive", "archive file lost", AF_EINTERNAL, ERROR);
    }
  }
  else {
    /* if archive has changed since last read */
    if (list->af_lastmod != ibuf.st_mtime) {
      if ((list->af_extent & AF_DATA) == AF_DATA) {
	af_unregtmpfile (dataTmpName);
	unlink (dataTmpName);
      }
      af_unregtmpfile (attrTmpName);
      unlink (attrTmpName);
      FAIL ("WriteArchive", "", AF_EARCHANGED, ERROR);
    }
  }

  if ((attrTmpFile = fopen (attrTmpName, "r")) == NULL) {
    if ((list->af_extent & AF_DATA) == AF_DATA) {
      af_unregtmpfile (dataTmpName);
      unlink (dataTmpName);
    }
    af_unregtmpfile (attrTmpName);
    unlink (attrTmpName);
    FATAL ("WriteArchive", "temporary archive file lost", AF_EINTERNAL, ERROR);
  }

  disableSig (SIGINT);
  disableSig (SIGQUIT);

  /* set lock on attr archive file and open (truncate) for writing */
  if ((attrArchFile = afOpenLock (attrArchSym, AF_WRITE, list)) == NULL) {
    if ((list->af_extent & AF_DATA) == AF_DATA) {
      af_unregtmpfile (dataTmpName);
      unlink (dataTmpName);
    }
    af_unregtmpfile (attrTmpName);
    unlink (attrTmpName);
    return (ERROR);
  }

  /* attributes archive file is empty now ! */

  /* copy contents of temporary file to archive file */
  err = FALSE;
  while ((siz = fread (buf, sizeof(char), (size_t) BUFSIZ, attrTmpFile)) == BUFSIZ) {
    if (fwrite (buf, sizeof(char), (size_t) BUFSIZ, attrArchFile) != BUFSIZ)
      err = TRUE;
  }
  if ((fwrite (buf, sizeof(char), (size_t) siz, attrArchFile) != siz) || err) {
    char errMsg[PATH_MAX+256], *arBackupName;
    fclose (attrTmpFile);
    if ((list->af_extent & AF_DATA) == AF_DATA) {
      af_unregtmpfile (dataTmpName);
      unlink (dataTmpName);
    }
    /* preserve temporary file, as attributes file is empty yet */
    arBackupName = af_entersym (afArchiveName (list->af_arpath, AF_ATTRBACKUPDIR, list->af_cattrs.af_globname, list->af_cattrs.af_globtype, AF_WRITE));
    sprintf (errMsg, "Cannot write attributes archive file (fwrite failed) --  preserving temporary file as %s", arBackupName);
    unlink (arBackupName);
    if (link (attrTmpName, arBackupName) == ERROR)
      sprintf (errMsg, "Cannot write attributes archive file (fwrite failed) --  preserving temporary file as %s", attrTmpName);
    else
      unlink (attrTmpName);
    af_unregtmpfile (attrTmpName);
    enableSig();
    FATAL ("WriteArchive", errMsg, AF_EMISC, ERROR);
  }
  fclose (attrTmpFile);
  af_unregtmpfile (attrTmpName);
  unlink (attrTmpName);

  /* if the data file has been written */
  if ((list->af_extent & AF_DATA) == AF_DATA) {
    mode_t fileMode;
    af_unregtmpfile (dataTmpName);
    unlink (dataArchSym);
    if (link (dataTmpName, dataArchSym) == ERROR) {
      afCloseUnlock (attrArchFile, AF_WRITE, list);
      unlink (dataTmpName);
      enableSig();
      FAIL ("WriteArchive", "link", AF_ESYSERR, ERROR);
    }
    chown (dataArchSym, geteuid(), list->af_owngid);
    if ((fileMode = afArchiveMode (list->af_arpath))) {
      fileMode &= ~(S_IXUSR|S_IXGRP|S_IXOTH|S_ISUID|S_ISGID);
      chmod (dataArchSym, fileMode);
    }
    unlink (dataTmpName);
  }
  enableSig();

  afCloseUnlock (attrArchFile, AF_WRITE, list);

  /* update list descriptor */
  if (stat (attrArchSym, &ibuf) != ERROR)
    list->af_lastmod = ibuf.st_mtime;

  return (AF_OK);
} /* afWriteArchive */

