kwin/xwl/xwayland.cpp
Vlad Zahorodnii 2093820aba xwayland: Avoid creating a tree query on crash
If Xwayland has crashed, the Workspace will block stacking order updates
and start destroying all X11 clients.

Once stacking order updates are unblocked, the Workspace will mark the X
stacking order as dirty and create a new Xcb::Tree object.

We don't want to create that Xcb::Tree object because accessing it
after the XCB connection has been shut down will lead to a crash.

BUG: 427688
FIXED-IN: 5.20.1
2020-10-14 16:17:42 +00:00

396 lines
12 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "xwayland.h"
#include "databridge.h"
#include "main_wayland.h"
#include "options.h"
#include "utils.h"
#include "wayland_server.h"
#include "xcbutils.h"
#include "xwayland_logging.h"
#include <KLocalizedString>
#include <KNotification>
#include <KSelectionOwner>
#include <QAbstractEventDispatcher>
#include <QFile>
#include <QFutureWatcher>
#include <QTimer>
#include <QtConcurrentRun>
// system
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_SYS_PROCCTL_H
#include <unistd.h>
#endif
#include <sys/socket.h>
#include <cerrno>
#include <cstring>
static QByteArray readDisplay(int pipe)
{
QByteArray displayName;
QFile readPipe;
if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
qCWarning(KWIN_XWL) << "Failed to open X11 display name pipe:" << readPipe.errorString();
} else {
displayName = readPipe.readLine();
displayName.prepend(QByteArrayLiteral(":"));
displayName.remove(displayName.size() - 1, 1);
}
// close our pipe
close(pipe);
return displayName;
}
namespace KWin
{
namespace Xwl
{
Xwayland::Xwayland(ApplicationWaylandAbstract *app, QObject *parent)
: XwaylandInterface(parent)
, m_app(app)
{
m_resetCrashCountTimer = new QTimer(this);
m_resetCrashCountTimer->setSingleShot(true);
connect(m_resetCrashCountTimer, &QTimer::timeout, this, &Xwayland::resetCrashCount);
}
Xwayland::~Xwayland()
{
stop();
}
QProcess *Xwayland::process() const
{
return m_xwaylandProcess;
}
void Xwayland::start()
{
if (m_xwaylandProcess) {
return;
}
int pipeFds[2];
if (pipe(pipeFds) != 0) {
qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno));
emit errorOccurred();
return;
}
int sx[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
emit errorOccurred();
return;
}
int fd = dup(sx[1]);
if (fd < 0) {
qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
emit errorOccurred();
return;
}
const int waylandSocket = waylandServer()->createXWaylandConnection();
if (waylandSocket == -1) {
qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
emit errorOccurred();
return;
}
const int wlfd = dup(waylandSocket);
if (wlfd < 0) {
qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
emit errorOccurred();
return;
}
m_xcbConnectionFd = sx[0];
m_displayFileDescriptor = pipeFds[0];
m_xwaylandProcess = new Process(this);
m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
QProcessEnvironment env = m_app->processStartupEnvironment();
env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
env.insert("EGL_PLATFORM", QByteArrayLiteral("DRM"));
m_xwaylandProcess->setProcessEnvironment(env);
m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"),
QString::number(pipeFds[1]),
QStringLiteral("-rootless"),
QStringLiteral("-wm"),
QString::number(fd)});
connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &Xwayland::handleXwaylandError);
connect(m_xwaylandProcess, &QProcess::started, this, &Xwayland::handleXwaylandStarted);
connect(m_xwaylandProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &Xwayland::handleXwaylandFinished);
m_xwaylandProcess->start();
close(pipeFds[1]);
}
void Xwayland::stop()
{
if (!m_xwaylandProcess) {
return;
}
m_app->setClosingX11Connection(true);
// If Xwayland has crashed, we must deactivate the socket notifier and ensure that no X11
// events will be dispatched before blocking; otherwise we will simply hang...
uninstallSocketNotifier();
DataBridge::destroy();
destroyX11Connection();
// When the Xwayland process is finally terminated, the finished() signal will be emitted,
// however we don't actually want to process it anymore. Furthermore, we also don't really
// want to handle any errors that may occur during the teardown.
if (m_xwaylandProcess->state() != QProcess::NotRunning) {
disconnect(m_xwaylandProcess, nullptr, this, nullptr);
m_xwaylandProcess->terminate();
m_xwaylandProcess->waitForFinished(5000);
}
delete m_xwaylandProcess;
m_xwaylandProcess = nullptr;
waylandServer()->destroyXWaylandConnection(); // This one must be destroyed last!
m_app->setClosingX11Connection(false);
}
void Xwayland::restart()
{
stop();
start();
}
void Xwayland::dispatchEvents()
{
xcb_connection_t *connection = kwinApp()->x11Connection();
if (!connection) {
qCWarning(KWIN_XWL, "Attempting to dispatch X11 events with no connection");
return;
}
const int connectionError = xcb_connection_has_error(connection);
if (connectionError) {
qCWarning(KWIN_XWL, "The X11 connection broke (error %d)", connectionError);
stop();
return;
}
while (xcb_generic_event_t *event = xcb_poll_for_event(connection)) {
long result = 0;
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
dispatcher->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
free(event);
}
xcb_flush(connection);
}
void Xwayland::installSocketNotifier()
{
const int fileDescriptor = xcb_get_file_descriptor(kwinApp()->x11Connection());
m_socketNotifier = new QSocketNotifier(fileDescriptor, QSocketNotifier::Read, this);
connect(m_socketNotifier, &QSocketNotifier::activated, this, &Xwayland::dispatchEvents);
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, &Xwayland::dispatchEvents);
connect(dispatcher, &QAbstractEventDispatcher::awake, this, &Xwayland::dispatchEvents);
}
void Xwayland::uninstallSocketNotifier()
{
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
disconnect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, &Xwayland::dispatchEvents);
disconnect(dispatcher, &QAbstractEventDispatcher::awake, this, &Xwayland::dispatchEvents);
delete m_socketNotifier;
m_socketNotifier = nullptr;
}
void Xwayland::handleXwaylandStarted()
{
m_watcher = new QFutureWatcher<QByteArray>(this);
connect(m_watcher, &QFutureWatcher<QByteArray>::finished, this, &Xwayland::handleXwaylandReady);
m_watcher->setFuture(QtConcurrent::run(readDisplay, m_displayFileDescriptor));
}
void Xwayland::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
qCDebug(KWIN_XWL) << "Xwayland process has quit with exit code" << exitCode;
switch (exitStatus) {
case QProcess::NormalExit:
stop();
break;
case QProcess::CrashExit:
handleXwaylandCrashed();
break;
}
}
void Xwayland::handleXwaylandCrashed()
{
KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed"));
m_resetCrashCountTimer->stop();
switch (options->xwaylandCrashPolicy()) {
case XwaylandCrashPolicy::Restart:
if (++m_crashCount <= options->xwaylandMaxCrashCount()) {
restart();
m_resetCrashCountTimer->start(std::chrono::minutes(10));
} else {
qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times "
"over the past 10 minutes", m_crashCount);
stop();
}
break;
case XwaylandCrashPolicy::Stop:
stop();
break;
}
}
void Xwayland::resetCrashCount()
{
qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount;
m_crashCount = 0;
}
void Xwayland::handleXwaylandError(QProcess::ProcessError error)
{
switch (error) {
case QProcess::FailedToStart:
qCWarning(KWIN_XWL) << "Xwayland process failed to start";
return;
case QProcess::Crashed:
qCWarning(KWIN_XWL) << "Xwayland process crashed";
break;
case QProcess::Timedout:
qCWarning(KWIN_XWL) << "Xwayland operation timed out";
break;
case QProcess::WriteError:
case QProcess::ReadError:
qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland";
break;
case QProcess::UnknownError:
qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland";
break;
}
emit errorOccurred();
}
void Xwayland::handleXwaylandReady()
{
m_displayName = m_watcher->result();
m_watcher->deleteLater();
m_watcher = nullptr;
if (!createX11Connection()) {
emit errorOccurred();
return;
}
qCInfo(KWIN_XWL) << "Xwayland server started on display" << m_displayName;
qputenv("DISPLAY", m_displayName);
// create selection owner for WM_S0 - magic X display number expected by XWayland
KSelectionOwner owner("WM_S0", kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
owner.claim(true);
DataBridge::create(this);
auto env = m_app->processStartupEnvironment();
env.insert(QStringLiteral("DISPLAY"), m_displayName);
m_app->setProcessStartupEnvironment(env);
emit started();
Xcb::sync(); // Trigger possible errors, there's still a chance to abort
}
bool Xwayland::createX11Connection()
{
xcb_connection_t *connection = xcb_connect_to_fd(m_xcbConnectionFd, nullptr);
const int errorCode = xcb_connection_has_error(connection);
if (errorCode) {
qCDebug(KWIN_XWL, "Failed to establish the XCB connection (error %d)", errorCode);
return false;
}
xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
Q_ASSERT(screen);
m_app->setX11Connection(connection);
m_app->setX11DefaultScreen(screen);
m_app->setX11ScreenNumber(0);
m_app->setX11RootWindow(screen->root);
m_app->createAtoms();
m_app->installNativeX11EventFilter();
installSocketNotifier();
// Note that it's very important to have valid x11RootWindow(), x11ScreenNumber(), and
// atoms when the rest of kwin is notified about the new X11 connection.
emit m_app->x11ConnectionChanged();
return true;
}
void Xwayland::destroyX11Connection()
{
if (!m_app->x11Connection()) {
return;
}
emit m_app->x11ConnectionAboutToBeDestroyed();
Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
m_app->destroyAtoms();
m_app->removeNativeX11EventFilter();
xcb_disconnect(m_app->x11Connection());
m_xcbConnectionFd = -1;
m_app->setX11Connection(nullptr);
m_app->setX11DefaultScreen(nullptr);
m_app->setX11ScreenNumber(-1);
m_app->setX11RootWindow(XCB_WINDOW_NONE);
emit m_app->x11ConnectionChanged();
}
DragEventReply Xwayland::dragMoveFilter(Toplevel *target, const QPoint &pos)
{
DataBridge *bridge = DataBridge::self();
if (!bridge) {
return DragEventReply::Wayland;
}
return bridge->dragMoveFilter(target, pos);
}
} // namespace Xwl
} // namespace KWin