[PATCH 2/2] advapi32: Implement NotifyServiceStatusChange

Andrew Eikum aeikum at codeweavers.com
Tue Jan 23 08:37:53 CST 2018


This fixes launching Office 2010 applications in >=vista prefixes, see
bug 38838.

Signed-off-by: Andrew Eikum <aeikum at codeweavers.com>
---
 dlls/advapi32/service.c       | 146 ++++++++++++++++++++++++++++++++++++------
 dlls/advapi32/tests/service.c | 141 ++++++++++++++++++++++++++++++----------
 2 files changed, 233 insertions(+), 54 deletions(-)

diff --git a/dlls/advapi32/service.c b/dlls/advapi32/service.c
index ddd6a21429..7d66326d51 100644
--- a/dlls/advapi32/service.c
+++ b/dlls/advapi32/service.c
@@ -48,6 +48,7 @@
 #include "advapi32_misc.h"
 
 #include "wine/exception.h"
+#include "wine/list.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(service);
 
@@ -83,6 +84,18 @@ typedef struct dispatcher_data_t
     HANDLE pipe;
 } dispatcher_data;
 
+typedef struct notify_data_t {
+    SC_HANDLE service;
+    SC_RPC_NOTIFY_PARAMS params;
+    SERVICE_NOTIFY_STATUS_CHANGE_PARAMS_2 cparams;
+    SC_NOTIFY_RPC_HANDLE notify_handle;
+    SERVICE_NOTIFYW *notify_buffer;
+    HANDLE calling_thread, ready_evt;
+    struct list entry;
+} notify_data;
+
+static struct list notify_list = LIST_INIT(notify_list);
+
 static CRITICAL_SECTION service_cs;
 static CRITICAL_SECTION_DEBUG service_cs_debug =
 {
@@ -2596,37 +2609,130 @@ BOOL WINAPI EnumDependentServicesW( SC_HANDLE hService, DWORD dwServiceState,
     return TRUE;
 }
 
+static DWORD WINAPI notify_thread(void *user)
+{
+    DWORD err;
+    notify_data *data = user;
+    SC_RPC_NOTIFY_PARAMS_LIST *list;
+    SERVICE_NOTIFY_STATUS_CHANGE_PARAMS_2 *cparams;
+    BOOL dummy;
+
+    __TRY
+    {
+        /* GetNotifyResults blocks until there is an event */
+        err = svcctl_GetNotifyResults(data->notify_handle, &list);
+    }
+    __EXCEPT(rpc_filter)
+    {
+        err = map_exception_code(GetExceptionCode());
+    }
+    __ENDTRY
+
+    EnterCriticalSection( &service_cs );
+
+    list_remove(&data->entry);
+
+    LeaveCriticalSection( &service_cs );
+
+    if (err == ERROR_SUCCESS && list)
+    {
+        cparams = list->NotifyParamsArray[0].u.params;
+
+        data->notify_buffer->dwNotificationStatus = cparams->dwNotificationStatus;
+        memcpy(&data->notify_buffer->ServiceStatus, &cparams->ServiceStatus,
+                sizeof(SERVICE_STATUS_PROCESS));
+        data->notify_buffer->dwNotificationTriggered = cparams->dwNotificationTriggered;
+        data->notify_buffer->pszServiceNames = NULL;
+
+        QueueUserAPC((PAPCFUNC)data->notify_buffer->pfnNotifyCallback,
+                data->calling_thread, (ULONG_PTR)data->notify_buffer);
+
+        HeapFree(GetProcessHeap(), 0, list);
+    }
+    else
+        WARN("GetNotifyResults server call failed: %u\n", err);
+
+
+    __TRY
+    {
+        err = svcctl_CloseNotifyHandle(&data->notify_handle, &dummy);
+    }
+    __EXCEPT(rpc_filter)
+    {
+        err = map_exception_code(GetExceptionCode());
+    }
+    __ENDTRY
+
+    if (err != ERROR_SUCCESS)
+        WARN("CloseNotifyHandle server call failed: %u\n", err);
+
+    CloseHandle(data->calling_thread);
+    HeapFree(GetProcessHeap(), 0, data);
+
+    return 0;
+}
+
 /******************************************************************************
  * NotifyServiceStatusChangeW [ADVAPI32.@]
  */
 DWORD WINAPI NotifyServiceStatusChangeW(SC_HANDLE hService, DWORD dwNotifyMask,
         SERVICE_NOTIFYW *pNotifyBuffer)
 {
-    DWORD dummy;
-    BOOL ret;
-    SERVICE_STATUS_PROCESS st;
-    static int once;
+    DWORD err;
+    BOOL b_dummy = FALSE;
+    GUID g_dummy = {0};
+    notify_data *data;
 
-    if (!once++) FIXME("%p 0x%x %p - semi-stub\n", hService, dwNotifyMask, pNotifyBuffer);
+    TRACE("%p 0x%x %p\n", hService, dwNotifyMask, pNotifyBuffer);
 
-    ret = QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (void*)&st, sizeof(st), &dummy);
-    if (ret)
+    data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*data));
+    if (!data)
+        return ERROR_NOT_ENOUGH_MEMORY;
+
+    data->service = hService;
+    data->notify_buffer = pNotifyBuffer;
+    if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
+            GetCurrentProcess(), &data->calling_thread, 0, FALSE,
+            DUPLICATE_SAME_ACCESS))
+    {
+        ERR("DuplicateHandle failed: %u\n", GetLastError());
+        HeapFree(GetProcessHeap(), 0, data);
+        return ERROR_NOT_ENOUGH_MEMORY;
+    }
+
+    data->params.dwInfoLevel = 2;
+    data->params.u.params = &data->cparams;
+
+    data->cparams.dwNotifyMask = dwNotifyMask;
+
+    EnterCriticalSection( &service_cs );
+
+    __TRY
     {
-        /* dwNotifyMask is a set of bitflags in same order as SERVICE_ statuses */
-        if (dwNotifyMask & (1 << (st.dwCurrentState - SERVICE_STOPPED)))
-        {
-            pNotifyBuffer->dwNotificationStatus = ERROR_SUCCESS;
-            memcpy(&pNotifyBuffer->ServiceStatus, &st, sizeof(pNotifyBuffer->ServiceStatus));
-            pNotifyBuffer->dwNotificationTriggered = 1 << (st.dwCurrentState - SERVICE_STOPPED);
-            pNotifyBuffer->pszServiceNames = NULL;
-            TRACE("Queueing notification: 0x%x\n", pNotifyBuffer->dwNotificationTriggered);
-            QueueUserAPC((PAPCFUNC)pNotifyBuffer->pfnNotifyCallback,
-                    GetCurrentThread(), (ULONG_PTR)pNotifyBuffer);
-        }
+        err = svcctl_NotifyServiceStatusChange(hService, data->params,
+                &g_dummy, &g_dummy, &b_dummy, &data->notify_handle);
     }
+    __EXCEPT(rpc_filter)
+    {
+        err = map_exception_code(GetExceptionCode());
+    }
+    __ENDTRY
+
+    if (err != ERROR_SUCCESS)
+    {
+        WARN("NotifyServiceStatusChange server call failed: %u\n", err);
+        LeaveCriticalSection( &service_cs );
+        CloseHandle(data->calling_thread);
+        CloseHandle(data->ready_evt);
+        HeapFree(GetProcessHeap(), 0, data);
+        return err;
+    }
+
+    CloseHandle(CreateThread(NULL, 0, &notify_thread, data, 0, NULL));
+
+    list_add_tail(&notify_list, &data->entry);
 
-    /* TODO: If the service is not currently in a matching state, we should
-     * tell `services` to monitor it. */
+    LeaveCriticalSection( &service_cs );
 
     return ERROR_SUCCESS;
 }
diff --git a/dlls/advapi32/tests/service.c b/dlls/advapi32/tests/service.c
index 405cef7662..cc4834753d 100644
--- a/dlls/advapi32/tests/service.c
+++ b/dlls/advapi32/tests/service.c
@@ -2263,73 +2263,146 @@ static DWORD try_start_stop(SC_HANDLE svc_handle, const char* name, DWORD is_nt4
     return le1;
 }
 
+#define PHASE_STOPPED 1
+#define PHASE_RUNNING 2
+
 struct notify_data {
     SERVICE_NOTIFYW notify;
     SC_HANDLE svc;
+    BOOL was_called;
+    DWORD phase;
 };
 
-static void CALLBACK cb_stopped(void *user)
+static void CALLBACK notify_cb(void *user)
 {
     struct notify_data *data = user;
-    BOOL br;
+    switch (data->phase)
+    {
+    case PHASE_STOPPED:
+        ok(data->notify.dwNotificationStatus == ERROR_SUCCESS,
+                "Got wrong notification status: %u\n", data->notify.dwNotificationStatus);
+        ok(data->notify.ServiceStatus.dwCurrentState == SERVICE_STOPPED,
+                "Got wrong service state: 0x%x\n", data->notify.ServiceStatus.dwCurrentState);
+        ok(data->notify.dwNotificationTriggered == SERVICE_NOTIFY_STOPPED,
+                "Got wrong notification triggered: 0x%x\n", data->notify.dwNotificationTriggered);
+        break;
 
-    ok(data->notify.dwNotificationStatus == ERROR_SUCCESS,
-            "Got wrong notification status: %u\n", data->notify.dwNotificationStatus);
-    ok(data->notify.ServiceStatus.dwCurrentState == SERVICE_STOPPED,
-            "Got wrong service state: 0x%x\n", data->notify.ServiceStatus.dwCurrentState);
-    ok(data->notify.dwNotificationTriggered == SERVICE_NOTIFY_STOPPED,
-            "Got wrong notification triggered: 0x%x\n", data->notify.dwNotificationTriggered);
+    case PHASE_RUNNING:
+        ok(data->notify.dwNotificationStatus == ERROR_SUCCESS,
+                "Got wrong notification status: %u\n", data->notify.dwNotificationStatus);
+        ok(data->notify.ServiceStatus.dwCurrentState == SERVICE_RUNNING,
+                "Got wrong service state: 0x%x\n", data->notify.ServiceStatus.dwCurrentState);
+        ok(data->notify.dwNotificationTriggered == SERVICE_NOTIFY_RUNNING,
+                "Got wrong notification triggered: 0x%x\n", data->notify.dwNotificationTriggered);
+        break;
+    }
 
-    br = StartServiceA(data->svc, 0, NULL);
-    ok(br, "StartService failed: %u\n", GetLastError());
+    data->was_called = TRUE;
 }
 
-static void CALLBACK cb_running(void *user)
+static void test_servicenotify(SC_HANDLE scm_handle, const char *servicename)
 {
-    struct notify_data *data = user;
-    BOOL br;
-    SERVICE_STATUS status;
-
-    ok(data->notify.dwNotificationStatus == ERROR_SUCCESS,
-            "Got wrong notification status: %u\n", data->notify.dwNotificationStatus);
-    ok(data->notify.ServiceStatus.dwCurrentState == SERVICE_RUNNING,
-            "Got wrong service state: 0x%x\n", data->notify.ServiceStatus.dwCurrentState);
-    ok(data->notify.dwNotificationTriggered == SERVICE_NOTIFY_RUNNING,
-            "Got wrong notification triggered: 0x%x\n", data->notify.dwNotificationTriggered);
-
-    br = ControlService(data->svc, SERVICE_CONTROL_STOP, &status);
-    ok(br, "ControlService failed: %u\n", GetLastError());
-}
-
-static void test_servicenotify(SC_HANDLE svc)
-{
-    DWORD dr;
+    DWORD dr, dr2;
     struct notify_data data;
+    struct notify_data data2;
+    BOOL br;
+    SERVICE_STATUS status;
+    HANDLE svc, svc2;
 
     if(!pNotifyServiceStatusChangeW){
         win_skip("No NotifyServiceStatusChangeW\n");
         return;
     }
 
+    svc = OpenServiceA(scm_handle, servicename, GENERIC_ALL);
+    svc2 = OpenServiceA(scm_handle, servicename, GENERIC_ALL);
+    ok(svc != NULL && svc2 != NULL, "Failed to open service\n");
+    if(!svc || !svc2)
+        return;
+
+    /* receive stopped notification, then start service */
     memset(&data.notify, 0, sizeof(data.notify));
     data.notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
-    data.notify.pfnNotifyCallback = &cb_stopped;
+    data.notify.pfnNotifyCallback = &notify_cb;
     data.notify.pContext = &data;
     data.svc = svc;
+    data.phase = PHASE_STOPPED;
+    data.was_called = FALSE;
 
     dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data.notify);
     ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
 
     dr = SleepEx(100, TRUE);
-    ok(dr == WAIT_IO_COMPLETION, "APC wasn't called\n");
+    ok(dr == WAIT_IO_COMPLETION, "Got wrong SleepEx result: %u\n", dr);
+    ok(data.was_called == TRUE, "APC wasn't called\n");
 
-    data.notify.pfnNotifyCallback = &cb_running;
+    br = StartServiceA(svc, 0, NULL);
+    ok(br, "StartService failed: %u\n", GetLastError());
+
+    /* receive running notification */
+    data.phase = PHASE_RUNNING;
+    data.was_called = FALSE;
 
     dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data.notify);
     ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
 
     dr = SleepEx(100, TRUE);
-    ok(dr == WAIT_IO_COMPLETION, "APC wasn't called\n");
+    ok(dr == WAIT_IO_COMPLETION, "Got wrong SleepEx result: %u\n", dr);
+    ok(data.was_called == TRUE, "APC wasn't called\n");
+
+    /* cannot register two notifications */
+    data.phase = PHASE_STOPPED;
+    data.was_called = FALSE;
+
+    dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data.notify);
+    ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
+
+    memset(&data2.notify, 0, sizeof(data2.notify));
+    data2.notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
+    data2.notify.pfnNotifyCallback = &notify_cb;
+    data2.notify.pContext = &data2;
+
+    dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data2.notify);
+    ok(dr == ERROR_SUCCESS || /* win8+ */
+            dr == ERROR_ALREADY_REGISTERED, "NotifyServiceStatusChangeW gave wrong result: %u\n", dr);
+
+    /* should receive no notification because status has not changed.
+     * on win8+, SleepEx quits early but the callback is still not invoked. */
+    dr2 = SleepEx(100, TRUE);
+    ok((dr == ERROR_SUCCESS && dr2 == WAIT_IO_COMPLETION) || /* win8+ */
+            (dr == ERROR_ALREADY_REGISTERED && dr2 == 0), "Got wrong SleepEx result: %u\n", dr);
+    ok(data.was_called == FALSE, "APC should not have been called\n");
+
+    /* stop service and receive notifiction */
+    br = ControlService(svc, SERVICE_CONTROL_STOP, &status);
+    ok(br, "ControlService failed: %u\n", GetLastError());
+
+    dr = SleepEx(100, TRUE);
+    ok(dr == WAIT_IO_COMPLETION, "Got wrong SleepEx result: %u\n", dr);
+    ok(data.was_called == TRUE, "APC wasn't called\n");
+
+    /* test cancelation: create notify on svc that will block until service
+     * start; close svc; start service on svc2; verify that notification does
+     * not happen */
+
+    data.phase = PHASE_RUNNING;
+    data.was_called = FALSE;
+    dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data.notify);
+    ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
+
+    CloseServiceHandle(svc);
+
+    br = StartServiceA(svc2, 0, NULL);
+    ok(br, "StartService failed: %u\n", GetLastError());
+
+    dr = SleepEx(100, TRUE);
+    ok(dr == 0, "Got wrong SleepEx result: %u\n", dr);
+    ok(data.was_called == FALSE, "APC should not have been called\n");
+
+    br = ControlService(svc2, SERVICE_CONTROL_STOP, &status);
+    ok(br, "ControlService failed: %u\n", GetLastError());
+
+    CloseServiceHandle(svc2);
 }
 
 static void test_start_stop(void)
@@ -2409,7 +2482,7 @@ static void test_start_stop(void)
     displayname = "Winetest Service";
     ret = ChangeServiceConfigA(svc_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, cmd, NULL, NULL, NULL, NULL, NULL, displayname);
     ok(ret, "ChangeServiceConfig() failed le=%u\n", GetLastError());
-    test_servicenotify(svc_handle);
+    test_servicenotify(scm_handle, servicename);
 
 cleanup:
     if (svc_handle)
-- 
2.16.0




More information about the wine-devel mailing list