Run hooks in response to certain events.
[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, bool must_exist) {\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_SET_VALUE) {\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     long error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key);\r
481     if (error != ERROR_SUCCESS) {\r
482       if (error == ERROR_FILE_NOT_FOUND && ! must_exist) return 0;\r
483       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
484       return 0;\r
485     }\r
486   }\r
487 \r
488   return key;\r
489 }\r
490 \r
491 HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) {\r
492   return open_registry(service_name, sub, sam, true);\r
493 }\r
494 \r
495 HKEY open_registry(const TCHAR *service_name, REGSAM sam) {\r
496   return open_registry(service_name, 0, sam, true);\r
497 }\r
498 \r
499 int get_io_parameters(nssm_service_t *service, HKEY key) {\r
500   /* stdin */\r
501   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
502     service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;\r
503     ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));\r
504     return 1;\r
505   }\r
506 \r
507   /* stdout */\r
508   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
509     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;\r
510     ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));\r
511     return 2;\r
512   }\r
513 \r
514   /* stderr */\r
515   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
516     service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;\r
517     ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));\r
518     return 3;\r
519   }\r
520 \r
521   return 0;\r
522 }\r
523 \r
524 int get_parameters(nssm_service_t *service, STARTUPINFO *si) {\r
525   unsigned long ret;\r
526 \r
527   /* Try to open the registry */\r
528   HKEY key = open_registry(service->name, KEY_READ);\r
529   if (! key) return 1;\r
530 \r
531   /* Don't expand parameters when retrieving for the GUI. */\r
532   bool expand = si ? true : false;\r
533 \r
534   /* Try to get executable file - MUST succeed */\r
535   if (get_string(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), expand, false, true)) {\r
536     RegCloseKey(key);\r
537     return 3;\r
538   }\r
539 \r
540   /* Try to get flags - may fail and we don't care */\r
541   if (get_string(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), expand, false, true)) {\r
542     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0);\r
543     ZeroMemory(service->flags, sizeof(service->flags));\r
544   }\r
545 \r
546   /* Try to get startup directory - may fail and we fall back to a default */\r
547   if (get_string(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), expand, true, true) || ! service->dir[0]) {\r
548     _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
549     strip_basename(service->dir);\r
550     if (service->dir[0] == _T('\0')) {\r
551       /* Help! */\r
552       ret = GetWindowsDirectory(service->dir, sizeof(service->dir));\r
553       if (! ret || ret > sizeof(service->dir)) {\r
554         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0);\r
555         RegCloseKey(key);\r
556         return 4;\r
557       }\r
558     }\r
559     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);\r
560   }\r
561 \r
562   /* Try to get processor affinity - may fail. */\r
563   TCHAR buffer[512];\r
564   if (get_string(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false, false) || ! buffer[0]) service->affinity = 0LL;\r
565   else if (affinity_string_to_mask(buffer, &service->affinity)) {\r
566     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer);\r
567     service->affinity = 0LL;\r
568   }\r
569   else {\r
570     DWORD_PTR affinity, system_affinity;\r
571 \r
572     if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
573       _int64 effective_affinity = service->affinity & system_affinity;\r
574       if (effective_affinity != service->affinity) {\r
575         TCHAR *system = 0;\r
576         if (! affinity_mask_to_string(system_affinity, &system)) {\r
577           TCHAR *effective = 0;\r
578           if (! affinity_mask_to_string(effective_affinity, &effective)) {\r
579             log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0);\r
580           }\r
581           HeapFree(GetProcessHeap(), 0, effective);\r
582         }\r
583         HeapFree(GetProcessHeap(), 0, system);\r
584       }\r
585     }\r
586   }\r
587 \r
588   /* Try to get environment variables - may fail */\r
589   get_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);\r
590   /* Environment variables to add to existing rather than replace - may fail. */\r
591   get_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);\r
592 \r
593   /* Try to get priority - may fail. */\r
594   unsigned long priority;\r
595   if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) {\r
596     if (priority == (priority & priority_mask())) service->priority = priority;\r
597     else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);\r
598   }\r
599 \r
600   /* Try to get file rotation settings - may fail. */\r
601   unsigned long rotate_files;\r
602   if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
603     if (rotate_files) service->rotate_files = true;\r
604     else service->rotate_files = false;\r
605   }\r
606   else service->rotate_files = false;\r
607   if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) {\r
608     if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true;\r
609     else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
610   }\r
611   else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
612   if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
613   if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
614   if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
615   override_milliseconds(service->name, key, NSSM_REG_ROTATE_DELAY, &service->rotate_delay, NSSM_ROTATE_DELAY, NSSM_EVENT_BOGUS_THROTTLE);\r
616 \r
617   /* Try to get force new console setting - may fail. */\r
618   if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;\r
619 \r
620   /* Change to startup directory in case stdout/stderr are relative paths. */\r
621   TCHAR cwd[PATH_LENGTH];\r
622   GetCurrentDirectory(_countof(cwd), cwd);\r
623   SetCurrentDirectory(service->dir);\r
624 \r
625   /* Try to get stdout and stderr */\r
626   if (get_io_parameters(service, key)) {\r
627     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
628     RegCloseKey(key);\r
629     SetCurrentDirectory(cwd);\r
630     return 5;\r
631   }\r
632 \r
633   /* Change back in case the startup directory needs to be deleted. */\r
634   SetCurrentDirectory(cwd);\r
635 \r
636   /* Try to get mandatory restart delay */\r
637   override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);\r
638 \r
639   /* Try to get throttle restart delay */\r
640   override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);\r
641 \r
642   /* Try to get service stop flags. */\r
643   unsigned long type = REG_DWORD;\r
644   unsigned long stop_method_skip;\r
645   unsigned long buflen = sizeof(stop_method_skip);\r
646   bool stop_ok = false;\r
647   ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);\r
648   if (ret != ERROR_SUCCESS) {\r
649     if (ret != ERROR_FILE_NOT_FOUND) {\r
650       if (type != REG_DWORD) {\r
651         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);\r
652       }\r
653       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);\r
654     }\r
655   }\r
656   else stop_ok = true;\r
657 \r
658   /* Try all methods except those requested to be skipped. */\r
659   service->stop_method = ~0;\r
660   if (stop_ok) service->stop_method &= ~stop_method_skip;\r
661 \r
662   /* Try to get kill delays - may fail. */\r
663   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
664   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
665   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
666 \r
667   /* Try to get process tree settings - may fail. */\r
668   unsigned long kill_process_tree;\r
669   if (get_number(key, NSSM_REG_KILL_PROCESS_TREE, &kill_process_tree, false) == 1) {\r
670     if (kill_process_tree) service->kill_process_tree = true;\r
671     else service->kill_process_tree = false;\r
672   }\r
673   else service->kill_process_tree = true;\r
674 \r
675   /* Try to get default exit action. */\r
676   bool default_action;\r
677   service->default_exit_action = NSSM_EXIT_RESTART;\r
678   TCHAR action_string[ACTION_LEN];\r
679   if (! get_exit_action(service->name, 0, action_string, &default_action)) {\r
680     for (int i = 0; exit_action_strings[i]; i++) {\r
681       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
682         service->default_exit_action = i;\r
683         break;\r
684       }\r
685     }\r
686   }\r
687 \r
688   /* Close registry */\r
689   RegCloseKey(key);\r
690 \r
691   return 0;\r
692 }\r
693 \r
694 /*\r
695   Sets the string for the exit action corresponding to the exit code.\r
696 \r
697   ret is a pointer to an unsigned long containing the exit code.\r
698   If ret is NULL, we retrieve the default exit action unconditionally.\r
699 \r
700   action is a buffer which receives the string.\r
701 \r
702   default_action is a pointer to a bool which is set to false if there\r
703   was an explicit string for the given exit code, or true if we are\r
704   returning the default action.\r
705 \r
706   Returns: 0 on success.\r
707            1 on error.\r
708 */\r
709 int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) {\r
710   /* Are we returning the default action or a status-specific one? */\r
711   *default_action = ! ret;\r
712 \r
713   /* Try to open the registry */\r
714   HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ);\r
715   if (! key) return 1;\r
716 \r
717   unsigned long type = REG_SZ;\r
718   unsigned long action_len = ACTION_LEN;\r
719 \r
720   TCHAR code[16];\r
721   if (! ret) code[0] = _T('\0');\r
722   else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) {\r
723     RegCloseKey(key);\r
724     return get_exit_action(service_name, 0, action, default_action);\r
725   }\r
726   if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) {\r
727     RegCloseKey(key);\r
728     /* Try again with * as the key if an exit code was defined */\r
729     if (ret) return get_exit_action(service_name, 0, action, default_action);\r
730     return 0;\r
731   }\r
732 \r
733   /* Close registry */\r
734   RegCloseKey(key);\r
735 \r
736   return 0;\r
737 }\r
738 \r
739 int set_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *cmd) {\r
740   /* Try to open the registry */\r
741   TCHAR registry[KEY_LENGTH];\r
742   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
743     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("set_hook()"), 0);\r
744     return 1;\r
745   }\r
746 \r
747   HKEY key;\r
748   long error;\r
749 \r
750   /* Don't create keys needlessly. */\r
751   if (! _tcslen(cmd)) {\r
752     key = open_registry(service_name, registry, KEY_READ, false);\r
753     if (! key) return 0;\r
754     error = RegQueryValueEx(key, hook_action, 0, 0, 0, 0);\r
755     RegCloseKey(key);\r
756     if (error == ERROR_FILE_NOT_FOUND) return 0;\r
757   }\r
758 \r
759   key = open_registry(service_name, registry, KEY_WRITE);\r
760   if (! key) return 1;\r
761 \r
762   int ret = 1;\r
763   if (_tcslen(cmd)) ret = set_string(key, (TCHAR *) hook_action, cmd, true);\r
764   else {\r
765     error = RegDeleteValue(key, hook_action);\r
766     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) ret = 0;\r
767   }\r
768 \r
769   /* Close registry */\r
770   RegCloseKey(key);\r
771 \r
772   return ret;\r
773 }\r
774 \r
775 int get_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *buffer, unsigned long buflen) {\r
776   /* Try to open the registry */\r
777   TCHAR registry[KEY_LENGTH];\r
778   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
779     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("get_hook()"), 0);\r
780     return 1;\r
781   }\r
782   HKEY key = open_registry(service_name, registry, KEY_READ, false);\r
783   if (! key) return 1;\r
784 \r
785   int ret = expand_parameter(key, (TCHAR *) hook_action, buffer, buflen, true, false);\r
786 \r
787   /* Close registry */\r
788   RegCloseKey(key);\r
789 \r
790   return ret;\r
791 }\r