Added mandatory restart delay.
[nssm.git] / registry.cpp
1 #include "nssm.h"\r
2 \r
3 extern const TCHAR *exit_action_strings[];\r
4 \r
5 int create_messages() {\r
6   HKEY key;\r
7 \r
8   TCHAR registry[KEY_LENGTH];\r
9   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s"), NSSM) < 0) {\r
10     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("eventlog registry"), _T("create_messages()"), 0);\r
11     return 1;\r
12   }\r
13 \r
14   if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, 0) != ERROR_SUCCESS) {\r
15     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
16     return 2;\r
17   }\r
18 \r
19   /* Get path of this program */\r
20   TCHAR path[MAX_PATH];\r
21   GetModuleFileName(0, path, _countof(path));\r
22 \r
23   /* Try to register the module but don't worry so much on failure */\r
24   RegSetValueEx(key, _T("EventMessageFile"), 0, REG_SZ, (const unsigned char *) path, (unsigned long) (_tcslen(path) +  1) * sizeof(TCHAR));\r
25   unsigned long types = EVENTLOG_INFORMATION_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_ERROR_TYPE;\r
26   RegSetValueEx(key, _T("TypesSupported"), 0, REG_DWORD, (const unsigned char *) &types, sizeof(types));\r
27 \r
28   return 0;\r
29 }\r
30 \r
31 int create_parameters(nssm_service_t *service, bool editing) {\r
32   /* Try to open the registry */\r
33   HKEY key = open_registry(service->name, KEY_WRITE);\r
34   if (! key) return 1;\r
35 \r
36   /* Try to create the parameters */\r
37   if (set_expand_string(key, NSSM_REG_EXE, service->exe)) {\r
38     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);\r
39     RegCloseKey(key);\r
40     return 2;\r
41   }\r
42   if (set_expand_string(key, NSSM_REG_FLAGS, service->flags)) {\r
43     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);\r
44     RegCloseKey(key);\r
45     return 3;\r
46   }\r
47   if (set_expand_string(key, NSSM_REG_DIR, service->dir)) {\r
48     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);\r
49     RegCloseKey(key);\r
50     return 4;\r
51   }\r
52 \r
53   /* Other non-default parameters. May fail. */\r
54   if (service->priority != NORMAL_PRIORITY_CLASS) set_number(key, NSSM_REG_PRIORITY, service->priority);\r
55   else if (editing) RegDeleteValue(key, NSSM_REG_PRIORITY);\r
56   if (service->affinity) {\r
57     TCHAR *string;\r
58     if (! affinity_mask_to_string(service->affinity, &string)) {\r
59       if (RegSetValueEx(key, NSSM_REG_AFFINITY, 0, REG_SZ, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
60         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_AFFINITY, error_string(GetLastError()), 0);\r
61         HeapFree(GetProcessHeap(), 0, string);\r
62         return 5;\r
63       }\r
64     }\r
65     if (string) HeapFree(GetProcessHeap(), 0, string);\r
66   }\r
67   else if (editing) RegDeleteValue(key, NSSM_REG_AFFINITY);\r
68   unsigned long stop_method_skip = ~service->stop_method;\r
69   if (stop_method_skip) set_number(key, NSSM_REG_STOP_METHOD_SKIP, stop_method_skip);\r
70   else if (editing) RegDeleteValue(key, NSSM_REG_STOP_METHOD_SKIP);\r
71   if (service->default_exit_action < NSSM_NUM_EXIT_ACTIONS) create_exit_action(service->name, exit_action_strings[service->default_exit_action], editing);\r
72   if (service->restart_delay) set_number(key, NSSM_REG_RESTART_DELAY, service->restart_delay);\r
73   else if (editing) RegDeleteValue(key, NSSM_REG_RESTART_DELAY);\r
74   if (service->throttle_delay != NSSM_RESET_THROTTLE_RESTART) set_number(key, NSSM_REG_THROTTLE, service->throttle_delay);\r
75   else if (editing) RegDeleteValue(key, NSSM_REG_THROTTLE);\r
76   if (service->kill_console_delay != NSSM_KILL_CONSOLE_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, service->kill_console_delay);\r
77   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD);\r
78   if (service->kill_window_delay != NSSM_KILL_WINDOW_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, service->kill_window_delay);\r
79   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD);\r
80   if (service->kill_threads_delay != NSSM_KILL_THREADS_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, service->kill_threads_delay);\r
81   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD);\r
82   if (service->stdin_path[0] || editing) {\r
83     if (service->stdin_path[0]) set_expand_string(key, NSSM_REG_STDIN, service->stdin_path);\r
84     else if (editing) RegDeleteValue(key, NSSM_REG_STDIN);\r
85     if (service->stdin_sharing != NSSM_STDIN_SHARING) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING, service->stdin_sharing);\r
86     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING);\r
87     if (service->stdin_disposition != NSSM_STDIN_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION, service->stdin_disposition);\r
88     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION);\r
89     if (service->stdin_flags != NSSM_STDIN_FLAGS) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS, service->stdin_flags);\r
90     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS);\r
91   }\r
92   if (service->stdout_path[0] || editing) {\r
93     if (service->stdout_path[0]) set_expand_string(key, NSSM_REG_STDOUT, service->stdout_path);\r
94     else if (editing) RegDeleteValue(key, NSSM_REG_STDOUT);\r
95     if (service->stdout_sharing != NSSM_STDOUT_SHARING) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING, service->stdout_sharing);\r
96     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING);\r
97     if (service->stdout_disposition != NSSM_STDOUT_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION, service->stdout_disposition);\r
98     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION);\r
99     if (service->stdout_flags != NSSM_STDOUT_FLAGS) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS, service->stdout_flags);\r
100     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS);\r
101   }\r
102   if (service->stderr_path[0] || editing) {\r
103     if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);\r
104     else if (editing) RegDeleteValue(key, NSSM_REG_STDERR);\r
105     if (service->stderr_sharing != NSSM_STDERR_SHARING) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING, service->stderr_sharing);\r
106     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING);\r
107     if (service->stderr_disposition != NSSM_STDERR_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION, service->stderr_disposition);\r
108     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION);\r
109     if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);\r
110     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS);\r
111   }\r
112   if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
113   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);\r
114   if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);\r
115   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_SECONDS);\r
116   if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);\r
117   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);\r
118   if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);\r
119   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);\r
120 \r
121   /* Environment */\r
122   if (service->env) {\r
123     if (RegSetValueEx(key, NSSM_REG_ENV, 0, REG_MULTI_SZ, (const unsigned char *) service->env, (unsigned long) service->envlen * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
124       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);\r
125     }\r
126   }\r
127   else if (editing) RegDeleteValue(key, NSSM_REG_ENV);\r
128   if (service->env_extra) {\r
129     if (RegSetValueEx(key, NSSM_REG_ENV_EXTRA, 0, REG_MULTI_SZ, (const unsigned char *) service->env_extra, (unsigned long) service->env_extralen * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
130       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV_EXTRA, error_string(GetLastError()), 0);\r
131     }\r
132   }\r
133   else if (editing) RegDeleteValue(key, NSSM_REG_ENV_EXTRA);\r
134 \r
135   /* Close registry. */\r
136   RegCloseKey(key);\r
137 \r
138   return 0;\r
139 }\r
140 \r
141 int create_exit_action(TCHAR *service_name, const TCHAR *action_string, bool editing) {\r
142   /* Get registry */\r
143   TCHAR registry[KEY_LENGTH];\r
144   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, NSSM_REG_EXIT) < 0) {\r
145     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REG_EXIT"), _T("create_exit_action()"), 0);\r
146     return 1;\r
147   }\r
148 \r
149   /* Try to open the registry */\r
150   HKEY key;\r
151   unsigned long disposition;\r
152   if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &disposition) != ERROR_SUCCESS) {\r
153     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
154     return 2;\r
155   }\r
156 \r
157   /* Do nothing if the key already existed */\r
158   if (disposition == REG_OPENED_EXISTING_KEY && ! editing) {\r
159     RegCloseKey(key);\r
160     return 0;\r
161   }\r
162 \r
163   /* Create the default value */\r
164   if (RegSetValueEx(key, 0, 0, REG_SZ, (const unsigned char *) action_string, (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
165     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_EXIT, error_string(GetLastError()), 0);\r
166     RegCloseKey(key);\r
167     return 3;\r
168   }\r
169 \r
170   /* Close registry */\r
171   RegCloseKey(key);\r
172 \r
173   return 0;\r
174 }\r
175 \r
176 int set_environment(TCHAR *service_name, HKEY key, TCHAR *value, TCHAR **env, unsigned long *envlen) {\r
177   unsigned long type = REG_MULTI_SZ;\r
178 \r
179   /* Dummy test to find buffer size */\r
180   unsigned long ret = RegQueryValueEx(key, value, 0, &type, NULL, envlen);\r
181   if (ret != ERROR_SUCCESS) {\r
182     *env = 0;\r
183     *envlen = 0;\r
184     /* The service probably doesn't have any environment configured */\r
185     if (ret == ERROR_FILE_NOT_FOUND) return 0;\r
186     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
187     return 1;\r
188   }\r
189 \r
190   if (type != REG_MULTI_SZ) {\r
191     *env = 0;\r
192     *envlen = 0;\r
193     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_INVALID_ENVIRONMENT_STRING_TYPE, value, service_name, 0);\r
194     return 2;\r
195   }\r
196 \r
197   /* Probably not possible */\r
198   if (! *envlen) return 0;\r
199 \r
200   /* Previously initialised? */\r
201   if (*env) HeapFree(GetProcessHeap(), 0, *env);\r
202 \r
203   *env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, *envlen);\r
204   if (! *env) {\r
205     *envlen = 0;\r
206     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("set_environment()"), 0);\r
207     return 3;\r
208   }\r
209 \r
210   /* Actually get the strings */\r
211   ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) *env, envlen);\r
212   if (ret != ERROR_SUCCESS) {\r
213     HeapFree(GetProcessHeap(), 0, *env);\r
214     *env = 0;\r
215     *envlen = 0;\r
216     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
217     return 4;\r
218   }\r
219 \r
220   return 0;\r
221 }\r
222 \r
223 /* Replace NULL with CRLF. Leave NULL NULL as the end marker. */\r
224 int format_environment(TCHAR *env, unsigned long envlen, TCHAR **formatted, unsigned long *newlen) {\r
225   unsigned long i, j;\r
226   *newlen = envlen;\r
227 \r
228   if (! *newlen) {\r
229     *formatted = 0;\r
230     return 0;\r
231   }\r
232 \r
233   for (i = 0; i < envlen; i++) if (! env[i] && env[i + 1]) ++*newlen;\r
234 \r
235   *formatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));\r
236   if (! *formatted) {\r
237     *newlen = 0;\r
238     return 1;\r
239   }\r
240 \r
241   for (i = 0, j = 0; i < envlen; i++) {\r
242     (*formatted)[j] = env[i];\r
243     if (! env[i]) {\r
244       if (env[i + 1]) {\r
245         (*formatted)[j] = _T('\r');\r
246         (*formatted)[++j] = _T('\n');\r
247       }\r
248     }\r
249     j++;\r
250   }\r
251 \r
252   return 0;\r
253 }\r
254 \r
255 /* Strip CR and replace LF with NULL. */\r
256 int unformat_environment(TCHAR *env, unsigned long envlen, TCHAR **unformatted, unsigned long *newlen) {\r
257   unsigned long i, j;\r
258   *newlen = 0;\r
259 \r
260   if (! envlen) {\r
261     *unformatted = 0;\r
262     return 0;\r
263   }\r
264 \r
265   for (i = 0; i < envlen; i++) if (env[i] != _T('\r')) ++*newlen;\r
266   /* Must end with two NULLs. */\r
267   *newlen += 2;\r
268 \r
269   *unformatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));\r
270   if (! *unformatted) return 1;\r
271 \r
272   for (i = 0, j = 0; i < envlen; i++) {\r
273     if (env[i] == _T('\r')) continue;\r
274     if (env[i] == _T('\n')) (*unformatted)[j] = _T('\0');\r
275     else (*unformatted)[j] = env[i];\r
276     j++;\r
277   }\r
278 \r
279   return 0;\r
280 }\r
281 \r
282 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise, bool must_exist) {\r
283   TCHAR *buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, datalen);\r
284   if (! buffer) {\r
285     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("expand_parameter()"), 0);\r
286     return 1;\r
287   }\r
288 \r
289   ZeroMemory(data, datalen);\r
290 \r
291   unsigned long type = REG_EXPAND_SZ;\r
292   unsigned long buflen = datalen;\r
293 \r
294   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
295   if (ret != ERROR_SUCCESS) {\r
296     unsigned long error = GetLastError();\r
297     HeapFree(GetProcessHeap(), 0, buffer);\r
298 \r
299     if (ret == ERROR_FILE_NOT_FOUND) {\r
300       if (! must_exist) return 0;\r
301     }\r
302 \r
303     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(error), 0);\r
304     return 2;\r
305   }\r
306 \r
307   /* Paths aren't allowed to contain quotes. */\r
308   if (sanitise) PathUnquoteSpaces(buffer);\r
309 \r
310   /* Technically we shouldn't expand environment strings from REG_SZ values */\r
311   if (type != REG_EXPAND_SZ) {\r
312     memmove(data, buffer, buflen);\r
313     HeapFree(GetProcessHeap(), 0, buffer);\r
314     return 0;\r
315   }\r
316 \r
317   ret = ExpandEnvironmentStrings((TCHAR *) buffer, data, datalen);\r
318   if (! ret || ret > datalen) {\r
319     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, buffer, error_string(GetLastError()), 0);\r
320     HeapFree(GetProcessHeap(), 0, buffer);\r
321     return 3;\r
322   }\r
323 \r
324   HeapFree(GetProcessHeap(), 0, buffer);\r
325   return 0;\r
326 }\r
327 \r
328 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {\r
329   return expand_parameter(key, value, data, datalen, sanitise, true);\r
330 }\r
331 \r
332 /*\r
333   Sets a string in the registry.\r
334   Returns: 0 if it was set.\r
335            1 on error.\r
336 */\r
337 int set_expand_string(HKEY key, TCHAR *value, TCHAR *string) {\r
338   if (RegSetValueEx(key, value, 0, REG_EXPAND_SZ, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS) return 0;\r
339   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);\r
340   return 1;\r
341 }\r
342 \r
343 /*\r
344   Set an unsigned long in the registry.\r
345   Returns: 0 if it was set.\r
346            1 on error.\r
347 */\r
348 int set_number(HKEY key, TCHAR *value, unsigned long number) {\r
349   if (RegSetValueEx(key, value, 0, REG_DWORD, (const unsigned char *) &number, sizeof(number)) == ERROR_SUCCESS) return 0;\r
350   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);\r
351   return 1;\r
352 }\r
353 \r
354 /*\r
355   Query an unsigned long from the registry.\r
356   Returns:  1 if a number was retrieved.\r
357             0 if none was found and must_exist is false.\r
358            -1 if none was found and must_exist is true.\r
359            -2 otherwise.\r
360 */\r
361 int get_number(HKEY key, TCHAR *value, unsigned long *number, bool must_exist) {\r
362   unsigned long type = REG_DWORD;\r
363   unsigned long number_len = sizeof(unsigned long);\r
364 \r
365   int ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) number, &number_len);\r
366   if (ret == ERROR_SUCCESS) return 1;\r
367 \r
368   if (ret == ERROR_FILE_NOT_FOUND) {\r
369     if (! must_exist) return 0;\r
370   }\r
371 \r
372   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
373   if (ret == ERROR_FILE_NOT_FOUND) return -1;\r
374 \r
375   return -2;\r
376 }\r
377 \r
378 int get_number(HKEY key, TCHAR *value, unsigned long *number) {\r
379   return get_number(key, value, number, true);\r
380 }\r
381 \r
382 void override_milliseconds(TCHAR *service_name, HKEY key, TCHAR *value, unsigned long *buffer, unsigned long default_value, unsigned long event) {\r
383   unsigned long type = REG_DWORD;\r
384   unsigned long buflen = sizeof(unsigned long);\r
385   bool ok = false;\r
386   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
387   if (ret != ERROR_SUCCESS) {\r
388     if (ret != ERROR_FILE_NOT_FOUND) {\r
389       if (type != REG_DWORD) {\r
390         TCHAR milliseconds[16];\r
391         _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), default_value);\r
392         log_event(EVENTLOG_WARNING_TYPE, event, service_name, value, milliseconds, 0);\r
393       }\r
394       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
395     }\r
396   }\r
397   else ok = true;\r
398 \r
399   if (! ok) *buffer = default_value;\r
400 }\r
401 \r
402 HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) {\r
403   /* Get registry */\r
404   TCHAR registry[KEY_LENGTH];\r
405   HKEY key;\r
406   int ret;\r
407 \r
408   if (sub) ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, sub);\r
409   else ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY, service_name);\r
410   if (ret < 0) {\r
411     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REGISTRY"), _T("open_registry()"), 0);\r
412     return 0;\r
413   }\r
414 \r
415   if (sam & KEY_WRITE) {\r
416     if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) {\r
417       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
418       return 0;\r
419     }\r
420   }\r
421   else {\r
422     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key) != ERROR_SUCCESS) {\r
423       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
424       return 0;\r
425     }\r
426   }\r
427 \r
428   return key;\r
429 }\r
430 \r
431 HKEY open_registry(const TCHAR *service_name, REGSAM sam) {\r
432   return open_registry(service_name, 0, sam);\r
433 }\r
434 \r
435 int get_parameters(nssm_service_t *service, STARTUPINFO *si) {\r
436   unsigned long ret;\r
437 \r
438   /* Try to open the registry */\r
439   HKEY key = open_registry(service->name, KEY_READ);\r
440   if (! key) return 1;\r
441 \r
442   /* Try to get executable file - MUST succeed */\r
443   if (expand_parameter(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), false)) {\r
444     RegCloseKey(key);\r
445     return 3;\r
446   }\r
447 \r
448   /* Try to get flags - may fail and we don't care */\r
449   if (expand_parameter(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), false)) {\r
450     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0);\r
451     ZeroMemory(service->flags, sizeof(service->flags));\r
452   }\r
453 \r
454   /* Try to get startup directory - may fail and we fall back to a default */\r
455   if (expand_parameter(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), true) || ! service->dir[0]) {\r
456     _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
457     strip_basename(service->dir);\r
458     if (service->dir[0] == _T('\0')) {\r
459       /* Help! */\r
460       ret = GetWindowsDirectory(service->dir, sizeof(service->dir));\r
461       if (! ret || ret > sizeof(service->dir)) {\r
462         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0);\r
463         RegCloseKey(key);\r
464         return 4;\r
465       }\r
466     }\r
467     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);\r
468   }\r
469 \r
470   /* Try to get processor affinity - may fail. */\r
471   TCHAR buffer[512];\r
472   if (expand_parameter(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false) || ! buffer[0]) service->affinity = 0LL;\r
473   else if (affinity_string_to_mask(buffer, &service->affinity)) {\r
474     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer);\r
475     service->affinity = 0LL;\r
476   }\r
477   else {\r
478     DWORD_PTR affinity, system_affinity;\r
479 \r
480     if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
481       _int64 effective_affinity = service->affinity & system_affinity;\r
482       if (effective_affinity != service->affinity) {\r
483         TCHAR *system = 0;\r
484         if (! affinity_mask_to_string(system_affinity, &system)) {\r
485           TCHAR *effective = 0;\r
486           if (! affinity_mask_to_string(effective_affinity, &effective)) {\r
487             log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0);\r
488           }\r
489           HeapFree(GetProcessHeap(), 0, effective);\r
490         }\r
491         HeapFree(GetProcessHeap(), 0, system);\r
492       }\r
493     }\r
494   }\r
495 \r
496   /* Try to get environment variables - may fail */\r
497   set_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);\r
498   /* Environment variables to add to existing rather than replace - may fail. */\r
499   set_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);\r
500 \r
501   if (si) {\r
502     if (service->env_extra) {\r
503       /* Append these to any other environment variables set. */\r
504       if (service->env) {\r
505         /* Append extra variables to configured variables. */\r
506         unsigned long envlen = service->envlen + service->env_extralen - 1;\r
507         TCHAR *env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, envlen);\r
508         if (env) {\r
509           memmove(env, service->env, service->envlen - sizeof(TCHAR));\r
510           /* envlen is in bytes. */\r
511           memmove(env + (service->envlen / sizeof(TCHAR)) - 1, service->env_extra, service->env_extralen);\r
512 \r
513           HeapFree(GetProcessHeap(), 0, service->env);\r
514           service->env = env;\r
515           service->envlen = envlen;\r
516         }\r
517         else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("get_parameters()"), 0);\r
518       }\r
519       else {\r
520         /* Append extra variables to our environment. */\r
521         TCHAR *env, *s;\r
522         size_t envlen, len;\r
523 \r
524         env = service->env_extra;\r
525         len = 0;\r
526         while (*env) {\r
527           envlen = _tcslen(env) + 1;\r
528           for (s = env; *s && *s != _T('='); s++);\r
529           if (*s == _T('=')) *s++ = _T('\0');\r
530           if (! SetEnvironmentVariable(env, s)) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETENVIRONMENTVARIABLE_FAILED, env, s, error_string(GetLastError()), 0);\r
531           env += envlen;\r
532         }\r
533       }\r
534     }\r
535   }\r
536 \r
537   /* Try to get priority - may fail. */\r
538   unsigned long priority;\r
539   if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) {\r
540     if (priority == (priority & priority_mask())) service->priority = priority;\r
541     else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);\r
542   }\r
543 \r
544   /* Try to get file rotation settings - may fail. */\r
545   unsigned long rotate_files;\r
546   if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
547     if (rotate_files) service->rotate_files = true;\r
548     else service->rotate_files = false;\r
549   }\r
550   else service->rotate_files = false;\r
551   if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
552   if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
553   if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
554 \r
555   /* Change to startup directory in case stdout/stderr are relative paths. */\r
556   TCHAR cwd[MAX_PATH];\r
557   GetCurrentDirectory(_countof(cwd), cwd);\r
558   SetCurrentDirectory(service->dir);\r
559 \r
560   /* Try to get stdout and stderr */\r
561   if (get_output_handles(service, key, si)) {\r
562     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
563     RegCloseKey(key);\r
564     SetCurrentDirectory(cwd);\r
565     return 5;\r
566   }\r
567 \r
568   /* Change back in case the startup directory needs to be deleted. */\r
569   SetCurrentDirectory(cwd);\r
570 \r
571   /* Try to get mandatory restart delay */\r
572   override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);\r
573 \r
574   /* Try to get throttle restart delay */\r
575   override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);\r
576 \r
577   /* Try to get service stop flags. */\r
578   unsigned long type = REG_DWORD;\r
579   unsigned long stop_method_skip;\r
580   unsigned long buflen = sizeof(stop_method_skip);\r
581   bool stop_ok = false;\r
582   ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);\r
583   if (ret != ERROR_SUCCESS) {\r
584     if (ret != ERROR_FILE_NOT_FOUND) {\r
585       if (type != REG_DWORD) {\r
586         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);\r
587       }\r
588       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);\r
589     }\r
590   }\r
591   else stop_ok = true;\r
592 \r
593   /* Try all methods except those requested to be skipped. */\r
594   service->stop_method = ~0;\r
595   if (stop_ok) service->stop_method &= ~stop_method_skip;\r
596 \r
597   /* Try to get kill delays - may fail. */\r
598   override_milliseconds(service->name, key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, &service->kill_console_delay, NSSM_KILL_CONSOLE_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD);\r
599   override_milliseconds(service->name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, &service->kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD);\r
600   override_milliseconds(service->name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, &service->kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD);\r
601 \r
602   /* Try to get default exit action. */\r
603   bool default_action;\r
604   service->default_exit_action = NSSM_EXIT_RESTART;\r
605   TCHAR action_string[ACTION_LEN];\r
606   if (! get_exit_action(service->name, 0, action_string, &default_action)) {\r
607     for (int i = 0; exit_action_strings[i]; i++) {\r
608       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
609         service->default_exit_action = i;\r
610         break;\r
611       }\r
612     }\r
613   }\r
614 \r
615   /* Close registry */\r
616   RegCloseKey(key);\r
617 \r
618   return 0;\r
619 }\r
620 \r
621 /*\r
622   Sets the string for the exit action corresponding to the exit code.\r
623 \r
624   ret is a pointer to an unsigned long containing the exit code.\r
625   If ret is NULL, we retrieve the default exit action unconditionally.\r
626 \r
627   action is a buffer which receives the string.\r
628 \r
629   default_action is a pointer to a bool which is set to false if there\r
630   was an explicit string for the given exit code, or true if we are\r
631   returning the default action.\r
632 \r
633   Returns: 0 on success.\r
634            1 on error.\r
635 */\r
636 int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) {\r
637   /* Are we returning the default action or a status-specific one? */\r
638   *default_action = ! ret;\r
639 \r
640   /* Try to open the registry */\r
641   HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ);\r
642   if (! key) return 1;\r
643 \r
644   unsigned long type = REG_SZ;\r
645   unsigned long action_len = ACTION_LEN;\r
646 \r
647   TCHAR code[16];\r
648   if (! ret) code[0] = _T('\0');\r
649   else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) {\r
650     RegCloseKey(key);\r
651     return get_exit_action(service_name, 0, action, default_action);\r
652   }\r
653   if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) {\r
654     RegCloseKey(key);\r
655     /* Try again with * as the key if an exit code was defined */\r
656     if (ret) return get_exit_action(service_name, 0, action, default_action);\r
657     return 0;\r
658   }\r
659 \r
660   /* Close registry */\r
661   RegCloseKey(key);\r
662 \r
663   return 0;\r
664 }\r