/**************************************************************
 *
 *	CRISP - Custom Reduced Instruction Set Programmers Editor
 *
 *	(C) Paul Fox, 1989, 1990, 1991
 *
 *    Please See COPYLEFT notice.
 *
 **************************************************************/
# include	<stdio.h>
# include	<sys/types.h>
# include	<sys/stat.h>
# include	"machine.h"
# include 	"foxlib/chkalloc.h"
# include	"gdir.h"
# if defined(GOT_STDLIB)
# 	include	<stdlib.h>
# endif
# include	<string.h>
# if defined(MSDOS)
#	define	INCL_BASE
#	define	INCL_NOPM
#	include	<os2.h>
# endif
# define	SHE_TOO_MANY_NAMES	-1
# define	TRUE	1
# define	FALSE	0

# define	MAX_NAMES	1024
# define	NAME_SIZE	128

# if	defined(STANDALONE)
#	undef	chk_alloc
#	undef	chk_free
#	define	chk_alloc	malloc
#	define	chk_free	free
void	*malloc();
# endif
char	**shell_wild();
void	she_error();
char	*wild_expand();
char	*get_sname();
static	int	compare();

# ifdef	TESTING_WILD
main(argc, argv)
char	**argv;
{
	char	lbuf[256];
	char	**files;
	int	i;
	char **shell_expand();

	while (1) {
		printf("Filename: ");
		gets(lbuf);
		if (lbuf[strlen(lbuf) - 1] == '\n')
			lbuf[strlen(lbuf) - 1] = NULL;
		files = shell_expand(lbuf);
		if (files == (char **) NULL) {
			printf("Expansion error.\n");
			continue;
			}
		for (i = 0; files[i]; i++) {
			if (files[i][0])
				printf("%s ", files[i]);
			chk_free(files[i]);
			}
		chk_free(files);
		putchar('\n');
		}
}
# endif

# if	!defined(VMS)

/**********************************************************************/
/*   Under  DOS  &  OS/2  we  don't have a concept of user names and  */
/*   home directories so we can avoid the code for this.	      */
/**********************************************************************/
# if !defined(MSDOS)
# 	include	<pwd.h>
# endif

/**********************************************************************/
/*   Global to indicate why expansion failed.			      */
/**********************************************************************/
int	shell_error;

/**********************************************************************/
/*   The  following  function  attempts  to  emulate the Unix shells  */
/*   attempt  at  globbing.  We  return  an  array  of  pointers  to  */
/*   strings  containing  the  names  of the files which match. Both  */
/*   the  strings  and  the  array  need  to be freed by the caller,  */
/*   using chk_free().						      */
/**********************************************************************/
char **
shell_expand(file)
char	*file;
{	

# if defined(MSDOS)
	int	get_current_drive PROTO((void));
	void	set_current_drive PROTO((int));
	char	**array;

	/***********************************************/
	/*   If  we  have  a  drive designator at the  */
	/*   start  of  the  file  spec,  then change  */
	/*   drives,   and   strip   off   the  drive  */
	/*   selector.  Do  the  wild card expansion,  */
	/*   and  then  put the drive designator back  */
	/*   on.				       */
	/***********************************************/
	if (file[0] && file[1] == ':') {
		int	i;
		int	curdriv = get_current_drive();
		set_current_drive(file[0]);
		array = shell_wild(file+2);
		set_current_drive(curdriv);
		if (array == NULL)
			return NULL;
		for (i = 0; array[i]; i++) {
			char	buf[NAME_SIZE];
			if (array[i][0]) {
				buf[0] = file[0];
				buf[1] = ':';
				strcpy(buf+2, array[i]);
				strcpy(array[i], buf);
				}
			}
		return array;
		}
	/***********************************************/
	/*   No   drive   select   so   just   do   a  */
	/*   straightforward expansion.		       */
	/***********************************************/
	return shell_wild(file);
# else
	char name[128];
	struct passwd *pwd;
	unsigned short getuid();
	char	**shell_wild();
	char	**shell_delete();
	char	*cp;
       
	/***********************************************/
	/*   If   we   don't  have  any  username  to  */
	/*   expand,  then  go  straight  to the guts  */
	/*   of the globbing code.		       */
	/***********************************************/
	if (file[0] != '~')
		return shell_wild(file);
		
	/***********************************************/
	/*   Handle  special  case  of  ~/  ==>  home  */
	/*   directory of user.			       */
	/***********************************************/
	if (*++file == '/' || *file == NULL) {
		if (*file == '/')
			file++;
		pwd = getpwuid(getuid());
		}
	else {
		/***********************************************/
		/*   Handle  ~name/  Get  the  users name and  */
		/*   find the passwd entry for it.	       */
		/***********************************************/
		for (cp = name; *file && *file != '/'; )
			*cp++ = *file++;
		if (*file == '/')
			file++;
		*cp = NULL;
		pwd = getpwnam(name);
		}

	/***********************************************/
	/*   Dont  let  user  get  past  here  if  we  */
	/*   cannot do the '~' expansion.	       */
	/***********************************************/
	if (pwd == NULL)
		return (char **) NULL;
	/***********************************************/
	/*   Change  the  ~.../ to the real value and  */
	/*   pass the result to shell_wild().	       */
	/***********************************************/
	sprintf(name, "%s/%s", pwd->pw_dir, file);
	endpwent();
	return shell_wild(name);
# endif
}
/**********************************************************************/
/*   Following  function  is  called  to perform a low level regular  */
/*   expression  comparison  of  str  against 'file'. Return true if  */
/*   it matches and false if not.				      */
/**********************************************************************/
int
wild_match(file, str)
register char	*file;
register char	*str;
{	register char *cp;
	char	*start_str = str;
	int	low, hi;

	while (*str) {
		switch (*str) {
			case '*':
				if (start_str == str && file[0] == '.')
					return FALSE;
				str++;
				if (*str == NULL)
					return TRUE;
				for (cp = file + strlen(file) - 1;
				     cp >= file; cp--)
					if (wild_match(cp, str))
						return TRUE;
				return FALSE;
			case '?':
				if (*file++ == NULL)
					return FALSE;
				str++;
				break;
			case '[':
				if (*file == NULL)
					return FALSE;
				str++;
				while (1) {
					if (*str == ']' || *str == NULL)
						return FALSE;
					 hi = low = *str++;
					 if (*str == '-') {
						 hi = str[1];
						 str += 2;
						 }
					 if (*file >= low && *file <= hi) {
						 file++;
						 break;
						 }
					 }
				 while (*str)
					 if (*str++ == ']')
						 break;
				 break;
			default:
				if (*file == NULL || *file++ != *str++)
					return FALSE;
				break;
			}
		}
	return *str == NULL && *file == NULL;
}

/**********************************************************************/
/*   This  function  is  called  to  prune  the  globbed  names. The  */
/*   algorithm  can  end  up  putting  invalid  file  names into the  */
/*   array  so  we  make  sure  here  that they really refer to real  */
/*   file.  If  we find a file which doesn't exist, we set the first  */
/*   character to a null.					      */
/**********************************************************************/
char **
shell_delete(files, wild)
char	**files;
int	wild;
{	int	i;
	char *cp;
	
	if (files == NULL || !wild)
		return files;
	for (i = 0; files[i]; i++) {
		struct stat stat_buf;
		cp = files[i];
		if (cp[0] == NULL)
			continue;
		while (*cp == '/' && cp[1] == '/')
			cp++;
		if (stat(cp, &stat_buf) < 0)
			files[i][0] = NULL;
		else
			strcpy(files[i], cp);
		}
	return files;

}

/**********************************************************************/
/*   Function to perform the real work of filename globbing.	      */
/**********************************************************************/
char **
shell_wild(file)
char	*file;
{	int	i, j, k;
	int	ecnt;
	char	*cp;
	char	suffix[256];
	char	**names;
	int	name_cnt = 0;
	int	found_wild = FALSE;

	shell_error = 0;
	names = (char **) chk_alloc(MAX_NAMES * sizeof(char*));

	if (file[0] == '/') {
		names[0] = chk_alloc(NAME_SIZE);
		strcpy(names[0], "/");
		file++;
		name_cnt = 1;
		}

	do {
		extern char *strpbrk();
		char	*prefix;
		suffix[0] = NULL;
		
		/***********************************************/
		/*   Split  file  spec fred/... into fred and  */
		/*   '...'.				       */
		/***********************************************/
		file = get_sname(file, suffix);
		
		/***********************************************/
		/*   Allow  user  to  put  $env names in file  */
		/*   specs, i.e. the same as the shell.	       */
		/***********************************************/
		if (suffix[0] == '$') {
			cp = getenv(suffix+1);
			if (cp)
				strcpy(suffix, cp);
			}
		if (suffix[0] && strpbrk(suffix, "*?[")) {
			found_wild = TRUE;
			j = name_cnt;
			for (i = 0; i <= j; i++) {
				if (i == j) {
					if (j == 0)
						prefix = NULL;
					else
						break;
					}
				else {
					prefix = names[i];
					if (prefix[0] == NULL)
						continue;
					}
				for (ecnt = 0; ; ecnt++) {
					if ((cp = wild_expand(prefix, suffix, ecnt)) == NULL) {
						if (j)
							names[i][0] = NULL;
						break;
						}
					names[name_cnt] = chk_alloc(NAME_SIZE);
					if (prefix) {
						if (prefix[strlen(prefix)-1] == '/')
							sprintf(names[name_cnt],
							"%s%s", prefix, cp);
						else
							sprintf(names[name_cnt],
							"%s/%s", prefix, cp);
						}
					else
						strcpy(names[name_cnt], cp);
					if (++name_cnt >= MAX_NAMES-1) {
						she_error(name_cnt, names, name_cnt);
						return NULL;
						}
					}
				}
			}
		else {
			if (name_cnt == 0) {
				names[0] = chk_alloc(NAME_SIZE);
				strcpy(names[0], suffix);
				name_cnt = 1;
				}	
			else
				for (k = 0; k < name_cnt; k++) {
					strcat(names[k], "/");
					strcat(names[k], suffix);
					}
			}
		}
	while (file);
	
	/***********************************************/
	/*   Terminate array with null pointer.	       */
	/***********************************************/
	names[name_cnt] = NULL;
	
	/***********************************************/
	/*   Sort names into aplhabetical order.       */
	/***********************************************/
	qsort(names, name_cnt, sizeof(char *), compare);
	return shell_delete(names, found_wild);
}
static int
compare(p1, p2)
char **p1, **p2;
{
	return strcmp(*p1, *p2);
}

/**********************************************************************/
/*   Free memory allocated for the globbed name array.		      */
/**********************************************************************/
void
she_error(err, names, name_cnt)
int	err;
char	**names;
int	name_cnt;
{	register int	i;

	shell_error = err;
	for (i = 0; i < name_cnt; i++)
		chk_free(names[i]);
	chk_free((void *) names);
}
char *
wild_expand(prefix, suffix, cnt)
char	*prefix;
char	*suffix;
int	cnt;
{	static DIR	*dirp = NULL;
	struct dirent	*de;
	static char name[DIRSIZ+1];

	if (dirp == NULL) {  
		char *cp = prefix ? prefix : ".";  
		struct stat stat_buf;
		while (*cp == '/' && cp[1] == '/')
			cp++;
		if (stat(cp, &stat_buf) < 0 || (stat_buf.st_mode & S_IFDIR) == 0)
			return NULL;
		if ((dirp = opendir(cp)) == NULL) {
			return (char *) NULL;
			}
		}
	while ((de = readdir(dirp)) != (struct dirent *) NULL) {
		int	len = strlen(de->d_name);
		if (len > DIRSIZ)
			len = DIRSIZ - 1;
		memcpy(name, de->d_name, len);
		name[len] = NULL;
		if (wild_match(name, suffix))
			return name;
		}
	closedir(dirp);
	dirp = NULL;
	return (char *) NULL;

}
/**********************************************************************/
/*   Expand  a  filename  into a single thing (e.g. when ~ occurs or  */
/*   whatever).							      */
/**********************************************************************/
char *
expand_filename(cp)
char	*cp;
{	static char	*mem;
	char	**files;

	if (strpbrk(cp, "~*?[$") == NULL)
		return cp;

	if (mem) {
		chk_free((void *) mem);
		mem = NULL;
		}
	files = shell_expand(cp);
	if (files != NULL) {
		int	j;
		cp = NULL;
		for (j = 0; files[j]; j++) {
			if (cp == NULL && files[j][0])
				mem = cp = files[j];
			else
				chk_free(files[j]);
			}
		chk_free((void *) files);
		}
	return mem == NULL ? "" : mem;
}
char *
get_sname(file, buf)
register char	*file;
register char	*buf;
{
	if (*file == NULL)
		return (char *) NULL;
	while (*file && *file != '/')
		*buf++ = *file++;
	if (*file == '/')
		file++;
	*buf = NULL;
	return *file ? file : NULL;
}
# if defined(STANDALONE) && defined(MSDOS)
/**********************************************************************/
/*   Get current drive.						      */
/**********************************************************************/
int
get_current_drive()
{	USHORT cur_drive;
	ULONG	drives;
	DosQCurDisk(&cur_drive, &drives);

	return cur_drive + 'A' - 1;
}

/**********************************************************************/
/*   Set the current drive.					      */
/**********************************************************************/
void
set_current_drive(ch)
int	ch;
{
	if (islower(ch))
		ch = toupper(ch);
# if CR_DOS
	DosSelectDisk(ch - 'A' + 1);
# else
	{
	union	REGS	regs;

	regs.h.ah = SELECT;
	regs.h.dl = ch - 'A';
	intdos(&regs, &regs);
	}
# endif
}
# endif
# endif
