/* MS-DOS GLOB (3C) FUNCTION
 *
 * MS-DOS GLOB FUNCTION - Copyright (c) 1990,1,2 Data Logic Limited.
 *
 * This code is subject to the following copyright restrictions:
 *
 * 1.  Redistribution and use in source and binary forms are permitted
 *     provided that the above copyright notice is duplicated in the
 *     source form.
 *
 *    $Header: /usr/users/istewart/src/shell/sh2.1/RCS/glob.c,v 2.0 1992/04/13 17:39:09 Ian_Stewartson Exp $
 *
 *    $Log: glob.c,v $
 * Revision 2.0  1992/04/13  17:39:09  Ian_Stewartson
 * MS-Shell 2.0 Baseline release
 *
 */

#include <sys/types.h>			/* MS-DOS type definitions      */
#include <sys/stat.h>			/* File status definitions	*/
#include <stdio.h>			/* Standard I/O delarations     */
#include <stdlib.h>			/* Standard library functions   */
#include <string.h>			/* String library functions     */
#include <limits.h>			/* String library functions     */
#if defined(__WATCOMC__) || defined(__TURBOC__)
# ifdef USE_LOCAL_DIR_H
/*
 * We include "dir.h" instead of <direct.h> because we are using a special,
 * local version of the directory handling routines.
 */
#  include "dir.h"			/* Directory I/O routines	*/
#  define dirent		direct
#  define NAME_MAX		12
# else
#  ifdef __WATCOMC__
#   include <direct.h>
#  endif
#  ifdef __TURBOC__
#   include <dirent.h>
#   define F_OK			0
#   define NAME_MAX		12
#  endif
# endif
# include <dos.h>			/* For _dos_getfileattr()	*/
# include <io.h>			/* For access(), et.al.		*/
#else
# include <dirent.h>			/* Direction I/O functions	*/
# include <unistd.h>			/* Other functions		*/
#endif
#include <ctype.h>			/* Character types function	*/
#include <malloc.h>			/* Malloc functions		*/

#include "dosglob.h"


#define TRUE	(1)
#define FALSE	(0)
typedef unsigned char	bool;


#if defined (MSDOS) || defined (__OS2__)
#  if defined (OS2) || defined (__OS2__)
#    define INCL_DOSDEVICES
#    include <os2.h>			/* OS2 functions declarations       */
#  else
#    include <bios.h>			/* DOS BIOS functions		*/
#    include <dos.h>			/* DOS functions		*/
#  endif
#endif

/*
 * OS/2 2.x has these missing
 */

#ifndef S_IFMT
#  define	S_IFMT	0xf000	/* type of file				*/
#endif

#ifndef S_ISDIR
#  define S_ISDIR(m)	((((m) & S_IFMT) == S_IFDIR))
#endif

/*
 * Functions
 */

static int	_GP_SortCompare		_PROTO ((const void *, const void *));
static int	_GP_ExpandField		_PROTO ((char *, char *, glob_t *));
static int	_GP_ExpandMetaCharacters _PROTO ((char *, glob_t *));
static int	_GP_AddArgument		_PROTO ((char *, glob_t *));
static bool	_GP_MatchPattern	_PROTO ((char *, char *));

static char	*_GP_MetaChars = "?*[";
static char	*_GP_NullString = "";

#ifdef MSDOS
static int	_GP_use_backslash = 0;
static	int	_GP_GetNumberofFloppyDrives (void);

#if defined (OS2) || defined (__OS2__)
static void	 _dos_setdrive (unsigned int, unsigned int *);
static void	 _dos_getdrive (unsigned int *);
#endif

static char	*_GP_CheckForMultipleDrives	_PROTO ((char *));
#endif


/* Free up space */

void _CDECL	globfree (gp)
glob_t	*gp;
{
    int		i = (gp->gl_flags & GLOB_DOOFFS) ? gp->gl_offs : 0;

    while (i < gp->gl_pathc)
	free (gp->gl_pathv[i++]);

    free (gp->gl_pathv);
}

/* Main search function */

int _CDECL	glob (Pattern, flags, ErrorFunction, gp)
char	*Pattern;
int	flags;
int	(_CDECL *ErrorFunction) _PROTO ((char *, int));
glob_t	*gp;
{
    int		ReturnValue;
    char	*PatternCopy;
    char	*cp;

#ifdef MSDOS
    if (strchr(Pattern, '\\')) {
	_GP_use_backslash = 1;
    }
#endif

/* If no append mode - initialise */

    if (!(flags & GLOB_APPEND))
    {
	gp->gl_pathc = 0;
	gp->gl_pathv = (char **)NULL;
    }

    gp->gl_flags = flags;
    gp->gl_ef = ErrorFunction;

    if ((PatternCopy = alloca (strlen (Pattern) + 1)) == (char *)NULL)
	return GLOB_NOSPACE;

/* Expand and kill environment */

    if (ReturnValue = _GP_ExpandMetaCharacters (strcpy (PatternCopy, Pattern),
						gp))
	return ReturnValue;

/* Check for no finds.  If add value, strip out \ from the string */

    if ((gp->gl_pathc == 0) && (flags & GLOB_NOCHECK))
    {
#ifndef MSDOS
	cp = strcpy (PatternCopy, Pattern);

	while ((cp = strpbrk (cp, "?*[")) != (char *)NULL)
	{
	    if ((cp == PatternCopy) || (*(cp - 1) != '\\'))
		cp++;

	    else
		memmove (cp - 1, cp, strlen (cp) + 1);
	}
#endif
	if (ReturnValue = _GP_AddArgument (PatternCopy, gp))
	    return ReturnValue;
    }

/* Terminate string */

    if ((gp->gl_pathc != 0) && (ReturnValue = _GP_AddArgument ((char *)NULL, gp)))
	return ReturnValue;

/* Get the sort length */

    ReturnValue = (gp->gl_flags & GLOB_DOOFFS) ? gp->gl_offs : 0;

    if ((!(flags & GLOB_NOSORT)) && (gp->gl_pathc > 1))
	qsort (&gp->gl_pathv[ReturnValue], gp->gl_pathc, sizeof (char *),
	       _GP_SortCompare);

    return 0;
}

/* Compare function for sort */

static int	_GP_SortCompare (a1, a2)
const void	*a1, *a2;
{
    return strcmp (*(char **)a1, *(char **)a2);
}

/* Expand a field if it has metacharacters in it */

static int	_GP_ExpandField (CurrentDirectoryPattern, AppendString, gp)
char		*CurrentDirectoryPattern;	/* Prefix field		*/
char		*AppendString;			/* Postfix field    	*/
glob_t		*gp;
{
    int 		i;
    int			ReturnValue = 0;	/* Return Value		*/
    char		*FullFileName;		/* Search file name	*/
    char		*FileNameStart;
    char		*MatchString;		/* Match string		*/
    DIR			*DirHandler;
    struct dirent	*CurrentDirectoryEntry;
#ifdef MSDOS
    unsigned int	CurrentDrive;		/* Current drive	*/
    unsigned int	MaxDrives;		/* Max drive		*/
    unsigned int	SelectedDrive;		/* Selected drive	*/
    unsigned int	x_drive, y_drive;	/* Dummies		*/
    char		*DriveCharacter;	/* Multi-drive flag	*/
    char		SDriveString[2];
    int			namelen;
    unsigned int	attribute;

/* Convert file name to lower case */

#if defined (OS2) || defined (__OS2__)
    if (!IsHPFSFileSystem (CurrentDirectoryPattern))
	strlwr (CurrentDirectoryPattern);
#else
    strlwr (CurrentDirectoryPattern);
#endif

/* Search all drives ? */

    if ((DriveCharacter = _GP_CheckForMultipleDrives (CurrentDirectoryPattern))
		!= (char *)NULL)
    {
	_dos_getdrive (&CurrentDrive);	/* Get number of drives		*/
	_dos_setdrive (CurrentDrive, &MaxDrives);
	SDriveString[1] = 0;

	for (SelectedDrive = 1; SelectedDrive <= MaxDrives; ++SelectedDrive)
	{
	    _dos_setdrive (SelectedDrive, &x_drive);
	    _dos_getdrive (&y_drive);
	    _dos_setdrive (CurrentDrive, &x_drive);

/* Check to see if the second diskette drive is really there */

	    if ((_GP_GetNumberofFloppyDrives () < 2) && (SelectedDrive == 2))
		continue;

/* If the drive exists and is in our list - process it */

	    *DriveCharacter = 0;
	    *SDriveString = (char)(SelectedDrive + 'a' - 1);
	    strlwr (CurrentDirectoryPattern);

	    if ((y_drive == SelectedDrive) &&
		_GP_MatchPattern (SDriveString, CurrentDirectoryPattern))
	    {
		if ((FullFileName = alloca (strlen (DriveCharacter) + 3))
				== (char *)NULL)
		    return GLOB_NOSPACE;

		*DriveCharacter = ':';
		*FullFileName = *SDriveString;
		strcpy (FullFileName + 1, DriveCharacter);

		if (i = _GP_ExpandField (FullFileName, AppendString, gp))
		    return i;
	    }

	    *DriveCharacter = ':';
	}

	return 0;
    }
#endif

/* Get the path length */

    MatchString = strrchr (CurrentDirectoryPattern, '/');
#ifdef MSDOS
    {
	char	*MatchString2;

	if ((MatchString2 = strrchr (CurrentDirectoryPattern, '\\')) != NULL) {
	    if (MatchString) {
		if (MatchString2 > MatchString) {
		    MatchString = MatchString2;
		}
	    } else {
		MatchString = MatchString2;
	    }
	}
    }
    if ((MatchString == (char *)NULL) &&
	(*(CurrentDirectoryPattern + 1) == ':'))
	MatchString = CurrentDirectoryPattern + 1;
#endif

/* Set up file name for search */

    if ((MatchString == (char *)NULL) || (*MatchString == ':'))
    {
	if ((FullFileName = alloca (NAME_MAX + 7 +
				    strlen (AppendString))) == (char *)NULL)
	    return GLOB_NOSPACE;

	if (MatchString != (char *)NULL)
	    *(strcpy (FullFileName, "x:.")) = *CurrentDirectoryPattern;

	else
	    strcpy (FullFileName, ".");

	FileNameStart = FullFileName +
			(int)((MatchString != (char *)NULL) ? 2 : 0);
    }

/* Case of /<directory>/... */

    else if ((FullFileName = alloca (NAME_MAX + 4 + strlen (AppendString) +
			    (i = (int)(MatchString - CurrentDirectoryPattern))))
			== (char *)NULL)
	    return GLOB_NOSPACE;

    else
    {
	strncpy (FullFileName, CurrentDirectoryPattern, i);
	*((FileNameStart = FullFileName + i)) = 0;
#ifdef MSDOS
	if (_GP_use_backslash) {
	    strcpy (FileNameStart++, "\\");
	} else {
	    strcpy (FileNameStart++, "/");
	}
#else
	strcpy (FileNameStart++, "/");
#endif
    }

    MatchString = (MatchString == (char *)NULL) ? CurrentDirectoryPattern
						: MatchString + 1;

#ifdef MSDOS
    /*
     * Yuk.  Some versions of opendir() don't like trailing slashes, and
     * so we must remove them before calling opendir().  We must also put them
     * back after the opendir() ....
     *
     * However, we must not remove the trailing slash if it is the root
     * directory (phooey) ....
     */
    namelen = strlen(FullFileName);
    if (namelen > 1 && (FullFileName[namelen - 1] == '/' ||
			FullFileName[namelen - 1] == '\\') &&
	!((namelen == 3) && FullFileName[1] == ':')
	) {
	FullFileName[namelen - 1] = '\0';
    }
#endif

/* Search for file names */

    if ((DirHandler = opendir (FullFileName)) == (DIR *)NULL)
    {
	i = 0;

	if (((gp->gl_ef != NULL) && (*gp->gl_ef)(FullFileName, errno)) ||
	    (gp->gl_flags & GLOB_ERR))
	    i = GLOB_ABEND;

	return i;
    }

#ifdef MSDOS
    /*
     * Put the slash back ...
     */
    if (namelen > 0 && FullFileName[namelen - 1] == '\0') {
	if (_GP_use_backslash) {
	    FullFileName[namelen - 1] = '\\';
	} else {
	    FullFileName[namelen - 1] = '/';
	}
    }
#endif

/* Are there any matches */

    while ((CurrentDirectoryEntry = readdir (DirHandler)) !=
	    (struct dirent *)NULL)
    {
	if ((*CurrentDirectoryEntry->d_name == '.') && (*MatchString != '.'))
	    continue;

#ifdef MSDOS
	strlwr (CurrentDirectoryEntry->d_name);
#endif

/* Check for match */

	if (_GP_MatchPattern (CurrentDirectoryEntry->d_name, MatchString))
	{
	    strcpy (FileNameStart, CurrentDirectoryEntry->d_name);

/* If the postfix is not null, this must be a directory */

	    if (strlen (AppendString))
	    {
		struct stat		statb;
		char			*p;

/* If not a directory - go to the next file */

		if (stat (FullFileName, &statb) < 0 ||
		    !S_ISDIR (statb.st_mode & S_IFMT))
		    continue;

/* Are there any metacharacters in the postfix? */

		if ((p = strpbrk (AppendString, _GP_MetaChars)) == (char *)NULL)
		{

/* No - build the file name and check it exists */

#ifdef MSDOS
		    if (_GP_use_backslash) {
			strcat (strcat (FileNameStart, "\\"), AppendString);
		    } else {
			strcat (strcat (FileNameStart, "/"), AppendString);
		    }
#else
		    strcat (strcat (FileNameStart, "/"), AppendString);
#endif

		    if ((access (FullFileName, F_OK) == 0) &&
			(ReturnValue = _GP_AddArgument (FullFileName, gp)))
			break;
		}

/* Yes - build the filename upto the start of the meta characters */

		else
		{
#ifdef MSDOS
		    if ((p = strpbrk (p, "\\/")) != (char *)NULL)
			*(p++) = 0;
		    else
			p = _GP_NullString;
#else
		    if ((p = strchr (p, '/')) != (char *)NULL)
			*(p++) = 0;
		    else
			p = _GP_NullString;
#endif

/* Build the new directory name and check it out */

#ifdef MSDOS
		    if (_GP_use_backslash) {
			strcat (strcat (FileNameStart, "\\"), AppendString);
		    } else {
			strcat (strcat (FileNameStart, "/"), AppendString);
		    }
		    ReturnValue = _GP_ExpandField (FullFileName, p, gp);

		    if (p != _GP_NullString)
			if (_GP_use_backslash) {
			    *(--p) = '\\';
			} else {
			    *(--p) = '/';
			}
#else
		    strcat (strcat (FileNameStart, "/"), AppendString);
		    ReturnValue = _GP_ExpandField (FullFileName, p, gp);

		    if (p != _GP_NullString)
		       *(--p) = '/';
#endif

/* Check for errors */

		    if (ReturnValue)
			break;
		}
	    }

/* Process this file.  If error - terminate */
#ifdef MSDOS
	    else if (_dos_getfileattr (FullFileName, &attribute) == 0) {
		/*
		 * The next line is a horrible, ugly kludge, which is
		 * needed to get file globbing to work on systems (e.g.,
		 * Novell Netware) that set/use file attribute bits that
		 * are not used by DOS.
		 *
		 * Note that Novell Netware uses the volume label bit for
		 * execute-only files.  Phooey.  I'm not going to add
		 * code to support this, and so execute-only files will
		 * be excluded from file globbing.
		 */
		attribute &= 0x3F;
		if ((attribute & (_A_RDONLY | _A_SUBDIR)) &&
		    !(attribute & (_A_HIDDEN | _A_SYSTEM)) ||
		    ((attribute & ~_A_ARCH) == _A_NORMAL)) {
		    /*
		     * We only add normal or read-only files.  Hidden and
		     * system files are ignored.
		     */
		    if (ReturnValue = _GP_AddArgument (FullFileName, gp)) {
			break;
		    }
		}
	    }
#else
	    else if ((access (FullFileName, F_OK) == 0) &&
		     (ReturnValue = _GP_AddArgument (FullFileName, gp)))
		break;
#endif
	}
    }

    closedir (DirHandler);
    return ReturnValue;
}

/* Find the location of meta-characters.  If no meta, add the argument and
 * return.  If meta characters, expand directory containing meta characters.
 */

static int	_GP_ExpandMetaCharacters (file, gp)
char		*file;
glob_t		*gp;
{
    char	*p;
    int		ReturnValue;

/* No metas - add to string */

    if ((p = strpbrk (file, _GP_MetaChars)) == (char *)NULL)
    {
	if (access (file, F_OK) < 0)
	    return 0;

	return _GP_AddArgument (file, gp);
    }

/* Ok - metas, find the end of the start of the directory */

#ifdef MSDOS
    else if ((p = strpbrk (p, "\\/")) != (char *)NULL)
	*(p++) = 0;
#else
    else if ((p = strchr (p, '/')) != (char *)NULL)
	*(p++) = 0;
#endif

    else
	p = _GP_NullString;

/* Continue recusive match */

    ReturnValue = _GP_ExpandField (file, p, gp);

/* Restore if necessary */

    if (p != _GP_NullString)
#ifdef MSDOS
	if (_GP_use_backslash) {
	    *(--p) = '\\';
	} else {
	    *(--p) = '/';
	}
#else
       *(--p) = '/';
#endif

    return ReturnValue;
}

/* Add an argument to the stack - file is assumed to be a array big enough
 * for the file name + 2
 */

static int	_GP_AddArgument (file, gp)
char		*file;
glob_t		*gp;
{
    int		Offset;
    char	**p1;
    struct stat	FileStatus;

    Offset = gp->gl_pathc + ((gp->gl_flags & GLOB_DOOFFS) ? gp->gl_offs : 0);
    p1  = gp->gl_pathv;

/* Malloc space if necessary */

    if (gp->gl_pathc == 0)
	p1 = (char **)calloc (sizeof (char *), (50 + Offset));

    else if ((gp->gl_pathc % 50) == 0)
	p1 = (char **)realloc (p1, (Offset + 50) * (sizeof (char *)));

    if (p1 == (char **)NULL)
	return GLOB_NOSPACE;

/* OK got space */

    gp->gl_pathv = p1;

/* End of list ? */

    if (file == (char *)NULL)
	p1[Offset] = (char *)NULL;

    else
    {
#ifdef MSDOS
	if ((gp->gl_flags & GLOB_MARK) &&
	    ((file[strlen (file) - 1] != '/') ||
	     (file[strlen (file) - 1] != '\\')) &&
	    (stat (file, &FileStatus) == 0) && (S_ISDIR (FileStatus.st_mode)))
	    if (_GP_use_backslash) {
		strcat (file, "\\");
	    } else {
		strcat (file, "/");
	    }
#else
	if ((gp->gl_flags & GLOB_MARK) && (file[strlen (file) - 1] != '/') &&
	    (stat (file, &FileStatus) == 0) && (S_ISDIR (FileStatus.st_mode)))
	    strcat (file, "/");
#endif

	if ((p1[Offset] = strdup (file)) == (char *)NULL)
	    return GLOB_NOSPACE;

	strcpy (p1[Offset], file);

/* Increment counter */

	++(gp->gl_pathc);
    }

    return 0;
}

#ifdef MSDOS
/* Check for multi_drive prefix */

static char	*_GP_CheckForMultipleDrives (prefix)
char		*prefix;
{
    if (strlen (prefix) < 2)
	return (char *)NULL;

    if (((*prefix == '*') || (*prefix == '?')) && (prefix[1] == ':'))
	return prefix + 1;

    if (*prefix != '[')
	return (char *)NULL;

    while (*prefix && (*prefix != ']'))
    {
#ifndef MSDOS
	if ((*prefix == '\\') && (*(prefix + 1)))
	    ++prefix;
#endif
	++prefix;
    }

    return (*prefix && (*(prefix + 1) == ':')) ? prefix + 1 : (char *)NULL;
}

/* Some OS/2 functions to emulate the DOS functions */

#if defined (OS2) || defined (__OS2__)
static void	 _dos_getdrive (cdp)
unsigned int	*cdp;
{
    USHORT	cdr;
    ULONG	ndr;

    DosQCurDisk((PUSHORT)&cdr, (PULONG) &ndr);
    *cdp = (unsigned int)cdr;
}

static void	 _dos_setdrive (cdr, ndp)
unsigned int	cdr;
unsigned int	*ndp;
{
    ULONG	ulDrives;
    USHORT	usDisk;
    int		i;

    DosSelectDisk ((USHORT)cdr);

/* Get the current disk and check that to see the number of drives */

    DosQCurDisk (&usDisk, &ulDrives);        /* gets current drive        */

    for (i = 25; (!(ulDrives & (1L << i))) && i >= 0; --i)
	continue;

    *ndp = (unsigned int)(i + 1);
}
#endif

/* Return the number of floppy disks */

static	int	_GP_GetNumberofFloppyDrives ()
{
#if defined (OS2) || defined (__OS2__)
    BYTE	nflop = 1;

    DosDevConfig (&nflop, DEVINFO_FLOPPY, 0);

    return nflop;
#else
    return ((_bios_equiplist () & 0x00c0) >> 6) + 1;
#endif
}
#endif

/*
 * Pattern Matching function
 */

static bool	_GP_MatchPattern (string, pattern)
char		*string;		/* String to match                  */
char		*pattern;		/* Pattern to match against         */
{
    register int	cur_s;		/* Current string character         */
    register int	cur_p;		/* Current pattern character        */

/* Match string */

    while (cur_p = *(pattern++))
    {
	cur_s = *(string++);		/* Load current string character    */

        switch (cur_p)			/* Switch on pattern character      */
        {
            case '[':			/* Match class of characters        */
            {
                while(1)
                {
                    if (!(cur_p = *(pattern++)))
			return 0;

                    if (cur_p == ']')
			return FALSE;

                    if (cur_s != cur_p)
                    {
                        if (*pattern == '-')
                        {
                            if(cur_p > cur_s)
                                continue;

                            if (cur_s > *(++pattern))
                                continue;
                        }
                        else
                            continue;
                    }

                    break;
                }

                while (*pattern)
                {
                    if (*(pattern++) == ']')
                        break;
                }

		break;
            }

            case '?':			/* Match any character              */
            {
                if (!cur_s)
		    return FALSE;

                break;
            }

            case '*':			/* Match any number of any character*/
            {
                string--;

                do
                {
                    if (_GP_MatchPattern (string, pattern))
			return TRUE;
                }
                while (*(string++));

		return FALSE;
            }

#ifndef MSDOS
            case '\\':			/* Next character is non-meta       */
            {
                if (!(cur_p = *(pattern++)))
		    return FALSE;
            }
#endif

            default:			/* Match against current pattern    */
            {
                if (cur_p != cur_s)
		    return FALSE;

                break;
            }
        }
    }

    return (!*string) ? TRUE : FALSE;
}

/*
 * Test program
 */

#ifdef TEST
main (int argc, char **argv)
{
    int		i;

    for (i = 0; i < argc; i++)
	printf ("Arg %d = <%s>\n", i, argv[i]);
}
#endif
