Revamped environment variables again.
authorIain Patterson <me@iain.cx>
Thu, 23 Jan 2014 21:19:32 +0000 (21:19 +0000)
committerIain Patterson <me@iain.cx>
Thu, 23 Jan 2014 21:37:53 +0000 (21:37 +0000)
A previous commit changed how environment variables were managed.
Initially we were calling SetEnvironmentVariable() on each variable
defined in AppEnvironmentExtra prior to starting the service.  That
behaviour was changed because it was technically incorrect, potentially
resulting in NSSM's own environment being harmed.

Unfortunately the changed method, creating a new environment block by
calling GetEnvironmentStrings() (or using the block from AppEnvironment
if that and AppEnvironmentExtra were both defined), failed if users did
something like set AppEnvironmentExtra to PATH=C:\bin;%PATH%.  That
would result in the process being started with a block which included
TWO occurrences of the PATH variable; the system-defined one and the
appended one.  The latter would be ignored, possibly causing the
application to fail to start.

For this third attempt we now call GetEnvironmentStrings() once at
startup, storing the block in a new variable which is only freed at
exit.  Immediately prior to calling CreateProcess() to start the service
we use the new environment functions duplicate_environment() (on
AppEnvironment) and set_environment_block() (on AppEnvironmentExtra) to
set the variables.  Then immediately after calling CreateProcess() we
call duplicate_environment() again to restore the original block.

Because they are passed to expand_environment_string(), the environment
blocks are subject to expansion.  That means that setting PATH as
described in the example above would work.  Trying to append items to
the PATH by setting AppEnvironment in a similar way will probably not
work.

Thanks Bryan Senseman.

README.txt
registry.cpp
service.cpp
service.h

index ded8b7a..85bf62e 100644 (file)
@@ -354,7 +354,33 @@ environment variables which will be added to the service's environment.
 Each entry in the list should be of the form KEY=VALUE.  It is possible to\r
 omit the VALUE but the = symbol is mandatory.\r
 \r
-srvany only supports AppEnvironment.\r
+Environment variables listed in both AppEnvironment and AppEnvironmentExtra\r
+are subject to normal expansion, so it is possible, for example, to update the\r
+system path by setting "PATH=C:\bin;%PATH%" in AppEnvironmentExtra.  Variables\r
+are expanded in the order in which they appear, so if you want to include the\r
+value of one variable in another variable you should declare the dependency\r
+first.\r
+\r
+Because variables defined in AppEnvironment override the existing\r
+environment it is not possible to refer to any variables which were previously\r
+defined.\r
+\r
+For example, the following AppEnvironment block:\r
+\r
+      PATH=C:\Windows\System32;C:\Windows\r
+      PATH=C:\bin;%PATH%\r
+\r
+Would result in a PATH of "C:\bin;C:\Windows\System32;C:\Windows" as expected.\r
+\r
+Whereas the following AppEnvironment block:\r
+\r
+      PATH=C:\bin;%PATH%\r
+\r
+Would result in a path containing only C:\bin and probably cause the\r
+application to fail to start.\r
+\r
+Most people will want to use AppEnvironmentExtra exclusively.  srvany only\r
+supports AppEnvironment.\r
 \r
 \r
 Managing services using the GUI\r
index 50f6dc3..bd79dc3 100644 (file)
@@ -466,67 +466,6 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
   /* Environment variables to add to existing rather than replace - may fail. */\r
   get_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);\r
 \r
-  if (si) {\r
-    if (service->env_extra) {\r
-      TCHAR *env;\r
-      unsigned long envlen;\r
-\r
-      /* Copy our environment for the application. */\r
-      if (! service->env) {\r
-        TCHAR *rawenv = GetEnvironmentStrings();\r
-        env = rawenv;\r
-        if (env) {\r
-          /*\r
-            The environment block starts with variables of the form\r
-            =C:=C:\Windows\System32 which we ignore.\r
-          */\r
-          while (*env == _T('=')) {\r
-            for ( ; *env; env++);\r
-            env++;\r
-          }\r
-          envlen = 0;\r
-          if (*env) {\r
-            while (true) {\r
-              for ( ; env[envlen]; envlen++);\r
-              if (! env[++envlen]) break;\r
-            }\r
-            envlen++;\r
-\r
-            service->envlen = envlen * sizeof(TCHAR);\r
-            service->env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->envlen);\r
-            memmove(service->env, env, service->envlen);\r
-            FreeEnvironmentStrings(rawenv);\r
-          }\r
-        }\r
-      }\r
-\r
-      /* Append extra variables to configured variables. */\r
-      if (service->env) {\r
-        envlen = service->envlen + service->env_extralen - sizeof(TCHAR)/*?*/;\r
-        env = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, envlen);\r
-        if (env) {\r
-          memmove(env, service->env, service->envlen - sizeof(TCHAR));\r
-          /* envlen is in bytes but env[i] is in characters. */\r
-          memmove(env + (service->envlen / sizeof(TCHAR)) - 1, service->env_extra, service->env_extralen);\r
-\r
-          HeapFree(GetProcessHeap(), 0, service->env);\r
-          HeapFree(GetProcessHeap(), 0, service->env_extra);\r
-          service->env = env;\r
-          service->envlen = envlen;\r
-        }\r
-        else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("get_parameters()"), 0);\r
-      }\r
-      else {\r
-        /* Huh?  No environment at all? */\r
-        service->env = service->env_extra;\r
-        service->envlen = service->env_extralen;\r
-      }\r
-    }\r
-\r
-    service->env_extra = 0;\r
-    service->env_extralen = 0;\r
-  }\r
-\r
   /* Try to get priority - may fail. */\r
   unsigned long priority;\r
   if (get_number(key, NSSM_REG_PRIORITY, &priority, false) == 1) {\r
index c920af8..ee60c10 100644 (file)
@@ -681,6 +681,7 @@ void cleanup_nssm_service(nssm_service_t *service) {
   if (service->wait_handle) UnregisterWait(service->process_handle);\r
   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
+  if (service->initial_env) FreeEnvironmentStrings(service->initial_env);\r
   HeapFree(GetProcessHeap(), 0, service);\r
 }\r
 \r
@@ -1324,6 +1325,9 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) {
     }\r
   }\r
 \r
+  /* Remember our initial environment. */\r
+  service->initial_env = GetEnvironmentStrings();\r
+\r
   monitor_service(service);\r
 }\r
 \r
@@ -1519,23 +1523,21 @@ int start_service(nssm_service_t *service) {
 \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
   bool inherit_handles = false;\r
   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
   unsigned long flags = service->priority & priority_mask();\r
   if (service->stdin_pipe) flags |= DETACHED_PROCESS;\r
   if (service->affinity) flags |= CREATE_SUSPENDED;\r
-#ifdef UNICODE\r
-  flags |= CREATE_UNICODE_ENVIRONMENT;\r
-#endif\r
-  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\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
-    if (error == ERROR_INVALID_PARAMETER && service->env) {\r
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
-      if (test_environment(service->env)) exitcode = 4;\r
-    }\r
-    else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\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(service->initial_env);\r
     return stop_service(service, exitcode, true, true);\r
   }\r
   service->process_handle = pi.hProcess;\r
@@ -1545,6 +1547,9 @@ int start_service(nssm_service_t *service) {
 \r
   close_output_handles(&si);\r
 \r
+  /* Restore our environment. */\r
+  duplicate_environment(service->initial_env);\r
+\r
   if (service->affinity) {\r
     /*\r
       We are explicitly storing service->affinity as a 64-bit unsigned integer\r
index 2eb2b42..fe59c9a 100644 (file)
--- a/service.h
+++ b/service.h
@@ -101,6 +101,7 @@ typedef struct {
   LARGE_INTEGER throttle_duetime;\r
   FILETIME creation_time;\r
   FILETIME exit_time;\r
+  TCHAR *initial_env;\r
 } nssm_service_t;\r
 \r
 void WINAPI service_main(unsigned long, TCHAR **);\r