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

Reply via email to