summaryrefslogtreecommitdiff
path: root/src/buildstream/sandbox/_mount.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/buildstream/sandbox/_mount.py')
-rw-r--r--src/buildstream/sandbox/_mount.py149
1 files changed, 149 insertions, 0 deletions
diff --git a/src/buildstream/sandbox/_mount.py b/src/buildstream/sandbox/_mount.py
new file mode 100644
index 000000000..c0f26c8d7
--- /dev/null
+++ b/src/buildstream/sandbox/_mount.py
@@ -0,0 +1,149 @@
+#
+# Copyright (C) 2017 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+# Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
+
+import os
+from collections import OrderedDict
+from contextlib import contextmanager, ExitStack
+
+from .. import utils
+from .._fuse import SafeHardlinks
+
+
+# Mount()
+#
+# Helper data object representing a single mount point in the mount map
+#
+class Mount():
+ def __init__(self, sandbox, mount_point, safe_hardlinks, fuse_mount_options=None):
+ # Getting _get_underlying_directory() here is acceptable as
+ # we're part of the sandbox code. This will fail if our
+ # directory is CAS-based.
+ root_directory = sandbox.get_virtual_directory()._get_underlying_directory()
+
+ self.mount_point = mount_point
+ self.safe_hardlinks = safe_hardlinks
+ self._fuse_mount_options = {} if fuse_mount_options is None else fuse_mount_options
+
+ # FIXME: When the criteria for mounting something and its parent
+ # mount is identical, then there is no need to mount an additional
+ # fuse layer (i.e. if the root is read-write and there is a directory
+ # marked for staged artifacts directly within the rootfs, they can
+ # safely share the same fuse layer).
+ #
+ # In these cases it would be saner to redirect the sub-mount to
+ # a regular mount point within the parent's redirected mount.
+ #
+ if self.safe_hardlinks:
+ scratch_directory = sandbox._get_scratch_directory()
+ # Redirected mount
+ self.mount_origin = os.path.join(root_directory, mount_point.lstrip(os.sep))
+ self.mount_base = os.path.join(scratch_directory, utils.url_directory_name(mount_point))
+ self.mount_source = os.path.join(self.mount_base, 'mount')
+ self.mount_tempdir = os.path.join(self.mount_base, 'temp')
+ os.makedirs(self.mount_origin, exist_ok=True)
+ os.makedirs(self.mount_tempdir, exist_ok=True)
+ else:
+ # No redirection needed
+ self.mount_source = os.path.join(root_directory, mount_point.lstrip(os.sep))
+
+ external_mount_sources = sandbox._get_mount_sources()
+ external_mount_source = external_mount_sources.get(mount_point)
+
+ if external_mount_source is None:
+ os.makedirs(self.mount_source, exist_ok=True)
+ else:
+ if os.path.isdir(external_mount_source):
+ os.makedirs(self.mount_source, exist_ok=True)
+ else:
+ # When mounting a regular file, ensure the parent
+ # directory exists in the sandbox; and that an empty
+ # file is created at the mount location.
+ parent_dir = os.path.dirname(self.mount_source.rstrip('/'))
+ os.makedirs(parent_dir, exist_ok=True)
+ if not os.path.exists(self.mount_source):
+ with open(self.mount_source, 'w'):
+ pass
+
+ @contextmanager
+ def mounted(self, sandbox):
+ if self.safe_hardlinks:
+ mount = SafeHardlinks(self.mount_origin, self.mount_tempdir, self._fuse_mount_options)
+ with mount.mounted(self.mount_source):
+ yield
+ else:
+ # Nothing to mount here
+ yield
+
+
+# MountMap()
+#
+# Helper object for mapping of the sandbox mountpoints
+#
+# Args:
+# sandbox (Sandbox): The sandbox object
+# root_readonly (bool): Whether the sandbox root is readonly
+#
+class MountMap():
+
+ def __init__(self, sandbox, root_readonly, fuse_mount_options=None):
+ # We will be doing the mounts in the order in which they were declared.
+ self.mounts = OrderedDict()
+
+ if fuse_mount_options is None:
+ fuse_mount_options = {}
+
+ # We want safe hardlinks on rootfs whenever root is not readonly
+ self.mounts['/'] = Mount(sandbox, '/', not root_readonly, fuse_mount_options)
+
+ for mark in sandbox._get_marked_directories():
+ directory = mark['directory']
+ artifact = mark['artifact']
+
+ # We want safe hardlinks for any non-root directory where
+ # artifacts will be staged to
+ self.mounts[directory] = Mount(sandbox, directory, artifact, fuse_mount_options)
+
+ # get_mount_source()
+ #
+ # Gets the host directory where the mountpoint in the
+ # sandbox should be bind mounted from
+ #
+ # Args:
+ # mountpoint (str): The absolute mountpoint path inside the sandbox
+ #
+ # Returns:
+ # The host path to be mounted at the mount point
+ #
+ def get_mount_source(self, mountpoint):
+ return self.mounts[mountpoint].mount_source
+
+ # mounted()
+ #
+ # A context manager which ensures all the mount sources
+ # were mounted with any fuse layers which may have been needed.
+ #
+ # Args:
+ # sandbox (Sandbox): The sandbox
+ #
+ @contextmanager
+ def mounted(self, sandbox):
+ with ExitStack() as stack:
+ for _, mount in self.mounts.items():
+ stack.enter_context(mount.mounted(sandbox))
+ yield