summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/Main/src-server/linux/HostPowerLinux.cpp198
1 files changed, 198 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/linux/HostPowerLinux.cpp b/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
new file mode 100644
index 00000000..c27b149a
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
@@ -0,0 +1,198 @@
+/* $Id: HostPowerLinux.cpp $ */
+/** @file
+ * VirtualBox interface to host's power notification service
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include "HostPower.h"
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/power.h>
+#include <iprt/time.h>
+
+static bool checkDBusError(DBusError *pError, DBusConnection **pConnection)
+{
+ if (dbus_error_is_set(pError))
+ {
+ LogRel(("HostPowerServiceLinux: DBus connection Error (%s)\n", pError->message));
+ dbus_error_free(pError);
+ if (*pConnection)
+ {
+ /* Close the socket or whatever underlying the connection. */
+ dbus_connection_close(*pConnection);
+ /* Free in-process resources used for the now-closed connection. */
+ dbus_connection_unref(*pConnection);
+ *pConnection = NULL;
+ }
+ return true;
+ }
+ return false;
+}
+
+HostPowerServiceLinux::HostPowerServiceLinux(VirtualBox *aVirtualBox)
+ : HostPowerService(aVirtualBox)
+ , mThread(NIL_RTTHREAD)
+ , mpConnection(NULL)
+{
+ DBusError error;
+
+ int vrc = RTDBusLoadLib();
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("HostPowerServiceLinux: DBus library not found. Service not available.\n"));
+ return;
+ }
+ dbus_error_init(&error);
+ /* Connect to the DBus. The connection will be not shared with any other
+ * in-process callers of dbus_bus_get(). This is considered wasteful (see
+ * API documentation) but simplifies our code, specifically shutting down.
+ * The session bus allows up to 100000 connections per user as it "is just
+ * running as the user anyway" (see session.conf.in in the DBus sources). */
+ mpConnection = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+ if (checkDBusError(&error, &mpConnection))
+ return;
+ /* We do not want to exit(1) if the connection is broken. */
+ dbus_connection_set_exit_on_disconnect(mpConnection, FALSE);
+ /* Tell the bus to wait for the sleep signal(s). */
+ /* The current systemd-logind interface. */
+ dbus_bus_add_match(mpConnection, "type='signal',interface='org.freedesktop.login1.Manager'", &error);
+ /* The previous UPower interfaces (2010 - ca 2013). */
+ dbus_bus_add_match(mpConnection, "type='signal',interface='org.freedesktop.UPower'", &error);
+ dbus_connection_flush(mpConnection);
+ if (checkDBusError(&error, &mpConnection))
+ return;
+
+ /* Grab another reference so that both the destruct and thread each has one: */
+ DBusConnection *pForAssert = dbus_connection_ref(mpConnection);
+ Assert(pForAssert == mpConnection); RT_NOREF(pForAssert);
+
+ /* Create the new worker thread. */
+ vrc = RTThreadCreate(&mThread, HostPowerServiceLinux::powerChangeNotificationThread, this, 0 /* cbStack */,
+ RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "MainPower");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("HostPowerServiceLinux: RTThreadCreate failed with %Rrc\n", vrc));
+ dbus_connection_unref(mpConnection);
+ }
+}
+
+
+HostPowerServiceLinux::~HostPowerServiceLinux()
+{
+ /* Closing the connection should cause the event loop to exit. */
+ LogFunc((": Stopping thread\n"));
+ if (mpConnection)
+ {
+ dbus_connection_close(mpConnection);
+ dbus_connection_unref(mpConnection);
+ mpConnection = NULL;
+ }
+
+ if (mThread != NIL_RTTHREAD)
+ {
+ /* HACK ALERT! This poke call should _not_ be necessary as dbus_connection_close()
+ should close the socket and force the poll/dbus_connection_read_write
+ call to return with POLLHUP/FALSE. It does so when stepping it in the
+ debugger, but not in real life (asan build; dbus-1.12.20-1.fc32; linux 5.8).
+
+ Poking the thread is a crude crude way to wake it up from whatever
+ stuff it's actually blocked on and realize that the connection has
+ been dropped. */
+
+ uint64_t msElapsed = RTTimeMilliTS();
+ int vrc = RTThreadWait(mThread, 10 /*ms*/, NULL);
+ if (RT_FAILURE(vrc))
+ {
+ RTThreadPoke(mThread);
+ vrc = RTThreadWait(mThread, RT_MS_5SEC, NULL);
+ }
+ msElapsed = RTTimeMilliTS() - msElapsed;
+ if (vrc != VINF_SUCCESS)
+ LogRelThisFunc(("RTThreadWait() failed after %llu ms: %Rrc\n", msElapsed, vrc));
+ mThread = NIL_RTTHREAD;
+ }
+}
+
+
+DECLCALLBACK(int) HostPowerServiceLinux::powerChangeNotificationThread(RTTHREAD hThreadSelf, void *pInstance)
+{
+ NOREF(hThreadSelf);
+ HostPowerServiceLinux *pPowerObj = static_cast<HostPowerServiceLinux *>(pInstance);
+ DBusConnection *pConnection = pPowerObj->mpConnection;
+
+ Log(("HostPowerServiceLinux: Thread started\n"));
+ while (dbus_connection_read_write(pConnection, -1))
+ {
+ DBusMessage *pMessage = NULL;
+
+ for (;;)
+ {
+ pMessage = dbus_connection_pop_message(pConnection);
+ if (pMessage == NULL)
+ break;
+
+ /* The systemd-logind interface notification. */
+ DBusMessageIter args;
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.login1.Manager", "PrepareForSleep")
+ && dbus_message_iter_init(pMessage, &args)
+ && dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_BOOLEAN)
+ {
+ dbus_bool_t fSuspend;
+ dbus_message_iter_get_basic(&args, &fSuspend);
+
+ /* Trinary operator does not work here as Reason_... is an
+ * anonymous enum. */
+ if (fSuspend)
+ pPowerObj->notify(Reason_HostSuspend);
+ else
+ pPowerObj->notify(Reason_HostResume);
+ }
+
+ /* The UPowerd interface notifications. Sleeping is the older one,
+ * NotifySleep the newer. This gives us one second grace before the
+ * suspend triggers. */
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "Sleeping")
+ || dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "NotifySleep"))
+ pPowerObj->notify(Reason_HostSuspend);
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "Resuming")
+ || dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "NotifyResume"))
+ pPowerObj->notify(Reason_HostResume);
+
+ /* Free local resources held for the message. */
+ dbus_message_unref(pMessage);
+ }
+ }
+
+ /* Close the socket or whatever underlying the connection. */
+ dbus_connection_close(pConnection);
+
+ /* Free in-process resources used for the now-closed connection. */
+ dbus_connection_unref(pConnection);
+
+ Log(("HostPowerServiceLinux: Exiting thread\n"));
+ return VINF_SUCCESS;
+}
+