Run hooks in response to certain events.
[nssm.git] / settings.cpp
1 #include "nssm.h"
2 /* XXX: (value && value->string) is probably bogus because value is probably never null */
3
4 /* Affinity. */
5 #define NSSM_AFFINITY_ALL _T("All")
6
7 extern const TCHAR *exit_action_strings[];
8 extern const TCHAR *startup_strings[];
9 extern const TCHAR *priority_strings[];
10
11 /* Does the parameter refer to the default value of the setting? */
12 static inline int is_default(const TCHAR *value) {
13   return (str_equiv(value, _T("default")) || str_equiv(value, _T("*")) || ! value[0]);
14 }
15
16 static int value_from_string(const TCHAR *name, value_t *value, const TCHAR *string) {
17   size_t len = _tcslen(string);
18   if (! len++) {
19     value->string = 0;
20     return 0;
21   }
22
23   value->string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
24   if (! value->string) {
25     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
26     return -1;
27   }
28
29   if (_sntprintf_s(value->string, len, _TRUNCATE, _T("%s"), string) < 0) {
30     HeapFree(GetProcessHeap(), 0, value->string);
31     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
32     return -1;
33   }
34
35   return 1;
36 }
37
38 /* Functions to manage NSSM-specific settings in the registry. */
39 static int setting_set_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
40   HKEY key = (HKEY) param;
41   if (! key) return -1;
42
43   unsigned long number;
44   long error;
45
46   /* Resetting to default? */
47   if (! value || ! value->string) {
48     error = RegDeleteValue(key, name);
49     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
50     print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
51     return -1;
52   }
53   if (str_number(value->string, &number)) return -1;
54
55   if (default_value && number == (unsigned long) default_value) {
56     error = RegDeleteValue(key, name);
57     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
58     print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
59     return -1;
60   }
61
62   if (set_number(key, (TCHAR *) name, number)) return -1;
63
64   return 1;
65 }
66
67 static int setting_get_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
68   HKEY key = (HKEY) param;
69   return get_number(key, (TCHAR *) name, &value->numeric, false);
70 }
71
72 static int setting_set_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
73   HKEY key = (HKEY) param;
74   if (! key) return -1;
75
76   long error;
77
78   /* Resetting to default? */
79   if (! value || ! value->string) {
80     if (default_value) value->string = (TCHAR *) default_value;
81     else {
82       error = RegDeleteValue(key, name);
83       if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
84       print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
85       return -1;
86     }
87   }
88   if (default_value && _tcslen((TCHAR *) default_value) && str_equiv(value->string, (TCHAR *) default_value)) {
89     error = RegDeleteValue(key, name);
90     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
91     print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
92     return -1;
93   }
94
95   if (set_expand_string(key, (TCHAR *) name, value->string)) return -1;
96
97   return 1;
98 }
99
100 static int setting_get_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
101   HKEY key = (HKEY) param;
102   TCHAR buffer[VALUE_LENGTH];
103
104   if (get_string(key, (TCHAR *) name, (TCHAR *) buffer, (unsigned long) sizeof(buffer), false, false, false)) return -1;
105
106   return value_from_string(name, value, buffer);
107 }
108
109 static int setting_set_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
110   unsigned long exitcode;
111   TCHAR *code;
112   TCHAR action_string[ACTION_LEN];
113
114   if (additional) {
115     /* Default action? */
116     if (is_default(additional)) code = 0;
117     else {
118       if (str_number(additional, &exitcode)) return -1;
119       code = (TCHAR *) additional;
120     }
121   }
122
123   HKEY key = open_registry(service_name, name, KEY_WRITE);
124   if (! key) return -1;
125
126   long error;
127   int ret = 1;
128
129   /* Resetting to default? */
130   if (value && value->string) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), value->string);
131   else {
132     if (code) {
133       /* Delete explicit action. */
134       error = RegDeleteValue(key, code);
135       RegCloseKey(key);
136       if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
137       print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, code, service_name, error_string(error));
138       return -1;
139     }
140     else {
141       /* Explicitly keep the default action. */
142       if (default_value) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), (TCHAR *) default_value);
143       ret = 0;
144     }
145   }
146
147   /* Validate the string. */
148   for (int i = 0; exit_action_strings[i]; i++) {
149     if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
150       if (default_value && str_equiv(action_string, (TCHAR *) default_value)) ret = 0;
151       if (RegSetValueEx(key, code, 0, REG_SZ, (const unsigned char *) exit_action_strings[i], (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
152         print_message(stderr, NSSM_MESSAGE_SETVALUE_FAILED, code, service_name, error_string(GetLastError()));
153         RegCloseKey(key);
154         return -1;
155       }
156
157       RegCloseKey(key);
158       return ret;
159     }
160   }
161
162   print_message(stderr, NSSM_MESSAGE_INVALID_EXIT_ACTION, action_string);
163   for (int i = 0; exit_action_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), exit_action_strings[i]);
164
165   return -1;
166 }
167
168 static int setting_get_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
169   unsigned long exitcode = 0;
170   unsigned long *code = 0;
171
172   if (additional) {
173     if (! is_default(additional)) {
174       if (str_number(additional, &exitcode)) return -1;
175       code = &exitcode;
176     }
177   }
178
179   TCHAR action_string[ACTION_LEN];
180   bool default_action;
181   if (get_exit_action(service_name, code, action_string, &default_action)) return -1;
182
183   value_from_string(name, value, action_string);
184
185   if (default_action && ! _tcsnicmp((const TCHAR *) action_string, (TCHAR *) default_value, ACTION_LEN)) return 0;
186   return 1;
187 }
188
189 static inline bool split_hook_name(const TCHAR *hook_name, TCHAR *hook_event, TCHAR *hook_action) {
190   TCHAR *s;
191
192   for (s = (TCHAR *) hook_name; *s; s++) {
193     if (*s == _T('/')) {
194       *s = _T('\0');
195       _sntprintf_s(hook_event, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), hook_name);
196       _sntprintf_s(hook_action, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), ++s);
197       return valid_hook_name(hook_event, hook_action, false);
198     }
199   }
200
201   print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_NAME, hook_name);
202   return false;
203 }
204
205 static int setting_set_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
206   TCHAR hook_event[HOOK_NAME_LENGTH];
207   TCHAR hook_action[HOOK_NAME_LENGTH];
208   if (! split_hook_name(additional, hook_event, hook_action)) return -1;
209
210   TCHAR *cmd;
211   if (value && value->string) cmd = value->string;
212   else cmd = _T("");
213
214   if (set_hook(service_name, hook_event, hook_action, cmd)) return -1;
215   if (! _tcslen(cmd)) return 0;
216   return 1;
217 }
218
219 static int setting_get_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
220   TCHAR hook_event[HOOK_NAME_LENGTH];
221   TCHAR hook_action[HOOK_NAME_LENGTH];
222   if (! split_hook_name(additional, hook_event, hook_action)) return -1;
223
224   TCHAR cmd[CMD_LENGTH];
225   if (get_hook(service_name, hook_event, hook_action, cmd, sizeof(cmd))) return -1;
226
227   value_from_string(name, value, cmd);
228
229   if (! _tcslen(cmd)) return 0;
230   return 1;
231 }
232
233 static int setting_set_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
234   HKEY key = (HKEY) param;
235   if (! key) return -1;
236
237   long error;
238   __int64 mask;
239   __int64 system_affinity = 0LL;
240
241   if (value && value->string) {
242     DWORD_PTR affinity;
243     if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, (DWORD_PTR *) &system_affinity)) system_affinity = ~0;
244
245     if (is_default(value->string) || str_equiv(value->string, NSSM_AFFINITY_ALL)) mask = 0LL;
246     else if (affinity_string_to_mask(value->string, &mask)) {
247       print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, value->string, num_cpus() - 1);
248       return -1;
249     }
250   }
251   else mask = 0LL;
252
253   if (! mask) {
254     error = RegDeleteValue(key, name);
255     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
256     print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
257     return -1;
258   }
259
260   /* Canonicalise. */
261   TCHAR *canon = 0;
262   if (affinity_mask_to_string(mask, &canon)) canon = value->string;
263
264   __int64 effective_affinity = mask & system_affinity;
265   if (effective_affinity != mask) {
266     /* Requested CPUs did not intersect with available CPUs? */
267     if (! effective_affinity) mask = effective_affinity = system_affinity;
268
269     TCHAR *system = 0;
270     if (! affinity_mask_to_string(system_affinity, &system)) {
271       TCHAR *effective = 0;
272       if (! affinity_mask_to_string(effective_affinity, &effective)) {
273         print_message(stderr, NSSM_MESSAGE_EFFECTIVE_AFFINITY_MASK, value->string, system, effective);
274         HeapFree(GetProcessHeap(), 0, effective);
275       }
276       HeapFree(GetProcessHeap(), 0, system);
277     }
278   }
279
280   if (RegSetValueEx(key, name, 0, REG_SZ, (const unsigned char *) canon, (unsigned long) (_tcslen(canon) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
281     if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
282     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, name, error_string(GetLastError()), 0);
283     return -1;
284   }
285
286   if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
287   return 1;
288 }
289
290 static int setting_get_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
291   HKEY key = (HKEY) param;
292   if (! key) return -1;
293
294   unsigned long type;
295   TCHAR *buffer = 0;
296   unsigned long buflen = 0;
297
298   int ret = RegQueryValueEx(key, name, 0, &type, 0, &buflen);
299   if (ret == ERROR_FILE_NOT_FOUND) {
300     if (value_from_string(name, value, NSSM_AFFINITY_ALL) == 1) return 0;
301     return -1;
302   }
303   if (ret != ERROR_SUCCESS) return -1;
304
305   if (type != REG_SZ) return -1;
306
307   buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
308   if (! buffer) {
309     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("affinity"), _T("setting_get_affinity"));
310     return -1;
311   }
312
313   if (get_string(key, (TCHAR *) name, buffer, buflen, false, false, true)) {
314     HeapFree(GetProcessHeap(), 0, buffer);
315     return -1;
316   }
317
318   __int64 affinity;
319   if (affinity_string_to_mask(buffer, &affinity)) {
320     print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, buffer, num_cpus() - 1);
321     HeapFree(GetProcessHeap(), 0, buffer);
322     return -1;
323   }
324
325   HeapFree(GetProcessHeap(), 0, buffer);
326
327   /* Canonicalise. */
328   if (affinity_mask_to_string(affinity, &buffer)) {
329     if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
330     return -1;
331   }
332
333   ret = value_from_string(name, value, buffer);
334   HeapFree(GetProcessHeap(), 0, buffer);
335   return ret;
336 }
337
338 static int setting_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
339   HKEY key = (HKEY) param;
340   if (! param) return -1;
341
342   if (! value || ! value->string || ! value->string[0]) {
343     long error = RegDeleteValue(key, name);
344     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
345     print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
346     return -1;
347   }
348
349   unsigned long envlen = (unsigned long) _tcslen(value->string) + 1;
350   TCHAR *unformatted = 0;
351   unsigned long newlen;
352   if (unformat_double_null(value->string, envlen, &unformatted, &newlen)) return -1;
353
354   if (test_environment(unformatted)) {
355     HeapFree(GetProcessHeap(), 0, unformatted);
356     print_message(stderr, NSSM_GUI_INVALID_ENVIRONMENT);
357     return -1;
358   }
359
360   if (RegSetValueEx(key, name, 0, REG_MULTI_SZ, (const unsigned char *) unformatted, (unsigned long) newlen * sizeof(TCHAR)) != ERROR_SUCCESS) {
361     if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
362     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);
363     return -1;
364   }
365
366   if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
367   return 1;
368 }
369
370 static int setting_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
371   HKEY key = (HKEY) param;
372   if (! param) return -1;
373
374   TCHAR *env = 0;
375   unsigned long envlen;
376   if (get_environment((TCHAR *) service_name, key, (TCHAR *) name, &env, &envlen)) return -1;
377   if (! envlen) return 0;
378
379   TCHAR *formatted;
380   unsigned long newlen;
381   if (format_double_null(env, envlen, &formatted, &newlen)) return -1;
382
383   int ret;
384   if (additional) {
385     /* Find named environment variable. */
386     TCHAR *s;
387     size_t len = _tcslen(additional);
388     for (s = env; *s; s++) {
389       /* Look for <additional>=<string> NULL NULL */
390       if (! _tcsnicmp(s, additional, len) && s[len] == _T('=')) {
391         /* Strip <key>= */
392         s += len + 1;
393         ret = value_from_string(name, value, s);
394         HeapFree(GetProcessHeap(), 0, env);
395         return ret;
396       }
397
398       /* Skip this string. */
399       for ( ; *s; s++);
400     }
401     HeapFree(GetProcessHeap(), 0, env);
402     return 0;
403   }
404
405   HeapFree(GetProcessHeap(), 0, env);
406
407   ret = value_from_string(name, value, formatted);
408   if (newlen) HeapFree(GetProcessHeap(), 0, formatted);
409   return ret;
410 }
411
412 static int setting_set_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
413   HKEY key = (HKEY) param;
414   if (! param) return -1;
415
416   TCHAR *priority_string;
417   int i;
418   long error;
419
420   if (value && value->string) priority_string = value->string;
421   else if (default_value) priority_string = (TCHAR *) default_value;
422   else {
423     error = RegDeleteValue(key, name);
424     if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
425     print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
426     return -1;
427   }
428
429   for (i = 0; priority_strings[i]; i++) {
430     if (! str_equiv(priority_strings[i], priority_string)) continue;
431
432     if (default_value && str_equiv(priority_string, (TCHAR *) default_value)) {
433       error = RegDeleteValue(key, name);
434       if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
435       print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
436       return -1;
437     }
438
439     if (set_number(key, (TCHAR *) name, priority_index_to_constant(i))) return -1;
440     return 1;
441   }
442
443   print_message(stderr, NSSM_MESSAGE_INVALID_PRIORITY, priority_string);
444   for (i = 0; priority_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), priority_strings[i]);
445
446   return -1;
447 }
448
449 static int setting_get_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
450   HKEY key = (HKEY) param;
451   if (! param) return -1;
452
453   unsigned long constant;
454   switch (get_number(key, (TCHAR *) name, &constant, false)) {
455     case 0: return value_from_string(name, value, (const TCHAR *) default_value);
456     case -1: return -1;
457   }
458
459   return value_from_string(name, value, priority_strings[priority_constant_to_index(constant)]);
460 }
461
462 /* Functions to manage native service settings. */
463 static int native_set_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
464   SC_HANDLE service_handle = (SC_HANDLE) param;
465   if (! service_handle) return -1;
466
467   /*
468     Get existing service dependencies because we must set both types together.
469   */
470   TCHAR *buffer;
471   unsigned long buflen;
472   if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1;
473
474   if (! value || ! value->string || ! value->string[0]) {
475     if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) {
476       print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
477       if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
478       return -1;
479     }
480
481     if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
482     return 0;
483   }
484
485   unsigned long len = (unsigned long) _tcslen(value->string) + 1;
486   TCHAR *unformatted = 0;
487   unsigned long newlen;
488   if (unformat_double_null(value->string, len, &unformatted, &newlen)) {
489     if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
490     return -1;
491   }
492
493   /* Prepend group identifier. */
494   unsigned long missing = 0;
495   TCHAR *canon = unformatted;
496   size_t canonlen = 0;
497   TCHAR *s;
498   for (s = unformatted; *s; s++) {
499     if (*s != SC_GROUP_IDENTIFIER) missing++;
500     size_t len = _tcslen(s);
501     canonlen += len + 1;
502     s += len;
503   }
504
505   if (missing) {
506     /* Missing identifiers plus double NULL terminator. */
507     canonlen += missing + 1;
508     newlen = (unsigned long) canonlen;
509
510     canon = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, canonlen * sizeof(TCHAR));
511     if (! canon) {
512       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("native_set_dependongroup"));
513       if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
514       if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
515       return -1;
516     }
517
518     size_t i = 0;
519     for (s = unformatted; *s; s++) {
520       if (*s != SC_GROUP_IDENTIFIER) canon[i++] = SC_GROUP_IDENTIFIER;
521       size_t len = _tcslen(s);
522       memmove(canon + i, s, (len + 1) * sizeof(TCHAR));
523       i += len + 1;
524       s += len;
525     }
526   }
527
528   TCHAR *dependencies;
529   if (buflen > 2) {
530     dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR));
531     if (! dependencies) {
532       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependongroup"));
533       if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon);
534       if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
535       if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
536       return -1;
537     }
538
539     memmove(dependencies, buffer, buflen * sizeof(TCHAR));
540     memmove(dependencies + buflen - 1, canon, newlen * sizeof(TCHAR));
541   }
542   else dependencies = canon;
543
544   int ret = 1;
545   if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1;
546   if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies);
547   if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon);
548   if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
549   if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
550
551   return ret;
552 }
553
554 static int native_get_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
555   SC_HANDLE service_handle = (SC_HANDLE) param;
556   if (! service_handle) return -1;
557
558   TCHAR *buffer;
559   unsigned long buflen;
560   if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1;
561
562   int ret;
563   if (buflen) {
564     TCHAR *formatted;
565     unsigned long newlen;
566     if (format_double_null(buffer, buflen, &formatted, &newlen)) {
567       HeapFree(GetProcessHeap(), 0, buffer);
568       return -1;
569     }
570
571     ret = value_from_string(name, value, formatted);
572     HeapFree(GetProcessHeap(), 0, formatted);
573     HeapFree(GetProcessHeap(), 0, buffer);
574   }
575   else {
576     value->string = 0;
577     ret = 0;
578   }
579
580   return ret;
581 }
582
583 static int native_set_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
584   SC_HANDLE service_handle = (SC_HANDLE) param;
585   if (! service_handle) return -1;
586
587   /*
588     Get existing group dependencies because we must set both types together.
589   */
590   TCHAR *buffer;
591   unsigned long buflen;
592   if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1;
593
594   if (! value || ! value->string || ! value->string[0]) {
595     if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) {
596       print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
597       if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
598       return -1;
599     }
600
601     if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
602     return 0;
603   }
604
605   unsigned long len = (unsigned long) _tcslen(value->string) + 1;
606   TCHAR *unformatted = 0;
607   unsigned long newlen;
608   if (unformat_double_null(value->string, len, &unformatted, &newlen)) {
609     if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
610     return -1;
611   }
612
613   TCHAR *dependencies;
614   if (buflen > 2) {
615     dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR));
616     if (! dependencies) {
617       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependonservice"));
618       if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
619       if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
620       return -1;
621     }
622
623     memmove(dependencies, buffer, buflen * sizeof(TCHAR));
624     memmove(dependencies + buflen - 1, unformatted, newlen * sizeof(TCHAR));
625   }
626   else dependencies = unformatted;
627
628   int ret = 1;
629   if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1;
630   if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies);
631   if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
632   if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
633
634   return ret;
635 }
636
637 static int native_get_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
638   SC_HANDLE service_handle = (SC_HANDLE) param;
639   if (! service_handle) return -1;
640
641   TCHAR *buffer;
642   unsigned long buflen;
643   if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1;
644
645   int ret;
646   if (buflen) {
647     TCHAR *formatted;
648     unsigned long newlen;
649     if (format_double_null(buffer, buflen, &formatted, &newlen)) {
650       HeapFree(GetProcessHeap(), 0, buffer);
651       return -1;
652     }
653
654     ret = value_from_string(name, value, formatted);
655     HeapFree(GetProcessHeap(), 0, formatted);
656     HeapFree(GetProcessHeap(), 0, buffer);
657   }
658   else {
659     value->string = 0;
660     ret = 0;
661   }
662
663   return ret;
664 }
665
666 int native_set_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
667   SC_HANDLE service_handle = (SC_HANDLE) param;
668   if (! service_handle) return -1;
669
670   TCHAR *description = 0;
671   if (value) description = value->string;
672   if (set_service_description(service_name, service_handle, description)) return -1;
673
674   if (description && description[0]) return 1;
675
676   return 0;
677 }
678
679 int native_get_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
680   SC_HANDLE service_handle = (SC_HANDLE) param;
681   if (! service_handle) return -1;
682
683   TCHAR buffer[VALUE_LENGTH];
684   if (get_service_description(service_name, service_handle, _countof(buffer), buffer)) return -1;
685
686   if (buffer[0]) return value_from_string(name, value, buffer);
687   value->string = 0;
688
689   return 0;
690 }
691
692 int native_set_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
693   SC_HANDLE service_handle = (SC_HANDLE) param;
694   if (! service_handle) return -1;
695
696   TCHAR *displayname = 0;
697   if (value && value->string) displayname = value->string;
698   else displayname = (TCHAR *) service_name;
699
700   if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, displayname)) {
701     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
702     return -1;
703   }
704
705   /*
706     If the display name and service name differ only in case,
707     ChangeServiceConfig() will return success but the display name will be
708     set to the service name, NOT the value passed to the function.
709     This appears to be a quirk of Windows rather than a bug here.
710   */
711   if (displayname != service_name && ! str_equiv(displayname, service_name)) return 1;
712
713   return 0;
714 }
715
716 int native_get_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
717   SC_HANDLE service_handle = (SC_HANDLE) param;
718   if (! service_handle) return -1;
719
720   QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
721   if (! qsc) return -1;
722
723   int ret = value_from_string(name, value, qsc->lpDisplayName);
724   HeapFree(GetProcessHeap(), 0, qsc);
725
726   return ret;
727 }
728
729 int native_set_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
730   SC_HANDLE service_handle = (SC_HANDLE) param;
731   if (! service_handle) return -1;
732
733   /* It makes no sense to try to reset the image path. */
734   if (! value || ! value->string) {
735     print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
736     return -1;
737   }
738
739   if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, value->string, 0, 0, 0, 0, 0, 0)) {
740     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
741     return -1;
742   }
743
744   return 1;
745 }
746
747 int native_get_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
748   SC_HANDLE service_handle = (SC_HANDLE) param;
749   if (! service_handle) return -1;
750
751   QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
752   if (! qsc) return -1;
753
754   int ret = value_from_string(name, value, qsc->lpBinaryPathName);
755   HeapFree(GetProcessHeap(), 0, qsc);
756
757   return ret;
758 }
759
760 int native_set_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
761   print_message(stderr, NSSM_MESSAGE_CANNOT_RENAME_SERVICE);
762   return -1;
763 }
764
765 int native_get_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
766   return value_from_string(name, value, service_name);
767 }
768
769 int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
770   SC_HANDLE service_handle = (SC_HANDLE) param;
771   if (! service_handle) return -1;
772
773   /*
774     Logical syntax is: nssm set <service> ObjectName <username> <password>
775     That means the username is actually passed in the additional parameter.
776   */
777   bool localsystem = false;
778   TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT;
779   TCHAR *password = 0;
780   if (additional) {
781     username = (TCHAR *) additional;
782     if (value && value->string) password = value->string;
783   }
784   else if (value && value->string) username = value->string;
785
786   const TCHAR *well_known = well_known_username(username);
787   size_t passwordsize = 0;
788   if (well_known) {
789     if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) localsystem = true;
790     username = (TCHAR *) well_known;
791     password = _T("");
792   }
793   else if (! password) {
794     /* We need a password if the account requires it. */
795     print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
796     return -1;
797   }
798   else passwordsize = _tcslen(password) * sizeof(TCHAR);
799
800   /*
801     ChangeServiceConfig() will fail to set the username if the service is set
802     to interact with the desktop.
803   */
804   unsigned long type = SERVICE_NO_CHANGE;
805   if (! localsystem) {
806     QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
807     if (! qsc) {
808       if (passwordsize) SecureZeroMemory(password, passwordsize);
809       return -1;
810     }
811
812     type = qsc->dwServiceType & ~SERVICE_INTERACTIVE_PROCESS;
813     HeapFree(GetProcessHeap(), 0, qsc);
814   }
815
816   if (! well_known) {
817     if (grant_logon_as_service(username)) {
818       if (passwordsize) SecureZeroMemory(password, passwordsize);
819       print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
820       return -1;
821     }
822   }
823
824   if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, 0)) {
825     if (passwordsize) SecureZeroMemory(password, passwordsize);
826     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
827     return -1;
828   }
829
830   if (passwordsize) SecureZeroMemory(password, passwordsize);
831
832   if (localsystem) return 0;
833
834   return 1;
835 }
836
837 int native_get_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
838   SC_HANDLE service_handle = (SC_HANDLE) param;
839   if (! service_handle) return -1;
840
841   QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
842   if (! qsc) return -1;
843
844   int ret = value_from_string(name, value, qsc->lpServiceStartName);
845   HeapFree(GetProcessHeap(), 0, qsc);
846
847   return ret;
848 }
849
850 int native_set_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
851   SC_HANDLE service_handle = (SC_HANDLE) param;
852   if (! service_handle) return -1;
853
854   /* It makes no sense to try to reset the startup type. */
855   if (! value || ! value->string) {
856     print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
857     return -1;
858   }
859
860   /* Map NSSM_STARTUP_* constant to Windows SERVICE_*_START constant. */
861   int service_startup = -1;
862   int i;
863   for (i = 0; startup_strings[i]; i++) {
864     if (str_equiv(value->string, startup_strings[i])) {
865       service_startup = i;
866       break;
867     }
868   }
869
870   if (service_startup < 0) {
871     print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_STARTUP, value->string);
872     for (i = 0; startup_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), startup_strings[i]);
873     return -1;
874   }
875
876   unsigned long startup;
877   switch (service_startup) {
878     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
879     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
880     default: startup = SERVICE_AUTO_START;
881   }
882
883   if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
884     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
885     return -1;
886   }
887
888   SERVICE_DELAYED_AUTO_START_INFO delayed;
889   ZeroMemory(&delayed, sizeof(delayed));
890   if (service_startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
891   else delayed.fDelayedAutostart = 0;
892   if (! ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
893     unsigned long error = GetLastError();
894     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
895     if (error != ERROR_INVALID_LEVEL) {
896       log_event(EVENTLOG_ERROR_TYPE, NSSM_MESSAGE_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service_name, error_string(error), 0);
897     }
898   }
899
900   return 1;
901 }
902
903 int native_get_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
904   SC_HANDLE service_handle = (SC_HANDLE) param;
905   if (! service_handle) return -1;
906
907   QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
908   if (! qsc) return -1;
909
910   unsigned long startup;
911   int ret = get_service_startup(service_name, service_handle, qsc, &startup);
912   HeapFree(GetProcessHeap(), 0, qsc);
913
914   if (ret) return -1;
915
916   unsigned long i;
917   for (i = 0; startup_strings[i]; i++);
918   if (startup >= i) return -1;
919
920   return value_from_string(name, value, startup_strings[startup]);
921 }
922
923 int native_set_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
924   SC_HANDLE service_handle = (SC_HANDLE) param;
925   if (! service_handle) return -1;
926
927   /* It makes no sense to try to reset the service type. */
928   if (! value || ! value->string) {
929     print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
930     return -1;
931   }
932
933   /*
934     We can only manage services of type SERVICE_WIN32_OWN_PROCESS
935     and SERVICE_INTERACTIVE_PROCESS.
936   */
937   unsigned long type = SERVICE_WIN32_OWN_PROCESS;
938   if (str_equiv(value->string, NSSM_INTERACTIVE_PROCESS)) type |= SERVICE_INTERACTIVE_PROCESS;
939   else if (! str_equiv(value->string, NSSM_WIN32_OWN_PROCESS)) {
940     print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_TYPE, value->string);
941     _ftprintf(stderr, _T("%s\n"), NSSM_WIN32_OWN_PROCESS);
942     _ftprintf(stderr, _T("%s\n"), NSSM_INTERACTIVE_PROCESS);
943     return -1;
944   }
945
946   /*
947     ChangeServiceConfig() will fail if the service runs under an account
948     other than LOCALSYSTEM and we try to make it interactive.
949   */
950   if (type & SERVICE_INTERACTIVE_PROCESS) {
951     QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
952     if (! qsc) return -1;
953
954     if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
955       HeapFree(GetProcessHeap(), 0, qsc);
956       print_message(stderr, NSSM_MESSAGE_INTERACTIVE_NOT_LOCALSYSTEM, value->string, service_name, NSSM_LOCALSYSTEM_ACCOUNT);
957       return -1;
958     }
959
960     HeapFree(GetProcessHeap(), 0, qsc);
961   }
962
963   if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
964     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
965     return -1;
966   }
967
968   return 1;
969 }
970
971 int native_get_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
972   SC_HANDLE service_handle = (SC_HANDLE) param;
973   if (! service_handle) return -1;
974
975   QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
976   if (! qsc) return -1;
977
978   value->numeric = qsc->dwServiceType;
979   HeapFree(GetProcessHeap(), 0, qsc);
980
981   const TCHAR *string;
982   switch (value->numeric) {
983     case SERVICE_KERNEL_DRIVER: string = NSSM_KERNEL_DRIVER; break;
984     case SERVICE_FILE_SYSTEM_DRIVER: string = NSSM_FILE_SYSTEM_DRIVER; break;
985     case SERVICE_WIN32_OWN_PROCESS: string = NSSM_WIN32_OWN_PROCESS; break;
986     case SERVICE_WIN32_SHARE_PROCESS: string = NSSM_WIN32_SHARE_PROCESS; break;
987     case SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_INTERACTIVE_PROCESS; break;
988     case SERVICE_WIN32_SHARE_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_SHARE_INTERACTIVE_PROCESS; break;
989     default: string = NSSM_UNKNOWN;
990   }
991
992   return value_from_string(name, value, string);
993 }
994
995 int set_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
996   if (! key) return -1;
997   int ret;
998
999   if (setting->set) ret = setting->set(service_name, (void *) key, setting->name, setting->default_value, value, additional);
1000   else ret = -1;
1001
1002   if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
1003   else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
1004   else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
1005
1006   return ret;
1007 }
1008
1009 int set_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
1010   if (! service_handle) return -1;
1011
1012   int ret;
1013   if (setting->set) ret = setting->set(service_name, service_handle, setting->name, setting->default_value, value, additional);
1014   else ret = -1;
1015
1016   if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
1017   else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
1018   else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
1019
1020   return ret;
1021 }
1022
1023 /*
1024   Returns:  1 if the value was retrieved.
1025             0 if the default value was retrieved.
1026            -1 on error.
1027 */
1028 int get_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
1029   if (! key) return -1;
1030   int ret;
1031
1032   switch (setting->type) {
1033     case REG_EXPAND_SZ:
1034     case REG_MULTI_SZ:
1035     case REG_SZ:
1036       value->string = (TCHAR *) setting->default_value;
1037       if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
1038       else ret = -1;
1039       break;
1040
1041     case REG_DWORD:
1042       value->numeric = (unsigned long) setting->default_value;
1043       if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
1044       else ret = -1;
1045       break;
1046
1047     default:
1048       ret = -1;
1049       break;
1050   }
1051
1052   if (ret < 0) print_message(stderr, NSSM_MESSAGE_GET_SETTING_FAILED, setting->name, service_name);
1053
1054   return ret;
1055 }
1056
1057 int get_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
1058   if (! service_handle) return -1;
1059   return setting->get(service_name, service_handle, setting->name, 0, value, additional);
1060 }
1061
1062 settings_t settings[] = {
1063   { NSSM_REG_EXE, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
1064   { NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
1065   { NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
1066   { NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action },
1067   { NSSM_REG_HOOK, REG_SZ, (void *) _T(""), false, ADDITIONAL_MANDATORY, setting_set_hook, setting_get_hook },
1068   { NSSM_REG_AFFINITY, REG_SZ, 0, false, 0, setting_set_affinity, setting_get_affinity },
1069   { NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
1070   { NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
1071   { NSSM_REG_NO_CONSOLE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1072   { NSSM_REG_PRIORITY, REG_SZ, (void *) priority_strings[NSSM_NORMAL_PRIORITY], false, 0, setting_set_priority, setting_get_priority },
1073   { NSSM_REG_RESTART_DELAY, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1074   { NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
1075   { NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number },
1076   { NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number },
1077   { NSSM_REG_STDIN NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDIN_FLAGS, false, 0, setting_set_number, setting_get_number },
1078   { NSSM_REG_STDOUT, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
1079   { NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number },
1080   { NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number },
1081   { NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number },
1082   { NSSM_REG_STDOUT NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1083   { NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
1084   { NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number },
1085   { NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number },
1086   { NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number },
1087   { NSSM_REG_STDERR NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1088   { NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1089   { NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
1090   { NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
1091   { NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
1092   { NSSM_REG_KILL_PROCESS_TREE, REG_DWORD, (void *) 1, false, 0, setting_set_number, setting_get_number },
1093   { NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number },
1094   { NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1095   { NSSM_REG_ROTATE_ONLINE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1096   { NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1097   { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1098   { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
1099   { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number },
1100   { NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup },
1101   { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice },
1102   { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description },
1103   { NSSM_NATIVE_DISPLAYNAME, REG_SZ, NULL, true, 0, native_set_displayname, native_get_displayname },
1104   { NSSM_NATIVE_IMAGEPATH, REG_EXPAND_SZ, NULL, true, 0, native_set_imagepath, native_get_imagepath },
1105   { NSSM_NATIVE_OBJECTNAME, REG_SZ, NSSM_LOCALSYSTEM_ACCOUNT, true, 0, native_set_objectname, native_get_objectname },
1106   { NSSM_NATIVE_NAME, REG_SZ, NULL, true, 0, native_set_name, native_get_name },
1107   { NSSM_NATIVE_STARTUP, REG_SZ, NULL, true, 0, native_set_startup, native_get_startup },
1108   { NSSM_NATIVE_TYPE, REG_SZ, NULL, true, 0, native_set_type, native_get_type },
1109   { NULL, NULL, NULL, NULL, NULL }
1110 };