/*********************************************************
- * 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
typedef void RpcIn_ErrorFunc(void *clientData, char const *status);
+typedef void RpcIn_ClearErrorFunc(void *clientData);
+
typedef struct RpcIn RpcIn;
#if defined(VMTOOLS_USE_GLIB) /* { */
gpointer clientData);
Bool RpcIn_start(RpcIn *in, unsigned int delay,
- RpcIn_ErrorFunc *errorFunc, void *errorData);
+ RpcIn_ErrorFunc *errorFunc,
+ RpcIn_ClearErrorFunc *clearErrorFunc,
+ void *errorData);
#else /* } { */
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.
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);
GMainContext *mainCtx,
gpointer appCtx,
RpcChannelResetCb resetCb,
- gpointer resetData);
+ gpointer resetData,
+ RpcChannelFailureCb failureCb,
+ guint maxFailures);
void
RpcChannel_RegisterCallback(RpcChannel *chan,
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;
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);
}
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);
}
}
/* 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);
/* Reset was successful. */
Debug(LGPFX "Channel was reset successfully.\n");
- chan->rpcErrorCount = 0;
+ chan->rpcResetErrorCount = 0;
Debug(LGPFX "Clearing backdoor behavior ...\n");
gVSocketFailed = FALSE;
* @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
GMainContext *mainCtx,
gpointer appCtx,
RpcChannelResetCb resetCb,
- gpointer resetData)
+ gpointer resetData,
+ RpcChannelFailureCb failureCb,
+ guint maxFailures)
{
size_t i;
RpcChannelInt *cdata = (RpcChannelInt *) 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;
}
+/**
+ * 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.
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);
cdata->resetCb = NULL;
cdata->resetData = NULL;
cdata->appCtx = NULL;
+ cdata->rpcFailureCb = NULL;
g_free(cdata->appName);
cdata->appName = NULL;
#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
/*********************************************************
- * 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
*/
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);
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 */
}
+/*
+ * 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);
+ }
+ }
+}
+
+
/*
*-----------------------------------------------------------------------------
*
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;
}
lastPrintMilli = now;
}
+ /* RpcIn connection is working - receiving. */
+ if (in->errStatus) {
+ RpcInClearErrorStatus(in);
+ }
+
/*
* Nothing to execute
*/
#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);
in->delay = 0;
in->maxDelay = delay;
in->errorFunc = errorFunc;
+ in->clearErrorFunc = clearErrorFunc;
in->errorData = errorData;
/* No initial result */
#include "conf.h"
#include "guestApp.h"
#include "serviceObj.h"
+#include "str.h"
#include "system.h"
#include "util.h"
#include "vmcheck.h"
#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 -- */ /**
}
+/**
+ * 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.
/*********************************************************
- * 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
void
ToolsCore_DumpState(ToolsServiceState *state);
+guint
+ToolsCore_GetVmusrLimit(ToolsServiceState *state);
+
const char *
ToolsCore_GetTcloName(ToolsServiceState *state);
}
+#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
}
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++) {