#include "config.h"
/*
 Copyright (c) 1986, 1990 by The Trustees of Columbia University in
 the City of New York.  Permission is granted to any individual or
 institution to use, copy, or redistribute this software so long as it
 is not sold for profit, provided this copyright notice is retained.

 Author: Howie Kaye
*/
/*
 * Code to parse filenames.  Parsing succeeds if current input contains a
 * delimeter, and and the input up to the delimeter is nonempty, and contains
 * either a uniquely matched filename, or an exactly matched wild or regexp
 * file spec.  In both of these cases, any other restrictions placed upon the
 * file (ie it's protection) must be met.  If a new file is being parsed, than
 * either an existing, or a nonexisting filename will parse.
 * Completion succeeds if the string passed to it is an unambiguous match
 * for a file name, or if it is a valid wildcard filespec.  If a new file is
 * being parse, no match is necessary.
 * Standard help displays up to `cmcsb._cmmax' filenames in a columnal
 * fashion.  If more filenames than this would be listed, then
 * the number of possible completions is listed instead.
 * The standard break table allows letters, digits, dots, dashes, underscores,
 * tildes, and '#'; wild characters: '*', '?', meta characters: '[', ']',
 * '{', '}'.
 */
					/* allocate file errors here */
#define FILERR				/* include _CMFIL error symbols */

#include "ccmdlib.h"
#include "cmfncs.h"
#include "cmfil.h"
#include "filelist.h"

#ifndef NGROUPS
#define NGROUPS 1
#endif

#if unix
/*
 * break table for filenames under Unix
 */
					/* standard break table */
static brktab filbrk = {		/* all valid chars for filenames */
  {					/* alphanums, "~#/_-\[]+" */
    0xff, 0xff, 0xff, 0xff, 0xa1, 0x00, 0x00, 0x0b,
    0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x09,
  },
  {
    0xff, 0xff, 0xff, 0xff, 0xa1, 0x00, 0x00, 0x0b,
    0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, 0x09,
  }
};
#else
#if MSDOS
/*
 * break table for filenames under MSDOS
 */
					/* standard break table */
static brktab filbrk = {		/* all valid chars for filenames */
  {					/* alphanums, "~", "#", "/" */
    0xff,  0xff,  0xff,  0xff,  0xf7,  0xc8,  0x00,  0x3f,
    0x80,  0x00,  0x00,  0x09,  0x80,  0x00,  0x00,  0x1d,
  },
  {
    0xff,  0xff,  0xff,  0xff,  0xf7,  0xc8,  0x00,  0x3f,
    0x80,  0x00,  0x00,  0x09,  0x80,  0x00,  0x00,  0x1d,
  }
};
					/* wild card break table */
static brktab filwldtab = {		/* all valid chars for filenames */
  {					/* alphanums, "~", "#", "/" */
    0xff,  0xff,  0xff,  0xff,  0xfb,  0xdc,  0x00,  0x3f,
    0x80,  0x00,  0x00,  0x17,  0x80,  0x00,  0x00,  0x1f,
  },
  {
    0xff,  0xff,  0xff,  0xff,  0xfb,  0xdc,  0x00,  0x3f,
    0x80,  0x00,  0x00,  0x17,  0x80,  0x00,  0x00,  0x1f,
  }
};
					/* regex break tab */
static brktab filregtab = {
  {
    0xff,  0xff,  0xff,  0xff,  0xf7,  0xfc,  0x00,  0x3f,
    0x80,  0x00,  0x00,  0x05,  0x80,  0x00,  0x00,  0x1f
  },
  {
    0xff,  0xff,  0xff,  0xff,  0xf7,  0xcc,  0x00,  0x3e,
    0x80,  0x00,  0x00,  0x01,  0x80,  0x00,  0x00,  0x1f
  },
};
#endif /* MSDOS */
#endif /* !unix */

#ifndef MAXPATHLEN
#define MAXPATHLEN (1024 + 1)
#endif

#define FNAME(x) (&namebuf[(x)->offset])

PASSEDSTATIC const char *default_ext ARGS((const char *fname, const char *cplt,
	 const char **ext, int is_new, dirfile **filelist, int fcount));
PASSEDSTATIC char *partial ARGS((const char *text, int textlen,
	 dirfile **filelist, int fcount, int pcount, int *exact));
PASSEDSTATIC int do_stat ARGS((char *name));
PASSEDSTATIC int fil_complete ARGS((char *text, int textlen, fdb *fdbp,
	 int full, const char **cplt, int *cpltlen));
PASSEDSTATIC int fil_help ARGS((char *text, int textlen, fdb *fdbp,
	 int cust, int lines));
PASSEDSTATIC int fil_match ARGS((const char *text, int textlen, fdb *fdbp,
	 dirfile ***mat, const char **term, int *pmatch, int *ematch, int *imatch));
PASSEDSTATIC int fil_parse ARGS((char *text, int textlen, fdb *fdbp,
	 int *parselen, pval *value));
PASSEDSTATIC int fnamlen ARGS((char *nam));
PASSEDSTATIC int validate ARGS((char *fname, flag_t flags));
PASSEDSTATIC pvfil buildexactfilelist ARGS((dirfile **fl, int count,
	 int matches));
PASSEDSTATIC pvfil buildfilelist ARGS((dirfile **fl, int count,
	 int matches, int matchtype));
PASSEDSTATIC pvfil buildpartialfilelist ARGS((dirfile **fl, int count,
	 int matches));
static dirfile **fake_search_path ARGS((const char *dir, const char *fname,
					int *count));

extern dirfile **search_path ARGS((const char **path, const char *suffix, int *fcount));

					/* standard break mask */
ftspec ft_fil =  { fil_parse, fil_help, fil_complete, 0, &filbrk };

extern int filecount;
extern char *namebuf;
static const char *dotpath[] = {".", NULL};
static filblk deffilblk = {		/* default fileblock */
  dotpath,				/* search path */
  NULL,					/* exceptionspec */
  NULL,					/* extension */
};


/*
 * parse routine
 * parse succeeds if:
 * non-wild parse, and one terminated match, or
 * wild parse, and 1 or more matches, or
 * new file parse and 0 or more matches.
 */
PASSEDSTATIC int
#if HAVE_STDC
fil_parse(char *text, int textlen, fdb *fdbp, int *parselen, pval *value)
#else /* K&R style */
fil_parse(text, textlen, fdbp, parselen, value)
char *text;
int textlen, *parselen;
fdb *fdbp;
pval *value;
#endif /* HAVE_STDC */
{
  static char retbuf[200];
  static char *ret[2] = { retbuf, NULL };
  int fcount,ecount,pcount,imatch;	/* files, exact, partial,
					   inv matches */
  dirfile **filelist;			/* list of files */
  const char *term;			/* termination character */
  flag_t flags;
  filblk *fblk;

  if ((fdbp->_cmffl & FIL_WLD) && (fdbp->_cmffl & FIL_PO))
    return(FILxINV);

  flags = fdbp->_cmffl;			/* allow directories too */
  if (fdbp->_cmffl & FIL_EXEC)
    fdbp->_cmffl |= FIL_ASRCH;
					/* find the number of matches */
  if (textlen == 0)			/* nothing typed */
      return(CMxINC);			/*  so don't do lookup */

  fcount = fil_match(text, textlen, fdbp, &filelist, &term, &pcount, &ecount,		     &imatch);

  fdbp->_cmffl = flags;			/* restore real flags */


  if (term == NULL) {			/* unterminated. */
    if (ecount == 0 && pcount == 0 && !(flags & FIL_PO))
      return(FILxNM);
    return(CMxINC);			/* pass incompleteness up */
  }

  if (ecount == 0) {			/* no matches */
    if (fdbp->_cmffl & FIL_PO) {	/* if new file parse then succeed */
      value->_pvfil = ret;
      if (text[0] == '~') {
	  char buf[200];
	  strncpy(buf, text, term-text);
	  buf[term-text]= '\0';
	  strcpy(value->_pvfil[0], tilde_expand(buf));
      }
      else {
	  strncpy(value->_pvfil[0],text,term-text);
	  value->_pvfil[0][term-text] = '\0';
      }
      *parselen = term-text;
      if (validate(value->_pvfil[0], fdbp->_cmffl)) /* or fail, if invalid */
						    /*   filename */
	return(CMxOK);
      else return(FILxBAD);
    }
    if (pcount > 0) {			/* check again with def extension */
      fblk = (filblk *) fdbp->_cmdat;
      if (fblk == NULL) fblk = &deffilblk;
      if (fblk->def_extension) {
	int i;
	static char buf[MAXPATHLEN];
	for ( i= 0; i < textlen; i++) {
	  if (isspace(text[i]&0x7f)) break;
	  buf[i] = text[i]&0x7f;
	}
	buf[i] = '\0';
	strcat(buf,default_ext(buf,"",fblk->def_extension,TRUE,filelist,
	  	fcount));
	*parselen = term - text;
	fcount = fil_match(buf, (int)strlen(buf), fdbp, &filelist, &term,
			   &pcount, &ecount, &imatch);
	if (ecount >= 1) {
	  if (iswild(buf))
	    return(FILxAMB);
	  value->_pvfil = buildexactfilelist(filelist,fcount,1);
	  return(CMxOK);
	}
	else return(FILxNM);
      }
      else {
	  if (flags & FIL_DIR) {	/* looking for directory names? */
	      int i;
	      char buf[256];
	      /*
	       * If input ends in / or consists of ~username, return
	       * the directory name
	       */
	      buf[i = term - text] = 0;
	      while (i--)		/* XXX need to strip 8th bit? */
		  buf[i] = text[i] & 0x7f;
	      i = term - text - 1;	/* look at last character */
	      if ((buf[i] == '/') ||
		  (buf[0] == '~' && index (buf, '/') == 0)) {
		  value->_pvfil = ret;
		  strcpy (retbuf, tilde_expand (buf));
		  *parselen = term - text;
		  return (CMxOK);
	      }
	  }
	  value->_pvfil = buildpartialfilelist(filelist,fcount,pcount);
	  return(FILxPMA);		/* return partial match */
      }
    }
    else return(FILxNM);		/* otherwise, no match */
  }
  else					/* exact match? */
    if ((ecount == 1) || ((ecount >= 1) && (fdbp->_cmffl & FIL_WLD)))
      goto success;
  else {				/* multiple matches */
    if (fdbp->_cmffl & FIL_WLD) {	/* wild? */
      value->_pvfil = buildexactfilelist(filelist,fcount,ecount);
      *parselen = term - text;		/* find the length */
      return(CMxOK);
    }
    else {
      static char tbuf[MAXPATHLEN];
      strncpy(tbuf,text,textlen); tbuf[textlen] = '\0';
      if (!iswild(tbuf)) {		/* take first name */
	value->_pvfil = buildexactfilelist(filelist,fcount,1);
	*parselen = term - text;
	return(CMxOK);
      }
      else return(FILxAMB);		/* otherwise ambiguous */
    }
  }
success:				/* a sucessful parse */
  *parselen = term - text;		/* find the length */
  value->_pvfil = buildexactfilelist(filelist,fcount,ecount);
  return(CMxOK);			/* return success */
}


/*
 * complete:
 * file name completion routine.
 */
PASSEDSTATIC int
#if HAVE_STDC
fil_complete(char *text, int textlen, fdb *fdbp, int full,
	     const char **cplt, int *cpltlen)
#else /* K&R style */
fil_complete(text, textlen, fdbp, full, cplt, cpltlen)
char *text;
const char **cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  int fcount,ecount,pcount,icount;
  dirfile **filelist;
  const char *term;
  flag_t flags;
  int exact;
  static char empty_string[] = "";
					/* find matches */
  *cplt = empty_string;
  *cpltlen = 0;
  flags = fdbp->_cmffl;
  if (fdbp->_cmffl & FIL_EXEC)
    fdbp->_cmffl |= FIL_ASRCH;

  fcount = fil_match(text, textlen, fdbp, &filelist, &term, &pcount, &ecount,
		     &icount);
  fdbp->_cmffl = flags;
  pcount -= icount;
  ecount -= icount;
  if (ecount >= 1) {			/* exact match.  use it */
    *cpltlen = 0;			/* 0 length completion */
    *cplt = empty_string;		/* point at nothing */
    goto done;				/* and return */
  }
  if (pcount == 0) {			/* no matches. */
    if (textlen > 0)
      if (fdbp->_cmffl & FIL_PO) {	/* a new file? */
	filblk *fblk;
	fblk = (filblk *) fdbp->_cmdat;
	if (fblk == NULL) fblk = &deffilblk;

	if (fblk->def_extension) {
	  static char completion[MAXPATHLEN];
	  static char tbuf[MAXPATHLEN];
	  strncpy(tbuf,text,textlen);
	  tbuf[textlen] = '\0';
	  strcpy(completion,*cplt);
	  strcat(completion,default_ext(tbuf,*cplt,fblk->def_extension,
	    TRUE,filelist,fcount));
	  if (strcmp(*cplt,completion) || fdbp->_cmffl & FIL_PO) {
	    *cplt = completion;
	    *cpltlen = (int)strlen(*cplt);
	    goto done;
	  }
	  return(CMP_BEL);
	}
	else {
	  *cplt = empty_string;		/* accept unmatching text */
	  *cpltlen = 0;
	  goto done;
	}
      }
					/* otherwise */
    *cplt = NULL;			/* return null pointer */
    *cpltlen = 0;
    return(CMP_BEL);			/* take action. */
  }

					/* one or more match */
  *cplt = partial(text,textlen,filelist,fcount,pcount,&exact);
  *cpltlen = (int)strlen(*cplt);
  if (!exact) {
    filblk *fblk;
    fblk = (filblk *) fdbp->_cmdat;
    if (fblk == NULL) fblk = &deffilblk;

    if (fblk->def_extension) {
      static char completion[MAXPATHLEN];
      static char tbuf[MAXPATHLEN];
      strncpy(tbuf,text,textlen);
      tbuf[textlen] = '\0';
      strcpy(completion,*cplt);
      strcat(completion,default_ext(tbuf,*cplt,fblk->def_extension,
	     			    fdbp->_cmffl & FIL_PO,filelist,fcount));
      if (strcmp(*cplt,completion) || (fdbp->_cmffl & FIL_PO)) {
	*cplt = completion;
	*cpltlen = (int)strlen(*cplt);
	goto done;
      }
      return(CMP_BEL);
    }
    return(CMP_BEL);
  }
done:
  if (full) {
    if (fdbp->_cmffl & FIL_NODIR) {
      char *cp;
      int isdir;
      cp = (char *)calloc(textlen+(int)strlen(*cplt)+1, 1);
      strncpy(cp,text,textlen);
      strcat(cp,*cplt);
      isdir = do_stat(tilde_expand(cp)) & FIL_ADR;
      free(cp);
      if (isdir) {
	static char buf[MAXPATHLEN+1];	/* allow space for extra "/" */
	strcpy(buf, *cplt);
        strcat(buf, "/");
	*cplt = &buf[0];
	(*cpltlen)++;
        return(CMP_BEL);
      }
    }
    return(CMP_GO | CMP_SPC);
  }
  else return(CMP_PNC);
}

/*
 * help routine
 */
PASSEDSTATIC int
#if HAVE_STDC
fil_help(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
fil_help(text, textlen, fdbp, cust, lines)
char *text;
int textlen, cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{
  int fcount,pcount,ecount,icount;	/* number of files/matches */
  dirfile **filelist;		/* list of them */
  const char *term;
  int found,i,maxlen,fillen;
  int cols,curcol,putlen;
  const char *realdir;
  filblk *fblk;
  int mylines = 0;

  if (!cust) {				/* standard help msg */
    cmxputs("filename, one of the following:");
  }
  cmxflsh();				/* fil_match may be slow...  */
  fcount = fil_match(text, textlen, fdbp, &filelist, &term, &pcount, &ecount,
		     &icount);
  pcount -= icount;
  ecount -= icount;
  if (pcount == 0) {
    cmxnl();
    cmxputs(" (No filenames match current input)"); /* none here */
    return(lines -1);			/* all done */
  }
  fblk = (filblk *) fdbp->_cmdat;
  if (fblk == NULL) fblk = &deffilblk;
  if (pcount > cmcsb._cmmax) {
    static char buf[MAXPATHLEN];
    sprintf(buf,"  (%d > %d matching filenames)",pcount,cmcsb._cmmax);
    cmxnl();
    cmxputs(buf);
    return(CMxOK);
  }

  maxlen = 0;
  found = 0;
  for (i = 0; i < fcount; i++) {
    if ((filelist[i]->flags & (FIL_MAT|FIL_NOR|FIL_INV)) == FIL_MAT) {
      if (fdbp->_cmffl & FIL_NOEXT)
	fillen = fnamlen(FNAME(filelist[i]));
      else
	fillen = (int)strlen(FNAME(filelist[i]));
      if (!(fdbp->_cmffl & FIL_NOPTH)) {
	fillen += (int)strlen(tilde_expand(filelist[i]->directory));
	if (!index(DIRSEP,
	      (int)(filelist[i]->directory[(int)strlen(filelist[i]->directory)-1])))
	  fillen+=1;
      }
      if (fdbp->_cmffl & FIL_TYPE) fillen += 1;
      if (maxlen < fillen) maxlen = fillen;
      if (++found >= pcount) break;
    }
  }
  maxlen += 3;
  /* XXX see comments in cmkey.c on the subject of multi-column output */
  cols = (cmcsb._cmcmx+2) / maxlen;	/* number of columns per line */
  if (cols <= 0) cols = 1;
  curcol = 0;				/* currently printing first column */
  for (i = 0, found = 0; i < fcount && found < pcount; i++) {
    if ((filelist[i]->flags & (FIL_MAT|FIL_NOR|FIL_INV)) == FIL_MAT) {
      found++;
      putlen = 0;
      if (curcol == 0) {
	mylines++;
        cmxnl();			/* new line for first column */
	if (mylines >= lines) {
	  if (!cmhelp_more("--space to continue, Q to stop--"))
	      return(-1);
	  else {
	      lines = cmcsb._cmrmx;
	      mylines = 0;
	  }
	}
        cmxputs(" ");			/* and offset a bit */
      }
      if (!(fdbp->_cmffl & FIL_NOPTH)) {
        if (strcmp(filelist[i]->directory,".")) {
	  realdir = tilde_expand(filelist[i]->directory);
  	  cmxputs(realdir);
	  if (!index(DIRSEP,(int)(realdir[(int)strlen(realdir)-1]))) {
	    cmxputc('/');
	    putlen++;
	  }
    	  putlen+= (int)strlen(realdir);
        }
	else realdir = filelist[i]->directory;
      }
      else realdir = "";
      if (!(fdbp->_cmffl & FIL_NOEXT)) {
        cmxputs(FNAME(filelist[i]));
        putlen += (int)strlen(FNAME(filelist[i]));
      }
      else {
        int l = fnamlen(FNAME(filelist[i]));
        int k;
        for (k = 0; k < l; k++) cmxputc((int)(FNAME(filelist[i])[k]));
        putlen += l;
      }
     if (fdbp->_cmffl & FIL_TYPE) {
	if (!(filelist[i]->flags & FIL_STAT)) {
	  static char fname[MAXPATHLEN];
	  strcpy(fname,realdir);
	  if ((int)strlen(realdir) != 0)
	    if (fname[(int)strlen(fname)-1] != '/')
	      strcat(fname,"/");
	  strcat(fname,FNAME(filelist[i]));
	  filelist[i]->flags |= do_stat(tilde_expand(fname));
	}
	if (filelist[i]->flags & FIL_ADR)
	  cmxputc('/');
	else if (filelist[i]->flags & FIL_ALK)
	  cmxputc('@');
	else if (filelist[i]->flags & FIL_AEX)
	  cmxputc('*');
	else cmxputc(' ');
	putlen++;
      }
      if (curcol < (cols-1)) {		/* space out if not last column */
	int j;
        for (j = putlen; j < maxlen; j++)
	  cmxputc(SPACE);
      }
      curcol = (curcol+1) % cols;	/* and move to next column */
    }
  }
  /* cmxnl(); */
  return(lines - mylines);			/* all done */
}

PASSEDSTATIC int
#if HAVE_STDC
fil_match(const char *text, int textlen, fdb *fdbp, dirfile ***mat,
	  const char **term, int *pmatch, int *ematch, int *imatch)
                        		/* termination character */
            				/* length of text */
          				/* pointer to fdb */
               				/* list of matching files */
                            		/* partial, and exact match counts */
#else /* K&R style */
fil_match(text, textlen, fdbp, mat, term,pmatch,ematch,imatch)
const char *text,**term;		/* termination character */
int textlen;				/* length of text */
fdb *fdbp;				/* pointer to fdb */
dirfile ***mat;				/* list of matching files */
int *pmatch,*ematch,*imatch;		/* partial, and exact match counts */
#endif /* HAVE_STDC */
{
  static char dirname[MAXPATHLEN], fname[MAXPATHLEN];
  int i,j;
  const char **path;
  char *xpath[2];
  char *suffix = NULL;
  filblk *fblk;
  int fcount;
  static char nambuf[MAXPATHLEN];
  int namlen;
  dirfile *df,**m;
  int access_flags,access;
  int e,p,toklen;
  brktab *btab;				/* break table to use */
  int nosearch = FALSE;
  char f[BUFSIZ];
  struct stat sbuf;


  if ((btab = fdbp->_cmbrk) == NULL)	/* get supplied break table */
    btab = &filbrk;			/* or use default */

  xpath[0] = dirname;
  xpath[1] = NULL;

  fblk = (filblk *) fdbp->_cmdat;
  if (fblk == NULL) fblk = &deffilblk;


  dirname[0] = fname[0] = '\0';		/* initially no name */

  for( i= 0; i < textlen; i++)
    if (BREAK(btab,text[i],i))
      break;
  toklen = i;

  for(i = toklen-1; i >= 0; i--) {	/* find final part of name */
    if (index(DIRSEP,(int)(text[i]))) break;
  }

  path = fblk->pathv;			/* so use default path passed us */
  if (path == NULL) {			/* and if none, use "." */
    path = dotpath;
  }

  if (i >= 0) {				/* found a directory  */
    for(j = 0; j < i; j++)		/* separate out directory */
      dirname[j] = text[j] & 0x7f;	/* and convert to 7 bit */
#ifdef undef
    if (j == 0) strcpy(dirname,"/");	/* check for just a "/" */
    else dirname[j] = '\0';		/* null terminate */
#else
    dirname[j] = '\0';
#endif
    if (index(STRUCTTERM,(int)(text[i])))
      strcat(dirname,":");
    if (index(STRUCTTERM,(int)(text[i-1])))
      strcat(dirname,"/");
    suffix = xpath[0];			/* and use this as the search path */
  }

  else if ((text[0] & 0x7f) == '~') {	/* no directory, but a ~username */
      char tmpbuf[30];			/* so make ~/.. the directory */
					/* and basename(~) the filename */

      strncpy(tmpbuf, text, textlen);
      tmpbuf[textlen] = '\0';

      strcpy(dirname, tilde_expand(tmpbuf));
      if (strcmp(dirname,tmpbuf) == 0) { /* couldn't expand... */
	  dirname[0] = '\0';		/* leave as filename */
      }
      else {
	  char *cp;

	  if (dirname[(int)strlen(dirname)] == '/' && (int)strlen(dirname) != 1)
	      dirname[(int)strlen(dirname)] = '\0'; /* remove trailing '/' */
	  cp = rindex(dirname, '/'); /* find last part of name */
	  if (cp) {
	      *cp++ = '\0';
	      strcpy(fname, cp);
	      nosearch = TRUE;
	      m = fake_search_path(dirname, fname, &fcount);
	      *term = &text[textlen];
	  }
	  else {			/* no directory in home dir??? */
	      strcpy(fname, dirname);
	      dirname[0] = '\0';
	      nosearch = TRUE;
	      m = fake_search_path(".", fname, &fcount);
	      *term = &text[textlen];
	  }
      }
  }

  if (!nosearch) {
      *term = NULL;			/* and find termination char */
      for(j = i+1; j < textlen; j++)	/* separate out the filename */
	  if (!isspace(text[j]&0x7f))
	      fname[j-(i+1)] = text[j] & 0x7f; /* and convert to 7 bit */
	  else {
	      *term = &text[j];
	      break;
	  }
      fname[j-(i+1)] = '\0';		/* and null terminate */
      m = search_path(path,suffix,&fcount); /* search the directories */
      if (fcount == 0) {		/* none found */
					/* see if unsearchable path */
					/* and fake the entry to be an */
					/* exact match */
	  sprintf(f,"%s/%s", tilde_expand(dirname), fname);
	  if (stat(f,&sbuf) == 0) {
	      m = fake_search_path(tilde_expand(dirname),fname,&fcount);
	  }
      }
  }



  *pmatch = 0;				/* no matches so far */
  *ematch = 0;
  *imatch = 0;

  access_flags = fdbp->_cmffl & FIL_ALL;/* extract file access codes */

  for (i = 0; i < fcount; i++) {
    e = p = 0;
    df = m[i];				/* current file */
    df->flags &= ~(FIL_MAT|FIL_EXA|FIL_INV|FIL_NOR);
    strcpy(nambuf,tilde_expand(df->directory));
    if (nambuf[(int)strlen(nambuf)-1] != '/')
      strcat(nambuf,"/");
    namlen = (int)strlen(nambuf);

    if (fmatch(FNAME(df),fname,TRUE)) {	/* partially matching? */
      df->flags |= FIL_MAT;		/* mark it matching */
      p = 1;				/* flag a match */
    }

    if (fmatch(FNAME(df),fname,FALSE)) { /* exact matching? */
      df->flags |= FIL_EXA;		/* yup...mark it */
      e = 1;				/* and flag it */
    }

    if (access_flags == 0) {
      if (e) (*ematch)++;
      if (p) {
	(*pmatch)++;
	if (fblk->exceptionspec != NULL) /* if in exception list */
	  if(fmatch(FNAME(df),fblk->exceptionspec,FALSE)) {
	    df->flags |= FIL_INV;		/* then ignore it */
	    (*imatch)++;
	  }
      }
      continue;
    }
    else if (e || p) {			/* if match, stat it */
      if (!(df->flags & FIL_STAT)) {	/* was it stat'ed yet? */
	strcpy(&nambuf[namlen],FNAME(df)); /* complete the name */
	df->flags |= do_stat(tilde_expand(nambuf));
      }
      access = FALSE;			/* check access flags */
      if (access_flags & FIL_EXEC && df->flags & FIL_AEX)
	access = TRUE;
      else
      if (access_flags & FIL_RD && df->flags & FIL_ARD)
	access = TRUE;
      else
      if (access_flags & FIL_WR && df->flags & FIL_AWR)
	access = TRUE;
      else
#ifdef MSDOS
      if (access_flags & FIL_HID && df->flags & FIL_AHD)
	access = TRUE;
      else
      if (access_flags & FIL_SYS && df->flags & FIL_ASY)
	access = TRUE;
      else
#endif /* MSDOS */
      if (access_flags & FIL_EXEC && df->flags & FIL_AEX)
	access = TRUE;
      else
      if (access_flags & FIL_DIR && df->flags & FIL_ADR)
	access = TRUE;
      if (access) {
	if (e) (*ematch)++;
	if (p) {			/* a match */
	  (*pmatch)++;			/* count it */
	  if (fblk->exceptionspec != NULL) /* if in exception list */
	    if(fmatch(FNAME(df),fblk->exceptionspec,FALSE)) {
	      df->flags |= FIL_INV;	/* make it invisible */
	      (*imatch)++;
	    }
	}
      }
      else df->flags |= FIL_NOR;	/* ignore it */
    }
  }
  *mat = m;
  return(fcount);
}

/*
 * build a list of all matching, unignored files.
 * matchtype can be either exact or partial
 */

PASSEDSTATIC pvfil
#if HAVE_STDC
buildfilelist(dirfile **fl, int count, int matches, int matchtype)
#else /* K&R style */
buildfilelist(fl,count,matches,matchtype)
dirfile **fl;
int count;
int matches;
int matchtype;
#endif /* HAVE_STDC */
{
  static char **retv=NULL;
  static int length=0;
  int i,j;
  static char dir[MAXPATHLEN];

  if (retv != NULL) {			/* free up previous values */
    for(i = 0; i < length; i++) free(retv[i]);
    free(retv);
  }
  length = matches;

					/* build list */
  retv = (char **) malloc(sizeof(char *) * (matches + 1));
					/* find matches */
  for(i = 0,j = 0; i < count && j < matches; i++) {
    if ((fl[i]->flags & (matchtype|FIL_NOR)) == (flag_t)matchtype) {
      strcpy(dir,tilde_expand(fl[i]->directory));
      if (index(DIRSEP,(int)(dir[(int)strlen(dir)-1]))) { /* if it has a '/' */
 	retv[j] = (char*)malloc(sizeof(char)*
				((int)strlen(FNAME(fl[i]))+(int)strlen(dir)+1));
	strcpy(retv[j],dir);		/* copy directory */
	strcat(retv[j],FNAME(fl[i]));	/* and filename */
      }
      else
	if (strcmp(dir,"."))  {		/* no slash.   copy name and insert */
	  retv[j] = (char*)malloc(sizeof(char)*
				  ((int)strlen(FNAME(fl[i]))+(int)strlen(dir)+2));
	  strcpy(retv[j],dir);
	  strcat(retv[j],"/");
	  strcat(retv[j],FNAME(fl[i]));
	}
      else {				/* in "." don't use directory */
	retv[j] = (char*)malloc(((int)strlen(FNAME(fl[i])) + 1) * sizeof(char *));
	strcpy(retv[j],FNAME(fl[i]));
      }
      j++;				/* for all files */
    }
  }
  retv[matches] = NULL;			/* null terminate vector */
  return(retv);
}

/*
 * build list of partially matching files
 */
PASSEDSTATIC pvfil
#if HAVE_STDC
buildpartialfilelist(dirfile **fl, int count, int matches)
#else /* K&R style */
buildpartialfilelist(fl,count,matches)
dirfile **fl; int count,matches;
#endif /* HAVE_STDC */
{
  return(buildfilelist(fl,count,matches,FIL_MAT));
}

/*
 * build list of exactly matching files
 */
PASSEDSTATIC pvfil
#if HAVE_STDC
buildexactfilelist(dirfile **fl, int count, int matches)
#else /* K&R style */
buildexactfilelist(fl,count,matches)
dirfile **fl; int count,matches;
#endif /* HAVE_STDC */
{
  return(buildfilelist(fl,count,matches,FIL_EXA));
}

/*
 * get length of filename up to the extension
 * skip leading dots
 */
PASSEDSTATIC int
#if HAVE_STDC
fnamlen(char *nam) {
#else /* K&R style */
fnamlen(nam) char *nam; {
#endif /* HAVE_STDC */
  register int i;
  for (i = 0; i < (int)strlen(nam); i++) /* skip leading dot's */
    if (nam[i] != '.') break;
  for (i = 1; i < (int)strlen(nam); i++) /* stop at final dot */
    if (nam[i] == '.') break;
  return(i);
}


/*
 * find a partial completion for a list of files
 */

PASSEDSTATIC char *
#if HAVE_STDC
partial(const char *text, int textlen, dirfile **filelist, int fcount, int pcount, int *exact)
#else /* K&R style */
partial(text,textlen,filelist,fcount,pcount,exact)
const char *text;
int textlen;
dirfile **filelist;
int fcount,pcount;
int *exact;
#endif /* HAVE_STDC */
{
  int i,j,k;
  static char buf[MAXPATHLEN];
  static char tbuf[MAXPATHLEN],fbuf[MAXPATHLEN],fname[MAXPATHLEN];
  int buflen,fbuflen;

  *exact = TRUE;			/* assume we find an exact match */
  for(i = textlen-1; i >= 0; i--)	/* get the name */
    if (index(DIRSEP,(int)(text[i]))) break;

  for(j = i+1; j < textlen; j++)
    tbuf[j-(i+1)] = text[j] & 0x7f;	/* copy text */
  tbuf[j-(i+1)] = '\0';			/* null terminate it */

  buf[0] = '\0';			/* start off with no matches */
  for(i = 0, j = 0; i < fcount && j < pcount; i++) {
    if ((filelist[i]->flags & (FIL_MAT|FIL_INV|FIL_NOR)) == FIL_MAT) {
      strcpy(fbuf,FNAME(filelist[i]));
      fbuflen = (int)strlen(fbuf);		/* copy then name */
      while(!fmatch(fbuf,tbuf,FALSE)) {	/* shorten until we match */
	fbuf[--fbuflen] = '\0';
      }
      strcpy(fname,FNAME(filelist[i]));
      if (j++ == 0)
	strcpy(buf,&fname[fbuflen]);	/* first time, grab completion */
      else {
	buflen = (int)strlen(buf);
	for(k = 0; k < buflen; k++)	/* otherwise trim it to match */
	  if (buf[k] != fname[fbuflen+k]) {
	    buf[k] = '\0';		/* if end of a name, then exact */
	    if (fname[fbuflen+k] != '\0') *exact = FALSE;
	    else *exact = TRUE;
	    break;
	  }
      }
    }
  }
  return(buf);
}

/*
 * based on current type in, return part of default extension to
 * append to filename
 */

PASSEDSTATIC const char *
#if HAVE_STDC
default_ext(const char *fname, const char *cplt, const char **ext, int is_new, dirfile **filelist, int fcount)
#else /* K&R style */
default_ext(fname, cplt, ext, is_new,filelist,fcount)
const char *fname,*cplt,**ext;
int is_new;
dirfile **filelist;
int fcount;
#endif /* HAVE_STDC */
{
  int i,j,k,l;
  int start;
  static char buf[MAXPATHLEN];
  static const char *e;

  if (ext == NULL) return ("");
  strcpy(buf,fname);			/* get name */
  strcat(buf,cplt);			/* append completion */
  for(start = 0; start < (int)strlen(buf); start++) /* skip leading dots */
    if (buf[start] != '.') break;
  for(i = (int)strlen(buf)-1; i >= start; i--) /* look for existing extension */
    if (buf[i] == '.') break;
  if (i <= start) i = (int)strlen(buf);	/* none.  point to end of string */
  if (!is_new)				/* if existing file  */
    if (buf[i] == '\0') return("");	/* and no "." inserted, all done */
  for(e=ext[0],k = 0; ext[k] != NULL; k++,e = ext[k]) {	/* for all extensions*/
    for(j = i; j < (int)strlen(buf); j++) {	/* check for a match */
      if (buf[j] != e[j-i]) {		/* no match.  try next extension */
	break;
      }
    }
    if (j == (int)strlen(buf)) {	/* success... */
      static char tbuf[MAXPATHLEN];	/* see how much of the extension */
      strcpy(tbuf,buf);			/* we need to use */
      strcat(tbuf,&e[j-i]);		/* cuz some may be typed already */
      for (l = 0; l < fcount; l++)
	if (filelist[l]->flags & FIL_MAT)
	  if (fmatch(FNAME(filelist[l]),tbuf,FALSE))
	    return(&e[j-i]);
    }
  }
  if (ext[k] == NULL) {			/* no matches.  if new ok */
    if (is_new) {
      for(start = 0; start < (int)strlen(buf); i++)
	if (buf[start] != '.') break;
      for(i = (int)strlen(buf)-1; i >= start; i--) /* use first extension */
	if (buf[i] == '.') break;	/* find the . */
      if (i <= start) i = (int)strlen(buf); /* no dot.  goto end */
      for(j = i; j < (int)strlen(buf); j++) { /* check for a match */
	if (buf[j] != ext[0][j-i]) {	/* no match.  */
	  return("");			/* fail */
	}
      }
      return(&ext[0][j-i]);		/* return extension */
    }
    else return("");			/* nothing to do */
  }
  return ("");
}

/*
 * validate a filename
 * for now, in MSDOS, just check the length
 */
PASSEDSTATIC int
#if HAVE_STDC
validate(char *fname, flag_t flags)
#else /* K&R style */
validate(fname, flags)
char *fname;
flag_t flags;
#endif /* HAVE_STDC */
{

#ifdef unix
  struct stat sbuf;
  char c, *cp;

  if (fname == NULL || *fname == '\0')
    return (FALSE);
  if (!(flags & FIL_VAL))		/* no validation requested */
    return (TRUE);			/* anything goes */

  if (stat (fname, &sbuf) == 0)		/* good enough. we can stat the file */
    return (TRUE);

  /* XXX think of DIRSEP instead of '/' */
  if ((cp = rindex (fname, '/')) == NULL) /* no directory name */
    return (TRUE);
  else {
    c = *cp;
    *cp = '\0';
    if (stat (fname, &sbuf) == 0) {
      *cp = c;
      return (TRUE);
    }
    else {
      *cp = c;
      return (FALSE);
    }
  }
#endif
#ifdef MSDOS
/*
 * for now, just check by length
 */

  int i,j;
  for(i = 0; i < (int)strlen(fname); i++)
    if (fname[i] == '.') break;
  if (i > 8) return(FALSE);
  for(j = i+1; j < (int)strlen(fname); j++);
    if (j - i > 4) return(FALSE);
  return(TRUE);
#endif
}


/*
 * get info on a file.
 * need to know if it is a link, a directory, and the user's access to it
 */
PASSEDSTATIC int
#if HAVE_STDC
do_stat(char *name)
#else /* K&R style */
do_stat(name)
char *name;
#endif /* HAVE_STDC */
{
  struct stat statbuf;
  flag_t flags=0;
#if unix
  int i;
  uid_t uid;
#if defined(__NeXT__)
	/* gid_t is short, but getgroups() second arg is int* */
  int gid[NGROUPS];			/* hold our groups */
#else
  gid_t gid[NGROUPS];			/* hold our groups */
#endif
  int ngroups = NGROUPS;
  int done = FALSE;

#ifdef S_IFLNK
  if (!lstat(name,&statbuf)) {		/* do a stat */
    flags |= FIL_STAT;			/* mark it stat()'ed */
    if ((statbuf.st_mode & S_IFLNK) == S_IFLNK) {
      flags |= FIL_ALK;			/* if a link, note it */
      stat(name,&statbuf);		/* and get the real info */
    }
#else
  if (!stat(name,&statbuf)) {
    flags |= FIL_STAT;
#endif /* S_IFLNK */

/*
 * check file protection.  check world first.  then owner,
 * then group
 */
    if(statbuf.st_mode & S_IFDIR)
	flags |= FIL_ADR;
    uid = geteuid();			/* get effective uid */
    if (uid == 0) {			/* root? */
	flags |= FIL_ARD | FIL_AWR;	/* automatic read/write access */
	if (statbuf.st_mode & 0111)	/* and execute access if anyone does */
	    flags |= FIL_AEX;
	done = TRUE;
    }
    else if (uid == statbuf.st_uid) {	/* am i the owner? */
	if (statbuf.st_mode & S_IREAD)	/* owner read? */
	    flags |= FIL_ARD;
	if (statbuf.st_mode & S_IWRITE)	/* owner write? */
	    flags |= FIL_AWR;
	if (statbuf.st_mode & S_IEXEC)	/* owner execute? */
	    flags |= FIL_AEX;
	done = TRUE;
    }
    if (!done) {			/* still don't know.  check group */
#if NGROUPS > 1
	getgroups(ngroups,gid);
#else
	gid[0] = getgid();
	ngroups = 1;
#endif
	for(i= 0; i < ngroups; i++)
	  if (gid[i] == statbuf.st_gid) {
	    if (statbuf.st_mode & 040)	/* group read? */
	      flags |= FIL_ARD;
	    if (statbuf.st_mode & 020)	/* group write? */
	      flags |= FIL_AWR;
	    if (statbuf.st_mode & 010)	/* group execute? */
	      flags |= FIL_AEX;
	    done = TRUE;
	    break;
	  }
    }
    if (!done) {
	if (statbuf.st_mode & 4)		/* world read? */
	    flags |= FIL_ARD;
	if (statbuf.st_mode & 2)		/* world write? */
	    flags |= FIL_AWR;
	if (statbuf.st_mode & 1)		/* world execute? */
	    flags |= FIL_AEX;
    }

  }
#endif /* unix */
#ifdef MSDOS
  if (!stat(name,&statbuf)) {
    flags = FIL_STAT;
    if (statbuf.st_mode & S_IFDIR)
      flags |= FIL_ADR;
    if (statbuf.st_mode & S_IREAD)
      flags |= FIL_ARD;
    if (statbuf.st_mode & S_IEXEC)
      flags |= FIL_AEX;
    if (statbuf.st_mode & S_IWRITE)
      flags |= FIL_AWR;
  }
#endif /* MSDOS */
					/* if dir and execute */
  if ((flags & (FIL_ADR|FIL_AEX)) == (FIL_ADR|FIL_AEX))
  {
    flags &= ~FIL_AEX;
    flags |= FIL_ASRCH;			/* then turn on search bit instead */
  }
  return(flags);
}


/*
 * fake a dirfile list of length one, pointing at dir and filename.
 */
static dirfile **
#if HAVE_STDC
fake_search_path(const char *dir, const char *fname, int *count)
#else /* K&R style */
fake_search_path(dir, fname, count)
const char *dir, *fname;
int *count;
#endif /* HAVE_STDC */
{
    static dirfile df;
    static dirfile *dfp = &df;
    static char nbuf[BUFSIZ];

    *count = 1;
    strcpy(nbuf, fname);
    namebuf = nbuf;
    dfp->directory = dir;
    dfp->offset = 0;
    dfp->flags = 0;
    return(&dfp);
}
