* Existing services can now be managed using the GUI
or on the command line.
- * NSSM can now set the priority class of the managed
- application.
+ * NSSM can now set the priority class and processor
+ affinity of the managed application.
* NSSM can now optionally rotate existing files when
redirecting I/O.
AppEnvironmentExtra in place of or in addition to the srvany-compatible\r
AppEnvironment.\r
\r
-Since version 2.22, NSSM can set the managed application's process priority.\r
+Since version 2.22, NSSM can set the managed application's process priority\r
+and CPU affinity.\r
\r
Since version 2.22, NSSM can rotate existing output files when redirecting I/O.\r
\r
application will be launched with normal priority.\r
\r
\r
+Processor affinity\r
+------------------\r
+NSSM can set the CPU affinity of the managed application. NSSM will look in\r
+the registry under HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\r
+for the REG_SZ entry AppAffinity. It should specify a comma-separated listed\r
+of zero-indexed processor IDs. A range of processors may optionally be\r
+specified with a dash. No other characters are allowed in the string.\r
+\r
+For example, to specify the first; second; third and fifth CPUs, an appropriate\r
+AppAffinity would be 0-2,4.\r
+\r
+If AppAffinity is missing or invalid, NSSM will not attempt to restrict the\r
+application to specific CPUs.\r
+\r
+Note that the 64-bit version of NSSM can configure a maximum of 64 CPUs in this\r
+way and that the 32-bit version can configure a maxium of 32 CPUs even when\r
+running on 64-bit Windows.\r
+\r
+\r
Stopping the service\r
--------------------\r
When stopping a service NSSM will attempt several different methods of killing\r
\r
nssm set UT2004 AppStdout c:\games\ut2004\service.log\r
\r
+To restrict the server to a single CPU:\r
+\r
+ nssm set UT2004 AppAffinity 0\r
+\r
To remove the server:\r
\r
nssm remove UT2004 confirm\r
Thanks to Arve Knudsen for spotting that child processes of the monitored\r
application could be left running on service shutdown, and that a missing\r
registry value for AppDirectory confused NSSM.\r
-Thanks to Peter Wagemans and Laszlo Keresztfalvi for suggesting throttling restarts.\r
+Thanks to Peter Wagemans and Laszlo Keresztfalvi for suggesting throttling\r
+restarts.\r
Thanks to Eugene Lifshitz for finding an edge case in CreateProcess() and for\r
advising how to build messages.mc correctly in paths containing spaces.\r
Thanks to Rob Sharp for pointing out that NSSM did not respect the\r
Thanks to Riccardo Gusmeroli for Italian translation.\r
Thanks to Eric Cheldelin for the inspiration to generate a Control-C event\r
on shutdown.\r
-Thanks to Brian Baxter for suggesting how to escape quotes from the command prompt.\r
+Thanks to Brian Baxter for suggesting how to escape quotes from the command\r
+prompt.\r
Thanks to Russ Holmann for suggesting that the shutdown timeout be configurable.\r
Thanks to Paul Spause for spotting a bug with default registry entries.\r
Thanks to BUGHUNTER for spotting more GUI bugs.\r
Thanks to Doug Watson for suggesting file rotation.\r
Thanks to Арслан Сайдуганов for suggesting setting process priority.\r
+Thanks to Robert Middleton for suggestion and draft implementation of process\r
+affinity support.\r
\r
Licence\r
-------\r
\r
/* Set existing details. */\r
HWND combo;\r
+ HWND list;\r
\r
/* Application tab. */\r
if (service->native) SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->image);\r
SendMessage(combo, CB_SETCURSEL, priority, 0);\r
}\r
\r
+ if (service->affinity) {\r
+ list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+ SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_UNCHECKED, 0);\r
+ EnableWindow(GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY), 1);\r
+\r
+ DWORD_PTR affinity, system_affinity;\r
+ if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
+ if ((service->affinity & (__int64) system_affinity) != service->affinity) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_AFFINITY);\r
+ }\r
+\r
+ for (int i = 0; i < num_cpus(); i++) {\r
+ if (! (service->affinity & (1LL << (__int64) i))) SendMessage(list, LB_SETSEL, 0, i);\r
+ }\r
+ }\r
+\r
/* Shutdown tab. */\r
if (! (service->stop_method & NSSM_STOP_METHOD_CONSOLE)) {\r
SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0);\r
EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), enabled);\r
}\r
\r
+static inline void set_affinity_enabled(unsigned char enabled) {\r
+ EnableWindow(GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY), enabled);\r
+}\r
+\r
static inline void set_rotation_enabled(unsigned char enabled) {\r
EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), enabled);\r
EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), enabled);\r
combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
service->priority = priority_index_to_constant((unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0));\r
\r
+ service->affinity = 0LL;\r
+ if (! (SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_GETCHECK, 0, 0) & BST_CHECKED)) {\r
+ HWND list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+ int selected = (int) SendMessage(list, LB_GETSELCOUNT, 0, 0);\r
+ int count = (int) SendMessage(list, LB_GETCOUNT, 0, 0);\r
+ if (! selected) {\r
+ popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_WARN_AFFINITY_NONE);\r
+ return 5;\r
+ }\r
+ else if (selected < count) {\r
+ for (int i = 0; i < count; i++) {\r
+ if (SendMessage(list, LB_GETSEL, i, 0)) service->affinity |= (1LL << (__int64) i);\r
+ }\r
+ }\r
+ }\r
+\r
/* Get stop method stuff. */\r
check_stop_method(service, NSSM_STOP_METHOD_CONSOLE, IDC_METHOD_CONSOLE);\r
check_stop_method(service, NSSM_STOP_METHOD_WINDOW, IDC_METHOD_WINDOW);\r
set_logon_enabled(1);\r
break;\r
\r
+ /* Affinity. */\r
+ case IDC_AFFINITY_ALL:\r
+ if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 0;\r
+ else enabled = 1;\r
+ set_affinity_enabled(enabled);\r
+ break;\r
+\r
/* Shutdown methods. */\r
case IDC_METHOD_CONSOLE:\r
set_timeout_enabled(LOWORD(w), IDC_KILL_CONSOLE);\r
\r
HWND tabs;\r
HWND combo;\r
+ HWND list;\r
+ int i, n;\r
tabs = GetDlgItem(window, IDC_TAB1);\r
if (! tabs) return 0;\r
\r
SendMessage(combo, CB_INSERTSTRING, NSSM_IDLE_PRIORITY, (LPARAM) message_string(NSSM_GUI_IDLE_PRIORITY_CLASS));\r
SendMessage(combo, CB_SETCURSEL, NSSM_NORMAL_PRIORITY, 0);\r
\r
+ list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+ n = num_cpus();\r
+ SendMessage(list, LB_SETCOLUMNWIDTH, 16, 0);\r
+ for (i = 0; i < n; i++) {\r
+ TCHAR buffer[3];\r
+ _sntprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("%d"), i);\r
+ SendMessage(list, LB_ADDSTRING, 0, (LPARAM) buffer);\r
+ }\r
+\r
+ /*\r
+ Size to fit.\r
+ The box is high enough for four rows. It is wide enough for eight\r
+ columns without scrolling. With scrollbars it shrinks to two rows.\r
+ Note that the above only holds if we set the column width BEFORE
+ adding the strings.
+ */\r
+ if (n < 32) {\r
+ int columns = (n - 1) / 4;\r
+ RECT rect;\r
+ GetWindowRect(list, &rect);\r
+ int width = rect.right - rect.left;
+ width -= (7 - columns) * 16;\r
+ int height = rect.bottom - rect.top;\r
+ if (n < 4) height -= SendMessage(list, LB_GETITEMHEIGHT, 0, 0) * (4 - n);
+ SetWindowPos(list, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER);\r
+ }\r
+ SendMessage(list, LB_SELITEMRANGE, 1, MAKELPARAM(0, n));\r
+\r
+ SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_CHECKED, 0);\r
+ set_affinity_enabled(0);\r
+\r
/* Shutdown tab. */\r
tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);\r
tab.cchTextMax = (int) _tcslen(tab.pszText);\r
FreeConsole();\r
}\r
\r
+int num_cpus() {\r
+ DWORD_PTR i, affinity, system_affinity;\r
+ if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) return 64;\r
+ for (i = 0; system_affinity & (1LL << i); i++);\r
+ return (int) i;\r
+}\r
+\r
int _tmain(int argc, TCHAR **argv) {\r
check_console();\r
\r
void strip_basename(TCHAR *);\r
int str_number(const TCHAR *, unsigned long *, TCHAR **);\r
int str_number(const TCHAR *, unsigned long *);\r
+int num_cpus();\r
int usage(int);\r
\r
#define NSSM _T("NSSM")\r
/* Other non-default parameters. May fail. */\r
if (service->priority != NORMAL_PRIORITY_CLASS) set_number(key, NSSM_REG_PRIORITY, service->priority);\r
else if (editing) RegDeleteValue(key, NSSM_REG_PRIORITY);\r
+ if (service->affinity) {\r
+ TCHAR *string;\r
+ if (! affinity_mask_to_string(service->affinity, &string)) {\r
+ if (RegSetValueEx(key, NSSM_REG_AFFINITY, 0, REG_SZ, (const unsigned char *) string, (unsigned long) (_tcslen(string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_AFFINITY, error_string(GetLastError()), 0);\r
+ HeapFree(GetProcessHeap(), 0, string);\r
+ return 5;\r
+ }\r
+ }\r
+ if (string) HeapFree(GetProcessHeap(), 0, string);\r
+ }\r
+ else if (editing) RegDeleteValue(key, NSSM_REG_AFFINITY);\r
unsigned long stop_method_skip = ~service->stop_method;\r
if (stop_method_skip) set_number(key, NSSM_REG_STOP_METHOD_SKIP, stop_method_skip);\r
else if (editing) RegDeleteValue(key, NSSM_REG_STOP_METHOD_SKIP);\r
log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);\r
}\r
\r
+ /* Try to get processor affinity - may fail. */\r
+ TCHAR buffer[512];\r
+ if (expand_parameter(key, NSSM_REG_AFFINITY, buffer, sizeof(buffer), false, false) || ! buffer[0]) service->affinity = 0LL;\r
+ else if (affinity_string_to_mask(buffer, &service->affinity)) {\r
+ log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_AFFINITY_MASK, service->name, buffer);\r
+ service->affinity = 0LL;\r
+ }\r
+ else {\r
+ DWORD_PTR affinity, system_affinity;\r
+\r
+ if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
+ _int64 effective_affinity = service->affinity & system_affinity;\r
+ if (effective_affinity != service->affinity) {\r
+ TCHAR *system = 0;\r
+ if (! affinity_mask_to_string(system_affinity, &system)) {\r
+ TCHAR *effective = 0;\r
+ if (! affinity_mask_to_string(effective_affinity, &effective)) {\r
+ log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_EFFECTIVE_AFFINITY_MASK, service->name, buffer, system, effective, 0);\r
+ }\r
+ HeapFree(GetProcessHeap(), 0, effective);\r
+ }\r
+ HeapFree(GetProcessHeap(), 0, system);\r
+ }\r
+ }\r
+ }\r
+\r
/* Try to get environment variables - may fail */\r
set_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);\r
/* Environment variables to add to existing rather than replace - may fail. */\r
#define NSSM_REG_ROTATE_BYTES_LOW _T("AppRotateBytes")\r
#define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh")\r
#define NSSM_REG_PRIORITY _T("AppPriority")\r
+#define NSSM_REG_AFFINITY _T("AppAffinity")\r
#define NSSM_STDIO_LENGTH 29\r
\r
HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam);\r
#define IDC_PASSWORD1 1038\r
#define IDC_PASSWORD2 1039\r
#define IDC_PRIORITY 1040\r
+#define IDC_AFFINITY_ALL 1041\r
+#define IDC_AFFINITY 1042\r
\r
// Next default values for new objects\r
// \r
#ifndef APSTUDIO_READONLY_SYMBOLS\r
#define _APS_NEXT_RESOURCE_VALUE 115\r
#define _APS_NEXT_COMMAND_VALUE 40001\r
-#define _APS_NEXT_CONTROL_VALUE 1041\r
+#define _APS_NEXT_CONTROL_VALUE 1043\r
#define _APS_NEXT_SYMED_VALUE 101\r
#endif\r
#endif\r
const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
const TCHAR *priority_strings[] = { _T("REALTIME_PRIORITY_CLASS"), _T("HIGH_PRIORITY_CLASS"), _T("ABOVE_NORMAL_PRIORITY_CLASS"), _T("NORMAL_PRIORITY_CLASS"), _T("BELOW_NORMAL_PRIORITY_CLASS"), _T("IDLE_PRIORITY_CLASS"), 0 };\r
\r
+typedef struct {\r
+ int first;\r
+ int last;\r
+} list_t;\r
+\r
+int affinity_mask_to_string(__int64 mask, TCHAR **string) {\r
+ if (! string) return 1;\r
+ if (! mask) {\r
+ *string = 0;\r
+ return 0;\r
+ }\r
+\r
+ __int64 i, n;\r
+\r
+ /* SetProcessAffinityMask() accepts a mask of up to 64 processors. */\r
+ list_t set[64];\r
+ for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;\r
+\r
+ for (i = 0, n = 0; i < _countof(set); i++) {\r
+ if (mask & (1LL << i)) {\r
+ if (set[n].first == -1) set[n].first = set[n].last = (int) i;\r
+ else if (set[n].last == (int) i - 1) set[n].last = (int) i;\r
+ else {\r
+ n++;\r
+ set[n].first = set[n].last = (int) i;\r
+ }\r
+ }\r
+ }\r
+\r
+ /* Worst case is 2x2 characters for first and last CPU plus - and/or , */\r
+ size_t len = (size_t) (n + 1) * 6;\r
+ *string = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(TCHAR));\r
+ if (! string) return 2;\r
+\r
+ size_t s = 0;\r
+ int ret;\r
+ for (i = 0; i <= n; i++) {\r
+ if (i) (*string)[s++] = _T(',');\r
+ ret = _sntprintf_s(*string + s, 3, _TRUNCATE, _T("%u"), set[i].first);\r
+ if (ret < 0) {\r
+ HeapFree(GetProcessHeap(), 0, *string);\r
+ *string = 0;\r
+ return 3;\r
+ }\r
+ else s += ret;\r
+ if (set[i].last != set[i].first) {\r
+ ret =_sntprintf_s(*string + s, 4, _TRUNCATE, _T("%c%u"), (set[i].last == set[i].first + 1) ? _T(',') : _T('-'), set[i].last);\r
+ if (ret < 0) {\r
+ HeapFree(GetProcessHeap(), 0, *string);\r
+ *string = 0;\r
+ return 4;\r
+ }\r
+ else s += ret;\r
+ }\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+int affinity_string_to_mask(TCHAR *string, __int64 *mask) {\r
+ if (! mask) return 1;\r
+\r
+ *mask = 0LL;\r
+ if (! string) return 0;\r
+\r
+ list_t set[64];\r
+\r
+ TCHAR *s = string;\r
+ TCHAR *end;\r
+ int ret;\r
+ int i;\r
+ int n = 0;\r
+ unsigned long number;\r
+\r
+ for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;\r
+ n = 0;\r
+\r
+ while (*s) {\r
+ ret = str_number(s, &number, &end);\r
+ s = end;\r
+ if (ret == 0 || ret == 2) {\r
+ if (number >= _countof(set)) return 2;\r
+ set[n].first = set[n].last = (int) number;\r
+\r
+ switch (*s) {\r
+ case 0:\r
+ break;\r
+\r
+ case _T(','):\r
+ n++;\r
+ s++;\r
+ break;\r
+\r
+ case _T('-'):\r
+ if (! *(++s)) return 3;\r
+ ret = str_number(s, &number, &end);\r
+ if (ret == 0 || ret == 2) {\r
+ s = end;\r
+ if (! *s || *s == _T(',')) {\r
+ set[n].last = (int) number;\r
+ if (! *s) break;\r
+ n++;\r
+ s++;\r
+ }\r
+ else return 3;\r
+ }\r
+ else return 3;\r
+ break;\r
+\r
+ default:\r
+ return 3;\r
+ }\r
+ }\r
+ else return 4;\r
+ }\r
+\r
+ for (i = 0; i <= n; i++) {\r
+ for (int j = set[i].first; j <= set[i].last; j++) (__int64) *mask |= (1LL << (__int64) j);\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
inline unsigned long priority_mask() {\r
return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS;\r
}\r
bool inherit_handles = false;\r
if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
unsigned long flags = service->priority & priority_mask();\r
+ if (service->affinity) flags |= CREATE_SUSPENDED;\r
#ifdef UNICODE\r
flags |= CREATE_UNICODE_ENVIRONMENT;\r
#endif\r
\r
close_output_handles(&si);\r
\r
+ if (service->affinity) {\r
+ /*\r
+ We are explicitly storing service->affinity as a 64-bit unsigned integer\r
+ so that we can parse it regardless of whether we're running in 32-bit\r
+ or 64-bit mode. The arguments to SetProcessAffinityMask(), however, are\r
+ defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
+ (or when running the 32-bit NSSM).\r
+\r
+ The result is a lot of seemingly-unnecessary casting throughout the code\r
+ and potentially confusion when we actually try to start the service.\r
+ Having said that, however, it's unlikely that we're actually going to\r
+ run in 32-bit mode on a system which has more than 32 CPUs so the\r
+ likelihood of seeing a confusing situation is somewhat diminished.\r
+ */\r
+ DWORD_PTR affinity, system_affinity;\r
+\r
+ if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
+ else {\r
+ affinity = (DWORD_PTR) service->affinity;\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+ }\r
+\r
+ if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
+ log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+ }\r
+\r
+ ResumeThread(pi.hThread);\r
+ }\r
+\r
/*\r
Wait for a clean startup before changing the service status to RUNNING\r
but be mindful of the fact that we are blocking the service control manager\r
TCHAR flags[VALUE_LENGTH];\r
TCHAR dir[MAX_PATH];\r
TCHAR *env;\r
+ __int64 affinity;\r
unsigned long envlen;\r
TCHAR *env_extra;\r
unsigned long env_extralen;\r
void log_service_control(TCHAR *, unsigned long, bool);\r
unsigned long WINAPI service_control_handler(unsigned long, unsigned long, void *, void *);\r
\r
+int affinity_mask_to_string(__int64, TCHAR **);\r
+int affinity_string_to_mask(TCHAR *, __int64 *);\r
unsigned long priority_mask();\r
int priority_constant_to_index(unsigned long);\r
unsigned long priority_index_to_constant(int);\r
#include "nssm.h"
/* XXX: (value && value->string) is probably bogus because value is probably never null */
+/* Affinity. */
+#define NSSM_AFFINITY_ALL _T("All")
+
extern const TCHAR *exit_action_strings[];
extern const TCHAR *startup_strings[];
extern const TCHAR *priority_strings[];
-/* Does the parameter refer to the default value of the AppExit setting? */
-static inline int is_default_exit_action(const TCHAR *value) {
+/* Does the parameter refer to the default value of the setting? */
+static inline int is_default(const TCHAR *value) {
return (str_equiv(value, _T("default")) || str_equiv(value, _T("*")) || ! value[0]);
}
if (additional) {
/* Default action? */
- if (is_default_exit_action(additional)) code = 0;
+ if (is_default(additional)) code = 0;
else {
if (str_number(additional, &exitcode)) return -1;
code = (TCHAR *) additional;
unsigned long *code = 0;
if (additional) {
- if (! is_default_exit_action(additional)) {
+ if (! is_default(additional)) {
if (str_number(additional, &exitcode)) return -1;
code = &exitcode;
}
return 1;
}
+static int setting_set_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+ HKEY key = (HKEY) param;
+ if (! key) return -1;
+
+ long error;
+ __int64 mask;
+ __int64 system_affinity = 0LL;
+
+ if (value && value->string) {
+ DWORD_PTR affinity;
+ if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, (DWORD_PTR *) &system_affinity)) system_affinity = ~0;
+
+ if (is_default(value->string) || str_equiv(value->string, NSSM_AFFINITY_ALL)) mask = 0LL;
+ else if (affinity_string_to_mask(value->string, &mask)) {
+ print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, value->string, num_cpus() - 1);
+ return -1;
+ }
+ }
+ else mask = 0LL;
+
+ if (! mask) {
+ error = RegDeleteValue(key, name);
+ if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+ print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+ return -1;
+ }
+
+ /* Canonicalise. */
+ TCHAR *canon = 0;
+ if (affinity_mask_to_string(mask, &canon)) canon = value->string;
+
+ __int64 effective_affinity = mask & system_affinity;
+ if (effective_affinity != mask) {
+ /* Requested CPUs did not intersect with available CPUs? */
+ if (! effective_affinity) mask = effective_affinity = system_affinity;
+
+ TCHAR *system = 0;
+ if (! affinity_mask_to_string(system_affinity, &system)) {
+ TCHAR *effective = 0;
+ if (! affinity_mask_to_string(effective_affinity, &effective)) {
+ print_message(stderr, NSSM_MESSAGE_EFFECTIVE_AFFINITY_MASK, value->string, system, effective);
+ HeapFree(GetProcessHeap(), 0, effective);
+ }
+ HeapFree(GetProcessHeap(), 0, system);
+ }
+ }
+
+ if (RegSetValueEx(key, name, 0, REG_SZ, (const unsigned char *) canon, (unsigned long) (_tcslen(canon) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
+ if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, name, error_string(GetLastError()), 0);
+ return -1;
+ }
+
+ if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
+ return 1;
+}
+
+static int setting_get_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+ HKEY key = (HKEY) param;
+ if (! key) return -1;
+
+ unsigned long type;
+ TCHAR *buffer = 0;
+ unsigned long buflen = 0;
+
+ int ret = RegQueryValueEx(key, name, 0, &type, 0, &buflen);
+ if (ret == ERROR_FILE_NOT_FOUND) {
+ if (value_from_string(name, value, NSSM_AFFINITY_ALL) == 1) return 0;
+ return -1;
+ }
+ if (ret != ERROR_SUCCESS) return -1;
+
+ if (type != REG_SZ) return -1;
+
+ buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
+ if (! buffer) {
+ print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("affinity"), _T("setting_get_affinity"));
+ return -1;
+ }
+
+ if (expand_parameter(key, (TCHAR *) name, buffer, buflen, false, true)) {
+ HeapFree(GetProcessHeap(), 0, buffer);
+ return -1;
+ }
+
+ __int64 affinity;
+ if (affinity_string_to_mask(buffer, &affinity)) {
+ print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, buffer, num_cpus() - 1);
+ HeapFree(GetProcessHeap(), 0, buffer);
+ return -1;
+ }
+
+ HeapFree(GetProcessHeap(), 0, buffer);
+
+ /* Canonicalise. */
+ if (affinity_mask_to_string(affinity, &buffer)) {
+ if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+ return -1;
+ }
+
+ ret = value_from_string(name, value, buffer);
+ HeapFree(GetProcessHeap(), 0, buffer);
+ return ret;
+}
+
static int setting_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
HKEY key = (HKEY) param;
if (! param) return -1;
{ NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
{ NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
{ NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action },
+ { NSSM_REG_AFFINITY, REG_SZ, 0, false, 0, setting_set_affinity, setting_get_affinity },
{ NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
{ NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
{ NSSM_REG_PRIORITY, REG_SZ, (void *) priority_strings[NSSM_NORMAL_PRIORITY], false, 0, setting_set_priority, setting_get_priority },