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