X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/7bc55d65082b3a39639508fcaebd1185b7e04089..3a9428dd1db2fd393dd90cd3ffc744c0bf45fe28:/services/fuse/arvados_fuse/unmount.py diff --git a/services/fuse/arvados_fuse/unmount.py b/services/fuse/arvados_fuse/unmount.py index 17ced4b4d0..12d047a8f3 100644 --- a/services/fuse/arvados_fuse/unmount.py +++ b/services/fuse/arvados_fuse/unmount.py @@ -6,6 +6,7 @@ import collections import errno import os import subprocess +import sys import time @@ -43,6 +44,41 @@ def paths_to_unmount(path, mnttype): return paths +def safer_realpath(path, loop=True): + """Similar to os.path.realpath(), but avoids calling lstat(). + + Leaves some symlinks unresolved.""" + if path == '/': + return path, True + elif not path.startswith('/'): + path = os.path.abspath(path) + while True: + path = path.rstrip('/') + dirname, basename = os.path.split(path) + try: + path, resolved = safer_realpath(os.path.join(dirname, os.readlink(path)), loop=False) + except OSError as e: + # Path is not a symlink (EINVAL), or is unreadable, or + # doesn't exist. If the error was EINVAL and dirname can + # be resolved, we will have eliminated all symlinks and it + # will be safe to call normpath(). + dirname, resolved = safer_realpath(dirname, loop=loop) + path = os.path.join(dirname, basename) + if resolved and e.errno == errno.EINVAL: + return os.path.normpath(path), True + else: + return path, False + except RuntimeError: + if not loop: + # Unwind to the point where we first started following + # symlinks. + raise + # Resolving the whole path landed in a symlink cycle, but + # we might still be able to resolve dirname. + dirname, _ = safer_realpath(dirname, loop=loop) + return os.path.join(dirname, basename), False + + def unmount(path, subtype=None, timeout=10, recursive=False): """Unmount the fuse mount at path. @@ -58,6 +94,8 @@ def unmount(path, subtype=None, timeout=10, recursive=False): fuse mount at all. Raises an exception if it cannot be unmounted. """ + path, _ = safer_realpath(path) + if subtype is None: mnttype = None elif subtype == '': @@ -79,6 +117,7 @@ def unmount(path, subtype=None, timeout=10, recursive=False): was_mounted = False attempted = False + fusermount_output = b'' if timeout is None: deadline = None else: @@ -118,6 +157,10 @@ def unmount(path, subtype=None, timeout=10, recursive=False): return was_mounted if attempted: + # Report buffered stderr from previous call to fusermount, + # now that we know it didn't succeed. + sys.stderr.buffer.write(fusermount_output) + delay = 1 if deadline: delay = min(delay, deadline - time.time()) @@ -129,12 +172,16 @@ def unmount(path, subtype=None, timeout=10, recursive=False): with open('/sys/fs/fuse/connections/{}/abort'.format(m.minor), 'w') as f: f.write("1") - except OSError as e: + except IOError as e: if e.errno != errno.ENOENT: raise attempted = True try: - subprocess.check_call(["fusermount", "-u", "-z", path]) - except subprocess.CalledProcessError: - pass + subprocess.check_output( + ["fusermount", "-u", "-z", path], + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + fusermount_output = e.output + else: + fusermount_output = b''