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