[SETUP:REACTOS] Improve Installation thread initialization and termination

- Improve install abort on errors;
- Wait for the install thread to finish within the page that created it;
- Display the hourglass cursor while waiting for the thread.
This commit is contained in:
Hermès Bélusca-Maïto
2026-07-24 23:10:19 +02:00
parent 1648ea7555
commit 5da41b3448
2 changed files with 296 additions and 108 deletions
+273 -107
View File
@@ -36,6 +36,7 @@ PPARTENTRY SystemPartition = NULL;
/* UI elements */
UI_CONTEXT UiContext;
HCURSOR hWaitCursor;
/* FUNCTIONS ****************************************************************/
@@ -1713,7 +1714,7 @@ FileCopyCallback(PVOID Context,
PCWSTR SrcFileName, DstFileName;
WaitForSingleObject(CopyContext->pSetupData->hHaltInstallEvent, INFINITE);
if (CopyContext->pSetupData->bStopInstall)
if (CopyContext->pSetupData->bAbortInstall)
return FILEOP_ABORT; // Stop committing files
switch (Notification)
@@ -1883,6 +1884,9 @@ PropSheet_SetCloseCancel(
MF_BYCOMMAND | (Enable ? MF_ENABLED : MF_GRAYED));
}
#define PM_INSTALL_START (WM_APP + 1)
#define PM_INSTALL_DONE (WM_APP + 2)
static DWORD
WINAPI
PrepareAndDoCopyThread(
@@ -1916,7 +1920,6 @@ PrepareAndDoCopyThread(
/* Disable the Close/Cancel buttons during all partition operations */
// TODO: Consider, alternatively, to just show an info-box saying
// that the installation process cannot be canceled at this stage?
// PropSheet_SetWizButtons(hWndParent, 0);
PropSheet_SetCloseCancel(hWndParent, FALSE);
@@ -1951,13 +1954,7 @@ PrepareAndDoCopyThread(
/* Re-enable the Close/Cancel buttons */
PropSheet_SetCloseCancel(hWndParent, TRUE);
/*
* We failed due to an unexpected error, keep on the copy page to view the current state,
* but enable the "Next" button to allow the user to continue to the Abort page.
*/
PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT);
return 1;
goto Quit;
}
@@ -1980,13 +1977,7 @@ PrepareAndDoCopyThread(
/* Re-enable the Close/Cancel buttons */
PropSheet_SetCloseCancel(hWndParent, TRUE);
/*
* We failed due to an unexpected error, keep on the copy page to view the current state,
* but enable the "Next" button to allow the user to continue to the Abort page.
*/
PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT);
return 1;
goto Quit;
}
@@ -1994,26 +1985,19 @@ PrepareAndDoCopyThread(
PropSheet_SetCloseCancel(hWndParent, TRUE);
/* Re-calculate the final destination paths */
ASSERT(InstallPartition);
Status = InitDestinationPaths(&pSetupData->USetupData,
NULL, // pSetupData->USetupData.InstallationDirectory,
InstallVolume);
if (!NT_SUCCESS(Status))
Success = NT_SUCCESS(Status);
if (!Success)
{
DisplayMessage(hWndParent, MB_ICONERROR, NULL, L"InitDestinationPaths() failed with status 0x%08lx\n", Status);
/*
* We failed due to an unexpected error, keep on the copy page to view the current state,
* but enable the "Next" button to allow the user to continue to the Abort page.
*/
PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT);
return 1;
goto Quit;
}
/*
* Preparation of the list of files to be copied
*/
@@ -2039,17 +2023,9 @@ PrepareAndDoCopyThread(
if (/*ErrorNumber != ERROR_SUCCESS*/ !Success)
{
/* Display an error only if an unexpected failure happened, and not because the user cancelled the installation */
if (!pSetupData->bStopInstall)
if (!pSetupData->bAbortInstall)
MessageBoxW(hWndParent, L"Failed to prepare the list of files!", NULL, MB_ICONERROR);
/*
* If we failed due to an unexpected error, keep on the copy page to view the current state,
* but enable the "Next" button to allow the user to continue to the Abort page.
* Otherwise we have been cancelled by the user, who has already switched to the Abort page.
*/
if (!pSetupData->bStopInstall)
PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT);
return 1;
goto Quit;
}
@@ -2069,20 +2045,13 @@ PrepareAndDoCopyThread(
CopyContext.CompletedOperations = 0;
/* Do the file copying - The callback handles whether or not we should stop file copying */
if (!DoFileCopy(&pSetupData->USetupData, FileCopyCallback, &CopyContext))
Success = DoFileCopy(&pSetupData->USetupData, FileCopyCallback, &CopyContext);
if (!Success)
{
/* Display an error only if an unexpected failure happened, and not because the user cancelled the installation */
if (!pSetupData->bStopInstall)
if (!pSetupData->bAbortInstall)
MessageBoxW(hWndParent, L"Failed to copy the files!", NULL, MB_ICONERROR);
/*
* If we failed due to an unexpected error, keep on the copy page to view the current state,
* but enable the "Next" button to allow the user to continue to the Abort page.
* Otherwise we have been cancelled by the user, who has already switched to the Abort page.
*/
if (!pSetupData->bStopInstall)
PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT);
return 1;
goto Quit;
}
// /* Set status text */
@@ -2095,6 +2064,11 @@ PrepareAndDoCopyThread(
InstallSetupInfFile(&pSetupData->USetupData);
/* Disable the Close/Cancel buttons for the remaining non-cancellable operations */
// TODO: Consider, alternatively, to just show an info-box saying
// that the installation process cannot be canceled at this stage?
PropSheet_SetCloseCancel(hWndParent, FALSE);
/*
* Create or update the registry hives
*/
@@ -2251,7 +2225,8 @@ PrepareAndDoCopyThread(
IDS_ERROR_BOOTLDR_FAILED,
Status);
}
break;
Success = FALSE;
goto Quit;
}
/* Skip installation */
@@ -2260,12 +2235,14 @@ PrepareAndDoCopyThread(
break;
}
/* We are done! Switch to the Finish page */
PropSheet_SetCurSelByID(hWndParent, IDD_FINISHPAGE);
return 0;
Quit:
/* Signal the wizard page that we have succeeded or failed */
PostMessage(hwndDlg, PM_INSTALL_DONE, Success, 0); // Status
return Success;
}
// TODO: Determine later which method is the best.
//#define TERMINATE_USE_PRESSBUTTON
static INT_PTR CALLBACK
ProcessDlgProc(
@@ -2294,6 +2271,16 @@ ProcessDlgProc(
break;
}
case WM_SETCURSOR:
{
/* Set a page-wide cursor */
if (!hWaitCursor)
break;
SetCursor(hWaitCursor);
SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE);
return TRUE;
}
case WM_NOTIFY:
{
LPNMHDR lpnm = (LPNMHDR)lParam;
@@ -2304,83 +2291,256 @@ ProcessDlgProc(
{
HWND hWndParent = GetParent(hwndDlg);
/* Create the file-copy halt (manual-reset) event */
pSetupData->hHaltInstallEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
if (!pSetupData->hHaltInstallEvent)
break;
pSetupData->bStopInstall = FALSE;
/*
* Disable all buttons during installation, and hide "Back" and "Next".
* Don't use the PropSheet_SetWizButtons() macro, because its
* posted message would be handled after hiding the buttons.
* The message would then interfere with the hidden buttons
* (when both "Back" and "Next" are hidden, "Next" gets forcefully shown).
*/
SendMessageW(hWndParent, PSM_SETWIZBUTTONS, 0, 0);
// PropSheet_ShowWizButtons(hWndParent, 0, PSWIZB_BACK | PSWIZB_NEXT);
ShowDlgItem(hWndParent, ID_WIZBACK, SW_HIDE);
ShowDlgItem(hWndParent, ID_WIZNEXT, SW_HIDE);
/* Start the prepare-and-copy files thread */
pSetupData->hInstallThread =
CreateThread(NULL, 0,
PrepareAndDoCopyThread,
(PVOID)hwndDlg,
CREATE_SUSPENDED,
NULL);
if (!pSetupData->hInstallThread)
/* Start the installation procedure once the page is fully displayed */
PostMessageW(hwndDlg, PM_INSTALL_START, 0, 0);
break;
}
case PSN_KILLACTIVE:
{
/* Avoid reentrant calls caused by the message dispatching
* done below: allow only the first page change, and ignore
* all the others. */
if (InterlockedFlagsTestAndSet8(&pSetupData->bStopInstall,
SETUP_PAGE_SWITCHING))
{
CloseHandle(pSetupData->hHaltInstallEvent);
pSetupData->hHaltInstallEvent = NULL;
MessageBoxW(hWndParent, L"Cannot create the prepare-and-copy files thread!", NULL, MB_ICONERROR);
break;
SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE);
return TRUE;
}
/* Disable all buttons during installation, they will be
* re-enabled by the installation thread; hide "Back" */
PropSheet_SetWizButtons(hWndParent, 0);
// PropSheet_ShowWizButtons(hWndParent, 0, PSWIZB_BACK);
ShowDlgItem(hWndParent, ID_WIZBACK, SW_HIDE);
/* If no installation is running, just change the page */
if (!pSetupData->hInstallThread)
break;
/* Resume the installation thread */
ResumeThread(pSetupData->hInstallThread);
/* Wait for the installation thread to terminate.
* Since we dispatch messages in the meantime, we must
* guard the property sheet notification handlers from
* being invoked multiple times. */
DWORD dwStatus = WaitForSingleObject(pSetupData->hInstallThread, 0);
if (dwStatus == WAIT_TIMEOUT)
{
/* Show the hourglass cursor */
HCURSOR hOldCursor;
hWaitCursor = LoadCursor(NULL, IDC_WAIT);
hOldCursor = SetCursor(hWaitCursor);
ShowCursor(TRUE);
for (;;)
{
MSG msg;
dwStatus = MsgWaitForMultipleObjects(1, &pSetupData->hInstallThread,
FALSE, INFINITE,
QS_ALLINPUT | QS_ALLPOSTMESSAGE);
if (dwStatus != (WAIT_OBJECT_0 + 1))
break; // Stop if anything else (thread terminated, timeout or failure).
/* We still need to process main window messages to avoid freeze */
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
/* Restore the old cursor */
ShowCursor(FALSE);
SetCursor(hOldCursor);
hWaitCursor = NULL;
}
#if 0 && DBG
if (dwStatus == WAIT_OBJECT_0)
{
DWORD dwExitCode = NO_ERROR;
GetExitCodeThread(pSetupData->hInstallThread, &dwExitCode);
DBG_UNREFERENCED_PARAMETER(dwExitCode);
}
#endif
/* Cleanup; we can now change the page */
CloseHandle(pSetupData->hInstallThread);
pSetupData->hInstallThread = NULL;
CloseHandle(pSetupData->hHaltInstallEvent);
pSetupData->hHaltInstallEvent = NULL;
break;
}
case PSN_QUERYCANCEL:
{
/* Halt the on-going file copy */
ResetEvent(pSetupData->hHaltInstallEvent);
INT nRet;
if (DisplayMessage(GetParent(hwndDlg),
MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2,
MAKEINTRESOURCEW(IDS_ABORTSETUP2),
MAKEINTRESOURCEW(IDS_ABORTSETUP)) == IDYES)
if (pSetupData->bStopInstall)
{
/* Stop the file copy thread */
pSetupData->bStopInstall = TRUE;
SetEvent(pSetupData->hHaltInstallEvent);
#if 0
/* Wait for any pending installation */
WaitForSingleObject(pSetupData->hInstallThread, INFINITE);
CloseHandle(pSetupData->hInstallThread);
pSetupData->hInstallThread = NULL;
CloseHandle(pSetupData->hHaltInstallEvent);
pSetupData->hHaltInstallEvent = NULL;
#endif
// TODO: Unwind installation?!
/* Go to the Abort page */
PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_ABORTPAGE);
/* The installation is terminating (either successfully,
* or cancelled by the user or due to an error), ignore all
* new requests since this is now too late to change bets. */
nRet = IDYES;
}
else
{
/* We don't stop installation, resume file copy */
/* Halt the on-going file copy */
ResetEvent(pSetupData->hHaltInstallEvent);
nRet = DisplayMessage(GetParent(hwndDlg),
MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2,
MAKEINTRESOURCEW(IDS_ABORTSETUP2),
MAKEINTRESOURCEW(IDS_ABORTSETUP));
if (nRet == IDYES)
{
/* Signal the file copy thread to stop */
InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_ABORT_INSTALL);
}
/* else, we don't stop installation, resume file copy */
SetEvent(pSetupData->hHaltInstallEvent);
}
/* If we are not already in the process of cancelling
* the installation or switching to a page, do it now. */
if ((nRet == IDYES) && !pSetupData->bPageSwitching &&
!InterlockedFlagsTestAndSet8(&pSetupData->bStopInstall,
SETUP_IS_CANCELLING))
{
/* Go to the Abort page */
#ifdef TERMINATE_USE_PRESSBUTTON
PropSheet_SetCloseCancel(GetParent(hwndDlg), FALSE);
PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_ABORTPAGE);
#else
PostMessageW(hwndDlg, PM_INSTALL_DONE, FALSE, 0);
#endif
}
/* Do not close the wizard too soon */
SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE);
return TRUE;
}
case PSN_WIZBACK:
/* Always disable going back */
SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, -1);
return TRUE;
case PSN_WIZNEXT:
{
#ifdef TERMINATE_USE_PRESSBUTTON
LONG_PTR nNextPage = (!pSetupData->bAbortInstall ? IDD_FINISHPAGE : IDD_ABORTPAGE);
SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, nNextPage);
#else
/* Always disable going next */
SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, -1);
#endif
return TRUE;
}
default:
break;
}
break;
}
case PM_INSTALL_START:
{
HWND hWndParent = GetParent(hwndDlg);
ASSERT(pSetupData->hInstallThread == NULL);
/* Force repainting first to make sure the wizard is visible */
InvalidateRect(hWndParent, NULL, FALSE);
UpdateWindow(hWndParent);
/* Create the file-copy halt (manual-reset) event */
pSetupData->hHaltInstallEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
if (!pSetupData->hHaltInstallEvent)
{
DisplayMessage(hWndParent, MB_ICONERROR, NULL,
L"Cannot create the install event, error %lu\n", GetLastError());
goto Fail;
}
/* Start the installation thread */
pSetupData->bStopInstall = FALSE;
pSetupData->hInstallThread = CreateThread(NULL, 0,
PrepareAndDoCopyThread,
(PVOID)hwndDlg,
0, NULL);
if (!pSetupData->hInstallThread)
{
DisplayMessage(hWndParent, MB_ICONERROR, NULL,
L"Cannot create the installation thread, error %lu\n", GetLastError());
CloseHandle(pSetupData->hHaltInstallEvent);
pSetupData->hHaltInstallEvent = NULL;
Fail:
/* Cancel the installation */
InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_ABORT_INSTALL);
#ifdef TERMINATE_USE_PRESSBUTTON
PropSheet_SetCloseCancel(hWndParent, FALSE);
PropSheet_PressButton(hWndParent, PSBTN_CANCEL);
#else
SendMessageW(hwndDlg, PM_INSTALL_DONE, FALSE, 0);
#endif
}
break;
}
case PM_INSTALL_DONE:
{
HWND hWndParent = GetParent(hwndDlg);
BOOL Success = !!wParam;
BOOL AutoSwitchPage = TRUE;
/* If an unexpected error happened, stay on the copy page to allow
* the user to view the current state, and keep the Close/Cancel
* buttons to allow going to the Abort page.
* Otherwise, we have been manually cancelled by the user and we
* will directly switch to the Abort page. */
if (!Success && !pSetupData->bStopInstall)
{
PropSheet_SetCloseCancel(hWndParent, TRUE);
AutoSwitchPage = FALSE;
}
else
{
/* Disable the Close/Cancel buttons, since the installation has
* either successfully terminated, or was already cancelled by
* the user. */
PropSheet_SetCloseCancel(hWndParent, FALSE);
}
if (!Success)
{
/* The installation was aborted */
InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_ABORT_INSTALL);
}
else if (pSetupData->bAbortInstall)
{
/* Override success in case the thread just terminated with some status
* while at the same time, the user chose to cancel the installation */
Success = FALSE;
}
/* We are done! Switch to the Finish or the Abort page */
if (!AutoSwitchPage)
break;
// TODO: if (!Success): Unwind installation.
#ifdef TERMINATE_USE_PRESSBUTTON
// NOTE: In this case, PSN_QUERYCANCEL **CANNOT** call PM_INSTALL_DONE
PropSheet_PressButton(hWndParent, Success ? PSBTN_NEXT : PSBTN_CANCEL);
#else
if (!Success)
InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_IS_CANCELLING);
PropSheet_SetCurSelByID(hWndParent, Success ? IDD_FINISHPAGE : IDD_ABORTPAGE);
#endif
break;
}
default:
break;
}
@@ -3298,6 +3458,19 @@ PrshtWndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
break;
}
case WM_SETCURSOR:
{
/* Set a wizard-wide cursor */
// NOTE: There is a problem, where when hovering over the wizard
// navigation buttons, the cursor would blink with the arrow.
// To mitigate this problem, only show the custom cursor when
// we are on the main wizard window.
if (!(hWaitCursor && (wParam == (WPARAM)hWnd)))
break;
SetCursor(hWaitCursor);
return TRUE;
}
case WM_DESTROY:
{
/* Restore the original dialog procedure */
@@ -3538,13 +3711,6 @@ _tWinMain(HINSTANCE hInst,
/* Display the wizard */
PropertySheetW(&psh);
/* Wait for any pending installation */
WaitForSingleObject(SetupData.hInstallThread, INFINITE);
CloseHandle(SetupData.hInstallThread);
SetupData.hInstallThread = NULL;
CloseHandle(SetupData.hHaltInstallEvent);
SetupData.hHaltInstallEvent = NULL;
if (SetupData.hBoldFont)
DeleteFont(SetupData.hBoldFont);
if (SetupData.hTitleFont)
+23 -1
View File
@@ -115,6 +115,12 @@ typedef struct _KBLAYOUT
} KBLAYOUT, *PKBLAYOUT;
#endif
#define InterlockedFlagsTestAndSet8(Target, Flags) \
!!(InterlockedOr8((PCHAR)(Target), (Flags)) & (Flags))
#define InterlockedFlagsTestAndReset8(Target, Flags) \
!!(InterlockedAnd8((PCHAR)(Target), ~(Flags)) & (Flags))
typedef struct _SETUPDATA
{
/* General */
@@ -127,7 +133,23 @@ typedef struct _SETUPDATA
HANDLE hInstallThread;
HANDLE hHaltInstallEvent;
BOOL bStopInstall;
union
{
struct
{
#define SETUP_ABORT_INSTALL 1
UCHAR bAbortInstall :2; /**< TRUE if the installation is to be aborted;
** FALSE if not (regular termination). */
#define SETUP_IS_CANCELLING 4
UCHAR bIsCancelling :2; /**< TRUE if the installation is being cancelled
** (via PSN_QUERYCANCEL); FALSE if not. */
#define SETUP_PAGE_SWITCHING 16
UCHAR bPageSwitching :2; /**< TRUE if a page switch is occurring
** (PSN_KILLACTIVE running); FALSE if not. */
UCHAR bReserved :2;
};
BOOLEAN bStopInstall;
};
NT_WIN32_PATH_MAPPING_LIST MappingList;