/* 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
 *
 * vfind.c - main program for "vfind" command
 *
 * Author: Andreas.Lampen@cs.tu-berlin.de
 * previous versions by Uli.Pralle@cs.tu-berlin.de
 * Credits for Steve Emmerson (steve@unidata.ucar.edu) who provided
 * the primary ``SinceName''. [15/Aug/90]      uli@coma
 *
 * $Header: vfind.c[5.1] Fri Dec 17 18:44:18 1993 andy@cs.tu-berlin.de frozen $
 */

#include <sys/wait.h>
#include "atfs.h"
#include "atfstk.h"
#include "sttk.h"

#define BLKSIZE 512		/* used in size */
#define VF_FIRST 0		/* used in position */
#define VF_LAST 1		/* dito. */
#define MAXDIRS 100		/* # of directories per nesting depth */

typedef struct expr *Expr;
struct expr {
  int (*eval)();		/* evaluator */
  Expr left;			/* left op */
  Expr right;			/* right op */
};

static int nac;			/* ac after option parsing */
static char **nav;		/* av after option parsing */
static char startDir[PATH_MAX]; /* dir at beginning of process */
static time_t now;		/* time in secs */
static int idx;			/* used as array index */
static dev_t thisDevice;	/* device # of starting point */
static char dirPrefix[PATH_MAX];/* current path name prefix */
static struct expr *exprList;	/* expression list */
static int trueHits;		/* # of expression yielding true */
static int maxDepth = 0;	/* max traverse depth if -cut given */

static int attrFlag = FALSE;
static int cacheFlag = FALSE;
static int cutFlag = FALSE;
static int forceFlag = FALSE;
static int hitsFlag = FALSE;
static int pruneFlag = FALSE;
static int stateFlag = FALSE;
static int userFlag = FALSE;
static int xFlag = FALSE;

/* Options */
LOCAL int handleCut (arg, opt)
     char *arg, *opt;
{
  cutFlag++;
  if ((*opt) && !(maxDepth = atoi(opt)) && (*opt != '0')) {
    fprintf(stderr, "%s: -cut: integer expected\n", stProgramName);
    return 1;
  }
  return 0;
}

LOCAL int printVersion ()
{
  char *vfversion();
  sprintf (stMessage, "This is %s", vfversion());
  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 },
  { "cache",	PSWITCH|PSET,	NULL,	 	&cacheFlag, 	NULL },
  { "cut",	POARG|PCALL, 	handleCut, 	NULL, 	 	NULL },
  { "help",	PCALL,		usage,		NULL,		NULL },
  { "force",	PSWITCH|PSET, 	NULL, 	 	&forceFlag, 	NULL },
  { "hits",	PSWITCH|PSET,	NULL,		&hitsFlag,	NULL },
  { "version",	PCALL,		printVersion,	NULL,		NULL },
  { "xdev",	PSWITCH|PSET,	NULL,		&xFlag,		NULL },
  { NULL, 	0, 		NULL, 		NULL, 		NULL },
};

static char helpmsg[] =
  "path-list expression\n\
  \tprimary expressions are: atime, ctime, mtime, stime, ltime, exec,\n\
  \texit, vl, name, perm, print, prune, symbolic, state, type, last, first,\n\
  \tuda, user, locked, locker, eq, lt, le, gt, ge, newer, SinceName, size.";

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

/*=================
 * primaries
 *=================*/

LOCAL int vfNoop () {		/* for performance tests only */
  return 1;
}

LOCAL int vfPrint (aso, exp)
     Af_key *aso;
     Expr exp;
{
  fprintf (stdout, "%s%s\n", dirPrefix, af_retattr (aso, AF_ATTBOUND));
  return 1;
}

LOCAL int vfPrune ()
{
  pruneFlag++;
  return 0;
}

LOCAL int vfTerminate (aso, exp)
     Af_key *aso;
     Expr exp;
{
  stExit ((int) exp->left);
  return 0;
}

LOCAL int vfName (aso, exp)
     Af_key *aso;
     Expr exp;
{
  char *cp;

  if ((cp = re_comp(stConvertPattern((char *) exp->left)))) {
    fprintf(stderr, "%s: %s\n", stProgramName, cp);
    stExit (1);
  }

  return re_exec (af_retattr (aso, AF_ATTUNIXNAME));
}

LOCAL int vfState (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return af_retnumattr (aso, AF_ATTSTATE) == (int) exp->left;
}

LOCAL int uda (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return atMatchAttr (aso, (char *) exp->left);
}

LOCAL int symname (aso, exp)
     Af_key *aso;
     Expr exp;
{
  char symnameuda[AT_MAXALIASLEN+AF_UDANAMLEN+2];

  sprintf(symnameuda, "%s=%s", AT_ATTALIAS, (char *) exp->left);
  return atMatchAttr (aso, symnameuda);
}

LOCAL time_t gettime (val)
     char *val;
{
  Af_attrs attrs;
  Af_set set;
  char thisvers[PATH_MAX + NAME_MAX + TYPE_MAX + 1];
  char *cp1, *cp2;
  int hits;
  time_t retVal;

  af_initattrs(&attrs);
  af_initset(&set);
  attrs.af_gen = AF_BUSYVERS;
  attrs.af_rev = AF_BUSYVERS;
  strcpy (thisvers, val);

  if ((cp1 = strrchr(thisvers, '[')) && (cp2 = strrchr(thisvers,']')) &&
      (cp1 < cp2) && (*(cp2+1) == '\0')) {
    if ((cp2 = strchr(cp1, '.')) && cp2 > cp1) {
      *cp1 = '\0'; cp1++; *cp2 = '\0'; cp2++;
      attrs.af_gen = atoi(cp1); attrs.af_rev = atoi(cp2);
    }
  }

  strcpy (attrs.af_syspath, af_afpath(thisvers));
  strcpy (attrs.af_name, af_afname(thisvers));
  strcpy (attrs.af_type, af_aftype(thisvers));

  if (af_find(&attrs, &set) <= 0) {
    fprintf (stderr, "%s: cannot access < %s >\n", stProgramName, val);
    stExit (1);
  }

  hits = af_nrofkeys (&set);
  if (hits > 1) {
    fprintf(stderr, "%s: ambigious < %s >\n", stProgramName, val);
    stExit (1);
  }
  af_allattrs(&(set.af_klist[hits-1]), &attrs);
  if (af_retnumattr (&(set.af_klist[hits-1]), AF_ATTSTATE) == AF_BUSY)
    retVal = af_rettimeattr (&(set.af_klist[hits-1]), AF_ATTMTIME);
  else
    retVal = af_rettimeattr (&(set.af_klist[hits-1]), AF_ATTSTIME);
  af_dropset (&set);
  return (retVal);
}

LOCAL int vfNewer (aso, exp)
     Af_key *aso;
     Expr exp;
{
  if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
    return (af_rettimeattr (aso, AF_ATTMTIME) > (time_t) exp->left);
  else
    return (af_rettimeattr (aso, AF_ATTSTIME) > (time_t) exp->left);
}

LOCAL int SinceName (aso, exp)
     Af_key *aso;
     Expr exp;
{
  int status = 0;		/* return status = no match */
  Af_set set;
  Af_attrs sattrs;
  char symnameuda[AT_MAXALIASLEN+AF_UDANAMLEN+2];

  af_initattrs (&sattrs);
  strcpy (sattrs.af_host, af_retattr (aso, AF_ATTHOST));
  strcpy (sattrs.af_syspath, af_retattr (aso, AF_ATTSPATH));
  strcpy (sattrs.af_name, af_retattr (aso, AF_ATTNAME));
  strcpy (sattrs.af_type, af_retattr (aso, AF_ATTTYPE));

  sprintf (symnameuda, "%s=%s", AT_ATTALIAS, (char *) exp->left);
  sattrs.af_udattrs[0] = symnameuda;
  sattrs.af_udattrs[1] = 0;

  af_initset(&set);
  if (af_find(&sattrs, &set) == -1) {
    sprintf(stMessage, "%s: af_find()", stProgramName);
    af_perror(stMessage);
  } else {
    int hits = af_nrofkeys(&set);

    if ((hits = af_nrofkeys(&set)) > 1) {
      fprintf(stderr, "%s: ambigious < %s[%s] >\n", stProgramName, 
	af_retattr (aso, AF_ATTUNIXNAME), (char*)exp->left);
    } else if (hits == 1) {
      if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
	status = af_rettimeattr (&(set.af_klist[0]), AF_ATTSTIME) <
	  af_rettimeattr (aso, AF_ATTMTIME);
      else
	status = af_rettimeattr (&(set.af_klist[0]), AF_ATTSTIME) <
	  af_rettimeattr (aso, AF_ATTSTIME);
    }
  }
  
  af_dropset(&set);

  return status;
}

LOCAL int vfPosition (aso, exp)
     Af_key *aso;
     Expr exp;
{
  Af_set tset;
  Af_attrs tattrs;
  Af_key tkey;
  int hits, i;

  af_initattrs (&tattrs);
  strcpy (tattrs.af_host, af_retattr (aso, AF_ATTHOST));
  strcpy (tattrs.af_syspath, af_retattr (aso, AF_ATTSPATH));
  strcpy (tattrs.af_name, af_retattr (aso, AF_ATTNAME));
  strcpy (tattrs.af_type, af_retattr (aso, AF_ATTTYPE));

  if (stateFlag)
    tattrs.af_state = af_retnumattr (aso, AF_ATTSTATE);
  if (userFlag) {
    Af_user *asoOwner = af_retuserattr (aso, AF_ATTOWNER);
    strcpy (tattrs.af_owner.af_username, asoOwner->af_username);
    strcpy (tattrs.af_owner.af_userdomain, asoOwner->af_userdomain);
  }
  if (attrFlag) {
    Af_attrs attrs;
    af_allattrs (aso, &attrs);
    for (i = 0; i < AF_MAXUDAS; i++)
      tattrs.af_udattrs[i] = attrs.af_udattrs[i];
    af_freeattrbuf (&attrs);
  }

  if (af_find(&tattrs, &tset) == -1) {
    sprintf(stMessage, "%s: af_find()", stProgramName);
    af_perror(stMessage);
    return 0;
  }

  if ((hits = af_nrofkeys(&tset)) > 1) {
    af_sortset(&tset, AF_ATTBOUND);
    if (((int) exp->left) == VF_FIRST)
      af_setgkey(&tset, 0, &tkey);
    else
      af_setgkey(&tset, hits - 1, &tkey);
    hits = ((af_retnumattr (&tkey, AF_ATTGEN) == af_retnumattr (aso, AF_ATTGEN)) &&
	    (af_retnumattr (&tkey, AF_ATTREV) == af_retnumattr (aso, AF_ATTREV))) ? 1 : 0;
    af_dropkey (&tkey);
  }
  af_dropset (&tset);
  return hits;
}

LOCAL int vfSize (aso, exp)
     Af_key *aso;
     Expr exp;
{
  int s;

  s = af_retnumattr (aso, AF_ATTSIZE) / BLKSIZE;
  if (af_retnumattr (aso, AF_ATTSIZE) % BLKSIZE) s++;

  return (s == (int) exp->left);
}

LOCAL int vfPerm (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return (af_retnumattr (aso, AF_ATTMODE) & (int) exp->right & 07777) == (int) exp->left;
}

LOCAL int vfUser (aso, exp)
     Af_key *aso;
     Expr exp;
{
  Af_user cmpUser, *asoUser;

  atScanUser ((char *)exp->left, &cmpUser);
  asoUser = af_retuserattr (aso, AF_ATTOWNER);

  return (!strcmp (cmpUser.af_username, asoUser->af_username)
	  && !strcmp (cmpUser.af_userdomain, asoUser->af_userdomain));
}

LOCAL int locked (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return (atUserValid (af_testlock(aso)));
}

LOCAL int locker (aso, exp)
     Af_key *aso;
     Expr exp;
{
  Af_user cmpUser, *asoUser;

  atScanUser ((char *)exp->left, &cmpUser);
  asoUser = af_retuserattr (aso, AF_ATTLOCKER);

  return (!strcmp (cmpUser.af_username, asoUser->af_username)
	  && !strcmp (cmpUser.af_userdomain, asoUser->af_userdomain));
}

LOCAL int vfType (aso, exp)
     Af_key *aso;
     Expr exp;
{
  switch ((int) exp->left) {
  case 1:
    if (S_ISBLK(af_retnumattr (aso, AF_ATTMODE)))
      return (TRUE);
    break;
  case 2:
    if (S_ISCHR(af_retnumattr (aso, AF_ATTMODE)))
      return (TRUE);
    break;
  case 3:
    if (S_ISDIR(af_retnumattr (aso, AF_ATTMODE)))
      return (TRUE);
    break;
  case 4:
    if (S_ISREG(af_retnumattr (aso, AF_ATTMODE)))
      return (TRUE);
    break;
#ifdef S_ISLNK
  case 5:
    if (S_ISLNK(af_retnumattr (aso, AF_ATTMODE)))
      return (TRUE);
    break;
#endif
#ifdef S_ISSOCK
  case 6:
    if (S_ISSOCK(af_retnumattr (aso, AF_ATTMODE)))
      return (TRUE);
    break;
#endif
  }
  return (FALSE);
}

LOCAL int vl (aso, exp)
     Af_key *aso;
     Expr exp;
{
  fprintf(stdout, "%s %s %s %8d %s %s%s\n",
	  atWriteMode (aso),
	  atWriteStatus (aso, FALSE),
	  atUserName (af_retuserattr (aso, AF_ATTOWNER)),
	  af_retnumattr (aso, AF_ATTSIZE),
	  (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY) ?
	    atWriteDate (aso, AF_ATTMTIME) : atWriteDate (aso, AF_ATTSTIME),
	  dirPrefix,
	  af_retattr (aso, AF_ATTBOUND));
  return 1;
}

LOCAL int eq (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return (af_retnumattr (aso, AF_ATTGEN) == (int) exp->left) &&
    (af_retnumattr (aso, AF_ATTREV) == (int) exp->right);
}

LOCAL int lt (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return (af_retnumattr (aso, AF_ATTGEN) < (int) exp->left ||
	  (af_retnumattr (aso, AF_ATTGEN) == (int) exp->left &&
	   af_retnumattr (aso, AF_ATTREV) < (int) exp->right));
}

LOCAL int le (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return lt (aso, exp) || eq (aso, exp);
}

LOCAL int gt (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return (af_retnumattr (aso, AF_ATTGEN) > (int) exp->left ||
	  (af_retnumattr (aso, AF_ATTGEN) == (int) exp->left &&
	   af_retnumattr (aso, AF_ATTREV) > (int) exp->right));
}

LOCAL int ge (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return gt (aso, exp) || eq (aso, exp);
}


#define SECSPERDAY 86400L
LOCAL int comptime(secs, days, sign)
     time_t secs;
     int days;
     char sign;
{
  int d;

  d = (int) ((now - secs) / SECSPERDAY);
  switch (sign) {
  case '+': return (d>days);
  case '-': return (d < (days * -1));
  default: return (d==days);
  }
}

LOCAL int vfMtime (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return comptime (af_rettimeattr (aso, AF_ATTMTIME), (time_t) exp->left, *(char*)(exp->right));
}

LOCAL int vfAtime (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return comptime (af_rettimeattr (aso, AF_ATTATIME), (time_t) exp->left, *(char*)(exp->right));
}

LOCAL int vfCtime (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return comptime (af_rettimeattr (aso, AF_ATTCTIME), (time_t) exp->left, *(char*)(exp->right));
}

LOCAL int vfStime (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return comptime (af_rettimeattr (aso, AF_ATTSTIME), (time_t) exp->left, *(char*)(exp->right));
}

LOCAL int vfLtime (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return comptime (af_rettimeattr (aso, AF_ATTLTIME), (time_t) exp->left, *(char*)(exp->right));
}

LOCAL int execute (aso, exp)
     Af_key *aso;
     Expr exp;
{
  char  *pav[128], *arg, pathName[PATH_MAX];
  int   i, j;
  Wait_t retcode;
  pid_t pid, wpid;

  strcpy (pathName, dirPrefix);
  if (af_retnumattr (aso, AF_ATTSTATE) == AF_BUSY)
    strcat (pathName, af_retattr (aso, AF_ATTUNIXNAME));
  else
    strcat (pathName, af_retattr (aso, AF_ATTBOUND));

  i = (int) exp->left;
  for (j = 0; strcmp((arg = nav[i++]), ";"); j++)
    if (!strcmp(arg, "{}"))
      pav[j] = pathName;
    else
      pav[j] = arg;

  pav[j] = (char *) NULL;
  if (j == 0) return 1;

  fflush(stdout);
  switch (pid = fork()) {
  case -1:
    sprintf (stMessage, "%s: fork failed\n", stProgramName);
    perror (stMessage);
    stExit (1);
    break;
  case 0:
    chdir(startDir);
    execvp(pav[0], pav);
    perror("vfind: exec failed");
    kill (getpid(), SIGHUP);
    _exit(127);
    break;
  default:
    while ((wpid = wait(&retcode)) != -1 && wpid != pid);
    if (WEXITSTATUS (retcode)) {
      fprintf(stderr, "%s: execution terminated\n", stProgramName);
      stExit (1);
    }
    return (!WEXITSTATUS (retcode));
    break;
  }
  
  /*NOTREACHED*/
  return 0;
}

LOCAL int not (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return ! ((*exp->left->eval) (aso, exp->left));
}

LOCAL int and (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return ((*exp->left->eval) (aso, exp->left) &&
	  (*exp->right->eval) (aso, exp->right)) ? 1 : 0;
}

LOCAL int or (aso, exp)
     Af_key *aso;
     Expr exp;
{
  return ((*exp->left->eval) (aso, exp->left) ||
	  (*exp->right->eval) (aso, exp->right)) ? 1 : 0;
}

/* constructors */
LOCAL Expr build(func, l, r)
     int (*func)();
     Expr l, r;
{
  Expr this;
  if ((this = (Expr) malloc((unsigned) sizeof (struct expr))) ==
      (Expr) NULL) {
    fprintf(stderr, "%s: out of memory.\n", stProgramName);
    stExit (1);
  }

  this->eval = func; this->left = l; this->right = r;
  return this;
}

LOCAL void skip() {
  if (nav[idx])
    idx++;
  else {
    fprintf(stderr, "%s: parsing error\n", stProgramName);
    stExit (1);
  }
}

LOCAL Expr primary () {
  char *prim, *val, *cp;
  char c;
  int i = 0;
  int mode = 0;
  int mask = 0;

  val = cp = (char *) NULL;

  if (!(prim = nav[idx])) {
    fprintf(stderr, "%s: parsing error\n", stProgramName);
    stExit (1);
  }

  if (*prim != '-') {
    fprintf(stderr, "%s: %s is not a primary\n", stProgramName, prim);
    stExit (1);
  }
  prim++;
  if (!strcmp(prim, "noop"))
    return build(vfNoop, (Expr) NULL, (Expr) NULL);
  if (!strcmp(prim, "print"))
    return build(vfPrint, (Expr) NULL, (Expr) NULL);
  if (!strcmp(prim, "prune"))
    return build(vfPrune, (Expr) NULL, (Expr) NULL);
  if (!strcmp(prim, "ls") || !strcmp(prim, "vl"))
    return build(vl, (Expr) NULL, (Expr) NULL);
  if (!strcmp(prim, "locked"))
    return build(locked, (Expr) NULL, (Expr) NULL);
  if(!strcmp(prim, "last"))
    return build(vfPosition, (Expr) VF_LAST, (Expr) NULL);
  if(!strcmp(prim, "first"))
    return build(vfPosition, (Expr) VF_FIRST, (Expr) NULL);

  skip();
  if (!(val = nav[idx])) {
    fprintf(stderr, "%s: parsing error\n", stProgramName);
    stExit (1);
  }

  if (!strcmp(prim, "name"))
    return build(vfName, (Expr) val, (Expr) NULL);
  if (!strcmp(prim, "type")) {
    c = *val;
    i = 0;
    if (c=='b')
      i = 1;
    else if (c=='c')
      i = 2;
    else if (c=='d')
      i = 3;
    else if (c=='f')
      i = 4;
#ifdef S_ISLNK
    else if (c=='l')
      i = 5;
#endif
#ifdef S_ISSOCK
    else if (c=='s')
      i = 6;
#endif
    return build(vfType, (Expr) i, (Expr) NULL);
  }
  if (!strcmp(prim, "perm")) {
    while((c = *val++))
      if (c=='-') mask++;
      else {
	c -= '0'; mode <<= 3; mode += c;
      }
    return build(vfPerm, (Expr) mode, (Expr) (mask ? mode : 07777));
  }
  if (!strcmp(prim, "atime"))
    return build(vfAtime, (Expr) atoi(val), (Expr) val);
  if (!strcmp(prim, "ctime"))
    return build(vfCtime, (Expr) atoi(val), (Expr) val);
  if (!strcmp(prim, "mtime"))
    return build(vfMtime, (Expr) atoi(val), (Expr) val);
  if (!strcmp(prim, "stime"))
    return build(vfStime, (Expr) atoi(val), (Expr) val);
  if (!strcmp(prim, "ltime"))
    return build(vfLtime, (Expr) atoi(val), (Expr) val);
  if (!strcmp(prim, "user")) {
    userFlag++;
    return build(vfUser, (Expr) val, (Expr) val);
  }
  if (!strcmp(prim, "exit"))
    return build(vfTerminate, (Expr) atoi(val), (Expr) NULL);
  if (!strcmp(prim, "eq")) {
    if ((cp = strrchr(val, '.'))) *cp++ = '\0';
    return build(eq, (Expr) atoi(val), (Expr) atoi(cp));
  }
  if (!strcmp(prim, "le")) {
    if ((cp = strrchr(val, '.'))) *cp++ = '\0';
    return build(le, (Expr) atoi(val), (Expr) atoi(cp));
  }
  if (!strcmp(prim, "lt")) {
    if ((cp = strrchr(val, '.'))) *cp++ = '\0';
    return build(lt, (Expr) atoi(val), (Expr) atoi(cp));
  }
  if (!strcmp(prim, "ge")) {
    if ((cp = strrchr(val, '.'))) *cp++ = '\0';
    return build(ge, (Expr) atoi(val), (Expr) atoi(cp));
  }
  if (!strcmp(prim, "gt")) {
    if ((cp = strrchr(val, '.'))) *cp++ = '\0';
    return build(gt, (Expr) atoi(val), (Expr) atoi(cp));
  }
  if (!strcmp(prim, "locker")) {
    if ((cp = strrchr(val, '@'))) *cp++ = '\0';
    return build(locker, (Expr) val, (Expr) cp);
  }
  if (!strcmp(prim, "exec") || !strcmp(prim, "ok")) {
    i = idx;
    while(nav[++idx] && strcmp(nav[idx], ";"));
    return build(execute, (Expr) i, (Expr) prim);
  }
  if (!strcmp(prim, "state")) {
    stateFlag++;
    return build(vfState, (Expr) atScanStatus (val), (Expr) NULL);
  }
  if (!strcmp(prim, "uda")) {
    attrFlag++;
    return build(uda, (Expr) val, (Expr) NULL);
  }
  if (!strcmp(prim, "symbolic")) {
    attrFlag++;
    return build(symname, (Expr) val, (Expr) NULL);
  }
  if (!strcmp(prim, "newer"))
    return build(vfNewer, (Expr) gettime(val), (Expr) NULL);
  if (!strcmp(prim, "SinceName")) {
    return build(SinceName, (Expr) val, (Expr) NULL);
  }
  if (!strcmp(prim, "size"))
    return build(vfSize, (Expr) atoi(val), (Expr) NULL);
  fprintf(stderr,"%s: unknown primary `%s'.\n", stProgramName, prim);
  stExit (1);

  /*NOTREACHED*/
  return 0;
}

LOCAL Expr alternation();

LOCAL Expr grouping () {
  Expr expr;

  if (!strcmp(nav[idx], "(")){
    skip();
    expr = alternation();
    if (nav[idx] && !strcmp(nav[idx], ")")) {
      skip();
      return expr;
    }
    fprintf(stderr, "%s: missing closing ')'.\n", stProgramName);
    stExit (1);
  }
  else {
    expr = primary();
    skip();		/* primary() does not skip the */
				/* processed token, so we do it here. */
    return expr;
  }

  /*NOTREACHED*/
  return 0;
}

LOCAL Expr negation() {
  if (nav[idx] && !strcmp(nav[idx], "!")) {
    skip();
    return build(not, grouping(), (Expr) NULL);
  }
  else
    return grouping();
}

LOCAL Expr concatenation() {
  Expr left;
  char *this;

  left = negation ();
  this = nav[idx];
  if (this && (!strcmp(this, "-a") || !strcmp(this, "!") ||
	       !strcmp(this, "(") || (*this == '-' && strcmp(this, "-o")))){
    if (!strcmp(this, "-a")) skip();
    return build(and, left, concatenation());
  }
  else
    return left;
}

LOCAL Expr alternation() {
  Expr left;

  left = concatenation();
  if (nav[idx] && !strcmp(nav[idx], "-o")) {
    skip();
    return build(or, left, concatenation());
  }
  else
    return left;
}

LOCAL Expr expression() {
  return alternation();
}

LOCAL void traverse (name)
     char *name;
{
  Af_set set, cacheSet;
  Af_key key;
  Af_attrs attrs;
  struct stat buf, abuf;
  int hits, i, ndir;
  char *prefix, thisaso[PATH_MAX+1];
  Af_key *dirs[MAXDIRS];
  static int ncalls = 0;

  if (af_access(af_afpath(name), af_afname(name), af_aftype(name), AF_CLASS_SOURCE) == -1) {
    af_perror (name);
    return;
  }
    
  if (stat(name, &buf) == -1)
    memset((char *) &buf, 0, sizeof (buf));

  prefix = dirPrefix + strlen (dirPrefix);
  ndir = 0;
  
  af_initset (&set);
  af_initattrs (&attrs);
  if (S_ISDIR (buf.st_mode)) {
    strcpy(attrs.af_syspath, name);
    i = strlen (name) - 1;
    if (name[i] == '/' && i > 0)
      name[i] = '\0';
    sprintf (prefix, "%s/", name);
  }
  else {
    strcpy (attrs.af_name, af_afpath(name));
    strcpy (attrs.af_name, af_afname(name));
    strcpy (attrs.af_type, af_aftype(name));
  }

  /* check only busy versions if AtFS is not a directory */
  if (!forceFlag) {
    sprintf (thisaso, "%s/%s", S_ISDIR (buf.st_mode) ? name : af_afpath(name), AF_SUBDIR);
    if ((stat (thisaso, &abuf) != -1) && (!S_ISDIR (abuf.st_mode)))
      attrs.af_state = AF_BUSY;
  }
  
  if (af_find(&attrs, &set) == -1) {
    sprintf(stMessage, "%s: af_find(%s)", stProgramName, name);
    af_perror(stMessage);
    return;
  }

  if (cacheFlag) {
    af_initset(&cacheSet);
    if (af_cachefind(&attrs, &cacheSet) == -1) {
      sprintf(stMessage, "%s: cachefind(%s)", stProgramName, name);
      af_perror(stMessage);
      af_dropset(&set);
      return;
    }
    af_union (&set, &cacheSet, &set, TRUE);
    af_dropset(&cacheSet);
  }

  hits = af_nrofkeys(&set);
  af_sortset (&set, AF_ATTBOUND);
  for (i = 0; i < hits; i++) {
    if (af_setgkey (&set, i, &key) == -1) {
      sprintf(stMessage, "%s: af_setgkey()", stProgramName);
      af_perror(stMessage);
      continue;
    }
    
    if (S_ISDIR (af_retnumattr (&key, AF_ATTMODE))) {
      if (ndir == MAXDIRS) {
	fprintf(stderr, "%s: too many directories.\n", stProgramName);
	af_dropkey (&key);
	stExit (1);
      }
      dirs[ndir] = (Af_key *) malloc((unsigned) sizeof(Af_key));
      if (dirs[ndir] == (Af_key *) NULL) {
	fprintf(stderr, "%s: out of memory\n", stProgramName);
	af_dropkey (&key);
	stExit (1);
      }
      memcpy ((char *) dirs[ndir++], (char *) &key, sizeof(Af_key));
      af_dropkey (&key);
      continue;
    }
    if ((*exprList->eval)(&key, exprList))
      trueHits++;
    af_dropkey (&key);
  }

  if ((!S_ISDIR (buf.st_mode)) ||
      (xFlag && (buf.st_dev != thisDevice)) ||
      (cutFlag && ncalls > maxDepth) ||
      !strcmp(name, AF_SUBDIR)) {
    af_dropset (&set);
    return;
  }

  if (chdir(name) == -1) {
    af_dropset (&set);
    return;
  }

  sprintf(prefix, "%s/", name);

  for (i = 0; i < ndir; i++) {
    strcpy (thisaso, af_retattr (dirs[i], AF_ATTUNIXNAME));
    if (!strcmp(thisaso, ".") || !strcmp(thisaso, "..") ||
	!strcmp(thisaso, AF_SUBDIR))
      continue;

    if ((*exprList->eval)(dirs[i], exprList))
      trueHits++;
    free((char *) dirs[i]);

    if (pruneFlag) {
      pruneFlag = 0;
      continue;
    }
    
    ncalls++;
    traverse(thisaso);
    ncalls--;
  }

  af_dropset (&set);
  chdir("..");
  *prefix = '\0';
  return;
}

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

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

EXPORT int main (argc, argv)
     int argc;
     char *argv[];
{
  char *cp;
  int  pathCount;
  
  stProgramName = (cp = strrchr (argv[0], '/')) ? ++cp : argv[0];

  if (stParseArgs (argc, argv, &nac, &nav, optDesc))
    stExit (2);

  getcwd (startDir, PATH_MAX);
  now = time ((time_t *)0);

  if (nac < 2) 
    usage();

  for (idx = 1; idx < nac; idx++) /* find path list */
    if (*nav[idx] == '-' ||  *nav[idx] == '!' || *nav[idx] == '(')
      break;

  if (!(pathCount = idx)){
    fprintf(stderr, "%s: no path list.\n", stProgramName);
    usage();
  }
  if (idx == nac) {
    fprintf(stderr, "%s: no expression.\n", stProgramName);
    usage();
  }

  if ((exprList = expression()) == (Expr) NULL)
    stExit (1);

  stInterruptAction = interrupt_action;
  stCatchSigs();

  for (idx = 1 ; idx < pathCount; idx++) {	/* for all directories */
    chdir (startDir);	/* back to normal */
    cp = nav[idx];
    
    if (xFlag) {
      struct stat buf;
      stat(cp, &buf);
      thisDevice = buf.st_dev;
    }

    *dirPrefix = '\0';
    traverse (cp);
  }

  stExit (hitsFlag ? trueHits : 0);
  return 0;
}
