Allow skipping kill_process_tree().
[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[PATH_LENGTH];\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->kill_process_tree) set_number(key, NSSM_REG_KILL_PROCESS_TREE, 0);\r
83   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_PROCESS_TREE);\r
84   if (service->stdin_path[0] || editing) {\r
85     if (service->stdin_path[0]) set_expand_string(key, NSSM_REG_STDIN, service->stdin_path);\r
86     else if (editing) RegDeleteValue(key, NSSM_REG_STDIN);\r
87     if (service->stdin_sharing != NSSM_STDIN_SHARING) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING, service->stdin_sharing);\r
88     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING);\r
89     if (service->stdin_disposition != NSSM_STDIN_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION, service->stdin_disposition);\r
90     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION);\r
91     if (service->stdin_flags != NSSM_STDIN_FLAGS) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS, service->stdin_flags);\r
92     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS);\r
93   }\r
94   if (service->stdout_path[0] || editing) {\r
95     if (service->stdout_path[0]) set_expand_string(key, NSSM_REG_STDOUT, service->stdout_path);\r
96     else if (editing) RegDeleteValue(key, NSSM_REG_STDOUT);\r
97     if (service->stdout_sharing != NSSM_STDOUT_SHARING) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING, service->stdout_sharing);\r
98     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING);\r
99     if (service->stdout_disposition != NSSM_STDOUT_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION, service->stdout_disposition);\r
100     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION);\r
101     if (service->stdout_flags != NSSM_STDOUT_FLAGS) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS, service->stdout_flags);\r
102     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS);\r
103   }\r
104   if (service->stderr_path[0] || editing) {\r
105     if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);\r
106     else if (editing) RegDeleteValue(key, NSSM_REG_STDERR);\r
107     if (service->stderr_sharing != NSSM_STDERR_SHARING) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING, service->stderr_sharing);\r
108     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING);\r
109     if (service->stderr_disposition != NSSM_STDERR_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION, service->stderr_disposition);\r
110     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION);\r
111     if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);\r
112     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS);\r
113   }\r
114   if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
115   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);\r
116   if (service->rotate_stdout_online) set_number(key, NSSM_REG_ROTATE_ONLINE, 1);\r
117   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_ONLINE);\r
118   if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);\r
119   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_SECONDS);\r
120   if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);\r
121   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);\r
122   if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);\r
123   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);\r
124   if (service->no_console) set_number(key, NSSM_REG_NO_CONSOLE, 1);\r
125   else if (editing) RegDeleteValue(key, NSSM_REG_NO_CONSOLE);\r
126 \r
127   /* Environment */\r
128   if (service->env) {\r
129     if (RegSetValueEx(key, NSSM_REG_ENV, 0, REG_MULTI_SZ, (const unsigned char *) service->env, (unsigned long) service->envlen * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
130       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);\r
131     }\r
132   }\r
133   else if (editing) RegDeleteValue(key, NSSM_REG_ENV);\r
134   if (service->env_extra) {\r
135     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
136       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV_EXTRA, error_string(GetLastError()), 0);\r
137     }\r
138   }\r
139   else if (editing) RegDeleteValue(key, NSSM_REG_ENV_EXTRA);\r
140 \r
141   /* Close registry. */\r
142   RegCloseKey(key);\r
143 \r
144   return 0;\r
145 }\r
146 \r
147 int create_exit_action(TCHAR *service_name, const TCHAR *action_string, bool editing) {\r
148   /* Get registry */\r
149   TCHAR registry[KEY_LENGTH];\r
150   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, NSSM_REG_EXIT) < 0) {\r
151     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REG_EXIT"), _T("create_exit_action()"), 0);\r
152     return 1;\r
153   }\r
154 \r
155   /* Try to open the registry */\r
156   HKEY key;\r
157   unsigned long disposition;\r
158   if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &disposition) != ERROR_SUCCESS) {\r
159     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
160     return 2;\r
161   }\r
162 \r
163   /* Do nothing if the key already existed */\r
164   if (disposition == REG_OPENED_EXISTING_KEY && ! editing) {\r
165     RegCloseKey(key);\r
166     return 0;\r
167   }\r
168 \r
169   /* Create the default value */\r
170   if (RegSetValueEx(key, 0, 0, REG_SZ, (const unsigned char *) action_string, (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
171     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_EXIT, error_string(GetLastError()), 0);\r
172     RegCloseKey(key);\r
173     return 3;\r
174   }\r
175 \r
176   /* Close registry */\r
177   RegCloseKey(key);\r
178 \r
179   return 0;\r
180 }\r
181 \r
182 int get_environment(TCHAR *service_name, HKEY key, TCHAR *value, TCHAR **env, unsigned long *envlen) {\r
183   unsigned long type = REG_MULTI_SZ;\r
184 \r
185   /* Dummy test to find buffer size */\r
186   unsigned long ret = RegQueryValueEx(key, value, 0, &type, NULL, envlen);\r
187   if (ret != ERROR_SUCCESS) {\r
188     *env = 0;\r
189     *envlen = 0;\r
190     /* The service probably doesn't have any environment configured */\r
191     if (ret == ERROR_FILE_NOT_FOUND) return 0;\r
192     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
193     return 1;\r
194   }\r
195 \r
196   if (type != REG_MULTI_SZ) {\r
197     *env = 0;\r
198     *envlen = 0;\r
199     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_INVALID_ENVIRONMENT_STRING_TYPE, value, service_name, 0);\r
200     return 2;\r
201   }\r
202 \r
203   /* Probably not possible */\r
204   if (! *envlen) return 0;\r
205 \r
206   /* Previously initialised? */\r
207   if (*env) HeapFree(GetProcessHeap(), 0, *env);\r
208 \r
209   *env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, *envlen);\r
210   if (! *env) {\r
211     *envlen = 0;\r
212     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_environment()"), 0);\r
213     return 3;\r
214   }\r
215 \r
216   /* Actually get the strings */\r
217   ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) *env, envlen);\r
218   if (ret != ERROR_SUCCESS) {\r
219     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
220     HeapFree(GetProcessHeap(), 0, *env);\r
221     *env = 0;\r
222     *envlen = 0;\r
223     return 4;\r
224   }\r
225 \r
226   return 0;\r
227 }\r
228 \r
229 \r
230 int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool expand, bool sanitise, bool must_exist) {\r
231   TCHAR *buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, datalen);\r
232   if (! buffer) {\r
233     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_string()"), 0);\r
234     return 1;\r
235   }\r
236 \r
237   ZeroMemory(data, datalen);\r
238 \r
239   unsigned long type = REG_EXPAND_SZ;\r
240   unsigned long buflen = datalen;\r
241 \r
242   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
243   if (ret != ERROR_SUCCESS) {\r
244     unsigned long error = GetLastError();\r
245     HeapFree(GetProcessHeap(), 0, buffer);\r
246 \r
247     if (ret == ERROR_FILE_NOT_FOUND) {\r
248       if (! must_exist) return 0;\r
249     }\r
250 \r
251     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(error), 0);\r
252     return 2;\r
253   }\r
254 \r
255   /* Paths aren't allowed to contain quotes. */\r
256   if (sanitise) PathUnquoteSpaces(buffer);\r
257 \r
258   /* Do we want to expand the string? */\r
259   if (! expand) {\r
260     if (type == REG_EXPAND_SZ) type = REG_SZ;\r
261   }\r
262 \r
263   /* Technically we shouldn't expand environment strings from REG_SZ values */\r
264   if (type != REG_EXPAND_SZ) {\r
265     memmove(data, buffer, buflen);\r
266     HeapFree(GetProcessHeap(), 0, buffer);\r
267     return 0;\r
268   }\r
269 \r
270   ret = ExpandEnvironmentStrings((TCHAR *) buffer, data, datalen);\r
271   if (! ret || ret > datalen) {\r
272     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, buffer, error_string(GetLastError()), 0);\r
273     HeapFree(GetProcessHeap(), 0, buffer);\r
274     return 3;\r
275   }\r
276 \r
277   HeapFree(GetProcessHeap(), 0, buffer);\r
278   return 0;\r
279 }\r
280 \r
281 int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {\r
282   return get_string(key, value, data, datalen, false, sanitise, true);\r
283 }\r
284 \r
285 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise, bool must_exist) {\r
286   return get_string(key, value, data, datalen, true, sanitise, must_exist);\r
287 }\r
288 \r
289 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {\r
290   return expand_parameter(key, value, data, datalen, sanitise, true);\r
291 }\r
292 \r
293 /*\r
294   Sets a string in the registry.\r
295   Returns: 0 if it was set.\r
296            1 on error.\r
297 */\r
298 int set_string(HKEY key, TCHAR *value, TCHAR *string, bool expand) {\r
299   unsigned long type = expand ? REG_EXPAND_SZ : REG_SZ;\r
300   if (RegSetValueEx(key, value, 0, type, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS) return 0;\r
301   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);\r
302   return 1;\r
303 }\r
304 \r
305 int set_string(HKEY key, TCHAR *value, TCHAR *string) {\r
306   return set_string(key, value, string, false);\r
307   return 1;\r
308 }\r
309 \r
310 int set_expand_string(HKEY key, TCHAR *value, TCHAR *string) {\r
311   return set_string(key, value, string, true);\r
312   return 1;\r
313 }\r
314 \r
315 /*\r
316   Set an unsigned long in the registry.\r
317   Returns: 0 if it was set.\r
318            1 on error.\r
319 */\r
320 int set_number(HKEY key, TCHAR *value, unsigned long number) {\r
321   if (RegSetValueEx(key, value, 0, REG_DWORD, (const unsigned char *) &number, sizeof(number)) == ERROR_SUCCESS) return 0;\r
322   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);\r
323   return 1;\r
324 }\r
325 \r
326 /*\r
327   Query an unsigned long from the registry.\r
328   Returns:  1 if a number was retrieved.\r
329             0 if none was found and must_exist is false.\r
330            -1 if none was found and must_exist is true.\r
331            -2 otherwise.\r
332 */\r
333 int get_number(HKEY key, TCHAR *value, unsigned long *number, bool must_exist) {\r
334   unsigned long type = REG_DWORD;\r
335   unsigned long number_len = sizeof(unsigned long);\r
336 \r
337   int ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) number, &number_len);\r
338   if (ret == ERROR_SUCCESS) return 1;\r
339 \r
340   if (ret == ERROR_FILE_NOT_FOUND) {\r
341     if (! must_exist) return 0;\r
342   }\r
343 \r
344   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
345   if (ret == ERROR_FILE_NOT_FOUND) return -1;\r
346 \r
347   return -2;\r
348 }\r
349 \r
350 int get_number(HKEY key, TCHAR *value, unsigned long *number) {\r
351   return get_number(key, value, number, true);\r
352 }\r
353 \r
354 /* Replace NULL with CRLF. Leave NULL NULL as the end marker. */\r
355 int format_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **formatted, unsigned long *newlen) {\r
356   unsigned long i, j;\r
357   *newlen = dnlen;\r
358 \r
359   if (! *newlen) {\r
360     *formatted = 0;\r
361     return 0;\r
362   }\r
363 \r
364   for (i = 0; i < dnlen; i++) if (! dn[i] && dn[i + 1]) ++*newlen;\r
365 \r
366   *formatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));\r
367   if (! *formatted) {\r
368     *newlen = 0;\r
369     return 1;\r
370   }\r
371 \r
372   for (i = 0, j = 0; i < dnlen; i++) {\r
373     (*formatted)[j] = dn[i];\r
374     if (! dn[i]) {\r
375       if (dn[i + 1]) {\r
376         (*formatted)[j] = _T('\r');\r
377         (*formatted)[++j] = _T('\n');\r
378       }\r
379     }\r
380     j++;\r
381   }\r
382 \r
383   return 0;\r
384 }\r
385 \r
386 /* Strip CR and replace LF with NULL. */\r
387 int unformat_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **unformatted, unsigned long *newlen) {\r
388   unsigned long i, j;\r
389   *newlen = 0;\r
390 \r
391   if (! dnlen) {\r
392     *unformatted = 0;\r
393     return 0;\r
394   }\r
395 \r
396   for (i = 0; i < dnlen; i++) if (dn[i] != _T('\r')) ++*newlen;\r
397 \r
398   /* Skip blank lines. */\r
399   for (i = 0; i < dnlen; i++) {\r
400     if (dn[i] == _T('\r') && dn[i + 1] == _T('\n')) {\r
401       /* This is the last CRLF. */\r
402       if (i >= dnlen - 2) break;\r
403 \r
404       /*\r
405         Strip at the start of the block or if the next characters are\r
406         CRLF too.\r
407       */\r
408       if (! i || (dn[i + 2] == _T('\r') && dn[i + 3] == _T('\n'))) {\r
409         for (j = i + 2; j < dnlen; j++) dn[j - 2] = dn[j];\r
410         dn[dnlen--] = _T('\0');\r
411         dn[dnlen--] = _T('\0');\r
412         i--;\r
413         --*newlen;\r
414       }\r
415     }\r
416   }\r
417 \r
418   /* Must end with two NULLs. */\r
419   *newlen += 2;\r
420 \r
421   *unformatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));\r
422   if (! *unformatted) return 1;\r
423 \r
424   for (i = 0, j = 0; i < dnlen; i++) {\r
425     if (dn[i] == _T('\r')) continue;\r
426     if (dn[i] == _T('\n')) (*unformatted)[j] = _T('\0');\r
427     else (*unformatted)[j] = dn[i];\r
428     j++;\r
429   }\r
430 \r
431   return 0;\r
432 }\r
433 \r
434 void override_milliseconds(TCHAR *service_name, HKEY key, TCHAR *value, unsigned long *buffer, unsigned long default_value, unsigned long event) {\r
435   unsigned long type = REG_DWORD;\r
436   unsigned long buflen = sizeof(unsigned long);\r
437   bool ok = false;\r
438   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
439   if (ret != ERROR_SUCCESS) {\r
440     if (ret != ERROR_FILE_NOT_FOUND) {\r
441       if (type != REG_DWORD) {\r
442         TCHAR milliseconds[16];\r
443         _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), default_value);\r
444         log_event(EVENTLOG_WARNING_TYPE, event, service_name, value, milliseconds, 0);\r
445       }\r
446       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
447     }\r
448   }\r
449   else ok = true;\r
450 \r
451   if (! ok) *buffer = default_value;\r
452 }\r
453 \r
454 HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) {\r
455   /* Get registry */\r
456   TCHAR registry[KEY_LENGTH];\r
457   HKEY key;\r
458   int ret;\r
459 \r
460   if (sub) ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, sub);\r
461   else ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY, service_name);\r
462   if (ret < 0) {\r
463     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REGISTRY"), _T("open_registry()"), 0);\r
464     return 0;\r
465   }\r
466 \r
467   if (sam & KEY_WRITE) {\r
468     if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) {\r
469       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
470       return 0;\r
471     }\r
472   }\r
473   else {\r
474     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key) != ERROR_SUCCESS) {\r
475       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
476       return 0;\r
477     }\r
478   }\r
479 \r
480   return key;\r
481 }\r
482 \r
483 HKEY open_registry(const TCHAR *service_name, REGSAM sam) {\r
484   return open_registry(service_name, 0, sam);\r
485 }\r
486 \r
487 int get_io_parameters(nssm_service_t *service, HKEY key) {\r
488   /* stdin */\r
489   if (get_createfile_parameters(key, NSSM_REG_STDIN, service->stdin_path, &service->stdin_sharing, NSSM_STDIN_SHARING, &service->stdin_disposition, NSSM_STDIN_DISPOSITION, &service->stdin_flags, NSSM_STDIN_FLAGS)) {\r
490     service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;\r
491     ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));\r
492     return 1;\r
493   }\r
494 \r
495   /* stdout */\r
496   if (get_createfile_parameters(key, NSSM_REG_STDOUT, service->stdout_path, &service->stdout_sharing, NSSM_STDOUT_SHARING, &service->stdout_disposition, NSSM_STDOUT_DISPOSITION, &service->stdout_flags, NSSM_STDOUT_FLAGS)) {\r
497     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;\r
498     ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));\r
499     return 2;\r
500   }\r
501 \r
502   /* stderr */\r
503   if (get_createfile_parameters(key, NSSM_REG_STDERR, service->stderr_path, &service->stderr_sharing, NSSM_STDERR_SHARING, &service->stderr_disposition, NSSM_STDERR_DISPOSITION, &service->stderr_flags, NSSM_STDERR_FLAGS)) {\r
504     service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;\r
505     ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));\r
506     return 3;\r
507   }\r
508 \r
509   return 0;\r
510 }\r
511 \r
512 int get_parameters(nssm_service_t *service, STARTUPINFO *si) {\r
513   unsigned long ret;\r
514 \r
515   /* Try to open the registry */\r
516   HKEY key = open_registry(service->name, KEY_READ);\r
517   if (! key) return 1;\r
518 \r
519   /* Don't expand parameters when retrieving for the GUI. */\r
520   bool expand = si ? true : false;\r
521 \r
522   /* Try to get executable file - MUST succeed */\r
523   if (get_string(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), expand, false, true)) {\r
524     RegCloseKey(key);\r
525     return 3;\r
526   }\r
527 \r
528   /* Try to get flags - may fail and we don't care */\r
529   if (get_string(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), expand, false, true)) {\r
530     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0);\r
531     ZeroMemory(service->flags, sizeof(service->flags));\r
532   }\r
533 \r
534   /* Try to get startup directory - may fail and we fall back to a default */\r
535   if (get_string(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), expand, true, true) || ! service->dir[0]) {\r
536     _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
537     strip_basename(service->dir);\r
538     if (service->dir[0] == _T('\0')) {\r
539       /* Help! */\r
540       ret = GetWindowsDirectory(service->dir, sizeof(service->dir));\r
541       if (! ret || ret > sizeof(service->dir)) {\r
542         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0);\r
543         RegCloseKey(key);\r
544         return 4;\r
545       }\r
546     }\r
547     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);\r
548   }\r
549 \r
550   /* Try to get processor affinity - may fail. */\r
551   TCHAR buffer[512];\r
552   if (get_string(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false, false) || ! buffer[0]) service->affinity = 0LL;\r
553   else if (affinity_string_to_mask(buffer, &service->affinity)) {\r
554     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer);\r
555     service->affinity = 0LL;\r
556   }\r
557   else {\r
558     DWORD_PTR affinity, system_affinity;\r
559 \r
560     if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
561       _int64 effective_affinity = service->affinity & system_affinity;\r
562       if (effective_affinity != service->affinity) {\r
563         TCHAR *system = 0;\r
564         if (! affinity_mask_to_string(system_affinity, &system)) {\r
565           TCHAR *effective = 0;\r
566           if (! affinity_mask_to_string(effective_affinity, &effective)) {\r
567             log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0);\r
568           }\r
569           HeapFree(GetProcessHeap(), 0, effective);\r
570         }\r
571         HeapFree(GetProcessHeap(), 0, system);\r
572       }\r
573     }\r
574   }\r
575 \r
576   /* Try to get environment variables - may fail */\r
577   get_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);\r
578   /* Environment variables to add to existing rather than replace - may fail. */\r
579   get_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);\r
580 \r
581   /* Try to get priority - may fail. */\r
582   unsigned long priority;\r
583   if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) {\r
584     if (priority == (priority & priority_mask())) service->priority = priority;\r
585     else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);\r
586   }\r
587 \r
588   /* Try to get file rotation settings - may fail. */\r
589   unsigned long rotate_files;\r
590   if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
591     if (rotate_files) service->rotate_files = true;\r
592     else service->rotate_files = false;\r
593   }\r
594   else service->rotate_files = false;\r
595   if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) {\r
596     if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true;\r
597     else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
598   }\r
599   else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
600   if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
601   if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
602   if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
603 \r
604   /* Try to get force new console setting - may fail. */\r
605   if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;\r
606 \r
607   /* Change to startup directory in case stdout/stderr are relative paths. */\r
608   TCHAR cwd[PATH_LENGTH];\r
609   GetCurrentDirectory(_countof(cwd), cwd);\r
610   SetCurrentDirectory(service->dir);\r
611 \r
612   /* Try to get stdout and stderr */\r
613   if (get_io_parameters(service, key)) {\r
614     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
615     RegCloseKey(key);\r
616     SetCurrentDirectory(cwd);\r
617     return 5;\r
618   }\r
619 \r
620   /* Change back in case the startup directory needs to be deleted. */\r
621   SetCurrentDirectory(cwd);\r
622 \r
623   /* Try to get mandatory restart delay */\r
624   override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);\r
625 \r
626   /* Try to get throttle restart delay */\r
627   override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);\r
628 \r
629   /* Try to get service stop flags. */\r
630   unsigned long type = REG_DWORD;\r
631   unsigned long stop_method_skip;\r
632   unsigned long buflen = sizeof(stop_method_skip);\r
633   bool stop_ok = false;\r
634   ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);\r
635   if (ret != ERROR_SUCCESS) {\r
636     if (ret != ERROR_FILE_NOT_FOUND) {\r
637       if (type != REG_DWORD) {\r
638         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);\r
639       }\r
640       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);\r
641     }\r
642   }\r
643   else stop_ok = true;\r
644 \r
645   /* Try all methods except those requested to be skipped. */\r
646   service->stop_method = ~0;\r
647   if (stop_ok) service->stop_method &= ~stop_method_skip;\r
648 \r
649   /* Try to get kill delays - may fail. */\r
650   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
651   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
652   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
653 \r
654   /* Try to get process tree settings - may fail. */\r
655   unsigned long kill_process_tree;\r
656   if (get_number(key, NSSM_REG_KILL_PROCESS_TREE, &kill_process_tree, false) == 1) {\r
657     if (kill_process_tree) service->kill_process_tree = true;\r
658     else service->kill_process_tree = false;\r
659   }\r
660   else service->kill_process_tree = true;\r
661 \r
662   /* Try to get default exit action. */\r
663   bool default_action;\r
664   service->default_exit_action = NSSM_EXIT_RESTART;\r
665   TCHAR action_string[ACTION_LEN];\r
666   if (! get_exit_action(service->name, 0, action_string, &default_action)) {\r
667     for (int i = 0; exit_action_strings[i]; i++) {\r
668       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
669         service->default_exit_action = i;\r
670         break;\r
671       }\r
672     }\r
673   }\r
674 \r
675   /* Close registry */\r
676   RegCloseKey(key);\r
677 \r
678   return 0;\r
679 }\r
680 \r
681 /*\r
682   Sets the string for the exit action corresponding to the exit code.\r
683 \r
684   ret is a pointer to an unsigned long containing the exit code.\r
685   If ret is NULL, we retrieve the default exit action unconditionally.\r
686 \r
687   action is a buffer which receives the string.\r
688 \r
689   default_action is a pointer to a bool which is set to false if there\r
690   was an explicit string for the given exit code, or true if we are\r
691   returning the default action.\r
692 \r
693   Returns: 0 on success.\r
694            1 on error.\r
695 */\r
696 int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) {\r
697   /* Are we returning the default action or a status-specific one? */\r
698   *default_action = ! ret;\r
699 \r
700   /* Try to open the registry */\r
701   HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ);\r
702   if (! key) return 1;\r
703 \r
704   unsigned long type = REG_SZ;\r
705   unsigned long action_len = ACTION_LEN;\r
706 \r
707   TCHAR code[16];\r
708   if (! ret) code[0] = _T('\0');\r
709   else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) {\r
710     RegCloseKey(key);\r
711     return get_exit_action(service_name, 0, action, default_action);\r
712   }\r
713   if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) {\r
714     RegCloseKey(key);\r
715     /* Try again with * as the key if an exit code was defined */\r
716     if (ret) return get_exit_action(service_name, 0, action, default_action);\r
717     return 0;\r
718   }\r
719 \r
720   /* Close registry */\r
721   RegCloseKey(key);\r
722 \r
723   return 0;\r
724 }\r