aboutsummaryrefslogtreecommitdiff
path: root/tests/test_helpers/mount_unmount.go
diff options
context:
space:
mode:
authorJakob Unterwurzacher2019-01-01 22:04:36 +0100
committerJakob Unterwurzacher2019-01-01 22:27:10 +0100
commite276e255dc7d88099f35c890b704ce64117f731e (patch)
tree99c9cbe2ac8a0aaafb7ba92921dcb52c6ee12bcc /tests/test_helpers/mount_unmount.go
parent10de105c13e4ef512fe83b8c1074fc453f3e70ff (diff)
tests: split mount_unmount.go from helpers.go
With the FD leak logic, the mount/unmount functions have become complex enough to give them their own file.
Diffstat (limited to 'tests/test_helpers/mount_unmount.go')
-rw-r--r--tests/test_helpers/mount_unmount.go189
1 files changed, 189 insertions, 0 deletions
diff --git a/tests/test_helpers/mount_unmount.go b/tests/test_helpers/mount_unmount.go
new file mode 100644
index 0000000..5035c2b
--- /dev/null
+++ b/tests/test_helpers/mount_unmount.go
@@ -0,0 +1,189 @@
+package test_helpers
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "os/signal"
+ "runtime"
+ "syscall"
+ "testing"
+ "time"
+)
+
+// Indexed by mountpoint. Initialized in doInit().
+var MountInfo map[string]mountInfo
+
+type mountInfo struct {
+ // PID of the running gocryptfs process. Set by Mount().
+ pid int
+ // List of open FDs of the running gocrypts process. Set by Mount().
+ fds []string
+}
+
+// Mount CIPHERDIR "c" on PLAINDIR "p"
+// Creates "p" if it does not exist.
+func Mount(c string, p string, showOutput bool, extraArgs ...string) error {
+ args := []string{"-q", "-wpanic", "-nosyslog", "-fg", fmt.Sprintf("-notifypid=%d", os.Getpid())}
+ args = append(args, extraArgs...)
+ //args = append(args, "-fusedebug")
+ //args = append(args, "-d")
+ args = append(args, c, p)
+
+ if _, err := os.Stat(p); err != nil {
+ err = os.Mkdir(p, 0777)
+ if err != nil {
+ return err
+ }
+ }
+
+ cmd := exec.Command(GocryptfsBinary, args...)
+ if showOutput {
+ // The Go test logic waits for our stdout to close, and when we share
+ // it with the subprocess, it will wait for it to close it as well.
+ // Use an intermediate pipe so the tests do not hang when unmouting
+ // fails.
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return err
+ }
+ // We can close the fd after cmd.Run() has executed
+ defer pw.Close()
+ cmd.Stderr = pw
+ cmd.Stdout = pw
+ go func() {
+ io.Copy(os.Stdout, pr)
+ pr.Close()
+ }()
+ }
+
+ // Two things can happen:
+ // 1) The mount fails and the process exits
+ // 2) The mount succeeds and the process sends us USR1
+ chanExit := make(chan error, 1)
+ chanUsr1 := make(chan os.Signal, 1)
+ signal.Notify(chanUsr1, syscall.SIGUSR1)
+
+ // Start the process and save the PID
+ err := cmd.Start()
+ if err != nil {
+ return err
+ }
+ pid := cmd.Process.Pid
+
+ // Wait for exit or usr1
+ go func() {
+ chanExit <- cmd.Wait()
+ }()
+ select {
+ case err := <-chanExit:
+ return err
+ case <-chanUsr1:
+ // noop
+ case <-time.After(1 * time.Second):
+ log.Panicf("Timeout waiting for process %d", pid)
+ }
+
+ // Save PID and open FDs
+ MountInfo[p] = mountInfo{pid, ListFds(pid)}
+ return nil
+}
+
+// MountOrExit calls Mount() and exits on failure.
+func MountOrExit(c string, p string, extraArgs ...string) {
+ err := Mount(c, p, true, extraArgs...)
+ if err != nil {
+ fmt.Printf("mount failed: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+// MountOrFatal calls Mount() and calls t.Fatal() on failure.
+func MountOrFatal(t *testing.T, c string, p string, extraArgs ...string) {
+ err := Mount(c, p, true, extraArgs...)
+ if err != nil {
+ t.Fatal(fmt.Errorf("mount failed: %v", err))
+ }
+}
+
+// UnmountPanic tries to umount "dir" and panics on error.
+func UnmountPanic(dir string) {
+ err := UnmountErr(dir)
+ if err != nil {
+ fmt.Printf("UnmountPanic: %v. Running lsof %s\n", err, dir)
+ cmd := exec.Command("lsof", dir)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Run()
+ panic("UnmountPanic: unmount failed: " + err.Error())
+ }
+}
+
+// UnmountErr tries to unmount "dir", retrying 10 times, and returns the
+// resulting error.
+func UnmountErr(dir string) (err error) {
+ var fdsNow []string
+ pid := MountInfo[dir].pid
+ fds := MountInfo[dir].fds
+ if pid <= 0 {
+ fmt.Printf("UnmountErr: %q was not found in MountInfo, cannot check for FD leaks\n", dir)
+ }
+
+ max := 10
+ // When a new filesystem is mounted, Gnome tries to read files like
+ // .xdg-volume-info, autorun.inf, .Trash.
+ // If we try to unmount before Gnome is done, the unmount fails with
+ // "Device or resource busy", causing spurious test failures.
+ // Retry a few times to hide that problem.
+ for i := 1; i <= max; i++ {
+ if pid > 0 {
+ fdsNow = ListFds(pid)
+ }
+ cmd := exec.Command(UnmountScript, "-u", dir)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ err = cmd.Run()
+ if err == nil {
+ if pid > 0 && len(fdsNow) > len(fds) {
+ fmt.Printf("FD leak? Details:\nold=%v \nnew=%v\n", fds, fdsNow)
+ }
+ return nil
+ }
+ code := ExtractCmdExitCode(err)
+ fmt.Printf("UnmountErr: got exit code %d, retrying (%d/%d)\n", code, i, max)
+ time.Sleep(100 * time.Millisecond)
+ }
+ return err
+}
+
+// ListFds lists the open file descriptors for process "pid". Pass pid=0 for
+// ourselves.
+func ListFds(pid int) []string {
+ // We need /proc to get the list of fds for other processes. Only exists
+ // on Linux.
+ if runtime.GOOS != "linux" && pid > 0 {
+ return nil
+ }
+ // Both Linux and MacOS have /dev/fd
+ dir := "/dev/fd"
+ if pid > 0 {
+ dir = fmt.Sprintf("/proc/%d/fd", pid)
+ }
+ f, err := os.Open(dir)
+ if err != nil {
+ log.Panic(err)
+ }
+ defer f.Close()
+ names, err := f.Readdirnames(0)
+ if err != nil {
+ log.Panic(err)
+ }
+ for i, n := range names {
+ // Note: Readdirnames filters "." and ".."
+ target, _ := os.Readlink(dir + "/" + n)
+ names[i] = n + "=" + target
+ }
+ return names
+}