File indexing completed on 2024-04-28 04:37:25
0001 /* 0002 SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "sessionlock.h" 0008 0009 #include "debug.h" 0010 #include "sessioncontroller.h" 0011 0012 #include <KLocalizedString> 0013 #include <KMessageBox> 0014 #include <KMessageBox_KDevCompat> 0015 0016 #include <QDBusConnectionInterface> 0017 #include <QFile> 0018 #include <QDir> 0019 0020 namespace KDevelop { 0021 QString lockFileForSession( const QString& id ) 0022 { 0023 return SessionController::sessionDirectory( id ) + QLatin1String("/lock"); 0024 } 0025 0026 QString dBusServiceNameForSession( const QString& id ) 0027 { 0028 // We remove starting "{" and ending "}" from the string UUID representation 0029 // as D-Bus apparently doesn't allow them in service names 0030 return QLatin1String("org.kdevelop.kdevplatform-lock-") + id.midRef(1, id.size() - 2); 0031 } 0032 0033 /// Force-removes the lock-file. 0034 void forceRemoveLockfile(const QString& lockFilename) 0035 { 0036 if( QFile::exists( lockFilename ) ) { 0037 QFile::remove( lockFilename ); 0038 } 0039 } 0040 0041 TryLockSessionResult SessionLock::tryLockSession(const QString& sessionId, bool doLocking) 0042 { 0043 ///FIXME: if this is hit, someone tried to lock a non-existing session 0044 /// this should be fixed by using a proper data type distinct from 0045 /// QString for id's, i.e. QUuid or similar. 0046 Q_ASSERT(QFile::exists(SessionController::sessionDirectory( sessionId ))); 0047 0048 /* 0049 * We've got two locking mechanisms here: D-Bus unique service name (based on the session id) 0050 * and a plain lockfile (QLockFile). 0051 * The latter is required to get the appname/pid of the locking instance 0052 * in case if it's stale/hanging/crashed (to make user know which PID he needs to kill). 0053 * D-Bus mechanism is the primary one. 0054 * 0055 * Since there is a kind of "logic tree", the code is a bit hard. 0056 */ 0057 const QString service = dBusServiceNameForSession( sessionId ); 0058 QDBusConnection connection = QDBusConnection::sessionBus(); 0059 QDBusConnectionInterface* connectionInterface = connection.interface(); 0060 0061 const QString lockFilename = lockFileForSession( sessionId ); 0062 QSharedPointer<QLockFile> lockFile(new QLockFile( lockFilename )); 0063 0064 const bool haveDBus = connection.isConnected(); 0065 const bool canLockDBus = haveDBus && connectionInterface && !connectionInterface->isServiceRegistered( service ); 0066 bool lockedDBus = false; 0067 0068 // Lock D-Bus if we can and we need to 0069 if( doLocking && canLockDBus ) { 0070 lockedDBus = connection.registerService( service ); 0071 } 0072 0073 // Attempt to lock file, despite the possibility to do so and presence of the request (doLocking) 0074 // This is required as QLockFile::getLockInfo() works only after QLockFile::lock() is called 0075 bool lockResult = lockFile->tryLock(); 0076 SessionRunInfo runInfo; 0077 if (lockResult) { 0078 // Unlock immediately if we shouldn't have locked it 0079 if( haveDBus && !lockedDBus ) { 0080 lockFile->unlock(); 0081 } 0082 } else { 0083 // If locking failed, retrieve the lock's metadata 0084 lockFile->getLockInfo(&runInfo.holderPid, &runInfo.holderHostname, &runInfo.holderApp ); 0085 runInfo.isRunning = !haveDBus || !canLockDBus; 0086 0087 if( haveDBus && lockedDBus ) { 0088 // Since the lock-file is secondary, try to force-lock it if D-Bus locking succeeded 0089 forceRemoveLockfile(lockFilename); 0090 lockResult = lockFile->tryLock(); 0091 Q_ASSERT(lockResult); 0092 } 0093 } 0094 0095 // Set the result by D-Bus status 0096 if (doLocking && (haveDBus ? lockedDBus : lockResult)) { 0097 return TryLockSessionResult(QSharedPointer<ISessionLock>(new SessionLock(sessionId, lockFile))); 0098 } else { 0099 return TryLockSessionResult(runInfo); 0100 } 0101 } 0102 0103 QString SessionLock::id() 0104 { 0105 return m_sessionId; 0106 } 0107 0108 SessionLock::SessionLock(const QString& sessionId, const QSharedPointer<QLockFile>& lockFile) 0109 : m_sessionId(sessionId) 0110 , m_lockFile(lockFile) 0111 { 0112 Q_ASSERT(lockFile->isLocked()); 0113 } 0114 0115 SessionLock::~SessionLock() 0116 { 0117 m_lockFile->unlock(); 0118 bool unregistered = QDBusConnection::sessionBus().unregisterService( dBusServiceNameForSession(m_sessionId) ); 0119 Q_UNUSED(unregistered); 0120 } 0121 0122 void SessionLock::removeFromDisk() 0123 { 0124 Q_ASSERT(m_lockFile->isLocked()); 0125 // unlock first to prevent warnings: "Could not remove our own lock file ..." 0126 m_lockFile->unlock(); 0127 QDir(SessionController::sessionDirectory(m_sessionId)).removeRecursively(); 0128 } 0129 0130 QString SessionLock::handleLockedSession(const QString& sessionName, const QString& sessionId, 0131 const SessionRunInfo& runInfo) 0132 { 0133 if( !runInfo.isRunning ) { 0134 return sessionId; 0135 } 0136 0137 // try to make the locked session active 0138 { 0139 // The timeout for "ensureVisible" call 0140 // Leave it sufficiently low to avoid waiting for hung instances. 0141 static const int timeout_ms = 1000; 0142 0143 QDBusMessage message = QDBusMessage::createMethodCall( dBusServiceNameForSession(sessionId), 0144 QStringLiteral("/kdevelop/MainWindow"), 0145 QStringLiteral("org.kdevelop.MainWindow"), 0146 QStringLiteral("ensureVisible") ); 0147 QDBusMessage reply = QDBusConnection::sessionBus().call( message, 0148 QDBus::Block, 0149 timeout_ms ); 0150 if( reply.type() == QDBusMessage::ReplyMessage ) { 0151 QTextStream out(stdout); 0152 out << i18nc( 0153 "@info:shell", 0154 "Running %1 instance (PID: %2) detected, making this one visible instead of starting a new one", 0155 runInfo.holderApp, runInfo.holderPid) 0156 << Qt::endl; 0157 return QString(); 0158 } else { 0159 qCWarning(SHELL) << i18nc("@info:shell", "Running %1 instance (PID: %2) is apparently hung", runInfo.holderApp, runInfo.holderPid); 0160 } 0161 } 0162 0163 // otherwise ask the user whether we should retry 0164 QString problemDescription = i18nc("@info", 0165 "The given application did not respond to a DBUS call, " 0166 "it may have crashed or is hanging."); 0167 0168 QString problemHeader; 0169 if( runInfo.holderPid != -1 ) { 0170 problemHeader = i18nc("@info", "Failed to lock the session <em>%1</em>, " 0171 "already locked by %2 on %3 (PID %4).", 0172 sessionName, runInfo.holderApp, runInfo.holderHostname, runInfo.holderPid); 0173 } else { 0174 problemHeader = i18nc("@info", "Failed to lock the session <em>%1</em> (lock-file unavailable).", 0175 sessionName); 0176 } 0177 0178 QString problemResolution = i18nc("@info", "<p>Please, close the offending application instance " 0179 "or choose another session to launch.</p>"); 0180 0181 QString errmsg = QLatin1String("<p>") + problemHeader + QLatin1String("<br>") + problemDescription + QLatin1String("</p>") + problemResolution; 0182 0183 KGuiItem retry = KStandardGuiItem::cont(); 0184 retry.setText(i18nc("@action:button", "Retry Startup")); 0185 0186 KGuiItem choose = KStandardGuiItem::configure(); 0187 choose.setText(i18nc("@action:button", "Choose Another Session")); 0188 0189 KGuiItem cancel = KStandardGuiItem::quit(); 0190 int ret = KMessageBox::warningTwoActionsCancel( 0191 nullptr, errmsg, i18nc("@title:window", "Failed to Lock Session %1", sessionName), retry, choose, cancel); 0192 switch( ret ) { 0193 case KMessageBox::PrimaryAction: 0194 return sessionId; 0195 0196 case KMessageBox::SecondaryAction: { 0197 QString errmsg = i18nc("@info", "The session %1 is already active in another running instance.", 0198 sessionName); 0199 return SessionController::showSessionChooserDialog(errmsg); 0200 } 0201 0202 case KMessageBox::Cancel: 0203 default: 0204 break; 0205 } 0206 0207 return QString(); 0208 } 0209 0210 }