File indexing completed on 2024-04-28 05:35:30

0001 /*
0002     ksmserver - the KDE session management server
0003 
0004     SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
0005 
0006     SPDX-FileContributor: Oswald Buddenhagen <ob6@inf.tu-dresden.de>
0007 
0008     some code taken from the dcopserver (part of the KDE libraries), which is
0009     SPDX-FileCopyrightText: 1999 Matthias Ettrich <ettrich@kde.org>
0010     SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
0011 
0012     SPDX-License-Identifier: MIT
0013 */
0014 
0015 #include <config-ksmserver.h>
0016 #include <config-workspace.h>
0017 
0018 #include <ksmserver_debug.h>
0019 
0020 #include <pwd.h>
0021 #include <sys/param.h>
0022 #include <sys/stat.h>
0023 #include <sys/types.h>
0024 #ifdef HAVE_SYS_TIME_H
0025 #include <sys/time.h>
0026 #endif
0027 #include <sys/socket.h>
0028 #include <sys/un.h>
0029 
0030 #include <assert.h>
0031 #include <climits>
0032 #include <errno.h>
0033 #include <signal.h>
0034 #include <stdlib.h>
0035 #include <string.h>
0036 #include <time.h>
0037 #include <unistd.h>
0038 
0039 #include <QApplication>
0040 #include <QFile>
0041 #include <QFutureWatcher>
0042 #include <QTimer>
0043 #include <QtConcurrentRun>
0044 
0045 #include "client.h"
0046 #include "global.h"
0047 #include "server.h"
0048 #include <KConfig>
0049 #include <KConfigGroup>
0050 #include <KLocalizedString>
0051 #include <KNotification>
0052 #include <KSharedConfig>
0053 
0054 #include "kwinsession_interface.h"
0055 #include "logoutprompt_interface.h"
0056 #include "shutdown_interface.h"
0057 
0058 enum KWinSessionState {
0059     Normal = 0,
0060     Saving = 1,
0061     Quitting = 2,
0062 };
0063 
0064 bool KSMServer::closeSession()
0065 {
0066     qCDebug(KSMSERVER) << "Close session called. Current state is:" << state;
0067 
0068     Q_ASSERT(calledFromDBus());
0069     setDelayedReply(true);
0070 
0071     const QDBusMessage callerContext = message();
0072 
0073     auto conn = std::make_shared<QMetaObject::Connection>(QMetaObject::Connection());
0074     *conn = connect(this, &KSMServer::logoutFinished, this, [callerContext, conn](bool sessionClosed) {
0075         auto reply = callerContext.createReply(sessionClosed);
0076         QDBusConnection::sessionBus().send(reply);
0077         QObject::disconnect(*conn);
0078     });
0079 
0080     performLogout();
0081     return false;
0082 }
0083 
0084 bool KSMServer::isShuttingDown() const
0085 {
0086     return state >= Shutdown;
0087 }
0088 
0089 void KSMServer::performLogout()
0090 {
0091     if (state >= Shutdown) { // already performing shutdown
0092         return;
0093     }
0094     if (state != Idle) {
0095         QTimer::singleShot(1000, this, &KSMServer::performLogout);
0096     }
0097 
0098     auto setStateReply = m_kwinInterface->setState(KWinSessionState::Saving);
0099 
0100     state = Shutdown;
0101 
0102     // shall we save the session on logout?
0103     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("General"));
0104     saveSession = (cg.readEntry("loginMode", QStringLiteral("restorePreviousLogout")) == QLatin1String("restorePreviousLogout"));
0105 
0106     qCDebug(KSMSERVER) << "saveSession is " << saveSession;
0107 
0108     if (saveSession) {
0109         sessionGroup = SESSION_PREFIX + SESSION_PREVIOUS_LOGOUT;
0110     }
0111 
0112     saveType = saveSession ? SmSaveBoth : SmSaveGlobal;
0113 #ifndef NO_LEGACY_SESSION_MANAGEMENT
0114     performLegacySessionSave();
0115 #endif
0116     startProtection();
0117 
0118     // Tell KWin to start saving before we start tearing down clients
0119     // as any "Save changes?" prompt might meddle with the state
0120     if (saveSession) {
0121         setStateReply.waitForFinished(); // do we have to wait for this to finish?
0122 
0123         qCDebug(KSMSERVER) << "Telling KWin we're about to save session" << currentSession();
0124 
0125         auto saveSessionCall = m_kwinInterface->aboutToSaveSession(currentSession());
0126         // We need to wait for KWin to save the initial state, e.g. active client and
0127         // current desktop before we signal any clients to quit. They might bring up
0128         // "Save changes?" prompts altering the state.
0129         // KWin doesn't talk to KSMServer directly anymore, so this won't deadlock.
0130         saveSessionCall.waitForFinished();
0131     }
0132 
0133     const auto pendingClients = clients;
0134 
0135     for (KSMClient *c : pendingClients) {
0136         c->resetState();
0137 
0138         SmsSaveYourself(c->connection(), saveType, true, SmInteractStyleAny, false);
0139     }
0140 
0141     qCDebug(KSMSERVER) << "clients should be empty, " << clients.count();
0142 
0143     if (clients.isEmpty()) {
0144         completeShutdownOrCheckpoint();
0145     }
0146 }
0147 
0148 void KSMServer::saveCurrentSession()
0149 {
0150     if (state != Idle) {
0151         return;
0152     }
0153 
0154     if (currentSession().isEmpty() || currentSession() == SESSION_PREVIOUS_LOGOUT) {
0155         sessionGroup = SESSION_PREFIX + QString::fromLocal8Bit(SESSION_BY_USER);
0156     }
0157 
0158     state = Checkpoint;
0159 
0160     saveType = SmSaveLocal;
0161     saveSession = true;
0162 #ifndef NO_LEGACY_SESSION_MANAGEMENT
0163     performLegacySessionSave();
0164 #endif
0165 
0166     auto aboutToSaveCall = m_kwinInterface->aboutToSaveSession(currentSession());
0167     aboutToSaveCall.waitForFinished();
0168 
0169     const auto pendingClients = clients;
0170     for (KSMClient *c : pendingClients) {
0171         c->resetState();
0172         SmsSaveYourself(c->connection(), saveType, false, SmInteractStyleNone, false);
0173     }
0174     if (clients.isEmpty()) {
0175         completeShutdownOrCheckpoint();
0176     }
0177 }
0178 
0179 void KSMServer::saveCurrentSessionAs(const QString &session)
0180 {
0181     if (state != Idle) {
0182         return;
0183     }
0184     sessionGroup = SESSION_PREFIX + session;
0185     saveCurrentSession();
0186 }
0187 
0188 // callbacks
0189 void KSMServer::saveYourselfDone(KSMClient *client, bool success)
0190 {
0191     Q_UNUSED(success)
0192     if (state == Idle) {
0193         // State saving when it's not shutdown or checkpoint. Probably
0194         // a shutdown was canceled and the client is finished saving
0195         // only now. Discard the saved state in order to avoid
0196         // the saved data building up.
0197         QStringList discard = client->discardCommand();
0198         if (!discard.isEmpty()) {
0199             executeCommand(discard);
0200         }
0201         return;
0202     }
0203     // Always fake success to make Plasma's logout not block with broken
0204     // apps. A perfect ksmserver would display a warning box at
0205     // the very end.
0206     client->saveYourselfDone = true;
0207     completeShutdownOrCheckpoint();
0208     startProtection();
0209 }
0210 
0211 void KSMServer::interactRequest(KSMClient *client, int /*dialogType*/)
0212 {
0213     if (state == Shutdown || state == ClosingSubSession) {
0214         client->pendingInteraction = true;
0215     } else {
0216         SmsInteract(client->connection());
0217     }
0218 
0219     handlePendingInteractions();
0220 }
0221 
0222 void KSMServer::interactDone(KSMClient *client, bool cancelShutdown_)
0223 {
0224     if (client != clientInteracting) {
0225         return; // should not happen
0226     }
0227     clientInteracting = nullptr;
0228     if (cancelShutdown_) {
0229         cancelShutdown(client);
0230     } else {
0231         handlePendingInteractions();
0232     }
0233 }
0234 
0235 void KSMServer::phase2Request(KSMClient *client)
0236 {
0237     client->waitForPhase2 = true;
0238     client->wasPhase2 = true;
0239     completeShutdownOrCheckpoint();
0240 }
0241 
0242 void KSMServer::handlePendingInteractions()
0243 {
0244     if (clientInteracting) {
0245         return;
0246     }
0247 
0248     foreach (KSMClient *c, clients) {
0249         if (c->pendingInteraction) {
0250             clientInteracting = c;
0251             c->pendingInteraction = false;
0252             break;
0253         }
0254     }
0255     if (clientInteracting) {
0256         endProtection();
0257         SmsInteract(clientInteracting->connection());
0258     } else {
0259         startProtection();
0260     }
0261 }
0262 
0263 void KSMServer::cancelShutdown(KSMClient *c)
0264 {
0265     clientInteracting = nullptr;
0266     qCDebug(KSMSERVER) << state;
0267     if (state == ClosingSubSession) {
0268         clientsToKill.clear();
0269         clientsToSave.clear();
0270         Q_EMIT subSessionCloseCanceled();
0271     } else {
0272         qCDebug(KSMSERVER) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown.";
0273         KNotification::event(QStringLiteral("cancellogout"), i18n("Logout canceled by '%1'", c->program()), QPixmap(), KNotification::DefaultEvent);
0274         foreach (KSMClient *c, clients) {
0275             SmsShutdownCancelled(c->connection());
0276             if (c->saveYourselfDone) {
0277                 // Discard also saved state.
0278                 QStringList discard = c->discardCommand();
0279                 if (!discard.isEmpty()) {
0280                     executeCommand(discard);
0281                 }
0282             }
0283             c->resetState();
0284         }
0285     }
0286     state = Idle;
0287 
0288     m_kwinInterface->setState(KWinSessionState::Normal);
0289 
0290     Q_EMIT logoutFinished(false);
0291 }
0292 
0293 void KSMServer::startProtection()
0294 {
0295     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0296     config->reparseConfiguration(); // config may have changed in the KControl module
0297     KConfigGroup cg(config, QStringLiteral("General"));
0298 
0299     int timeout = cg.readEntry("clientShutdownTimeoutSecs", 15) * 1000;
0300 
0301     protectionTimer.setSingleShot(true);
0302     protectionTimer.start(timeout);
0303 }
0304 
0305 void KSMServer::endProtection()
0306 {
0307     protectionTimer.stop();
0308 }
0309 
0310 /*
0311 Internal protection slot, invoked when clients do not react during
0312 shutdown.
0313 */
0314 void KSMServer::protectionTimeout()
0315 {
0316     if ((state != Shutdown && state != Checkpoint && state != ClosingSubSession) || clientInteracting) {
0317         return;
0318     }
0319 
0320     foreach (KSMClient *c, clients) {
0321         if (!c->saveYourselfDone && !c->waitForPhase2) {
0322             qCDebug(KSMSERVER) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")";
0323             c->saveYourselfDone = true;
0324         }
0325     }
0326     completeShutdownOrCheckpoint();
0327     startProtection();
0328 }
0329 
0330 void KSMServer::completeShutdownOrCheckpoint()
0331 {
0332     qCDebug(KSMSERVER) << "completeShutdownOrCheckpoint called";
0333     if (state != Shutdown && state != Checkpoint && state != ClosingSubSession)
0334         return;
0335 
0336     QList<KSMClient *> pendingClients;
0337     if (state == ClosingSubSession) {
0338         pendingClients = clientsToSave;
0339     } else {
0340         pendingClients = clients;
0341     }
0342 
0343     foreach (KSMClient *c, pendingClients) {
0344         if (!c->saveYourselfDone && !c->waitForPhase2) {
0345             return; // not done yet
0346         }
0347     }
0348 
0349     // do phase 2
0350     bool waitForPhase2 = false;
0351     foreach (KSMClient *c, pendingClients) {
0352         if (!c->saveYourselfDone && c->waitForPhase2) {
0353             c->waitForPhase2 = false;
0354             SmsSaveYourselfPhase2(c->connection());
0355             waitForPhase2 = true;
0356         }
0357     }
0358     if (waitForPhase2) {
0359         return;
0360     }
0361 
0362     if (saveSession) {
0363         storeSession();
0364     } else {
0365         discardSession();
0366     }
0367 
0368     qCDebug(KSMSERVER) << "state is " << state;
0369     if (state == Shutdown) {
0370         KNotification *n = KNotification::event(QStringLiteral("exitkde"), QString(), QPixmap(), KNotification::DefaultEvent); // Plasma says good bye
0371         connect(n, &KNotification::closed, this, &KSMServer::startKilling);
0372         state = WaitingForKNotify;
0373         // https://bugs.kde.org/show_bug.cgi?id=228005
0374         // if sound is not working for some reason (e.g. no phonon
0375         // backends are installed) the closed() signal never happens
0376         // and logoutSoundFinished() never gets called. Add this timer to make
0377         // sure the shutdown procedure continues even if sound system is broken.
0378         QTimer::singleShot(5000, this, [=, this] {
0379             if (state == WaitingForKNotify) {
0380                 n->deleteLater();
0381                 startKilling();
0382             }
0383         });
0384     } else if (state == Checkpoint) {
0385         foreach (KSMClient *c, clients) {
0386             SmsSaveComplete(c->connection());
0387         }
0388         state = Idle;
0389     } else { // ClosingSubSession
0390         startKillingSubSession();
0391     }
0392 }
0393 
0394 void KSMServer::startKilling()
0395 {
0396     qCDebug(KSMSERVER) << "Starting killing clients";
0397     if (state == Killing) {
0398         // we are already killing
0399         return;
0400     }
0401     // kill all clients
0402     state = Killing;
0403 
0404     m_kwinInterface->setState(KWinSessionState::Quitting);
0405 
0406     foreach (KSMClient *c, clients) {
0407         qCDebug(KSMSERVER) << "startKilling: client " << c->program() << "(" << c->clientId() << ")";
0408         SmsDie(c->connection());
0409     }
0410 
0411     qCDebug(KSMSERVER) << " We killed all clients. We have now clients.count()=" << clients.count() << Qt::endl;
0412     completeKilling();
0413     QTimer::singleShot(10000, this, &KSMServer::timeoutQuit);
0414 }
0415 
0416 void KSMServer::completeKilling()
0417 {
0418     qCDebug(KSMSERVER) << "KSMServer::completeKilling clients.count()=" << clients.count() << Qt::endl;
0419     if (state == Killing) {
0420         if (!clients.isEmpty()) // still waiting for clients to go away
0421             return;
0422         killingCompleted();
0423     }
0424 }
0425 
0426 // shutdown is fully complete
0427 void KSMServer::killingCompleted()
0428 {
0429     Q_EMIT logoutFinished(true);
0430 }
0431 
0432 void KSMServer::timeoutQuit()
0433 {
0434     foreach (KSMClient *c, clients) {
0435         qCWarning(KSMSERVER) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")";
0436     }
0437     killingCompleted();
0438 }
0439 
0440 void KSMServer::saveSubSession(const QString &name, QStringList saveAndClose, QStringList saveOnly)
0441 {
0442     if (state != Idle) { // performing startup
0443         qCDebug(KSMSERVER) << "not idle!" << state;
0444         return;
0445     }
0446     qCDebug(KSMSERVER) << name << saveAndClose << saveOnly;
0447     state = ClosingSubSession;
0448     saveType = SmSaveBoth; // both or local? what does it mean?
0449     saveSession = true;
0450     sessionGroup = SUBSESSION_PREFIX + name;
0451 
0452 #ifndef NO_LEGACY_SESSION_MANAGEMENT
0453     // performLegacySessionSave(); FIXME
0454 #endif
0455 
0456     startProtection();
0457     foreach (KSMClient *c, clients) {
0458         if (saveAndClose.contains(QString::fromLocal8Bit(c->clientId()))) {
0459             c->resetState();
0460             SmsSaveYourself(c->connection(), saveType, true, SmInteractStyleAny, false);
0461             clientsToSave << c;
0462             clientsToKill << c;
0463         } else if (saveOnly.contains(QString::fromLocal8Bit(c->clientId()))) {
0464             c->resetState();
0465             SmsSaveYourself(c->connection(), saveType, true, SmInteractStyleAny, false);
0466             clientsToSave << c;
0467         }
0468     }
0469     completeShutdownOrCheckpoint();
0470 }
0471 
0472 void KSMServer::startKillingSubSession()
0473 {
0474     qCDebug(KSMSERVER) << "Starting killing clients";
0475     // kill all clients
0476     state = KillingSubSession;
0477     foreach (KSMClient *c, clientsToKill) {
0478         qCDebug(KSMSERVER) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")";
0479         SmsDie(c->connection());
0480     }
0481 
0482     qCDebug(KSMSERVER) << " We killed some clients. We have now clients.count()=" << clients.count() << Qt::endl;
0483     completeKillingSubSession();
0484     QTimer::singleShot(10000, this, &KSMServer::signalSubSessionClosed);
0485 }
0486 
0487 void KSMServer::completeKillingSubSession()
0488 {
0489     qCDebug(KSMSERVER) << "KSMServer::completeKillingSubSession clients.count()=" << clients.count() << Qt::endl;
0490     if (state == KillingSubSession) {
0491         if (!clientsToKill.isEmpty()) {
0492             return; // still waiting for clients to go away
0493         }
0494         signalSubSessionClosed();
0495     }
0496 }
0497 
0498 void KSMServer::signalSubSessionClosed()
0499 {
0500     if (state != KillingSubSession) {
0501         return;
0502     }
0503     clientsToKill.clear();
0504     clientsToSave.clear();
0505     // TODO tell the subSession manager the close request was carried out
0506     // so that plasma can close its stuff
0507     state = Idle;
0508     qCDebug(KSMSERVER) << state;
0509     Q_EMIT subSessionClosed();
0510 }
0511 
0512 void KSMServer::resetLogout()
0513 {
0514     state = Idle;
0515 }