File indexing completed on 2024-05-19 16:35:37

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0006     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0007     SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "xwaylandlauncher.h"
0012 
0013 #include <config-kwin.h>
0014 
0015 #include "xwayland_logging.h"
0016 #include "xwaylandsocket.h"
0017 
0018 #include "options.h"
0019 #include "wayland_server.h"
0020 
0021 #if KWIN_BUILD_NOTIFICATIONS
0022 #include <KLocalizedString>
0023 #include <KNotification>
0024 #endif
0025 
0026 #include <QAbstractEventDispatcher>
0027 #include <QDataStream>
0028 #include <QFile>
0029 #include <QHostInfo>
0030 #include <QRandomGenerator>
0031 #include <QScopeGuard>
0032 #include <QTimer>
0033 
0034 // system
0035 #include <cerrno>
0036 #include <cstring>
0037 #include <sys/socket.h>
0038 #include <unistd.h>
0039 
0040 namespace KWin
0041 {
0042 namespace Xwl
0043 {
0044 
0045 XwaylandLauncher::XwaylandLauncher(QObject *parent)
0046     : QObject(parent)
0047 {
0048     m_resetCrashCountTimer = new QTimer(this);
0049     m_resetCrashCountTimer->setSingleShot(true);
0050     connect(m_resetCrashCountTimer, &QTimer::timeout, this, &XwaylandLauncher::resetCrashCount);
0051 }
0052 
0053 XwaylandLauncher::~XwaylandLauncher()
0054 {
0055 }
0056 
0057 void XwaylandLauncher::setListenFDs(const QVector<int> &listenFds)
0058 {
0059     m_listenFds = listenFds;
0060 }
0061 
0062 void XwaylandLauncher::setDisplayName(const QString &displayName)
0063 {
0064     m_displayName = displayName;
0065 }
0066 
0067 void XwaylandLauncher::setXauthority(const QString &xauthority)
0068 {
0069     m_xAuthority = xauthority;
0070 }
0071 
0072 void XwaylandLauncher::start()
0073 {
0074     if (m_xwaylandProcess) {
0075         return;
0076     }
0077 
0078     if (!m_listenFds.isEmpty()) {
0079         Q_ASSERT(!m_displayName.isEmpty());
0080     } else {
0081         m_socket.reset(new XwaylandSocket(XwaylandSocket::OperationMode::CloseFdsOnExec));
0082         if (!m_socket->isValid()) {
0083             qFatal("Failed to establish X11 socket");
0084         }
0085         m_displayName = m_socket->name();
0086         m_listenFds = m_socket->fileDescriptors();
0087     }
0088 
0089     startInternal();
0090 }
0091 
0092 bool XwaylandLauncher::startInternal()
0093 {
0094     Q_ASSERT(!m_xwaylandProcess);
0095 
0096     QVector<int> fdsToClose;
0097     auto cleanup = qScopeGuard([&fdsToClose] {
0098         for (const int fd : std::as_const(fdsToClose)) {
0099             close(fd);
0100         }
0101     });
0102 
0103     int pipeFds[2];
0104     if (pipe(pipeFds) != 0) {
0105         qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno));
0106         Q_EMIT errorOccurred();
0107         return false;
0108     }
0109     fdsToClose << pipeFds[1];
0110 
0111     int sx[2];
0112     if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
0113         qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
0114         Q_EMIT errorOccurred();
0115         return false;
0116     }
0117     int fd = dup(sx[1]);
0118     if (fd < 0) {
0119         qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
0120         Q_EMIT errorOccurred();
0121         return false;
0122     }
0123 
0124     const int waylandSocket = waylandServer()->createXWaylandConnection();
0125     if (waylandSocket == -1) {
0126         qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
0127         Q_EMIT errorOccurred();
0128         return false;
0129     }
0130     const int wlfd = dup(waylandSocket);
0131     if (wlfd < 0) {
0132         qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
0133         Q_EMIT errorOccurred();
0134         return false;
0135     }
0136 
0137     m_xcbConnectionFd = sx[0];
0138 
0139     QStringList arguments;
0140 
0141     arguments << m_displayName;
0142 
0143     if (!m_listenFds.isEmpty()) {
0144         // xauthority externally set and managed
0145         if (!m_xAuthority.isEmpty()) {
0146             arguments << QStringLiteral("-auth") << m_xAuthority;
0147         }
0148 
0149         for (int socket : std::as_const(m_listenFds)) {
0150             int dupSocket = dup(socket);
0151             fdsToClose << dupSocket;
0152 #if HAVE_XWAYLAND_LISTENFD
0153             arguments << QStringLiteral("-listenfd") << QString::number(dupSocket);
0154 #else
0155             arguments << QStringLiteral("-listen") << QString::number(dupSocket);
0156 #endif
0157         }
0158     }
0159 
0160     arguments << QStringLiteral("-displayfd") << QString::number(pipeFds[1]);
0161     arguments << QStringLiteral("-rootless");
0162     arguments << QStringLiteral("-wm") << QString::number(fd);
0163 
0164     m_xwaylandProcess = new QProcess(this);
0165     m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
0166     m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
0167     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0168     env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
0169     if (qEnvironmentVariableIsSet("KWIN_XWAYLAND_DEBUG")) {
0170         env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1"));
0171     }
0172     m_xwaylandProcess->setProcessEnvironment(env);
0173     m_xwaylandProcess->setArguments(arguments);
0174     connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &XwaylandLauncher::handleXwaylandError);
0175     connect(m_xwaylandProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
0176             this, &XwaylandLauncher::handleXwaylandFinished);
0177 
0178     // When Xwayland starts writing the display name to displayfd, it is ready. Alternatively,
0179     // the Xwayland can send us the SIGUSR1 signal, but it's already reserved for VT hand-off.
0180     m_readyNotifier = new QSocketNotifier(pipeFds[0], QSocketNotifier::Read, this);
0181     connect(m_readyNotifier, &QSocketNotifier::activated, this, [this]() {
0182         maybeDestroyReadyNotifier();
0183         Q_EMIT started();
0184     });
0185 
0186     m_xwaylandProcess->start();
0187 
0188     return true;
0189 }
0190 
0191 void XwaylandLauncher::stop()
0192 {
0193     if (!m_xwaylandProcess) {
0194         return;
0195     }
0196 
0197     stopInternal();
0198 }
0199 
0200 QString XwaylandLauncher::displayName() const
0201 {
0202     return m_displayName;
0203 }
0204 
0205 QString XwaylandLauncher::xauthority() const
0206 {
0207     return m_xAuthority;
0208 }
0209 
0210 int XwaylandLauncher::xcbConnectionFd() const
0211 {
0212     return m_xcbConnectionFd;
0213 }
0214 
0215 QProcess *XwaylandLauncher::process() const
0216 {
0217     return m_xwaylandProcess;
0218 }
0219 
0220 void XwaylandLauncher::stopInternal()
0221 {
0222     Q_EMIT finished();
0223 
0224     maybeDestroyReadyNotifier();
0225     waylandServer()->destroyXWaylandConnection();
0226 
0227     // When the Xwayland process is finally terminated, the finished() signal will be emitted,
0228     // however we don't actually want to process it anymore. Furthermore, we also don't really
0229     // want to handle any errors that may occur during the teardown.
0230     if (m_xwaylandProcess->state() != QProcess::NotRunning) {
0231         disconnect(m_xwaylandProcess, nullptr, this, nullptr);
0232         m_xwaylandProcess->terminate();
0233         m_xwaylandProcess->waitForFinished(5000);
0234     }
0235     delete m_xwaylandProcess;
0236     m_xwaylandProcess = nullptr;
0237 }
0238 
0239 void XwaylandLauncher::restartInternal()
0240 {
0241     if (m_xwaylandProcess) {
0242         stopInternal();
0243     }
0244     startInternal();
0245 }
0246 
0247 void XwaylandLauncher::maybeDestroyReadyNotifier()
0248 {
0249     if (m_readyNotifier) {
0250         close(m_readyNotifier->socket());
0251 
0252         delete m_readyNotifier;
0253         m_readyNotifier = nullptr;
0254     }
0255 }
0256 
0257 void XwaylandLauncher::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus)
0258 {
0259     qCDebug(KWIN_XWL) << "Xwayland process has quit with exit status:" << exitStatus << "exit code:" << exitCode;
0260 
0261 #if KWIN_BUILD_NOTIFICATIONS
0262     KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed"));
0263 #endif
0264     m_resetCrashCountTimer->stop();
0265 
0266     switch (options->xwaylandCrashPolicy()) {
0267     case XwaylandCrashPolicy::Restart:
0268         if (++m_crashCount <= options->xwaylandMaxCrashCount()) {
0269             restartInternal();
0270             m_resetCrashCountTimer->start(std::chrono::minutes(10));
0271         } else {
0272             qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times "
0273                                 "over the past 10 minutes",
0274                       m_crashCount);
0275             stop();
0276         }
0277         break;
0278     case XwaylandCrashPolicy::Stop:
0279         stop();
0280         break;
0281     }
0282 }
0283 
0284 void XwaylandLauncher::resetCrashCount()
0285 {
0286     qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount;
0287     m_crashCount = 0;
0288 }
0289 
0290 void XwaylandLauncher::handleXwaylandError(QProcess::ProcessError error)
0291 {
0292     switch (error) {
0293     case QProcess::FailedToStart:
0294         qCWarning(KWIN_XWL) << "Xwayland process failed to start";
0295         return;
0296     case QProcess::Crashed:
0297         qCWarning(KWIN_XWL) << "Xwayland process crashed";
0298         break;
0299     case QProcess::Timedout:
0300         qCWarning(KWIN_XWL) << "Xwayland operation timed out";
0301         break;
0302     case QProcess::WriteError:
0303     case QProcess::ReadError:
0304         qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland";
0305         break;
0306     case QProcess::UnknownError:
0307         qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland";
0308         break;
0309     }
0310     Q_EMIT errorOccurred();
0311 }
0312 
0313 }
0314 }