advapi32: Implement NotifyServiceStatusChange.
Signed-off-by: Andrew Eikum <aeikum@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
73e8d0e6a5
commit
1f88b90b74
|
@ -48,6 +48,7 @@
|
||||||
#include "advapi32_misc.h"
|
#include "advapi32_misc.h"
|
||||||
|
|
||||||
#include "wine/exception.h"
|
#include "wine/exception.h"
|
||||||
|
#include "wine/list.h"
|
||||||
|
|
||||||
WINE_DEFAULT_DEBUG_CHANNEL(service);
|
WINE_DEFAULT_DEBUG_CHANNEL(service);
|
||||||
|
|
||||||
|
@ -83,6 +84,18 @@ typedef struct dispatcher_data_t
|
||||||
HANDLE pipe;
|
HANDLE pipe;
|
||||||
} dispatcher_data;
|
} 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 service_cs;
|
||||||
static CRITICAL_SECTION_DEBUG service_cs_debug =
|
static CRITICAL_SECTION_DEBUG service_cs_debug =
|
||||||
{
|
{
|
||||||
|
@ -2596,37 +2609,130 @@ BOOL WINAPI EnumDependentServicesW( SC_HANDLE hService, DWORD dwServiceState,
|
||||||
return TRUE;
|
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.@]
|
* NotifyServiceStatusChangeW [ADVAPI32.@]
|
||||||
*/
|
*/
|
||||||
DWORD WINAPI NotifyServiceStatusChangeW(SC_HANDLE hService, DWORD dwNotifyMask,
|
DWORD WINAPI NotifyServiceStatusChangeW(SC_HANDLE hService, DWORD dwNotifyMask,
|
||||||
SERVICE_NOTIFYW *pNotifyBuffer)
|
SERVICE_NOTIFYW *pNotifyBuffer)
|
||||||
{
|
{
|
||||||
DWORD dummy;
|
DWORD err;
|
||||||
BOOL ret;
|
BOOL b_dummy = FALSE;
|
||||||
SERVICE_STATUS_PROCESS st;
|
GUID g_dummy = {0};
|
||||||
static int once;
|
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);
|
data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*data));
|
||||||
if (ret)
|
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 */
|
ERR("DuplicateHandle failed: %u\n", GetLastError());
|
||||||
if (dwNotifyMask & (1 << (st.dwCurrentState - SERVICE_STOPPED)))
|
HeapFree(GetProcessHeap(), 0, data);
|
||||||
{
|
return ERROR_NOT_ENOUGH_MEMORY;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: If the service is not currently in a matching state, we should
|
data->params.dwInfoLevel = 2;
|
||||||
* tell `services` to monitor it. */
|
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;
|
return ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2263,73 +2263,146 @@ static DWORD try_start_stop(SC_HANDLE svc_handle, const char* name, DWORD is_nt4
|
||||||
return le1;
|
return le1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define PHASE_STOPPED 1
|
||||||
|
#define PHASE_RUNNING 2
|
||||||
|
|
||||||
struct notify_data {
|
struct notify_data {
|
||||||
SERVICE_NOTIFYW notify;
|
SERVICE_NOTIFYW notify;
|
||||||
SC_HANDLE svc;
|
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;
|
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,
|
case PHASE_RUNNING:
|
||||||
"Got wrong notification status: %u\n", data->notify.dwNotificationStatus);
|
ok(data->notify.dwNotificationStatus == ERROR_SUCCESS,
|
||||||
ok(data->notify.ServiceStatus.dwCurrentState == SERVICE_STOPPED,
|
"Got wrong notification status: %u\n", data->notify.dwNotificationStatus);
|
||||||
"Got wrong service state: 0x%x\n", data->notify.ServiceStatus.dwCurrentState);
|
ok(data->notify.ServiceStatus.dwCurrentState == SERVICE_RUNNING,
|
||||||
ok(data->notify.dwNotificationTriggered == SERVICE_NOTIFY_STOPPED,
|
"Got wrong service state: 0x%x\n", data->notify.ServiceStatus.dwCurrentState);
|
||||||
"Got wrong notification triggered: 0x%x\n", data->notify.dwNotificationTriggered);
|
ok(data->notify.dwNotificationTriggered == SERVICE_NOTIFY_RUNNING,
|
||||||
|
"Got wrong notification triggered: 0x%x\n", data->notify.dwNotificationTriggered);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
br = StartServiceA(data->svc, 0, NULL);
|
data->was_called = TRUE;
|
||||||
ok(br, "StartService failed: %u\n", GetLastError());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
BOOL br;
|
||||||
SERVICE_STATUS status;
|
SERVICE_STATUS status;
|
||||||
|
HANDLE svc, svc2;
|
||||||
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;
|
|
||||||
|
|
||||||
if(!pNotifyServiceStatusChangeW){
|
if(!pNotifyServiceStatusChangeW){
|
||||||
win_skip("No NotifyServiceStatusChangeW\n");
|
win_skip("No NotifyServiceStatusChangeW\n");
|
||||||
return;
|
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));
|
memset(&data.notify, 0, sizeof(data.notify));
|
||||||
data.notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
|
data.notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
|
||||||
data.notify.pfnNotifyCallback = &cb_stopped;
|
data.notify.pfnNotifyCallback = ¬ify_cb;
|
||||||
data.notify.pContext = &data;
|
data.notify.pContext = &data;
|
||||||
data.svc = svc;
|
data.svc = svc;
|
||||||
|
data.phase = PHASE_STOPPED;
|
||||||
|
data.was_called = FALSE;
|
||||||
|
|
||||||
dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data.notify);
|
dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data.notify);
|
||||||
ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
|
ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
|
||||||
|
|
||||||
dr = SleepEx(100, TRUE);
|
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);
|
dr = pNotifyServiceStatusChangeW(svc, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING, &data.notify);
|
||||||
ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
|
ok(dr == ERROR_SUCCESS, "NotifyServiceStatusChangeW failed: %u\n", dr);
|
||||||
|
|
||||||
dr = SleepEx(100, TRUE);
|
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)
|
static void test_start_stop(void)
|
||||||
|
@ -2409,7 +2482,7 @@ static void test_start_stop(void)
|
||||||
displayname = "Winetest Service";
|
displayname = "Winetest Service";
|
||||||
ret = ChangeServiceConfigA(svc_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, cmd, NULL, NULL, NULL, NULL, NULL, displayname);
|
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());
|
ok(ret, "ChangeServiceConfig() failed le=%u\n", GetLastError());
|
||||||
test_servicenotify(svc_handle);
|
test_servicenotify(scm_handle, servicename);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
if (svc_handle)
|
if (svc_handle)
|
||||||
|
|
Loading…
Reference in New Issue