/* 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 
 *
 * atfsrepair.c -- try to repair corrupted archive
 *
 * Author: Andreas Lampen (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: atfsrepair.c[7.1] Thu Aug  4 16:03:02 1994 andy@cs.tu-berlin.de frozen $
 */

#include <setjmp.h>
#include "atfs.h"
#include "afarchive.h"
#include "atfsrepair.h"

extern int  errno;
extern char *sys_errlist[];

/*==========================================================================
 * globals
 *==========================================================================*/

int repairCache = FALSE; /* -C option switches this on */

jmp_buf env;

char *arTmpFilename, *datTmpFilename;

int        outputVersion = AF_ARCURVERS;
Input      attrIn, dataIn;
ConstAttrs cAttrs;
Revision   revList[MAXREVS];
Udas       udaList[MAXREVS];
Note       noteList[MAXREVS];
Data       dataList[MAXREVS];
int        curRev, curUda, curNote, curData;

extern char *optarg;
extern int optind;

LOCAL void errorExit ()
{
  unlink (arTmpFilename);
  unlink (datTmpFilename);
  exit (1);
}

LOCAL void cleanup ()
{
  unlink (arTmpFilename);
  unlink (datTmpFilename);
  af_cleanup();
  fprintf (stderr, "->   Abort:\tprocessing of this file...\n");
  longjmp (env, 1);
}

/*==========================================================================
 * afReadName -- for backward compatibility
 *==========================================================================*/

LOCAL char *xfirstitem (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]);
}

LOCAL char *xnextitem (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);
}

LOCAL char *afReadName (archname)
     char *archname;
{
  register FILE *archfile;
  register char *itemptr;
  char          newarname[PATH_MAX+1], line[AF_LINESIZ];
  static char   name[NAME_MAX+1];

  archname[strlen (archname)-1] = AF_OLD_ARCHEXT;
  if ((archfile = fopen (archname, "r")) == NULL) {
    strcpy (newarname, afArchivePath (af_uniqpath (".")));
    strcat (newarname, "/");
    strcat (newarname, archname);
    if ((archfile = fopen (newarname, "r")) == NULL) {
      strcpy (name, archname + strlen (AF_OLD_ATFSFILEID));
      name[strlen(name)-1] = '\0';
      return (name);
    }
  }

  fgets (line, AF_LINESIZ, archfile);
  fgets (line, AF_LINESIZ, archfile);

  itemptr = xfirstitem (line); /* ID string */
  itemptr = xnextitem (itemptr); /* host */
  itemptr = xnextitem (itemptr); /* path */
  itemptr = xnextitem (itemptr); /* name */
  strcpy (name, itemptr);
  itemptr = xnextitem (itemptr); /* type */
  if (strcmp (itemptr, AF_NOSTRING)) {
    strcat (name, ".");
    strcat (name, itemptr);
  }

  fclose (archfile);
  return (name);
}

/*===========================================================================
 * askConfirm -- ask for confirmation
 *==========================================================================*/

LOCAL int askConfirm (expAnswer)
     char *expAnswer;
{
  char strBuf[256], answer[10], *cp;

  fflush (stdin);
  printf ("[%s] ", expAnswer);
  strBuf[0] = '\0';
  answer[0] = '\0';
  gets (strBuf);
  sscanf (strBuf, "%s", answer);
  if (answer[0] == '\0') return TRUE; /* assumption acknowledged */
  cp = answer;
  while (*cp ? (*cp++ |= ' ') : 0); /* make sure answer is lowercase */
  return !strncmp (expAnswer, answer, strlen (expAnswer));
}

/*===========================================================================
 * lookup, netItem, itemCmp
 * Functions for random positioning in input stream
 *==========================================================================*/

LOCAL int itemCmp (baseStr, searchStr, len)
     char *baseStr, *searchStr;
     int  len;
{
  register int i=0;
  while (baseStr[i] == searchStr[i]) {
    i++;
    if (i == len) {
      if ((baseStr[i] == ' ') || (baseStr[i] == '\t') || (baseStr[i] == '\0') || (baseStr[i] == '\n'))
	return (0);
      else
	return (1);
    }
  }
  return (1);
}

LOCAL int lookup (input, searchStr, pos)
     Input *input;
     char  *searchStr;
     int   pos;
{
  if (pos == 0)
    input->curPos = 0;
  if (input->curPos == input->length)
    return (-1);

  while (itemCmp (&(input->string[input->curPos]), searchStr, strlen (searchStr))) {
    input->curPos++;
    while (input->string[input->curPos] != searchStr[0]) {
      if (input->curPos == input->length)
	return (-1);
      input->curPos++;
    }
  }
  return (input->curPos);
}

LOCAL void nextItem (input)
     Input *input;
{
  if (input->curPos == input->length)
    return;
  /* search next white space */
  while ((input->string[input->curPos] != ' ') && 
	 (input->string[input->curPos] != '\t') && 
	 (input->string[input->curPos] != '\n'))
    if (++input->curPos == input->length)
      return;

  /* skip white spaces */
  while ((input->string[input->curPos] == ' ') || 
	 (input->string[input->curPos] == '\t') || 
	 (input->string[input->curPos] == '\n'))
    if (++input->curPos == input->length)
      return;
}

LOCAL void nextLine(input)
     Input *input;
{
  if (input->curPos == input->length)
    return;
  /* search beginning of next line */
  while (input->string[input->curPos] != '\n')
    if (++input->curPos == input->length)
      return;

  /* skip newline */
  if (++input->curPos == input->length)
    return;
}

LOCAL char *itemCopy (str1, str2)
     char *str1, *str2;
{
  if (!strncmp (str2, AF_NOSTRING, 2)) {
    str1[0] = '\0';
    return (str1);
  }
  
  while ((*str1 = *str2)) {
    if ((*str2 ==' ') || (*str2 =='\t') || (*str2 =='\n'))
      { *str1 = '\0'; return (str1); }
    str1++;
    str2++;
  }
  return (str1);
}

/*=========================================================================
 * verifyString, verifyInt, verifyDate, complainString
 *=========================================================================*/

int verbosityLevel = NORMAL;

LOCAL char *verifyString (name, input, level)
     char  *name;
     Input input;
     int   level;
{
  static char value[256];

  itemCopy (value, &input.string[input.curPos]);

  if (level <= verbosityLevel) {
    fprintf (stdout, "->   Verify:\tassume %s to be: ->%s<-, ok ? (y/n) ", name, value);
    if (askConfirm ("y"))
      goto outhere;
    fprintf (stdout, "->   Enter:\tnew %s (*0* for empty string): ", name);
    fscanf (stdin, "%s", value);
    if (!strncmp (value, "*0*", 3))
      return (NULL);
  }
 outhere:
  if (value[0])
    return (value);
  else
    return (NULL);
}
  
LOCAL char *complainString (name, string1, string2, level)
     char  *name, *string1, *string2;
     int   level;
{
  if (level <= verbosityLevel) {
    fprintf (stdout, "->   Inconsist:\tvalue for %s is ->%s<-\n", name, string1);
    fprintf (stdout, "\t\tshould be ->%s<- -- fix ? (y/n)", string2);
    if (!askConfirm ("y"))
      return (string1);
  }
  return (string2);
}
  
LOCAL int verifyInt (name, input, level)
     char  *name;
     Input input;
     int   level;
{
  int value;

  value = atoi (&input.string[input.curPos]);
  
  if (level <= verbosityLevel) {
    fprintf (stdout, "->   Verify:\tassume %s to be: ->%d<-, ok ? (y/n) ", name, value);
    if (askConfirm ("y"))
      return (value);
    fprintf (stdout, "->   Enter:\tnew %s: ", name);
    fscanf (stdin, "%d", &value);
  }
  return (value);
}
  
LOCAL int verifyOct (name, input, level)
     char  *name;
     Input input;
     int   level;
{
  unsigned int value;

  sscanf (&input.string[input.curPos], "%o", &value);

  if (level <= verbosityLevel) {
    fprintf (stdout, "->   Verify:\tassume %s to be: ->%o<-, ok ? (y/n) ", name, value);
    if (askConfirm ("y"))
      return (value);
    fprintf (stdout, "->   Enter:\tnew %s: ", name);
    fscanf (stdin, "%o", &value);
  }
  return (value);
}
  
LOCAL int verifyDate (name, input, level)
     char  *name;
     Input input;
     int   level;
{
  int value;

  value = atoi (&input.string[input.curPos]);

  if (level <= verbosityLevel) {
    fprintf (stdout, "->   Verify:\tassume %s to be: ->%d<-, ok ? (y/n) ", name, value);
    if (askConfirm ("y"))
      return (value);
    fprintf (stdout, "->   Enter:\tnew date: ");
    fscanf (stdin, "%d", &value);
  }
  return (value);
}

LOCAL void backup (arPath, arType, arBackupDir, oldName)
     char *arPath;
     char *arType;
     char *arBackupDir;
     char *oldName;
{
  int doBackup = TRUE;
  char *arBackupName;

  arBackupName = af_entersym (afArchiveName (arPath, arBackupDir, cAttrs.name, cAttrs.type, AF_WRITE));

  if (SILENT < verbosityLevel)
    fprintf (stdout, "-> Moving old %s archive file to %s...\n", arType, arBackupName);

  /* check if there is already a backup */
  if ((af_retfsize (oldName) < af_retfsize (arBackupName))) {
    fprintf (stderr,"->   Warning:\tThere already exists a backup file which is\n");
    fprintf (stderr,"->\t\tlarger than the old %s file", arType);
    if (NORMAL <= verbosityLevel) {
      fprintf (stdout, " -- overwrite (y/n) ? ");
      if (askConfirm ("n"))
	doBackup = FALSE;
    }
    else
      doBackup = FALSE;
  }

  if (doBackup) {
    unlink (arBackupName);
    if (link (oldName, arBackupName) == ERROR)
      fprintf (stderr,"->   Warning:\tcannot create backup of %s archive file\n", arType);
  }
}

/*=========
 * repair
 *=========*/

LOCAL int repair (path, filename)
     char *path, *filename;
{
  char *sPtr, commandLine[2*PATH_MAX+32], *updateStr = "";
  char *arPath, *arFilename, *datFilename;
  register int i, j;
  Af_user *owner;
  bool error, writeOk, confirm = FALSE, busyEx = FALSE, needUpdate = FALSE;
  FILE *inFile, *tmpFile;
  struct stat ibuf;
  int    size, arVersion=0;
  size_t newDeltaSize;
  gid_t  arGid;
  mode_t attrArMode;

  cAttrs.host = af_gethostname ();
  arPath = afArchivePath (af_uniqpath(path));
  cAttrs.name = af_afname (filename);
  cAttrs.type = af_aftype (filename);
  
  if (repairCache) {
    char *busyname = af_gbusname (NULL, cAttrs.name, cAttrs.type);
    if (NORMAL <= verbosityLevel) {
      fprintf (stdout, "Cannot repair derived object caches yet -- remove all\n");
      fprintf (stdout, "cached files for %s from the derived object cache instead ? (y/n) ", busyname);
      if (askConfirm ("y")) {
	if (!path || !*path || !strcmp (path, "."))
	  fprintf (stdout, "Cleaning out %s from derived object cache in current directory ...\n", busyname);
	else
	  fprintf (stdout, "Cleaning out %s from derived object cache in directory %s ...\n", busyname, path);
	sprintf (commandLine, "rm -f %s/%s%s* %s/%s%s*", arPath, AF_CACHEFILEID, busyname,
		 arPath, AF_OLD_BPFILEID, busyname);
	system (commandLine);
	fprintf (stdout, "->    Done.\n");
      }
    }
    else {
      sprintf (commandLine, "rm -f %s/%s%s* %s/%s%s*", arPath, AF_CACHEFILEID, busyname,
	       arPath, AF_OLD_BPFILEID, busyname);
      system (commandLine);
    }
    return (AF_OK);
  }

  if (SILENT < verbosityLevel) {
    if (!path || !*path || !strcmp (path, "."))
      fprintf (stdout, "Checking %s ...\n", af_gbusname (NULL, cAttrs.name, cAttrs.type));
    else
      fprintf (stdout, "Checking %s/%s ...\n", path, af_gbusname (NULL, cAttrs.name, cAttrs.type));
  }

  arFilename = af_entersym (afArchiveName (arPath, AF_ATTRDIR, cAttrs.name, cAttrs.type, AF_WRITE));
	   
  if ((owner = afArchiveOwner (arPath, &writeOk, &arGid)) == NULL) {
    if (NORMAL <= verbosityLevel)
      fprintf (stdout, "->   Warning:\tcannot determine owner of AtFS subdirectory !\n");
    owner = af_afuser ((uid_t) geteuid());
  }
  cAttrs.ownerName = af_entersym (owner->af_username);
  cAttrs.ownerHost = af_enterhost (owner->af_userhost);
  cAttrs.ownerDomain = af_enterdomain (owner->af_userdomain);

  /* read the two archive files (if present) */
  if ((inFile = fopen (arFilename, "r"))) {
#if defined(ATFS_OWN_LOCKING)
    char *lckFilename;
    struct stat ibuf;
#else
    struct flock lockInfo;
#endif
    if ((attrIn.length = af_retfsize (arFilename)) == 0) {
      fprintf (stderr, "->   Error:\tAttributes archive file is empty!\n");
      /* ToDo: try to reconstruct attr archive file */
      fclose (inFile);
      cleanup ();
    }

#if defined(ATFS_OWN_LOCKING)
    /* look for Lockfile */
    lckFilename = af_entersym (afArchiveName (arPath, AF_LOCKDIR, cAttrs.name, cAttrs.type, AF_WRITE));
    if (stat (lckFilename, &ibuf) != ERROR) { /* lock file present */
      if (NORMAL <= verbosityLevel) {
	fprintf (stdout, "-> Warning:\tArchive file is locked!\n");
	fprintf (stdout, "\t\tThis might be a spurious lockfile, or another application is\n");
	fprintf (stdout, "\t\tcurrently working on the archive file. Delete the lock ?");
	if (!askConfirm ("y")) {
	  fclose (inFile);
	  cleanup ();
	}
      }
      unlink (lckFilename);
    }
#else
    /* check for a read or write lock */
    lockInfo.l_type = F_WRLCK;
    lockInfo.l_whence = SEEK_SET;
    lockInfo.l_start = (off_t) 0;
    lockInfo.l_len = (off_t) 0;
    lockInfo.l_pid = (pid_t)0;
    if (fcntl (fileno(inFile), F_GETLK, &lockInfo) == -1) {
      fprintf (stderr, "->   Error:\tCannot get lock info for %s -- fcntl failed (%s)!\n",
	       arFilename, sys_errlist[errno]);
      fclose (inFile);
      cleanup ();
    }
    if (lockInfo.l_type != F_UNLCK) {
      char *lockMsg;
      if (lockInfo.l_type == F_WRLCK)
	lockMsg = "writing";
      else
	lockMsg = "reading";
      fprintf (stderr,"->   Warning:\tArchive file %s is locked for %s.\n", arFilename, lockMsg);
      if (NORMAL <= verbosityLevel) {
	fprintf (stdout, "->\t\t-- Unlock (y/n) ? ");
	if (!askConfirm ("y")) {
	  fclose (inFile);
	  cleanup ();
	}
      }
    }
#endif

#if !defined(ATFS_OWN_LOCKING)
    /* try to unlock attributes archive file */
    lockInfo.l_type = F_UNLCK;
    lockInfo.l_whence = SEEK_SET;
    lockInfo.l_start = (off_t) 0;
    lockInfo.l_len = (off_t) 0;
    lockInfo.l_pid = (pid_t)0;
    if (fcntl (fileno(inFile), F_SETLK, &lockInfo) == -1) {
      fprintf (stderr, "->   Error:\tCannot unlock %s -- fcntl failed (%s)!\n",
	       arFilename, sys_errlist[errno]);
      fclose (inFile);
      cleanup ();
    }
#endif

    if ((attrIn.string = malloc ((unsigned) attrIn.length+2)) == NULL) {
      fprintf (stderr, "->   Syst. Error:\tnot enough memory (malloc)\n");
      errorExit ();
    }
    if (fread (attrIn.string, sizeof (char), (size_t) attrIn.length, inFile) != attrIn.length) {
      fclose (inFile);
      fprintf (stderr, "->   Error:\tCannot read attributes archive file %s!\n", arFilename);
      fclose (inFile);
      cleanup ();
    }
      
    fclose (inFile);
    attrIn.string[attrIn.length] = '\0';
    attrIn.string[attrIn.length+1] = '\0';
    attrIn.curPos = 0;
  }
  else {
    if (access (arFilename, F_OK) == 0) {
      fprintf (stderr, "->   Error:\tCannot open attributes archive file %s!\n", arFilename);
      cleanup ();
    }
    attrIn.string = NULL;
    attrIn.length = 0;
    attrIn.curPos = 0;
  }
	   
  datFilename = af_entersym (afArchiveName (arPath, AF_DATADIR, cAttrs.name, cAttrs.type,AF_WRITE));
  if ((inFile = fopen (datFilename, "r"))) {
    if ((dataIn.length = af_retfsize (datFilename)) == 0) {
      fprintf (stderr, "->   Error:\tData archive file is empty!\n");
      cleanup ();
    }
    if ((dataIn.string = malloc ((unsigned) dataIn.length+1)) == NULL) {
      fprintf (stderr, "->   Syst. Error:\tnot enough memory (malloc)\n\n");
      errorExit ();
    }
    if (fread (dataIn.string, sizeof (char), (size_t) dataIn.length, inFile) != dataIn.length) {
      fclose (inFile);
      fprintf (stderr, "->   Error:\tCannot read data archive file %s!\n", datFilename);
      cleanup ();
    }
    fclose (inFile);
    dataIn.string[dataIn.length] = '\0';
  }
  else {
    if (access (datFilename, F_OK) == 0) {
      fprintf (stderr, "->   Error:\tCannot open data archive file %s!\n", datFilename);
      cleanup ();
    }
    dataIn.string = NULL;
    dataIn.length = 0;
    dataIn.curPos = 0;
  }

  if ((attrIn.string == NULL) && (dataIn.string == NULL)) {
    if (SILENT < verbosityLevel)
      fprintf (stdout, "->   Warning:\tNo archive files for %s\n",
	       af_gbusname (NULL, cAttrs.name, cAttrs.type));
    return (1);
  }

  if ((attrIn.string == NULL) && dataIn.string) {
    fprintf (stderr, "->   Error:\tAttributes archive file for %s is missing!\n",
	     af_gbusname (NULL, cAttrs.name, cAttrs.type));
    cleanup ();
  }

  if (attrIn.string && (dataIn.string == NULL)) {
    fprintf (stderr, "->   Error:\tData archive file for %s is missing!\n",
	     af_gbusname (NULL, cAttrs.name, cAttrs.type));
    cleanup ();
  }

  /* test existence of busy file */
  if (access (af_gbusname (NULL, cAttrs.name, cAttrs.type), F_OK) == 0)
    busyEx = TRUE;

  /*=================================================
   * Phase 1 (check version independent attributes)
   *================================================*/

  /* try to determine archive version */
  if (!strncmp (attrIn.string, AF_ARHEADER, AF_SEGSTRLEN))
    arVersion = atoi (&(attrIn.string[AF_SEGSTRLEN+1]));
  
  lookup (&attrIn, AF_NAMEID, 0);

  nextItem (&attrIn); /* host */
  sPtr = verifyString ("hostname", attrIn, EDIT);
  cAttrs.host = af_entersym (sPtr);
	
  nextItem (&attrIn); /* syspath */
  sPtr = verifyString ("syspath", attrIn, EDIT);
  cAttrs.syspath = af_entersym (sPtr);
	
  nextItem (&attrIn); /* name */
  sPtr = verifyString ("name", attrIn, EDIT);
  if (strcmp (sPtr, cAttrs.name))
    sPtr = complainString ("name", sPtr, cAttrs.name, NORMAL);
  cAttrs.name = af_entersym (sPtr);
	
  nextItem (&attrIn); /* type */
  sPtr = verifyString ("type", attrIn, EDIT);
  if (strcmp (sPtr ? sPtr : "", cAttrs.type))
    sPtr = complainString ("type", sPtr, cAttrs.type, NORMAL);
  cAttrs.type = af_entersym (sPtr);

  lookup (&attrIn, AF_OWNID, 0);

  nextItem (&attrIn); /* owner's name */
  sPtr = verifyString ("owner's name", attrIn, EDIT);
  cAttrs.ownerName = af_entersym (sPtr);

  nextItem (&attrIn); /* owner's host */
  sPtr = verifyString ("owner's host", attrIn, EDIT);
  cAttrs.ownerHost = af_entersym (sPtr);

  if (arVersion > 2) {
    nextItem (&attrIn); /* owner's domain */
    sPtr = verifyString ("owner's domain", attrIn, EDIT);
  }
  else {
    sPtr = af_getdomain();
  }
  cAttrs.ownerDomain = af_entersym (sPtr);


  /*========================================
   * Phase 2 (check version attributes)
   *========================================*/

  /* initialize busy version --- not *all* attributes get initialized !!! */
  revList[0].generation = AF_BUSYVERS;
  revList[0].revision = AF_BUSYVERS;
  lookup (&attrIn, AF_NAMEID, 0);
  nextItem (&attrIn); nextItem (&attrIn);
  nextItem (&attrIn); nextItem (&attrIn);
  nextItem (&attrIn); /* variant (ignore) */

  lookup (&attrIn, AF_LOCKID, 0);
  nextItem (&attrIn);
  sPtr = verifyString ("locker's name", attrIn, EDIT);
  revList[0].lockerName = af_entersym (sPtr);
  nextItem (&attrIn);
  sPtr = verifyString ("locker's host", attrIn, EDIT);
  revList[0].lockerHost = af_entersym (sPtr);
  if (arVersion > 2) {
    nextItem (&attrIn);
    sPtr = verifyString ("locker's domain", attrIn, EDIT);
  }
  else {
    sPtr = af_getdomain ();
  }
  revList[0].lockerDomain = af_entersym (sPtr);

  if ((revList[0].lockerName == NULL) && revList[0].lockerHost) {
    if (SILENT < verbosityLevel)
      fprintf (stdout, "->   Warning:\tlockerID inconsistent -- lock cancelled\n");
    revList[0].lockerName = NULL;
    revList[0].lockerHost = NULL;
    revList[0].lockerDomain = NULL;
  }
  if (revList[0].lockerName == NULL)
    revList[0].lockerDomain = NULL;

  if (revList[0].lockerName && (revList[0].lockerHost == NULL)) {
    if (SILENT < verbosityLevel) 
      fprintf (stdout,"->   Warning:\tlocker's host missing - inserting author's host\n");
    revList[0].lockerHost = revList[0].authorHost;
  }
  if (revList[0].lockerName && (revList[0].lockerDomain == NULL)) {
    if (SILENT < verbosityLevel)
      fprintf (stdout, "->   Warning:\tlocker's domain missing - inserting author's domain\n");
    revList[0].lockerHost = revList[0].authorDomain;
  }

  nextItem (&attrIn);
  revList[0].lockTime = verifyDate ("locking date", attrIn, EDIT);
  if (revList[0].lockerName && (revList[0].lockTime == AF_NOTIME)) {
    if (SILENT < verbosityLevel)
      fprintf (stdout, "->   Warning:\tlocking date inconsistent -- setting actual date\n");
    revList[0].lockTime = af_acttime();
  }

  lookup (&attrIn, AF_PRDID, 0);
  nextItem (&attrIn);
  revList[0].predGen = verifyInt ("pred(gen) of busy version", attrIn, EDIT);
  nextItem (&attrIn);
  revList[0].predRev = verifyInt ("pred(rev) of busy version", attrIn, EDIT);

  attrIn.curPos = 0;
  curRev = 0;
  while (lookup (&attrIn, AF_REVID, 1) != -1) {
    curRev++;
    nextItem (&attrIn);
    revList[curRev].generation = verifyInt ("gen", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].revision = verifyInt ("rev", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].state = verifyInt ("state", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].mode = (mode_t) verifyOct ("mode", attrIn, EDIT);
    nextItem (&attrIn); /* variant (ignore) */
      
    lookup (&attrIn, AF_AUTHORID, 1);
    nextItem (&attrIn); /* author's name */
    sPtr = verifyString ("author's name", attrIn, EDIT);
    if (sPtr == NULL) {
      if (SILENT < verbosityLevel)
	fprintf (stdout,"->   Warning:\tauthor's name missing -- inserting owner's name\n");
      revList[curRev].authorName = cAttrs.ownerName;
    }
    else
      revList[curRev].authorName = af_entersym (sPtr);
    
    nextItem (&attrIn); /* author's host */
    sPtr = verifyString ("author's host", attrIn, EDIT);
    if (sPtr == NULL) {
      if (SILENT < verbosityLevel)
	fprintf (stdout,"->   Warning:\tauthor's host missing -- inserting owner's host\n");
      revList[curRev].authorHost = cAttrs.ownerHost;
    }
    else
      revList[curRev].authorHost = af_entersym (sPtr);
    
    if (arVersion > 2) {
      nextItem (&attrIn); /* author's domain */
      sPtr = verifyString ("author's domain", attrIn, EDIT);
	if (sPtr == NULL) {
	  if (SILENT < verbosityLevel)
	    fprintf (stdout,"->   Warning: author's domain missing -- inserting owner's domain\n");
	  revList[curRev].authorDomain = cAttrs.ownerDomain;
	}
	else
	  revList[curRev].authorDomain = af_entersym (sPtr);
      }
      else {
	sPtr = af_getdomain ();
	revList[curRev].authorDomain = af_entersym (sPtr);
      }

      nextItem (&attrIn); /* locker's name */
      sPtr = verifyString ("locker's name", attrIn, EDIT);
      revList[curRev].lockerName = af_entersym (sPtr);

      nextItem (&attrIn); /* locker's host */
      sPtr = verifyString ("locker's host", attrIn, EDIT);
      if ((sPtr == NULL) && revList[curRev].lockerName) {
	if (SILENT < verbosityLevel)
	  fprintf (stdout,"->   Warning:\tlocker's host missing - inserting author's host\n");
	revList[curRev].lockerHost = revList[curRev].authorHost;
      }
      else
	revList[curRev].lockerHost = af_entersym (sPtr);

      if (arVersion > 2) {
	nextItem (&attrIn); /* locker's domain */
	sPtr = verifyString ("locker's domain", attrIn, EDIT);
	if ((sPtr == NULL) && revList[curRev].lockerName) {
	  if (SILENT < verbosityLevel)
	    fprintf (stdout,"->   Warning:\tlocker's domain missing - inserting author's domain\n");
	  revList[curRev].lockerDomain = revList[curRev].authorDomain;
	}
	else
	  revList[curRev].lockerDomain = af_entersym (sPtr);
      }
      else {
	sPtr = af_getdomain();
	revList[curRev].lockerDomain = af_entersym (sPtr);
      }

    lookup (&attrIn, AF_DATEID, 1);
    nextItem (&attrIn);
    revList[curRev].modTime = verifyDate ("mod. date", attrIn, EDIT);
    if (revList[curRev].modTime == AF_NOTIME) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "->   Warning:\tmodification date missing -- %s\n", "inserting actual date");
      revList[curRev].modTime = af_acttime();
    }
    nextItem (&attrIn);
    revList[curRev].accessTime = verifyDate ("acc. date", attrIn,EDIT);
    if (revList[curRev].accessTime == AF_NOTIME) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "->   Warning:\taccess date missing -- %s\n", "inserting actual date");
      revList[curRev].accessTime = af_acttime();
    }
    nextItem (&attrIn);
    revList[curRev].statChangeTime =
      verifyDate ("status change date", attrIn, EDIT);
    if (revList[curRev].statChangeTime == AF_NOTIME) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "->   Warning:\tstatus change date missing -- %s\n", "inserting actual date");
      revList[curRev].statChangeTime = af_acttime();
    }
    nextItem (&attrIn);
    revList[curRev].saveTime = verifyDate ("save date", attrIn, EDIT);
    if (revList[curRev].saveTime == AF_NOTIME) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "->   Warning:\tsave date missing -- %s\n", "inserting actual date");
      revList[curRev].saveTime = af_acttime();
    }
    nextItem (&attrIn);
    revList[curRev].lockTime = verifyDate ("lock date", attrIn, EDIT);
    if ((revList[curRev].lockTime == AF_NOTIME) && revList[curRev].lockerName) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "->   Warning:\tlocking date missing -- %s\n", "inserting actual date");
      revList[curRev].lockTime = af_acttime();
    }
    
    lookup (&attrIn, AF_REPRID, 1);
    nextItem (&attrIn);
    revList[curRev].representation = verifyInt ("repr", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].fileSize = verifyInt ("filesize", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].deltaSize = verifyInt ("deltasize", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].succGen = verifyInt ("succ. gen", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].succRev = verifyInt ("succ. rev", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].predGen = verifyInt ("pred. gen", attrIn, EDIT);
    nextItem (&attrIn);
    revList[curRev].predRev = verifyInt ("pred. rev", attrIn, EDIT);
  } /* revision loop */
  
  /*============================================
   * Phase 3 (check user defined attributes)
   *============================================*/

  attrIn.curPos = 0;
  curUda = 0;
  while (lookup (&attrIn, AF_UDAID, 1) != -1) {
    nextItem (&attrIn);
    udaList[curUda].generation = verifyInt ("gen", attrIn, EDIT);
    nextItem (&attrIn);
    udaList[curUda].revision = verifyInt ("rev", attrIn, EDIT);
    nextItem (&attrIn);
    i = 0;
    size = 0;
    while (i < MAXUDAS) {
      udaList[curUda].uda[i] = &attrIn.string[attrIn.curPos];
      udaList[curUda].size = udaList[curUda].size + 
	(strlen (udaList[curUda].uda[i]) + sizeof (char));
      while (attrIn.string[attrIn.curPos] != '\0')
	attrIn.curPos++;
      attrIn.curPos++;
      i++;
      if (!strcmp (&attrIn.string[attrIn.curPos], AF_UDAID))
	break;
      if (attrIn.string[attrIn.curPos] == '\0') {
	udaList[curUda].size++;
	break;
      }
    } /* uda loop */
    udaList[curUda].uda[i] = NULL;
    curUda++;
  } /* revision loop */
  
  /*=========================
   * Phase 4 (check notes)
   *=========================*/

  dataIn.curPos = 0;
  curNote = 0;
  while (lookup (&dataIn, AF_NOTEID, 1) != -1) {
    nextItem (&dataIn);
    noteList[curNote].generation = verifyInt ("gen", dataIn, EDIT);
    nextItem (&dataIn);
    noteList[curNote].revision = verifyInt ("rev", dataIn, EDIT);
    nextItem (&dataIn);
    nextLine (&dataIn); /* skip size */
    size = 0;
    noteList[curNote].contents = &dataIn.string[dataIn.curPos];
    while (((noteList[curNote].contents)[size] != AF_DATAID[0]) &&
	   ((noteList[curNote].contents)[size] != '\0'))
      size++;
    if (size == 0) {
      noteList[curNote].contents = NULL;
      size = 1;
    }
    else
      (noteList[curNote].contents)[size-1] = '\0';
    noteList[curNote].size = size;
    curNote++;
  } /* revision loop */
  
  /*=======================
   * Phase 5 (check data)
   *=======================*/

  /* try to determine archive version */
  if (!strncmp (dataIn.string, AF_DATAHEADER, AF_SEGSTRLEN))
    arVersion = atoi (&(dataIn.string[AF_SEGSTRLEN+1]));
  
  dataIn.curPos = 0;
  curData = 0;
  while (lookup (&dataIn, AF_DATAID, 1) != -1) {
    nextItem (&dataIn);
    dataList[curData].generation = verifyInt ("gen", dataIn, EDIT);
    nextItem (&dataIn);
    dataList[curData].revision = verifyInt ("rev", dataIn, EDIT);
    nextItem (&dataIn);
    dataList[curData].representation = verifyInt ("repr",dataIn, EDIT);
    nextItem (&dataIn);
    nextLine (&dataIn); /* skip size */
    size = 0;
    if (dataIn.curPos == dataIn.length)
      goto loopexit;
    dataList[curData].contents = &dataIn.string[dataIn.curPos];
    while (itemCmp (&(dataList[curData].contents)[size],
		    AF_NOTEID, AF_IDSTRLEN) &&
	   itemCmp (&(dataList[curData].contents)[size],
		    AF_DATAID, AF_IDSTRLEN)) {
      size++;
      while ((dataList[curData].contents)[size] != AF_NOTEID[0]) {
	size++;
	if ((dataIn.curPos + size) == dataIn.length)
	  goto loopexit;
      }
    }
  loopexit:
    if (size == 0)
      dataList[curData].contents = NULL;
    dataList[curData].size = size;
    
    /* convert delta */
    if ((size > 0) && (arVersion == 1) && (dataList[curData].representation == AF_DELTA)) {
      dataList[curData].contents = afConvertDelta (dataList[curData].contents, dataList[curData].size, &newDeltaSize);
      dataList[curData].size = newDeltaSize;
    }
    
    /* check plausibility of delta */
    if ((dataList[curData].size > 0) && (dataList[curData].representation == AF_DELTA)) {
      if ((dataList[curData].contents[0] != '\0') && (dataList[curData].contents[0] != '\01')) {
	dataList[curData].representation = AF_CHUNK;
	if (SILENT < verbosityLevel)
	  fprintf (stderr, "->   Warning:\tfound bogus delta -- fixed !\n");
      }
    }
    curData++;
  } /* revision loop */
  
  /*==============================
   * Phase 6 (check connectivity)
   *==============================*/

  /* test order of revisions -- not yet implemented */
  /* test existence on successors and predecessors -- not yet implemented */

  /* look for zero size files (due to known AtFS error) */
  for (i=1; i<curRev; i++) {
    if ((revList[i].fileSize == 0) && (revList[i].representation == AF_DELTA)) {
      j = i;
      while (revList[j++].fileSize == 0);
      revList[i].fileSize = revList[j-1].fileSize;
    }
  }

  /* calculate number of revisions and size of data */
  cAttrs.noOfRevisions = curRev;

  cAttrs.datasize = 0;
  for (i=0; i<curRev; i++)
    cAttrs.datasize = cAttrs.datasize + noteList[i].size;
  for (i=0; i<curRev; i++)
    cAttrs.datasize = cAttrs.datasize + dataList[i].size;

  /* test integrity of attr and data file */
  error = FALSE;
  for (i=0; i<=cAttrs.noOfRevisions; i++) {
    if ((udaList[i].generation != revList[i].generation) ||
	(udaList[i].revision != revList[i].revision)) {
      error = TRUE;
      udaList[i].generation = revList[i].generation;
      udaList[i].revision = revList[i].revision;
    }
  }
    
  for (i=0; i<cAttrs.noOfRevisions; i++) {
    if ((noteList[i].generation != revList[i+1].generation) ||
	(noteList[i].revision != revList[i+1].revision)) {
      error = TRUE;
      noteList[i].generation = revList[i+1].generation;
      noteList[i].revision = revList[i+1].revision;
    }
    if ((dataList[i].generation != revList[i+1].generation) ||
	(dataList[i].revision != revList[i+1].revision)) {
      error = TRUE;
      dataList[i].generation = revList[i+1].generation;
      dataList[i].revision = revList[i+1].revision;
    }
  }
  if (error)
    if (SILENT < verbosityLevel)
      fprintf (stderr, "->   Warning:\tversion numbering inconsistent -- fixed !\n");

  /* move lock from busy version to last saved version,
   * when reading a version 2 (or 1) archive
   */
  if ((arVersion <= 2) && (cAttrs.noOfRevisions > 0)) {
    if (revList[0].lockerName) {
      revList[cAttrs.noOfRevisions-1].lockerName = revList[0].lockerName;
      revList[cAttrs.noOfRevisions-1].lockerHost = revList[0].lockerHost;
      revList[cAttrs.noOfRevisions-1].lockerDomain = revList[0].lockerDomain;
      revList[0].lockerName = NULL;
      revList[0].lockerHost = NULL;
      revList[0].lockerDomain = NULL;
	}
    }
  /* ensure delta type consistency */
  for (i=0; i<cAttrs.noOfRevisions; i++)
    revList[i+1].representation = dataList[i].representation;


  /*==============================
   * Write temporary archive files
   *==============================*/

  /* if all revisions have been removed -- do nothing */
  /* maybe there is a busy version somewhere else pointing to this archive */
  
  /* open tmpfile */
  arTmpFilename = af_gtmpname (arPath);
  if ((tmpFile = fopen (arTmpFilename, "w")) == NULL) {
    fprintf (stderr, "->   Syst. Error:\tcannot open temporary file %s for writing\n", arTmpFilename);
    cleanup ();
  }

  /* write header */
  fprintf (tmpFile, "%s %d %d %ld\n", AF_ARHEADER, outputVersion, cAttrs.noOfRevisions + 1, cAttrs.datasize);

  /* write constant attributes */
  fprintf (tmpFile, "%s %s %s %s %s %s\n", AF_NAMEID, 
	   cAttrs.host, cAttrs.syspath, cAttrs.name, NOTMT (cAttrs.type),
	   AF_NOSTRING); /* former variant string */

  /* write owner */
  fprintf (tmpFile, "%s %s %s %s\n", AF_OWNID, cAttrs.ownerName, cAttrs.ownerHost, cAttrs.ownerDomain);

  /* write predecessor of busy version -- do not check if busy version exists */
  fprintf (tmpFile, "%s %d %d\n", AF_PRDID, revList[0].predGen, revList[0].predRev);

  /* write locker of busy version */
  fprintf (tmpFile, "%s %s %s %s %ld\n", AF_LOCKID,
	   NOTMT (revList[0].lockerName), NOTMT (revList[0].lockerHost),
	   NOTMT (revList[0].lockerDomain), revList[0].lockTime);
  
  /* write list of version attributes */
  for (i=1; i <= cAttrs.noOfRevisions; i++) {
    /* write revision ID */
    fprintf (tmpFile, "%s %d %d %d %o %s\n", AF_REVID, 
	     revList[i].generation, revList[i].revision, revList[i].state,
	     revList[i].mode, AF_NOSTRING); /* former variant string */

    /* write author */
    fprintf (tmpFile, "\t%s %s %s %s %s %s %s\n", AF_AUTHORID,
	     revList[i].authorName, revList[i].authorHost,
	     revList[i].authorDomain, NOTMT (revList[i].lockerName),
	     NOTMT (revList[i].lockerHost), NOTMT (revList[i].lockerDomain));

    /* write dates */
    fprintf (tmpFile, "\t%s %ld %ld %ld %ld %ld\n", AF_DATEID, 
	     revList[i].modTime, revList[i].accessTime, 
	     revList[i].statChangeTime, revList[i].saveTime,
	     revList[i].lockTime);
    
    /* write kind of representation and tree connects */
    fprintf (tmpFile, "\t%s %d %ld %ld %d %d %d %d\n", AF_REPRID,
	     revList[i].representation, revList[i].fileSize,
	     (revList[i].representation == AF_DELTA) ? dataList[i-1].size :
	     revList[i].deltaSize, revList[i].succGen, revList[i].succRev,
	     revList[i].predGen, revList[i].predRev);
  }

  /* write user defined attributes */
  fprintf (tmpFile, "%s\n", AF_UDASEG);

  for (i=0; i <= cAttrs.noOfRevisions; i++) {
    fprintf (tmpFile, "%s %d %d\n", AF_UDAID, udaList[i].generation, udaList[i].revision);
    j=0;
    while (udaList[i].uda[j])
      fprintf (tmpFile, "%s%c", udaList[i].uda[j++], '\0');
    if (j==0) /* if no user defined attribute has been written */
      fputc ('\0', tmpFile);
    fputc ('\0', tmpFile);
    if (fputc ('\n', tmpFile) == EOF) {
      fclose (tmpFile);
      fprintf (stderr, "->   Error:\tcannot write to temporary file %s\n", arTmpFilename);
      cleanup ();
    }
  }
  if (fclose (tmpFile) < 0) {
    fprintf (stderr, "->   Error:\tcannot write to temporary file %s (disk full ?)\n", arTmpFilename);
    cleanup ();
  }

  /* release attrIn */
  free (attrIn.string);
  attrIn.string = NULL;
  attrIn.length = 0;
  attrIn.curPos = 0;

  /* open datatmpfile */
  datTmpFilename = af_gtmpname (arPath);
  if ((tmpFile = fopen (datTmpFilename, "w")) == NULL) {
    fprintf (stderr, "->   Syst. Error:\tcannot open temporary file %s for writing\n", datTmpFilename);
    cleanup ();
  }

  fprintf (tmpFile, "%s %d\n", AF_DATAHEADER, AF_ARCURVERS);
      
  for (i=0; i < cAttrs.noOfRevisions; i++) {
    fprintf (tmpFile, "%s %d %d %ld\n", AF_NOTEID,
	     noteList[i].generation, noteList[i].revision, noteList[i].size);
    if (noteList[i].size > 1)
      if (fwrite (noteList[i].contents, sizeof(char), (size_t)(noteList[i].size - sizeof(char)), tmpFile) == 0) {
	fclose (tmpFile);
	fprintf (stderr, "->   Error:\tcannot write to temporary file %s\n", datTmpFilename);
	cleanup ();
      }
    if (fputc ('\n', tmpFile) == EOF) {
      fclose (tmpFile);
      fprintf (stderr, "->   Error:\tcannot write to temporary file %s\n", datTmpFilename);
      cleanup ();
    }

    fprintf (tmpFile, "%s %d %d %d %ld\n", AF_DATAID,
	     dataList[i].generation, dataList[i].revision,
	     dataList[i].representation, dataList[i].size);

    if (dataList[i].size > 0)
      if (fwrite (dataList[i].contents, sizeof(char), (size_t)dataList[i].size, tmpFile) == 0) {
	fclose (tmpFile);
	fprintf (stderr, "->   Error:\tcannot write to temporary file %s\n", datTmpFilename);
	cleanup ();
      }
  }
  if (fclose (tmpFile) < 0) {
    fprintf (stderr, "->   Error:\tcannot write to temporary file %s (disk full ?)\n", arTmpFilename);
    cleanup ();
  }

  /* release dataIn */
  free (dataIn.string);
  dataIn.string = NULL;
  dataIn.length = 0;
  dataIn.curPos = 0;
      
  /* check and eventually adjust attr archive protection */
  /* compute corrcet protection */
  if ((attrArMode = afArchiveMode (arPath)))
    attrArMode &= ~(S_IXUSR|S_IXGRP|S_IXOTH|S_ISUID|S_ISGID);
  else
    attrArMode = S_IRUSR|S_IWUSR|S_IRGRP|S_IXGRP;

  /* get old group and protection */
  stat (arFilename, &ibuf);

  if (ibuf.st_gid != arGid) {
    if (SILENT < verbosityLevel) {
      fprintf (stdout, "-> group id of archive files not the same as group id of AtFS directory\n");
      fprintf (stdout, "\t\t\t\t-- will try to update ... ");
    }
    if (chown (arFilename, -1, arGid) == -1) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "failed !\n");
      needUpdate = TRUE;
    }
    else if (chown (datFilename, -1, arGid) == -1) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "failed !\n");
      needUpdate = TRUE;
    }
    else {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "Ok, done!\n");
      updateStr = " contents";
    }
  }

  if ((ibuf.st_mode & 07777) != (attrArMode & 07777)) {
    if (SILENT < verbosityLevel) {
      fprintf (stdout, "-> protection of archive file does not conform with protection of AtFS dir.\n");
      fprintf (stdout, "\t\t\t\t-- will try to update ... ");
    }
    if (chmod (arFilename, attrArMode) == -1) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "failed !\n");
      needUpdate = TRUE;
    }
    else if (chmod (datFilename, attrArMode) == -1) {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "failed !\n");
      needUpdate = TRUE;
    }
    else {
      if (SILENT < verbosityLevel)
	fprintf (stdout, "Ok, done!\n");
      updateStr = " contents";
    }
  }

  /* test, if archive files are changed -- if not, exit */
  sprintf (commandLine, "cmp -s %s %s", arFilename, arTmpFilename);
  /* if files are identical and need not to be updated */
  if (!needUpdate && (system (commandLine) == 0)) {
     sprintf (commandLine, "cmp -s %s %s", datFilename, datTmpFilename);
    if (system (commandLine) == 0) {
      unlink (arTmpFilename);
      unlink (datTmpFilename);
      if (SILENT < verbosityLevel)
	fprintf (stdout, "-> Ok -- archive files%s unchanged!\n", updateStr);
      return (AF_OK);
    }
  }

  switch (verbosityLevel) {
  case SILENT:
    confirm = TRUE;
    break;
  case NOASK:
    if (needUpdate)
      fprintf (stdout, "-> Archive files for %s must be rewritten due to mode change !\n",
	       af_gbusname (NULL, cAttrs.name, cAttrs.type));
    else
      fprintf (stdout, "-> Archive files for %s changed !\n",
	       af_gbusname (NULL, cAttrs.name, cAttrs.type));
    confirm = TRUE;
    break;
  case NORMAL:
  case EDIT:
    if (needUpdate)
      fprintf (stdout, "-> Archive files for %s must be rewritten due to mode change !",
	       af_gbusname (NULL, cAttrs.name, cAttrs.type));
    else
      fprintf (stdout, "-> Archive files for %s changed !",
	       af_gbusname (NULL, cAttrs.name, cAttrs.type));
    fprintf (stdout, " - write ? (y/n) ");
    confirm = askConfirm ("y");
  }

  if (!confirm) {
    unlink (arTmpFilename);
    unlink (datTmpFilename);
    if (SILENT < verbosityLevel)
      fprintf (stdout, "-> No archive files written\n");
    return (AF_OK);
  }
  
  /* keep old archive */
  backup (arPath, "attribute", AF_ATTRBACKUPDIR, arFilename);

  if (SILENT < verbosityLevel)
    fprintf (stdout, "-> Writing updated archive attributes archive file...\n");
  unlink (arFilename);
  if (link (arTmpFilename, arFilename) == ERROR) {
    fprintf (stderr,"->   Warning:\tcannot create new attributes archive file\n");
    fprintf (stderr,"->\t\t -- preserving temporary file %s\n", arTmpFilename);
  }
  else {
    chown (arFilename, -1, arGid);
    chmod (arFilename, attrArMode);
    unlink (arTmpFilename);
  }

  /* keep old archive */
  backup (arPath, "attribute", AF_DATABACKUPDIR, datFilename);

  if (SILENT < verbosityLevel)
    fprintf (stdout, "-> Writing updated archive data archive file...\n");
  unlink (datFilename);
  if (link (datTmpFilename, datFilename) == ERROR) {
    fprintf (stderr,"->   Warning:\tcannot create new data archive file\n");
    fprintf (stderr,"-> \t\t -- preserving temporary file %s\n", datTmpFilename);
  }
  else {
    chown (datFilename, -1, arGid);
    chmod (datFilename, attrArMode);
    unlink (datTmpFilename);
  }
  return (AF_OK);
}

/*==========================================================================
 * convert from old archive file naming style to new one
 *==========================================================================*/

LOCAL int convert (archivePath, archiveName, fileName)
     char *archivePath, *archiveName, *fileName;
{
  char *newArFullName, *newDatFullName, oldArFullName[PATH_MAX], oldDatFullName[PATH_MAX];
  char commandLine[PATH_MAX*4];
  struct stat ibuf;

  if (verbosityLevel > SILENT)
    fprintf (stdout, "Moving archive files for %s to new locations\n", fileName);

  newArFullName = af_entersym (afArchiveName (archivePath, AF_ATTRDIR, af_afname (fileName), af_aftype (fileName), AF_WRITE));
  newDatFullName = af_entersym (afArchiveName (archivePath, AF_DATADIR, af_afname (fileName), af_aftype (fileName), AF_WRITE));

  /* check if destination file already exists -- if so, ask for confirmation */
  if (stat (newArFullName, &ibuf) != ERROR) {
    if (verbosityLevel > SILENT) {
      fprintf (stdout, "->   Warning:\tDestination file %s already exists, overwrite ? (y/n) ", newArFullName);
      if (askConfirm ("n"))
	return (-1);
    }
  }
  
  sprintf (oldArFullName, "%s/%s", archivePath, archiveName);
  sprintf (oldDatFullName, "%s/%s", archivePath, archiveName);
  oldDatFullName[strlen(oldDatFullName)-1] = AF_OLD_DATAEXT;

  sprintf (commandLine, "mv -f %s %s; mv -f %s %s",
	   oldArFullName, newArFullName, oldDatFullName, newDatFullName);
  disableSig (SIGINT);
  disableSig (SIGQUIT);
  system (commandLine);
  enableSig();

  /* remove old style object cache */
  sprintf (commandLine, "rm -f %s/%s* AFS/%s*", afArchivePath ("."), AF_OLD_BPFILEID, AF_OLD_BPFILEID);
  system (commandLine);

  return (0);
}

/*============
 * repairDir
 *============*/

LOCAL int repairDir (dirName)
     char *dirName;
{
  char *arPathPtr, attrArPath[PATH_MAX], commandLine[3*PATH_MAX+32];
  char curPath[PATH_MAX];
  int  foundOldArchives = FALSE;
  int reallyRename = FALSE;
  DIR  *dirPointer;
  struct dirent *dirEntry;
  struct stat   atfsIbuf, subIbuf;

  getcwd (curPath, PATH_MAX);

  if (chdir (dirName) == -1) {
    fprintf (stderr, "->   Error:\tCannot change to directory %s\n", dirName);
    return (-1);
  }

  arPathPtr = afArchivePath (af_uniqpath (dirName));

  /* check permissions */
  if (stat (AF_SUBDIR, &atfsIbuf) != ERROR) {
    int i, modeConfirmed = FALSE, linkConfirmed = FALSE, ownerConfirmed = FALSE;

    /* check if AF_SUBDIR is a real directory */
    if (S_ISREG (atfsIbuf.st_mode)) {
      if ((NORMAL <= verbosityLevel) && !modeConfirmed) {
	fprintf (stdout, "->   Warning:\t'%s' is not a directory nor a symbolic link!\n", AF_SUBDIR);
	fprintf (stdout, "\t\tIt seems to be a file containing the name of an AtFS\n");
	fprintf (stdout, "\t\trepository elsewhere. This will not be able to straighten\n");
	fprintf (stdout, "\t\tall permission bits. Proceed anyway (y/n) ? ");
	if (!askConfirm ("n"))
	  linkConfirmed = TRUE;
      }
      if (!linkConfirmed)
	return (0);
    }
    else {
      /* check if protection of AtFS dir and subdirs conforms */
      for (i=0; i<4; i++) {
	switch (i) {
	case 0:
	  sprintf (attrArPath, "%s/%s", arPathPtr, AF_ATTRDIR);
	  break;
	case 1:
	  sprintf (attrArPath, "%s/%s", arPathPtr, AF_DATADIR);
	  break;
	case 2:
	  sprintf (attrArPath, "%s/%s", arPathPtr, AF_LOCKDIR);
	  break;
	case 3:
	  sprintf (attrArPath, "%s/%s", arPathPtr, AF_CACHENAME);
	  break;
	}
	if (stat (attrArPath, &subIbuf) != ERROR) {
	  if (i==3) {
	    atfsIbuf.st_mode &= ~(S_IXUSR|S_IXGRP|S_IXOTH|S_ISUID|S_ISGID);
	    atfsIbuf.st_mode &= 0777;
	    subIbuf.st_mode &= 0777;
	  }
	  if (atfsIbuf.st_mode != subIbuf.st_mode) {
	    if ((NORMAL <= verbosityLevel) && !modeConfirmed) {
	      fprintf (stdout, "->   Warning:\tProtection of '%s' directory\n", AF_SUBDIR);
	      if (i==3)
		fprintf (stdout, "\t\tand derived object cache differ! Adjust (y/n) ? ");
	      else
		fprintf (stdout, "\t\tand '%s' subdirectory differ! Adjust (y/n) ? ", attrArPath);
	      if (askConfirm ("y"))
		modeConfirmed = TRUE;
	    }
	    if (modeConfirmed) {
	      if (chmod (attrArPath, atfsIbuf.st_mode) == -1)
		fprintf (stderr, "Error:\tCannot change protection of '%s': %s\n",
			 attrArPath, sys_errlist[errno]);
	    }
	  }
	  if (atfsIbuf.st_gid != subIbuf.st_gid) {
	    if ((NORMAL <= verbosityLevel) && !ownerConfirmed) {
	      fprintf (stdout, "->   Warning:\tOwner/Group of '%s' directory\n", AF_SUBDIR);
	      if (i==3)
		fprintf (stdout, "\t\tand derived object cache differ! Adjust (y/n) ? ");
	      else
		fprintf (stdout, "\t\tand '%s' subdirectory differ! Adjust (y/n) ? ", attrArPath);
	      if (askConfirm ("y"))
		ownerConfirmed = TRUE;
	    }
	    if (ownerConfirmed)
	      if (chown (attrArPath, atfsIbuf.st_uid, atfsIbuf.st_gid) == -1)
		if (chown (attrArPath, geteuid(), atfsIbuf.st_gid) == -1) {
		  fprintf (stderr, "Error:\tCannot change Owner/Group of '%s': %s\n",
			   attrArPath, sys_errlist[errno]);
		}
	  }
	}
      } /* for */
    } /* else part */
  }

  if (repairCache) {
    int tMax, nMax, aMax, otMax, onMax, oaMax, sizesOk;
    sizesOk = af_cachesize (".", -1, -1, -1, &tMax, &nMax, &aMax);
    if (NORMAL <= verbosityLevel) {
      fprintf (stdout, "Cannot repair derived object caches yet -- \nthe only way ");
      fprintf (stdout, "to fix a derived object cache is to clear it ! (y/n) ? ");
      if (askConfirm ("y")) {
	if (!strcmp (dirName, "."))
	  fprintf (stdout, "Clearing derived object cache in current directory ...\n");
	else
	  fprintf (stdout, "Clearing derived object cache in directory %s ...\n", dirName);
	sprintf (commandLine, "rm -f %s/%s* %s/%s* %s/%s*", arPathPtr, AF_CACHEFILEID,
		 arPathPtr, AF_CACHENAME, arPathPtr, AF_OLD_BPFILEID);
	system (commandLine);
	fprintf (stdout, "->    Done.\n");
      }
    }
    else {
      sprintf (commandLine, "rm -f %s/%s* %s/%s* %s/%s*", arPathPtr, AF_CACHEFILEID,
	       arPathPtr, AF_CACHENAME, arPathPtr, AF_OLD_BPFILEID);
      system (commandLine);
    }
    if (sizesOk != -1)
      af_cachesize (dirName, tMax, nMax, aMax, &otMax, &onMax, &oaMax);
    return (1);
  }

  /* repair all archive files in AtFS subdirectory */
  sprintf (attrArPath, "%s/%s", arPathPtr, AF_ATTRDIR);
  if ((dirPointer = opendir (attrArPath))) {
    while ((dirEntry = (struct dirent *)readdir (dirPointer))) {
      if (!strcmp (dirEntry->d_name, ".") || !strcmp (dirEntry->d_name, ".."))
	continue;
      if (setjmp (env) == 0)
	repair (dirName, dirEntry->d_name);
    }
  }

  /* Check for archive files with old style name */
  if ((dirPointer = opendir (arPathPtr)) == NULL) {
    /* check for old AtFS directory */
    if (access ("AFS", F_OK) == -1) {
      if (verbosityLevel > SILENT)
	fprintf (stderr, "Error:\tNo AtFS Database found\n");
      chdir (curPath);
      return (-1);
    }
    sprintf (commandLine, "mv AFS %s", AF_SUBDIR);
    system (commandLine);
    if ((dirPointer = opendir (arPathPtr)) == NULL) {
      /* no way */
      if (verbosityLevel > SILENT)
	fprintf (stderr, "Error:\tNo AtFS Database found\n");
      chdir (curPath);
      return (-1);
    }
  }

  while ((dirEntry = (struct dirent *)readdir (dirPointer))) {
    int  len;
    char fileName[NAME_MAX+1];

    fileName[0] = '\0';
    len = strlen (dirEntry->d_name);
    if (!strncmp (dirEntry->d_name, AF_OLD_ATFSFILEID, AF_IDSTRLEN) &&
	(dirEntry->d_name[len - 1] == AF_OLD_ARCHEXT)) {
      if (!foundOldArchives) {
	foundOldArchives = TRUE;
	if (NORMAL <= verbosityLevel) {
	  fprintf (stdout, "\nFound old style (1.3 or lower) named archive files -- rename them ? (y/n) ? ");
	  if (!askConfirm ("y")) {
	    chdir (curPath);
	    return (0);
	  }
	}
      }
      /* check if effective user id matches AtFS-dir owner id */
      if (stat (arPathPtr, &atfsIbuf) == ERROR) {
	/* this should not happen */
	fprintf (stdout, "Cannot stat %s directory", AF_SUBDIR);
	chdir (curPath);
	return (0);
      }
      if ((atfsIbuf.st_uid != geteuid()) && !reallyRename) {
	if (NORMAL <= verbosityLevel) {
	  fprintf (stdout, "\nYou are not the owner of the AtFS directory -- really rename ? (y/n) ? ");
	  if (!askConfirm ("n"))
	    reallyRename = TRUE;
	}
	if (!reallyRename) {
	  chdir (curPath);
	  return (0);
	}
      }

      /* On System V machines, the name might be incomplete */
      if (len == NAME_MAX) {
	strcpy (fileName, afReadName (dirEntry->d_name));
      }
      else {
	strcpy (fileName, dirEntry->d_name + strlen (AF_OLD_ATFSFILEID));
	len = strlen (fileName) - 1;
	fileName[len] = '\0';
      }
      
      if (setjmp (env) == 0) {
	convert (arPathPtr, dirEntry->d_name, fileName);
	repair (dirName, fileName);
      }
    }
  }

  if (foundOldArchives) {
    /* remove old archive files and old object cache */
    sprintf (commandLine, "rm -f %s/%s* %s/%s*",
	     arPathPtr, AF_OLD_BPFILEID, arPathPtr, AF_OLD_ATFSFILEID);
    system (commandLine);
  }

  chdir (curPath);
  return (0);
}

/*==========================================================================
 * main and friends
 *==========================================================================*/

LOCAL void usage ()
{
  fprintf (stderr, "Usage:\tatfsrepair [-C] [-e] [-n] [-q] [-v] files\n");
}

LOCAL Sigret_t sigHand ()
{
  unlink (arTmpFilename);
  unlink (datTmpFilename);
  exit (0);
}

EXPORT int main (argc, argv)
     int  argc;
     char **argv;
{
  short c, i, nfiles, retCode = 0;

  while ((c = getopt (argc, argv, "Ceinqv")) != EOF) {
    switch (c) {
    case 'C': /* repair derived object cache */
      repairCache = TRUE;
      break;
    case 'e': /* edit mode  */
      verbosityLevel = EDIT;
      break;
    case 'i': /* interactive mode  */
      verbosityLevel = NORMAL;
      break;
    case 'n': /* don't ask mode  */
      verbosityLevel = NOASK;
      break;
    case 'q': /* really quiet mode  */
      verbosityLevel = SILENT;
      break;
    case 'v': /* print current version of this program */
      printf ("This is atfsrepair version %s.\n", af_version());
      exit (0);
    default:
      usage ();
      exit (1);
    }
  }  /* end of command line parsing */
  
  signal (SIGINT, sigHand);

  nfiles = argc - optind;
  if (nfiles > 0) {
    /* process arguments */
    for (i = 0; i < nfiles; i++) {
      struct stat iBuf;
      /* check is argument is a directory */
      if ((stat (argv[i+optind], &iBuf) != ERROR) && S_ISDIR(iBuf.st_mode)) {
	repairDir (argv[i+optind]);
      }
      else {
	char *path = af_afpath (argv[i+optind]);

	if (path && *path) {
	  char curPath[PATH_MAX];
	  getcwd (curPath, PATH_MAX);
	  if (chdir (path) == -1) {
	    fprintf (stderr, "Error:\tCannot change to directory %s\n", path);
	    chdir (curPath);
	    continue;
	  }
	  if (setjmp (env) == 0)
	    repair (path, argv[i+optind]);
	  chdir (curPath);
	}
	else {
	  if (setjmp (env) == 0)
	    repair (path, argv[i+optind]);
	}
      } /*else*/
    } /*for*/
    exit (retCode);
  }

  return (repairDir ("."));
} /* end of main */
