Use nssm_imagepath().
[nssm.git] / hook.cpp
1 #include "nssm.h"
2
3 typedef struct {
4   TCHAR *name;
5   HANDLE process_handle;
6   unsigned long pid;
7   unsigned long deadline;
8   FILETIME creation_time;
9   kill_t k;
10 } hook_t;
11
12 static unsigned long WINAPI await_hook(void *arg) {
13   hook_t *hook = (hook_t *) arg;
14   if (! hook) return NSSM_HOOK_STATUS_ERROR;
15
16   int ret = 0;
17   if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = NSSM_HOOK_STATUS_TIMEOUT;
18
19   /* Tidy up hook process tree. */
20   if (hook->name) hook->k.name = hook->name;
21   else hook->k.name = _T("hook");
22   hook->k.process_handle = hook->process_handle;
23   hook->k.pid = hook->pid;
24   hook->k.stop_method = ~0;
25   hook->k.kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
26   hook->k.kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
27   hook->k.kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
28   hook->k.creation_time = hook->creation_time;
29   GetSystemTimeAsFileTime(&hook->k.exit_time);
30   kill_process_tree(&hook->k, hook->pid);
31
32   if (ret) {
33     CloseHandle(hook->process_handle);
34     if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
35     HeapFree(GetProcessHeap(), 0, hook);
36     return ret;
37   }
38
39   unsigned long exitcode;
40   GetExitCodeProcess(hook->process_handle, &exitcode);
41   CloseHandle(hook->process_handle);
42
43   if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
44   HeapFree(GetProcessHeap(), 0, hook);
45
46   if (exitcode == NSSM_HOOK_STATUS_ABORT) return NSSM_HOOK_STATUS_ABORT;
47   if (exitcode) return NSSM_HOOK_STATUS_FAILED;
48
49   return NSSM_HOOK_STATUS_SUCCESS;
50 }
51
52 static void set_hook_runtime(TCHAR *v, FILETIME *start, FILETIME *now) {
53   if (start && now) {
54     ULARGE_INTEGER s;
55     s.LowPart = start->dwLowDateTime;
56     s.HighPart = start->dwHighDateTime;
57     if (s.QuadPart) {
58       ULARGE_INTEGER t;
59       t.LowPart = now->dwLowDateTime;
60       t.HighPart = now->dwHighDateTime;
61       if (t.QuadPart && t.QuadPart >= s.QuadPart) {
62         t.QuadPart -= s.QuadPart;
63         t.QuadPart /= 10000LL;
64         TCHAR number[16];
65         _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%llu"), t.QuadPart);
66         SetEnvironmentVariable(v, number);
67         return;
68       }
69     }
70   }
71   SetEnvironmentVariable(v, _T(""));
72 }
73
74 static void add_thread_handle(hook_thread_t *hook_threads, HANDLE thread_handle, TCHAR *name) {
75   if (! hook_threads) return;
76
77   int num_threads = hook_threads->num_threads + 1;
78   hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t));
79   if (! data) {
80     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook_thread_t"), _T("add_thread_handle()"), 0);
81     return;
82   }
83
84   int i;
85   for (i = 0; i < hook_threads->num_threads; i++) memmove(&data[i], &hook_threads->data[i], sizeof(data[i]));
86   memmove(data[i].name, name, sizeof(data[i].name));
87   data[i].thread_handle = thread_handle;
88
89   if (hook_threads->data) HeapFree(GetProcessHeap(), 0, hook_threads->data);
90   hook_threads->data = data;
91   hook_threads->num_threads = num_threads;
92 }
93
94 bool valid_hook_name(const TCHAR *hook_event, const TCHAR *hook_action, bool quiet) {
95   bool valid_event = false;
96   bool valid_action = false;
97
98   /* Exit/Post */
99   if (str_equiv(hook_event, NSSM_HOOK_EVENT_EXIT)) {
100     if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
101     if (quiet) return false;
102     print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
103     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
104     return false;
105   }
106
107   /* Power/{Change,Resume} */
108   if (str_equiv(hook_event, NSSM_HOOK_EVENT_POWER)) {
109     if (str_equiv(hook_action, NSSM_HOOK_ACTION_CHANGE)) return true;
110     if (str_equiv(hook_action, NSSM_HOOK_ACTION_RESUME)) return true;
111     if (quiet) return false;
112     print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
113     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_CHANGE);
114     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_RESUME);
115     return false;
116   }
117
118   /* Rotate/{Pre,Post} */
119   if (str_equiv(hook_event, NSSM_HOOK_EVENT_ROTATE)) {
120     if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
121     if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
122     if (quiet) return false;
123     print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
124     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
125     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
126     return false;
127   }
128
129   /* Start/{Pre,Post} */
130   if (str_equiv(hook_event, NSSM_HOOK_EVENT_START)) {
131     if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
132     if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
133     if (quiet) return false;
134     print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
135     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
136     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
137     return false;
138   }
139
140   /* Stop/Pre */
141   if (str_equiv(hook_event, NSSM_HOOK_EVENT_STOP)) {
142     if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
143     if (quiet) return false;
144     print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
145     _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
146     return false;
147   }
148
149   if (quiet) return false;
150   print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_EVENT);
151   _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_EXIT);
152   _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_POWER);
153   _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_ROTATE);
154   _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_START);
155   _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_STOP);
156   return false;
157 }
158
159 void await_hook_threads(hook_thread_t *hook_threads, SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, unsigned long deadline) {
160   if (! hook_threads) return;
161   if (! hook_threads->num_threads) return;
162
163   int *retain = (int *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hook_threads->num_threads * sizeof(int));
164   if (! retain) {
165     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("retain"), _T("await_hook_threads()"), 0);
166     return;
167   }
168
169   /*
170     We could use WaitForMultipleObjects() but await_single_object() can update
171     the service status as well.
172   */
173   int num_threads = 0;
174   int i;
175   for (i = 0; i < hook_threads->num_threads; i++) {
176     if (deadline) {
177       if (await_single_handle(status_handle, status, hook_threads->data[i].thread_handle, hook_threads->data[i].name, _T(__FUNCTION__), deadline) != 1) {
178         CloseHandle(hook_threads->data[i].thread_handle);
179         continue;
180       }
181     }
182     else if (WaitForSingleObject(hook_threads->data[i].thread_handle, 0) != WAIT_TIMEOUT) {
183       CloseHandle(hook_threads->data[i].thread_handle);
184       continue;
185     }
186
187     retain[num_threads++]= i;
188   }
189
190   if (num_threads) {
191     hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t));
192     if (! data) {
193     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("data"), _T("await_hook_threads()"), 0);
194       HeapFree(GetProcessHeap(), 0, retain);
195       return;
196     }
197
198     for (i = 0; i < num_threads; i++) memmove(&data[i], &hook_threads->data[retain[i]], sizeof(data[i]));
199
200     HeapFree(GetProcessHeap(), 0, hook_threads->data);
201     hook_threads->data = data;
202     hook_threads->num_threads = num_threads;
203   }
204   else {
205     HeapFree(GetProcessHeap(), 0, hook_threads->data);
206     ZeroMemory(hook_threads, sizeof(*hook_threads));
207   }
208
209   HeapFree(GetProcessHeap(), 0, retain);
210 }
211
212 /*
213    Returns:
214    NSSM_HOOK_STATUS_SUCCESS  if the hook ran successfully.
215    NSSM_HOOK_STATUS_NOTFOUND if no hook was found.
216    NSSM_HOOK_STATUS_ABORT    if the hook failed and we should cancel service start.
217    NSSM_HOOK_STATUS_ERROR    on error.
218    NSSM_HOOK_STATUS_NOTRUN   if the hook didn't run.
219    NSSM_HOOK_STATUS_TIMEOUT  if the hook timed out.
220    NSSM_HOOK_STATUS_FAILED   if the hook failed.
221 */
222 int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline, bool async) {
223   int ret = 0;
224
225   hook_t *hook = (hook_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(hook_t));
226   if (! hook) {
227     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0);
228     return NSSM_HOOK_STATUS_ERROR;
229   }
230
231   FILETIME now;
232   GetSystemTimeAsFileTime(&now);
233
234   EnterCriticalSection(&service->hook_section);
235
236   /* Set the environment. */
237   set_service_environment(service);
238
239   /* ABI version. */
240   TCHAR number[16];
241   _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), NSSM_HOOK_VERSION);
242   SetEnvironmentVariable(NSSM_HOOK_ENV_VERSION, number);
243
244   /* Event triggering this action. */
245   SetEnvironmentVariable(NSSM_HOOK_ENV_EVENT, hook_event);
246
247   /* Hook action. */
248   SetEnvironmentVariable(NSSM_HOOK_ENV_ACTION, hook_action);
249
250   /* Control triggering this action.  May be empty. */
251   if (hook_control) SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control));
252   else SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, _T(""));
253
254   /* Last control handled. */
255   SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control));
256
257   /* Path to NSSM, unquoted for the environment. */
258   SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath());
259
260   /* NSSM version. */
261   SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION);
262   SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_VERSION, NSSM_VERSION);
263   SetEnvironmentVariable(NSSM_HOOK_ENV_BUILD_DATE, NSSM_DATE);
264
265   /* NSSM PID. */
266   _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId());
267   SetEnvironmentVariable(NSSM_HOOK_ENV_PID, number);
268
269   /* NSSM runtime. */
270   set_hook_runtime(NSSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now);
271
272   /* Application PID. */
273   if (service->pid) {
274     _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid);
275     SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, number);
276     /* Application runtime. */
277     set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now);
278     /* Exit code. */
279     SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T(""));
280   }
281   else {
282     SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, _T(""));
283     if (str_equiv(hook_event, NSSM_HOOK_EVENT_START) && str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) {
284       SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_RUNTIME, _T(""));
285       SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T(""));
286     }
287     else {
288       set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time);
289       /* Exit code. */
290       _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode);
291       SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, number);
292     }
293   }
294
295   /* Deadline for this script. */
296   _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline);
297   SetEnvironmentVariable(NSSM_HOOK_ENV_DEADLINE, number);
298
299   /* Service name. */
300   SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_NAME, service->name);
301   SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_DISPLAYNAME, service->displayname);
302
303   /* Times the service was asked to start. */
304   _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_requested_count);
305   SetEnvironmentVariable(NSSM_HOOK_ENV_START_REQUESTED_COUNT, number);
306
307   /* Times the service actually did start. */
308   _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count);
309   SetEnvironmentVariable(NSSM_HOOK_ENV_START_COUNT, number);
310
311   /* Times the service exited. */
312   _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count);
313   SetEnvironmentVariable(NSSM_HOOK_ENV_EXIT_COUNT, number);
314
315   /* Throttled count. */
316   _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle);
317   SetEnvironmentVariable(NSSM_HOOK_ENV_THROTTLE_COUNT, number);
318
319   /* Command line. */
320   TCHAR app[CMD_LENGTH];
321   _sntprintf_s(app, _countof(app), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags);
322   SetEnvironmentVariable(NSSM_HOOK_ENV_COMMAND_LINE, app);
323
324   TCHAR cmd[CMD_LENGTH];
325   if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) {
326     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0);
327     unset_service_environment(service);
328     LeaveCriticalSection(&service->hook_section);
329     HeapFree(GetProcessHeap(), 0, hook);
330     return NSSM_HOOK_STATUS_ERROR;
331   }
332
333   /* No hook. */
334   if (! _tcslen(cmd)) {
335     unset_service_environment(service);
336     LeaveCriticalSection(&service->hook_section);
337     HeapFree(GetProcessHeap(), 0, hook);
338     return NSSM_HOOK_STATUS_NOTFOUND;
339   }
340
341   /* Run the command. */
342   STARTUPINFO si;
343   ZeroMemory(&si, sizeof(si));
344   si.cb = sizeof(si);
345   PROCESS_INFORMATION pi;
346   ZeroMemory(&pi, sizeof(pi));
347   unsigned long flags = 0;
348 #ifdef UNICODE
349   flags |= CREATE_UNICODE_ENVIRONMENT;
350 #endif
351   ret = NSSM_HOOK_STATUS_NOTRUN;
352   if (CreateProcess(0, cmd, 0, 0, false, flags, 0, service->dir, &si, &pi)) {
353     hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR));
354     if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action);
355     hook->process_handle = pi.hProcess;
356     hook->pid = pi.dwProcessId;
357     hook->deadline = deadline;
358     if (get_process_creation_time(hook->process_handle, &hook->creation_time)) GetSystemTimeAsFileTime(&hook->creation_time);
359
360     unsigned long tid;
361     HANDLE thread_handle = CreateThread(NULL, 0, await_hook, (void *) hook, 0, &tid);
362     if (thread_handle) {
363       if (async) {
364         ret = 0;
365         await_hook_threads(hook_threads, service->status_handle, &service->status, 0);
366         add_thread_handle(hook_threads, thread_handle, hook->name);
367       }
368       else {
369         await_single_handle(service->status_handle, &service->status, thread_handle, hook->name, _T(__FUNCTION__), deadline + NSSM_SERVICE_STATUS_DEADLINE);
370         unsigned long exitcode;
371         GetExitCodeThread(thread_handle, &exitcode);
372         ret = (int) exitcode;
373         CloseHandle(thread_handle);
374       }
375     }
376     else {
377       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
378       await_hook(hook);
379       if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
380       HeapFree(GetProcessHeap(), 0, hook);
381     }
382   }
383   else {
384     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0);
385     HeapFree(GetProcessHeap(), 0, hook);
386   }
387
388   /* Restore our environment. */
389   unset_service_environment(service);
390
391   LeaveCriticalSection(&service->hook_section);
392
393   return ret;
394 }
395
396 int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline) {
397   return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, deadline, true);
398 }
399
400 int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control) {
401   return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, NSSM_HOOK_DEADLINE);
402 }