6c1567ce152bb5806e329456f0ea542703849ced
[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     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
218     HeapFree(GetProcessHeap(), 0, *env);\r
219     *env = 0;\r
220     *envlen = 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 \r
396   /* Skip blank lines. */\r
397   for (i = 0; i < dnlen; i++) {\r
398     if (dn[i] == _T('\r') && dn[i + 1] == _T('\n')) {\r
399       /* This is the last CRLF. */\r
400       if (i >= dnlen - 2) break;\r
401 \r
402       /*\r
403         Strip at the start of the block or if the next characters are\r
404         CRLF too.\r
405       */\r
406       if (! i || (dn[i + 2] == _T('\r') && dn[i + 3] == _T('\n'))) {\r
407         for (j = i + 2; j < dnlen; j++) dn[j - 2] = dn[j];\r
408         dn[dnlen--] = _T('\0');\r
409         dn[dnlen--] = _T('\0');\r
410         i--;\r
411         --*newlen;\r
412       }\r
413     }\r
414   }\r
415 \r
416   /* Must end with two NULLs. */\r
417   *newlen += 2;\r
418 \r
419   *unformatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));\r
420   if (! *unformatted) return 1;\r
421 \r
422   for (i = 0, j = 0; i < dnlen; i++) {\r
423     if (dn[i] == _T('\r')) continue;\r
424     if (dn[i] == _T('\n')) (*unformatted)[j] = _T('\0');\r
425     else (*unformatted)[j] = dn[i];\r
426     j++;\r
427   }\r
428 \r
429   return 0;\r
430 }\r
431 \r
432 void override_milliseconds(TCHAR *service_name, HKEY key, TCHAR *value, unsigned long *buffer, unsigned long default_value, unsigned long event) {\r
433   unsigned long type = REG_DWORD;\r
434   unsigned long buflen = sizeof(unsigned long);\r
435   bool ok = false;\r
436   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
437   if (ret != ERROR_SUCCESS) {\r
438     if (ret != ERROR_FILE_NOT_FOUND) {\r
439       if (type != REG_DWORD) {\r
440         TCHAR milliseconds[16];\r
441         _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), default_value);\r
442         log_event(EVENTLOG_WARNING_TYPE, event, service_name, value, milliseconds, 0);\r
443       }\r
444       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
445     }\r
446   }\r
447   else ok = true;\r
448 \r
449   if (! ok) *buffer = default_value;\r
450 }\r
451 \r
452 HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) {\r
453   /* Get registry */\r
454   TCHAR registry[KEY_LENGTH];\r
455   HKEY key;\r
456   int ret;\r
457 \r
458   if (sub) ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, sub);\r
459   else ret = _sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY, service_name);\r
460   if (ret < 0) {\r
461     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REGISTRY"), _T("open_registry()"), 0);\r
462     return 0;\r
463   }\r
464 \r
465   if (sam & KEY_WRITE) {\r
466     if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, &key, 0) != ERROR_SUCCESS) {\r
467       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
468       return 0;\r
469     }\r
470   }\r
471   else {\r
472     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, &key) != ERROR_SUCCESS) {\r
473       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
474       return 0;\r
475     }\r
476   }\r
477 \r
478   return key;\r
479 }\r
480 \r
481 HKEY open_registry(const TCHAR *service_name, REGSAM sam) {\r
482   return open_registry(service_name, 0, sam);\r
483 }\r
484 \r
485 int get_io_parameters(nssm_service_t *service, HKEY key) {\r
486   /* stdin */\r
487   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
488     service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;\r
489     ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));\r
490     return 1;\r
491   }\r
492 \r
493   /* stdout */\r
494   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
495     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;\r
496     ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));\r
497     return 2;\r
498   }\r
499 \r
500   /* stderr */\r
501   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
502     service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;\r
503     ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));\r
504     return 3;\r
505   }\r
506 \r
507   return 0;\r
508 }\r
509 \r
510 int get_parameters(nssm_service_t *service, STARTUPINFO *si) {\r
511   unsigned long ret;\r
512 \r
513   /* Try to open the registry */\r
514   HKEY key = open_registry(service->name, KEY_READ);\r
515   if (! key) return 1;\r
516 \r
517   /* Don't expand parameters when retrieving for the GUI. */\r
518   bool expand = si ? true : false;\r
519 \r
520   /* Try to get executable file - MUST succeed */\r
521   if (get_string(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), expand, false, true)) {\r
522     RegCloseKey(key);\r
523     return 3;\r
524   }\r
525 \r
526   /* Try to get flags - may fail and we don't care */\r
527   if (get_string(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), expand, false, true)) {\r
528     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0);\r
529     ZeroMemory(service->flags, sizeof(service->flags));\r
530   }\r
531 \r
532   /* Try to get startup directory - may fail and we fall back to a default */\r
533   if (get_string(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), expand, true, true) || ! service->dir[0]) {\r
534     _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
535     strip_basename(service->dir);\r
536     if (service->dir[0] == _T('\0')) {\r
537       /* Help! */\r
538       ret = GetWindowsDirectory(service->dir, sizeof(service->dir));\r
539       if (! ret || ret > sizeof(service->dir)) {\r
540         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0);\r
541         RegCloseKey(key);\r
542         return 4;\r
543       }\r
544     }\r
545     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);\r
546   }\r
547 \r
548   /* Try to get processor affinity - may fail. */\r
549   TCHAR buffer[512];\r
550   if (get_string(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false, false) || ! buffer[0]) service->affinity = 0LL;\r
551   else if (affinity_string_to_mask(buffer, &service->affinity)) {\r
552     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer);\r
553     service->affinity = 0LL;\r
554   }\r
555   else {\r
556     DWORD_PTR affinity, system_affinity;\r
557 \r
558     if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
559       _int64 effective_affinity = service->affinity & system_affinity;\r
560       if (effective_affinity != service->affinity) {\r
561         TCHAR *system = 0;\r
562         if (! affinity_mask_to_string(system_affinity, &system)) {\r
563           TCHAR *effective = 0;\r
564           if (! affinity_mask_to_string(effective_affinity, &effective)) {\r
565             log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0);\r
566           }\r
567           HeapFree(GetProcessHeap(), 0, effective);\r
568         }\r
569         HeapFree(GetProcessHeap(), 0, system);\r
570       }\r
571     }\r
572   }\r
573 \r
574   /* Try to get environment variables - may fail */\r
575   get_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);\r
576   /* Environment variables to add to existing rather than replace - may fail. */\r
577   get_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);\r
578 \r
579   /* Try to get priority - may fail. */\r
580   unsigned long priority;\r
581   if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) {\r
582     if (priority == (priority & priority_mask())) service->priority = priority;\r
583     else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);\r
584   }\r
585 \r
586   /* Try to get file rotation settings - may fail. */\r
587   unsigned long rotate_files;\r
588   if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
589     if (rotate_files) service->rotate_files = true;\r
590     else service->rotate_files = false;\r
591   }\r
592   else service->rotate_files = false;\r
593   if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) {\r
594     if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true;\r
595     else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
596   }\r
597   else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
598   if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
599   if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
600   if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
601 \r
602   /* Try to get force new console setting - may fail. */\r
603   if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;\r
604 \r
605   /* Change to startup directory in case stdout/stderr are relative paths. */\r
606   TCHAR cwd[PATH_LENGTH];\r
607   GetCurrentDirectory(_countof(cwd), cwd);\r
608   SetCurrentDirectory(service->dir);\r
609 \r
610   /* Try to get stdout and stderr */\r
611   if (get_io_parameters(service, key)) {\r
612     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
613     RegCloseKey(key);\r
614     SetCurrentDirectory(cwd);\r
615     return 5;\r
616   }\r
617 \r
618   /* Change back in case the startup directory needs to be deleted. */\r
619   SetCurrentDirectory(cwd);\r
620 \r
621   /* Try to get mandatory restart delay */\r
622   override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);\r
623 \r
624   /* Try to get throttle restart delay */\r
625   override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);\r
626 \r
627   /* Try to get service stop flags. */\r
628   unsigned long type = REG_DWORD;\r
629   unsigned long stop_method_skip;\r
630   unsigned long buflen = sizeof(stop_method_skip);\r
631   bool stop_ok = false;\r
632   ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);\r
633   if (ret != ERROR_SUCCESS) {\r
634     if (ret != ERROR_FILE_NOT_FOUND) {\r
635       if (type != REG_DWORD) {\r
636         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);\r
637       }\r
638       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);\r
639     }\r
640   }\r
641   else stop_ok = true;\r
642 \r
643   /* Try all methods except those requested to be skipped. */\r
644   service->stop_method = ~0;\r
645   if (stop_ok) service->stop_method &= ~stop_method_skip;\r
646 \r
647   /* Try to get kill delays - may fail. */\r
648   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
649   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
650   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
651 \r
652   /* Try to get default exit action. */\r
653   bool default_action;\r
654   service->default_exit_action = NSSM_EXIT_RESTART;\r
655   TCHAR action_string[ACTION_LEN];\r
656   if (! get_exit_action(service->name, 0, action_string, &default_action)) {\r
657     for (int i = 0; exit_action_strings[i]; i++) {\r
658       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
659         service->default_exit_action = i;\r
660         break;\r
661       }\r
662     }\r
663   }\r
664 \r
665   /* Close registry */\r
666   RegCloseKey(key);\r
667 \r
668   return 0;\r
669 }\r
670 \r
671 /*\r
672   Sets the string for the exit action corresponding to the exit code.\r
673 \r
674   ret is a pointer to an unsigned long containing the exit code.\r
675   If ret is NULL, we retrieve the default exit action unconditionally.\r
676 \r
677   action is a buffer which receives the string.\r
678 \r
679   default_action is a pointer to a bool which is set to false if there\r
680   was an explicit string for the given exit code, or true if we are\r
681   returning the default action.\r
682 \r
683   Returns: 0 on success.\r
684            1 on error.\r
685 */\r
686 int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) {\r
687   /* Are we returning the default action or a status-specific one? */\r
688   *default_action = ! ret;\r
689 \r
690   /* Try to open the registry */\r
691   HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ);\r
692   if (! key) return 1;\r
693 \r
694   unsigned long type = REG_SZ;\r
695   unsigned long action_len = ACTION_LEN;\r
696 \r
697   TCHAR code[16];\r
698   if (! ret) code[0] = _T('\0');\r
699   else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) {\r
700     RegCloseKey(key);\r
701     return get_exit_action(service_name, 0, action, default_action);\r
702   }\r
703   if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) {\r
704     RegCloseKey(key);\r
705     /* Try again with * as the key if an exit code was defined */\r
706     if (ret) return get_exit_action(service_name, 0, action, default_action);\r
707     return 0;\r
708   }\r
709 \r
710   /* Close registry */\r
711   RegCloseKey(key);\r
712 \r
713   return 0;\r
714 }\r