1
0
mirror of https://github.com/wine-mirror/wine synced 2024-07-01 07:14:31 +00:00

services: Change running service state once its process dies.

This commit is contained in:
Paul Gofman 2023-03-29 18:19:03 -06:00 committed by Alexandre Julliard
parent 3580d466cc
commit c0c68ee83a
4 changed files with 178 additions and 3 deletions

View File

@ -868,7 +868,7 @@ static void fill_notify(struct sc_notify_handle *notify, struct service_entry *s
SetEvent(notify->event);
}
static void notify_service_state(struct service_entry *service)
void notify_service_state(struct service_entry *service)
{
struct sc_service_handle *service_handle;
DWORD mask;

View File

@ -47,7 +47,7 @@ static DWORD default_preshutdown_timeout = 180000;
static DWORD autostart_delay = 120000;
static void *environment = NULL;
static HKEY service_current_key = NULL;
static HANDLE job_object;
static HANDLE job_object, job_completion_port;
static const BOOL is_win64 = (sizeof(void *) > sizeof(int));
@ -1280,6 +1280,50 @@ static void load_registry_parameters(void)
RegCloseKey( key );
}
static DWORD WINAPI process_monitor_thread_proc( void *arg )
{
struct scmdatabase *db = active_database;
struct service_entry *service;
struct process_entry *process;
OVERLAPPED *overlapped;
ULONG_PTR value;
DWORD key, pid;
while (GetQueuedCompletionStatus(job_completion_port, &key, &value, &overlapped, INFINITE))
{
if (!key)
break;
if (key != JOB_OBJECT_MSG_EXIT_PROCESS)
continue;
pid = (ULONG_PTR)overlapped;
WINE_TRACE("pid %04lx exited.\n", pid);
scmdatabase_lock(db);
LIST_FOR_EACH_ENTRY(service, &db->services, struct service_entry, entry)
{
if (service->status.dwCurrentState != SERVICE_RUNNING || !service->process
|| service->process->process_id != pid) continue;
WINE_TRACE("Stopping service %s.\n", debugstr_w(service->config.lpBinaryPathName));
service->status.dwCurrentState = SERVICE_STOPPED;
service->status.dwControlsAccepted = 0;
service->status.dwWin32ExitCode = ERROR_PROCESS_ABORTED;
service->status.dwServiceSpecificExitCode = 0;
service->status.dwCheckPoint = 0;
service->status.dwWaitHint = 0;
SetEvent(service->status_changed_event);
process = service->process;
service->process = NULL;
process->use_count--;
release_process(process);
notify_service_state(service);
}
scmdatabase_unlock(db);
}
WINE_TRACE("Terminating.\n");
return 0;
}
int __cdecl main(int argc, char *argv[])
{
static const WCHAR service_current_key_str[] = { 'S','Y','S','T','E','M','\\',
@ -1288,7 +1332,8 @@ int __cdecl main(int argc, char *argv[])
'S','e','r','v','i','c','e','C','u','r','r','e','n','t',0};
static const WCHAR svcctl_started_event[] = SVCCTL_STARTED_EVENT;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_limit;
HANDLE started_event;
JOBOBJECT_ASSOCIATE_COMPLETION_PORT port_info;
HANDLE started_event, process_monitor_thread;
DWORD err;
job_object = CreateJobObjectW(NULL, NULL);
@ -1298,6 +1343,15 @@ int __cdecl main(int argc, char *argv[])
WINE_ERR("Failed to initialized job object, err %lu.\n", GetLastError());
return GetLastError();
}
job_completion_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
port_info.CompletionPort = job_completion_port;
port_info.CompletionKey = job_object;
if (!SetInformationJobObject(job_object, JobObjectAssociateCompletionPortInformation,
&port_info, sizeof(port_info)))
{
WINE_ERR("Failed to set completion port for job, err %lu.\n", GetLastError());
return GetLastError();
}
started_event = CreateEventW(NULL, TRUE, FALSE, svcctl_started_event);
@ -1316,8 +1370,11 @@ int __cdecl main(int argc, char *argv[])
if ((err = RPC_Init()) == ERROR_SUCCESS)
{
scmdatabase_autostart_services(active_database);
process_monitor_thread = CreateThread(NULL, 0, process_monitor_thread_proc, NULL, 0, NULL);
SetEvent(started_event);
WaitForSingleObject(exit_event, INFINITE);
PostQueuedCompletionStatus(job_completion_port, 0, 0, NULL);
WaitForSingleObject(process_monitor_thread, INFINITE);
scmdatabase_wait_terminate(active_database);
if (delayed_autostart_cleanup)
{

View File

@ -93,6 +93,7 @@ void release_service(struct service_entry *service);
void service_lock(struct service_entry *service);
void service_unlock(struct service_entry *service);
DWORD service_start(struct service_entry *service, DWORD service_argc, LPCWSTR *service_argv);
void notify_service_state(struct service_entry *service);
/* Process functions */

View File

@ -616,6 +616,122 @@ static void test_runner(void (*p_run_test)(void))
CloseHandle(thread);
}
static void CALLBACK notify_cb(void *user)
{
}
static inline void test_kill_service_process(void)
{
static const char *argv[2] = {"param1", "param2"};
SC_HANDLE service_handle = register_service("simple_service");
SERVICE_STATUS_PROCESS status2;
SERVICE_NOTIFYW notify;
SERVICE_STATUS status;
DWORD bytes, error;
HANDLE process;
BOOL res;
if(!service_handle)
return;
trace("starting...\n");
res = StartServiceA(service_handle, 2, argv);
ok(res, "StartService failed: %lu\n", GetLastError());
if(!res) {
DeleteService(service_handle);
CloseServiceHandle(service_handle);
return;
}
expect_event("RUNNING");
memset(&notify, 0, sizeof(notify));
notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
notify.dwNotificationStatus = 0xdeadbeef;
notify.pfnNotifyCallback = notify_cb;
notify.pContext = &notify;
error = NotifyServiceStatusChangeW(service_handle, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING
| SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_PAUSE_PENDING | SERVICE_NOTIFY_STOP_PENDING, &notify);
ok(error == ERROR_SUCCESS, "got error %lu.\n", error);
/* This shouldn't wait, we are supposed to get service start notification before. */
SleepEx(5000, TRUE);
ok(!notify.dwNotificationStatus, "got %#lx.\n", notify.dwNotificationStatus);
ok(notify.dwNotificationTriggered == SERVICE_NOTIFY_RUNNING, "got %#lx.\n", notify.dwNotificationTriggered);
ok(notify.ServiceStatus.dwCurrentState == SERVICE_RUNNING, "got %#lx.\n", notify.ServiceStatus.dwCurrentState);
ok(!notify.ServiceStatus.dwWin32ExitCode, "got %#lx.\n", notify.ServiceStatus.dwWin32ExitCode);
res = QueryServiceStatus(service_handle, &status);
ok(res, "got error %lu.\n", GetLastError());
ok(status.dwServiceType == SERVICE_WIN32_OWN_PROCESS, "got %lx.\n", status.dwServiceType);
ok(status.dwCurrentState == SERVICE_RUNNING, "got %lx.\n", status.dwCurrentState);
res = QueryServiceStatusEx(service_handle, SC_STATUS_PROCESS_INFO, (BYTE *)&status2, sizeof(status2), &bytes);
ok(res, "got error %lu.\n", GetLastError());
ok(status2.dwCurrentState == SERVICE_RUNNING, "got %lx.\n", status2.dwCurrentState);
ok(status2.dwProcessId, "got %ld\n", status2.dwProcessId);
process = OpenProcess(PROCESS_TERMINATE, FALSE, status2.dwProcessId);
ok(!!process, "got NULL.\n");
res = TerminateProcess(process, 0xdeadbeef);
ok(res, "got error %lu.\n", GetLastError());
CloseHandle(process);
memset(&notify, 0, sizeof(notify));
notify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
notify.dwNotificationStatus = 0xdeadbeef;
notify.pfnNotifyCallback = notify_cb;
notify.pContext = &notify;
error = NotifyServiceStatusChangeW(service_handle, SERVICE_NOTIFY_STOPPED | SERVICE_NOTIFY_RUNNING
| SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_PAUSE_PENDING | SERVICE_NOTIFY_STOP_PENDING, &notify);
ok(error == ERROR_SUCCESS, "got error %lu.\n", error);
SleepEx(3000, TRUE);
ok(!notify.dwNotificationStatus, "got %#lx.\n", notify.dwNotificationStatus);
ok(notify.dwNotificationTriggered == SERVICE_NOTIFY_STOPPED, "got %#lx.\n", notify.dwNotificationTriggered);
ok(notify.ServiceStatus.dwCurrentState == SERVICE_STOPPED, "got %#lx.\n", notify.ServiceStatus.dwCurrentState);
ok(notify.ServiceStatus.dwWin32ExitCode == ERROR_PROCESS_ABORTED, "got %#lx.\n", notify.ServiceStatus.dwWin32ExitCode);
ok(!notify.ServiceStatus.dwServiceSpecificExitCode, "got %#lx.\n", notify.ServiceStatus.dwWin32ExitCode);
res = QueryServiceStatus(service_handle, &status);
ok(res, "got error %lu.\n", error);
ok(status.dwServiceType == SERVICE_WIN32_OWN_PROCESS, "got %lx.\n", status.dwServiceType);
ok(status.dwCurrentState == SERVICE_STOPPED, "got %lx.\n", status.dwCurrentState);
ok(!status.dwControlsAccepted, "got %lx\n", status.dwControlsAccepted);
ok(status.dwWin32ExitCode == ERROR_PROCESS_ABORTED, "got %ld.\n", status.dwWin32ExitCode);
ok(!status.dwServiceSpecificExitCode, "got %ld.\n",
status.dwServiceSpecificExitCode);
ok(!status.dwCheckPoint, "got %ld.\n", status.dwCheckPoint);
ok(!status.dwWaitHint, "got %ld.\n", status.dwWaitHint);
res = QueryServiceStatusEx(service_handle, SC_STATUS_PROCESS_INFO, (BYTE *)&status2, sizeof(status2), &bytes);
ok(res, "got error %lu.\n", error);
ok(!status2.dwProcessId, "got %ld.\n", status2.dwProcessId);
res = DeleteService(service_handle);
ok(res, "got error %lu.\n", error);
res = QueryServiceStatus(service_handle, &status);
ok(res, "got error %lu.\n", error);
ok(status.dwServiceType == SERVICE_WIN32_OWN_PROCESS, "got %lx.\n", status.dwServiceType);
ok(status.dwCurrentState == SERVICE_STOPPED, "got %lx.\n", status.dwCurrentState);
ok(status.dwControlsAccepted == 0, "got %lx.\n", status.dwControlsAccepted);
ok(status.dwWin32ExitCode == ERROR_PROCESS_ABORTED, "got %ld.\n", status.dwWin32ExitCode);
ok(status.dwServiceSpecificExitCode == 0, "got %ld.\n",
status.dwServiceSpecificExitCode);
ok(!status.dwCheckPoint, "got %ld.\n", status.dwCheckPoint);
ok(!status.dwWaitHint, "got %ld.\n", status.dwWaitHint);
res = QueryServiceStatusEx(service_handle, SC_STATUS_PROCESS_INFO, (BYTE *)&status2, sizeof(status2), &bytes);
ok(res, "got error %lu.\n", GetLastError());
ok(!status2.dwProcessId, "got %ld.\n", status2.dwProcessId);
CloseServiceHandle(service_handle);
res = QueryServiceStatus(service_handle, &status);
ok(!res, "QueryServiceStatus should have failed.\n");
ok(GetLastError() == ERROR_INVALID_HANDLE, "got %ld.\n", GetLastError());
}
START_TEST(service)
{
char **argv;
@ -641,6 +757,7 @@ START_TEST(service)
if(argc < 3) {
test_runner(test_service);
test_runner(test_no_stop);
test_runner(test_kill_service_process);
}else {
strcpy(service_name, argv[3]);
sprintf(named_pipe_name, "\\\\.\\pipe\\%s_pipe", service_name);