Subversion Repositories DevTools

Rev

Blame | Last modification | View Log | RSS feed

/* -*- mode: c; tabs: 4; -*- ************************************************
* Module name   : vpath.c
* Module type   : CMDFILE source file
* Environment(s): n/a
*
* Description:
*
    vpath --- dependencies search path support
*
* Version   Who      Date       Description
            APY      12/99      Created
            APY      14/03/00   fixpath's,vpath2
                                Check for abs path prior to scanning vpath.
            APY      30/11/00   - VPATH, wild arguments
                                -vlib()
                     11/10/04   vlint
                                vlib, host specification
*
* $Source: /cvsroot/device/DEVL/UTILS/CMDFILE/vpath.c,v $
* $Revision: 1.7 $ $Date: 2004/10/11 08:59:27 $ $State: Exp $
* $Author: ayoung $ $Locker:  $
*.........................................................................*/

#if defined(HAVE_DIRECT_H)
#include <direct.h>
#define  NAMLEN(dirent)     strlen((dirent)->d_name)
#elif defined(HAVE_DIRENT_H) || \
                defined(sun) || defined(linux) || defined(WIN32)
#include <dirent.h>
#define  NAMLEN(dirent)     strlen((dirent)->d_name)
#else
# define dirent             direct
# define NAMLEN(dirent)     (dirent)->d_namlen
# if defined(HAVE_SYS_NDIR_H)
#  include <sys/ndir.h>
# endif
# if defined(HAVE_SYS_DIR_H)
#  include <sys/dir.h>
# endif
# if defined(HAVE_NDIR_H)
#  include <ndir.h>
# endif
#endif

#include <unistd.h>
#include <ctype.h>
#include "cmdfile.h"

#ifdef MSDOS
#include <dos.h>            /* For intdos() */
#endif

typedef struct {
#define FICASE       0x0001
    int              gl_argc;
    char           **gl_argv;
} Glob_t;

#define HWIN32      0x01
#define HUNIX       0x02

static const char   *find_on_vpath(int flags, const char *name,
                        const char *vpath, char *buf);
static const char   *find_on_vpath2(int flags, const char *name,
                        const char *vpath, char *buf);
static const char   *next_path(const char *path, char sep,
                        const char *name, char *dir, char *buf);

static void          fixpath(const char *in, char *out, char sep);
static int           is_term(int c);
static int           is_slash(int c);
#if defined(MSDOS) || defined(WIN32)
static int           GetDisk(void);
#endif
static char         *GetCwd(char *out, int drive, char sep);

static int           Glob(int flags, const char *pPath,
                        Glob_t *pGlob, const char *pPattern);
static void          GlobFree(Glob_t *pGlob);
static void          GlobPrint(Glob_t *pGlob);

const char *         vpath_lib_default = NULL;
char                 vpath_sep = ';';           /*default*/

#if defined(unix)
int                  vpath_host = HUNIX;

#elif defined(WIN32) || defined(MSDOS)
int                  vpath_host = HWIN32;

#else
#error vpath: Unknown host ...
#endif

int do_clean_sep ( String_t *str, const char *s )
{
    char  gsep[2];
    char  name[ MACRO_MAX ];
    int   len = 0, len2;
    char  *i;

    if (expected(s++, ',') > 0 &&
            (len2 = pathargument(s, gsep, 2)) > 0)
    {
        len += 1+len2, s += len2;
        if (expected(s++, ',') > 0 &&
                (len2 = pathargument(s, name, MACRO_MAX)) > 0)
        {
            len += 1+len2, s += len2;

            /* Step through the input path */
            for (i = name; *i; i++)
            {
                if (is_slash(*i))
                    *i = gsep[0];
            }

            StringCat( str, name );
            return (len);
        }
    }
    return (-1);
}


int
vpath( String_t *str, const char *s, int flags )
{
    char    name[ PATH_MAX ], vpath[ MACRO_MAX ], result[ PATH_MAX ];
    char    gsep[2];
    int     len = 0, len2;
    const   char *p;

    if (expected(s++, ',') > 0 &&
            (len2 = pathargument(s, name, PATH_MAX)) > 0)
    {
        len = 1+len2, s += len2;
        if (expected(s++, ',') > 0 &&
                (len2 = pathargument(s, vpath, MACRO_MAX)) > 0)
        {
            len += 1+len2, s += len2;
            gsep[0] = 0;
            if ( *s++ == ','  && (len2 = pathargument(s, gsep, 2)) > 0)
            {
                len += 1+len2, s += len2;
            }

            if (vflg)
            {
                const char *type = 
                        (flags & VFLAG_LINT     ? "vlint" :
                        (flags & VFLAG_LIBRARY  ? "vlib"  :
                        (flags & VFLAG_GLOB     ? "vglob" : "vpath" )));

                verbose( " %s%s(%s,%s)", type,
                    (flags & VFLAG_OPTIONAL ? "3":
                        (flags & VFLAG_MUSTRESOLVE ? "2": "")),
                    name, vpath );
            }

            if ((p = find_on_vpath(flags, name, vpath, result)) == NULL)
            {
                if (flags & VFLAG_MUSTRESOLVE)
                {
                    if (strchr(vpath, '/') || strchr(vpath,'\\') ||
                              strchr(vpath, '.'))
                        p = NULL;
                    else
                    {
                        p = getenv(vpath);
                    }

                    /*
                    **  Generate a readable error message
                    **  Display each element of the path on a new line
                    */
                    {
                        int len;
                        const char *ptr;

                        message ( "ERROR: Unable to locate the file '%s' " \
                                  "using the path:\n", name);

                        p = (p ? p : vpath);
                        for ( ptr = p; *p ; p++  )
                        {
                            if ( *p == vpath_sep )
                            {
                                len = p - ptr;
                                if ( len )
                                {
                                    message ( "    %-*.*s\n", len, len, ptr  );
                                    len = 0;
                                }
                                ptr = p+1;
                            }
                        }

                        len = p - ptr;
                        if ( len )
                        {
                            message ( "    %-*.*s\n", len, len, ptr  );
                        }

                        fatalerr( "Unable to resolve '%s'\n", name );
                    }
                }

                else if ((flags & VFLAG_OPTIONAL) == 0)
                {
                    p = name;                   /* quote path */
                }
            }
            else
            {
                verbose( " path = %s", p );
            }
            if ( gsep[0] )
                fixpath( p, (char *)p, gsep[0]);
            StringPath( str, p );
            return (len);
        }
    }
    return (-1);
}


int
do_vhost( const char *s )
{
    char host[ 32 ];
    int len = 0, len2;

    if (expected(s++, ',') > 0 &&
            (len2 = argument(s, host, sizeof(host))) > 0)
    {
        len = 1+len2, s += len2;
        if (strcasecmp(host, "unix") == 0)
            vpath_host = HUNIX;
        else if (strcasecmp(host, "win32") == 0)
            vpath_host = HWIN32;
        else
        {
            fatalerr( "Unknown host '%s'\n", host );
        }
    }
    return (len);
}


int
do_vsep( const char *s )
{
    char sep[ 32 ];
    int len = 0, len2;

    if (expected(s++, ',') > 0 &&
            (len2 = argument(s, sep, sizeof(sep))) > 0)
    {
        len = 1+len2, s += len2;
        if (!strcasecmp(sep, ":") || !strcasecmp(sep, "colon"))
            vpath_sep = ':';
        else if (!strcasecmp(sep, ";") || !strcasecmp(sep, "semicolon"))
            vpath_sep = ';';
        else
        {
            fatalerr( "Unknown seperator '%s'\n", sep );
        }
    }
    return (len);
}


static char *
fcat(char *buf, const char *dir, char sep, const char *name)
{
    char  tbuf[ PATH_MAX ];
    char  *s = tbuf;
    int   fsize;

    (void) strncpy(tbuf, dir, PATH_MAX);
    s += (fsize = strlen(dir));
    if (fsize + strlen(name) + 1 >= PATH_MAX)
         fatalerr( "PATH_MAX(%d) exceeded on dir '%s'\n", PATH_MAX, buf );

    if ((tbuf != s) && !is_slash(s[-1]) && (fsize++ < PATH_MAX))
         *s++ = sep;
    *s = '\0';
    (void) strncpy(s, name, PATH_MAX-fsize);
    tbuf[ PATH_MAX-1 ] = '\0';
    (void) fixpath((const char *)tbuf, buf, sep);
    return (buf);
}


static int
x_exists( const char *buf )
{
    int     ret;

    ret = access( buf, F_OK );
    verbose( "   access(%s) : %d", buf, ret );
    return (ret);;
}


static int
file_access( char sep, const char *name, char *buf )
{
    (void) fixpath( name, buf, sep );
    return x_exists( buf );
}


static int
lib_access( const char *dir, char sep, const char *name, char *buf )
{
    char    *s;
    int     fsize;

    (void) fcat(buf, dir, sep, name);
    verbose( "  libsearch(%s%c%s=%s)", dir, sep, name, buf );
    s = buf + (fsize = strlen(buf));

    if (vpath_host == HUNIX)
    {                                           /* shared library */
        (void) strncpy(s, ".so", PATH_MAX-fsize);   
        buf[ PATH_MAX-1 ] = '\0';
        if (x_exists( buf ) == 0)
            return (0);

                                                /* static library */
        (void) strncpy(s, ".a", PATH_MAX-fsize);    
        buf[ PATH_MAX-1 ] = '\0';
        if (x_exists( buf ) == 0)
            return (0);

    } 
    else if (vpath_host == HWIN32) 
    {                                           /* static/implib */
        (void) strncpy(s, ".lib", PATH_MAX-fsize);  
        buf[ PATH_MAX-1 ] = '\0';
        if (x_exists( buf ) == 0)
            return (0);
    }    
    return (ENOENT);
}


static int
lint_access( const char *dir, char sep, const char *name, char *buf )
{
    char    *s;
    int     fsize;

    (void) fcat(buf, dir, sep, name);
    verbose( "  lintsearch(%s%c%s=%s)", dir, sep, name, buf );
    s = buf + (fsize = strlen(buf));

    (void) strncpy(s, ".lob", PATH_MAX-fsize);  /* lint object */
    buf[ PATH_MAX-1 ] = '\0';
    if (x_exists( buf ) == 0)
        return (0);

    (void) strncpy(s, ".lnt", PATH_MAX-fsize);  /* lint specification */
    buf[ PATH_MAX-1 ] = '\0';
    if (x_exists( buf ) == 0)
        return (0);

    return (ENOENT);
}


static int
glob_access(const char *dir, char sep, const char *name, char *buf)
{
    Glob_t  glob;

    verbose( "  globbing(%s, %s)", dir, name );
    if (Glob(0, dir, &glob, name) == 0)
    {
        int i;

        for (i=0; i<glob.gl_argc; i++)
        {
            fcat(buf, dir, sep, glob.gl_argv[i]);
            verbose( "  testing access(%s)", buf );
            if (access(buf, F_OK) == 0)
            {
                (void) GlobFree(&glob);
                return (0);
            }
        }
        (void) GlobFree(&glob);
     }
     return (ENOENT);
}


static const char *
find_on_vpath(
    int flags, const char *name, const char *vpath, char *buf )
{
    const char *ret;
                                                /* specified path */
    if ((ret = find_on_vpath2( flags, name, vpath, buf )) == NULL)
    {
        if (flags & VFLAG_LIBRARY)              /* default LIB path, if any */
            if (vpath_lib_default != NULL)
            {
                ret = find_on_vpath2( flags, name, vpath_lib_default, buf );
            }
    }
    return (ret);
}


static const char *
find_on_vpath2(
    int flags, const char *name, const char *vpath, char *buf )
{
    char  dir[ PATH_MAX ];
    const char *sp;
    char  sep;

    if (strchr(vpath, '/') || strchr(vpath, '\\') || strchr(vpath, '.'))
        sp = vpath;
    else if ((sp = getenv(vpath)) == NULL)
        sp = "";

    sep = (strchr(sp, '/') ? '/' : '\\');

    verbose( "  vpath(%s), sep=%c", sp, sep );

    if (file_access( sep, name, buf ) == 0 )    /* is path already absolute */
        return name;

    do {                                        /* scan vpath */
        sp = next_path( sp, sep, name, dir, buf );

        if (dir[0] == '\0')
            continue;                           /* empty specification */

        if (x_exists( buf ) == 0)               /* abs match ? */
            return buf;

        if ((flags & VFLAG_LINT) &&             /* lint search */
                lint_access(dir, sep, name, buf) == 0)
            return (buf);

        if ((flags & VFLAG_LIBRARY) &&          /* library search */
                lib_access(dir, sep, name, buf) == 0)
            return (buf);

        if ((flags & VFLAG_GLOB) &&             /* globbing */
                glob_access(dir, sep, name, buf) == 0)
            return buf;

    } while (sp != (char *)NULL);
    errno = ENOENT;
    return (NULL);                              /* no match */
}


/*
 *  Build next path
 */
static const char *
next_path(
    const char *path, char sep, const char *name, char *dir, char *buf)
{
    char  *d = dir;
    int   fsize = 0;

/* retrieve path */
    while (*path && *path != vpath_sep) {
        if (*path != ' ' || d != dir)           /* trim leading spaces */
            if (fsize < PATH_MAX) {
                *d++ = *path, fsize++;
            }
        path++;
    }

    while (fsize && d[-1] == ' ')               /* trim trailing spaces */
       d--, fsize--;

    if (fsize > 2 && dir[0] == '"' && d[-1] == '"') {
        fsize -= 2;                             /* strip quoted path */
        memmove( dir, dir+1, fsize );
        d = dir + fsize;
    }

                                                /* add seperator */
    if (fsize && !is_slash(d[-1]) && (++fsize < PATH_MAX))
        *d++ = sep;

    *d = '\0';                                  /* terminator */

/* build fullname */
    (void) fcat( buf, dir, sep, name );
    verbose( "  search(%s,%s=%s)", dir, name, buf );
    return (*path ? ++path : (char *)NULL);
}


/* fixpath ---
 *
 *  Takes as input an arbitrary path.  Fixes up the path by:
 *      1.  Convert relative path to absolute
 *      2.  Removing consecutive slashes
 *      3.  Removing trailing slashes
 *      4.  Making the path absolute if it wasn't already
 *      5.  Removing "." in the path
 *      6.  Removing ".." entries in the path (and the directory above them)
 */
static void
fixpath( const char *in, char *out, char sep )
{
    register const char *i = in;
    register char *o = out;

/* Convert relative path to absolute */
#if defined(MSDOS) || defined(WIN32)
    {
        int drive;

        if (*(i+1) == ':' &&
                ((*i >= 'a' && *i <= 'z') || (*i >= 'A' && *i <= 'Z'))) {
            if (*i >= 'a' && *i <= 'z')
                drive = *i - 'a';
            else drive = *i - 'A';
            *o++ = *i++;
            *o++ = *i++;
        } else {
            drive = GetDisk();
            *o++ = drive + 'A';
            *o++ = ':';
        }
        if (!is_slash(*i))
            o = GetCwd(o, drive, sep);
    }

#else   /*unix*/
    if (!is_slash(*i))
        o = GetCwd(o, -1, sep);
#endif

/* Step through the input path */
    while (*i != '\0')
    {
    /* Skip input slashes */
        if (is_slash(*i))
        {
            i++;
            continue;
        }

    /* Skip "." and output nothing */
        if (*i == '.' && is_term(*(i + 1)))
        {
            i++; continue;
        }

    /* Skip ".." and remove previous output directory */
        if (*i == '.' && *(i + 1) == '.' && is_term(*(i + 2)))
        {
        /* Don't back up over drive spec or consume ../.. */
            if (o > out+2 && strncmp(o-2, "..", 2) != 0)
            {
            /* Consume .. and parent directory */
                i += 2;
                while (--o > out+2 && !is_slash(*o))
                    ;
                continue;
            }
        }

    /* Copy path component from in to p */
        *o++ = sep;

    /* Copy next filename/directory */
        while (!is_term(*i))
            *o++ = *i++;
    }
    *o = '\0';
}


static int
is_term(int c)
{
    return c == '/' || c == '\\' || c == '\0';
}


static int
is_slash(int c)
{
    if (c == '/' || c == '\\')
        return 1;
    return 0;
}


#if defined(MSDOS)
static int
GetDisk(void)
{
#ifdef __WATCOMC__
    unsigned drive;
    _dos_getdrive(&drive);
    return drive - 1;
#else
    union REGS regs;
    regs.h.ah = 0x19;                           /* DOS Get Default Drive call */
    regs.h.al = 0;
    (void) intdos(&regs, &regs);
    return regs.h.al;
#endif
}

#elif defined(WIN32)
static int
GetDisk(void)
{
    char path[ PATH_MAX ];

    getcwd(path, PATH_MAX);
    return (toupper(path[0])-'A');              /* A=1, B=2 etc */
}
#endif


static char *
GetCwd(char *out, int drive, char sep)
{
#if defined(MSDOS)
    static   char cwd[ PATH_MAX ];
    register char *p, *o;

/* Get current work directory */
    if (*cwd == '\0')
    {
        union REGS  regs;

        regs.h.ah   = 0x47;
        regs.h.dl   = drive + 1;
    #ifdef __386__
        regs.x.esi  = (unsigned long) cwd;
    #else
        regs.x.si   = (unsigned long) cwd;
    #endif
        (void) intdos(&regs, &regs);
    #ifdef __WATCOMC__
        if (regs.w.cflag != 0) {
            errno = regs.w.ax;
            return out;
        }
    #else
        if (regs.x.cflag != 0) {
            errno = regs.x.ax;
            return out;
        }
    #endif
    }

/* Copy and convert path */
    for (p = cwd, o = out + 1; *p; p++)
         *o++ = is_slash(*p) ? sep : *p;
    *o = '\0';

/* Root path, don't insert "/", it'll be added later */
    if (*(out+1) != '\0')
        *out = sep;
    else *out = '\0';
    return (out);

#elif defined(WIN32)
    int len;

    out[0] = '\0';
    (void) _getdcwd(drive + 1, out, PATH_MAX);
    if ((len = strlen(out)) >= 2 && out[1] == ':')
    {
        memmove(out, (const char *)(out+2), len-1);
        len -= 2;
    }
    return (out + len);

#else
    out[0] = '\0';
    (void) getcwd(out, PATH_MAX);
    return (out + strlen(out));

#endif
}


/*--------------------------------------------------------------  */

static  int          Match(int, const char *, const char *);

/*----------------------------------------------------------------------------
    Synopsis:
        static int Glob
            (
                            int flags,
                const char *pPath,
                Glob_t *pGlob,
                const char *pPattern
            )
        static GlobFree(Glob_t *pGlob)

    MT-Level:
        Safe (only one thread may own the Glob_t structure)

    Purpose:
        The Glob function stores the number of matched path names
        into 'pGlob->gl_argc' and a pointer to a list of pointers
        to path names into 'pGlob->gl_argv'.

            typedef struct {
                int     gl_argc;
                char    **gl_argv;
            } Glob_t;

        The path names are sorted as encountered within the directory.
        The first pointer after the last path name is a NULL pointer.
        If the pattern does not match any path names, the returned
        number of matched paths is set to zero, and the contents
        of 'pGlob->gl_argv' is undefined.

        The argument 'pPath' is a pointer to a path name to be
        searched, using the argument 'pPattern' to be expanded. The
        glob function matches all accessible path names against
        this pattern and develops a list of all path names that
        match. In order to have access to a path name, glob requires
        search permission on every component of a path except the
        last, and read permission on each directory of any filename
        component of pattern that contains any of the following
        special characters:

                *   ?   [

        It is the caller's responsibility to create the structure
        pointed to by pGlob. The glob function allocates other space
        as needed, including the memory pointed to by gl_pathv. The
        GlobFree() function frees any space associated with pGlob
        from a previous call to Glob.

   Parameters:
        pPath,
        pGlob,
        pPattern

    Returns:
        The following values are returned by glob():

    Example:

        Glob_t    globbuf;

        Glob(".", &globbuf, "*.c");
        Print(&globbuf);
        GlobFree(&globuf);
*/
static int
Glob(int flags, const char *pPath, Glob_t *pGlob, const char *pPattern)
{
    struct  dirent *dp;
    char **ppNames;
    int total, cnt;
    DIR *pDir;
    char  dir_path[ PATH_MAX ];

    /*
    **  Remove trailing / from pPath
    */
    strcpy ( dir_path, pPath );
    cnt = strlen(dir_path);
    if ( is_slash( dir_path[cnt-1]) )
        dir_path[cnt-1] = 0;

    if ((pDir = opendir(dir_path)) == (DIR *)NULL)
    {
        return (errno);
    }
    ppNames = NULL;
    for (total=0, cnt=0; (dp = readdir(pDir)) != NULL;)
    {
        if (pPattern == NULL || Match(flags, pPattern, dp->d_name) == 0)
        {
        /* (re)alloc name storage */
            if (cnt >= total)
            {
                total += 256;
                ppNames = (char **)realloc(ppNames, total*sizeof(ppNames[0]));
                if (ppNames == NULL)
                    goto nomem;
            }

         /* Store directory details */
            verbose( "  .. matched(%s)", dp->d_name );
            if ((ppNames[cnt] = (char *)strdup(dp->d_name)) == (char *)NULL)
                goto nomem;
            cnt++;
        }
    }
    (void) closedir(pDir);
    if (cnt)
        ppNames[cnt] = NULL;
    pGlob->gl_argv = ppNames;
    pGlob->gl_argc = cnt;
    return (0);

nomem:
    fatalerr("Memory error globing %s", pPattern);
    pGlob->gl_argv = ppNames;
    pGlob->gl_argc = cnt;
    GlobFree(pGlob);
    pGlob->gl_argc = 0;
    return (ENOMEM);
}


/*
 *  Dump the glob list
 */
static void
GlobPrint(
    Glob_t *pGlob)
{
    if (pGlob && pGlob->gl_argc > 0)
    {
        register int cnt = pGlob->gl_argc;
        register int i;

        for(i=0; i < cnt; i++)
            printf("[%2d]: %s", i, pGlob->gl_argv[i]);
    }
}


/*
 *  Free the list of files:
 *      The Globfree() function frees any memory allocated by
 *      Glob() associated with 'pGlob'.
 */
static void
GlobFree(
    Glob_t *pGlob)
{
    if (pGlob && pGlob->gl_argc > 0)
    {
        register int cnt = pGlob->gl_argc;

        while (cnt > 0)
            free(pGlob->gl_argv[--cnt]);
        free(pGlob->gl_argv);
    }
}


/*      Match ---
 *        Pattern matching
 * Usage:
 *
    An ordinary character is a pattern that matches itself.  It
    can be any character in the supported character set except
    for NUL, and the following three special pattern characters.

    When unquoted and outside a bracket expression, the following
    three characters will have special meaning:

        \   quotes the next character.

        ?   is a pattern that will match any character.

        *   is a pattern that will match multiple characters.

        [   introduces a pattern bracket expression, that will
            matches a single collating element contained in the
            non-empty set of collating elements.

            The following rules and definitions apply to bracket
            expressions:

            1.  A bracket expression is either a matching list
                expression or a non-matching list expression.  It
                consists of one or more expressions.

            2.  A matching list expression  specifies  a  list  that
                matches any one of the expressions represented in the
                list.  The first character in the list  must not be
                the circumflex (^).  For example, [abc] is a pattern
                that matches any of the characters a, b or c.

            3.  A non-matching list expression begins with a circum-
                flex (^), and specifies a list that matches any char-
                acter or collating element except for the expressions
                represented in the list after the leading circumflex.
                For example, [^abc] is a pattern that matches any
                character or collating element except the characters
                a, b or c. The circumflex will have this special
                meaning only when it occurs first in the list,
                immediately following the left-bracket.

            4.  A range expression represents the set of collating
                elements that fall between two elements in the
                current collation sequence,  inclusively.  It is
                expressed  as the starting point and the ending point
                separated by a hyphen (-). For example, [a-z] is a
                pattern that matches any of the characters a to z
                inclusive.

                The hyphen character will be treated as itself if
                it occurs first (after an initial ^, if any) or
                last in the list, or as an ending range point in a
                range expression.  As examples, the expressions
                [-ac] and [ac-] are equivalent and match any of the
                characters a,  c,  or  -; [^-ac] and [^ac-] are
                equivalent and match any characters except a, c, or -;
                the expression [%--] matches any of the characters
                between % and - inclusive; the expression [--@]
                matches any of the characters between  -  and @
                inclusive.

        +   a bracket expression followed by this means one or
            more time.

        {   introduces a pattern set expression, that will
            match one or the colon seperated patterns.
*.........................................................................*/

static int
Match(int flags, const char *pPattern, const char *pString)
{
    register const char *p = pPattern;
    register const char *n = pString;
    register char ch;

#define __TOLOWER(c)    ((flags & FICASE) && isupper(c)?tolower(c):c)
    while ((ch = *p++) != '\0')
    {
        ch = __TOLOWER(ch);
        switch (ch)
        {
        case '?':       /* Match next character */
            if (*n == '\0')
                return -1;
            break;

        case '*':       /* Match next [n] characters */
            for (ch = *p++; ch == '?' || ch == '*'; ch = *p++, n++)
            {
                if (ch == '?' && *n == '\0')
                    return -1;
            }
            if (ch == '\0')
                return 0;
            {
                char c1 = (char)ch;

                c1 = __TOLOWER(c1);
                for (--p; *n != '\0'; ++n)
                    if ((ch == '[' || __TOLOWER(*n) == c1) &&
                            Match(flags, p, n) == 0)
                        return 0;
                return -1;
            }

        case '[':       /* Match one of the given set */
            {
                const char *start;
                int xnot, cnt=0;

                start = p;                  /* Start of set */
                if (*n == '\0')
                    return -1;
            more:;
                xnot = (*p == '!' || *p == '^');
                if (xnot)
                    ++p;
                ch = (int)*p++;
                for (;;)
                {
                    register char cstart = ch, cend = ch;

                    cstart = cend = __TOLOWER(cstart);

                    if (ch == '\0')         /* [... (unterminated) loses.  */
                        return -1;

                    ch = *p++;
                    ch = __TOLOWER(ch);

                    if (ch == '-' && *p != ']')
                    {
                        cend = *p++;
                        if (cend == '\0')   /* [... (unterminated) loses.  */
                            return -1;
                        cend = __TOLOWER(cend);
                        ch = *p++;
                    }

                    if (__TOLOWER(*n) >= cstart && __TOLOWER(*n) <= cend)
                        goto matched;

                    if (ch == ']')
                        break;
                }

            /* nomatch */
                if (!xnot)
                {
                /* Match expected, at least one previous match required */
                    if (!cnt)
                        return -1;
                    if (*p == '+')
                        p++;                /* remove attribute */
                    n--;                    /* push terminator */
                }
                else
                {
                /* No match expected, loop if required otherwise success */
                    if (*p == '+')
                    {
                        cnt++;
                        if (n[1])
                        {   /* Additional characters to scan */
                            n++, p = start;
                            goto more;
                        }
                        p++;                /* remove attribute */
                    }
                }
                break;

            matched:;
                /* Skip the rest of the [...] that already matched */
                while (ch != ']')
                {
                    if (ch == '\0')         /* [... (unterminated) loses.  */
                        return -1;
                    ch = *p++;
                }

                if (xnot)
                {
                /* No match excepted, if [!x]+ previous match required */
                    if (*p == '+')
                    {
                        if (!cnt)
                            return -1;
                        p++;                /* remove attribute */
                    }
                    n--;                    /* push terminator */
                }
                else
                {
                /* Match expected, loop if required otherwise success */
                    if (*p == '+')
                    {
                        cnt++;
                        if (n[1])
                        {   /* Additional characters to scan */
                            n++, p = start;
                            goto more;
                        }
                        p++;                /* remove attribute */
                    }
                }
            }
            break;

        case '{':       /* Set */
            {
                const char *se = p;
                char  buf[256];             /* XXX, max set size */
                char  *bp= buf;
                int   i;

                while (*se != /*{*/ '}')
                {
                    if (*se++ == '\0')
                        return -1;
                }
                se++;

                while (*p != /*{*/ '}')
                {
                    for (i=0, bp=buf; i < sizeof(buf)-1 &&
                            *p != '|' && *p != '}'; i++, *bp++ = *p++)
                        /*LINTED*/
                        ;
                    *bp = '\0';
                    if (*se != '\0') {
                        strncpy(bp, se, sizeof(buf)-1);
                        buf[ sizeof(buf)-1 ] = '\0';
                    }
                    if (Match(flags, buf, n) == 0)
                        return 0;
                    if (*p == '|')          /* treat '|' as sep */
                        p++;
                }
                return -1;
            }
            break;

        case '\\':      /* Quote */
            if ((ch = *p++) == '\0')        /* \... (unterminated) loses.  */
                return -1;

        default:
            if (ch != __TOLOWER(*n))
                return -1;
        }
        n++;
    }
#undef  __TOLOWER

    if (*n == '\0')
        return 0;
    return -1;
}

/*----------------------------------------------------------------------------
** FUNCTION           : from_env
**
** DESCRIPTION        : Get data from the users environment
**
**
** INPUTS             : str     - output string
**                      s       - input arguments
**
** RETURNS            : length of arguments consumed
**
----------------------------------------------------------------------------*/

int
from_env(String_t *str, const char *s)
{
    char varname[ 32 ];
    int len = 0, len2;
    char *sp;

    if (expected(s++, ',') > 0 &&
            (len2 = argument(s, varname, sizeof(varname))) > 0)
    {
        len = 1+len2, s += len2;
        sp = getenv(varname);
        if ( ! sp )
            fatalerr( "Environment Variable not found: '%s'\n", varname );

        verbose( " EnvVar %s  = %s", varname, sp );
        StringCat(str, sp);
    }
    return (len);
}