Added quote().
[nssm.git] / service.cpp
index cc11b64..164fbc0 100644 (file)
@@ -240,7 +240,7 @@ int affinity_string_to_mask(TCHAR *string, __int64 *mask) {
   return 0;\r
 }\r
 \r
-inline unsigned long priority_mask() {\r
+unsigned long priority_mask() {\r
  return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS;\r
 }\r
 \r
@@ -273,6 +273,27 @@ static inline unsigned long throttle_milliseconds(unsigned long throttle) {
   return ret * 1000;\r
 }\r
 \r
+void set_service_environment(nssm_service_t *service) {\r
+  if (! service) return;\r
+\r
+  /*\r
+    We have to duplicate the block because this function will be called\r
+    multiple times between registry reads.\r
+  */\r
+  if (service->env) duplicate_environment_strings(service->env);\r
+  if (! service->env_extra) return;\r
+  TCHAR *env_extra = copy_environment_block(service->env_extra);\r
+  if (! env_extra) return;\r
+\r
+  set_environment_block(env_extra);\r
+  HeapFree(GetProcessHeap(), 0, env_extra);\r
+}\r
+\r
+void unset_service_environment(nssm_service_t *service) {\r
+  if (! service) return;\r
+  duplicate_environment_strings(service->initial_env);\r
+}\r
+\r
 /*\r
   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
   control immediately.\r
@@ -281,6 +302,14 @@ static unsigned long WINAPI shutdown_service(void *arg) {
   return stop_service((nssm_service_t *) arg, 0, true, true);\r
 }\r
 \r
+/*\r
+ Wrapper to be called in a new thread so that we can acknowledge start\r
+ immediately.\r
+*/\r
+static unsigned long WINAPI launch_service(void *arg) {\r
+  return monitor_service((nssm_service_t *) arg);\r
+}\r
+\r
 /* Connect to the service manager */\r
 SC_HANDLE open_service_manager(unsigned long access) {\r
   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, access);\r
@@ -624,8 +653,6 @@ int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle,
     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
     return 4;\r
   }\r
-\r
-  return 0;\r
 }\r
 \r
 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {\r
@@ -733,7 +760,7 @@ void cleanup_nssm_service(nssm_service_t *service) {
   if (! service) return;\r
   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
   if (service->password) {\r
-    SecureZeroMemory(service->password, service->passwordlen);\r
+    SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR));\r
     HeapFree(GetProcessHeap(), 0, service->password);\r
   }\r
   if (service->dependencies) HeapFree(GetProcessHeap(), 0, service->dependencies);\r
@@ -745,7 +772,7 @@ void cleanup_nssm_service(nssm_service_t *service) {
   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
   if (service->hook_section_initialised) DeleteCriticalSection(&service->hook_section);\r
-  if (service->initial_env) FreeEnvironmentStrings(service->initial_env);\r
+  if (service->initial_env) HeapFree(GetProcessHeap(), 0, service->initial_env);\r
   HeapFree(GetProcessHeap(), 0, service);\r
 }\r
 \r
@@ -1097,7 +1124,7 @@ int install_service(nssm_service_t *service) {
   }\r
 \r
   /* Get path of this program */\r
-  GetModuleFileName(0, service->image, _countof(service->image));\r
+  _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), nssm_imagepath());\r
 \r
   /* Create the service - settings will be changed in edit_service() */\r
   service->handle = CreateService(services, service->name, service->name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
@@ -1450,12 +1477,16 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) {
   service->hook_section_initialised = true;\r
 \r
   /* Remember our initial environment. */\r
-  service->initial_env = GetEnvironmentStrings();\r
+  service->initial_env = copy_environment();\r
 \r
   /* Remember our creation time. */\r
   if (get_process_creation_time(GetCurrentProcess(), &service->nssm_creation_time)) ZeroMemory(&service->nssm_creation_time, sizeof(service->nssm_creation_time));\r
 \r
-  monitor_service(service);\r
+  service->allow_restart = true;\r
+  if (! CreateThread(NULL, 0, launch_service, (void *) service, 0, NULL)) {\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
+    stop_service(service, 0, true, true);\r
+  }\r
 }\r
 \r
 /* Make sure service recovery actions are taken where necessary */\r
@@ -1564,9 +1595,13 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
       service->last_control = control;\r
       log_service_control(service->name, control, true);\r
 \r
-      /* Pre-stop hook. */\r
+      /* Immediately block further controls. */\r
+      service->allow_restart = false;\r
       service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+      service->status.dwControlsAccepted = 0;\r
       SetServiceStatus(service->status_handle, &service->status);\r
+\r
+      /* Pre-stop hook. */\r
       nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false);\r
 \r
       /*\r
@@ -1651,7 +1686,6 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
 /* Start the service */\r
 int start_service(nssm_service_t *service) {\r
   service->stopping = false;\r
-  service->allow_restart = true;\r
 \r
   if (service->process_handle) return 0;\r
   service->start_requested_count++;\r
@@ -1669,6 +1703,7 @@ int start_service(nssm_service_t *service) {
   int ret = get_parameters(service, &si);\r
   if (ret) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
+    unset_service_environment(service);\r
     return stop_service(service, 2, true, true);\r
   }\r
 \r
@@ -1676,96 +1711,106 @@ int start_service(nssm_service_t *service) {
   TCHAR cmd[CMD_LENGTH];\r
   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
+    unset_service_environment(service);\r
     return stop_service(service, 2, true, true);\r
   }\r
 \r
   throttle_restart(service);\r
 \r
-  /* Set the environment. */\r
-  if (service->env) duplicate_environment(service->env);\r
-  if (service->env_extra) set_environment_block(service->env_extra);\r
-\r
-  /* Pre-start hook. */\r
-  unsigned long control = NSSM_SERVICE_CONTROL_START;\r
   service->status.dwCurrentState = SERVICE_START_PENDING;\r
   service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
-  if (nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false) == NSSM_HOOK_STATUS_ABORT) {\r
-    TCHAR code[16];\r
-    _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT);\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0);\r
-    return stop_service(service, 5, true, true);\r
-  }\r
+  SetServiceStatus(service->status_handle, &service->status);\r
 \r
-  /* Set up I/O redirection. */\r
-  if (get_output_handles(service, &si)) {\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
-    if (! service->no_console) FreeConsole();\r
-    close_output_handles(&si);\r
-    return stop_service(service, 4, true, true);\r
-  }\r
+  unsigned long control = NSSM_SERVICE_CONTROL_START;\r
 \r
-  bool inherit_handles = false;\r
-  if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
-  unsigned long flags = service->priority & priority_mask();\r
-  if (service->affinity) flags |= CREATE_SUSPENDED;\r
-  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
-    unsigned long exitcode = 3;\r
-    unsigned long error = GetLastError();\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
-    close_output_handles(&si);\r
-    duplicate_environment_strings(service->initial_env);\r
-    return stop_service(service, exitcode, true, true);\r
-  }\r
-  service->start_count++;\r
-  service->process_handle = pi.hProcess;\r
-  service->pid = pi.dwProcessId;\r
+  /* Did another thread receive a stop control? */\r
+  if (service->allow_restart) {\r
+    /* Set up I/O redirection. */\r
+    if (get_output_handles(service, &si)) {\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
+      if (! service->no_console) FreeConsole();\r
+      close_output_handles(&si);\r
+      unset_service_environment(service);\r
+      return stop_service(service, 4, true, true);\r
+    }\r
 \r
-  if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
+    /* Pre-start hook. May need I/O to have been redirected already. */\r
+    if (nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false) == NSSM_HOOK_STATUS_ABORT) {\r
+      TCHAR code[16];\r
+      _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT);\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0);\r
+      unset_service_environment(service);\r
+      return stop_service(service, 5, true, true);\r
+    }\r
 \r
-  close_output_handles(&si);\r
+    /* The pre-start hook will have cleaned the environment. */\r
+    set_service_environment(service);\r
+\r
+    bool inherit_handles = false;\r
+    if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
+    unsigned long flags = service->priority & priority_mask();\r
+    if (service->affinity) flags |= CREATE_SUSPENDED;\r
+    if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
+      unsigned long exitcode = 3;\r
+      unsigned long error = GetLastError();\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
+      close_output_handles(&si);\r
+      unset_service_environment(service);\r
+      return stop_service(service, exitcode, true, true);\r
+    }\r
+    service->start_count++;\r
+    service->process_handle = pi.hProcess;\r
+    service->pid = pi.dwProcessId;\r
 \r
-  if (! service->no_console) FreeConsole();\r
+    if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
 \r
-  /* Restore our environment. */\r
-  duplicate_environment_strings(service->initial_env);\r
+    close_output_handles(&si);\r
 \r
-  if (service->affinity) {\r
-    /*\r
-      We are explicitly storing service->affinity as a 64-bit unsigned integer\r
-      so that we can parse it regardless of whether we're running in 32-bit\r
-      or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
-      defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
-      (or when running the 32-bit NSSM).\r
-\r
-      The result is a lot of seemingly-unnecessary casting throughout the code\r
-      and potentially confusion when we actually try to start the service.\r
-      Having said that, however, it's unlikely that we're actually going to\r
-      run in 32-bit mode on a system which has more than 32 CPUs so the\r
-      likelihood of seeing a confusing situation is somewhat diminished.\r
-    */\r
-    DWORD_PTR affinity, system_affinity;\r
+    if (! service->no_console) FreeConsole();\r
 \r
-    if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
-    else {\r
-      affinity = (DWORD_PTR) service->affinity;\r
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
-    }\r
+    if (service->affinity) {\r
+      /*\r
+        We are explicitly storing service->affinity as a 64-bit unsigned integer\r
+        so that we can parse it regardless of whether we're running in 32-bit\r
+        or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
+        defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
+        (or when running the 32-bit NSSM).\r
+\r
+        The result is a lot of seemingly-unnecessary casting throughout the code\r
+        and potentially confusion when we actually try to start the service.\r
+        Having said that, however, it's unlikely that we're actually going to\r
+        run in 32-bit mode on a system which has more than 32 CPUs so the\r
+        likelihood of seeing a confusing situation is somewhat diminished.\r
+      */\r
+      DWORD_PTR affinity, system_affinity;\r
 \r
-    if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
-      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
-    }\r
+      if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
+      else {\r
+        affinity = (DWORD_PTR) service->affinity;\r
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+      }\r
+\r
+      if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
+        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+      }\r
 \r
-    ResumeThread(pi.hThread);\r
+      ResumeThread(pi.hThread);\r
+    }\r
   }\r
 \r
+  /* Restore our environment. */\r
+  unset_service_environment(service);\r
+\r
   /*\r
     Wait for a clean startup before changing the service status to RUNNING\r
     but be mindful of the fact that we are blocking the service control manager\r
     so abandon the wait before too much time has elapsed.\r
   */\r
-  service->status.dwCurrentState = SERVICE_START_PENDING;\r
   if (await_single_handle(service->status_handle, &service->status, service->process_handle, service->name, _T("start_service"), service->throttle_delay) == 1) service->throttle = 0;\r
 \r
+  /* Did another thread receive a stop control? */\r
+  if (! service->allow_restart) return 0;\r
+\r
   /* Signal successful start */\r
   service->status.dwCurrentState = SERVICE_RUNNING;\r
   service->status.dwControlsAccepted &= ~SERVICE_ACCEPT_PAUSE_CONTINUE;\r
@@ -1801,9 +1846,8 @@ int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful,
   if (graceful) {\r
     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
+    SetServiceStatus(service->status_handle, &service->status);\r
   }\r
-  service->status.dwControlsAccepted = 0;\r
-  SetServiceStatus(service->status_handle, &service->status);\r
 \r
   /* Nothing to do if service isn't running */\r
   if (service->pid) {\r
@@ -1937,7 +1981,6 @@ void CALLBACK end_service(void *arg, unsigned char why) {
       wait_for_hooks(service, false);\r
       free_imports();\r
       exit(exitcode);\r
-    break;\r
   }\r
 }\r
 \r
@@ -2070,3 +2113,62 @@ awaited:
 \r
   return ret;\r
 }\r
+\r
+int list_nssm_services() {\r
+  /* Open service manager. */\r
+  SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
+  if (! services) {\r
+    print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+    return 1;\r
+  }\r
+\r
+  unsigned long bufsize, required, count, i;\r
+  unsigned long resume = 0;\r
+  EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
+  unsigned long error = GetLastError();\r
+  if (error != ERROR_MORE_DATA) {\r
+    print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
+    return 2;\r
+  }\r
+\r
+  ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
+  if (! status) {\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("list_nssm_services()"));\r
+    return 3;\r
+  }\r
+\r
+  bufsize = required;\r
+  while (true) {\r
+    int ret = EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
+    if (! ret) {\r
+      error = GetLastError();\r
+      if (error != ERROR_MORE_DATA) {\r
+        HeapFree(GetProcessHeap(), 0, status);\r
+        print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
+        return 4;\r
+      }\r
+    }\r
+\r
+    for (i = 0; i < count; i++) {\r
+      /* Try to get the service parameters. */\r
+      nssm_service_t *service = alloc_nssm_service();\r
+      if (! service) {\r
+        HeapFree(GetProcessHeap(), 0, status);\r
+        print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("nssm_service_t"), _T("list_nssm_services()"));\r
+        return 5;\r
+      }\r
+      _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), status[i].lpServiceName);\r
+\r
+      get_parameters(service, 0);\r
+      /* We manage the service if we have an Application. */\r
+      if (service->exe[0]) _tprintf(_T("%s\n"), service->name);\r
+\r
+      cleanup_nssm_service(service);\r
+    }\r
+\r
+    if (ret) break;\r
+  }\r
+\r
+  HeapFree(GetProcessHeap(), 0, status);\r
+  return 0;\r
+}\r