12037: Pull default arvados/jobs:latest image as needed.
[arvados.git] / services / fuse / arvados_fuse / unmount.py
index 8be549ef1a925256017e10f8193073a65da7f9f4..09a5f6254e6b7ac4a20fe416c4cab16aec5b6b47 100644 (file)
@@ -1,9 +1,36 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+import collections
 import errno
 import os
 import subprocess
 import time
 
-def unmount(path, timeout=10):
+
+MountInfo = collections.namedtuple(
+    'MountInfo', ['is_fuse', 'major', 'minor', 'mnttype', 'path'])
+
+
+def mountinfo():
+    mi = []
+    with open('/proc/self/mountinfo') as f:
+        for m in f.readlines():
+            mntid, pmntid, dev, root, path, extra = m.split(" ", 5)
+            mnttype = extra.split(" - ")[1].split(" ", 1)[0]
+            major, minor = dev.split(":")
+            mi.append(MountInfo(
+                is_fuse=(mnttype == "fuse" or mnttype.startswith("fuse.")),
+                major=major,
+                minor=minor,
+                mnttype=mnttype,
+                path=path,
+            ))
+    return mi
+
+
+def unmount(path, subtype=None, timeout=10, recursive=False):
     """Unmount the fuse mount at path.
 
     Unmounting is done by writing 1 to the "abort" control file in
@@ -20,22 +47,40 @@ def unmount(path, timeout=10):
 
     path = os.path.realpath(path)
 
+    if subtype is None:
+        mnttype = None
+    elif subtype == '':
+        mnttype = 'fuse'
+    else:
+        mnttype = 'fuse.' + subtype
+
+    if recursive:
+        paths = []
+        for m in mountinfo():
+            if m.path == path or m.path.startswith(path+"/"):
+                paths.append(m.path)
+                if not (m.is_fuse and (mnttype is None or
+                                       mnttype == m.mnttype)):
+                    raise Exception(
+                        "cannot unmount {}: mount type is {}".format(
+                            path, m.mnttype))
+        for path in sorted(paths, key=len, reverse=True):
+            unmount(path, timeout=timeout, recursive=False)
+        return len(paths) > 0
+
     was_mounted = False
-    t0 = time.time()
-    delay = 0
-    while True:
-        if timeout and t0 + timeout < time.time():
-            raise Exception("timed out")
+    attempted = False
+    if timeout is None:
+        deadline = None
+    else:
+        deadline = time.time() + timeout
 
+    while True:
         mounted = False
-        with open('/proc/self/mountinfo') as mi:
-            for m in mi.readlines():
-                mntid, pmntid, dev, root, mnt, extra = m.split(" ", 5)
-                mnttype = extra.split(" - ")[1].split(" ")[0]
-                if not (mnttype == "fuse" or mnttype.startswith("fuse.")):
-                    continue
+        for m in mountinfo():
+            if m.is_fuse and (mnttype is None or mnttype == m.mnttype):
                 try:
-                    if os.path.realpath(mnt) == path:
+                    if os.path.realpath(m.path) == path:
                         was_mounted = True
                         mounted = True
                         break
@@ -44,18 +89,24 @@ def unmount(path, timeout=10):
         if not mounted:
             return was_mounted
 
-        major, minor = dev.split(":")
+        if attempted:
+            delay = 1
+            if deadline:
+                delay = min(delay, deadline - time.time())
+                if delay <= 0:
+                    raise Exception("timed out")
+            time.sleep(delay)
+
         try:
-            with open('/sys/fs/fuse/connections/'+str(minor)+'/abort', 'w') as f:
+            with open('/sys/fs/fuse/connections/{}/abort'.format(m.minor),
+                      'w') as f:
                 f.write("1")
         except OSError as e:
             if e.errno != errno.ENOENT:
                 raise
+
+        attempted = True
         try:
             subprocess.check_call(["fusermount", "-u", "-z", path])
         except subprocess.CalledProcessError:
             pass
-
-        time.sleep(delay)
-        if delay == 0:
-            delay = 1