From de108d3fc06dd9de404c497dfa9faa26956f43e3 Mon Sep 17 00:00:00 2001
From: Jakob Unterwurzacher <jakobunt@gmail.com>
Date: Sat, 2 Jan 2021 18:11:18 +0100
Subject: [PATCH] -idle: don't lazy-unmount

When a process has its working dir inside the mount,
the only way we notice is that we get EBUSY when trying
to unmount.

We used to lazy-unmount in this case, but this means
pulling the rug from under the process.

For example, bash will start throwing

  cd: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory

messages.

Fixes https://github.com/rfjakob/gocryptfs/issues/533
---
 Documentation/MANPAGE.md |  3 +++
 README.md                |  1 +
 mount.go                 | 28 +++++++++++++++++++++-------
 3 files changed, 25 insertions(+), 7 deletions(-)

diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md
index a984aa6..5febe90 100644
--- a/Documentation/MANPAGE.md
+++ b/Documentation/MANPAGE.md
@@ -241,6 +241,9 @@ Only for forward mode: automatically unmount the filesystem if it has been idle
 for the specified duration. Durations can be specified like "500s" or "2h45m".
 0 (the default) means stay mounted indefinitely.
 
+When a process has open files or its working directory in the mount,
+this will keep it not idle indefinitely.
+
 #### -kernel_cache
 Enable the kernel_cache option of the FUSE filesystem, see fuse(8) for details.
 
diff --git a/README.md b/README.md
index 6b59cf0..1014471 100644
--- a/README.md
+++ b/README.md
@@ -192,6 +192,7 @@ Changelog
 
 vNEXT, in progress
 * MANPAGE: Split options into sections acc. to where they apply ([#517](https://github.com/rfjakob/gocryptfs/issues/517))
+* `-idle`: count cwd inside the mount as busy ([#533](https://github.com/rfjakob/gocryptfs/issues/533))
 
 v2.0-beta2, 2020-11-14
 * Improve [performance](Documentation/performance.txt#L69)
diff --git a/mount.go b/mount.go
index 9240afc..e4a36d1 100644
--- a/mount.go
+++ b/mount.go
@@ -175,11 +175,15 @@ func doMount(args *argContainer) {
 const checksDuringTimeoutPeriod = 4
 
 func idleMonitor(idleTimeout time.Duration, fs *fusefrontend.RootNode, srv *fuse.Server, mountpoint string) {
-	sleepTimeBetweenChecks := contentenc.MinUint64(
+	// sleepNs is the sleep time between checks, in nanoseconds.
+	sleepNs := contentenc.MinUint64(
 		uint64(idleTimeout/checksDuringTimeoutPeriod),
 		uint64(2*time.Minute))
-	timeoutCycles := int(math.Ceil(float64(idleTimeout) / float64(sleepTimeBetweenChecks)))
+	timeoutCycles := int(math.Ceil(float64(idleTimeout) / float64(sleepNs)))
 	idleCount := 0
+	idleTime := func() time.Duration {
+		return time.Duration(sleepNs * uint64(idleCount))
+	}
 	for {
 		// Atomically check whether the flag is 0 and reset it to 1 if so.
 		isIdle := !atomic.CompareAndSwapUint32(&fs.IsIdle, 0, 1)
@@ -191,13 +195,21 @@ func idleMonitor(idleTimeout time.Duration, fs *fusefrontend.RootNode, srv *fuse
 			idleCount++
 		}
 		tlog.Debug.Printf(
-			"Checking for idle (isIdle = %t, open = %d): %s",
-			isIdle, openFileCount, time.Now().String())
+			"idleMonitor: idle for %v (idleCount = %d, isIdle = %t, open = %d)",
+			idleTime(), idleCount, isIdle, openFileCount)
 		if idleCount > 0 && idleCount%timeoutCycles == 0 {
-			tlog.Info.Printf("Filesystem idle; unmounting: %s", mountpoint)
-			unmount(srv, mountpoint)
+			tlog.Info.Printf("idleMonitor: filesystem idle; unmounting: %s", mountpoint)
+			err := srv.Unmount()
+			if err != nil {
+				// We get "Device or resource busy" when a process has its
+				// working directory on the mount. Log the event at Info level
+				// so the user finds out why their filesystem does not get
+				// unmounted.
+				tlog.Info.Printf("idleMonitor: unmount failed: %v. Resetting idle time.", err)
+				idleCount = 0
+			}
 		}
-		time.Sleep(time.Duration(sleepTimeBetweenChecks))
+		time.Sleep(time.Duration(sleepNs))
 	}
 }
 
@@ -483,6 +495,8 @@ func handleSigint(srv *fuse.Server, mountpoint string) {
 	}()
 }
 
+// unmount() calls srv.Unmount(), and if that fails, calls "fusermount -u -z"
+// (lazy unmount).
 func unmount(srv *fuse.Server, mountpoint string) {
 	err := srv.Unmount()
 	if err != nil {