From: Oliver Kurth Date: Fri, 26 Oct 2018 17:44:55 +0000 (-0700) Subject: Allow only a single instance of vmusr when multiple users are logged into a VM X-Git-Tag: stable-11.0.0~390 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6e7835970e73978db54bef24731d4a3d49cc9e2a;p=thirdparty%2Fopen-vm-tools.git Allow only a single instance of vmusr when multiple users are logged into a VM When a vmusr process gets the "channel conflict" error while attempting to open the toolbox-dnd channel, a channel reset is triggered. That reset results in the channel being restarted and a subsequent conflict and reset occurs - every second until the channel becomes available. For *nix guests: The fix is making use of the repetitive channel resets where the only RpcIn message received is a "reset" to catch this channel "permanently" unavailable state. If other RpcIn messages are received, a channel is considered to be working and the cumulative error count is cleared.. lib/rpcin/rpcin.c: - struct RpcIn: Added error status boolean and callback function to notify the dependent layer that a channel error has been resolved. - RpcInLoop(): If a non "reset" message is received, clear any channel error status. This will also notify the dependent layer that the channel is functioning. - RpcIn_start(): Added additional argument for new callback; NULL if not needed. lib/rpcChannel/rpcChannel.c: - struct rpcChannelInt: - Renamed "rpcErrorCount" to "rpcResetErrorCount" since it is actually a count of the consecutive channel reset failures and not a count of RpcChannel errors. - Added counter "rpcFailureCount" for the cumulative channel errors. - Added "rpcFailureCb" for optional callback to notify the app of a "permanent" channel failure. - New function RpcChannelClearError() for RpcIn to notify when the channel is working; to clear the rpcFailureCount . - RpcChannel_Setup() - added two arguments for (1) an optional function to be called when there is a channel failure and (2) a failure count threshold. These optional values are stored in the RpcChannel structure being created. - RpcChannelError(): Added logic to notify the calling app if the error threshold has been reached and notify the app if a callback was provided. A zero threshold signifies the single vmusr limit should not be enforced. (fix disable switch). services/vmtoolsd/mainLoop.c: - New function ToolsCore_GetVmusrLimit() to retrieve the channel error threshold default or over-ride from tools.conf. services/vmtoolsd/toolsRpc.c: - Added ToolsCoreAppChannelFail(): Callback for "permanent" channel connection failure. A warning is logged based on whether another "vmtoolsd -n vmusr" is running or not and the process is terminated. On Mac OS, the process is terminated with exit(1) as an indication to launchd that the vmusr process should not automatically be restarted. The current implementation uses the error callback only for the vmusr server on Linux (*nix). The default channel error limit is 5 (approx. 5 second), but is user configurable in tools.conf. [vmusr] maxChannelAttempts = n # where allowed n = 0, 3-15 When "maxChannelAttempts = 0" is used, the restriction to a single running vmusr process is not enforced. The existing behavior is restored with all the accompanying VMX log spew. This is essentially a user configurable feature disablement switch. --- diff --git a/open-vm-tools/lib/include/rpcin.h b/open-vm-tools/lib/include/rpcin.h index 64b815514..5f06546e8 100644 --- a/open-vm-tools/lib/include/rpcin.h +++ b/open-vm-tools/lib/include/rpcin.h @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2007-2016 VMware, Inc. All rights reserved. + * Copyright (C) 2007-2018 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 @@ -33,6 +33,8 @@ extern "C" { typedef void RpcIn_ErrorFunc(void *clientData, char const *status); +typedef void RpcIn_ClearErrorFunc(void *clientData); + typedef struct RpcIn RpcIn; #if defined(VMTOOLS_USE_GLIB) /* { */ @@ -44,7 +46,9 @@ RpcIn *RpcIn_Construct(GMainContext *mainCtx, gpointer clientData); Bool RpcIn_start(RpcIn *in, unsigned int delay, - RpcIn_ErrorFunc *errorFunc, void *errorData); + RpcIn_ErrorFunc *errorFunc, + RpcIn_ClearErrorFunc *clearErrorFunc, + void *errorData); #else /* } { */ @@ -66,7 +70,10 @@ RpcIn *RpcIn_Construct(DblLnkLst_Links *eventQueue); Bool RpcIn_start(RpcIn *in, unsigned int delay, RpcIn_Callback resetCallback, void *resetClientData, - RpcIn_ErrorFunc *errorFunc, void *errorData); + RpcIn_ErrorFunc *errorFunc, + RpcIn_ClearErrorFunc *clearErrorFunc, + void *errorData); + /* * Don't use this function anymore - it's here only for backwards compatibility. diff --git a/open-vm-tools/lib/include/vmware/tools/guestrpc.h b/open-vm-tools/lib/include/vmware/tools/guestrpc.h index 695fbb1b3..768b6ba15 100644 --- a/open-vm-tools/lib/include/vmware/tools/guestrpc.h +++ b/open-vm-tools/lib/include/vmware/tools/guestrpc.h @@ -125,6 +125,15 @@ typedef void (*RpcChannelResetCb)(RpcChannel *chan, gboolean success, gpointer data); +/** + * Signature for the application callback function when unable to establish + * an RpcChannel connection. + * + * @param[in] _state Client data. + */ +typedef void (*RpcChannelFailureCb)(gpointer _state); + + gboolean RpcChannel_Start(RpcChannel *chan); @@ -160,7 +169,9 @@ RpcChannel_Setup(RpcChannel *chan, GMainContext *mainCtx, gpointer appCtx, RpcChannelResetCb resetCb, - gpointer resetData); + gpointer resetData, + RpcChannelFailureCb failureCb, + guint maxFailures); void RpcChannel_RegisterCallback(RpcChannel *chan, diff --git a/open-vm-tools/lib/rpcChannel/rpcChannel.c b/open-vm-tools/lib/rpcChannel/rpcChannel.c index e6c7e0db7..6fc511c8a 100644 --- a/open-vm-tools/lib/rpcChannel/rpcChannel.c +++ b/open-vm-tools/lib/rpcChannel/rpcChannel.c @@ -52,7 +52,15 @@ typedef struct RpcChannelInt { RpcChannelResetCb resetCb; gpointer resetData; gboolean rpcError; - guint rpcErrorCount; + guint rpcResetErrorCount; /* channel reset failures */ + /* + * The rpcFailureCount is a cumulative count of calls made to + * RpcChannelError(). When getting repeated channel failures, + * the channel is constantly stopped and restarted. + */ + guint rpcFailureCount; /* cumulative channel failures */ + RpcChannelFailureCb rpcFailureCb; + guint rpcMaxFailures; #endif } RpcChannelInt; @@ -122,7 +130,7 @@ RpcChannelRestart(gpointer _chan) chanStarted = RpcChannel_Start(&chan->impl); g_static_mutex_unlock(&chan->impl.outLock); if (!chanStarted) { - Warning("Channel restart failed [%d]\n", chan->rpcErrorCount); + Warning("Channel restart failed [%d]\n", chan->rpcResetErrorCount); if (chan->resetCb != NULL) { chan->resetCb(&chan->impl, FALSE, chan->resetData); } @@ -153,9 +161,9 @@ RpcChannelCheckReset(gpointer _chan) if (chan->rpcError) { GSource *src; - if (++(chan->rpcErrorCount) > channelTimeoutAttempts) { + if (++(chan->rpcResetErrorCount) > channelTimeoutAttempts) { Warning("Failed to reset channel after %u attempts\n", - chan->rpcErrorCount - 1); + chan->rpcResetErrorCount - 1); if (chan->resetCb != NULL) { chan->resetCb(&chan->impl, FALSE, chan->resetData); } @@ -163,7 +171,7 @@ RpcChannelCheckReset(gpointer _chan) } /* Schedule the channel restart for 1 sec in the future. */ - Debug(LGPFX "Resetting channel [%u]\n", chan->rpcErrorCount); + Debug(LGPFX "Resetting channel [%u]\n", chan->rpcResetErrorCount); src = g_timeout_source_new(1000); g_source_set_callback(src, RpcChannelRestart, chan, NULL); g_source_attach(src, chan->mainCtx); @@ -173,7 +181,7 @@ RpcChannelCheckReset(gpointer _chan) /* Reset was successful. */ Debug(LGPFX "Channel was reset successfully.\n"); - chan->rpcErrorCount = 0; + chan->rpcResetErrorCount = 0; Debug(LGPFX "Clearing backdoor behavior ...\n"); gVSocketFailed = FALSE; @@ -428,6 +436,8 @@ exit: * @param[in] appCtx Application context. * @param[in] resetCb Callback for when a reset occurs. * @param[in] resetData Client data for the reset callback. + * @param[in] failureCB Callback for when the channel failure limit is hit. + * @param[in] maxFailures Maximum channel failures allowed. */ void @@ -436,7 +446,9 @@ RpcChannel_Setup(RpcChannel *chan, GMainContext *mainCtx, gpointer appCtx, RpcChannelResetCb resetCb, - gpointer resetData) + gpointer resetData, + RpcChannelFailureCb failureCb, + guint maxFailures) { size_t i; RpcChannelInt *cdata = (RpcChannelInt *) chan; @@ -446,6 +458,8 @@ RpcChannel_Setup(RpcChannel *chan, cdata->mainCtx = g_main_context_ref(mainCtx); cdata->resetCb = resetCb; cdata->resetData = resetData; + cdata->rpcFailureCb = failureCb; + cdata->rpcMaxFailures = maxFailures; cdata->resetReg.name = "reset"; cdata->resetReg.callback = RpcChannelReset; @@ -513,6 +527,24 @@ RpcChannel_UnregisterCallback(RpcChannel *chan, } +/** + * Callback function to clear the cumulative channel error count when RpcIn + * is able to establish a working connection following an error or reset. + * + * @param[in] _chan The RPC channel. + */ + +static void +RpcChannelClearError(void *_chan) +{ + RpcChannelInt *chan = _chan; + + Debug(LGPFX " %s: Clearing cumulative RpcChannel error count; was %d\n", + __FUNCTION__, chan->rpcFailureCount); + chan->rpcFailureCount = 0; +} + + /** * Error handling function for the RPC channel. Enqueues the "check reset" * function for running later, if it's not yet enqueued. @@ -523,16 +555,32 @@ RpcChannel_UnregisterCallback(RpcChannel *chan, static void RpcChannelError(void *_chan, - char const *status) + char const *status) { RpcChannelInt *chan = _chan; + chan->rpcError = TRUE; + /* * XXX: Workaround for PR 935520. * Revert the log call to Warning() after fixing PR 955746. */ Debug(LGPFX "Error in the RPC receive loop: %s.\n", status); + /* + * If an RPC failure callback has been registered and the failure limit + * check has not been suppressed, check whether the RpcChannel failure + * limit has been reached. + */ + if (chan->rpcFailureCb != NULL && + chan->rpcMaxFailures > 0 && + ++chan->rpcFailureCount >= chan->rpcMaxFailures) { + /* Maximum number of channel errors has been reached. */ + Warning(LGPFX "RpcChannel failure limit reached; calling the failure " + "callback function.\n"); + chan->rpcFailureCb(chan->resetData); + } + if (chan->resetCheck == NULL) { chan->resetCheck = g_idle_source_new(); g_source_set_callback(chan->resetCheck, RpcChannelCheckReset, chan, NULL); @@ -596,6 +644,7 @@ RpcChannel_Destroy(RpcChannel *chan) cdata->resetCb = NULL; cdata->resetData = NULL; cdata->appCtx = NULL; + cdata->rpcFailureCb = NULL; g_free(cdata->appName); cdata->appName = NULL; @@ -771,7 +820,8 @@ RpcChannel_Start(RpcChannel *chan) #if defined(NEED_RPCIN) if (chan->in != NULL && !chan->inStarted) { - ok = RpcIn_start(chan->in, RPCIN_MAX_DELAY, RpcChannelError, chan); + ok = RpcIn_start(chan->in, RPCIN_MAX_DELAY, RpcChannelError, + RpcChannelClearError, chan); chan->inStarted = ok; } #endif diff --git a/open-vm-tools/lib/rpcIn/rpcin.c b/open-vm-tools/lib/rpcIn/rpcin.c index c2c515837..8eea7d86b 100644 --- a/open-vm-tools/lib/rpcIn/rpcin.c +++ b/open-vm-tools/lib/rpcIn/rpcin.c @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 1998-2017 VMware, Inc. All rights reserved. + * Copyright (C) 1998-2018 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 @@ -178,6 +178,13 @@ struct RpcIn { */ Bool inLoop; // RpcInLoop is running. Bool shouldStop; // Stop the channel the next time RpcInLoop exits. + + /* + * RpcInConnErrorHandler called; cleared when a non "reset" reply has been + * received. + */ + Bool errStatus; + RpcIn_ClearErrorFunc *clearErrorFunc; }; static Bool RpcInSend(RpcIn *in, int flags); @@ -1070,6 +1077,8 @@ RpcInConnErrorHandler(int err, // IN Debug("RpcIn: Error in socket %d, closing connection: %s.\n", AsyncSocket_GetFd(asock), AsyncSocket_Err2String(err)); + in->errStatus = TRUE; + if (conn->connected) { RpcInCloseChannel(conn->in, errmsg); } else { /* the connection never gets connected */ @@ -1489,6 +1498,26 @@ exit: } +/* + * RpcInClearErrorStatus -- + * + * Clear the errStatus indicator and if a callback has been registered, + * notify the RpcChannel layer that an error condition has been cleared. + */ + +static void +RpcInClearErrorStatus(RpcIn *in) // IN +{ + if (in->errStatus) { + Debug("RpcIn: %s: Clearing errStatus\n", __FUNCTION__); + in->errStatus = FALSE; + if (in->clearErrorFunc != NULL) { + in->clearErrorFunc(in->errorData); + } + } +} + + /* *----------------------------------------------------------------------------- * @@ -1569,6 +1598,12 @@ RpcInLoop(void *clientData) // IN if (repLen) { char *s = ByteDump(reply, repLen); Debug("RpcIn: received %d bytes, content:\"%s\"\n", (int) repLen, s); + + /* If reply is not a "reset", the channel is functioning. */ + if (in->errStatus && strcmp(s, "reset") != 0) { + RpcInClearErrorStatus(in); + } + if (!RpcInExecRpc(in, reply, repLen, &errmsg)) { goto error; } @@ -1584,6 +1619,11 @@ RpcInLoop(void *clientData) // IN lastPrintMilli = now; } + /* RpcIn connection is working - receiving. */ + if (in->errStatus) { + RpcInClearErrorStatus(in); + } + /* * Nothing to execute */ @@ -1792,18 +1832,21 @@ error: #if defined(VMTOOLS_USE_GLIB) Bool -RpcIn_start(RpcIn *in, // IN - unsigned int delay, // IN - RpcIn_ErrorFunc *errorFunc, // IN - void *errorData) // IN +RpcIn_start(RpcIn *in, // IN + unsigned int delay, // IN + RpcIn_ErrorFunc *errorFunc, // IN + RpcIn_ClearErrorFunc *clearErrorFunc, // IN + void *errorData) // IN + #else Bool -RpcIn_start(RpcIn *in, // IN - unsigned int delay, // IN - RpcIn_Callback resetCallback, // IN - void *resetClientData, // IN - RpcIn_ErrorFunc *errorFunc, // IN - void *errorData) // IN +RpcIn_start(RpcIn *in, // IN + unsigned int delay, // IN + RpcIn_Callback resetCallback, // IN + void *resetClientData, // IN + RpcIn_ErrorFunc *errorFunc, // IN + RpcIn_ClearErrorFunc *clearErrorFunc, // IN + void *errorData) // IN #endif { ASSERT(in); @@ -1811,6 +1854,7 @@ RpcIn_start(RpcIn *in, // IN in->delay = 0; in->maxDelay = delay; in->errorFunc = errorFunc; + in->clearErrorFunc = clearErrorFunc; in->errorData = errorData; /* No initial result */ diff --git a/open-vm-tools/services/vmtoolsd/mainLoop.c b/open-vm-tools/services/vmtoolsd/mainLoop.c index 88d18fd9f..fdbf5078a 100644 --- a/open-vm-tools/services/vmtoolsd/mainLoop.c +++ b/open-vm-tools/services/vmtoolsd/mainLoop.c @@ -33,6 +33,7 @@ #include "conf.h" #include "guestApp.h" #include "serviceObj.h" +#include "str.h" #include "system.h" #include "util.h" #include "vmcheck.h" @@ -43,6 +44,34 @@ #include "vmware/tools/utils.h" #include "vmware/tools/vmbackup.h" + +/* + * Establish the default and maximum vmusr RPC channel error limits + * that will be used to detect that the single allowed toolbox-dnd channel + * is not available. + */ + +/* + * Lowest number of RPC channel errors to reasonably indicate that the + * single allowed toolbox-dnd channel is currently in use by another + * process. + */ +#define VMUSR_CHANNEL_ERR_MIN 3 /* approximately 3 secs. */ + +/* + * The default number of vmusr channel errors before quitting the vmusr + * process start-up. + */ +#define VMUSR_CHANNEL_ERR_DEFAULT 5 /* approximately 5 secs. */ + +/* + * Arbitrary upper vmusr channel error count limit. + */ +#define VMUSR_CHANNEL_ERR_MAX 15 /* approximately 15 secs. */ + +#define CONFNAME_MAX_CHANNEL_ATTEMPTS "maxChannelAttempts" + + /* ****************************************************************************** * ToolsCoreCleanup -- */ /** @@ -490,6 +519,44 @@ ToolsCore_DumpState(ToolsServiceState *state) } +/** + * Return the RpcChannel failure threshold for the tools user service. + * + * @param[in] state The service state. + * + * @return The RpcChannel failure limit for the user tools service. + */ + +guint +ToolsCore_GetVmusrLimit(ToolsServiceState *state) // IN +{ + gint errorLimit = 0; /* Special value 0 means no error threshold. */ + + if (TOOLS_IS_USER_SERVICE(state)) { + errorLimit = VMTools_ConfigGetInteger(state->ctx.config, + state->ctx.name, + CONFNAME_MAX_CHANNEL_ATTEMPTS, + VMUSR_CHANNEL_ERR_DEFAULT); + + /* + * A zero value is allowed and will disable the single vmusr + * process restriction. + */ + if (errorLimit != 0 && + (errorLimit < VMUSR_CHANNEL_ERR_MIN || + errorLimit > VMUSR_CHANNEL_ERR_MAX)) { + g_warning("%s: Invalid %s: %s (%d) specified in tools configuration; " + "using default value (%d)\n", __FUNCTION__, + state->ctx.name, CONFNAME_MAX_CHANNEL_ATTEMPTS, + errorLimit, VMUSR_CHANNEL_ERR_DEFAULT); + errorLimit = VMUSR_CHANNEL_ERR_DEFAULT; + } + } + + return errorLimit; +} + + /** * Returns the name of the TCLO app name. This will only return non-NULL * if the service is either the tools "guestd" or "userd" service. diff --git a/open-vm-tools/services/vmtoolsd/toolsCoreInt.h b/open-vm-tools/services/vmtoolsd/toolsCoreInt.h index 3095fd418..006d58f83 100644 --- a/open-vm-tools/services/vmtoolsd/toolsCoreInt.h +++ b/open-vm-tools/services/vmtoolsd/toolsCoreInt.h @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2008-2016 VMware, Inc. All rights reserved. + * Copyright (C) 2008-2018 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 @@ -113,6 +113,9 @@ ToolsCore_DumpPluginInfo(ToolsServiceState *state); void ToolsCore_DumpState(ToolsServiceState *state); +guint +ToolsCore_GetVmusrLimit(ToolsServiceState *state); + const char * ToolsCore_GetTcloName(ToolsServiceState *state); diff --git a/open-vm-tools/services/vmtoolsd/toolsRpc.c b/open-vm-tools/services/vmtoolsd/toolsRpc.c index aaf6c1d6d..21c3290ba 100644 --- a/open-vm-tools/services/vmtoolsd/toolsRpc.c +++ b/open-vm-tools/services/vmtoolsd/toolsRpc.c @@ -110,6 +110,73 @@ ToolsCoreCheckReset(RpcChannel *chan, } +#if !defined(_WIN32) +/** + * ToolsCoreAppChannelFail -- + * + * Call-back function for RpcChannel to report that the RPC channel for the + * toolbox-dnd (vmusr) application cannot be acquired. This would signify + * that the channel is currently in use by another vmusr process. + * + * @param[in] _state The service state. + */ + +static void +ToolsCoreAppChannelFail(UNUSED_PARAM(gpointer _state)) +{ + char *cmdGrepVmusrTools; +#if !defined(__APPLE__) + ToolsServiceState *state = _state; +#endif +#if defined(__linux__) || defined(__FreeBSD__) || defined(sun) + static const char *vmusrGrepExpr = "'vmtoolsd.*vmusr'"; +#if defined(sun) + static const char *psCmd = "ps -aef"; +#else + static const char *psCmd = "ps ax"; /* using BSD syntax */ +#endif +#else /* Mac OS */ + static const char *vmusrGrepExpr = "'vmware-tools-daemon.*vmusr'"; + static const char *psCmd = "ps -ex"; +#endif + + cmdGrepVmusrTools = Str_Asprintf(NULL, "%s | egrep %s | egrep -v 'grep|%d'", + psCmd, vmusrGrepExpr, (int) getpid()); + + /* + * Check if there is another vmtoolsd vmusr process running on the + * system and log the appropriate warning message before terminating + * this vmusr process. + */ + if (system(cmdGrepVmusrTools) == 0) { + g_warning("Exiting the vmusr process. Another vmusr process is " + "currently running.\n"); + } else { + g_warning("Exiting the vmusr process; unable to acquire the channel.\n"); + } + free(cmdGrepVmusrTools); + +#if !defined(__APPLE__) + if (g_main_loop_is_running(state->ctx.mainLoop)) { + g_warning("Calling g_main_loop_quit() to terminate the process.\n"); + g_main_loop_quit(state->ctx.mainLoop); + } else { + g_warning("Exiting the process.\n"); + exit(1); + } +#else /* Mac OS */ + /* + * On Mac OS X, always exit with non-zero status. This is a signal to + * launchd that the vmusr process had a "permanent" failure and should + * not be automatically restarted for this user session. + */ + g_warning("Exiting the process.\n"); + exit(1); +#endif +} +#endif + + /** * Checks all loaded plugins for their capabilities, and sends the data to the * host. The code will try to send all capabilities, just logging errors as @@ -322,12 +389,31 @@ ToolsCore_InitRpc(ToolsServiceState *state) } if (state->ctx.rpc) { + + /* + * Default tools RpcChannel setup: No channel error threshold limit and + * no notification callback function. + */ + RpcChannelFailureCb failureCb = NULL; + guint errorLimit = 0; + +#if !defined(_WIN32) + + /* For the *nix user service app. */ + if (TOOLS_IS_USER_SERVICE(state)) { + failureCb = ToolsCoreAppChannelFail; + errorLimit = ToolsCore_GetVmusrLimit(state); + } +#endif + RpcChannel_Setup(state->ctx.rpc, app, mainCtx, &state->ctx, ToolsCoreCheckReset, - state); + state, + failureCb, + errorLimit); /* Register the "built in" RPCs. */ for (i = 0; i < ARRAYSIZE(rpcs); i++) {