From 081dcafa03f1b2604ce52e9dc5be696444d8f71e Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 4 Sep 2018 14:30:35 +0100 Subject: fuse: Report the correct device number for devices This fixes all devices being mapped to the non-existant device 0, which prevents being able to use even safe devices like /dev/null through the hardlinks FUSE layer. --- buildstream/_fuse/hardlinks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildstream/_fuse/hardlinks.py b/buildstream/_fuse/hardlinks.py index 1386f14cf..f9e2b959b 100644 --- a/buildstream/_fuse/hardlinks.py +++ b/buildstream/_fuse/hardlinks.py @@ -121,7 +121,7 @@ class SafeHardlinkOps(Operations): st = os.lstat(full_path) return dict((key, getattr(st, key)) for key in ( 'st_atime', 'st_ctime', 'st_gid', 'st_mode', - 'st_mtime', 'st_nlink', 'st_size', 'st_uid')) + 'st_mtime', 'st_nlink', 'st_size', 'st_uid', 'st_rdev')) def readdir(self, path, fh): full_path = self._full_path(path) -- cgit v1.2.1 From d042560842ce504bb57605b1388ccc7daaa16c66 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 4 Sep 2018 14:37:54 +0100 Subject: FUSE: Mount with -odev in chroot sandbox This is needed to permit access to the device nodes added to /dev on Linux when FUSE is used as root. The chroot sandbox only works with all privileges, so there's no explicit check for being root or having the appropriate capabilities. A check for whether it's running as root isn't needed on Linux with bubblewrap because /dev or its devices are mounted on top of the FUSE layer, so device nodes are accessed directly rather than through the FUSE layer. --- buildstream/_fuse/hardlinks.py | 3 ++- buildstream/_fuse/mount.py | 6 +++++- buildstream/sandbox/_mount.py | 11 ++++++----- buildstream/sandbox/_sandboxchroot.py | 6 +++++- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/buildstream/_fuse/hardlinks.py b/buildstream/_fuse/hardlinks.py index f9e2b959b..0797cb4bc 100644 --- a/buildstream/_fuse/hardlinks.py +++ b/buildstream/_fuse/hardlinks.py @@ -42,9 +42,10 @@ from .mount import Mount # class SafeHardlinks(Mount): - def __init__(self, directory, tempdir): + def __init__(self, directory, tempdir, fuse_mount_options={}): self.directory = directory self.tempdir = tempdir + super().__init__(fuse_mount_options=fuse_mount_options) def create_operations(self): return SafeHardlinkOps(self.directory, self.tempdir) diff --git a/buildstream/_fuse/mount.py b/buildstream/_fuse/mount.py index 0ab1ce715..30cc85b77 100644 --- a/buildstream/_fuse/mount.py +++ b/buildstream/_fuse/mount.py @@ -87,6 +87,9 @@ class Mount(): # User Facing API # ################################################ + def __init__(self, fuse_mount_options={}): + self._fuse_mount_options = fuse_mount_options + # mount(): # # User facing API for mounting a fuse subclass implementation @@ -184,7 +187,8 @@ class Mount(): # Run fuse in foreground in this child process, internally libfuse # will handle SIGTERM and gracefully exit it's own little main loop. # - FUSE(self.__operations, self.__mountpoint, nothreads=True, foreground=True, nonempty=True) + FUSE(self.__operations, self.__mountpoint, nothreads=True, foreground=True, nonempty=True, + **self._fuse_mount_options) # Explicit 0 exit code, if the operations crashed for some reason, the exit # code will not be 0, and we want to know about it. diff --git a/buildstream/sandbox/_mount.py b/buildstream/sandbox/_mount.py index 49068fe92..4fca3d1b0 100644 --- a/buildstream/sandbox/_mount.py +++ b/buildstream/sandbox/_mount.py @@ -30,7 +30,7 @@ from .._fuse import SafeHardlinks # Helper data object representing a single mount point in the mount map # class Mount(): - def __init__(self, sandbox, mount_point, safe_hardlinks): + def __init__(self, sandbox, mount_point, safe_hardlinks, fuse_mount_options={}): scratch_directory = sandbox._get_scratch_directory() # Getting _get_underlying_directory() here is acceptable as # we're part of the sandbox code. This will fail if our @@ -39,6 +39,7 @@ class Mount(): self.mount_point = mount_point self.safe_hardlinks = safe_hardlinks + self._fuse_mount_options = fuse_mount_options # FIXME: When the criteria for mounting something and it's parent # mount is identical, then there is no need to mount an additional @@ -82,7 +83,7 @@ class Mount(): @contextmanager def mounted(self, sandbox): if self.safe_hardlinks: - mount = SafeHardlinks(self.mount_origin, self.mount_tempdir) + mount = SafeHardlinks(self.mount_origin, self.mount_tempdir, self._fuse_mount_options) with mount.mounted(self.mount_source): yield else: @@ -100,12 +101,12 @@ class Mount(): # class MountMap(): - def __init__(self, sandbox, root_readonly): + def __init__(self, sandbox, root_readonly, fuse_mount_options={}): # We will be doing the mounts in the order in which they were declared. self.mounts = OrderedDict() # We want safe hardlinks on rootfs whenever root is not readonly - self.mounts['/'] = Mount(sandbox, '/', not root_readonly) + self.mounts['/'] = Mount(sandbox, '/', not root_readonly, fuse_mount_options) for mark in sandbox._get_marked_directories(): directory = mark['directory'] @@ -113,7 +114,7 @@ class MountMap(): # We want safe hardlinks for any non-root directory where # artifacts will be staged to - self.mounts[directory] = Mount(sandbox, directory, artifact) + self.mounts[directory] = Mount(sandbox, directory, artifact, fuse_mount_options) # get_mount_source() # diff --git a/buildstream/sandbox/_sandboxchroot.py b/buildstream/sandbox/_sandboxchroot.py index 64fb3c1bc..b3a2a6d9d 100644 --- a/buildstream/sandbox/_sandboxchroot.py +++ b/buildstream/sandbox/_sandboxchroot.py @@ -35,6 +35,9 @@ from . import Sandbox, SandboxFlags class SandboxChroot(Sandbox): + + _FUSE_MOUNT_OPTIONS = {'dev': True} + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -67,7 +70,8 @@ class SandboxChroot(Sandbox): # Create the mount map, this will tell us where # each mount point needs to be mounted from and to - self.mount_map = MountMap(self, flags & SandboxFlags.ROOT_READ_ONLY) + self.mount_map = MountMap(self, flags & SandboxFlags.ROOT_READ_ONLY, + self._FUSE_MOUNT_OPTIONS) root_mount_source = self.mount_map.get_mount_source('/') # Create a sysroot and run the command inside it -- cgit v1.2.1 From 8430fdc7da59a20d6a3ec9b526d681b8f68920ec Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Mon, 17 Sep 2018 14:25:08 +0100 Subject: tests: test that integration commands can use /dev --- tests/integration/project/elements/integration.bst | 9 +++++++++ tests/integration/shell.py | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/integration/project/elements/integration.bst diff --git a/tests/integration/project/elements/integration.bst b/tests/integration/project/elements/integration.bst new file mode 100644 index 000000000..be21ae31b --- /dev/null +++ b/tests/integration/project/elements/integration.bst @@ -0,0 +1,9 @@ +kind: manual +depends: +- base.bst + +public: + bst: + integration-commands: + - | + echo noise >/dev/null diff --git a/tests/integration/shell.py b/tests/integration/shell.py index 18953aa2d..947650ff1 100644 --- a/tests/integration/shell.py +++ b/tests/integration/shell.py @@ -342,3 +342,13 @@ def test_sysroot_workspace_visible(cli, tmpdir, datafiles): ]) assert result.exit_code == 0 assert result.output == workspace_hello + + +# Test system integration commands can access devices in /dev +@pytest.mark.datafiles(DATA_DIR) +def test_integration_devices(cli, tmpdir, datafiles): + project = os.path.join(datafiles.dirname, datafiles.basename) + element_name = 'integration.bst' + + result = execute_shell(cli, project, ["true"], element=element_name) + assert result.exit_code == 0 -- cgit v1.2.1