Alternative open_registry_key() signature.
[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->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
162   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);\r
163   if (service->rotate_stdout_online) set_number(key, NSSM_REG_ROTATE_ONLINE, 1);\r
164   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_ONLINE);\r
165   if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);\r
166   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_SECONDS);\r
167   if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);\r
168   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);\r
169   if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);\r
170   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);\r
171   if (service->rotate_delay != NSSM_ROTATE_DELAY) set_number(key, NSSM_REG_ROTATE_DELAY, service->rotate_delay);\r
172   else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_DELAY);\r
173   if (service->no_console) set_number(key, NSSM_REG_NO_CONSOLE, 1);\r
174   else if (editing) RegDeleteValue(key, NSSM_REG_NO_CONSOLE);\r
175 \r
176   /* Environment */\r
177   if (service->env) {\r
178     if (RegSetValueEx(key, NSSM_REG_ENV, 0, REG_MULTI_SZ, (const unsigned char *) service->env, (unsigned long) service->envlen * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
179       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);\r
180     }\r
181   }\r
182   else if (editing) RegDeleteValue(key, NSSM_REG_ENV);\r
183   if (service->env_extra) {\r
184     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
185       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV_EXTRA, error_string(GetLastError()), 0);\r
186     }\r
187   }\r
188   else if (editing) RegDeleteValue(key, NSSM_REG_ENV_EXTRA);\r
189 \r
190   /* Close registry. */\r
191   RegCloseKey(key);\r
192 \r
193   return 0;\r
194 }\r
195 \r
196 int create_exit_action(TCHAR *service_name, const TCHAR *action_string, bool editing) {\r
197   /* Get registry */\r
198   TCHAR registry[KEY_LENGTH];\r
199   if (service_registry_path(service_name, true, NSSM_REG_EXIT, registry, _countof(registry)) < 0) {\r
200     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("NSSM_REG_EXIT"), _T("create_exit_action()"), 0);\r
201     return 1;\r
202   }\r
203 \r
204   /* Try to open the registry */\r
205   HKEY key;\r
206   unsigned long disposition;\r
207   if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, &disposition) != ERROR_SUCCESS) {\r
208     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENKEY_FAILED, registry, error_string(GetLastError()), 0);\r
209     return 2;\r
210   }\r
211 \r
212   /* Do nothing if the key already existed */\r
213   if (disposition == REG_OPENED_EXISTING_KEY && ! editing) {\r
214     RegCloseKey(key);\r
215     return 0;\r
216   }\r
217 \r
218   /* Create the default value */\r
219   if (RegSetValueEx(key, 0, 0, REG_SZ, (const unsigned char *) action_string, (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
220     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_EXIT, error_string(GetLastError()), 0);\r
221     RegCloseKey(key);\r
222     return 3;\r
223   }\r
224 \r
225   /* Close registry */\r
226   RegCloseKey(key);\r
227 \r
228   return 0;\r
229 }\r
230 \r
231 int get_environment(TCHAR *service_name, HKEY key, TCHAR *value, TCHAR **env, unsigned long *envlen) {\r
232   unsigned long type = REG_MULTI_SZ;\r
233 \r
234   /* Dummy test to find buffer size */\r
235   unsigned long ret = RegQueryValueEx(key, value, 0, &type, NULL, envlen);\r
236   if (ret != ERROR_SUCCESS) {\r
237     *env = 0;\r
238     *envlen = 0;\r
239     /* The service probably doesn't have any environment configured */\r
240     if (ret == ERROR_FILE_NOT_FOUND) return 0;\r
241     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
242     return 1;\r
243   }\r
244 \r
245   if (type != REG_MULTI_SZ) {\r
246     *env = 0;\r
247     *envlen = 0;\r
248     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_INVALID_ENVIRONMENT_STRING_TYPE, value, service_name, 0);\r
249     return 2;\r
250   }\r
251 \r
252   /* Probably not possible */\r
253   if (! *envlen) return 0;\r
254 \r
255   /* Previously initialised? */\r
256   if (*env) HeapFree(GetProcessHeap(), 0, *env);\r
257 \r
258   *env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, *envlen);\r
259   if (! *env) {\r
260     *envlen = 0;\r
261     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_environment()"), 0);\r
262     return 3;\r
263   }\r
264 \r
265   /* Actually get the strings */\r
266   ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) *env, envlen);\r
267   if (ret != ERROR_SUCCESS) {\r
268     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
269     HeapFree(GetProcessHeap(), 0, *env);\r
270     *env = 0;\r
271     *envlen = 0;\r
272     return 4;\r
273   }\r
274 \r
275   return 0;\r
276 }\r
277 \r
278 \r
279 int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool expand, bool sanitise, bool must_exist) {\r
280   TCHAR *buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, datalen);\r
281   if (! buffer) {\r
282     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, _T("get_string()"), 0);\r
283     return 1;\r
284   }\r
285 \r
286   ZeroMemory(data, datalen);\r
287 \r
288   unsigned long type = REG_EXPAND_SZ;\r
289   unsigned long buflen = datalen;\r
290 \r
291   unsigned long ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) buffer, &buflen);\r
292   if (ret != ERROR_SUCCESS) {\r
293     unsigned long error = GetLastError();\r
294     HeapFree(GetProcessHeap(), 0, buffer);\r
295 \r
296     if (ret == ERROR_FILE_NOT_FOUND) {\r
297       if (! must_exist) return 0;\r
298     }\r
299 \r
300     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(error), 0);\r
301     return 2;\r
302   }\r
303 \r
304   /* Paths aren't allowed to contain quotes. */\r
305   if (sanitise) PathUnquoteSpaces(buffer);\r
306 \r
307   /* Do we want to expand the string? */\r
308   if (! expand) {\r
309     if (type == REG_EXPAND_SZ) type = REG_SZ;\r
310   }\r
311 \r
312   /* Technically we shouldn't expand environment strings from REG_SZ values */\r
313   if (type != REG_EXPAND_SZ) {\r
314     memmove(data, buffer, buflen);\r
315     HeapFree(GetProcessHeap(), 0, buffer);\r
316     return 0;\r
317   }\r
318 \r
319   ret = ExpandEnvironmentStrings((TCHAR *) buffer, data, datalen);\r
320   if (! ret || ret > datalen) {\r
321     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, buffer, error_string(GetLastError()), 0);\r
322     HeapFree(GetProcessHeap(), 0, buffer);\r
323     return 3;\r
324   }\r
325 \r
326   HeapFree(GetProcessHeap(), 0, buffer);\r
327   return 0;\r
328 }\r
329 \r
330 int get_string(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {\r
331   return get_string(key, value, data, datalen, false, sanitise, true);\r
332 }\r
333 \r
334 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise, bool must_exist) {\r
335   return get_string(key, value, data, datalen, true, sanitise, must_exist);\r
336 }\r
337 \r
338 int expand_parameter(HKEY key, TCHAR *value, TCHAR *data, unsigned long datalen, bool sanitise) {\r
339   return expand_parameter(key, value, data, datalen, sanitise, true);\r
340 }\r
341 \r
342 /*\r
343   Sets a string in the registry.\r
344   Returns: 0 if it was set.\r
345            1 on error.\r
346 */\r
347 int set_string(HKEY key, TCHAR *value, TCHAR *string, bool expand) {\r
348   unsigned long type = expand ? REG_EXPAND_SZ : REG_SZ;\r
349   if (RegSetValueEx(key, value, 0, type, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) == ERROR_SUCCESS) return 0;\r
350   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, value, error_string(GetLastError()), 0);\r
351   return 1;\r
352 }\r
353 \r
354 int set_string(HKEY key, TCHAR *value, TCHAR *string) {\r
355   return set_string(key, value, string, false);\r
356   return 1;\r
357 }\r
358 \r
359 int set_expand_string(HKEY key, TCHAR *value, TCHAR *string) {\r
360   return set_string(key, value, string, true);\r
361   return 1;\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, true);\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 file rotation settings - may fail. */\r
644   unsigned long rotate_files;\r
645   if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
646     if (rotate_files) service->rotate_files = true;\r
647     else service->rotate_files = false;\r
648   }\r
649   else service->rotate_files = false;\r
650   if (get_number(key, NSSM_REG_ROTATE_ONLINE, &rotate_files, false) == 1) {\r
651     if (rotate_files) service->rotate_stdout_online = service->rotate_stderr_online = true;\r
652     else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
653   }\r
654   else service->rotate_stdout_online = service->rotate_stderr_online = false;\r
655   if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
656   if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
657   if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
658   override_milliseconds(service->name, key, NSSM_REG_ROTATE_DELAY, &service->rotate_delay, NSSM_ROTATE_DELAY, NSSM_EVENT_BOGUS_THROTTLE);\r
659 \r
660   /* Try to get force new console setting - may fail. */\r
661   if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;\r
662 \r
663   /* Change to startup directory in case stdout/stderr are relative paths. */\r
664   TCHAR cwd[PATH_LENGTH];\r
665   GetCurrentDirectory(_countof(cwd), cwd);\r
666   SetCurrentDirectory(service->dir);\r
667 \r
668   /* Try to get stdout and stderr */\r
669   if (get_io_parameters(service, key)) {\r
670     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
671     RegCloseKey(key);\r
672     SetCurrentDirectory(cwd);\r
673     return 5;\r
674   }\r
675 \r
676   /* Change back in case the startup directory needs to be deleted. */\r
677   SetCurrentDirectory(cwd);\r
678 \r
679   /* Try to get mandatory restart delay */\r
680   override_milliseconds(service->name, key, NSSM_REG_RESTART_DELAY, &service->restart_delay, 0, NSSM_EVENT_BOGUS_RESTART_DELAY);\r
681 \r
682   /* Try to get throttle restart delay */\r
683   override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);\r
684 \r
685   /* Try to get service stop flags. */\r
686   unsigned long type = REG_DWORD;\r
687   unsigned long stop_method_skip;\r
688   unsigned long buflen = sizeof(stop_method_skip);\r
689   bool stop_ok = false;\r
690   ret = RegQueryValueEx(key, NSSM_REG_STOP_METHOD_SKIP, 0, &type, (unsigned char *) &stop_method_skip, &buflen);\r
691   if (ret != ERROR_SUCCESS) {\r
692     if (ret != ERROR_FILE_NOT_FOUND) {\r
693       if (type != REG_DWORD) {\r
694         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);\r
695       }\r
696       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);\r
697     }\r
698   }\r
699   else stop_ok = true;\r
700 \r
701   /* Try all methods except those requested to be skipped. */\r
702   service->stop_method = ~0;\r
703   if (stop_ok) service->stop_method &= ~stop_method_skip;\r
704 \r
705   /* Try to get kill delays - may fail. */\r
706   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
707   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
708   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
709 \r
710   /* Try to get process tree settings - may fail. */\r
711   unsigned long kill_process_tree;\r
712   if (get_number(key, NSSM_REG_KILL_PROCESS_TREE, &kill_process_tree, false) == 1) {\r
713     if (kill_process_tree) service->kill_process_tree = true;\r
714     else service->kill_process_tree = false;\r
715   }\r
716   else service->kill_process_tree = true;\r
717 \r
718   /* Try to get default exit action. */\r
719   bool default_action;\r
720   service->default_exit_action = NSSM_EXIT_RESTART;\r
721   TCHAR action_string[ACTION_LEN];\r
722   if (! get_exit_action(service->name, 0, action_string, &default_action)) {\r
723     for (int i = 0; exit_action_strings[i]; i++) {\r
724       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
725         service->default_exit_action = i;\r
726         break;\r
727       }\r
728     }\r
729   }\r
730 \r
731   /* Close registry */\r
732   RegCloseKey(key);\r
733 \r
734   return 0;\r
735 }\r
736 \r
737 /*\r
738   Sets the string for the exit action corresponding to the exit code.\r
739 \r
740   ret is a pointer to an unsigned long containing the exit code.\r
741   If ret is NULL, we retrieve the default exit action unconditionally.\r
742 \r
743   action is a buffer which receives the string.\r
744 \r
745   default_action is a pointer to a bool which is set to false if there\r
746   was an explicit string for the given exit code, or true if we are\r
747   returning the default action.\r
748 \r
749   Returns: 0 on success.\r
750            1 on error.\r
751 */\r
752 int get_exit_action(const TCHAR *service_name, unsigned long *ret, TCHAR *action, bool *default_action) {\r
753   /* Are we returning the default action or a status-specific one? */\r
754   *default_action = ! ret;\r
755 \r
756   /* Try to open the registry */\r
757   HKEY key = open_registry(service_name, NSSM_REG_EXIT, KEY_READ);\r
758   if (! key) return 1;\r
759 \r
760   unsigned long type = REG_SZ;\r
761   unsigned long action_len = ACTION_LEN;\r
762 \r
763   TCHAR code[16];\r
764   if (! ret) code[0] = _T('\0');\r
765   else if (_sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), *ret) < 0) {\r
766     RegCloseKey(key);\r
767     return get_exit_action(service_name, 0, action, default_action);\r
768   }\r
769   if (RegQueryValueEx(key, code, 0, &type, (unsigned char *) action, &action_len) != ERROR_SUCCESS) {\r
770     RegCloseKey(key);\r
771     /* Try again with * as the key if an exit code was defined */\r
772     if (ret) return get_exit_action(service_name, 0, action, default_action);\r
773     return 0;\r
774   }\r
775 \r
776   /* Close registry */\r
777   RegCloseKey(key);\r
778 \r
779   return 0;\r
780 }\r
781 \r
782 int set_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *cmd) {\r
783   /* Try to open the registry */\r
784   TCHAR registry[KEY_LENGTH];\r
785   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
786     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("set_hook()"), 0);\r
787     return 1;\r
788   }\r
789 \r
790   HKEY key;\r
791   long error;\r
792 \r
793   /* Don't create keys needlessly. */\r
794   if (! _tcslen(cmd)) {\r
795     key = open_registry(service_name, registry, KEY_READ, false);\r
796     if (! key) return 0;\r
797     error = RegQueryValueEx(key, hook_action, 0, 0, 0, 0);\r
798     RegCloseKey(key);\r
799     if (error == ERROR_FILE_NOT_FOUND) return 0;\r
800   }\r
801 \r
802   key = open_registry(service_name, registry, KEY_WRITE);\r
803   if (! key) return 1;\r
804 \r
805   int ret = 1;\r
806   if (_tcslen(cmd)) ret = set_string(key, (TCHAR *) hook_action, cmd, true);\r
807   else {\r
808     error = RegDeleteValue(key, hook_action);\r
809     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) ret = 0;\r
810   }\r
811 \r
812   /* Close registry */\r
813   RegCloseKey(key);\r
814 \r
815   return ret;\r
816 }\r
817 \r
818 int get_hook(const TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *buffer, unsigned long buflen) {\r
819   /* Try to open the registry */\r
820   TCHAR registry[KEY_LENGTH];\r
821   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, _T("%s\\%s"), NSSM_REG_HOOK, hook_event) < 0) {\r
822     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook registry"), _T("get_hook()"), 0);\r
823     return 1;\r
824   }\r
825   HKEY key = open_registry(service_name, registry, KEY_READ, false);\r
826   if (! key) return 1;\r
827 \r
828   int ret = expand_parameter(key, (TCHAR *) hook_action, buffer, buflen, true, false);\r
829 \r
830   /* Close registry */\r
831   RegCloseKey(key);\r
832 \r
833   return ret;\r
834 }\r