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