From 82da364b8b9c6073e4af58cddc85357674a1a2e5 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sun, 14 Dec 2014 23:27:57 +0100
Subject: [PATCH] lib/nfs/Connection: implement mount timeout

---
 NEWS                       |  1 +
 src/lib/nfs/Connection.cxx | 24 ++++++++++++++++++++++++
 src/lib/nfs/Connection.hxx |  9 +++++++--
 3 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index d20dba397..c84f4b891 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ ver 0.19.7 (not yet released)
   - nfs: fix crash while canceling a failing file open operation
   - nfs: fix memory leak on connection failure
   - nfs: fix reconnect after mount failure
+  - nfs: implement mount timeout (60 seconds)
 * playlist
   - don't skip non-existent songs in "listplaylist"
 * fix memory allocator bug on Windows
diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx
index 4b6a6fc78..9d9cd8a52 100644
--- a/src/lib/nfs/Connection.cxx
+++ b/src/lib/nfs/Connection.cxx
@@ -34,6 +34,8 @@ extern "C" {
 
 #include <poll.h> /* for POLLIN, POLLOUT */
 
+static constexpr unsigned NFS_MOUNT_TIMEOUT = 60;
+
 inline bool
 NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
 					 const char *path,
@@ -379,6 +381,11 @@ NfsConnection::DestroyContext()
 	in_destroy = true;
 #endif
 
+	if (!mount_finished) {
+		assert(TimeoutMonitor::IsActive());
+		TimeoutMonitor::Cancel();
+	}
+
 	/* cancel pending DeferredMonitor that was scheduled to notify
 	   new leases */
 	DeferredMonitor::Cancel();
@@ -528,6 +535,9 @@ NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs,
 
 	mount_finished = true;
 
+	assert(TimeoutMonitor::IsActive() || in_destroy);
+	TimeoutMonitor::Cancel();
+
 	if (status < 0) {
 		postponed_mount_error.Format(nfs_domain, status,
 					     "nfs_mount_async() failed: %s",
@@ -560,6 +570,8 @@ NfsConnection::MountInternal(Error &error)
 	postponed_mount_error.Clear();
 	mount_finished = false;
 
+	TimeoutMonitor::ScheduleSeconds(NFS_MOUNT_TIMEOUT);
+
 #ifndef NDEBUG
 	in_service = false;
 	in_event = false;
@@ -620,6 +632,18 @@ NfsConnection::BroadcastError(Error &&error)
 	BroadcastMountError(std::move(error));
 }
 
+void
+NfsConnection::OnTimeout()
+{
+	assert(GetEventLoop().IsInside());
+	assert(!mount_finished);
+
+	mount_finished = true;
+	DestroyContext();
+
+	BroadcastMountError(Error(nfs_domain, "Mount timeout"));
+}
+
 void
 NfsConnection::RunDeferred()
 {
diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx
index 310ccdc44..3969a7e8f 100644
--- a/src/lib/nfs/Connection.hxx
+++ b/src/lib/nfs/Connection.hxx
@@ -23,6 +23,7 @@
 #include "Lease.hxx"
 #include "Cancellable.hxx"
 #include "event/SocketMonitor.hxx"
+#include "event/TimeoutMonitor.hxx"
 #include "event/DeferredMonitor.hxx"
 #include "util/Error.hxx"
 
@@ -40,7 +41,7 @@ class NfsCallback;
 /**
  * An asynchronous connection to a NFS server.
  */
-class NfsConnection : SocketMonitor, DeferredMonitor {
+class NfsConnection : SocketMonitor, TimeoutMonitor, DeferredMonitor {
 	class CancellableCallback : public CancellablePointer<NfsCallback> {
 		NfsConnection &connection;
 
@@ -142,7 +143,8 @@ public:
 	gcc_nonnull_all
 	NfsConnection(EventLoop &_loop,
 		      const char *_server, const char *_export_name)
-		:SocketMonitor(_loop), DeferredMonitor(_loop),
+		:SocketMonitor(_loop), TimeoutMonitor(_loop),
+		 DeferredMonitor(_loop),
 		 server(_server), export_name(_export_name),
 		 context(nullptr) {}
 
@@ -227,6 +229,9 @@ private:
 	/* virtual methods from SocketMonitor */
 	virtual bool OnSocketReady(unsigned flags) override;
 
+	/* virtual methods from TimeoutMonitor */
+	void OnTimeout() final;
+
 	/* virtual methods from DeferredMonitor */
 	virtual void RunDeferred() override;
 };