1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/dos/dos32krnl/process.c
5  * PURPOSE:         DOS32 Processes
6  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7  *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8  */
9 
10 /* INCLUDES *******************************************************************/
11 
12 #include "ntvdm.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 #include "emulator.h"
18 #include "cpu/cpu.h"
19 
20 #include "dos.h"
21 #include "dos/dem.h"
22 #include "dosfiles.h"
23 #include "handle.h"
24 #include "process.h"
25 #include "memory.h"
26 
27 #include "bios/bios.h"
28 
29 #include "io.h"
30 #include "hardware/ps2.h"
31 
32 #include "vddsup.h"
33 
34 /* PRIVATE FUNCTIONS **********************************************************/
35 
36 static VOID DosInitPsp(IN WORD Segment,
37                        IN WORD EnvBlock,
38                        IN LPCSTR CommandLine,
39                        IN LPCSTR ProgramName)
40 {
41     PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment);
42     PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
43     LPCSTR PspName;
44     USHORT i;
45 
46     /* Link the environment block */
47     PspBlock->EnvBlock = EnvBlock;
48 
49     /*
50      * Copy the command line.
51      * Format of the CommandLine parameter: 1 byte for size; 127 bytes for contents.
52      */
53     PspBlock->CommandLineSize = min(*(PBYTE)CommandLine, DOS_CMDLINE_LENGTH);
54     CommandLine++;
55     RtlCopyMemory(PspBlock->CommandLine, CommandLine, DOS_CMDLINE_LENGTH);
56 
57     /*
58      * Initialize the owner name of the MCB of the PSP.
59      */
60 
61     /* Find the start of the file name, skipping all the path elements */
62     PspName = ProgramName;
63     while (*ProgramName)
64     {
65         switch (*ProgramName++)
66         {
67             /* Path delimiter, skip it */
68             case ':': case '\\': case '/':
69                 PspName = ProgramName;
70                 break;
71         }
72     }
73     /* Copy the file name up to the extension... */
74     for (i = 0; i < sizeof(Mcb->Name) && PspName[i] != '.' && PspName[i] != '\0'; ++i)
75     {
76         Mcb->Name[i] = RtlUpperChar(PspName[i]);
77     }
78     /* ... and NULL-terminate if needed */
79     if (i < sizeof(Mcb->Name)) Mcb->Name[i] = '\0';
80 
81     // FIXME: Initialize the FCBs
82 }
83 
84 static inline VOID DosSaveState(VOID)
85 {
86     PDOS_REGISTER_STATE State;
87     WORD StackPointer = getSP();
88 
89 #ifdef ADVANCED_DEBUGGING
90     DPRINT1("\n"
91             "DosSaveState(before) -- SS:SP == %04X:%04X\n"
92             "Original CPU State =\n"
93             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
94             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
95             "\n",
96             getSS(), getSP(),
97             getDS(), getES(), getAX(), getCX(),
98             getDX(), getBX(), getBP(), getSI(), getDI());
99 #endif
100 
101     /*
102      * Allocate stack space for the registers. Note that we
103      * already have one word allocated (the interrupt number).
104      */
105     StackPointer -= sizeof(DOS_REGISTER_STATE) - sizeof(WORD);
106     State = SEG_OFF_TO_PTR(getSS(), StackPointer);
107     setSP(StackPointer);
108 
109     /* Save */
110     State->DS = getDS();
111     State->ES = getES();
112     State->AX = getAX();
113     State->CX = getCX();
114     State->DX = getDX();
115     State->BX = getBX();
116     State->BP = getBP();
117     State->SI = getSI();
118     State->DI = getDI();
119 
120 #ifdef ADVANCED_DEBUGGING
121     DPRINT1("\n"
122             "DosSaveState(after) -- SS:SP == %04X:%04X\n"
123             "Saved State =\n"
124             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
125             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
126             "\n",
127             getSS(), getSP(),
128             State->DS, State->ES, State->AX, State->CX,
129             State->DX, State->BX, State->BP, State->SI, State->DI);
130 #endif
131 }
132 
133 static inline VOID DosRestoreState(VOID)
134 {
135     PDOS_REGISTER_STATE State;
136 
137     /*
138      * Pop the state structure from the stack. Note that we
139      * already have one word allocated (the interrupt number).
140      */
141     State = SEG_OFF_TO_PTR(getSS(), getSP());
142 
143 #ifdef ADVANCED_DEBUGGING
144     DPRINT1("\n"
145             "DosRestoreState(before) -- SS:SP == %04X:%04X\n"
146             "Saved State =\n"
147             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
148             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
149             "\n",
150             getSS(), getSP(),
151             State->DS, State->ES, State->AX, State->CX,
152             State->DX, State->BX, State->BP, State->SI, State->DI);
153 #endif
154 
155     setSP(getSP() + sizeof(DOS_REGISTER_STATE) - sizeof(WORD));
156 
157     /* Restore */
158     setDS(State->DS);
159     setES(State->ES);
160     setAX(State->AX);
161     setCX(State->CX);
162     setDX(State->DX);
163     setBX(State->BX);
164     setBP(State->BP);
165     setSI(State->SI);
166     setDI(State->DI);
167 
168 #ifdef ADVANCED_DEBUGGING
169     DPRINT1("\n"
170             "DosRestoreState(after) -- SS:SP == %04X:%04X\n"
171             "Restored CPU State =\n"
172             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
173             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
174             "\n",
175             getSS(), getSP(),
176             getDS(), getES(), getAX(), getCX(),
177             getDX(), getBX(), getBP(), getSI(), getDI());
178 #endif
179 }
180 
181 static WORD DosCopyEnvironmentBlock(IN LPCSTR Environment OPTIONAL,
182                                     IN LPCSTR ProgramName)
183 {
184     PCHAR Ptr, DestBuffer = NULL;
185     SIZE_T TotalSize = 0;
186     WORD DestSegment;
187 
188     /* If we have an environment strings list, compute its size */
189     if (Environment)
190     {
191         /* Calculate the size of the environment block */
192         Ptr = (PCHAR)Environment;
193         while (*Ptr) Ptr += strlen(Ptr) + 1;
194         TotalSize = (ULONG_PTR)Ptr - (ULONG_PTR)Environment;
195     }
196     else
197     {
198         /* Empty environment string */
199         TotalSize = 1;
200     }
201     /* Add the final environment block NULL-terminator */
202     TotalSize++;
203 
204     /* Add the two bytes for the program name tag */
205     TotalSize += 2;
206 
207     /* Add the string buffer size */
208     TotalSize += strlen(ProgramName) + 1;
209 
210     /* Allocate the memory for the environment block */
211     DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL);
212     if (!DestSegment) return 0;
213 
214     DestBuffer = (PCHAR)SEG_OFF_TO_PTR(DestSegment, 0);
215 
216     /* If we have an environment strings list, copy it */
217     if (Environment)
218     {
219         Ptr = (PCHAR)Environment;
220         while (*Ptr)
221         {
222             /* Copy the string and NULL-terminate it */
223             strcpy(DestBuffer, Ptr);
224             DestBuffer += strlen(Ptr);
225             *(DestBuffer++) = '\0';
226 
227             /* Move to the next string */
228             Ptr += strlen(Ptr) + 1;
229         }
230     }
231     else
232     {
233         /* Empty environment string */
234         *(DestBuffer++) = '\0';
235     }
236     /* NULL-terminate the environment block */
237     *(DestBuffer++) = '\0';
238 
239     /* Store the special program name tag */
240     *(DestBuffer++) = LOBYTE(DOS_PROGRAM_NAME_TAG);
241     *(DestBuffer++) = HIBYTE(DOS_PROGRAM_NAME_TAG);
242 
243     /* Copy the program name after the environment block */
244     strcpy(DestBuffer, ProgramName);
245 
246     return DestSegment;
247 }
248 
249 /* PUBLIC FUNCTIONS ***********************************************************/
250 
251 VOID DosClonePsp(WORD DestSegment, WORD SourceSegment)
252 {
253     PDOS_PSP DestPsp    = SEGMENT_TO_PSP(DestSegment);
254     PDOS_PSP SourcePsp  = SEGMENT_TO_PSP(SourceSegment);
255     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
256 
257     /* Literally copy the PSP first */
258     RtlCopyMemory(DestPsp, SourcePsp, sizeof(*DestPsp));
259 
260     /* Save the interrupt vectors */
261     DestPsp->TerminateAddress = IntVecTable[0x22];
262     DestPsp->BreakAddress     = IntVecTable[0x23];
263     DestPsp->CriticalAddress  = IntVecTable[0x24];
264 
265     /* No parent PSP */
266     DestPsp->ParentPsp = 0;
267 
268     /* Set the handle table pointers to the internal handle table */
269     DestPsp->HandleTableSize = DEFAULT_JFT_SIZE;
270     DestPsp->HandleTablePtr  = MAKELONG(0x18, DestSegment);
271 
272     /* Copy the parent handle table without referencing the SFT */
273     RtlCopyMemory(FAR_POINTER(DestPsp->HandleTablePtr),
274                   FAR_POINTER(SourcePsp->HandleTablePtr),
275                   DEFAULT_JFT_SIZE);
276 }
277 
278 VOID DosCreatePsp(WORD Segment, WORD ProgramSize)
279 {
280     PDOS_PSP PspBlock   = SEGMENT_TO_PSP(Segment);
281     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
282 
283     RtlZeroMemory(PspBlock, sizeof(*PspBlock));
284 
285     /* Set the exit interrupt */
286     PspBlock->Exit[0] = 0xCD; // int 0x20
287     PspBlock->Exit[1] = 0x20;
288 
289     /* Set the number of the last paragraph */
290     PspBlock->LastParagraph = Segment + ProgramSize;
291 
292     /* Save the interrupt vectors */
293     PspBlock->TerminateAddress = IntVecTable[0x22];
294     PspBlock->BreakAddress     = IntVecTable[0x23];
295     PspBlock->CriticalAddress  = IntVecTable[0x24];
296 
297     /* Set the parent PSP */
298     PspBlock->ParentPsp = Sda->CurrentPsp;
299 
300     if (Sda->CurrentPsp != SYSTEM_PSP)
301     {
302         /* Link to the parent's environment block */
303         PspBlock->EnvBlock = SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock;
304     }
305 /*
306     else
307     {
308         PspBlock->EnvBlock = SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0);
309     }
310 */
311 
312     /* Copy the parent handle table */
313     DosCopyHandleTable(PspBlock->HandleTable);
314 
315     /* Set the handle table pointers to the internal handle table */
316     PspBlock->HandleTableSize = DEFAULT_JFT_SIZE;
317     PspBlock->HandleTablePtr  = MAKELONG(0x18, Segment);
318 
319     /* Set the DOS version */
320     // FIXME: This is here that SETVER stuff enters into action!
321     PspBlock->DosVersion = DosData->DosVersion;
322 
323     /* Set the far call opcodes */
324     PspBlock->FarCall[0] = 0xCD; // int 0x21
325     PspBlock->FarCall[1] = 0x21;
326     PspBlock->FarCall[2] = 0xCB; // retf
327 }
328 
329 VOID DosSetProcessContext(WORD Segment)
330 {
331     Sda->CurrentPsp = Segment;
332     Sda->DiskTransferArea = MAKELONG(0x80, Segment);
333 }
334 
335 DWORD DosLoadExecutableInternal(IN DOS_EXEC_TYPE LoadType,
336                                 IN LPBYTE ExeBuffer,
337                                 IN DWORD ExeBufferSize,
338                                 IN LPCSTR ExePath,
339                                 IN PDOS_EXEC_PARAM_BLOCK Parameters,
340                                 IN LPCSTR CommandLine OPTIONAL,
341                                 IN LPCSTR Environment OPTIONAL,
342                                 IN DWORD ReturnAddress OPTIONAL)
343 {
344     DWORD Result = ERROR_SUCCESS;
345     WORD Segment = 0;
346     WORD EnvBlock = 0;
347     WORD ExeSignature;
348     WORD LoadSegment;
349     WORD MaxAllocSize;
350 
351     WORD FinalSS, FinalSP;
352     WORD FinalCS, FinalIP;
353 
354     /* Buffer for command line conversion: 1 byte for size; 127 bytes for contents */
355     CHAR CmdLineBuffer[1 + DOS_CMDLINE_LENGTH];
356 
357     DPRINT1("DosLoadExecutableInternal(%d, 0x%p, '%s', 0x%p, 0x%p, 0x%p)\n",
358             LoadType, ExeBuffer, ExePath, Parameters, CommandLine, Environment);
359 
360     if (LoadType != DOS_LOAD_OVERLAY)
361     {
362         /* If an optional Win32 command line is given... */
363         if (CommandLine)
364         {
365             /* ... convert it into DOS format */
366             BYTE CmdLineLen;
367 
368             PBYTE CmdLineSize  = (PBYTE)CmdLineBuffer;
369             LPSTR CmdLineStart = CmdLineBuffer + 1;
370             LPSTR CmdLinePtr   = CmdLineStart;
371 
372             // For debugging purposes
373             RtlFillMemory(CmdLineBuffer, sizeof(CmdLineBuffer), 0xFF);
374 
375             /*
376              * Set the command line: it is either an empty command line or has
377              * the format: " foo bar ..." (with at least one leading whitespace),
378              * and is then always followed by '\r' (and optionally by '\n').
379              */
380             CmdLineLen = (BYTE)strlen(CommandLine);
381             *CmdLineSize = 0;
382 
383             /*
384              * Add the leading space if the command line is not empty
385              * and doesn't already start with some whitespace...
386              */
387             if (*CommandLine && *CommandLine != '\r' && *CommandLine != '\n' &&
388                 *CommandLine != ' ' && *CommandLine != '\t')
389             {
390                 (*CmdLineSize)++;
391                 *CmdLinePtr++ = ' ';
392             }
393 
394             /* Compute the number of characters we need to copy from the original command line */
395             CmdLineLen = min(CmdLineLen, DOS_CMDLINE_LENGTH - *CmdLineSize);
396 
397             /* The trailing '\r' or '\n' do not count in the PSP command line size parameter */
398             while (CmdLineLen && (CommandLine[CmdLineLen - 1] == '\r' || CommandLine[CmdLineLen - 1] == '\n'))
399             {
400                 CmdLineLen--;
401             }
402 
403             /* Finally, set everything up */
404             *CmdLineSize += CmdLineLen;
405             RtlCopyMemory(CmdLinePtr, CommandLine, CmdLineLen);
406             CmdLineStart[*CmdLineSize] = '\r';
407 
408             /* Finally make the pointer point to the static buffer */
409             CommandLine = CmdLineBuffer;
410         }
411         else
412         {
413             /*
414              * ... otherwise, get the one from the parameter block.
415              * Format of the command line: 1 byte for size; 127 bytes for contents.
416              */
417             ASSERT(Parameters);
418             CommandLine = (LPCSTR)FAR_POINTER(Parameters->CommandLine);
419         }
420 
421         /* If no optional environment is given... */
422         if (Environment == NULL)
423         {
424             ASSERT(Parameters);
425             /* ... get the one from the parameter block (if not NULL)... */
426             if (Parameters->Environment)
427                 Environment = (LPCSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
428             /* ... or the one from the parent (otherwise) */
429             else
430                 Environment = (LPCSTR)SEG_OFF_TO_PTR(SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock, 0);
431         }
432 
433         /* Copy the environment block to DOS memory */
434         EnvBlock = DosCopyEnvironmentBlock(Environment, ExePath);
435         if (EnvBlock == 0)
436         {
437             Result = Sda->LastErrorCode;
438             goto Cleanup;
439         }
440     }
441 
442     /*
443      * Check if this is an EXE file or a COM file by looking
444      * at the MZ signature:
445      * 0x4D5A 'MZ': old signature (stored as 0x5A, 0x4D)
446      * 0x5A4D 'ZM': new signature (stored as 0x4D, 0x5A)
447      */
448     ExeSignature = *(PWORD)ExeBuffer;
449     if (ExeSignature == 'MZ' || ExeSignature == 'ZM')
450     {
451         /* EXE file */
452         PIMAGE_DOS_HEADER Header;
453         DWORD BaseSize;
454         PDWORD RelocationTable;
455         PWORD RelocWord;
456         WORD RelocFactor;
457         WORD i;
458 
459         /* Get the MZ header */
460         Header = (PIMAGE_DOS_HEADER)ExeBuffer;
461 
462         /* Get the base size of the file, in paragraphs (rounded up) */
463 #if 0   // Normally this is not needed to check for the number of bytes in the last pages.
464         BaseSize = ((((Header->e_cp - (Header->e_cblp != 0)) * 512) + Header->e_cblp) >> 4)
465                     - Header->e_cparhdr;
466 #else
467         // e_cp is the number of 512-byte blocks. 512 == (1 << 9)
468         // so this corresponds to (1 << 5) number of paragraphs.
469         //
470         // For DOS compatibility we need to truncate BaseSize to a WORD value.
471         // This fact is exploited by some EXEs which are bigger than 1 Mb while
472         // being able to load on DOS, the main EXE code loads the remaining data.
473 
474         BaseSize = ((Header->e_cp << 5) - Header->e_cparhdr) & 0xFFFF;
475 #endif
476 
477         if (LoadType != DOS_LOAD_OVERLAY)
478         {
479             BOOLEAN LoadHigh = FALSE;
480             DWORD TotalSize;
481 
482             /* Find the maximum amount of memory that can be allocated */
483             DosAllocateMemory(0xFFFF, &MaxAllocSize);
484 
485             /* Compute the total needed size, in paragraphs */
486             TotalSize = BaseSize + (sizeof(DOS_PSP) >> 4);
487 
488             /* We must have the required minimum amount of memory. If not, bail out. */
489             if (MaxAllocSize < TotalSize + Header->e_minalloc)
490             {
491                 Result = ERROR_NOT_ENOUGH_MEMORY;
492                 goto Cleanup;
493             }
494 
495             /* Check if the program should be loaded high */
496             if (Header->e_minalloc == 0 && Header->e_maxalloc == 0)
497             {
498                 /* Yes it should. Use all the available memory. */
499                 LoadHigh  = TRUE;
500                 TotalSize = MaxAllocSize;
501             }
502             else
503             {
504                 /* Compute the maximum memory size that can be allocated */
505                 if (Header->e_maxalloc != 0)
506                     TotalSize = min(TotalSize + Header->e_maxalloc, MaxAllocSize);
507                 else
508                     TotalSize = MaxAllocSize; // Use all the available memory
509             }
510 
511             /* Try to allocate that much memory */
512             Segment = DosAllocateMemory((WORD)TotalSize, NULL);
513             if (Segment == 0)
514             {
515                 Result = Sda->LastErrorCode;
516                 goto Cleanup;
517             }
518 
519             /* The process owns its memory */
520             DosChangeMemoryOwner(Segment , Segment);
521             DosChangeMemoryOwner(EnvBlock, Segment);
522 
523             /* Set INT 22h to the return address */
524             ((PULONG)BaseAddress)[0x22] = ReturnAddress;
525 
526             /* Create the PSP and initialize it */
527             DosCreatePsp(Segment, (WORD)TotalSize);
528             DosInitPsp(Segment, EnvBlock, CommandLine, ExePath);
529 
530             /* Calculate the segment where the program should be loaded */
531             if (!LoadHigh)
532                 LoadSegment = Segment + (sizeof(DOS_PSP) >> 4);
533             else
534                 LoadSegment = Segment + TotalSize - BaseSize;
535 
536             RelocFactor = LoadSegment;
537         }
538         else
539         {
540             ASSERT(Parameters);
541             LoadSegment = Parameters->Overlay.Segment;
542             RelocFactor = Parameters->Overlay.RelocationFactor;
543         }
544 
545         /* Copy the program to the code segment */
546         RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0),
547                       ExeBuffer + (Header->e_cparhdr << 4),
548                       min(ExeBufferSize - (Header->e_cparhdr << 4), BaseSize << 4));
549 
550         /* Get the relocation table */
551         RelocationTable = (PDWORD)(ExeBuffer + Header->e_lfarlc);
552 
553         /* Perform relocations */
554         for (i = 0; i < Header->e_crlc; i++)
555         {
556             /* Get a pointer to the word that needs to be patched */
557             RelocWord = (PWORD)SEG_OFF_TO_PTR(LoadSegment + HIWORD(RelocationTable[i]),
558                                               LOWORD(RelocationTable[i]));
559 
560             /* Add the relocation factor to it */
561             *RelocWord += RelocFactor;
562         }
563 
564         /* Set the stack to the location from the header */
565         FinalSS = LoadSegment + Header->e_ss;
566         FinalSP = Header->e_sp;
567 
568         /* Set the code segment/pointer */
569         FinalCS = LoadSegment + Header->e_cs;
570         FinalIP = Header->e_ip;
571     }
572     else
573     {
574         /* COM file */
575 
576         if (LoadType != DOS_LOAD_OVERLAY)
577         {
578             /* Find the maximum amount of memory that can be allocated */
579             DosAllocateMemory(0xFFFF, &MaxAllocSize);
580 
581             /* Make sure it's enough for the whole program and the PSP */
582             if (((DWORD)MaxAllocSize << 4) < (ExeBufferSize + sizeof(DOS_PSP)))
583             {
584                 Result = ERROR_NOT_ENOUGH_MEMORY;
585                 goto Cleanup;
586             }
587 
588             /* Allocate all of it */
589             Segment = DosAllocateMemory(MaxAllocSize, NULL);
590             if (Segment == 0)
591             {
592                 Result = Sda->LastErrorCode;
593                 goto Cleanup;
594             }
595 
596             /* The process owns its memory */
597             DosChangeMemoryOwner(Segment , Segment);
598             DosChangeMemoryOwner(EnvBlock, Segment);
599 
600             /* Set INT 22h to the return address */
601             ((PULONG)BaseAddress)[0x22] = ReturnAddress;
602 
603             /* Create the PSP and initialize it */
604             DosCreatePsp(Segment, MaxAllocSize);
605             DosInitPsp(Segment, EnvBlock, CommandLine, ExePath);
606 
607             /* Calculate the segment where the program should be loaded */
608             LoadSegment = Segment + (sizeof(DOS_PSP) >> 4);
609         }
610         else
611         {
612             ASSERT(Parameters);
613             LoadSegment = Parameters->Overlay.Segment;
614         }
615 
616         /* Copy the program to the code segment */
617         RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0),
618                       ExeBuffer, ExeBufferSize);
619 
620         /* Set the stack to the last word of the segment */
621         FinalSS = Segment;
622         FinalSP = 0xFFFE;
623 
624         /*
625          * Set the value on the stack to 0x0000, so that a near return
626          * jumps to PSP:0000 which has the exit code.
627          */
628         *((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0x0000;
629 
630         /* Set the code segment/pointer */
631         FinalCS = Segment;
632         FinalIP = 0x0100;
633     }
634 
635     if (LoadType == DOS_LOAD_AND_EXECUTE)
636     {
637         /* Save the program state */
638         if (Sda->CurrentPsp != SYSTEM_PSP)
639         {
640             /* Push the task state */
641             DosSaveState();
642 
643 #ifdef ADVANCED_DEBUGGING
644             DPRINT1("Sda->CurrentPsp = 0x%04x; Old LastStack = 0x%08x, New LastStack = 0x%08x\n",
645                    Sda->CurrentPsp, SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack, MAKELONG(getSP(), getSS()));
646 #endif
647 
648             /* Update the last stack in the PSP */
649             SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack = MAKELONG(getSP(), getSS());
650         }
651 
652         /* Set the initial segment registers */
653         setDS(Segment);
654         setES(Segment);
655 
656         /* Set the stack */
657         setSS(FinalSS);
658         setSP(FinalSP);
659 
660         /*
661          * Set the other registers as in real DOS: some demos expect them so!
662          * See http://www.fysnet.net/yourhelp.htm
663          * and http://www.beroset.com/asm/showregs.asm
664          */
665         setDX(Segment);
666         setDI(FinalSP);
667         setBP(0x091E); // DOS base stack pointer relic value. In MS-DOS 5.0 and Windows' NTVDM it's 0x091C. This is in fact the old SP value inside DosData disk stack.
668         setSI(FinalIP);
669 
670         setAX(0/*0xFFFF*/); // FIXME: fcbcode
671         setBX(0/*0xFFFF*/); // FIXME: fcbcode
672         setCX(0x00FF);
673 
674         /*
675          * Keep critical flags, clear test flags (OF, SF, ZF, AF, PF, CF)
676          * and explicitly set the interrupt flag.
677          */
678         setEFLAGS((getEFLAGS() & ~0x08D5) | 0x0200);
679 
680         /* Notify VDDs of process execution */
681         VDDCreateUserHook(Segment);
682 
683         /* Execute */
684         DosSetProcessContext(Segment);
685         CpuExecute(FinalCS, FinalIP);
686     }
687     else if (LoadType == DOS_LOAD_ONLY)
688     {
689         ASSERT(Parameters);
690         Parameters->StackLocation = MAKELONG(FinalSP, FinalSS);
691         Parameters->EntryPoint    = MAKELONG(FinalIP, FinalCS);
692     }
693 
694 Cleanup:
695     if (Result != ERROR_SUCCESS)
696     {
697         /* It was not successful, cleanup the DOS memory */
698         if (EnvBlock) DosFreeMemory(EnvBlock);
699         if (Segment)  DosFreeMemory(Segment);
700     }
701 
702     return Result;
703 }
704 
705 DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
706                         IN LPCSTR ExecutablePath,
707                         IN PDOS_EXEC_PARAM_BLOCK Parameters,
708                         IN LPCSTR CommandLine OPTIONAL,
709                         IN LPCSTR Environment OPTIONAL,
710                         IN DWORD ReturnAddress OPTIONAL)
711 {
712     DWORD Result = ERROR_SUCCESS;
713     HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
714     DWORD FileSize;
715     LPBYTE Address = NULL;
716     CHAR FullPath[MAX_PATH];
717     CHAR ShortFullPath[MAX_PATH];
718 
719     DPRINT1("DosLoadExecutable(%d, '%s', 0x%p, 0x%p, 0x%p)\n",
720             LoadType, ExecutablePath, Parameters, CommandLine, Environment);
721 
722     /* Try to get the full path to the executable */
723     if (GetFullPathNameA(ExecutablePath, sizeof(FullPath), FullPath, NULL))
724     {
725         /* Get the corresponding short path */
726         if (GetShortPathNameA(FullPath, ShortFullPath, sizeof(ShortFullPath)))
727         {
728             /* Use the shortened full path from now on */
729             ExecutablePath = ShortFullPath;
730         }
731     }
732 
733     /* Open a handle to the executable */
734     FileHandle = CreateFileA(ExecutablePath,
735                              GENERIC_READ,
736                              FILE_SHARE_READ,
737                              NULL,
738                              OPEN_EXISTING,
739                              FILE_ATTRIBUTE_NORMAL,
740                              NULL);
741     if (FileHandle == INVALID_HANDLE_VALUE)
742     {
743         Result = GetLastError();
744         goto Cleanup;
745     }
746 
747     /* Get the file size */
748     FileSize = GetFileSize(FileHandle, NULL);
749 
750     /* Create a mapping object for the file */
751     FileMapping = CreateFileMapping(FileHandle,
752                                     NULL,
753                                     PAGE_READONLY,
754                                     0,
755                                     0,
756                                     NULL);
757     if (FileMapping == NULL)
758     {
759         Result = GetLastError();
760         goto Cleanup;
761     }
762 
763     /* Map the file into memory */
764     Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
765     if (Address == NULL)
766     {
767         Result = GetLastError();
768         goto Cleanup;
769     }
770 
771     Result = DosLoadExecutableInternal(LoadType,
772                                        Address,
773                                        FileSize,
774                                        ExecutablePath,
775                                        Parameters,
776                                        CommandLine,
777                                        Environment,
778                                        ReturnAddress);
779 
780 Cleanup:
781     /* Unmap the file*/
782     if (Address != NULL) UnmapViewOfFile(Address);
783 
784     /* Close the file mapping object */
785     if (FileMapping != NULL) CloseHandle(FileMapping);
786 
787     /* Close the file handle */
788     if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
789 
790     return Result;
791 }
792 
793 WORD DosCreateProcess(IN LPCSTR ProgramName,
794                       IN PDOS_EXEC_PARAM_BLOCK Parameters,
795                       IN DWORD ReturnAddress OPTIONAL)
796 {
797     DWORD Result = ERROR_SUCCESS;
798     DWORD BinaryType;
799 
800     /* Get the binary type */
801     if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError();
802 
803     /* Check the type of the program */
804     switch (BinaryType)
805     {
806         /* Those are handled by NTVDM */
807         case SCS_WOW_BINARY:
808         {
809             static const PCSTR AppName = "\"%ProgramFiles%\\otvdm\\otvdmw.exe\" ";
810 
811             STARTUPINFOA si;
812             PROCESS_INFORMATION pi;
813             union { DWORD Size; NTSTATUS Status; } Ret;
814             CHAR ExpName[MAX_PATH];
815 
816             Ret.Size = ExpandEnvironmentStringsA(AppName, ExpName, _countof(ExpName));
817             if ((Ret.Size == 0) || (Ret.Size > _countof(ExpName)))
818             {
819                 /* We failed or buffer too small, fall back to DOS execution */
820                 goto RunAsDOS;
821             }
822             Ret.Size--; // Remove NULL-terminator from count
823 
824             /* Add double-quotes before and after ProgramName */
825             Ret.Status = RtlStringCchPrintfA(ExpName + Ret.Size, _countof(ExpName) - Ret.Size,
826                                              "\"%s\"", ProgramName);
827             if (!NT_SUCCESS(Ret.Status))
828             {
829                 /* We failed or buffer too small, fall back to DOS execution */
830                 goto RunAsDOS;
831             }
832 
833             ZeroMemory(&pi, sizeof(pi));
834             ZeroMemory(&si, sizeof(si));
835             si.cb = sizeof(si);
836 
837             /* Create the process */
838             if (CreateProcessA(NULL,    // No Application Name
839                                ExpName, // Just our Command Line
840                                NULL,    // Cannot inherit Process Handle
841                                NULL,    // Cannot inherit Thread Handle
842                                FALSE,   // No handle inheritance
843                                0,       // No extra creation flags
844                                NULL,    // No environment block
845                                NULL,    // No starting directory
846                                &si,
847                                &pi))
848             {
849                 /* Close the handles */
850                 CloseHandle(pi.hThread);
851                 CloseHandle(pi.hProcess);
852                 break;
853             }
854             else
855             {
856                 /* Retrieve the actual path to the "Program Files" directory for displaying the error */
857                 ExpandEnvironmentStringsA("%ProgramFiles%", ExpName, _countof(ExpName));
858 
859                 DisplayMessage(L"Trying to load '%S'.\n"
860                                L"WOW16 applications are not supported internally by NTVDM at the moment.\n"
861                                L"Consider installing WineVDM from the ReactOS Applications Manager in\n'%S'.\n\n"
862                                L"Click on OK to continue.",
863                                ProgramName, ExpName);
864             }
865             // Fall through
866         }
867         RunAsDOS:
868         case SCS_DOS_BINARY:
869         {
870             /* Load the executable */
871             Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
872                                        ProgramName,
873                                        Parameters,
874                                        NULL,
875                                        NULL,
876                                        ReturnAddress);
877             if (Result != ERROR_SUCCESS)
878             {
879                 DisplayMessage(L"Could not load '%S'. Error: %u", ProgramName, Result);
880             }
881 
882             break;
883         }
884 
885         /* Not handled by NTVDM */
886         default:
887         {
888             LPSTR Environment = NULL;
889             CHAR CmdLine[MAX_PATH + DOS_CMDLINE_LENGTH + 1];
890             LPSTR CmdLinePtr;
891             ULONG CmdLineSize;
892 
893             /* Did the caller specify an environment segment? */
894             if (Parameters->Environment)
895             {
896                 /* Yes, use it instead of the parent one */
897                 Environment = (LPSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
898             }
899 
900             /*
901              * Convert the DOS command line to Win32-compatible format, by concatenating
902              * the program name with the converted command line.
903              * Format of the DOS command line: 1 byte for size; 127 bytes for contents.
904              */
905             CmdLinePtr = CmdLine;
906             strncpy(CmdLinePtr, ProgramName, MAX_PATH); // Concatenate the program name
907             CmdLinePtr += strlen(CmdLinePtr);
908             *CmdLinePtr++ = ' ';                        // Add separating space
909 
910             CmdLineSize = min(*(PBYTE)FAR_POINTER(Parameters->CommandLine), DOS_CMDLINE_LENGTH);
911             RtlCopyMemory(CmdLinePtr,
912                           (LPSTR)FAR_POINTER(Parameters->CommandLine) + 1,
913                           CmdLineSize);
914             /* NULL-terminate it */
915             CmdLinePtr[CmdLineSize] = '\0';
916 
917             /* Remove any trailing return carriage character and NULL-terminate the command line */
918             while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
919             *CmdLinePtr = '\0';
920 
921             Result = DosStartProcess32(ProgramName, CmdLine,
922                                        Environment, ReturnAddress,
923                                        TRUE);
924             if (Result != ERROR_SUCCESS)
925             {
926                 DisplayMessage(L"Could not load 32-bit '%S'. Error: %u", ProgramName, Result);
927             }
928 
929             break;
930         }
931     }
932 
933     return Result;
934 }
935 
936 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
937 {
938     WORD McbSegment = SysVars->FirstMcb;
939     PDOS_MCB CurrentMcb;
940     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
941     PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
942     LPWORD Stack;
943     BYTE TerminationType;
944 
945     DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n",
946            Psp, ReturnCode, KeepResident);
947 
948     /* Notify VDDs of process termination */
949     VDDTerminateUserHook(Psp);
950 
951     /* Check if this PSP is its own parent */
952     if (PspBlock->ParentPsp == Psp) goto Done;
953 
954     if (KeepResident == 0)
955     {
956         WORD i;
957         for (i = 0; i < PspBlock->HandleTableSize; i++)
958         {
959             /* Close the handle */
960             DosCloseHandle(i);
961         }
962     }
963 
964     /* Free the memory used by the process */
965     while (TRUE)
966     {
967         /* Get a pointer to the MCB */
968         CurrentMcb = SEGMENT_TO_MCB(McbSegment);
969 
970         /* Make sure the MCB is valid */
971         if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z') break;
972 
973         /* Check if this block was allocated by the process */
974         if (CurrentMcb->OwnerPsp == Psp)
975         {
976             if (KeepResident)
977             {
978                 /* Check if this is the PSP block and we should reduce its size */
979                 if ((McbSegment + 1) == Psp && KeepResident < CurrentMcb->Size)
980                 {
981                     /* Reduce the size of the block */
982                     DosResizeMemory(McbSegment + 1, KeepResident, NULL);
983                     break;
984                 }
985             }
986             else
987             {
988                 /* Free this entire block */
989                 DosFreeMemory(McbSegment + 1);
990             }
991         }
992 
993         /* If this was the last block, quit */
994         if (CurrentMcb->BlockType == 'Z') break;
995 
996         /* Update the segment and continue */
997         McbSegment += CurrentMcb->Size + 1;
998     }
999 
1000 Done:
1001     /* Restore the interrupt vectors */
1002     IntVecTable[0x22] = PspBlock->TerminateAddress;
1003     IntVecTable[0x23] = PspBlock->BreakAddress;
1004     IntVecTable[0x24] = PspBlock->CriticalAddress;
1005 
1006     /* Update the current PSP with the parent's one */
1007     if (Psp == Sda->CurrentPsp)
1008     {
1009         DosSetProcessContext(PspBlock->ParentPsp);
1010         if (Sda->CurrentPsp == SYSTEM_PSP)
1011         {
1012             // NOTE: we can also use the DOS BIOS exit code.
1013             CpuUnsimulate();
1014             return;
1015         }
1016     }
1017 
1018     /* Save the return code - Normal termination or TSR */
1019     TerminationType = (KeepResident != 0 ? 0x03 : 0x00);
1020     Sda->ErrorLevel = MAKEWORD(ReturnCode, TerminationType);
1021 
1022 #ifdef ADVANCED_DEBUGGING
1023     DPRINT1("PspBlock->ParentPsp = 0x%04x; Sda->CurrentPsp = 0x%04x\n",
1024            PspBlock->ParentPsp, Sda->CurrentPsp);
1025 #endif
1026 
1027     if (Sda->CurrentPsp != SYSTEM_PSP)
1028     {
1029 #ifdef ADVANCED_DEBUGGING
1030         DPRINT1("Sda->CurrentPsp = 0x%04x; Old SS:SP = %04X:%04X going to be LastStack = 0x%08x\n",
1031                Sda->CurrentPsp, getSS(), getSP(), SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack);
1032 #endif
1033 
1034         /* Restore the parent's stack */
1035         setSS(HIWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
1036         setSP(LOWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
1037 
1038         /* Pop the task state */
1039         DosRestoreState();
1040     }
1041 
1042     /* Return control to the parent process */
1043     Stack = (LPWORD)SEG_OFF_TO_PTR(getSS(), getSP());
1044     Stack[STACK_CS] = HIWORD(PspBlock->TerminateAddress);
1045     Stack[STACK_IP] = LOWORD(PspBlock->TerminateAddress);
1046 }
1047 
1048 /* EOF */
1049