| #!/usr/bin/env python3 |
| # |
| # 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 Maat <tristan.maat@codethink.co.uk> |
| |
| import sys |
| from contextlib import contextmanager |
| |
| from .._exceptions import SandboxError |
| from .. import utils, _signals |
| |
| |
| # A class to wrap the `mount` and `umount` system commands |
| class Mounter(object): |
| @classmethod |
| def _mount(cls, dest, src=None, mount_type=None, |
| stdout=sys.stdout, stderr=sys.stderr, options=None, |
| flags=None): |
| |
| argv = [utils.get_host_tool('mount')] |
| if mount_type: |
| argv.extend(['-t', mount_type]) |
| if options: |
| argv.extend(['-o', options]) |
| if flags: |
| argv.extend(flags) |
| |
| if src is not None: |
| argv += [src] |
| argv += [dest] |
| |
| status, _ = utils._call( |
| argv, |
| terminate=True, |
| stdout=stdout, |
| stderr=stderr |
| ) |
| |
| if status != 0: |
| raise SandboxError('`{}` failed with exit code {}' |
| .format(' '.join(argv), status)) |
| |
| return dest |
| |
| @classmethod |
| def _umount(cls, path, stdout=sys.stdout, stderr=sys.stderr): |
| |
| cmd = [utils.get_host_tool('umount'), '-R', path] |
| status, _ = utils._call( |
| cmd, |
| terminate=True, |
| stdout=stdout, |
| stderr=stderr |
| ) |
| |
| if status != 0: |
| raise SandboxError('`{}` failed with exit code {}' |
| .format(' '.join(cmd), status)) |
| |
| # mount() |
| # |
| # A wrapper for the `mount` command. The device is unmounted when |
| # the context is left. |
| # |
| # Args: |
| # dest (str) - The directory to mount to |
| # src (str) - The directory to mount |
| # stdout (file) - stdout |
| # stderr (file) - stderr |
| # mount_type (str|None) - The mount type (can be omitted or None) |
| # kwargs - Arguments to pass to the mount command, such as `ro=True` |
| # |
| # Yields: |
| # (str) The path to the destination |
| # |
| @classmethod |
| @contextmanager |
| def mount(cls, dest, src=None, stdout=sys.stdout, |
| stderr=sys.stderr, mount_type=None, **kwargs): |
| |
| def kill_proc(): |
| cls._umount(dest, stdout, stderr) |
| |
| options = ','.join([key for key, val in kwargs.items() if val]) |
| |
| with _signals.terminator(kill_proc): |
| yield cls._mount(dest, src, mount_type, stdout=stdout, stderr=stderr, options=options) |
| |
| cls._umount(dest, stdout, stderr) |
| |
| # bind_mount() |
| # |
| # Mount a directory to a different location (a hardlink for all |
| # intents and purposes). The directory is unmounted when the |
| # context is left. |
| # |
| # Args: |
| # dest (str) - The directory to mount to |
| # src (str) - The directory to mount |
| # stdout (file) - stdout |
| # stderr (file) - stderr |
| # kwargs - Arguments to pass to the mount command, such as `ro=True` |
| # |
| # Yields: |
| # (str) The path to the destination |
| # |
| # While this is equivalent to `mount --rbind`, this option may not |
| # exist and can be dangerous, requiring careful cleanupIt is |
| # recommended to use this function over a manual mount invocation. |
| # |
| @classmethod |
| @contextmanager |
| def bind_mount(cls, dest, src=None, stdout=sys.stdout, |
| stderr=sys.stderr, **kwargs): |
| |
| def kill_proc(): |
| cls._umount(dest, stdout, stderr) |
| |
| kwargs['rbind'] = True |
| options = ','.join([key for key, val in kwargs.items() if val]) |
| |
| path = cls._mount(dest, src, None, stdout, stderr, options) |
| |
| with _signals.terminator(kill_proc): |
| # Make the rbind a slave to avoid unmounting vital devices in |
| # /proc |
| cls._mount(dest, flags=['--make-rslave']) |
| yield path |
| |
| cls._umount(dest, stdout, stderr) |