From bd173fa33303f1a5402005f28a57c7315ae9fe41 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 11 Jan 2021 11:21:45 +0100 Subject: [PATCH] Shutdown using systemd-logind if available (#2416) The Reboot() and PowerOff() methods of the systemd D-Bus API are not meant to be called directly when logind is in use. Make sure to use the logind APIs if available. Since OS release 5.1 systemd-logind is enabled. --- supervisor/dbus/__init__.py | 8 ++++++++ supervisor/dbus/const.py | 2 ++ supervisor/dbus/logind.py | 41 +++++++++++++++++++++++++++++++++++++ supervisor/host/control.py | 26 ++++++++++++++++++----- 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 supervisor/dbus/logind.py diff --git a/supervisor/dbus/__init__.py b/supervisor/dbus/__init__.py index 4ad5bb44d..2abdc854e 100644 --- a/supervisor/dbus/__init__.py +++ b/supervisor/dbus/__init__.py @@ -6,6 +6,7 @@ from ..const import SOCKET_DBUS from ..coresys import CoreSys, CoreSysAttributes from .hostname import Hostname from .interface import DBusInterface +from .logind import Logind from .network import NetworkManager from .rauc import Rauc from .systemd import Systemd @@ -21,6 +22,7 @@ class DBusManager(CoreSysAttributes): self.coresys: CoreSys = coresys self._systemd: Systemd = Systemd() + self._logind: Logind = Logind() self._hostname: Hostname = Hostname() self._rauc: Rauc = Rauc() self._network: NetworkManager = NetworkManager() @@ -30,6 +32,11 @@ class DBusManager(CoreSysAttributes): """Return the systemd interface.""" return self._systemd + @property + def logind(self) -> Logind: + """Return the logind interface.""" + return self._logind + @property def hostname(self) -> Hostname: """Return the hostname interface.""" @@ -55,6 +62,7 @@ class DBusManager(CoreSysAttributes): dbus_loads: List[DBusInterface] = [ self.systemd, + self.logind, self.hostname, self.network, self.rauc, diff --git a/supervisor/dbus/const.py b/supervisor/dbus/const.py index 3cd04d154..97edbdf2e 100644 --- a/supervisor/dbus/const.py +++ b/supervisor/dbus/const.py @@ -18,6 +18,7 @@ DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED = ( "org.freedesktop.NetworkManager.Connection.Active.PropertiesChanged" ) DBUS_NAME_SYSTEMD = "org.freedesktop.systemd1" +DBUS_NAME_LOGIND = "org.freedesktop.login1" DBUS_OBJECT_BASE = "/" DBUS_OBJECT_DNS = "/org/freedesktop/NetworkManager/DnsManager" @@ -25,6 +26,7 @@ DBUS_OBJECT_SETTINGS = "/org/freedesktop/NetworkManager/Settings" DBUS_OBJECT_HOSTNAME = "/org/freedesktop/hostname1" DBUS_OBJECT_NM = "/org/freedesktop/NetworkManager" DBUS_OBJECT_SYSTEMD = "/org/freedesktop/systemd1" +DBUS_OBJECT_LOGIND = "/org/freedesktop/login1" DBUS_ATTR_ACTIVE_CONNECTIONS = "ActiveConnections" DBUS_ATTR_ACTIVE_CONNECTION = "ActiveConnection" diff --git a/supervisor/dbus/logind.py b/supervisor/dbus/logind.py new file mode 100644 index 000000000..7e1bad92d --- /dev/null +++ b/supervisor/dbus/logind.py @@ -0,0 +1,41 @@ +"""Interface to Logind over D-Bus.""" +import logging + +from ..exceptions import DBusError, DBusInterfaceError +from ..utils.gdbus import DBus +from .const import DBUS_NAME_LOGIND, DBUS_OBJECT_LOGIND +from .interface import DBusInterface +from .utils import dbus_connected + +_LOGGER: logging.Logger = logging.getLogger(__name__) + + +class Logind(DBusInterface): + """Logind function handler.""" + + name = DBUS_NAME_LOGIND + + async def connect(self): + """Connect to D-Bus.""" + try: + self.dbus = await DBus.connect(DBUS_NAME_LOGIND, DBUS_OBJECT_LOGIND) + except DBusError: + _LOGGER.warning("Can't connect to systemd-logind") + except DBusInterfaceError: + _LOGGER.info("No systemd-logind support on the host.") + + @dbus_connected + def reboot(self): + """Reboot host computer. + + Return a coroutine. + """ + return self.dbus.Manager.Reboot(False) + + @dbus_connected + def power_off(self): + """Power off host computer. + + Return a coroutine. + """ + return self.dbus.Manager.PowerOff(False) diff --git a/supervisor/host/control.py b/supervisor/host/control.py index 03cf26bfb..f38eb361e 100644 --- a/supervisor/host/control.py +++ b/supervisor/host/control.py @@ -19,7 +19,9 @@ class SystemControl(CoreSysAttributes): def _check_dbus(self, flag): """Check if systemd is connect or raise error.""" - if flag == MANAGER and self.sys_dbus.systemd.is_connected: + if flag == MANAGER and ( + self.sys_dbus.systemd.is_connected or self.sys_dbus.logind.is_connected + ): return if flag == HOSTNAME and self.sys_dbus.hostname.is_connected: return @@ -31,21 +33,35 @@ class SystemControl(CoreSysAttributes): """Reboot host system.""" self._check_dbus(MANAGER) - _LOGGER.info("Initialize host reboot over systemd") + use_logind = self.sys_dbus.logind.is_connected + _LOGGER.info( + "Initialize host reboot using %s", "logind" if use_logind else "systemd" + ) + try: await self.sys_core.shutdown() finally: - await self.sys_dbus.systemd.reboot() + if use_logind: + await self.sys_dbus.logind.reboot() + else: + await self.sys_dbus.systemd.reboot() async def shutdown(self): """Shutdown host system.""" self._check_dbus(MANAGER) - _LOGGER.info("Initialize host power off over systemd") + use_logind = self.sys_dbus.logind.is_connected + _LOGGER.info( + "Initialize host power off %s", "logind" if use_logind else "systemd" + ) + try: await self.sys_core.shutdown() finally: - await self.sys_dbus.systemd.power_off() + if use_logind: + await self.sys_dbus.logind.power_off() + else: + await self.sys_dbus.systemd.power_off() async def set_hostname(self, hostname): """Set local a new Hostname."""