[Libreoffice-commits] core.git: setup_native/source

Mike Kaganski (via logerrit) logerrit at kemper.freedesktop.org
Mon Apr 22 02:23:32 UTC 2019


 setup_native/source/win32/customactions/inst_msu/inst_msu.cxx |  136 +++++++---
 1 file changed, 96 insertions(+), 40 deletions(-)

New commits:
commit bb6906b66d7ceac8465b43d7e2b1f05976d43600
Author:     Mike Kaganski <mike.kaganski at collabora.com>
AuthorDate: Sun Apr 21 22:39:43 2019 +0300
Commit:     Mike Kaganski <mike.kaganski at collabora.com>
CommitDate: Mon Apr 22 04:22:57 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.kaganski at collabora.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 ebcb96c5a4da..6ce517f2f863 100644
--- a/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx
+++ b/setup_native/source/win32/customactions/inst_msu/inst_msu.cxx
@@ -141,14 +141,14 @@ void ShowWarning(MSIHANDLE hInst, const std::wstring& sKBNo, const char* sMessag
 }
 
 // Set custom action description visible in progress dialog
-void SetStatusText(MSIHANDLE hInst, const std::wstring& sKBNo)
+void SetStatusText(MSIHANDLE hInst, const std::wstring& actName, const std::wstring& actDesc)
 {
     PMSIHANDLE hRec = MsiCreateRecord(3);
     // For INSTALLMESSAGE_ACTIONSTART, record's Field 0 must be null
-    std::wstring s(sKBNo + L" installation");
-    MsiRecordSetStringW(hRec, 1, s.c_str()); // Field 1: Action name - must be non-null
-    s = L"Installing " + sKBNo;
-    MsiRecordSetStringW(hRec, 2, s.c_str()); // Field 2: Action description - displayed in dialog
+    // Field 1: Action name - must be non-null
+    MsiRecordSetStringW(hRec, 1, actName.c_str());
+    // Field 2: Action description - displayed in dialog
+    MsiRecordSetStringW(hRec, 2, actDesc.c_str());
     // Let Field 3 stay null - no action template
     MsiProcessMessage(hInst, INSTALLMESSAGE_ACTIONSTART, hRec);
 }
@@ -199,6 +199,46 @@ bool IsWow64Process()
 #endif
 }
 
+// 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.
@@ -218,7 +258,7 @@ public:
             if (mhService)
             {
                 EnsureServiceEnabled(mhInstall, mhService.get(), false);
-                StopService(mhInstall, mhService.get());
+                StopService(mhInstall, mhService.get(), false);
             }
         }
         catch (std::exception& e)
@@ -246,8 +286,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))
@@ -268,9 +309,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());
@@ -344,7 +384,7 @@ private:
         return aServiceStatus.dwCurrentState;
     }
 
-    static void StopService(MSIHANDLE hInstall, SC_HANDLE hService)
+    static void StopService(MSIHANDLE hInstall, SC_HANDLE hService, bool bWait)
     {
         try
         {
@@ -352,12 +392,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");
@@ -490,12 +552,16 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall)
         sKBNo = std::wstring(sCustomActionData, sBinaryName - sCustomActionData);
         ++sBinaryName;
         auto aDeleteFileGuard(Guard(sBinaryName));
-        SetStatusText(hInstall, sKBNo);
+
+        SetStatusText(hInstall, L"WU service state check",
+                      L"Checking Windows Update service state");
 
         // In case the Windows Update service is disabled, we temporarily enable it here. We also
         // stop running WU service, to avoid wusa.exe freeze (see comment in EnableWUService).
         WUServiceEnabler aWUServiceEnabler(hInstall);
 
+        SetStatusText(hInstall, sKBNo + L" installation", L"Installing " + sKBNo);
+
         const bool bWow64Process = IsWow64Process();
         WriteLog(hInstall, "Is Wow64 Process:", bWow64Process ? "YES" : "NO");
         std::wstring sWUSAPath = bWow64Process ? GetKnownFolder(FOLDERID_Windows) + L"\\SysNative"
@@ -519,33 +585,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);
             }
@@ -570,6 +619,10 @@ extern "C" __declspec(dllexport) UINT __stdcall InstallMSU(MSIHANDLE hInstall)
                 ThrowHResult("Execution of wusa.exe", hr);
         }
     }
+    catch (const UserInputChecker::eUserCancelled&)
+    {
+        return ERROR_INSTALL_USEREXIT;
+    }
     catch (std::exception& e)
     {
         WriteLog(hInstall, e.what());
@@ -596,7 +649,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)


More information about the Libreoffice-commits mailing list