X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/86ec536f01179ecc1bca0f32a4f41ad307b23c7e..68bdf4cbb1d2c22d689b1d74a1c2a77415921b4d:/services/fuse/arvados_fuse/unmount.py diff --git a/services/fuse/arvados_fuse/unmount.py b/services/fuse/arvados_fuse/unmount.py index 8be549ef1a..09a5f6254e 100644 --- a/services/fuse/arvados_fuse/unmount.py +++ b/services/fuse/arvados_fuse/unmount.py @@ -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