{***********************************************************************}
{									}
{ __ExecPgm   Load and execute a child process				}
{									}
{ procedure __ExecPgm(PathName : _Path ;				}
{		       CmdLine : string;				}
{		       FcbFlag : boolean; var ErrorCode : word);	}
{									}
{	      PathName	The full path and file name of the process	}
{			to be loaded and executed.  The extension of	}
{			the file must be included.			}
{	      CmdLine	The command line string to pass to the child	}
{			process.					}
{	      FcbFlag	If TRUE, offsets $5C and $6C of the child	}
{			process's program segment prefix are filled	}
{			with formatted FCBs representing the first	}
{			two command line parameters.  If zero, the FCBs }
{			are inherited from the parent process. COMMAND	}
{			loads a process as if this flag were TRUE;	}
{			hence many programs expect the command line	}
{			parameters so formatted.			}
{	      ErrorCode The returned error status code: 		}
{			0 - File successfully loaded and executed.	}
{			2 - File not found; make sure the extension	}
{			    is specified.				}
{			3 - Path not found.  One of the entries in the	}
{			    path was not present.			}
{			5 - Access denied.				}
{			8 - Insufficient memory to load the program.	}
{			If DOS version 3.0 or later is used, a call to	}
{			__RetErUtl can be made to return more informa-	}
{			tion on the error.				}
{									}
{ Description This procedure loads and executes the program whose file	}
{	      name is specified in PathName.  The program is loaded and }
{	      executed as if invoked from DOS with the command line	}
{	      given in CmdLine.  The spawned program returns control	}
{	      when either execution ends or Ctrl/Break is pressed (un-	}
{	      less, of course, the child process alters the normal	}
{	      termination process by altering the usual exit addresses	}
{	      in the program segment prefix).  A zero error code	}
{	      indicates successfuly execution.				}
{									}
{	      Many of the DOS external commands (e.g., FORMAT, DISKCOPY,}
{	      BACKUP, etc.) expect the command line parameters to be	}
{	      formatted in the file control blocks at offset $5C and	}
{	      $6C in the program segment prefix.  In fact, when 	}
{	      COMMAND loads a program it always does just that for	}
{	      COM and EXE files.  If the FcbFlag is TRUE, __ExecPgm	}
{	      will format the child's program segment prefix in this	}
{	      way; otherwise the parent's unformatted file control	}
{	      blocks are inherited by the child process. Moreover, if	}
{	      the FcbFlag is TRUE, the command line (placed at offset	}
{	      $80 in the PSP) is formatted as COMMAND does:		}
{									}
{	      o A trailing #13 is appended, but the leading length	}
{		byte indicator does not reflect its presence.		}
{	      o If the command line is not NULL, a leading blank is	}
{		inserted (and the length byte indicates this).		}
{									}
{	      Note that vectors $0, $2, $23, $24 and $75 are preserved, }
{	      because Turbo Pascal uses these vectors and must guar-	}
{	      antee that the child process does not leave them in an	}
{	      altered state.  Similarly, for safety, the Ctrl/Break	}
{	      ($1B) vector is preserved. These vectors are placed in	}
{	      the global (exported) variables __CurVectorXX so that if	}
{	      parent gains control before the child terminates (e.g.,	}
{	      via an interrupt), it can set the vectors accordingly.	}
{									}
{	      Finally, if there is insufficent DOS memory to load the	}
{	      process and the global variable _UseHeap is TRUE, the	}
{	      procedure attempts to utilitize heap space to load the	}
{	      child.  The procedure __HeapMem is used to reduce the	}
{	      heap.  There must be enough available heap space to	}
{	      guarantee the parent has _MinHeap bytes available, and	}
{	      the child has access to _MinMemory bytes.  These values	}
{	      are stored in the global variable _ExecMemory exported	}
{	      through the unit interface.				}
{									}
{ Version     4.00 (C)Copyright Blaise Computing Inc.  1987		}
{_______________________________________________________________________}


procedure __ExecPgm(PathName  : _Path ;
		      CmdLine : string;
		     FcbFlag  : boolean; var ErrorCode : word);

  type
    FcbBlk  = record		       { File control block		}
		Drive	  : char;
		FileName  : array[1..8] of char;
		FileExt   : array[1..3] of char;
		CurBlock  : integer;
		RecSize   : integer;
		SizeLo	  : integer;
		SizeHi	  : integer;
		Creation  : integer;
		Reserved  : array[1..10] of byte;
		CurRecNo  : byte;
		RelSizeLo : integer;
		RelSizeHi : integer
	      end;

    ParmBlk = record		       { Parameter block used by DOS	}
		SegAds	  : word;      { Segment address of environment }
		CmdPtr	  : pointer;   { Pointer to command line	}
		Fcb1Ptr   : pointer;   { Pointer to FCB to be put at $5C}
		Fcb2Ptr   : pointer    { Pointer to FCB to be put at $6C}
	      end;

  var
    Reg,SaveReg : registers;
    BlockValue	: ParmBlk;
    Fcb1,Fcb2	: FcbBlk;
    TempPtr	: pointer;
    EnvPtr	: pointer;
    NewEnvPtr	: pointer;
    EnvOffset	: word;
    CmdLineZ	: string;
    Command	: string;
    CmdLen	: word;
    PathZ	: _Path;
    PathLen	: integer;
    TopAvail	: longint;
    FreeSize	: word;
    NewMaxAvail : longint;
    ErCode	: word;

  begin {__ExecPgm}

    { The segment address of the environment is at offset $2C within	}
    { the program segment prefix.  This is true even if the environment }
    { is altered with __PutEnUtl, because it updates the PSP.		}

    BlockValue.SegAds := MemW[PrefixSeg:$2C];

    { Now the command line.  It is in the right format: the length	}
    { followed by the actual characters.  If, however, FcbFlag is	}
    { TRUE, the command line is adjusted in the manner that COMMAND	}
    { uses.  See the prologue documentation.				}

    if (FcbFlag) then
       begin
	 if (Length(CmdLine) <> 0) then
	    Command := #32 + CmdLine
	 else
	    Command := '';
	 Command    := Command + #13;
	 Command[0] := Chr(Length(Command) - 1)
       end
    else
       Command := CmdLine;

    BlockValue.CmdPtr := @Command;

    { If the FcbFlag is TRUE, build the FCBs out of the command line.	}
    { DOS function call $29 is used to scan the command line and	}
    { create the FCBs.							}

    if (FcbFlag) then
       begin
	 CmdLen := Length(Command) + 1;      { First must build an	}
	 Move(Command[1],CmdLineZ,CmdLen);   { ASCIIZ string.		}
	 CmdLineZ[CmdLen + 1] := #0;
	 with Reg do
	   begin
	     AH := $29;
	     AL := 1;
	     DS := Seg(CmdLineZ);
	     SI := Ofs(CmdLineZ);
	     ES := Seg(Fcb1);
	     DI := Ofs(Fcb1);
	     Intr(_DOS,Reg);	       { DS:SI now points to the next	}
	     AH := $29; 	       { item in the command line.	}
	     AL := 1;
	     ES := Seg(Fcb2);
	     DI := Ofs(Fcb2);
	     Intr(_DOS,Reg);
	     BlockValue.Fcb1Ptr := @Fcb1;
	     BlockValue.Fcb2Ptr := @Fcb2
	   end
       end
    else			       { Use FCBs in parent's PSP	}
       begin
	 BlockValue.Fcb1Ptr := Ptr(PrefixSeg,$5C);
	 BlockValue.Fcb2Ptr := Ptr(PrefixSeg,$6C)
       end;

    { Now save the current values of the interrupt vectors that 	}
    { Turbo Pascal uses (also Ctrl/Break), and set them to their	}
    { original values (SaveIntXX).  Upon return, the vectors are set	}
    { back to the current values.					}

    GetIntVec($00,_CurVector00);       { Save the interrupt vectors	}
    GetIntVec($02,_CurVector02);       { so if the parent gains control }
    GetIntVec($1B,_CurVector1B);       { before the child terminates,	}
    GetIntVec($23,_CurVector23);       { it can reset the vectors.	}
    GetIntVec($24,_CurVector24);
    GetIntVec($75,_CurVector75);

    SetIntVec($00,SaveInt00);	       { Set the original values for	}
    SetIntVec($02,SaveInt02);	       { the child.			}
    SetIntVec($23,SaveInt23);
    SetIntVec($24,SaveInt24);
    SetIntVec($75,SaveInt75);

    with Reg do 		       { Call DOS function $4B (EXEC)	}
      begin
	AH := $4B;
	AL := 0;
	ES := Seg(BlockValue);	       { ES:BX points to the parameter	}
	BX := Ofs(BlockValue);	       { block we constructed.		}
	PathLen := Length(PathName);
	Move(PathName[1],PathZ,PathLen);
	PathZ[PathLen] := #0;
	DS := Seg(PathZ);	       { DS:DX points to an ASCIIZ	}
	DX := Ofs(PathZ);	       { string representing the full	}
				       { pathname of the file loaded.	}

	SaveReg := Reg; 	       { Reg is destroyed by EXEC call, }
	__DOSPgm(Reg);		       { but may be required for next	}
				       { attempt to load program.	}
	if ((Flags and FCarry) <> 0) then
	   begin
	     ErrorCode := AX;	       { Carry Flag is set, so an error }

	     { If the error is insufficient memory (8), and the global	}
	     { variable _UseHeap is TRUE, and there is enough space on	}
	     { the heap (the requirement is determined by _ExecMemory), }
	     { free space from the heap and attempt to load the program.}
	     { TopAvail is the available space at the top of the heap,	}
	     { which the only heap area that can be released to DOS.	}

	     TopAvail := __TopHpMem(FreeSize);
	     if ((ErrorCode = 8) and _UseHeap and
		 (TopAvail >
		   (_ExecMemory._MinMemory + _ExecMemory._MinHeap))) then
		begin
		  __HeapMem(_ExecMemory._MinHeap,NewMaxAvail,ErrorCode);
		  if (ErrorCode = 0) then
		     begin	       { Attempt to load the program	}
		       Reg := SaveReg; { after restoring the registers. }
		       __DOSPgm(Reg);
		       if ((Flags and FCarry) <> 0) then
			  ErrorCode := AX
		       else
			  ErrorCode := 0;

		       { Now attempt to restore the heap to its prior	}
		       { size.	This may be impossible if the child has }
		       { altered available memory (e.g., stayed resi-	}
		       { dent) or if parent heap operations have taken	}
		       { place.  However, it will be grown as large as	}
		       { possible.					}

		       __HeapMem(TopAvail,NewMaxAvail,ErCode)
		     end
		end
	   end
	else
	   ErrorCode := 0
     end;

    SetIntVec($00,_CurVector00);       { Restore the interrupt vectors	}
    SetIntVec($02,_CurVector02);       { which may not be restored by	}
    SetIntVec($1B,_CurVector1B);       { the child process. DOS restores}
    SetIntVec($23,_CurVector23);       { the $23 and $24 to the values	}
    SetIntVec($24,_CurVector24);       { they had upon the EXEC call;	}
    SetIntVec($75,_CurVector75)        { thus we must restore them.	}

  end;	{__ExecPgm}
