[BASESRV]
[reactos.git] / subsystems / win / basesrv / vdm.c
index 63125e9..63f4425 100644 (file)
@@ -20,6 +20,7 @@
 BOOLEAN FirstVDM = TRUE;
 LIST_ENTRY VDMConsoleListHead;
 RTL_CRITICAL_SECTION DosCriticalSection;
+RTL_CRITICAL_SECTION WowCriticalSection;
 
 /* FUNCTIONS ******************************************************************/
 
@@ -39,6 +40,153 @@ NTSTATUS NTAPI BaseSrvGetConsoleRecord(HANDLE ConsoleHandle, PVDM_CONSOLE_RECORD
     return CurrentRecord ? STATUS_SUCCESS : STATUS_NOT_FOUND;
 }
 
+ULONG NTAPI GetNextDosSesId(VOID)
+{
+    ULONG SessionId;
+    PLIST_ENTRY i;
+    PVDM_CONSOLE_RECORD CurrentRecord = NULL;
+    BOOLEAN Found;
+
+    /* Search for an available session ID */
+    for (SessionId = 1; SessionId != 0; SessionId++)
+    {
+        Found = FALSE;
+
+        /* Check if the ID is already in use */
+        for (i = VDMConsoleListHead.Flink; i != &VDMConsoleListHead; i = i->Flink)
+        {
+            CurrentRecord = CONTAINING_RECORD(i, VDM_CONSOLE_RECORD, Entry);
+            if (CurrentRecord->SessionId == SessionId) Found = TRUE;
+        }
+
+        /* If not, we found one */
+        if (!Found) break;
+    }
+
+    ASSERT(SessionId != 0);
+
+    /* Return the session ID */
+    return SessionId;
+}
+
+BOOLEAN NTAPI BaseSrvIsVdmAllowed(VOID)
+{
+    NTSTATUS Status;
+    BOOLEAN VdmAllowed = TRUE;
+    HANDLE RootKey, KeyHandle;
+    UNICODE_STRING KeyName, ValueName, MachineKeyName;
+    OBJECT_ATTRIBUTES Attributes;
+    UCHAR ValueBuffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG)];
+    PKEY_VALUE_PARTIAL_INFORMATION ValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ValueBuffer;
+    ULONG ActualSize;
+
+    /* Initialize the unicode strings */
+    RtlInitUnicodeString(&MachineKeyName, L"\\Registry\\Machine");
+    RtlInitUnicodeString(&KeyName, VDM_POLICY_KEY_NAME);
+    RtlInitUnicodeString(&ValueName, VDM_DISALLOWED_VALUE_NAME);
+
+    InitializeObjectAttributes(&Attributes,
+                               &MachineKeyName,
+                               OBJ_CASE_INSENSITIVE,
+                               NULL,
+                               NULL);
+
+    /* Open the local machine key */
+    Status = NtOpenKey(&RootKey, KEY_READ, &Attributes);
+    if (!NT_SUCCESS(Status)) return FALSE;
+
+    InitializeObjectAttributes(&Attributes,
+                               &KeyName,
+                               OBJ_CASE_INSENSITIVE,
+                               RootKey,
+                               NULL);
+
+    /* Open the policy key in the local machine hive, if it exists */
+    if (NT_SUCCESS(NtOpenKey(&KeyHandle, KEY_READ, &Attributes)))
+    {
+        /* Read the value, if it's set */
+        if (NT_SUCCESS(NtQueryValueKey(KeyHandle,
+                                       &ValueName,
+                                       KeyValuePartialInformation,
+                                       ValueInfo,
+                                       sizeof(ValueBuffer),
+                                       &ActualSize)))
+        {
+            if (*((PULONG)ValueInfo->Data))
+            {
+                /* The VDM has been disabled in the registry */
+                VdmAllowed = FALSE;
+            }
+        }
+
+        NtClose(KeyHandle);
+    }
+
+    /* Close the local machine key */
+    NtClose(RootKey);
+
+    /* If it's disabled system-wide, there's no need to check the user key */
+    if (!VdmAllowed) return FALSE;
+
+    /* Open the current user key of the client */
+    if (!CsrImpersonateClient(NULL)) return VdmAllowed;
+    Status = RtlOpenCurrentUser(KEY_READ, &RootKey);
+    CsrRevertToSelf();
+
+    /* If that fails, return the system-wide setting */
+    if (!NT_SUCCESS(Status)) return VdmAllowed;
+
+    InitializeObjectAttributes(&Attributes,
+                               &KeyName,
+                               OBJ_CASE_INSENSITIVE,
+                               RootKey,
+                               NULL);
+
+    /* Open the policy key in the current user hive, if it exists */
+    if (NT_SUCCESS(NtOpenKey(&KeyHandle, KEY_READ, &Attributes)))
+    {
+        /* Read the value, if it's set */
+        if (NT_SUCCESS(NtQueryValueKey(KeyHandle,
+                                       &ValueName,
+                                       KeyValuePartialInformation,
+                                       ValueInfo,
+                                       sizeof(ValueBuffer),
+                                       &ActualSize)))
+        {
+            if (*((PULONG)ValueInfo->Data))
+            {
+                /* The VDM has been disabled in the registry */
+                VdmAllowed = FALSE;
+            }
+        }
+
+        NtClose(KeyHandle);
+    }
+
+    return VdmAllowed;
+}
+
+NTSTATUS NTAPI BaseSrvCreatePairWaitHandles(PHANDLE ServerEvent, PHANDLE ClientEvent)
+{
+    NTSTATUS Status;
+
+    /* Create the event */
+    Status = NtCreateEvent(ServerEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE);
+    if (!NT_SUCCESS(Status)) return Status;
+
+    /* Duplicate the event into the client process */
+    Status = NtDuplicateObject(NtCurrentProcess(),
+                               *ServerEvent,
+                               CsrGetClientThread()->Process->ProcessHandle,
+                               ClientEvent,
+                               0,
+                               0,
+                               DUPLICATE_SAME_ATTRIBUTES | DUPLICATE_SAME_ACCESS);
+
+    if (!NT_SUCCESS(Status)) NtClose(*ServerEvent);
+    return Status;
+}
+
 VOID NTAPI BaseInitializeVDM(VOID)
 {
     /* Initialize the list head */
@@ -46,13 +194,22 @@ VOID NTAPI BaseInitializeVDM(VOID)
 
     /* Initialize the critical section */
     RtlInitializeCriticalSection(&DosCriticalSection);
+    RtlInitializeCriticalSection(&WowCriticalSection);
 }
 
 /* PUBLIC SERVER APIS *********************************************************/
 
 CSR_API(BaseSrvCheckVDM)
 {
+    NTSTATUS Status;
     PBASE_CHECK_VDM CheckVdmRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.CheckVDMRequest;
+    PRTL_CRITICAL_SECTION CriticalSection = NULL;
+    PVDM_CONSOLE_RECORD ConsoleRecord = NULL;
+    PVDM_DOS_RECORD DosRecord = NULL;
+    BOOLEAN NewConsoleRecord = FALSE;
+
+    /* Don't do anything if the VDM has been disabled in the registry */
+    if (!BaseSrvIsVdmAllowed()) return STATUS_ACCESS_DENIED;
 
     /* Validate the message buffers */
     if (!CsrValidateMessageBuffer(ApiMessage,
@@ -87,8 +244,104 @@ CSR_API(BaseSrvCheckVDM)
         return STATUS_INVALID_PARAMETER;
     }
 
-    // TODO: NOT IMPLEMENTED
-    return STATUS_NOT_IMPLEMENTED;
+    CriticalSection = (CheckVdmRequest->BinaryType != BINARY_TYPE_SEPARATE_WOW)
+                      ? &DosCriticalSection
+                      : &WowCriticalSection;
+
+    /* Enter the critical section */
+    RtlEnterCriticalSection(CriticalSection);
+
+    /* Check if this is a DOS or WOW VDM */
+    if (CheckVdmRequest->BinaryType != BINARY_TYPE_SEPARATE_WOW)
+    {
+        /* Get the console record */
+        Status = BaseSrvGetConsoleRecord(CheckVdmRequest->ConsoleHandle,
+                                         &ConsoleRecord);
+
+        if (!NT_SUCCESS(Status))
+        {
+            /* Allocate a new console record */
+            ConsoleRecord = (PVDM_CONSOLE_RECORD)RtlAllocateHeap(BaseSrvHeap,
+                                                                 HEAP_ZERO_MEMORY,
+                                                                 sizeof(VDM_CONSOLE_RECORD));
+            if (ConsoleRecord == NULL)
+            {
+                Status = STATUS_NO_MEMORY;
+                goto Cleanup;
+            }
+
+            /* Initialize the console record */
+            ConsoleRecord->ConsoleHandle = CheckVdmRequest->ConsoleHandle;
+            ConsoleRecord->CurrentDirs = NULL;
+            ConsoleRecord->CurDirsLength = 0;
+            ConsoleRecord->SessionId = GetNextDosSesId();
+            InitializeListHead(&ConsoleRecord->DosListHead);
+            // TODO: The console record structure is incomplete
+
+            /* Remember that the console record was allocated here */
+            NewConsoleRecord = TRUE;
+        }
+
+        /* Allocate a new DOS record */
+        DosRecord = (PVDM_DOS_RECORD)RtlAllocateHeap(BaseSrvHeap,
+                                                     HEAP_ZERO_MEMORY,
+                                                     sizeof(VDM_DOS_RECORD));
+        if (DosRecord == NULL)
+        {
+            Status = STATUS_NO_MEMORY;
+            goto Cleanup;
+        }
+
+        /* Initialize the DOS record */
+        DosRecord->State = NewConsoleRecord ? VDM_NOT_LOADED : VDM_READY;
+        DosRecord->ExitCode = 0;
+        // TODO: The DOS record structure is incomplete
+
+        Status = BaseSrvCreatePairWaitHandles(&DosRecord->ServerEvent, &DosRecord->ClientEvent);
+        if (!NT_SUCCESS(Status)) goto Cleanup;
+
+        /* Add the DOS record */
+        InsertHeadList(&ConsoleRecord->DosListHead, &DosRecord->Entry);
+
+        if (NewConsoleRecord)
+        {
+            /* Add the console record */
+            InsertTailList(&VDMConsoleListHead, &ConsoleRecord->Entry);
+        }
+
+        CheckVdmRequest->VDMState = DosRecord->State;
+        Status = STATUS_SUCCESS;
+    }
+    else
+    {
+        // TODO: NOT IMPLEMENTED
+        UNIMPLEMENTED;
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+Cleanup:
+    /* Check if it failed */
+    if (!NT_SUCCESS(Status))
+    {
+        /* Free the DOS record */
+        if (DosRecord != NULL)
+        {
+            RtlFreeHeap(BaseSrvHeap, 0, DosRecord);
+            DosRecord = NULL;
+        }
+
+        /* Free the console record if it was allocated here */
+        if (NewConsoleRecord)
+        {
+            RtlFreeHeap(BaseSrvHeap, 0, ConsoleRecord);
+            ConsoleRecord = NULL;
+        }
+    }
+
+    /* Leave the critical section */
+    RtlLeaveCriticalSection(CriticalSection);
+
+    return Status;
 }
 
 CSR_API(BaseSrvUpdateVDMEntry)
@@ -105,8 +358,61 @@ CSR_API(BaseSrvGetNextVDMCommand)
 
 CSR_API(BaseSrvExitVDM)
 {
-    DPRINT1("%s not yet implemented\n", __FUNCTION__);
-    return STATUS_NOT_IMPLEMENTED;
+    NTSTATUS Status;
+    PBASE_EXIT_VDM ExitVdmRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.ExitVDMRequest;
+    PRTL_CRITICAL_SECTION CriticalSection = NULL;
+    PVDM_CONSOLE_RECORD ConsoleRecord = NULL;
+    PVDM_DOS_RECORD DosRecord;
+
+    CriticalSection = (ExitVdmRequest->iWowTask == 0)
+                      ? &DosCriticalSection
+                      : &WowCriticalSection;
+
+    /* Enter the critical section */
+    RtlEnterCriticalSection(CriticalSection);
+
+    if (ExitVdmRequest->iWowTask == 0)
+    {
+        /* Get the console record */
+        Status = BaseSrvGetConsoleRecord(ExitVdmRequest->ConsoleHandle, &ConsoleRecord);
+        if (!NT_SUCCESS(Status)) goto Cleanup;
+
+        /* Cleanup the DOS records */
+        while (ConsoleRecord->DosListHead.Flink != &ConsoleRecord->DosListHead)
+        {
+            DosRecord = CONTAINING_RECORD(ConsoleRecord->DosListHead.Flink,
+                                          VDM_DOS_RECORD,
+                                          Entry);
+
+            /* Remove the DOS entry */
+            RemoveEntryList(&DosRecord->Entry);
+            RtlFreeHeap(BaseSrvHeap, 0, DosRecord);
+        }
+
+        if (ConsoleRecord->CurrentDirs != NULL)
+        {
+            /* Free the current directories */
+            RtlFreeHeap(BaseSrvHeap, 0, ConsoleRecord->CurrentDirs);
+            ConsoleRecord->CurrentDirs = NULL;
+            ConsoleRecord->CurDirsLength = 0;
+        }
+
+        /* Remove the console record */
+        RemoveEntryList(&ConsoleRecord->Entry);
+        RtlFreeHeap(BaseSrvHeap, 0, ConsoleRecord);
+    }
+    else
+    {
+        // TODO: NOT IMPLEMENTED
+        UNIMPLEMENTED;
+        return STATUS_NOT_IMPLEMENTED;
+    }
+
+Cleanup:
+    /* Leave the critical section */
+    RtlLeaveCriticalSection(CriticalSection);
+
+    return Status;
 }
 
 CSR_API(BaseSrvIsFirstVDM)
@@ -115,7 +421,7 @@ CSR_API(BaseSrvIsFirstVDM)
 
     /* Return the result */
     IsFirstVDMRequest->FirstVDM = FirstVDM;
-    
+
     /* Clear the first VDM flag */
     FirstVDM = FALSE;
 
@@ -124,8 +430,52 @@ CSR_API(BaseSrvIsFirstVDM)
 
 CSR_API(BaseSrvGetVDMExitCode)
 {
-    DPRINT1("%s not yet implemented\n", __FUNCTION__);
-    return STATUS_NOT_IMPLEMENTED;
+    NTSTATUS Status;
+    PBASE_GET_VDM_EXIT_CODE GetVDMExitCodeRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.GetVDMExitCodeRequest;
+    PLIST_ENTRY i = NULL;
+    PVDM_CONSOLE_RECORD ConsoleRecord = NULL;
+    PVDM_DOS_RECORD DosRecord = NULL;
+
+    /* Enter the critical section */
+    RtlEnterCriticalSection(&DosCriticalSection);
+
+    /* Get the console record */
+    Status = BaseSrvGetConsoleRecord(GetVDMExitCodeRequest->ConsoleHandle, &ConsoleRecord);
+    if (!NT_SUCCESS(Status)) goto Cleanup;
+
+    /* Search for a DOS record that has the same parent process handle */
+    for (i = ConsoleRecord->DosListHead.Flink; i != &ConsoleRecord->DosListHead; i = i->Flink)
+    {
+        DosRecord = CONTAINING_RECORD(i, VDM_DOS_RECORD, Entry);
+        if (DosRecord->ClientEvent == GetVDMExitCodeRequest->hParent) break;
+    }
+
+    /* Check if no DOS record was found */
+    if (i == &ConsoleRecord->DosListHead)
+    {
+        Status = STATUS_NOT_FOUND;
+        goto Cleanup;
+    }
+
+    /* Check if this task is still running */
+    if (DosRecord->State == VDM_READY)
+    {
+        GetVDMExitCodeRequest->ExitCode = STATUS_PENDING;
+        goto Cleanup;
+    }
+
+    /* Return the exit code */
+    GetVDMExitCodeRequest->ExitCode = DosRecord->ExitCode;
+
+    /* Since this is a zombie task record, remove it */
+    RemoveEntryList(&DosRecord->Entry);
+    RtlFreeHeap(BaseSrvHeap, 0, DosRecord);
+
+Cleanup:
+    /* Leave the critical section */
+    RtlLeaveCriticalSection(&DosCriticalSection);
+
+    return Status;
 }
 
 CSR_API(BaseSrvSetReenterCount)
@@ -136,14 +486,108 @@ CSR_API(BaseSrvSetReenterCount)
 
 CSR_API(BaseSrvSetVDMCurDirs)
 {
-    DPRINT1("%s not yet implemented\n", __FUNCTION__);
-    return STATUS_NOT_IMPLEMENTED;
+    NTSTATUS Status;
+    PBASE_GETSET_VDM_CURDIRS VDMCurrentDirsRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.VDMCurrentDirsRequest;
+    PVDM_CONSOLE_RECORD ConsoleRecord;
+    PCHAR Buffer = NULL;
+
+    /* Validate the input buffer */
+    if (!CsrValidateMessageBuffer(ApiMessage,
+                                  (PVOID*)&VDMCurrentDirsRequest->lpszzCurDirs,
+                                  VDMCurrentDirsRequest->cchCurDirs,
+                                  sizeof(*VDMCurrentDirsRequest->lpszzCurDirs)))
+    {
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /* Enter the critical section */
+    RtlEnterCriticalSection(&DosCriticalSection);
+
+    /* Find the console record */
+    Status = BaseSrvGetConsoleRecord(VDMCurrentDirsRequest->ConsoleHandle, &ConsoleRecord);
+    if (!NT_SUCCESS(Status)) goto Cleanup;
+
+    if (ConsoleRecord->CurrentDirs == NULL)
+    {
+        /* Allocate memory for the current directory information */
+        Buffer = RtlAllocateHeap(BaseSrvHeap,
+                                 HEAP_ZERO_MEMORY,
+                                 VDMCurrentDirsRequest->cchCurDirs);
+    }
+    else
+    {
+        /* Resize the amount of allocated memory */
+        Buffer = RtlReAllocateHeap(BaseSrvHeap,
+                                   HEAP_ZERO_MEMORY,
+                                   ConsoleRecord->CurrentDirs,
+                                   VDMCurrentDirsRequest->cchCurDirs);
+    }
+
+    if (Buffer == NULL)
+    {
+        /* Allocation failed */
+        Status = STATUS_NO_MEMORY;
+        goto Cleanup;
+    }
+
+    /* Update the console record */
+    ConsoleRecord->CurrentDirs = Buffer;
+    ConsoleRecord->CurDirsLength = VDMCurrentDirsRequest->cchCurDirs;
+
+    /* Copy the data */
+    RtlMoveMemory(ConsoleRecord->CurrentDirs,
+                  VDMCurrentDirsRequest->lpszzCurDirs,
+                  VDMCurrentDirsRequest->cchCurDirs);
+
+Cleanup:
+    /* Leave the critical section */
+    RtlLeaveCriticalSection(&DosCriticalSection);
+
+    return Status;
 }
 
 CSR_API(BaseSrvGetVDMCurDirs)
 {
-    DPRINT1("%s not yet implemented\n", __FUNCTION__);
-    return STATUS_NOT_IMPLEMENTED;
+    NTSTATUS Status;
+    PBASE_GETSET_VDM_CURDIRS VDMCurrentDirsRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.VDMCurrentDirsRequest;
+    PVDM_CONSOLE_RECORD ConsoleRecord;
+
+    /* Validate the output buffer */
+    if (!CsrValidateMessageBuffer(ApiMessage,
+                                  (PVOID*)&VDMCurrentDirsRequest->lpszzCurDirs,
+                                  VDMCurrentDirsRequest->cchCurDirs,
+                                  sizeof(*VDMCurrentDirsRequest->lpszzCurDirs)))
+    {
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /* Enter the critical section */
+    RtlEnterCriticalSection(&DosCriticalSection);
+
+    /* Find the console record */
+    Status = BaseSrvGetConsoleRecord(VDMCurrentDirsRequest->ConsoleHandle, &ConsoleRecord);
+    if (!NT_SUCCESS(Status)) goto Cleanup;
+
+    /* Return the actual size of the current directory information */
+    VDMCurrentDirsRequest->cchCurDirs = ConsoleRecord->CurDirsLength;
+
+    /* Check if the buffer is large enough */
+    if (VDMCurrentDirsRequest->cchCurDirs < ConsoleRecord->CurDirsLength)
+    {
+        Status = STATUS_BUFFER_TOO_SMALL;
+        goto Cleanup;
+    }
+
+    /* Copy the data */
+    RtlMoveMemory(VDMCurrentDirsRequest->lpszzCurDirs,
+                  ConsoleRecord->CurrentDirs,
+                  ConsoleRecord->CurDirsLength);
+
+Cleanup:
+    /* Leave the critical section */
+    RtlLeaveCriticalSection(&DosCriticalSection);
+
+    return Status;
 }
 
 CSR_API(BaseSrvBatNotification)