[PATCH] RPC: proper soft timeout behavior for rpcbind

 Implement a best practice:  for soft mounts, an rpcbind timeout should
 cause an RPC request to fail.

 This also provides an FSM hook for retrying an rpcbind with a different
 rpcbind protocol version.  We'll use this later to try multiple rpcbind
 protocol versions when binding.  To enable this, expose the RPC error
 code returned during a portmap request to the FSM so it can make some
 decision about how to report, retry, or fail the request.

 Test-plan:
 Hundreds of passes with connectathon NFSv3 locking suite, on the client
 and server.

 Version: Thu, 11 Aug 2005 16:01:53 -0400

 Signed-off-by: Chuck Lever <cel@netapp.com>
 Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
index f17e615..2d3cf0a 100644
--- a/net/sunrpc/clnt.c
+++ b/net/sunrpc/clnt.c
@@ -53,6 +53,7 @@
 static void	call_encode(struct rpc_task *task);
 static void	call_decode(struct rpc_task *task);
 static void	call_bind(struct rpc_task *task);
+static void	call_bind_status(struct rpc_task *task);
 static void	call_transmit(struct rpc_task *task);
 static void	call_status(struct rpc_task *task);
 static void	call_refresh(struct rpc_task *task);
@@ -734,43 +735,94 @@
 call_bind(struct rpc_task *task)
 {
 	struct rpc_clnt	*clnt = task->tk_client;
-	struct rpc_xprt *xprt = clnt->cl_xprt;
 
-	dprintk("RPC: %4d call_bind xprt %p %s connected\n", task->tk_pid,
-			xprt, (xprt_connected(xprt) ? "is" : "is not"));
+	dprintk("RPC: %4d call_bind (status %d)\n",
+				task->tk_pid, task->tk_status);
 
-	task->tk_action = (xprt_connected(xprt)) ? call_transmit : call_connect;
-
+	task->tk_action = call_connect;
 	if (!clnt->cl_port) {
-		task->tk_action = call_connect;
+		task->tk_action = call_bind_status;
 		task->tk_timeout = RPC_CONNECT_TIMEOUT;
 		rpc_getport(task, clnt);
 	}
 }
 
 /*
- * 4a.	Connect to the RPC server (TCP case)
+ * 4a.	Sort out bind result
+ */
+static void
+call_bind_status(struct rpc_task *task)
+{
+	int status = -EACCES;
+
+	if (task->tk_status >= 0) {
+		dprintk("RPC: %4d call_bind_status (status %d)\n",
+					task->tk_pid, task->tk_status);
+		task->tk_status = 0;
+		task->tk_action = call_connect;
+		return;
+	}
+
+	switch (task->tk_status) {
+	case -EACCES:
+		dprintk("RPC: %4d remote rpcbind: RPC program/version unavailable\n",
+				task->tk_pid);
+		break;
+	case -ETIMEDOUT:
+		dprintk("RPC: %4d rpcbind request timed out\n",
+				task->tk_pid);
+		if (RPC_IS_SOFT(task)) {
+			status = -EIO;
+			break;
+		}
+		goto retry_bind;
+	case -EPFNOSUPPORT:
+		dprintk("RPC: %4d remote rpcbind service unavailable\n",
+				task->tk_pid);
+		break;
+	case -EPROTONOSUPPORT:
+		dprintk("RPC: %4d remote rpcbind version 2 unavailable\n",
+				task->tk_pid);
+		break;
+	default:
+		dprintk("RPC: %4d unrecognized rpcbind error (%d)\n",
+				task->tk_pid, -task->tk_status);
+		status = -EIO;
+		break;
+	}
+
+	rpc_exit(task, status);
+	return;
+
+retry_bind:
+	task->tk_status = 0;
+	task->tk_action = call_bind;
+	return;
+}
+
+/*
+ * 4b.	Connect to the RPC server
  */
 static void
 call_connect(struct rpc_task *task)
 {
-	struct rpc_clnt *clnt = task->tk_client;
+	struct rpc_xprt *xprt = task->tk_xprt;
 
-	dprintk("RPC: %4d call_connect status %d\n",
-				task->tk_pid, task->tk_status);
+	dprintk("RPC: %4d call_connect xprt %p %s connected\n",
+			task->tk_pid, xprt,
+			(xprt_connected(xprt) ? "is" : "is not"));
 
-	if (xprt_connected(clnt->cl_xprt)) {
-		task->tk_action = call_transmit;
-		return;
+	task->tk_action = call_transmit;
+	if (!xprt_connected(xprt)) {
+		task->tk_action = call_connect_status;
+		if (task->tk_status < 0)
+			return;
+		xprt_connect(task);
 	}
-	task->tk_action = call_connect_status;
-	if (task->tk_status < 0)
-		return;
-	xprt_connect(task);
 }
 
 /*
- * 4b. Sort out connect result
+ * 4c.	Sort out connect result
  */
 static void
 call_connect_status(struct rpc_task *task)
@@ -778,6 +830,9 @@
 	struct rpc_clnt *clnt = task->tk_client;
 	int status = task->tk_status;
 
+	dprintk("RPC: %5u call_connect_status (status %d)\n", 
+				task->tk_pid, task->tk_status);
+
 	task->tk_status = 0;
 	if (status >= 0) {
 		clnt->cl_stats->netreconn++;
@@ -785,17 +840,19 @@
 		return;
 	}
 
-	/* Something failed: we may have to rebind */
+	/* Something failed: remote service port may have changed */
 	if (clnt->cl_autobind)
 		clnt->cl_port = 0;
+
 	switch (status) {
 	case -ENOTCONN:
 	case -ETIMEDOUT:
 	case -EAGAIN:
-		task->tk_action = (clnt->cl_port == 0) ? call_bind : call_connect;
+		task->tk_action = call_bind;
 		break;
 	default:
 		rpc_exit(task, -EIO);
+		break;
 	}
 }