/*
;    File              : $Workfile: BATCH.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
*/

/*
 * 27 Oct 87 Improve GOTO command to ignore trailing white space, only
 *		match on the first 8 characters and only allow valid
 *		filename characters.
 * 28 Oct 87 Correct duplicate prompt display when a batch file label
 *		is read and the echo flag is ON.
 *  9 Nov 87 Change Batch file termination so that Output Redirection is
 *		correctly handled.
 * 13 Jan 88 If prompt display is aborted because of a critical error
 *		the following prompt will be forced to "$n$g".
 * 25 Jan 88 Support redirection on the FOR command correctly
 * 24 Feb 88 Generate batch file parameter %0 correctly as WS200 install
 *		requires that the drive specifier be present. Garry Silvey
 *  5 May 88 Batch paramater %0 is now a copied from the invoking command
 *		line.
 * 18 May 88 Support the ESC_CHAR in the command line.
 * 20 May 88 Disable MULTI_CHAR support in DOSPLUS when the user enters the
 *		first command.
 * 26 May 88 Added STACK switch and support $q in prompt.
 * 27 May 88 Added string undefs.
 *  6 Jun 88 Call resident portion to do readline (SideKick+ problem)
 * 23 Jun 88 Support CR only delimited batch files and if errorlevel ==
 *		syntax used in installation files.
 *  6 Jul 88 Support the FOR ... CALL syntax correctly. (IMS)
 * 17 Aug 88 Jump to labels followed by comments (Ashton Tate By-Line)
 * 21 Sep 88 Use KEYIN_FLG to allow the default ECHO state to be ON.
 * 16 Nov 88 Disable BACK_CHAR in Concurrent DOS.
 * 21 Dec 88 Allow leading whitespace before labels
 *  5 Jan 89 Support Quoted strings in Batch files.
 * 18 Apr 89 Support Quoted strings in IF command
 * 19 Apr 89 Quotes in IF command: "x == "x" parses, "x=x" == "x.. doesn't
 * 24 Apr 89 Increase MAX_LINE to 128 for pctools ver 5
 * 19 May 89 Take out support for quoted strings in IF command
 * 05 Jun 89 Do not echo command or display the prompt during FOR command.
 *		Restore support for batch files with long lines.
 * 02 Nov 89 batch_line bodge which substitutes 0xFF for 0x00 changed - we
 *		now substitute '\r\n', then throw away rest of the line.
 * 01 Dec 89 batch_line - trailing % at end of line is discarded
 * 15 Dec 89 "if errorlevel -1" allowed
 * 30 Jan 90 Added int2e_start and int2e_finish to save and restore
 *		important batch file variables to allow novell menu program
 *		to use int 2e to invoke batch files.
 * 30 Jan 90 Forced batch data structures to appear on segment boundaries;
 *		Added dummy memory descriptor before batch structure;
 *		Put segment address of batch structure in batch_seg_ptr;
 *		All so novell can find and patch the drive letter of
 *		autoexec.bat during a remote boot.
 *  6-Mar-90 Watcom C v 7.0
 *  7-Mar-90 allow ESC_CHAR through unless followed by MULTI/BACK_CHAR
 * 14-Mar-90 Reduce batch_buf to 32 bytes like wot dos is
 * 20-Mar-90 Batch structures allocated by mem_alloc (ie. MS_M_ALLOC)
 *		rather than on heap. Batch file nesting no longer heap limited.
 * 27-Mar-90 Allow "=", "==", "===" etc in "if errorlevel==n"
 * 10-Apr-90 Make errorlevel 999 same as errorlevel 231 (mod 256)
 *  8-May-90 Don't echo getcmd unless batch file (eg. "dir|more" shouldn't
 *		echo "C:>more")
 * 23-May-90 batch_read no longer repeatedly deblanks line (which leads
 *		to the buffer happily wandering up memory).
 * 30-May-90 "if ab de==ef" form doesn't generate syntax error
 * 13-Jun-90 batch_line rejects unmatched "|" as syntax error
 * 20-Sep-90 is_filechar() and is_pathchar() now take pointer instead of byte
 *		Changed batch_char() to return pointer instead of byte,
 *		renamed to batch_ptr().
 *		Amended make_label(), batch_start() and cmd_for() to check for
 *		DBCS lead bytes.
 * 24-Sep-90 Add $m and $u option to PROMPT to display status of mail
             and user name respectively
 * 27-Sep-90 Add IF USERID <userid> COMMAND
                       IF LOGINNAME <loginname> COMMAND
                       IF GROUPNAME <groupname> COMMAND 
                       IF ASK ["string"] <char> COMMAND
                       .. AND .. OR .. to IF processing
                       INPUT ["string"] <environment-variable>
                       INPUTC ["string"] <environment-variable>
 * 10-Oct-90 inherit batch files from TMP when TSR auto-loads CDOS.COM
 		(a bodge for Stellar)
 * 25-Oct-90 inherit echoflg from TMP when TSR auto-loads CDOS.COM
 * 31-Oct-90 change IF ASK command to IF KEY command

DRDOS BUXTON
------------
 * 25-Apr-91 '!' is now ignored during COMMAND /C processing.
 * 28-May-91 leading white space is now ignored before labels.
 * 09-Jun-91 Added GOSUB, RETURN and SWITCH commands.  
 * 24-Jul-91 if batch_seg_ptr is poked to zero all batch processing is
 		 terminated.
 * 26-Jul-91 batch files are now read into a far buffer. This avoids
 		 reading directly to seg FFFF if we happen to be there and so
		 solves some NOVELL problems.

 * 18-Jun-92 Support ? in batch files as in config.sys.
 * 23-Jun-92 $u in prompt causes LOGINNAME in environment to be displayed.
 * 07-Jul-92 IF EXIST now finds hidden files. 
*/

#include	"defines.h"

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

#include	<portab.h>

#if !defined(DOSPLUS)
#include	<ccpm.h>
#endif

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

#include	<setjmp.h>

/*RG-01*/
#if defined(CDOSTMP) || defined(CDOS)
#include    <pd.h>
#define	PATH_LEN	    65		 /* max path length (null terminated) */
EXTERN PD FAR * CDECL pd;	/* Far pointer to Current PD */
EXTERN VOID CDECL cmd_set(BYTE *);	            /* COMINT.C */
#if !defined (NOSECURITY) 
#include    "security.h"
#include    "login.h"
#endif
#endif
/*RG-01-end*/

EXTERN	VOID CDECL cmd_pause();

EXTERN	BOOLEAN	parse(BYTE *);

EXTERN	UWORD	boot_key_scan_code; /* in COM.C */

/*RG-03*/
BOOLEAN if_context=FALSE;
BOOLEAN ifcond=FALSE;
/*RG-03-end*/

EXTERN jmp_buf break_env;

#define	MAX_LINE	128	/* Maximum No of Chars in input line	*/

#if defined(CPM)
EXTERN UWORD user;		/* USER Number variable for CPM.EXE	*/
#endif
EXTERN BYTE msg_prmeq[];	/* Static Environ String "PROMPT="	*/

#if 0
#define FCONTROL struct fcc
MLOCAL FCONTROL {
	BOOLEAN  sflg;			/* FOR File Search Flag 	*/
	DTA	 search;		/* FOR Search structure 	*/
	BYTE	 *files;		/* FOR File list		*/
	BYTE	 *cmd;			/* FOR Command Line		*/
	BYTE	 forvar;		/* FOR variable char		*/
};
#endif 

MLOCAL FCONTROL *forptr;

#if 0
#define BCONTROL struct bcc
GLOBAL BCONTROL {
	BCONTROL FAR *bcontrol;		/* Previous Batch Control Structure  */
	BOOLEAN  eof;			/* End of File Flag		     */
	LONG	 offset;		/* Offset in BATCH file 	     */
	LONG	 ret_offset[4];		/* return offset from gosub          */
	BYTE	 *batcmd;		/* Batch File Input parameters	     */
	UWORD	 batshift;		/* Shift Offset 		     */
	BYTE	 batfile[MAX_PATHLEN];	/* Batch File Name		     */
	UWORD	 stream;		/* Stream for this Batch File	     */
	FCONTROL *fcontrol;		/* Pointer to previous FOR command   */
	BYTE	 *heap_start;		/* Heap pointer before extra bytes   */
	WORD	 heap_size;		/* are added to shift to segment     */
	BYTE     save_area[1];		/* boundary. - EJH		     */
	} FAR *batch, FAR *batch_save;	/* Master Batch Control Stucture     */
#endif

/* Handle 255 is closed */
#define	CLOSED	0xff

/*	Keyboard  Variables		*/
GLOBAL BYTE	kbdbuf[MAX_LINE+2]= {0};/* Keyboard Input Buffer	     */
GLOBAL BYTE	*kbdptr = kbdbuf+2;	/* Keyboard Buffer Pointer	     */
MLOCAL BOOLEAN	keyin_flg = FALSE;	/* This flag is set to TRUE when the */
					/* initial command line buffer setup */
					/* by INIT() has been exhausted.     */
MLOCAL WORD	batchflg_save;		/* Used during INT 2E handling.      */
MLOCAL WORD	echoflg_save;		/* ditto above                       */
GLOBAL WORD	echoflg_save2;		/* saves echo state when batch file  */
					/* execed.			     */
/*
 *	Batch file buffering control structures.
 */ 
MLOCAL LONG	batch_off;		/* Offset of buffered data in File */
MLOCAL WORD	batch_cnt = 0;		/* Number of bytes in buffer	   */
MLOCAL BYTE	batch_buf[32];
MLOCAL BYTE	batch_eof[] = "\x1a";	/* End of file string */

MLOCAL BYTE	batch_sep[] = "\t ;,="; /* Batch Command line option delimiters */

EXTERN VOID CDECL cmd_ver(BYTE *);		/* COMINT.C Display Version */
EXTERN VOID docmd(BYTE *, BOOLEAN);		/* COM.C		    */
EXTERN VOID CDECL int_break(VOID);			/* COM.C		    */

GLOBAL BOOLEAN getcmd(BYTE *);
GLOBAL VOID for_end();
GLOBAL VOID batch_start(BYTE *, BYTE *, BYTE *);
GLOBAL VOID batch_end(VOID);
GLOBAL VOID batch_close(VOID);
MLOCAL VOID for_in(BYTE *);
MLOCAL WORD batch_open(VOID);
MLOCAL VOID batch_read(BYTE *, BOOLEAN);
MLOCAL VOID batch_line(BYTE *, BOOLEAN);
MLOCAL BYTE *batch_ptr(VOID);
MLOCAL VOID prompt(VOID);
MLOCAL BOOLEAN novell_extension(BYTE *, BYTE *);

#if !defined(CDOSTMP)
EXTERN UWORD FAR *batch_seg_ptr; /* For novell remote boot. see CSTART.ASM */
#endif

#if defined(CDOS) || defined(CDOSTMP)
#define TmpPspEchoFlgPtr 0x58	/* Magic location to keep echo flag */
#define	TmpPspDataSeg	0x5a	/* Magic location to keep Data Seg */
#define TmpPspBatchSeg	0x5c	/* Magic location to keep Batch Ptr */
#define TmpPspMpad	0x5e	/* Magic location to keep unlinked MPAD */
#endif
#if defined(CDOS)
typedef struct _mpad
{
	UWORD	link;			/* address of next MPAD		*/
	UWORD	start;			/* seg of allocation unit	*/
	UWORD	length;			/* length in paras		*/
	UWORD	res0x06;		/* reserved			*/
	UWORD	xios;			/* Process id			*/
} MPAD;
GLOBAL VOID inherit_TMP_state(VOID);
#endif

/*.pa*/
GLOBAL BOOLEAN getcmd(line)		/* read command line */
BYTE *line;
{
	BYTE *s;
	BOOLEAN quote = FALSE;
	BOOLEAN	cancel_prompt = FALSE;
#if defined(DOSPLUS)
	WORD	i;
	BYTE	cmd_name[16];
#endif

	back_flag = FALSE;		/* Disable BackGround Processing*/

	*line = '\0';

	FOREVER {
	    if(for_flag) {		/* If Processing a FOR command	*/
		for_in(line);		/* then call then use the FOR_IN*/
		return NO;	 	/* routine to fill the keyboard */
	    }				/* buffer and then return	*/

	    if(!batchflg)		/* If no batch processing then  */
	        break;			/* skip further tests		*/
batch_restart:
	    
#if defined(DOSPLUS)
	    if (!*batch_seg_ptr) {	/* if batch_seg_ptr has been set to */
	    	batch_endall();		/* zero terminate all batch files   */
		return NO;
	    }
#endif
	    
	    if(batchflg && batch->eof) {  /* Close the batch file if at */
		batch_end();		  /* the end of the file.	*/
	        if(batchflg == 0)	  /* BREAK if batch processing	*/
		   return NO;		  /* is complete. 		*/
	        continue;
	    }	    

	    if(!batch_open())		/* Open the file and read a line*/
		return YES;		/* from the file		*/

	    batch_read(line, NO);	/* Read Line			*/

	    if(batch->eof)		/* If the end of the batch file */
	        batch_close();		/* has been detected then close */
					/* the file.			*/

	    if (*line == '?' || boot_key_scan_code == 0x4200) {
	        optional_line(line);
	    }

	    if(*line == '@') {		/* If first character in the	*/
		strcpy(line, line+1);	/* command line is '@' donot	*/
		return NO;		/* echo the command and move the*/
	    }				/* string down 1 character.	*/

	    if (!cancel_prompt) {
		if(crlfflg && echoflg)
		    crlf();
		prompt();
	    }
	    return echoflg;
	}

	if(!*kbdptr) {			/* Set the Keyboard Input flag  */
	    keyin_flg = TRUE;		/* after a initial command line */
					/* buffer has been exhausted	*/
#if 1
	    if (c_option) {		/* insert an EXIT command if we */
		kbdptr = &kbdbuf[2];    /* are processing a /C command  */
		strcpy(&kbdbuf[2],"exit");
	     }
#endif
	}	

	if (!*kbdptr) {			/* Check for existing line      */


/* NEIL */
	    if(crlfflg && echoflg)
		crlf();	
/* NEIL end */
	    prompt();			/* issue command line prompt	*/
	    allow_pexec = FALSE;

	    /* $x in prompt string may cause batchflg to be set. */
	    /* If so we must jump to batch processing code.	 */
	    if (batchflg) {
	    	cancel_prompt = TRUE;
		goto batch_restart;
	    }
	    
	    kbdptr = "";		/* Force KBDPTR to point to '\0'*/
					/* in case we get ABORTED and	*/
					/* drop through here again.	*/
	    kbdbuf[0] = MAX_LINE;	/* set max. input length	*/
	    kbdbuf[kbdbuf[1]+2] = '\r'; /* Terminate current line.	*/
#if defined(CDOSTMP)
	    system(C_READSTR, kbdbuf);	/* read a line			*/
#else

	    readline(kbdbuf);
#endif

	    crlf();
	    
            kbdbuf[kbdbuf[1] + 2]='\0'; /* terminate input		*/
	    kbdptr = kbdbuf + 2;
	}

	s = kbdptr;

	while(*s) {
	    if(*s == '"')		/* Check for a " character and  */
		quote = !quote; 	/* update the flag correctly	*/

#if !defined(DOSPLUS)
	    if(*s == ESC_CHAR &&	/* If the Escape character has  */
	       !quote &&		/* been specified then do not	*/
	       ((*(s+1) == MULTI_CHAR) || (*(s+1) == BACK_CHAR))) {
		*line++ = *++s;		/* process the following char.	*/
		s++;
		continue;
	    }
#endif

#if defined(DOSPLUS)			/* Disable MULTI_CHAR support   */
	    if(!(keyin_flg||c_option||k_option))	
	    				/* after the init command line  */
#endif					/* has been exhausted.		*/
	        if(*s == MULTI_CHAR &&	/* If a Multiple command char	*/
		        !quote) {	/* and the QUOTE flag is FALSE	*/
		    s++;		/* then break the command here	*/
		    break;		/* and save the rest of the line*/
	        }			/* for next time.		*/

#if FALSE				/* defined(CDOSTMP)		*/
	    if(*s == BACK_CHAR &&	/* If a Back Ground processing	*/
			!quote) {	/* and the QUOTE flag is FALSE	*/
		s++;			/* then treat as for MULTI_CHAR	*/
		back_flag = TRUE;	/* except that the current      */
		break;			/* command is executed in the   */
	    }				/* background.			*/
#endif
	    if(*s == PIPE_CHAR &&	/* If a Pipe enable character	*/
			!quote) {	/* and the QUOTE flag is FALSE	*/
		s++;			/* then break the command here	*/
		pipe_out = YES; 	/* and save the rest of the line*/
		break;			/* for next time.		*/
	    }

	    copy_char(&line, &s);	/* Just save the character	*/
	}
	
	*line = '\0';			/* Terminate the Buffer 	*/
	kbdptr = deblank(s);		/* Copy the possibly null length*/
	return NO;		 	/* string to KBDBUF for next    */
					/* next invocation and save CCP */
}

MLOCAL VOID for_in(line)	 	/* A FOR command is currently	*/
BYTE *line; 				/* executing so build the line	*/
{					/* from the internal FOR data	*/
	BYTE *s,*t;			/* initialized by CMD_FOR	*/
	BYTE *bp1, *fp;
	WORD	i;
	
	FOREVER {
	    fp = forptr->files; 	/* Get the next string and stop     */
	    if(strlen(fp) == 0) {	/* if its the zero length string    */
		*line = '\0';		/* which marks the end of the FOR   */
		crlfflg = YES;		/* search list. 		    */
		for_end();
		return;
	    }

	    if(!iswild(fp)) {			/* If not an ambiguous file  */
		forptr->sflg = NO;		/* then update the FOR	     */
		forptr->files += strlen(fp)+1;	/* pointer to the next file  */
		break;				/* in the search list.	     */
	    }

	    if(forptr->sflg)			/* Search for the next file  */
		i = ms_x_next(&forptr->search); /* file on the disk if	     */
	    else				/* FOR_SFLG otherwise get first */
		i = ms_x_first(fp, ATTR_RO, &forptr->search);

	    if(i < 0) { 			/* If the search failed      */
		forptr->sflg = NO;		/* then update the FOR	     */
		forptr->files += strlen(fp)+1;	/* pointer to the next file  */
		continue;			/* in the search list.	     */
	    }					/* and get the next  entry   */

	    fp = (BYTE *) heap();
	    strip_path(forptr->files, fp);	/* Extract the Path	     */
	    strcat(fp, forptr->search.fname);	/* and then add the matching */
	    forptr->sflg = YES; 		/* filename and update the   */
	    strupr(fp); 			/* variables.		     */
	    break;				/* Force name to uppercase   */
	}

 	s = forptr->cmd;
 	t = line;
 	while (*s && (t - line) < MAX_LINE) {	/* Copy the command 	 */
 	    if(*s == '%' && *(s+1) == forptr->forvar) { /* line looking for  */
 		s += 2; 			/* the imbedded %c and insert*/
 		bp1 = fp;			/* the current substition    */
 		while(*bp1 && (t - line) < MAX_LINE) /* string pointed at by FP   */
 		    copy_char(&t, &bp1);
 		continue;
 	    }
 
 	    copy_char(&t, &s);
 	}
 
 	*t = '\0';	 			/* and terminate the string */
}

/*.pa*/
/*
 *	BATCH FILE CONTROL ROUTINES
 *	===========================
 *
 *	The following routines provide the interface from COMMAND.COM to
 *	a batch file. BATCH_START sets up all the local variables to enable
 *	batch processing while BATCH_END terminates batch processing. BATCH_READ
 *	reads a line of data from the batch file and expands it to contain the
 *	command line variables and variables from the environment.
 */
GLOBAL VOID batch_start(argv0, path, tail)
BYTE	*argv0;			/* Invoking Command	*/ 
BYTE	*path;			/* Complete filename	*/
BYTE	*tail;			/* Command Line Options	*/
{
BYTE *s2;
BYTE	dirbuf[MAX_PATHLEN];
WORD	i;

	if(batchflg)			/* If a batch file is currently */
	    batch_close();		/* close it. So minimum number	*/
					/* of handles are used. 	*/

	s2 = path;			/* Save the original Path	*/
	if((path = d_check(path)) == NULL)  /* Check that the file	*/
	    return;			    /* exists.			*/

	batch_new();			/* new incarnation of batch	*/
	
	forptr = (FCONTROL *) NULL;	/* Disable current FOR control	*/
	for_flag = NO;			/* and Global FOR flag		*/

	/*
	 *	Generate the full path specification for the batch file
	 *	and store in the batch control information. If the user
	 *	has specified the full path use it otherwise determine the
	 *	full path using ms_x_curdir.
	 */
	if (ddrive != -1 && *path != *pathchar) {
	    ms_x_curdir(ddrive+1, dirbuf);
	    sprintf(heap(), "%c:%s%s%s%s", ddrive + 'A',
					 pathchar,
					 dirbuf,
					 (*dirbuf ? pathchar : ""),
					 path);
	}
	else if (ddrive != -1)
	    sprintf(heap(), "%c:%s", ddrive + 'A', path);
	else
	    ms_x_expand(heap(), path);

	for (i=0; i<MAX_PATHLEN; i++)	/* save the batch pathname */
	    batch->batfile[i] = heap()[i];
	batch->batfile[MAX_PATHLEN-1] = 0;

	/*
	 *	Copy the invoking command and the individual elements
	 *	of the command line into a buffer ready for processing
	 */
	batch->batcmd = (BYTE *)heap();	/* Initialize Parameter Buffer	*/
	strcpy(heap(), argv0);		/* Copy the invoking command	*/
	heap_get(strlen(heap())+1);	/* and protect the buffer	*/

	while(*tail) {			/* While there are command line */
	    s2 = (BYTE *)heap();	/* parameters copy them 	*/
	    while(*tail && strchr(batch_sep, *tail))
		tail = skip_char(tail);

	    while(*tail && !strchr(batch_sep, *tail))
		copy_char(&s2, &tail);

	    *s2++ = '\0';
	    heap_get(strlen(heap()) + 1);
	}

	*(WORD *)heap_get(2) = 0;	/* Double NULL is a terminator	 */
					/* for command line params	 */

	if(in_flag & REDIR_ACTIVE)	/* If Input redirection has been */
	    in_flag |= REDIR_BATCH;	/* enabled for this command force*/
					/* it on for the complete command*/

	if(out_flag & REDIR_ACTIVE)	/* If Output redirection has been*/
	    out_flag |= REDIR_BATCH;	/* enabled for this command force*/
					/* it on for the complete command*/


	batchflg++;			/* increment batch flag		*/

	crlfflg  = YES;			/* print CR/LF after this  */
}

GLOBAL VOID batch_endall()		/* This terminates BATCH	*/
{					/* processing by closing ALL	*/
	while(batchflg) {		/* active batch files		*/
	    batch_end();
	}
}

GLOBAL VOID batch_end()			/* This function is called for	*/
{					/* both NORMAL and ABNORMAL	*/
	if(batchflg == 0)		/* termination of batch file	*/
	    return;			/* processing			*/

	boot_key_scan_code = 0;

	batch_close();			/* Close the Batch file 	*/
	for_end();			/* Terminate Any FOR command	*/
	batch_old();			/* Restore the previous batch 	*/
					/*  control structures to heap	*/
	if(--batchflg == 0) {
	    *batch_seg_ptr = 0;
	    echoflg = echoflg_save2;	/* Restore the original ECHO	*/
	    crlfflg = YES;		/* flag and set CR/LF flag when */
	}				/* returning to the keyboard.	*/
}


MLOCAL BOOLEAN batch_open()
{
WORD	h, i;
BYTE	*name;

	if(batch->eof) {		/* If the End of the batch file */
	    batch_end();		/* was discovered last time then*/
	    return FALSE;		/* End Batch file input and exit*/
	}
	
	if(batch->stream != CLOSED)
	    return batch->stream;

	name = heap();

	for (i=0; i<MAX_PATHLEN; i++)
	    name[i] = batch->batfile[i];
	while ((h = ms_x_open(name, OPEN_READ)) < 0) {
	    err_flag = TRUE;
	    eprintf(MSG_BATMISS, name);	/* prompt for batch file    */
	    heap_get(strlen(name)+1);
	    cmd_pause("");
	    heap_set(name);
	}
	err_flag = FALSE;
	batch->stream = h;
	return TRUE;
}

GLOBAL VOID batch_close()
{
	if(batchflg != 0 && 
	       batch->stream != CLOSED) {	/* Check if the batch file  */
	    batch_cnt = 0;			/* currently open if YES    */
	    ms_x_close(batch->stream);		/* then flush the internal  */
	    batch->stream = CLOSED;		/* buffer and close file.   */
	}
}

#if defined(DOSPLUS)

GLOBAL VOID inherit_batch_file(bc)
BCONTROL FAR *bc;
{
	WORD	i;
	BYTE	FAR *p_heap;
	BYTE	*l_heap;

	/* inherit any parent batch file first */
	if (bc->bcontrol) inherit_batch_file(bc->bcontrol);

	/* create a new batch structure */
	batch_new();

	batch->offset = bc->offset;	/* continue at same offset */
	for (i=0;i<4;i++) batch->ret_offset[i] = bc->ret_offset[i];
	batch->batshift = bc->batshift;

	for (i=0; i<MAX_PATHLEN; i++)	/* get the batch pathname */
	    batch->batfile[i] = bc->batfile[i];
	batch->batfile[MAX_PATHLEN-1] = 0;
	
	/* get command line */

	p_heap = MK_FP(*parent_psp+16,bc->batcmd);
	l_heap = heap();		

	while (1) {
	     while(*p_heap) *l_heap++ = *p_heap++;
	     *l_heap++ = *p_heap++;
	     if (*p_heap == 0) {
	         *l_heap = 0;
		 break;
	     }
	}
	heap_get(l_heap-heap());

	batchflg++;
}


GLOBAL VOID inherit_parent_state()
{
	UWORD	FAR *p;
	BCONTROL FAR *bc;
	UWORD	root_psp;
	
	root_psp = *parent_psp;
	while(1) {
	    p = MK_FP(root_psp-1,8);
	    if (p[0] == 0x4F43 && p[1] == 0x4D4D &&
	        p[2] == 0x4E41 && p[3] == 0x0044) break;
	
	    p = MK_FP(root_psp,0x16);
	    root_psp = *p;
	}	              

	
	p = MK_FP(root_psp+16,batch_seg_ptr);
#if 0
	printf("batch_seg_ptr = %04X:%04X\n",p);
	printf("parent batch_seg_ptr = %04X\n",*p); 
#endif
	if (*p == 0 || *p == 0xFFFF) return;
	
	bc = MK_FP(*p,0);

	inherit_batch_file(bc);	

	*p = 0;

	p = MK_FP(root_psp+16,&echoflg);
	echoflg = *p;
}
#endif

GLOBAL VOID batch_new()
/* save current batch file heap contexts to high memory */
{
BYTE   *hp_start;
WORD	hp_size;
UWORD	i;
BCONTROL FAR *bc;
#if defined(CDOSTMP)
UWORD FAR *ptr;
#endif

	if (batchflg != 0)
	    hp_start = batch->heap_start;
	else
	    hp_start = heap();

	hp_size = heap() - hp_start;

	i = (sizeof(BCONTROL) + hp_size + 15)/16;
	mem_alloc(&bc, &i, i, i);	/* allocate new batch structure */
	
	if (i == 0) {			/* if we can't allocate one	*/
	    longjmp(break_env, IA_HEAP);/* then pretend heap has run out*/
	}				/* to force termination.        */

	bc->bcontrol = batch;		/* Link to Previous Structure	*/
	batch = bc;			/* make this current batch struc*/
	batch->eof = NO;		/* Have not found the EOF yet	*/
	batch->offset = 0L;		/* start at beginning of File	*/
	for (i=0;i<4;i++) batch->ret_offset[i] = 0L;
	batch->batshift = 0;		/* No Shift Factor		*/
	batch->stream = CLOSED;		/* Batch file is not open	*/
	batch->fcontrol = forptr;	/* Save current FOR control	*/
	batch->heap_start = hp_start;	/* Save original heap		*/
	batch->heap_size = hp_size;

	for (i=0; i < hp_size; i++) {
	    batch->save_area[i] = hp_start[i];
	}

	heap_set(hp_start);		/* free up heap used by old batch */

#if defined(CDOSTMP)
	ptr = MK_FP(pd->P_PSP, TmpPspEchoFlgPtr);
	*ptr = (UWORD)&echoflg;
	ptr = (UWORD FAR *) &ptr;	/* coerce a FAR * to local data */
	i = FP_SEG(ptr);		/* so we can get local data segment */
	ptr = MK_FP(pd->P_PSP, TmpPspDataSeg);
	*ptr = i;			/* save local data segment */
	ptr = MK_FP(pd->P_PSP, TmpPspBatchSeg);
	*ptr = (UWORD)(((ULONG)batch) >> 16);
#else
	/* Get segment address of batch and put it where novell	*/
	/* can find it.						*/

	*batch_seg_ptr = (UWORD)(((ULONG)batch) >> 16);
#endif
}


MLOCAL VOID batch_old()
/* restore current batch file heap contents from high memory */
{
BCONTROL FAR *bc;
UWORD	i;
#if defined(CDOSTMP)
UWORD FAR *ptr;
#endif

	heap_set(batch->heap_start+batch->heap_size);
	for (i=0; i<batch->heap_size; i++) {
	    batch->heap_start[i] = batch->save_area[i];
	}
	bc = batch;
	forptr = batch->fcontrol;	/* Restore the previous for	*/
	for_flag = (BOOLEAN) forptr;	/*  control structures 		*/
	batch = batch->bcontrol;	/* restore ptr to previous batch */
	mem_free(&bc);			/* free up batch memory */
#if defined(CDOSTMP)
	ptr = MK_FP(pd->P_PSP, TmpPspBatchSeg);
	*ptr = (UWORD)(((ULONG)batch) >> 16);
#endif
}

/*
 *	Read lines repeatedly from the batch file until a line in 
 *	the correct format is read from the batch file or the EOF
 *	has been reached.
 */
MLOCAL VOID batch_read(line, goto_flg)
BYTE	*line;		/* Command Line Buffer		*/
BOOLEAN goto_flg;	/* Goto Command Flag		*/
{
BYTE	*l;		/* we need to deblank line */
	do {
	    batch_line(line, goto_flg);		/* Read the next line from  */
	    l = deblank(line);			/* the batch file and return*/
	    if(*l != ';') {
		if(goto_flg && *l == ':')	/* a line in the correct    */
		    return;			/* format.		    */

		if(!goto_flg && *l != ':')
		    return;
	    }
	} while(!batch->eof);
}

/*
 *	Read one line from the batch file and place the expanded data into
 *	the buffer LINE.
 */
MLOCAL VOID batch_line(line, goto_flg)
BYTE	*line;		/* Command Line Buffer		*/
BOOLEAN goto_flg;	/* Goto Command Flag		*/
{
REG WORD i;
REG BYTE *s;
WORD	n, env_start;
BYTE	c, *bp;
BYTE	env_str[128];
BOOLEAN quote = FALSE;
LONG old_offset;
int j;

	env_start = NULL;		/* Copy the environment into a	 */

#if 0
/* 'eject any line starting with 'rem' */
	old_offset = batch->offset;
	i=0;
	do{
	 switch(c=*batch_ptr()){
	 case  0x1a: 
	            batch->eof = YES;		/* We have come to the end  */
   	            c = '\r';			/* of the batch file so set */
    	            break;			/* flag and mark end of line*/
 	 default:
    		if (i < MAX_LINE)
			line[i++] = c;
    		if (dbcs_lead(c)) {
		 if ((c = *batch_ptr()) >= ' ' && i < MAX_LINE)
	    		line[i++] = c;
		}
	 }/*switch*/
	}while(c!='\r');
	
	if (*batch_ptr() != '\n')  batch->offset--;
	 	line[i]=0;
	j=0;
	while(line[j]){
		if (line[j] != ' ')
			break;
		j++;
		}
	if (( strlwr(line[j])   == 'r') &&
    	    ( strlwr(line[j+1]) == 'e') &&
    	    ( strlwr(line[j+2]) == 'm') &&
    	    ( strlwr(line[j+3]) == ' ')){
		if (echoflg)
			printf("%s\n",line);
		line[0]='\0';  
		return;
		}

	batch->offset = old_offset;
	batch->eof    = NO;

#endif
/*rbf-end*/
	
/* process line */
	i = 0;
	do {
 	    switch(c = *batch_ptr()) {
		case '\0':			/* In OS/2 install.bat file  */
#if 0
		    if (i < MAX_LINE)		/* "ECHO \0" displays blank  */
			line[i++] = '\r';	/* line - so we insert a CR  */
		    while (c != '\r') {		/* then swallow rest of line */
			c = *batch_ptr();	/* read next character - if  */
			if (c == 0x1a)		/* it's an EOF mark then use */
			    goto end_of_file;	/* end-of-file code else end */
		    }				/* up falling into '\r' code */
#else
		    c = *batch_ptr();
		    if ((c == '\r') && (i < MAX_LINE))
			line[i++] = '\r';
#endif

		case '\r':			/* carriage return */
		    if(*batch_ptr() != '\n')	/*  skip line feed */
			batch->offset--;	/* if present	   */
		    break;

		case '"':			/* Support Quoted strings   */
		    quote = !quote;		/* in batch files.	    */
		    goto save_it;

		case PIPE_CHAR: 		/* Handle Piped Output	    */
		    if(goto_flg || quote)	/* Ignore this character if */
			goto save_it;		/* we are searching for a   */
						/* Label or Quote.	    */
		    line[i] = '\0';
		    c = *deblank(line);		/* normal case we just      */
		    if ((c !='\0') && (c != ':') && following_command()) {
			c = '\r';		/* simulate a CR and set    */
			pipe_out = YES;		/* Pipe Output flag.	    */
		    } else if (c == ':') {	/* if it's a label */
		    	for(;(c != '\r') && (c != 0x1A); c = *batch_ptr())
			    if (c == 0x1A)	/* eat rest of the line     */
				batch->eof = YES;
			if(*batch_ptr() != '\n')/*  skip line feed */
			    batch->offset--;	/* if present	   */
			c = '\r';
		    } else {			/* if it's a syntax error    */
			swallow_line(line);	/* discard the rest of line  */
			i = 0;			/* start again with new line */
		    }
		    break;

		case '%':	/* The following code checks to see if the   */
				/* string starting at line[env_start-1] is   */
				/* define in the environment if it is then   */
				/* its definition replaces it in the input   */
				/* line. Otherwise no change is made.	     */
		    if(env_start) {
			env_start--;
			line[i] = '\0'; 		/* Terminate Input   */
			strcpy(env_str, line+env_start);/* Copy the String   */
			strupr(env_str);		/* and force string  */
			bp = (BYTE *)heap();		/* into Uppercase    */
			i = env_start;
			env_start = NULL;

			strcat(env_str,"=");
			if (env_scan(env_str,bp)) {
			    if (novell_extension(env_str,bp)) break;
			}
			
			while(*bp && i < MAX_LINE-1)
			    line[i++] = *bp++;
			break;
		    }
		    
		    c = *batch_ptr();
		    if (c == '\r') {
			batch->offset--;	/* rewind to point to '\r'   */
		    	break;			/* then break to normal code */
		    }
		    if (c < '0' || c > '9') {	/* if not a parameter	     */
			if(c != '%')		/* or a '%' character	     */
			    env_start = i+1;	/* save its start address in */
			goto save_it;		/* the string and wait for   */
		    }				/* the terminating '%'	     */
		    
		    n = c - '0' + batch->batshift;     /* get parameter # 0-9 and   */
						/* add in SHIFT offset	     */
		    s = batch->batcmd;
		    while(n-- && strlen(s))	/* skip all other parameters */
			s += strlen(s)+1;	/*   before the one we want  */

		    if((strlen(s) + i) >= MAX_LINE) /* Break if Greater than MAX_LINE*/
			break;
		    strcpy (line + i, s);	/* get the substitution */
		    i += strlen (s);		/* add in its size */
		    break;

		case 0x1a:
	    end_of_file:
		    batch->eof = YES;		/* We have come to the end  */
		    c = '\r';			/* of the batch file so set */
		    break;			/* flag and mark end of line*/

		 default:
	    save_it:
		    if (i < MAX_LINE)
			line[i++] = c;
		    if (dbcs_lead(c)) {
			if ((c = *batch_ptr()) >= ' ' && i < MAX_LINE)
			    line[i++] = c;
		    }
		}
	} while (c != '\r');			/* repeat until CR	   */

	line[i] = '\0'; 			/* Terminate the line and  */

	if(batch->eof)
	    return;

#if 0	/* not DOS compatible */
	if(*batch_ptr() == 0x1A)		/* Check if the next this  */
	    batch->eof = YES;			/* the end of the file if  */
	else					/* YES then set the flag   */
	    batch->offset--;			/* force the character to  */
#endif						/* be re-read next time    */
	return; 				/* return to the caller    */
}

MLOCAL BOOLEAN following_command()
/* return true if we have a possible command on the rest of the line */
{
LONG	old_offset;
BOOLEAN	res = FALSE;
BYTE	*s;

	old_offset = batch->offset;		/* save batch offset */
	while (TRUE) {
	    s = batch_ptr();			/* look ahead at batch file */
	    if (*s == '\r' || *s == 0x1a || (!dbcs_lead(*s) && *s == PIPE_CHAR))
		break;
	    if (!is_blank(s)) {
	        res = TRUE;			/* possible command if we   */
		break;				/* hit non whitespace char  */
	    }
	    if (dbcs_lead(*s)) {
		s = batch_ptr();
		if (*s == '\r' || *s == 0x1a)
		    break;
	    }
	}
	batch->offset = old_offset;		/* restore batch offset */
	return res;
}

MLOCAL VOID swallow_line(s)
BYTE	*s;
/* there is a syntax error on this line - swallow it and say so */
{
BYTE	c;

	prompt();				/* possibly echo the prompt */
	if (echoflg)				/* echo to screen if wanted */
	    printf("%s%c",s,PIPE_CHAR);
	
	do {
	    c = *batch_ptr();
	    if (c ==  0x1a) {
		c = '\r';			/* pretend to be end of line*/
		batch->eof = YES;		/* We have come to the end  */
		break;				/* flag and mark end of line*/
	    }
	    if (echoflg)			/* echo to screen if wanted */
		putc(c);
	} while (c != '\r');
	if (echoflg)
	    putc('\n');

	if (*batch_ptr() != '\n')		/*  skip line feed */
		   batch->offset--;		/* if present	   */

	eprintf(MSG_SYNTAX);			/* report syntax error	*/
}

/*
 *	In order to improve performance of the batch file processing
 *	the Batch file is read in blocks of BATCH_BUF characters.
 *	and the routine BATCH_CHAR then returns a pointer to a character
 *	from the buffer (filling the buffer if required).
 */
MLOCAL BYTE *batch_ptr()
{
	BYTE FAR *buf;
	UWORD bufsize;
	UWORD i;
	
	if(batch->eof)
	    return(batch_eof);
	    
	if (batch->offset < batch_off ||
		batch->offset >= (batch_off + (LONG) (batch_cnt - 1))) {

	    batch_off = batch->offset;
	    ms_x_lseek (batch->stream, batch->offset, 0);
	    batch_cnt = far_read(batch->stream, gp_far_buff, sizeof(batch_buf));
	    if(batch_cnt <= 0) {
		batch->eof = YES;
		return(batch_eof);
	    }
	    for (i=0; i<sizeof(batch_buf); i++) batch_buf[i] = gp_far_buff[i];
	}
	return(&batch_buf[(UWORD) (batch->offset++ - batch_off)]);
}


/*.pa*/
/*
 *	BATCH FILE COMMANDS
 *	===================
 *
 *	The following commands are used almost entirely in BATCH files and
 *	have little or no meaning outside that environment.
 */
GLOBAL VOID CDECL cmd_shift ()
{
	batch->batshift++;		/* Increment the Shift Offset	*/
}


MLOCAL WORD label_ignore_char(s)
BYTE	*s;
{
	if (*s == '=') return(1);
	if (*s == ';') return(1);
	if (*s == ',') return(1);
	if (*s == ' ') return(1);
	return(0);
}

/*
 *	Extract the first a valid characters from the label and then
 *	zero terminate the resulting string.
 */
MLOCAL BYTE * make_label(label)
BYTE *label;  
{
REG BYTE *bp;
UWORD i;

	label = deblank(label);		/* remove leading white space */
	while (label_ignore_char(label))
	    label = skip_char(label);
	
	bp = label;
	
	while (is_filechar(bp))		/* skip over valid chars      */
	    bp = skip_char(bp);

	*bp = '\0';			/* make label zero terminated */
	return label;
}

GLOBAL VOID CDECL cmd_goto (label)		  /* goto label in batch file */
REG BYTE    *label;
{
UWORD	i;
BYTE	*bp, s[MAX_LINE+2]; 		/* Allocate buffer for Batch Input  */
	
	if (!batchflg)			/* if not in batch mode 	    */
	    return;			/* this command is ignored	    */

	if(*label == ':')		/* Ignore any leading ':'	    */
	    label++;
	
	label = make_label(label);	/* Convert to Label Format	    */

	batch->offset = 0L;		/* rewind the batch file	    */
	batch->eof = NO;		/* So it cannot be EOF		    */

	if(!batch_open())		/* Check the Batch file is open     */
	    return;			/* and stop if the function fails.  */

	while(!batch->eof) {		/* while not end of file read next  */
	    batch_read(s, YES); 	/* line and return the next command  */
	    bp = deblank(s);
	    if((*bp == ':') && !strnicmp(make_label(bp+1),label, 8))
		return;
	}

	batch_end();			/* Stop any further batch file	   */
	crlfflg = YES;			/* processing and print the error  */
	eprintf(MSG_LABEL, label);
}


GLOBAL VOID CDECL cmd_gosub (label)	  /* gosub label in batch file */
REG BYTE    *label;
{
UWORD	i;
BYTE	*bp, s[MAX_LINE+2]; 		/* Allocate buffer for Batch Input  */
	
	if (!batchflg)			/* if not in batch mode 	    */
	    return;			/* this command is ignored	    */

	if (batch->ret_offset[3] != 0L) {
		batch_end();
		crlfflg = YES;
		eprintf(MSG_GOSUB);
		return;
	}

	if(*label == ':')		/* Ignore any leading ':'	    */
	    label++;
	
	label = make_label(label);	/* Convert to Label Format	    */

	i = 0;
	while (batch->ret_offset[i] != 0L) i++;
	batch->ret_offset[i] = batch->offset;
	
	batch->offset = 0L;		/* rewind the batch file	    */
	batch->eof = NO;		/* So it cannot be EOF		    */

	if(!batch_open())		/* Check the Batch file is open     */
	    return;			/* and stop if the function fails.  */

	while(!batch->eof) {		/* while not end of file read next  */
	    batch_read(s, YES); 	/* line and return the next command  */
	    bp = deblank(s);
	    if((*bp == ':') && !strnicmp(make_label(bp+1),label, 8))
		return;
	}

	batch_end();			/* Stop any further batch file	   */
	crlfflg = YES;			/* processing and print the error  */
	eprintf(MSG_LABEL, label);
}

GLOBAL	VOID CDECL cmd_return()
{
	UWORD i;
	
	if (!batchflg) return;
	if (batch->ret_offset[0] == 0L) {
		batch_end();
		crlfflg = YES;
		eprintf(MSG_RETURN);
		return;
	}
	i = 0;
	while ((batch->ret_offset[i] != 0L)&&(i<4)) i++;
	batch->offset = batch->ret_offset[i-1];
	batch->ret_offset[i-1] = 0L;
}

#if SWITCH_ENABLED
GLOBAL	VOID CDECL cmd_switch(list)
REG BYTE	*list;
{
	BYTE	*list_start;
	BYTE	*label;
	WORD	i,j;
	BYTE	c;

	if (!batchflg) return;
	list_start = list;

switch_retry:
	list = list_start;
	i = psp_poke(STDIN,1);

#if defined(CDOSTMP)
	c =(BYTE) bdos(C_RAWIO, 0xFD);	/* Get a character from console */

	if ((c==0) ||(dbcs_lead(c)))
	    bdos(C_RAWIO, 0xFD);	/* skip second byte in DBCS pair */
#else
	c = (BYTE) msdos(MS_C_RAWIN, 0);/* Get a character from console */
	if ((c==0) || (dbcs_lead(c)))
	    msdos(MS_C_RAWIN, 0);	/* skip second byte in DBCS pair */
#endif
	psp_poke(STDIN,i);
	
	if (c==0x03) int_break();	/* check for CTRL-C */	
	if (c==0x0d) c = '1';		/* return gives default of 1 */
	
	i = (WORD) (c - '1');
	if (i<0 || i>8) goto switch_retry;	/* ignore invalid keys */

	j = 0;	
	while (j<i) {
		while (*list != ',' && *list != 0) list++;
		if (*list == 0) goto switch_retry;
		j++;
		list++;
		list = deblank(list);
	}
	label = list;
	while (*list != ',' && *list != 0) list++;
	*list = 0;
	cmd_gosub(label);
}
#endif


/*.pa*/
/*
 *	The IF command supports the following syntax:-
 *
 *	IF [NOT] string1 == string2 COMMAND
 *	IF [NOT] ERRORLEVEL n COMMAND
 *	IF [NOT] EXIST filename COMMAND
/*RG-02-
 *	IF [NOT] USERID n COMMAND
 *	IF [NOT] LOGINNAME string COMMAND
 *	IF [NOT] GROUPNAME string COMMAND
 *	IF [NOT] KEY ["string"] char COMMAND
 *
 */
#if defined(CDOSTMP) || defined(CDOS)
MLOCAL BYTE *if_opt[] = {"exist", "direxist", "errorlevel", "key", "userid", "loginname", "groupname", NULL };
#else
MLOCAL BYTE *if_opt[] = {"exist", "direxist", "errorlevel", NULL };
#endif
/*RG-02-end*/

MLOCAL UWORD if_index(cmd)
BYTE **cmd;
{
UWORD	i, j;

	for(i = 0; if_opt[i]; i++) {		/* Scan Through the option   */
	    j = strlen(if_opt[i]);		/* list and return the index */
						/* of the matching option    */
	    if(strnicmp(*cmd, if_opt[i], j))	/* and update the string     */
	        continue;			/* pointer.		     */
	    *cmd = deblank(*cmd+j);

	    while(*(*cmd) == '=')		/* Remove any "=" string     */
	        (*cmd)++;			/* present in the command    */
	    *cmd = deblank(*cmd);		/* Used by many install files*/
	    break;
	}

	return i;
}

#define	OP_EQ 0
#define OP_NE 1
#define OP_LE 2
#define OP_LT 3
#define OP_GE 4
#define OP_GT 5

MLOCAL	WORD get_operator(op)
BYTE	*op;
{
	if (op[0] == '=') return(OP_EQ);
	if (op[0] == '!' && op[1] == '=') return(OP_NE);
	if (op[0] == '<') {
	    if (op[1] == '>') return(OP_NE);
	    if (op[1] == '=') return(OP_LE);
	    return(OP_LT);
	}
	if (op[0] == '>') {
	    if (op[1] == '=') return(OP_GE);
	    return(OP_GT);
	}
	return(-1);
}

MLOCAL	LONG	get_decimal(s)
BYTE	*s;
{
	LONG	total = 0;
	
	if (*s == '#') s++;
	
	while (*s>='0' && *s<='9') {
	    total *= 10;
	    total += (LONG) (*s-'0');
	    s++;
	}

	return(total);
}

MLOCAL BOOLEAN CDECL test_cond(cptr)
BYTE	**cptr;
{
	BYTE	*cmd,*str1, *str2, *ptr;
	DTA	search;
	BOOLEAN not, cond, neg,is_user;
	BYTE	level;
        BYTE    c[]=" \n";
        WORD	attr;
	UWORD   userid;
	LONG	val1,val2;

        cmd=*cptr;
        not = cond = NO;			/* Initialise the Flags     */

	if(!strnicmp(cmd = deblank(cmd), "not", 3)) {
	    not = YES;
	    cmd = deblank(cmd+3);
	}

	switch(if_index(&cmd)) {
	
	    /*
	     *	EXIST Option extract the possibly ambiguous filename
	     *	and check if it exists.
	     */
	    case 0:
		cmd = deblank(get_filename(heap(), cmd, YES));
		cond = !ms_x_first(heap(), ATTR_STD|ATTR_HID, &search);
		break;
		
	    /*
	     * DIREXIST Option checks if the given directory exists
	     */
	    case 1:
		cmd = deblank(get_filename(heap(), cmd, YES));
		attr = ms_x_chmod(heap(), ATTR_ALL, 0);
		if (attr < 0) cond = FALSE;
		else cond = (attr & 0x10);
		break;

	    /*
	     *	ERRORLEVEL Option extract the decimal number from the
	     *	command line.
	     */
	    case 2:
		level = 0;
		neg = FALSE;
		
		if(*cmd =='-') {
		    neg = TRUE;
		    cmd++;
		}

		if(!isdigit(*cmd)) {		/* SYNTAX error if the	    */ 
		    syntax();			/* first character is not a */
		    return FALSE;			/* digit.		    */
		}

		while(isdigit(*cmd))
		    level = level * 10 + (*cmd++ - '0');

		level = level & 0x00FF;

		if (neg) level = -level;

	        cond = (level<=(err_ret & 0x00FF)); 
		break;


/*RG-02*/
#if !defined(NOXBATCH)
#if (defined(CDOSTMP) || defined(CDOS))
	    /*
	     *	KEY ["string"] [==] [""] Search for the string "string" and
             *  display it if it exists, then read a key and echo it.
             *  However, if the string to match is null "" then do a keyboard
             *  status check and return TRUE if there is no key there
	     */
	    case 3:
		    cmd = deblank(cmd);
                ptr=cmd;
                if (display_string(&ptr)!=0)
                    return FALSE;

        	    cmd = deblank(ptr); 
	        while(*cmd == '=')		/* Remove any "=" string     */
	            cmd++;

	        cmd = deblank(cmd);
                if ((*cmd==0)||(*(cmd+1)!=' ')) { /* check for condition */
 	            syntax();
                    return FALSE;
                }

                /* read a character from the keyboard */
    	        c[0]=bdos(C_RAWIO, 0xFD);	/* Get a character from console */

                /* echo the char typed */
	        if (echoflg) 				/* echo to screen if wanted */
		    putc(c[0]);
	        crlf();			/* print cr/lf			*/
                
                /* check if it matches */
                if((tolower(c[0])==*cmd)||(toupper(c[0])==*cmd))
                    cond=TRUE;
                else 
                    cond=FALSE;

                /* skip the condition */
	        while (*cmd!=' ')
                    cmd++; /* skip the char */
        
		    break;

#endif
#if !defined (NOSECURITY) && (defined(CDOSTMP) || defined(CDOS))
	    /*
	     *	USERID Option extract the 4 digit hex user id
	     *	and check if it is us.
	     */
	    case 4:
                if (!login_enabled())
                    return FALSE;
		    cmd = deblank(cmd);
	        if(aschextobin(cmd)==get_user_on_station())
                    cond=TRUE;
                else
                    cond=FALSE;
                
                do {   /* skip past the user id */
                    if ((*cmd>='0' && *cmd<='9')
                        ||(tolower(*cmd)>='a' && tolower(*cmd)<='f'))
                        cmd++; /* skip the hex digit */
                    else {
               		    syntax();	
		            return FALSE;
                    }
	        } while (*cmd!=' ');
		break;

            /*
	     *	LOGINNAME Option extract the loginname and check if it is us.
	     */
	    case 5:
                if (!login_enabled())
                    return FALSE;
                is_user=TRUE;

            /*
	     *	GROUPNAME Option extract the loginname and check if it is us.
	     */
	    case 6:
                if (!login_enabled())
                    return FALSE;
		    cmd = deblank(cmd);
                if(aschextobin(user_info.userid)!=get_user_on_station()) {
	            if(get_user_info(get_user_on_station())!=0) {
                        syntax();
                        is_user=cond=FALSE;
			return FALSE;
                    }
                }
                if((is_user==TRUE)&&(strncmp(strlwr(cmd),strlwr(user_info.loginname),strlen(user_info.loginname))==0)) {
                    cond=TRUE;
                    cmd+=strlen(user_info.loginname);
                }
                else if((is_user!=TRUE)&&(strncmp(strlwr(cmd),strlwr(user_info.groupname),strlen(user_info.groupname))==0)) {
                    cond=TRUE;
                    cmd+=strlen(user_info.groupname);
                }
                else {
                    cond=FALSE;
	            while (*cmd!=' ')
                        cmd++; /* skip the name */
                }
                if (*cmd!=' ') {
                    is_user=cond=FALSE;
                }
                is_user=FALSE;
		break;

#endif
#endif /*NOXBATCH*/
/*RG-02-end*/            
            /*
	     *	String comparison option.
	     */
	    default:
		str1 = cmd;			/* Extract String 1	    */

		while ((!is_blank(cmd)) && (*cmd != '=') &&
		       ((*cmd != '!') || (cmd[1]!= '=')) &&
		       (*cmd != '<') && (*cmd != '>')) {

		    cmd = skip_char(cmd);
		}

		str2 = cmd;
		cmd = deblank(cmd);

		attr = get_operator(cmd++);

		if (attr == -1) {
		    syntax();
		    return(FALSE);
		}

		*str2 = 0;

		if (*cmd == '=' || *cmd == '>') cmd++;

		cmd = deblank(cmd);
		str2 = cmd;
		while (!is_blank(cmd)) cmd = skip_char(cmd);
		*cmd++ = 0;

		if (*str1 == '#') {
		    val1 = get_decimal(str1);
		    val2 = get_decimal(str2);    
		    switch(attr) {
		      case OP_EQ: cond = (val1==val2); break;
		      case OP_NE: cond = (val1!=val2); break;
		      case OP_LT: cond = (val1<val2); break;
		      case OP_LE: cond = (val1<=val2); break;
		      case OP_GT: cond = (val1>val2); break;
		      case OP_GE: cond = (val1>=val2); break;
		    }
		}
		else switch(attr) {
		case OP_EQ:
		    cond = (strcmp(str1,str2) == 0);
		    break;
		case OP_NE:
		    cond = (strcmp(str1,str2) != 0);
		    break;
		case OP_LT:
		    cond = (strcmp(str1,str2) < 0);
		    break;
		case OP_LE:
		    cond = (strcmp(str1,str2) <= 0);
		    break;
		case OP_GT:
		    cond = (strcmp(str1,str2) > 0);
		    break;
		case OP_GE:
		    cond = (strcmp(str1,str2) >= 0);
		    break;
		}
		break;
	}

	if(not) 				/* if negated condition     */
	    cond = !cond;

        *cptr=cmd;
        return cond; /* write result back */
}

#if !defined(NOXBATCH)
BOOLEAN is_it_or(BYTE *cmd)
{
	cmd--;
	if ((*cmd != 0) && (*cmd != '\t') && (*cmd != ' '))
	    return FALSE;
        cmd++;
	if (strnicmp(cmd, "or", 2) != 0)
	    return FALSE;
	cmd+=2;    
	if ((*cmd != '\t') && (*cmd != ' '))
	    return FALSE;
	return TRUE;
}
#endif
GLOBAL VOID CDECL cmd_if(cmd)
BYTE	*cmd;
{
        BOOLEAN cond;
        
	ifcond=cond=test_cond(&cmd);
	
	if(!*deblank(cmd)) {		/* and return a SYNTAX error*/
	    syntax();			/* if it is empty.	    */
	    return;
	}
        if(!cond) {
#if !defined(NOXBATCH)
	    while (!is_it_or(cmd) &&
		       (*cmd != 0)) {
	        if (strnicmp(cmd,"ECHO",4)==0) return;       
	        cmd++;
	    }
            if (*cmd==0) return; /* no OR's so quit now */
	    
	    if_context = TRUE;
	    
	    docmd(deblank(cmd), YES); /* New command starts at "or" */
            
#endif
        }
	else {
	    cmd = deblank(cmd);

	    if (strnicmp(cmd,"AND",3)) {
		if (parse(cmd)) return;	  /* IF won't have been 'parsed' for */
					  /* > or < redirectors so do it now */
	    }

	    if_context = TRUE;
	    				  /* Execute command if the    */
	    docmd(cmd, YES);              /* condition flag is TRUE    */
	}
        if_context=FALSE;
}

/*RG-03*/
#if !defined(NOXBATCH)
GLOBAL VOID CDECL cmd_or(cmd)
BYTE	*cmd;
{
        BOOLEAN cond;
        BYTE    *org_cmd;
        
        cond=test_cond(&cmd);
	if(!*deblank(cmd)) {		/* and return a SYNTAX error*/
	    syntax();			/* if it is empty.	    */
	    return;
	}
        if(!cond) {
       	    org_cmd = cmd;			/* now look for "OR" */
	    while (!is_it_or(cmd) &&
		   (*cmd != 0)) {
		if (strnicmp(cmd,"ECHO",4)==0) while(*cmd) cmd++;
		else cmd++;
	    }
            if (*cmd==0) { /* oh dear, no ORs */
                if (ifcond) /* but so far so good, so do command anyway */
	           docmd(deblank(org_cmd), YES); 
                return;
            }
	    docmd(deblank(cmd), YES); /* New command starts at "or" */
            return;
        }
	else {
            cmd = deblank(cmd);
	    
	    if (strnicmp(cmd,"AND",3)) {
	        if (parse(cmd)) return;	  /* IF won't have been 'parsed' for */
					  /* > or < redirectors so do it now */
	    }
	    
	    ifcond=cond;                 /* Execute command if the    */
	    docmd(cmd, YES);    	 /* condition flag is TRUE    */
        }
}
#endif /*NOXBATCH*/
/*RG-03-end*/


GLOBAL VOID CDECL cmd_for(s)
BYTE *s;
{
FCONTROL *fc;
BYTE *bp1;

	fc = (FCONTROL *) heap_get(sizeof(FCONTROL));

						/* Allocate Control Struct  */
	if(forptr)				/* and prevent nesting of  */
	    goto for_error;			/* FOR Command. 	   */

	s = deblank(s); 			/* Remove leading blanks   */
	if ((*s++ != '%') ||			/* Get the FOR variable    */
	    (fc->forvar = *s++) < ' ')		/* character and save	   */
	    goto for_error;

	if(strnicmp(s = deblank(s), "in", 2))	/* Check for the correct   */
	    goto for_error;			/* command syntax.	   */

	s = deblank(s+2);
	if (*s++ != '(')
	    goto for_error;

	fc->files = (BYTE *)heap();		/* Allocate FOR parameter  */
	while(*s && *s != ')') {		/* buffer and scan the	   */
	    bp1 = (BYTE *)heap();		/* command line generating */
						/* zero terminated strings */
	    while(strchr(batch_sep, *s))	/* Skip any separators	   */
		s = skip_char(s);

	    while(   *s != ')' &&		/* then copy all valid	   */
		     !strchr(batch_sep, *s))	/* characters into buffer  */
		      				/* then zero terminate	   */
		copy_char(&bp1, &s);

	    *bp1++ = '\0';
	    heap_get(strlen(heap()) + 1);	/* Preserve String	    */
	}
	
	*(BYTE *)heap_get(1) = '\0'; 		/* Final String is zero     */
						/* bytes in length	    */
	
	s = deblank(s);
	if(*s++ != ')')
	    goto for_error;
	
	if(strnicmp(s = deblank(s), "do", 2))
	    goto for_error;

	if(in_flag & REDIR_ACTIVE)	/* If Input redirection has been */
	    in_flag |= REDIR_FOR;	/* enabled for this command force*/
					/* it on for the complete command*/

	if(out_flag & REDIR_ACTIVE)	/* If Output redirection has been*/
	    out_flag |= REDIR_FOR;	/* enabled for this command force*/
					/* it on for the complete command*/

	fc->cmd = (BYTE *)heap_get(strlen(s = deblank(s+2)) +1);
	strcpy(fc->cmd, s);

	fc->sflg = NO;			/* File matching inactive  */
	for_flag = YES; 		/* Turn FOR processing ON  */
	forptr = fc;			/* Save control Structure  */
	return;
	
for_error:				/* When a Syntax error occurs	*/
	heap_set((BYTE *) fc);		/* restore the heap and print	*/
	syntax();			/* an error message.		*/
	return;
}

GLOBAL VOID for_end()
{
	if(for_flag) {
	    heap_set((BYTE *) forptr);	/* Terminate FOR processing	*/
	    forptr = (FCONTROL *) NULL; /* restore the HEAP and reset	*/
	    for_flag = NO;		/* control flags.		*/
	}
}

/*.pa*/
/*
 *	This command generates the displayed prompt based on the contents
 *	of the string PROMPT= in the environment. Otherwise the default
 *	prompt string DEFAULT_PROMPT is used.
 */

MLOCAL BOOLEAN prompt_flg = FALSE;	/* Prompt Flag 			*/

MLOCAL VOID prompt()			/* display command line prompt	*/
{
	REG BYTE *cp;
	BYTE	 buf[MAX_PATHLEN];
	BYTE	 c;
#if !STACK
	BYTE	 cpbuf[MAX_ENVLEN];
#endif
/*rbf*/
#if 1
BYTE	prmptcpbuf[MAX_ENVLEN];
REG BYTE *prmptcp = prmptcpbuf;
#endif

	if(!echoflg)			/* Return if No Echo	*/
	    return;



#if defined(CPM)
	cp = heap();
	strcpy(cp, "[CPM] $u$p$g");
#else
	if(env_scan(msg_prmeq, cp = (BYTE *)heap()))
	    strcpy(cp, DEFAULT_PROMPT);
#endif

	if(prompt_flg)			/* If the previous Prompt display   */
	    strcpy(cp, "$n$g");		/* terminated due to a Critical	    */
					/* then just display the default    */
	prompt_flg = TRUE;		/* drive.			    */

#if STACK
	cp = stack(strlen(cp) + 1);
#else
	cp = &cpbuf[0];
#endif
	strcpy(cp, heap());

	while((c = *cp++) != 0) {	/* get next character */
	    if (c != '$')		/* if not '$', print as is */
		putc (c);
	    else {
		c = *cp++;
		switch(tolower(c)) {	/* else get next character */
		 case '\0':		/* Treat "$\0" as an invalid 	    */
		     cp--;		/* prompt command sequence	    */
		     break;
		 case 't':		/* print current time */
		    disp_systime();
		    break;
		 case 'd':		/* print current date */
		    disp_sysdate();
		    break;
		 case 'p':		/* print current path */
		    if (ms_x_curdir(drive+1, buf) < 0)
		        printf(MSG_DRV_INVALID);
		    else
		        printf("%c:%s%s", drive+'A', pathchar, buf);
		    break;
		 case 'v':		/* print version number */
		    cmd_ver("");
		    break;
		 case 'n':		/* print default drive */
		    putc((BYTE) drive+'A');
		    break;
		 case 'g':		/* print ">" */
		    putc ('>');
		    break;
		 case 'l':		/* print "<" */
		    putc ('<');
		    break;
		 case 'b':		/* print "|" */
		    putc ('|');
		    break;
		 case 'q':		/* print "=" */
		    putc ('=');
		    break;
		 case '_':		/* print CR,LF */
		    crlf();
		    break;
		 case 'h':		/* print backspace, space, backspace */
		    printf("\b \b");
		    break;
		 case 'e':		/* print ESC character */
		    putc ('\33');
		    break;
/*RG-01 */ 

#if !defined(NOXBATCH)
#if !defined (NOSECURITY) 
#if defined(CDOSTMP) || defined(CDOS)
		 case 'm':		/* print mail status */
                if (login_enabled()) {
                    if(chk_mail()) { /* we have mail */
	                if(env_scan("MAIL=", heap())) 
                            printf(MSG_UHAVEMAIL);
                        else
                            printf("%s",heap());
                    }
                }
		    break;

		 case 'u':		/* Display the User Name */
                if (login_enabled()) {
                    if((aschextobin(user_info.userid)!=get_user_on_station())||(strlen(user_info.userid)==0)) {
	                if(get_user_info(get_user_on_station())!=0) 
		                printf("#%04X",get_user_on_station());
                        else
		                printf("%s",user_info.loginname);
                    }
                    else
		            printf("%s",user_info.loginname);
                }
                break;
#endif
#endif
#endif /*NOXBATCH*/
/*RG-01-end*/
#if defined(CPM)
		 case 'u':		/* Display the User Number */
		    printf("%d", user);
		    break;
#endif
#if defined(DOSPLUS)
		 case 'u':
		    if (!env_scan("LOGINNAME=",heap()))
		        printf("%s",heap());
		    break;
#endif		    
		 case '$':
		    putc ('$'); 	/* print single '$' */
		    break;
		 case 'x':
		    if ((allow_pexec)&&(!batchflg)) prompt_exec();
		    break;
		 default:		/* Otherwise the character */
		    break;
		}
	    }
	}

	prompt_flg = FALSE;		/* Prompt display completed OK	*/
}


#if !defined(CDOSTMP)
/* The following functions are called by the int2e_handler function in
 * COM.C. If a program is run from a batch file and calls INT 2E to
 * execute a new batch file the original batch file must NOT be terminated.
 * Therefore batch and batchflg must be saved, set to zero, and then
 * restored when INT 2E returns. - EJH 
 */
 
GLOBAL VOID int2e_start()
{
	batchflg_save = batchflg;
	batchflg = 0;
	batch_save = batch;
	echoflg_save = echoflg;
	echoflg = ECHO_ON;
}

GLOBAL VOID int2e_finish()
{
	batchflg = batchflg_save;
	batch = batch_save;
	echoflg = echoflg_save;
}

#endif

#if defined(CDOS)
MLOCAL VOID map_user_page(UWORD window_page, UWORD physical_page)
{
UWORD pblk[3];
	pblk[0] = window_page;
	pblk[1] = physical_page;
	pblk[2] = 0;
	bdos(181, pblk);
}


MLOCAL BYTE FAR * map_pd_mem(UWORD ppd, UWORD wp, UWORD seg, UWORD offset)
{
/* Map memory for another process into our memory space so we examine it */
UWORD base_seg;
UWORD page;
PD FAR * pdptr;
UWORD sysdat;
UWORD mptbl;
UWORD FAR * ptr;
UWORD mp_off;
UWORD i;
MPAD FAR * mp;


	sysdat = FP_SEG(pd);
	ptr = MK_FP(sysdat, 0xc8);
/* DEBUG */
	if (*ptr == 0) /* for DEBUG under XM assume non-banked system */
	    return(MK_FP(seg, offset));
/* DEBUG */
	ptr = MK_FP(sysdat, *ptr + 8);
	mptbl = *ptr;

	/* get segment of 4k page to map */
	base_seg = (seg + (offset+15)/16) & 0xff00;


	/* now find physical page to map */

	pdptr = (PD FAR *) MK_FP(sysdat, ppd);
	
	for (mp_off = pdptr->P_MPAR; mp_off !=0;) {
	    mp = MK_FP(sysdat, mp_off);
	    mp_off = mp->link;
	    if ((base_seg >= mp->start) && 
		(base_seg < (mp->start + mp->length))) {
		page = mp->xios;
		for (i = 0; i < ((base_seg - mp->start)/1024); i++){
		    ptr = MK_FP(mptbl, page*2);
		    page = *ptr;
		}
		page = 4*page + (((base_seg - mp->start)/0x0100) & 3);
		map_user_page(wp, page);
		return(MK_FP(wp*0x0100,(seg*16+offset) & 0x0fff));
	    }
	}

	/* not found in mpad's - must be non-banked address */
	return(MK_FP(seg, offset));
}

MLOCAL VOID ip_poke(UWORD ppd, UWORD wp, UWORD seg, UWORD offset, UBYTE val)
{
UBYTE FAR *ptr;

	ptr = map_pd_mem(ppd, wp, seg, offset);
	*ptr = val;
}

MLOCAL UBYTE ip_peek(UWORD ppd, UWORD wp, UWORD seg, UWORD offset)
{
UBYTE FAR *ptr;

	ptr = map_pd_mem(ppd, wp, seg, offset);
	return(*ptr);
}

MLOCAL UWORD ip_peek_word(UWORD ppd, UWORD wp, UWORD seg, UWORD offset)
{
	return (ip_peek(ppd, wp, seg, offset) + 
			256*ip_peek(ppd, wp, seg, offset+1));
}

GLOBAL VOID inherit_TMP_state(VOID)
/* inherit batch files */
/* NB. This is bodged - we don't inherit FOR state, or redirection */
{
PD FAR *parent;
UWORD batch_seg;
UWORD tmp_mpad;
UWORD mp_off;
MPAD FAR * mp;
VOID FAR *window;
UWORD win_page;
UWORD i;

	parent = (PD FAR *) MK_FP(FP_SEG(pd), pd->P_PARENT);

	/* verify our parent is a Tmp, forget if it isn't */
	if ((parent->P_NAME[0] != 'T') ||
	    (parent->P_NAME[1] != 'm') ||
	    (parent->P_NAME[2] != 'p'))
	    return;

	/* allocate a 4k aligned window to bank data into */
	i = 2*4096/16;
	mem_alloc(&window, &i, i, i);
	if (i==0)
	    return;
	win_page = (FP_SEG(window) + 0x0100) / 0x100;

	batch_seg = ip_peek_word(pd->P_PARENT, win_page,
		parent->P_PSP, TmpPspBatchSeg);

	/* do we have any batch files to inherit */
	if (batch_seg) {

            echoflg = (BOOLEAN)ip_peek_word(pd->P_PARENT, win_page,
		ip_peek_word(pd->P_PARENT, win_page,
		    parent->P_PSP, TmpPspDataSeg),
		ip_peek_word(pd->P_PARENT, win_page,
		    parent->P_PSP, TmpPspEchoFlgPtr));

		/* recover Tmp's hidden mpads */

	    tmp_mpad = ip_peek_word(pd->P_PARENT, win_page,
		parent->P_PSP, TmpPspMpad);

	    for (mp_off = parent->P_MPAR; mp_off != 0;) {
		mp  = MK_FP(FP_SEG(parent), parent->P_MPAR);
		mp_off  = mp->link;
	    }	
	
	    mp->link = tmp_mpad;
	    inherit_batch_file(pd->P_PARENT, win_page, batch_seg);
	    mp->link = 0;		/* unlink again */
	}
	map_user_page(0, 0);	/* force user page to be unmapped */
	bdos(141,0);		/* dispatch to re-map TPA memory */
	mem_free(&window);	/* free the window */
}

MLOCAL VOID inherit_batch_file(UWORD ppd, UWORD win_page, UWORD batch_seg)
{
UBYTE FAR * dst;
BCONTROL FAR *save0;
FCONTROL *save1;
BYTE	 *save2;
WORD	 save3;
UWORD	i;
UWORD	data_seg;
PD FAR *parent;



	/* if we are in a nested batch file, inherit older batch file first */
	i = ip_peek_word(ppd, win_page, batch_seg, 2);

	if (i != 0)
	    inherit_batch_file( ppd, win_page, i);

	batch_new();			/* new incarnation of batch	*/

	save0 = batch->bcontrol; save1 = batch->fcontrol;
	save2 = batch->heap_start; save3 = batch->heap_size;

	dst = (BYTE FAR *) batch;		/* copy batch structure */
	for (i=0; i<sizeof(BCONTROL); i++)	/* from parental PD */
	    dst[i] = ip_peek(ppd, win_page, batch_seg, i);

	/* now terminate parental batch processing by poking EOF */
	ip_poke(ppd, win_page,
		batch_seg, (UWORD) &batch->eof - (UWORD) &batch->bcontrol,
		(UBYTE) TRUE);

	/*
	 *	Copy the invoking command and the individual elements 
	 *	of the command line into a buffer ready for processing
	 */

	parent = (PD FAR *) MK_FP(FP_SEG(pd), pd->P_PARENT);
	data_seg = ip_peek_word(pd->P_PARENT, win_page,
		parent->P_PSP, TmpPspDataSeg);

	i = (UWORD) batch->batcmd;
	batch->batcmd = (BYTE *)heap();	/* Initialize Parameter Buffer	*/

	while (ip_peek_word(ppd, win_page, data_seg, i) != 0)
		*(BYTE *)heap_get(1) = ip_peek(ppd, win_page, data_seg, i++);

	*(WORD *)heap_get(2) = 0;	/* Double NULL is a terminator	 */
					/* for command line params	 */

	batch->bcontrol = save0; batch->fcontrol = save1;
	batch->heap_start = save2; batch->heap_size = save3;

	batchflg++;			/* increment batch flag		*/

	crlfflg  = YES;			/* print CR/LF after this  */
}
#endif


EXTERN	N_CMD	novell_ext_list[];

EXTERN BYTE FAR * CDECL farptr(BYTE *);
EXTERN BYTE FAR * CDECL cgroupptr(BYTE *);

EXTERN BOOLEAN CDECL call_novell(BYTE *, BYTE *, WORD);
EXTERN BOOLEAN CDECL nov_station(WORD *);
EXTERN WORD    CDECL nov_connection();


MLOCAL	BOOLEAN novell_extension(src,dst)
BYTE	*src;
BYTE	*dst;
/*
 * Check if src string is a novell string to be expanded. eg login_name.
 * if so, put expansion in dst.
 */
{
	N_CMD FAR *n_cmd_p;
	BYTE  FAR *cpf; 
	WORD	i;

	for (i=0;src[i] && src[i]!='=';i++);
	src[i] = 0;

	n_cmd_p = (N_CMD FAR *)farptr((BYTE *)&novell_ext_list[0]);

	while(n_cmd_p->string) {

	    cpf = cgroupptr(n_cmd_p->string);

	    for(i=0; (cpf[i]==src[i]) && src[i]; i++);

	    if(cpf[i]==src[i]) {
	        (*n_cmd_p->func)(dst);
	    	return(0);
	    }
	    
	    n_cmd_p++;
	}
	return(1);
}



GLOBAL	VOID	CDECL get_login_name(dst)
BYTE	*dst;
{
	struct s_nov_e346_in {
	    WORD	len;
	    BYTE	code;
	} nov_e346_in;
	struct s_nov_e346_out {
	    WORD	len;
	    BYTE	level;
	    LONG	id;
	} nov_e346_out;
	struct s_nov_e336_in {
	    WORD	len;
	    BYTE	code;
	    LONG	id;
	} nov_e336_in;
	struct s_nov_e336_out {
	    WORD	len;
	    LONG	id;
	    WORD	type;
	    BYTE	name[48];
	} nov_e336_out;
	
	nov_e346_in.len  = 1;
	nov_e346_in.code = 0x46;
	nov_e346_out.len = 5;
	nov_e346_out.id = -1L;
	call_novell((BYTE *)&nov_e346_in, (BYTE *)&nov_e346_out, 0xE3);
	
	if (nov_e346_out.id == -1L) {
	    *dst = 0;
	    return;
	}
	
	nov_e336_in.len  = 5;
	nov_e336_in.code = 0x36;
	nov_e336_in.id   = nov_e346_out.id;
	nov_e336_out.len = 54;
	call_novell((BYTE *)&nov_e336_in, (BYTE *)&nov_e336_out, 0xE3);

	strcpy(dst,nov_e336_out.name);

}

GLOBAL	VOID CDECL get_pstation(dst)
BYTE	*dst;
{
	WORD	sn[3];
	
	if (nov_station(sn)) {
	    *dst = 0;
	    return;
	}
	
	sprintf(dst,"%04X%04X%04X",sn[0],sn[1],sn[2]);
}


GLOBAL	VOID CDECL get_full_name(dst)
BYTE	*dst;
{
/* scan bindery */
struct s_nov_e337_in {
	WORD	len;
	BYTE	code;
	LONG	last_object_id;
	WORD	object_type;
	BYTE	object_name_len;
	BYTE	object_name[48];
	} nov_e337_in;

struct s_nov_e337_out {
	WORD	len;
	LONG	object_id;
	WORD	object_type;
	BYTE	object_name[48];
	BYTE	object_flag;
	BYTE	object_security;
	BYTE	object_has_properties;
	} nov_e337_out;


/* scan property */
struct s_nov_e33c_in {
	WORD	len;
	BYTE	code;
	WORD	object_type;
	BYTE	object_name_len;
	BYTE	object_name[48];
	LONG	sequence_num;
	BYTE	property_name_len;
	BYTE	property_name[16];
	} nov_e33c_in;

struct s_nov_e33c_out {
	WORD 	len;
	BYTE	property_name[16];
	BYTE	property_flags;
	BYTE	property_security;
	LONG	sequence_num;
	BYTE	property_has_value;
	BYTE	more_properties;
	} nov_e33c_out;


/* read property */
struct s_nov_e33d_in {
	WORD 	len;
	BYTE	code;
	WORD	object_type;
	BYTE	object_name_len;
	BYTE	object_name[48];
	BYTE	segment_num;
	BYTE	property_name_len;
	BYTE	property_name[16];
	} nov_e33d_in;

struct s_nov_e33d_out {
	WORD	len;
	BYTE	property_value[128];
	BYTE	more_segments;
	BYTE	property_flags;
	} nov_e33d_out;

int res;



get_login_name(nov_e337_in.object_name);
nov_e337_in.object_name_len = strlen(nov_e337_in.object_name);
if (!nov_e337_in.object_name_len){
	*dst = 0;
	return;
	}

nov_e337_in.code = 0x37;
nov_e337_in.len = sizeof(struct s_nov_e337_in) - 2;
nov_e337_out.len = sizeof(struct s_nov_e337_out) -2 ;
nov_e337_in.last_object_id = -1L;
nov_e337_in.object_type = 0x0100;  /*user*/


for(;;){
	res = call_novell((BYTE *)&nov_e337_in, (BYTE *)&nov_e337_out, 0xe3 );
	if ( res == 0xfc )
		break;
	else if ( res ){
		*dst= 0;
		return;
		}

	if ( nov_e337_out.object_has_properties ){
	   nov_e33c_in.code = 0x3c;
	   nov_e33c_in.len = sizeof(struct s_nov_e33c_in) - 2;
	   nov_e33c_out.len = sizeof(struct s_nov_e33c_out) - 2;
	   nov_e33c_in.object_type = nov_e337_out.object_type;
	   nov_e33c_in.object_name_len = sizeof( nov_e33c_in.object_name );
	   strcpy( nov_e33c_in.object_name, nov_e337_out.object_name );
	   nov_e33c_in.sequence_num = -1L;
	   nov_e33c_in.property_name_len = 1;
	   nov_e33c_in.property_name[0] = '*';

	   for(;;){
	          res = call_novell( (BYTE *)&nov_e33c_in, (BYTE *)&nov_e33c_out, 0xe3 );
		  if ( res == 0xfb )
			break;
		  else if ( res ){
			*dst = 0;
			return;
			}

		  if ( nov_e33c_out.property_has_value ){
			nov_e33d_in.code = 0x3d;
			nov_e33d_in.len = sizeof (struct s_nov_e33d_in) - 2;
			nov_e33d_out.len = sizeof (struct s_nov_e33d_out) - 2;
			nov_e33d_in.object_type = nov_e337_out.object_type;
			nov_e33d_in.object_name_len = sizeof( nov_e33d_in.object_name );
			strcpy( nov_e33d_in.object_name, nov_e337_out.object_name );
			nov_e33d_in.segment_num = 1;
			nov_e33d_in.property_name_len = strlen( nov_e33c_out.property_name );
			strcpy(nov_e33d_in.property_name, nov_e33c_out.property_name );
			for(;;){
				res = call_novell( (BYTE *)&nov_e33d_in, (BYTE *)&nov_e33d_out, 0xe3);
				if ( res == 0xec )
					break;
				else if ( res ){
					*dst = 0;
					return;
					}

				if (!strcmp(nov_e33c_out.property_name,"IDENTIFICATION")){
					strcpy(dst,nov_e33d_out.property_value);
					return;
					}
				nov_e33d_in.segment_num++;
				}/*for*/
   		      }/*if*/
		      nov_e33c_in.sequence_num = nov_e33c_out.sequence_num;
		  }/*for*/
	    nov_e337_in.last_object_id = nov_e337_out.object_id;
	    }/*if*/
	}/*for*/
}



GLOBAL	VOID CDECL get_hour(dst)
BYTE	*dst;
{
	SYSTIME  time;
		
	ms_gettime(&time);
	
	if (time.hour > 12) time.hour -= 12;
	if (time.hour == 0) time.hour = 12;

	sprintf(dst,"%d",time.hour);
}

GLOBAL	VOID CDECL get_hour24(dst)
BYTE	*dst;
{
	SYSTIME  time;
		
	ms_gettime(&time);

	sprintf(dst,"%02d",time.hour);
}

GLOBAL	VOID CDECL get_minute(dst)
BYTE	*dst;
{
	SYSTIME  time;
		
	ms_gettime(&time);

	sprintf(dst,"%02d",time.min);
}

GLOBAL	VOID CDECL get_second(dst)
BYTE	*dst;
{
	SYSTIME  time;
		
	ms_gettime(&time);

	sprintf(dst,"%02d",time.sec);
}

GLOBAL	VOID CDECL get_am_pm(dst)
BYTE	*dst;
{
	SYSTIME	time;
	BYTE	FAR *p;
	
	ms_gettime(&time);
	
	if (time.hour >= 12) p = cgroupptr(PM_TIME);
	else		     p = cgroupptr(AM_TIME);

	while (*p) *dst++ = *p++;
	*dst = 0;
}

GLOBAL	VOID CDECL get_greeting(dst)
BYTE	*dst;
{
	SYSTIME	time;
	BYTE	FAR *p;
	
	ms_gettime(&time);
	
	if (time.hour < 12)
	    p = cgroupptr(GREETING_MORNING);
	else if (time.hour < 17)
	    p = cgroupptr(GREETING_AFTERNOON);
	else
	    p = cgroupptr(GREETING_EVENING);

	while (*p) *dst++ = *p++;
	*dst = 0;
}

GLOBAL	VOID CDECL get_year(dst)
BYTE	*dst;
{
	SYSDATE	date;
	
	ms_getdate(&date);
	
	sprintf(dst,"%d",date.year);
}

GLOBAL	VOID CDECL get_short_year(dst)
BYTE	*dst;
{
	SYSDATE date;
	
	ms_getdate(&date);

	sprintf(dst,"%d",date.year%100);
}

GLOBAL	VOID CDECL get_month(dst)
BYTE	*dst;
{
	SYSDATE	date;
	
	ms_getdate(&date);
	
	sprintf(dst,"%d",date.month);
}

GLOBAL	VOID CDECL get_month_name(dst)
BYTE	*dst;
{
	SYSDATE	date;
	BYTE	*m;
		
	ms_getdate(&date);
	
	switch(date.month) {
	case 1:  m = JAN_M; break;
	case 2:  m = FEB_M; break;
	case 3:  m = MAR_M; break;
	case 4:  m = APR_M; break;
	case 5:  m = MAY_M; break;
	case 6:  m = JUN_M; break;
	case 7:  m = JUL_M; break;
	case 8:  m = AUG_M; break;
	case 9:  m = SEP_M; break;
	case 10: m = OCT_M; break;
	case 11: m = NOV_M; break;
	case 12: m = DEC_M; break;
	}

	sprintf(dst,"%s",m);
}




GLOBAL	VOID CDECL get_day(dst)
BYTE	*dst;
{
	SYSDATE	date;
	
	ms_getdate(&date);
	
	sprintf(dst,"%d",date.day);
}

GLOBAL	VOID CDECL get_nday_of_week(dst)
BYTE	*dst;
{
	SYSDATE date;
	
	ms_getdate(&date);
	
	sprintf(dst,"%d",date.dow+1);
}

GLOBAL	VOID CDECL get_day_of_week(dst)
BYTE	*dst;
{
	SYSDATE date;
	
	ms_getdate(&date);
	
	sprintf(dst,"%s",day_names(date.dow));
}

GLOBAL	VOID CDECL get_os_version(dst)
BYTE	*dst;
{
	env_scan("VER=",dst);
}

GLOBAL	VOID CDECL get_connection(dst)
BYTE	*dst;
{
	int	i;
	
	i = nov_connection();
	if (i==-1) {
	    *dst = 0;
	    return;
	}
	
	sprintf(dst,"%d",i);
}
