File indexing completed on 2024-04-28 11:40:31

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #ifndef QT_NO_CAST_FROM_ASCII
0009 #define QT_NO_CAST_FROM_ASCII
0010 #endif
0011 
0012 #include "klauncher.h"
0013 #include "klauncher_cmds.h"
0014 #include "klauncher_adaptor.h"
0015 #include "kslavelauncheradaptor.h"
0016 #include <klauncher_debug.h>
0017 
0018 #include <stdio.h>
0019 #include <qplatformdefs.h>
0020 #include <signal.h>
0021 #include <cerrno>
0022 
0023 #if HAVE_X11
0024 #include <kstartupinfo.h>
0025 #include <QGuiApplication>
0026 #endif
0027 
0028 #if HAVE_XCB
0029 #include <xcb/xcb.h>
0030 #endif
0031 
0032 #include <QDBusConnectionInterface>
0033 #include <QFile>
0034 #include <QFileInfo>
0035 #include <QUrl>
0036 #include <qplatformdefs.h>
0037 
0038 #include <KConfig>
0039 #include <QDebug>
0040 #include <KLocalizedString>
0041 #include <KDesktopFile>
0042 #include <KPluginLoader> // to find kioslave modules
0043 #include <KProtocolManager>
0044 #include <KProtocolInfo>
0045 #include <KRun> // TODO port away from kiofilewidgets
0046 
0047 #include <kio/desktopexecparser.h>
0048 #include <kio/global.h>
0049 #include <kio/slaveinterface.h>
0050 #include <kiogui_export.h>
0051 
0052 #ifdef Q_OS_WIN
0053 #include <qt_windows.h>
0054 //windows.h feels like defining this...
0055 #undef interface
0056 #endif
0057 
0058 // Dispose slaves after being idle for SLAVE_MAX_IDLE seconds
0059 #define SLAVE_MAX_IDLE  30
0060 
0061 static const char *const s_DBusStartupTypeToString[] =
0062 { "DBusNone", "DBusUnique", "DBusMulti", "ERROR" };
0063 
0064 using namespace KIO;
0065 
0066 static KLauncher *g_klauncher_self;
0067 
0068 // From qcore_unix_p.h. We could also port to QLocalSocket :)
0069 #define K_EINTR_LOOP(var, cmd)                    \
0070     do {                                        \
0071         var = cmd;                              \
0072     } while (var == -1 && errno == EINTR)
0073 
0074 ssize_t kde_safe_write(int fd, const void *buf, size_t count)
0075 {
0076     ssize_t ret = 0;
0077     K_EINTR_LOOP(ret, QT_WRITE(fd, buf, count));
0078     if (ret < 0) {
0079         qCWarning(KLAUNCHER) << "write failed:" << strerror(errno);
0080     }
0081     return ret;
0082 }
0083 
0084 #ifndef USE_KPROCESS_FOR_KIOSLAVES
0085 KLauncher::KLauncher(int _kdeinitSocket)
0086     : QObject(nullptr),
0087       kdeinitSocket(_kdeinitSocket)
0088 #else
0089 KLauncher::KLauncher()
0090     : QObject(0)
0091 #endif
0092 {
0093 #if HAVE_X11
0094     mIsX11 = QGuiApplication::platformName() == QStringLiteral("xcb");
0095 #endif
0096     Q_ASSERT(g_klauncher_self == nullptr);
0097     g_klauncher_self = this;
0098 
0099     new KLauncherAdaptor(this);
0100     mSlaveLauncherAdaptor = new KSlaveLauncherAdaptor(this);
0101     QDBusConnection::sessionBus().registerObject(QStringLiteral("/KLauncher"), this); // same as ktoolinvocation.cpp
0102 
0103     connect(QDBusConnection::sessionBus().interface(),
0104             SIGNAL(serviceOwnerChanged(QString,QString,QString)),
0105             SLOT(slotNameOwnerChanged(QString,QString,QString)));
0106 
0107     mConnectionServer.listenForRemote();
0108     connect(&mConnectionServer, SIGNAL(newConnection()), SLOT(acceptSlave()));
0109     if (!mConnectionServer.isListening()) {
0110         // Severe error!
0111         qCWarning(KLAUNCHER, "KLauncher: Fatal error, can't create tempfile!");
0112         ::_exit(1);
0113     }
0114 
0115     connect(&mTimer, SIGNAL(timeout()), SLOT(idleTimeout()));
0116 
0117 #ifndef USE_KPROCESS_FOR_KIOSLAVES
0118     kdeinitNotifier = new QSocketNotifier(kdeinitSocket, QSocketNotifier::Read);
0119     connect(kdeinitNotifier, SIGNAL(activated(int)),
0120             this, SLOT(slotKDEInitData(int)));
0121     kdeinitNotifier->setEnabled(true);
0122 #endif
0123     lastRequest = nullptr;
0124     bProcessingQueue = false;
0125 
0126     mSlaveDebug = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_WAIT"));
0127     if (!mSlaveDebug.isEmpty()) {
0128 #ifndef USE_KPROCESS_FOR_KIOSLAVES
0129         qCWarning(KLAUNCHER, "Klauncher running in slave-debug mode for slaves of protocol '%s'", qPrintable(mSlaveDebug));
0130 #else
0131         // Slave debug mode causes kdeinit to suspend the process waiting
0132         // for the developer to attach gdb to the process; we do not have
0133         // a good way of doing a similar thing if we are using QProcess.
0134         mSlaveDebug.clear();
0135         qCWarning(KLAUNCHER, "slave-debug mode is not available as Klauncher is not using kdeinit");
0136 #endif
0137     }
0138     mSlaveValgrind = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND"));
0139     if (!mSlaveValgrind.isEmpty()) {
0140         mSlaveValgrindSkin = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND_SKIN"));
0141         qCWarning(KLAUNCHER, "Klauncher running slaves through valgrind for slaves of protocol '%s'", qPrintable(mSlaveValgrind));
0142     }
0143 #ifdef USE_KPROCESS_FOR_KIOSLAVES
0144     qCDebug(KLAUNCHER) << "LAUNCHER_OK";
0145 #else
0146     klauncher_header request_header;
0147     request_header.cmd = LAUNCHER_OK;
0148     request_header.arg_length = 0;
0149     kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
0150 #endif
0151 }
0152 
0153 KLauncher::~KLauncher()
0154 {
0155     close();
0156     g_klauncher_self = nullptr;
0157 }
0158 
0159 void KLauncher::close()
0160 {
0161 #if HAVE_XCB
0162     if (mCached) {
0163         xcb_disconnect(mCached.conn);
0164         mCached = XCBConnection();
0165     }
0166 #endif
0167 }
0168 
0169 void
0170 KLauncher::destruct()
0171 {
0172     if (g_klauncher_self) {
0173         g_klauncher_self->close();
0174     }
0175     // We don't delete the app here, that's intentional.
0176     ::_exit(255);
0177 }
0178 
0179 void KLauncher::setLaunchEnv(const QString &name, const QString &value)
0180 {
0181 #ifndef USE_KPROCESS_FOR_KIOSLAVES
0182     klauncher_header request_header;
0183     QByteArray requestData;
0184     requestData.append(name.toLocal8Bit()).append('\0').append(value.toLocal8Bit()).append('\0');
0185     request_header.cmd = LAUNCHER_SETENV;
0186     request_header.arg_length = requestData.size();
0187     kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
0188     kde_safe_write(kdeinitSocket, requestData.data(), request_header.arg_length);
0189 #else
0190     Q_UNUSED(name);
0191     Q_UNUSED(value);
0192 #endif
0193 }
0194 
0195 #ifndef USE_KPROCESS_FOR_KIOSLAVES
0196 /*
0197  * Read 'len' bytes from 'sock' into buffer.
0198  * returns -1 on failure, 0 on no data.
0199  */
0200 static int
0201 read_socket(int sock, char *buffer, int len)
0202 {
0203     int bytes_left = len;
0204     while (bytes_left > 0) {
0205         // in case we get a request to start an application and data arrive
0206         // to kdeinitSocket at the same time, requestStart() will already
0207         // call slotKDEInitData(), so we must check there's still something
0208         // to read, otherwise this would block
0209 
0210         // Same thing if kdeinit dies without warning.
0211 
0212         fd_set in;
0213         timeval tm = { 30, 0 }; // 30 seconds timeout, so we're not stuck in case kdeinit dies on us
0214         FD_ZERO(&in);
0215         FD_SET(sock, &in);
0216         select(sock + 1, &in, nullptr, nullptr, &tm);
0217         if (!FD_ISSET(sock, &in)) {
0218             qCDebug(KLAUNCHER) << "read_socket" << sock << "nothing to read, kdeinit5 must be dead";
0219             return -1;
0220         }
0221 
0222         const ssize_t result = read(sock, buffer, bytes_left);
0223         if (result > 0) {
0224             buffer += result;
0225             bytes_left -= result;
0226         } else if (result == 0) {
0227             return -1;
0228         } else if ((result == -1) && (errno != EINTR)) {
0229             return -1;
0230         }
0231     }
0232     return 0;
0233 }
0234 #endif
0235 
0236 void
0237 KLauncher::slotKDEInitData(int)
0238 {
0239 #ifndef USE_KPROCESS_FOR_KIOSLAVES
0240     klauncher_header request_header;
0241     QByteArray requestData;
0242 
0243     int result = read_socket(kdeinitSocket, (char *) &request_header,
0244                              sizeof(request_header));
0245     if (result != -1) {
0246         requestData.resize(request_header.arg_length);
0247         result = read_socket(kdeinitSocket, (char *) requestData.data(),
0248                              request_header.arg_length);
0249     }
0250     if (result == -1) {
0251         qCDebug(KLAUNCHER) << "Exiting on read_socket errno:" << errno;
0252         signal(SIGHUP, SIG_IGN);
0253         signal(SIGTERM, SIG_IGN);
0254         destruct(); // Exit!
0255     }
0256 
0257     processRequestReturn(request_header.cmd, requestData);
0258 #endif
0259 }
0260 
0261 void KLauncher::processRequestReturn(int status, const QByteArray &requestData)
0262 {
0263     if (status == LAUNCHER_CHILD_DIED) {
0264         long *request_data;
0265         request_data = (long *) requestData.data();
0266         processDied(request_data[0], request_data[1]);
0267         return;
0268     }
0269     if (lastRequest && (status == LAUNCHER_OK)) {
0270         long *request_data;
0271         request_data = (long *) requestData.data();
0272         lastRequest->pid = (pid_t)(*request_data);
0273         qCDebug(KLAUNCHER).nospace() << lastRequest->name << " (pid " << lastRequest->pid << ") up and running.";
0274         switch (lastRequest->dbus_startup_type) {
0275         case KService::DBusNone:
0276             if (lastRequest->wait) {
0277                 lastRequest->status = KLaunchRequest::Launching;
0278             } else {
0279                 lastRequest->status = KLaunchRequest::Running;
0280             }
0281             break;
0282         case KService::DBusUnique:
0283         case KService::DBusMulti:
0284             lastRequest->status = KLaunchRequest::Launching;
0285             break;
0286         }
0287         lastRequest = nullptr;
0288         return;
0289     }
0290     if (lastRequest && (status == LAUNCHER_ERROR)) {
0291         lastRequest->status = KLaunchRequest::Error;
0292         qCDebug(KLAUNCHER) << lastRequest->name << " failed.";
0293         if (!requestData.isEmpty()) {
0294             lastRequest->errorMsg = QString::fromUtf8((char *) requestData.data());
0295         }
0296         lastRequest = nullptr;
0297         return;
0298     }
0299 
0300     qCWarning(KLAUNCHER) << "Unexpected request return" << (unsigned int) status;
0301 }
0302 
0303 void
0304 KLauncher::processDied(pid_t pid, long exitStatus)
0305 {
0306     qCDebug(KLAUNCHER) << pid << "exitStatus=" << exitStatus;
0307     for (KLaunchRequest *request : std::as_const(requestList)) {
0308         qCDebug(KLAUNCHER) << "  had pending request" << request->pid;
0309         if (request->pid == pid) {
0310             if ((request->dbus_startup_type == KService::DBusUnique)
0311                     && QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) {
0312                 request->status = KLaunchRequest::Running;
0313                 qCDebug(KLAUNCHER) << pid << "running as a unique app";
0314             } else if(request->dbus_startup_type == KService::DBusNone && request->wait) {
0315                 request->status = KLaunchRequest::Running;
0316                 qCDebug(KLAUNCHER) << pid << "running as DBusNone with wait to true";
0317             } else if (exitStatus == 0 &&
0318                        (request->dbus_startup_type == KService::DBusUnique ||
0319                         request->dbus_startup_type == KService::DBusMulti)) {
0320                 // e.g. opening kate from a widget on the panel/desktop, where it
0321                 // shows the session chooser dialog before ever entering the main
0322                 // app event loop, then quitting/closing the dialog without starting kate
0323                 request->status = KLaunchRequest::Done;
0324                 qCDebug(KLAUNCHER) << pid << "exited without error, requestDone. status=" << request->status;
0325             } else {
0326                 request->status = KLaunchRequest::Error;
0327                 qCDebug(KLAUNCHER) << pid << "died, requestDone. status=" << request->status;
0328             }
0329             requestDone(request);
0330             return;
0331         }
0332     }
0333     qCDebug(KLAUNCHER) << "found no pending requests for PID" << pid;
0334 }
0335 
0336 static bool matchesPendingRequest(const QString &appId, const QString &pendingAppId)
0337 {
0338     // appId just registered, e.g. org.koffice.kword-12345
0339     // Let's see if this is what pendingAppId (e.g. org.koffice.kword or *.kword) was waiting for.
0340 
0341     const QString newAppId = appId.left(appId.lastIndexOf(QLatin1Char('-'))); // strip out the -12345 if present.
0342 
0343     qCDebug(KLAUNCHER) << "appId=" << appId << "newAppId=" << newAppId << "pendingAppId=" << pendingAppId;
0344 
0345     if (pendingAppId.startsWith(QLatin1String("*."))) {
0346         const QString pendingName = pendingAppId.mid(2);
0347         const QString appName = newAppId.mid(newAppId.lastIndexOf(QLatin1Char('.')) + 1);
0348         qCDebug(KLAUNCHER) << "appName=" << appName;
0349         return appName == pendingName;
0350     }
0351 
0352     // Match sandboxed apps (e.g. flatpak), see https://phabricator.kde.org/D5775
0353     if (newAppId.endsWith(QLatin1String(".kdbus"))) {
0354         return newAppId.leftRef(newAppId.length() - 6) == pendingAppId;
0355     }
0356 
0357     return newAppId == pendingAppId;
0358 }
0359 
0360 void
0361 KLauncher::slotNameOwnerChanged(const QString &appId, const QString &oldOwner,
0362                                 const QString &newOwner)
0363 {
0364     Q_UNUSED(oldOwner);
0365     if (appId.isEmpty() || newOwner.isEmpty()) {
0366         return;
0367     }
0368 
0369     qCDebug(KLAUNCHER) << "new app" << appId;
0370     for (KLaunchRequest *request : std::as_const(requestList)) {
0371         if (request->status != KLaunchRequest::Launching) {
0372             continue;
0373         }
0374 
0375         qCDebug(KLAUNCHER) << "had pending request" << request->name << s_DBusStartupTypeToString[request->dbus_startup_type] << "dbus_name" << request->dbus_name << request->tolerant_dbus_name;
0376 
0377         // For unique services check the requested service name first
0378         if (request->dbus_startup_type == KService::DBusUnique) {
0379             if ((appId == request->dbus_name) || // just started
0380                     QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { // was already running
0381                 request->status = KLaunchRequest::Running;
0382                 qCDebug(KLAUNCHER) << "OK, unique app" << request->dbus_name << "is running";
0383                 requestDone(request);
0384                 continue;
0385             } else {
0386                 qCDebug(KLAUNCHER) << "unique app" << request->dbus_name << "not running yet";
0387             }
0388         }
0389 
0390         const QString rAppId = !request->tolerant_dbus_name.isEmpty() ? request->tolerant_dbus_name : request->dbus_name;
0391         qCDebug(KLAUNCHER) << "using" << rAppId << "for matching";
0392         if (rAppId.isEmpty()) {
0393             continue;
0394         }
0395 
0396         if (matchesPendingRequest(appId, rAppId)) {
0397             qCDebug(KLAUNCHER) << "ok, request done";
0398             request->dbus_name = appId;
0399             request->status = KLaunchRequest::Running;
0400             requestDone(request);
0401             continue;
0402         }
0403     }
0404 }
0405 
0406 void
0407 KLauncher::requestDone(KLaunchRequest *request)
0408 {
0409     if ((request->status == KLaunchRequest::Running) ||
0410             (request->status == KLaunchRequest::Done)) {
0411         requestResult.result = 0;
0412         requestResult.dbusName = request->dbus_name;
0413         requestResult.error = QStringLiteral(""); // not null, cf assert further down
0414         requestResult.pid = request->pid;
0415     } else {
0416         requestResult.result = 1;
0417         requestResult.dbusName.clear();
0418         requestResult.error = i18n("KDEInit could not launch '%1'", request->name);
0419         if (!request->errorMsg.isEmpty()) {
0420             requestResult.error += QStringLiteral(":\n") + request->errorMsg;
0421         }
0422         requestResult.pid = 0;
0423 
0424 #if HAVE_XCB
0425         if (!request->startup_dpy.isEmpty() && mIsX11) {
0426             XCBConnection conn = getXCBConnection(request->startup_dpy);
0427             if (conn) {
0428                 KStartupInfoId id;
0429                 id.initId(request->startup_id);
0430                 KStartupInfo::sendFinishXcb(conn.conn, conn.screen, id);
0431             }
0432         }
0433 #endif
0434     }
0435 
0436     if (request->transaction.type() != QDBusMessage::InvalidMessage) {
0437         if (requestResult.dbusName.isNull()) { // null strings can't be sent
0438             requestResult.dbusName.clear();
0439         }
0440         Q_ASSERT(!requestResult.error.isNull());
0441         quintptr stream_pid = requestResult.pid;
0442         QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << requestResult.result
0443                                            << requestResult.dbusName
0444                                            << requestResult.error
0445                                            << stream_pid));
0446     }
0447 
0448     qCDebug(KLAUNCHER) << "removing done request" << request->name << "PID" << request->pid;
0449     requestList.removeAll(request);
0450     delete request;
0451 }
0452 
0453 static void appendLong(QByteArray &ba, long l)
0454 {
0455     const int sz = ba.size();
0456     ba.resize(sz + sizeof(long));
0457     memcpy(ba.data() + sz, &l, sizeof(long));
0458 }
0459 
0460 void
0461 KLauncher::requestStart(KLaunchRequest *request)
0462 {
0463 #ifdef USE_KPROCESS_FOR_KIOSLAVES
0464     requestList.append(request);
0465     lastRequest = request;
0466 
0467     QProcess *process  = new QProcess;
0468     process->setProcessChannelMode(QProcess::MergedChannels);
0469     connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(slotGotOutput()));
0470     connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotFinished(int,QProcess::ExitStatus)));
0471     request->process = process;
0472 
0473     QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
0474     for (const QString &env : std::as_const(request->envs)) {
0475         const int pos = env.indexOf(QLatin1Char('='));
0476         const QString envVariable = env.left(pos);
0477         const QString envValue = env.mid(pos + 1);
0478         environment.insert(envVariable, envValue);
0479     }
0480     process->setProcessEnvironment(environment);
0481 
0482     QStringList args;
0483     for (const QString &arg : std::as_const(request->arg_list)) {
0484         args << arg;
0485     }
0486 
0487     const QString executable = QStandardPaths::findExecutable(request->name);
0488     if (executable.isEmpty()) {
0489         qCDebug(KLAUNCHER) << "KLauncher couldn't find" << request->name << "executable.";
0490         return;
0491     }
0492 
0493     process->start(executable, args);
0494 
0495     if (!process->waitForStarted()) {
0496         processRequestReturn(LAUNCHER_ERROR, "");
0497     } else {
0498         request->pid = process->processId();
0499         QByteArray data((char *)&request->pid, sizeof(int));
0500         processRequestReturn(LAUNCHER_OK, data);
0501     }
0502     return;
0503 
0504 #else
0505     requestList.append(request);
0506     // Send request to kdeinit.
0507     klauncher_header request_header;
0508     QByteArray requestData;
0509     requestData.reserve(1024);
0510 
0511     appendLong(requestData, request->arg_list.count() + 1);
0512     requestData.append(request->name.toLocal8Bit());
0513     requestData.append('\0');
0514     for (const QString &arg : std::as_const(request->arg_list)) {
0515         requestData.append(arg.toLocal8Bit()).append('\0');
0516     }
0517     appendLong(requestData, request->envs.count());
0518     for (const QString &env : std::as_const(request->envs)) {
0519         requestData.append(env.toLocal8Bit()).append('\0');
0520     }
0521     appendLong(requestData, 0); // avoid_loops, always false here
0522 #if HAVE_X11
0523     bool startup_notify = mIsX11 && !request->startup_id.isNull() && request->startup_id != "0";
0524     if (startup_notify) {
0525         requestData.append(request->startup_id).append('\0');
0526     }
0527 #endif
0528     if (!request->cwd.isEmpty()) {
0529         requestData.append(QFile::encodeName(request->cwd)).append('\0');
0530     }
0531 
0532 #if HAVE_X11
0533     request_header.cmd = startup_notify ? LAUNCHER_EXT_EXEC : LAUNCHER_EXEC_NEW;
0534 #else
0535     request_header.cmd = LAUNCHER_EXEC_NEW;
0536 #endif
0537     request_header.arg_length = requestData.length();
0538 
0539     qCDebug(KLAUNCHER) << "Asking kdeinit to start" << request->name << request->arg_list
0540             << "cmd=" << commandToString(request_header.cmd);
0541 
0542     kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
0543     kde_safe_write(kdeinitSocket, requestData.data(), requestData.length());
0544 
0545     // Wait for pid to return.
0546     lastRequest = request;
0547     do {
0548         slotKDEInitData(kdeinitSocket);
0549     } while (lastRequest != nullptr);
0550 #endif
0551 }
0552 
0553 void KLauncher::exec_blind(const QString &name, const QStringList &arg_list, const QStringList &envs, const QString &startup_id)
0554 {
0555     KLaunchRequest *request = new KLaunchRequest;
0556     request->name = name;
0557     request->arg_list =  arg_list;
0558     request->dbus_startup_type = KService::DBusNone;
0559     request->pid = 0;
0560     request->status = KLaunchRequest::Launching;
0561     request->envs = envs;
0562     request->wait = false;
0563     // Find service, if any - strip path if needed
0564     KService::Ptr service = KService::serviceByDesktopName(name.mid(name.lastIndexOf(QLatin1Char('/')) + 1));
0565     if (service) {
0566         send_service_startup_info(request, service, startup_id.toLocal8Bit(), QStringList());
0567     } else { // no .desktop file, no startup info
0568         cancel_service_startup_info(request, startup_id.toLocal8Bit(), envs);
0569     }
0570 
0571     requestStart(request);
0572     // We don't care about this request any longer....
0573     requestDone(request);
0574 }
0575 
0576 bool
0577 KLauncher::start_service_by_desktop_path(const QString &serviceName, const QStringList &urls,
0578         const QStringList &envs, const QString &startup_id, bool blind, const QDBusMessage &msg)
0579 {
0580     KService::Ptr service;
0581     // Find service
0582     const QFileInfo fi(serviceName);
0583     if (fi.isAbsolute() && fi.exists()) {
0584         // Full path
0585         service = new KService(serviceName);
0586     } else {
0587         service = KService::serviceByDesktopPath(serviceName);
0588         // TODO?
0589         //if (!service)
0590         //    service = KService::serviceByStorageId(serviceName); // This method should be named start_service_by_storage_id ideally...
0591     }
0592     if (!service) {
0593         requestResult.result = ENOENT;
0594         requestResult.error = i18n("Could not find service '%1'.", serviceName);
0595         cancel_service_startup_info(nullptr, startup_id.toLocal8Bit(), envs);   // cancel it if any
0596         return false;
0597     }
0598     return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, msg);
0599 }
0600 
0601 bool
0602 KLauncher::start_service_by_desktop_name(const QString &serviceName, const QStringList &urls,
0603         const QStringList &envs, const QString &startup_id, bool blind, const QDBusMessage &msg)
0604 {
0605     KService::Ptr service = KService::serviceByDesktopName(serviceName);
0606     if (!service) {
0607         requestResult.result = ENOENT;
0608         requestResult.error = i18n("Could not find service '%1'.", serviceName);
0609         cancel_service_startup_info(nullptr, startup_id.toLocal8Bit(), envs);   // cancel it if any
0610         return false;
0611     }
0612     return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, msg);
0613 }
0614 
0615 bool
0616 KLauncher::start_service(KService::Ptr service, const QStringList &_urls,
0617                          const QStringList &envs, const QByteArray &startup_id,
0618                          bool blind, const QDBusMessage &msg)
0619 {
0620     QStringList urls = _urls;
0621     bool runPermitted = KDesktopFile::isAuthorizedDesktopFile(service->entryPath());
0622 
0623     if (!service->isValid() || !runPermitted) {
0624         requestResult.result = ENOEXEC;
0625         if (service->isValid()) {
0626             requestResult.error = i18n("Service '%1' must be executable to run.", service->entryPath());
0627         } else {
0628             requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
0629         }
0630         cancel_service_startup_info(nullptr, startup_id, envs);   // cancel it if any
0631         return false;
0632     }
0633     KLaunchRequest *request = new KLaunchRequest;
0634 
0635     enum DiscreteGpuCheck { NotChecked, Present, Absent };
0636     static DiscreteGpuCheck s_gpuCheck = NotChecked;
0637 
0638     if (service->runOnDiscreteGpu() && s_gpuCheck == NotChecked) {
0639         // Check whether we have a discrete gpu
0640         bool hasDiscreteGpu = false;
0641         QDBusInterface iface(QLatin1String("org.kde.Solid.PowerManagement"),
0642                              QLatin1String("/org/kde/Solid/PowerManagement"),
0643                              QLatin1String("org.kde.Solid.PowerManagement"),
0644                              QDBusConnection::sessionBus());
0645         if (iface.isValid()) {
0646             QDBusReply<bool> reply = iface.call(QLatin1String("hasDualGpu"));
0647             if (reply.isValid()) {
0648                 hasDiscreteGpu = reply.value();
0649             }
0650         }
0651 
0652         s_gpuCheck = hasDiscreteGpu ? Present : Absent;
0653     }
0654 
0655     QStringList _envs = envs;
0656     if (service->runOnDiscreteGpu() && s_gpuCheck == Present) {
0657         _envs << QLatin1String("DRI_PRIME=1");
0658     }
0659 
0660     if ((urls.count() > 1) && !service->allowMultipleFiles()) {
0661         // We need to launch the application N times. That sucks.
0662         // We ignore the result for application 2 to N.
0663         // For the first file we launch the application in the
0664         // usual way. The reported result is based on this
0665         // application.
0666         QStringList::ConstIterator it = urls.constBegin();
0667         for (++it;
0668                 it != urls.constEnd();
0669                 ++it) {
0670             QStringList singleUrl;
0671             singleUrl.append(*it);
0672             QByteArray startup_id2 = startup_id;
0673             if (!startup_id2.isEmpty() && startup_id2 != "0") {
0674                 startup_id2 = "0";    // can't use the same startup_id several times // krazy:exclude=doublequote_chars
0675             }
0676             start_service(service, singleUrl, _envs, startup_id2, true, msg);
0677         }
0678         const QString firstURL = urls.at(0);
0679         urls.clear();
0680         urls.append(firstURL);
0681     }
0682     const QList<QUrl> qurls = QUrl::fromStringList(urls);
0683 
0684     createArgs(request, service, qurls);
0685 
0686     // We must have one argument at least!
0687     if (request->arg_list.isEmpty()) {
0688         requestResult.result = ENOEXEC;
0689         requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
0690         delete request;
0691         cancel_service_startup_info(nullptr, startup_id, _envs);
0692         return false;
0693     }
0694 
0695     request->name = request->arg_list.takeFirst();
0696 
0697     if (request->name.endsWith(QLatin1String("/kioexec"))) {
0698         // Special case for kioexec; if createArgs said we were going to use it,
0699         // then we have to expect a kioexec-PID, not a org.kde.finalapp...
0700         // Testcase: konqueror www.kde.org, RMB on link, open with, kruler.
0701 
0702         request->dbus_startup_type = KService::DBusMulti;
0703         request->dbus_name = QStringLiteral("org.kde.kioexec");
0704     } else {
0705         request->dbus_startup_type = service->dbusStartupType();
0706 
0707         if ((request->dbus_startup_type == KService::DBusUnique) ||
0708                 (request->dbus_startup_type == KService::DBusMulti)) {
0709             const QVariant v = service->property(QStringLiteral("X-DBUS-ServiceName"));
0710             if (v.isValid()) {
0711                 request->dbus_name = v.toString();
0712             }
0713             if (request->dbus_name.isEmpty()) {
0714                 const QString binName = KIO::DesktopExecParser::executableName(service->exec());
0715                 request->dbus_name = QStringLiteral("org.kde.") + binName;
0716                 request->tolerant_dbus_name = QStringLiteral("*.") + binName;
0717             }
0718         }
0719     }
0720 
0721     qCDebug(KLAUNCHER) << "name=" << request->name << "dbus_name=" << request->dbus_name
0722             << "startup type=" << s_DBusStartupTypeToString[request->dbus_startup_type];
0723 
0724     request->pid = 0;
0725     request->wait = false;
0726     request->envs = _envs;
0727     send_service_startup_info(request, service, startup_id, _envs);
0728 
0729     // Request will be handled later.
0730     if (!blind) {
0731         msg.setDelayedReply(true);
0732         request->transaction = msg;
0733     }
0734     queueRequest(request);
0735     return true;
0736 }
0737 
0738 
0739 namespace KIOGuiPrivate {
0740 // defined in kprocessrunner.cpp
0741 extern bool KIOGUI_EXPORT checkStartupNotify(const KService *service, bool *silent_arg,
0742                                              QByteArray *wmclass_arg);
0743 }
0744 
0745 void
0746 KLauncher::send_service_startup_info(KLaunchRequest *request, KService::Ptr service, const QByteArray &startup_id,
0747                                      const QStringList &envs)
0748 {
0749 #if HAVE_XCB
0750     if (!mIsX11) {
0751         return;
0752     }
0753     request->startup_id = "0";// krazy:exclude=doublequote_chars
0754     if (startup_id == "0") {
0755         return;
0756     }
0757     bool silent;
0758     QByteArray wmclass;
0759 
0760     if (!KIOGuiPrivate::checkStartupNotify(service.data(), &silent, &wmclass)) {
0761         return;
0762     }
0763     KStartupInfoId id;
0764     id.initId(startup_id);
0765     QByteArray dpy_str;
0766     for (const QString &env : envs) {
0767         if (env.startsWith(QLatin1String("DISPLAY="))) {
0768             dpy_str = env.mid(8).toLocal8Bit();
0769         }
0770     }
0771 
0772     XCBConnection conn = getXCBConnection(dpy_str);
0773     request->startup_id = id.id();
0774     if (!conn) {
0775         cancel_service_startup_info(request, startup_id, envs);
0776         return;
0777     }
0778 
0779     request->startup_dpy = conn.displayName;
0780 
0781     KStartupInfoData data;
0782     data.setName(service->name());
0783     data.setIcon(service->icon());
0784     data.setDescription(i18n("Launching %1",  service->name()));
0785     if (!wmclass.isEmpty()) {
0786         data.setWMClass(wmclass);
0787     }
0788     if (silent) {
0789         data.setSilent(KStartupInfoData::Yes);
0790     }
0791     data.setApplicationId(service->entryPath());
0792     // the rest will be sent by kdeinit
0793     KStartupInfo::sendStartupXcb(conn.conn, conn.screen, id, data);
0794 #endif
0795 }
0796 
0797 void
0798 KLauncher::cancel_service_startup_info(KLaunchRequest *request, const QByteArray &startup_id,
0799                                        const QStringList &envs)
0800 {
0801 #if HAVE_XCB
0802     if (request != nullptr) {
0803         request->startup_id = "0";    // krazy:exclude=doublequote_chars
0804     }
0805     if (!startup_id.isEmpty() && startup_id != "0" && mIsX11) {
0806         QString dpy_str;
0807         for (const QString &env : envs) {
0808             if (env.startsWith(QLatin1String("DISPLAY="))) {
0809                 dpy_str = env.mid(8);
0810             }
0811         }
0812         XCBConnection conn = getXCBConnection(dpy_str.toLocal8Bit());
0813         if (!conn) {
0814             return;
0815         }
0816         KStartupInfoId id;
0817         id.initId(startup_id);
0818         KStartupInfo::sendFinishXcb(conn.conn, conn.screen, id);
0819     }
0820 #endif
0821 }
0822 
0823 bool
0824 KLauncher::kdeinit_exec(const QString &app, const QStringList &args,
0825                         const QString &workdir, const QStringList &envs,
0826                         const QString &startup_id, bool wait, const QDBusMessage &msg)
0827 {
0828     KLaunchRequest *request = new KLaunchRequest;
0829     request->arg_list = args;
0830     request->name = app;
0831     request->dbus_startup_type = KService::DBusNone;
0832     request->pid = 0;
0833     request->wait = wait;
0834 #if HAVE_X11
0835     request->startup_id = startup_id.toLocal8Bit();
0836 #endif
0837     request->envs = envs;
0838     request->cwd = workdir;
0839 #if HAVE_X11
0840     if (!app.endsWith(QLatin1String("kbuildsycoca5"))) { // avoid stupid loop
0841         // Find service, if any - strip path if needed
0842         const QString desktopName = app.mid(app.lastIndexOf(QLatin1Char('/')) + 1);
0843         KService::Ptr service = KService::serviceByDesktopName(desktopName);
0844         if (service)
0845             send_service_startup_info(request, service,
0846                                       request->startup_id, envs);
0847         else { // no .desktop file, no startup info
0848             cancel_service_startup_info(request, request->startup_id, envs);
0849         }
0850     }
0851 #endif
0852     msg.setDelayedReply(true);
0853     request->transaction = msg;
0854     queueRequest(request);
0855     return true;
0856 }
0857 
0858 void
0859 KLauncher::queueRequest(KLaunchRequest *request)
0860 {
0861     requestQueue.append(request);
0862     if (!bProcessingQueue) {
0863         bProcessingQueue = true;
0864         QTimer::singleShot(0, this, SLOT(slotDequeue()));
0865     }
0866 }
0867 
0868 void
0869 KLauncher::slotDequeue()
0870 {
0871     do {
0872         KLaunchRequest *request = requestQueue.takeFirst();
0873         // process request
0874         request->status = KLaunchRequest::Launching;
0875         requestStart(request);
0876         if (request->status != KLaunchRequest::Launching) {
0877             // Request handled.
0878             qCDebug(KLAUNCHER) << "Request handled already";
0879             requestDone(request);
0880             continue;
0881         }
0882     } while (!requestQueue.isEmpty());
0883     bProcessingQueue = false;
0884 }
0885 
0886 void
0887 KLauncher::createArgs(KLaunchRequest *request, const KService::Ptr service,
0888                       const QList<QUrl> &urls)
0889 {
0890     KIO::DesktopExecParser parser(*service, urls);
0891     const QStringList params = parser.resultingArguments();
0892     for (const QString &arg : params) {
0893         request->arg_list.append(arg);
0894     }
0895 
0896     const QString& path = service->workingDirectory();
0897     if (!path.isEmpty()) {
0898         request->cwd = path;
0899     } else if (!urls.isEmpty()) {
0900         const QUrl& url = urls.first();
0901         if (url.isLocalFile()) {
0902             request->cwd = url.adjusted(QUrl::RemoveFilename).toLocalFile();
0903         }
0904     }
0905 }
0906 
0907 ///// IO-Slave functions
0908 
0909 pid_t
0910 KLauncher::requestHoldSlave(const QString &urlStr, const QString &app_socket)
0911 {
0912     const QUrl url(urlStr);
0913     IdleSlave *slave = nullptr;
0914     for (IdleSlave *p : std::as_const(mSlaveList)) {
0915         if (p->onHold(url)) {
0916             slave = p;
0917             break;
0918         }
0919     }
0920     if (slave) {
0921         mSlaveList.removeAll(slave);
0922         slave->connect(app_socket);
0923         return slave->pid();
0924     }
0925     return 0;
0926 }
0927 
0928 pid_t
0929 KLauncher::requestSlave(const QString &protocol,
0930                         const QString &host,
0931                         const QString &app_socket,
0932                         QString &error)
0933 {
0934     IdleSlave *slave = nullptr;
0935     for (IdleSlave *p : std::as_const(mSlaveList)) {
0936         if (p->match(protocol, host, true)) {
0937             slave = p;
0938             break;
0939         }
0940     }
0941     if (!slave) {
0942         for (IdleSlave *p : std::as_const(mSlaveList)) {
0943             if (p->match(protocol, host, false)) {
0944                 slave = p;
0945                 break;
0946             }
0947         }
0948     }
0949     if (!slave) {
0950         for (IdleSlave *p : std::as_const(mSlaveList)) {
0951             if (p->match(protocol, QString(), false)) {
0952                 slave = p;
0953                 break;
0954             }
0955         }
0956     }
0957     if (slave) {
0958         mSlaveList.removeAll(slave);
0959         slave->connect(app_socket);
0960         return slave->pid();
0961     }
0962 
0963     QString slaveModule = KProtocolInfo::exec(protocol);
0964     if (slaveModule.isEmpty()) {
0965         error = i18n("Unknown protocol '%1'.\n", protocol);
0966         return 0;
0967     }
0968     KPluginLoader loader(slaveModule);
0969     QString slaveModulePath = loader.fileName();
0970     if (slaveModulePath.isEmpty()) {
0971         error = i18n("Could not find the '%1' plugin.\n", slaveModule);
0972         return 0;
0973     }
0974 
0975     QStringList arg_list;
0976 #ifdef USE_KPROCESS_FOR_KIOSLAVES
0977     arg_list << slaveModulePath;
0978     arg_list << protocol;
0979     arg_list << mConnectionServer.address().toString();
0980     arg_list << app_socket;
0981 #ifdef Q_OS_WIN
0982     QString name = QLatin1String("kioslave");
0983 #else
0984     QString name = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioslave");
0985 #endif
0986 #else
0987     QString arg1 = protocol;
0988     QString arg2 = mConnectionServer.address().toString();
0989     QString arg3 = app_socket;
0990     arg_list.append(arg1);
0991     arg_list.append(arg2);
0992     arg_list.append(arg3);
0993     QString name = slaveModulePath;
0994 #endif
0995 
0996     qCDebug(KLAUNCHER) << "KLauncher: launching new slave" << name << "with protocol=" << protocol << "args=" << arg_list;
0997 
0998 #ifdef Q_OS_UNIX
0999 #ifndef USE_KPROCESS_FOR_KIOSLAVES
1000     // see comments where mSlaveDebug is set in KLauncher::KLauncher
1001     if (mSlaveDebug == protocol) {
1002         klauncher_header request_header;
1003         request_header.cmd = LAUNCHER_DEBUG_WAIT;
1004         request_header.arg_length = 0;
1005         kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
1006     }
1007 #endif
1008     if (mSlaveValgrind == protocol) {
1009         arg_list.prepend(name);
1010 #ifndef USE_KPROCESS_FOR_KIOSLAVES // otherwise we've already done this
1011 #ifdef Q_OS_WIN
1012         arg_list.prepend(QLatin1String("kioslave"));
1013 #else
1014         arg_list.prepend(QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioslave"));
1015 #endif
1016 #endif
1017         name = QStringLiteral("valgrind");
1018 
1019         if (!mSlaveValgrindSkin.isEmpty()) {
1020             arg_list.prepend(QLatin1String("--tool=") + mSlaveValgrindSkin);
1021         } else {
1022             arg_list.prepend(QLatin1String("--tool=memcheck"));
1023         }
1024     }
1025 #endif
1026     KLaunchRequest *request = new KLaunchRequest;
1027     request->name = name;
1028     request->arg_list =  arg_list;
1029     request->dbus_startup_type = KService::DBusNone;
1030     request->pid = 0;
1031     request->wait = false;
1032 #if HAVE_X11
1033     request->startup_id = "0"; // krazy:exclude=doublequote_chars
1034 #endif
1035     request->status = KLaunchRequest::Launching;
1036     requestStart(request);
1037     pid_t pid = request->pid;
1038 
1039 //    qCDebug(KLAUNCHER) << "Slave launched, pid = " << pid;
1040 
1041     // We don't care about this request any longer....
1042     requestDone(request);
1043     if (!pid) {
1044         error = i18n("Error loading '%1'.", name);
1045     }
1046     return pid;
1047 }
1048 
1049 bool KLauncher::checkForHeldSlave(const QString &urlStr)
1050 {
1051     QUrl url(urlStr);
1052     for (const IdleSlave *p : std::as_const(mSlaveList)) {
1053         if (p->onHold(url)) {
1054             return true;
1055         }
1056     }
1057     return false;
1058 }
1059 
1060 void
1061 KLauncher::waitForSlave(int pid)
1062 {
1063     Q_ASSERT(calledFromDBus());
1064     for (IdleSlave *slave : std::as_const(mSlaveList)) {
1065         if (slave->pid() == static_cast<pid_t>(pid)) {
1066             return;    // Already here.
1067         }
1068     }
1069     SlaveWaitRequest *waitRequest = new SlaveWaitRequest;
1070     setDelayedReply(true);
1071     waitRequest->transaction = message(); // from QDBusContext
1072     waitRequest->pid = static_cast<pid_t>(pid);
1073     mSlaveWaitRequest.append(waitRequest);
1074 }
1075 
1076 void
1077 KLauncher::acceptSlave()
1078 {
1079     IdleSlave *slave = new IdleSlave(this);
1080     mConnectionServer.setNextPendingConnection(slave->connection());
1081     mSlaveList.append(slave);
1082     connect(slave, SIGNAL(destroyed()), this, SLOT(slotSlaveGone()));
1083     connect(slave, SIGNAL(statusUpdate(IdleSlave*)),
1084             this, SLOT(slotSlaveStatus(IdleSlave*)));
1085     if (!mTimer.isActive()) {
1086         mTimer.start(1000 * 10);
1087     }
1088 }
1089 
1090 void
1091 KLauncher::slotSlaveStatus(IdleSlave *slave)
1092 {
1093     QMutableListIterator<SlaveWaitRequest *> it(mSlaveWaitRequest);
1094     while (it.hasNext()) {
1095         SlaveWaitRequest *waitRequest = it.next();
1096         if (waitRequest->pid == slave->pid()) {
1097             QDBusConnection::sessionBus().send(waitRequest->transaction.createReply());
1098             it.remove();
1099             delete waitRequest;
1100         }
1101     }
1102 
1103     if (slave->hasTempAuthorization()) {
1104         mSlaveList.removeAll(slave);
1105         slave->deleteLater();
1106     }
1107 }
1108 
1109 void
1110 KLauncher::slotSlaveGone()
1111 {
1112     IdleSlave *slave = (IdleSlave *) sender();
1113     mSlaveList.removeAll(slave);
1114     if ((mSlaveList.isEmpty()) && (mTimer.isActive())) {
1115         mTimer.stop();
1116     }
1117 }
1118 
1119 void
1120 KLauncher::idleTimeout()
1121 {
1122     bool keepOneFileSlave = true;
1123     QDateTime now = QDateTime::currentDateTime();
1124     for (IdleSlave *slave : std::as_const(mSlaveList)) {
1125         if ((slave->protocol() == QLatin1String("file")) && (keepOneFileSlave)) {
1126             keepOneFileSlave = false;
1127         } else if (slave->age(now) > SLAVE_MAX_IDLE) {
1128             // killing idle slave
1129             slave->deleteLater();
1130         }
1131     }
1132 }
1133 
1134 void KLauncher::reparseConfiguration()
1135 {
1136     KProtocolManager::reparseConfiguration();
1137     for (IdleSlave *slave : std::as_const(mSlaveList)) {
1138         slave->reparseConfiguration();
1139     }
1140 }
1141 
1142 void
1143 KLauncher::slotGotOutput()
1144 {
1145 #ifdef USE_KPROCESS_FOR_KIOSLAVES
1146     QProcess *p = static_cast<QProcess *>(sender());
1147     QByteArray _stdout = p->readAllStandardOutput();
1148     qCDebug(KLAUNCHER) << _stdout.data();
1149 #endif
1150 }
1151 
1152 void
1153 KLauncher::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
1154 {
1155 #ifdef USE_KPROCESS_FOR_KIOSLAVES
1156     QProcess *p = static_cast<QProcess *>(sender());
1157     qCDebug(KLAUNCHER) << "process finished exitcode=" << exitCode << "exitStatus=" << exitStatus;
1158 
1159     for (KLaunchRequest *request : std::as_const(requestList)) {
1160         if (request->process == p) {
1161             qCDebug(KLAUNCHER) << "found KProcess, request done";
1162             if (exitCode == 0  && exitStatus == QProcess::NormalExit) {
1163                 request->status = KLaunchRequest::Done;
1164             } else {
1165                 request->status = KLaunchRequest::Error;
1166             }
1167             requestDone(request);
1168             request->process = 0;
1169         }
1170     }
1171     delete p;
1172 #else
1173     Q_UNUSED(exitCode);
1174     Q_UNUSED(exitStatus);
1175 #endif
1176 }
1177 
1178 void KLauncher::terminate_kdeinit()
1179 {
1180     qCDebug(KLAUNCHER);
1181 #ifndef USE_KPROCESS_FOR_KIOSLAVES
1182     klauncher_header request_header;
1183     request_header.cmd = LAUNCHER_TERMINATE_KDEINIT;
1184     request_header.arg_length = 0;
1185     kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header));
1186 #endif
1187 }
1188 
1189 #if HAVE_XCB
1190 KLauncher::XCBConnection KLauncher::getXCBConnection(const QByteArray &_displayName)
1191 {
1192     const auto displayName = !_displayName.isEmpty() ? _displayName : qgetenv("DISPLAY");
1193 
1194     // If cached connection is same as request
1195     if (mCached && displayName == mCached.displayName) {
1196         // Check error, if so close it, otherwise it's still valid, reuse the cached one
1197         if (xcb_connection_has_error(mCached.conn)) {
1198             close();
1199         } else {
1200             return mCached;
1201         }
1202     }
1203 
1204     // At this point, the cached connection can't be reused, make a new connection
1205     XCBConnection conn;
1206     conn.conn = xcb_connect(displayName.constData(), &conn.screen);
1207     if (conn) {
1208         // check error first, if so return an empty one
1209         if (xcb_connection_has_error(conn.conn)) {
1210             xcb_disconnect(conn.conn);
1211             return XCBConnection();
1212         }
1213 
1214         // if it's valid, replace mCached with new connection
1215         conn.displayName = displayName;
1216         close();
1217         mCached = conn;
1218     }
1219     return conn;
1220 }
1221 #endif
1222 
1223 #include "moc_klauncher.cpp"