/* File-name wildcard pattern matching for GNU.
   Copyright (C) 1985, 1988, 1989 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* To whomever it may concern: I have never seen the code which most
   Unix programs use to perform this function.  I wrote this from scratch
   based on specifications for the pattern matching.  --RMS.  */

#include "config.h"
extern "C" {
#include "regex.h"
#ifdef USE_RX
#include "rx.h"
#include "rxparse.h"
#include "rxrun.h"
#endif
}
extern "C" int re_set_syntax (int);

#include <stdio.h> /* Should get rid of this (stderr below) ? */

#if defined (USG) && !defined (Xenix)
#  define USGr3
#endif

#include <sys/types.h>
extern "C" {
#include <sys/stat.h>
}
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif

#ifdef __GNU_LIBRARY
extern "C" {
#include <dirent.h>
}
#include <string.h>
#  define direct dirent
#else /* !__GNU_LIBRARY */
#if defined (USGr3) || defined (DIRENT)
#  include <dirent.h>
#  define direct dirent
#  define	D_NAMLEN(d) strlen((d)->d_name)
#else
#  define D_NAMLEN(d) ((d)->d_namlen)
#  if defined (Xenix)
#    include <sys/ndir.h>
#  else
#    if defined (USG)
#      include "ndir.h"
#     else
#      include <sys/dir.h>
#    endif
#  endif
#endif	/* USGr3 || DIRENT.  */

#if defined (NeXT)
#include <string.h>
#else
#if defined (USG)
#include <memory.h>
#include <string.h>
#if defined (RISC6000)
extern void bcopy ();
#else /* RISC6000 */
#define bcopy(s, d, n) ((void) memcpy ((d), (s), (n)))
#endif /* RISC6000 */
#define rindex	strrchr

#else /* !USG */
#include <strings.h>

extern void bcopy ();
#endif /* !USG */
#endif /* !NeXT */
#endif /* !__GNU_LIBRARY */

#include "genmap.h"

#ifndef NULL
#define NULL 0
#endif

/* Global variable which controls whether or not * matches .*.
   Non-zero means don't match .*.  */
int noglob_dot_filenames = 1;

struct globlist
{
    struct globlist *next;
    StringList *names;
};
struct globval
{
    struct globval *next;
    StringC *name;
};



/* Return nonzero if PATTERN has any special globbing chars in it.  */
/* Return 2 for []*?|, and 1 for \()". */
int glob_pattern_p (char *pattern)
{
  register char *p = pattern;
  register char c;
  int has_quote_or_parens = 0;
  int	open = 0;

  while ((c = *p++) != '\0')
    switch (c)
      {
      case '?':
      case '*':
      case '|':
	return 2;

      case '[':		/* Only accept an open brace if there is a close */
	open++;		/* brace to match it.  Bracket expressions must be */
	continue;	/* complete, according to Posix.2 */
      case ']':
	if (open)
	  return 2;
	continue;      

      case '(':
      case ')':
	has_quote_or_parens = 1;
	continue;

      case '\"':
	has_quote_or_parens = 1;
	for (;;) {
	    c = *p++;
	    if (c == '\0')
		return 1;
	    if (c == '\"')
		break;
	    if (c == '\\')
		if (*p++ == '\0')
		    return 1;
	}
	break;

      case '\\':
	has_quote_or_parens = 1;
	if (*p++ == '\0')
	  return 1;
      }

  return has_quote_or_parens;
}

#define GLOB_MAX 1024

StringList *
#ifdef USE_RX
glob_directory (char *dir, int dir_length, struct rx_buf *pat_buf)
#else
glob_directory (char *dir, int dir_length, struct re_pattern_buffer *pat_buf)
#endif
{
  DIR *d;
  StringList *list;
  StringC **list_ptr;
  register struct direct *dp;
  struct re_registers regs;

  struct globval *globvals = NULL;
  struct globlist *globlists = NULL;
  int val_count = 0;

  struct stat stat_buf;
  if (stat (dir, &stat_buf) != 0 || !S_ISDIR (stat_buf.st_mode))
      return &NullSequence;

  d = opendir (dir);
  if (d == NULL)
    return NULL;

  if (dir_length && dir[dir_length-1] != '/')
      dir[dir_length++] = '/';

  while (1)
    {
      char *full_name = dir;
      int full_length, re_val;
      dp = readdir (d);
      if (dp == NULL)
	break;
      if (dp->d_ino == 0) continue;
      full_length = dir_length + dp->d_namlen;
      strcpy(full_name + dir_length, dp->d_name);
#ifdef USE_RX
      re_val = rx_match (pat_buf, full_name, full_length, 0, &regs);
#else
      re_val = re_match (pat_buf, full_name, full_length, 0, &regs);
#endif
      if (re_val == full_length) {
	  struct globval *nextval;
#if 0
	  full_name[full_length] = '\0'; /* Remove final '/'. */ 
	  // The idea was to allow foo/ to match a directory-name.
	  // The problem is that foo/* matches foo/ which is not
	  // what is wanted. One could look that the final char
	  // of the pattern is /, but what about a|b/ ?
	  if (re_val == full_length+1) {
	      /* Match includes final '/'. */
	      /* Check that full_name is a directory. */
	      struct stat stat_buf;
	      if (stat(full_name, &stat_buf) != 0) continue;
	      if ((stat_buf.st_mode & S_IFMT) != S_IFDIR) continue;
	      full_name[full_length] = '/'; /* Restore final '/'. */ 
	  }
#endif
	  nextval = (struct globval *) alloca (sizeof (struct globval));
	  nextval->next = globvals;
	  nextval->name = NewString(full_length, full_name);
	  globvals = nextval;
	  val_count++;
      }
      else if (re_val == REGEX_PREFIX_MATCH) {
	  struct globlist *nextlist;
	  StringList *list = glob_directory(full_name, full_length, pat_buf);
	  if (list == NULL || list == &NullSequence)
	      continue;
	  nextlist = (struct globlist *) alloca (sizeof (struct globlist));
	  nextlist->next = globlists;
	  nextlist->names = list;
	  globlists = nextlist;
	  val_count += StringsLen(list);
      }
  }
  (void) closedir (d);

  if (val_count == 0)
     return &NullSequence;
  list = NewVector(val_count);
  if (list == NULL) ;
  list_ptr = StringsPtr(list);
  while (globvals) {
      *list_ptr++ = globvals->name;
      globvals = globvals->next;
  }
  while (globlists) {
      StringC **glob_ptr = StringsPtr(globlists->names);
      int count = StringsLen(globlists->names);
      while (--count >= 0) *list_ptr++ = *glob_ptr++;
      delete globlists->names;
      globlists = globlists->next;
  }
  return list;
}

/* Do globbing on PATHNAME.  Return an array of pathnames that match,
   marking the end of the array with a null-pointer as an element.
   If no pathnames match, then the array is empty (first element is null).
   If there isn't enough memory, or if a file system error occurs,
   return NULL; `errno' has the error code.  */
StringList * glob_filename (char *pathname)
{
  int len = 0;
  int dir_len = 0;
  register char *p;
/*  int re_val;*/
#ifdef USE_RX
  struct rx_buf pat_buf;
#else
  struct re_pattern_buffer pat_buf;
#endif
/*  struct re_registers regs;*/
  char *msg;
  int nesting;
#ifdef USE_RX
  int old_syntax = re_set_syntax(RE_GLOB_STYLE|RE_NO_BK_PARENS|RE_NO_BK_VBAR
				 |RE_CHAR_CLASSES);
#else
  int old_syntax = re_set_syntax((noglob_dot_filenames * RE_FILE_GLOB)
				 |RE_GLOB_STYLE|RE_NO_BK_PARENS|RE_NO_BK_VBAR
				 |RE_CHAR_CLASSES);
  pat_buf.used = 0;
#endif
  pat_buf.buffer = NULL;
  pat_buf.allocated = 0;
  pat_buf.fastmap = NULL;
  pat_buf.translate = NULL;
#ifdef USE_RX
  msg = rx_compile_pattern(pathname, strlen(pathname), &pat_buf);
#else
  msg = re_compile_pattern(pathname, strlen(pathname), &pat_buf);
#endif
  if (msg)
    {
	fprintf(stderr, "[Misformed regular expression: %s - %s]\n",
		pathname, msg);
	return NULL;
    }
  re_set_syntax(old_syntax); /* Restore syntax. */

  /* Optimization: Check for directory prefix without globbing chars.
   * If there is such a prefix AND there are no non-nested '|' operators,
   * then use the prefix as an initial prefix. */

  for (p = pathname, nesting = 0; ; p++)
    {
      switch (*p)
	{
	  case 0:
	    break;
	  case '*':
	  case '+':
	  case '?':
	  case '[':
	  case '^':
	  case '$':
	    len = -1;
	    continue;
	  case '|':
	    len = -1;
	    if (nesting > 0)
	        continue;
	    dir_len = 0;
	    break;
	  case '/':
	    /* Don't allow prefix to be the entire filename. */
	    /* (Could do so, if we do a stat.) */
	    if (p[1] == 0)
	        len = -1;
	    else if (len >= 0)
		dir_len = len;
	    len++;
	    continue;
	  case '(':
	    nesting++;
	    continue;
	  case ')':
	    nesting--;
	    continue;
	  case '\\':
	    p++;
	    if (*p == 0)
		break;
	  default:
	    len++;
	    continue;
	}
      break;
    }
  if (dir_len + 2 >= GLOB_MAX)
      return NULL;
  char dir[GLOB_MAX];
  if (dir_len)
    {
      char *dir_ptr;
      for (dir_ptr = dir, p = pathname, len = dir_len; --len >= 0; )
	{
	  if (*p == '\\' || *p == '(' || *p == ')')
	      p++;
	  *dir_ptr++ = *p++;
	}
      *dir_ptr = 0;
    }
  else if (pathname[0] == '/') {
      dir[0] = '/';
      dir[1] = 0;
      dir_len = 1;
  }
  else {
      dir[0] = '.';
      dir[1] = 0;
      dir_len = 0;
  }

  return glob_directory(dir, dir_len, &pat_buf);
}

#ifdef TEST

main (argc, argv)
     int argc;
     char **argv;
{
  unsigned int i;

  for (i = 1; i < argc; ++i)
    {
      char **value = glob_filename (argv[i]);
      if (value == NULL)
	puts ("Out of memory.");
      else if ((int) value == -1)
	perror (argv[i]);
      else
	for (i = 0; value[i] != NULL; i++)
	  puts (value[i]);
    }

  exit (0);
}
#endif	/* TEST.  */
