/*
;    File              : $Workfile: SUPPORT.C$
;
;    Description       :
;
;    Original Author   : DIGITAL RESEARCH
;
;    Last Edited By    : $CALDERA$
;
;-----------------------------------------------------------------------;
;    Copyright Work of Caldera, Inc. All Rights Reserved.
;      
;    THIS WORK IS A COPYRIGHT WORK AND CONTAINS CONFIDENTIAL,
;    PROPRIETARY AND TRADE SECRET INFORMATION OF CALDERA, INC.
;    ACCESS TO THIS WORK IS RESTRICTED TO (I) CALDERA, INC. EMPLOYEES
;    WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF
;    THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN CALDERA, INC. WHO
;    HAVE ACCEPTED THE CALDERA OPENDOS SOURCE LICENSE OR OTHER CALDERA LICENSE
;    AGREEMENTS. EXCEPT UNDER THE EXPRESS TERMS OF THE CALDERA LICENSE
;    AGREEMENT NO PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED,
;    COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED,
;    CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST,
;    TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF
;    CALDERA, INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT
;    AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND
;    CIVIL LIABILITY.
;-----------------------------------------------------------------------;
;
;    *** Current Edit History ***
;    *** End of Current Edit History ***
;
;    $Log$
;
;    ENDLOG
*/

/*
	File		SUPPORT.C
	Title		SUPPORT routines for command.com 

Revision History:-
==================

Date       Description
-----------------------------------------------------------------------------
10 Apr 86  EQU function now makes a case independant match.
29 Apr 86  Single $ preceeds the screen control variables
           Fix bug where if the last character of a screen control
           variable was an octal character it was not printed
 6 May 86  yes() now requires a default to be specified previously
           it assumed the default was NO.
21 May 86  Fixed bug with yes() function when default is NO.
22 May 86  Allow the screen control codes to be patched to longer strings
           Upd`te ZAP_SPACES to delete all white space
Version 1.2
===========
31 Jul 86  Conditionally remove functions that cannot be fully supported
           in a CDOS version of COMMAND.COM
 4 Aug 86  ANSI C compatibility MSC /W1
30 Oct 86  FREE_DRIVE now forces the drive to point to the root
           directory of the current drive.
17 Feb 87  Rewrote repwild() function to fix minor problems 
   Aug 87  Make all routines PASCAL calling convention  (for Concurrent 6)
26 Oct 87  Update the F_CHECK routine to support multiple switches ie
           "/wc" as well as "/w /c".
27 Oct 87  Stop F_CHECK from forcing the input line to lower case.
10 Nov 87  Modify F_CHECK flag zapping
18 Nov 87  Prevent GET_FILENAME copying a filename > than MAX_PATHLEN
16 Dec 87  Update D_CHECK to make fewer system calls.
29 Feb 88  Tidy the REPWILD routine - No Functional Change
15 Mar 88  Use ANSI Escape sequences for DOS Plus
27 Apr 88  Restore Range check to D_CHECK routine
25 May 88  Print a CR after a CLS to reset the current column count.
26 May 88  Change the Default HighLight and LowLight strings to NULL
           for DOSPLUS. 
27 May 88  Added string undefs.
23 Jun 88  Add new ONOFF function for XXX [=] ON|OFF parsing 
28 Jun 88  Correct bug in YES routine for FAR messages
 1 Jul 88  Support Control-C from YES for TMP.
 6 Jul 88  Support the CON:filename syntax for COPY.
19 Sep 88  Enhance error checking on the F_CHECK routine. Now traps the
           condition when switchar is at the end of the line.
 1 Dec 88  Convert all path parsing routines to be KANJI friendly.
21 Dec 88  Add generic ISDEV routine to SUPPORT.C
21 Dec 88  "*" to "*.*" conversion moved to CMD_DIR. 
18 Apr 89  c_write: write to STDERR if err_flag set
6 Jun 89   Correct GET_FILENAME to parse path passwords.
6 Jun 89   echo: check for endline after on/off.
10 Aug 89  get rid of sysdate_fmt (year does not need to be %04d, which
           upsets Japanese day)
10 Aug 89  day_name_len for day_names array elements
22 Aug 89  Japanese dayname comes after date
           Remove day_name_len & work out length from array.
11 Sep 89  strupr(), strlwr() and stricmp() are now Kanji aware.
           toupper() is now in DOSIF.ASM and uses international routine.
16 Oct 89  Kanji specific routines replaced with DBCS routines.
30 Oct 89  "Internal Error" moved to message.c (and no longer resident)
15 Dec 89  errors 50-72 give "Network error" rather than "Internal error"
6-Mar-90   Watcom C v 7.0
13-Mar-90  Output CR/LF after errors
4-May-90   append_slash added
4-May-90   get_filename stops at ';' unless followed by pathchar(s)
           ("append;" bug)
20-Sep-90  Created skip_char() and copy_char().
           Created is_blank() and amended deblank(), zap_spaces(),
           is_filechar() and is_pathchar() to use it.
13-Mar-91  changed nofiles() function to cope with DIR.. on NOVELL drives.
18-Mar-91  tolower() ( and therefore strlwr() ) is now aware of TURKISH
           capital I's with dots. 
6-Aug-91   prompt_exec() function no longer uses the heap. This screwed us
           up if the file to be execed was a batch file.
09-Jun-92  is_pathchar and d_check now cope with drive '[', etc, as used
           by some novell applications. See also INVALID_DRV in command.h.
18-Jun-92  Added optional_line() function for '?' support in batch files.
-----------------------------------------------------------------------------
*/

#include	"defines.h"
#include	<setjmp.h>
/*#include	<string.h>*/

#if defined(MWC) && defined(strlen)
#undef strcmp			/* These are defined as macros in string.h */
#undef strcpy			/* which are expaneded in line under */
#undef strlen			/* Metaware C. These undefs avoid this. */
#endif

#include	<portab.h>
#include	<mserror.h>
#if defined(CDOSTMP)
#include	<ccpm.h>
#include	<sysdat.h>
#endif

#include	"command.h"
#include	"toupper.h"
#include	"dosif.h"
#include	"global.h"
#include	"dos.h"

EXTERN jmp_buf break_env;

EXTERN VOID CDECL printf(BYTE *, ...);
EXTERN VOID CDECL eprintf(BYTE *, ...);
EXTERN VOID CDECL sprintf(BYTE *, BYTE *, ...);
#if defined(CDOSTMP)
EXTERN VOID CDECL int_break(VOID);		/* COM.C Internal Break */
#endif

MLOCAL VOID screen(BYTE *, BYTE *);
MLOCAL VOID outs(BYTE *);
GLOBAL VOID crlf(VOID);
GLOBAL VOID putc(BYTE);
GLOBAL VOID c_write(BYTE *, UWORD);
GLOBAL BYTE * fptr(BYTE *);
GLOBAL BYTE * day_names(UWORD);

/*.pa*/
/*
 *	The following screen control sequences can be redefined or removed
 *	by entries in the environment or the OEM can patch these areas in the
 *	COMMAND.COM object and change the default values.
 *
 */
#define CLS_KEY 	"$CLS=" 	/* CLS entry in environment	    */
#define REVON_KEY	"$ON="		/* REVON entry in environment	    */
#define REVOFF_KEY	"$OFF=" 	/* REVOFF entry in environment	    */

#ifdef DOSPLUS
/*
 *	For DOSPLUS the default screen control sequences are for an
 *	ANSI Terminal (IBM Sub-Set).
 */
#define CLS_DEF 	"\033[2J\0***"	/* Default CLS string "ESC [2J"*/
#define REVON_DEF	"\0********"	/* Default REVON string NULL   */
#define REVOFF_DEF	"\0********"	/* Default REVOFF string NULL  */

EXTERN BOOLEAN CDECL int10_cls(VOID);	/* Dirty Clear Screen Subroutine    */
#else
/*
 *	For Concurrent DOS the default screen control sequences are for a
 *	standard VT52 Terminal.
 */
#define CLS_DEF 	"\033E\0*****"	/* Default CLS string "ESC E"	    */
#define REVON_DEF	"\033p\0*****"	/* Default REVON string "ESC p"     */
#define REVOFF_DEF	"\033q\0*****"	/* Default REVOFF string "ESC q"    */
#endif
					/* Invalid FileName Characters as   */
					/* specified by the IBM DOS Tech    */
					/* Reference Page 6-96 "func 29"    */
MLOCAL BYTE	invalid_filechar[] = "*?\\.:;,=+<>|/\"[]";

/*.pa*/
/*
 *	TIME AND DATE SUPPORT ROUTINES
 *	==============================
 *
 *	The following routines print the TIME and DATE using the international
 *	data. Two sets of routines are provided to display the SYSTEM Time and
 *	Date the second routine displays FILE based time and date.
 */
MLOCAL BYTE	date_fmt []    = "%2d%c%02d%c%02d";

GLOBAL VOID disp_filetime(time)
unsigned time;
{
	WORD	h, m;
	BYTE	ap;
	
	ap = ' ';			/* assume no AM/PM used */
	h = (time >> 11);
	m = (time >> 5) & 0x3f;
	if (country.ampm == 0)		/* if anglo saxon fashion */
	{				/* need to convert things */
	    ap = 'a';			/* assume morning */
	    if (h == 0) 		/* 00:00 through 00:59 */
		h = 12; 		/*    is special... */
	    else if (h >= 12)		/* test if afternoon */
	    {
		ap = 'p';		/* mark as afternoon */
		if (h > 12)		/* if 13:00 through 23:59 */
		    h -= 12;		/* need to make " 1:00p" */
	    }
	}

	printf ("%2d%c%02d%c",
		h, country.dtime[0],	/* print hour, delimiter */
		m, ap); 		/* print minute, am/pm (if enabled) */
}

GLOBAL VOID disp_filedate(date)
unsigned date;
{
	WORD	y, m, d;
	WORD	b;

	y = ((date >> 9) + 80)%100;
	m = (date >> 5) & 0x0f;
	d = date & 0x1f;
	b = country.ddate[0];

	switch (country.dt_fmt)
	{
	 case 1:			/* European format dd/mm/yy */
	    printf (date_fmt, d,b,m,b,y);
	    break;

	 case 2:			/* Japanese format yy/mm/dd */
	    printf (date_fmt, y,b,m,b,d);
	    break;

	 default:			/* US format mm/dd/yy for the rest*/
	    printf (date_fmt, m,b,d,b,y);
	    break;

	};
}

GLOBAL VOID disp_systime()
{
	SYSTIME  time;
	WORD	b = country.dtime[0];
		
	ms_gettime(&time);
	printf ("%2d%c%02d%c%02d.%02d",
		time.hour, b,		/* print hour, delimiter */
		time.min, b,		/* print minute, delimiter */
		time.sec, time.hsec);	/* Print the second and Hundredths */
}


/* Return address of null terminated day name, given index (day). */

GLOBAL BYTE * day_names(day)
UWORD	day;			/* day of week: 0=sunday, 1=monday, .. */
{
    switch (day)
    {
    case 0:	return SUN_D;
    case 1:	return MON_D;
    case 2:	return TUE_D;
    case 3:	return WED_D;
    case 4:	return THU_D;
    case 5:	return FRI_D;
    default:	break;
    }

    return SAT_D;
}


GLOBAL VOID disp_sysdate()
{
	SYSDATE date;
	WORD	y, m, d;
	WORD	b;

	ms_getdate(&date);		/* Get the current date */
	y = date.year;
	m = date.month;
	d = date.day;
	b = country.ddate[0];

	if (country.dt_fmt != 2)	/* Japanese day comes after date */
	    printf("%s ", day_names(date.dow));
	
	switch (country.dt_fmt)
	{
	 case 1:			/* European format dd/mm/yy */
	    printf (date_fmt, d,b,m,b,y);
	    break;

	 case 2:			/* Japanese format yy/mm/dd */
	    printf (date_fmt, y,b,m,b,d);
	    printf(" %s", day_names(date.dow));
	    break;

	 default:			/* US format mm/dd/yy for the rest*/
	    printf (date_fmt, m,b,d,b,y);
	    break;
	};
}

/*.pa*/
/*
 *	Screen handling routines to CLEAR the screen and emphasise text
*/

GLOBAL VOID CDECL cmd_cls()
{
#if defined(DOSPLUS)
 	if(!int10_cls())			/* If no console device is */
	    screen(CLS_KEY, CLS_DEF);		/* active use the default  */
						/* $CLS string.		   */
#else						/* For Concurrent DOS never*/
	screen(CLS_KEY, CLS_DEF);		/* use INT 10		   */
#endif						/* Finally print a CR to   */
	putc('\r');				/* reset the internal	   */
						/* Column count.	   */
}

GLOBAL VOID revon()
{
	screen(REVON_KEY, REVON_DEF);
}

GLOBAL VOID revoff()
{
	screen(REVOFF_KEY, REVOFF_DEF);
}

MLOCAL VOID screen(key, def)
BYTE	*key;		/* Key name to match in the environment */
BYTE	*def;		/* Default string to output if no match */
{
REG BYTE *cp;

	if(!env_scan(key, cp = (BYTE *)heap()))
					/* and then search for the key  */
	    outs(cp);			/* output the string on a match */
	else				/* otherwise use the default	*/
	    printf(def);		/* supplied on entry		*/
}

/*
 *	This string output function will output a string containing
 *	a C format imbedded Octal number. This is mainly used for the
 *	CLS function.
 */
MLOCAL VOID outs(s)
BYTE *s;
{
BYTE	b = 0;
REG WORD f = 0;

	for(; *s; s++) {
		if(f) { 			/* Generating an OCTAL number*/
		    if(*s >= '0' && *s < '8') { /* check for a valid number  */
			b <<= 3;		/* and flush the buffer if it*/
			b += *s - '0';		/* is illegal. Otherwise add */
		    }				/* to the buffer and flush   */
		    else {			/* after three characters    */
			if(f != 3)
			    putc(b);
			putc(*s);
			f = 0;
			continue;
		    }

		    if(f-- == 1)
			putc(b);

		    continue;
		}

		if(*s == '\\') {
		    f = 3;		/* Initialise the Character count */
		    b = 0;		/* Zero the display value	  */
		    continue;
		}

		putc(*s);
	}

	if(f && b)
	    putc(b);
}


/*.pa*/
/*
 *	GENERAL PURPOSE STRING MANIPULATION ROUTINES
 *	============================================
 *
 */
GLOBAL BYTE tolower(b)
BYTE b;
{
	if (b==0x8D) return('i'); /* For turkish dotted capital I */
	return((b < 'A' || b > 'Z') ? b : b + 0x20);
}

GLOBAL BOOLEAN isdigit(b)
BYTE b;
{
	return (b >= '0' && b <= '9');
}

GLOBAL BYTE * skip_char(s)
REG BYTE *s;
{
	s++;
	if (dbcs_lead(*(s - 1)) && *s >= ' ')
	    s++;
	return(s);
}

GLOBAL BYTE * copy_char(dest, source)
REG BYTE **dest, **source;
{
	if (dbcs_lead(**source))
	{
	    if (*(*source + 1) >= ' ')
	    {
		*(*dest)++ = *(*source)++;
		*(*dest)++ = *(*source);
	    }
	}
	else
	{
	    *(*dest)++ = *(*source);
	}
	(*source)++;
	return(*source);
}

/* Check if character in string is blank.
   Return size in bytes of blank character, zero if not blank character. */

GLOBAL WORD is_blank(s)
REG BYTE *s;
{
WORD blank_size;

	if (*s == ' ' || *s == '\t')
	    blank_size = 1;
	else if (dbcs_expected() && *s == 0x81 && *(s + 1) == 0x40)
	    blank_size = 2;		/* KANJI space */
	else
	    blank_size = 0;
	return(blank_size);
}

GLOBAL BYTE * deblank(s) 		/* scan off leading white space */
REG BYTE *s;				/* starting address of scan */
{
REG WORD blank_size;

	while ((blank_size = is_blank(s)) != 0)
	    s += blank_size;
	return (s);			/* return deblanked string */
}

#if !(defined(MSC) || defined(MWC) || defined(TURBOC) || defined(WATCOMC))
GLOBAL BYTE * strchr(s, b)
BYTE *s, b;
{
	while(b != *s && *s)
	    s++;

	if(*s)
	    return s;
	else
	    return NULL;
}
#endif

/* Convert all uppercase characters in ASCIIZ string to lowercase. 
   If country code is JAPAN Kanji character code pairs (ie 8 bit Kanji
   escape code followed immediatly by 8 bit Kanji character code) are 
   not changed. */

GLOBAL BYTE 	*strlwr(s)
BYTE	*s;
{
REG BYTE *bp;


    if (dbcs_expected())		/* are we looking out for DBCS? */
    {					/*  yes - DON'T CHANGE DBCS CODES */
	for (bp = s; *bp; bp++)
	{
    	    if (dbcs_lead(*bp))		/* is this first of a DBCS pair? */
	    {
	    	bp++;			/*  yes - skip over it */
		if (*bp == '\0')	/* it is followed by its partner? */
		    break;		/*  no - invalid DBCS, exit loop */
	    }
	    else
	    	*bp = tolower(*bp);	/*  no - lower case it */
	}
    }
    else
    	for (bp = s; *bp; bp++)
    	    *bp = tolower(*bp);

    return s;
}



/* Convert all lowercase characters in ASCIIZ string to uppercase. 
   Double byte characters are not changed. */

GLOBAL BYTE 	*strupr(s)
BYTE	*s;
{
REG BYTE *bp;


    if (dbcs_expected())		/* are we looking out for DBCS? */
    {					/*  yes - DON'T CHANGE DBCS CODES */
	for (bp = s; *bp; bp++)
	{
    	    if (dbcs_lead(*bp))		/* is this first of a DBCS pair? */
	    {
	    	bp++;			/*  yes - skip over it */
		if (*bp == '\0')	/* it is followed by its partner? */
		    break;		/*  no - invalid DBCS, exit loop */
	    }
	    else
	    	*bp = toupper(*bp);	/*  no - upper case it */
	}
    }
    else
    	for (bp = s; *bp; bp++)
    	    *bp = toupper(*bp);

    return s;
}



/*
 *	STRNICMP does a caseless match on the two input strings for LEN
 *	characters. This function is only used for token matching for
 *	commands like FOR and IF. Double byte character set aware.
 */
GLOBAL WORD 	strnicmp(str1, str2, len)
REG BYTE *str1, *str2;
UWORD	 len;
{

	while (len--)			/* loop until len == 0 */
	{
	    if (dbcs_lead(*str1) || dbcs_lead(*str2))
	    {				/* one or both characters are DBCS */
	    	if (*str1 != *str2)	/* are they identical? */
		    return -1;		/*  no - return SMALER */

		if(!*str1)
	            break;

		str1++; str2++;		/* skip DBCS escape */

		if (*str1 != *str2)	/* are DBCS char codes identical? */
		    return -1;		/*  no - return SMALLER */
	    }
	    else
	    	if (toupper(*str1) != toupper(*str2))
		    return -1;

	    if(!*str1)
	        break;

	    str1++; str2++;
	}

	return 0;			/* return EQUAL */
}



/*
 *	ZAP_SPACES removes all white space from a string
 */
GLOBAL VOID zap_spaces(cp)
REG BYTE *cp;
{
REG BYTE *cp1;

	do {
	    cp1 = deblank(cp);		/* Skip leading whitespace   */

	    if (cp1 != cp)		/* If whitespace has been    */
		strcpy(cp, cp1);	/* skipped then move string  */
					
	    while (*cp && !is_blank(cp))	/* Now skip over     */
		cp++;				/* normal characters */
					/* and repeat till the end    */
	} while (*cp);
}


GLOBAL VOID strip_path(path, dir)
BYTE	*path;
BYTE	*dir;
{
	REG BYTE *cp;
	REG WORD i;

	i = 0;					/* assume empty path	*/
	for (cp=path; *cp; cp++) {		/* scan the file name	*/
	    if(dbcs_lead(*cp)) {		/* If this is a DBCS	*/
	        cp++;				/* character then skip  */
		continue;			/* the next char	*/
	    }		
	    if ((*cp == *pathchar) ||		/* if path delimiter	*/
		(*cp == ':'))			/* or drive specifier	*/
		i = (cp+1)-path;		/* remember offset	*/
	}
	strcpy (dir, path);			/* make a copy		*/
	dir[i] = '\0';				/* discard all but path */
}

GLOBAL BOOLEAN getdigit(n, s)
WORD *n;			/* Pointer to the word number to save */
BYTE **s;			/* String to Process */
{

	*n = 0; 			/* Zero the number */

	while(!isdigit(**s) && **s)	/* Skip all non digits */
	    (*s)++;

	if(**s) {
	    while(isdigit(**s)) {	/* Add all the digits in */
		*n = **s - '0' + *n * 10;
		(*s)++;
	    }
	    return TRUE;		/* and return success */
	}
	else
	    return FALSE;
}

/*
 *	Check that the number input by the user and held in S is in
 *	the range specified by MIN and MAX if this is so then update
 *	VALUE and return SUCCESS otherwise VALUE is unchabged and 
 *	return FAILURE.
 */
GLOBAL BOOLEAN check_num(s, min, max, value)
BYTE *s;	/* Input String */
WORD min, max;	/* Minimum and Maximum values */
UWORD *value;	 /* Value Input */
{
	WORD u;

	deblank(s);

	if(getdigit(&u, &s) == FALSE)
	    return FAILURE;

	if(*s)
	    return FAILURE;

	if(u < min || u > max)
	    return FAILURE;

	*value = u;
	return SUCCESS;
}

GLOBAL BOOLEAN iswild (path)
REG BYTE *path;
{
	while (*path && (*path != '*') && (*path != '?'))
	    path ++;
	return (*path != '\0');
}

GLOBAL BOOLEAN is_filechar(s)
REG BYTE *s;
{
	if (*s == 0) return FALSE;
	
	if (is_blank(s) || strchr(invalid_filechar, *s))
	    return FALSE;

	return TRUE;
}

GLOBAL BOOLEAN is_pathchar(s)
REG BYTE *s;
{
	if (is_filechar(s) ||
	   *s == *pathchar ||
	   *s == '.' ||
	   *s == ':' ||
	   (*s >= 'Z'+1 && *s <= 'Z'+6))

		return TRUE;

	return FALSE;
}

/*
 *	Copy the file specification from OLDPATH to the buffer NEWPATH
 *	and return a pointer to the first byte after the extracted name.
 *	Remove any terminating ':' character from the file specification
 *	as the FDOS/PCMODE will try to match all characters in the string.
 */
GLOBAL BYTE * get_filename(newpath, oldpath, ambiguous)
REG BYTE *oldpath, *newpath;
BOOLEAN ambiguous;
{
UWORD	count = 0;
BYTE	*pathname = oldpath;

#if defined(PASSWORD)
	while(is_pathchar(oldpath) ||
	      ((*oldpath == *pwdchar) && (is_pathchar(oldpath + 1))) ||
	      (ambiguous && (*oldpath == '*' || *oldpath == '?'))) {
#else
	while(is_pathchar(oldpath) ||
	      (ambiguous && (*oldpath == '*' || *oldpath == '?'))) {
#endif
	    if(++count < MAX_PATHLEN) {
	        *newpath++ = *oldpath;
		if(dbcs_lead(*oldpath)) {
	 	    *newpath++=*++oldpath;
		    count++;
		}

	    }
	    if(*oldpath++ == ':' && count > 2 && !is_pathchar(oldpath)) {
						/* Handle the CON:filename  */
	        newpath--;			/* so loved by the users of */
		break;				/* the COPY command.	    */
	    }
	}

	*newpath = '\0';			/* Terminate newpath with \0 */

	if(count >= MAX_PATHLEN) {
	    longjmp(break_env, IA_FILENAME);
	}
	
	return oldpath;
}


/*
 *	Returns the offset of the filename in a correctly formatted 
 *	pathname string.
 */
GLOBAL BYTE * fptr(s)
REG BYTE  *s;
{
	REG BYTE  *tp;

	for(tp = s; *s; s++) {
	    if(dbcs_lead(*s)) {
	        s++;
		continue;
	    }

	    if(*s == ':' || *s == *pathchar)
		tp = s+1;	 
	}
	return(tp);		       
}

/*	repwild replaces the wildcards in the destination filespec with
 *      the relevant information from the source filespec 
 *	src is an explicit filename
 *	dest is a filename with wildcards
 *
 *	eg  src = fred.lst
 *	    dest = *.txt      becomes  fred.txt
 *	    dest = ?.*	      becomes  f.lst
 *	    dest = z*.*       becomes  zred.lst
 * 
 *	nb dest must be in a buffer with room for expansion
 */
GLOBAL VOID repwild(src,dest)
REG BYTE  *src,*dest;
{
        BYTE    t[13];
	BYTE	*temp;
	
	temp=&t[0];				/* ptr to temp array */

        if(!iswild(dest))			/* return if not wild */
            return;

        src = fptr(src);        	/* point to filename 		*/
	if(!*src)			/* If a blank filename has been */
	    return;			/* specified then return as an	*/
					/* invalid source specification */
	dest = fptr(dest);	

	strcpy(temp,dest);	/* copy wild dest to temp, as dest will get overwritten */
	
	while (*temp) {		/* while still more temp to process */
	    if(*temp=='.') {		/* advance src ptr to '.' */
		while(*src && *src!='.')
		    src++;
	    }		/* drop into next check */

	    if(*src=='.') {
		if(*temp=='.') {
		    *dest=*temp;	/* copy '.' */
		    goto inc;
		}
		else {            /* advance temp to '.' or '\0', copying valid chars to dest */
		    while(*temp && *temp!='.') {
			if(*temp!='*' && *temp!='?') { 
			    *dest=*temp;  
			    dest++;
			}
			temp++;
		    } 
		    goto skipinc;		
		}
	    }

	    if(*temp=='*') {
		while(*src && *src!='.') {	/* copy rest of src till */
		    *dest=*src;			/* src = '.' or '\0' 	 */
		    dest++;
		    src++;
		}
		while(*temp && *temp!='.')	/* inc temp past '*' */
		    temp++;
		goto skipinc;
	    }			

	    if (*temp=='?')
		*dest=*src;		/* copy src character to dest */
	    else   			/* else  *temp==normal char */
		*dest=*temp;		/* copy temp character to dest */

inc:
	    if(*src)			/* dont advance past terminator */
		src++;

	    dest++;
	    temp++;
skipinc:
	    ;
	}			/* loop till end of while */	

	*dest='\0';		/* add terminator to dest */
}


/*.pa*/
/*
 *	Read a character from the console. The ABORT flag determines
 *	whether a ^C will abort the command or be treated as a negative
 *	reponse. DEF determines the default value that yes() will use.
 *
 */

EXTERN BYTE FAR * CDECL farptr(BYTE *);

#define	YES_CHAR	(*farptr(YES_NO+0))
#define	NO_CHAR		(*farptr(YES_NO+1))

GLOBAL BOOLEAN yes(abort, def)
BOOLEAN abort, def;
{
BYTE	yn;

#if defined(CDOSTMP)
	yn = (BYTE) bdos(C_RAWIO, 0xFD);	/* Input a character and */
	if(abort && yn == 0x03)			/* check for Control-C   */
	    int_break();
#else
	yn = (BYTE) msdos((abort ? MS_C_NOECHO : MS_C_RAWIN), NULL);	 
#endif						/* read the response	*/
	if(yn >= ' ')				/* If its printable then*/
	    putc(yn);				/* display the charcter */
	crlf(); 				/* new line		*/
	if(def) 				/* Now using the correct*/
	    return((yn & 0xdf) != NO_CHAR);	/* default value return */
	else					/* process the users	*/
	    return((yn & 0xdf) == YES_CHAR);	/* input and return.	*/
}

/*
 *	ONOFF scans the command line for [=](ON|OFF) and returns
 *	YES, NO or FAILURE 
 */
GLOBAL WORD onoff(cmd)
BYTE *cmd;
{

	cmd = deblank(cmd);			/* Deblank the string and    */
	if (*cmd == '=')			/* remove optional '='	     */
	    cmd = deblank(cmd+1);

	sprintf(heap(), "%s", MSG_ON);		/* Check for ON		    */
	if(!strnicmp(cmd, heap(), strlen(heap())))
	    if (*(deblank (cmd + strlen(heap()))) == 0)	/* end of line?	    */
		return YES;

	sprintf(heap(), "%s", MSG_OFF);		/* Check for OFF	    */
	if(!strnicmp(cmd, heap(), strlen(heap())))
	    if (*(deblank (cmd + strlen(heap()))) == 0)	/* end of line?	    */
		return NO;

	return FAILURE;
}

GLOBAL VOID syntax()
{
	eprintf(MSG_SYNTAX);
	crlfflg = YES;
}

GLOBAL VOID crlf()
{
	printf("\n");
}

GLOBAL VOID putc(c)
BYTE	c;
{
	printf("%c", c);
}


GLOBAL VOID puts(s)
BYTE	*s;
{
	printf("%s",s);
}


GLOBAL VOID c_write(s, l)
BYTE	*s;
UWORD	 l;
{
	ms_x_write (err_flag ? STDERR : STDOUT, s, l);
}


GLOBAL WORD e_check(ret)
REG WORD    ret;
{
REG BYTE   *s;

    if (ret >= 0)			/* if no error code */
	return ret; 			/* it's O.K. */

    if (ret == ED_GENFAIL) ret = extended_error();

    crlfflg = YES;			/* Force a CR LF after the error    */

    switch (ret)
    {
	case ED_ROOM:	  /* Force ED_ROOM to return File Not Found msg */
	case ED_FILE:	  s = ERR02;	break;	/* File Not Found Error	*/
	case -1:
	case ED_PATH:	  s = ERR03;	break;	/* Path Not Found	*/
	case ED_HANDLE:	  s = ERR04;	break;	/* Too many Open Files	*/
	case ED_ACCESS:	  s = ERR05;	break;	/* Access denied	*/
	case ED_MEMORY:	  s = ERR08;	break;	/* Insufficient Memory	*/
	case ED_ENVIRON:  s = MSG_ENVERR;break;	/* Invalid Environment	*/
	case ED_DRIVE:	  s = ERR15;	break;	/* Invalid Drive Spec	*/
	case ED_PROTECT:  s = ERR19;	break;	/* Write Protect Disk	*/
	case ED_SHAREFAIL:s = ERR20;	break;	/* Sharing Conflict	*/
	default:
	    if(ret==ED_FAIL)
	 		 {s = ERR83;	break;} /* Physical Media - FAILED */
	    if(ret==ED_PASSWORD)
			 {s = ERR86;	break;}	/* Invalid Password	*/
#if defined(CDOS) || defined(CDOSTMP)
	    if(ret==(-255))
	    		 {s = ERR_RSC;	break;}	/* Resource is not Available */
#endif
	    if (ED_NET >= ret && ret > ED_NETPWD)
		      {s = MSG_NETWORK;	break;}	/* Network Error	*/

	    s = MSG_INTERNAL; break;		/* Internal Error	*/
    }

    eprintf(s, 0-ret);
    eprintf("\n");
    return ret;
}

#if 0
MLOCAL BYTE *err_tab[] =  { NULLPTR,	/* 01 - Invalid Function Code	*/
			    err02,	/* 02 - File Not Found Error	*/
			    err03,	/* 03 - Path Not Found		*/
			    err04,	/* 04 - Too many Open Files	*/
			    err05,	/* 05 - Access denied		*/
			    NULLPTR,	/* 06 - Invalid Handle		*/
			    NULLPTR,	/* 07 - Invalid Memory Cntl Blk */
			    err08,	/* 08 - Insufficient Memory	*/
			    NULLPTR,	/* 09 - Invalid Memory Cntl Blk */
			    msg_enverr, /* 10 - Invalid Environment	*/
			    NULLPTR,	/* 11 - Invalid Format		*/
			    NULLPTR,	/* 12 - Invalid Access Code	*/
			    NULLPTR,	/* 13 - Invalid Data		*/
			    NULLPTR,	/* 14 - Unused Error Code	*/
			    err15,	/* 15 - Invalid Drive Spec	*/
			    err20,	/* 20 - Sharing Conflict	*/
			    err83,	/* 83 - Physical Media - FAILED */
			    err86,	/* 86 - Invalid Password	*/
			    err_rsc};	/* Resource is not Available	*/

GLOBAL WORD e_check(ret)
REG WORD    ret;
{
REG WORD    error;			/* Local copy of the error code */

	if (ret >= 0)			/* if no error code */
	    return ret; 		/* it's O.K. */

	crlfflg = YES;			/* Force a CR LF after the error    */
	error = ret;			/* message has been displayed	    */
	if(error == ED_ROOM) 		/* Force ED_ROOM to return File Not */
	    error = ED_FILE;		/* found message.		    */

	if(error < ED_DRIVE) {		/* Check for error codes which have */
	    if(error == ED_SHAREFAIL)	/* been remapped so save space	    */
		error = (-16);

	    if(error == ED_FAIL)	/* Check for FAIL		    */
	        error = (-17);

	    if(error == ED_PASSWORD)	/* Password Error		    */
	        error = (-18);
	}

	error = -error;
	if(error <= sizeof(err_tab)/sizeof(BYTE *) && err_tab[error-1])
	    eprintf(err_tab[error-1]);
	else
	    eprintf("Internal Error Code %03d", error);

	return ret;
}
#endif

GLOBAL BOOLEAN UNC(char *path) {
	if (*path == '\\' && path[1] == '\\') return TRUE;
	if (!*(path++) || !*(path++)) return FALSE;
	while (*path) if (*(path++) == ':') return TRUE;
	return FALSE;
}


GLOBAL BYTE * d_check(path)
REG BYTE *path;
{

	ddrive = -1;				/* return -1 for UNC names  */
	if (UNC(path)) return(path);
	ddrive = drive;				/* if no drive is specified */
	if(!*path || path[1] != ':')		/* then DDRIVE is set to    */
	    return(path);			/* the default drive.	    */

	ddrive = toupper(path[0]) - 'A';	/* Otherwise the requested  */
	path += 2;				/* drive is selected and    */
						/* range checked	    */
	if(ddrive == drive)			/* If the requested drive is*/
	    return(path); 			/* the default drive is OK. */

/*
 *	If TRUE the D_CHECK only range checks the selected drive and
 *	returns a pointer to the next element of the path. If FALSE
 *	the drive is phyically selected.
 */
#if TRUE
	if(!INVALID_DRV(ddrive))
	    return(path);
#else
	if(!INVALID_DRV(ddrive) && ms_drv_set(ddrive) == ddrive) {
	    ms_drv_set(drive);			/* Restore Original Drive   */
	    return(path);			/* and return Path	    */
	}
#endif
	e_check(ED_DRIVE);			/* Print an error message   */
	return (NULL);				/* and return a NULLPTR	    */
}


GLOBAL BOOLEAN f_check(cmd, fchars, farray, ignore)
REG BYTE *cmd;
BYTE	 *fchars;
UWORD	 *farray;
BOOLEAN  ignore;		/* Ignore Illegal Options */
{
	BYTE 	*s, *flg_start;
	BOOLEAN	 flg_skip, flg_error;
	BYTE	 c;

	*farray = 0;		/* assume none of the flags present */

	while(*cmd) {
	    if(*cmd++ != *switchar)
	        continue;

	    flg_start = cmd - 1;		/* Save switchar offset	    */
	    flg_skip = FALSE;			/* No Chars skipped	    */
	    flg_error = TRUE;			/* Assume first char is bad */
	    FOREVER {
	        c = tolower(*cmd);		/* Scan the string till the */
		if(!((c>='a' && c<='z')||(c>='0' && c<='9')))
		    break;			/* first non-alpha character*/

		if((s=(BYTE *)strchr(fchars, c))) {/* check each char against  */
		    *farray |= 1 << (s-fchars); /* options string passed by */
		    strcpy(cmd, cmd+1); 	/* the calling routine.	    */
		    flg_error = FALSE;		/* Reset error flag and set */
		}				/* correct flag bit.	    */
		else {
		    flg_skip = flg_error = TRUE;/* On error set flg_skip and*/
		    if(!ignore)			/* break out of the loop if */
		        break;			/* ignore is FALSE.	    */
		    cmd++;
		}
	    }

	    if(!flg_skip)			/* If all characters have    */
	        *flg_start = ' ';		/* been used then remove '/' */

	    if(flg_error && !ignore) {		/* If an invalid char and    */
		eprintf(MSG_BADOPT, *switchar, c); /* ignore is FALSE then    */
		crlfflg = YES;			/* print the error message   */
		return FAILURE;
	    }
	}
	return SUCCESS;
}


GLOBAL BOOLEAN nofiles(path, attrib, exist, append_stardotstar)
REG BYTE *path; 		/* Search Path		*/
WORD	 attrib;		/* Search Attributes	*/
BOOLEAN  exist; 		/* Must files exist	*/
BOOLEAN  append_stardotstar;
{
	REG BYTE *cp;
	DTA	search;
	WORD	ret;

	if ((cp = d_check (path)) == NULLPTR)	/* if bad drive letter */
	    return FAILURE;			/*    don't do it */

	if (!*fptr(cp)) {			/* If only a path has been */
	    strcat(cp, d_slash_stardotstar+3);	/* specified expand to *.* */
	}
	else if(!iswild (cp))			/* else is it path or file? */
	{					/* wild cards imply files */
	    ret = ms_x_first(path, ATTR_ALL, &search);	/* get attributes */

	    if(ret == ED_ROOM)
	        ret = ED_FILE;

	    if (ret < 0)			/* if any errors	     */
		if(!exist && (ret == ED_FILE || ret == ED_PATH)) {
						/* If file does not exist and*/
						/* this is NOT an error...   */
		    if (append_stardotstar) {
		        append_slash(path);	/* "DIR .." on NOVELL drives */
		        strcat(path, d_slash_stardotstar+3);
					   	/* requires that we append   */
						/* "*.*" here                */
		    }
		    return SUCCESS;		/* return OK.                */
		}
		else {
		    e_check (ret);		/* otherwise print message   */
		    return FAILURE;		/* no files found	     */
		}

	    if (search.fattr & ATTR_DIR) {	/* if path names directory   */
		append_slash(path); 		/* make it all files in it   */
		strcat(path, d_slash_stardotstar+3);
	    }
	    
	    if(!exist)				/* Must we check the file(s) */
		return SUCCESS; 		/* exist. If no return	     */
	}

	ret = ms_x_first(path, attrib, &search);  /* Search for the file     */

	if(ret < 0)				/* Check the error returned  */
	    if(!exist && (ret==ED_FILE || ret==ED_ROOM)) /* If file does not exist but*/
		return SUCCESS; 		/* this is not an error then */
	    else {				/* return Ok.		     */
		e_check (ret);			/* otherwise print message   */
		return FAILURE; 		/* no files found	     */
	    }

	return SUCCESS;
}

/*
 *	Check if FILENAME can be opened in Read Only mode and return the
 *	result. The file is then closed
 */
GLOBAL BOOLEAN file_exist(filename)
BYTE *filename;
{
BYTE filebuf[MAX_PATHLEN];
WORD h;

	get_filename(filebuf, filename, NO);
	if((h = ms_x_open(filebuf, OPEN_READ)) > 0) {
	    ms_x_close(h);
	    return TRUE;
	}

	return FALSE;	     
}

/*
 *	Check if the handle passed to this routine is open on a FILE
 *	or a DEVICE.
 */
GLOBAL BOOLEAN isdev(handle)
UWORD handle;
{
	return (0x0080 & ms_x_ioctl(handle) ? TRUE : FALSE);
}

/*
 *	If the string that has been passed doesn't end with a '\' add one
 */
GLOBAL	append_slash(s)
BYTE	*s;
{
BYTE	lastchar;
    while (*s) {
    	lastchar = *s;
	if (dbcs_lead(*s))		/* is this first of a DBCS pair? */
	    s++;
	s++;
    }
    if ((lastchar != '\\') && (lastchar != '/'))
	strcat(s, pathchar);		/* append a slash */
}


GLOBAL	VOID prompt_exec()
{
	BYTE	temp[128];
	
	if (!env_scan("PEXEC=",temp)) docmd(temp,TRUE);
}

GLOBAL	VOID optional_line(line)
BYTE	*line;
{
	BYTE	c;
	BYTE	*s;	

	if (*line == 13 || *line == 10 || *line == 0) return;

	if (*line == '?') strcpy(line,line+1);

	if (*line == '\"') {
	    s = line+1;
	    while (*s && *s != '\"') putc(*s++);	    
	    s++;
	    strcpy(line,s);
	}
	else printf(MSG_OPTLINE,line);
	
	if (!yes(NO,NO)) *line = 0;

	/*printf("\n");*/
	
}
