X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/aabf1ca0e99701550f9af785e9f1fee098b0020a..864c3b0afd16c77e046f0072d8517d34c5a44792:/services/fuse/arvados_fuse/unmount.py diff --git a/services/fuse/arvados_fuse/unmount.py b/services/fuse/arvados_fuse/unmount.py index 06a02e6a76..dbfea1f904 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,7 +94,7 @@ def unmount(path, subtype=None, timeout=10, recursive=False): fuse mount at all. Raises an exception if it cannot be unmounted. """ - path = os.path.abspath(path) + path, _ = safer_realpath(path) if subtype is None: mnttype = None @@ -81,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: @@ -120,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.write(fusermount_output) + delay = 1 if deadline: delay = min(delay, deadline - time.time()) @@ -131,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''