From b76b00f4926b851f271eaf3c14af0342a8a44d35 Mon Sep 17 00:00:00 2001 From: Pier Luigi Fiorini Date: Tue, 27 Aug 2019 22:31:00 +0200 Subject: [PATCH] WIP Xorg user session We want to support Xorg user session and keep running it as root as an option, so we don't break the workflow of some users. As a matter of fact, when we run the X11 server as sddm user, we also start the display start and stop scripts as user. Scripts that need root privileges won't work. To support both modes, we move the code to sddm-x11-helper and either run it as root or sddm user using sddm-helper. Closes: #246 --- src/CMakeLists.txt | 1 + src/common/Configuration.h | 2 + src/common/x11helperprotocol.h | 53 +++++ src/daemon/Display.cpp | 3 - src/daemon/DisplayServer.h | 1 - src/daemon/XorgDisplayServer.cpp | 333 +++++++++++++++++++------------ src/daemon/XorgDisplayServer.h | 18 +- src/x11helper/CMakeLists.txt | 17 ++ src/x11helper/main.cpp | 100 ++++++++++ src/x11helper/runner.cpp | 313 +++++++++++++++++++++++++++++ src/x11helper/runner.h | 71 +++++++ 11 files changed, 773 insertions(+), 139 deletions(-) create mode 100644 src/common/x11helperprotocol.h create mode 100644 src/x11helper/CMakeLists.txt create mode 100644 src/x11helper/main.cpp create mode 100644 src/x11helper/runner.cpp create mode 100644 src/x11helper/runner.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b8ea946f..6a6b95245 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,3 +3,4 @@ configure_file("common/Constants.h.in" "common/Constants.h" IMMEDIATE @ONLY) add_subdirectory(daemon) add_subdirectory(greeter) add_subdirectory(helper) +add_subdirectory(x11helper) diff --git a/src/common/Configuration.h b/src/common/Configuration.h index 3b894ad07..a9152f59e 100644 --- a/src/common/Configuration.h +++ b/src/common/Configuration.h @@ -43,6 +43,8 @@ namespace SDDM { "If property is set to none, numlock won't be changed\n" "NOTE: Currently ignored if autologin is enabled.")); Entry(InputMethod, QString, QStringLiteral("qtvirtualkeyboard"), _S("Input method module")); + Entry(DisplayServer, QString, _S("x11"), _S("Which display server should be used.\n" + "Valid values are: x11, x11-user.")); Entry(Namespaces, QStringList, QStringList(), _S("Comma-separated list of Linux namespaces for user session to enter")); Entry(DisplayServer, QString, _S("x11"), _S("Which display server should be used.\n" "NOTE: Wayland support is currently considered experimental.\n" diff --git a/src/common/x11helperprotocol.h b/src/common/x11helperprotocol.h new file mode 100644 index 000000000..145ec9398 --- /dev/null +++ b/src/common/x11helperprotocol.h @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2019 Pier Luigi Fiorini + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + ***************************************************************************/ + +#ifndef SDDM_X11HELPERPROTOCOL_H +#define SDDM_X11HELPERPROTOCOL_H + +#include + +namespace SDDM { + +enum class X11HelperMessage { + Unknown, + Started, + Last +}; + +inline QDataStream &operator>>(QDataStream &s, X11HelperMessage &m) +{ + qint32 what; + s >> what; + if (what < qint32(X11HelperMessage::Unknown) || what >= qint32(X11HelperMessage::Last)) { + s.setStatus(QDataStream::ReadCorruptData); + return s; + } + m = X11HelperMessage(what); + return s; +} + +inline QDataStream &operator<<(QDataStream &s, const X11HelperMessage &m) +{ + s << qint32(m); + return s; +} + +} // namespace SDDM + +#endif // SDDM_X11HELPERPROTOCOL_H diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index 72cc46ef5..6def9b3aa 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -147,9 +147,6 @@ namespace SDDM { if (m_started) return; - // setup display - m_displayServer->setupDisplay(); - // log message qDebug() << "Display server started."; diff --git a/src/daemon/DisplayServer.h b/src/daemon/DisplayServer.h index 3f72913b0..45e098f3e 100644 --- a/src/daemon/DisplayServer.h +++ b/src/daemon/DisplayServer.h @@ -44,7 +44,6 @@ namespace SDDM { virtual bool start() = 0; virtual void stop() = 0; virtual void finished() = 0; - virtual void setupDisplay() = 0; signals: void started(); diff --git a/src/daemon/XorgDisplayServer.cpp b/src/daemon/XorgDisplayServer.cpp index e7eb342cb..b6cd5c750 100644 --- a/src/daemon/XorgDisplayServer.cpp +++ b/src/daemon/XorgDisplayServer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** -* Copyright (c) 2014 Pier Luigi Fiorini +* Copyright (c) 2014-2019 Pier Luigi Fiorini * Copyright (c) 2013 Abdurrahman AVCI * * This program is free software; you can redistribute it and/or modify @@ -18,19 +18,24 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ***************************************************************************/ +#include +#include +#include +#include + #include "XorgDisplayServer.h" #include "Configuration.h" +#include "Constants.h" #include "DaemonApp.h" #include "Display.h" +#include "SafeDataStream.h" #include "SignalHandler.h" #include "Seat.h" +#include "x11helperprotocol.h" #include -#include -#include #include -#include #include @@ -41,31 +46,8 @@ namespace SDDM { XorgDisplayServer::XorgDisplayServer(Display *parent) : DisplayServer(parent) { - // get auth directory - QString authDir = QStringLiteral(RUNTIME_DIR); - - // use "." as authdir in test mode - if (daemonApp->testing()) - authDir = QStringLiteral("."); - - // create auth dir if not existing - QDir().mkpath(authDir); - - // set auth path - m_authPath = QStringLiteral("%1/%2").arg(authDir).arg(QUuid::createUuid().toString()); - - // generate cookie - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, 15); - - // resever 32 bytes - m_cookie.reserve(32); - - // create a random hexadecimal number - const char *digits = "0123456789abcdef"; - for (int i = 0; i < 32; ++i) - m_cookie[i] = digits[dis(gen)]; + createAuthFile(); + changeOwner(m_authPath); } XorgDisplayServer::~XorgDisplayServer() { @@ -88,6 +70,7 @@ namespace SDDM { return m_cookie; } +<<<<<<< HEAD bool XorgDisplayServer::addCookie(const QString &file) { // log message qDebug() << "Adding cookie to" << file; @@ -113,11 +96,14 @@ namespace SDDM { return pclose(fp) == 0; } +======= +>>>>>>> b1ba4e7 (WIP Xorg user session) bool XorgDisplayServer::start() { // check flag if (m_started) return false; +<<<<<<< HEAD // create process process = new QProcess(this); @@ -200,33 +186,73 @@ namespace SDDM { // close the other side of pipe in our process, otherwise reading // from it may stuck even X server exit. close(pipeFds[1]); - - QFile readPipe; - - if (!readPipe.open(pipeFds[0], QIODevice::ReadOnly)) { - qCritical("Failed to open pipe to start X Server"); - - close(pipeFds[0]); - return false; - } - QByteArray displayNumber = readPipe.readLine(); - if (displayNumber.size() < 2) { - // X server gave nothing (or a whitespace). - qCritical("Failed to read display number from pipe"); - - close(pipeFds[0]); +======= + // Start socket server + if (m_socketServer) + m_socketServer->deleteLater(); + m_socketServer = new QLocalServer(this); + connect(m_socketServer, &QLocalServer::newConnection, + this, &XorgDisplayServer::handleNewConnection); + m_socketServer->listen(QStringLiteral("sddm-x11-helper-%1").arg(QUuid::createUuid().toString().replace(QRegExp(QStringLiteral("[{}]")), QString()))); + changeOwner(m_socketServer->fullServerName()); + + // Command arguments + QStringList args; + args << QStringLiteral("--seat") << displayPtr()->seat()->name() + << QStringLiteral("--vt") << QString::number(displayPtr()->terminalId()) + << QStringLiteral("--socket") << m_socketServer->fullServerName() + << QStringLiteral("--auth") << m_authPath; + if (daemonApp->testing()) + args << QStringLiteral("--test-mode"); +>>>>>>> b1ba4e7 (WIP Xorg user session) + + qInfo("Starting X11 server..."); + + // Run helper + if (mainConfig.DisplayServer.get() == QStringLiteral("x11")) { + if (m_process) + m_process->deleteLater(); + m_process = new QProcess(this); + connect(m_process, QOverload::of(&QProcess::finished), + this, &XorgDisplayServer::finished); + + m_process->start(QStringLiteral(LIBEXEC_INSTALL_DIR "/sddm-x11-helper"), args); + if (!m_process->waitForStarted()) { + qCritical("Failed to start display server"); return false; } - displayNumber.prepend(QByteArray(":")); - displayNumber.remove(displayNumber.size() -1, 1); // trim trailing whitespace - m_display = QString::fromLocal8Bit(displayNumber); - - // close our pipe - close(pipeFds[0]); - - emit started(); + } else if (mainConfig.DisplayServer.get() == QStringLiteral("x11-user")) { + if (m_auth) + m_auth->deleteLater(); + m_auth = new Auth(this); + m_auth->setVerbose(true); + m_auth->setUser(QStringLiteral("sddm")); + m_auth->setGreeter(true); + connect(m_auth, &Auth::requestChanged, this, &XorgDisplayServer::onRequestChanged); + connect(m_auth, &Auth::sessionStarted, this, &XorgDisplayServer::onSessionStarted); + connect(m_auth, &Auth::finished, this, &XorgDisplayServer::onHelperFinished); + connect(m_auth, &Auth::info, this, &XorgDisplayServer::authInfo); + connect(m_auth, &Auth::error, this, &XorgDisplayServer::authError); + + QStringList cmd; + cmd << QStringLiteral(LIBEXEC_INSTALL_DIR "/sddm-x11-helper") + << args; + + QProcessEnvironment env; + env.insert(QStringLiteral("XDG_SEAT"), displayPtr()->seat()->name()); + env.insert(QStringLiteral("XDG_SEAT_PATH"), daemonApp->displayManager()->seatPath(displayPtr()->seat()->name())); + env.insert(QStringLiteral("XDG_SESSION_PATH"), daemonApp->displayManager()->sessionPath(QStringLiteral("Session%1").arg(daemonApp->newSessionId()))); + env.insert(QStringLiteral("XDG_VTNR"), QString::number(displayPtr()->terminalId())); + env.insert(QStringLiteral("XDG_SESSION_CLASS"), QStringLiteral("greeter")); + env.insert(QStringLiteral("XDG_SESSION_TYPE"), QStringLiteral("x11")); + env.insert(QStringLiteral("XORG_RUN_AS_USER_OK"), QStringLiteral("1")); + m_auth->insertEnvironment(env); + + m_auth->setSession(cmd.join(QLatin1Char(' '))); + m_auth->start(); } +<<<<<<< HEAD // The file is also used by the greeter, which does care about the // display number. Write the proper entry, if it's different. if(m_display != QStringLiteral(":0")) { @@ -241,6 +267,8 @@ namespace SDDM { m_started = true; // return success +======= +>>>>>>> b1ba4e7 (WIP Xorg user session) return true; } @@ -250,14 +278,16 @@ namespace SDDM { return; // log message - qDebug() << "Display server stopping..."; + qDebug() << "Stopping X11 server"; - // terminate process - process->terminate(); + if (m_process) { + // terminate process + m_process->terminate(); - // wait for finished - if (!process->waitForFinished(5000)) - process->kill(); + // wait for finished + if (!m_process->waitForFinished(5000)) + m_process->kill(); + } } void XorgDisplayServer::finished() { @@ -269,98 +299,137 @@ namespace SDDM { m_started = false; // log message - qDebug() << "Display server stopped."; - - QString displayStopCommand = mainConfig.X11.DisplayStopCommand.get(); - - // create display setup script process - QProcess *displayStopScript = new QProcess(); - - // set process environment - QProcessEnvironment env; - env.insert(QStringLiteral("DISPLAY"), m_display); - env.insert(QStringLiteral("HOME"), QStringLiteral("/")); - env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); - env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); - displayStopScript->setProcessEnvironment(env); - - // start display stop script - qDebug() << "Running display stop script " << displayStopCommand; - displayStopScript->start(displayStopCommand); + qInfo("X11 server stopped"); - // wait for finished - if (!displayStopScript->waitForFinished(5000)) - displayStopScript->kill(); - - // clean up the script process - displayStopScript->deleteLater(); - displayStopScript = nullptr; + // Stop socket server + if (m_socketServer) { + m_socketServer->close(); + m_socketServer->deleteLater(); + m_socketServer = nullptr; + } // clean up - process->deleteLater(); - process = nullptr; + if (m_process) { + m_process->deleteLater(); + m_process = nullptr; + } - // remove authority file - QFile::remove(m_authPath); + // Remove authority file + if (!m_authPath.isEmpty() && QFile::exists(m_authPath)) + QFile::remove(m_authPath); // emit signal emit stopped(); } - void XorgDisplayServer::setupDisplay() { - QString displayCommand = mainConfig.X11.DisplayCommand.get(); - - // create cursor setup process - QProcess *setCursor = new QProcess(); - // create display setup script process - QProcess *displayScript = new QProcess(); - - // set process environment - QProcessEnvironment env; - env.insert(QStringLiteral("DISPLAY"), m_display); - env.insert(QStringLiteral("HOME"), QStringLiteral("/")); - env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); - env.insert(QStringLiteral("XAUTHORITY"), m_authPath); - env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); - env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig.Theme.CursorTheme.get()); - setCursor->setProcessEnvironment(env); - displayScript->setProcessEnvironment(env); - - qDebug() << "Setting default cursor"; - setCursor->start(QStringLiteral("xsetroot -cursor_name left_ptr")); - - // delete setCursor on finish - connect(setCursor, QOverload::of(&QProcess::finished), setCursor, &QProcess::deleteLater); - - // wait for finished - if (!setCursor->waitForFinished(1000)) { - qWarning() << "Could not setup default cursor"; - setCursor->kill(); - } + void XorgDisplayServer::createAuthFile() + { + // Create auth directory + QString authDir = QStringLiteral(RUNTIME_DIR); + if (daemonApp->testing()) + authDir = QStringLiteral("."); + QDir().mkpath(authDir); + + // Set auth path + m_authPath = QStringLiteral("%1/%2").arg(authDir).arg(QUuid::createUuid().toString()); + + // Generate cookie + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 15); + + // Reseve 32 bytes + m_cookie.reserve(32); - // start display setup script - qDebug() << "Running display setup script " << displayCommand; - displayScript->start(displayCommand); + // Create a random hexadecimal number + const char *digits = "0123456789abcdef"; + for (int i = 0; i < 32; ++i) + m_cookie[i] = digits[dis(gen)]; + } - // delete displayScript on finish - connect(displayScript, QOverload::of(&QProcess::finished), displayScript, &QProcess::deleteLater); + void XorgDisplayServer::addCookie(const QString &fileName) + { + qDebug() << "Adding cookie to" << fileName; - // wait for finished - if (!displayScript->waitForFinished(30000)) - displayScript->kill(); + // Touch file + QFile fileHandler(fileName); + fileHandler.open(QIODevice::Append); + fileHandler.close(); - // reload config if needed - mainConfig.load(); + // Run xauth + QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(fileName); + FILE *fp = popen(qPrintable(cmd), "w"); + if (!fp) + return; + fprintf(fp, "remove %s\n", qPrintable(m_display)); + fprintf(fp, "add %s . %s\n", qPrintable(m_display), qPrintable(m_cookie)); + fprintf(fp, "exit\n"); + pclose(fp); } - void XorgDisplayServer::changeOwner(const QString &fileName) { - // change the owner and group of the auth file to the sddm user + void XorgDisplayServer::changeOwner(const QString &fileName) + { + // Change the owner and group of the auth file to the sddm user struct passwd *pw = getpwnam("sddm"); - if (!pw) - qWarning() << "Failed to find the sddm user. Owner of the auth file will not be changed."; - else { + if (pw) { if (chown(qPrintable(fileName), pw->pw_uid, pw->pw_gid) == -1) qWarning() << "Failed to change owner of the auth file."; + } else { + qWarning() << "Failed to find the sddm user. Owner of the auth file will not be changed."; + } + } + + void XorgDisplayServer::handleNewConnection() + { + while (m_socketServer->hasPendingConnections()) { + X11HelperMessage m = X11HelperMessage::Unknown; + QLocalSocket *socket = m_socketServer->nextPendingConnection(); + SafeDataStream str(socket); + str.receive(); + str >> m; + + if (m == X11HelperMessage::Started) { + str >> m_display; + + qInfo("X11 server started"); + + // Add cookie to auth file + addCookie(m_authPath); + + m_started = true; + emit started(); + } } } + + void XorgDisplayServer::onRequestChanged() + { + m_auth->request()->setFinishAutomatically(true); + } + + void XorgDisplayServer::onSessionStarted(bool success) + { + if (!success) + qWarning("X11 server failed to start"); + } + + void XorgDisplayServer::onHelperFinished(Auth::HelperExitStatus status) + { + finished(); + + m_auth->deleteLater(); + m_auth = nullptr; + } + + void XorgDisplayServer::authInfo(const QString &message, Auth::Info info) + { + Q_UNUSED(info) + qDebug("Message from X11 server: %s", qPrintable(message)); + } + + void XorgDisplayServer::authError(const QString &message, Auth::Error error) + { + Q_UNUSED(error) + qWarning("Error from X11 server: %s", qPrintable(message)); + } } diff --git a/src/daemon/XorgDisplayServer.h b/src/daemon/XorgDisplayServer.h index e97a0b531..d81e081fb 100644 --- a/src/daemon/XorgDisplayServer.h +++ b/src/daemon/XorgDisplayServer.h @@ -21,9 +21,11 @@ #ifndef SDDM_XORGDISPLAYSERVER_H #define SDDM_XORGDISPLAYSERVER_H +#include "Auth.h" #include "DisplayServer.h" class QProcess; +class QLocalServer; namespace SDDM { class XorgDisplayServer : public DisplayServer { @@ -46,15 +48,25 @@ namespace SDDM { bool start(); void stop(); void finished(); - void setupDisplay(); private: + QLocalServer *m_socketServer = nullptr; + QProcess *m_process = nullptr; + Auth *m_auth = nullptr; + QString m_authPath; QString m_cookie; - QProcess *process { nullptr }; - + void createAuthFile(); void changeOwner(const QString &fileName); + + private slots: + void handleNewConnection(); + void onRequestChanged(); + void onSessionStarted(bool success); + void onHelperFinished(Auth::HelperExitStatus status); + void authInfo(const QString &message, Auth::Info info); + void authError(const QString &message, Auth::Error error); }; } diff --git a/src/x11helper/CMakeLists.txt b/src/x11helper/CMakeLists.txt new file mode 100644 index 000000000..25c056b26 --- /dev/null +++ b/src/x11helper/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories( + "${CMAKE_SOURCE_DIR}/src/common" + "${CMAKE_BINARY_DIR}/src/common" +) + +set(SOURCES + ${CMAKE_SOURCE_DIR}/src/common/Configuration.cpp + ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.cpp + ${CMAKE_SOURCE_DIR}/src/common/SafeDataStream.cpp + main.cpp + runner.cpp + runner.h +) + +add_executable(sddm-x11-helper ${SOURCES}) +target_link_libraries(sddm-x11-helper Qt5::Core Qt5::Network) +install(TARGETS sddm-x11-helper DESTINATION "${CMAKE_INSTALL_LIBEXECDIR}") diff --git a/src/x11helper/main.cpp b/src/x11helper/main.cpp new file mode 100644 index 000000000..a2865f0a9 --- /dev/null +++ b/src/x11helper/main.cpp @@ -0,0 +1,100 @@ +/*************************************************************************** +* Copyright (C) 2019 Pier Luigi Fiorini +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +***************************************************************************/ + +#include +#include + +#include "runner.h" + +#define TR(x) QT_TRANSLATE_NOOP("Command line parser", QStringLiteral(x)) + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + app.setApplicationName(QStringLiteral("sddm-x11-helper")); + app.setOrganizationName(QStringLiteral("SDDM")); + + QCommandLineParser parser; + parser.setApplicationDescription(TR("SDDM X11 Helper")); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption socketOption(QStringLiteral("socket"), + TR("Socket name"), + TR("name")); + parser.addOption(socketOption); + + QCommandLineOption testModeOption(QStringLiteral("test-mode"), + TR("Enable test mode")); + parser.addOption(testModeOption); + + QCommandLineOption seatOption(QStringLiteral("seat"), + TR("Seat name"), + TR("seat")); + parser.addOption(seatOption); + + QCommandLineOption vtOption(QStringLiteral("vt"), + TR("Terminal identifier"), + TR("number")); + parser.addOption(vtOption); + + QCommandLineOption authFileOption(QStringLiteral("auth"), + TR("Auth file path"), + TR("path")); + parser.addOption(authFileOption); + + parser.process(app); + + if (!parser.isSet(socketOption)) { + qCritical("Please specify a socket name with --socket"); + return 127; + } + if (!parser.isSet(seatOption)) { + qCritical("Please specify a seat name with --seat"); + return 127; + } + if (!parser.isSet(vtOption)) { + qCritical("Please specify a terminal identifier with --vt"); + return 127; + } + if (!parser.isSet(authFileOption)) { + qCritical("Please specify an auth file with --auth"); + return 127; + } + + bool vtOptionValid = false; + int vt = parser.value(vtOption).toInt(&vtOptionValid); + if (!vtOptionValid) { + qCritical("Terminal identifier must be a number"); + return 127; + } + + auto *runner = new Runner(); + runner->setSocketName(parser.value(socketOption)); + runner->setTestMode(parser.isSet(testModeOption)); + runner->setSeat(parser.value(seatOption)); + runner->setTerminalId(vt); + runner->setAuthPath(parser.value(authFileOption)); + if (!runner->start()) { + runner->stop(); + return 1; + } + + return app.exec(); +} diff --git a/src/x11helper/runner.cpp b/src/x11helper/runner.cpp new file mode 100644 index 000000000..b2772b378 --- /dev/null +++ b/src/x11helper/runner.cpp @@ -0,0 +1,313 @@ +/*************************************************************************** +* Copyright (C) 2019 Pier Luigi Fiorini +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +***************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "runner.h" + +#include "Configuration.h" +#include "Constants.h" +#include "SafeDataStream.h" +#include "x11helperprotocol.h" + +#include + +Runner::Runner(QObject *parent) + : QObject(parent) + , m_socket(new QLocalSocket(this)) +{ + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, + this, &Runner::deleteLater); +} + +Runner::~Runner() +{ + m_socket->close(); + + stop(); +} + +QString Runner::socketName() const +{ + return m_socketName; +} + +void Runner::setSocketName(const QString &name) +{ + if (!m_socketName.isEmpty()) { + qWarning("Cannot set socket name after initialization"); + return; + } + + m_socketName = name; + + m_socket->connectToServer(m_socketName, QIODevice::ReadWrite | QIODevice::Unbuffered); + if (!m_socket->waitForConnected(2500)) + qCritical("Failed to connect to the daemon: %s", qPrintable(m_socket->errorString())); +} + +bool Runner::isTestMode() const +{ + return m_testMode; +} + +void Runner::setTestMode(bool testMode) +{ + m_testMode = testMode; +} + +QString Runner::display() const +{ + return m_display; +} + +QString Runner::seat() const +{ + return m_seat; +} + +void Runner::setSeat(const QString &seat) +{ + if (!m_seat.isEmpty()) { + qWarning("Cannot set seat name after initialization"); + return; + } + + m_seat = seat; +} + +int Runner::terminalId() const +{ + return m_terminalId; +} + +void Runner::setTerminalId(int id) +{ + m_terminalId = id; +} + +QString Runner::authPath() const +{ + return m_authPath; +} + +void Runner::setAuthPath(const QString &path) +{ + if (!m_authPath.isEmpty()) { + qWarning("Cannot set auth file path after initialization"); + return; + } + + m_authPath = path; +} + +bool Runner::start() +{ + if (m_process) + m_process->deleteLater(); + + m_process = new QProcess(this); + connect(m_process, &QProcess::started, + this, &Runner::started); + connect(m_process, QOverload::of(&QProcess::finished), + this, &Runner::finished); + + if (m_testMode) { + QStringList args; + QDir x11socketDir(QStringLiteral("/tmp/.X11-unix")); + int display = 100; + while (x11socketDir.exists(QStringLiteral("X%1").arg(display))) { + ++display; + } + m_display = QStringLiteral(":%1").arg(display); + args << m_display << QStringLiteral("-ac") + << QStringLiteral("-br") + << QStringLiteral("-noreset") + << QStringLiteral("-screen") << QStringLiteral("800x600"); + m_process->start(SDDM::mainConfig.X11.XephyrPath.get(), args); + if (!m_process->waitForStarted()) { + qCritical("Failed to start X11 server process"); + return false; + } + } else { + // Set process environment + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("XCURSOR_THEME"), SDDM::mainConfig.Theme.CursorTheme.get()); + m_process->setProcessEnvironment(env); + + // Create pipe for communicating with X server + // 0 == read from X, 1 == write to from X + int pipeFds[2]; + if (pipe(pipeFds) != 0) { + qCritical("Could not create pipe to start X server"); + return false; + } + + // Start display server + QStringList args = SDDM::mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), QString::SkipEmptyParts); + args << QStringLiteral("-auth") << m_authPath + << QStringLiteral("-background") << QStringLiteral("none") + << QStringLiteral("-noreset") + << QStringLiteral("-keeptty") + << QStringLiteral("-displayfd") << QString::number(pipeFds[1]) + << QStringLiteral("-seat") << m_seat; + if (m_seat == QLatin1String("seat0")) + args << QStringLiteral("vt%1").arg(m_terminalId); + qDebug() << "Running:" + << qPrintable(SDDM::mainConfig.X11.ServerPath.get()) + << qPrintable(args.join(QLatin1Char(' '))); + m_process->start(SDDM::mainConfig.X11.ServerPath.get(), args); + if (!m_process->waitForStarted()) { + qCritical("Failed to start X11 server process"); + close(pipeFds[0]); + return false; + } + + // Close the other side of pipe in our process, otherwise reading + // from it may stuck even X server exit + close(pipeFds[1]); + + QFile readPipe; + if (!readPipe.open(pipeFds[0], QIODevice::ReadOnly)) { + qCritical("Failed to open pipe to start X11 server"); + close(pipeFds[0]); + return false; + } + QByteArray displayNumber = readPipe.readLine(); + if (displayNumber.size() < 2) { + // X server gave nothing (or a whitespace) + qCritical("Failed to read display number from pipe"); + close(pipeFds[0]); + return false; + } + displayNumber.prepend(QByteArray(":")); + displayNumber.remove(displayNumber.size() -1, 1); // trim trailing whitespace + m_display = QString::fromLocal8Bit(displayNumber); + close(pipeFds[0]); + } + + // Send it back to the daemon + SDDM::SafeDataStream str(m_socket); + str << SDDM::X11HelperMessage::Started << m_display; + str.send(); + + return true; +} + +void Runner::stop() +{ + // Terminate process + if (m_process) { + m_process->terminate(); + if (!m_process->waitForFinished(5000)) + m_process->kill(); + } +} + +void Runner::started() +{ + if (m_display.isEmpty()) + return; + + QString displayCommand = SDDM::mainConfig.X11.DisplayCommand.get(); + + // Create cursor setup process + QProcess *setCursor = new QProcess(); + + // Create display setup script process + QProcess *displayScript = new QProcess(); + + // Set process environment + QProcessEnvironment env; + env.insert(QStringLiteral("DISPLAY"), m_display); + env.insert(QStringLiteral("HOME"), QStringLiteral("/")); + env.insert(QStringLiteral("PATH"), SDDM::mainConfig.Users.DefaultPath.get()); + env.insert(QStringLiteral("XAUTHORITY"), m_authPath); + env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); + env.insert(QStringLiteral("XCURSOR_THEME"), SDDM::mainConfig.Theme.CursorTheme.get()); + setCursor->setProcessEnvironment(env); + displayScript->setProcessEnvironment(env); + + qDebug() << "Setting default cursor"; + setCursor->start(QStringLiteral("xsetroot -cursor_name left_ptr")); + + // Delete setCursor on finish + connect(setCursor, QOverload::of(&QProcess::finished), + setCursor, &QProcess::deleteLater); + + // Wait for finished + if (!setCursor->waitForFinished(1000)) { + qWarning() << "Could not set default cursor"; + setCursor->kill(); + } + + // Start display setup script + qDebug() << "Running display setup script:" << displayCommand; + displayScript->start(displayCommand); + + // Delete displayScript on finish + connect(displayScript, QOverload::of(&QProcess::finished), + displayScript, &QProcess::deleteLater); + + // Wait for finished + if (!displayScript->waitForFinished(30000)) + displayScript->kill(); + + // Reload config if needed + SDDM::mainConfig.load(); +} + +void Runner::finished() +{ + if (!m_display.isEmpty()) { + QString displayStopCommand = SDDM::mainConfig.X11.DisplayStopCommand.get(); + + // create display setup script process + QProcess *displayStopScript = new QProcess(); + + // Set process environment + QProcessEnvironment env; + env.insert(QStringLiteral("DISPLAY"), m_display); + env.insert(QStringLiteral("HOME"), QStringLiteral("/")); + env.insert(QStringLiteral("PATH"), SDDM::mainConfig.Users.DefaultPath.get()); + env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); + displayStopScript->setProcessEnvironment(env); + + // Start display stop script + qDebug() << "Running display stop script:" << displayStopCommand; + displayStopScript->start(displayStopCommand); + + // Wait for finished + if (!displayStopScript->waitForFinished(5000)) + displayStopScript->kill(); + + // Clean up the script process + displayStopScript->deleteLater(); + displayStopScript = nullptr; + } + + // Clean up + m_process->deleteLater(); + m_process = nullptr; +} diff --git a/src/x11helper/runner.h b/src/x11helper/runner.h new file mode 100644 index 000000000..0cc2f4918 --- /dev/null +++ b/src/x11helper/runner.h @@ -0,0 +1,71 @@ +/*************************************************************************** +* Copyright (C) 2019 Pier Luigi Fiorini +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the +* Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +***************************************************************************/ + +#ifndef SDDMX11HELPER_RUNNER_H +#define SDDMX11HELPER_RUNNER_H + +#include + +class QLocalSocket; +class QProcess; + +class Runner : public QObject +{ + Q_OBJECT +public: + explicit Runner(QObject *parent = nullptr); + ~Runner(); + + QString socketName() const; + void setSocketName(const QString &name); + + bool isTestMode() const; + void setTestMode(bool testMode); + + QString display() const; + + QString seat() const; + void setSeat(const QString &seat); + + int terminalId() const; + void setTerminalId(int id); + + QString authPath() const; + void setAuthPath(const QString &path); + + bool start(); + void stop(); + +private: + QString m_socketName; + bool m_testMode = false; + QString m_display; + QString m_seat; + int m_terminalId = 0; + QString m_authPath; + + QLocalSocket *m_socket = nullptr; + QProcess *m_process = nullptr; + +private slots: + void started(); + void finished(); +}; + +#endif // SDDMX11HELPER_RUNNER_H