From 87d3ed9187cb9caccaa8df0769d4cb722cc5bd00 Mon Sep 17 00:00:00 2001 From: Jesse Dunietz Date: Sat, 6 Oct 2018 15:49:33 -0400 Subject: Add option for autounmount Even though filesystem notifications aren't implemented for FUSE, I decided to try my hand at implementing the autounmount feature (#128). I based it on the EncFS autounmount code, which records filesystem accesses and checks every X seconds whether it's idled long enough to unmount. I've tested the feature locally, but I haven't added any tests for this flag. I also haven't worked with Go before. So please let me know if there's anything that should be done differently. One particular concern: I worked from the assumption that the open files table is unique per-filesystem. If that's not true, I'll need to add an open file count and associated lock to the Filesystem type instead. https://github.com/rfjakob/gocryptfs/pull/265 --- mount.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 13 deletions(-) (limited to 'mount.go') diff --git a/mount.go b/mount.go index f473e1e..b4b6e61 100644 --- a/mount.go +++ b/mount.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "log/syslog" + "math" "net" "os" "os/exec" @@ -14,6 +15,7 @@ import ( "runtime" "runtime/debug" "strings" + "sync/atomic" "syscall" "time" @@ -29,6 +31,7 @@ import ( "github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse" "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/openfiletable" "github.com/rfjakob/gocryptfs/internal/tlog" ) @@ -98,7 +101,7 @@ func doMount(args *argContainer) { fs, wipeKeys := initFuseFrontend(args) // Initialize go-fuse FUSE server srv := initGoFuse(fs, args) - // Try to wipe secrect keys from memory after unmount + // Try to wipe secret keys from memory after unmount defer wipeKeys() tlog.Info.Println(tlog.ColorGreen + "Filesystem mounted and ready." + tlog.ColorReset) @@ -137,10 +140,49 @@ func doMount(args *argContainer) { // Return memory that was allocated for scrypt (64M by default!) and other // stuff that is no longer needed to the OS debug.FreeOSMemory() + // Set up autounmount, if requested. + if args.idle > 0 && !args.reverse { + // Not being in reverse mode means we always have a forward file system. + fwdFs := fs.(*fusefrontend.FS) + go idleMonitor(args.idle, fwdFs, srv, args.mountpoint) + } // Jump into server loop. Returns when it gets an umount request from the kernel. srv.Serve() } +// Based on the EncFS idle monitor: +// https://github.com/vgough/encfs/blob/1974b417af189a41ffae4c6feb011d2a0498e437/encfs/main.cpp#L851 +// idleMonitor is a function to be run as a thread that checks for +// filesystem idleness and unmounts if we've been idle for long enough. +const checksDuringTimeoutPeriod = 4 + +func idleMonitor(idleTimeout time.Duration, fs *fusefrontend.FS, srv *fuse.Server, mountpoint string) { + sleepTimeBetweenChecks := contentenc.MinUint64( + uint64(idleTimeout/checksDuringTimeoutPeriod), + uint64(2*time.Minute)) + timeoutCycles := int(math.Ceil(float64(idleTimeout) / float64(sleepTimeBetweenChecks))) + idleCount := 0 + for { + // Atomically check whether the access flag is set and reset it to 0 if so. + recentAccess := atomic.CompareAndSwapUint32(&fs.AccessedSinceLastCheck, 1, 0) + // Any form of current or recent access resets the idle counter. + openFileCount := openfiletable.CountOpenFiles() + if recentAccess || openFileCount > 0 { + idleCount = 0 + } else { + idleCount++ + } + tlog.Debug.Printf( + "Checking for idle (recentAccess = %t, open = %d): %s", + recentAccess, openFileCount, time.Now().String()) + if idleCount > 0 && idleCount%timeoutCycles == 0 { + tlog.Info.Printf("Filesystem idle; unmounting: %s", mountpoint) + unmount(srv, mountpoint) + } + time.Sleep(time.Duration(sleepTimeBetweenChecks)) + } +} + // setOpenFileLimit tries to increase the open file limit to 4096 (the default hard // limit on Linux). func setOpenFileLimit() { @@ -379,18 +421,22 @@ func handleSigint(srv *fuse.Server, mountpoint string) { signal.Notify(ch, syscall.SIGTERM) go func() { <-ch - err := srv.Unmount() - if err != nil { - tlog.Warn.Printf("handleSigint: srv.Unmount returned %v", err) - if runtime.GOOS == "linux" { - // MacOSX does not support lazy unmount - tlog.Info.Printf("Trying lazy unmount") - cmd := exec.Command("fusermount", "-u", "-z", mountpoint) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Run() - } - } + unmount(srv, mountpoint) os.Exit(exitcodes.SigInt) }() } + +func unmount(srv *fuse.Server, mountpoint string) { + err := srv.Unmount() + if err != nil { + tlog.Warn.Printf("unmount: srv.Unmount returned %v", err) + if runtime.GOOS == "linux" { + // MacOSX does not support lazy unmount + tlog.Info.Printf("Trying lazy unmount") + cmd := exec.Command("fusermount", "-u", "-z", mountpoint) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() + } + } +} -- cgit v1.2.3