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