summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
blob: c27b149a56aaeb91902c49464617c5314edb8e45 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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;
}