setup_native/source/win32/customactions/inst_msu/inst_msu.cxx | 120 +++++++--- 1 file changed, 86 insertions(+), 34 deletions(-)
New commits: commit 1151e8fe6c8061f8b9c4777fba9665464ab5b5b8 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Sun Apr 21 22:39:43 2019 +0300 Commit: Caolán McNamara <caol...@redhat.com> CommitDate: Thu Apr 25 13:00:55 2019 +0200 tdf#124794: Wait for WU service stopped before launching wusa.exe It seems that wusa.exe would fail with error code 0x80080005 if launched too soon after WU service was sent a stop control (which was added in tdf#123832). Change-Id: I470a8a8e933e8a0cd6208c095ed63c1761b3b434 Reviewed-on: https://gerrit.libreoffice.org/71045 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> Reviewed-on: https://gerrit.libreoffice.org/71062 Reviewed-by: Caolán McNamara <caol...@redhat.com> Tested-by: Caolán McNamara <caol...@redhat.com> diff --git a/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx b/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx index d81532020cce..d8c196ccd1a8 100644 --- a/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx +++ b/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx @@ -181,6 +181,46 @@ public: } }; +// This class uses MsiProcessMessage to check for user input: it returns IDCANCEL when user cancels +// installation. It throws a special exception, to be intercepted in main action function to return +// corresponding exit code. +class UserInputChecker +{ +public: + class eUserCancelled + { + }; + + UserInputChecker(MSIHANDLE hInstall) + : m_hInstall(hInstall) + , m_hProgressRec(MsiCreateRecord(3)) + { + // Use explicit progress messages + MsiRecordSetInteger(m_hProgressRec, 1, 1); + MsiRecordSetInteger(m_hProgressRec, 2, 1); + MsiRecordSetInteger(m_hProgressRec, 3, 0); + int nResult = MsiProcessMessage(m_hInstall, INSTALLMESSAGE_PROGRESS, m_hProgressRec); + if (nResult == IDCANCEL) + throw eUserCancelled(); + // Prepare the record to following progress update calls + MsiRecordSetInteger(m_hProgressRec, 1, 2); + MsiRecordSetInteger(m_hProgressRec, 2, 0); // step by 0 - don't move progress + MsiRecordSetInteger(m_hProgressRec, 3, 0); + } + + void ThrowIfUserCancelled() + { + // Check if user has cancelled + int nResult = MsiProcessMessage(m_hInstall, INSTALLMESSAGE_PROGRESS, m_hProgressRec); + if (nResult == IDCANCEL) + throw eUserCancelled(); + } + +private: + MSIHANDLE m_hInstall; + PMSIHANDLE m_hProgressRec; +}; + // Checks if Windows Update service is disabled, and if it is, enables it temporarily. // Also stops the service if it's currently running, because it seems that wusa.exe // does not freeze when it starts the service itself. @@ -200,7 +240,7 @@ public: if (mhService) { EnsureServiceEnabled(mhInstall, mhService.get(), false); - StopService(mhInstall, mhService.get()); + StopService(mhInstall, mhService.get(), false); } } catch (std::exception& e) @@ -230,8 +270,9 @@ private: // Stop currently running service to prevent wusa.exe from hanging trying to detect if the // update is applicable (sometimes this freezes it ~indefinitely; it seems that it doesn't // happen if wusa.exe starts the service itself: https://superuser.com/questions/1044528/). + // tdf#124794: Wait for service to stop. if (nCurrentStatus == SERVICE_RUNNING) - StopService(hInstall, hService.get()); + StopService(hInstall, hService.get(), true); if (nCurrentStatus == SERVICE_RUNNING || !EnsureServiceEnabled(hInstall, hService.get(), true)) @@ -260,9 +301,8 @@ private: DWORD nCbRequired = 0; if (!QueryServiceConfigW(hService, nullptr, 0, &nCbRequired)) { - DWORD nError = GetLastError(); - if (nError != ERROR_INSUFFICIENT_BUFFER) - ThrowLastError("QueryServiceConfigW"); + if (DWORD nError = GetLastError(); nError != ERROR_INSUFFICIENT_BUFFER) + ThrowWin32Error("QueryServiceConfigW", nError); } std::unique_ptr<char[]> pBuf(new char[nCbRequired]); LPQUERY_SERVICE_CONFIGW pConfig = reinterpret_cast<LPQUERY_SERVICE_CONFIGW>(pBuf.get()); @@ -336,7 +376,7 @@ private: return aServiceStatus.dwCurrentState; } - static void StopService(MSIHANDLE hInstall, SC_HANDLE hService) + static void StopService(MSIHANDLE hInstall, SC_HANDLE hService, bool bWait) { try { @@ -344,12 +384,34 @@ private: { SERVICE_STATUS aServiceStatus{}; if (!ControlService(hService, SERVICE_CONTROL_STOP, &aServiceStatus)) - WriteLog(hInstall, Win32ErrorMessage("ControlService", GetLastError())); - else - WriteLog( - hInstall, - "Successfully sent SERVICE_CONTROL_STOP code to Windows Update service"); - // No need to wait for the service stopped + ThrowLastError("ControlService"); + WriteLog(hInstall, + "Successfully sent SERVICE_CONTROL_STOP code to Windows Update service"); + if (aServiceStatus.dwCurrentState != SERVICE_STOPPED && bWait) + { + // Let user cancel too long wait + UserInputChecker aInputChecker(hInstall); + // aServiceStatus.dwWaitHint is unreasonably high for Windows Update (30000), + // so don't use it, but simply poll service status each second + for (int nWait = 0; nWait < 30; ++nWait) // arbitrary limit of 30 s + { + for (int i = 0; i < 2; ++i) // check user input twice a second + { + Sleep(500); + aInputChecker.ThrowIfUserCancelled(); + } + + if (!QueryServiceStatus(hService, &aServiceStatus)) + ThrowLastError("QueryServiceStatus"); + + if (aServiceStatus.dwCurrentState == SERVICE_STOPPED) + break; + } + } + if (aServiceStatus.dwCurrentState == SERVICE_STOPPED) + WriteLog(hInstall, "Successfully stopped Windows Update service"); + else if (bWait) + WriteLog(hInstall, "Wait for Windows Update stop timed out - proceeding"); } else WriteLog(hInstall, "Windows Update service is not running"); @@ -496,33 +558,16 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall) { // This block waits when the started wusa.exe process finishes. Since it's possible - // for wusa.exe in some circumstances to wait really long (indefinitely?), we use - // MsiProcessMessage to check for user input: it returns IDCANCEL when user cancels - // installation. - PMSIHANDLE hProgressRec = MsiCreateRecord(3); - // Use explicit progress messages - MsiRecordSetInteger(hProgressRec, 1, 1); - MsiRecordSetInteger(hProgressRec, 2, 1); - MsiRecordSetInteger(hProgressRec, 3, 0); - int nResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hProgressRec); - if (nResult == IDCANCEL) - return ERROR_INSTALL_USEREXIT; - // Prepare the record to following progress update calls - MsiRecordSetInteger(hProgressRec, 1, 2); - MsiRecordSetInteger(hProgressRec, 2, 0); // step by 0 - don't move progress - MsiRecordSetInteger(hProgressRec, 3, 0); + // for wusa.exe in some circumstances to wait really long (indefinitely?), we check + // for user input here. + UserInputChecker aInputChecker(hInstall); for (;;) { DWORD nWaitResult = WaitForSingleObject(pi.hProcess, 500); if (nWaitResult == WAIT_OBJECT_0) break; // wusa.exe finished else if (nWaitResult == WAIT_TIMEOUT) - { - // Check if user has cancelled - nResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hProgressRec); - if (nResult == IDCANCEL) - return ERROR_INSTALL_USEREXIT; - } + aInputChecker.ThrowIfUserCancelled(); else ThrowWin32Error("WaitForSingleObject", nWaitResult); } @@ -558,6 +603,10 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall) "continue. You may need to install the required update manually"); return ERROR_SUCCESS; } + catch (const UserInputChecker::eUserCancelled&) + { + return ERROR_INSTALL_USEREXIT; + } catch (std::exception& e) { WriteLog(hInstall, e.what()); @@ -583,7 +632,10 @@ extern "C" __declspec(dllexport) UINT __stdcall CleanupMSU(MSIHANDLE hInstall) WriteLog(hInstall, "Got CustomActionData value:", sBinaryName); if (!DeleteFileW(sBinaryName)) - ThrowLastError("DeleteFileW"); + { + if (DWORD nError = GetLastError(); nError != ERROR_FILE_NOT_FOUND) + ThrowWin32Error("DeleteFileW", nError); + } WriteLog(hInstall, "File successfully removed"); } catch (std::exception& e) _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits