[libcalamares] Block sleep with systemd/CK login manager if present

Calamares running as root is not always conducive to successfully
communicating with the fd.o PowerManagement interface on the user
session bus.

The login manager interface has long provided a similarly capable
sleep inhibition functionality, which at this point would be present
on the vast majority of distributions. Use it if systemd-logind
or ConsoleKit2 have registered this service on the system bus.
Regardless of running as root or user, Calamares shouldn't have
an issue contacting this interface.

Fixes #2384
This commit is contained in:
Jakob Petsovits 2024-12-07 12:27:36 -05:00
parent b5533caece
commit 37179801eb

View File

@ -17,6 +17,7 @@
#include "utils/Logger.h"
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingReply>
@ -24,6 +25,8 @@
#include <QThread>
#include <memory>
#include <optional>
#include <unistd.h> // for close()
namespace
{
@ -36,7 +39,7 @@ namespace
// SPDX-License-Identifier: LGPL-3.0-or-later
/** @brief Class to manage sleep / suspend on inactivity
/** @brief Class to manage sleep / suspend on inactivity (via fd.o Inhibit service on the user bus)
*
* Create an object of this class on the heap. Call inhibitSleep()
* to (try to) stop system sleep / suspend. Call uninhibitSleep()
@ -166,19 +169,165 @@ PowerManagementInterface::uninhibitSleep()
replyWatcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInterface::uninhibitDBusCallFinished );
}
/** @brief Class to manage sleep / suspend on inactivity (via logind/CK2 service on the system bus)
*
* Create an object of this class on the heap. Call inhibitSleep()
* to (try to) stop system sleep / suspend. Call uninhibitSleep()
* when the object is no longer needed. The object self-deletes
* after uninhibitSleep() completes.
*/
class LoginManagerInterface : public QObject
{
Q_OBJECT
public:
static LoginManagerInterface* makeForRegisteredService( QObject* parent = nullptr );
~LoginManagerInterface() override;
public Q_SLOTS:
void inhibitSleep();
void uninhibitSleep();
private Q_SLOTS:
void inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher );
private:
enum class Service
{
Logind,
ConsoleKit,
};
LoginManagerInterface( Service service, QObject* parent = nullptr );
int m_inhibitFd = -1;
Service m_service;
};
LoginManagerInterface*
LoginManagerInterface::makeForRegisteredService( QObject* parent )
{
if ( QDBusConnection::systemBus().interface()->isServiceRegistered( QStringLiteral( "org.freedesktop.login1" ) ) )
{
return new LoginManagerInterface( Service::Logind, parent );
}
else if ( QDBusConnection::systemBus().interface()->isServiceRegistered(
QStringLiteral( "org.freedesktop.ConsoleKit" ) ) )
{
return new LoginManagerInterface( Service::ConsoleKit, parent );
}
else
{
return nullptr;
}
}
LoginManagerInterface::LoginManagerInterface( Service service, QObject* parent )
: QObject( parent )
, m_service( service )
{
}
LoginManagerInterface::~LoginManagerInterface() = default;
void
LoginManagerInterface::inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher )
{
QDBusPendingReply< uint > reply = *aWatcher;
if ( reply.isError() )
{
cError() << "Could not inhibit sleep:" << reply.error();
// m_inhibitFd = -1; // unchanged
}
else
{
m_inhibitFd = reply.argumentAt< 0 >();
cDebug() << "Sleep inhibited, file descriptor" << m_inhibitFd;
}
aWatcher->deleteLater();
}
void
LoginManagerInterface::inhibitSleep()
{
if ( m_inhibitFd == -1 )
{
cDebug() << "Sleep is already inhibited.";
return;
}
auto systemBus = QDBusConnection::systemBus();
QDBusMessage inhibitCall;
if ( m_service == Service::Logind )
{
inhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.login1" ),
QStringLiteral( "/org/freedesktop/login1" ),
QStringLiteral( "org.freedesktop.login1.Manager" ),
QStringLiteral( "Inhibit" ) );
}
else if ( m_service == Service::ConsoleKit )
{
inhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.ConsoleKit" ),
QStringLiteral( "/org/freedesktop/ConsoleKit/Manager" ),
QStringLiteral( "org.freedesktop.ConsoleKit.Manager" ),
QStringLiteral( "Inhibit" ) );
}
else
{
cError() << "System sleep interface not supported.";
return;
}
inhibitCall.setArguments(
{ { "sleep:shutdown" }, { tr( "Calamares" ) }, { tr( "Installation in progress", "@status" ) }, { "block" } } );
auto asyncReply = systemBus.asyncCall( inhibitCall );
auto* replyWatcher = new QDBusPendingCallWatcher( asyncReply, this );
QObject::connect(
replyWatcher, &QDBusPendingCallWatcher::finished, this, &LoginManagerInterface::inhibitDBusCallFinished );
}
void
LoginManagerInterface::uninhibitSleep()
{
if ( m_inhibitFd == -1 )
{
cDebug() << "Sleep was never inhibited.";
this->deleteLater();
return;
}
if ( close( m_inhibitFd ) != 0 )
{
cError() << "Could not uninhibit sleep:" << strerror( errno );
}
this->deleteLater();
}
} // namespace
namespace Calamares
{
SleepInhibitor::SleepInhibitor()
{
// Create a PowerManagementInterface object with intentionally no parent
// Create a LoginManagerInterface object with intentionally no parent
// so it is not destroyed along with this. Instead, when this
// is destroyed, **start** the uninhibit-sleep call which will (later)
// destroy the PowerManagementInterface object.
auto* p = new PowerManagementInterface( nullptr );
p->inhibitSleep();
connect( this, &QObject::destroyed, p, &PowerManagementInterface::uninhibitSleep );
// destroy the LoginManagerInterface object.
if ( auto* l = LoginManagerInterface::makeForRegisteredService( nullptr ) )
{
l->inhibitSleep();
connect( this, &QObject::destroyed, l, &LoginManagerInterface::uninhibitSleep );
}
// If no login manager service was present, try the same thing
// with PowerManagementInterface.
else
{
auto* p = new PowerManagementInterface( nullptr );
p->inhibitSleep();
connect( this, &QObject::destroyed, p, &PowerManagementInterface::uninhibitSleep );
}
}
SleepInhibitor::~SleepInhibitor() = default;