diff --git a/dlls/advapi32/advapi32.spec b/dlls/advapi32/advapi32.spec index 322360d6b5f..e7b7cf7ebc4 100644 --- a/dlls/advapi32/advapi32.spec +++ b/dlls/advapi32/advapi32.spec @@ -496,7 +496,7 @@ @ stdcall NotifyChangeEventLog (long long) # @ stub NotifyServiceStatusChange # @ stub NotifyServiceStatusChangeA -# @ stub NotifyServiceStatusChangeW +@ stdcall NotifyServiceStatusChangeW(ptr long ptr) @ stdcall ObjectCloseAuditAlarmA(str ptr long) @ stdcall ObjectCloseAuditAlarmW(wstr ptr long) # @ stub ObjectDeleteAuditAlarmA diff --git a/dlls/advapi32/service.c b/dlls/advapi32/service.c index ae002c9eeae..4657bd9fbe9 100644 --- a/dlls/advapi32/service.c +++ b/dlls/advapi32/service.c @@ -2335,3 +2335,37 @@ BOOL WINAPI EnumDependentServicesW( SC_HANDLE hService, DWORD dwServiceState, *lpServicesReturned = 0; return TRUE; } + +/****************************************************************************** + * NotifyServiceStatusChangeW [ADVAPI32.@] + */ +DWORD WINAPI NotifyServiceStatusChangeW(SC_HANDLE hService, DWORD dwNotifyMask, + SERVICE_NOTIFYW *pNotifyBuffer) +{ + DWORD dummy; + BOOL ret; + SERVICE_STATUS_PROCESS st; + + FIXME("%p 0x%x %p - semi-stub\n", hService, dwNotifyMask, pNotifyBuffer); + + ret = QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (void*)&st, sizeof(st), &dummy); + if (ret) + { + /* 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); + } + } + + /* TODO: If the service is not currently in a matching state, we should + * tell `services` to monitor it. */ + + return ERROR_SUCCESS; +} diff --git a/dlls/advapi32/tests/service.c b/dlls/advapi32/tests/service.c index 0fc8e3a2a56..f87508b3e6e 100644 --- a/dlls/advapi32/tests/service.c +++ b/dlls/advapi32/tests/service.c @@ -50,6 +50,7 @@ static BOOL (WINAPI *pQueryServiceStatusEx)(SC_HANDLE, SC_STATUS_TYPE, LPBYTE, DWORD, LPDWORD); static BOOL (WINAPI *pQueryServiceObjectSecurity)(SC_HANDLE, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, LPDWORD); +static DWORD (WINAPI *pNotifyServiceStatusChangeW)(SC_HANDLE,DWORD,SERVICE_NOTIFYW*); static void init_function_pointers(void) { @@ -63,6 +64,7 @@ static void init_function_pointers(void) pQueryServiceConfig2W= (void*)GetProcAddress(hadvapi32, "QueryServiceConfig2W"); pQueryServiceStatusEx= (void*)GetProcAddress(hadvapi32, "QueryServiceStatusEx"); pQueryServiceObjectSecurity = (void*)GetProcAddress(hadvapi32, "QueryServiceObjectSecurity"); + pNotifyServiceStatusChangeW = (void*)GetProcAddress(hadvapi32, "NotifyServiceStatusChangeW"); } static void test_open_scm(void) @@ -2195,6 +2197,75 @@ static DWORD try_start_stop(SC_HANDLE svc_handle, const char* name, DWORD is_nt4 return le1; } +struct notify_data { + SERVICE_NOTIFYW notify; + SC_HANDLE svc; +}; + +void CALLBACK cb_stopped(void *user) +{ + struct notify_data *data = user; + BOOL br; + + 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); + + br = StartServiceA(data->svc, 0, NULL); + ok(br, "StartService failed: %u\n", GetLastError()); +} + +void CALLBACK cb_running(void *user) +{ + 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; + struct notify_data data; + + if(!pNotifyServiceStatusChangeW){ + win_skip("No NotifyServiceStatusChangeW\n"); + return; + } + + memset(&data.notify, 0, sizeof(data.notify)); + data.notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE; + data.notify.pfnNotifyCallback = &cb_stopped; + data.notify.pContext = &data; + data.svc = svc; + + 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"); + + data.notify.pfnNotifyCallback = &cb_running; + + 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"); +} + static void test_start_stop(void) { BOOL ret; @@ -2267,6 +2338,13 @@ static void test_start_stop(void) le = try_start_stop(svc_handle, displayname, is_nt4); ok(le == ERROR_SERVICE_REQUEST_TIMEOUT, "%d != ERROR_SERVICE_REQUEST_TIMEOUT\n", le); + /* create a real service and test notifications */ + sprintf(cmd, "%s service serve", selfname); + 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); + cleanup: if (svc_handle) { @@ -2370,6 +2448,57 @@ static void test_refcount(void) CloseServiceHandle(scm_handle); } +static DWORD WINAPI ctrl_handler(DWORD ctl, DWORD type, void *data, void *user) +{ + HANDLE evt = user; + + switch(ctl){ + case SERVICE_CONTROL_STOP: + SetEvent(evt); + break; + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + } + + return ERROR_CALL_NOT_IMPLEMENTED; +} + +static void WINAPI service_main(DWORD argc, char **argv) +{ + SERVICE_STATUS_HANDLE st_handle; + SERVICE_STATUS st; + HANDLE evt = CreateEventW(0, FALSE, FALSE, 0); + + st_handle = RegisterServiceCtrlHandlerExA("", &ctrl_handler, evt); + + st.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + st.dwServiceSpecificExitCode = 0; + st.dwCurrentState = SERVICE_RUNNING; + st.dwWin32ExitCode = NO_ERROR; + st.dwWaitHint = 0; + st.dwControlsAccepted = SERVICE_ACCEPT_STOP; + st.dwCheckPoint = 0; + + SetServiceStatus(st_handle, &st); + + WaitForSingleObject(evt, 5000); + + st.dwCurrentState = SERVICE_STOPPED; + + SetServiceStatus(st_handle, &st); +} + +static void run_service(void) +{ + char empty[] = {0}; + SERVICE_TABLE_ENTRYA table[] = { + {empty, &service_main }, + {0, 0} + }; + + StartServiceCtrlDispatcherA(table); +} + START_TEST(service) { SC_HANDLE scm_handle; @@ -2380,6 +2509,8 @@ START_TEST(service) GetFullPathNameA(myARGV[0], sizeof(selfname), selfname, NULL); if (myARGC >= 3) { + if (strcmp(myARGV[2], "serve") == 0) + run_service(); return; } diff --git a/include/winsvc.h b/include/winsvc.h index c1af50940d6..7d768f5ae28 100644 --- a/include/winsvc.h +++ b/include/winsvc.h @@ -159,6 +159,43 @@ typedef struct _SERVICE_STATUS_PROCESS DWORD dwServiceFlags; } SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS; +#define SERVICE_NOTIFY_STATUS_CHANGE 2 + +#define SERVICE_NOTIFY_STOPPED 0x1 +#define SERVICE_NOTIFY_START_PENDING 0x2 +#define SERVICE_NOTIFY_STOP_PENDING 0x4 +#define SERVICE_NOTIFY_RUNNING 0x8 +#define SERVICE_NOTIFY_CONTINUE_PENDING 0x10 +#define SERVICE_NOTIFY_PAUSE_PENDING 0x20 +#define SERVICE_NOTIFY_PAUSED 0x40 +#define SERVICE_NOTIFY_CREATED 0x80 +#define SERVICE_NOTIFY_DELETED 0x100 +#define SERVICE_NOTIFY_DELETE_PENDING 0x200 + +typedef void (CALLBACK *PFN_SC_NOTIFY_CALLBACK)(void *); + +typedef struct _SERVICE_NOTIFY_2A { + DWORD dwVersion; + PFN_SC_NOTIFY_CALLBACK pfnNotifyCallback; + void *pContext; + DWORD dwNotificationStatus; + SERVICE_STATUS_PROCESS ServiceStatus; + DWORD dwNotificationTriggered; + char *pszServiceNames; +} SERVICE_NOTIFY_2A, SERVICE_NOTIFYA; + +typedef struct _SERVICE_NOTIFY_2W { + DWORD dwVersion; + PFN_SC_NOTIFY_CALLBACK pfnNotifyCallback; + void *pContext; + DWORD dwNotificationStatus; + SERVICE_STATUS_PROCESS ServiceStatus; + DWORD dwNotificationTriggered; + WCHAR *pszServiceNames; +} SERVICE_NOTIFY_2W, SERVICE_NOTIFYW; + +DWORD WINAPI NotifyServiceStatusChangeW(SC_HANDLE,DWORD,SERVICE_NOTIFYW*); + typedef enum _SC_STATUS_TYPE { SC_STATUS_PROCESS_INFO = 0 } SC_STATUS_TYPE;