]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Allow only a single instance of vmusr when multiple users are logged into a VM
authorOliver Kurth <okurth@vmware.com>
Fri, 26 Oct 2018 17:44:55 +0000 (10:44 -0700)
committerOliver Kurth <okurth@vmware.com>
Fri, 26 Oct 2018 17:44:55 +0000 (10:44 -0700)
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.

open-vm-tools/lib/include/rpcin.h
open-vm-tools/lib/include/vmware/tools/guestrpc.h
open-vm-tools/lib/rpcChannel/rpcChannel.c
open-vm-tools/lib/rpcIn/rpcin.c
open-vm-tools/services/vmtoolsd/mainLoop.c
open-vm-tools/services/vmtoolsd/toolsCoreInt.h
open-vm-tools/services/vmtoolsd/toolsRpc.c

index 64b815514ea6abc9f1c84163b8aa97222c875fb0..5f06546e8095b7043394e71ea3d9263574bd6170 100644 (file)
@@ -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.
index 695fbb1b39929cb5e2dca2a939597da5a55bcc0b..768b6ba15980750967dc3225479798b384849ca4 100644 (file)
@@ -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,
index e6c7e0db71fae6e5787e4951ccfa50ab76282079..6fc511c8a953139f813adefffce77f906952ef93 100644 (file)
@@ -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
index c2c51583760523f1cfacba6d646dc616b5a8c103..8eea7d86b4b03a26b95cc0b47cfb1e8a6ef0de11 100644 (file)
@@ -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 */
index 88d18fd9f46a4ed0dee3e1dd09f95801c855b196..fdbf5078a247778318a76736d35b1cca8fadc4bf 100644 (file)
@@ -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"
 #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.
index 3095fd4180ed8b75173dbb1658313160d6c338a8..006d58f8344c2625567ea1a28a96c8e09c290c03 100644 (file)
@@ -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);
 
index aaf6c1d6dbf8e5a8f2183753ad1759020a7b3481..21c3290bad6f510b0e77aeb39cec0fc6a686e2bd 100644 (file)
@@ -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++) {