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 199 200 201 202 203 204 205 206 207 208 209 210
|
/*
SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "sessionlock.h"
#include "debug.h"
#include "sessioncontroller.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageBox_KDevCompat>
#include <QDBusConnectionInterface>
#include <QFile>
#include <QDir>
namespace KDevelop {
QString lockFileForSession( const QString& id )
{
return SessionController::sessionDirectory( id ) + QLatin1String("/lock");
}
QString dBusServiceNameForSession( const QString& id )
{
// We remove starting "{" and ending "}" from the string UUID representation
// as D-Bus apparently doesn't allow them in service names
return QLatin1String("org.kdevelop.kdevplatform-lock-") + id.midRef(1, id.size() - 2);
}
/// Force-removes the lock-file.
void forceRemoveLockfile(const QString& lockFilename)
{
if( QFile::exists( lockFilename ) ) {
QFile::remove( lockFilename );
}
}
TryLockSessionResult SessionLock::tryLockSession(const QString& sessionId, bool doLocking)
{
///FIXME: if this is hit, someone tried to lock a non-existing session
/// this should be fixed by using a proper data type distinct from
/// QString for id's, i.e. QUuid or similar.
Q_ASSERT(QFile::exists(SessionController::sessionDirectory( sessionId )));
/*
* We've got two locking mechanisms here: D-Bus unique service name (based on the session id)
* and a plain lockfile (QLockFile).
* The latter is required to get the appname/pid of the locking instance
* in case if it's stale/hanging/crashed (to make user know which PID he needs to kill).
* D-Bus mechanism is the primary one.
*
* Since there is a kind of "logic tree", the code is a bit hard.
*/
const QString service = dBusServiceNameForSession( sessionId );
QDBusConnection connection = QDBusConnection::sessionBus();
QDBusConnectionInterface* connectionInterface = connection.interface();
const QString lockFilename = lockFileForSession( sessionId );
QSharedPointer<QLockFile> lockFile(new QLockFile( lockFilename ));
const bool haveDBus = connection.isConnected();
const bool canLockDBus = haveDBus && connectionInterface && !connectionInterface->isServiceRegistered( service );
bool lockedDBus = false;
// Lock D-Bus if we can and we need to
if( doLocking && canLockDBus ) {
lockedDBus = connection.registerService( service );
}
// Attempt to lock file, despite the possibility to do so and presence of the request (doLocking)
// This is required as QLockFile::getLockInfo() works only after QLockFile::lock() is called
bool lockResult = lockFile->tryLock();
SessionRunInfo runInfo;
if (lockResult) {
// Unlock immediately if we shouldn't have locked it
if( haveDBus && !lockedDBus ) {
lockFile->unlock();
}
} else {
// If locking failed, retrieve the lock's metadata
lockFile->getLockInfo(&runInfo.holderPid, &runInfo.holderHostname, &runInfo.holderApp );
runInfo.isRunning = !haveDBus || !canLockDBus;
if( haveDBus && lockedDBus ) {
// Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded
forceRemoveLockfile(lockFilename);
lockResult = lockFile->tryLock();
Q_ASSERT(lockResult);
}
}
// Set the result by D-Bus status
if (doLocking && (haveDBus ? lockedDBus : lockResult)) {
return TryLockSessionResult(QSharedPointer<ISessionLock>(new SessionLock(sessionId, lockFile)));
} else {
return TryLockSessionResult(runInfo);
}
}
QString SessionLock::id()
{
return m_sessionId;
}
SessionLock::SessionLock(const QString& sessionId, const QSharedPointer<QLockFile>& lockFile)
: m_sessionId(sessionId)
, m_lockFile(lockFile)
{
Q_ASSERT(lockFile->isLocked());
}
SessionLock::~SessionLock()
{
m_lockFile->unlock();
bool unregistered = QDBusConnection::sessionBus().unregisterService( dBusServiceNameForSession(m_sessionId) );
Q_UNUSED(unregistered);
}
void SessionLock::removeFromDisk()
{
Q_ASSERT(m_lockFile->isLocked());
// unlock first to prevent warnings: "Could not remove our own lock file ..."
m_lockFile->unlock();
QDir(SessionController::sessionDirectory(m_sessionId)).removeRecursively();
}
QString SessionLock::handleLockedSession(const QString& sessionName, const QString& sessionId,
const SessionRunInfo& runInfo)
{
if( !runInfo.isRunning ) {
return sessionId;
}
// try to make the locked session active
{
// The timeout for "ensureVisible" call
// Leave it sufficiently low to avoid waiting for hung instances.
static const int timeout_ms = 1000;
QDBusMessage message = QDBusMessage::createMethodCall( dBusServiceNameForSession(sessionId),
QStringLiteral("/kdevelop/MainWindow"),
QStringLiteral("org.kdevelop.MainWindow"),
QStringLiteral("ensureVisible") );
QDBusMessage reply = QDBusConnection::sessionBus().call( message,
QDBus::Block,
timeout_ms );
if( reply.type() == QDBusMessage::ReplyMessage ) {
QTextStream out(stdout);
out << i18nc(
"@info:shell",
"Running %1 instance (PID: %2) detected, making this one visible instead of starting a new one",
runInfo.holderApp, runInfo.holderPid)
<< Qt::endl;
return QString();
} else {
qCWarning(SHELL) << i18nc("@info:shell", "Running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid);
}
}
// otherwise ask the user whether we should retry
QString problemDescription = i18nc("@info",
"The given application did not respond to a DBUS call, "
"it may have crashed or is hanging.");
QString problemHeader;
if( runInfo.holderPid != -1 ) {
problemHeader = i18nc("@info", "Failed to lock the session <em>%1</em>, "
"already locked by %2 on %3 (PID %4).",
sessionName, runInfo.holderApp, runInfo.holderHostname, runInfo.holderPid);
} else {
problemHeader = i18nc("@info", "Failed to lock the session <em>%1</em> (lock-file unavailable).",
sessionName);
}
QString problemResolution = i18nc("@info", "<p>Please, close the offending application instance "
"or choose another session to launch.</p>");
QString errmsg = QLatin1String("<p>") + problemHeader + QLatin1String("<br>") + problemDescription + QLatin1String("</p>") + problemResolution;
KGuiItem retry = KStandardGuiItem::cont();
retry.setText(i18nc("@action:button", "Retry Startup"));
KGuiItem choose = KStandardGuiItem::configure();
choose.setText(i18nc("@action:button", "Choose Another Session"));
KGuiItem cancel = KStandardGuiItem::quit();
int ret = KMessageBox::warningTwoActionsCancel(
nullptr, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), retry, choose, cancel);
switch( ret ) {
case KMessageBox::PrimaryAction:
return sessionId;
case KMessageBox::SecondaryAction: {
QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.",
sessionName);
return SessionController::showSessionChooserDialog(errmsg);
}
case KMessageBox::Cancel:
default:
break;
}
return QString();
}
}
|