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