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.
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
#
# SPDX-License-Identifier: AGPL-3.0
+import arvados_fuse.unmount
import os
import subprocess
+import shutil
+import tempfile
import time
+import unittest
from integration_test import IntegrationTest
self.assertEqual(mounts, self._mounted(mounts))
subprocess.check_call(['./bin/arv-mount', '--unmount-all', self.tmp])
self.assertEqual([], self._mounted(mounts))
+
+
+
+class SaferRealpath(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp)
+
+ def test_safer_realpath(self):
+ os.mkdir(self.tmp+"/dir")
+ os.mkdir(self.tmp+"/dir/dir2")
+ os.symlink("missing", self.tmp+"/relative-missing")
+ os.symlink("dir", self.tmp+"/./relative-dir")
+ os.symlink("relative-dir", self.tmp+"/relative-indirect")
+ os.symlink(self.tmp+"/dir", self.tmp+"/absolute-dir")
+ os.symlink("./dir/../loop", self.tmp+"/loop")
+ os.symlink(".", self.tmp+"/dir/self")
+ os.symlink("..", self.tmp+"/dir/dir2/parent")
+ os.symlink("../dir3", self.tmp+"/dir/dir2/sibling")
+ os.symlink("../missing/../danger", self.tmp+"/dir/tricky")
+ os.symlink("/proc/1/fd/12345", self.tmp+"/eperm")
+ for (inpath, outpath, ok) in [
+ ("dir/self", "dir", True),
+ ("dir/dir2/parent", "dir", True),
+ ("dir/dir2/sibling", "dir/dir3", False),
+ ("dir", "dir", True),
+ ("relative-dir", "dir", True),
+ ("relative-missing", "missing", False),
+ ("relative-indirect", "dir", True),
+ ("absolute-dir", "dir", True),
+ ("loop", "loop", False),
+ # "missing" doesn't exist, so "missing/.." isn't our
+ # tmpdir; it's important not to contract this to just
+ # "danger".
+ ("dir/tricky", "missing/../danger", False),
+ ("eperm", "/proc/1/fd/12345", False),
+ ]:
+ if not outpath.startswith('/'):
+ outpath = self.tmp + '/' + outpath
+ self.assertEqual((outpath, ok), arvados_fuse.unmount.safer_realpath(self.tmp+"/"+inpath))