Tidy up French GUI.
[nssm.git] / registry.cpp
1 #include "nssm.h"\r
2 \r
3 extern const TCHAR *exit_action_strings[];\r
4 \r
5 static int service_registry_path(const TCHAR *service_name, bool parameters, const TCHAR *sub, TCHAR *buffer, unsigned long buflen) {\r
6   int ret;\r
7 \r
8   if (parameters) {\r
9     if (sub) ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS _T("\\%s"), service_name, sub);\r
10     else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY _T("\\") NSSM_REG_PARAMETERS, service_name);\r
11   }\r
12   else ret = _sntprintf_s(buffer, buflen, _TRUNCATE, NSSM_REGISTRY, service_name);\r
13 \r
14   return ret;\r
15 }\r
16 \r
17 static long open_registry_key(const TCHAR *registry, REGSAM sam, HKEY *key, bool must_exist) {\r
18   long error;\r
19 \r
20   if (sam & KEY_SET_VALUE) {\r
21     error = RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, sam, 0, key, 0);\r
22     if (error != ERROR_SUCCESS) {\r
23       *key = 0;\r
24       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
25       return error;\r
26     }\r
27   }\r
28   else {\r
29     error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, sam, key);\r
30     if (error != ERROR_SUCCESS) {\r
31       *key = 0;\r
32       if (error != ERROR_FILE_NOT_FOUND || must_exist) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
33     }\r
34   }\r
35 \r
36   return error;\r
37 }\r
38 \r
39 static HKEY open_registry_key(const TCHAR *registry, REGSAM sam, bool must_exist) {\r
40   HKEY key;\r
41   long error = open_registry_key(registry, sam, &key, must_exist);\r
42   return key;\r
43 }\r
44 \r
45 int create_messages() {\r
46   HKEY key;\r
47 \r
48   TCHAR registry[KEY_LENGTH];\r
49   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s"), NSSM) < 0) {\r
50     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("eventlog registry"), _T("create_messages()"), 0);\r
51     return 1;\r
52   }\r
53 \r
54   if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, 0) != ERROR_SUCCESS) {\r
55     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
56     return 2;\r
57   }\r
58 \r
59   /* Get path of this program */\r
60   const TCHAR *path = nssm_unquoted_imagepath();\r
61 \r
62   /* Try to register the module but don't worry so much on failure */\r
63   RegSetValueEx(key, _T("EventMessageFile"), 0, REG_SZ, (const unsigned char *) path, (unsigned long) (_tcslen(path) +  1) * sizeof(TCHAR));\r
64   unsigned long types = EVENTLOG_INFORMATION_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_ERROR_TYPE;\r
65   RegSetValueEx(key, _T("TypesSupported"), 0, REG_DWORD, (const unsigned char *) &types, sizeof(types));\r
66 \r
67   return 0;\r
68 }\r
69 \r
70 long enumerate_registry_values(HKEY key, unsigned long *index, TCHAR *name, unsigned long namelen) {\r
71   unsigned long type;\r
72   unsigned long datalen = namelen;\r
73   long error = RegEnumValue(key, *index, name, &datalen, 0, &type, 0, 0);\r
74   if (error == ERROR_SUCCESS) ++*index;\r
75   return error;\r
76 }\r
77 \r
78 int create_parameters(nssm_service_t *service, bool editing) {\r
79   /* Try to open the registry */\r
80   HKEY key = open_registry(service->name, KEY_WRITE);\r
81   if (! key) return 1;\r
82 \r
83   /* Remember parameters in case we need to delete them. */\r
84   TCHAR registry[KEY_LENGTH];\r
85   int ret = service_registry_path(service->name, true, 0, registry, _countof(registry));\r
86 \r
87   /* Try to create the parameters */\r
88   if (set_expand_string(key, NSSM_REG_EXE, service->exe)) {\r
89     if (ret > 0) RegDeleteKey(HKEY_LOCAL_MACHINE, registry);\r
90     RegCloseKey(key);\r
91     return 2;\r
92   }\r
93   if (set_expand_string(key, NSSM_REG_FLAGS, service->flags)) {\r
94     if (ret > 0) RegDeleteKey(HKEY_LOCAL_MACHINE, registry);\r
95     RegCloseKey(key);\r
96     return 3;\r
97   }\r
98   if (set_expand_string(key, NSSM_REG_DIR, service->dir)) {\r
99     if (ret > 0) RegDeleteKey(HKEY_LOCAL_MACHINE, registry);\r
100     RegCloseKey(key);\r
101     return 4;\r
102   }\r
103 \r
104   /* Other non-default parameters. May fail. */\r
105   if (service->priority != NORMAL_PRIORITY_CLASS) set_number(key, NSSM_REG_PRIORITY, service->priority);\r
106   else if (editing) RegDeleteValue(key, NSSM_REG_PRIORITY);\r
107   if (service->affinity) {\r
108     TCHAR *string;\r
109     if (! affinity_mask_to_string(service->affinity, &string)) {\r
110       if (RegSetValueEx(key, NSSM_REG_AFFINITY, 0, REG_SZ, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
111         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_AFFINITY, error_string(GetLastError()), 0);\r
112         HeapFree(GetProcessHeap(), 0, string);\r
113         return 5;\r
114       }\r
115     }\r
116     if (string) HeapFree(GetProcessHeap(), 0, string);\r
117   }\r
118   else if (editing) RegDeleteValue(key, NSSM_REG_AFFINITY);\r
119   unsigned long stop_method_skip = ~service->stop_method;\r
120   if (stop_method_skip) set_number(key, NSSM_REG_STOP_METHOD_SKIP, stop_method_skip);\r
121   else if (editing) RegDeleteValue(key, NSSM_REG_STOP_METHOD_SKIP);\r
122   if (service->default_exit_action < NSSM_NUM_EXIT_ACTIONS) create_exit_action(service->name, exit_action_strings[service->default_exit_action], editing);\r
123   if (service->restart_delay) set_number(key, NSSM_REG_RESTART_DELAY, service->restart_delay);\r
124   else if (editing) RegDeleteValue(key, NSSM_REG_RESTART_DELAY);\r
125   if (service->throttle_delay != NSSM_RESET_THROTTLE_RESTART) set_number(key, NSSM_REG_THROTTLE, service->throttle_delay);\r
126   else if (editing) RegDeleteValue(key, NSSM_REG_THROTTLE);\r
127   if (service->kill_console_delay != NSSM_KILL_CONSOLE_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, service->kill_console_delay);\r
128   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD);\r
129   if (service->kill_window_delay != NSSM_KILL_WINDOW_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, service->kill_window_delay);\r
130   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD);\r
131   if (service->kill_threads_delay != NSSM_KILL_THREADS_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, service->kill_threads_delay);\r
132   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD);\r
133   if (! service->kill_process_tree) set_number(key, NSSM_REG_KILL_PROCESS_TREE, 0);\r
134   else if (editing) RegDeleteValue(key, NSSM_REG_KILL_PROCESS_TREE);\r
135   if (service->stdin_path[0] || editing) {\r
136     if (service->stdin_path[0]) set_expand_string(key, NSSM_REG_STDIN, service->stdin_path);\r
137     else if (editing) RegDeleteValue(key, NSSM_REG_STDIN);\r
138     if (service->stdin_sharing != NSSM_STDIN_SHARING) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING, service->stdin_sharing);\r
139     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING);\r
140     if (service->stdin_disposition != NSSM_STDIN_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION, service->stdin_disposition);\r
141     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION);\r
142     if (service->stdin_flags != NSSM_STDIN_FLAGS) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS, service->stdin_flags);\r
143     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS);\r
144   }\r
145   if (service->stdout_path[0] || editing) {\r
146     if (service->stdout_path[0]) set_expand_string(key, NSSM_REG_STDOUT, service->stdout_path);\r
147     else if (editing) RegDeleteValue(key, NSSM_REG_STDOUT);\r
148     if (service->stdout_sharing != NSSM_STDOUT_SHARING) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING, service->stdout_sharing);\r
149     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING);\r
150     if (service->stdout_disposition != NSSM_STDOUT_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION, service->stdout_disposition);\r
151     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION);\r
152     if (service->stdout_flags != NSSM_STDOUT_FLAGS) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS, service->stdout_flags);\r
153     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS);\r
154     if (service->stdout_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1);\r
155     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_COPY_AND_TRUNCATE);\r
156   }\r
157   if (service->stderr_path[0] || editing) {\r
158     if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);\r
159     else if (editing) RegDeleteValue(key, NSSM_REG_STDERR);\r
160     if (service->stderr_sharing != NSSM_STDERR_SHARING) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING, service->stderr_sharing);\r
161     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING);\r
162     if (service->stderr_disposition != NSSM_STDERR_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION, service->stderr_disposition);\r
163     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION);\r
164     if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);\r
165     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS);\r
166     if (service->stderr_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1);\r
167     else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE);\r
168   }\r
169   if (service->timestamp_log) set_number(key, NSSM_REG_TIMESTAMP_LOG, 1);\r
170   else if (editing) RegDeleteValue(key, NSSM_REG_TIMESTAMP_LOG);\r
171   if (service->hook_share_output_handles) set_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, 1);\r
172   else if (editing) RegDeleteValue(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES);\r
173   if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
174   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);\r
175   if (service->rotate_stdout_online) set_number(key, NSSM_REG_ROTATE_ONLINE, 1);\r
176   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_ONLINE);\r
177   if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);\r
178   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_SECONDS);\r
179   if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);\r
180   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);\r
181   if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);\r
182   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);\r
183   if (service->rotate_delay != NSSM_ROTATE_DELAY) set_number(key, NSSM_REG_ROTATE_DELAY, service->rotate_delay);\r
184   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_DELAY);\r
185   if (service->no_console) set_number(key, NSSM_REG_NO_CONSOLE, 1);\r
186   else if (editing) RegDeleteValue(key, NSSM_REG_NO_CONSOLE);\r
187 \r
188   /* Environment */\r
189   if (service->env) {\r
190     if (RegSetValueEx(key, NSSM_REG_ENV, 0, REG_MULTI_SZ, (const unsigned char *) service->env, (unsigned long) service->envlen * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
191       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);\r
192     }\r
193   }\r
194   else if (editing) RegDeleteValue(key, NSSM_REG_ENV);\r
195   if (service->env_extra) {\r
196     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
197       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV_EXTRA, error_string(GetLastError()), 0);\r
198     }\r
199   }\r
200   else if (editing) RegDeleteValue(key, NSSM_REG_ENV_EXTRA);\r
201 \r
202   /* Close registry. */\r
203   RegCloseKey(key);\r
204 \r
205   return 0;\r
206 }\r
207 \r
208 int create_exit_action(TCHAR *service_name, const TCHAR *action_string, bool editing) {\r
209   /* Get registry */\r
210   TCHAR registry[KEY_LENGTH];\r
211   if (service_registry_path(service_name, true, NSSM_REG_EXIT, registry, _countof(registry)) < 0) {\r
212     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REG_EXIT"), _T("create_exit_action()"), 0);\r
213     return 1;\r
214   }\r
215 \r
216   /* Try to open the registry */\r
217   HKEY key;\r
218   unsigned long disposition;\r
219   if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &disposition) != ERROR_SUCCESS) {\r
220     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
221     return 2;\r
222   }\r
223 \r
224   /* Do nothing if the key already existed */\r
225   if (disposition == REG_OPENED_EXISTING_KEY && ! editing) {\r
226     RegCloseKey(key);\r
227     return 0;\r
228   }\r
229 \r
230   /* Create the default value */\r
231   if (RegSetValueEx(key, 0, 0, REG_SZ, (const unsigned char *) action_string, (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
232     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_EXIT, error_string(GetLastError()), 0);\r
233     RegCloseKey(key);\r
234     return 3;\r
235   }\r
236 \r
237   /* Close registry */\r
238   RegCloseKey(key);\r
239 \r
240   return 0;\r
241 }\r
242 \r
243 int get_environment(TCHAR *service_name, HKEY key, TCHAR *value, TCHAR **env, unsigned long *envlen) {\r
244   unsigned long type = REG_MULTI_SZ;\r
245   unsigned long envsize;\r
246 \r
247   *envlen = 0;\r
248 \r
249   /* Dummy test to find buffer size */\r
250   unsigned long ret = RegQueryValueEx(key, value, 0, &type, NULL, &envsize);\r
251   if (ret != ERROR_SUCCESS) {\r
252     *env = 0;\r
253     /* The service probably doesn't have any environment configured */\r
254     if (ret == ERROR_FILE_NOT_FOUND) return 0;\r
255     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(ret), 0);\r
256     return 1;\r
257   }\r
258 \r
259   if (type != REG_MULTI_SZ) {\r
260     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_INVALID_ENVIRONMENT_STRING_TYPE, value, service_name, 0);\r
261     *env = 0;\r
262     return 2;\r
263   }\r
264 \r
265   /* Minimum usable environment would be A= NULL NULL. */\r
266   if (envsize < 4 * sizeof(TCHAR)) {\r
267     *env = 0;\r
268     return 3;\r
269   }\r
270 \r
271   /* Previously initialised? */\r
272   if (*env) HeapFree(GetProcessHeap(), 0, *env);\r
273 \r
274   *env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, envsize);\r
275   if (! *env) {\r
276     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_environment()"), 0);\r
277     return 4;\r
278   }\r
279 \r
280   /* Actually get the strings. */\r
281   ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) *env, &envsize);\r
282   if (ret != ERROR_SUCCESS) {\r
283     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(ret), 0);\r
284     HeapFree(GetProcessHeap(), 0, *env);\r
285     *env = 0;\r
286     return 5;\r
287   }\r
288 \r
289   /* Value retrieved by RegQueryValueEx() is SIZE not COUNT. */\r
290   *envlen = (unsigned long) environment_length(*env);\r
291 \r
292   return 0;\r
293 }\r
294 \r
295 \r
296 int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool expand, bool sanitise, bool must_exist) {\r
297   TCHAR *buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, datalen);\r
298   if (! buffer) {\r
299     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_string()"), 0);\r
300     return 1;\r
301   }\r
302 \r
303   ZeroMemory(data, datalen);\r
304 \r
305   unsigned long type = REG_EXPAND_SZ;\r
306   unsigned long buflen = datalen;\r
307 \r
308   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
309   if (ret != ERROR_SUCCESS) {\r
310     HeapFree(GetProcessHeap(), 0, buffer);\r
311 \r
312     if (ret == ERROR_FILE_NOT_FOUND) {\r
313       if (! must_exist) return 0;\r
314     }\r
315 \r
316     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(ret), 0);\r
317     return 2;\r
318   }\r
319 \r
320   /* Paths aren't allowed to contain quotes. */\r
321   if (sanitise) PathUnquoteSpaces(buffer);\r
322 \r
323   /* Do we want to expand the string? */\r
324   if (! expand) {\r
325     if (type == REG_EXPAND_SZ) type = REG_SZ;\r
326   }\r
327 \r
328   /* Technically we shouldn't expand environment strings from REG_SZ values */\r
329   if (type != REG_EXPAND_SZ) {\r
330     memmove(data, buffer, buflen);\r
331     HeapFree(GetProcessHeap(), 0, buffer);\r
332     return 0;\r
333   }\r
334 \r
335   ret = ExpandEnvironmentStrings((TCHAR *) buffer, data, datalen);\r
336   if (! ret || ret > datalen) {\r
337     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, buffer, error_string(GetLastError()), 0);\r
338     HeapFree(GetProcessHeap(), 0, buffer);\r
339     return 3;\r
340   }\r
341 \r
342   HeapFree(GetProcessHeap(), 0, buffer);\r
343   return 0;\r
344 }\r
345 \r
346 int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {\r
347   return get_string(key, value, data, datalen, false, sanitise, true);\r
348 }\r
349 \r
350 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise, bool must_exist) {\r
351   return get_string(key, value, data, datalen, true, sanitise, must_exist);\r
352 }\r
353 \r
354 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {\r
355   return expand_parameter(key, value, data, datalen, sanitise, true);\r
356 }\r
357 \r
358 /*\r
359   Sets a string in the registry.\r
360   Returns: 0 if it was set.\r
361            1 on error.\r
362 */\r
363 int set_string(HKEY key, TCHAR *value, TCHAR *string, bool expand) {\r
364   unsigned long type = expand ? REG_EXPAND_SZ : REG_SZ;\r
365   if (RegSetValueEx(key, value, 0, type, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS) return 0;\r
366   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);\r
367   return 1;\r
368 }\r
369 \r
370 int set_string(HKEY key, TCHAR *value, TCHAR *string) {\r
371   return set_string(key, value, string, false);\r
372 }\r
373 \r
374 int set_expand_string(HKEY key, TCHAR *value, TCHAR *string) {\r
375   return set_string(key, value, string, true);\r
376 }\r
377 \r
378 /*\r
379   Set an unsigned long in the registry.\r
380   Returns: 0 if it was set.\r
381            1 on error.\r
382 */\r
383 int set_number(HKEY key, TCHAR *value, unsigned long number) {\r
384   if (RegSetValueEx(key, value, 0, REG_DWORD, (const unsigned char *) &number, sizeof(number)) == ERROR_SUCCESS) return 0;\r
385   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);\r
386   return 1;\r
387 }\r
388 \r
389 /*\r
390   Query an unsigned long from the registry.\r
391   Returns:  1 if a number was retrieved.\r
392             0 if none was found and must_exist is false.\r
393            -1 if none was found and must_exist is true.\r
394            -2 otherwise.\r
395 */\r
396 int get_number(HKEY key, TCHAR *value, unsigned long *number, bool must_exist) {\r
397   unsigned long type = REG_DWORD;\r
398   unsigned long number_len = sizeof(unsigned long);\r
399 \r
400   int ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) number, &number_len);\r
401   if (ret == ERROR_SUCCESS) return 1;\r
402 \r
403   if (ret == ERROR_FILE_NOT_FOUND) {\r
404     if (! must_exist) return 0;\r
405   }\r
406 \r
407   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(ret), 0);\r
408   if (ret == ERROR_FILE_NOT_FOUND) return -1;\r
409 \r
410   return -2;\r
411 }\r
412 \r
413 int get_number(HKEY key, TCHAR *value, unsigned long *number) {\r
414   return get_number(key, value, number, true);\r
415 }\r
416 \r
417 /* Replace NULL with CRLF. Leave NULL NULL as the end marker. */\r
418 int format_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **formatted, unsigned long *newlen) {\r
419   unsigned long i, j;\r
420   *newlen = dnlen;\r
421 \r
422   if (! *newlen) {\r
423     *formatted = 0;\r
424     return 0;\r
425   }\r
426 \r
427   for (i = 0; i < dnlen; i++) if (! dn[i] && dn[i + 1]) ++*newlen;\r
428 \r
429   *formatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));\r
430   if (! *formatted) {\r
431     *newlen = 0;\r
432     return 1;\r
433   }\r
434 \r
435   for (i = 0, j = 0; i < dnlen; i++) {\r
436     (*formatted)[j] = dn[i];\r
437     if (! dn[i]) {\r
438       if (dn[i + 1]) {\r
439         (*formatted)[j] = _T('\r');\r
440         (*formatted)[++j] = _T('\n');\r
441       }\r
442     }\r
443     j++;\r
444   }\r
445 \r
446   return 0;\r
447 }\r
448 \r
449 /* Strip CR and replace LF with NULL.  */\r
450 int unformat_double_null(TCHAR *formatted, unsigned long formattedlen, TCHAR **dn, unsigned long *newlen) {\r
451   unsigned long i, j;\r
452   *newlen = 0;\r
453 \r
454   /* Don't count trailing NULLs. */\r
455   for (i = 0; i < formattedlen; i++) {\r
456     if (! formatted[i]) {\r
457       formattedlen = i;\r
458       break;\r
459     }\r
460   }\r
461 \r
462   if (! formattedlen) {\r
463     *dn = 0;\r
464     return 0;\r
465   }\r
466 \r
467   for (i = 0; i < formattedlen; i++) if (formatted[i] != _T('\r')) ++*newlen;\r
468 \r
469   /* Skip blank lines. */\r
470   for (i = 0; i < formattedlen; i++) {\r
471     if (formatted[i] == _T('\r') && formatted[i + 1] == _T('\n')) {\r
472       /* This is the last CRLF. */\r
473       if (i >= formattedlen - 2) break;\r
474 \r
475       /*\r
476         Strip at the start of the block or if the next characters are\r
477         CRLF too.\r
478       */\r
479       if (! i || (formatted[i + 2] == _T('\r') && formatted[i + 3] == _T('\n'))) {\r
480         for (j = i + 2; j < formattedlen; j++) formatted[j - 2] = formatted[j];\r
481         formatted[formattedlen--] = _T('\0');\r
482         formatted[formattedlen--] = _T('\0');\r
483         i--;\r
484         --*newlen;\r
485       }\r
486     }\r
487   }\r
488 \r
489   /* Must end with two NULLs. */\r
490   *newlen += 2;\r
491 \r
492   *dn = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *newlen * sizeof(TCHAR));\r
493   if (! *dn) return 1;\r
494 \r
495   for (i = 0, j = 0; i < formattedlen; i++) {\r
496     if (formatted[i] == _T('\r')) continue;\r
497     if (formatted[i] == _T('\n')) (*dn)[j] = _T('\0');\r
498     else (*dn)[j] = formatted[i];\r
499     j++;\r
500   }\r
501 \r
502   return 0;\r
503 }\r
504 \r
505 /* Copy a block. */\r
506 int copy_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **newdn) {\r
507   if (! newdn) return 1;\r
508 \r
509   *newdn = 0;\r
510   if (! dn) return 0;\r
511 \r
512   *newdn = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, dnlen * sizeof(TCHAR));\r
513   if (! *newdn) {\r
514     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("dn"), _T("copy_double_null()"), 0);\r
515     return 2;\r
516   }\r
517 \r
518   memmove(*newdn, dn, dnlen * sizeof(TCHAR));\r
519   return 0;\r
520 }\r
521 \r
522 /*\r
523   Create a new block with all the strings of the first block plus a new string.\r
524   The new string may be specified as <key> <delimiter> <value> and the keylen\r
525   gives the offset into the string to compare against existing entries.\r
526   If the key is already present its value will be overwritten in place.\r
527   If the key is blank or empty the new block will still be allocated and have\r
528   non-zero length.\r
529 */\r
530 int append_to_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **newdn, unsigned long *newlen, TCHAR *append, size_t keylen, bool case_sensitive) {\r
531   if (! append || ! append[0]) return copy_double_null(dn, dnlen, newdn);\r
532   size_t appendlen = _tcslen(append);\r
533   int (*fn)(const TCHAR *, const TCHAR *, size_t) = (case_sensitive) ? _tcsncmp : _tcsnicmp;\r
534 \r
535   /* Identify the key, if any, or treat the whole string as the key. */\r
536   TCHAR *key = 0;\r
537   if (! keylen || keylen > appendlen) keylen = appendlen;\r
538   key = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (keylen + 1) * sizeof(TCHAR));\r
539   if (! key) {\r
540     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("key"), _T("append_to_double_null()"), 0);\r
541     return 1;\r
542   }\r
543   memmove(key, append, keylen * sizeof(TCHAR));\r
544   key[keylen] = _T('\0');\r
545 \r
546   /* Find the length of the block not including any existing key. */\r
547   size_t len = 0;\r
548   TCHAR *s;\r
549   for (s = dn; *s; s++) {\r
550     if (fn(s, key, keylen)) len += _tcslen(s) + 1;\r
551     for ( ; *s; s++);\r
552   }\r
553 \r
554   /* Account for new entry. */\r
555   len += _tcslen(append) + 1;\r
556 \r
557   /* Account for trailing NULL. */\r
558   len++;\r
559 \r
560   /* Allocate a new block. */\r
561   *newdn = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(TCHAR));\r
562   if (! *newdn) {\r
563     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("newdn"), _T("append_to_double_null()"), 0);\r
564     HeapFree(GetProcessHeap(), 0, key);\r
565     return 2;\r
566   }\r
567 \r
568   /* Copy existing entries.*/\r
569   *newlen = (unsigned long) len;\r
570   TCHAR *t = *newdn;\r
571   TCHAR *u;\r
572   bool replaced = false;\r
573   for (s = dn; *s; s++) {\r
574     if (fn(s, key, keylen)) u = s;\r
575     else {\r
576       u = append;\r
577       replaced = true;\r
578     }\r
579     len = _tcslen(u) + 1;\r
580     memmove(t, u, len * sizeof(TCHAR));\r
581     t += len;\r
582     for ( ; *s; s++);\r
583   }\r
584 \r
585   /* Add the entry if it wasn't already replaced.  The buffer was zeroed. */\r
586   if (! replaced) memmove(t, append, _tcslen(append) * sizeof(TCHAR));\r
587 \r
588   HeapFree(GetProcessHeap(), 0, key);\r
589   return 0;\r
590 }\r
591 \r
592 /*\r
593   Create a new block with all the string of the first block minus the given\r
594   string.\r
595   The keylen parameter gives the offset into the string to compare against\r
596   existing entries.  If a substring of existing value matches the string to\r
597   the given length it will be removed.\r
598   If the last entry is removed the new block will still be allocated and\r
599   have non-zero length.\r
600 */\r
601 int remove_from_double_null(TCHAR *dn, unsigned long dnlen, TCHAR **newdn, unsigned long *newlen, TCHAR *remove, size_t keylen, bool case_sensitive) {\r
602   if (! remove || !remove[0]) return copy_double_null(dn, dnlen, newdn);\r
603   size_t removelen = _tcslen(remove);\r
604   int (*fn)(const TCHAR *, const TCHAR *, size_t) = (case_sensitive) ? _tcsncmp : _tcsnicmp;\r
605 \r
606   /* Identify the key, if any, or treat the whole string as the key. */\r
607   TCHAR *key = 0;\r
608   if (! keylen || keylen > removelen) keylen = removelen;\r
609   key = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (keylen + 1) * sizeof(TCHAR));\r
610   if (! key) {\r
611     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("key"), _T("remove_from_double_null()"), 0);\r
612     return 1;\r
613   }\r
614   memmove(key, remove, keylen * sizeof(TCHAR));\r
615   key[keylen] = _T('\0');\r
616 \r
617   /* Find the length of the block not including any existing key. */\r
618   size_t len = 0;\r
619   TCHAR *s;\r
620   for (s = dn; *s; s++) {\r
621     if (fn(s, key, keylen)) len += _tcslen(s) + 1;\r
622     for ( ; *s; s++);\r
623   }\r
624 \r
625   /* Account for trailing NULL. */\r
626   if (++len < 2) len = 2;\r
627 \r
628   /* Allocate a new block. */\r
629   *newdn = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(TCHAR));\r
630   if (! *newdn) {\r
631     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("newdn"), _T("remove_from_double_null()"), 0);\r
632     HeapFree(GetProcessHeap(), 0, key);\r
633     return 2;\r
634   }\r
635 \r
636   /* Copy existing entries.*/\r
637   *newlen = (unsigned long) len;\r
638   TCHAR *t = *newdn;\r
639   for (s = dn; *s; s++) {\r
640     if (fn(s, key, keylen)) {\r
641       len = _tcslen(s) + 1;\r
642       memmove(t, s, len * sizeof(TCHAR));\r
643       t += len;\r
644     }\r
645     for ( ; *s; s++);\r
646   }\r
647 \r
648   HeapFree(GetProcessHeap(), 0, key);\r
649   return 0;\r
650 }\r
651 \r
652 void override_milliseconds(TCHAR *service_name, HKEY key, TCHAR *value, unsigned long *buffer, unsigned long default_value, unsigned long event) {\r
653   unsigned long type = REG_DWORD;\r
654   unsigned long buflen = sizeof(unsigned long);\r
655   bool ok = false;\r
656   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
657   if (ret != ERROR_SUCCESS) {\r
658     if (ret != ERROR_FILE_NOT_FOUND) {\r
659       if (type != REG_DWORD) {\r
660         TCHAR milliseconds[16];\r
661         _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), default_value);\r
662         log_event(EVENTLOG_WARNING_TYPE, event, service_name, value, milliseconds, 0);\r
663       }\r
664       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(ret), 0);\r
665     }\r
666   }\r
667   else ok = true;\r
668 \r
669   if (! ok) *buffer = default_value;\r
670 }\r
671 \r
672 /* Open the key of the service itself Services\<service_name>. */\r
673 HKEY open_service_registry(const TCHAR *service_name, REGSAM sam, bool must_exist) {\r
674   /* Get registry */\r
675   TCHAR registry[KEY_LENGTH];\r
676   if (service_registry_path(service_name, false, 0, registry, _countof(registry)) < 0) {\r
677     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REGISTRY, _T("open_service_registry()"), 0);\r
678     return 0;\r
679   }\r
680 \r
681   return open_registry_key(registry, sam, must_exist);\r
682 }\r
683 \r
684 /* Open a subkey of the service Services\<service_name>\<sub>. */\r
685 long open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, HKEY *key, bool must_exist) {\r
686   /* Get registry */\r
687   TCHAR registry[KEY_LENGTH];\r
688   if (service_registry_path(service_name, true, sub, registry, _countof(registry)) < 0) {\r
689     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REGISTRY, _T("open_registry()"), 0);\r
690     return 0;\r
691   }\r
692 \r
693   return open_registry_key(registry, sam, key, must_exist);\r
694 }\r
695 \r
696 HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam, bool must_exist) {\r
697   HKEY key;\r
698   long error = open_registry(service_name, sub, sam, &key, must_exist);\r
699   return key;\r
700 }\r
701 \r
702 HKEY open_registry(const TCHAR *service_name, const TCHAR *sub, REGSAM sam) {\r
703   return open_registry(service_name, sub, sam, true);\r
704 }\r
705 \r
706 HKEY open_registry(const TCHAR *service_name, REGSAM sam) {\r
707   return open_registry(service_name, 0, sam, true);\r
708 }\r
709 \r
710 int get_io_parameters(nssm_service_t *service, HKEY key) {\r
711   /* stdin */\r
712   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
713     service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;\r
714     ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));\r
715     return 1;\r
716   }\r
717 \r
718   /* stdout */\r
719   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
720     service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;\r
721     ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));\r
722     return 2;\r
723   }\r
724 \r
725   /* stderr */\r
726   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
727     service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;\r
728     ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));\r
729     return 3;\r
730   }\r
731 \r
732   return 0;\r
733 }\r
734 \r
735 int get_parameters(nssm_service_t *service, STARTUPINFO *si) {\r
736   unsigned long ret;\r
737 \r
738   /* Try to open the registry */\r
739   HKEY key = open_registry(service->name, KEY_READ);\r
740   if (! key) return 1;\r
741 \r
742   /* Don't expand parameters when retrieving for the GUI. */\r
743   bool expand = si ? true : false;\r
744 \r
745   /* Try to get environment variables - may fail */\r
746   get_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);\r
747   /* Environment variables to add to existing rather than replace - may fail. */\r
748   get_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);\r
749 \r
750   /* Set environment if we are starting the service. */\r
751   if (si) set_service_environment(service);\r
752 \r
753   /* Try to get executable file - MUST succeed */\r
754   if (get_string(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), expand, false, true)) {\r
755     RegCloseKey(key);\r
756     return 3;\r
757   }\r
758 \r
759   /* Try to get flags - may fail and we don't care */\r
760   if (get_string(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), expand, false, true)) {\r
761     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0);\r
762     ZeroMemory(service->flags, sizeof(service->flags));\r
763   }\r
764 \r
765   /* Try to get startup directory - may fail and we fall back to a default */\r
766   if (get_string(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), expand, true, true) || ! service->dir[0]) {\r
767     _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
768     strip_basename(service->dir);\r
769     if (service->dir[0] == _T('\0')) {\r
770       /* Help! */\r
771       ret = GetWindowsDirectory(service->dir, sizeof(service->dir));\r
772       if (! ret || ret > sizeof(service->dir)) {\r
773         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0);\r
774         RegCloseKey(key);\r
775         return 4;\r
776       }\r
777     }\r
778     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);\r
779   }\r
780 \r
781   /* Try to get processor affinity - may fail. */\r
782   TCHAR buffer[512];\r
783   if (get_string(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false, false) || ! buffer[0]) service->affinity = 0LL;\r
784   else if (affinity_string_to_mask(buffer, &service->affinity)) {\r
785     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer);\r
786     service->affinity = 0LL;\r
787   }\r
788   else {\r
789     DWORD_PTR affinity, system_affinity;\r
790 \r
791     if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
792       _int64 effective_affinity = service->affinity & system_affinity;\r
793       if (effective_affinity != service->affinity) {\r
794         TCHAR *system = 0;\r
795         if (! affinity_mask_to_string(system_affinity, &system)) {\r
796           TCHAR *effective = 0;\r
797           if (! affinity_mask_to_string(effective_affinity, &effective)) {\r
798             log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0);\r
799           }\r
800           HeapFree(GetProcessHeap(), 0, effective);\r
801         }\r
802         HeapFree(GetProcessHeap(), 0, system);\r
803       }\r
804     }\r
805   }\r
806 \r
807   /* Try to get priority - may fail. */\r
808   unsigned long priority;\r
809   if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) {\r
810     if (priority == (priority & priority_mask())) service->priority = priority;\r
811     else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);\r
812   }\r
813 \r
814   /* Try to get hook I/O sharing - may fail. */\r
815   unsigned long hook_share_output_handles;\r
816   if (get_number(key, NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES, &hook_share_output_handles, false) == 1) {\r
817     if (hook_share_output_handles) service->hook_share_output_handles = true;\r
818     else service->hook_share_output_handles = false;\r
819   }\r
820   else hook_share_output_handles = false;\r
821   /* Try to get file rotation settings - may fail. */\r
822   unsigned long rotate_files;\r
823   if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
824     if (rotate_files) service->rotate_files = true;\r
825     else service->rotate_files = false;\r
826   }\r
827   else service->rotate_files = false;\r
828   if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) {\r
829     if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true;\r
830     else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
831   }\r
832   else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
833   /* Log timestamping requires a logging thread.*/\r
834   unsigned long timestamp_log;\r
835   if (get_number(key, NSSM_REG_TIMESTAMP_LOG, &timestamp_log, false) == 1) {\r
836     if (timestamp_log) service->timestamp_log = true;\r
837     else service->timestamp_log = false;\r
838   }\r
839   else service->timestamp_log = false;\r
840 \r
841   /* Hook I/O sharing and online rotation need a pipe. */\r
842   service->use_stdout_pipe = service->rotate_stdout_online || service->timestamp_log || hook_share_output_handles;\r
843   service->use_stderr_pipe = service->rotate_stderr_online || service->timestamp_log || hook_share_output_handles;\r
844   if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
845   if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
846   if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
847   override_milliseconds(service->name, key, NSSM_REG_ROTATE_DELAY, &service->rotate_delay, NSSM_ROTATE_DELAY, NSSM_EVENT_BOGUS_THROTTLE);\r
848 \r
849   /* Try to get force new console setting - may fail. */\r
850   if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;\r
851 \r
852   /* Change to startup directory in case stdout/stderr are relative paths. */\r
853   TCHAR cwd[PATH_LENGTH];\r
854   GetCurrentDirectory(_countof(cwd), cwd);\r
855   SetCurrentDirectory(service->dir);\r
856 \r
857   /* Try to get stdout and stderr */\r
858   if (get_io_parameters(service, key)) {\r
859     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
860     RegCloseKey(key);\r
861     SetCurrentDirectory(cwd);\r
862     return 5;\r
863   }\r
864 \r
865   /* Change back in case the startup directory needs to be deleted. */\r
866   SetCurrentDirectory(cwd);\r
867 \r
868   /* Try to get mandatory restart delay */\r
869   override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);\r
870 \r
871   /* Try to get throttle restart delay */\r
872   override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);\r
873 \r
874   /* Try to get service stop flags. */\r
875   unsigned long type = REG_DWORD;\r
876   unsigned long stop_method_skip;\r
877   unsigned long buflen = sizeof(stop_method_skip);\r
878   bool stop_ok = false;\r
879   ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);\r
880   if (ret != ERROR_SUCCESS) {\r
881     if (ret != ERROR_FILE_NOT_FOUND) {\r
882       if (type != REG_DWORD) {\r
883         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);\r
884       }\r
885       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(ret), 0);\r
886     }\r
887   }\r
888   else stop_ok = true;\r
889 \r
890   /* Try all methods except those requested to be skipped. */\r
891   service->stop_method = ~0;\r
892   if (stop_ok) service->stop_method &= ~stop_method_skip;\r
893 \r
894   /* Try to get kill delays - may fail. */\r
895   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
896   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
897   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
898 \r
899   /* Try to get process tree settings - may fail. */\r
900   unsigned long kill_process_tree;\r
901   if (get_number(key, NSSM_REG_KILL_PROCESS_TREE, &kill_process_tree, false) == 1) {\r
902     if (kill_process_tree) service->kill_process_tree = true;\r
903     else service->kill_process_tree = false;\r
904   }\r
905   else service->kill_process_tree = true;\r
906 \r
907   /* Try to get default exit action. */\r
908   bool default_action;\r
909   service->default_exit_action = NSSM_EXIT_RESTART;\r
910   TCHAR action_string[ACTION_LEN];\r
911   if (! get_exit_action(service->name, 0, action_string, &default_action)) {\r
912     for (int i = 0; exit_action_strings[i]; i++) {\r
913       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
914         service->default_exit_action = i;\r
915         break;\r
916       }\r
917     }\r
918   }\r
919 \r
920   /* Close registry */\r
921   RegCloseKey(key);\r
922 \r
923   return 0;\r
924 }\r
925 \r
926 /*\r
927   Sets the string for the exit action corresponding to the exit code.\r
928 \r
929   ret is a pointer to an unsigned long containing the exit code.\r
930   If ret is NULL, we retrieve the default exit action unconditionally.\r
931 \r
932   action is a buffer which receives the string.\r
933 \r
934   default_action is a pointer to a bool which is set to false if there\r
935   was an explicit string for the given exit code, or true if we are\r
936   returning the default action.\r
937 \r
938   Returns: 0 on success.\r
939            1 on error.\r
940 */\r
941 int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) {\r
942   /* Are we returning the default action or a status-specific one? */\r
943   *default_action = ! ret;\r
944 \r
945   /* Try to open the registry */\r
946   HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ);\r
947   if (! key) return 1;\r
948 \r
949   unsigned long type = REG_SZ;\r
950   unsigned long action_len = ACTION_LEN;\r
951 \r
952   TCHAR code[16];\r
953   if (! ret) code[0] = _T('\0');\r
954   else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) {\r
955     RegCloseKey(key);\r
956     return get_exit_action(service_name, 0, action, default_action);\r
957   }\r
958   if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) {\r
959     RegCloseKey(key);\r
960     /* Try again with * as the key if an exit code was defined */\r
961     if (ret) return get_exit_action(service_name, 0, action, default_action);\r
962     return 0;\r
963   }\r
964 \r
965   /* Close registry */\r
966   RegCloseKey(key);\r
967 \r
968   return 0;\r
969 }\r
970 \r
971 int set_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *cmd) {\r
972   /* Try to open the registry */\r
973   TCHAR registry[KEY_LENGTH];\r
974   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
975     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("set_hook()"), 0);\r
976     return 1;\r
977   }\r
978 \r
979   HKEY key;\r
980   long error;\r
981 \r
982   /* Don't create keys needlessly. */\r
983   if (! _tcslen(cmd)) {\r
984     key = open_registry(service_name, registry, KEY_READ, false);\r
985     if (! key) return 0;\r
986     error = RegQueryValueEx(key, hook_action, 0, 0, 0, 0);\r
987     RegCloseKey(key);\r
988     if (error == ERROR_FILE_NOT_FOUND) return 0;\r
989   }\r
990 \r
991   key = open_registry(service_name, registry, KEY_WRITE);\r
992   if (! key) return 1;\r
993 \r
994   int ret = 1;\r
995   if (_tcslen(cmd)) ret = set_string(key, (TCHAR *) hook_action, cmd, true);\r
996   else {\r
997     error = RegDeleteValue(key, hook_action);\r
998     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) ret = 0;\r
999   }\r
1000 \r
1001   /* Close registry */\r
1002   RegCloseKey(key);\r
1003 \r
1004   return ret;\r
1005 }\r
1006 \r
1007 int get_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *buffer, unsigned long buflen) {\r
1008   /* Try to open the registry */\r
1009   TCHAR registry[KEY_LENGTH];\r
1010   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
1011     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("get_hook()"), 0);\r
1012     return 1;\r
1013   }\r
1014   HKEY key;\r
1015   long error = open_registry(service_name, registry, KEY_READ, &key, false);\r
1016   if (! key) {\r
1017     if (error == ERROR_FILE_NOT_FOUND) {\r
1018       ZeroMemory(buffer, buflen);\r
1019       return 0;\r
1020     }\r
1021     return 1;\r
1022   }\r
1023 \r
1024   int ret = expand_parameter(key, (TCHAR *) hook_action, buffer, buflen, true, false);\r
1025 \r
1026   /* Close registry */\r
1027   RegCloseKey(key);\r
1028 \r
1029   return ret;\r
1030 }\r