From 1f88b90b741b81162b88246206d138549e7cbdbe Mon Sep 17 00:00:00 2001 From: Andrew Eikum Date: Tue, 23 Jan 2018 08:37:53 -0600 Subject: [PATCH] advapi32: Implement NotifyServiceStatusChange. Signed-off-by: Andrew Eikum Signed-off-by: Alexandre Julliard --- dlls/advapi32/service.c | 146 +++++++++++++++++++++++++++++----- dlls/advapi32/tests/service.c | 139 ++++++++++++++++++++++++-------- 2 files changed, 232 insertions(+), 53 deletions(-) diff --git a/dlls/advapi32/service.c b/dlls/advapi32/service.c index ddd6a214297..7d66326d51e 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)) { - /* 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("DuplicateHandle failed: %u\n", GetLastError()); + HeapFree(GetProcessHeap(), 0, data); + return ERROR_NOT_ENOUGH_MEMORY; } - /* TODO: If the service is not currently in a matching state, we should - * tell `services` to monitor it. */ + data->params.dwInfoLevel = 2; + data->params.u.params = &data->cparams; + + data->cparams.dwNotifyMask = dwNotifyMask; + + EnterCriticalSection( &service_cs ); + + __TRY + { + 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, ¬ify_thread, data, 0, NULL)); + + list_add_tail(¬ify_list, &data->entry); + + LeaveCriticalSection( &service_cs ); return ERROR_SUCCESS; } diff --git a/dlls/advapi32/tests/service.c b/dlls/advapi32/tests/service.c index 405cef7662c..cc4834753d5 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; + DWORD dr, dr2; + struct notify_data data; + struct notify_data data2; 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; - struct notify_data data; + 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 = ¬ify_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 = ¬ify_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)