xwayland: Manually create sockets

This provides kwin greater control over how X11 sockets are created for
Xwayland. For example, it can be used to ensure that the DISPLAY remains
the same across Xwayland server restarts or launching Xwayland on
demand.

Even though -listen <fd> option is deprecated, we still pass it because
older versions of Xwayland may not have the -listenfd option.
This commit is contained in:
Vlad Zahorodnii 2021-02-08 17:36:41 +02:00
parent 3d363fb797
commit 9f0f452702
5 changed files with 312 additions and 39 deletions

View file

@ -302,6 +302,7 @@ add_library(KWinXwaylandServerModule OBJECT
xwl/selection_source.cpp
xwl/transfer.cpp
xwl/xwayland.cpp
xwl/xwaylandsocket.cpp
${xwaylandlogging_SOURCES}
)

View file

@ -10,6 +10,7 @@
*/
#include "xwayland.h"
#include "databridge.h"
#include "xwaylandsocket.h"
#include "main_wayland.h"
#include "options.h"
@ -27,6 +28,7 @@
#include <QFile>
#include <QHostInfo>
#include <QRandomGenerator>
#include <QScopeGuard>
#include <QTimer>
#include <QtConcurrentRun>
@ -42,22 +44,6 @@
#include <cerrno>
#include <cstring>
static int readDisplay(int pipe)
{
int display = -1;
QFile readPipe;
if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
qCWarning(KWIN_XWL) << "Failed to open X11 display name pipe:" << readPipe.errorString();
} else {
display = readPipe.readLine().trimmed().toInt();
}
// close our pipe
close(pipe);
return display;
}
namespace KWin
{
namespace Xwl
@ -88,6 +74,35 @@ void Xwayland::start()
return;
}
QScopedPointer<XwaylandSocket> socket(new XwaylandSocket());
if (!socket->isValid()) {
qCWarning(KWIN_XWL) << "Failed to create Xwayland connection sockets";
emit errorOccurred();
return;
}
// The abstract socket file descriptor will be passed to Xwayland and closed by us.
const int abstractSocket = dup(socket->abstractFileDescriptor());
if (abstractSocket == -1) {
qCWarning(KWIN_XWL, "Failed to duplicate file descriptor: %s", strerror(errno));
emit errorOccurred();
return;
}
auto abstractSocketCleanup = qScopeGuard([&abstractSocket]() {
close(abstractSocket);
});
// The unix socket file descriptor will be passed to Xwayland and closed by us.
const int unixSocket = dup(socket->unixFileDescriptor());
if (unixSocket == -1) {
qCWarning(KWIN_XWL, "Failed to duplicate file descriptor: %s", strerror(errno));
emit errorOccurred();
return;
}
auto unixSocketCleanup = qScopeGuard([&unixSocket]() {
close(unixSocket);
});
int pipeFds[2];
if (pipe(pipeFds) != 0) {
qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno));
@ -127,7 +142,7 @@ void Xwayland::start()
}
m_xcbConnectionFd = sx[0];
m_displayFileDescriptor = pipeFds[0];
m_socket.reset(socket.take());
m_xwaylandProcess = new Process(this);
m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
@ -139,15 +154,22 @@ void Xwayland::start()
env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1"));
}
m_xwaylandProcess->setProcessEnvironment(env);
m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"),
QString::number(pipeFds[1]),
m_xwaylandProcess->setArguments({m_socket->name(),
QStringLiteral("-displayfd"), QString::number(pipeFds[1]),
QStringLiteral("-rootless"),
QStringLiteral("-wm"), QString::number(fd),
QStringLiteral("-auth"), m_authorityFile.fileName()});
QStringLiteral("-auth"), m_authorityFile.fileName(),
QStringLiteral("-listen"), QString::number(abstractSocket),
QStringLiteral("-listen"), QString::number(unixSocket)});
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);
// When Xwayland starts writing the display name to displayfd, it is ready. Alternatively,
// the Xwayland can send us the SIGUSR1 signal, but it's already reserved for VT hand-off.
m_readyNotifier = new QSocketNotifier(pipeFds[0], QSocketNotifier::Read, this);
connect(m_readyNotifier, &QSocketNotifier::activated, this, &Xwayland::handleXwaylandReady);
m_xwaylandProcess->start();
close(pipeFds[1]);
}
@ -163,6 +185,7 @@ void Xwayland::stop()
// 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();
maybeDestroyReadyNotifier();
DataBridge::destroy();
m_selectionOwner.reset();
@ -180,6 +203,7 @@ void Xwayland::stop()
delete m_xwaylandProcess;
m_xwaylandProcess = nullptr;
m_socket.reset();
waylandServer()->destroyXWaylandConnection(); // This one must be destroyed last!
m_app->setClosingX11Connection(false);
@ -238,13 +262,6 @@ void Xwayland::uninstallSocketNotifier()
m_socketNotifier = nullptr;
}
void Xwayland::handleXwaylandStarted()
{
m_watcher = new QFutureWatcher<int>(this);
connect(m_watcher, &QFutureWatcher<int>::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;
@ -312,17 +329,16 @@ void Xwayland::handleXwaylandError(QProcess::ProcessError error)
void Xwayland::handleXwaylandReady()
{
m_display = m_watcher->result();
m_watcher->deleteLater();
m_watcher = nullptr;
// We don't care what Xwayland writes to the displayfd, we just want to know when it's ready.
maybeDestroyReadyNotifier();
if (!createX11Connection() || !writeXauthorityEntries()) {
emit errorOccurred();
return;
}
const QByteArray displayName = ':' + QByteArray::number(m_display);
const QByteArray displayName = ':' + QByteArray::number(m_socket->display());
qCInfo(KWIN_XWL) << "Xwayland server started on display" << displayName;
qputenv("DISPLAY", displayName);
qputenv("XAUTHORITY", m_authorityFile.fileName().toUtf8());
@ -343,6 +359,16 @@ void Xwayland::handleXwaylandReady()
Xcb::sync(); // Trigger possible errors, there's still a chance to abort
}
void Xwayland::maybeDestroyReadyNotifier()
{
if (m_readyNotifier) {
close(m_readyNotifier->socket());
delete m_readyNotifier;
m_readyNotifier = nullptr;
}
}
bool Xwayland::createX11Connection()
{
xcb_connection_t *connection = xcb_connect_to_fd(m_xcbConnectionFd, nullptr);
@ -436,7 +462,7 @@ static QByteArray generateXauthorityCookie()
bool Xwayland::writeXauthorityEntries()
{
const QByteArray hostname = QHostInfo::localHostName().toUtf8();
const QByteArray display = QByteArray::number(m_display);
const QByteArray display = QByteArray::number(m_socket->display());
const QByteArray name = QByteArrayLiteral("MIT-MAGIC-COOKIE-1");
const QByteArray cookie = generateXauthorityCookie();

View file

@ -12,7 +12,6 @@
#include "xwayland_interface.h"
#include <QFutureWatcher>
#include <QProcess>
#include <QSocketNotifier>
#include <QTemporaryFile>
@ -22,6 +21,7 @@ class KSelectionOwner;
namespace KWin
{
class ApplicationWaylandAbstract;
class XwaylandSocket;
namespace Xwl
{
@ -85,7 +85,6 @@ private Q_SLOTS:
void dispatchEvents();
void resetCrashCount();
void handleXwaylandStarted();
void handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus);
void handleXwaylandCrashed();
void handleXwaylandError(QProcess::ProcessError error);
@ -94,6 +93,7 @@ private Q_SLOTS:
private:
void installSocketNotifier();
void uninstallSocketNotifier();
void maybeDestroyReadyNotifier();
bool createX11Connection();
void destroyX11Connection();
@ -103,16 +103,15 @@ private:
DragEventReply dragMoveFilter(Toplevel *target, const QPoint &pos) override;
int m_displayFileDescriptor = -1;
int m_xcbConnectionFd = -1;
QProcess *m_xwaylandProcess = nullptr;
QSocketNotifier *m_socketNotifier = nullptr;
QSocketNotifier *m_readyNotifier = nullptr;
QTimer *m_resetCrashCountTimer = nullptr;
int m_display = -1;
QFutureWatcher<int> *m_watcher = nullptr;
ApplicationWaylandAbstract *m_app;
QScopedPointer<KSelectionOwner> m_selectionOwner;
QTemporaryFile m_authorityFile;
QScopedPointer<XwaylandSocket> m_socket;
int m_crashCount = 0;
Q_DISABLE_COPY(Xwayland)

211
src/xwl/xwaylandsocket.cpp Normal file
View file

@ -0,0 +1,211 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "xwaylandsocket.h"
#include "xwayland_logging.h"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace KWin
{
class UnixSocketAddress
{
public:
enum class Type {
Unix,
Abstract,
};
UnixSocketAddress(const QString &socketPath, Type type);
const sockaddr *data() const;
int size() const;
private:
QByteArray m_buffer;
};
UnixSocketAddress::UnixSocketAddress(const QString &socketPath, Type type)
{
const QByteArray encodedSocketPath = QFile::encodeName(socketPath);
int byteCount = offsetof(sockaddr_un, sun_path) + encodedSocketPath.size();
if (type == Type::Abstract) {
byteCount++; // For the first '\0'.
}
m_buffer.resize(byteCount);
sockaddr_un *address = reinterpret_cast<sockaddr_un *>(m_buffer.data());
address->sun_family = AF_UNIX;
if (type == Type::Unix) {
qstrcpy(address->sun_path, encodedSocketPath);
} else {
*address->sun_path = '\0';
qstrcpy(address->sun_path + 1, encodedSocketPath);
}
}
const sockaddr *UnixSocketAddress::data() const
{
return reinterpret_cast<const sockaddr *>(m_buffer.data());
}
int UnixSocketAddress::size() const
{
return m_buffer.size();
}
static QString lockFileNameForDisplay(int display)
{
return QStringLiteral("/tmp/.X%1-lock").arg(display);
}
static QString socketFileNameForDisplay(int display)
{
return QStringLiteral("/tmp/.X11-unix/X%1").arg(display);
}
static bool tryLockFile(const QString &lockFileName)
{
for (int attempt = 0; attempt < 3; ++attempt) {
QFile lockFile(lockFileName);
if (lockFile.open(QFile::WriteOnly | QFile::NewOnly)) {
char buffer[12];
snprintf(buffer, sizeof(buffer), "%10lld\n", QCoreApplication::applicationPid());
if (lockFile.write(buffer, sizeof(buffer) - 1) != sizeof(buffer) - 1) {
qCWarning(KWIN_XWL) << "Failed to write pid to lock file:" << lockFile.errorString();
lockFile.remove();
return false;
}
return true;
} else if (lockFile.open(QFile::ReadOnly)) {
const int lockPid = lockFile.readLine().trimmed().toInt();
if (!lockPid) {
return false;
}
if (kill(lockPid, 0) < 0 && errno == ESRCH) {
lockFile.remove(); // Try to grab the lock file in the next loop iteration.
} else {
return false;
}
}
}
return false;
}
static int listen_helper(const QString &filePath, UnixSocketAddress::Type type)
{
const UnixSocketAddress socketAddress(filePath, type);
int fileDescriptor = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fileDescriptor == -1) {
return -1;
}
if (bind(fileDescriptor, socketAddress.data(), socketAddress.size()) == -1) {
close(fileDescriptor);
return -1;
}
if (listen(fileDescriptor, 1) == -1) {
close(fileDescriptor);
return -1;
}
return fileDescriptor;
}
XwaylandSocket::XwaylandSocket()
{
QDir socketDirectory(QStringLiteral("/tmp/.X11-unix"));
if (!socketDirectory.exists()) {
socketDirectory.mkpath(QStringLiteral("."));
}
for (int display = 0; display < 100; ++display) {
const QString socketFilePath = socketFileNameForDisplay(display);
const QString lockFilePath = lockFileNameForDisplay(display);
if (!tryLockFile(lockFilePath)) {
continue;
}
const int unixFileDescriptor = listen_helper(socketFilePath, UnixSocketAddress::Type::Unix);
if (unixFileDescriptor == -1) {
QFile::remove(lockFilePath);
continue;
}
const int abstractFileDescriptor = listen_helper(socketFilePath, UnixSocketAddress::Type::Abstract);
if (abstractFileDescriptor == -1) {
QFile::remove(lockFilePath);
QFile::remove(socketFilePath);
close(unixFileDescriptor);
continue;
}
m_socketFilePath = socketFilePath;
m_lockFilePath = lockFilePath;
m_unixFileDescriptor = unixFileDescriptor;
m_abstractFileDescriptor = abstractFileDescriptor;
m_display = display;
return;
}
qCWarning(KWIN_XWL) << "Failed to find free X11 connection socket";
}
XwaylandSocket::~XwaylandSocket()
{
if (m_unixFileDescriptor != -1) {
close(m_unixFileDescriptor);
}
if (m_abstractFileDescriptor != -1) {
close(m_abstractFileDescriptor);
}
if (!m_socketFilePath.isEmpty()) {
QFile::remove(m_socketFilePath);
}
if (!m_lockFilePath.isEmpty()) {
QFile::remove(m_lockFilePath);
}
}
bool XwaylandSocket::isValid() const
{
return m_display != -1;
}
int XwaylandSocket::unixFileDescriptor() const
{
return m_unixFileDescriptor;
}
int XwaylandSocket::abstractFileDescriptor() const
{
return m_abstractFileDescriptor;
}
int XwaylandSocket::display() const
{
return m_display;
}
QString XwaylandSocket::name() const
{
return ":" + QString::number(m_display);
}
} // namespace KWin

36
src/xwl/xwaylandsocket.h Normal file
View file

@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QByteArray>
#include <QString>
namespace KWin
{
class XwaylandSocket
{
public:
XwaylandSocket();
~XwaylandSocket();
bool isValid() const;
int display() const;
QString name() const;
int unixFileDescriptor() const;
int abstractFileDescriptor() const;
private:
int m_unixFileDescriptor = -1;
int m_abstractFileDescriptor = -1;
int m_display = -1;
QString m_socketFilePath;
QString m_lockFilePath;
};
} // namespace KWin