/* glob.c: The csh et al glob pattern matching routines.

Portions of this software are Copyright 1995 by Randall Atkinson and Dan
McDonald, All Rights Reserved. All Rights under this copyright are assigned
to the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and
License Agreement applies to this software.

	History:

	Modified at NRL for OPIE 2.0.
	Originally from BSD.
*/
/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char sccsid[] = "@(#)glob.c	5.9 (Berkeley) 2/25/91";

#endif	/* not lint */

/*
 * C-shell glob for random programs.
 */

#include "opie_cfg.h"

#include <sys/param.h>
#include <sys/stat.h>

#include <dirent.h>
#include <pwd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
/* #include <stdlib.h> */

#ifndef NCARGS
#define NCARGS 600
#endif	/* NCARGS */
#define	QUOTE 0200
#define	TRIM 0177
#define	eq(a,b)		(strcmp((a),(b)) == (0))
#define	GAVSIZ		(NCARGS/6)
#define	isdir(d)	(((d.st_mode) & S_IFMT) == S_IFDIR)

static char **gargv;	/* Pointer to the (stack) arglist */
static int gargc;	/* Number args in gargv */
static int gnleft;
static short gflag;
static int tglob();
char **ftpglob();
char *globerr;
char *home;
extern int errno;
char *strspl();
static char *strend();
char **copyblk();

static int globcnt;

char *globchars = "`{[*?";

static char *gpath, *gpathp, *lastgpathp;
static int globbed;
static char *entp;
static char **sortbas;

static int amatch __ARGS((char *p, char *s));
static int execbrc __ARGS((register char *p, register char *s));
int any __ARGS((int, char *));
void opiefatal __ARGS((char *));
char **copyblk __ARGS((char **));

static int match(s, p)
char *s, *p;
{
  register int c;
  register char *sentp;
  char sglobbed = globbed;

  if (*s == '.' && *p != '.')
    return (0);
  sentp = entp;
  entp = s;
  c = amatch(s, p);
  entp = sentp;
  globbed = sglobbed;
  return (c);
}


static int Gmatch(s, p)
register char *s, *p;
{
  register int scc;
  int ok, lc;
  int c, cc;

  for (;;) {
    scc = *s++ & TRIM;
    switch (c = *p++) {

    case '[':
      ok = 0;
      lc = 077777;
      while (cc = *p++) {
	if (cc == ']') {
	  if (ok)
	    break;
	  return (0);
	}
	if (cc == '-') {
	  if (lc <= scc && scc <= *p++)
	    ok++;
	} else
	  if (scc == (lc = cc))
	    ok++;
      }
      if (cc == 0)
	if (ok)
	  p--;
	else
	  return 0;
      continue;

    case '*':
      if (!*p)
	return (1);
      for (s--; *s; s++)
	if (Gmatch(s, p))
	  return (1);
      return (0);

    case 0:
      return (scc == 0);

    default:
      if ((c & TRIM) != scc)
	return (0);
      continue;

    case '?':
      if (scc == 0)
	return (0);
      continue;

    }
  }
}

static void Gcat(s1, s2)
register char *s1, *s2;
{
  register int len = strlen(s1) + strlen(s2) + 1;

  if (len >= gnleft || gargc >= GAVSIZ - 1)
    globerr = "Arguments too long";
  else {
    gargc++;
    gnleft -= len;
    gargv[gargc] = 0;
    gargv[gargc - 1] = strspl(s1, s2);
  }
}

static void addpath(c)
char c;
{

  if (gpathp >= lastgpathp)
    globerr = "Pathname too long";
  else {
    *gpathp++ = c;
    *gpathp = 0;
  }
}

static void rscan(t, f)
register char **t;
int (*f) ();
{
  register char *p, c;

  while (p = *t++) {
    if (f == tglob)
      if (*p == '~')
	gflag |= 2;
      else
	if (eq(p, "{") || eq(p, "{}"))
	  continue;
    while (c = *p++)
      (*f) (c);
  }
}

static int tglob(c)
register char c;
{
  if (any(c, globchars))
    gflag |= c == '{' ? 2 : 1;
  return (c);
}

int letter(c)
register char c;
{

  return (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_');
}

int digit(c)
register char c;
{

  return (c >= '0' && c <= '9');
}

int any(c, s)
int c;
char *s;
{

  while (*s)
    if (*s++ == c)
      return (1);
  return (0);
}

int blklen(av)
register char **av;
{
  register int i = 0;

  while (*av++)
    i++;
  return (i);
}

char **
     blkcpy(oav, bv)
char **oav;
register char **bv;
{
  register char **av = oav;

  while (*av++ = *bv++)
    continue;
  return (oav);
}


void blkfree(av0)
char **av0;
{
  register char **av = av0;

  while (*av)
    free(*av++);
}

char *strspl(cp, dp)
register char *cp, *dp;
{
  register char *ep = (char *) malloc((unsigned) (strlen(cp) +
						  strlen(dp) + 1));

  if (ep == (char *) 0)
    opiefatal("Out of memory");
  strcpy(ep, cp);
  strcat(ep, dp);
  return (ep);
}

char **copyblk(v)
char **v;
{
  register char **nv = (char **) malloc((unsigned) ((blklen(v) + 1) *
						    sizeof(char **)));

  if (nv == (char **) 0)
    opiefatal("Out of memory");

  return (blkcpy(nv, v));
}

static char *
     strend(cp)
register char *cp;
{

  while (*cp)
    cp++;
  return (cp);
}

/*
 * Extract a home directory from the password file
 * The argument points to a buffer where the name of the
 * user whose home directory is sought is currently.
 * We write the home directory of the user back there.
 */
int gethdir(home)
char *home;
{
  register struct passwd *pp = getpwnam(home);

  if (!pp || home + strlen(pp->pw_dir) >= lastgpathp)
    return (1);
  strcpy(home, pp->pw_dir);
  return (0);
}

static void ginit(agargv)
char **agargv;
{

  agargv[0] = 0;
  gargv = agargv;
  sortbas = agargv;
  gargc = 0;
  gnleft = NCARGS - 4;
}


static void sort()
{
  register char **p1, **p2, *c;
  char **Gvp = &gargv[gargc];

  p1 = sortbas;
  while (p1 < Gvp - 1) {
    p2 = p1;
    while (++p2 < Gvp)
      if (strcmp(*p1, *p2) > 0)
	c = *p1, *p1 = *p2, *p2 = c;
    p1++;
  }
  sortbas = Gvp;
}

static void matchdir(pattern)
char *pattern;
{
  struct stat stb;

  register struct dirent *dp;

  DIR *dirp;

  dirp = opendir(*gpath == '\0' ? "." : gpath);
  if (dirp == NULL) {
    if (globbed)
      return;
    goto patherr2;
  }
#if !defined(linux)
  if (fstat(dirp->dd_fd, &stb) < 0)
    goto patherr1;
  if (!isdir(stb)) {
    errno = ENOTDIR;
    goto patherr1;
  }
#endif /* !defined(linux) */
  while ((dp = readdir(dirp)) != NULL) {
    if (dp->d_ino == 0)
      continue;
    if (match(dp->d_name, pattern)) {
      Gcat(gpath, dp->d_name);
      globcnt++;
    }
  }
  closedir(dirp);
  return;

patherr1:
  closedir(dirp);
patherr2:
  globerr = "Bad directory components";
}

static void expand(as)
char *as;
{
  register char *cs;
  register char *sgpathp, *oldcs;
  struct stat stb;

  sgpathp = gpathp;
  cs = as;
  if (*cs == '~' && gpathp == gpath) {
    addpath('~');
    for (cs++; letter(*cs) || digit(*cs) || *cs == '-';)
      addpath(*cs++);
    if (!*cs || *cs == '/') {
      if (gpathp != gpath + 1) {
	*gpathp = 0;
	if (gethdir(gpath + 1))
	  globerr = "Unknown user name after ~";
	strcpy(gpath, gpath + 1);
      } else
	strcpy(gpath, home);
      gpathp = strend(gpath);
    }
  }
  while (!any(*cs, globchars)) {
    if (*cs == 0) {
      if (!globbed)
	Gcat(gpath, "");
      else
	if (stat(gpath, &stb) >= 0) {
	  Gcat(gpath, "");
	  globcnt++;
	}
      goto endit;
    }
    addpath(*cs++);
  }
  oldcs = cs;
  while (cs > as && *cs != '/')
    cs--, gpathp--;
  if (*cs == '/')
    cs++, gpathp++;
  *gpathp = 0;
  if (*oldcs == '{') {
    execbrc(cs, ((char *) 0));
    return;
  }
  matchdir(cs);
endit:
  gpathp = sgpathp;
  *gpathp = 0;
}

static int execbrc(p, s)
char *p, *s;
{
  char restbuf[BUFSIZ + 2];
  register char *pe, *pm, *pl;
  int brclev = 0;
  char *lm, savec, *sgpathp;

  for (lm = restbuf; *p != '{'; *lm++ = *p++)
    continue;
  for (pe = ++p; *pe; pe++)
    switch (*pe) {

    case '{':
      brclev++;
      continue;

    case '}':
      if (brclev == 0)
	goto pend;
      brclev--;
      continue;

    case '[':
      for (pe++; *pe && *pe != ']'; pe++)
	continue;
      continue;
    }
pend:
  brclev = 0;
  for (pl = pm = p; pm <= pe; pm++)
    switch (*pm & (QUOTE | TRIM)) {

    case '{':
      brclev++;
      continue;

    case '}':
      if (brclev) {
	brclev--;
	continue;
      }
      goto doit;

    case ',' | QUOTE:
    case ',':
      if (brclev)
	continue;
  doit:
      savec = *pm;
      *pm = 0;
      strcpy(lm, pl);
      strcat(restbuf, pe + 1);
      *pm = savec;
      if (s == 0) {
	sgpathp = gpathp;
	expand(restbuf);
	gpathp = sgpathp;
	*gpathp = 0;
      } else
	if (amatch(s, restbuf))
	  return (1);
      sort();
      pl = pm + 1;
      if (brclev)
	return (0);
      continue;

    case '[':
      for (pm++; *pm && *pm != ']'; pm++)
	continue;
      if (!*pm)
	pm--;
      continue;
    }
  if (brclev)
    goto doit;
  return (0);
}

static void acollect(as)
register char *as;
{
  register int ogargc = gargc;

  gpathp = gpath;
  *gpathp = 0;
  globbed = 0;
  expand(as);
  if (gargc != ogargc)
    sort();
}

static void collect(as)
register char *as;
{
  if (eq(as, "{") || eq(as, "{}")) {
    Gcat(as, "");
    sort();
  } else
    acollect(as);
}

static int amatch(s, p)
register char *s, *p;
{
  register int scc;
  int ok, lc;
  char *sgpathp;
  struct stat stb;
  int c, cc;

  globbed = 1;
  for (;;) {
    scc = *s++ & TRIM;
    switch (c = *p++) {

    case '{':
      return (execbrc(p - 1, s - 1));

    case '[':
      ok = 0;
      lc = 077777;
      while (cc = *p++) {
	if (cc == ']') {
	  if (ok)
	    break;
	  return (0);
	}
	if (cc == '-') {
	  if (lc <= scc && scc <= *p++)
	    ok++;
	} else
	  if (scc == (lc = cc))
	    ok++;
      }
      if (cc == 0)
	if (ok)
	  p--;
	else
	  return 0;
      continue;

    case '*':
      if (!*p)
	return (1);
      if (*p == '/') {
	p++;
	goto slash;
      }
      s--;
      do {
	if (amatch(s, p))
	  return (1);
      }
      while (*s++);
      return (0);

    case 0:
      return (scc == 0);

    default:
      if (c != scc)
	return (0);
      continue;

    case '?':
      if (scc == 0)
	return (0);
      continue;

    case '/':
      if (scc)
	return (0);
  slash:
      s = entp;
      sgpathp = gpathp;
      while (*s)
	addpath(*s++);
      addpath('/');
      if (stat(gpath, &stb) == 0 && isdir(stb))
	if (*p == 0) {
	  Gcat(gpath, "");
	  globcnt++;
	} else
	  expand(p);
      gpathp = sgpathp;
      *gpathp = 0;
      return (0);
    }
  }
}


char **
     ftpglob(v)
register char *v;
{
  char agpath[BUFSIZ];
  char *agargv[GAVSIZ];
  char *vv[2];

  vv[0] = v;
  vv[1] = 0;
  gflag = 0;
  rscan(vv, tglob);
  if (gflag == 0) {
    vv[0] = strspl(v, "");
    return (copyblk(vv));
  }
  globerr = 0;
  gpath = agpath;
  gpathp = gpath;
  *gpathp = 0;
  lastgpathp = &gpath[sizeof agpath - 2];
  ginit(agargv);
  globcnt = 0;
  collect(v);
  if (globcnt == 0 && (gflag & 1)) {
    blkfree(gargv), gargv = 0;
    return (0);
  } else
    return (gargv = copyblk(gargv));
}
