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