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