]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Common header file change not directly applicable to open-vm-tools.
authorJohn Wolfe <jwolfe@vmware.com>
Thu, 20 May 2021 18:38:37 +0000 (11:38 -0700)
committerJohn Wolfe <jwolfe@vmware.com>
Thu, 20 May 2021 18:38:37 +0000 (11:38 -0700)
open-vm-tools/configure.ac
open-vm-tools/lib/Makefile.am
open-vm-tools/lib/globalConfig/Makefile.am [new file with mode: 0644]
open-vm-tools/lib/globalConfig/globalConfig.c [new file with mode: 0644]
open-vm-tools/lib/include/globalConfig.h [new file with mode: 0644]
open-vm-tools/services/vmtoolsd/Makefile.am
open-vm-tools/services/vmtoolsd/mainLoop.c
open-vm-tools/services/vmtoolsd/toolsCoreInt.h
open-vm-tools/toolbox/Makefile.am
open-vm-tools/toolbox/toolboxCmdInt.h
open-vm-tools/toolbox/toolboxcmd-globalconf.c [new file with mode: 0644]

index 9a8afef1b3d7b9f435452ec5380a3a89beca4b77..4730c485c66e3798f3f25db7622717855c0b0579 100644 (file)
@@ -1615,6 +1615,7 @@ AC_CONFIG_FILES([                      \
    lib/file/Makefile                   \
    lib/foundryMsg/Makefile             \
    lib/glibUtils/Makefile              \
+   lib/globalConfig/Makefile           \
    lib/guestApp/Makefile               \
    lib/guestRpc/Makefile               \
    lib/guestStoreClientHelper/Makefile \
index 2c7661715df8a7d09f2f3c261bea80cef8328179..523e7231750f6cbbc0be2b75603ce9afaef401c4 100644 (file)
@@ -39,6 +39,7 @@ SUBDIRS += glibUtils
 SUBDIRS += guestApp
 if LINUX
    SUBDIRS += guestStoreClientHelper
+   SUBDIRS += globalConfig
 endif
 SUBDIRS += hgfs
 SUBDIRS += hgfsBd
diff --git a/open-vm-tools/lib/globalConfig/Makefile.am b/open-vm-tools/lib/globalConfig/Makefile.am
new file mode 100644 (file)
index 0000000..e18f1fe
--- /dev/null
@@ -0,0 +1,24 @@
+################################################################################
+### Copyright (c) 2021 VMware, Inc.  All rights reserved.
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of version 2 of the GNU General Public License as
+### published by the Free Software Foundation.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software
+### Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+################################################################################
+
+noinst_LTLIBRARIES = libGlobalConfig.la
+
+libGlobalConfig_la_SOURCES =
+libGlobalConfig_la_SOURCES += globalConfig.c
+
+libGlobalConfig_la_CPPFLAGS =
+libGlobalConfig_la_CPPFLAGS += @GLIB2_CPPFLAGS@
diff --git a/open-vm-tools/lib/globalConfig/globalConfig.c b/open-vm-tools/lib/globalConfig/globalConfig.c
new file mode 100644 (file)
index 0000000..aebded8
--- /dev/null
@@ -0,0 +1,976 @@
+/*********************************************************
+ * Copyright (C) 2020-2021 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
+ *
+ *********************************************************/
+
+/**
+ * @file globalConfig.c
+ *
+ *    Implementation of the module that downloads the configuration from the
+ *    GuestStore.
+ */
+
+/* Need this header in the beginning for GLOBALCONFIG_SUPPORTED definition. */
+#include "globalConfig.h"
+
+#if !defined(GLOBALCONFIG_SUPPORTED)
+#   error This file should not be compiled
+#endif
+
+#include <errno.h>
+#include "conf.h"
+#include "file.h"
+#include "guestApp.h"
+#include <string.h>
+#include <glib/gstdio.h>
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include "str.h"
+#include "vmware/tools/guestStore.h"
+#include "vmware/tools/utils.h"
+#include "vmware/tools/threadPool.h"
+#include "guestStoreClient.h"
+
+/**
+ * Default value for CONFNAME_GLOBALCONF_ENABLED setting in tools.conf.
+ *
+ * FALSE will enable the module.
+ */
+#define GLOBALCONF_DEFAULT_ENABLED FALSE
+
+/**
+ * Default value for CONFNAME_GLOBALCONF_POLL_INTERVAL setting in tools.conf.
+ */
+#define GLOBALCONF_DEFAULT_POLL_INTERVAL (60 * 60)
+
+/**
+ * Minimum poll interval for fetching the global configuration from GuestStore.
+ */
+#ifdef VMX86_DEBUG
+#define GLOBALCONF_MIN_POLL_INTERVAL (2 * 60)
+#else
+#define GLOBALCONF_MIN_POLL_INTERVAL (30 * 60)
+#endif
+
+/**
+ * Default value for CONFNAME_GLOBALCONF_RESOURCE setting in tools.conf.
+ */
+#if defined(_WIN32)
+#define GLOBALCONF_DEFAULT_RESOURCE \
+        "/vmware/configurations/vmtools/windows/tools.conf"
+#else
+#define GLOBALCONF_DEFAULT_RESOURCE \
+        "/vmware/configurations/vmtools/linux/tools.conf"
+#endif
+
+/**
+ * Name of the local file populated with the global tools configuration.
+ */
+#define GLOBALCONF_LOCAL_FILENAME "tools-global.conf"
+
+/**
+ * Name of the local temp file populated with the global tools configuration.
+ */
+#define GLOBALCONF_LOCAL_TEMP_FILENAME "temp-global.conf"
+
+/**
+ * Macro to get the resource path from the config dictionary.
+ */
+#define GLOBALCONF_GET_RESOURCE_PATH(cfg) \
+           VMTools_ConfigGetString(cfg, CONFGROUPNAME_GLOBALCONF, \
+                                   CONFNAME_GLOBALCONF_RESOURCE, \
+                                   GLOBALCONF_DEFAULT_RESOURCE)
+
+
+typedef struct GlobalConfigInfo {
+   gchar *localConfPath;
+   gchar *localTempPath;
+   gchar *guestStoreResource;
+   guint pollInterval;
+} GlobalConfigInfo;
+
+typedef struct GlobalConfigThreadState {
+   /*
+    * 'mutex' protect concurrent accesses to terminate, cond, guestStoreEnabled
+    * and useRandomInterval.
+    */
+   GMutex mutex;
+   GCond cond;
+   gboolean terminate;
+   gboolean guestStoreEnabled;
+   gboolean useRandomInterval;
+   GlobalConfigInfo *configInfo;
+} GlobalConfigThreadState;
+
+static GlobalConfigThreadState globalConfigThreadState;
+
+
+/*
+ **************************************************************************
+ * GuestStoreStateChanged --                                         */ /**
+ *
+ * GuestStore access state has changed, now update the download thread
+ * accordingly.
+ *
+ * @param[in] src               Unused
+ * @param[in] guestStoreEnabled GuestStore state enabled if TRUE
+ * @param[in] unused            Unused
+ *
+ **************************************************************************
+ */
+
+static void
+GuestStoreStateChanged(gpointer src,
+                       gboolean guestStoreEnabled,
+                       gpointer unused)
+{
+   GlobalConfigThreadState *threadState = &globalConfigThreadState;
+
+   g_mutex_lock(&threadState->mutex);
+
+   g_debug("%s: GuestStore old state: %d and new state: %d\n",
+           __FUNCTION__, threadState->guestStoreEnabled, guestStoreEnabled);
+
+   if (threadState->guestStoreEnabled != guestStoreEnabled) {
+      threadState->guestStoreEnabled = guestStoreEnabled;
+      g_debug("%s: Signalling the change in the GuestStore state.\n",
+              __FUNCTION__);
+      g_cond_signal(&threadState->cond);
+   }
+
+   g_mutex_unlock(&threadState->mutex);
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfigGetPollInterval --                                    */ /**
+ *
+ * Parses the configuration and returns the poll-interval.
+ *
+ * @param[in] cfg   VMTools configuration dictionary.
+ *
+ * @return unsigned integer representing the poll interval.
+ *         0 if the globalconfig module is disabled.
+ *         GLOBALCONF_DEFAULT_POLL_INTERVAL if an invalid value is specified.
+ *
+ **************************************************************************
+ */
+
+static guint
+GlobalConfigGetPollInterval(GKeyFile *cfg)
+{
+   gint pollInterval = 0;
+   gboolean enabled = GlobalConfig_GetEnabled(cfg);
+
+   if (!enabled) {
+      g_info("%s: global config module is disabled.", __FUNCTION__);
+      return pollInterval;
+   }
+
+   pollInterval = VMTools_ConfigGetInteger(cfg, CONFGROUPNAME_GLOBALCONF,
+                                           CONFNAME_GLOBALCONF_POLL_INTERVAL,
+                                           GLOBALCONF_DEFAULT_POLL_INTERVAL);
+   if (pollInterval < GLOBALCONF_MIN_POLL_INTERVAL) {
+      g_warning("%s: Invalid value %d specified for '%s'. Using default %us",
+                __FUNCTION__, pollInterval, CONFNAME_GLOBALCONF_POLL_INTERVAL,
+                GLOBALCONF_DEFAULT_POLL_INTERVAL);
+      pollInterval = GLOBALCONF_DEFAULT_POLL_INTERVAL;
+   }
+
+   return pollInterval;
+}
+
+
+/*
+ **************************************************************************
+ * GenerateRandomInterval --                                         */ /**
+ *
+ * Generate a random value between MIN_RAND_WAIT_INTERVAL and
+ * MAX_RAND_WAIT_INTERVAL.
+ *
+ * @return a random generated unsigned integer value.
+ *
+ **************************************************************************
+ */
+
+static guint
+GenerateRandomInterval(void)
+{
+   GRand *gRand = g_rand_new();
+
+   /*
+    * The following min and max values are taken randomly.
+    */
+#define MIN_RAND_WAIT_INTERVAL 30
+#define MAX_RAND_WAIT_INTERVAL 300
+
+   guint randomInterval = g_rand_int_range(gRand,
+                                           MIN_RAND_WAIT_INTERVAL,
+                                           MAX_RAND_WAIT_INTERVAL);
+   g_rand_free(gRand);
+
+#undef MIN_RAND_WAIT_INTERVAL
+#undef MAX_RAND_WAIT_INTERVAL
+
+   g_info("%s: Using random interval: %u.\n", __FUNCTION__, randomInterval);
+
+   return randomInterval;
+}
+
+
+/*
+ **************************************************************************
+ * VMToolsChannelReset --                                            */ /**
+ *
+ * Callback function that gets called when the VMTools channel gets reset.
+ *
+ * @param[in] src       Unused
+ * @param[in] ctx       Unused
+ * @param[in] unused    Unused
+ *
+ **************************************************************************
+ */
+
+static void
+VMToolsChannelReset(gpointer src,
+                    ToolsAppCtx *ctx,
+                    gpointer unused)
+{
+   GlobalConfigThreadState *threadState = &globalConfigThreadState;
+
+   g_debug("%s: VMTools channel got reset.\n", __FUNCTION__);
+
+   g_mutex_lock(&threadState->mutex);
+
+   if (threadState->configInfo->pollInterval > 0) {
+      /*
+       * The RPC channel may get reset due to various conditions like
+       * snapshotting the VM, vmotion the VM, instant cloning of the VM.
+       * In order to avoid potential load spikes in case of instant clones,
+       * wait for a randomized interval.
+       */
+      threadState->useRandomInterval = TRUE;
+   }
+
+   g_mutex_unlock(&threadState->mutex);
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfigToolsConfReload --                                    */ /**
+ *
+ * Callback function that gets called when the VMTools configuration gets
+ * reloaded.
+ *
+ * @param[in] src       Unused
+ * @param[in] ctx       Application Context
+ * @param[in] unused    Unused
+ *
+ **************************************************************************
+ */
+
+static void
+GlobalConfigToolsConfReload(gpointer src,
+                            ToolsAppCtx *ctx,
+                            gpointer unused)
+{
+   guint pollInterval;
+   GlobalConfigThreadState *threadState = &globalConfigThreadState;
+   gchar *newResourcePath;
+
+   g_debug("%s: VMTools configuration got reloaded.\n", __FUNCTION__);
+
+   g_mutex_lock(&threadState->mutex);
+
+   newResourcePath = GLOBALCONF_GET_RESOURCE_PATH(ctx->config);
+   if (g_strcmp0(newResourcePath,
+                 threadState->configInfo->guestStoreResource)) {
+      g_info("%s: '%s' changed. Old: %s, New: %s\n",
+             __FUNCTION__, CONFNAME_GLOBALCONF_RESOURCE,
+             threadState->configInfo->guestStoreResource,
+             newResourcePath);
+      g_free(threadState->configInfo->guestStoreResource);
+      threadState->configInfo->guestStoreResource = newResourcePath;
+   } else {
+      g_free(newResourcePath);
+      newResourcePath = NULL;
+   }
+
+   pollInterval = GlobalConfigGetPollInterval(ctx->config);
+   if (pollInterval != threadState->configInfo->pollInterval) {
+      g_info("%s: '%s' changed. Old: '%u', New: '%u' "
+             "Signalling the change in the globalConfing configuration.\n",
+             __FUNCTION__, CONFNAME_GLOBALCONF_POLL_INTERVAL,
+             threadState->configInfo->pollInterval,
+             pollInterval);
+      threadState->configInfo->pollInterval = pollInterval;
+      g_cond_signal(&threadState->cond);
+   } else if (pollInterval == 0) {
+      /*
+       * Delete the stale config files (if any).
+       */
+      GlobalConfig_DeleteConfig();
+   }
+
+   g_mutex_unlock(&threadState->mutex);
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfGetConfPath --                                          */ /**
+ *
+ * Returns the path to the global configuration file.
+ *
+ * @return Path of the global configuration file. The caller shouldn't
+ *         free the memory. Returns NULL if the path cannot be computed.
+ *
+ **************************************************************************
+ */
+
+static const gchar *
+GlobalConfGetConfPath(void)
+{
+   static gchar *globalConfPath = NULL;
+
+   if (globalConfPath == NULL) {
+      char *guestAppConfPath = GuestApp_GetConfPath();
+      if (guestAppConfPath == NULL) {
+         g_warning("%s: Failed to get configuration directory.\n",
+                   __FUNCTION__);
+      } else {
+         globalConfPath = g_build_filename(guestAppConfPath,
+                                           GLOBALCONF_LOCAL_FILENAME,
+                                           NULL);
+      }
+      free(guestAppConfPath);
+   }
+
+   return globalConfPath;
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfigThreadStateInit --                                    */ /**
+ *
+ * Reads the key/value pairs related to globalconf module from the user
+ * specified configuration dictionary and intializes the globaconf module.
+ *
+ * @param[in] cfg    Tools configuration dictionary.
+ *
+ * @return TRUE if the globalconf module is initialized.
+ *         FALSE if any error happened while initializing.
+ *
+ **************************************************************************
+ */
+
+static gboolean
+GlobalConfigThreadStateInit(GKeyFile *cfg)
+{
+   gchar *guestAppConfPath;
+   GlobalConfigInfo *configInfo;
+   GlobalConfigThreadState *threadState = &globalConfigThreadState;
+
+   ASSERT(cfg != NULL);
+
+   guestAppConfPath = GuestApp_GetConfPath();
+   if (guestAppConfPath == NULL) {
+      g_warning("%s: Failed to get tools install path.\n", __FUNCTION__);
+      return FALSE;
+   }
+
+   ASSERT(threadState->configInfo == NULL);
+
+   configInfo = g_malloc0(sizeof(*configInfo));
+   configInfo->localConfPath = g_build_filename(guestAppConfPath,
+                                                GLOBALCONF_LOCAL_FILENAME,
+                                                NULL);
+
+   configInfo->localTempPath = g_build_filename(guestAppConfPath,
+                                                GLOBALCONF_LOCAL_TEMP_FILENAME,
+                                                NULL);
+
+   vm_free(guestAppConfPath);
+
+   configInfo->pollInterval = GlobalConfigGetPollInterval(cfg);
+
+   g_debug("%s: %s: %d", __FUNCTION__, CONFNAME_GLOBALCONF_POLL_INTERVAL,
+           configInfo->pollInterval);
+
+   configInfo->guestStoreResource = GLOBALCONF_GET_RESOURCE_PATH(cfg);
+
+   g_debug("%s: Configuration Resource path in GuestStore: %s",
+           __FUNCTION__, configInfo->guestStoreResource);
+
+   threadState->configInfo = configInfo;
+   return TRUE;
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfigInfoFree --                                           */ /**
+ *
+ * Frees up memory allocated for GlobalConfigInfo structure.
+ *
+ * @param[in] data   GlobalConfigInfo structure that needs to be freed.
+ *
+ **************************************************************************
+ */
+
+static void
+GlobalConfigInfoFree(GlobalConfigInfo *configInfo)
+{
+   if (configInfo != NULL) {
+      g_free(configInfo->localConfPath);
+      g_free(configInfo->localTempPath);
+      g_free(configInfo->guestStoreResource);
+   }
+
+   g_free(configInfo);
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfigThreadStateFree --                                    */ /**
+ *
+ * Frees up memory allocated for GlobalConfigThread structure.
+ *
+ * @param[in] unused Callback data. Not used.
+ *
+ **************************************************************************
+ */
+
+static void
+GlobalConfigThreadStateFree(gpointer unused)
+{
+   GlobalConfigThreadState *threadState = &globalConfigThreadState;
+
+   GlobalConfigInfoFree(threadState->configInfo);
+   threadState->configInfo = NULL;
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfigThreadTerminate --                                    */ /**
+ *
+ * Signals the 'global config' thread to exit.
+ *
+ * @param[in] ctx    Application context
+ * @param[in] data   Pointer to GlobalConfigThreadState sturcture.
+ *
+ **************************************************************************
+ */
+
+static void
+GlobalConfigThreadTerminate(ToolsAppCtx *ctx,
+                            gpointer data)
+{
+   GlobalConfigThreadState *threadState = &globalConfigThreadState;
+
+   g_mutex_lock(&threadState->mutex);
+
+   threadState->terminate = TRUE;
+   threadState->guestStoreEnabled = FALSE;
+
+   g_cond_signal(&threadState->cond);
+
+   g_mutex_unlock(&threadState->mutex);
+}
+
+
+/*
+ **************************************************************************
+ * LoadConfigFile --                                                 */ /**
+ *
+ * Loads the specified config file.
+ *
+ * @param[in] confPath Path to the configuration file.
+ * @param[in|out] config  Configuration dictionary that is loaded with the
+ *                        contents from the specified configuration file. When
+ *                        loading, the old content is destroyed. Before
+ *                        invoking this function the first time for a specific
+ *                        confPath, the config must be initialized to NULL.
+ * @param[in|out] mtime   Last known modification time of the config file.
+ *                        When the function succeeds, will contain the new
+ *                        modification time read from the file. If NULL (or 0),
+ *                        the configuration dictionary is always loaded.
+ *
+ * @return TRUE Whether a new configuration dictionary was loaded.
+ *
+ **************************************************************************
+ */
+
+static gboolean
+LoadConfigFile(const gchar *confPath,
+               GKeyFile **config,
+               time_t *mtime)
+{
+   gboolean configUpdated = FALSE;
+   GStatBuf confStat;
+   GError *err = NULL;
+   GKeyFile *cfg;
+
+   if (config == NULL || confPath == NULL ) {
+      g_debug("%s: Invalid arguments specified.\n", __FUNCTION__);
+      goto exit;
+   }
+
+  if (g_stat(confPath, &confStat) == -1) {
+      /*
+       * If the file doesn't exist, it's not an error.
+       */
+      if (errno != ENOENT) {
+         g_warning("%s: Failed to stat conf file: %s, Error: '%s'\n",
+                   __FUNCTION__, confPath, strerror(errno));
+      } else {
+         /*
+          * If we used to have a file, set the config to NULL.
+          */
+         if (*config != NULL) {
+            g_key_file_free(*config);
+            *config = NULL;
+            configUpdated = TRUE;
+         }
+      }
+      goto exit;
+   }
+
+   /* Check if we really need to load the data. */
+   if (mtime != NULL && confStat.st_mtime <= *mtime) {
+      goto exit;
+   }
+
+   cfg = g_key_file_new();
+   g_key_file_load_from_file(cfg, confPath,
+                             G_KEY_FILE_NONE, &err);
+   if (err != NULL) {
+      g_warning("%s: Failed to load the configuration from '%s'. Error: '%s'",
+                __FUNCTION__, confPath, err->message);
+      g_clear_error(&err);
+      g_key_file_free(cfg);
+      cfg = NULL;
+      goto exit;
+   }
+
+   if (*config != NULL) {
+      g_key_file_free(*config);
+   }
+   *config = cfg;
+   configUpdated = TRUE;
+
+   if (mtime != NULL) {
+      *mtime = confStat.st_mtime;
+   }
+
+   g_debug("%s: Loaded the configuration from %s.\n", __FUNCTION__, confPath);
+
+exit:
+   return configUpdated;
+}
+
+
+/*
+ **************************************************************************
+ * DownloadConfig --                                                 */ /**
+ *
+ * Downloads the tools.conf from the GuestStore.
+ *
+ * @param[in] guestStoreResource Resource path in the GuestStore.
+ * @param[in|opt] localTempPath  File path to be used for temporarily download
+ *                               of the resource from the GuestStore. If this is
+ *                               NULL, then a random file path is used.
+ *
+ * @return GuestStoreClientError.
+ **************************************************************************
+ */
+
+static GuestStoreClientError
+DownloadConfig(const char *guestStoreResource,
+               const char *localTempPath)
+{
+   GuestStoreClientError status = GSLIBERR_GENERIC;
+   const gchar *localConfPath = GlobalConfGetConfPath();
+   gchar *randomLocalTempPath = NULL;
+
+   if (localConfPath == NULL) {
+      g_warning("%s: Failed to get the configuration file path.\n",
+                __FUNCTION__);
+      return status;
+   }
+
+   if (localTempPath == NULL) {
+      int fd = File_MakeSafeTemp(NULL, &randomLocalTempPath);
+      if (fd != -1) {
+         close(fd);
+      } else {
+         g_warning("%s: Failed to get the random temporary file.\n",
+                   __FUNCTION__);
+         return status;
+      }
+      localTempPath = randomLocalTempPath;
+   }
+
+   g_debug("%s: Downloading the configuration to %s\n", __FUNCTION__,
+           localTempPath);
+
+   status = GuestStoreClient_GetContent(guestStoreResource,
+                                        localTempPath,
+                                        NULL, NULL);
+   if (status == GSLIBERR_SUCCESS) {
+      GKeyFile *newGlobalCfg = NULL;
+
+      g_debug("%s: Successfully downloaded the configuration from GuestStore.",
+              __FUNCTION__);
+
+      LoadConfigFile(localTempPath, &newGlobalCfg, NULL);
+
+      if (newGlobalCfg != NULL) {
+         GKeyFile *existingGlobalCfg = NULL;
+
+         LoadConfigFile(localConfPath, &existingGlobalCfg, NULL);
+         if (!VMTools_CompareConfig(existingGlobalCfg, newGlobalCfg)) {
+            /*
+             * Write the config to the filesystem using VMTools_WriteConfig and
+             * the content will be normalized.
+             */
+            VMTools_WriteConfig(localConfPath,
+                                newGlobalCfg,
+                                NULL);
+         }
+
+         if (existingGlobalCfg != NULL) {
+            g_key_file_free(existingGlobalCfg);
+         }
+
+         g_key_file_free(newGlobalCfg);
+      }
+   } else {
+      g_debug("%s: Failed to download the configuration "
+              "from GuestStore. Error: %d",
+              __FUNCTION__, status);
+      /*
+       * If the global configuration is not available in the GuestStore or
+       * VM is not allowed to access it, then delete the local copy of global
+       * configuration downloaded previously.
+       */
+      if (status == GSLIBERR_CONTENT_NOT_FOUND ||
+          status == GSLIBERR_CONTENT_FORBIDDEN) {
+         File_UnlinkIfExists(localConfPath);
+      }
+   }
+
+   File_UnlinkIfExists(localTempPath);
+   g_free(randomLocalTempPath);
+   return status;
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfigThreadStart --                                        */ /**
+ *
+ * Entry function for the thread to download the global configuration from
+ * the GuestStore.
+ *
+ * @param[in] ctx    Application context
+ * @param[in] unused Callback data. Not used.
+ *
+ **************************************************************************
+ */
+
+static void
+GlobalConfigThreadStart(ToolsAppCtx *ctx,
+                        gpointer unused)
+{
+   GlobalConfigThreadState *threadState = &globalConfigThreadState;
+   gboolean waitBeforeDownload = FALSE;
+
+   g_mutex_lock(&threadState->mutex);
+
+   while (!threadState->terminate) {
+      GlobalConfigInfo *configInfo = threadState->configInfo;
+      if (threadState->guestStoreEnabled && configInfo->pollInterval > 0) {
+         gint64 endTime;
+
+         if (waitBeforeDownload) {
+            waitBeforeDownload = FALSE;
+            endTime = g_get_monotonic_time() +
+                     (configInfo->pollInterval * G_TIME_SPAN_SECOND);
+            g_cond_wait_until(&threadState->cond, &threadState->mutex, endTime);
+            continue;
+         }  else if (threadState->useRandomInterval) {
+            threadState->useRandomInterval = FALSE;
+            endTime = g_get_monotonic_time() +
+                      (GenerateRandomInterval() * G_TIME_SPAN_SECOND);
+            g_cond_wait_until(&threadState->cond,
+                              &threadState->mutex, endTime);
+            continue;
+         }
+
+         g_mutex_unlock(&threadState->mutex);
+
+         DownloadConfig(configInfo->guestStoreResource,
+                        configInfo->localTempPath);
+
+         g_mutex_lock(&threadState->mutex);
+
+         waitBeforeDownload = TRUE;
+      } else {
+         if (configInfo->pollInterval == 0) {
+            GlobalConfig_DeleteConfig();
+         }
+         g_cond_wait(&threadState->cond, &threadState->mutex);
+         waitBeforeDownload = FALSE;
+      }
+   }
+
+   g_mutex_unlock(&threadState->mutex);
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfig_Start --                                             */ /**
+ *
+ * Initializes the global config module. If the feature is not enabled in
+ * tools.conf file, the module is not enabled. If this function is called
+ * in the context of the Tools main service, a thread is started in the
+ * background to periodically download the global configuration from the
+ * GuestStore.
+ *
+ * @param[in] ctx                   Application context.
+ *
+ * @return TRUE if the download config module is successfully started
+ *
+ **************************************************************************
+ */
+
+gboolean
+GlobalConfig_Start(ToolsAppCtx *ctx)
+{
+   gboolean ret = FALSE;
+   GKeyFile *cfg;
+
+   ASSERT(ctx != NULL);
+   cfg = ctx->config;
+   ASSERT(cfg != NULL);
+
+   if (!GlobalConfigThreadStateInit(cfg)) {
+      g_warning("%s: Failed to initialize global config module.",
+                  __FUNCTION__);
+      goto exit;
+   }
+
+   if (TOOLS_IS_MAIN_SERVICE(ctx)) {
+      /*
+       * Start the background thread only when this module is started by
+       * 'vmsvc' service.
+       */
+      ret = ToolsCorePool_StartThread(ctx,
+                                      "toolsGlobalConfig",
+                                      GlobalConfigThreadStart,
+                                      GlobalConfigThreadTerminate,
+                                      NULL,
+                                      GlobalConfigThreadStateFree);
+      if (!ret) {
+         g_info("%s: Unable to start the GuestStore download config thread",
+                __FUNCTION__);
+         GlobalConfigThreadStateFree(NULL);
+         goto exit;
+      }
+
+      if (g_signal_lookup(TOOLS_CORE_SIG_GUESTSTORE_STATE,
+                          G_OBJECT_TYPE(ctx->serviceObj)) != 0) {
+         g_signal_connect(ctx->serviceObj,
+                          TOOLS_CORE_SIG_GUESTSTORE_STATE,
+                          G_CALLBACK(GuestStoreStateChanged),
+                          NULL);
+      }
+
+      if (g_signal_lookup(TOOLS_CORE_SIG_RESET,
+                          G_OBJECT_TYPE(ctx->serviceObj)) != 0) {
+         g_signal_connect(ctx->serviceObj,
+                          TOOLS_CORE_SIG_RESET,
+                          G_CALLBACK(VMToolsChannelReset),
+                          NULL);
+      }
+   } else {
+      ret = TRUE;
+   }
+
+   if (g_signal_lookup(TOOLS_CORE_SIG_CONF_RELOAD,
+                       G_OBJECT_TYPE(ctx->serviceObj)) != 0) {
+      g_signal_connect(ctx->serviceObj,
+                       TOOLS_CORE_SIG_CONF_RELOAD,
+                       G_CALLBACK(GlobalConfigToolsConfReload),
+                       NULL);
+   }
+
+exit:
+   return ret;
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfig_LoadConfig --                                        */ /**
+ *
+ * Loads the Global configuration downloaded from the GuestStore. The
+ * modification time of the configuration file is checked and it's loaded only
+ * if it has been updated since the caller specified modification time.
+ *
+ * @param[in|out] config  Configuration dictionary that is loaded with the
+ *                        contents downloaded from the GuestStore. When
+ *                        loading, the old content is destroyed. The caller
+ *                        must initialize this to NULL before the first
+ *                        invocation of this function.
+ * @param[in|out] mtime   Last known modification time of the config file.
+ *                        When the function succeeds, will contain the new
+ *                        modification time read from the file. If NULL (or 0),
+ *                        the configuration dictionary is always loaded.
+ *
+ * @return TRUE Whether a new configuration dictionary was loaded.
+ *
+ **************************************************************************
+ */
+
+gboolean
+GlobalConfig_LoadConfig(GKeyFile **config,
+                        time_t *mtime)
+{
+   static const gchar *confPath = NULL;
+
+   if (confPath == NULL) {
+      confPath = GlobalConfGetConfPath();
+   }
+
+   if (confPath != NULL) {
+      return LoadConfigFile(confPath, config, mtime);
+   } else {
+      return FALSE;
+   }
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfig_GetEnabled --                                        */ /**
+ *
+ * Query the given configuration dictionary and returns the status of
+ * globaconf module.
+ *
+ * @param[in] config  Configuration dictionary that needs to be queried.
+ *
+ * @return TRUE if the globalconf module is enabled. FALSE otherwise.
+ *
+ **************************************************************************
+ */
+
+gboolean
+GlobalConfig_GetEnabled(GKeyFile *config)
+{
+   return VMTools_ConfigGetBoolean(config,
+                                   CONFGROUPNAME_GLOBALCONF,
+                                   CONFNAME_GLOBALCONF_ENABLED,
+                                   GLOBALCONF_DEFAULT_ENABLED);
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfig_SetEnabled --                                        */ /**
+ *
+ * Changes the 'enabled' status of globalconf module in the specified
+ * configuration dictionary.
+ *
+ * @param[in]     enabled Desired state of the globalconf module.
+ * @param[in|out] config  Configuration dictionary that needs to be updated.
+ *
+ **************************************************************************
+ */
+
+void
+GlobalConfig_SetEnabled(gboolean enabled,
+                        GKeyFile *config)
+{
+   if (config != NULL) {
+      g_key_file_set_boolean(config, CONFGROUPNAME_GLOBALCONF,
+                             CONFNAME_GLOBALCONF_ENABLED, enabled);
+   }
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfig_DeleteConfig --                                      */ /**
+ *
+ * Delete the global configuration downloaded from the GuestStore.
+ *
+ * @return TRUE if the global configuration is successfully deleted.
+ *         FALSE if any error happens.
+ *
+ **************************************************************************
+ */
+
+gboolean
+GlobalConfig_DeleteConfig(void) {
+   const gchar *confPath = GlobalConfGetConfPath();
+
+   return confPath != NULL && File_UnlinkIfExists(confPath) == 0;
+}
+
+
+/*
+ **************************************************************************
+ * GlobalConfig_DownloadConfig --                                    */ /**
+ *
+ * Download the global configuration from the GuestStore.
+ *
+ * @param[in] config  Configuration dictionary.
+ *
+ * @return GuestStoreClientError
+ *
+ **************************************************************************
+ */
+
+GuestStoreClientError
+GlobalConfig_DownloadConfig(GKeyFile *config)
+{
+   GuestStoreClientError status = GSLIBERR_GENERIC;
+   char *guestStoreResource;
+
+   if (config == NULL) {
+      g_warning("%s: Invalid arguments specified.\n", __FUNCTION__);
+      return status;
+   }
+
+   guestStoreResource = GLOBALCONF_GET_RESOURCE_PATH(config);
+
+   status = DownloadConfig(guestStoreResource, NULL);
+
+   g_free(guestStoreResource);
+
+   return status;
+}
diff --git a/open-vm-tools/lib/include/globalConfig.h b/open-vm-tools/lib/include/globalConfig.h
new file mode 100644 (file)
index 0000000..cb18d36
--- /dev/null
@@ -0,0 +1,56 @@
+/*********************************************************
+ * Copyright (C) 2020-2021 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
+ *
+ *********************************************************/
+
+#ifndef _GLOBAL_CONFIG_H_
+#define _GLOBAL_CONFIG_H_
+
+#if defined(_WIN32) || (defined(__linux__) && !defined(USERWORLD))
+
+#define GLOBALCONFIG_SUPPORTED 1
+
+#include "vmware/tools/plugin.h"
+#include <time.h>
+#include "guestStoreClient.h"
+
+/**
+ * @file globalConfig.h
+ *
+ * Interface of the module to fetch the tools.conf file from GuestStore.
+ */
+
+gboolean GlobalConfig_Start(ToolsAppCtx *ctx);
+
+gboolean GlobalConfig_LoadConfig(GKeyFile **config,
+                                 time_t *mtime);
+
+gboolean GlobalConfig_GetEnabled(GKeyFile *conf);
+
+void GlobalConfig_SetEnabled(gboolean enabled,
+                             GKeyFile *conf);
+
+gboolean GlobalConfig_DeleteConfig(void);
+
+GuestStoreClientError GlobalConfig_DownloadConfig(GKeyFile *config);
+
+#else
+
+#undef GLOBALCONFIG_SUPPORTED
+
+#endif
+
+#endif /* _GLOBAL_CONFIG_H_ */
index c2ac93a011be49c8f72a60315de526e5070d154d..f33703177e8e7c4bb8c5fc9d900f292372848b4c 100644 (file)
@@ -1,5 +1,5 @@
 ################################################################################
-### Copyright (C) 2009-2020 VMware, Inc.  All rights reserved.
+### Copyright (c) 2009-2021 VMware, Inc.  All rights reserved.
 ###
 ### This program is free software; you can redistribute it and/or modify
 ### it under the terms of version 2 of the GNU General Public License as
@@ -30,6 +30,9 @@ vmtoolsd_LDADD += @VMTOOLS_LIBS@
 vmtoolsd_LDADD += @GMODULE_LIBS@
 vmtoolsd_LDADD += @GOBJECT_LIBS@
 vmtoolsd_LDADD += @GTHREAD_LIBS@
+if LINUX
+   vmtoolsd_LDADD += ../../lib/globalConfig/libGlobalConfig.la
+endif
 
 vmtoolsd_SOURCES =
 vmtoolsd_SOURCES += cmdLine.c
index 23c9afd5e5f57eeaa4f298723e3e1fadbd51548b..e68b908d1c13e6aa2bab0fa60ca902b9d7e4bc63 100644 (file)
 #if defined(_WIN32) || \
    (defined(__linux__) && !defined(USERWORLD))
 #  include "vmware/tools/guestStore.h"
+#  include "globalConfig.h"
+#endif
+
+/*
+ * guestStoreClient library is needed for both Gueststore based tools upgrade
+ * and also for GlobalConfig module.
+ */
+#if defined(_WIN32) || defined(GLOBALCONFIG_SUPPORTED)
+#  include "guestStoreClient.h"
 #endif
 
 #if defined(_WIN32)
 #  include "codeset.h"
-#  include "guestStoreClient.h"
-#  include "globalConfig.h"
 #  include "toolsNotify.h"
 #  include "windowsu.h"
 #else
@@ -129,10 +136,17 @@ ToolsCoreCleanup(ToolsServiceState *state)
    }
 #endif
 
-#if defined(_WIN32)
+/*
+ * guestStoreClient library is needed for both Gueststore based tools upgrade
+ * and also for GlobalConfig module.
+ */
+#if defined(_WIN32) || defined(GLOBALCONFIG_SUPPORTED)
    if (state->mainService && GuestStoreClient_DeInit()) {
       g_info("%s: De-initialized GuestStore client.\n", __FUNCTION__);
    }
+#endif
+
+#if defined(_WIN32)
    if (state->mainService && ToolsNotify_End()) {
       g_info("%s: End Tools notifications.\n", __FUNCTION__);
    }
@@ -436,7 +450,11 @@ ToolsCoreRunLoop(ToolsServiceState *state)
       ToolsCoreReportVersionData(state);
    }
 
-#if defined(_WIN32)
+/*
+ * guestStoreClient library is needed for both Gueststore based tools upgrade
+ * and also for GlobalConfig module.
+ */
+#if defined(_WIN32) || defined(GLOBALCONFIG_SUPPORTED)
    if (state->mainService && GuestStoreClient_Init()) {
       g_info("%s: Initialized GuestStore client.\n", __FUNCTION__);
    }
index d79bad8485cc5d8397cf2287a668f004b40b9307..386b47f10bd05f78ff459762bbf7904da9811230 100644 (file)
@@ -32,7 +32,7 @@
 #include <glib-object.h>
 #include <gmodule.h>
 #include <time.h>
-#if defined(_WIN32)
+#if defined(_WIN32) || (defined(__linux__) && !defined(USERWORLD))
 /* Need this header for GLOBALCONFIG_SUPPORTED definition.*/
 #include "globalConfig.h"
 #endif
index 000d391d0d6d14ec7364ec234bcd81feb1684a6b..ca84c23d623dcc5380b6bfe370e3346419153812 100644 (file)
@@ -1,5 +1,5 @@
 ################################################################################
-### Copyright (c) 2007-2018,2020 VMware, Inc.  All rights reserved.
+### Copyright (c) 2007-2018,2020-2021 VMware, Inc.  All rights reserved.
 ###
 ### This program is free software; you can redistribute it and/or modify
 ### it under the terms of version 2 of the GNU General Public License as
@@ -33,6 +33,9 @@ vmware_toolbox_cmd_SOURCES += toolboxcmd-devices.c
 vmware_toolbox_cmd_SOURCES += toolboxcmd-info.c
 if LINUX
    vmware_toolbox_cmd_SOURCES += toolboxcmd-gueststore.c
+   vmware_toolbox_cmd_SOURCES += toolboxcmd-globalconf.c
+   vmware_toolbox_cmd_LDADD   += ../lib/globalConfig/libGlobalConfig.la
+   vmware_toolbox_cmd_LDADD   += @GOBJECT_LIBS@
 endif
 vmware_toolbox_cmd_SOURCES += toolboxcmd-logging.c
 vmware_toolbox_cmd_SOURCES += toolboxcmd-scripts.c
index d8f6f6be1fcc2bcb62e8cbfa0577792a6c1e0c02..9dc4dab2d4668441495d339032879d4238f246eb 100644 (file)
 #include <string.h>
 #ifdef _WIN32
 #   include "getoptwin32.h"
-#   include "globalConfig.h"
 #else
 #   include <getopt.h>
 #   include <sysexits.h>
 #   include <unistd.h>
 #endif
 
+#if defined(_WIN32) || (defined(__linux__) && !defined(USERWORLD))
+#include "globalConfig.h"
+#endif
+
 #include "vmGuestLib.h"
 
 /*
@@ -157,12 +160,10 @@ DECLARE_COMMAND(Upgrade);
 DECLARE_COMMAND(GuestStore);
 #endif
 
-#if defined(_WIN32)
-DECLARE_COMMAND(GlobalConf)
-#endif
-
 #if defined(GLOBALCONFIG_SUPPORTED)
 
+DECLARE_COMMAND(GlobalConf)
+
 #define TOOLBOXCMD_LOAD_GLOBALCONFIG(conf)                        \
    {                                                              \
       if (GlobalConfig_GetEnabled(conf)) {                        \
diff --git a/open-vm-tools/toolbox/toolboxcmd-globalconf.c b/open-vm-tools/toolbox/toolboxcmd-globalconf.c
new file mode 100644 (file)
index 0000000..48b7be2
--- /dev/null
@@ -0,0 +1,310 @@
+/*********************************************************
+ * Copyright (C) 2020-2021 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
+ *
+ *********************************************************/
+
+/*
+ * toolboxcmd-globalconf.c --
+ *
+ *     Global Configuration operations for toolbox-cmd.
+ */
+
+#include "globalConfig.h"
+
+#if !defined(GLOBALCONFIG_SUPPORTED)
+#   error This file should not be compiled
+#endif
+
+#include "vm_product.h"
+#include "vm_assert.h"
+#include "vm_basic_defs.h"
+#include "toolboxCmdInt.h"
+#include "vmware/tools/i18n.h"
+
+/*
+ * GuestStore client library error messages
+ *
+ * Localization can be done in this way if needed:
+ *
+ * #define GUESTSTORE_LIB_ERR_ITEM(err, msgid, msg) MSGID(msgid) msg,
+ *
+ * call this to get localized error message:
+ *
+ * VMTools_GetString(VMW_TEXT_DOMAIN, guestStoreLibErrMsgs[errCode])
+ */
+#define GUESTSTORE_LIB_ERR_ITEM(err, msgid, msg) msg,
+static const char * const guestStoreLibErrMsgs[] = {
+GUESTSTORE_LIB_ERR_LIST
+};
+#undef GUESTSTORE_LIB_ERR_ITEM
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * GlobalConfRefresh --
+ *
+ *      Trigger a new download of the global configuration from the GuestStore.
+ *
+ * Results:
+ *      GSLIBERR_SUCCESS on success.
+ *      GuestStoreClientError on error.
+ *
+ * Side effects:
+ *      None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static GuestStoreClientError
+GlobalConfRefresh(GKeyFile *confDict)
+{
+   GuestStoreClientError downloadStatus;
+
+   ASSERT(confDict != NULL);
+
+   if (!GuestStoreClient_Init()) {
+      g_critical("GuestStoreClient_Init failed.\n");
+      downloadStatus = GSLIBERR_NOT_INITIALIZED;
+   } else {
+      downloadStatus = GlobalConfig_DownloadConfig(confDict);
+      GuestStoreClient_DeInit();
+   }
+
+   return downloadStatus;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * GlobalConfStatus --
+ *
+ *      Handler for 'status', 'enable', 'disable' commands for globalconf.
+ *      'status' will query and prints the status.
+ *      'enable' will enable the module. tools.conf is updated.
+ *      'disable' will disable the module. tools.conf is updated.
+ *      If the tools 'vmsvc' service is running, then the service
+ *      is stopped, config is updated and the service is started.
+ *
+ * Results:
+ *      EXIT_SUCCESS on success.
+ *      EX_TEMPFAIL, EX_SOFTWARE or EX_USAGE on error.
+ *
+ * Side effects:
+ *      None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+
+static int
+GlobalConfStatus(const char *command)   // IN: command specified by the user.
+{
+   GKeyFile *confDict = NULL;
+   int ret = EXIT_SUCCESS;
+   gboolean currentEnabledState;
+   gboolean desiredEnabledState;
+
+   ASSERT(command != NULL);
+
+   VMTools_LoadConfig(NULL,
+                      G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
+                      &confDict,
+                      NULL);
+
+   if (confDict == NULL) {
+      confDict = g_key_file_new();
+   }
+
+   currentEnabledState = GlobalConfig_GetEnabled(confDict);
+
+   if (toolbox_strcmp(command, "status") == 0) {
+      ToolsCmd_Print(SU_(globalconf.status,
+                         "The status of globalconf module is '%s'\n"),
+                     currentEnabledState ?
+                        SU_(option.enabled,
+                            "Enabled") :
+                        SU_(option.disabled,
+                            "Disabled"));
+      desiredEnabledState = currentEnabledState;
+   } else if (toolbox_strcmp(command, "enable") == 0) {
+      desiredEnabledState = TRUE;
+   } else if (toolbox_strcmp(command, "disable") == 0) {
+      desiredEnabledState = FALSE;
+   } else {
+      desiredEnabledState = currentEnabledState;
+      ret = EX_USAGE;
+   }
+
+   if (currentEnabledState != desiredEnabledState) {
+      GError *err = NULL;
+      GlobalConfig_SetEnabled(desiredEnabledState, confDict);
+
+      ToolsCmd_Print(SU_(globalconf.update_config,
+                         "%s: Updating the Configuration.\n"),
+                     command);
+      if (!VMTools_WriteConfig(NULL, confDict, &err)) {
+         g_warning("%s: Error writing config: %s.\n",
+                   __FUNCTION__, err ? err->message : "");
+         g_clear_error(&err);
+         ret = EX_TEMPFAIL;
+         goto error;
+      }
+
+      if (!desiredEnabledState) {
+         if (GlobalConfig_DeleteConfig()) {
+            g_debug("%s: Deleted the global configuration.\n", __FUNCTION__);
+         } else {
+            g_warning("%s: Failed to delete the global configuration.\n",
+                      __FUNCTION__);
+         }
+      }
+   }
+
+error:
+   g_key_file_free(confDict);
+
+   return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * GlobalConf_Command --
+ *
+ *      Parse and handle globalconf command.
+ *
+ * Results:
+ *      0 on success.
+ *
+ *      Error code from GuestStore client library or
+ *      general process error exit code.
+ *
+ * Side effects:
+ *      None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+int
+GlobalConf_Command(char **argv,      // IN: Command line arguments
+                   int argc,         // IN: Length of the command line arguments
+                   gboolean quiet)   // IN
+{
+   int ret;
+
+   if (toolbox_strcmp(argv[optind], "refresh") == 0) {
+      GuestStoreClientError downloadStatus;
+      GKeyFile *confDict = NULL;
+
+      VMTools_LoadConfig(NULL,
+                        G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
+                        &confDict,
+                        NULL);
+
+      if (confDict == NULL) {
+         confDict = g_key_file_new();
+      }
+
+      ret = EX_SOFTWARE;
+
+      if (!GlobalConfig_GetEnabled(confDict)) {
+         ToolsCmd_PrintErr(SU_(globalconf.refresh.failed,
+                               "'%s' failed, since globalconf module is"
+                               " disabled.\n"),
+                           argv[optind]);
+         g_key_file_free(confDict);
+         return ret;
+      }
+
+      downloadStatus = GlobalConfRefresh(confDict);
+      g_key_file_free(confDict);
+
+      if (downloadStatus == GSLIBERR_SUCCESS) {
+         ToolsCmd_Print(SU_(result.succeeded,
+                            "'%s' succeeded.\n"),
+                        argv[optind]);
+         ret = EXIT_SUCCESS;
+      } else if (downloadStatus < GUESTSTORE_LIB_ERR_MAX) {
+         ToolsCmd_PrintErr(SU_(gueststore.error.client_lib,
+                               "'%s' failed, GuestStore client library "
+                               "error: %s.\n"), argv[optind],
+                           guestStoreLibErrMsgs[downloadStatus]);
+      } else {
+         ToolsCmd_PrintErr(SU_(result.error.failed,
+                               "'%s' failed, check %s log for "
+                               "more information.\n"),
+                           argv[optind], argv[0]);
+      }
+   } else if (toolbox_strcmp(argv[optind], "status") == 0 ||
+              toolbox_strcmp(argv[optind], "enable") == 0 ||
+              toolbox_strcmp(argv[optind], "disable") == 0) {
+      ret = GlobalConfStatus(argv[optind]);
+      if (ret != EXIT_SUCCESS) {
+         ToolsCmd_PrintErr(SU_(result.error.failed,
+                               "'%s' failed, check %s log for "
+                               "more information.\n"),
+                           argv[optind], argv[0]);
+      }
+   } else {
+      ToolsCmd_UnknownEntityError(argv[0],
+                                  SU_(arg.subcommand, "subcommand"),
+                                  argv[optind]);
+      ret = EX_USAGE;
+   }
+
+   return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * GlobalConf_Help --
+ *
+ *      Prints the help for the globalconf command.
+ *
+ * Results:
+ *      None
+ *
+ * Side effects:
+ *      None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+GlobalConf_Help(const char *progName, // IN: Name of the program from argv[0]
+                const char *cmd)      // IN
+{
+   g_print(SU_(help.globalconf,
+               "%s: Manage global configuration downloads "
+               "from the GuestStore\n"
+               "Usage: %s %s <subcommand>\n\n"
+               "ESX guests only subcommands:\n"
+               "   enable: "
+               "Enable the global configuration module\n"
+               "   disable: "
+               "Disable the global configuration module\n"
+               "   refresh: "
+               "Trigger a new download of the global configuration "
+               "from the GuestStore\n"
+               "   status: "
+               "Print the status of the global configuration module\n"),
+           cmd, progName, cmd);
+}