/* 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.
 */
/*
 * ShapeTools Version Control System
 *
 * vl.c - main program for "vl" command
 *
 * Author: Andreas.Lampen@cs.tu-berlin.de
 * previous versions by Uli.Pralle@cs.tu-berlin.de
 *
 * $Header: vl.c[9.0] Thu Jan 20 13:10:24 1994 andy@cs.tu-berlin.de frozen $
 */

extern int errno;

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

void initDisplay ();
char *asoName (), *histAddName ();
void displayAsoAttrs (), displayAsoShort (), displayAsoLong ();
void displayAsoFormat (), displayAsoAll ();
void displayHistShort (), displayHistLong (), displayHistAll ();
void displayHistFormat (), displayColumns ();

/*====================
 *  global variables
 *====================*/

static int  outputTty; /* true if output goes to a tty */
EXPORT char examineDir[PATH_MAX];
static int  retCode = 0;
				/* -?,-help */
int singleColumnFlag = FALSE;	/* -1 (ls) */
int allFlag = FALSE;		/* -a (ls) */
int showAllFlag = FALSE;	/* -all */
int showHiddenAttrsFlag = TRUE;	/* (always true) */
char *requiredAttrs[AF_MAXUDAS];/* -attr <attribute> */
LOCAL int requiredAttrCount = 0;
int nearlyAllFlag = FALSE;	/* -A (ls) */
int sortCtimeFlag = FALSE;	/* -c (ls) */
int multiColumnFlag = FALSE;	/* -C (ls) */
int cacheFlag = FALSE;		/* -cache */
int directoryFlag = FALSE;	/* -d (ls) */
int expandAttrFlag = FALSE;	/* -expand | -xpon */
int fastFlag = FALSE;		/* -fast */
char *formatString = NULL;	/* -format <format string> */
int fileClassFlag = FALSE;	/* -F (ls) */
int groupFlag = FALSE;		/* -g (ls) */
int justHistoriesFlag = FALSE;	/* -h */
int intentFlag = FALSE;		/* -intent */
int longFlag = FALSE;		/* -l (ls) */
int lockedFlag = FALSE;		/* -locked */
				/* -lll (== -l -locker -locked) */
int logFlag = FALSE;		/* -log */
int followSymLinksFlag = FALSE;	/* -L (ls) */
int lockerFlag = FALSE;		/* -locker */
char *bindName = NULL;		/* -n <alias> (for compatibility) */
/* expandAttrFlag */		/* -noexpand | -xpoff */
int ownerFlag = FALSE;		/* -O */
int showAllAttrsFlag = FALSE;	/* -p [all|<attrname>] */
char *showAttrs[AF_MAXUDAS];
LOCAL int showAttrCount = 0;
int replNonGraphicFlag = FALSE;	/* -q (ls) (not implemented) */
/* stQuietFlag */	        /* -Q */
int reverseOrderFlag = FALSE;	/* -r (ls) */
int recursiveFlag = FALSE;	/* -R (ls) */
int longStatusFlag = FALSE;	/* -S */
int sortMtimeFlag = FALSE;	/* -t (ls) */
int sortAtimeFlag = FALSE;	/* -u (ls) */
int fullUser = FALSE;		/* -U */
/* justHistoriesFlag */		/* -v */
				/* -version */
				/* -V <version number> (for compatibility) */
int sortLinesFlag = FALSE;	/* -x (ls) */
int displayPath = FALSE;
int vlogFlag = FALSE;

/*========================
 *  command line parsing
 *========================*/

LOCAL int handleFormat (option, arg)
     char *option, *arg;
{
  char *srcPtr, *formatPtr;

  if (!arg || !(*arg)) {
    stLog ("You must specify a format string.", ST_LOG_MSGERR);
    stExit (2);
  }
  if (formatString) {
    sprintf (stMessage, "You can specify only one format string -- add. '-%s' ignored.", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (0);
  }

  if ((formatString = malloc ((unsigned) (strlen (arg) + 2))) == NULL) {
    stLog ("Not enough memory.", ST_LOG_ERROR);
    stExit (2);
  }

  /* handle special characters */
  srcPtr = arg;
  formatPtr = formatString;
  while (*srcPtr) {
    if (*srcPtr != '\\') {
      *formatPtr++ = *srcPtr++;
      continue;
    }
    if (!*++srcPtr)
      continue;
    switch (*srcPtr) {
    case 'n':
      *formatPtr++ = '\n';
      break;
    case 't':
      *formatPtr++ = '\t';
      break;
    case '\\':
      *formatPtr++ = '\\';
      break;
    }
    srcPtr++;
  }
  *formatPtr = '\0';
  return (0);
}

LOCAL int handleAttr (option, arg)
     char *option, *arg;
{
  if (!arg || !(*arg)) {
    sprintf (stMessage, "No attribute specified -- '-%s' ignored.", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (0);
  }

  if (requiredAttrCount == AF_MAXUDAS) {
    sprintf (stMessage, "Too many attributes specified -- additional '-%s' ignored.", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (0);
  }

  requiredAttrs[requiredAttrCount++] = arg;
  requiredAttrs[requiredAttrCount] = NULL;
  return (0);
}

LOCAL int handleBindName (option, arg)
     char *option, *arg;
{
  if (!arg || !(*arg)) {
    sprintf (stMessage, "No bind name specified -- '-%s' ignored.", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (0);
  }

  if (bindName) {
    sprintf (stMessage, "Too many bind names (-n,-V,-bind) specified -- '-%s' ignored.", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (0);
  }
  bindName = arg;
  return (0);
}

LOCAL int handleLll ()
{
  longFlag = TRUE;
  lockedFlag = TRUE;
  lockerFlag = TRUE;
  return (0);
}

LOCAL int handleShowAttr (option, arg)
     char *option, *arg;
{
  if (!arg || !(*arg)) {
    showAllAttrsFlag = TRUE;
    return (0);
  }

  if (!strcmp (arg, "all")) {
    showAllAttrsFlag = TRUE;
    return (0);
  }

  if (!strcmp (arg, AT_ATTLOG)) {
    logFlag = TRUE;
    return (0);
  }

  if (showAttrCount == AF_MAXUDAS) {
    sprintf (stMessage, "Too many attribute names specified -- additional '-%s' ignored.", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (0);
  }

  showAttrs[showAttrCount++] = arg;
  showAttrs[showAttrCount] = NULL;
  return (0);
}

LOCAL int printVersion ()
{
  char *vlversion();
  sprintf (stMessage, "This is %s", vlversion());
  stLog (stMessage, ST_LOG_MSG);
  sprintf (stMessage, "  using %s,", atVersion());
  stLog (stMessage, ST_LOG_MSG);
  sprintf (stMessage, "        %s,", af_version());
  stLog (stMessage, ST_LOG_MSG);
  sprintf (stMessage, "    and %s.", stVersion());
  stLog (stMessage, ST_LOG_MSG);
  stExit (0);
  return (0);
}

static int usage();

static StOptDesc optDesc[] = {
  { "?",	PCALL,		usage,		NULL,			NULL },
  { "1",	PSWITCH|PSET,	NULL,		&singleColumnFlag,	NULL },
  { "a",	PSWITCH|PSET,	NULL,		&allFlag,		NULL },
  { "all",	PSWITCH|PSET,	NULL,		&showAllFlag,		NULL },
  { "attr",	PARG|PCALL,	handleAttr,	NULL,			NULL },
  { "A",	PSWITCH|PSET, 	NULL,	 	&nearlyAllFlag,		NULL },
  { "c",	PSWITCH|PSET, 	NULL,	 	&sortCtimeFlag,		NULL },
  { "C",	PSWITCH|PSET, 	NULL, 		&multiColumnFlag,	NULL },
  { "cache",	PSWITCH|PSET, 	NULL,	 	&cacheFlag,		NULL },
  { "d",	PSWITCH|PSET,	NULL,		&directoryFlag,		NULL },
  { "expand",	PSWITCH|PSET,	NULL,		&expandAttrFlag,	NULL },
  { "fast",	PSWITCH|PSET,	NULL,		&fastFlag,		NULL },
  { "xpon",	PSWITCH|PSET,	NULL,		&expandAttrFlag,	NULL },
  { "format",	PARG|PCALL,	handleFormat,	NULL,			NULL },
  { "F",	PSWITCH|PSET, 	NULL,		&fileClassFlag,		NULL },
  { "g",	PSWITCH|PSET,	NULL,		&groupFlag,		NULL },
  { "h",	PSWITCH|PSET,	NULL,		&justHistoriesFlag,	NULL },
  { "help",	PCALL,		usage,		NULL,			NULL },
  { "intent",	PSWITCH|PSET,	NULL,		&intentFlag,		NULL },
  { "l",	PSWITCH|PSET,	NULL,		&longFlag,		NULL },
  { "lll",	PCALL,		handleLll,	NULL,			NULL },
  { "locked",	PSWITCH|PSET,	NULL,		&lockedFlag,		NULL },
  { "locker",	PSWITCH|PSET,	NULL,		&lockerFlag,		NULL },
  { "log",	PSWITCH|PSET,	NULL,		&logFlag,		NULL },
  { "L",	PSWITCH|PSET,	NULL,		&followSymLinksFlag,	NULL },
  { "n",	PARG|PCALL,	handleBindName,	NULL,			NULL },
  { "noexpand",	PSWITCH|PCLEAR,	NULL,		&expandAttrFlag,	NULL },
  { "xpoff",	PSWITCH|PCLEAR,	NULL,		&expandAttrFlag,	NULL },
  { "O",	PSWITCH|PSET,	NULL,		&ownerFlag,		NULL },
  { "p",	POARG|PCALL,	handleShowAttr,	NULL,			NULL },
  { "q",	PSWITCH|PSET,	NULL,		&replNonGraphicFlag,	NULL },
  { "Q",	PSWITCH|PSET,	NULL,		&stQuietFlag,		NULL },
  { "r",	PSWITCH|PSET,	NULL,		&reverseOrderFlag,	NULL },
  { "R",	PSWITCH|PSET,	NULL,		&recursiveFlag,		NULL },
  { "S",	PSWITCH|PSET,	NULL,		&longStatusFlag,	NULL },
  { "t",	PSWITCH|PSET,	NULL,		&sortMtimeFlag,		NULL },
  { "u",	PSWITCH|PSET,	NULL,		&sortAtimeFlag,		NULL },
  { "U",	PSWITCH|PSET,	NULL,		&fullUser,		NULL },
  { "v",	PSWITCH|PCLEAR,	NULL,		&justHistoriesFlag,	NULL },
  { "version",	PCALL,		printVersion,	NULL,			NULL },
  { "V",	PARG|PCALL,	handleBindName,	NULL,			NULL },
  { "x",	PSWITCH|PSET,	NULL,		&sortLinesFlag,		NULL },
  { NULL, 	0, 		NULL, 		NULL, 			NULL },
};

LOCAL int usage()
{
  stLog ("Usage:", ST_LOG_MSGERR);
  stShortUsage (stProgramName, optDesc, "names...");
  atBindUsage ("");
  stExit (2);
  return (2);
}

/*==============================
 *  histories (local routines)
 *==============================*/

LOCAL int histNameComp (name1, name2)
     char **name1, **name2;
{
  if (!*name1 && !*name2)
    return (0);
  else if (!*name1)
    return (1);
  else if (!*name2)
    return (-1);

  return (strcmp (*name1, *name2));
}

LOCAL int histCtimeComp (name1, name2)
     char **name1, **name2;
{
  /* ToDo: histCtimeComp */
  return (0);
}

LOCAL int histMtimeComp (name1, name2)
     char **name1, **name2;
{
  /* ToDo: histMtimeComp */
  return (0);
}

LOCAL int histAtimeComp (name1, name2)
     char **name1, **name2;
{
  /* ToDo: histAtimeComp */
  return (0);
}

/*==============================
 *  sort and print histories
 *==============================*/

LOCAL void printHistories (histList, path)
     char **histList;
     char *path;
{
  int histCount=0, subtractCount=0, nameIdx=0, maxNameLen=0;
  int len, pathLen=0, i;
  char **nameList, *nameExt;

  if (path && *path)
    pathLen = strlen (path) +1; /* additional byte for the '/' */

  while (histList[histCount])
    histCount++;

  /*---------- narrow list-----------------*/

  for (i=0; i<histCount; i++) {
    /* ToDo: check if history has required attributes */

    if (!allFlag && histList[i][0] == '.') {
      if ((nearlyAllFlag) && strcmp (histList[i], ".") && strcmp (histList[i], ".."))
	continue;
      free (histList[i]);
      histList[i] = NULL;
      subtractCount++;
      continue;
    }
    if (!allFlag && !strcmp (histList[i], AF_SUBDIR)) {
      free (histList[i]);
      histList[i] = NULL;
      subtractCount++;
      continue;
    }
  } /* for */

  /*-------------- sort -------------------*/

  if (sortCtimeFlag)
    qsort (histList, histCount, sizeof (char*), histCtimeComp);
  else if (sortMtimeFlag)
    qsort (histList, histCount, sizeof (char*), histMtimeComp);
  else if (sortAtimeFlag)
    qsort (histList, histCount, sizeof (char*), histAtimeComp);
  else
    qsort (histList, histCount, sizeof (char*), histNameComp);

  if ((histCount -= subtractCount) == 0) {
    retCode = 1;
    return;
  }

  if (reverseOrderFlag) {
    /* reverse Set */
    int lo = 0, hi = histCount-1;
    char *tmp;

    while (lo < hi) {
      tmp = histList[hi];
      histList[hi--] = histList[lo];
      histList[lo++] = tmp;
    }
  }

  /*-------------- display -------------------*/

  if (stQuietFlag)
    return;

  /* display columns */
  if ((!formatString && !showAllFlag && !showAllAttrsFlag && !showAttrs[0]
       && !logFlag && !intentFlag && !longFlag && !singleColumnFlag) &&
      (outputTty || multiColumnFlag)) {

    if ((nameList = (char **)malloc (sizeof(char*) * (histCount+1))) == NULL) {
      stLog ("Not enough memory.", ST_LOG_ERROR);
      stExit (2);
    }

    for (i=0; i < histCount; i++) {
      nameExt = histAddName (histList[i], 1);
      len = strlen (histList[i]) + strlen (nameExt) + pathLen;

      if ((nameList[nameIdx] = malloc ((unsigned) len+1)) == NULL) {
	stLog ("Not enough memory.", ST_LOG_ERROR);
	stExit (2);
      }

      if (path && *path) {
	strcpy (nameList[nameIdx], path);
	strcat (nameList[nameIdx], "/");
	strcat (nameList[nameIdx], histList[i]);
      }
      else
	strcpy (nameList[nameIdx], histList[i]);

      if (nameExt[0])
	strcat (nameList[nameIdx], nameExt);
      nameIdx++;
      if (len > maxNameLen)
	maxNameLen = len;
    }
    if (histCount)
      displayColumns (nameList, histCount, maxNameLen);
  }
  else {
    i=0;
    while (histList[i]) {
      if (formatString)
	displayHistFormat (histList[i], formatString);
      else if (showAllFlag)
	displayHistAll (histList[i]);
      else if (longFlag)
	displayHistLong (histList[i], path);
      else
	displayHistShort (histList[i], path);
      i++;
    }
  }
}

/*======================
 *  collect histories
 *======================*/

LOCAL char **collectHistories (path, name)
     char *path, *name;
{
  char **sourceList = NULL, **cacheList = NULL;

  if (!cacheFlag)
    if ((sourceList = af_histories (path, name)) == NULL)
      return (NULL);

  if (cacheFlag || allFlag || nearlyAllFlag)
    if ((cacheList = af_cachenames (path, name)) == NULL)
      return (NULL);

  if (sourceList && cacheList) {
    /* check cacheList for names that are not known in sourceList */
    int sourceCount = 0, cacheCount = 0, newCount, i, j;
    while (sourceList[sourceCount])
      sourceCount++;
    while (cacheList[cacheCount])
      cacheCount++;
    if ((sourceList = (char **)realloc (sourceList, (unsigned) ((sourceCount+cacheCount)*sizeof (char *)))) == NULL) {
      stLog ("Not enough memory.", ST_LOG_ERROR);
      stExit (2);
    }
    newCount = sourceCount;
    for (i=0; i<cacheCount; i++) {
      for (j=0; j<sourceCount; j++) {
	if (cacheList[i] == sourceList[j])
	  break;
      }
      if (j == sourceCount)
	sourceList[newCount++] = cacheList[i];
    }
    sourceList[newCount] = NULL;
    free (cacheList);
    return (sourceList);
  }
  else if (sourceList)
    return (sourceList);
  else if (cacheList)
    return (cacheList);
  return (NULL);
}

/*======================
 *  historyTraverse
 *======================*/

LOCAL void historyTraverse (histList)
     char **histList;
{
  int  i=0;
  struct stat iBuf;
  char *pathAddPtr, **traverseList;

  while (histList[i]) {
    /* skip ".", "..", and "AtFS" */
    if (!strcmp (histList[i], ".") || !strcmp (histList[i], "..") ||
	!strcmp (histList[i], AF_SUBDIR)) {
      i++;
      continue;
    }
    /* ToDo: follow symbolic links */

    pathAddPtr = &examineDir[strlen(examineDir)];
    if (pathAddPtr > examineDir) {
      *pathAddPtr = '/';
      strcpy (pathAddPtr+1, histList[i]);
    }
    else
      strcpy (pathAddPtr, histList[i]);

    if ((stat (examineDir, &iBuf) != ERROR) && S_ISDIR(iBuf.st_mode)) {
      if ((traverseList = collectHistories (examineDir, "^.*$")) == NULL) {
	sprintf (stMessage, "\n%s -- unreadable.", examineDir);
	stLog (stMessage, ST_LOG_MSGERR);
	retCode = 1;
      }
      else {
	fputc ('\n', stdout);
	fputs (examineDir, stdout);
	fputs (":\n", stdout);

	printHistories (traverseList, NULL);
	free (traverseList);
	traverseList = af_histories (examineDir, "^.*$");
	historyTraverse (traverseList);
	free (traverseList);
      }
    }
    *pathAddPtr = '\0';
    i++;
  }
}

/*======================
 *  histories
 *======================*/

LOCAL void histories (newArgc, newArgv)
     int newArgc;
     char **newArgv;
{
  int  i, prevWasDir=FALSE;
  char **histList, path[PATH_MAX], *name;
  struct stat iBuf;

  /* no names given on the command line */
  if (newArgc == 1) {
    if ((histList = collectHistories (NULL, "^.*$")) == NULL) {
      stLog (af_errmsg (""), ST_LOG_ERROR);
      stExit (2);
    }
    strcpy (examineDir, "");
    printHistories (histList, NULL);
    free (histList);
    if (recursiveFlag) {
      histList = af_histories (NULL, "^.*$");
      historyTraverse (histList);
      free (histList);
    }
    return;
  }

  /* explicit names on the command line */
  for (i = 1; i < newArgc; i++) {
    /* cut trailing slash if present */
    if (newArgv[i][strlen(newArgv[i])-1] == '/') {
      newArgv[i][strlen(newArgv[i])-1] = '\0';
      followSymLinksFlag = TRUE;
    }

    /* list directory contents when directory name is given an no -d */
    if (!directoryFlag && (stat (newArgv[i], &iBuf) != ERROR)
	&& S_ISDIR(iBuf.st_mode)) {
      if ((histList = collectHistories (newArgv[i], "^.*$")) == NULL) {
	sprintf (stMessage, "%s -- unreadable.", newArgv[i]);
	stLog (stMessage, ST_LOG_MSGERR);
	retCode = 1;
	continue;
      }
      /* if there are more than two args, print directory name */
      if (newArgc > 2) {
	if (i>1)
	  fputc ('\n', stdout);
	fputs (newArgv[i], stdout);
	fputs (":\n", stdout);
      }
      strcpy (examineDir, newArgv[i]);
      printHistories (histList, NULL);
      prevWasDir = TRUE;
    }
    else {
      strcpy (path, af_afpath (newArgv[i]));
      if (*path) {
	char *type = af_aftype (newArgv[i]);
	name = af_afname (newArgv[i]);
	if (*type) {
	  strcat (name, ".");
	  strcat (name, type);
	}
      }
      else
	name = newArgv[i];
      if ((histList = collectHistories (path, stConvertPattern(name))) == NULL) {
	stLog (af_errmsg (""), ST_LOG_ERROR);
	stExit (2);
      }
      if (histList[0]) {
	/* ToDo: collect lists for columnized output */
	strcpy (examineDir, path);
	if (prevWasDir)
	  fputc ('\n', stdout);
	printHistories (histList, path);
	prevWasDir = FALSE;
      }
      else { /* list is empty */
	sprintf (stMessage, "%s -- not found.", newArgv[i]);
	stLog (stMessage, ST_LOG_MSGERR);
	retCode = 1;
      }
    }
    free (histList);
    if (recursiveFlag) {
      histList = af_histories (newArgv[i], "^.*$");
      historyTraverse (histList);
      free (histList);
    }
  }
}

/*===========================
 *  sort and print versions
 *===========================*/

LOCAL int needReverseCorrection = TRUE;

LOCAL void printVersions (asoSet, path)
     Af_set *asoSet;
     char *path;
{
  char **nameList, *name;
  int  asoCount, nameIdx = 0, maxNameLen = 0, len, i, j, cleanOut, pathLen=0;
  Af_key tmpKey;

  if (path && *path)
    pathLen = strlen (path) +1; /* additional byte for the '/' */

  /*---------- narrow set-----------------*/

  asoCount = af_nrofkeys (asoSet);

  for (i=asoCount-1; i >= 0; i--) {
    af_setgkey (asoSet, i, &tmpKey);
    /* check if aso has required attributes */
    j = 0;
    cleanOut = FALSE;
    while (requiredAttrs[j]) {
      if (!atMatchAttr (&tmpKey, requiredAttrs[j])) {
	cleanOut = TRUE;
	break;
      }
      j++;
    }
    if (cleanOut) {
      af_dropkey (&tmpKey);
      af_setposrmkey (asoSet, i);
      continue;
    }
    name = af_retattr (&tmpKey, AF_ATTNAME);
    if (!allFlag && name[0] == '.') {
      if ((nearlyAllFlag) && strcmp (name, ".") && strcmp (name, ".."))
	continue;
      af_dropkey (&tmpKey);
      af_setposrmkey (asoSet, i);
      continue;
    }
    if (!allFlag && !strcmp (name, AF_SUBDIR)) {
      af_dropkey (&tmpKey);
      af_setposrmkey (asoSet, i);
      continue;
    }
    af_dropkey (&tmpKey);
  } /* for */

  /*-------------- sort -------------------*/

  if (sortCtimeFlag) {
    af_sortset (asoSet, AF_ATTCTIME);
    if (needReverseCorrection) {
      if (reverseOrderFlag)
	reverseOrderFlag = FALSE;
      else
	reverseOrderFlag = TRUE;
      needReverseCorrection = FALSE;
    }
  }
  else if (sortMtimeFlag) {
    af_sortset (asoSet, AF_ATTMTIME);
    if (needReverseCorrection) {
      if (reverseOrderFlag)
	reverseOrderFlag = FALSE;
      else
	reverseOrderFlag = TRUE;
      needReverseCorrection = FALSE;
    }
  }
  else if (sortAtimeFlag) {
    af_sortset (asoSet, AF_ATTATIME);
    if (needReverseCorrection) {
      if (reverseOrderFlag)
	reverseOrderFlag = FALSE;
      else
	reverseOrderFlag = TRUE;
      needReverseCorrection = FALSE;
    }
  }
  else
    af_sortset (asoSet, AF_ATTBOUND);

  if ((asoCount = af_nrofkeys (asoSet)) == 0) {
    retCode = 1;
    return;
  }

  if (reverseOrderFlag) {
    /* reverse Set */
    int lo = 0, hi = asoCount-1;
    Af_key tmp;

    while (lo < hi) {
      tmp = asoSet->af_klist[hi];
      asoSet->af_klist[hi--] = asoSet->af_klist[lo];
      asoSet->af_klist[lo++] = tmp;
    }
  }

  /*-------------- display -------------------*/

  if (stQuietFlag)
    return;

  /* display columns */
  if ((!formatString && !showAllFlag && !showAllAttrsFlag && !showAttrs[0]
       && !logFlag && !intentFlag && !longFlag && !singleColumnFlag) &&
      (outputTty || multiColumnFlag)) {

    if (allFlag) {
      /* ToDo: fake "." and ".." */;
    }

    if ((nameList = (char **)malloc (sizeof(char*) * (asoCount+1))) == NULL) {
      stLog ("Not enough memory.", ST_LOG_ERROR);
      stExit (2);
    }

    for (i=0; i < asoCount; i++) {
      af_setgkey (asoSet, i, &tmpKey);
      name = asoName (&tmpKey);
      af_dropkey (&tmpKey);
      len = strlen (name) + pathLen;

      if ((nameList[nameIdx] = malloc ((unsigned) len+1)) == NULL) {
	stLog ("Not enough memory.", ST_LOG_ERROR);
	stExit (2);
      }

      if (path && *path) {
	strcpy (nameList[nameIdx], path);
	strcat (nameList[nameIdx], "/");
	strcat (nameList[nameIdx], name);
      }
      else
	strcpy (nameList[nameIdx], name);
      nameIdx++;
      if (len > maxNameLen)
	maxNameLen = len;
    }
    if (asoCount)
      displayColumns (nameList, asoCount, maxNameLen);
  }
  else {
    if (allFlag) {
      /* ToDo: fake "." and ".." */;
    }
    for (i=0; i < asoCount; i++) {
      af_setgkey (asoSet, i, &tmpKey);
      if (formatString)
	displayAsoFormat (&tmpKey, formatString);
      else if (showAllFlag)
	displayAsoAll (&tmpKey);
      else if (longFlag)
	displayAsoLong (&tmpKey, path);
      else
	displayAsoShort (&tmpKey, path);
      af_dropkey (&tmpKey);
    }
  }
}

/*======================
 *  collect versions
 *======================*/

LOCAL Af_set *collectVersions (name)
     char *name;
{
  Af_set *sourceSet = NULL, *cacheSet = NULL, emptySet;
  static Af_set resultSet;

  af_initset (&emptySet);

  if (!cacheFlag)
    if ((sourceSet = atBindSet (name, NULL, 0)) == NULL)
      return (NULL);

  if (cacheFlag || allFlag || nearlyAllFlag)
    if ((cacheSet = atBindCache (name, NULL)) == NULL)
      return (NULL);

  /* fourth parameter "FALSE" means, that no checking for
     duplicated elements in the result set is performed */
  af_union (sourceSet ? sourceSet : &emptySet,
	    cacheSet ? cacheSet : &emptySet, &resultSet, FALSE);
  if (sourceSet)
    af_dropset (sourceSet);
  if (cacheSet)
    af_dropset (cacheSet);
  return (&resultSet);
}

/*======================
 *  versionTraverse
 *======================*/

LOCAL void versionTraverse (asoSet)
     Af_set *asoSet;
{
  Af_key tmpKey;
  Af_set *bindSet, *tmpSet, traverseSet;
  char *pathAddPtr, *asoName, pathPattern[PATH_MAX];
  int  i;

  for (i=0; i < af_nrofkeys (asoSet); i++) {
    af_setgkey (asoSet, i, &tmpKey);
    /* skip ".", "..", and "AtFS" */
    asoName = af_retattr (&tmpKey, AF_ATTUNIXNAME);
    if (!strcmp (asoName, ".") || !strcmp (asoName, "..") ||
	!strcmp (asoName, AF_SUBDIR)) {
      af_dropkey (&tmpKey);
      continue;
    }
    /* ToDo: follow symbolic links */
    if (S_ISDIR(af_retnumattr (&tmpKey, AF_ATTMODE))) {
      pathAddPtr = &examineDir[strlen(examineDir)];
      if (pathAddPtr > examineDir) {
	*pathAddPtr = '/';
	strcpy (pathAddPtr+1, asoName);
      }
      else
	strcpy (pathAddPtr, asoName);

      strcpy (pathPattern, examineDir);
      strcat (pathPattern, "/*");
      if ((bindSet = collectVersions (pathPattern)) == NULL) {
	sprintf (stMessage, "\n%s -- unreadable.", examineDir);
	stLog (stMessage, ST_LOG_MSGERR);
	retCode = 1;
      }
      else {
	fputc ('\n', stdout);
	fputs (examineDir, stdout);
	fputs (":\n", stdout);

	printVersions (bindSet, NULL);
	af_dropset (bindSet);
	tmpSet = atBindSet (pathPattern, "[]", AT_BIND_LAST);
	af_copyset (tmpSet, &traverseSet);
	af_dropset (tmpSet);
	versionTraverse (&traverseSet);
	af_dropset (&traverseSet);
      }
      *pathAddPtr = '\0';
    }
    af_dropkey (&tmpKey);
  }
}

/*======================
 *  versions
 *======================*/

LOCAL void versions (newArgc, newArgv)
     int newArgc;
     char **newArgv;
{
  int i, prevWasDir=FALSE;
  Af_set *bindSet, *tmpSet, traverseSet;
  struct stat iBuf;

  /* if there are no name arguments on the command line */
  if (newArgc == 1) {
    if ((bindSet = collectVersions ("*")) == NULL) {
      stLog (atBindErrorMsg, ST_LOG_ERROR);
      stExit (2);
    }
    strcpy (examineDir, "");
    printVersions (bindSet, NULL);
    af_dropset (bindSet);
    if (recursiveFlag) {
      tmpSet = atBindSet ("*", "[]", AT_BIND_LAST);
      af_copyset (tmpSet, &traverseSet);
      af_dropset (tmpSet);
      versionTraverse (&traverseSet);
      af_dropset (&traverseSet);
    }
    return;
  }

  /* explicit names on the command line */
  for (i = 1; i < newArgc; i++) {
    /* cut trailing slash if present */
    if (newArgv[i][strlen(newArgv[i])-1] == '/') {
      newArgv[i][strlen(newArgv[i])-1] = '\0';
      followSymLinksFlag = TRUE;
    }

    /* list directory contents when directory name is given an no -d */
    if (!directoryFlag && (stat (newArgv[i], &iBuf) != ERROR)
	&& S_ISDIR(iBuf.st_mode)) {
      char realName[PATH_MAX];
      strcpy (realName, newArgv[i]);
      strcat (realName, "/*");
      if ((bindSet = collectVersions (realName)) == NULL) {
	sprintf (stMessage, "%s -- unreadable.", newArgv[i]);
	stLog (stMessage, ST_LOG_MSGERR);
	retCode = 1;
	continue;
      }
      /* if there are more than two args, print directory name */
      if (newArgc > 2) {
	if (i>1)
	  fputc ('\n', stdout);
	fputs (newArgv[i], stdout);
	fputs (":\n", stdout);
      }
      strcpy (examineDir, newArgv[i]);
      printVersions (bindSet, NULL);
      prevWasDir = TRUE;
    }
    else {
      if ((bindSet = collectVersions (newArgv[i])) == NULL) {
	retCode = 1;
	stLog (atBindErrorMsg, ST_LOG_ERROR);
	continue;
      }
      if (af_nrofkeys (bindSet)) {
	/* ToDo: collect sets for columnized output */
	strcpy (examineDir, af_afpath (newArgv[i]));
	if (prevWasDir)
	  fputc ('\n', stdout);
	printVersions (bindSet, examineDir);
	prevWasDir = FALSE;
      }
      else { /* set is empty */
	sprintf (stMessage, "%s -- not found.", newArgv[i]);
	stLog (stMessage, ST_LOG_MSGERR);
	retCode = 1;
      }
    }
    af_dropset (bindSet);
    if (recursiveFlag) {
      char pathPattern[PATH_MAX];
      strcpy (pathPattern, newArgv[i]);
      strcat (pathPattern, "/*");
      tmpSet = atBindSet (pathPattern, "[]", AT_BIND_LAST);
      af_copyset (tmpSet, &traverseSet);
      af_dropset (tmpSet);
      versionTraverse (&traverseSet);
      af_dropset (&traverseSet);
    }
  }
}

/*==============
 *  main
 *==============*/

LOCAL Sigret_t interrupt_action ()
     /* is executed by appropriate signal handler */
{
  stExit (1);
}

int main (argc, argv)
     int argc;
     char *argv[];
{
  int  tmpArgc, newArgc;
  char *cp, **tmpArgv, **newArgv;

  stProgramName = (cp = strrchr (argv[0], '/')) ? ++cp : argv[0];

  if (geteuid() == 0)
    allFlag = TRUE;

  if (atBindOptions (argc, argv, &tmpArgc, &tmpArgv))
    stExit (2);

  if (stParseArgs (tmpArgc, tmpArgv, &newArgc, &newArgv, optDesc))
    stExit (2);

  /* map vl -n option to bind -bind option */
  if (bindName) {
    char *auxArgv[4];
    int auxArgc = 3;
    auxArgv[0] = stProgramName;
    auxArgv[1] = "-bind";
    auxArgv[2] = bindName;
    auxArgv[3] = NULL;
    atBindOptions (auxArgc, auxArgv, &tmpArgc, &tmpArgv);
  }

  if (!(outputTty = isatty (fileno (stdout)))) {
    replNonGraphicFlag = TRUE;
    fileClassFlag = FALSE;
  }

  if (!strcmp (stProgramName, "vlog")) {
    vlogFlag = TRUE;
    if (!intentFlag && !logFlag)
      intentFlag = logFlag = TRUE;
    atBindModeOption = AT_BIND_LASTSAVED;
  }

  if (intentFlag) {
    showHiddenAttrsFlag = TRUE;
    showAttrs[showAttrCount++] = AT_ATTINTENT;
    showAttrs[showAttrCount] = NULL;
    /*
      requiredAttrs[requiredAttrCount++] = AT_ATTINTENT;
      requiredAttrs[requiredAttrCount] = NULL;
    */
  }

  if (logFlag) {
    showHiddenAttrsFlag = TRUE;
    /* 
      requiredAttrs[requiredAttrCount++] = AT_ATTLOG;
      requiredAttrs[requiredAttrCount] = NULL;
    */
  }

  if (lockedFlag) {
    requiredAttrs[requiredAttrCount++] = AF_ATTLOCKER;
    requiredAttrs[requiredAttrCount] = NULL;
  }

  if (showAllFlag) {
    showHiddenAttrsFlag = TRUE;
    showAllAttrsFlag = TRUE;
  }

  af_noReadLock = TRUE;
  if (fastFlag) {
    af_suppressUdas = TRUE;
  }
  else { /* check if reading udas can be suppressed */
    int bindOptionRequiringUdas = TRUE;
    /* if there is no bind argument except "-last" or "-laststaved" */
    if (argc == tmpArgc)
      bindOptionRequiringUdas = FALSE;
    else if ((argc == tmpArgc+1) && 
	     ((atBindModeOption == AT_BIND_LAST) || (atBindModeOption == AT_BIND_LASTSAVED)))
      bindOptionRequiringUdas = FALSE;

    if ((newArgc == 1) /* no filename arguments */
	&& !bindOptionRequiringUdas
	&& !showAllFlag
	&& (requiredAttrCount == 0)
	&& !formatString
	&& !intentFlag
	&& !lockedFlag
	&& !lockerFlag
	&& !logFlag
	&& !bindName
	&& !showAllAttrsFlag
	&& (showAttrCount == 0)
	&& !vlogFlag)
      af_suppressUdas = TRUE;
  }

  stInterruptAction = interrupt_action;
  stCatchSigs ();
  signal (SIGPIPE, interrupt_action);

  initDisplay ();

  if (justHistoriesFlag)
    histories (newArgc, newArgv);
  else
    versions (newArgc, newArgv);

  return (retCode);
}
