Thread safety.
[nssm.git] / process.cpp
1 #include "nssm.h"
2
3 /* Send some window messages and hope the window respects one or more. */
4 int CALLBACK kill_window(HWND window, LPARAM arg) {
5   kill_t *k = (kill_t *) arg;
6
7   unsigned long pid;
8   if (! GetWindowThreadProcessId(window, &pid)) return 1;
9   if (pid != k->pid) return 1;
10
11   /* First try sending WM_CLOSE to request that the window close. */
12   k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
13
14   /*
15     Then tell the window that the user is logging off and it should exit
16     without worrying about saving any data.
17   */
18   k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
19
20   return 1;
21 }
22
23 /*
24   Try to post a message to the message queues of threads associated with the
25   given process ID.  Not all threads have message queues so there's no
26   guarantee of success, and we don't want to be left waiting for unsignalled
27   processes so this function returns only true if at least one thread was
28   successfully prodded.
29 */
30 int kill_threads(char *service_name, kill_t *k) {
31   int ret = 0;
32
33   /* Get a snapshot of all threads in the system. */
34   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
35   if (! snapshot) {
36     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, error_string(GetLastError()), 0);
37     return 0;
38   }
39
40   THREADENTRY32 te;
41   ZeroMemory(&te, sizeof(te));
42   te.dwSize = sizeof(te);
43
44   if (! Thread32First(snapshot, &te)) {
45     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
46     return 0;
47   }
48
49   /* This thread belongs to the doomed process so signal it. */
50   if (te.th32OwnerProcessID == k->pid) {
51     ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
52   }
53
54   while (true) {
55     /* Try to get the next thread. */
56     if (! Thread32Next(snapshot, &te)) {
57       unsigned long error = GetLastError();
58       if (error == ERROR_NO_MORE_FILES) break;
59       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
60       return ret;
61     }
62
63     if (te.th32OwnerProcessID == k->pid) {
64       ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
65     }
66   }
67
68   return ret;
69 }
70
71 /* Give the process a chance to die gracefully. */
72 int kill_process(char *service_name, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
73   /* Shouldn't happen. */
74   if (! pid) return 1;
75
76   kill_t k = { pid, exitcode, 0 };
77
78   /*
79     Try to post messages to the windows belonging to the given process ID.
80     If the process is a console application it won't have any windows so there's
81     no guarantee of success.
82   */
83   EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
84   if (k.signalled) {
85     if (! WaitForSingleObject(process_handle, NSSM_KILL_WINDOW_GRACE_PERIOD)) return 1;
86   }
87
88   /*
89     Try to post messages to any thread message queues associated with the
90     process.  Console applications might have them (but probably won't) so
91     there's still no guarantee of success.
92   */
93   if (kill_threads(service_name, &k)) {
94     if (! WaitForSingleObject(process_handle, NSSM_KILL_THREADS_GRACE_PERIOD)) return 1;
95   }
96
97   /* We tried being nice.  Time for extreme prejudice. */
98   return TerminateProcess(process_handle, exitcode);
99 }
100
101 void kill_process_tree(char *service_name, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
102   /* Shouldn't happen unless the service failed to start. */
103   if (! pid) return;
104
105   char pid_string[16], code[16];
106   _snprintf(pid_string, sizeof(pid_string), "%d", pid);
107   _snprintf(code, sizeof(code), "%d", exitcode);
108   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service_name, pid_string, code, 0);
109
110   /* Get a snapshot of all processes in the system. */
111   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
112   if (! snapshot) {
113     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service_name, error_string(GetLastError()), 0);
114     return;
115   }
116
117   PROCESSENTRY32 pe;
118   ZeroMemory(&pe, sizeof(pe));
119   pe.dwSize = sizeof(pe);
120
121   if (! Process32First(snapshot, &pe)) {
122     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
123     return;
124   }
125
126   /* This is a child of the doomed process so kill it. */
127   if (pe.th32ParentProcessID == pid) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid);
128
129   while (true) {
130     /* Try to get the next process. */
131     if (! Process32Next(snapshot, &pe)) {
132       unsigned long ret = GetLastError();
133       if (ret == ERROR_NO_MORE_FILES) break;
134       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
135       return;
136     }
137
138     if (pe.th32ParentProcessID == pid) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid);
139   }
140
141   /* We will need a process handle in order to call TerminateProcess() later. */
142   HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
143   if (! process_handle) {
144     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service_name, error_string(GetLastError()), 0);
145     return;
146   }
147
148   char ppid_string[16];
149   _snprintf(ppid_string, sizeof(ppid_string), "%d", ppid);
150   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service_name, 0);
151   if (! kill_process(service_name, process_handle, pid, exitcode)) {
152     /* Maybe it already died. */
153     unsigned long ret;
154     if (! GetExitCodeProcess(process_handle, &ret)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, service_name, error_string(GetLastError()), 0);
155     return;
156   }
157 }