From: John Wolfe Date: Tue, 22 Dec 2020 20:22:04 +0000 (-0800) Subject: Changes to common source files not immediately applicable to open-vm-tools. X-Git-Tag: stable-11.3.0~194 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2b6aac74cdc34ce88a7ca04aee3bfa4df888f391;p=thirdparty%2Fopen-vm-tools.git Changes to common source files not immediately applicable to open-vm-tools. Staging source files and changes for a future feature. --- diff --git a/open-vm-tools/Makefile.am b/open-vm-tools/Makefile.am index a58c65db9..9f75ce305 100644 --- a/open-vm-tools/Makefile.am +++ b/open-vm-tools/Makefile.am @@ -1,5 +1,5 @@ ################################################################################ -### Copyright (C) 2007-2020 VMware, Inc. All rights reserved. +### Copyright (c) 2007-2020 VMware, Inc. All rights reserved. ### ### Top-level Makefile for building the VMware OSS Tools. ### @@ -24,6 +24,9 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = SUBDIRS += lib +if LINUX + SUBDIRS += libguestStoreClient +endif SUBDIRS += libvmtools SUBDIRS += libhgfs SUBDIRS += hgfsclient diff --git a/open-vm-tools/configure.ac b/open-vm-tools/configure.ac index 4f2e7bcf9..6e1baf798 100644 --- a/open-vm-tools/configure.ac +++ b/open-vm-tools/configure.ac @@ -1592,6 +1592,7 @@ AC_CONFIG_FILES([ \ lib/glibUtils/Makefile \ lib/guestApp/Makefile \ lib/guestRpc/Makefile \ + lib/guestStoreClientHelper/Makefile \ lib/hgfs/Makefile \ lib/hgfsBd/Makefile \ lib/hgfsHelper/Makefile \ @@ -1632,6 +1633,7 @@ AC_CONFIG_FILES([ \ services/plugins/desktopEvents/Makefile \ services/plugins/dndcp/Makefile \ services/plugins/guestInfo/Makefile \ + services/plugins/guestStore/Makefile \ services/plugins/hgfsServer/Makefile \ services/plugins/powerOps/Makefile \ services/plugins/resolutionSet/Makefile \ @@ -1658,6 +1660,7 @@ AC_CONFIG_FILES([ \ libDeployPkg/Makefile \ libDeployPkg/libDeployPkg.pc \ libhgfs/Makefile \ + libguestStoreClient/Makefile \ libvmtools/Makefile \ xferlogs/Makefile \ modules/Makefile \ diff --git a/open-vm-tools/lib/Makefile.am b/open-vm-tools/lib/Makefile.am index f9d84805a..dee38ed79 100644 --- a/open-vm-tools/lib/Makefile.am +++ b/open-vm-tools/lib/Makefile.am @@ -1,5 +1,5 @@ ################################################################################ -### Copyright (C) 2007-2016 VMware, Inc. All rights reserved. +### Copyright (c) 2007-2016,2020 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 @@ -37,6 +37,9 @@ SUBDIRS += file SUBDIRS += foundryMsg SUBDIRS += glibUtils SUBDIRS += guestApp +if LINUX + SUBDIRS += guestStoreClientHelper +endif SUBDIRS += hgfs SUBDIRS += hgfsBd SUBDIRS += hgfsHelper diff --git a/open-vm-tools/lib/guestStoreClientHelper/Makefile.am b/open-vm-tools/lib/guestStoreClientHelper/Makefile.am new file mode 100644 index 000000000..7f19b0e60 --- /dev/null +++ b/open-vm-tools/lib/guestStoreClientHelper/Makefile.am @@ -0,0 +1,24 @@ +################################################################################ +### Copyright (c) 2020 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 = libGuestStoreClientHelper.la + +libGuestStoreClientHelper_la_SOURCES = +libGuestStoreClientHelper_la_SOURCES += guestStoreClient.c + +libGuestStoreClientHelper_la_CPPFLAGS = +libGuestStoreClientHelper_la_CPPFLAGS += @GLIB2_CPPFLAGS@ \ No newline at end of file diff --git a/open-vm-tools/lib/guestStoreClientHelper/guestStoreClient.c b/open-vm-tools/lib/guestStoreClientHelper/guestStoreClient.c new file mode 100644 index 000000000..38139bb3f --- /dev/null +++ b/open-vm-tools/lib/guestStoreClientHelper/guestStoreClient.c @@ -0,0 +1,379 @@ +/********************************************************* + * Copyright (C) 2020 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. + * + *********************************************************/ + +/* + * guestStoreClient.c -- + * + * Wrapper functions to load/unload and get content from GuestStore. + */ + +#define G_LOG_DOMAIN "guestStoreClient" + +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#endif + +#include "vm_assert.h" +#include "vm_basic_defs.h" +#include "guestStoreClient.h" + + +#if defined(_WIN32) +#define GUESTSTORE_CLIENTLIB_DLL WSTR("guestStoreClient.dll") +#else +#define GUESTSTORE_CLIENTLIB_DLL "libguestStoreClient.so" +#endif + +/* + * Track whether the library has been initialized or not. + */ +static gboolean gsClientInit = FALSE; + +/* + * Module handle of GuestStore client library. + */ +#if defined(_WIN32) +static HMODULE gsClientLibModule = NULL; +#else +static void *gsClientLibModule = NULL; +#endif + + +/* + * Function pointer types for GuestStore client library exports. + */ +typedef GuestStoreLibError (*GuestStoreLibInit)(void); +typedef GuestStoreLibError (*GuestStoreLibDeInit)(void); +typedef GuestStoreLibError (*GuestStoreLibGetContent)(const char* contentPath, + const char* outputPath, + GuestStore_Logger logger, + GuestStore_Panic panic, + GuestStore_GetContentCallback getContentCb, + void* clientData); + +/* + * Function pointer definitions for GuestStore client library exports. + */ + +static GuestStoreLibInit GuestStoreLib_Init; +static GuestStoreLibDeInit GuestStoreLib_DeInit; +static GuestStoreLibGetContent GuestStoreLib_GetContent; + +/* + * Macro to get the export function address from GuestStore client library. + */ +#if defined(_WIN32) + +#define GET_GUESTSTORELIB_FUNC_ADDR(funcname) \ + do { \ + (FARPROC) XXCONC(GuestStoreLib_,funcname) = GetProcAddress(gsClientLibModule, \ + "GuestStore_" #funcname); \ + if (XXCONC(GuestStoreLib_,funcname) == NULL) { \ + error = GetLastError(); \ + g_critical("GetProcAddress failed for \'%s\': error=%u.\n", \ + "GuestStore_" #funcname, error); \ + return FALSE; \ + } \ + } while (0) + +#else + +#define GET_GUESTSTORELIB_FUNC_ADDR(funcname) \ + do { \ + dlerror(); \ + *(void **)(&XXCONC(GuestStoreLib_,funcname)) = dlsym(gsClientLibModule, \ + "GuestStore_" #funcname); \ + if ((dlErrStr = dlerror()) != NULL) { \ + g_critical("dlsym failed for \'%s\': %s\n", \ + "GuestStore_" #funcname, dlErrStr); \ + return FALSE; \ + } \ + } while (0) + +#endif + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreGetLibExportFunctions -- + * + * Get the export function addresses from GuestStore client library. + * + * Results: + * TRUE on success. + * FALSE on failure. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +GuestStoreGetLibExportFunctions(void) +{ + +#if defined(_WIN32) + DWORD error; + + g_debug("Entering %s.\n", __FUNCTION__); + + gsClientLibModule = LoadLibraryW(GUESTSTORE_CLIENTLIB_DLL); + if (gsClientLibModule == NULL) { + error = GetLastError(); + g_critical("%s: LoadLibrary failed: error=%u.\n", __FUNCTION__, error); + return FALSE; + } +#else + char const *dlErrStr; + + g_debug("Entering %s.\n", __FUNCTION__); + + gsClientLibModule = dlopen(GUESTSTORE_CLIENTLIB_DLL, RTLD_NOW); + if (gsClientLibModule == NULL) { + dlErrStr = dlerror(); + g_critical("%s: dlopen failed: %s\n", __FUNCTION__, dlErrStr); + return FALSE; + } +#endif + + GET_GUESTSTORELIB_FUNC_ADDR(Init); // For GuestStore_Init + GET_GUESTSTORELIB_FUNC_ADDR(GetContent); // For GuestStore_GetContent + GET_GUESTSTORELIB_FUNC_ADDR(DeInit); // For GuestStore_DeInit + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreClientLogger -- + * + * Log messages from GuestStore client library. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +GuestStoreClientLogger(GuestStoreLibLogLevel level, // IN + const char *message, // IN + void *clientData) // IN +{ + switch (level) { + case GSLIBLOGLEVEL_ERROR: + g_critical("%s: Error: %s\n", __FUNCTION__, message); + break; + + case GSLIBLOGLEVEL_WARNING: + g_warning("%s: Warning: %s\n", __FUNCTION__, message); + break; + + case GSLIBLOGLEVEL_INFO: + g_info("%s: Info: %s\n", __FUNCTION__, message); + break; + + case GSLIBLOGLEVEL_DEBUG: + g_debug("%s: Debug: %s\n", __FUNCTION__, message); + break; + + default: + ASSERT(0); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreClientPanic -- + * + * Panic handler for GuestStore client library. + * + * Results: + * None + * + * Side effects: + * Process crashes? + * + *----------------------------------------------------------------------------- + */ + +static void +GuestStoreClientPanic(const char *message, // IN + void *clientData) // IN +{ + g_critical("%s: %s\n", __FUNCTION__, message); +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreClient_Init -- + * + * Initialize the guest store client library access + * + * Results: + * TRUE on success., FALSE otherwise + * + * Error code from GuestStore client library or + * general process error exit code. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +gboolean +GuestStoreClient_Init(void) +{ + GuestStoreLibError libErr; + + g_debug("Entering %s.\n", __FUNCTION__); + + if (!GuestStoreGetLibExportFunctions()) { + goto exit; + } + + libErr = GuestStoreLib_Init(); + if (libErr != GSLIBERR_SUCCESS) { + g_critical("%s: GuestStoreLib_Init failed: error=%d.\n", + __FUNCTION__, libErr); + goto exit; + } + gsClientInit = TRUE; + +exit: + g_debug("%s: Exit -> %d.\n", __FUNCTION__, gsClientInit); + return gsClientInit; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreClient_DeInit -- + * + * Deinitialize the guest store client library access + * + * Results: + * TRUE on success, FALSE otherwise + * + * Error code from GuestStore client library or + * general process error exit code. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +gboolean +GuestStoreClient_DeInit(void) +{ + GuestStoreLibError libErr; + + g_debug("Entering %s.\n", __FUNCTION__); + + if (!gsClientInit) { + return gsClientInit; + } + + libErr = GuestStoreLib_DeInit(); + if (libErr != GSLIBERR_SUCCESS) { + g_critical("%s: GuestStore_DeInit failed: error=%d.\n", + __FUNCTION__, libErr); + } + + if (gsClientLibModule != NULL) { +#if defined(_WIN32) + if (!FreeLibrary(gsClientLibModule)) { + g_critical("%s: FreeLibrary failed: error=%d.\n", + __FUNCTION__, GetLastError()); + } +#else + if (dlclose(gsClientLibModule) != 0) { + g_critical("%s: dlclose failed with error: %s\n", + __FUNCTION__, dlerror()); + } +#endif + + gsClientLibModule = NULL; + } + + g_debug("Exiting %s.\n", __FUNCTION__); + + gsClientInit = FALSE; + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreClient_GetContent -- + * + * Handle and parse gueststore command. + * + * Results: + * Error code from GuestStore client library or + * general process error exit code. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +GuestStoreClientError +GuestStoreClient_GetContent(const char *contentPath, // IN: content file path + const char *outputPath, // IN: output file path + GuestStoreClient_GetContentCb getContentCb, // IN: OPTIONAL callback + void *clientCbData) // IN: OPTIONAL callback data +{ + g_debug("Entering %s.\n", __FUNCTION__); + + if (!gsClientInit) { + return GSLIBERR_NOT_INITIALIZED; + } + + return GuestStoreLib_GetContent(contentPath, outputPath, + GuestStoreClientLogger, + GuestStoreClientPanic, + getContentCb, clientCbData); +} \ No newline at end of file diff --git a/open-vm-tools/lib/include/guestStoreClient.h b/open-vm-tools/lib/include/guestStoreClient.h new file mode 100644 index 000000000..53ac717d2 --- /dev/null +++ b/open-vm-tools/lib/include/guestStoreClient.h @@ -0,0 +1,50 @@ +/********************************************************* + * Copyright (C) 2020 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. + * + *********************************************************/ + +/* + * guestStoreClient.h -- + * + * Wrapper functions to load the GuestStore libraries. + */ + +#ifndef _GUEST_STORE_CLIENT_H_ +#define _GUEST_STORE_CLIENT_H_ + +#include "vmware/tools/guestStoreClientLib.h" + +typedef GuestStoreLibError GuestStoreClientError; + +/* + * Caller provided callback to get total content size in bytes and so far + * received bytes. Return FALSE to cancel content download. + */ +typedef GuestStore_GetContentCallback GuestStoreClient_GetContentCb; + +gboolean +GuestStoreClient_Init(void); + +gboolean +GuestStoreClient_DeInit(void); + +GuestStoreClientError +GuestStoreClient_GetContent(const char *contentPath, + const char *outputPath, + GuestStoreClient_GetContentCb getContentCb, + void *clientCbData); + +#endif /* _GUEST_STORE_CLIENT_H_ */ \ No newline at end of file diff --git a/open-vm-tools/lib/include/guestStoreConst.h b/open-vm-tools/lib/include/guestStoreConst.h new file mode 100644 index 000000000..2729356f0 --- /dev/null +++ b/open-vm-tools/lib/include/guestStoreConst.h @@ -0,0 +1,81 @@ +/********************************************************* + * Copyright (C) 2019-2020 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. + * + *********************************************************/ + +/* + * guestStoreConst.h -- + * + * GuestStore common constant definitions. + */ + +#ifndef _GUESTSTORE_CONST_H_ +#define _GUESTSTORE_CONST_H_ + +/* + * GuestStore maximum content path length, set it + * to be less than USERLINUX_PATH_MAX. + */ +#define GUESTSTORE_CONTENT_PATH_MAX 1024 + +/* + * Buffer size to handle GuestStore content request. The request is + * a content path encoded in HTTP protocol or DataMap. + */ +#define GUESTSTORE_REQUEST_BUFFER_SIZE (1024 * 4) + +/* + * Buffer size to receive and forward GuestStore content bytes, set to + * the maximum size of an IP packet. + */ +#define GUESTSTORE_RESPONSE_BUFFER_SIZE (1024 * 64) + +/* + * GuestStore vmx to guest connection pending timeout value. + */ +#define GUESTSTORE_VMX_TO_GUEST_CONN_TIMEOUT 5 // seconds + +/* + * GuestStore default connection inactivity timeout value. This value shall + * be greater than gstored timeout value which is currently 60 seconds. + */ +#define GUESTSTORE_DEFAULT_CONN_TIMEOUT 900 // seconds + + +/* + * NOTE: changing the following IDs may break data map encoding compatibility. + */ + +/* Tools to VMX field IDs */ +enum { + GUESTSTORE_REQ_FLD_CMD = 1, + GUESTSTORE_REQ_FLD_PATH = 2, + GUESTSTORE_REQ_FLD_MAX +}; + +/* Command Types */ +enum { + GUESTSTORE_REQ_CMD_GET = 1, + GUESTSTORE_REQ_CMD_CLOSE = 2, +}; + +/* VMX to tools field IDs */ +enum { + GUESTSTORE_RES_FLD_ERROR_CODE = 1, + GUESTSTORE_RES_FLD_CONTENT_SIZE = 2, +}; + +#endif /* _GUESTSTORE_CONST_H_ */ diff --git a/open-vm-tools/lib/include/guestStoreDefs.h b/open-vm-tools/lib/include/guestStoreDefs.h new file mode 100644 index 000000000..1e297fb4e --- /dev/null +++ b/open-vm-tools/lib/include/guestStoreDefs.h @@ -0,0 +1,79 @@ +/********************************************************* + * Copyright (C) 2019 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. + * + *********************************************************/ + +/* + * guestStoreDefs.h -- + * Common definitions for VMware Tools guestStore plugin and client library. + */ + +#ifndef __GUESTSTOREDEFS_H__ +#define __GUESTSTOREDEFS_H__ + +#include "vm_basic_defs.h" + +/* + * GuestStore client connection definitions. + */ +#ifdef _WIN32 +#define GUESTSTORE_LOOPBACK_PORT_MIN 7332 +#define GUESTSTORE_LOOPBACK_PORT_MAX 7342 +#else +#define GUESTSTORE_PIPE_DIR "/var/run/vmware" +#define GUESTSTORE_PIPE_NAME GUESTSTORE_PIPE_DIR "/guestStorePipe" +#endif + + +/* + * HTTP definitions. + */ +#define HTTP_VER "HTTP/1.1" + +#define HTTP_LINE_END "\r\n" + +#define HTTP_HEADER_END HTTP_LINE_END HTTP_LINE_END +#define HTTP_HEADER_END_LEN (sizeof HTTP_HEADER_END - 1) + +#define HTTP_REQ_METHOD_GET "GET" + +#define HTTP_STATUS_CODE_OK 200 +#define HTTP_STATUS_CODE_FORBIDDEN 403 +#define HTTP_STATUS_CODE_NOT_FOUND 404 + +#define HTTP_RES_OK_LINE HTTP_VER " " \ + XSTR(HTTP_STATUS_CODE_OK) " OK" HTTP_LINE_END +#define HTTP_RES_FORBIDDEN_LINE HTTP_VER " " \ + XSTR(HTTP_STATUS_CODE_FORBIDDEN) " Forbidden" HTTP_LINE_END +#define HTTP_RES_NOT_FOUND_LINE HTTP_VER " " \ + XSTR(HTTP_STATUS_CODE_NOT_FOUND) " Not Found" HTTP_LINE_END + +#define CONTENT_LENGTH_HEADER "Content-Length: " +#define CONTENT_LENGTH_HEADER_LEN ((int)(sizeof(CONTENT_LENGTH_HEADER) - 1)) + +#define HTTP_RES_COMMON_HEADERS "Date: %s" HTTP_LINE_END \ + "Server: VMGuestStore" HTTP_LINE_END \ + "Accept-Ranges: bytes" HTTP_LINE_END \ + CONTENT_LENGTH_HEADER "%" FMT64 "d" HTTP_LINE_END \ + "Content-Type: application/octet-stream" HTTP_LINE_END \ + "Connection: close" HTTP_LINE_END \ + HTTP_LINE_END + +#define HTTP_RES_OK HTTP_RES_OK_LINE HTTP_RES_COMMON_HEADERS +#define HTTP_RES_FORBIDDEN HTTP_RES_FORBIDDEN_LINE HTTP_RES_COMMON_HEADERS +#define HTTP_RES_NOT_FOUND HTTP_RES_NOT_FOUND_LINE HTTP_RES_COMMON_HEADERS + +#endif /* __GUESTSTOREDEFS_H__ */ diff --git a/open-vm-tools/lib/include/vmware/tools/guestStore.h b/open-vm-tools/lib/include/vmware/tools/guestStore.h new file mode 100644 index 000000000..aea06c567 --- /dev/null +++ b/open-vm-tools/lib/include/vmware/tools/guestStore.h @@ -0,0 +1,89 @@ +/********************************************************* + * Copyright (C) 2020 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 _GUESTSTORE_H_ +#define _GUESTSTORE_H_ + +/** + * @file guestStore.h + * + * Public interface for the GuestStore plugin. + * + * @addtogroup vmtools_plugins + * @{ + */ + +#include +#include "vmware/tools/plugin.h" + +/** + * Signal sent when GuestStore is enabled or disabled. + * + * @param[in] src The source object. + * @param[in] enabled TRUE if VMX GuestStore access is enabled, FALSE otherwise. + * @param[in] data Client data. + */ +#define TOOLS_CORE_SIG_GUESTSTORE_STATE "tcs_gueststore_state" + +/* + * Property name of the guestStore plugin in the tools application context + * service object. + */ +#define TOOLS_PLUGIN_SVC_PROP_GUESTSTORE "tps_prop_gueststore" + +/** + * @brief Type of the public interface of the guestStore plugin. + * + * This struct is published in the tools application context service object's + * TOOLS_PLUGIN_SVC_PROP_GUESTSTORE property. + */ +typedef struct ToolsPluginSvcGuestStore { + void (*shutdown)(void); +} ToolsPluginSvcGuestStore; + + +/* + ****************************************************************************** + * ToolsPluginSvcGuestStore_Shutdown -- */ /** + * + * @brief Shuts down guestStore plugin. + * + * To avoid possible deadlock at vmtoolsd shutdown time, guestStore plugin + * needs to be shut down before tools core thread pool. This function provides + * a special way to shut down guestStore plugin other than regular in-plugin + * TOOLS_CORE_SIG_SHUTDOWN signal handler. + * + * @param[in] ctx The app context + * + ****************************************************************************** + */ + +static inline void +ToolsPluginSvcGuestStore_Shutdown(ToolsAppCtx *ctx) // IN +{ + ToolsPluginSvcGuestStore *svcGuestStore = NULL; + g_object_get(ctx->serviceObj, TOOLS_PLUGIN_SVC_PROP_GUESTSTORE, + &svcGuestStore, NULL); + if (svcGuestStore != NULL && svcGuestStore->shutdown != NULL) { + svcGuestStore->shutdown(); + } +} + +/** @} */ + +#endif /* _GUESTSTORE_H_ */ diff --git a/open-vm-tools/lib/include/vmware/tools/guestStoreClientLib.h b/open-vm-tools/lib/include/vmware/tools/guestStoreClientLib.h new file mode 100644 index 000000000..2f147c260 --- /dev/null +++ b/open-vm-tools/lib/include/vmware/tools/guestStoreClientLib.h @@ -0,0 +1,168 @@ +/********************************************************* + * Copyright (C) 2019 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. + * + *********************************************************/ + +/* + * guestStoreClientLib.h -- + * Definitions for VMware Tools guestStore client library. + */ + +#ifndef __GUESTSTORECLIENTLIB_H__ +#define __GUESTSTORECLIENTLIB_H__ + +#include "vm_basic_types.h" + +#define GUESTSTORE_LIB_ERR_LIST \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_SUCCESS = 0, \ + gsliberr.success, \ + "Success") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_GENERIC, \ + gsliberr.generic, \ + "Generic error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_TLS, \ + gsliberr.tls, \ + "TLS error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_NOT_INITIALIZED, \ + gsliberr.not.initialized, \ + "Not initialized") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_INVALID_PARAMETER, \ + gsliberr.invalid.parameter, \ + "Invalid parameter") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_NOT_ENOUGH_MEMORY, \ + gsliberr.not.enough.memory, \ + "Not enough memory") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CREATE_OUTPUT_FILE, \ + gsliberr.create.output.file, \ + "Create output file error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_WRITE_OUTPUT_FILE, \ + gsliberr.write.output.file, \ + "Write output file error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CONNECT_GENERIC, \ + gsliberr.connect.generic, \ + "Connect generic error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CONNECT_SERVICE_NOT_RUNNING, \ + gsliberr.connect.service.not.running, \ + "Connect service not running") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CONNECT_PERMISSION_DENIED, \ + gsliberr.connect.permission.denied, \ + "Connect permission denied") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CONNECT_SECURITY_VIOLATION, \ + gsliberr.connect.security.violation, \ + "Connect security violation") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CONNECT_PEER_RESET, \ + gsliberr.connect.peer.reset, \ + "Connect peer reset") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_SEND, \ + gsliberr.send, \ + "Send error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_RECV, \ + gsliberr.recv, \ + "Receive error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CONTENT_FORBIDDEN, \ + gsliberr.content.forbidden, \ + "Content forbidden") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CONTENT_NOT_FOUND, \ + gsliberr.content.not.found, \ + "Content not found") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_SERVER, \ + gsliberr.server, \ + "Server error") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CANCELLED, \ + gsliberr.cancelled, \ + "Cancelled") \ + GUESTSTORE_LIB_ERR_ITEM(GSLIBERR_CHECKSUM, \ + gsliberr.checksum, \ + "Checksum error") + +/* + * Error codes + */ +#define GUESTSTORE_LIB_ERR_ITEM(a, b, c) a, +typedef enum { +GUESTSTORE_LIB_ERR_LIST +GUESTSTORE_LIB_ERR_MAX +} GuestStoreLibError; +#undef GUESTSTORE_LIB_ERR_ITEM + +/* + * Log levels + */ +typedef enum { + GSLIBLOGLEVEL_ERROR = 1, + GSLIBLOGLEVEL_WARNING, + GSLIBLOGLEVEL_INFO, + GSLIBLOGLEVEL_DEBUG, +} GuestStoreLibLogLevel; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Caller provided function to receive log messages from GuestStore client + * library. Caller can log the messages to its own logging facilities. + */ +typedef void (*GuestStore_Logger) (GuestStoreLibLogLevel level, + const char *message, + void *clientData); + +/* + * Caller provided Panic function in non-recoverable error situations. + * This function shall exit the library host process. + */ +typedef void (*GuestStore_Panic) (const char *message, + void *clientData); + +/* + * Caller provided callback to get total content size in bytes and so far + * received bytes. Return FALSE to cancel content download. + */ +typedef Bool (*GuestStore_GetContentCallback) (int64 contentSize, + int64 contentBytesReceived, + void *clientData); + +/* + * GuestStore client library Init entry point function. + */ +GuestStoreLibError +GuestStore_Init(void); + +/* + * GuestStore client library GetContent entry point function. + */ +GuestStoreLibError +GuestStore_GetContent( + const char *contentPath, // IN + const char *outputPath, // IN + GuestStore_Logger logger, // IN, OPTIONAL + GuestStore_Panic panic, // IN, OPTIONAL + GuestStore_GetContentCallback getContentCb, // IN, OPTIONAL + void *clientData); // IN, OPTIONAL + +/* + * GuestStore client library DeInit entry point function. + * Call of GuestStore_DeInit should match succeeded GuestStore_Init call. + */ +GuestStoreLibError +GuestStore_DeInit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __GUESTSTORECLIENTLIB_H__ */ diff --git a/open-vm-tools/libguestStoreClient/COPYING b/open-vm-tools/libguestStoreClient/COPYING new file mode 100644 index 000000000..09f465ab7 --- /dev/null +++ b/open-vm-tools/libguestStoreClient/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library 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; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/open-vm-tools/libguestStoreClient/Makefile.am b/open-vm-tools/libguestStoreClient/Makefile.am new file mode 100644 index 000000000..adf69309a --- /dev/null +++ b/open-vm-tools/libguestStoreClient/Makefile.am @@ -0,0 +1,36 @@ +################################################################################ +### Copyright (c) 2020 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 +################################################################################ + +lib_LTLIBRARIES = libguestStoreClient.la + +libguestStoreClient_la_LIBADD = +libguestStoreClient_la_LIBADD += ../lib/err/libErr.la +libguestStoreClient_la_LIBADD += ../lib/string/libString.la +libguestStoreClient_la_LIBADD += ../lib/unicode/libUnicode.la +libguestStoreClient_la_LIBADD += ../lib/misc/libMisc.la +libguestStoreClient_la_LIBADD += -lpthread +libguestStoreClient_la_LIBADD += -ldl + +libguestStoreClient_la_SOURCES = +libguestStoreClient_la_SOURCES += guestStoreClientLib.c + +libguestStoreClient_la_LDFLAGS = +# We require GCC, so we're fine passing compiler-specific flags. +libguestStoreClient_la_LDFLAGS += -Wl,-z,defs +# Needed for OS's that don't link shared libraries against libc by +#default, e.g. FreeBSD +libguestStoreClient_la_LDFLAGS += -Wl,-lc diff --git a/open-vm-tools/libguestStoreClient/guestStoreClientLib.c b/open-vm-tools/libguestStoreClient/guestStoreClientLib.c new file mode 100644 index 000000000..4854f6229 --- /dev/null +++ b/open-vm-tools/libguestStoreClient/guestStoreClientLib.c @@ -0,0 +1,1163 @@ +/********************************************************* + * Copyright (C) 2019-2020 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 guestStoreClientLib.c + * + * GuestStore client library, connecting to vmtoolsd GuestStore plugin. + * + */ + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +/* + * #define TLS_OUT_OF_INDEXES ((DWORD)0xFFFFFFFF) + */ +#define TLS_INDEX_TYPE DWORD +#else +#define _GNU_SOURCE // For struct ucred +#define __USE_GNU // For struct ucred (glibc 2.17) +#include +#undef __USE_GNU +#include +#include +#include +#include +#include +#include +#include +#include +/* + * typedef unsigned int pthread_key_t; + * #define PTHREAD_KEYS_MAX 1024 + */ +#define TLS_INDEX_TYPE pthread_key_t +#define TLS_OUT_OF_INDEXES ((pthread_key_t)-1) +#endif + +#include "guestStoreClientLibInt.h" + +#include "vm_version.h" +#include "embed_version.h" +#include "gueststoreclientlib_version.h" +VM_EMBED_VERSION(GUESTSTORECLIENTLIB_VERSION_STRING); + +#define GSLIBLOG_TAG "[guestStoreClientLib] " +#define GSLIBLOG_TAG_LEN (sizeof(GSLIBLOG_TAG) - 1) + + +/* + * Library Init/DeInit reference count. + */ +static Atomic_uint32 initLibCount = { 0 }; + +/* + * Thread local storage index for CallCtx pointer. + */ +static TLS_INDEX_TYPE callCtxTlsIndex = TLS_OUT_OF_INDEXES; + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreAllocTls -- + * + * Allocate a thread local storage index for CallCtx pointer. + * + * Results: + * GSLIBERR_SUCCESS or GSLIBERR_TLS. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreAllocTls(void) +{ + ASSERT(callCtxTlsIndex == TLS_OUT_OF_INDEXES); + +#ifdef _WIN32 + callCtxTlsIndex = TlsAlloc(); +#else + if (pthread_key_create(&callCtxTlsIndex, NULL) != 0) { + callCtxTlsIndex = TLS_OUT_OF_INDEXES; + } +#endif + + return (callCtxTlsIndex == TLS_OUT_OF_INDEXES) ? GSLIBERR_TLS : + GSLIBERR_SUCCESS; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreFreeTls -- + * + * Free the allocated thread local storage index for CallCtx pointer. + * + * Results: + * GSLIBERR_SUCCESS or GSLIBERR_TLS. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreFreeTls(void) +{ + Bool res; + + ASSERT(callCtxTlsIndex != TLS_OUT_OF_INDEXES); + +#ifdef _WIN32 + res = TlsFree(callCtxTlsIndex) ? TRUE : FALSE; +#else + res = pthread_key_delete(callCtxTlsIndex) == 0 ? TRUE : FALSE; +#endif + + callCtxTlsIndex = TLS_OUT_OF_INDEXES; + + return res ? GSLIBERR_SUCCESS : GSLIBERR_TLS; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreSetTls -- + * + * Set CallCtx pointer to the slot of the thread local storage index. + * When ctx is NULL, clear the slot. + * + * Results: + * GSLIBERR_SUCCESS or GSLIBERR_TLS. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreSetTls(CallCtx *ctx) // IN +{ + Bool res; + + ASSERT(callCtxTlsIndex != TLS_OUT_OF_INDEXES); + +#ifdef _WIN32 + res = TlsSetValue(callCtxTlsIndex, ctx) ? TRUE : FALSE; +#else + res = pthread_setspecific(callCtxTlsIndex, ctx) == 0 ? TRUE : FALSE; +#endif + + return res ? GSLIBERR_SUCCESS : GSLIBERR_TLS; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreGetTls -- + * + * Get CallCtx pointer from the slot of the thread local storage index. + * + * Results: + * CallCtx/NULL pointer + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static CallCtx * +GuestStoreGetTls(void) +{ + ASSERT(callCtxTlsIndex != TLS_OUT_OF_INDEXES); + +#ifdef _WIN32 + return (CallCtx *)TlsGetValue(callCtxTlsIndex); +#else + return (CallCtx *)pthread_getspecific(callCtxTlsIndex); +#endif +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStore_Init -- + * + * GuestStore client library Init entry point function. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +GuestStoreLibError +GuestStore_Init(void) +{ + if (Atomic_ReadInc32(&initLibCount) == 0) { + GuestStoreLibError retVal = GuestStoreAllocTls(); + if (retVal != GSLIBERR_SUCCESS) { + Atomic_Dec32(&initLibCount); + } + return retVal; + } + + return GSLIBERR_SUCCESS; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStore_DeInit -- + * + * GuestStore client library DeInit entry point function. + * Call of GuestStore_DeInit should match succeeded GuestStore_Init call. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +GuestStoreLibError +GuestStore_DeInit(void) +{ + uint32 oldVal; + uint32 newVal; + + do { + oldVal = Atomic_Read32(&initLibCount); + if (oldVal == 0) { + return GSLIBERR_NOT_INITIALIZED; + } + + newVal = oldVal - 1; + } while (Atomic_ReadIfEqualWrite32(&initLibCount, + oldVal, newVal) != oldVal); + + if (oldVal == 1) { + return GuestStoreFreeTls(); + } + + return GSLIBERR_SUCCESS; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreLogV -- + * + * Internal log function. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +GuestStoreLogV(CallCtx *ctx, // IN + GuestStoreLibLogLevel level, // IN + const char *fmt, // IN + va_list args) // IN +{ + char buf[1024] = GSLIBLOG_TAG; + + ASSERT(ctx != NULL && ctx->logger != NULL); + + Str_Vsnprintf(buf + GSLIBLOG_TAG_LEN, sizeof(buf) - GSLIBLOG_TAG_LEN, + fmt, args); + ctx->logger(level, buf, ctx->clientData); +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreLog -- + * + * Internal log function. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +void +GuestStoreLog(CallCtx *ctx, // IN + GuestStoreLibLogLevel level, // IN + const char *fmt, ...) // IN +{ + va_list args; + + ASSERT(ctx != NULL && ctx->logger != NULL); + + va_start(args, fmt); + GuestStoreLogV(ctx, level, fmt, args); + va_end(args); +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreFreeCtxResources -- + * + * Free resources allocated in each GuestStore_GetContent call. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +GuestStoreFreeCtxResources(CallCtx *ctx) // IN / OUT +{ + int res; + + if (ctx->output != NULL) { + fclose(ctx->output); + ctx->output = NULL; + + /* + * The output file was created after the content size was received. + */ + ASSERT(ctx->contentSize >= 0); + + /* + * Delete the output file if not all the content bytes were received. + */ + if (ctx->contentBytesReceived != ctx->contentSize) { + res = Posix_Unlink(ctx->outputPath); + if (res != 0) { + LOG_ERR(ctx, "Posix_Unlink failed: outputPath='%s', error=%d.", + ctx->outputPath, errno); + } + } + } + + if (ctx->sd != INVALID_SOCKET) { +#ifdef _WIN32 + res = closesocket(ctx->sd); +#else + res = close(ctx->sd); +#endif + + if (res == SOCKET_ERROR) { + LOG_ERR(ctx, "close failed on socket %d: error=%d.", + ctx->sd, SocketGetLastError()); + } + + ctx->sd = INVALID_SOCKET; + } + + free(ctx->buf); + ctx->buf = NULL; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreCreateOutputFile -- + * + * Create an output file stream for writing. + * + * If the given file exists, its content is destroyed. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreCreateOutputFile(CallCtx *ctx) // IN / OUT +{ + FILE *output = Posix_Fopen(ctx->outputPath, "wb"); + if (output == NULL) { + LOG_ERR(ctx, "Posix_Fopen failed: outputPath='%s', error=%d.", + ctx->outputPath, errno); + return GSLIBERR_CREATE_OUTPUT_FILE; + } + + ctx->output = output; + return GSLIBERR_SUCCESS; +} + + +#ifndef _WIN32 + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreConnect -- + * + * Connect to vmtoolsd GuestStore plugin. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +GuestStoreLibError +GuestStoreConnect(CallCtx *ctx) // IN / OUT +{ + struct sockaddr_un svcAddr; + int res; + int err; + struct ucred peerCred; + socklen_t peerCredLen; + + ctx->sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (ctx->sd == INVALID_SOCKET) { + LOG_ERR(ctx, "socket failed: error=%d.", SocketGetLastError()); + return GSLIBERR_CONNECT_GENERIC; + } + + svcAddr.sun_family = AF_UNIX; + ASSERT(sizeof(GUESTSTORE_PIPE_NAME) < sizeof(svcAddr.sun_path)); + memcpy(svcAddr.sun_path, GUESTSTORE_PIPE_NAME, + sizeof(GUESTSTORE_PIPE_NAME)); + + do { + res = connect(ctx->sd, (struct sockaddr*)&svcAddr, + (socklen_t)sizeof(svcAddr)); + } while (res == SOCKET_ERROR && + (err = SocketGetLastError()) == SYSERR_EINTR); + + if (res == SOCKET_ERROR) { + LOG_ERR(ctx, "connect failed on socket %d: error=%d.", + ctx->sd, err); + + if (err == SYSERR_ECONNREFUSED) { + return GSLIBERR_CONNECT_SERVICE_NOT_RUNNING; + } else if (err == SYSERR_EACCESS) { + return GSLIBERR_CONNECT_PERMISSION_DENIED; + } else { + return GSLIBERR_CONNECT_GENERIC; + } + } + + /* + * On Linux, the SO_PEERCRED socket option will give us the PID, + * effective UID, and GID of the peer (the server in this case). + */ + peerCredLen = (socklen_t)sizeof(peerCred); + res = getsockopt(ctx->sd, SOL_SOCKET, SO_PEERCRED, &peerCred, &peerCredLen); + if (res == SOCKET_ERROR) { + LOG_ERR(ctx, "getsockopt SO_PEERCRED failed: error=%d.", + SocketGetLastError()); + return GSLIBERR_CONNECT_GENERIC; + } else if (peerCred.uid != 0) { + LOG_ERR(ctx, "Peer is not supper user."); + return GSLIBERR_CONNECT_SECURITY_VIOLATION; + } + + return GSLIBERR_SUCCESS; +} + +#endif + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreRecvBytes -- + * + * Partially receive bytes from vmtoolsd GuestStore plugin. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreRecvBytes(CallCtx *ctx, // IN + char *buf, // OUT + int bytesToRecv, // IN + int *bytesReceived) // OUT +{ + GuestStoreLibError retVal; + + do { + int res; + + res = recv(ctx->sd, // Synchronous recv + buf, + bytesToRecv, + 0); + if (res > 0) { + *bytesReceived = res; + retVal = GSLIBERR_SUCCESS; + break; + } else if (res == 0) { + LOG_ERR(ctx, "peer closed on socket %d.", ctx->sd); + retVal = GSLIBERR_CONNECT_PEER_RESET; + break; + } else { // SOCKET_ERROR + int err = SocketGetLastError(); + if (err == SYSERR_EINTR) { + continue; + } + + LOG_ERR(ctx, "recv failed on socket %d: error=%d.", + ctx->sd, err); + retVal = GSLIBERR_RECV; + break; + } + } while (TRUE); + + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreSendBytes -- + * + * Send the specified amount of bytes to vmtoolsd GuestStore plugin. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreSendBytes(CallCtx *ctx, // IN + char *buf, // IN + int bytesToSend) // IN +{ + GuestStoreLibError retVal = GSLIBERR_SUCCESS; + int bytesSent = 0; + + while (bytesSent < bytesToSend) { + int res; + res = send(ctx->sd, // Synchronous send + buf + bytesSent, + bytesToSend - bytesSent, + 0); + if (res == SOCKET_ERROR) { + int err = SocketGetLastError(); + if (err == SYSERR_EINTR) { + continue; + } + + LOG_ERR(ctx, "send failed on socket %d: error=%d.", + ctx->sd, err); + retVal = GSLIBERR_SEND; + break; + } + + bytesSent += res; + } + + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreSendHTTPRequest -- + * + * Send HTTP request for content download to vmtoolsd GuestStore plugin. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreSendHTTPRequest(const char *contentPath, // IN + CallCtx *ctx) // IN +{ + int len = 0; + int pathLen; + int index; + + /* + * HTTP GET request: GET HTTP/1.1\r\n\r\n + * excluding , it is 17 bytes. + * Reserve 1024 bytes for it. + * + * Length of contentPath is restricted to GUESTSTORE_CONTENT_PATH_MAX (1024) + * maximum length after URL escaping is 3 * GUESTSTORE_CONTENT_PATH_MAX. + * + * ctx->bufSize is GUESTSTORE_RESPONSE_BUFFER_SIZE (64 * 1024) + */ + ASSERT((1024 + 3 * GUESTSTORE_CONTENT_PATH_MAX) < ctx->bufSize); + + #define COPY_STR_TO_BUF(str) \ + memcpy(ctx->buf + len, str, sizeof(str) - 1); \ + len += (int)(sizeof(str) - 1) + + COPY_STR_TO_BUF(HTTP_REQ_METHOD_GET); + COPY_STR_TO_BUF(" "); + + /* + * ' ', '?' and '%' are the 3 protocol characters GuestStore plugin parses. + */ + pathLen = (int)strlen(contentPath); + for (index = 0; index < pathLen; index++) { + char c = contentPath[index]; + if (c == ' ') { + COPY_STR_TO_BUF("%20"); + } else if (c == '%') { + COPY_STR_TO_BUF("%25"); + } else if (c == '?') { + COPY_STR_TO_BUF("%3F"); + } else { + *(ctx->buf + len++) = c; + } + } + + COPY_STR_TO_BUF(" "); + COPY_STR_TO_BUF(HTTP_VER); + COPY_STR_TO_BUF(HTTP_HEADER_END); + + return GuestStoreSendBytes(ctx, ctx->buf, len); +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreRecvHTTPResponseHeader -- + * + * Receive HTTP response header from vmtoolsd GuestStore plugin. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreRecvHTTPResponseHeader(CallCtx *ctx) // IN +{ + int totalBytesReceived = 0; + int recvBufSize = ctx->bufSize - 1; // Reserve the last byte for '\0' + char *httpHeaderEnd; + char *next_token; + char *httpVer; + char *httpStatus; + int status; + char *contentLengthHeader; + char *content; + int httpResHeaderLen; + GuestStoreLibError retVal; + + do { + int bytesReceived = 0; + + retVal = GuestStoreRecvBytes(ctx, ctx->buf + totalBytesReceived, + recvBufSize - totalBytesReceived, + &bytesReceived); + if (retVal != GSLIBERR_SUCCESS) { + return retVal; + } + + totalBytesReceived += bytesReceived; + ctx->buf[totalBytesReceived] = '\0'; + httpHeaderEnd = strstr(ctx->buf, HTTP_HEADER_END); + if (httpHeaderEnd != NULL) { + *httpHeaderEnd = '\0'; + break; + } + + if (totalBytesReceived == recvBufSize) { + LOG_ERR(ctx, "Protocol header end mark not found."); + return GSLIBERR_SERVER; + } + } while (TRUE); + + httpVer = strtok_r(ctx->buf, " ", &next_token); + if (NULL == httpVer || strcmp(httpVer, HTTP_VER) != 0) { + LOG_ERR(ctx, "Protocol version not correct."); + return GSLIBERR_SERVER; + } + + httpStatus = strtok_r(NULL, " ", &next_token); + if (NULL == httpStatus) { + LOG_ERR(ctx, "Protocol status code not found."); + return GSLIBERR_SERVER; + } + + status = atoi(httpStatus); + + if (status == HTTP_STATUS_CODE_FORBIDDEN) { + LOG_ERR(ctx, "Content forbidden."); + return GSLIBERR_CONTENT_FORBIDDEN; + } + + if (status == HTTP_STATUS_CODE_NOT_FOUND) { + LOG_ERR(ctx, "Content not found."); + return GSLIBERR_CONTENT_NOT_FOUND; + } + + if (status != HTTP_STATUS_CODE_OK) { + LOG_ERR(ctx, "Invalid protocol status '%s'.", httpStatus); + return GSLIBERR_SERVER; + } + + contentLengthHeader = strstr(httpStatus + strlen(httpStatus) + 1, + CONTENT_LENGTH_HEADER); + if (NULL == contentLengthHeader) { + LOG_ERR(ctx, "Protocol content length not found."); + return GSLIBERR_SERVER; + } + + contentLengthHeader += CONTENT_LENGTH_HEADER_LEN; + while (*contentLengthHeader >= '0' && *contentLengthHeader <= '9') { + ctx->contentSize = ctx->contentSize * 10 + (*contentLengthHeader - '0'); + contentLengthHeader++; + } + + if (ctx->contentSize < 0) { + LOG_ERR(ctx, "Invalid protocol content length."); + return GSLIBERR_SERVER; + } + + /* + * We've got content to save, create the output file now. + */ + retVal = GuestStoreCreateOutputFile(ctx); + if (retVal != GSLIBERR_SUCCESS) { + return retVal; + } + + /* + * Save content bytes that follow HTTP response header. + */ + content = httpHeaderEnd + HTTP_HEADER_END_LEN; + httpResHeaderLen = (int)(content - ctx->buf); + if (httpResHeaderLen < totalBytesReceived) { + int contentLen = totalBytesReceived - httpResHeaderLen; + + ctx->contentBytesReceived += contentLen; + if (ctx->contentBytesReceived > ctx->contentSize) { + LOG_ERR(ctx, "Bytes received exceeded content size."); + return GSLIBERR_SERVER; + } + + if (fwrite(content, sizeof(char), contentLen, ctx->output) != contentLen) { + LOG_ERR(ctx, "fwrite failed: error=%d.", errno); + return GSLIBERR_WRITE_OUTPUT_FILE; + } + + if (!REPORT_PROGRESS(ctx)) { + LOG_ERR(ctx, "Request cancelled."); + return GSLIBERR_CANCELLED; + } + } + + return GSLIBERR_SUCCESS; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreRecvHTTPResponseBody -- + * + * Receive HTTP response body, i.e., content bytes, from vmtoolsd + * GuestStore plugin. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static GuestStoreLibError +GuestStoreRecvHTTPResponseBody(CallCtx *ctx) // IN +{ + GuestStoreLibError retVal = GSLIBERR_SUCCESS; + + while (ctx->contentBytesReceived < ctx->contentSize) { + int bytesReceived = 0; + + retVal = GuestStoreRecvBytes(ctx, ctx->buf, ctx->bufSize, &bytesReceived); + if (retVal != GSLIBERR_SUCCESS) { + break; + } + + ctx->contentBytesReceived += bytesReceived; + if (ctx->contentBytesReceived > ctx->contentSize) { + LOG_ERR(ctx, "Bytes received exceeded content size."); + retVal = GSLIBERR_SERVER; + break; + } + + if (fwrite(ctx->buf, sizeof(char), bytesReceived, ctx->output) != bytesReceived) { + LOG_ERR(ctx, "fwrite failed: error=%d.", errno); + retVal = GSLIBERR_WRITE_OUTPUT_FILE; + break; + } + + if (!REPORT_PROGRESS(ctx)) { + LOG_ERR(ctx, "Request cancelled."); + retVal = GSLIBERR_CANCELLED; + break; + } + } + + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStore_GetContent -- + * + * GuestStore client library GetContent entry point function. + * + * Results: + * GSLIBERR_SUCCESS or an error code of GSLIBERR_*. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +GuestStoreLibError +GuestStore_GetContent( + const char *contentPath, // IN + const char *outputPath, // IN + GuestStore_Logger logger, // IN, OPTIONAL + GuestStore_Panic panic, // IN, OPTIONAL + GuestStore_GetContentCallback getContentCb, // IN, OPTIONAL + void *clientData) // IN, OPTIONAL +{ + GuestStoreLibError retVal; + CallCtx ctx = { 0 }; +#ifdef _WIN32 + WSADATA wsaData; + int res; +#endif + + /* + * Set ctx before first LOG_ERR. + */ + ctx.contentPath = contentPath ? contentPath : ""; + ctx.outputPath = outputPath ? outputPath : ""; + ctx.logger = logger; + ctx.panic = panic; + ctx.getContentCb = getContentCb; + ctx.clientData = clientData; + ctx.sd = INVALID_SOCKET; + + if (contentPath == NULL || *contentPath != '/' || + strlen(contentPath) > GUESTSTORE_CONTENT_PATH_MAX) { + LOG_ERR(&ctx, "Invalid content path."); + return GSLIBERR_INVALID_PARAMETER; + } + + if (outputPath == NULL || *outputPath == '\0') { + LOG_ERR(&ctx, "Invalid output file path."); + return GSLIBERR_INVALID_PARAMETER; + } + + if (Atomic_Read32(&initLibCount) == 0 || + callCtxTlsIndex == TLS_OUT_OF_INDEXES) { + LOG_ERR(&ctx, "Library is not properly initialized."); + return GSLIBERR_NOT_INITIALIZED; + } + + retVal = GuestStoreSetTls(&ctx); + if (retVal != GSLIBERR_SUCCESS) { + LOG_ERR(&ctx, "GuestStoreSetTls failed."); + return retVal; + } + +#ifdef _WIN32 + res = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (res != 0) { + retVal = GSLIBERR_CONNECT_GENERIC; + LOG_ERR(&ctx, "WSAStartup failed: error=%d.", res); + goto exit; + } +#endif + + retVal = GuestStoreConnect(&ctx); + if (retVal != GSLIBERR_SUCCESS) { + goto exit; + } + + ctx.bufSize = GUESTSTORE_RESPONSE_BUFFER_SIZE; + ctx.buf = (char *)Util_SafeMalloc(ctx.bufSize); + + retVal = GuestStoreSendHTTPRequest(contentPath, &ctx); + if (retVal != GSLIBERR_SUCCESS) { + goto exit; + } + + retVal = GuestStoreRecvHTTPResponseHeader(&ctx); + if (retVal != GSLIBERR_SUCCESS) { + goto exit; + } + + retVal = GuestStoreRecvHTTPResponseBody(&ctx); + +exit: + + GuestStoreFreeCtxResources(&ctx); // Should be before WSACleanup() + +#ifdef _WIN32 + if (res == 0) { + if (retVal != GSLIBERR_SUCCESS) { + /* + * WSASetLastError needs a successful WSAStartup call, + * WSAGetLastError does not. + * + * Note: WSACleanup may change WSA last error again. + */ + WSASetLastError(ctx.winWSAErrNum); + } + + WSACleanup(); + } +#endif + + GuestStoreSetTls(NULL); // Ignore its return value + + /* + * Restore the last error in the end. + */ + if (retVal != GSLIBERR_SUCCESS) { + Err_SetErrno(ctx.errNum); +#ifdef _WIN32 + errno = ctx.winErrNum; +#endif + } + + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * Debug -- + * + * Stub for Debug. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +void +Debug(const char *fmt, ...) +{ + WITH_ERRNO(__errNum__, + { + CallCtx *ctx = GuestStoreGetTls(); + + if (ctx != NULL && ctx->logger != NULL) { + va_list args; + + va_start(args, fmt); + GuestStoreLogV(ctx, GSLIBLOGLEVEL_DEBUG, fmt, args); + va_end(args); + } + } + ); +} + + +/* + *----------------------------------------------------------------------------- + * + * Log -- + * + * Stub for Log. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +void +Log(const char *fmt, ...) +{ + WITH_ERRNO(__errNum__, + { + CallCtx *ctx = GuestStoreGetTls(); + + if (ctx != NULL && ctx->logger != NULL) { + va_list args; + + va_start(args, fmt); + GuestStoreLogV(ctx, GSLIBLOGLEVEL_INFO, fmt, args); + va_end(args); + } + } + ); +} + + +/* + *----------------------------------------------------------------------------- + * + * Warning -- + * + * Stub for Warning. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +void +Warning(const char *fmt, ...) +{ + WITH_ERRNO(__errNum__, + { + CallCtx *ctx = GuestStoreGetTls(); + + if (ctx != NULL && ctx->logger != NULL) { + va_list args; + + va_start(args, fmt); + GuestStoreLogV(ctx, GSLIBLOGLEVEL_WARNING, fmt, args); + va_end(args); + } + } + ); +} + + +/* + *----------------------------------------------------------------------------- + * + * Panic -- + * + * Stub for Panic. + * + * Results: + * Does not return. + * + * Side-effects: + * Process exits. + * + *----------------------------------------------------------------------------- + */ + +void +Panic(const char *fmt, ...) +{ + va_list args; + CallCtx *ctx; + + va_start(args, fmt); + + ctx = GuestStoreGetTls(); + if (ctx != NULL && ctx->panic != NULL) { + char buf[1024] = GSLIBLOG_TAG; + + Str_Vsnprintf(buf + GSLIBLOG_TAG_LEN, sizeof(buf) - GSLIBLOG_TAG_LEN, + fmt, args); + ctx->panic(buf, ctx->clientData); // No return + } else { + fprintf(stderr, "Panic: " GSLIBLOG_TAG); + vfprintf(stderr, fmt, args); + } + + va_end(args); + + exit(-1); +} diff --git a/open-vm-tools/libguestStoreClient/guestStoreClientLibInt.h b/open-vm-tools/libguestStoreClient/guestStoreClientLibInt.h new file mode 100644 index 000000000..5f178ad73 --- /dev/null +++ b/open-vm-tools/libguestStoreClient/guestStoreClientLibInt.h @@ -0,0 +1,198 @@ +/********************************************************* + * Copyright (C) 2019 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. + * + *********************************************************/ + +/* + * guestStoreClientLibInt.h -- + * Private definitions for guestStoreClientLib. + */ + +#ifndef __GUESTSTORECLIENTLIBINT_H__ +#define __GUESTSTORECLIENTLIBINT_H__ + +#include "vm_assert.h" +#include "vm_atomic.h" +#include "vm_basic_types.h" +#include "vm_basic_defs.h" +#include "err.h" +#include "str.h" +#include "util.h" +#include "posix.h" + +#include "guestStoreConst.h" +#include "guestStoreDefs.h" +#include "vmware/tools/guestStoreClientLib.h" + +#ifdef _WIN32 + +#define SYSERR_EADDRINUSE WSAEADDRINUSE +#define SYSERR_EACCESS WSAEACCES +#define SYSERR_EINTR WSAEINTR +#define SYSERR_ECONNRESET WSAECONNRESET +#define SYSERR_ECONNREFUSED WSAECONNREFUSED + +#define SHUTDOWN_RECV SD_RECEIVE +#define SHUTDOWN_SEND SD_SEND +#define SHUTDOWN_BOTH SD_BOTH + +typedef int socklen_t; + +#else + +#define SYSERR_EADDRINUSE EADDRINUSE +#define SYSERR_EACCESS EACCES +#define SYSERR_EINTR EINTR +#define SYSERR_ECONNRESET ECONNRESET +#define SYSERR_ECONNREFUSED ECONNREFUSED + +#define SHUTDOWN_RECV SHUT_RD +#define SHUTDOWN_SEND SHUT_WR +#define SHUTDOWN_BOTH SHUT_RDWR + +typedef int SOCKET; + +#define SOCKET_ERROR (-1) +#define INVALID_SOCKET ((SOCKET) -1) + +#endif + + +/* + * Context of each GuestStore_GetContent call. + */ +typedef struct _CallCtx { + const char *contentPath; // Requested content path + const char *outputPath; // Output file path + GuestStore_Logger logger; // Caller provided logger function + GuestStore_Panic panic; // Caller provided panic function + GuestStore_GetContentCallback getContentCb; // Progress callback + void *clientData; // Parameter for caller provided functions + FILE *output; // Output file stream + SOCKET sd; // Socket descriptor connecting to vmtoolsd GuestStore plugin + int64 contentSize; // Total content bytes + int64 contentBytesReceived; // Received content bytes + int bufSize; // Content download buffer size + char *buf; // Content download buffer + Err_Number errNum; // Preserve the last error +#ifdef _WIN32 + int winErrNum; + int winWSAErrNum; +#endif +} CallCtx; + +/* + * Preserve the first last error that fails API GuestStore_GetContent() and + * restore it when the API returns in case API exit resource freeing calls + * change the last error. + * + * WSASetLastError needs a successful WSAStartup call, + * WSAGetLastError does not. + */ +#ifdef _WIN32 +#define LOG_ERR(ctx, format, ...) \ + if ((ctx)->errNum == 0 && \ + (ctx)->winErrNum == 0 && \ + (ctx)->winWSAErrNum == 0) { \ + (ctx)->errNum = Err_Errno(); \ + (ctx)->winErrNum = errno; \ + (ctx)->winWSAErrNum = WSAGetLastError(); \ + } \ + if ((ctx)->logger != NULL) { \ + GuestStoreLog((ctx), GSLIBLOGLEVEL_ERROR, \ + format, ##__VA_ARGS__); \ + } +#else +#define LOG_ERR(ctx, format, ...) \ + if ((ctx)->errNum == 0) { \ + (ctx)->errNum = Err_Errno(); \ + } \ + if ((ctx)->logger != NULL) { \ + GuestStoreLog((ctx), GSLIBLOGLEVEL_ERROR, \ + format, ##__VA_ARGS__); \ + } +#endif + +#define LOG_WARN(ctx, format, ...) \ + if ((ctx)->logger != NULL) { \ + GuestStoreLog((ctx), GSLIBLOGLEVEL_WARNING, \ + format, ##__VA_ARGS__); \ + } + +#define LOG_INFO(ctx, format, ...) \ + if ((ctx)->logger != NULL) { \ + GuestStoreLog((ctx), GSLIBLOGLEVEL_INFO, \ + format, ##__VA_ARGS__); \ + } + +#define LOG_DEBUG(ctx, format, ...) \ + if ((ctx)->logger != NULL) { \ + GuestStoreLog((ctx), GSLIBLOGLEVEL_DEBUG, \ + format, ##__VA_ARGS__); \ + } + +#define REPORT_PROGRESS(ctx) \ + (((ctx)->getContentCb != NULL) ? (ctx)->getContentCb( \ + (ctx)->contentSize, \ + (ctx)->contentBytesReceived, \ + (ctx)->clientData) \ + : TRUE) + + +/* + *----------------------------------------------------------------------------- + * + * SocketGetLastError -- + * + * Get the last failed sock function error code. + * + * Results: + * error code. + * + * Side effects: + * None. + * + *----------------------------------------------------------------------------- + */ + +static inline int +SocketGetLastError(void) +{ +#ifdef _WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + + +#ifdef __cplusplus +extern "C" { +#endif + +void +GuestStoreLog(CallCtx *ctx, // IN + GuestStoreLibLogLevel level, // IN + const char *fmt, ...); // IN + +GuestStoreLibError +GuestStoreConnect(CallCtx *ctx); // IN / OUT + +#ifdef __cplusplus +} +#endif + +#endif /* __GUESTSTORECLIENTLIBINT_H__ */ diff --git a/open-vm-tools/libguestStoreClient/gueststoreclientlib_version.h b/open-vm-tools/libguestStoreClient/gueststoreclientlib_version.h new file mode 100644 index 000000000..1951a4d01 --- /dev/null +++ b/open-vm-tools/libguestStoreClient/gueststoreclientlib_version.h @@ -0,0 +1,26 @@ +/********************************************************* + * Copyright (C) 2019 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 _GUESTSTORECLIENTLIB_VERSION_H_ +#define _GUESTSTORECLIENTLIB_VERSION_H_ + +#include "vm_tools_version.h" +#define GUESTSTORECLIENTLIB_VERSION_COMMAS TOOLS_VERSION_EXT_CURRENT_CSV +#define GUESTSTORECLIENTLIB_VERSION_STRING TOOLS_VERSION_EXT_CURRENT_STR + +#endif /* _GUESTSTORECLIENTLIB_VERSION_H_ */ diff --git a/open-vm-tools/libvmtools/Makefile.am b/open-vm-tools/libvmtools/Makefile.am index 290e87d61..837c9dae2 100644 --- a/open-vm-tools/libvmtools/Makefile.am +++ b/open-vm-tools/libvmtools/Makefile.am @@ -1,5 +1,5 @@ ################################################################################ -### Copyright (C) 2008-2020 VMware, Inc. All rights reserved. +### Copyright (c) 2008-2020 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 @@ -35,6 +35,9 @@ libvmtools_la_LIBADD += ../lib/file/libFile.la libvmtools_la_LIBADD += ../lib/glibUtils/libGlibUtils.la libvmtools_la_LIBADD += ../lib/guestApp/libGuestApp.la libvmtools_la_LIBADD += ../lib/guestRpc/libGuestRpc.la +if LINUX + libvmtools_la_LIBADD += ../lib/guestStoreClientHelper/libGuestStoreClientHelper.la +endif libvmtools_la_LIBADD += ../lib/message/libMessage.la libvmtools_la_LIBADD += ../lib/netUtil/libNetUtil.la libvmtools_la_LIBADD += ../lib/nicInfo/libNicInfo.la diff --git a/open-vm-tools/services/plugins/Makefile.am b/open-vm-tools/services/plugins/Makefile.am index 08b7c81b4..eb725a672 100644 --- a/open-vm-tools/services/plugins/Makefile.am +++ b/open-vm-tools/services/plugins/Makefile.am @@ -17,8 +17,9 @@ SUBDIRS = if LINUX - SUBDIRS += gdp SUBDIRS += appInfo + SUBDIRS += gdp + SUBDIRS += guestStore endif if ENABLE_SDMP SUBDIRS += serviceDiscovery diff --git a/open-vm-tools/services/plugins/guestStore/COPYING b/open-vm-tools/services/plugins/guestStore/COPYING new file mode 100644 index 000000000..09f465ab7 --- /dev/null +++ b/open-vm-tools/services/plugins/guestStore/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library 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; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/open-vm-tools/services/plugins/guestStore/Makefile.am b/open-vm-tools/services/plugins/guestStore/Makefile.am new file mode 100644 index 000000000..a9c650f39 --- /dev/null +++ b/open-vm-tools/services/plugins/guestStore/Makefile.am @@ -0,0 +1,34 @@ +################################################################################ +### Copyright (c) 2020 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 +################################################################################ + +SUBDIRS = + +plugindir = @VMSVC_PLUGIN_INSTALLDIR@ +plugin_LTLIBRARIES = libguestStore.la + +libguestStore_la_CPPFLAGS = +libguestStore_la_CPPFLAGS += @PLUGIN_CPPFLAGS@ + +libguestStore_la_LDFLAGS = +libguestStore_la_LDFLAGS += @PLUGIN_LDFLAGS@ + +libguestStore_la_LIBADD = +libguestStore_la_LIBADD += @VMTOOLS_LIBS@ +libguestStore_la_LIBADD += @GOBJECT_LIBS@ + +libguestStore_la_SOURCES = +libguestStore_la_SOURCES += guestStorePlugin.c diff --git a/open-vm-tools/services/plugins/guestStore/guestStorePlugin.c b/open-vm-tools/services/plugins/guestStore/guestStorePlugin.c new file mode 100644 index 000000000..51b3c1c2b --- /dev/null +++ b/open-vm-tools/services/plugins/guestStore/guestStorePlugin.c @@ -0,0 +1,3072 @@ +/********************************************************* + * Copyright (C) 2019-2020 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 guestStorePlugin.c + * + * GuestStore plugin, allow client to download content from GuestStore. + */ + + +#define G_LOG_DOMAIN "guestStore" + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include "posix.h" +#include "file.h" +#endif + +#include "vm_assert.h" +#include "vm_basic_types.h" +#include "vmware/tools/guestStore.h" +#include "vmware/tools/log.h" +#include "vmware/tools/plugin.h" +#include "vmware/tools/utils.h" +#include "vmware/guestrpc/tclodefs.h" +#include "vm_version.h" +#include "embed_version.h" +#include "vmtoolsd_version.h" +#include "dataMap.h" +#include "guestStoreConst.h" +#include "guestStoreDefs.h" +#include "rpcout.h" +#include "poll.h" +#ifdef OPEN_VM_TOOLS +#include "vmci_sockets.h" +#else +#include "vsockCommon.h" +#endif +#include "asyncsocket.h" +#include "str.h" +#include "util.h" +#include "guestApp.h" +#include "vmcheck.h" +#ifdef _WIN32 +#include "guestStoreWin32.h" +#endif + +VM_EMBED_VERSION(VMTOOLSD_VERSION_STRING); + +#ifndef sockerr +#ifdef _WIN32 +#define sockerr() WSAGetLastError() +#else +#define sockerr() errno +#endif +#endif // sockerr + +/* + * User level send/recv buffers + */ + +/* + * Client connection send/recv buffer size + */ +#define CLIENT_CONN_SEND_RECV_BUF_SIZE GUESTSTORE_REQUEST_BUFFER_SIZE + +/* + * VMX connection send/recv buffer size + */ +#define VMX_CONN_SEND_RECV_BUF_SIZE GUESTSTORE_RESPONSE_BUFFER_SIZE + +/* + * Maximum concurrent client connections + */ +#define DEFAULT_MAX_CLIENT_CONNECTIONS 8 + +/* + * Default timeout value in seconds for receiving from client connections + */ +#define DEFAULT_CLIENT_RECV_TIMEOUT 3 // seconds + + +/* + * Client connection details + */ +typedef struct _ClientConnInfo { + AsyncSocket *asock; + + char *buf; // Send/recv buffer for HTTP request/response head + int32 bufLen; // Send/recv buffer length + + Bool shutDown; // Close connection in send callback. + + Bool isCurrent; // True for the current client connection + char *requestPath; // Requested GuestStore content path + GSource *timeoutSource; // Timeout source for receiving HTTP request +} ClientConnInfo; + +/* + * VMX connection details + */ +typedef struct _VmxConnInfo { + AsyncSocket *asock; + + char *buf; // Send/recv buffer for content transfer + int32 bufLen; // Send/recv buffer length + + Bool shutDown; // Close connection in send callback. + + int32 dataMapLen; // Recv buffer for VMX data map size + int32 connTimeout; // Connection inactivity timeout + int64 bytesRemaining; // Track remaining content size to transfer + GSource *timeoutSource; // Timeout source for connection inactivity +} VmxConnInfo; + +typedef struct { + AsyncSocket *vmxListenSock; // For vsocket connections from VMX + AsyncSocket *clientListenSock; // For connections from clients + + GList *clientConnWaitList; // Client connections in waiting list + + ClientConnInfo *clientConn; // The current client connection being served + VmxConnInfo *vmxConn; // The VMX connection providing service + + ToolsAppCtx *ctx; // vmtoolsd application context + + Bool featureDisabled; // Track tools.conf [guestStore]disabled change + Bool adminOnly; // Track tools.conf [guestStore]adminOnly change + + Bool guestStoreAccessEnabled; // VMX GuestStore access enable status + + Bool vmxConnectRequested; // VMX connect request sent status + GSource *timeoutSource; // Timeout source for VMX to guest connection + Bool shutdown; // vmtoolsd shutdown +} PluginData; + +static PluginData pluginData = {0}; + +#define currentClientConn pluginData.clientConn +#define theVmxConn pluginData.vmxConn + +#define ReceivedHttpRequestFromCurrentClientConn() \ + (currentClientConn->requestPath != NULL) + +/* + * Macros to read values from config file + */ +#define GUESTSTORE_CONFIG_GET_BOOL(key, defVal) \ + VMTools_ConfigGetBoolean(pluginData.ctx->config, "guestStore", key, defVal) + +#define GUESTSTORE_CONFIG_GET_INT(key, defVal) \ + VMTools_ConfigGetInteger(pluginData.ctx->config, "guestStore", key, defVal) + + +/* + *----------------------------------------------------------------------------- + * + * GetCurrentUtcStr -- + * + * Get the current UTC time in a string format suitable for usage + * in HTTP response. + * + * Results: + * Return the current UTC time string, e.g. + * + * Wed, 07 Nov 2018 20:50:11 GMT + * + * The caller should call g_free() to free it. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static gchar * +GetCurrentUtcStr(void) +{ + gchar *res = NULL; + GDateTime *utcTime = g_date_time_new_now_utc(); + + if (NULL != utcTime) { + res = g_date_time_format(utcTime, "%a, %d %b %Y %T GMT"); + g_date_time_unref(utcTime); + } + + return res; +} + + +/* + *----------------------------------------------------------------------------- + * + * IsFeatureDisabled -- + * + * Check if guest admin/root has disabled GuestStore access. + * + * Results: + * Return the configured boolean value, default is FALSE. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline Bool +IsFeatureDisabled(void) +{ + return GUESTSTORE_CONFIG_GET_BOOL("disabled", FALSE); +} + +#define CheckAndUpdateFeatureDisabled() \ + (pluginData.featureDisabled = IsFeatureDisabled()) + + +/* + *----------------------------------------------------------------------------- + * + * IsAdminOnly -- + * + * Check if only guest admin/root has access to GuestStore. + * + * Results: + * Return the configured boolean value, default is FALSE. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline Bool +IsAdminOnly(void) +{ + return GUESTSTORE_CONFIG_GET_BOOL("adminOnly", FALSE); +} + +#define CheckAndUpdateAdminOnly() \ + (pluginData.adminOnly = IsAdminOnly()) + + +static void +StartServeNextClientConn(void); + +static void +CloseClientConn(ClientConnInfo *clientConn); // IN + +#define CloseCurrentClientConn() \ + if (currentClientConn != NULL) { \ + CloseClientConn(currentClientConn); \ + } + +#define CloseCurrentClientConnIfReceivedHttpRequest() \ + if (currentClientConn != NULL && \ + currentClientConn->requestPath != NULL) { \ + CloseClientConn(currentClientConn); \ + } + +#define CloseClientConnsInWait() \ + while (pluginData.clientConnWaitList != NULL) { \ + ClientConnInfo *clientConn = \ + (ClientConnInfo *)(pluginData.clientConnWaitList->data); \ + CloseClientConn(clientConn); \ + } + +static void +CloseVmxConn(void); + +static void +CloseActiveConnections(void); + +static void +HandleCurrentClientConnError(void); + +static void +HandleVmxConnError(void); + +static Bool +RecvHttpRequestFromCurrentClientConn(void *buf, // OUT + int len); // IN + +static Bool +StartRecvHttpRequestFromCurrentClientConn(void); + +static inline void +StopRecvFromCurrentClientConn(void); + +static Bool +SendToCurrentClientConn(void *buf, // IN + int len); // IN + +static Bool +SendHttpResponseToCurrentClientConn(const char *headFmt, // IN + int64 contentLen, // IN + Bool shutdown); // IN + +#define SendHttpResponseOKToCurrentClientConn(contentSize) \ + SendHttpResponseToCurrentClientConn( \ + HTTP_RES_OK, \ + contentSize, \ + (0 == contentSize ? TRUE : FALSE)) + +#define SendHttpResponseForbiddenToCurrentClientConn() \ + SendHttpResponseToCurrentClientConn( \ + HTTP_RES_FORBIDDEN, \ + 0, \ + TRUE) + +#define SendHttpResponseNotFoundToCurrentClientConn() \ + SendHttpResponseToCurrentClientConn( \ + HTTP_RES_NOT_FOUND, \ + 0, \ + TRUE) + +static Bool +SendConnectRequestToVmx(void); + +static Bool +SendDataMapToVmxConn(void); + +#define CheckSendShutdownDataMapToVmxConn() \ + ASSERT(currentClientConn == NULL); \ + if (theVmxConn != NULL && !theVmxConn->shutDown) { \ + SendDataMapToVmxConn(); \ + } + +#define CheckSendRequestDataMapToVmxConn() \ + ASSERT(currentClientConn != NULL); \ + if (ReceivedHttpRequestFromCurrentClientConn() && \ + theVmxConn != NULL && !theVmxConn->shutDown) { \ + SendDataMapToVmxConn(); \ + } + +static Bool +RecvDataMapFromVmxConn(void *buf, // OUT + int len); // IN + +static inline void +StopRecvFromVmxConn(void); + +static Bool +ProcessVmxDataMap(const DataMap *map); // IN + +static Bool +RecvContentFromVmxConn(void); + +static void +StartCurrentClientConnRecvTimeout(void); + +static inline void +StopClientConnRecvTimeout(ClientConnInfo *clientConn); // IN + +static inline void +StopCurrentClientConnRecvTimeout(void); + +static Bool +CurrentClientConnRecvTimeoutCb(gpointer clientData); // IN + +static inline void +StartVmxToGuestConnTimeout(void); + +static inline void +StopVmxToGuestConnTimeout(void); + +static Bool +VmxToGuestConnTimeoutCb(gpointer clientData); // IN + +static inline void +StartConnInactivityTimeout(void); + +static inline void +StopConnInactivityTimeout(void); + +static Bool +ConnInactivityTimeoutCb(gpointer clientData); // IN + +static void +ClientConnErrorCb(int err, // IN + AsyncSocket *asock, // IN + void *clientData); // IN + +static void +CurrentClientConnSendCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData); // IN + +static void +CurrentClientConnRecvHttpRequestCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData); // IN + +static void +ClientConnectCb(AsyncSocket *asock, // IN + void *clientData); // IN + +static void +VmxConnErrorCb(int err, // IN + AsyncSocket *asock, // IN + void *clientData); // IN + +static void +VmxConnSendDataMapCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData); // IN + +static void +VmxConnRecvDataMapCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData); // IN + +static void +VmxConnRecvContentCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData); // IN + +static void +VmxConnectCb(AsyncSocket *asock, // IN + void *clientData); // IN + + +/* + *----------------------------------------------------------------------------- + * + * StartServeNextClientConn -- + * + * Remove the next client connection from the waiting list, make it + * the current client connection and start receiving HTTP request + * from it. + * + * If the waiting list is empty, initiate shutdown VMX connection. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +StartServeNextClientConn(void) +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(currentClientConn == NULL); + + if (pluginData.clientConnWaitList != NULL) { + currentClientConn = (ClientConnInfo *) + (pluginData.clientConnWaitList->data); + pluginData.clientConnWaitList = g_list_remove( + pluginData.clientConnWaitList, currentClientConn); + currentClientConn->isCurrent = TRUE; + + StartRecvHttpRequestFromCurrentClientConn(); + } else { + CheckSendShutdownDataMapToVmxConn(); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * CloseClientConn -- + * + * Close a client connection and remove its reference. + * + * Note: AsyncSocket does not differentiate read/write errors yet and + * does not try to send any data to the other end on close, so pending + * send data is dropped when a connection is closed even the socket may + * be still good for write. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +CloseClientConn(ClientConnInfo *clientConn) // IN +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(clientConn != NULL); + ASSERT(clientConn->asock != NULL); + + g_info("Closing client connection %d.\n", + AsyncSocket_GetFd(clientConn->asock)); + + AsyncSocket_Close(clientConn->asock); + clientConn->asock = NULL; + + if (clientConn->buf != NULL) { + free(clientConn->buf); + clientConn->buf = NULL; + } + + if (clientConn->requestPath != NULL) { + free(clientConn->requestPath); + clientConn->requestPath = NULL; + } + + StopClientConnRecvTimeout(clientConn); + + if (clientConn->isCurrent) { + ASSERT(currentClientConn == clientConn); + /* + * AsyncSocketSendFn (CurrentClientConnSendCb) can be invoked inside + * AsyncSocket_Close(). + */ + currentClientConn = NULL; + } else { + /* + * This client connection is in the waiting list. + */ + pluginData.clientConnWaitList = + g_list_remove(pluginData.clientConnWaitList, clientConn); + } + + free(clientConn); +} + + +/* + *----------------------------------------------------------------------------- + * + * CloseVmxConn -- + * + * Close the VMX connection. + * + * Note: AsyncSocket does not differentiate read/write errors yet and + * does not try to send any data to the other end on close, so pending + * send data is dropped when a connection is closed even the socket may + * be still good for write. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +CloseVmxConn(void) +{ + g_debug("Entering %s.\n", __FUNCTION__); + + if (theVmxConn == NULL) { + return; + } + + ASSERT(theVmxConn->asock != NULL); + + g_info("Closing VMX connection %d.\n", + AsyncSocket_GetFd(theVmxConn->asock)); + + /* + * AsyncSocketSendFn (VmxConnSendDataMapCb) can be invoked inside + * AsyncSocket_Close(). + */ + AsyncSocket_Close(theVmxConn->asock); + theVmxConn->asock = NULL; + + if (theVmxConn->buf != NULL) { + free(theVmxConn->buf); + theVmxConn->buf = NULL; + } + + StopConnInactivityTimeout(); + + free(theVmxConn); + theVmxConn = NULL; + pluginData.vmxConnectRequested = FALSE; +} + + +/* + *----------------------------------------------------------------------------- + * + * CloseActiveConnections -- + * + * Close the current client connection and the VMX connection, force to + * restart from the next client connection in the waiting list if it + * exists. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +CloseActiveConnections(void) +{ + g_debug("Entering %s.\n", __FUNCTION__); + + CloseCurrentClientConn(); + + if (theVmxConn != NULL && !theVmxConn->shutDown) { + /* + * After CloseCurrentClientConn(), send shutdown data map to VMX. + */ + SendDataMapToVmxConn(); + } else { + /* + * Force to restart. + */ + CloseVmxConn(); + StartServeNextClientConn(); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * HandleCurrentClientConnError -- + * + * Handle the current client connection error. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +HandleCurrentClientConnError(void) +{ + Bool requestReceived; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + requestReceived = ReceivedHttpRequestFromCurrentClientConn(); + + CloseCurrentClientConn(); + + if (requestReceived) { + /* + * The VMX connection that serves the current client connection after + * it has received HTTP request has to be reset too. + */ + CheckSendShutdownDataMapToVmxConn(); + } else { + /* + * HTTP request not received from the current client connection yet, + * the VMX connection is still clean. + */ + StartServeNextClientConn(); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * HandleVmxConnError -- + * + * Handle the VMX connection error. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +HandleVmxConnError(void) +{ + g_debug("Entering %s.\n", __FUNCTION__); + + CloseVmxConn(); + + /* + * The current client connection being served after received HTTP request + * has to be reset too. + */ + CloseCurrentClientConnIfReceivedHttpRequest(); + + if (pluginData.guestStoreAccessEnabled && + currentClientConn == NULL) { + StartServeNextClientConn(); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * RecvHttpRequestFromCurrentClientConn -- + * + * Receive HTTP request from the current client connection. + * + * Results: + * TURE on success, FALSE otherwise. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +RecvHttpRequestFromCurrentClientConn(void *buf, // OUT + int len) // IN +{ + int res; + + g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + res = AsyncSocket_RecvPartial(currentClientConn->asock, buf, len, + CurrentClientConnRecvHttpRequestCb, + currentClientConn); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_RecvPartial failed " + "on current client connection %d: %s\n", + AsyncSocket_GetFd(currentClientConn->asock), + AsyncSocket_Err2String(res)); + HandleCurrentClientConnError(); + return FALSE; + } + + if (currentClientConn->timeoutSource == NULL) { + StartCurrentClientConnRecvTimeout(); + } + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * StartRecvHttpRequestFromCurrentClientConn -- + * + * Start receiving HTTP request, with timeout, from + * the current client connection. + * + * Results: + * TURE on success, FALSE otherwise. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +StartRecvHttpRequestFromCurrentClientConn(void) +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + ASSERT(currentClientConn->buf == NULL); + + currentClientConn->bufLen = CLIENT_CONN_SEND_RECV_BUF_SIZE; + currentClientConn->buf = Util_SafeMalloc(currentClientConn->bufLen); + + return RecvHttpRequestFromCurrentClientConn(currentClientConn->buf, + currentClientConn->bufLen); +} + + +/* + *----------------------------------------------------------------------------- + * + * StopRecvFromCurrentClientConn -- + * + * Stop receiving from the current client connection, safe to call in + * the same connection recv callback. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StopRecvFromCurrentClientConn(void) +{ + int res = AsyncSocket_CancelRecvEx(currentClientConn->asock, + NULL, NULL, NULL, TRUE); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_CancelRecvEx failed " + "on current client connection %d: %s\n", + AsyncSocket_GetFd(currentClientConn->asock), + AsyncSocket_Err2String(res)); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * SendToCurrentClientConn -- + * + * Send to the current client connection. + * + * Results: + * TRUE on success, FALSE otherwise. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +SendToCurrentClientConn(void *buf, // IN + int len) // IN +{ + int res; + + //g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + res = AsyncSocket_Send(currentClientConn->asock, buf, len, + CurrentClientConnSendCb, currentClientConn); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_Send failed " + "on current client connection %d: %s\n", + AsyncSocket_GetFd(currentClientConn->asock), + AsyncSocket_Err2String(res)); + HandleCurrentClientConnError(); + return FALSE; + } + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * SendHttpResponseToCurrentClientConn -- + * + * Send HTTP response head to the current client connection. + * + * Results: + * TRUE on success, FALSE otherwise. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +SendHttpResponseToCurrentClientConn(const char *headFmt, // IN + int64 contentLen, // IN + Bool shutdown) // IN +{ + gchar *utcStr; + int len; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + utcStr = GetCurrentUtcStr(); + len = Str_Sprintf(currentClientConn->buf, currentClientConn->bufLen, + headFmt, + utcStr != NULL ? utcStr : "", contentLen); + g_free(utcStr); + + currentClientConn->shutDown = shutdown; + return SendToCurrentClientConn(currentClientConn->buf, len); +} + + +/* + *----------------------------------------------------------------------------- + * + * SendConnectRequestToVmx -- + * + * Request VMX to connect to our VSOCK listening port via RPC command. + * + * This function should be called when pluginData.vmxConnectRequested + * is FALSE. + * + * Results: + * TRUE on success, FALSE otherwise + * + * Side-effects: + * All outstanding client connections are closed if failed. + * + *----------------------------------------------------------------------------- + */ + +static Bool +SendConnectRequestToVmx(void) +{ + Bool retVal; + int fd; + struct sockaddr_vm addr; +#ifdef _WIN32 + int addrLen = (int)sizeof(addr); +#else + socklen_t addrLen = (socklen_t)sizeof(addr); +#endif + char msg[32]; + int msgLen; + char *result; + size_t resultLen; + RpcChannelType rpcChannelType; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(!pluginData.vmxConnectRequested); + ASSERT(theVmxConn == NULL); + ASSERT(pluginData.vmxListenSock != NULL); + + fd = AsyncSocket_GetFd(pluginData.vmxListenSock); + + /* + * Get the listening port. + */ +#ifdef _WIN32 + /* coverity[negative_returns] */ + if (0 != getsockname((SOCKET)fd, (struct sockaddr *)&addr, &addrLen)) +#else + /* coverity[negative_returns] */ + if (0 != getsockname(fd, (struct sockaddr *)&addr, &addrLen)) +#endif + { + g_warning("getsockname failed on VMX listening socket %d: sockerr=%d.\n", + fd, sockerr()); + retVal = FALSE; + goto exit; + } + + msgLen = Str_Sprintf(msg, sizeof msg, + "guestStore.connect %d", addr.svm_port); + result = NULL; + rpcChannelType = RpcChannel_GetType(pluginData.ctx->rpc); + g_debug("Current guest RPC channel type: %d.\n", rpcChannelType); + + /* + * "guestStore.connect" is a privileged guest RPC that should + * go through a privileged vSock RPC channel. + */ + if (rpcChannelType == RPCCHANNEL_TYPE_PRIV_VSOCK) { + retVal = RpcChannel_Send(pluginData.ctx->rpc, msg, msgLen, + &result, &resultLen); + } else { + /* + * After the vmsvc RPC channel falls back to backdoor, it could not + * send through privileged guest RPC any more. + */ + retVal = RpcChannel_SendOneRawPriv(msg, msgLen, + &result, &resultLen); + } + + if (retVal) { + g_info("Connect request sent to VMX.\n"); + } else { + g_warning("Failed to send connect request to VMX: %s.\n", + result != NULL ? result : ""); + } + vm_free(result); + +exit: + if (!retVal) { + CloseCurrentClientConn(); + CloseClientConnsInWait(); + } else { + StartVmxToGuestConnTimeout(); + } + + pluginData.vmxConnectRequested = retVal; + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * SendDataMapToVmxConn -- + * + * Send a data map to the VMX connection. + * + * After received request path from the current client connection, data + * map field GUESTSTORE_REQ_FLD_PATH with the request path is sent to + * the VMX connection. VMX will send back a response data map with error + * code. + * + * When no more client to serve, initiate shutdown VMX connection by + * sending data map field GUESTSTORE_REQ_FLD_NONE to the VMX connection + * so that VMX side can close its vsocket. + * + * Results: + * TRUE on success, FALSE otherwise. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +SendDataMapToVmxConn(void) +{ + int fd; + ErrorCode res; + DataMap map; + Bool mapCreated = FALSE; + int cmdType; + char *serBuf = NULL; + uint32 serBufLen; + int resSock; + Bool retVal = FALSE; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + fd = AsyncSocket_GetFd(theVmxConn->asock); + + res = DataMap_Create(&map); + if (res != DMERR_SUCCESS) { + g_warning("DataMap_Create failed for VMX connection %d: error=%d.\n", + fd, res); + goto exit; + } + + mapCreated = TRUE; + + if (currentClientConn == NULL) { + /* + * No client to serve, inform VMX side to close its vsocket proactively, + * rather than waiting for ASOCKERR_REMOTE_DISCONNECT (4) error callback + * which may never happen. + */ + ASSERT(!theVmxConn->shutDown); + + theVmxConn->shutDown = TRUE; + StopRecvFromVmxConn(); + cmdType = GUESTSTORE_REQ_CMD_CLOSE; + } else { + char *str; + + ASSERT(ReceivedHttpRequestFromCurrentClientConn()); + + str = Util_SafeStrdup(currentClientConn->requestPath); + res = DataMap_SetString(&map, GUESTSTORE_REQ_FLD_PATH, str, -1, TRUE); + if (res != DMERR_SUCCESS) { + g_warning("DataMap_SetString (field path) failed " + "for VMX connection %d: error=%d.\n", fd, res); + free(str); + goto exit; + } + + cmdType = GUESTSTORE_REQ_CMD_GET; + } + + res = DataMap_SetInt64(&map, GUESTSTORE_REQ_FLD_CMD, cmdType, TRUE); + if (res != DMERR_SUCCESS) { + g_warning("DataMap_SetInt64 (field cmd) failed " + "for VMX connection %d: error=%d.\n", fd, res); + goto exit; + } + + res = DataMap_Serialize(&map, &serBuf, &serBufLen); + if (res != DMERR_SUCCESS) { + g_warning("DataMap_Serialize failed " + "for VMX connection %d: error=%d.\n", fd, res); + goto exit; + } + + if (serBufLen > theVmxConn->bufLen) { + g_warning("Data map to VMX connection %d is too large: length=%d.\n", + fd, serBufLen); + goto exit; + } + + memcpy(theVmxConn->buf, serBuf, serBufLen); + resSock = AsyncSocket_Send(theVmxConn->asock, + theVmxConn->buf, serBufLen, + VmxConnSendDataMapCb, theVmxConn); + if (resSock != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_Send failed on VMX connection %d: %s\n", + fd, AsyncSocket_Err2String(resSock)); + goto exit; + } + + retVal = TRUE; + +exit: + if (mapCreated) { + free(serBuf); + DataMap_Destroy(&map); + } + + if (!retVal) { + HandleVmxConnError(); + } + + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * RecvDataMapFromVmxConn -- + * + * Start receiving data map from the VMX connection. + * + * Results: + * TURE on success, FALSE otherwise. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +RecvDataMapFromVmxConn(void *buf, // OUT + int len) // IN +{ + int res; + + g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + res = AsyncSocket_Recv(theVmxConn->asock, buf, len, + VmxConnRecvDataMapCb, theVmxConn); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_Recv failed on VMX connection %d: %s\n", + AsyncSocket_GetFd(theVmxConn->asock), + AsyncSocket_Err2String(res)); + HandleVmxConnError(); + return FALSE; + } + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * StopRecvFromVmxConn -- + * + * Stop receiving from the VMX connection, safe to call in the same + * connection recv callback. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StopRecvFromVmxConn(void) +{ + int res = AsyncSocket_CancelRecvEx(theVmxConn->asock, + NULL, NULL, NULL, TRUE); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_CancelRecvEx failed on VMX connection %d: %s\n", + AsyncSocket_GetFd(theVmxConn->asock), + AsyncSocket_Err2String(res)); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * ProcessVmxDataMap -- + * + * Process the data map received from the VMX connection. + * + * The data map should contain field GUESTSTORE_RES_FLD_ERROR_CODE. In + * success case, field GUESTSTORE_RES_FLD_CONTENT_SIZE should also exist + * with the content size and the data map is followed by content bytes. + * + * Results: + * TRUE on success, FALSE on error. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +ProcessVmxDataMap(const DataMap *map) // IN +{ + int fd; + ErrorCode res; + int64 errorCode; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + fd = AsyncSocket_GetFd(theVmxConn->asock); + + res = DataMap_GetInt64(map, GUESTSTORE_RES_FLD_ERROR_CODE, &errorCode); + if (res != DMERR_SUCCESS) { + g_warning("DataMap_GetInt64 (field error code) failed in data map " + "from VMX connection %d: error=%d.\n", fd, res); + goto error; + } + + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + switch ((int32)errorCode) { + case 0: // ERROR_SUCCESS + { + int64 contentSize; + res = DataMap_GetInt64(map, GUESTSTORE_RES_FLD_CONTENT_SIZE, + &contentSize); + if (res != DMERR_SUCCESS) { + g_warning("DataMap_GetInt64 (field content size) failed " + "in data map from VMX connection %d: error=%d.\n", + fd, res); + goto error; + } + + if (contentSize < 0) { + g_warning("Invalid content size in data map " + "from VMX connection %d: contentSize=%" FMT64 "d.\n", + fd, contentSize); + goto error; + } + + theVmxConn->bytesRemaining = contentSize; + return SendHttpResponseOKToCurrentClientConn(contentSize); + } + case EPERM: + { + return SendHttpResponseForbiddenToCurrentClientConn(); + } + case ENOENT: + { + return SendHttpResponseNotFoundToCurrentClientConn(); + } + default: + g_warning("Unexpected error code value %" FMT64 "d in data map " + "from VMX connection %d.\n", errorCode, fd); + break; + } + +error: + HandleVmxConnError(); + return FALSE; +} + + +/* + *----------------------------------------------------------------------------- + * + * RecvContentFromVmxConn -- + * + * Start receiving content bytes from the VMX connection. + * + * Results: + * TURE on success, FALSE otherwise. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +RecvContentFromVmxConn(void) +{ + int res; + + //g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + res = AsyncSocket_RecvPartial(theVmxConn->asock, + theVmxConn->buf, + theVmxConn->bufLen, + VmxConnRecvContentCb, + theVmxConn); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_RecvPartial failed on VMX connection %d: %s\n", + AsyncSocket_GetFd(theVmxConn->asock), + AsyncSocket_Err2String(res)); + HandleVmxConnError(); + return FALSE; + } + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * StartCurrentClientConnRecvTimeout -- + * + * Start the current client connection recv timeout. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +StartCurrentClientConnRecvTimeout(void) +{ + int clientRecvTimeout; + + ASSERT(currentClientConn->timeoutSource == NULL); + + clientRecvTimeout = GUESTSTORE_CONFIG_GET_INT("clientRecvTimeout", + DEFAULT_CLIENT_RECV_TIMEOUT); + if (clientRecvTimeout <= 0) { + g_warning("Invalid clientRecvTimeout (%d); Using default (%d).\n", + clientRecvTimeout, DEFAULT_CLIENT_RECV_TIMEOUT); + clientRecvTimeout = DEFAULT_CLIENT_RECV_TIMEOUT; + } + + currentClientConn->timeoutSource = g_timeout_source_new( + clientRecvTimeout * 1000); + VMTOOLSAPP_ATTACH_SOURCE(pluginData.ctx, + currentClientConn->timeoutSource, + CurrentClientConnRecvTimeoutCb, + currentClientConn, NULL); +} + + +/* + *----------------------------------------------------------------------------- + * + * StopClientConnRecvTimeout -- + * + * Stop client connection recv timeout. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StopClientConnRecvTimeout(ClientConnInfo *clientConn) // IN +{ + ASSERT(clientConn != NULL); + + if (clientConn->timeoutSource != NULL) { + g_source_destroy(clientConn->timeoutSource); + g_source_unref(clientConn->timeoutSource); + clientConn->timeoutSource = NULL; + } +} + + +/* + *----------------------------------------------------------------------------- + * + * StopCurrentClientConnRecvTimeout -- + * + * Stop the current client connection recv timeout. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StopCurrentClientConnRecvTimeout(void) +{ + StopClientConnRecvTimeout(currentClientConn); +} + + +/* + *----------------------------------------------------------------------------- + * + * CurrentClientConnRecvTimeoutCb -- + * + * Poll callback function for the current client connection recv timeout. + * + * Results: + * The current client connection is closed. + * The timeout source is removed from poll. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +CurrentClientConnRecvTimeoutCb(gpointer clientData) // IN +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(currentClientConn == clientData); + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + g_warning("The current client connection %d recv timed out.\n", + AsyncSocket_GetFd(currentClientConn->asock)); + + /* + * Follow the pattern in ConnInactivityTimeoutCb() + */ + StopCurrentClientConnRecvTimeout(); + + HandleCurrentClientConnError(); + + return G_SOURCE_REMOVE; +} + + +/* + *----------------------------------------------------------------------------- + * + * StartVmxToGuestConnTimeout -- + * + * Start VMX to guest connection timeout. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StartVmxToGuestConnTimeout(void) +{ + ASSERT(pluginData.timeoutSource == NULL); + + pluginData.timeoutSource = g_timeout_source_new( + GUESTSTORE_VMX_TO_GUEST_CONN_TIMEOUT * 1000); + VMTOOLSAPP_ATTACH_SOURCE(pluginData.ctx, + pluginData.timeoutSource, + VmxToGuestConnTimeoutCb, + NULL, NULL); +} + + +/* + *----------------------------------------------------------------------------- + * + * StopVmxToGuestConnTimeout -- + * + * Stop VMX to guest connection timeout. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StopVmxToGuestConnTimeout(void) +{ + if (pluginData.timeoutSource != NULL) { + g_source_destroy(pluginData.timeoutSource); + g_source_unref(pluginData.timeoutSource); + pluginData.timeoutSource = NULL; + } +} + + +/* + *----------------------------------------------------------------------------- + * + * VmxToGuestConnTimeoutCb -- + * + * Poll callback function for VMX to guest connection timeout. + * + * Results: + * All outstanding client connections are closed. + * The timeout source is removed from poll. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +VmxToGuestConnTimeoutCb(gpointer clientData) // IN +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(theVmxConn == NULL); + + g_warning("VMX to guest connection timed out.\n"); + + StopVmxToGuestConnTimeout(); + + CloseCurrentClientConn(); + CloseClientConnsInWait(); + + pluginData.vmxConnectRequested = FALSE; + + return G_SOURCE_REMOVE; +} + + +/* + *----------------------------------------------------------------------------- + * + * StartConnInactivityTimeout -- + * + * Start connection inactivity timeout. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StartConnInactivityTimeout(void) +{ + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->timeoutSource == NULL); + ASSERT(theVmxConn->connTimeout != 0); + + theVmxConn->timeoutSource = g_timeout_source_new( + theVmxConn->connTimeout * 1000); + VMTOOLSAPP_ATTACH_SOURCE(pluginData.ctx, + theVmxConn->timeoutSource, + ConnInactivityTimeoutCb, + theVmxConn, NULL); +} + + +/* + *----------------------------------------------------------------------------- + * + * StopConnInactivityTimeout -- + * + * Stop connection inactivity timeout. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static inline void +StopConnInactivityTimeout(void) +{ + ASSERT(theVmxConn != NULL); + + if (theVmxConn->timeoutSource != NULL) { + g_source_destroy(theVmxConn->timeoutSource); + g_source_unref(theVmxConn->timeoutSource); + theVmxConn->timeoutSource = NULL; + } +} + + +/* + *----------------------------------------------------------------------------- + * + * ConnInactivityTimeoutCb -- + * + * Poll callback function for connection inactivity timeout. + * + * Results: + * The VMX connection and the current client connection are closed. + * The timeout source is removed from poll. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +ConnInactivityTimeoutCb(gpointer clientData) // IN +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(theVmxConn == clientData); + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + g_warning("Connection inactivity timed out.\n"); + + /* + * Issue observed: + * If g_source_destroy() is not called on the inactivity timeout source + * and the next client connection in the waiting list becomes current + * and starts its new recv timeout source, g_main_dispatch() does not + * remove the inactivity timeout source after this callback returns + * G_SOURCE_REMOVE (FALSE). + * + * Solution: + * Call g_source_destroy() before the new timeout source starts. + * After this callback returns G_SOURCE_REMOVE (FALSE), g_main_dispatch() + * detects the inactivity timeout source destroyed and skips same action. + */ + StopConnInactivityTimeout(); + + CloseActiveConnections(); + + return G_SOURCE_REMOVE; +} + + +/* + *----------------------------------------------------------------------------- + * + * ClientConnErrorCb -- + * + * Client connection error handler for asyncsocket. + * + * Results: + * The connection is closed. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +ClientConnErrorCb(int err, // IN + AsyncSocket *asock, // IN + void *clientData) // IN +{ + ClientConnInfo *clientConn = (ClientConnInfo *)clientData; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(clientConn->asock != NULL); + g_info("Client connection %d error callback: %s\n", + AsyncSocket_GetFd(clientConn->asock), + AsyncSocket_Err2String(err)); + + if (clientConn->isCurrent) { + ASSERT(currentClientConn == clientConn); + HandleCurrentClientConnError(); + } else { + CloseClientConn(clientConn); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * CurrentClientConnSendCb -- + * + * Callback function after sent to the current client connection. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +CurrentClientConnSendCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData) // IN +{ + //g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(currentClientConn == clientData); + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + if (AsyncSocket_GetState(currentClientConn->asock) != + AsyncSocketConnected) { + /* + * This callback may be called after the connection is closed for + * freeing the send buffer. + */ + return; + } + + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->timeoutSource != NULL); + + /* + * Restart connection inactivity timeout. + */ + StopConnInactivityTimeout(); + StartConnInactivityTimeout(); + + if (currentClientConn->shutDown) { + g_info("Finished with current client connection %d.\n", + AsyncSocket_GetFd(currentClientConn->asock)); + + CloseCurrentClientConn(); + StartServeNextClientConn(); + } else { + ASSERT(theVmxConn->bytesRemaining > 0); + + RecvContentFromVmxConn(); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * CurrentClientConnRecvHttpRequestCb -- + * + * Callback function after received from the current client connection. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +CurrentClientConnRecvHttpRequestCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData) // IN +{ + int fd; + int recvLen; + char *next_token; + char *requestMethod; + char *requestPath; + + g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(currentClientConn == clientData); + ASSERT(currentClientConn != NULL); + ASSERT(currentClientConn->asock != NULL); + + fd = AsyncSocket_GetFd(currentClientConn->asock); + + recvLen = (int)((char *)buf - currentClientConn->buf) + len; + if (recvLen >= currentClientConn->bufLen) { + g_warning("Recv from current client connection %d " + "reached buffer limit.\n", fd); + goto error; + } + + /* + * Check for HTTP request end. + */ + if (recvLen < HTTP_HEADER_END_LEN || + strncmp(currentClientConn->buf + recvLen - HTTP_HEADER_END_LEN, + HTTP_HEADER_END, HTTP_HEADER_END_LEN) != 0) { + RecvHttpRequestFromCurrentClientConn(currentClientConn->buf + + recvLen, + currentClientConn->bufLen - + recvLen); + return; + } + + StopCurrentClientConnRecvTimeout(); + + *(currentClientConn->buf + recvLen) = '\0'; + g_debug("HTTP request from current client connection %d:\n%s\n", + fd, currentClientConn->buf); + + requestMethod = strtok_r(currentClientConn->buf, " ", &next_token); + if (NULL == requestMethod || + strcmp(requestMethod, HTTP_REQ_METHOD_GET) != 0) { + g_warning("Invalid HTTP request method.\n"); + goto error; + } + + /* + * Ignore HTTP query part. + */ + requestPath = strtok_r(NULL, "? ", &next_token); + if (NULL == requestPath) { + g_warning("HTTP request path not found.\n"); + goto error; + } + + currentClientConn->requestPath = g_uri_unescape_string(requestPath, NULL); + if (NULL == currentClientConn->requestPath || + '/' != *currentClientConn->requestPath || + strlen(currentClientConn->requestPath) > GUESTSTORE_CONTENT_PATH_MAX) { + g_warning("Invalid HTTP request path.\n"); + goto error; + } + + g_info("HTTP request path from current client connection %d: \"%s\"", + fd, currentClientConn->requestPath); + + StopRecvFromCurrentClientConn(); + + if (!pluginData.vmxConnectRequested) { + ASSERT(theVmxConn == NULL); + SendConnectRequestToVmx(); + } else { + CheckSendRequestDataMapToVmxConn(); + } + + return; + +error: + HandleCurrentClientConnError(); +} + + +/* + *----------------------------------------------------------------------------- + * + * ClientConnectCb -- + * + * Poll callback function for a new client connection. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +ClientConnectCb(AsyncSocket *asock, // IN + void *clientData) // IN +{ + int fd = AsyncSocket_GetFd(asock); + int maxConnections; + ClientConnInfo *clientConn = NULL; + int res; + + g_debug("Entering %s.\n", __FUNCTION__); + g_info("Got new client connection %d.\n", fd); + + if (AsyncSocket_GetState(asock) != AsyncSocketConnected) { + g_info("Client connection %d is not in connected state.\n", fd); + goto error; + } + + maxConnections = GUESTSTORE_CONFIG_GET_INT("maxConnections", + DEFAULT_MAX_CLIENT_CONNECTIONS); + if ((g_list_length(pluginData.clientConnWaitList) + + ((currentClientConn != NULL) ? 1 : 0)) >= maxConnections) { + g_info("Client connection %d has exceeded maximum limit " + "of %d client connections.\n", fd, maxConnections); + goto error; + } + +#ifdef _WIN32 + CheckAndUpdateAdminOnly(); + + /* coverity[negative_returns] */ + if (pluginData.adminOnly && !IsAdminClient(fd)) { + g_info("Decline non admin/root client connection %d.\n", fd); + goto error; + } +#endif + + if (!AsyncSocket_EstablishMinBufferSizes(asock, + GUESTSTORE_RESPONSE_BUFFER_SIZE, // sendSz + GUESTSTORE_REQUEST_BUFFER_SIZE)) { // recvSz + g_warning("AsyncSocket_EstablishMinBufferSizes failed " + "on client connection %d.\n", fd); + goto error; + } + + clientConn = (ClientConnInfo *)Util_SafeCalloc(1, sizeof *clientConn); + clientConn->asock = asock; + + res = AsyncSocket_SetErrorFn(asock, ClientConnErrorCb, clientConn); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_SetErrorFn failed on client connection %d: %s\n", + fd, AsyncSocket_Err2String(res)); + goto error; + } + + if (currentClientConn == NULL) { + /* + * Make the first client connection be the current client connection. + */ + currentClientConn = clientConn; + currentClientConn->isCurrent = TRUE; + StartRecvHttpRequestFromCurrentClientConn(); + } else { + pluginData.clientConnWaitList = g_list_append( + pluginData.clientConnWaitList, clientConn); + } + + return; + +error: + g_info("Closing client connection %d.\n", fd); + AsyncSocket_Close(asock); + free(clientConn); +} + + +/* + *----------------------------------------------------------------------------- + * + * VmxConnErrorCb -- + * + * The VMX connection error handler for asyncsocket. + * + * Results: + * The connection is closed. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +VmxConnErrorCb(int err, // IN + AsyncSocket *asock, // IN + void *clientData) // IN +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(theVmxConn == clientData); + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + g_info("VMX connection %d error callback: %s\n", + AsyncSocket_GetFd(theVmxConn->asock), + AsyncSocket_Err2String(err)); + + HandleVmxConnError(); +} + + +/* + *----------------------------------------------------------------------------- + * + * VmxConnSendDataMapCb -- + * + * Callback function after sent to the VMX connection. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +VmxConnSendDataMapCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData) // IN +{ + int fd; + + g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(theVmxConn == clientData); + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + fd = AsyncSocket_GetFd(theVmxConn->asock); + + if (AsyncSocket_GetState(theVmxConn->asock) != AsyncSocketConnected) { + /* + * This callback may be called after the connection is closed for + * freeing the send buffer. + */ + return; + } + + if (theVmxConn->shutDown) { + g_info("Shut down VMX connection %d.\n", fd); + CloseVmxConn(); + + if (pluginData.guestStoreAccessEnabled) { + if (currentClientConn == NULL) { + StartServeNextClientConn(); + } else if (ReceivedHttpRequestFromCurrentClientConn()) { + SendConnectRequestToVmx(); + } + } + } else { + RecvDataMapFromVmxConn(&theVmxConn->dataMapLen, + (int)sizeof(theVmxConn->dataMapLen)); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * VmxConnRecvDataMapCb -- + * + * Callback function after received data map from the VMX connection. + * + * VMX responds with a data map, followed by content bytes if no error. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +VmxConnRecvDataMapCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData) // IN +{ + int fd; + + g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(theVmxConn == clientData); + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + fd = AsyncSocket_GetFd(theVmxConn->asock); + + if (buf == &theVmxConn->dataMapLen) { + int dataMapLen = ntohl(theVmxConn->dataMapLen); + + ASSERT(len == sizeof theVmxConn->dataMapLen); + + if (dataMapLen > (theVmxConn->bufLen - sizeof theVmxConn->dataMapLen)) { + g_warning("Data map from VMX connection %d " + "is too large: length=%d.\n", fd, dataMapLen); + goto error; + } + + *((int32 *)(theVmxConn->buf)) = theVmxConn->dataMapLen; + RecvDataMapFromVmxConn(theVmxConn->buf + sizeof theVmxConn->dataMapLen, + dataMapLen); + } else { + ErrorCode res; + DataMap map; + + ASSERT(buf == (theVmxConn->buf + sizeof theVmxConn->dataMapLen)); + ASSERT(len == ntohl(theVmxConn->dataMapLen)); + + res = DataMap_Deserialize(theVmxConn->buf, + len + (int)sizeof(theVmxConn->dataMapLen), + &map); + if (res != DMERR_SUCCESS) { + g_warning("DataMap_Deserialize failed for data map " + "from VMX connection %d: error=%d.\n", fd, res); + goto error; + } + + StopRecvFromVmxConn(); + ProcessVmxDataMap(&map); + DataMap_Destroy(&map); + } + + return; + +error: + HandleVmxConnError(); +} + + +/* + *----------------------------------------------------------------------------- + * + * VmxConnRecvContentCb -- + * + * Callback function after received content bytes from the VMX connection. + * + * VMX responds with a data map, followed by content bytes if no error. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +VmxConnRecvContentCb(void *buf, // IN + int len, // IN + AsyncSocket *asock, // IN + void *clientData) // IN +{ + //g_debug("Entering %s: len=%d.\n", __FUNCTION__, len); + + ASSERT(theVmxConn == clientData); + ASSERT(theVmxConn != NULL); + ASSERT(theVmxConn->asock != NULL); + + theVmxConn->bytesRemaining -= len; + if (theVmxConn->bytesRemaining < 0) { + g_warning("Recv from VMX connection %d exceeded content size.\n", + AsyncSocket_GetFd(theVmxConn->asock)); + HandleVmxConnError(); + return; + } + + StopRecvFromVmxConn(); + + if (theVmxConn->bytesRemaining == 0) { + currentClientConn->shutDown = TRUE; + } + + SendToCurrentClientConn(buf, len); +} + + +/* + *----------------------------------------------------------------------------- + * + * VmxConnectCb -- + * + * Poll callback function for a new VMX connection. + * + * Results: + * None + * + * Side-effects: + * None + *----------------------------------------------------------------------------- + */ + +static void +VmxConnectCb(AsyncSocket *asock, // IN + void *clientData) // IN +{ + int fd = AsyncSocket_GetFd(asock); + int res; + + g_debug("Entering %s.\n", __FUNCTION__); + g_info("Got new VMX connection %d.\n", fd); + + StopVmxToGuestConnTimeout(); + + if (!pluginData.vmxConnectRequested) { + g_warning("Closing the unexpected VMX connection %d.\n", fd); + AsyncSocket_Close(asock); + return; + } + + if (theVmxConn != NULL) { + g_warning("The VMX connection already exists, closing the extra " + "VMX connection %d.\n", fd); + AsyncSocket_Close(asock); + return; + } + + if (AsyncSocket_GetState(asock) != AsyncSocketConnected) { + g_info("VMX connection %d is not in connected state.\n", fd); + goto error; + } + + if (!AsyncSocket_EstablishMinBufferSizes(asock, + GUESTSTORE_REQUEST_BUFFER_SIZE, // sendSz + GUESTSTORE_RESPONSE_BUFFER_SIZE)) { // recvSz + g_warning("AsyncSocket_EstablishMinBufferSizes failed " + "on VMX connection %d.\n", fd); + goto error; + } + + theVmxConn = (VmxConnInfo *)Util_SafeCalloc(1, sizeof *theVmxConn); + + theVmxConn->asock = asock; + + res = AsyncSocket_SetErrorFn(asock, VmxConnErrorCb, theVmxConn); + if (res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_SetErrorFn failed " + "on VMX connection %d: %s\n", + fd, AsyncSocket_Err2String(res)); + goto error; + } + + theVmxConn->bufLen = VMX_CONN_SEND_RECV_BUF_SIZE; + theVmxConn->buf = Util_SafeMalloc(theVmxConn->bufLen); + + theVmxConn->connTimeout = GUESTSTORE_CONFIG_GET_INT("connTimeout", + GUESTSTORE_DEFAULT_CONN_TIMEOUT); + if (theVmxConn->connTimeout <= 0) { + g_warning("Invalid connTimeout (%d); Using default (%d).\n", + theVmxConn->connTimeout, GUESTSTORE_DEFAULT_CONN_TIMEOUT); + theVmxConn->connTimeout = GUESTSTORE_DEFAULT_CONN_TIMEOUT; + } + + StartConnInactivityTimeout(); + + if (currentClientConn == NULL) { + StartServeNextClientConn(); + } else { + CheckSendRequestDataMapToVmxConn(); + } + + return; + +error: + g_info("Closing VMX connection %d.\n", fd); + AsyncSocket_Close(asock); + if (theVmxConn != NULL) { + free(theVmxConn); + theVmxConn = NULL; + } + + CloseCurrentClientConn(); + CloseClientConnsInWait(); + pluginData.vmxConnectRequested = FALSE; +} + + +/* + *----------------------------------------------------------------------------- + * + * CreateVmxListenSocket -- + * + * Create listening vsocket to accept connection from VMX. + * The auto-assigned port number will be sent to VMX via guest RPC. + * + * Results: + * TRUE on success, FALSE otherwise + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +CreateVmxListenSocket(void) +{ + int res = ASOCKERR_SUCCESS; + AsyncSocket *asock; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(pluginData.vmxListenSock == NULL); + + asock = AsyncSocket_ListenVMCI(VMCISock_GetLocalCID(), VMADDR_PORT_ANY, + VmxConnectCb, NULL, NULL, &res); + if (NULL == asock || res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_ListenVMCI failed: %s\n", + AsyncSocket_Err2String(res)); + if (asock != NULL) { + AsyncSocket_Close(asock); + } + return FALSE; + } + + pluginData.vmxListenSock = asock; + + return TRUE; +} + + +#ifdef _WIN32 + +/* + *----------------------------------------------------------------------------- + * + * CreateClientListenSocket -- + * + * Create listening socket to accept connections from clients. + * + * Results: + * TRUE on success, FALSE otherwise + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +CreateClientListenSocket(void) +{ + AsyncSocket *asock = NULL; + uint16 port; + PortUsage portUseMap[GUESTSTORE_LOOPBACK_PORT_MAX - + GUESTSTORE_LOOPBACK_PORT_MIN + 1] = { 0 }; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(pluginData.clientListenSock == NULL); + + /* + * Use output of GetPortUseMap as a hint, it does not matter + * if GetPortUseMap fails. + */ + GetPortUseMap(GUESTSTORE_LOOPBACK_PORT_MIN, + GUESTSTORE_LOOPBACK_PORT_MAX, + portUseMap); + + for (port = GUESTSTORE_LOOPBACK_PORT_MIN; + port <= GUESTSTORE_LOOPBACK_PORT_MAX; + port++) { + int res = ASOCKERR_SUCCESS; + PortUsage *portUse = &portUseMap[port - GUESTSTORE_LOOPBACK_PORT_MIN]; + + /* + * Use || instead of && to avoid confusion to see a port used by + * one service on tcp but another service on tcp6. + */ + if (portUse->inet4 || portUse->inet6) { + continue; + } + + asock = AsyncSocket_ListenLoopback(port, ClientConnectCb, NULL, NULL, &res); + if (asock != NULL) { + break; + } + + if (res == ASOCKERR_BINDADDRINUSE || res == ASOCK_EADDRINUSE) { + g_info("Port %u is already in use.\n", port); + } else { + g_warning("AsyncSocket_ListenLoopback failed on port %u: %s\n", + port, AsyncSocket_Err2String(res)); + break; + } + } + + if (asock == NULL) { + return FALSE; + } + + pluginData.clientListenSock = asock; + return TRUE; +} + +#else + +/* + *----------------------------------------------------------------------------- + * + * CreateSocketDir -- + * + * Create the UNIX domain socket directory with proper permissions. + * + * Results: + * Return TRUE if the directory is created or already exists, and the + * permissions are set properly. Otherwise FALSE. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +CreateSocketDir(const char *sockDir) // IN +{ + Bool retVal; + int mode = 0755; // Same mode as VGAuth service socket dir + struct stat st; + + ASSERT(sockDir != NULL && *sockDir != '\0'); + + retVal = File_EnsureDirectoryEx(sockDir, mode); + if (!retVal) { + g_warning("Unable to create folder %s: error=%d.\n", + sockDir, errno); + goto exit; + } + + /* + * Verify the directory owner and permissions. + */ + if (Posix_Lstat(sockDir, &st) != 0) { + g_warning("Unable to retrieve the attributes of %s: error=%d.\n", + sockDir, errno); + retVal = FALSE; + goto exit; + } + + if (st.st_uid != getuid()) { + g_warning("%s has the wrong owner.\n", sockDir); + retVal = FALSE; + goto exit; + } + + if ((st.st_mode & 0777) != mode && + !File_SetFilePermissions(sockDir, (st.st_mode & 07000) | mode)) { + g_warning("%s has improper permissions.\n", sockDir); + retVal = FALSE; + } + +exit: + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * AdjustSocketFilePermissions -- + * + * Adjust the UNIX domain socket file permissions to control who can + * connect. + * + * Results: + * Return TRUE if the permissions are set properly. Otherwise FALSE. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +AdjustSocketFilePermissions(const char *sockFile, // IN + Bool onlyRootCanConnect) // IN +{ + Bool retVal = FALSE; + int mode; + struct stat st; + + ASSERT(sockFile != NULL && *sockFile != '\0'); + + /* + * Add sticky bit if everyone can connect. + */ + mode = (onlyRootCanConnect ? 0755 : 01777); + + if (Posix_Lstat(sockFile, &st) != 0) { + g_warning("Unable to retrieve the attributes of %s: error=%d.\n", + sockFile, errno); + goto exit; + } + + if ((st.st_mode & 01777) != mode && + !File_SetFilePermissions(sockFile, (st.st_mode & 07000) | mode)) { + g_warning("%s has improper permissions.\n", sockFile); + goto exit; + } + + retVal = TRUE; + +exit: + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * CreateClientListenSocket -- + * + * Create listening socket to accept connections from clients. + * + * Results: + * TRUE on success, FALSE otherwise + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +CreateClientListenSocket(void) +{ + int res = ASOCKERR_SUCCESS; + AsyncSocket *asock; + + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(pluginData.clientListenSock == NULL); + + CheckAndUpdateAdminOnly(); + + if (!CreateSocketDir(GUESTSTORE_PIPE_DIR)) { + g_warning("CreateSocketDir failed.\n"); + return FALSE; + } + + File_Unlink(GUESTSTORE_PIPE_NAME); + + asock = AsyncSocket_ListenSocketUDS( + GUESTSTORE_PIPE_NAME, + ClientConnectCb, NULL, + NULL, &res); + + if (asock == NULL || res != ASOCKERR_SUCCESS) { + g_warning("AsyncSocket_ListenSocketUDS failed: %s\n", + AsyncSocket_Err2String(res)); + + if (asock != NULL) { + AsyncSocket_Close(asock); + } + + return FALSE; + } + + /* + * Ideally, this should be done after bind() and before listen() and + * accept(). Since asyncsocket library shares TCP socket implementation + * code, there is no such interface to do it. Doing it here is fine, + * because the initial permission settings allow root to connect only. + */ + if (!AdjustSocketFilePermissions(GUESTSTORE_PIPE_NAME, + pluginData.adminOnly)) { + g_warning("AdjustSocketFilePermissions failed.\n"); + AsyncSocket_Close(asock); + return FALSE; + } + + pluginData.clientListenSock = asock; + return TRUE; +} + +#endif + + +/* + *----------------------------------------------------------------------------- + * + * InitPluginData -- + * + * Init pluginData structure. + * - 'ctx': ToolsAppCtx + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +InitPluginData(ToolsAppCtx *ctx) // IN +{ + pluginData.ctx = ctx; + CheckAndUpdateFeatureDisabled(); + CheckAndUpdateAdminOnly(); +} + + +/* + *----------------------------------------------------------------------------- + * + * InitPluginSignals -- + * + * Init signals for notification. + * - 'ctx': ToolsAppCtx + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +InitPluginSignals(ToolsAppCtx* ctx) // IN +{ + /* Register the signals we'll use to notify people interested in this event. */ + g_signal_new(TOOLS_CORE_SIG_GUESTSTORE_STATE, + G_OBJECT_TYPE(ctx->serviceObj), + 0, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); +} + + +/* + *---------------------------------------------------------------------------- + * + * GuestStoreAccessDisable -- + * + * Close all sockets/connections and reset plugin internal states. + * + * Results: + * None + * + * Side-effects: + * None + * + *---------------------------------------------------------------------------- + */ + +static void +GuestStoreAccessDisable(void) +{ + g_debug("Entering %s.\n", __FUNCTION__); + + if (!pluginData.shutdown) { + g_signal_emit_by_name(pluginData.ctx->serviceObj, + TOOLS_CORE_SIG_GUESTSTORE_STATE, + FALSE); + } + + pluginData.guestStoreAccessEnabled = FALSE; + + if (pluginData.vmxListenSock) { + AsyncSocket_Close(pluginData.vmxListenSock); + pluginData.vmxListenSock = NULL; + } + + if (pluginData.clientListenSock) { + AsyncSocket_Close(pluginData.clientListenSock); + pluginData.clientListenSock = NULL; + } + + CloseCurrentClientConn(); + CloseClientConnsInWait(); + + if (theVmxConn != NULL && !theVmxConn->shutDown) { + /* + * After CloseCurrentClientConn(), send shutdown data map to VMX. + */ + SendDataMapToVmxConn(); + } else { + /* + * Force to stop. + */ + CloseVmxConn(); + StopVmxToGuestConnTimeout(); + pluginData.vmxConnectRequested = FALSE; // To make sure + } +} + + +/* + *---------------------------------------------------------------------------- + * + * GuestStoreAccessEnable -- + * + * Create the sockets and start listening. + * + * Results: + * None + * + * Side-effects: + * None + * + *---------------------------------------------------------------------------- + */ + +static void +GuestStoreAccessEnable(void) +{ + g_debug("Entering %s.\n", __FUNCTION__); + + ASSERT(!pluginData.guestStoreAccessEnabled); + + if (!CreateVmxListenSocket() || !CreateClientListenSocket()) { + g_warning("GuestStore access is disabled " + "due to initialization error.\n"); + GuestStoreAccessDisable(); + return; + } + + pluginData.guestStoreAccessEnabled = TRUE; + g_signal_emit_by_name(pluginData.ctx->serviceObj, + TOOLS_CORE_SIG_GUESTSTORE_STATE, + TRUE); +} + + +/* + *----------------------------------------------------------------------------- + * + * GetVmxGuestStoreAccessEnabledState -- + * + * Send a GuestRpc command to VMX to retrieve guestStore.accessEnabled + * state. + * + * Result: + * TRUE : access enabled + * FALSE: access disabled + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +GetVmxGuestStoreAccessEnabledState(void) +{ + Bool retVal; + const char *msg = "guestStore.accessEnabled"; + char *result = NULL; + size_t resultLen; + + retVal = RpcChannel_Send(pluginData.ctx->rpc, msg, strlen(msg), + &result, &resultLen); + if (retVal) { + retVal = (strcmp(result, "true") == 0 ? TRUE : FALSE); + } else { + g_warning("Failed to send accessEnabled message to VMX: %s.\n", + result != NULL ? result : ""); + } + + vm_free(result); + + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreShutdown -- + * + * Disable GuestStore access before shutdown. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +GuestStoreShutdown(void) +{ + pluginData.shutdown = TRUE; + g_object_set(pluginData.ctx->serviceObj, TOOLS_PLUGIN_SVC_PROP_GUESTSTORE, + NULL, NULL); + + if (pluginData.guestStoreAccessEnabled) { + GuestStoreAccessDisable(); + } +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreConfReload -- + * + * Disable/enable GuestStore access after guest side config change. + * + * Results: + * None + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static void +GuestStoreConfReload(gpointer src, // IN + ToolsAppCtx *ctx, // IN + gpointer data) // IN +{ + Bool featureDisabled = IsFeatureDisabled(); + + if (pluginData.featureDisabled != featureDisabled) { + pluginData.featureDisabled = featureDisabled; + + if (pluginData.guestStoreAccessEnabled && featureDisabled) { + g_info("Disable GuestStore access after guest side " + "config change.\n"); + GuestStoreAccessDisable(); + } else if (!pluginData.guestStoreAccessEnabled && + !featureDisabled && + GetVmxGuestStoreAccessEnabledState()) { + g_info("Enable GuestStore access after guest side " + "config change.\n"); + GuestStoreAccessEnable(); + } + } else { + Bool adminOnly = IsAdminOnly(); + + if (pluginData.adminOnly != adminOnly) { + pluginData.adminOnly = adminOnly; + + if (pluginData.guestStoreAccessEnabled) { + g_info("Reset GuestStore access after guest side " + "config change.\n"); + GuestStoreAccessDisable(); + GuestStoreAccessEnable(); + } + } + } +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreReset -- + * + * VMX connection ASOCKERR_REMOTE_DISCONNECT (4) error callback is not + * seen on Windows guests after suspend/resume, address this in tools + * reset signal handler. + * + * Results: + * None + * + * Side-effects: + * Client connections will also be interrupted by tools reset due to + * sporadic guest hang. + * + *----------------------------------------------------------------------------- + */ + +static void +GuestStoreReset(gpointer src, // IN + ToolsAppCtx *ctx, // IN + gpointer data) // IN +{ + if (theVmxConn != NULL) { +#ifdef _WIN32 + /* + * After suspend/resume, VMX side vsocket is closed, VMX connection is + * broken, but VmxConnErrorCb() is not called on Windows guests. + * We still send shutdown data map to VMX connection here. We see + * AsyncSocket_Send() succeeds and either VmxConnSendDataMapCb or + * VmxConnErrorCb() is called in tests. This minimizes impact on + * sporadic guest hang case where VMX connection is not broken and + * we want VMX to close its side vsocket proactively. + */ + g_info("Perform tools reset by closing active connections.\n"); + CloseActiveConnections(); +#endif + } else if (pluginData.vmxConnectRequested) { + /* + * Closing pluginData.vmxListenSock cancels pending VmxConnectCb() call, + * second call of AsyncSocket_ListenVMCI() results in a new vsocket + * listening port number. + */ + g_info("Perform tools reset without VMX connection " + "but VMX connect request was made.\n"); + GuestStoreAccessDisable(); // Calls StopVmxToGuestConnTimeout() + if (pluginData.guestStoreAccessEnabled && + !CheckAndUpdateFeatureDisabled()) { + GuestStoreAccessEnable(); + } + } +} + + +/* + *---------------------------------------------------------------------------- + * + * GuestStoreSetOption -- + * + * Handle TOOLSOPTION_ENABLE_GUESTSTORE_ACCESS Set_Option callback. + * + * Results: + * TRUE on success. + * + * Side-effects: + * None + * + *---------------------------------------------------------------------------- + */ + +static gboolean +GuestStoreSetOption(gpointer src, // IN + ToolsAppCtx *ctx, // IN + const gchar *option, // IN + const gchar *value, // IN + gpointer data) // IN +{ + gboolean retVal = FALSE; + + if (strcmp(option, TOOLSOPTION_ENABLE_GUESTSTORE_ACCESS) == 0) { + g_debug("Tools set option %s=%s.\n", + TOOLSOPTION_ENABLE_GUESTSTORE_ACCESS, value); + + if (strcmp(value, "1") == 0 && + !pluginData.guestStoreAccessEnabled) { + if (CheckAndUpdateFeatureDisabled()) { + g_info("GuestStore access is disabled on guest side.\n"); + } else { + GuestStoreAccessEnable(); + retVal = TRUE; + } + } else if (strcmp(value, "0") == 0 && + pluginData.guestStoreAccessEnabled) { + GuestStoreAccessDisable(); + retVal = TRUE; + } + } + + return retVal; +} + + +/* + *----------------------------------------------------------------------------- + * + * ToolsOnLoad -- + * + * Return the registration data for the GuestStore plugin. + * + * Results: + * Return the registration data. + * + * Side-effects: + * None + * + *----------------------------------------------------------------------------- + */ + +TOOLS_MODULE_EXPORT ToolsPluginData * +ToolsOnLoad(ToolsAppCtx *ctx) // IN +{ + static ToolsPluginData regData = { "guestStore", NULL, NULL, NULL }; + static ToolsPluginSvcGuestStore svcGuestStore = { GuestStoreShutdown }; + + ToolsServiceProperty propGuestStore = { TOOLS_PLUGIN_SVC_PROP_GUESTSTORE }; + + uint32 vmxVersion = 0; + uint32 vmxType = VMX_TYPE_UNSET; + + ToolsPluginSignalCb sigs[] = { + { TOOLS_CORE_SIG_CONF_RELOAD, GuestStoreConfReload, NULL }, + { TOOLS_CORE_SIG_RESET, GuestStoreReset, NULL }, + { TOOLS_CORE_SIG_SET_OPTION, GuestStoreSetOption, NULL } + }; + ToolsAppReg regs[] = { + { TOOLS_APP_SIGNALS, + VMTools_WrapArray(sigs, sizeof *sigs, ARRAYSIZE(sigs)) } + }; + + /* + * Return NULL to disable the plugin if not running in vmsvc daemon. + */ + if (!TOOLS_IS_MAIN_SERVICE(ctx)) { + g_info("Not running in vmsvc daemon: container name='%s'.\n", + ctx->name); + return NULL; + } + + /* + * Return NULL to disable the plugin if not running in a VMware VM. + */ + if (!ctx->isVMware) { + g_info("Not running in a VMware VM.\n"); + return NULL; + } + + /* + * Return NULL to disable the plugin if VM is not running on ESX host. + */ + if (!VmCheck_GetVersion(&vmxVersion, &vmxType) || + vmxType != VMX_TYPE_SCALABLE_SERVER) { + g_info("VM is not running on ESX host.\n"); + return NULL; + } + + InitPluginData(ctx); + InitPluginSignals(ctx); + Poll_InitGtk(); + + ctx->registerServiceProperty(ctx->serviceObj, &propGuestStore); + g_object_set(ctx->serviceObj, TOOLS_PLUGIN_SVC_PROP_GUESTSTORE, + &svcGuestStore, NULL); + + regData.regs = VMTools_WrapArray(regs, sizeof *regs, ARRAYSIZE(regs)); + + return ®Data; +} diff --git a/open-vm-tools/services/vmtoolsd/mainLoop.c b/open-vm-tools/services/vmtoolsd/mainLoop.c index 7189caa38..32d74b0b7 100644 --- a/open-vm-tools/services/vmtoolsd/mainLoop.c +++ b/open-vm-tools/services/vmtoolsd/mainLoop.c @@ -45,8 +45,9 @@ #include "vmware/tools/utils.h" #include "vmware/tools/vmbackup.h" -#if defined(_WIN32) -#include "vmware/tools/guestStore.h" +#if defined(_WIN32) || \ + (defined(__linux__) && !defined(USERWORLD)) +# include "vmware/tools/guestStore.h" #endif #if defined(_WIN32) @@ -109,7 +110,8 @@ static gboolean gGlobalConfEnabled = FALSE; static void ToolsCoreCleanup(ToolsServiceState *state) { -#if defined(_WIN32) +#if defined(_WIN32) || \ + (defined(__linux__) && !defined(USERWORLD)) if (state->mainService) { /* * Shut down guestStore plugin first to prevent worker threads from being diff --git a/open-vm-tools/toolbox/Makefile.am b/open-vm-tools/toolbox/Makefile.am index 75f81b2a3..000d391d0 100644 --- a/open-vm-tools/toolbox/Makefile.am +++ b/open-vm-tools/toolbox/Makefile.am @@ -1,5 +1,5 @@ ################################################################################ -### Copyright (C) 2007-2018 VMware, Inc. All rights reserved. +### Copyright (c) 2007-2018,2020 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 @@ -31,6 +31,9 @@ vmware_toolbox_cmd_SOURCES += toolbox-cmd.c vmware_toolbox_cmd_SOURCES += toolboxcmd-config.c vmware_toolbox_cmd_SOURCES += toolboxcmd-devices.c vmware_toolbox_cmd_SOURCES += toolboxcmd-info.c +if LINUX + vmware_toolbox_cmd_SOURCES += toolboxcmd-gueststore.c +endif vmware_toolbox_cmd_SOURCES += toolboxcmd-logging.c vmware_toolbox_cmd_SOURCES += toolboxcmd-scripts.c vmware_toolbox_cmd_SOURCES += toolboxcmd-shrink.c diff --git a/open-vm-tools/toolbox/toolbox-cmd.c b/open-vm-tools/toolbox/toolbox-cmd.c index 89eaadbcc..f50d013ef 100644 --- a/open-vm-tools/toolbox/toolbox-cmd.c +++ b/open-vm-tools/toolbox/toolbox-cmd.c @@ -112,6 +112,9 @@ static CmdTable commands[] = { #if defined(_WIN32) || \ (defined(__linux__) && !defined(OPEN_VM_TOOLS) && !defined(USERWORLD)) { "upgrade", Upgrade_Command, TRUE, TRUE, Upgrade_Help}, +#endif +#if defined(_WIN32) || \ + (defined(__linux__) && !defined(USERWORLD)) { "gueststore", GuestStore_Command, TRUE, FALSE, GuestStore_Help}, #endif #if defined(_WIN32) diff --git a/open-vm-tools/toolbox/toolboxCmdInt.h b/open-vm-tools/toolbox/toolboxCmdInt.h index 88190e39c..005eb0ae5 100644 --- a/open-vm-tools/toolbox/toolboxCmdInt.h +++ b/open-vm-tools/toolbox/toolboxCmdInt.h @@ -149,6 +149,10 @@ DECLARE_COMMAND(Config); #if defined(_WIN32) || \ (defined(__linux__) && !defined(OPEN_VM_TOOLS) && !defined(USERWORLD)) DECLARE_COMMAND(Upgrade); +#endif + +#if defined(_WIN32) || \ + (defined(__linux__) && !defined(USERWORLD)) DECLARE_COMMAND(GuestStore); #endif diff --git a/open-vm-tools/toolbox/toolboxcmd-gueststore.c b/open-vm-tools/toolbox/toolboxcmd-gueststore.c new file mode 100644 index 000000000..9972f5d6f --- /dev/null +++ b/open-vm-tools/toolbox/toolboxcmd-gueststore.c @@ -0,0 +1,288 @@ +/********************************************************* + * Copyright (C) 2019-2020 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-gueststore.c -- + * + * GuestStore getcontent operation for toolbox-cmd. + */ + + +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#include +#endif + +#include "vm_assert.h" +#include "vm_basic_defs.h" +#include "toolboxCmdInt.h" +#include "vmware/tools/i18n.h" +#include "guestStoreClient.h" + + +/* + * GuestStore client library error messages + * + * Localization can be done in this way if needed: + * + * #define GUESTSTORE_LIB_ERR_ITEM(a, b, c) MSGID(b) c, + * + * call this to get localized error message: + * + * VMTools_GetString(VMW_TEXT_DOMAIN, guestStoreLibErrMsgs[errCode]) + */ +#define GUESTSTORE_LIB_ERR_ITEM(a, b, c) c, +static const char * const guestStoreLibErrMsgs[] = { +GUESTSTORE_LIB_ERR_LIST +}; +#undef GUESTSTORE_LIB_ERR_ITEM + +/* + * Passed in from main(), command line --quiet (q) option. + */ +static gboolean gQuiet = FALSE; + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreReportProgress -- + * + * Report progress from GuestStore client library. + * + * Results: + * TRUE to continue getting content. + * FALSE to cancel getting content. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +static Bool +GuestStoreReportProgress(int64 fileSize, // IN + int64 bytesReceived, // IN + void *clientData) // IN +{ + static Bool first = TRUE; + static int lastProgress = 0; + int percentage; + int progress; + int i; + + if (gQuiet) { + return TRUE; + } + + if (first) { + g_print("%s", SU_(gueststore.content_size, + "Content size in bytes: ")); + + g_print("%" FMT64 "d\n", fileSize); + + first = FALSE; + } + + percentage = (int)((bytesReceived * 100) / fileSize); + progress = percentage / 5; // increment by every 5% + if (progress == lastProgress) { + return TRUE; + } + + lastProgress = progress; + + g_print(SU_(gueststore.progress, + "\rProgress: %d%%"), + percentage); + g_print(" ["); + + for (i = 0; i < progress; i++) { + putchar('='); + } + + g_print(">%*c", 20 - i, ']'); + fflush(stdout); + + if (percentage == 100) { + g_print("\n"); + } + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStoreRemovePathEnclosingQuotes -- + * + * Remove the beginning and ending quotes in a path. + * + * Results: + * Path without enclosing quotes. + * + * Side effects: + * Input path may have been changed. + * + *----------------------------------------------------------------------------- + */ + +static char * +GuestStoreRemovePathEnclosingQuotes(char *path) // IN/OUT +{ + char *lastChar; + + if (*path != '"') { + return path; + } + + path++; + lastChar = path + strlen(path) - 1; + if (*lastChar == '"') { + *lastChar = '\0'; + } + + return path; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStore_Command -- + * + * Handle and parse gueststore command. + * + * Results: + * 0 on success. + * + * Error code from GuestStore client library or + * general process error exit code. + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +int +GuestStore_Command(char **argv, // IN: Command line arguments + int argc, // IN: Length of the command line arguments + gboolean quiet) // IN +{ + int exitCode; + char *contentPath; + char *outputPath; + + if (toolbox_strcmp(argv[optind], "getcontent") != 0) { + ToolsCmd_UnknownEntityError(argv[0], + SU_(arg.subcommand, "subcommand"), + argv[optind]); + return EX_USAGE; + } + + if (optind != (argc - 3)) { + return EX_USAGE; + } + + gQuiet = quiet; + + if (!GuestStoreClient_Init()) { + g_critical("GuestStoreClient_Init failed.\n"); + exitCode = EX_SOFTWARE; + goto exit; + } + + contentPath = GuestStoreRemovePathEnclosingQuotes(argv[argc - 2]); + outputPath = GuestStoreRemovePathEnclosingQuotes(argv[argc - 1]); + + exitCode = GuestStoreClient_GetContent(contentPath, outputPath, + GuestStoreReportProgress, NULL); + if (exitCode != GSLIBERR_SUCCESS) { + g_critical("GuestStoreClient_GetContent failed: error=%d.\n", exitCode); + } + + if (!GuestStoreClient_DeInit()) { + g_warning("GuestStoreClient_DeInit failed.\n"); + } + +exit: + + if (exitCode == GSLIBERR_SUCCESS) { + ToolsCmd_Print(SU_(result.succeeded, + "'%s' succeeded.\n"), + argv[optind]); + } else if (exitCode < GUESTSTORE_LIB_ERR_MAX) { + ToolsCmd_PrintErr(SU_(gueststore.error.client_lib, + "'%s' failed, GuestStore client library " + "error: %s.\n"), + argv[optind], guestStoreLibErrMsgs[exitCode]); + } else { + ToolsCmd_PrintErr(SU_(result.error.failed, + "'%s' failed, check %s log for " + "more information.\n"), + argv[optind], argv[0]); + } + + return exitCode; +} + + +/* + *----------------------------------------------------------------------------- + * + * GuestStore_Help -- + * + * Prints the help for the gueststore command. + * + * Results: + * None + * + * Side effects: + * None + * + *----------------------------------------------------------------------------- + */ + +void +GuestStore_Help(const char *progName, // IN: Name of the program from argv[0] + const char *cmd) // IN +{ + g_print(SU_(help.gueststore, + "%s: get resource content from GuestStore\n" + "Usage: %s %s \n\n" + "ESX guests only subcommands:\n" + " getcontent : " + "get resource content from GuestStore and " + "save to output file.\n\n" + " starts with / and represents a unique " + "resource in GuestStore. If it ends with /, defaults to " + "retrieve the underlying 'metadata.json' resource.\n" + " is the path of a file " + "to save resource content to.\n"), + cmd, progName, cmd); +}