From 032e7baeffc02d9d8f6e285e11f5fb2973ba59a2 Mon Sep 17 00:00:00 2001 From: Sam Thursfield Date: Thu, 21 May 2020 12:42:24 +0200 Subject: trackertestutils: Add an optional D-Bus system bus to the sandbox This allows us to work with umockdev to simulate hardware events. --- utils/trackertestutils/__main__.py | 2 +- utils/trackertestutils/dbusdaemon.py | 64 ++++++++++++++++++++++++++++++------ utils/trackertestutils/helpers.py | 2 +- utils/trackertestutils/sandbox.py | 58 +++++++++++++++++++++++--------- 4 files changed, 99 insertions(+), 27 deletions(-) (limited to 'utils/trackertestutils') diff --git a/utils/trackertestutils/__main__.py b/utils/trackertestutils/__main__.py index ba64eb4d7..991da79bf 100644 --- a/utils/trackertestutils/__main__.py +++ b/utils/trackertestutils/__main__.py @@ -297,7 +297,7 @@ class MinerStatusWatch(): def setup(self): log.debug(f"Set up status watch on {self.dbus_name}") self._proxy = Gio.DBusProxy.new_sync( - self._sandbox.get_connection(), + self._sandbox.get_session_bus_connection(), Gio.DBusProxyFlags.NONE, None, self.dbus_name, self.object_path, 'org.freedesktop.Tracker3.Miner', None) diff --git a/utils/trackertestutils/dbusdaemon.py b/utils/trackertestutils/dbusdaemon.py index e55c03595..67c947af5 100644 --- a/utils/trackertestutils/dbusdaemon.py +++ b/utils/trackertestutils/dbusdaemon.py @@ -1,4 +1,4 @@ -# Copyright (C) 2018,2019, Sam Thursfield +# Copyright (C) 2018-2020, Sam Thursfield # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -26,19 +26,49 @@ import signal import subprocess import threading +from . import mainloop + +DEFAULT_TIMEOUT = 10 + log = logging.getLogger(__name__) -dbus_stderr_log = logging.getLogger(__name__ + '.stderr') -dbus_stdout_log = logging.getLogger(__name__ + '.stdout') class DaemonNotStartedError(Exception): pass +def await_bus_name(conn, bus_name, timeout=DEFAULT_TIMEOUT): + """Blocks until 'bus_name' has an owner.""" + + log.info("Blocking until name %s has owner", bus_name) + loop = mainloop.MainLoop() + + def name_appeared_cb(connection, name, name_owner): + log.info("Name %s appeared (owned by %s)", name, name_owner) + loop.quit() + + def timeout_cb(): + log.info("Timeout fired after %s seconds", timeout) + raise AwaitTimeoutException( + f"Timeout awaiting bus name '{bus_name}'") + + watch_id = Gio.bus_watch_name_on_connection( + conn, bus_name, Gio.BusNameWatcherFlags.NONE, name_appeared_cb, None) + timeout_id = GLib.timeout_add_seconds(timeout, timeout_cb) + + loop.run_checked() + + Gio.bus_unwatch_name(watch_id) + GLib.source_remove(timeout_id) + + class DBusDaemon: - """The private D-Bus instance that provides the sandbox's session bus.""" + """A private D-Bus daemon instance.""" + + def __init__(self, config_file=None, name='dbus-daemon'): + self.name = name + self.config_file = config_file - def __init__(self): self.process = None self.address = None @@ -77,12 +107,13 @@ class DBusDaemon: return dbus_daemon - def start(self, config_file=None, env=None, new_session=False): + def start(self, env=None, new_session=False): dbus_command = [self._dbus_daemon_path(), '--print-address=1', '--print-pid=1'] - if config_file: - dbus_command += ['--config-file=' + config_file] + if self.config_file: + dbus_command += ['--config-file=' + self.config_file] else: dbus_command += ['--session'] + log.debug("Running: %s", dbus_command) self.process = subprocess.Popen( dbus_command, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -101,10 +132,13 @@ class DBusDaemon: log.debug("Using new D-Bus session with address '%s' with PID %d", self.address, self.pid) + stderr_log = logging.getLogger(self.name + '.stderr') + stdout_log = logging.getLogger(self.name + '.stdout') + # We must read from the pipes continuously, otherwise the daemon # process will block. - self._threads=[threading.Thread(target=self.pipe_to_log, args=(self.process.stdout, dbus_stdout_log), daemon=True), - threading.Thread(target=self.pipe_to_log, args=(self.process.stderr, dbus_stdout_log), daemon=True)] + self._threads=[threading.Thread(target=self.pipe_to_log, args=(self.process.stdout, stdout_log), daemon=True), + threading.Thread(target=self.pipe_to_log, args=(self.process.stderr, stderr_log), daemon=True)] self._threads[0].start() self._threads[1].start() @@ -199,3 +233,13 @@ class DBusDaemon: return None else: raise + + def activate_service(self, bus_name, object_path): + GDBUS_DEFAULT_TIMEOUT = -1 + self.get_connection().call_sync( + bus_name, object_path, 'org.freedesktop.DBus.Peer', 'Ping', + None, None, Gio.DBusCallFlags.NONE, GDBUS_DEFAULT_TIMEOUT, None) + self.await_bus_name(bus_name) + + def await_bus_name(self, bus_name, timeout=DEFAULT_TIMEOUT): + await_bus_name(self.get_connection(), bus_name, timeout) diff --git a/utils/trackertestutils/helpers.py b/utils/trackertestutils/helpers.py index c1795b974..91086618f 100644 --- a/utils/trackertestutils/helpers.py +++ b/utils/trackertestutils/helpers.py @@ -17,5 +17,5 @@ # FIXME: Compatibility module due to recent API breaks. # Remove this before 3.0. -from .sandbox import TrackerSandbox +from .sandbox import TrackerSandbox as TrackerDBusSandbox from .storehelper import StoreHelper diff --git a/utils/trackertestutils/sandbox.py b/utils/trackertestutils/sandbox.py index ce2b51866..55897b184 100644 --- a/utils/trackertestutils/sandbox.py +++ b/utils/trackertestutils/sandbox.py @@ -23,10 +23,13 @@ Sandbox environment for running tests. The sandbox is essentially a private D-Bus daemon. """ +from gi.repository import Gio + import atexit import logging import os import signal +import subprocess from . import dbusdaemon from . import dconf @@ -51,16 +54,25 @@ atexit.register(_cleanup_processes) class TrackerSandbox: """ - Private D-Bus session bus which executes a sandboxed Tracker instance. + Run Tracker daemons isolated from the real user session. + + The primary method of sandboxing is running one or more private D-Bus + daemons, which take place of the host's session and system bus. """ - def __init__(self, dbus_daemon_config_file, extra_env=None): - self.dbus_daemon_config_file = dbus_daemon_config_file + def __init__(self, session_bus_config_file, system_bus_config_file=None, + extra_env=None): self.extra_env = extra_env or {} - self.daemon = dbusdaemon.DBusDaemon() + self.session_bus = dbusdaemon.DBusDaemon( + name='sandbox-session-bus', config_file=session_bus_config_file) + if system_bus_config_file: + self.system_bus = dbusdaemon.DBusDaemon( + name='sandbox-system-bus', config_file=system_bus_config_file) + else: + self.system_bus = None - def start(self, new_session=False): + def get_environment(self): env = os.environ env.update(self.extra_env) env['G_MESSAGES_PREFIXED'] = 'all' @@ -81,17 +93,25 @@ class TrackerSandbox: if xdg_runtime_dir: os.makedirs(xdg_runtime_dir, exist_ok=True) - log.info("Starting D-Bus daemon for sandbox.") + def start(self, new_session=False): + if self.system_bus: + log.info("Starting D-Bus system bus for sandbox.") + log.debug("Added environment variables: %s", self.extra_env) + self.system_bus.start(env=self.get_environment(), new_session=new_session) + + self.extra_env['DBUS_SYSTEM_BUS_ADDRESS'] = self.system_bus.get_address() + + log.info("Starting D-Bus session bus for sandbox.") log.debug("Added environment variables: %s", self.extra_env) - self.daemon.start(self.dbus_daemon_config_file, env=env, new_session=new_session) + self.session_bus.start(env=self.get_environment(), new_session=new_session) def stop(self): tracker_processes = [] - log.info("Looking for active Tracker processes on the bus") - for busname in self.daemon.list_names_sync(): + log.info("Looking for active Tracker processes on the session bus") + for busname in self.session_bus.list_names_sync(): if busname.startswith(TRACKER_DBUS_PREFIX): - pid = self.daemon.get_connection_unix_process_id_sync(busname) + pid = self.session_bus.get_connection_unix_process_id_sync(busname) if pid is not None: tracker_processes.append(pid) @@ -109,8 +129,12 @@ class TrackerSandbox: # # (tracker-miner-fs:14955): GLib-GIO-CRITICAL **: 11:38:40.386: Error while sending AddMatch() message: The connection is closed - log.info("Stopping D-Bus daemon for sandbox.") - self.daemon.stop() + log.info("Stopping D-Bus session bus for sandbox.") + self.session_bus.stop() + + if self.system_bus: + log.info("Stopping D-Bus system bus for sandbox.") + self.system_bus.stop() def stop_daemon(self, busname): """Stops the daemon that owns 'busname'. @@ -128,12 +152,16 @@ class TrackerSandbox: else: log.info("Couldn't find a process owning %s.", busname) - def get_connection(self): - return self.daemon.get_connection() + def get_session_bus_connection(self): + """Return a Gio.BusConnection to the sandbox D-Bus session bus.""" + return self.session_bus.get_connection() + def get_system_bus_connection(self): + """Return a Gio.BusConnection to the sandbox D-Bus system bus.""" + return self.system_bus.get_connection() def get_session_bus_address(self): - return self.daemon.get_address() + return self.session_bus.get_address() def set_config(self, schema_config_dict): """Set config values in multiple GSettings schemas. -- cgit v1.2.1