From 070d053e5cc47e572e9f9e647315082bd7a15c63 Mon Sep 17 00:00:00 2001 From: Chandan Singh Date: Wed, 24 Apr 2019 22:53:19 +0100 Subject: Move source from 'buildstream' to 'src/buildstream' This was discussed in #1008. Fixes #1009. --- src/buildstream/sandbox/_mount.py | 149 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/buildstream/sandbox/_mount.py (limited to 'src/buildstream/sandbox/_mount.py') 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 . +# +# Authors: +# Tristan Van Berkom + +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 -- cgit v1.2.1