├── .gitattributes ├── CODEOWNERS ├── ChangeLog.txt ├── README.txt ├── account.cpp ├── account.h ├── appveyor.yml ├── console.cpp ├── console.h ├── env.cpp ├── env.h ├── event.cpp ├── event.h ├── gui.cpp ├── gui.h ├── hook.cpp ├── hook.h ├── imports.cpp ├── imports.h ├── io.cpp ├── io.h ├── messages.mc ├── nssm.cpp ├── nssm.h ├── nssm.ico ├── nssm.rc ├── nssm.sln ├── nssm.vcproj ├── nssm.vcxproj ├── process.cpp ├── process.h ├── registry.cpp ├── registry.h ├── resource.h ├── service.cpp ├── service.h ├── settings.cpp ├── settings.h ├── utf8.cpp ├── utf8.h └── version.cmd /.gitattributes: -------------------------------------------------------------------------------- 1 | *.mc diff 2 | *.rc diff 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # should be maintained by the team responsible for the agent 2 | * @puppetlabs/phoenix 3 | # former Windows team also has an interest in this 4 | * @puppetlabs/windows 5 | -------------------------------------------------------------------------------- /ChangeLog.txt: -------------------------------------------------------------------------------- 1 | Changes since 2.24 2 | ------------------ 3 | * Allow skipping kill_process_tree(). 4 | 5 | * NSSM can now sleep a configurable amount of time after 6 | rotating output files. 7 | 8 | * NSSM can now rotate log files by calling CopyFile() 9 | followed by SetEndOfFile(), allowing it to rotate files 10 | which other processes hold open. 11 | 12 | * NSSM now sets the service environment before querying 13 | parameters from the registry, so paths and arguments 14 | can reference environment configured in AppEnvironment 15 | or AppEnvironmentExtra. 16 | 17 | Changes since 2.23 18 | ------------------ 19 | * NSSM once again calls TerminateProcess() correctly. 20 | 21 | Changes since 2.22 22 | ------------------ 23 | * NSSM no longer clutters the event log with "The specified 24 | procedure could not be found" on legacy Windows releases. 25 | 26 | * Fixed failure to set a local username to run the service. 27 | 28 | Changes since 2.21 29 | ------------------ 30 | * Existing services can now be managed using the GUI 31 | or on the command line. 32 | 33 | * NSSM can now set the priority class and processor 34 | affinity of the managed application. 35 | 36 | * NSSM can now apply an unconditional delay before 37 | restarting the application. 38 | 39 | * NSSM can now optionally rotate existing files when 40 | redirecting I/O. 41 | 42 | * Unqualified path names are now relative to the 43 | application startup directory when redirecting I/O. 44 | 45 | * NSSM can now set the service display name, description, 46 | startup type and log on details. 47 | 48 | * All services now receive a standard console window, 49 | allowing them to read input correctly (if running in 50 | interactive mode). 51 | 52 | Changes since 2.20 53 | ------------------ 54 | * Services installed from the GUI no longer have incorrect 55 | AppParameters set in the registry. 56 | 57 | Changes since 2.19 58 | ------------------ 59 | * Services installed from the commandline without using the 60 | GUI no longer have incorrect AppStopMethod* registry 61 | entries set. 62 | 63 | Changes since 2.18 64 | ------------------ 65 | * Support AppEnvironmentExtra to append to the environment 66 | instead of replacing it. 67 | 68 | * The GUI is significantly less sucky. 69 | 70 | Changes since 2.17 71 | ------------------ 72 | * Timeouts for each shutdown method can be configured in 73 | the registry. 74 | 75 | * The GUI is slightly less sucky. 76 | 77 | Changes since 2.16 78 | ------------------ 79 | * NSSM can now redirect the service's I/O streams to any path 80 | capable of being opened by CreateFile(). 81 | 82 | * Allow building on Visual Studio Express. 83 | 84 | * Silently ignore INTERROGATE control. 85 | 86 | * Try to send Control-C events to console applications when 87 | shutting them down. 88 | 89 | Changes since 2.15 90 | ------------------ 91 | * Fixed case where NSSM could kill unrelated processes when 92 | shutting down. 93 | 94 | Changes since 2.14 95 | ------------------ 96 | * NSSM is now translated into Italian. 97 | 98 | * Fixed GUI not allowing paths longer than 256 characters. 99 | 100 | Changes since 2.13 101 | ------------------ 102 | * Fixed default GUI language being French not English. 103 | 104 | Changes since 2.12 105 | ------------------ 106 | * Fixed failure to run on Windows 2000. 107 | 108 | Changes since 2.11 109 | ------------------ 110 | * NSSM is now translated into French. 111 | 112 | * Really ensure systems recovery actions can happen. 113 | 114 | The change supposedly introduced in v2.4 to allow service recovery 115 | actions to be activated when the application exits gracefully with 116 | a non-zero error code didn't actually work. 117 | 118 | Changes since 2.10 119 | ------------------ 120 | * Support AppEnvironment for compatibility with srvany. 121 | 122 | Changes since 2.9 123 | ----------------- 124 | * Fixed failure to compile messages.mc in paths containing spaces. 125 | 126 | * Fixed edge case with CreateProcess(). 127 | 128 | Correctly handle the case where the application executable is under 129 | a path which contains space and an executable sharing the initial 130 | part of that path (up to a space) exists. 131 | 132 | Changes since 2.8 133 | ----------------- 134 | * Fixed failure to run on Windows versions prior to Vista. 135 | 136 | Changes since 2.7 137 | ----------------- 138 | * Read Application, AppDirectory and AppParameters before each restart so 139 | a change to any one doesn't require restarting NSSM itself. 140 | 141 | * Fixed messages not being sent to the event log correctly in some 142 | cases. 143 | 144 | * Try to handle (strictly incorrect) quotes in AppDirectory. 145 | 146 | Windows directories aren't allowed to contain quotes so CreateProcess() 147 | will fail if the AppDirectory is quoted. Note that it succeeds even if 148 | Application itself is quoted as the application plus parameters are 149 | interpreted as a command line. 150 | 151 | * Fixed failed to write full arguments to AppParameters when 152 | installing a service. 153 | 154 | * Throttle restarts. 155 | 156 | Back off from restarting the application immediately if it starts 157 | successfully but exits too soon. The default value of "too soon" is 158 | 1500 milliseconds. This can be configured by adding a DWORD value 159 | AppThrottle to the registry. 160 | 161 | Handle resume messages from the service console to restart the 162 | application immediately even if it is throttled. 163 | 164 | * Try to kill the process tree gracefully. 165 | 166 | Before calling TerminateProcess() on all processes assocatiated with 167 | the monitored application, enumerate all windows and threads and 168 | post appropriate messages to them. If the application bothers to 169 | listen for such messages it has a chance to shut itself down gracefully. 170 | 171 | Changes since 2.6 172 | ----------------- 173 | * Handle missing registry values. 174 | 175 | Warn if AppParameters is missing. Warn if AppDirectory is missing or 176 | unset and choose a fallback directory. 177 | First try to find the parent directory of the application. If that 178 | fails, eg because the application path is just "notepad" or something, 179 | start in the Windows directory. 180 | 181 | * Kill process tree when stopping service. 182 | 183 | Ensure that all child processes of the monitored application are 184 | killed when the service stops by recursing through all running 185 | processes and terminating those whose parent is the application 186 | or one of its descendents. 187 | 188 | Changes since 2.5 189 | ----------------- 190 | * Removed incorrect ExpandEnvironmentStrings() error. 191 | 192 | A log_event() call was inadvertently left in the code causing an error 193 | to be set to the eventlog saying that ExpandEnvironmentStrings() had 194 | failed when it had actually succeeded. 195 | 196 | Changes since 2.4 197 | ----------------- 198 | * Allow use of REG_EXPAND_SZ values in the registry. 199 | 200 | * Don't suicide on exit status 0 by default. 201 | 202 | Suiciding when the application exits 0 will cause recovery actions to be 203 | taken. Usually this is inappropriate. Only suicide if there is an 204 | explicit AppExit value for 0 in the registry. 205 | 206 | Technically such behaviour could be abused to do something like run a 207 | script after successful completion of a service but in most cases a 208 | suicide is undesirable when no actual failure occurred. 209 | 210 | * Don't hang if startup parameters couldn't be determined. 211 | Instead, signal that the service entered the STOPPED state. 212 | Set START_PENDING state prior to actual startup. 213 | 214 | Changes since 2.3 215 | ----------------- 216 | * Ensure systems recovery actions can happen. 217 | 218 | In Windows versions earlier than Vista the service manager would only 219 | consider a service failed (and hence eligible for recovery action) if 220 | the service exited without setting its state to SERVICE_STOPPED, even if 221 | it signalled an error exit code. 222 | In Vista and later the service manager can be configured to treat a 223 | graceful shutdown with error code as a failure but this is not the 224 | default behaviour. 225 | 226 | Try to configure the service manager to use the new behaviour when 227 | starting the service so users who set AppExit to Exit can use recovery 228 | actions as expected. 229 | 230 | Also recognise the new AppExit option Suicide for use on pre-Vista 231 | systems. When AppExit is Suicide don't stop the service but exit 232 | inelegantly, which should be seen as a failure. 233 | 234 | Changes since 2.2 235 | ----------------- 236 | * Send properly formatted messages to the event log. 237 | 238 | * Fixed truncation of very long path lengths in the registry. 239 | 240 | Changes since 2.1 241 | ----------------- 242 | * Decide how to handle application exit. 243 | 244 | When the service exits with exit code n look in 245 | HKLM\SYSTEM\CurrentControlSet\Services\\Parameters\AppExit\, 246 | falling back to the unnamed value if no such code is listed. Parse the 247 | (string) value of this entry as follows: 248 | 249 | Restart: Start the application again (NSSM default). 250 | Ignore: Do nothing (srvany default). 251 | Exit: Stop the service. 252 | 253 | Changes since 2.0 254 | ----------------- 255 | * Added support for building a 64-bit executable. 256 | 257 | * Added project files for newer versions of Visual Studio. 258 | -------------------------------------------------------------------------------- /account.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | #include 4 | 5 | #ifndef STATUS_SUCCESS 6 | #define STATUS_SUCCESS ERROR_SUCCESS 7 | #endif 8 | 9 | extern imports_t imports; 10 | 11 | /* Open Policy object. */ 12 | int open_lsa_policy(LSA_HANDLE *policy) { 13 | LSA_OBJECT_ATTRIBUTES attributes; 14 | ZeroMemory(&attributes, sizeof(attributes)); 15 | 16 | NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy); 17 | if (status != STATUS_SUCCESS) { 18 | print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status))); 19 | return 1; 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | /* Look up SID for an account. */ 26 | int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) { 27 | LSA_HANDLE handle; 28 | if (! policy) { 29 | policy = &handle; 30 | if (open_lsa_policy(policy)) return 1; 31 | } 32 | 33 | /* 34 | LsaLookupNames() can't look up .\username but can look up 35 | %COMPUTERNAME%\username. ChangeServiceConfig() writes .\username to the 36 | registry when %COMPUTERNAME%\username is a passed as a parameter. We 37 | need to preserve .\username when calling ChangeServiceConfig() without 38 | changing the username, but expand to %COMPUTERNAME%\username when calling 39 | LsaLookupNames(). 40 | */ 41 | TCHAR *expanded; 42 | unsigned long expandedlen; 43 | if (_tcsnicmp(_T(".\\"), username, 2)) { 44 | expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR); 45 | expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen); 46 | if (! expanded) { 47 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid")); 48 | if (policy == &handle) LsaClose(handle); 49 | return 2; 50 | } 51 | memmove(expanded, username, expandedlen); 52 | } 53 | else { 54 | TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1]; 55 | expandedlen = _countof(computername); 56 | GetComputerName(computername, &expandedlen); 57 | expandedlen += (unsigned long) _tcslen(username); 58 | 59 | expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR)); 60 | if (! expanded) { 61 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid")); 62 | if (policy == &handle) LsaClose(handle); 63 | return 2; 64 | } 65 | _sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2); 66 | } 67 | 68 | LSA_UNICODE_STRING lsa_username; 69 | int ret = to_utf16(expanded, &lsa_username.Buffer, (unsigned long *) &lsa_username.Length); 70 | HeapFree(GetProcessHeap(), 0, expanded); 71 | if (ret) { 72 | if (policy == &handle) LsaClose(handle); 73 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()")); 74 | return 4; 75 | } 76 | lsa_username.Length *= sizeof(wchar_t); 77 | lsa_username.MaximumLength = lsa_username.Length + sizeof(wchar_t); 78 | 79 | LSA_REFERENCED_DOMAIN_LIST *translated_domains; 80 | LSA_TRANSLATED_SID *translated_sid; 81 | NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid); 82 | HeapFree(GetProcessHeap(), 0, lsa_username.Buffer); 83 | if (policy == &handle) LsaClose(handle); 84 | if (status != STATUS_SUCCESS) { 85 | LsaFreeMemory(translated_domains); 86 | LsaFreeMemory(translated_sid); 87 | print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status))); 88 | return 5; 89 | } 90 | 91 | if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) { 92 | if (translated_sid->Use != SidTypeUnknown || _tcsnicmp(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN _T("\\"), username, _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + 1)) { 93 | LsaFreeMemory(translated_domains); 94 | LsaFreeMemory(translated_sid); 95 | print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); 96 | return 6; 97 | } 98 | } 99 | 100 | LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex]; 101 | if (! trust || ! IsValidSid(trust->Sid)) { 102 | LsaFreeMemory(translated_domains); 103 | LsaFreeMemory(translated_sid); 104 | print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); 105 | return 7; 106 | } 107 | 108 | /* GetSidSubAuthority*() return pointers! */ 109 | unsigned char *n = GetSidSubAuthorityCount(trust->Sid); 110 | 111 | /* Convert translated SID to SID. */ 112 | *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1)); 113 | if (! *sid) { 114 | LsaFreeMemory(translated_domains); 115 | LsaFreeMemory(translated_sid); 116 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid")); 117 | return 8; 118 | } 119 | 120 | unsigned long error; 121 | if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) { 122 | error = GetLastError(); 123 | HeapFree(GetProcessHeap(), 0, *sid); 124 | LsaFreeMemory(translated_domains); 125 | LsaFreeMemory(translated_sid); 126 | print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error)); 127 | return 9; 128 | } 129 | 130 | for (unsigned char i = 0; i <= *n; i++) { 131 | unsigned long *sub = GetSidSubAuthority(*sid, i); 132 | if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i); 133 | else *sub = translated_sid->RelativeId; 134 | } 135 | 136 | ret = 0; 137 | if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) { 138 | print_message(stderr, NSSM_GUI_INVALID_USERNAME, username); 139 | ret = 10; 140 | } 141 | 142 | LsaFreeMemory(translated_domains); 143 | LsaFreeMemory(translated_sid); 144 | 145 | return ret; 146 | } 147 | 148 | int username_sid(const TCHAR *username, SID **sid) { 149 | return username_sid(username, sid, 0); 150 | } 151 | 152 | int canonicalise_username(const TCHAR *username, TCHAR **canon) { 153 | LSA_HANDLE policy; 154 | if (open_lsa_policy(&policy)) return 1; 155 | 156 | SID *sid; 157 | if (username_sid(username, &sid, &policy)) return 2; 158 | PSID sids = { sid }; 159 | 160 | LSA_REFERENCED_DOMAIN_LIST *translated_domains; 161 | LSA_TRANSLATED_NAME *translated_name; 162 | NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name); 163 | if (status != STATUS_SUCCESS) { 164 | LsaFreeMemory(translated_domains); 165 | LsaFreeMemory(translated_name); 166 | print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status))); 167 | return 3; 168 | } 169 | 170 | LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex]; 171 | LSA_UNICODE_STRING lsa_canon; 172 | lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t); 173 | lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t); 174 | lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength); 175 | if (! lsa_canon.Buffer) { 176 | LsaFreeMemory(translated_domains); 177 | LsaFreeMemory(translated_name); 178 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid")); 179 | return 9; 180 | } 181 | 182 | /* Buffer is wchar_t but Length is in bytes. */ 183 | memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length); 184 | memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t)); 185 | memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length); 186 | 187 | unsigned long canonlen; 188 | if (from_utf16(lsa_canon.Buffer, canon, &canonlen)) { 189 | LsaFreeMemory(translated_domains); 190 | LsaFreeMemory(translated_name); 191 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid")); 192 | return 10; 193 | } 194 | HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer); 195 | 196 | LsaFreeMemory(translated_domains); 197 | LsaFreeMemory(translated_name); 198 | 199 | return 0; 200 | } 201 | 202 | /* Do two usernames map to the same SID? */ 203 | int username_equiv(const TCHAR *a, const TCHAR *b) { 204 | SID *sid_a, *sid_b; 205 | if (username_sid(a, &sid_a)) return 0; 206 | 207 | if (username_sid(b, &sid_b)) { 208 | FreeSid(sid_a); 209 | return 0; 210 | } 211 | 212 | int ret = 0; 213 | if (EqualSid(sid_a, sid_b)) ret = 1; 214 | 215 | FreeSid(sid_a); 216 | FreeSid(sid_b); 217 | 218 | return ret; 219 | } 220 | 221 | /* Does the username represent the LocalSystem account? */ 222 | int is_localsystem(const TCHAR *username) { 223 | if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1; 224 | if (! imports.IsWellKnownSid) return 0; 225 | 226 | SID *sid; 227 | if (username_sid(username, &sid)) return 0; 228 | 229 | int ret = 0; 230 | if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1; 231 | 232 | FreeSid(sid); 233 | 234 | return ret; 235 | } 236 | 237 | /* Build the virtual account name. */ 238 | TCHAR *virtual_account(const TCHAR *service_name) { 239 | size_t len = _tcslen(NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN) + _tcslen(service_name) + 2; 240 | TCHAR *name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); 241 | if (! name) { 242 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("name"), _T("virtual_account")); 243 | return 0; 244 | } 245 | 246 | _sntprintf_s(name, len, _TRUNCATE, _T("%s\\%s"), NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN, service_name); 247 | return name; 248 | } 249 | 250 | /* Does the username represent a virtual account for the service? */ 251 | int is_virtual_account(const TCHAR *service_name, const TCHAR *username) { 252 | if (! imports.IsWellKnownSid) return 0; 253 | if (! service_name) return 0; 254 | if (! username) return 0; 255 | 256 | TCHAR *canon = virtual_account(service_name); 257 | int ret = str_equiv(canon, username); 258 | HeapFree(GetProcessHeap(), 0, canon); 259 | return ret; 260 | } 261 | 262 | /* 263 | Get well-known alias for LocalSystem and friends. 264 | Returns a pointer to a static string. DO NOT try to free it. 265 | */ 266 | const TCHAR *well_known_sid(SID *sid) { 267 | if (! imports.IsWellKnownSid) return 0; 268 | if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT; 269 | if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT; 270 | if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT; 271 | return 0; 272 | } 273 | 274 | const TCHAR *well_known_username(const TCHAR *username) { 275 | if (! username) return NSSM_LOCALSYSTEM_ACCOUNT; 276 | if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT; 277 | SID *sid; 278 | if (username_sid(username, &sid)) return 0; 279 | 280 | const TCHAR *well_known = well_known_sid(sid); 281 | FreeSid(sid); 282 | 283 | return well_known; 284 | } 285 | 286 | int grant_logon_as_service(const TCHAR *username) { 287 | if (! username) return 0; 288 | 289 | /* Open Policy object. */ 290 | LSA_OBJECT_ATTRIBUTES attributes; 291 | ZeroMemory(&attributes, sizeof(attributes)); 292 | 293 | LSA_HANDLE policy; 294 | NTSTATUS status; 295 | 296 | if (open_lsa_policy(&policy)) return 1; 297 | 298 | /* Look up SID for the account. */ 299 | SID *sid; 300 | if (username_sid(username, &sid, &policy)) { 301 | LsaClose(policy); 302 | return 2; 303 | } 304 | 305 | /* 306 | Shouldn't happen because it should have been checked before callling this function. 307 | */ 308 | if (well_known_sid(sid)) { 309 | LsaClose(policy); 310 | return 3; 311 | } 312 | 313 | /* Check if the SID has the "Log on as a service" right. */ 314 | LSA_UNICODE_STRING lsa_right; 315 | lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT; 316 | lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t); 317 | lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t); 318 | 319 | LSA_UNICODE_STRING *rights; 320 | unsigned long count = ~0; 321 | status = LsaEnumerateAccountRights(policy, sid, &rights, &count); 322 | if (status != STATUS_SUCCESS) { 323 | /* 324 | If the account has no rights set LsaEnumerateAccountRights() will return 325 | STATUS_OBJECT_NAME_NOT_FOUND and set count to 0. 326 | */ 327 | unsigned long error = LsaNtStatusToWinError(status); 328 | if (error != ERROR_FILE_NOT_FOUND) { 329 | FreeSid(sid); 330 | LsaClose(policy); 331 | print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error)); 332 | return 4; 333 | } 334 | } 335 | 336 | for (unsigned long i = 0; i < count; i++) { 337 | if (rights[i].Length != lsa_right.Length) continue; 338 | if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue; 339 | /* The SID has the right. */ 340 | FreeSid(sid); 341 | LsaFreeMemory(rights); 342 | LsaClose(policy); 343 | return 0; 344 | } 345 | LsaFreeMemory(rights); 346 | 347 | /* Add the right. */ 348 | status = LsaAddAccountRights(policy, sid, &lsa_right, 1); 349 | FreeSid(sid); 350 | LsaClose(policy); 351 | if (status != STATUS_SUCCESS) { 352 | print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status))); 353 | return 5; 354 | } 355 | 356 | print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username); 357 | return 0; 358 | } 359 | -------------------------------------------------------------------------------- /account.h: -------------------------------------------------------------------------------- 1 | #ifndef ACCOUNT_H 2 | #define ACCOUNT_H 3 | 4 | #include 5 | 6 | /* Not really an account. The canonical name is NT Authority\System. */ 7 | #define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem") 8 | /* Other well-known accounts which can start a service without a password. */ 9 | #define NSSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService") 10 | #define NSSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService") 11 | /* Virtual service accounts. */ 12 | #define NSSM_VIRTUAL_SERVICE_ACCOUNT_DOMAIN _T("NT Service") 13 | /* This is explicitly a wide string. */ 14 | #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight" 15 | 16 | int open_lsa_policy(LSA_HANDLE *); 17 | int username_sid(const TCHAR *, SID **, LSA_HANDLE *); 18 | int username_sid(const TCHAR *, SID **); 19 | int username_equiv(const TCHAR *, const TCHAR *); 20 | int canonicalise_username(const TCHAR *, TCHAR **); 21 | int is_localsystem(const TCHAR *); 22 | TCHAR *virtual_account(const TCHAR *); 23 | int is_virtual_account(const TCHAR *, const TCHAR *); 24 | const TCHAR *well_known_sid(SID *); 25 | const TCHAR *well_known_username(const TCHAR *); 26 | int grant_logon_as_service(const TCHAR *); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 2.24.{build} 2 | clone_depth: 10 3 | matrix: 4 | fast_finish: false 5 | 6 | environment: 7 | matrix: 8 | # VS 2017 + SDK 8.1 - Primary x64 target 9 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 10 | PlatformToolset: v141 11 | TargetPlatformVersion: 8.1 12 | Platform: x64 13 | # VS 2017 + SDK 8.1 - Primary x86 target 14 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 15 | PlatformToolset: v141 16 | TargetPlatformVersion: 8.1 17 | Platform: Win32 18 | # VS 2015 + SDK 8.1 19 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 20 | PlatformToolset: v140 21 | TargetPlatformVersion: 8.1 22 | Platform: x64 23 | # VS 2017 + SDK 10 24 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 25 | PlatformToolset: v141 26 | TargetPlatformVersion: 10.0.15063.0 27 | Platform: x64 28 | 29 | before_build: 30 | - | 31 | chcp 32 | SET 33 | 34 | build_script: 35 | - msbuild nssm.vcxproj /detailedsummary /p:Configuration=Release /p:Platform=%Platform% /p:PlatformToolset=%PlatformToolset% /p:TargetPlatformVersion=%TargetPlatformVersion% 36 | 37 | notifications: 38 | - provider: Email 39 | to: 40 | - nobody@nowhere.com 41 | on_build_success: false 42 | on_build_failure: false 43 | on_build_status_changed: false 44 | -------------------------------------------------------------------------------- /console.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | /* See if we were launched from a console window. */ 4 | bool check_console() { 5 | /* If we're running in a service context there will be no console window. */ 6 | HWND console = GetConsoleWindow(); 7 | if (! console) return false; 8 | 9 | unsigned long pid; 10 | if (! GetWindowThreadProcessId(console, &pid)) return false; 11 | 12 | /* 13 | If the process associated with the console window handle is the same as 14 | this process, we were not launched from an existing console. The user 15 | probably double-clicked our executable. 16 | */ 17 | if (GetCurrentProcessId() != pid) return true; 18 | 19 | /* We close our new console so that subsequent messages appear in a popup. */ 20 | FreeConsole(); 21 | return false; 22 | } 23 | 24 | void alloc_console(nssm_service_t *service) { 25 | if (service->no_console) return; 26 | 27 | AllocConsole(); 28 | } 29 | -------------------------------------------------------------------------------- /console.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSOLE_H 2 | #define CONSOLE_H 3 | 4 | bool check_console(); 5 | void alloc_console(nssm_service_t *); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /env.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | /* 4 | Environment block is of the form: 5 | 6 | KEY1=VALUE1 NULL 7 | KEY2=VALUE2 NULL 8 | NULL 9 | 10 | A single variable KEY=VALUE has length 15: 11 | 12 | KEY=VALUE (13) NULL (1) 13 | NULL (1) 14 | 15 | Environment variable names are case-insensitive! 16 | */ 17 | 18 | /* Find the length in characters of an environment block. */ 19 | size_t environment_length(TCHAR *env) { 20 | size_t len = 0; 21 | 22 | TCHAR *s; 23 | for (s = env; ; s++) { 24 | len++; 25 | if (*s == _T('\0')) { 26 | if (*(s + 1) == _T('\0')) { 27 | len++; 28 | break; 29 | } 30 | } 31 | } 32 | 33 | return len; 34 | } 35 | 36 | /* Copy an environment block. */ 37 | TCHAR *copy_environment_block(TCHAR *env) { 38 | TCHAR *newenv; 39 | if (copy_double_null(env, (unsigned long) environment_length(env), &newenv)) return 0; 40 | return newenv; 41 | } 42 | 43 | /* 44 | The environment block starts with variables of the form 45 | =C:=C:\Windows\System32 which we ignore. 46 | */ 47 | TCHAR *useful_environment(TCHAR *rawenv) { 48 | TCHAR *env = rawenv; 49 | 50 | if (env) { 51 | while (*env == _T('=')) { 52 | for ( ; *env; env++); 53 | env++; 54 | } 55 | } 56 | 57 | return env; 58 | } 59 | 60 | /* Expand an environment variable. Must call HeapFree() on the result. */ 61 | TCHAR *expand_environment_string(TCHAR *string) { 62 | unsigned long len; 63 | 64 | len = ExpandEnvironmentStrings(string, 0, 0); 65 | if (! len) { 66 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0); 67 | return 0; 68 | } 69 | 70 | TCHAR *ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR)); 71 | if (! ret) { 72 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("ExpandEnvironmentStrings()"), _T("expand_environment_string"), 0); 73 | return 0; 74 | } 75 | 76 | if (! ExpandEnvironmentStrings(string, ret, len)) { 77 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0); 78 | HeapFree(GetProcessHeap(), 0, ret); 79 | return 0; 80 | } 81 | 82 | return ret; 83 | } 84 | 85 | /* 86 | Set all the environment variables from an environment block in the current 87 | environment or remove all the variables in the block from the current 88 | environment. 89 | */ 90 | static int set_environment_block(TCHAR *env, bool set) { 91 | int ret = 0; 92 | 93 | TCHAR *s, *t; 94 | for (s = env; *s; s++) { 95 | for (t = s; *t && *t != _T('='); t++); 96 | if (*t == _T('=')) { 97 | *t = _T('\0'); 98 | if (set) { 99 | TCHAR *expanded = expand_environment_string(++t); 100 | if (expanded) { 101 | if (! SetEnvironmentVariable(s, expanded)) ret++; 102 | HeapFree(GetProcessHeap(), 0, expanded); 103 | } 104 | else { 105 | if (! SetEnvironmentVariable(s, t)) ret++; 106 | } 107 | } 108 | else { 109 | if (! SetEnvironmentVariable(s, NULL)) ret++; 110 | } 111 | for (t++; *t; t++); 112 | } 113 | s = t; 114 | } 115 | 116 | return ret; 117 | } 118 | 119 | int set_environment_block(TCHAR *env) { 120 | return set_environment_block(env, true); 121 | } 122 | 123 | static int unset_environment_block(TCHAR *env) { 124 | return set_environment_block(env, false); 125 | } 126 | 127 | /* Remove all variables from the process environment. */ 128 | int clear_environment() { 129 | TCHAR *rawenv = GetEnvironmentStrings(); 130 | TCHAR *env = useful_environment(rawenv); 131 | 132 | int ret = unset_environment_block(env); 133 | 134 | if (rawenv) FreeEnvironmentStrings(rawenv); 135 | 136 | return ret; 137 | } 138 | 139 | /* Set the current environment to exactly duplicate an environment block. */ 140 | int duplicate_environment(TCHAR *rawenv) { 141 | int ret = clear_environment(); 142 | TCHAR *env = useful_environment(rawenv); 143 | ret += set_environment_block(env); 144 | return ret; 145 | } 146 | 147 | /* 148 | Verify an environment block. 149 | Returns: 1 if environment is invalid. 150 | 0 if environment is OK. 151 | -1 on error. 152 | */ 153 | int test_environment(TCHAR *env) { 154 | TCHAR *path = (TCHAR *) nssm_imagepath(); 155 | STARTUPINFO si; 156 | ZeroMemory(&si, sizeof(si)); 157 | si.cb = sizeof(si); 158 | PROCESS_INFORMATION pi; 159 | ZeroMemory(&pi, sizeof(pi)); 160 | unsigned long flags = CREATE_SUSPENDED; 161 | #ifdef UNICODE 162 | flags |= CREATE_UNICODE_ENVIRONMENT; 163 | #endif 164 | 165 | /* 166 | Try to relaunch ourselves but with the candidate environment set. 167 | Assuming no solar flare activity, the only reason this would fail is if 168 | the environment were invalid. 169 | */ 170 | if (CreateProcess(0, path, 0, 0, 0, flags, env, 0, &si, &pi)) { 171 | TerminateProcess(pi.hProcess, 0); 172 | } 173 | else { 174 | unsigned long error = GetLastError(); 175 | if (error == ERROR_INVALID_PARAMETER) return 1; 176 | else return -1; 177 | } 178 | 179 | return 0; 180 | } 181 | 182 | /* 183 | Duplicate an environment block returned by GetEnvironmentStrings(). 184 | Since such a block is by definition readonly, and duplicate_environment() 185 | modifies its inputs, this function takes a copy of the input and operates 186 | on that. 187 | */ 188 | void duplicate_environment_strings(TCHAR *env) { 189 | TCHAR *newenv = copy_environment_block(env); 190 | if (! newenv) return; 191 | 192 | duplicate_environment(newenv); 193 | HeapFree(GetProcessHeap(), 0, newenv); 194 | } 195 | 196 | /* Safely get a copy of the current environment. */ 197 | TCHAR *copy_environment() { 198 | TCHAR *rawenv = GetEnvironmentStrings(); 199 | if (! rawenv) return NULL; 200 | TCHAR *env = copy_environment_block(rawenv); 201 | FreeEnvironmentStrings(rawenv); 202 | return env; 203 | } 204 | 205 | /* 206 | Create a new block with all the strings of the first block plus a new string. 207 | If the key is already present its value will be overwritten in place. 208 | If the key is blank or empty the new block will still be allocated and have 209 | non-zero length. 210 | */ 211 | int append_to_environment_block(TCHAR *env, unsigned long envlen, TCHAR *string, TCHAR **newenv, unsigned long *newlen) { 212 | size_t keylen = 0; 213 | if (string && string[0]) { 214 | for (; string[keylen]; keylen++) { 215 | if (string[keylen] == _T('=')) { 216 | keylen++; 217 | break; 218 | } 219 | } 220 | } 221 | return append_to_double_null(env, envlen, newenv, newlen, string, keylen, false); 222 | } 223 | 224 | /* 225 | Create a new block with all the strings of the first block minus the given 226 | string. 227 | If the key is not present the new block will be a copy of the original. 228 | If the string is KEY=VALUE the key will only be removed if its value is 229 | VALUE. 230 | If the string is just KEY the key will unconditionally be removed. 231 | If removing the string results in an empty list the new block will still be 232 | allocated and have non-zero length. 233 | */ 234 | int remove_from_environment_block(TCHAR *env, unsigned long envlen, TCHAR *string, TCHAR **newenv, unsigned long *newlen) { 235 | if (! string || ! string[0] || string[0] == _T('=')) return 1; 236 | 237 | TCHAR *key = 0; 238 | size_t len = _tcslen(string); 239 | size_t i; 240 | for (i = 0; i < len; i++) if (string[i] == _T('=')) break; 241 | 242 | /* Rewrite KEY to KEY= but leave KEY=VALUE alone. */ 243 | size_t keylen = len; 244 | if (i == len) keylen++; 245 | 246 | key = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (keylen + 1) * sizeof(TCHAR)); 247 | if (! key) { 248 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("key"), _T("remove_from_environment_block()"), 0); 249 | return 2; 250 | } 251 | memmove(key, string, len * sizeof(TCHAR)); 252 | if (keylen > len) key[keylen - 1] = _T('='); 253 | key[keylen] = _T('\0'); 254 | 255 | int ret = remove_from_double_null(env, envlen, newenv, newlen, key, keylen, false); 256 | HeapFree(GetProcessHeap(), 0, key); 257 | 258 | return ret; 259 | } 260 | -------------------------------------------------------------------------------- /env.h: -------------------------------------------------------------------------------- 1 | #ifndef ENV_H 2 | #define ENV_H 3 | 4 | size_t environment_length(TCHAR *); 5 | TCHAR *copy_environment_block(TCHAR *); 6 | TCHAR *useful_environment(TCHAR *); 7 | TCHAR *expand_environment_string(TCHAR *); 8 | int set_environment_block(TCHAR *); 9 | int clear_environment(); 10 | int duplicate_environment(TCHAR *); 11 | int test_environment(TCHAR *); 12 | void duplicate_environment_strings(TCHAR *); 13 | TCHAR *copy_environment(); 14 | int append_to_environment_block(TCHAR *, unsigned long, TCHAR *, TCHAR **, unsigned long *); 15 | int remove_from_environment_block(TCHAR *, unsigned long, TCHAR *, TCHAR **, unsigned long *); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /event.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | #define NSSM_SOURCE _T("nssm") 4 | #define NSSM_ERROR_BUFSIZE 65535 5 | #define NSSM_NUM_EVENT_STRINGS 16 6 | unsigned long tls_index; 7 | 8 | /* Convert error code to error string - must call LocalFree() on return value */ 9 | TCHAR *error_string(unsigned long error) { 10 | /* Thread-safe buffer */ 11 | TCHAR *error_message = (TCHAR *) TlsGetValue(tls_index); 12 | if (! error_message) { 13 | error_message = (TCHAR *) LocalAlloc(LPTR, NSSM_ERROR_BUFSIZE); 14 | if (! error_message) return _T(""); 15 | TlsSetValue(tls_index, (void *) error_message); 16 | } 17 | 18 | if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, GetUserDefaultLangID(), (TCHAR *) error_message, NSSM_ERROR_BUFSIZE, 0)) { 19 | if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (TCHAR *) error_message, NSSM_ERROR_BUFSIZE, 0)) { 20 | if (_sntprintf_s(error_message, NSSM_ERROR_BUFSIZE, _TRUNCATE, _T("system error %lu"), error) < 0) return 0; 21 | } 22 | } 23 | return error_message; 24 | } 25 | 26 | /* Convert message code to format string */ 27 | TCHAR *message_string(unsigned long error) { 28 | TCHAR *ret; 29 | if (! FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, GetUserDefaultLangID(), (LPTSTR) &ret, NSSM_ERROR_BUFSIZE, 0)) { 30 | if (! FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (LPTSTR) &ret, NSSM_ERROR_BUFSIZE, 0)) { 31 | ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 32 * sizeof(TCHAR)); 32 | if (_sntprintf_s(ret, NSSM_ERROR_BUFSIZE, _TRUNCATE, _T("system error %lu"), error) < 0) return 0; 33 | } 34 | } 35 | return ret; 36 | } 37 | 38 | /* Log a message to the Event Log */ 39 | void log_event(unsigned short type, unsigned long id, ...) { 40 | va_list arg; 41 | TCHAR *strings[NSSM_NUM_EVENT_STRINGS]; 42 | TCHAR buffer[NSSM_ERROR_BUFSIZE]; 43 | 44 | TCHAR *format = message_string(id); 45 | if (!format) return; 46 | 47 | /* Open event log */ 48 | HANDLE handle = RegisterEventSource(0, NSSM_SOURCE); 49 | if (! handle) return; 50 | 51 | /* Log it */ 52 | va_start(arg, id); 53 | _vsntprintf_s(buffer, _countof(buffer), _TRUNCATE, format, arg); 54 | strings[0] = buffer; 55 | strings[1] = 0; 56 | ReportEvent(handle, type, 0, type, 0, 1, 0, (const TCHAR **) strings, 0); 57 | va_end(arg); 58 | LocalFree(format); 59 | /* Close event log */ 60 | DeregisterEventSource(handle); 61 | } 62 | 63 | /* Log a message to the console */ 64 | void print_message(FILE *file, unsigned long id, ...) { 65 | va_list arg; 66 | 67 | TCHAR *format = message_string(id); 68 | if (! format) return; 69 | 70 | va_start(arg, id); 71 | _vftprintf(file, format, arg); 72 | va_end(arg); 73 | 74 | LocalFree(format); 75 | } 76 | 77 | /* Show a GUI dialogue */ 78 | int popup_message(HWND owner, unsigned int type, unsigned long id, ...) { 79 | va_list arg; 80 | 81 | TCHAR *format = message_string(id); 82 | if (! format) { 83 | return MessageBox(0, _T("The message which was supposed to go here is missing!"), NSSM, MB_OK | MB_ICONEXCLAMATION); 84 | } 85 | 86 | TCHAR blurb[NSSM_ERROR_BUFSIZE]; 87 | va_start(arg, id); 88 | if (_vsntprintf_s(blurb, _countof(blurb), _TRUNCATE, format, arg) < 0) { 89 | va_end(arg); 90 | LocalFree(format); 91 | return MessageBox(0, _T("The message which was supposed to go here is too big!"), NSSM, MB_OK | MB_ICONEXCLAMATION); 92 | } 93 | va_end(arg); 94 | 95 | MSGBOXPARAMS params; 96 | ZeroMemory(¶ms, sizeof(params)); 97 | params.cbSize = sizeof(params); 98 | params.hInstance = GetModuleHandle(0); 99 | params.hwndOwner = owner; 100 | params.lpszText = blurb; 101 | params.lpszCaption = NSSM; 102 | params.dwStyle = type; 103 | if (type == MB_OK) { 104 | params.dwStyle |= MB_USERICON; 105 | params.lpszIcon = MAKEINTRESOURCE(IDI_NSSM); 106 | } 107 | 108 | int ret = MessageBoxIndirect(¶ms); 109 | 110 | LocalFree(format); 111 | 112 | return ret; 113 | } 114 | -------------------------------------------------------------------------------- /event.h: -------------------------------------------------------------------------------- 1 | #ifndef EVENT_H 2 | #define EVENT_H 3 | 4 | TCHAR *error_string(unsigned long); 5 | TCHAR *message_string(unsigned long); 6 | void log_event(unsigned short, unsigned long, ...); 7 | void print_message(FILE *, unsigned long, ...); 8 | int popup_message(HWND, unsigned int, unsigned long, ...); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /gui.h: -------------------------------------------------------------------------------- 1 | #ifndef GUI_H 2 | #define GUI_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "resource.h" 8 | 9 | int nssm_gui(int, nssm_service_t *); 10 | void centre_window(HWND); 11 | int configure(HWND, nssm_service_t *, nssm_service_t *); 12 | int install(HWND); 13 | int remove(HWND); 14 | int edit(HWND, nssm_service_t *); 15 | void browse(HWND); 16 | INT_PTR CALLBACK nssm_dlg(HWND, UINT, WPARAM, LPARAM); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /hook.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | typedef struct { 4 | TCHAR *name; 5 | HANDLE process_handle; 6 | unsigned long pid; 7 | unsigned long deadline; 8 | FILETIME creation_time; 9 | kill_t k; 10 | } hook_t; 11 | 12 | const TCHAR *hook_event_strings[] = { NSSM_HOOK_EVENT_START, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_EVENT_ROTATE, NULL }; 13 | const TCHAR *hook_action_strings[] = { NSSM_HOOK_ACTION_PRE, NSSM_HOOK_ACTION_POST, NSSM_HOOK_ACTION_CHANGE, NSSM_HOOK_ACTION_RESUME, NULL }; 14 | 15 | static unsigned long WINAPI await_hook(void *arg) { 16 | hook_t *hook = (hook_t *) arg; 17 | if (! hook) return NSSM_HOOK_STATUS_ERROR; 18 | 19 | int ret = 0; 20 | if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = NSSM_HOOK_STATUS_TIMEOUT; 21 | 22 | /* Tidy up hook process tree. */ 23 | if (hook->name) hook->k.name = hook->name; 24 | else hook->k.name = _T("hook"); 25 | hook->k.process_handle = hook->process_handle; 26 | hook->k.pid = hook->pid; 27 | hook->k.stop_method = ~0; 28 | hook->k.kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD; 29 | hook->k.kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD; 30 | hook->k.kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD; 31 | hook->k.creation_time = hook->creation_time; 32 | GetSystemTimeAsFileTime(&hook->k.exit_time); 33 | kill_process_tree(&hook->k, hook->pid); 34 | 35 | if (ret) { 36 | CloseHandle(hook->process_handle); 37 | if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); 38 | HeapFree(GetProcessHeap(), 0, hook); 39 | return ret; 40 | } 41 | 42 | unsigned long exitcode; 43 | GetExitCodeProcess(hook->process_handle, &exitcode); 44 | CloseHandle(hook->process_handle); 45 | 46 | if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); 47 | HeapFree(GetProcessHeap(), 0, hook); 48 | 49 | if (exitcode == NSSM_HOOK_STATUS_ABORT) return NSSM_HOOK_STATUS_ABORT; 50 | if (exitcode) return NSSM_HOOK_STATUS_FAILED; 51 | 52 | return NSSM_HOOK_STATUS_SUCCESS; 53 | } 54 | 55 | static void set_hook_runtime(TCHAR *v, FILETIME *start, FILETIME *now) { 56 | if (start && now) { 57 | ULARGE_INTEGER s; 58 | s.LowPart = start->dwLowDateTime; 59 | s.HighPart = start->dwHighDateTime; 60 | if (s.QuadPart) { 61 | ULARGE_INTEGER t; 62 | t.LowPart = now->dwLowDateTime; 63 | t.HighPart = now->dwHighDateTime; 64 | if (t.QuadPart && t.QuadPart >= s.QuadPart) { 65 | t.QuadPart -= s.QuadPart; 66 | t.QuadPart /= 10000LL; 67 | TCHAR number[16]; 68 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%llu"), t.QuadPart); 69 | SetEnvironmentVariable(v, number); 70 | return; 71 | } 72 | } 73 | } 74 | SetEnvironmentVariable(v, _T("")); 75 | } 76 | 77 | static void add_thread_handle(hook_thread_t *hook_threads, HANDLE thread_handle, TCHAR *name) { 78 | if (! hook_threads) return; 79 | 80 | int num_threads = hook_threads->num_threads + 1; 81 | hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); 82 | if (! data) { 83 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook_thread_t"), _T("add_thread_handle()"), 0); 84 | return; 85 | } 86 | 87 | int i; 88 | for (i = 0; i < hook_threads->num_threads; i++) memmove(&data[i], &hook_threads->data[i], sizeof(data[i])); 89 | memmove(data[i].name, name, sizeof(data[i].name)); 90 | data[i].thread_handle = thread_handle; 91 | 92 | if (hook_threads->data) HeapFree(GetProcessHeap(), 0, hook_threads->data); 93 | hook_threads->data = data; 94 | hook_threads->num_threads = num_threads; 95 | } 96 | 97 | bool valid_hook_name(const TCHAR *hook_event, const TCHAR *hook_action, bool quiet) { 98 | bool valid_event = false; 99 | bool valid_action = false; 100 | 101 | /* Exit/Post */ 102 | if (str_equiv(hook_event, NSSM_HOOK_EVENT_EXIT)) { 103 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; 104 | if (quiet) return false; 105 | print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); 106 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); 107 | return false; 108 | } 109 | 110 | /* Power/{Change,Resume} */ 111 | if (str_equiv(hook_event, NSSM_HOOK_EVENT_POWER)) { 112 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_CHANGE)) return true; 113 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_RESUME)) return true; 114 | if (quiet) return false; 115 | print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); 116 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_CHANGE); 117 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_RESUME); 118 | return false; 119 | } 120 | 121 | /* Rotate/{Pre,Post} */ 122 | if (str_equiv(hook_event, NSSM_HOOK_EVENT_ROTATE)) { 123 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; 124 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; 125 | if (quiet) return false; 126 | print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); 127 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); 128 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); 129 | return false; 130 | } 131 | 132 | /* Start/{Pre,Post} */ 133 | if (str_equiv(hook_event, NSSM_HOOK_EVENT_START)) { 134 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; 135 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true; 136 | if (quiet) return false; 137 | print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); 138 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); 139 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST); 140 | return false; 141 | } 142 | 143 | /* Stop/Pre */ 144 | if (str_equiv(hook_event, NSSM_HOOK_EVENT_STOP)) { 145 | if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true; 146 | if (quiet) return false; 147 | print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event); 148 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE); 149 | return false; 150 | } 151 | 152 | if (quiet) return false; 153 | print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_EVENT); 154 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_EXIT); 155 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_POWER); 156 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_ROTATE); 157 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_START); 158 | _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_STOP); 159 | return false; 160 | } 161 | 162 | void await_hook_threads(hook_thread_t *hook_threads, SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, unsigned long deadline) { 163 | if (! hook_threads) return; 164 | if (! hook_threads->num_threads) return; 165 | 166 | int *retain = (int *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hook_threads->num_threads * sizeof(int)); 167 | if (! retain) { 168 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("retain"), _T("await_hook_threads()"), 0); 169 | return; 170 | } 171 | 172 | /* 173 | We could use WaitForMultipleObjects() but await_single_object() can update 174 | the service status as well. 175 | */ 176 | int num_threads = 0; 177 | int i; 178 | for (i = 0; i < hook_threads->num_threads; i++) { 179 | if (deadline) { 180 | if (await_single_handle(status_handle, status, hook_threads->data[i].thread_handle, hook_threads->data[i].name, _T(__FUNCTION__), deadline) != 1) { 181 | CloseHandle(hook_threads->data[i].thread_handle); 182 | continue; 183 | } 184 | } 185 | else if (WaitForSingleObject(hook_threads->data[i].thread_handle, 0) != WAIT_TIMEOUT) { 186 | CloseHandle(hook_threads->data[i].thread_handle); 187 | continue; 188 | } 189 | 190 | retain[num_threads++]= i; 191 | } 192 | 193 | if (num_threads) { 194 | hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t)); 195 | if (! data) { 196 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("data"), _T("await_hook_threads()"), 0); 197 | HeapFree(GetProcessHeap(), 0, retain); 198 | return; 199 | } 200 | 201 | for (i = 0; i < num_threads; i++) memmove(&data[i], &hook_threads->data[retain[i]], sizeof(data[i])); 202 | 203 | HeapFree(GetProcessHeap(), 0, hook_threads->data); 204 | hook_threads->data = data; 205 | hook_threads->num_threads = num_threads; 206 | } 207 | else { 208 | HeapFree(GetProcessHeap(), 0, hook_threads->data); 209 | ZeroMemory(hook_threads, sizeof(*hook_threads)); 210 | } 211 | 212 | HeapFree(GetProcessHeap(), 0, retain); 213 | } 214 | 215 | /* 216 | Returns: 217 | NSSM_HOOK_STATUS_SUCCESS if the hook ran successfully. 218 | NSSM_HOOK_STATUS_NOTFOUND if no hook was found. 219 | NSSM_HOOK_STATUS_ABORT if the hook failed and we should cancel service start. 220 | NSSM_HOOK_STATUS_ERROR on error. 221 | NSSM_HOOK_STATUS_NOTRUN if the hook didn't run. 222 | NSSM_HOOK_STATUS_TIMEOUT if the hook timed out. 223 | NSSM_HOOK_STATUS_FAILED if the hook failed. 224 | */ 225 | int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline, bool async) { 226 | int ret = 0; 227 | 228 | hook_t *hook = (hook_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(hook_t)); 229 | if (! hook) { 230 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0); 231 | return NSSM_HOOK_STATUS_ERROR; 232 | } 233 | 234 | FILETIME now; 235 | GetSystemTimeAsFileTime(&now); 236 | 237 | EnterCriticalSection(&service->hook_section); 238 | 239 | /* Set the environment. */ 240 | set_service_environment(service); 241 | 242 | /* ABI version. */ 243 | TCHAR number[16]; 244 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), NSSM_HOOK_VERSION); 245 | SetEnvironmentVariable(NSSM_HOOK_ENV_VERSION, number); 246 | 247 | /* Event triggering this action. */ 248 | SetEnvironmentVariable(NSSM_HOOK_ENV_EVENT, hook_event); 249 | 250 | /* Hook action. */ 251 | SetEnvironmentVariable(NSSM_HOOK_ENV_ACTION, hook_action); 252 | 253 | /* Control triggering this action. May be empty. */ 254 | if (hook_control) SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control)); 255 | else SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, _T("")); 256 | 257 | /* Last control handled. */ 258 | SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control)); 259 | 260 | /* Path to NSSM, unquoted for the environment. */ 261 | SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath()); 262 | 263 | /* NSSM version. */ 264 | SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION); 265 | SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_VERSION, NSSM_VERSION); 266 | SetEnvironmentVariable(NSSM_HOOK_ENV_BUILD_DATE, NSSM_DATE); 267 | 268 | /* NSSM PID. */ 269 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId()); 270 | SetEnvironmentVariable(NSSM_HOOK_ENV_PID, number); 271 | 272 | /* NSSM runtime. */ 273 | set_hook_runtime(NSSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now); 274 | 275 | /* Application PID. */ 276 | if (service->pid) { 277 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid); 278 | SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, number); 279 | /* Application runtime. */ 280 | set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now); 281 | /* Exit code. */ 282 | SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); 283 | } 284 | else { 285 | SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, _T("")); 286 | if (str_equiv(hook_event, NSSM_HOOK_EVENT_START) && str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) { 287 | SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_RUNTIME, _T("")); 288 | SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T("")); 289 | } 290 | else { 291 | set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time); 292 | /* Exit code. */ 293 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode); 294 | SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, number); 295 | } 296 | } 297 | 298 | /* Deadline for this script. */ 299 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline); 300 | SetEnvironmentVariable(NSSM_HOOK_ENV_DEADLINE, number); 301 | 302 | /* Service name. */ 303 | SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_NAME, service->name); 304 | SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_DISPLAYNAME, service->displayname); 305 | 306 | /* Times the service was asked to start. */ 307 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_requested_count); 308 | SetEnvironmentVariable(NSSM_HOOK_ENV_START_REQUESTED_COUNT, number); 309 | 310 | /* Times the service actually did start. */ 311 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count); 312 | SetEnvironmentVariable(NSSM_HOOK_ENV_START_COUNT, number); 313 | 314 | /* Times the service exited. */ 315 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count); 316 | SetEnvironmentVariable(NSSM_HOOK_ENV_EXIT_COUNT, number); 317 | 318 | /* Throttled count. */ 319 | _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle); 320 | SetEnvironmentVariable(NSSM_HOOK_ENV_THROTTLE_COUNT, number); 321 | 322 | /* Command line. */ 323 | TCHAR app[CMD_LENGTH]; 324 | _sntprintf_s(app, _countof(app), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags); 325 | SetEnvironmentVariable(NSSM_HOOK_ENV_COMMAND_LINE, app); 326 | 327 | TCHAR cmd[CMD_LENGTH]; 328 | if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) { 329 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0); 330 | unset_service_environment(service); 331 | LeaveCriticalSection(&service->hook_section); 332 | HeapFree(GetProcessHeap(), 0, hook); 333 | return NSSM_HOOK_STATUS_ERROR; 334 | } 335 | 336 | /* No hook. */ 337 | if (! _tcslen(cmd)) { 338 | unset_service_environment(service); 339 | LeaveCriticalSection(&service->hook_section); 340 | HeapFree(GetProcessHeap(), 0, hook); 341 | return NSSM_HOOK_STATUS_NOTFOUND; 342 | } 343 | 344 | /* Run the command. */ 345 | STARTUPINFO si; 346 | ZeroMemory(&si, sizeof(si)); 347 | si.cb = sizeof(si); 348 | PROCESS_INFORMATION pi; 349 | ZeroMemory(&pi, sizeof(pi)); 350 | if (service->hook_share_output_handles) (void) use_output_handles(service, &si); 351 | bool inherit_handles = false; 352 | if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true; 353 | unsigned long flags = 0; 354 | #ifdef UNICODE 355 | flags |= CREATE_UNICODE_ENVIRONMENT; 356 | #endif 357 | ret = NSSM_HOOK_STATUS_NOTRUN; 358 | if (CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) { 359 | close_output_handles(&si); 360 | hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR)); 361 | if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action); 362 | hook->process_handle = pi.hProcess; 363 | hook->pid = pi.dwProcessId; 364 | hook->deadline = deadline; 365 | if (get_process_creation_time(hook->process_handle, &hook->creation_time)) GetSystemTimeAsFileTime(&hook->creation_time); 366 | 367 | unsigned long tid; 368 | HANDLE thread_handle = CreateThread(NULL, 0, await_hook, (void *) hook, 0, &tid); 369 | if (thread_handle) { 370 | if (async) { 371 | ret = 0; 372 | await_hook_threads(hook_threads, service->status_handle, &service->status, 0); 373 | add_thread_handle(hook_threads, thread_handle, hook->name); 374 | } 375 | else { 376 | await_single_handle(service->status_handle, &service->status, thread_handle, hook->name, _T(__FUNCTION__), deadline + NSSM_SERVICE_STATUS_DEADLINE); 377 | unsigned long exitcode; 378 | GetExitCodeThread(thread_handle, &exitcode); 379 | ret = (int) exitcode; 380 | CloseHandle(thread_handle); 381 | } 382 | } 383 | else { 384 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0); 385 | await_hook(hook); 386 | if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name); 387 | HeapFree(GetProcessHeap(), 0, hook); 388 | } 389 | } 390 | else { 391 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0); 392 | HeapFree(GetProcessHeap(), 0, hook); 393 | close_output_handles(&si); 394 | } 395 | 396 | /* Restore our environment. */ 397 | unset_service_environment(service); 398 | 399 | LeaveCriticalSection(&service->hook_section); 400 | 401 | return ret; 402 | } 403 | 404 | int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline) { 405 | return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, deadline, true); 406 | } 407 | 408 | int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control) { 409 | return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, NSSM_HOOK_DEADLINE); 410 | } 411 | -------------------------------------------------------------------------------- /hook.h: -------------------------------------------------------------------------------- 1 | #ifndef HOOK_H 2 | #define HOOK_H 3 | 4 | #define NSSM_HOOK_EVENT_START _T("Start") 5 | #define NSSM_HOOK_EVENT_STOP _T("Stop") 6 | #define NSSM_HOOK_EVENT_EXIT _T("Exit") 7 | #define NSSM_HOOK_EVENT_POWER _T("Power") 8 | #define NSSM_HOOK_EVENT_ROTATE _T("Rotate") 9 | 10 | #define NSSM_HOOK_ACTION_PRE _T("Pre") 11 | #define NSSM_HOOK_ACTION_POST _T("Post") 12 | #define NSSM_HOOK_ACTION_CHANGE _T("Change") 13 | #define NSSM_HOOK_ACTION_RESUME _T("Resume") 14 | 15 | /* Hook name will be " (/)" */ 16 | #define HOOK_NAME_LENGTH SERVICE_NAME_LENGTH * 2 17 | 18 | #define NSSM_HOOK_VERSION 1 19 | 20 | /* Hook ran successfully. */ 21 | #define NSSM_HOOK_STATUS_SUCCESS 0 22 | /* No hook configured. */ 23 | #define NSSM_HOOK_STATUS_NOTFOUND 1 24 | /* Hook requested abort. */ 25 | #define NSSM_HOOK_STATUS_ABORT 99 26 | /* Internal error launching hook. */ 27 | #define NSSM_HOOK_STATUS_ERROR 100 28 | /* Hook was not run. */ 29 | #define NSSM_HOOK_STATUS_NOTRUN 101 30 | /* Hook timed out. */ 31 | #define NSSM_HOOK_STATUS_TIMEOUT 102 32 | /* Hook returned non-zero. */ 33 | #define NSSM_HOOK_STATUS_FAILED 111 34 | 35 | /* Version 1. */ 36 | #define NSSM_HOOK_ENV_VERSION _T("NSSM_HOOK_VERSION") 37 | #define NSSM_HOOK_ENV_IMAGE_PATH _T("NSSM_EXE") 38 | #define NSSM_HOOK_ENV_NSSM_CONFIGURATION _T("NSSM_CONFIGURATION") 39 | #define NSSM_HOOK_ENV_NSSM_VERSION _T("NSSM_VERSION") 40 | #define NSSM_HOOK_ENV_BUILD_DATE _T("NSSM_BUILD_DATE") 41 | #define NSSM_HOOK_ENV_PID _T("NSSM_PID") 42 | #define NSSM_HOOK_ENV_DEADLINE _T("NSSM_DEADLINE") 43 | #define NSSM_HOOK_ENV_SERVICE_NAME _T("NSSM_SERVICE_NAME") 44 | #define NSSM_HOOK_ENV_SERVICE_DISPLAYNAME _T("NSSM_SERVICE_DISPLAYNAME") 45 | #define NSSM_HOOK_ENV_COMMAND_LINE _T("NSSM_COMMAND_LINE") 46 | #define NSSM_HOOK_ENV_APPLICATION_PID _T("NSSM_APPLICATION_PID") 47 | #define NSSM_HOOK_ENV_EVENT _T("NSSM_EVENT") 48 | #define NSSM_HOOK_ENV_ACTION _T("NSSM_ACTION") 49 | #define NSSM_HOOK_ENV_TRIGGER _T("NSSM_TRIGGER") 50 | #define NSSM_HOOK_ENV_LAST_CONTROL _T("NSSM_LAST_CONTROL") 51 | #define NSSM_HOOK_ENV_START_REQUESTED_COUNT _T("NSSM_START_REQUESTED_COUNT") 52 | #define NSSM_HOOK_ENV_START_COUNT _T("NSSM_START_COUNT") 53 | #define NSSM_HOOK_ENV_THROTTLE_COUNT _T("NSSM_THROTTLE_COUNT") 54 | #define NSSM_HOOK_ENV_EXIT_COUNT _T("NSSM_EXIT_COUNT") 55 | #define NSSM_HOOK_ENV_EXITCODE _T("NSSM_EXITCODE") 56 | #define NSSM_HOOK_ENV_RUNTIME _T("NSSM_RUNTIME") 57 | #define NSSM_HOOK_ENV_APPLICATION_RUNTIME _T("NSSM_APPLICATION_RUNTIME") 58 | 59 | typedef struct { 60 | TCHAR name[HOOK_NAME_LENGTH]; 61 | HANDLE thread_handle; 62 | } hook_thread_data_t; 63 | 64 | typedef struct { 65 | hook_thread_data_t *data; 66 | int num_threads; 67 | } hook_thread_t; 68 | 69 | bool valid_hook_name(const TCHAR *, const TCHAR *, bool); 70 | void await_hook_threads(hook_thread_t *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long); 71 | int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long, bool); 72 | int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long); 73 | int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *); 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /imports.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | imports_t imports; 4 | 5 | /* 6 | Try to set up function pointers. 7 | In this first implementation it is not an error if we can't load them 8 | because we aren't currently trying to load any functions which we 9 | absolutely need. If we later add some indispensible imports we can 10 | return non-zero here to force an application exit. 11 | */ 12 | HMODULE get_dll(const TCHAR *dll, unsigned long *error) { 13 | *error = 0; 14 | 15 | HMODULE ret = LoadLibrary(dll); 16 | if (! ret) { 17 | *error = GetLastError(); 18 | if (*error != ERROR_PROC_NOT_FOUND) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_LOADLIBRARY_FAILED, dll, error_string(*error), 0); 19 | } 20 | 21 | return ret; 22 | } 23 | 24 | FARPROC get_import(HMODULE library, const char *function, unsigned long *error) { 25 | *error = 0; 26 | 27 | FARPROC ret = GetProcAddress(library, function); 28 | if (! ret) { 29 | *error = GetLastError(); 30 | if (*error != ERROR_PROC_NOT_FOUND) { 31 | TCHAR *function_name; 32 | if (! from_utf8(function, &function_name, 0)) { 33 | log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_GETPROCADDRESS_FAILED, function_name, error_string(*error), 0); 34 | HeapFree(GetProcessHeap(), 0, function_name); 35 | } 36 | } 37 | } 38 | 39 | return ret; 40 | } 41 | 42 | int get_imports() { 43 | unsigned long error; 44 | 45 | ZeroMemory(&imports, sizeof(imports)); 46 | 47 | imports.kernel32 = get_dll(_T("kernel32.dll"), &error); 48 | if (imports.kernel32) { 49 | imports.AttachConsole = (AttachConsole_ptr) get_import(imports.kernel32, "AttachConsole", &error); 50 | if (! imports.AttachConsole) { 51 | if (error != ERROR_PROC_NOT_FOUND) return 2; 52 | } 53 | 54 | imports.QueryFullProcessImageName = (QueryFullProcessImageName_ptr) get_import(imports.kernel32, QUERYFULLPROCESSIMAGENAME_EXPORT, &error); 55 | if (! imports.QueryFullProcessImageName) { 56 | if (error != ERROR_PROC_NOT_FOUND) return 3; 57 | } 58 | 59 | imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error); 60 | if (! imports.SleepConditionVariableCS) { 61 | if (error != ERROR_PROC_NOT_FOUND) return 4; 62 | } 63 | 64 | imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error); 65 | if (! imports.WakeConditionVariable) { 66 | if (error != ERROR_PROC_NOT_FOUND) return 5; 67 | } 68 | } 69 | else if (error != ERROR_MOD_NOT_FOUND) return 1; 70 | 71 | imports.advapi32 = get_dll(_T("advapi32.dll"), &error); 72 | if (imports.advapi32) { 73 | imports.CreateWellKnownSid = (CreateWellKnownSid_ptr) get_import(imports.advapi32, "CreateWellKnownSid", &error); 74 | if (! imports.CreateWellKnownSid) { 75 | if (error != ERROR_PROC_NOT_FOUND) return 7; 76 | } 77 | imports.IsWellKnownSid = (IsWellKnownSid_ptr) get_import(imports.advapi32, "IsWellKnownSid", &error); 78 | if (! imports.IsWellKnownSid) { 79 | if (error != ERROR_PROC_NOT_FOUND) return 8; 80 | } 81 | } 82 | else if (error != ERROR_MOD_NOT_FOUND) return 6; 83 | 84 | return 0; 85 | } 86 | 87 | void free_imports() { 88 | if (imports.kernel32) FreeLibrary(imports.kernel32); 89 | if (imports.advapi32) FreeLibrary(imports.advapi32); 90 | ZeroMemory(&imports, sizeof(imports)); 91 | } 92 | -------------------------------------------------------------------------------- /imports.h: -------------------------------------------------------------------------------- 1 | #ifndef IMPORTS_H 2 | #define IMPORTS_H 3 | 4 | /* Some functions don't have decorated versions. */ 5 | #ifdef UNICODE 6 | #define QUERYFULLPROCESSIMAGENAME_EXPORT "QueryFullProcessImageNameW" 7 | #else 8 | #define QUERYFULLPROCESSIMAGENAME_EXPORT "QueryFullProcessImageNameA" 9 | #endif 10 | 11 | typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD); 12 | typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD); 13 | typedef BOOL (WINAPI *QueryFullProcessImageName_ptr)(HANDLE, unsigned long, LPTSTR, unsigned long *); 14 | typedef void (WINAPI *WakeConditionVariable_ptr)(PCONDITION_VARIABLE); 15 | typedef BOOL (WINAPI *CreateWellKnownSid_ptr)(WELL_KNOWN_SID_TYPE, SID *, SID *, unsigned long *); 16 | typedef BOOL (WINAPI *IsWellKnownSid_ptr)(SID *, WELL_KNOWN_SID_TYPE); 17 | 18 | typedef struct { 19 | HMODULE kernel32; 20 | HMODULE advapi32; 21 | AttachConsole_ptr AttachConsole; 22 | SleepConditionVariableCS_ptr SleepConditionVariableCS; 23 | QueryFullProcessImageName_ptr QueryFullProcessImageName; 24 | WakeConditionVariable_ptr WakeConditionVariable; 25 | CreateWellKnownSid_ptr CreateWellKnownSid; 26 | IsWellKnownSid_ptr IsWellKnownSid; 27 | } imports_t; 28 | 29 | HMODULE get_dll(const TCHAR *, unsigned long *); 30 | FARPROC get_import(HMODULE, const char *, unsigned long *); 31 | int get_imports(); 32 | void free_imports(); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /io.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | #define COMPLAINED_READ (1 << 0) 4 | #define COMPLAINED_WRITE (1 << 1) 5 | #define COMPLAINED_ROTATE (1 << 2) 6 | #define TIMESTAMP_FORMAT "%04u-%02u-%02u %02u:%02u:%02u.%03u: " 7 | #define TIMESTAMP_LEN 25 8 | 9 | static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *source_description, TCHAR *dest_description, unsigned long flags) { 10 | if (! dest_handle_ptr) return 1; 11 | 12 | if (! DuplicateHandle(GetCurrentProcess(), source_handle, GetCurrentProcess(), dest_handle_ptr, 0, true, flags)) { 13 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DUPLICATEHANDLE_FAILED, source_description, dest_description, error_string(GetLastError()), 0); 14 | return 2; 15 | } 16 | return 0; 17 | } 18 | 19 | static int dup_handle(HANDLE source_handle, HANDLE *dest_handle_ptr, TCHAR *source_description, TCHAR *dest_description) { 20 | return dup_handle(source_handle, dest_handle_ptr, source_description, dest_description, DUPLICATE_SAME_ACCESS); 21 | } 22 | 23 | /* 24 | read_handle: read from application 25 | pipe_handle: stdout of application 26 | write_handle: to file 27 | */ 28 | static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long rotate_delay, unsigned long *tid_ptr, unsigned long *rotate_online, bool timestamp_log, bool copy_and_truncate) { 29 | *tid_ptr = 0; 30 | 31 | /* Pipe between application's stdout/stderr and our logging handle. */ 32 | if (read_handle_ptr && ! *read_handle_ptr) { 33 | if (pipe_handle_ptr && ! *pipe_handle_ptr) { 34 | if (CreatePipe(read_handle_ptr, pipe_handle_ptr, 0, 0)) { 35 | SetHandleInformation(*pipe_handle_ptr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); 36 | } 37 | else { 38 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPIPE_FAILED, service_name, path, error_string(GetLastError())); 39 | return (HANDLE) 0; 40 | } 41 | } 42 | } 43 | 44 | logger_t *logger = (logger_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(logger_t)); 45 | if (! logger) { 46 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("logger"), _T("create_logging_thread()"), 0); 47 | return (HANDLE) 0; 48 | } 49 | 50 | ULARGE_INTEGER size; 51 | size.LowPart = rotate_bytes_low; 52 | size.HighPart = rotate_bytes_high; 53 | 54 | logger->service_name = service_name; 55 | logger->path = path; 56 | logger->sharing = sharing; 57 | logger->disposition = disposition; 58 | logger->flags = flags; 59 | logger->read_handle = *read_handle_ptr; 60 | logger->write_handle = *write_handle_ptr; 61 | logger->size = (__int64) size.QuadPart; 62 | logger->tid_ptr = tid_ptr; 63 | logger->timestamp_log = timestamp_log; 64 | logger->line_length = 0; 65 | logger->rotate_online = rotate_online; 66 | logger->rotate_delay = rotate_delay; 67 | logger->copy_and_truncate = copy_and_truncate; 68 | 69 | HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr); 70 | if (! thread_handle) { 71 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0); 72 | HeapFree(GetProcessHeap(), 0, logger); 73 | } 74 | 75 | return thread_handle; 76 | } 77 | 78 | static inline unsigned long guess_charsize(void *address, unsigned long bufsize) { 79 | if (IsTextUnicode(address, bufsize, 0)) return (unsigned long) sizeof(wchar_t); 80 | else return (unsigned long) sizeof(char); 81 | } 82 | 83 | static inline void write_bom(logger_t *logger, unsigned long *out) { 84 | wchar_t bom = L'\ufeff'; 85 | if (! WriteFile(logger->write_handle, (void *) &bom, sizeof(bom), out, 0)) { 86 | log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SOMEBODY_SET_UP_US_THE_BOM, logger->service_name, logger->path, error_string(GetLastError()), 0); 87 | } 88 | } 89 | 90 | void close_handle(HANDLE *handle, HANDLE *remember) { 91 | if (remember) *remember = INVALID_HANDLE_VALUE; 92 | if (! handle) return; 93 | if (! *handle) return; 94 | CloseHandle(*handle); 95 | if (remember) *remember = *handle; 96 | *handle = 0; 97 | } 98 | 99 | void close_handle(HANDLE *handle) { 100 | close_handle(handle, NULL); 101 | } 102 | 103 | /* Get path, share mode, creation disposition and flags for a stream. */ 104 | int get_createfile_parameters(HKEY key, TCHAR *prefix, TCHAR *path, unsigned long *sharing, unsigned long default_sharing, unsigned long *disposition, unsigned long default_disposition, unsigned long *flags, unsigned long default_flags, bool *copy_and_truncate) { 105 | TCHAR value[NSSM_STDIO_LENGTH]; 106 | 107 | /* Path. */ 108 | if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s"), prefix) < 0) { 109 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, prefix, _T("get_createfile_parameters()"), 0); 110 | return 1; 111 | } 112 | switch (expand_parameter(key, value, path, PATH_LENGTH, true, false)) { 113 | case 0: if (! path[0]) return 0; break; /* OK. */ 114 | default: return 2; /* Error. */ 115 | } 116 | 117 | /* ShareMode. */ 118 | if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_SHARING) < 0) { 119 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_SHARING, _T("get_createfile_parameters()"), 0); 120 | return 3; 121 | } 122 | switch (get_number(key, value, sharing, false)) { 123 | case 0: *sharing = default_sharing; break; /* Missing. */ 124 | case 1: break; /* Found. */ 125 | case -2: return 4; /* Error. */ 126 | } 127 | 128 | /* CreationDisposition. */ 129 | if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_DISPOSITION) < 0) { 130 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_DISPOSITION, _T("get_createfile_parameters()"), 0); 131 | return 5; 132 | } 133 | switch (get_number(key, value, disposition, false)) { 134 | case 0: *disposition = default_disposition; break; /* Missing. */ 135 | case 1: break; /* Found. */ 136 | case -2: return 6; /* Error. */ 137 | } 138 | 139 | /* Flags. */ 140 | if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_FLAGS) < 0) { 141 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_FLAGS, _T("get_createfile_parameters()"), 0); 142 | return 7; 143 | } 144 | switch (get_number(key, value, flags, false)) { 145 | case 0: *flags = default_flags; break; /* Missing. */ 146 | case 1: break; /* Found. */ 147 | case -2: return 8; /* Error. */ 148 | } 149 | 150 | /* Rotate with CopyFile() and SetEndOfFile(). */ 151 | if (copy_and_truncate) { 152 | unsigned long data; 153 | if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_COPY_AND_TRUNCATE) < 0) { 154 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_COPY_AND_TRUNCATE, _T("get_createfile_parameters()"), 0); 155 | return 9; 156 | } 157 | switch (get_number(key, value, &data, false)) { 158 | case 0: *copy_and_truncate = false; break; /* Missing. */ 159 | case 1: /* Found. */ 160 | if (data) *copy_and_truncate = true; 161 | else *copy_and_truncate = false; 162 | break; 163 | case -2: return 9; /* Error. */ 164 | } 165 | } 166 | 167 | return 0; 168 | } 169 | 170 | int set_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix, unsigned long number) { 171 | TCHAR value[NSSM_STDIO_LENGTH]; 172 | 173 | if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) { 174 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("set_createfile_parameter()"), 0); 175 | return 1; 176 | } 177 | 178 | return set_number(key, value, number); 179 | } 180 | 181 | int delete_createfile_parameter(HKEY key, TCHAR *prefix, TCHAR *suffix) { 182 | TCHAR value[NSSM_STDIO_LENGTH]; 183 | 184 | if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, suffix) < 0) { 185 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, suffix, _T("delete_createfile_parameter()"), 0); 186 | return 1; 187 | } 188 | 189 | if (RegDeleteValue(key, value)) return 0; 190 | return 1; 191 | } 192 | 193 | HANDLE write_to_file(TCHAR *path, unsigned long sharing, SECURITY_ATTRIBUTES *attributes, unsigned long disposition, unsigned long flags) { 194 | static LARGE_INTEGER offset = { 0 }; 195 | HANDLE ret = CreateFile(path, FILE_WRITE_DATA, sharing, attributes, disposition, flags, 0); 196 | if (ret != INVALID_HANDLE_VALUE) { 197 | if (SetFilePointerEx(ret, offset, 0, FILE_END)) SetEndOfFile(ret); 198 | return ret; 199 | } 200 | 201 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, path, error_string(GetLastError()), 0); 202 | return ret; 203 | } 204 | 205 | static void rotated_filename(TCHAR *path, TCHAR *rotated, unsigned long rotated_len, SYSTEMTIME *st) { 206 | if (! st) { 207 | SYSTEMTIME now; 208 | st = &now; 209 | GetSystemTime(st); 210 | } 211 | 212 | TCHAR buffer[PATH_LENGTH]; 213 | memmove(buffer, path, sizeof(buffer)); 214 | TCHAR *ext = PathFindExtension(buffer); 215 | TCHAR extension[PATH_LENGTH]; 216 | _sntprintf_s(extension, _countof(extension), _TRUNCATE, _T("-%04u%02u%02uT%02u%02u%02u.%03u%s"), st->wYear, st->wMonth, st->wDay, st->wHour, st->wMinute, st->wSecond, st->wMilliseconds, ext); 217 | *ext = _T('\0'); 218 | _sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension); 219 | } 220 | 221 | void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long delay, unsigned long low, unsigned long high, bool copy_and_truncate) { 222 | unsigned long error; 223 | 224 | /* Now. */ 225 | SYSTEMTIME st; 226 | GetSystemTime(&st); 227 | 228 | BY_HANDLE_FILE_INFORMATION info; 229 | 230 | /* Try to open the file to check if it exists and to get attributes. */ 231 | HANDLE file = CreateFile(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 232 | if (file != INVALID_HANDLE_VALUE) { 233 | /* Get file attributes. */ 234 | if (! GetFileInformationByHandle(file, &info)) { 235 | /* Reuse current time for rotation timestamp. */ 236 | seconds = low = high = 0; 237 | SystemTimeToFileTime(&st, &info.ftLastWriteTime); 238 | } 239 | 240 | CloseHandle(file); 241 | } 242 | else { 243 | error = GetLastError(); 244 | if (error == ERROR_FILE_NOT_FOUND) return; 245 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("CreateFile()"), path, error_string(error), 0); 246 | /* Reuse current time for rotation timestamp. */ 247 | seconds = low = high = 0; 248 | SystemTimeToFileTime(&st, &info.ftLastWriteTime); 249 | } 250 | 251 | /* Check file age. */ 252 | if (seconds) { 253 | FILETIME ft; 254 | SystemTimeToFileTime(&st, &ft); 255 | 256 | ULARGE_INTEGER s; 257 | s.LowPart = ft.dwLowDateTime; 258 | s.HighPart = ft.dwHighDateTime; 259 | s.QuadPart -= seconds * 10000000LL; 260 | ft.dwLowDateTime = s.LowPart; 261 | ft.dwHighDateTime = s.HighPart; 262 | if (CompareFileTime(&info.ftLastWriteTime, &ft) > 0) return; 263 | } 264 | 265 | /* Check file size. */ 266 | if (low || high) { 267 | if (info.nFileSizeHigh < high) return; 268 | if (info.nFileSizeHigh == high && info.nFileSizeLow < low) return; 269 | } 270 | 271 | /* Get new filename. */ 272 | FileTimeToSystemTime(&info.ftLastWriteTime, &st); 273 | 274 | TCHAR rotated[PATH_LENGTH]; 275 | rotated_filename(path, rotated, _countof(rotated), &st); 276 | 277 | /* Rotate. */ 278 | bool ok = true; 279 | TCHAR *function; 280 | if (copy_and_truncate) { 281 | function = _T("CopyFile()"); 282 | if (CopyFile(path, rotated, TRUE)) { 283 | file = write_to_file(path, NSSM_STDOUT_SHARING, 0, NSSM_STDOUT_DISPOSITION, NSSM_STDOUT_FLAGS); 284 | Sleep(delay); 285 | SetFilePointer(file, 0, 0, FILE_BEGIN); 286 | SetEndOfFile(file); 287 | CloseHandle(file); 288 | } 289 | else ok = false; 290 | } 291 | else { 292 | function = _T("MoveFile()"); 293 | if (! MoveFile(path, rotated)) ok = false; 294 | } 295 | if (ok) { 296 | log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, service_name, path, rotated, 0); 297 | return; 298 | } 299 | error = GetLastError(); 300 | 301 | if (error == ERROR_FILE_NOT_FOUND) return; 302 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, function, rotated, error_string(error), 0); 303 | return; 304 | } 305 | 306 | int get_output_handles(nssm_service_t *service, STARTUPINFO *si) { 307 | if (! si) return 1; 308 | bool inherit_handles = false; 309 | 310 | /* Allocate a new console so we get a fresh stdin, stdout and stderr. */ 311 | alloc_console(service); 312 | 313 | /* stdin */ 314 | if (service->stdin_path[0]) { 315 | si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, 0, service->stdin_disposition, service->stdin_flags, 0); 316 | if (si->hStdInput == INVALID_HANDLE_VALUE) { 317 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0); 318 | return 2; 319 | } 320 | 321 | inherit_handles = true; 322 | } 323 | 324 | /* stdout */ 325 | if (service->stdout_path[0]) { 326 | if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stdout_copy_and_truncate); 327 | HANDLE stdout_handle = write_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags); 328 | if (stdout_handle == INVALID_HANDLE_VALUE) return 4; 329 | service->stdout_si = 0; 330 | 331 | if (service->use_stdout_pipe) { 332 | service->stdout_pipe = si->hStdOutput = 0; 333 | service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &service->stdout_si, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stdout_tid, &service->rotate_stdout_online, service->timestamp_log, service->stdout_copy_and_truncate); 334 | if (! service->stdout_thread) { 335 | CloseHandle(service->stdout_pipe); 336 | CloseHandle(service->stdout_si); 337 | } 338 | } 339 | else service->stdout_thread = 0; 340 | 341 | if (! service->stdout_thread) { 342 | if (dup_handle(stdout_handle, &service->stdout_si, NSSM_REG_STDOUT, _T("stdout"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 4; 343 | service->rotate_stdout_online = NSSM_ROTATE_OFFLINE; 344 | } 345 | 346 | if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_si"), _T("stdout"))) close_handle(&service->stdout_thread); 347 | 348 | inherit_handles = true; 349 | } 350 | 351 | /* stderr */ 352 | if (service->stderr_path[0]) { 353 | /* Same as stdout? */ 354 | if (str_equiv(service->stderr_path, service->stdout_path)) { 355 | service->stderr_sharing = service->stdout_sharing; 356 | service->stderr_disposition = service->stdout_disposition; 357 | service->stderr_flags = service->stdout_flags; 358 | service->rotate_stderr_online = NSSM_ROTATE_OFFLINE; 359 | 360 | /* Two handles to the same file will create a race. */ 361 | /* XXX: Here we assume that either both or neither handle must be a pipe. */ 362 | if (dup_handle(service->stdout_si, &service->stderr_si, _T("stdout"), _T("stderr"))) return 6; 363 | } 364 | else { 365 | if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stderr_copy_and_truncate); 366 | HANDLE stderr_handle = write_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags); 367 | if (stderr_handle == INVALID_HANDLE_VALUE) return 7; 368 | service->stderr_si = 0; 369 | 370 | if (service->use_stderr_pipe) { 371 | service->stderr_pipe = si->hStdError = 0; 372 | service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &service->stderr_si, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stderr_tid, &service->rotate_stderr_online, service->timestamp_log, service->stderr_copy_and_truncate); 373 | if (! service->stderr_thread) { 374 | CloseHandle(service->stderr_pipe); 375 | CloseHandle(service->stderr_si); 376 | } 377 | } 378 | else service->stderr_thread = 0; 379 | 380 | if (! service->stderr_thread) { 381 | if (dup_handle(stderr_handle, &service->stderr_si, NSSM_REG_STDERR, _T("stderr"), DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) return 7; 382 | service->rotate_stderr_online = NSSM_ROTATE_OFFLINE; 383 | } 384 | } 385 | 386 | if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_si"), _T("stderr"))) close_handle(&service->stderr_thread); 387 | 388 | inherit_handles = true; 389 | } 390 | 391 | /* 392 | We need to set the startup_info flags to make the new handles 393 | inheritable by the new process. 394 | */ 395 | if (inherit_handles) si->dwFlags |= STARTF_USESTDHANDLES; 396 | 397 | return 0; 398 | } 399 | 400 | /* Reuse output handles for a hook. */ 401 | int use_output_handles(nssm_service_t *service, STARTUPINFO *si) { 402 | si->dwFlags &= ~STARTF_USESTDHANDLES; 403 | 404 | if (service->stdout_si) { 405 | if (dup_handle(service->stdout_si, &si->hStdOutput, _T("stdout_pipe"), _T("hStdOutput"))) return 1; 406 | si->dwFlags |= STARTF_USESTDHANDLES; 407 | } 408 | 409 | if (service->stderr_si) { 410 | if (dup_handle(service->stderr_si, &si->hStdError, _T("stderr_pipe"), _T("hStdError"))) { 411 | if (si->hStdOutput) { 412 | si->dwFlags &= ~STARTF_USESTDHANDLES; 413 | CloseHandle(si->hStdOutput); 414 | } 415 | return 2; 416 | } 417 | si->dwFlags |= STARTF_USESTDHANDLES; 418 | } 419 | 420 | return 0; 421 | } 422 | 423 | void close_output_handles(STARTUPINFO *si) { 424 | if (si->hStdInput) CloseHandle(si->hStdInput); 425 | if (si->hStdOutput) CloseHandle(si->hStdOutput); 426 | if (si->hStdError) CloseHandle(si->hStdError); 427 | } 428 | 429 | void cleanup_loggers(nssm_service_t *service) { 430 | unsigned long interval = NSSM_CLEANUP_LOGGERS_DEADLINE; 431 | HANDLE thread_handle = INVALID_HANDLE_VALUE; 432 | 433 | close_handle(&service->stdout_thread, &thread_handle); 434 | /* Close write end of the data pipe so logging thread can finalise read. */ 435 | close_handle(&service->stdout_si); 436 | /* Await logging thread then close read end. */ 437 | if (thread_handle != INVALID_HANDLE_VALUE) WaitForSingleObject(thread_handle, interval); 438 | close_handle(&service->stdout_pipe); 439 | 440 | thread_handle = INVALID_HANDLE_VALUE; 441 | close_handle(&service->stderr_thread, &thread_handle); 442 | close_handle(&service->stderr_si); 443 | if (thread_handle != INVALID_HANDLE_VALUE) WaitForSingleObject(thread_handle, interval); 444 | close_handle(&service->stderr_pipe); 445 | } 446 | 447 | /* 448 | Try multiple times to read from a file. 449 | Returns: 0 on success. 450 | 1 on non-fatal error. 451 | -1 on fatal error. 452 | */ 453 | static int try_read(logger_t *logger, void *address, unsigned long bufsize, unsigned long *in, int *complained) { 454 | int ret = 1; 455 | unsigned long error; 456 | for (int tries = 0; tries < 5; tries++) { 457 | if (ReadFile(logger->read_handle, address, bufsize, in, 0)) return 0; 458 | 459 | error = GetLastError(); 460 | switch (error) { 461 | /* Other end closed the pipe. */ 462 | case ERROR_BROKEN_PIPE: 463 | ret = -1; 464 | goto complain_read; 465 | 466 | /* Couldn't lock the buffer. */ 467 | case ERROR_NOT_ENOUGH_QUOTA: 468 | Sleep(2000 + tries * 3000); 469 | ret = 1; 470 | continue; 471 | 472 | /* Write was cancelled by the other end. */ 473 | case ERROR_OPERATION_ABORTED: 474 | ret = 1; 475 | goto complain_read; 476 | 477 | default: 478 | ret = -1; 479 | } 480 | } 481 | 482 | complain_read: 483 | /* Ignore the error if we've been requested to exit anyway. */ 484 | if (*logger->rotate_online != NSSM_ROTATE_ONLINE) return ret; 485 | if (! (*complained & COMPLAINED_READ)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_READFILE_FAILED, logger->service_name, logger->path, error_string(error), 0); 486 | *complained |= COMPLAINED_READ; 487 | return ret; 488 | } 489 | 490 | /* 491 | Try multiple times to write to a file. 492 | Returns: 0 on success. 493 | 1 on non-fatal error. 494 | -1 on fatal error. 495 | */ 496 | static int try_write(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained) { 497 | int ret = 1; 498 | unsigned long error; 499 | for (int tries = 0; tries < 5; tries++) { 500 | if (WriteFile(logger->write_handle, address, bufsize, out, 0)) return 0; 501 | 502 | error = GetLastError(); 503 | if (error == ERROR_IO_PENDING) { 504 | /* Operation was successful pending flush to disk. */ 505 | return 0; 506 | } 507 | 508 | switch (error) { 509 | /* Other end closed the pipe. */ 510 | case ERROR_BROKEN_PIPE: 511 | ret = -1; 512 | goto complain_write; 513 | 514 | /* Couldn't lock the buffer. */ 515 | case ERROR_NOT_ENOUGH_QUOTA: 516 | /* Out of disk space. */ 517 | case ERROR_DISK_FULL: 518 | Sleep(2000 + tries * 3000); 519 | ret = 1; 520 | continue; 521 | 522 | default: 523 | /* We'll lose this line but try to read and write subsequent ones. */ 524 | ret = 1; 525 | } 526 | } 527 | 528 | complain_write: 529 | if (! (*complained & COMPLAINED_WRITE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_WRITEFILE_FAILED, logger->service_name, logger->path, error_string(error), 0); 530 | *complained |= COMPLAINED_WRITE; 531 | return ret; 532 | } 533 | 534 | /* Note that the timestamp is created in UTF-8. */ 535 | static inline int write_timestamp(logger_t *logger, unsigned long charsize, unsigned long *out, int *complained) { 536 | char timestamp[TIMESTAMP_LEN + 1]; 537 | 538 | SYSTEMTIME now; 539 | GetSystemTime(&now); 540 | _snprintf_s(timestamp, _countof(timestamp), _TRUNCATE, TIMESTAMP_FORMAT, now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, now.wMilliseconds); 541 | 542 | if (charsize == sizeof(char)) return try_write(logger, (void *) timestamp, TIMESTAMP_LEN, out, complained); 543 | 544 | wchar_t *utf16; 545 | unsigned long utf16len; 546 | if (to_utf16(timestamp, &utf16, &utf16len)) return -1; 547 | int ret = try_write(logger, (void *) *utf16, utf16len * sizeof(wchar_t), out, complained); 548 | HeapFree(GetProcessHeap(), 0, utf16); 549 | return ret; 550 | } 551 | 552 | static int write_with_timestamp(logger_t *logger, void *address, unsigned long bufsize, unsigned long *out, int *complained, unsigned long charsize) { 553 | if (logger->timestamp_log) { 554 | unsigned long log_out; 555 | int log_complained; 556 | unsigned long timestamp_out = 0; 557 | int timestamp_complained; 558 | if (! logger->line_length) { 559 | write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained); 560 | logger->line_length += (__int64) timestamp_out; 561 | *out += timestamp_out; 562 | *complained |= timestamp_complained; 563 | } 564 | 565 | unsigned long i; 566 | void *line = address; 567 | unsigned long offset = 0; 568 | int ret; 569 | for (i = 0; i < bufsize; i++) { 570 | if (((char *) address)[i] == '\n') { 571 | ret = try_write(logger, line, i - offset + 1, &log_out, &log_complained); 572 | line = (void *) ((char *) line + i - offset + 1); 573 | logger->line_length = 0LL; 574 | *out += log_out; 575 | *complained |= log_complained; 576 | offset = i + 1; 577 | if (offset < bufsize) { 578 | write_timestamp(logger, charsize, ×tamp_out, ×tamp_complained); 579 | logger->line_length += (__int64) timestamp_out; 580 | *out += timestamp_out; 581 | *complained |= timestamp_complained; 582 | } 583 | } 584 | } 585 | 586 | if (offset < bufsize) { 587 | ret = try_write(logger, line, bufsize - offset, &log_out, &log_complained); 588 | *out += log_out; 589 | *complained |= log_complained; 590 | } 591 | 592 | return ret; 593 | } 594 | else return try_write(logger, address, bufsize, out, complained); 595 | } 596 | 597 | /* Wrapper to be called in a new thread for logging. */ 598 | unsigned long WINAPI log_and_rotate(void *arg) { 599 | logger_t *logger = (logger_t *) arg; 600 | if (! logger) return 1; 601 | 602 | __int64 size; 603 | BY_HANDLE_FILE_INFORMATION info; 604 | 605 | /* Find initial file size. */ 606 | if (! GetFileInformationByHandle(logger->write_handle, &info)) logger->size = 0LL; 607 | else { 608 | ULARGE_INTEGER l; 609 | l.HighPart = info.nFileSizeHigh; 610 | l.LowPart = info.nFileSizeLow; 611 | size = l.QuadPart; 612 | } 613 | 614 | char buffer[1024]; 615 | void *address; 616 | unsigned long in, out; 617 | unsigned long charsize = 0; 618 | unsigned long error; 619 | int ret; 620 | int complained = 0; 621 | 622 | while (true) { 623 | /* Read data from the pipe. */ 624 | address = &buffer; 625 | ret = try_read(logger, address, sizeof(buffer), &in, &complained); 626 | if (ret < 0) { 627 | close_handle(&logger->read_handle); 628 | close_handle(&logger->write_handle); 629 | HeapFree(GetProcessHeap(), 0, logger); 630 | return 2; 631 | } 632 | else if (ret) continue; 633 | 634 | if (*logger->rotate_online == NSSM_ROTATE_ONLINE_ASAP || (logger->size && size + (__int64) in >= logger->size)) { 635 | /* Look for newline. */ 636 | unsigned long i; 637 | for (i = 0; i < in; i++) { 638 | if (buffer[i] == '\n') { 639 | if (! charsize) charsize = guess_charsize(address, in); 640 | i += charsize; 641 | 642 | /* Write up to the newline. */ 643 | ret = try_write(logger, address, i, &out, &complained); 644 | if (ret < 0) { 645 | close_handle(&logger->read_handle); 646 | close_handle(&logger->write_handle); 647 | HeapFree(GetProcessHeap(), 0, logger); 648 | return 3; 649 | } 650 | size += (__int64) out; 651 | 652 | /* Rotate. */ 653 | *logger->rotate_online = NSSM_ROTATE_ONLINE; 654 | TCHAR rotated[PATH_LENGTH]; 655 | rotated_filename(logger->path, rotated, _countof(rotated), 0); 656 | 657 | /* 658 | Ideally we'd try the rename first then close the handle but 659 | MoveFile() will fail if the handle is still open so we must 660 | risk losing everything. 661 | */ 662 | if (logger->copy_and_truncate) FlushFileBuffers(logger->write_handle); 663 | close_handle(&logger->write_handle); 664 | bool ok = true; 665 | TCHAR *function; 666 | if (logger->copy_and_truncate) { 667 | function = _T("CopyFile()"); 668 | if (CopyFile(logger->path, rotated, TRUE)) { 669 | HANDLE file = write_to_file(logger->path, NSSM_STDOUT_SHARING, 0, NSSM_STDOUT_DISPOSITION, NSSM_STDOUT_FLAGS); 670 | Sleep(logger->rotate_delay); 671 | SetFilePointer(file, 0, 0, FILE_BEGIN); 672 | SetEndOfFile(file); 673 | CloseHandle(file); 674 | } 675 | else ok = false; 676 | } 677 | else { 678 | function = _T("MoveFile()"); 679 | if (! MoveFile(logger->path, rotated)) ok = false; 680 | } 681 | if (ok) { 682 | log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, logger->service_name, logger->path, rotated, 0); 683 | size = 0LL; 684 | } 685 | else { 686 | error = GetLastError(); 687 | if (error != ERROR_FILE_NOT_FOUND) { 688 | if (! (complained & COMPLAINED_ROTATE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, function, rotated, error_string(error), 0); 689 | complained |= COMPLAINED_ROTATE; 690 | /* We can at least try to re-open the existing file. */ 691 | logger->disposition = OPEN_ALWAYS; 692 | } 693 | } 694 | 695 | /* Reopen. */ 696 | logger->write_handle = write_to_file(logger->path, logger->sharing, 0, logger->disposition, logger->flags); 697 | if (logger->write_handle == INVALID_HANDLE_VALUE) { 698 | error = GetLastError(); 699 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, logger->path, error_string(error), 0); 700 | /* Oh dear. Now we can't log anything further. */ 701 | close_handle(&logger->read_handle); 702 | close_handle(&logger->write_handle); 703 | HeapFree(GetProcessHeap(), 0, logger); 704 | return 4; 705 | } 706 | 707 | /* Resume writing after the newline. */ 708 | address = (void *) ((char *) address + i); 709 | in -= i; 710 | } 711 | } 712 | } 713 | 714 | if (! size || logger->timestamp_log) if (! charsize) charsize = guess_charsize(address, in); 715 | if (! size) { 716 | /* Write a BOM to the new file. */ 717 | if (charsize == sizeof(wchar_t)) write_bom(logger, &out); 718 | size += (__int64) out; 719 | } 720 | 721 | /* Write the data, if any. */ 722 | if (! in) continue; 723 | 724 | ret = write_with_timestamp(logger, address, in, &out, &complained, charsize); 725 | size += (__int64) out; 726 | if (ret < 0) { 727 | close_handle(&logger->read_handle); 728 | close_handle(&logger->write_handle); 729 | HeapFree(GetProcessHeap(), 0, logger); 730 | return 3; 731 | } 732 | } 733 | 734 | close_handle(&logger->read_handle); 735 | close_handle(&logger->write_handle); 736 | HeapFree(GetProcessHeap(), 0, logger); 737 | return 0; 738 | } 739 | -------------------------------------------------------------------------------- /io.h: -------------------------------------------------------------------------------- 1 | #ifndef IO_H 2 | #define IO_H 3 | 4 | #define NSSM_STDIN_SHARING FILE_SHARE_WRITE 5 | #define NSSM_STDIN_DISPOSITION OPEN_EXISTING 6 | #define NSSM_STDIN_FLAGS FILE_ATTRIBUTE_NORMAL 7 | #define NSSM_STDOUT_SHARING (FILE_SHARE_READ | FILE_SHARE_WRITE) 8 | #define NSSM_STDOUT_DISPOSITION OPEN_ALWAYS 9 | #define NSSM_STDOUT_FLAGS FILE_ATTRIBUTE_NORMAL 10 | #define NSSM_STDERR_SHARING (FILE_SHARE_READ | FILE_SHARE_WRITE) 11 | #define NSSM_STDERR_DISPOSITION OPEN_ALWAYS 12 | #define NSSM_STDERR_FLAGS FILE_ATTRIBUTE_NORMAL 13 | 14 | typedef struct { 15 | TCHAR *service_name; 16 | TCHAR *path; 17 | unsigned long sharing; 18 | unsigned long disposition; 19 | unsigned long flags; 20 | HANDLE read_handle; 21 | HANDLE write_handle; 22 | __int64 size; 23 | unsigned long *tid_ptr; 24 | unsigned long *rotate_online; 25 | bool timestamp_log; 26 | __int64 line_length; 27 | bool copy_and_truncate; 28 | unsigned long rotate_delay; 29 | } logger_t; 30 | 31 | void close_handle(HANDLE *, HANDLE *); 32 | void close_handle(HANDLE *); 33 | int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long, bool *); 34 | int set_createfile_parameter(HKEY, TCHAR *, TCHAR *, unsigned long); 35 | int delete_createfile_parameter(HKEY, TCHAR *, TCHAR *); 36 | HANDLE write_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long); 37 | void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long, unsigned long, bool); 38 | int get_output_handles(nssm_service_t *, STARTUPINFO *); 39 | int use_output_handles(nssm_service_t *, STARTUPINFO *); 40 | void close_output_handles(STARTUPINFO *); 41 | void cleanup_loggers(nssm_service_t *); 42 | unsigned long WINAPI log_and_rotate(void *); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /messages.mc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/nssm/e627816b29a62298e90a94f3ab5373d5f39da11f/messages.mc -------------------------------------------------------------------------------- /nssm.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | extern unsigned long tls_index; 4 | extern bool is_admin; 5 | extern imports_t imports; 6 | 7 | static TCHAR unquoted_imagepath[PATH_LENGTH]; 8 | static TCHAR imagepath[PATH_LENGTH]; 9 | static TCHAR imageargv0[PATH_LENGTH]; 10 | 11 | void nssm_exit(int status) { 12 | free_imports(); 13 | unsetup_utf8(); 14 | exit(status); 15 | } 16 | 17 | /* Are two strings case-insensitively equivalent? */ 18 | int str_equiv(const TCHAR *a, const TCHAR *b) { 19 | size_t len = _tcslen(a); 20 | if (_tcslen(b) != len) return 0; 21 | if (_tcsnicmp(a, b, len)) return 0; 22 | return 1; 23 | } 24 | 25 | /* Convert a string to a number. */ 26 | int str_number(const TCHAR *string, unsigned long *number, TCHAR **bogus) { 27 | if (! string) return 1; 28 | 29 | *number = _tcstoul(string, bogus, 0); 30 | if (**bogus) return 2; 31 | 32 | return 0; 33 | } 34 | 35 | /* User requested us to print our version. */ 36 | static bool is_version(const TCHAR *s) { 37 | if (! s || ! *s) return false; 38 | /* /version */ 39 | if (*s == '/') s++; 40 | else if (*s == '-') { 41 | /* -v, -V, -version, --version */ 42 | s++; 43 | if (*s == '-') s++; 44 | else if (str_equiv(s, _T("v"))) return true; 45 | } 46 | if (str_equiv(s, _T("version"))) return true; 47 | return false; 48 | } 49 | 50 | int str_number(const TCHAR *string, unsigned long *number) { 51 | TCHAR *bogus; 52 | return str_number(string, number, &bogus); 53 | } 54 | 55 | /* Does a char need to be escaped? */ 56 | static bool needs_escape(const TCHAR c) { 57 | if (c == _T('"')) return true; 58 | if (c == _T('&')) return true; 59 | if (c == _T('%')) return true; 60 | if (c == _T('^')) return true; 61 | if (c == _T('<')) return true; 62 | if (c == _T('>')) return true; 63 | if (c == _T('|')) return true; 64 | return false; 65 | } 66 | 67 | /* Does a char need to be quoted? */ 68 | static bool needs_quote(const TCHAR c) { 69 | if (c == _T(' ')) return true; 70 | if (c == _T('\t')) return true; 71 | if (c == _T('\n')) return true; 72 | if (c == _T('\v')) return true; 73 | if (c == _T('"')) return true; 74 | if (c == _T('*')) return true; 75 | return needs_escape(c); 76 | } 77 | 78 | /* https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ */ 79 | /* http://www.robvanderwoude.com/escapechars.php */ 80 | int quote(const TCHAR *unquoted, TCHAR *buffer, size_t buflen) { 81 | size_t i, j, n; 82 | size_t len = _tcslen(unquoted); 83 | if (len > buflen - 1) return 1; 84 | 85 | bool escape = false; 86 | bool quotes = false; 87 | 88 | for (i = 0; i < len; i++) { 89 | if (needs_escape(unquoted[i])) { 90 | escape = quotes = true; 91 | break; 92 | } 93 | if (needs_quote(unquoted[i])) quotes = true; 94 | } 95 | if (! quotes) { 96 | memmove(buffer, unquoted, (len + 1) * sizeof(TCHAR)); 97 | return 0; 98 | } 99 | 100 | /* "" */ 101 | size_t quoted_len = 2; 102 | if (escape) quoted_len += 2; 103 | for (i = 0; ; i++) { 104 | n = 0; 105 | 106 | while (i != len && unquoted[i] == _T('\\')) { 107 | i++; 108 | n++; 109 | } 110 | 111 | if (i == len) { 112 | quoted_len += n * 2; 113 | break; 114 | } 115 | else if (unquoted[i] == _T('"')) quoted_len += n * 2 + 2; 116 | else quoted_len += n + 1; 117 | if (needs_escape(unquoted[i])) quoted_len += n; 118 | } 119 | if (quoted_len > buflen - 1) return 1; 120 | 121 | TCHAR *s = buffer; 122 | if (escape) *s++ = _T('^'); 123 | *s++ = _T('"'); 124 | 125 | for (i = 0; ; i++) { 126 | n = 0; 127 | 128 | while (i != len && unquoted[i] == _T('\\')) { 129 | i++; 130 | n++; 131 | } 132 | 133 | if (i == len) { 134 | for (j = 0; j < n * 2; j++) { 135 | if (escape) *s++ = _T('^'); 136 | *s++ = _T('\\'); 137 | } 138 | break; 139 | } 140 | else if (unquoted[i] == _T('"')) { 141 | for (j = 0; j < n * 2 + 1; j++) { 142 | if (escape) *s++ = _T('^'); 143 | *s++ = _T('\\'); 144 | } 145 | if (escape && needs_escape(unquoted[i])) *s++ = _T('^'); 146 | *s++ = unquoted[i]; 147 | } 148 | else { 149 | for (j = 0; j < n; j++) { 150 | if (escape) *s++ = _T('^'); 151 | *s++ = _T('\\'); 152 | } 153 | if (escape && needs_escape(unquoted[i])) *s++ = _T('^'); 154 | *s++ = unquoted[i]; 155 | } 156 | } 157 | if (escape) *s++ = _T('^'); 158 | *s++ = _T('"'); 159 | *s++ = _T('\0'); 160 | 161 | return 0; 162 | } 163 | 164 | /* Remove basename of a path. */ 165 | void strip_basename(TCHAR *buffer) { 166 | size_t len = _tcslen(buffer); 167 | size_t i; 168 | for (i = len; i && buffer[i] != _T('\\') && buffer[i] != _T('/'); i--); 169 | /* X:\ is OK. */ 170 | if (i && buffer[i - 1] == _T(':')) i++; 171 | buffer[i] = _T('\0'); 172 | } 173 | 174 | /* How to use me correctly */ 175 | int usage(int ret) { 176 | if ((! GetConsoleWindow() || ! GetStdHandle(STD_OUTPUT_HANDLE)) && GetProcessWindowStation()) popup_message(0, MB_OK, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE); 177 | else print_message(stderr, NSSM_MESSAGE_USAGE, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE); 178 | return(ret); 179 | } 180 | 181 | void check_admin() { 182 | is_admin = false; 183 | 184 | /* Lifted from MSDN examples */ 185 | PSID AdministratorsGroup; 186 | SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; 187 | if (! AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) return; 188 | CheckTokenMembership(0, AdministratorsGroup, /*XXX*/(PBOOL) &is_admin); 189 | FreeSid(AdministratorsGroup); 190 | } 191 | 192 | static int elevate(int argc, TCHAR **argv, unsigned long message) { 193 | print_message(stderr, message); 194 | 195 | SHELLEXECUTEINFO sei; 196 | ZeroMemory(&sei, sizeof(sei)); 197 | sei.cbSize = sizeof(sei); 198 | sei.lpVerb = _T("runas"); 199 | sei.lpFile = (TCHAR *) nssm_imagepath(); 200 | 201 | TCHAR *args = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EXE_LENGTH * sizeof(TCHAR)); 202 | if (! args) { 203 | print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetCommandLine()"), _T("elevate()")); 204 | return 111; 205 | } 206 | 207 | /* Get command line, which includes the path to NSSM, and skip that part. */ 208 | _sntprintf_s(args, EXE_LENGTH, _TRUNCATE, _T("%s"), GetCommandLine()); 209 | size_t s = _tcslen(argv[0]) + 1; 210 | if (args[0] == _T('"')) s += 2; 211 | while (isspace(args[s])) s++; 212 | 213 | sei.lpParameters = args + s; 214 | sei.nShow = SW_SHOW; 215 | 216 | unsigned long exitcode = 0; 217 | if (! ShellExecuteEx(&sei)) exitcode = 100; 218 | 219 | HeapFree(GetProcessHeap(), 0, (void *) args); 220 | return exitcode; 221 | } 222 | 223 | int num_cpus() { 224 | DWORD_PTR i, affinity, system_affinity; 225 | if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) return 64; 226 | for (i = 0; system_affinity & (1LL << i); i++) if (i == 64) break; 227 | return (int) i; 228 | } 229 | 230 | const TCHAR *nssm_unquoted_imagepath() { 231 | return unquoted_imagepath; 232 | } 233 | 234 | const TCHAR *nssm_imagepath() { 235 | return imagepath; 236 | } 237 | 238 | const TCHAR *nssm_exe() { 239 | return imageargv0; 240 | } 241 | 242 | int _tmain(int argc, TCHAR **argv) { 243 | if (check_console()) setup_utf8(); 244 | 245 | /* Remember if we are admin */ 246 | check_admin(); 247 | 248 | /* Set up function pointers. */ 249 | if (get_imports()) nssm_exit(111); 250 | 251 | /* Remember our path for later. */ 252 | _sntprintf_s(imageargv0, _countof(imageargv0), _TRUNCATE, _T("%s"), argv[0]); 253 | PathQuoteSpaces(imageargv0); 254 | GetModuleFileName(0, unquoted_imagepath, _countof(unquoted_imagepath)); 255 | GetModuleFileName(0, imagepath, _countof(imagepath)); 256 | PathQuoteSpaces(imagepath); 257 | 258 | /* Elevate */ 259 | if (argc > 1) { 260 | /* 261 | Valid commands are: 262 | start, stop, pause, continue, install, edit, get, set, reset, unset, remove 263 | status, statuscode, rotate, list, processes, version 264 | */ 265 | if (is_version(argv[1])) { 266 | _tprintf(_T("%s %s %s %s\n"), NSSM, NSSM_VERSION, NSSM_CONFIGURATION, NSSM_DATE); 267 | nssm_exit(0); 268 | } 269 | if (str_equiv(argv[1], _T("start"))) nssm_exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2)); 270 | if (str_equiv(argv[1], _T("stop"))) nssm_exit(control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2)); 271 | if (str_equiv(argv[1], _T("restart"))) { 272 | int ret = control_service(SERVICE_CONTROL_STOP, argc - 2, argv + 2); 273 | if (ret) nssm_exit(ret); 274 | nssm_exit(control_service(NSSM_SERVICE_CONTROL_START, argc - 2, argv + 2)); 275 | } 276 | if (str_equiv(argv[1], _T("pause"))) nssm_exit(control_service(SERVICE_CONTROL_PAUSE, argc - 2, argv + 2)); 277 | if (str_equiv(argv[1], _T("continue"))) nssm_exit(control_service(SERVICE_CONTROL_CONTINUE, argc - 2, argv + 2)); 278 | if (str_equiv(argv[1], _T("status"))) nssm_exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2)); 279 | if (str_equiv(argv[1], _T("statuscode"))) nssm_exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2, true)); 280 | if (str_equiv(argv[1], _T("rotate"))) nssm_exit(control_service(NSSM_SERVICE_CONTROL_ROTATE, argc - 2, argv + 2)); 281 | if (str_equiv(argv[1], _T("install"))) { 282 | if (! is_admin) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL)); 283 | create_messages(); 284 | nssm_exit(pre_install_service(argc - 2, argv + 2)); 285 | } 286 | if (str_equiv(argv[1], _T("edit")) || str_equiv(argv[1], _T("get")) || str_equiv(argv[1], _T("set")) || str_equiv(argv[1], _T("reset")) || str_equiv(argv[1], _T("unset")) || str_equiv(argv[1], _T("dump"))) { 287 | int ret = pre_edit_service(argc - 1, argv + 1); 288 | if (ret == 3 && ! is_admin && argc == 3) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT)); 289 | /* There might be a password here. */ 290 | for (int i = 0; i < argc; i++) SecureZeroMemory(argv[i], _tcslen(argv[i]) * sizeof(TCHAR)); 291 | nssm_exit(ret); 292 | } 293 | if (str_equiv(argv[1], _T("list"))) nssm_exit(list_nssm_services(argc - 2, argv + 2)); 294 | if (str_equiv(argv[1], _T("processes"))) nssm_exit(service_process_tree(argc - 2, argv + 2)); 295 | if (str_equiv(argv[1], _T("remove"))) { 296 | if (! is_admin) nssm_exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE)); 297 | nssm_exit(pre_remove_service(argc - 2, argv + 2)); 298 | } 299 | } 300 | 301 | /* Thread local storage for error message buffer */ 302 | tls_index = TlsAlloc(); 303 | 304 | /* Register messages */ 305 | if (is_admin) create_messages(); 306 | 307 | /* 308 | Optimisation for Windows 2000: 309 | When we're run from the command line the StartServiceCtrlDispatcher() call 310 | will time out after a few seconds on Windows 2000. On newer versions the 311 | call returns instantly. Check for stdin first and only try to call the 312 | function if there's no input stream found. Although it's possible that 313 | we're running with input redirected it's much more likely that we're 314 | actually running as a service. 315 | This will save time when running with no arguments from a command prompt. 316 | */ 317 | if (! GetStdHandle(STD_INPUT_HANDLE)) { 318 | /* Start service magic */ 319 | SERVICE_TABLE_ENTRY table[] = { { NSSM, service_main }, { 0, 0 } }; 320 | if (! StartServiceCtrlDispatcher(table)) { 321 | unsigned long error = GetLastError(); 322 | /* User probably ran nssm with no argument */ 323 | if (error == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) nssm_exit(usage(1)); 324 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_DISPATCHER_FAILED, error_string(error), 0); 325 | nssm_exit(100); 326 | } 327 | } 328 | else nssm_exit(usage(1)); 329 | 330 | /* And nothing more to do */ 331 | nssm_exit(0); 332 | } 333 | -------------------------------------------------------------------------------- /nssm.h: -------------------------------------------------------------------------------- 1 | #ifndef NSSM_H 2 | #define NSSM_H 3 | 4 | /* 5 | MSDN says, basically, that the maximum length of a path is 260 characters, 6 | which is represented by the constant MAX_PATH. Except when it isn't. 7 | 8 | The maximum length of a directory path is MAX_PATH - 12 because it must be 9 | possible to create a file in 8.3 format under any valid directory. 10 | 11 | Unicode versions of filesystem API functions accept paths up to 32767 12 | characters if the first four (wide) characters are L"\\?\" and each component 13 | of the path, separated by L"\", does not exceed the value of 14 | lpMaximumComponentLength returned by GetVolumeInformation(), which is 15 | probably 255. But might not be. 16 | 17 | Relative paths are always limited to MAX_PATH because the L"\\?\" prefix 18 | is not valid for a relative path. 19 | 20 | Note that we don't care about the last two paragraphs because we're only 21 | concerned with allocating buffers big enough to store valid paths. If the 22 | user tries to store invalid paths they will fit in the buffers but the 23 | application will fail. The reason for the failure will end up in the 24 | event log and the user will realise the mistake. 25 | 26 | So that's that cleared up, then. 27 | */ 28 | #ifdef UNICODE 29 | #define PATH_LENGTH 32767 30 | #else 31 | #define PATH_LENGTH MAX_PATH 32 | #endif 33 | #define DIR_LENGTH PATH_LENGTH - 12 34 | 35 | #define _WIN32_WINNT 0x0500 36 | 37 | #define APSTUDIO_HIDDEN_SYMBOLS 38 | #include 39 | #include 40 | #undef APSTUDIO_HIDDEN_SYMBOLS 41 | #include 42 | #include 43 | #ifndef NSSM_COMPILE_RC 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include "utf8.h" 50 | #include "service.h" 51 | #include "account.h" 52 | #include "console.h" 53 | #include "env.h" 54 | #include "event.h" 55 | #include "hook.h" 56 | #include "imports.h" 57 | #include "messages.h" 58 | #include "process.h" 59 | #include "registry.h" 60 | #include "settings.h" 61 | #include "io.h" 62 | #include "gui.h" 63 | #endif 64 | 65 | void nssm_exit(int); 66 | int str_equiv(const TCHAR *, const TCHAR *); 67 | int quote(const TCHAR *, TCHAR *, size_t); 68 | void strip_basename(TCHAR *); 69 | int str_number(const TCHAR *, unsigned long *, TCHAR **); 70 | int str_number(const TCHAR *, unsigned long *); 71 | int num_cpus(); 72 | int usage(int); 73 | const TCHAR *nssm_unquoted_imagepath(); 74 | const TCHAR *nssm_imagepath(); 75 | const TCHAR *nssm_exe(); 76 | 77 | #define NSSM _T("NSSM") 78 | #ifdef _WIN64 79 | #define NSSM_ARCHITECTURE _T("64-bit") 80 | #else 81 | #define NSSM_ARCHITECTURE _T("32-bit") 82 | #endif 83 | #ifdef _DEBUG 84 | #define NSSM_DEBUG _T(" debug") 85 | #else 86 | #define NSSM_DEBUG _T("") 87 | #endif 88 | #define NSSM_CONFIGURATION NSSM_ARCHITECTURE NSSM_DEBUG 89 | #include "version.h" 90 | 91 | /* 92 | Throttle the restart of the service if it stops before this many 93 | milliseconds have elapsed since startup. Override in registry. 94 | */ 95 | #define NSSM_RESET_THROTTLE_RESTART 1500 96 | 97 | /* 98 | How many milliseconds to wait for the application to die after sending 99 | a Control-C event to its console. Override in registry. 100 | */ 101 | #define NSSM_KILL_CONSOLE_GRACE_PERIOD 1500 102 | /* 103 | How many milliseconds to wait for the application to die after posting to 104 | its windows' message queues. Override in registry. 105 | */ 106 | #define NSSM_KILL_WINDOW_GRACE_PERIOD 1500 107 | /* 108 | How many milliseconds to wait for the application to die after posting to 109 | its threads' message queues. Override in registry. 110 | */ 111 | #define NSSM_KILL_THREADS_GRACE_PERIOD 1500 112 | 113 | /* How many milliseconds to pause after rotating logs. */ 114 | #define NSSM_ROTATE_DELAY 0 115 | 116 | /* Margin of error for service status wait hints in milliseconds. */ 117 | #define NSSM_WAITHINT_MARGIN 2000 118 | 119 | /* Methods used to try to stop the application. */ 120 | #define NSSM_STOP_METHOD_CONSOLE (1 << 0) 121 | #define NSSM_STOP_METHOD_WINDOW (1 << 1) 122 | #define NSSM_STOP_METHOD_THREADS (1 << 2) 123 | #define NSSM_STOP_METHOD_TERMINATE (1 << 3) 124 | 125 | /* Startup types. */ 126 | #define NSSM_STARTUP_AUTOMATIC 0 127 | #define NSSM_STARTUP_DELAYED 1 128 | #define NSSM_STARTUP_MANUAL 2 129 | #define NSSM_STARTUP_DISABLED 3 130 | 131 | /* Exit actions. */ 132 | #define NSSM_EXIT_RESTART 0 133 | #define NSSM_EXIT_IGNORE 1 134 | #define NSSM_EXIT_REALLY 2 135 | #define NSSM_EXIT_UNCLEAN 3 136 | #define NSSM_NUM_EXIT_ACTIONS 4 137 | 138 | /* Process priority. */ 139 | #define NSSM_REALTIME_PRIORITY 0 140 | #define NSSM_HIGH_PRIORITY 1 141 | #define NSSM_ABOVE_NORMAL_PRIORITY 2 142 | #define NSSM_NORMAL_PRIORITY 3 143 | #define NSSM_BELOW_NORMAL_PRIORITY 4 144 | #define NSSM_IDLE_PRIORITY 5 145 | 146 | /* How many milliseconds to wait before updating service status. */ 147 | #define NSSM_SERVICE_STATUS_DEADLINE 20000 148 | 149 | /* User-defined service controls can be in the range 128-255. */ 150 | #define NSSM_SERVICE_CONTROL_START 0 151 | #define NSSM_SERVICE_CONTROL_ROTATE 128 152 | 153 | /* How many milliseconds to wait for a hook. */ 154 | #define NSSM_HOOK_DEADLINE 60000 155 | 156 | /* How many milliseconds to wait for outstanding hooks. */ 157 | #define NSSM_HOOK_THREAD_DEADLINE 80000 158 | 159 | /* How many milliseconds to wait for closing logging thread. */ 160 | #define NSSM_CLEANUP_LOGGERS_DEADLINE 1500 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /nssm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/nssm/e627816b29a62298e90a94f3ab5373d5f39da11f/nssm.ico -------------------------------------------------------------------------------- /nssm.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/nssm/e627816b29a62298e90a94f3ab5373d5f39da11f/nssm.rc -------------------------------------------------------------------------------- /nssm.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual Studio 2008 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nssm", "nssm.vcproj", "{32995E05-606F-4D83-A2E6-C2B361B34DF1}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Release|win64 = Release|win64 9 | Release|win32 = Release|win32 10 | Debug|win64 = Debug|win64 11 | Debug|win32 = Debug|win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|win64.ActiveCfg = Release|x64 15 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|win64.Build.0 = Release|x64 16 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|win32.ActiveCfg = Release|Win32 17 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Release|win32.Build.0 = Release|Win32 18 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|win64.ActiveCfg = Debug|x64 19 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|win64.Build.0 = Debug|x64 20 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|win32.ActiveCfg = Debug|Win32 21 | {32995E05-606F-4D83-A2E6-C2B361B34DF1}.Debug|win32.Build.0 = Debug|Win32 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /nssm.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 17 | 18 | 19 | 20 | 21 | 31 | 36 | 42 | 45 | 48 | 53 | 68 | 71 | 76 | 79 | 91 | 94 | 97 | 100 | 105 | 108 | 111 | 114 | 115 | 125 | 130 | 136 | 139 | 142 | 148 | 163 | 166 | 171 | 174 | 186 | 189 | 192 | 195 | 200 | 203 | 206 | 209 | 210 | 220 | 225 | 231 | 234 | 237 | 242 | 258 | 261 | 266 | 269 | 283 | 286 | 289 | 292 | 297 | 300 | 303 | 306 | 307 | 317 | 322 | 328 | 331 | 334 | 340 | 356 | 359 | 364 | 367 | 381 | 384 | 387 | 390 | 395 | 398 | 401 | 404 | 405 | 406 | 407 | 408 | 409 | 413 | 416 | 417 | 420 | 421 | 424 | 425 | 428 | 431 | 435 | 436 | 439 | 443 | 444 | 447 | 451 | 452 | 455 | 459 | 460 | 461 | 464 | 467 | 471 | 472 | 475 | 479 | 480 | 483 | 487 | 488 | 491 | 495 | 496 | 497 | 500 | 501 | 504 | 505 | 508 | 509 | 512 | 515 | 519 | 520 | 523 | 527 | 528 | 531 | 535 | 536 | 539 | 543 | 544 | 545 | 548 | 551 | 555 | 556 | 559 | 563 | 564 | 567 | 571 | 572 | 575 | 579 | 580 | 581 | 584 | 587 | 591 | 592 | 595 | 599 | 600 | 603 | 607 | 608 | 611 | 615 | 616 | 617 | 620 | 623 | 627 | 628 | 631 | 635 | 636 | 639 | 643 | 644 | 647 | 651 | 652 | 653 | 656 | 657 | 660 | 661 | 662 | 666 | 669 | 670 | 673 | 674 | 677 | 678 | 681 | 682 | 685 | 686 | 689 | 690 | 693 | 694 | 697 | 698 | 701 | 702 | 705 | 706 | 709 | 710 | 713 | 714 | 717 | 718 | 721 | 722 | 723 | 727 | 730 | 731 | 734 | 737 | 741 | 742 | 745 | 749 | 750 | 753 | 757 | 758 | 761 | 765 | 766 | 767 | 768 | 771 | 774 | 780 | 781 | 784 | 790 | 791 | 794 | 801 | 802 | 805 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | -------------------------------------------------------------------------------- /nssm.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {32995E05-606F-4D83-A2E6-C2B361B34DF1} 23 | nssm 24 | 25 | 26 | 27 | Application 28 | v141 29 | 8.1 30 | false 31 | Unicode 32 | 33 | 34 | Application 35 | v141 36 | 8.1 37 | false 38 | Unicode 39 | 40 | 41 | Application 42 | v141 43 | 8.1 44 | false 45 | Unicode 46 | 47 | 48 | Application 49 | v141 50 | 8.1 51 | false 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | <_ProjectFileVersion>15.0.26730.3 76 | 77 | 78 | out\$(Configuration)\win32\ 79 | tmp\$(Configuration)\win32\ 80 | true 81 | 82 | 83 | out\$(Configuration)\win64\ 84 | tmp\$(Configuration)\win64\ 85 | true 86 | 87 | 88 | out\$(Configuration)\win32\ 89 | tmp\$(Configuration)\win32\ 90 | false 91 | 92 | 93 | out\$(Configuration)\win64\ 94 | tmp\$(Configuration)\win64\ 95 | false 96 | 97 | 98 | 99 | Setting version information 100 | version.cmd 101 | 102 | 103 | 104 | 105 | 106 | 107 | $(IntDir)$(ProjectName).tlb 108 | 109 | 110 | 111 | Disabled 112 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 113 | true 114 | EnableFastChecks 115 | MultiThreadedDebug 116 | $(IntDir)$(ProjectName).pch 117 | $(IntDir) 118 | $(IntDir) 119 | $(IntDir) 120 | Level3 121 | true 122 | ProgramDatabase 123 | 124 | 125 | _DEBUG;%(PreprocessorDefinitions) 126 | 0x0809 127 | 128 | 129 | psapi.lib;shlwapi.lib;%(AdditionalDependencies) 130 | true 131 | true 132 | $(OutDir)$(ProjectName).pdb 133 | Console 134 | true 135 | 136 | MachineX86 137 | 138 | 139 | true 140 | $(IntDir)$(ProjectName).bsc 141 | 142 | 143 | 144 | 145 | Setting version information 146 | version.cmd 147 | 148 | 149 | 150 | 151 | 152 | 153 | X64 154 | $(IntDir)$(ProjectName).tlb 155 | 156 | 157 | 158 | Disabled 159 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 160 | true 161 | EnableFastChecks 162 | MultiThreadedDebug 163 | $(IntDir)$(ProjectName).pch 164 | $(IntDir) 165 | $(IntDir) 166 | $(IntDir) 167 | Level3 168 | true 169 | ProgramDatabase 170 | 171 | 172 | _DEBUG;_WIN64;%(PreprocessorDefinitions) 173 | 0x0809 174 | 175 | 176 | psapi.lib;shlwapi.lib;%(AdditionalDependencies) 177 | true 178 | true 179 | $(OutDir)$(ProjectName).pdb 180 | Console 181 | true 182 | 183 | MachineX64 184 | 185 | 186 | true 187 | $(IntDir)$(ProjectName).bsc 188 | 189 | 190 | 191 | 192 | Setting version information 193 | version.cmd 194 | 195 | 196 | 197 | 198 | 199 | 200 | $(IntDir)$(ProjectName).tlb 201 | 202 | 203 | 204 | MaxSpeed 205 | OnlyExplicitInline 206 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 207 | true 208 | MultiThreaded 209 | true 210 | $(IntDir)$(ProjectName).pch 211 | $(IntDir) 212 | $(IntDir) 213 | $(IntDir) 214 | Level3 215 | true 216 | ProgramDatabase 217 | 218 | 219 | NDEBUG;%(PreprocessorDefinitions) 220 | 0x0809 221 | 222 | 223 | psapi.lib;shlwapi.lib;%(AdditionalDependencies) 224 | true 225 | true 226 | $(OutDir)$(ProjectName).pdb 227 | Console 228 | true 229 | true 230 | true 231 | 232 | MachineX86 233 | 234 | 235 | true 236 | $(IntDir)$(ProjectName).bsc 237 | 238 | 239 | 240 | 241 | Setting version information 242 | version.cmd 243 | 244 | 245 | 246 | 247 | 248 | 249 | X64 250 | $(IntDir)$(ProjectName).tlb 251 | 252 | 253 | 254 | MaxSpeed 255 | OnlyExplicitInline 256 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 257 | true 258 | MultiThreaded 259 | true 260 | $(IntDir)$(ProjectName).pch 261 | $(IntDir) 262 | $(IntDir) 263 | $(IntDir) 264 | Level3 265 | true 266 | ProgramDatabase 267 | 268 | 269 | NDEBUG;_WIN64;%(PreprocessorDefinitions) 270 | 0x0809 271 | 272 | 273 | psapi.lib;shlwapi.lib;%(AdditionalDependencies) 274 | true 275 | true 276 | $(OutDir)$(ProjectName).pdb 277 | Console 278 | true 279 | true 280 | true 281 | 282 | MachineX64 283 | 284 | 285 | true 286 | $(IntDir)$(ProjectName).bsc 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | Compiling messages 330 | mc -u -U %(Filename).mc -r . -h . 331 | 332 | %(Filename).rc;%(Filename).h;%(Outputs) 333 | Compiling messages 334 | mc -u -U %(Filename).mc -r . -h . 335 | 336 | %(Filename).rc;%(Filename).h;%(Outputs) 337 | Compiling messages 338 | mc -u -U %(Filename).mc -r . -h . 339 | 340 | %(Filename).rc;%(Filename).h;%(Outputs) 341 | Compiling messages 342 | mc -u -U %(Filename).mc -r . -h . 343 | 344 | %(Filename).rc;%(Filename).h;%(Outputs) 345 | 346 | 347 | 348 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /process.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | extern imports_t imports; 4 | 5 | HANDLE get_debug_token() { 6 | long error; 7 | HANDLE token; 8 | if (! OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, &token)) { 9 | error = GetLastError(); 10 | if (error == ERROR_NO_TOKEN) { 11 | (void) ImpersonateSelf(SecurityImpersonation); 12 | (void) OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, &token); 13 | } 14 | } 15 | if (! token) return INVALID_HANDLE_VALUE; 16 | 17 | TOKEN_PRIVILEGES privileges, old; 18 | unsigned long size = sizeof(TOKEN_PRIVILEGES); 19 | LUID luid; 20 | if (! LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) { 21 | CloseHandle(token); 22 | return INVALID_HANDLE_VALUE; 23 | } 24 | 25 | privileges.PrivilegeCount = 1; 26 | privileges.Privileges[0].Luid = luid; 27 | privileges.Privileges[0].Attributes = 0; 28 | 29 | if (! AdjustTokenPrivileges(token, false, &privileges, size, &old, &size)) { 30 | CloseHandle(token); 31 | return INVALID_HANDLE_VALUE; 32 | } 33 | 34 | old.PrivilegeCount = 1; 35 | old.Privileges[0].Luid = luid; 36 | old.Privileges[0].Attributes |= SE_PRIVILEGE_ENABLED; 37 | 38 | if (! AdjustTokenPrivileges(token, false, &old, size, NULL, NULL)) { 39 | CloseHandle(token); 40 | return INVALID_HANDLE_VALUE; 41 | } 42 | 43 | return token; 44 | } 45 | 46 | void service_kill_t(nssm_service_t *service, kill_t *k) { 47 | if (! service) return; 48 | if (! k) return; 49 | 50 | ZeroMemory(k, sizeof(*k)); 51 | k->name = service->name; 52 | k->process_handle = service->process_handle; 53 | k->pid = service->pid; 54 | k->exitcode = service->exitcode; 55 | k->stop_method = service->stop_method; 56 | k->kill_console_delay = service->kill_console_delay; 57 | k->kill_window_delay = service->kill_window_delay; 58 | k->kill_threads_delay = service->kill_threads_delay; 59 | k->status_handle = service->status_handle; 60 | k->status = &service->status; 61 | k->creation_time = service->creation_time; 62 | k->exit_time = service->exit_time; 63 | } 64 | 65 | int get_process_creation_time(HANDLE process_handle, FILETIME *ft) { 66 | FILETIME creation_time, exit_time, kernel_time, user_time; 67 | 68 | if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { 69 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0); 70 | return 1; 71 | } 72 | 73 | memmove(ft, &creation_time, sizeof(creation_time)); 74 | 75 | return 0; 76 | } 77 | 78 | int get_process_exit_time(HANDLE process_handle, FILETIME *ft) { 79 | FILETIME creation_time, exit_time, kernel_time, user_time; 80 | 81 | if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) { 82 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0); 83 | return 1; 84 | } 85 | 86 | if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2; 87 | memmove(ft, &exit_time, sizeof(exit_time)); 88 | 89 | return 0; 90 | } 91 | 92 | int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) { 93 | /* Check parent process ID matches. */ 94 | if (pe->th32ParentProcessID != ppid) return 1; 95 | 96 | /* 97 | Process IDs can be reused so do a sanity check by making sure the child 98 | has been running for less time than the parent. 99 | Though unlikely, it's possible that the parent exited and its process ID 100 | was already reused, so we'll also compare against its exit time. 101 | */ 102 | HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID); 103 | if (! process_handle) { 104 | TCHAR pid_string[16]; 105 | _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID); 106 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); 107 | return 2; 108 | } 109 | 110 | FILETIME ft; 111 | if (get_process_creation_time(process_handle, &ft)) { 112 | CloseHandle(process_handle); 113 | return 3; 114 | } 115 | 116 | CloseHandle(process_handle); 117 | 118 | /* Verify that the parent's creation time is not later. */ 119 | if (CompareFileTime(&k->creation_time, &ft) > 0) return 4; 120 | 121 | /* Verify that the parent's exit time is not earlier. */ 122 | if (CompareFileTime(&k->exit_time, &ft) < 0) return 5; 123 | 124 | return 0; 125 | } 126 | 127 | /* Send some window messages and hope the window respects one or more. */ 128 | int CALLBACK kill_window(HWND window, LPARAM arg) { 129 | kill_t *k = (kill_t *) arg; 130 | 131 | unsigned long pid; 132 | if (! GetWindowThreadProcessId(window, &pid)) return 1; 133 | if (pid != k->pid) return 1; 134 | 135 | /* First try sending WM_CLOSE to request that the window close. */ 136 | k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0); 137 | 138 | /* 139 | Then tell the window that the user is logging off and it should exit 140 | without worrying about saving any data. 141 | */ 142 | k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF); 143 | 144 | return 1; 145 | } 146 | 147 | /* 148 | Try to post a message to the message queues of threads associated with the 149 | given process ID. Not all threads have message queues so there's no 150 | guarantee of success, and we don't want to be left waiting for unsignalled 151 | processes so this function returns only true if at least one thread was 152 | successfully prodded. 153 | */ 154 | int kill_threads(nssm_service_t *service, kill_t *k) { 155 | int ret = 0; 156 | 157 | /* Get a snapshot of all threads in the system. */ 158 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 159 | if (snapshot == INVALID_HANDLE_VALUE) { 160 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0); 161 | return 0; 162 | } 163 | 164 | THREADENTRY32 te; 165 | ZeroMemory(&te, sizeof(te)); 166 | te.dwSize = sizeof(te); 167 | 168 | if (! Thread32First(snapshot, &te)) { 169 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); 170 | CloseHandle(snapshot); 171 | return 0; 172 | } 173 | 174 | /* This thread belongs to the doomed process so signal it. */ 175 | if (te.th32OwnerProcessID == k->pid) { 176 | ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0); 177 | } 178 | 179 | while (true) { 180 | /* Try to get the next thread. */ 181 | if (! Thread32Next(snapshot, &te)) { 182 | unsigned long error = GetLastError(); 183 | if (error == ERROR_NO_MORE_FILES) break; 184 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); 185 | CloseHandle(snapshot); 186 | return ret; 187 | } 188 | 189 | if (te.th32OwnerProcessID == k->pid) { 190 | ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0); 191 | } 192 | } 193 | 194 | CloseHandle(snapshot); 195 | 196 | return ret; 197 | } 198 | 199 | int kill_threads(kill_t *k) { 200 | return kill_threads(NULL, k); 201 | } 202 | 203 | /* Give the process a chance to die gracefully. */ 204 | int kill_process(nssm_service_t *service, kill_t *k) { 205 | if (! k) return 1; 206 | 207 | unsigned long ret; 208 | if (GetExitCodeProcess(k->process_handle, &ret)) { 209 | if (ret != STILL_ACTIVE) return 1; 210 | } 211 | 212 | /* Try to send a Control-C event to the console. */ 213 | if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) { 214 | if (! kill_console(k)) return 1; 215 | } 216 | 217 | /* 218 | Try to post messages to the windows belonging to the given process ID. 219 | If the process is a console application it won't have any windows so there's 220 | no guarantee of success. 221 | */ 222 | if (k->stop_method & NSSM_STOP_METHOD_WINDOW) { 223 | EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k); 224 | if (k->signalled) { 225 | if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1; 226 | k->signalled = 0; 227 | } 228 | } 229 | 230 | /* 231 | Try to post messages to any thread message queues associated with the 232 | process. Console applications might have them (but probably won't) so 233 | there's still no guarantee of success. 234 | */ 235 | if (k->stop_method & NSSM_STOP_METHOD_THREADS) { 236 | if (kill_threads(k)) { 237 | if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1; 238 | } 239 | } 240 | 241 | /* We tried being nice. Time for extreme prejudice. */ 242 | if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) { 243 | return TerminateProcess(k->process_handle, k->exitcode); 244 | } 245 | 246 | return 0; 247 | } 248 | 249 | int kill_process(kill_t *k) { 250 | return kill_process(NULL, k); 251 | } 252 | 253 | /* Simulate a Control-C event to our console (shared with the app). */ 254 | int kill_console(nssm_service_t *service, kill_t *k) { 255 | unsigned long ret; 256 | 257 | if (! k) return 1; 258 | 259 | /* Check we loaded AttachConsole(). */ 260 | if (! imports.AttachConsole) return 4; 261 | 262 | /* Try to attach to the process's console. */ 263 | if (! imports.AttachConsole(k->pid)) { 264 | ret = GetLastError(); 265 | 266 | switch (ret) { 267 | case ERROR_INVALID_HANDLE: 268 | /* The app doesn't have a console. */ 269 | return 1; 270 | 271 | case ERROR_GEN_FAILURE: 272 | /* The app already exited. */ 273 | return 2; 274 | 275 | case ERROR_ACCESS_DENIED: 276 | default: 277 | /* We already have a console. */ 278 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0); 279 | return 3; 280 | } 281 | } 282 | 283 | /* Ignore the event ourselves. */ 284 | ret = 0; 285 | BOOL ignored = SetConsoleCtrlHandler(0, TRUE); 286 | if (! ignored) { 287 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0); 288 | ret = 4; 289 | } 290 | 291 | /* Send the event. */ 292 | if (! ret) { 293 | if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) { 294 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0); 295 | ret = 5; 296 | } 297 | } 298 | 299 | /* Detach from the console. */ 300 | if (! FreeConsole()) { 301 | log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0); 302 | } 303 | 304 | /* Wait for process to exit. */ 305 | if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6; 306 | 307 | /* Remove our handler. */ 308 | if (ignored && ! SetConsoleCtrlHandler(0, FALSE)) { 309 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0); 310 | } 311 | 312 | return ret; 313 | } 314 | 315 | int kill_console(kill_t *k) { 316 | return kill_console(NULL, k); 317 | } 318 | 319 | void walk_process_tree(nssm_service_t *service, walk_function_t fn, kill_t *k, unsigned long ppid) { 320 | if (! k) return; 321 | /* Shouldn't happen unless the service failed to start. */ 322 | if (! k->pid) return; /* XXX: needed? */ 323 | unsigned long pid = k->pid; 324 | unsigned long depth = k->depth; 325 | 326 | TCHAR pid_string[16], code[16]; 327 | _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid); 328 | _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode); 329 | if (fn == kill_process) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0); 330 | 331 | /* We will need a process handle in order to call TerminateProcess() later. */ 332 | HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid); 333 | if (process_handle) { 334 | /* Kill this process first, then its descendents. */ 335 | TCHAR ppid_string[16]; 336 | _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid); 337 | if (fn == kill_process) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0); 338 | k->process_handle = process_handle; /* XXX: open directly? */ 339 | if (! fn(service, k)) { 340 | /* Maybe it already died. */ 341 | unsigned long ret; 342 | if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) { 343 | if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); 344 | else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0); 345 | } 346 | } 347 | 348 | CloseHandle(process_handle); 349 | } 350 | else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0); 351 | 352 | /* Get a snapshot of all processes in the system. */ 353 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 354 | if (snapshot == INVALID_HANDLE_VALUE) { 355 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0); 356 | return; 357 | } 358 | 359 | PROCESSENTRY32 pe; 360 | ZeroMemory(&pe, sizeof(pe)); 361 | pe.dwSize = sizeof(pe); 362 | 363 | if (! Process32First(snapshot, &pe)) { 364 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); 365 | CloseHandle(snapshot); 366 | return; 367 | } 368 | 369 | /* This is a child of the doomed process so kill it. */ 370 | k->depth++; 371 | if (! check_parent(k, &pe, pid)) { 372 | k->pid = pe.th32ProcessID; 373 | walk_process_tree(service, fn, k, ppid); 374 | } 375 | k->pid = pid; 376 | 377 | while (true) { 378 | /* Try to get the next process. */ 379 | if (! Process32Next(snapshot, &pe)) { 380 | unsigned long ret = GetLastError(); 381 | if (ret == ERROR_NO_MORE_FILES) break; 382 | log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0); 383 | CloseHandle(snapshot); 384 | k->depth = depth; 385 | return; 386 | } 387 | 388 | if (! check_parent(k, &pe, pid)) { 389 | k->pid = pe.th32ProcessID; 390 | walk_process_tree(service, fn, k, ppid); 391 | } 392 | k->pid = pid; 393 | } 394 | k->depth = depth; 395 | 396 | CloseHandle(snapshot); 397 | } 398 | 399 | void kill_process_tree(kill_t *k, unsigned long ppid) { 400 | return walk_process_tree(NULL, kill_process, k, ppid); 401 | } 402 | 403 | int print_process(nssm_service_t *service, kill_t *k) { 404 | TCHAR exe[EXE_LENGTH]; 405 | TCHAR *buffer = 0; 406 | if (k->depth) { 407 | buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (k->depth + 1) * sizeof(TCHAR)); 408 | if (buffer) { 409 | unsigned long i; 410 | for (i = 0; i < k->depth; i++) buffer[i] = _T(' '); 411 | buffer[i] = _T('\0'); 412 | } 413 | } 414 | 415 | unsigned long size = _countof(exe); 416 | if (! imports.QueryFullProcessImageName || ! imports.QueryFullProcessImageName(k->process_handle, 0, exe, &size)) { 417 | /* 418 | Fall back to GetModuleFileNameEx(), which won't work for WOW64 processes. 419 | */ 420 | if (! GetModuleFileNameEx(k->process_handle, NULL, exe, _countof(exe))) { 421 | long error = GetLastError(); 422 | if (error == ERROR_PARTIAL_COPY) _sntprintf_s(exe, _countof(exe), _TRUNCATE, _T("[WOW64]")); 423 | else _sntprintf_s(exe, _countof(exe), _TRUNCATE, _T("???")); 424 | } 425 | } 426 | 427 | _tprintf(_T("% 8lu %s%s\n"), k->pid, buffer ? buffer : _T(""), exe); 428 | 429 | if (buffer) HeapFree(GetProcessHeap(), 0, buffer); 430 | return 1; 431 | } 432 | 433 | int print_process(kill_t *k) { 434 | return print_process(NULL, k); 435 | } 436 | -------------------------------------------------------------------------------- /process.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESS_H 2 | #define PROCESS_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | TCHAR *name; 9 | HANDLE process_handle; 10 | unsigned long depth; 11 | unsigned long pid; 12 | unsigned long exitcode; 13 | unsigned long stop_method; 14 | unsigned long kill_console_delay; 15 | unsigned long kill_window_delay; 16 | unsigned long kill_threads_delay; 17 | SERVICE_STATUS_HANDLE status_handle; 18 | SERVICE_STATUS *status; 19 | FILETIME creation_time; 20 | FILETIME exit_time; 21 | int signalled; 22 | } kill_t; 23 | 24 | typedef int (*walk_function_t)(nssm_service_t *, kill_t *); 25 | 26 | HANDLE get_debug_token(); 27 | void service_kill_t(nssm_service_t *, kill_t *); 28 | int get_process_creation_time(HANDLE, FILETIME *); 29 | int get_process_exit_time(HANDLE, FILETIME *); 30 | int check_parent(kill_t *, PROCESSENTRY32 *, unsigned long); 31 | int CALLBACK kill_window(HWND, LPARAM); 32 | int kill_threads(nssm_service_t *, kill_t *); 33 | int kill_threads(kill_t *); 34 | int kill_console(nssm_service_t *, kill_t *); 35 | int kill_console(kill_t *); 36 | int kill_process(nssm_service_t *, kill_t *); 37 | int kill_process(kill_t *); 38 | int print_process(nssm_service_t *, kill_t *); 39 | int print_process(kill_t *); 40 | void walk_process_tree(nssm_service_t *, walk_function_t, kill_t *, unsigned long); 41 | void kill_process_tree(kill_t *, unsigned long); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /registry.h: -------------------------------------------------------------------------------- 1 | #ifndef REGISTRY_H 2 | #define REGISTRY_H 3 | 4 | #define NSSM_REGISTRY _T("SYSTEM\\CurrentControlSet\\Services\\%s") 5 | #define NSSM_REG_PARAMETERS _T("Parameters") 6 | #define NSSM_REGISTRY_GROUPS _T("SYSTEM\\CurrentControlSet\\Control\\ServiceGroupOrder") 7 | #define NSSM_REG_GROUPS _T("List") 8 | #define NSSM_REG_EXE _T("Application") 9 | #define NSSM_REG_FLAGS _T("AppParameters") 10 | #define NSSM_REG_DIR _T("AppDirectory") 11 | #define NSSM_REG_ENV _T("AppEnvironment") 12 | #define NSSM_REG_ENV_EXTRA _T("AppEnvironmentExtra") 13 | #define NSSM_REG_EXIT _T("AppExit") 14 | #define NSSM_REG_RESTART_DELAY _T("AppRestartDelay") 15 | #define NSSM_REG_THROTTLE _T("AppThrottle") 16 | #define NSSM_REG_STOP_METHOD_SKIP _T("AppStopMethodSkip") 17 | #define NSSM_REG_KILL_CONSOLE_GRACE_PERIOD _T("AppStopMethodConsole") 18 | #define NSSM_REG_KILL_WINDOW_GRACE_PERIOD _T("AppStopMethodWindow") 19 | #define NSSM_REG_KILL_THREADS_GRACE_PERIOD _T("AppStopMethodThreads") 20 | #define NSSM_REG_KILL_PROCESS_TREE _T("AppKillProcessTree") 21 | #define NSSM_REG_STDIN _T("AppStdin") 22 | #define NSSM_REG_STDOUT _T("AppStdout") 23 | #define NSSM_REG_STDERR _T("AppStderr") 24 | #define NSSM_REG_STDIO_SHARING _T("ShareMode") 25 | #define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition") 26 | #define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes") 27 | #define NSSM_REG_STDIO_COPY_AND_TRUNCATE _T("CopyAndTruncate") 28 | #define NSSM_REG_HOOK_SHARE_OUTPUT_HANDLES _T("AppRedirectHook") 29 | #define NSSM_REG_ROTATE _T("AppRotateFiles") 30 | #define NSSM_REG_ROTATE_ONLINE _T("AppRotateOnline") 31 | #define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds") 32 | #define NSSM_REG_ROTATE_BYTES_LOW _T("AppRotateBytes") 33 | #define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh") 34 | #define NSSM_REG_ROTATE_DELAY _T("AppRotateDelay") 35 | #define NSSM_REG_TIMESTAMP_LOG _T("AppTimestampLog") 36 | #define NSSM_REG_PRIORITY _T("AppPriority") 37 | #define NSSM_REG_AFFINITY _T("AppAffinity") 38 | #define NSSM_REG_NO_CONSOLE _T("AppNoConsole") 39 | #define NSSM_REG_HOOK _T("AppEvents") 40 | #define NSSM_STDIO_LENGTH 29 41 | 42 | HKEY open_service_registry(const TCHAR *, REGSAM sam, bool); 43 | long open_registry(const TCHAR *, const TCHAR *, REGSAM sam, HKEY *, bool); 44 | HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam, bool); 45 | HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam); 46 | HKEY open_registry(const TCHAR *, REGSAM sam); 47 | long enumerate_registry_values(HKEY, unsigned long *, TCHAR *, unsigned long); 48 | int create_messages(); 49 | int create_parameters(nssm_service_t *, bool); 50 | int create_exit_action(TCHAR *, const TCHAR *, bool); 51 | int get_environment(TCHAR *, HKEY, TCHAR *, TCHAR **, unsigned long *); 52 | int get_string(HKEY, TCHAR *, TCHAR *, unsigned long, bool, bool, bool); 53 | int get_string(HKEY, TCHAR *, TCHAR *, unsigned long, bool); 54 | int expand_parameter(HKEY, TCHAR *, TCHAR *, unsigned long, bool, bool); 55 | int expand_parameter(HKEY, TCHAR *, TCHAR *, unsigned long, bool); 56 | int set_string(HKEY, TCHAR *, TCHAR *, bool); 57 | int set_string(HKEY, TCHAR *, TCHAR *); 58 | int set_expand_string(HKEY, TCHAR *, TCHAR *); 59 | int set_number(HKEY, TCHAR *, unsigned long); 60 | int get_number(HKEY, TCHAR *, unsigned long *, bool); 61 | int get_number(HKEY, TCHAR *, unsigned long *); 62 | int format_double_null(TCHAR *, unsigned long, TCHAR **, unsigned long *); 63 | int unformat_double_null(TCHAR *, unsigned long, TCHAR **, unsigned long *); 64 | int copy_double_null(TCHAR *, unsigned long, TCHAR **); 65 | int append_to_double_null(TCHAR *, unsigned long, TCHAR **, unsigned long *, TCHAR *, size_t, bool); 66 | int remove_from_double_null(TCHAR *, unsigned long, TCHAR **, unsigned long *, TCHAR *, size_t, bool); 67 | void override_milliseconds(TCHAR *, HKEY, TCHAR *, unsigned long *, unsigned long, unsigned long); 68 | int get_io_parameters(nssm_service_t *, HKEY); 69 | int get_parameters(nssm_service_t *, STARTUPINFO *); 70 | int get_exit_action(const TCHAR *, unsigned long *, TCHAR *, bool *); 71 | int set_hook(const TCHAR *, const TCHAR *, const TCHAR *, TCHAR *); 72 | int get_hook(const TCHAR *, const TCHAR *, const TCHAR *, TCHAR *, unsigned long); 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Developer Studio generated include file. 3 | // Used by nssm.rc 4 | // 5 | #define IDC_STATIC (-1) 6 | #define IDI_NSSM 101 7 | #define IDD_INSTALL 102 8 | #define IDD_REMOVE 103 9 | #define IDD_EDIT 104 10 | #define IDD_APPLICATION 105 11 | #define IDD_DETAILS 106 12 | #define IDD_LOGON 107 13 | #define IDD_IO 108 14 | #define IDD_ROTATION 109 15 | #define IDD_APPEXIT 110 16 | #define IDD_SHUTDOWN 111 17 | #define IDD_ENVIRONMENT 112 18 | #define IDD_NATIVE 113 19 | #define IDD_PROCESS 114 20 | #define IDD_DEPENDENCIES 115 21 | #define IDD_HOOKS 116 22 | #define IDC_PATH 1000 23 | #define IDC_TAB1 1001 24 | #define IDC_CANCEL 1002 25 | #define IDC_BROWSE 1003 26 | #define IDC_FLAGS 1004 27 | #define IDC_NAME 1005 28 | #define IDC_REMOVE 1007 29 | #define IDC_METHOD_CONSOLE 1008 30 | #define IDC_METHOD_WINDOW 1009 31 | #define IDC_METHOD_THREADS 1010 32 | #define IDC_METHOD_TERMINATE 1011 33 | #define IDC_KILL_CONSOLE 1012 34 | #define IDC_KILL_WINDOW 1013 35 | #define IDC_KILL_THREADS 1014 36 | #define IDC_STDIN 1015 37 | #define IDC_STDOUT 1016 38 | #define IDC_STDERR 1017 39 | #define IDC_BROWSE_STDIN 1018 40 | #define IDC_BROWSE_STDOUT 1019 41 | #define IDC_BROWSE_STDERR 1020 42 | #define IDC_THROTTLE 1021 43 | #define IDC_APPEXIT 1022 44 | #define IDC_RESTART_DELAY 1023 45 | #define IDC_DIR 1024 46 | #define IDC_BROWSE_DIR 1025 47 | #define IDC_ENVIRONMENT 1026 48 | #define IDC_ENVIRONMENT_REPLACE 1027 49 | #define IDC_TRUNCATE 1028 50 | #define IDC_ROTATE 1029 51 | #define IDC_ROTATE_ONLINE 1030 52 | #define IDC_ROTATE_SECONDS 1031 53 | #define IDC_ROTATE_BYTES_LOW 1032 54 | #define IDC_DISPLAYNAME 1033 55 | #define IDC_DESCRIPTION 1034 56 | #define IDC_STARTUP 1035 57 | #define IDC_LOCALSYSTEM 1036 58 | #define IDC_INTERACT 1037 59 | #define IDC_ACCOUNT 1038 60 | #define IDC_USERNAME 1039 61 | #define IDC_PASSWORD1 1040 62 | #define IDC_PASSWORD2 1041 63 | #define IDC_PRIORITY 1042 64 | #define IDC_AFFINITY_ALL 1043 65 | #define IDC_AFFINITY 1044 66 | #define IDC_CONSOLE 1045 67 | #define IDC_DEPENDENCIES 1046 68 | #define IDC_KILL_PROCESS_TREE 1047 69 | #define IDC_HOOK_EVENT 1048 70 | #define IDC_HOOK_ACTION 1049 71 | #define IDC_HOOK 1050 72 | #define IDC_BROWSE_HOOK 1051 73 | #define IDC_REDIRECT_HOOK 1052 74 | #define IDC_VIRTUAL_SERVICE 1053 75 | #define IDC_TIMESTAMP 1054 76 | 77 | // Next default values for new objects 78 | // 79 | #ifdef APSTUDIO_INVOKED 80 | #ifndef APSTUDIO_READONLY_SYMBOLS 81 | #define _APS_NEXT_RESOURCE_VALUE 117 82 | #define _APS_NEXT_COMMAND_VALUE 40001 83 | #define _APS_NEXT_CONTROL_VALUE 1055 84 | #define _APS_NEXT_SYMED_VALUE 101 85 | #endif 86 | #endif 87 | 88 | -------------------------------------------------------------------------------- /service.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVICE_H 2 | #define SERVICE_H 3 | 4 | /* 5 | MSDN says the commandline in CreateProcess() is limited to 32768 characters 6 | and the application name to MAX_PATH. 7 | A service name and service display name are limited to 256 characters. 8 | A registry key is limited to 255 characters. 9 | A registry value is limited to 16383 characters. 10 | Therefore we limit the service name to accommodate the path under HKLM. 11 | */ 12 | #define EXE_LENGTH PATH_LENGTH 13 | #define CMD_LENGTH 32768 14 | #define KEY_LENGTH 255 15 | #define VALUE_LENGTH 16383 16 | #define SERVICE_NAME_LENGTH 256 17 | 18 | #define ACTION_LEN 16 19 | 20 | #define NSSM_KERNEL_DRIVER _T("SERVICE_KERNEL_DRIVER") 21 | #define NSSM_FILE_SYSTEM_DRIVER _T("SERVICE_FILE_SYSTEM_DRIVER") 22 | #define NSSM_WIN32_OWN_PROCESS _T("SERVICE_WIN32_OWN_PROCESS") 23 | #define NSSM_WIN32_SHARE_PROCESS _T("SERVICE_WIN32_SHARE_PROCESS") 24 | #define NSSM_INTERACTIVE_PROCESS _T("SERVICE_INTERACTIVE_PROCESS") 25 | #define NSSM_SHARE_INTERACTIVE_PROCESS NSSM_WIN32_SHARE_PROCESS _T("|") NSSM_INTERACTIVE_PROCESS 26 | #define NSSM_UNKNOWN _T("?") 27 | 28 | #define NSSM_ROTATE_OFFLINE 0 29 | #define NSSM_ROTATE_ONLINE 1 30 | #define NSSM_ROTATE_ONLINE_ASAP 2 31 | 32 | typedef struct { 33 | bool native; 34 | TCHAR name[SERVICE_NAME_LENGTH]; 35 | TCHAR displayname[SERVICE_NAME_LENGTH]; 36 | TCHAR description[VALUE_LENGTH]; 37 | unsigned long startup; 38 | TCHAR *username; 39 | size_t usernamelen; 40 | TCHAR *password; 41 | size_t passwordlen; 42 | unsigned long type; 43 | TCHAR image[PATH_LENGTH]; 44 | TCHAR exe[EXE_LENGTH]; 45 | TCHAR flags[VALUE_LENGTH]; 46 | TCHAR dir[DIR_LENGTH]; 47 | TCHAR *env; 48 | __int64 affinity; 49 | TCHAR *dependencies; 50 | unsigned long dependencieslen; 51 | unsigned long envlen; 52 | TCHAR *env_extra; 53 | unsigned long env_extralen; 54 | unsigned long priority; 55 | unsigned long no_console; 56 | TCHAR stdin_path[PATH_LENGTH]; 57 | unsigned long stdin_sharing; 58 | unsigned long stdin_disposition; 59 | unsigned long stdin_flags; 60 | TCHAR stdout_path[PATH_LENGTH]; 61 | unsigned long stdout_sharing; 62 | unsigned long stdout_disposition; 63 | unsigned long stdout_flags; 64 | bool use_stdout_pipe; 65 | HANDLE stdout_si; 66 | HANDLE stdout_pipe; 67 | HANDLE stdout_thread; 68 | unsigned long stdout_tid; 69 | TCHAR stderr_path[PATH_LENGTH]; 70 | unsigned long stderr_sharing; 71 | unsigned long stderr_disposition; 72 | unsigned long stderr_flags; 73 | bool use_stderr_pipe; 74 | HANDLE stderr_si; 75 | HANDLE stderr_pipe; 76 | HANDLE stderr_thread; 77 | unsigned long stderr_tid; 78 | bool hook_share_output_handles; 79 | bool rotate_files; 80 | bool timestamp_log; 81 | bool stdout_copy_and_truncate; 82 | bool stderr_copy_and_truncate; 83 | unsigned long rotate_stdout_online; 84 | unsigned long rotate_stderr_online; 85 | unsigned long rotate_seconds; 86 | unsigned long rotate_bytes_low; 87 | unsigned long rotate_bytes_high; 88 | unsigned long rotate_delay; 89 | unsigned long default_exit_action; 90 | unsigned long restart_delay; 91 | unsigned long throttle_delay; 92 | unsigned long stop_method; 93 | unsigned long kill_console_delay; 94 | unsigned long kill_window_delay; 95 | unsigned long kill_threads_delay; 96 | bool kill_process_tree; 97 | SC_HANDLE handle; 98 | SERVICE_STATUS status; 99 | SERVICE_STATUS_HANDLE status_handle; 100 | HANDLE process_handle; 101 | unsigned long pid; 102 | HANDLE wait_handle; 103 | unsigned long exitcode; 104 | bool stopping; 105 | bool allow_restart; 106 | unsigned long throttle; 107 | CRITICAL_SECTION throttle_section; 108 | bool throttle_section_initialised; 109 | CRITICAL_SECTION hook_section; 110 | bool hook_section_initialised; 111 | CONDITION_VARIABLE throttle_condition; 112 | HANDLE throttle_timer; 113 | LARGE_INTEGER throttle_duetime; 114 | FILETIME nssm_creation_time; 115 | FILETIME creation_time; 116 | FILETIME exit_time; 117 | TCHAR *initial_env; 118 | unsigned long last_control; 119 | unsigned long start_requested_count; 120 | unsigned long start_count; 121 | unsigned long exit_count; 122 | } nssm_service_t; 123 | 124 | void WINAPI service_main(unsigned long, TCHAR **); 125 | TCHAR *service_control_text(unsigned long); 126 | TCHAR *service_status_text(unsigned long); 127 | void log_service_control(TCHAR *, unsigned long, bool); 128 | unsigned long WINAPI service_control_handler(unsigned long, unsigned long, void *, void *); 129 | 130 | int affinity_mask_to_string(__int64, TCHAR **); 131 | int affinity_string_to_mask(TCHAR *, __int64 *); 132 | unsigned long priority_mask(); 133 | int priority_constant_to_index(unsigned long); 134 | unsigned long priority_index_to_constant(int); 135 | 136 | nssm_service_t *alloc_nssm_service(); 137 | void set_nssm_service_defaults(nssm_service_t *); 138 | void cleanup_nssm_service(nssm_service_t *); 139 | SC_HANDLE open_service_manager(unsigned long); 140 | SC_HANDLE open_service(SC_HANDLE, TCHAR *, unsigned long, TCHAR *, unsigned long); 141 | QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *, SC_HANDLE); 142 | int append_to_dependencies(TCHAR *, unsigned long, TCHAR *, TCHAR **, unsigned long *, int); 143 | int remove_from_dependencies(TCHAR *, unsigned long, TCHAR *, TCHAR **, unsigned long *, int); 144 | int set_service_dependencies(const TCHAR *, SC_HANDLE, TCHAR *); 145 | int get_service_dependencies(const TCHAR *, SC_HANDLE, TCHAR **, unsigned long *, int); 146 | int get_service_dependencies(const TCHAR *, SC_HANDLE, TCHAR **, unsigned long *); 147 | int set_service_description(const TCHAR *, SC_HANDLE, TCHAR *); 148 | int get_service_description(const TCHAR *, SC_HANDLE, unsigned long, TCHAR *); 149 | int get_service_startup(const TCHAR *, SC_HANDLE, const QUERY_SERVICE_CONFIG *, unsigned long *); 150 | int get_service_username(const TCHAR *, const QUERY_SERVICE_CONFIG *, TCHAR **, size_t *); 151 | void set_service_environment(nssm_service_t *); 152 | void unset_service_environment(nssm_service_t *); 153 | int pre_install_service(int, TCHAR **); 154 | int pre_remove_service(int, TCHAR **); 155 | int pre_edit_service(int, TCHAR **); 156 | int install_service(nssm_service_t *); 157 | int remove_service(nssm_service_t *); 158 | int edit_service(nssm_service_t *, bool); 159 | int control_service(unsigned long, int, TCHAR **, bool); 160 | int control_service(unsigned long, int, TCHAR **); 161 | void set_service_recovery(nssm_service_t *); 162 | int monitor_service(nssm_service_t *); 163 | int start_service(nssm_service_t *); 164 | int stop_service(nssm_service_t *, unsigned long, bool, bool); 165 | void CALLBACK end_service(void *, unsigned char); 166 | void throttle_restart(nssm_service_t *); 167 | int await_single_handle(SERVICE_STATUS_HANDLE, SERVICE_STATUS *, HANDLE, TCHAR *, TCHAR *, unsigned long); 168 | int list_nssm_services(int, TCHAR **); 169 | int service_process_tree(int, TCHAR **); 170 | 171 | #endif 172 | -------------------------------------------------------------------------------- /settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | #define NSSM_NATIVE_DEPENDONGROUP _T("DependOnGroup") 5 | #define NSSM_NATIVE_DEPENDONSERVICE _T("DependOnService") 6 | #define NSSM_NATIVE_DESCRIPTION _T("Description") 7 | #define NSSM_NATIVE_DISPLAYNAME _T("DisplayName") 8 | #define NSSM_NATIVE_ENVIRONMENT _T("Environment") 9 | #define NSSM_NATIVE_IMAGEPATH _T("ImagePath") 10 | #define NSSM_NATIVE_NAME _T("Name") 11 | #define NSSM_NATIVE_OBJECTNAME _T("ObjectName") 12 | #define NSSM_NATIVE_STARTUP _T("Start") 13 | #define NSSM_NATIVE_TYPE _T("Type") 14 | 15 | /* Are additional arguments needed? */ 16 | #define ADDITIONAL_GETTING (1 << 0) 17 | #define ADDITIONAL_SETTING (1 << 1) 18 | #define ADDITIONAL_RESETTING (1 << 2) 19 | #define ADDITIONAL_CRLF (1 << 3) 20 | #define ADDITIONAL_MANDATORY ADDITIONAL_GETTING|ADDITIONAL_SETTING|ADDITIONAL_RESETTING 21 | 22 | #define DEPENDENCY_SERVICES (1 << 0) 23 | #define DEPENDENCY_GROUPS (1 << 1) 24 | #define DEPENDENCY_ALL (DEPENDENCY_SERVICES|DEPENDENCY_GROUPS) 25 | 26 | typedef union { 27 | unsigned long numeric; 28 | TCHAR *string; 29 | } value_t; 30 | 31 | typedef int (*setting_function_t)(const TCHAR *, void *, const TCHAR *, void *, value_t *, const TCHAR *); 32 | 33 | typedef struct { 34 | const TCHAR *name; 35 | unsigned long type; 36 | void *default_value; 37 | bool native; 38 | int additional; 39 | setting_function_t set; 40 | setting_function_t get; 41 | setting_function_t dump; 42 | } settings_t; 43 | 44 | int set_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *); 45 | int set_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *); 46 | int get_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *); 47 | int get_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *); 48 | int dump_setting(const TCHAR *, HKEY, SC_HANDLE, settings_t *); 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /utf8.cpp: -------------------------------------------------------------------------------- 1 | #include "nssm.h" 2 | 3 | static unsigned long cp; 4 | 5 | void setup_utf8() { 6 | #ifdef UNICODE 7 | /* 8 | Ensure we write in UTF-8 mode, so that non-ASCII characters don't get 9 | mangled. If we were compiled in ANSI mode it won't work. 10 | */ 11 | cp = GetConsoleOutputCP(); 12 | SetConsoleOutputCP(CP_UTF8); 13 | _setmode(_fileno(stdout), _O_U8TEXT); 14 | _setmode(_fileno(stderr), _O_U8TEXT); 15 | #endif 16 | } 17 | 18 | void unsetup_utf8() { 19 | if (cp) SetConsoleOutputCP(cp); 20 | } 21 | 22 | /* 23 | Conversion functions. 24 | 25 | to_utf8/16() converts a string which may be either utf8 or utf16 to 26 | the desired format. If no conversion is needed a new string is still 27 | allocated and the old string is copied. 28 | 29 | from_utf8/16() converts a string which IS in the specified format to 30 | whichever format is needed according to whether UNICODE is defined. 31 | It simply wraps the appropriate to_utf8/16() function. 32 | 33 | Therefore the caller must ALWAYS free the destination pointer after a 34 | successful (return code 0) call to one of these functions. 35 | 36 | The length pointer is optional. Pass NULL if you don't care about 37 | the length of the converted string. 38 | 39 | Both the destination and the length, if supplied, will be zeroed if 40 | no conversion was done. 41 | */ 42 | int to_utf8(const wchar_t *utf16, char **utf8, unsigned long *utf8len) { 43 | *utf8 = 0; 44 | if (utf8len) *utf8len = 0; 45 | int size = WideCharToMultiByte(CP_UTF8, 0, utf16, -1, NULL, 0, NULL, NULL); 46 | if (! size) return 1; 47 | 48 | *utf8 = (char *) HeapAlloc(GetProcessHeap(), 0, size); 49 | if (! *utf8) return 2; 50 | 51 | if (! WideCharToMultiByte(CP_UTF8, 0, utf16, -1, *utf8, size, NULL, NULL)) { 52 | HeapFree(GetProcessHeap(), 0, *utf8); 53 | *utf8 = 0; 54 | return 3; 55 | } 56 | 57 | if (utf8len) *utf8len = (unsigned long) strlen(*utf8); 58 | 59 | return 0; 60 | } 61 | 62 | int to_utf8(const char *ansi, char **utf8, unsigned long *utf8len) { 63 | *utf8 = 0; 64 | if (utf8len) *utf8len = 0; 65 | size_t len = strlen(ansi); 66 | int size = (int) len + 1; 67 | 68 | *utf8 = (char *) HeapAlloc(GetProcessHeap(), 0, size); 69 | if (! *utf8) return 2; 70 | 71 | if (utf8len) *utf8len = (unsigned long) len; 72 | memmove(*utf8, ansi, size); 73 | 74 | return 0; 75 | } 76 | 77 | int to_utf16(const char *utf8, wchar_t **utf16, unsigned long *utf16len) { 78 | *utf16 = 0; 79 | if (utf16len) *utf16len = 0; 80 | int size = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); 81 | if (! size) return 1; 82 | 83 | *utf16 = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, size * sizeof(wchar_t)); 84 | if (! *utf16) return 2; 85 | 86 | if (! MultiByteToWideChar(CP_UTF8, 0, utf8, -1, *utf16, size)) { 87 | HeapFree(GetProcessHeap(), 0, *utf16); 88 | *utf16 = 0; 89 | return 3; 90 | } 91 | 92 | if (utf16len) *utf16len = (unsigned long) wcslen(*utf16); 93 | 94 | return 0; 95 | } 96 | 97 | int to_utf16(const wchar_t *unicode, wchar_t **utf16, unsigned long *utf16len) { 98 | *utf16 = 0; 99 | if (utf16len) *utf16len = 0; 100 | size_t len = wcslen(unicode); 101 | int size = ((int) len + 1) * sizeof(wchar_t); 102 | 103 | *utf16 = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, size); 104 | if (! *utf16) return 2; 105 | 106 | if (utf16len) *utf16len = (unsigned long) len; 107 | memmove(*utf16, unicode, size); 108 | 109 | return 0; 110 | } 111 | 112 | int from_utf8(const char *utf8, TCHAR **buffer, unsigned long *buflen) { 113 | #ifdef UNICODE 114 | return to_utf16(utf8, buffer, buflen); 115 | #else 116 | return to_utf8(utf8, buffer, buflen); 117 | #endif 118 | } 119 | 120 | int from_utf16(const wchar_t *utf16, TCHAR **buffer, unsigned long *buflen) { 121 | #ifdef UNICODE 122 | return to_utf16(utf16, buffer, buflen); 123 | #else 124 | return to_utf8(utf16, buffer, buflen); 125 | #endif 126 | } 127 | -------------------------------------------------------------------------------- /utf8.h: -------------------------------------------------------------------------------- 1 | #ifndef UTF8_H 2 | #define UTF8_H 3 | 4 | void setup_utf8(); 5 | void unsetup_utf8(); 6 | int to_utf8(const wchar_t *, char **, unsigned long *); 7 | int to_utf8(const char *, char **, unsigned long *); 8 | int to_utf16(const char *, wchar_t **utf16, unsigned long *); 9 | int to_utf16(const wchar_t *, wchar_t **utf16, unsigned long *); 10 | int from_utf8(const char *, TCHAR **, unsigned long *); 11 | int from_utf16(const wchar_t *, TCHAR **, unsigned long *); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /version.cmd: -------------------------------------------------------------------------------- 1 | @rem Set default version in case git isn't available. 2 | set description=0.0-0-prerelease 3 | @rem Get canonical version from git tags, eg v2.21-24-g2c60e53. 4 | for /f %%v in ('git describe --tags --long') do set description=%%v 5 | 6 | @rem Strip leading v if present, eg 2.21-24-g2c60e53. 7 | set description=%description:v=% 8 | set version=%description% 9 | 10 | @rem Get the number of commits and commit hash, eg 24-g2c60e53. 11 | set n=%version:*-=% 12 | set commit=%n:*-=% 13 | call set n=%%n:%commit%=%% 14 | set n=%n:~0,-1% 15 | 16 | @rem Strip n and commit, eg 2.21. 17 | call set version=%%version:%n%-%commit%=%% 18 | set version=%version:~0,-1% 19 | 20 | @rem Find major and minor. 21 | set minor=%version:*.=% 22 | call set major=%%version:.%minor%=%% 23 | 24 | @rem Build flags. 25 | set flags=0L 26 | 27 | @rem Don't include n and commit if we match a tag exactly. 28 | if "%n%" == "0" (set description=%major%.%minor%) else set flags=VS_FF_PRERELEASE 29 | @rem Maybe we couldn't get the git tag. 30 | if "%commit%" == "prerelease" set flags=VS_FF_PRERELEASE 31 | 32 | @rem Ignore the build number if this isn't Jenkins. 33 | if "%BUILD_NUMBER%" == "" set BUILD_NUMBER=0 34 | 35 | @rem Copyright year provided by Jenkins. 36 | set md=%BUILD_ID:*-=% 37 | call set year=%%BUILD_ID:%md%=%% 38 | set year=%year:~0,-1% 39 | if "%BUILD_ID%" == "" set year= 40 | 41 | @rem Create version.h. 42 | @echo>version.h.new #define NSSM_VERSION _T("%description%") 43 | @echo>>version.h.new #define NSSM_VERSIONINFO %major%,%minor%,%n%,%BUILD_NUMBER% 44 | @echo>>version.h.new #define NSSM_DATE _T("%DATE%") 45 | @echo>>version.h.new #define NSSM_FILEFLAGS %flags% 46 | @echo>>version.h.new #define NSSM_COPYRIGHT _T("Public Domain; Author Iain Patterson 2003-%year%") 47 | 48 | fc version.h version.h.new >NUL: 2>NUL: 49 | if %ERRORLEVEL% == 0 (del version.h.new) else (move /y version.h.new version.h) 50 | --------------------------------------------------------------------------------