[ARVADOS] updated: 1.1.0-110-g08a4ebb

Git user git at public.curoverse.com
Tue Nov 7 18:22:08 EST 2017


Summary of changes:
 services/fuse/arvados_fuse/unmount.py | 37 +++++++++++++++++++++++++++-
 services/fuse/tests/test_unmount.py   | 46 +++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 1 deletion(-)

       via  08a4ebba0e5bfbc179103ac5e6916164bc8083fa (commit)
      from  aabf1ca0e99701550f9af785e9f1fee098b0020a (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 08a4ebba0e5bfbc179103ac5e6916164bc8083fa
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date:   Tue Nov 7 18:10:13 2017 -0500

    12306: Resolve any readable symlinks on unmount path.
    
    This avoids getting stuck in realpath/lstat in certain cases: e.g.,
    the mount point is $HOME/keep, $HOME is "/home/foo", and "/home" is a
    symlink to "/data/home".
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>

diff --git a/services/fuse/arvados_fuse/unmount.py b/services/fuse/arvados_fuse/unmount.py
index 06a02e6..4a647c3 100644
--- a/services/fuse/arvados_fuse/unmount.py
+++ b/services/fuse/arvados_fuse/unmount.py
@@ -43,6 +43,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 +93,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
diff --git a/services/fuse/tests/test_unmount.py b/services/fuse/tests/test_unmount.py
index 1b0fac0..bf180be 100644
--- a/services/fuse/tests/test_unmount.py
+++ b/services/fuse/tests/test_unmount.py
@@ -2,9 +2,13 @@
 #
 # 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
 
@@ -89,3 +93,45 @@ class UnmountTest(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))

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list