[NTVDM]: Fix (again) command-line parsing in DosCreateProcess.
[reactos.git] / reactos / subsystems / mvdm / ntvdm / dos / dos32krnl / process.c
index 57024e0..a841ab5 100644 (file)
@@ -37,10 +37,62 @@ static inline VOID DosSetPspCommandLine(WORD Segment, LPCSTR CommandLine)
 {
     PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment);
 
-    /* Set the command line */
-    PspBlock->CommandLineSize = (BYTE)min(strlen(CommandLine), DOS_CMDLINE_LENGTH - 1);
-    RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
-    PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
+    /*
+     * Copy the command line block.
+     * Format of the CommandLine parameter: 1 byte for size; 127 bytes for contents.
+     */
+    PspBlock->CommandLineSize = min(*(PBYTE)CommandLine, DOS_CMDLINE_LENGTH);
+    CommandLine++;
+    RtlCopyMemory(PspBlock->CommandLine, CommandLine, DOS_CMDLINE_LENGTH);
+}
+
+static inline VOID DosSaveState(VOID)
+{
+    PDOS_REGISTER_STATE State;
+    WORD StackPointer = getSP();
+
+    /* Allocate stack space for the registers */
+    StackPointer -= sizeof(DOS_REGISTER_STATE);
+    State = SEG_OFF_TO_PTR(getSS(), StackPointer);
+    setSP(StackPointer);
+
+    /* Save */
+    State->EAX = getEAX();
+    State->ECX = getECX();
+    State->EDX = getEDX();
+    State->EBX = getEBX();
+    State->ESP = getESP();
+    State->EBP = getEBP();
+    State->ESI = getESI();
+    State->EDI = getEDI();
+    State->DS = getDS();
+    State->ES = getES();
+    State->FS = getFS();
+    State->GS = getGS();
+    State->Flags = getEFLAGS();
+}
+
+static inline VOID DosRestoreState(VOID)
+{
+    PDOS_REGISTER_STATE State;
+
+    /* Pop the state structure from the stack */
+    State = SEG_OFF_TO_PTR(getSS(), getSP());
+    setSP(getSP() + sizeof(DOS_REGISTER_STATE));
+
+    /* Restore */
+    setEAX(State->EAX);
+    setECX(State->ECX);
+    setEDX(State->EDX);
+    setEBX(State->EBX);
+    setEBP(State->EBP);
+    setESI(State->ESI);
+    setEDI(State->EDI);
+    setDS(State->DS);
+    setES(State->ES);
+    setFS(State->FS);
+    setGS(State->GS);
+    setEFLAGS(State->Flags);
 }
 
 static WORD DosCopyEnvironmentBlock(LPCSTR Environment OPTIONAL,
@@ -115,8 +167,8 @@ static WORD DosCopyEnvironmentBlock(LPCSTR Environment OPTIONAL,
 
 VOID DosClonePsp(WORD DestSegment, WORD SourceSegment)
 {
-    PDOS_PSP DestPsp = SEGMENT_TO_PSP(DestSegment);
-    PDOS_PSP SourcePsp = SEGMENT_TO_PSP(SourceSegment);
+    PDOS_PSP DestPsp    = SEGMENT_TO_PSP(DestSegment);
+    PDOS_PSP SourcePsp  = SEGMENT_TO_PSP(SourceSegment);
     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
 
     /* Literally copy the PSP first */
@@ -132,7 +184,7 @@ VOID DosClonePsp(WORD DestSegment, WORD SourceSegment)
 
     /* Set the handle table pointers to the internal handle table */
     DestPsp->HandleTableSize = DEFAULT_JFT_SIZE;
-    DestPsp->HandleTablePtr = MAKELONG(0x18, DestSegment);
+    DestPsp->HandleTablePtr  = MAKELONG(0x18, DestSegment);
 
     /* Copy the parent handle table without referencing the SFT */
     RtlCopyMemory(FAR_POINTER(DestPsp->HandleTablePtr),
@@ -142,7 +194,7 @@ VOID DosClonePsp(WORD DestSegment, WORD SourceSegment)
 
 VOID DosCreatePsp(WORD Segment, WORD ProgramSize)
 {
-    PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment);
+    PDOS_PSP PspBlock   = SEGMENT_TO_PSP(Segment);
     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
 
     RtlZeroMemory(PspBlock, sizeof(*PspBlock));
@@ -170,7 +222,7 @@ VOID DosCreatePsp(WORD Segment, WORD ProgramSize)
 
     /* Set the handle table pointers to the internal handle table */
     PspBlock->HandleTableSize = DEFAULT_JFT_SIZE;
-    PspBlock->HandleTablePtr = MAKELONG(0x18, Segment);
+    PspBlock->HandleTablePtr  = MAKELONG(0x18, Segment);
 
     /* Set the DOS version */
     PspBlock->DosVersion = DOS_VERSION;
@@ -191,8 +243,7 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
                         IN LPCSTR ExecutablePath,
                         IN PDOS_EXEC_PARAM_BLOCK Parameters,
                         IN LPCSTR CommandLine OPTIONAL,
-                        IN LPCSTR Environment OPTIONAL,
-                        IN DWORD ReturnAddress OPTIONAL)
+                        IN LPCSTR Environment OPTIONAL)
 {
     DWORD Result = ERROR_SUCCESS;
     HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
@@ -202,12 +253,29 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
     WORD LoadSegment;
     WORD MaxAllocSize;
     DWORD i, FileSize;
+    CHAR FullPath[MAX_PATH];
+    CHAR ShortFullPath[MAX_PATH];
 
-    DPRINT1("DosLoadExecutable(%d, %s, 0x%08X, 0x%08X)\n",
+    /* Buffer for command line conversion: 1 byte for size; 127 bytes for contents */
+    CHAR CmdLineBuffer[1 + DOS_CMDLINE_LENGTH];
+
+    DPRINT1("DosLoadExecutable(%d, '%s', 0x%08X, 0x%08X, 0x%08X)\n",
             LoadType,
             ExecutablePath,
             Parameters,
-            ReturnAddress);
+            CommandLine,
+            Environment);
+
+    /* Try to get the full path to the executable */
+    if (GetFullPathNameA(ExecutablePath, sizeof(FullPath), FullPath, NULL))
+    {
+        /* Try to shorten the full path */
+        if (GetShortPathNameA(FullPath, ShortFullPath, sizeof(ShortFullPath)))
+        {
+            /* Use the shortened full path from now on */
+            ExecutablePath = ShortFullPath;
+        }
+    }
 
     /* Open a handle to the executable */
     FileHandle = CreateFileA(ExecutablePath,
@@ -249,25 +317,73 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
 
     if (LoadType != DOS_LOAD_OVERLAY)
     {
-        LPSTR CmdLinePtr;
+        /* If an optional Win32 command line is given... */
+        if (CommandLine)
+        {
+            /* ... convert it into DOS format */
+            BYTE CmdLineLen;
+
+            PBYTE CmdLineSize  = (PBYTE)CmdLineBuffer;
+            LPSTR CmdLineStart = CmdLineBuffer + 1;
+            LPSTR CmdLinePtr   = CmdLineStart;
+
+            // For debugging purposes
+            RtlFillMemory(CmdLineBuffer, sizeof(CmdLineBuffer), 0xFF);
+
+            /*
+             * Set the command line: it is either an empty command line or has
+             * the format: " foo bar ..." (with at least one leading whitespace),
+             * and is then always followed by '\r' (and optionally by '\n').
+             */
+            CmdLineLen = (BYTE)strlen(CommandLine);
+            *CmdLineSize = 0;
+
+            /*
+             * Add the leading space if the command line is not empty
+             * and doesn't already start with some whitespace...
+             */
+            if (*CommandLine && *CommandLine != '\r' && *CommandLine != '\n' &&
+                *CommandLine != ' ' && *CommandLine != '\t')
+            {
+                (*CmdLineSize)++;
+                *CmdLinePtr++ = ' ';
+            }
+
+            /* Compute the number of characters we need to copy from the original command line */
+            CmdLineLen = min(CmdLineLen, DOS_CMDLINE_LENGTH - *CmdLineSize);
+
+            /* The trailing '\r' or '\n' do not count in the PSP command line size parameter */
+            while (CmdLineLen && (CommandLine[CmdLineLen - 1] == '\r' || CommandLine[CmdLineLen - 1] == '\n'))
+            {
+                CmdLineLen--;
+            }
+
+            /* Finally, set everything up */
+            *CmdLineSize += CmdLineLen;
+            RtlCopyMemory(CmdLinePtr, CommandLine, CmdLineLen);
+            CmdLineStart[*CmdLineSize] = '\r';
 
-        if (CommandLine == NULL)
+            /* Finally make the pointer point to the static buffer */
+            CommandLine = CmdLineBuffer;
+        }
+        else
         {
-            /* Get the command line from the parameter block */
+            /*
+             * ... otherwise, get the one from the parameter block.
+             * Format of the command line: 1 byte for size; 127 bytes for contents.
+             */
+            ASSERT(Parameters);
             CommandLine = (LPCSTR)FAR_POINTER(Parameters->CommandLine);
         }
 
+        /* If no optional environment is given... */
         if (Environment == NULL)
         {
-            /* Get the environment from the parameter block */
+            /* ... get the one from the parameter block */
+            ASSERT(Parameters);
             Environment = (LPCSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
         }
 
-        /* NULL-terminate the command line by removing the return carriage character */
-        CmdLinePtr = (LPSTR)CommandLine;
-        while (*CmdLinePtr && *CmdLinePtr != '\r') CmdLinePtr++;
-        *CmdLinePtr = '\0';
-
         /* Copy the environment block to DOS memory */
         EnvBlock = DosCopyEnvironmentBlock(Environment, ExecutablePath);
         if (EnvBlock == 0)
@@ -292,7 +408,8 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
         Header = (PIMAGE_DOS_HEADER)Address;
 
         /* Get the base size of the file, in paragraphs (rounded up) */
-        BaseSize = (((Header->e_cp - 1) * 512) + Header->e_cblp + 0x0F) >> 4;
+        BaseSize = ((((Header->e_cp - (Header->e_cblp != 0)) * 512)
+                   + Header->e_cblp + 0x0F) >> 4) - Header->e_cparhdr;
 
         if (LoadType != DOS_LOAD_OVERLAY)
         {
@@ -336,8 +453,8 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
             DosChangeMemoryOwner(Segment, Segment);
             DosChangeMemoryOwner(EnvBlock, Segment);
 
-            /* Set INT 22h to the return address */
-            ((PULONG)BaseAddress)[0x22] = ReturnAddress;
+            /* Set INT 22h to the current CS:IP */
+            ((PULONG)BaseAddress)[0x22] = MAKELONG(getIP(), getCS());
 
             /* Create the PSP */
             DosCreatePsp(Segment, (WORD)TotalSize);
@@ -352,6 +469,7 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
         }
         else
         {
+            ASSERT(Parameters);
             LoadSegment = Parameters->Overlay.Segment;
             RelocFactor = Parameters->Overlay.RelocationFactor;
         }
@@ -377,6 +495,16 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
 
         if (LoadType == DOS_LOAD_AND_EXECUTE)
         {
+            /* Save the program state */
+            if (CurrentPsp != SYSTEM_PSP)
+            {
+                /* Push the task state */
+                DosSaveState();
+
+                /* Update the last stack in the PSP */
+                SEGMENT_TO_PSP(CurrentPsp)->LastStack = MAKELONG(getSP(), getSS());
+            }
+
             /* Set the initial segment registers */
             setDS(Segment);
             setES(Segment);
@@ -391,8 +519,9 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
         }
         else if (LoadType == DOS_LOAD_ONLY)
         {
+            ASSERT(Parameters);
             Parameters->StackLocation = MAKELONG(Header->e_sp, LoadSegment + Header->e_ss);
-            Parameters->EntryPoint = MAKELONG(Header->e_ip, LoadSegment + Header->e_cs);
+            Parameters->EntryPoint    = MAKELONG(Header->e_ip, LoadSegment + Header->e_cs);
         }
     }
     else
@@ -423,8 +552,8 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
             DosChangeMemoryOwner(Segment, Segment);
             DosChangeMemoryOwner(EnvBlock, Segment);
 
-            /* Set INT 22h to the return address */
-            ((PULONG)BaseAddress)[0x22] = ReturnAddress;
+            /* Set INT 22h to the current CS:IP */
+            ((PULONG)BaseAddress)[0x22] = MAKELONG(getIP(), getCS());
 
             /* Create the PSP */
             DosCreatePsp(Segment, MaxAllocSize);
@@ -436,15 +565,27 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
         }
         else
         {
+            ASSERT(Parameters);
             LoadSegment = Parameters->Overlay.Segment;
         }
 
+        /* Copy the program to the code segment */
         RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0),
                       Address,
                       FileSize);
 
         if (LoadType == DOS_LOAD_AND_EXECUTE)
         {
+            /* Save the program state */
+            if (CurrentPsp != SYSTEM_PSP)
+            {
+                /* Push the task state */
+                DosSaveState();
+
+                /* Update the last stack in the PSP */
+                SEGMENT_TO_PSP(CurrentPsp)->LastStack = MAKELONG(getSP(), getSS());
+            }
+
             /* Set the initial segment registers */
             setDS(Segment);
             setES(Segment);
@@ -465,8 +606,9 @@ DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
         }
         else if (LoadType == DOS_LOAD_ONLY)
         {
+            ASSERT(Parameters);
             Parameters->StackLocation = MAKELONG(0xFFFE, Segment);
-            Parameters->EntryPoint = MAKELONG(0x0100, Segment);
+            Parameters->EntryPoint    = MAKELONG(0x0100, Segment);
         }
     }
 
@@ -495,27 +637,34 @@ DWORD DosStartProcess(IN LPCSTR ExecutablePath,
                       IN LPCSTR Environment OPTIONAL)
 {
     DWORD Result;
-    LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
+
+    SIZE_T CmdLen = strlen(CommandLine);
+    DPRINT1("Starting '%s' ('%.*s')...\n",
+            ExecutablePath,
+            /* Display the command line without the terminating 0d 0a (and skip the terminating NULL) */
+            CmdLen >= 2 ? (CommandLine[CmdLen - 2] == '\r' ? CmdLen - 2
+                                                           : CmdLen)
+                        : CmdLen,
+            CommandLine);
 
     Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
                                ExecutablePath,
                                NULL,
                                CommandLine,
-                               Environment,
-                               IntVecTable[0x20]);
+                               Environment);
 
     if (Result != ERROR_SUCCESS) goto Quit;
 
+#ifndef STANDALONE
+    /* Update console title if we run in a separate console */
+    if (SessionId != 0)
+        SetConsoleTitleA(ExecutablePath);
+#endif
+
     /* Attach to the console */
     ConsoleAttach();
     VidBiosAttachToConsole();
 
-    // HACK: Simulate a ENTER key release scancode on the PS/2 port because
-    // some apps expect to read a key release scancode (> 0x80) when they
-    // are started.
-    IOWriteB(PS2_CONTROL_PORT, 0xD2);     // Next write is for the first PS/2 port
-    IOWriteB(PS2_DATA_PORT, 0x80 | 0x1C); // ENTER key release
-
     /* Start simulation */
     SetEvent(VdmTaskEvent);
     CpuSimulate();
@@ -530,26 +679,31 @@ Quit:
 
 #ifndef STANDALONE
 WORD DosCreateProcess(LPCSTR ProgramName,
-                      PDOS_EXEC_PARAM_BLOCK Parameters,
-                      DWORD ReturnAddress)
+                      PDOS_EXEC_PARAM_BLOCK Parameters)
 {
     DWORD Result;
     DWORD BinaryType;
     LPVOID Environment = NULL;
     VDM_COMMAND_INFO CommandInfo;
-    CHAR CmdLine[MAX_PATH];
+    CHAR CmdLine[MAX_PATH + DOS_CMDLINE_LENGTH + 1];
     CHAR AppName[MAX_PATH];
     CHAR PifFile[MAX_PATH];
     CHAR Desktop[MAX_PATH];
     CHAR Title[MAX_PATH];
+    LPSTR CmdLinePtr;
+    ULONG CmdLineSize;
     ULONG EnvSize = 256;
-    PVOID Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
+    PVOID Env;
     STARTUPINFOA StartupInfo;
     PROCESS_INFORMATION ProcessInfo;
 
     /* Get the binary type */
     if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError();
 
+    /* Initialize Win32-VDM environment */
+    Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
+    if (Env == NULL) return GetLastError();
+
     /* Did the caller specify an environment segment? */
     if (Parameters->Environment)
     {
@@ -561,9 +715,30 @@ WORD DosCreateProcess(LPCSTR ProgramName,
     RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
     StartupInfo.cb = sizeof(StartupInfo);
 
+    /*
+     * Convert the DOS command line to Win32-compatible format, by concatenating
+     * the program name with the converted command line.
+     * Format of the DOS command line: 1 byte for size; 127 bytes for contents.
+     */
+    CmdLinePtr = CmdLine;
+    strncpy(CmdLinePtr, ProgramName, MAX_PATH); // Concatenate the program name
+    CmdLinePtr += strlen(CmdLinePtr);
+    *CmdLinePtr++ = ' ';                        // Add separating space
+
+    CmdLineSize = min(*(PBYTE)FAR_POINTER(Parameters->CommandLine), DOS_CMDLINE_LENGTH);
+    RtlCopyMemory(CmdLinePtr,
+                  (LPSTR)FAR_POINTER(Parameters->CommandLine) + 1,
+                  CmdLineSize);
+    /* NULL-terminate it */
+    CmdLinePtr[CmdLineSize] = '\0';
+
+    /* Remove any trailing return carriage character and NULL-terminate the command line */
+    while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
+    *CmdLinePtr = '\0';
+
     /* Create the process */
     if (!CreateProcessA(ProgramName,
-                        FAR_POINTER(Parameters->CommandLine),
+                        CmdLine,
                         NULL,
                         NULL,
                         FALSE,
@@ -573,6 +748,7 @@ WORD DosCreateProcess(LPCSTR ProgramName,
                         &StartupInfo,
                         &ProcessInfo))
     {
+        RtlFreeHeap(RtlGetProcessHeap(), 0, Env);
         return GetLastError();
     }
 
@@ -626,8 +802,7 @@ Command:
                                        AppName,
                                        Parameters,
                                        CmdLine,
-                                       Env,
-                                       ReturnAddress);
+                                       Env);
             if (Result == ERROR_SUCCESS)
             {
                 /* Increment the re-entry count */
@@ -667,6 +842,9 @@ VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
     PDOS_MCB CurrentMcb;
     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
     PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
+#ifndef STANDALONE
+    VDM_COMMAND_INFO CommandInfo;
+#endif
 
     DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n",
            Psp,
@@ -697,23 +875,20 @@ VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
         /* Check if this block was allocated by the process */
         if (CurrentMcb->OwnerPsp == Psp)
         {
-            if (KeepResident == 0)
-            {
-                /* Free this entire block */
-                DosFreeMemory(McbSegment + 1);
-            }
-            else if (KeepResident < CurrentMcb->Size)
+            if (KeepResident)
             {
-                /* Reduce the size of the block */
-                DosResizeMemory(McbSegment + 1, KeepResident, NULL);
-
-                /* No further paragraphs need to stay resident */
-                KeepResident = 0;
+                /* Check if this is the PSP block and we should reduce its size */
+                if ((McbSegment + 1) == Psp && KeepResident < CurrentMcb->Size)
+                {
+                    /* Reduce the size of the block */
+                    DosResizeMemory(McbSegment + 1, KeepResident, NULL);
+                    break;
+                }
             }
             else
             {
-                /* Just reduce the amount of paragraphs we need to keep resident */
-                KeepResident -= CurrentMcb->Size;
+                /* Free this entire block */
+                DosFreeMemory(McbSegment + 1);
             }
         }
 
@@ -738,34 +913,41 @@ Done:
         {
             ResetEvent(VdmTaskEvent);
             CpuUnsimulate();
+            return;
         }
     }
 
 #ifndef STANDALONE
-    // FIXME: This is probably not the best way to do it
-    /* Check if this was a nested DOS task */
-    if (CurrentPsp != SYSTEM_PSP)
-    {
-        VDM_COMMAND_INFO CommandInfo;
 
-        /* Decrement the re-entry count */
-        CommandInfo.TaskId = SessionId;
-        CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
-        GetNextVDMCommand(&CommandInfo);
+    /* Decrement the re-entry count */
+    CommandInfo.TaskId = SessionId;
+    CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
+    GetNextVDMCommand(&CommandInfo);
 
-        /* Clear the structure */
-        RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
+    /* Clear the structure */
+    RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
+
+    /* Update the VDM state of the task */
+    CommandInfo.TaskId = SessionId;
+    CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
+    GetNextVDMCommand(&CommandInfo);
 
-        /* Update the VDM state of the task */
-        CommandInfo.TaskId = SessionId;
-        CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
-        GetNextVDMCommand(&CommandInfo);
-    }
 #endif
 
     /* Save the return code - Normal termination */
     DosErrorLevel = MAKEWORD(ReturnCode, 0x00);
 
+    /* Restore the old stack */
+    setSS(HIWORD(SEGMENT_TO_PSP(CurrentPsp)->LastStack));
+    setSP(LOWORD(SEGMENT_TO_PSP(CurrentPsp)->LastStack));
+
+    /* Are we returning to DOS code? */
+    if (HIWORD(PspBlock->TerminateAddress) == DOS_CODE_SEGMENT)
+    {
+        /* Pop the task state */
+        DosRestoreState();
+    }
+
     /* Return control to the parent process */
     CpuExecute(HIWORD(PspBlock->TerminateAddress),
                LOWORD(PspBlock->TerminateAddress));