* 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 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
+\r
Since version 2.22, NSSM can rotate existing output files when redirecting I/O.\r
\r
Since version 2.22, NSSM can set service display name, description, startup\r
If only the default action is set to Suicide NSSM will instead exit gracefully.\r
\r
\r
+Application priority\r
+--------------------\r
+NSSM can set the priority class of the managed application. NSSM will look in\r
+the registry under HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\r
+for the REG_DWORD entry AppPriority. Valid values correspond to arguments to\r
+SetProcessPriorityClass(). If AppPriority() is missing or invalid the\r
+application will be launched with normal priority.\r
+\r
+\r
Stopping the service\r
--------------------\r
When stopping a service NSSM will attempt several different methods of killing\r
nssm set <servicename> AppExit 2 Exit\r
\r
\r
+The AppPriority parameter is used to set the priority class of the\r
+managed application. Valid priorities are as follows:\r
+\r
+ REALTIME_PRIORITY_CLASS\r
+ HIGH_PRIORITY_CLASS\r
+ ABOVE_NORMAL_PRIORITY_CLASS\r
+ NORMAL_PRIORITY_CLASS\r
+ BELOW_NORMAL_PRIORITY_CLASS\r
+ IDLE_PRIORITY_CLASS\r
+\r
+\r
The Name parameter can only be queried, not set. It returns the service's\r
registry key name. This may be useful to know if you take advantage of\r
the fact that you can substitute the service's display name anywhere where\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
\r
Licence\r
-------\r
#include "nssm.h"\r
\r
-static enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS };\r
+static enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_PROCESS, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_NUM_TABS };\r
static HWND tablist[NSSM_NUM_TABS];\r
static int selected_tab;\r
\r
if (service->type & SERVICE_INTERACTIVE_PROCESS) SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_SETCHECK, BST_CHECKED, 0);\r
}\r
\r
+ /* Process tab. */\r
+ if (service->priority) {\r
+ int priority = priority_constant_to_index(service->priority);\r
+ combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+ SendMessage(combo, CB_SETCURSEL, priority, 0);\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
/* Remaining tabs are only for services we manage. */\r
if (service->native) return 0;\r
\r
+ /* Get process stuff. */\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
/* 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
/* Remaining tabs are only for services we manage. */\r
if (service->native) return 1;\r
\r
+ /* Process tab. */\r
+ tab.pszText = message_string(NSSM_GUI_TAB_PROCESS);\r
+ tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+ SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_PROCESS, (LPARAM) &tab);\r
+ tablist[NSSM_TAB_PROCESS] = dialog(MAKEINTRESOURCE(IDD_PROCESS), window, tab_dlg);\r
+ ShowWindow(tablist[NSSM_TAB_PROCESS], SW_HIDE);\r
+\r
+ /* Set defaults. */\r
+ combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+ SendMessage(combo, CB_INSERTSTRING, NSSM_REALTIME_PRIORITY, (LPARAM) message_string(NSSM_GUI_REALTIME_PRIORITY_CLASS));\r
+ SendMessage(combo, CB_INSERTSTRING, NSSM_HIGH_PRIORITY, (LPARAM) message_string(NSSM_GUI_HIGH_PRIORITY_CLASS));\r
+ SendMessage(combo, CB_INSERTSTRING, NSSM_ABOVE_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_ABOVE_NORMAL_PRIORITY_CLASS));\r
+ SendMessage(combo, CB_INSERTSTRING, NSSM_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_NORMAL_PRIORITY_CLASS));\r
+ SendMessage(combo, CB_INSERTSTRING, NSSM_BELOW_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_BELOW_NORMAL_PRIORITY_CLASS));\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
/* Shutdown tab. */\r
tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);\r
tab.cchTextMax = (int) _tcslen(tab.pszText);\r
#define NSSM_EXIT_UNCLEAN 3\r
#define NSSM_NUM_EXIT_ACTIONS 4\r
\r
+/* Process priority. */\r
+#define NSSM_REALTIME_PRIORITY 0\r
+#define NSSM_HIGH_PRIORITY 1\r
+#define NSSM_ABOVE_NORMAL_PRIORITY 2\r
+#define NSSM_NORMAL_PRIORITY 3\r
+#define NSSM_BELOW_NORMAL_PRIORITY 4\r
+#define NSSM_IDLE_PRIORITY 5\r
+\r
/* How many milliseconds to wait before updating service status. */\r
#define NSSM_SERVICE_STATUS_DEADLINE 20000\r
\r
}\r
\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
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
}\r
}\r
\r
+ /* Try to get priority - may fail. */\r
+ unsigned long priority;\r
+ if (get_number(key, NSSM_REG_PRIORITY, &priority) == 1) {\r
+ if (priority == (priority & priority_mask())) service->priority = priority;\r
+ else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_PRIORITY, service->name, NSSM_REG_PRIORITY, 0);\r
+ }\r
+\r
/* Try to get file rotation settings - may fail. */\r
unsigned long rotate_files;\r
if (get_number(key, NSSM_REG_ROTATE, &rotate_files, false) == 1) {\r
#define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds")\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_STDIO_LENGTH 29\r
\r
HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam);\r
#define IDD_SHUTDOWN 111\r
#define IDD_ENVIRONMENT 112\r
#define IDD_NATIVE 113\r
+#define IDD_PROCESS 114\r
#define IDC_PATH 1000\r
#define IDC_TAB1 1001\r
#define IDC_CANCEL 1002\r
#define IDC_USERNAME 1037\r
#define IDC_PASSWORD1 1038\r
#define IDC_PASSWORD2 1039\r
+#define IDC_PRIORITY 1040\r
\r
// Next default values for new objects\r
// \r
#ifdef APSTUDIO_INVOKED\r
#ifndef APSTUDIO_READONLY_SYMBOLS\r
-#define _APS_NEXT_RESOURCE_VALUE 114\r
+#define _APS_NEXT_RESOURCE_VALUE 115\r
#define _APS_NEXT_COMMAND_VALUE 40001\r
-#define _APS_NEXT_CONTROL_VALUE 1040\r
+#define _APS_NEXT_CONTROL_VALUE 1041\r
#define _APS_NEXT_SYMED_VALUE 101\r
#endif\r
#endif\r
\r
const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\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
+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
+\r
+int priority_constant_to_index(unsigned long constant) {\r
+ switch (constant & priority_mask()) {\r
+ case REALTIME_PRIORITY_CLASS: return NSSM_REALTIME_PRIORITY;\r
+ case HIGH_PRIORITY_CLASS: return NSSM_HIGH_PRIORITY;\r
+ case ABOVE_NORMAL_PRIORITY_CLASS: return NSSM_ABOVE_NORMAL_PRIORITY;\r
+ case BELOW_NORMAL_PRIORITY_CLASS: return NSSM_BELOW_NORMAL_PRIORITY;\r
+ case IDLE_PRIORITY_CLASS: return NSSM_IDLE_PRIORITY;\r
+ }\r
+ return NSSM_NORMAL_PRIORITY;\r
+}\r
+\r
+unsigned long priority_index_to_constant(int index) {\r
+ switch (index) {\r
+ case NSSM_REALTIME_PRIORITY: return REALTIME_PRIORITY_CLASS;\r
+ case NSSM_HIGH_PRIORITY: return HIGH_PRIORITY_CLASS;\r
+ case NSSM_ABOVE_NORMAL_PRIORITY: return ABOVE_NORMAL_PRIORITY_CLASS;\r
+ case NSSM_BELOW_NORMAL_PRIORITY: return BELOW_NORMAL_PRIORITY_CLASS;\r
+ case NSSM_IDLE_PRIORITY: return IDLE_PRIORITY_CLASS;\r
+ }\r
+ return NORMAL_PRIORITY_CLASS;\r
+}\r
\r
static inline int throttle_milliseconds(unsigned long throttle) {\r
/* pow() operates on doubles. */\r
if (! service) return;\r
\r
service->type = SERVICE_WIN32_OWN_PROCESS;\r
+ service->priority = NORMAL_PRIORITY_CLASS;\r
service->stdin_sharing = NSSM_STDIN_SHARING;\r
service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
service->stdin_flags = NSSM_STDIN_FLAGS;\r
\r
bool inherit_handles = false;\r
if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
- unsigned long flags = 0;\r
+ unsigned long flags = service->priority & priority_mask();\r
#ifdef UNICODE\r
flags |= CREATE_UNICODE_ENVIRONMENT;\r
#endif\r
unsigned long envlen;\r
TCHAR *env_extra;\r
unsigned long env_extralen;\r
+ unsigned long priority;\r
TCHAR stdin_path[MAX_PATH];\r
unsigned long stdin_sharing;\r
unsigned long stdin_disposition;\r
void log_service_control(TCHAR *, unsigned long, bool);\r
unsigned long WINAPI service_control_handler(unsigned long, unsigned long, void *, void *);\r
\r
+unsigned long priority_mask();\r
+int priority_constant_to_index(unsigned long);\r
+unsigned long priority_index_to_constant(int);\r
+\r
nssm_service_t *alloc_nssm_service();\r
void set_nssm_service_defaults(nssm_service_t *);\r
void cleanup_nssm_service(nssm_service_t *);\r
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) {
return ret;
}
+static int setting_set_priority(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;
+
+ TCHAR *priority_string;
+ int i;
+ long error;
+
+ if (value && value->string) priority_string = value->string;
+ else if (default_value) priority_string = (TCHAR *) default_value;
+ else {
+ 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;
+ }
+
+ for (i = 0; priority_strings[i]; i++) {
+ if (! str_equiv(priority_strings[i], priority_string)) continue;
+
+ if (default_value && str_equiv(priority_string, (TCHAR *) default_value)) {
+ 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;
+ }
+
+ if (set_number(key, (TCHAR *) name, priority_index_to_constant(i))) return -1;
+ return 1;
+ }
+
+ print_message(stderr, NSSM_MESSAGE_INVALID_PRIORITY, priority_string);
+ for (i = 0; priority_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), priority_strings[i]);
+
+ return -1;
+}
+
+static int setting_get_priority(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;
+
+ unsigned long constant;
+ switch (get_number(key, (TCHAR *) name, &constant, false)) {
+ case 0: return value_from_string(name, value, (const TCHAR *) default_value);
+ case -1: return -1;
+ }
+
+ return value_from_string(name, value, priority_strings[priority_constant_to_index(constant)]);
+}
+
/* Functions to manage native service settings. */
int native_set_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
SC_HANDLE service_handle = (SC_HANDLE) param;
{ 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_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 },
{ NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
{ NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number },