/* glob.c (emx+gcc) -- Copyright (c) 1995-1996 by Eberhard Mattes */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fnmatch.h>
#include <glob.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/param.h>


struct globi
{
  glob_t *pglob;
  size_t index;
  int (*errfun)(const char *, int);
};


void _globfree (glob_t *pglob)
{
  size_t i, j;

  if (pglob->gl_pathv != NULL)
    {
      j = (pglob->gl_flags & GLOB_DOOFFS) ? pglob->gl_offs : 0;
      for (i = 0; i < pglob->gl_pathc; ++i)
        free (pglob->gl_pathv[i + j]);
      free (pglob->gl_pathv);
      pglob->gl_pathv = NULL;
    }
  pglob->gl_pathc = 0;
  pglob->gl_offs = 0;
  pglob->gl_flags = 0;
}


static int glob_add (struct globi *pg, const char *s0)
{
  char *s;

  if (s0 == NULL)
    s = NULL;
  else
    {
      s = _strdup (s0);
      if (s == NULL)
        goto nospace;
    }
  if (pg->index >= pg->pglob->gl_alloc)
    {
      char **v;

      pg->pglob->gl_alloc += 16;
      v = realloc (pg->pglob->gl_pathv, pg->pglob->gl_alloc * sizeof (char *));
      if (v == NULL)
        goto nospace;
      pg->pglob->gl_pathv = v;
    }
  pg->pglob->gl_pathv[pg->index++] = s;
  return 1;

nospace:
  pg->pglob->gl_pathc = pg->index;
  _globfree (pg->pglob);
  return 0;
}


static int glob_recurse (struct globi *pg, const char *base,
                         const char *pattern)
{
  const char *s, *lpat;
  size_t base_len, len;
  char path[MAXPATHLEN+1];
  DIR *dir;
  struct dirent *ent;
  int r, fnm_flags;

  /* TODO: Don't forget to check for DBCS lead bytes when adding
     support for backslashes! */

  s = strchr (pattern, '/');
  if (s == NULL)
    lpat = pattern;
  else
    {
      char *tmp = alloca (s - pattern + 1);
      memcpy (tmp, pattern, s - pattern);
      tmp[s - pattern] = 0;
      lpat = tmp;
    }
  base_len = strlen (base);
  if (base_len != 0)
    {
      if (base_len >= sizeof (path))
        {
          if (pg->errfun != NULL && pg->errfun (base, ENAMETOOLONG) != 0)
            return GLOB_ABEND;
          if (pg->pglob->gl_flags & GLOB_ERR)
            return GLOB_ABEND;
        }
      memcpy (path, base, base_len);
    }

  fnm_flags = 0;
  if (pg->pglob->gl_flags & GLOB_NOESCAPE)
    fnm_flags |= FNM_NOESCAPE;
  if (!(pg->pglob->gl_flags & GLOB_PERIOD))
    fnm_flags |= FNM_PERIOD;

  /* TODO: Optimize for lpat not containing global characters. */

  dir = _opendir (*base != 0 ? base : ".");
  if (dir == NULL)
    {
      if (pg->errfun != NULL && pg->errfun (base, errno) != 0)
        return GLOB_ABEND;
      if (pg->pglob->gl_flags & GLOB_ERR)
        return GLOB_ABEND;
      return 0;
    }
  while ((ent = _readdir (dir)) != NULL)
    {
      if (_fnmatch (lpat, ent->d_name, fnm_flags) == 0)
        {
          len = base_len + ent->d_namlen;
          if (len >= sizeof (path) - 1)
            {
              /* This cannot happen.  Handle it anyway. */
              if (pg->errfun != NULL && pg->errfun (base, ENAMETOOLONG) != 0)
                return GLOB_ABEND;
              if (pg->pglob->gl_flags & GLOB_ERR)
                return GLOB_ABEND;
            }
          if (s == NULL)
            {
              memcpy (path + base_len, ent->d_name, ent->d_namlen);
              if ((pg->pglob->gl_flags & GLOB_MARK) && (ent->d_attr & A_DIR))
                path[len++] = '/';
              path[len] = 0;
              if (!glob_add (pg, path))
                {
                  _closedir (dir);
                  return GLOB_NOSPACE;
                }
              pg->pglob->gl_pathc += 1;
            }
          else if (ent->d_attr & A_DIR)
            {
              memcpy (path + base_len, ent->d_name, ent->d_namlen);
              path[len++] = '/';
              path[len] = 0;
              r = glob_recurse (pg, path, s + 1);
              if (r != 0)
                {
                  _closedir (dir);
                  return r;
                }
            }
        }
    }
  _closedir (dir);
  return 0;
}


static int glob_compare (const void *x1, const void *x2)
{
  return strcoll (*(const char * const *)x1, *(const char * const *)x2);
}


int _glob (const char *pattern, int flags,
           int (*errfun)(const char *e_path, int e_errno), glob_t *pglob)
{
  struct globi g;
  size_t i, old, offs, prefix_len;
  int r;
  char prefix[4];

  g.pglob = pglob;
  g.errfun = errfun;
  offs = (flags & GLOB_DOOFFS) ? pglob->gl_offs : 0;
  if (flags & GLOB_APPEND)
    g.index = offs + pglob->gl_pathc;
  else
    {
      g.index = 0;
      pglob->gl_pathc = 0;
      pglob->gl_pathv = NULL;
      pglob->gl_flags = flags;
      pglob->gl_alloc = 0;
      for (i = 0; i < offs; ++i)
        if (!glob_add (&g, NULL))
          return GLOB_NOSPACE;
    }
  old = g.index;

  prefix_len = 0;
  if (strncmp (pattern + prefix_len, "//", 2) == 0)
    prefix_len += 2;            /* UNC */
  else
    {
      if (_fngetdrive (pattern + prefix_len) != 0)
        prefix_len += 2;
      if (pattern[prefix_len] == '/')
        prefix_len += 1;
    }
  memcpy (prefix, pattern, prefix_len);
  prefix[prefix_len] = 0;
  r = glob_recurse (&g, prefix, pattern + prefix_len);
  if (r != 0)
    return r;

  if (g.index == old)
    {
      if (flags & GLOB_NOCHECK)
        {
          if (!glob_add (&g, pattern))
            return GLOB_NOSPACE;
          pglob->gl_pathc += 1;
        }
      else
        return GLOB_NOMATCH;
    }

  if (!(flags & GLOB_NOSORT))
    qsort (pglob->gl_pathv + old, g.index - old, sizeof (pglob->gl_pathv[0]),
           glob_compare);

  if (!glob_add (&g, NULL))
    return GLOB_NOSPACE;
  return 0;
}
