]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Configurable environment for vmtoolsd.
authorOliver Kurth <okurth@vmware.com>
Tue, 12 Nov 2019 02:12:22 +0000 (18:12 -0800)
committerOliver Kurth <okurth@vmware.com>
Tue, 12 Nov 2019 02:12:22 +0000 (18:12 -0800)
If a user wants to override(*) an environment variable e.g.
TMPDIR for vmtoolsd, the only choices for the user are:
1. Change system environment variable, that may affect more
than vmtoolsd
2. Change vmtoolsd service startup scripts on Linux.
Some of these methods, especially #2 gets overwritten by
upgrades and user is forced to re-apply the change on every
upgrade of VMTools. Also, #2 is somewhat complex due to
different type of VMTools installations and differences
in Linux distros.

We can't override the environment completely from within
service but we can configure the environment to a large
extent once vmtoolsd comes up and reads tools.conf.

*=> "override" term here applies to setting, modifying and/or
unsetting an environment variable.

This is mainly required for system service vmsvc, but
given that vmusr shares code with vmsvc, we can provide
this functionality for both.

Updated example tools.conf with the new configuration.

open-vm-tools/lib/include/conf.h
open-vm-tools/services/vmtoolsd/mainLoop.c
open-vm-tools/tools.conf

index 4788a9cb1eef43ca06b4c8fb0c3ad9d6cca5d113..5cfc527090fe1819da4afbd398077acf9e7590de 100644 (file)
  ******************************************************************************
  */
 
+/*
+ ******************************************************************************
+ * BEGIN environment goodies.
+ */
+
+/**
+ * Defines the strings used for setenvironment and unsetenvironment
+ * config groups.
+ *
+ * These config groups are used for setting and unsetting environment
+ * variables for the service. The keys in this config group can be
+ * specified in following 2 formats:
+ *
+ * 1. <variableName> = <value>
+ * 2. <serviceName>.<variableName> = <value>
+ *
+ * Variables specified in format #1 are applied to all services reading
+ * the config file whereas variables specified in format #2 are applied
+ * only to the specified service.
+ */
+#define CONFGROUPNAME_SET_ENVIRONMENT "setenvironment"
+#define CONFGROUPNAME_UNSET_ENVIRONMENT "unsetenvironment"
+
+/*
+ * END environment goodies.
+ ******************************************************************************
+ */
+
 #endif /* __CONF_H__ */
index 2c62b4b1b6813bbd7a2f99aa01fb1d5330c5ad1f..7eaf1db2f763223426bc2066511e79f256d1b3cc 100644 (file)
 #include "vmware/tools/log.h"
 #include "vmware/tools/utils.h"
 #include "vmware/tools/vmbackup.h"
-
+#if defined(_WIN32)
+#  include "windowsu.h"
+#else
+#  include "posix.h"
+#endif
 
 /*
  * Establish the default and maximum vmusr RPC channel error limits
@@ -103,7 +107,7 @@ ToolsCoreCleanup(ToolsServiceState *state)
    g_key_file_free(state->ctx.config);
    g_main_loop_unref(state->ctx.mainLoop);
 
-#if defined(G_PLATFORM_WIN32)
+#if defined(_WIN32)
    if (state->ctx.comInitialized) {
       CoUninitialize();
       state->ctx.comInitialized = FALSE;
@@ -644,6 +648,318 @@ ToolsCore_ReloadConfig(ToolsServiceState *state,
 }
 
 
+#if defined(_WIN32)
+
+/**
+ * Gets error message for the last error.
+ *
+ * @param[in]  error    Error code to be converted to string message.
+ *
+ * @return The error message, or NULL in case of failure.
+ */
+
+static char *
+ToolCoreGetLastErrorMsg(DWORD error)
+{
+   char *msg = Win32U_FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
+                                     FORMAT_MESSAGE_IGNORE_INSERTS,
+                                     NULL,
+                                     error,
+                                     0,       // Default language
+                                     NULL);
+   if (msg == NULL) {
+      g_warning("Failed to get error message for %d, error=%d.\n",
+                error, GetLastError());
+      return NULL;
+   }
+
+   return msg;
+}
+
+#endif
+
+
+/**
+ * Gets an environment variable for the current process.
+ *
+ * @param[in]  name       Name of the env variable.
+ *
+ * @return The value of env variable, or NULL in case of error.
+ */
+
+static gchar *
+ToolsCoreEnvGetVar(const char *name)      // IN
+{
+   gchar *value;
+
+#if defined(_WIN32)
+   DWORD valueSize;
+   /*
+    * Win32U_GetEnvironmentVariable requires buffer to be accurate size.
+    * So, we need to get the value size first.
+    *
+    * Windows bug: GetEnvironmentVariable() does not clear stale
+    * error when the return value is 0 because of env variable
+    * holding empty string value (just NUL-char). So, we need to
+    * clear it before we call the Win32 API.
+    */
+   SetLastError(ERROR_SUCCESS);
+   valueSize = Win32U_GetEnvironmentVariable(name, NULL, 0);
+   if (valueSize == 0) {
+      goto error;
+   }
+
+   value = g_malloc(valueSize);
+   SetLastError(ERROR_SUCCESS);
+   if (Win32U_GetEnvironmentVariable(name, value, valueSize) == 0) {
+      g_free(value);
+      goto error;
+   }
+
+   return value;
+
+error:
+{
+   DWORD error = GetLastError();
+   if (error == ERROR_SUCCESS) {
+      g_message("Env variable %s is empty.\n", name);
+   //} else if (error == ERROR_ENVVAR_NOT_FOUND) {
+   //   g_message("Env variable %s not found.\n", name);
+   } else {
+      char *errorMsg = ToolCoreGetLastErrorMsg(error);
+      if (errorMsg != NULL) {
+         g_warning("Failed to get env variable size %s, error=%s.\n",
+                   name, errorMsg);
+         free(errorMsg);
+      } else {
+         g_warning("Failed to get env variable size %s, error=%d.\n",
+                   name, error);
+      }
+   }
+   return NULL;
+}
+#else
+   value = Posix_Getenv(name);
+   return value == NULL ? value : g_strdup(value);
+#endif
+}
+
+
+/**
+ * Sets an environment variable for the current process.
+ *
+ * @param[in]  name       Name of the env variable.
+ * @param[in]  value      Value for the env variable.
+ *
+ * @return gboolean, TRUE on success or FALSE in case of error.
+ */
+
+static gboolean
+ToolsCoreEnvSetVar(const char *name,      // IN
+                   const char *value)     // IN
+{
+#if defined(_WIN32)
+   if (!Win32U_SetEnvironmentVariable(name, value)) {
+      char *errorMsg;
+      DWORD error = GetLastError();
+
+      errorMsg = ToolCoreGetLastErrorMsg(error);
+      if (errorMsg != NULL) {
+         g_warning("Failed to set env variable %s=%s, error=%s.\n",
+                   name, value, errorMsg);
+         free(errorMsg);
+      } else {
+         g_warning("Failed to set env variable %s=%s, error=%d.\n",
+                   name, value, error);
+      }
+      return FALSE;
+   }
+#else
+   if (Posix_Setenv(name, value, TRUE) != 0) {
+      g_warning("Failed to set env variable %s=%s, error=%s.\n",
+                name, value, strerror(errno));
+      return FALSE;
+   }
+#endif
+   return TRUE;
+}
+
+
+/**
+ * Unsets an environment variable for the current process.
+ *
+ * @param[in]  name       Name of the env variable.
+ *
+ * @return gboolean, TRUE on success or FALSE in case of error.
+ */
+
+static gboolean
+ToolsCoreEnvUnsetVar(const char *name)    // IN
+{
+#if defined(_WIN32)
+   if (!Win32U_SetEnvironmentVariable(name, NULL)) {
+      char *errorMsg;
+      DWORD error = GetLastError();
+
+      errorMsg = ToolCoreGetLastErrorMsg(error);
+      if (errorMsg != NULL) {
+         g_warning("Failed to unset env variable %s, error=%s.\n",
+                   name, errorMsg);
+         free(errorMsg);
+      } else {
+         g_warning("Failed to unset env variable %s, error=%d.\n",
+                   name, error);
+      }
+      return FALSE;
+   }
+#else
+   if (Posix_Unsetenv(name) != 0) {
+      g_warning("Failed to unset env variable %s, error=%s.\n",
+                name, strerror(errno));
+      return FALSE;
+   }
+#endif
+   return TRUE;
+}
+
+
+/**
+ * Setup environment variables for the current process from
+ * a given config group.
+ *
+ * @param[in]  ctx       Application context.
+ * @param[in]  group     Configuration group to be read.
+ * @param[in]  doUnset   Whether to unset the environment vars.
+ */
+
+static void
+ToolsCoreInitEnvGroup(ToolsAppCtx *ctx,   // IN
+                      const gchar *group, // IN
+                      gboolean doUnset)   // IN
+{
+   gsize i;
+   gsize length;
+   GError *err = NULL;
+   gchar **keys = g_key_file_get_keys(ctx->config, group, &length, &err);
+   if (err != NULL) {
+      if (err->code != G_KEY_FILE_ERROR_GROUP_NOT_FOUND) {
+         g_warning("Failed to get keys for config group %s (err=%d).\n",
+                   group, err->code);
+      }
+      g_clear_error(&err);
+      g_info("Skipping environment initialization for %s from %s config.\n",
+             ctx->name, group);
+      return;
+   }
+
+   g_info("Found %"FMTSZ"d environment variable(s) in %s config.\n",
+          length, group);
+
+   /*
+    * Following 2 formats are supported:
+    * 1. <variableName> = <value>
+    * 2. <serviceName>.<variableName> = <value>
+    *
+    * Variables specified in format #1 are applied to all services and
+    * variables specified in format #2 are applied to specified service only.
+    */
+   for (i = 0; i < length; i++) {
+      const gchar *name = NULL;
+      const gchar *key = keys[i];
+      const gchar *delim;
+
+      /*
+       * Pick the keys that have service name prefix or no prefix.
+       */
+      delim = strchr(key, '.');
+      if (delim == NULL) {
+         name = key;
+      } else if (strncmp(key, ctx->name, delim - key) == 0) {
+         name = delim + 1;
+      }
+
+      /*
+       * Ignore entries with empty env variable names.
+       */
+      if (name != NULL && *name != '\0') {
+         gchar *oldValue = ToolsCoreEnvGetVar(name);
+         if (doUnset) {
+            /*
+             * We can't avoid duplicate removals, but removing a non-existing
+             * environment variable is a no-op anyway.
+             */
+            if (ToolsCoreEnvUnsetVar(name)) {
+               g_message("Removed env var %s=[%s]\n",
+                         name, oldValue == NULL ? "(null)" : oldValue);
+            }
+         } else {
+            gchar *value = VMTools_ConfigGetString(ctx->config, group,
+                                                   key, NULL);
+            if (value != NULL) {
+               /*
+                * Get rid of trailing space.
+                */
+               g_strchomp(value);
+
+               /*
+                * Avoid updating environment var if it is already set to
+                * the same value.
+                *
+                * Also, g_key_file_get_keys() does not filter out duplicates
+                * but, VMTools_ConfigGetString returns only last entry
+                * for the key. So, by comparing old value, we avoid setting
+                * the environment multiple times when there are duplicates.
+                *
+                * NOTE: Need to use g_strcmp0 because oldValue can be NULL.
+                * As value can't be NULL but oldValue can be NULL, we might
+                * still do an unnecessary update in cases like setting a
+                * variable to empty/no value twice. However, it does not harm
+                * and is not worth avoiding it.
+                */
+               if (g_strcmp0(oldValue, value) == 0) {
+                  g_info("Env var %s already set to [%s], skipping.\n",
+                         name, oldValue);
+                  g_free(oldValue);
+                  g_free(value);
+                  continue;
+               }
+               g_debug("Changing env var %s from [%s] -> [%s]\n",
+                       name, oldValue == NULL ? "(null)" : oldValue, value);
+               if (ToolsCoreEnvSetVar(name, value)) {
+                  g_message("Updated env var %s from [%s] -> [%s]\n",
+                            name, oldValue == NULL ? "(null)" : oldValue,
+                            value);
+               }
+               g_free(value);
+            }
+         }
+         g_free(oldValue);
+      }
+   }
+
+   g_info("Initialized environment for %s from %s config.\n",
+          ctx->name, group);
+   g_strfreev(keys);
+}
+
+
+/**
+ * Setup environment variables for the current process.
+ *
+ * @param[in]  ctx       Application context.
+ */
+
+static void
+ToolsCoreInitEnv(ToolsAppCtx *ctx)
+{
+   /*
+    * First apply unset environment configuration to start clean.
+    */
+   ToolsCoreInitEnvGroup(ctx, CONFGROUPNAME_UNSET_ENVIRONMENT, TRUE);
+   ToolsCoreInitEnvGroup(ctx, CONFGROUPNAME_SET_ENVIRONMENT, FALSE);
+}
+
+
 /**
  * Performs any initial setup steps for the service's main loop.
  *
@@ -692,6 +1008,8 @@ ToolsCore_Setup(ToolsServiceState *state)
    ToolsCoreService_RegisterProperty(state->ctx.serviceObj,
                                      &ctxProp);
    g_object_set(state->ctx.serviceObj, TOOLS_CORE_PROP_CTX, &state->ctx, NULL);
+   /* Initialize the environment from config. */
+   ToolsCoreInitEnv(&state->ctx);
    ToolsCorePool_Init(&state->ctx);
 
    /* Initializes the debug library if needed. */
index 59437ed1c982b77758341e99f1b95af5469b2319..7c99b4dcd75d560db06ef5a55cf242216cb32d69 100644 (file)
@@ -3,11 +3,63 @@
 # VMware Tools services every 5 seconds."
 #
 # Lines must not end with trailing white space
-# All file paths need to be in Unix format
-# (forward slashes) and in utf-8, for all operating systems.
 
-[logging]
+[unsetenvironment]
+# Defines environment variables to be removed from the service reading
+# the configuration file. Supported formats are:
+#
+# 1. <variableName>=
+# 2. <serviceName>.<variableName>=
+#
+# Where <serviceName> refers to the 'vmsvc' and 'vmusr',
+# <variableName> refers to the name of the environment
+# variable to be removed. '=' sign after <variableName>
+# is mandatory to maintain the configuration file syntax.
+# However, anything after '=' is ignored.
+#
+# Case-sensitive behavior is defined by the operating system.
+#
+# Note: unsetenvironment group is processed before setenvironment group.
+# As the service environment is setup at start up time, any changes
+# in this group require service to be restarted in order to take effect.
+#
+# Unsetting PATH for all services:
+# PATH=
+#
+# Unsetting PATH for vmsvc only:
+# vmsvc.PATH=
+#
+# Unsetting PATH for vmusr only:
+# vmusr.PATH=
 
+[setenvironment]
+# Defines environment variables to be set for the service reading
+# the configuration file. Supported formats are:
+#
+# 1. <variableName>=<variableValue>
+# 2. <serviceName>.<variableName>=<variableValue>
+#
+# Where <serviceName> refers to the 'vmsvc' and 'vmusr',
+# <variableName> refers to the name of the environment
+# variable to be set, and <variableValue> refers to the
+# value to be assigned to the environment variable.
+#
+# Case-sensitive behavior is defined by the operating system.
+#
+# Note: setenvironment group is processed after unsetenvironment group.
+# As the service environment is setup at start up time, any changes
+# in this group require service to be restarted in order to take effect.
+#
+# Setting TMPDIR for all services:
+# TMPDIR=/vmware/temp
+#
+# Setting TMPDIR for vmsvc only:
+# vmsvc.TMPDIR=/vmware/vmsvc/temp
+#
+# Setting TMPDIR for vmusr only:
+# vmusr.TMPDIR=/vmware/vmusr/temp
+
+[logging]
 # set to false to disable logging
 #log = true
 
@@ -21,6 +73,9 @@
 # Possible values for handler are:
 # file: logs to a file. Set *.data to the file name
 # file+: same as 'file', but appends to the file
+#  All file paths used in *.data value need to be in Unix
+#  format (forward slashes) and in utf-8, for all operating
+#  systems.
 # vmx: logs to the host (ESXi, Workstation, Fusion)
 # std: Logs to stdout for level >= 'message',
 # and to stderr for more severe than 'message'.