File indexing completed on 2024-05-12 11:54:13
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2020 Henri Chain <henri.chain@enioka.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "kiogui_debug.h" 0009 #include "systemdprocessrunner_p.h" 0010 0011 #include "managerinterface.h" 0012 #include "propertiesinterface.h" 0013 #include "unitinterface.h" 0014 0015 #include <QTimer> 0016 0017 #include <mutex> 0018 #include <signal.h> 0019 0020 using namespace org::freedesktop; 0021 0022 bool SystemdProcessRunner::isAvailable() 0023 { 0024 static std::once_flag dbusRegistered; 0025 static bool runAsService = false; 0026 std::call_once(dbusRegistered, []() { 0027 if (QDBusConnection::sessionBus().interface()->isServiceRegistered(systemdService)) { 0028 runAsService = true; 0029 qDBusRegisterMetaType<QVariantMultiItem>(); 0030 qDBusRegisterMetaType<QVariantMultiMap>(); 0031 qDBusRegisterMetaType<TransientAux>(); 0032 qDBusRegisterMetaType<TransientAuxList>(); 0033 qDBusRegisterMetaType<ExecCommand>(); 0034 qDBusRegisterMetaType<ExecCommandList>(); 0035 } 0036 }); 0037 return runAsService; 0038 } 0039 0040 SystemdProcessRunner::SystemdProcessRunner() 0041 : KProcessRunner() 0042 { 0043 } 0044 0045 bool SystemdProcessRunner::waitForStarted(int timeout) 0046 { 0047 if (m_pid || m_exited) { 0048 return true; 0049 } 0050 QEventLoop loop; 0051 bool success = false; 0052 loop.connect(this, &KProcessRunner::processStarted, [&loop, &success]() { 0053 loop.quit(); 0054 success = true; 0055 }); 0056 QTimer::singleShot(timeout, &loop, &QEventLoop::quit); 0057 QObject::connect(this, &KProcessRunner::error, &loop, &QEventLoop::quit); 0058 loop.exec(); 0059 return success; 0060 } 0061 0062 void SystemdProcessRunner::startProcess() 0063 { 0064 m_serviceName = SystemdProcessRunner::maybeAliasedName(QStringLiteral("app-%1@%2.service")); 0065 0066 // Watch for new services 0067 m_manager = new systemd1::Manager(systemdService, systemdPath, QDBusConnection::sessionBus(), this); 0068 m_manager->Subscribe(); 0069 connect(m_manager, &systemd1::Manager::UnitNew, this, &SystemdProcessRunner::handleUnitNew); 0070 0071 // Watch for service creation job error 0072 connect(m_manager, 0073 &systemd1::Manager::JobRemoved, 0074 this, 0075 [this](uint jobId, const QDBusObjectPath &jobPath, const QString &unitName, const QString &result) { 0076 Q_UNUSED(jobId) 0077 if (jobPath.path() == m_jobPath && unitName == m_serviceName && result != QLatin1String("done")) { 0078 qCWarning(KIO_GUI) << "Failed to launch process as service:" << m_serviceName << ", result " << result; 0079 // result=failed is not a fatal error, service is actually created in this case 0080 if (result != QLatin1String("failed")) { 0081 systemdError(result); 0082 } 0083 } 0084 }); 0085 0086 // Ask systemd for a new transient service 0087 const auto startReply = m_manager->StartTransientUnit( 0088 m_serviceName, 0089 QStringLiteral("fail"), // mode defines what to do in the case of a name conflict, in this case, just do nothing 0090 { 0091 // Properties of the transient service unit 0092 {QStringLiteral("Type"), QStringLiteral("simple")}, 0093 {QStringLiteral("ExitType"), QStringLiteral("cgroup")}, 0094 {QStringLiteral("Slice"), QStringLiteral("app.slice")}, 0095 {QStringLiteral("Description"), m_description}, 0096 {QStringLiteral("SourcePath"), m_desktopFilePath}, 0097 {QStringLiteral("AddRef"), true}, // Asks systemd to avoid garbage collecting the service if it immediately crashes, 0098 // so we can be notified (see https://github.com/systemd/systemd/pull/3984) 0099 {QStringLiteral("Environment"), m_process->environment()}, 0100 {QStringLiteral("WorkingDirectory"), m_process->workingDirectory()}, 0101 {QStringLiteral("ExecStart"), QVariant::fromValue(ExecCommandList{{m_process->program().first(), m_process->program(), false}})}, 0102 }, 0103 {} // aux is currently unused and should be passed as empty array. 0104 ); 0105 connect(new QDBusPendingCallWatcher(startReply, this), &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { 0106 QDBusPendingReply<QDBusObjectPath> reply = *watcher; 0107 watcher->deleteLater(); 0108 if (reply.isError()) { 0109 qCWarning(KIO_GUI) << "Failed to launch process as service:" << m_serviceName << reply.error().name() << reply.error().message(); 0110 return systemdError(reply.error().message()); 0111 } 0112 qCDebug(KIO_GUI) << "Successfully asked systemd to launch process as service:" << m_serviceName; 0113 m_jobPath = reply.argumentAt<0>().path(); 0114 }); 0115 } 0116 0117 void SystemdProcessRunner::handleProperties(QDBusPendingCallWatcher *watcher) 0118 { 0119 const QDBusPendingReply<QVariantMap> reply = *watcher; 0120 watcher->deleteLater(); 0121 if (reply.isError()) { 0122 qCWarning(KIO_GUI) << "Failed to get properties for service:" << m_serviceName << reply.error().name() << reply.error().message(); 0123 return systemdError(reply.error().message()); 0124 } 0125 qCDebug(KIO_GUI) << "Successfully retrieved properties for service:" << m_serviceName; 0126 if (m_exited) { 0127 return; 0128 } 0129 const auto properties = reply.argumentAt<0>(); 0130 if (!m_pid) { 0131 setPid(properties[QStringLiteral("ExecMainPID")].value<quint32>()); 0132 return; 0133 } 0134 const auto activeState = properties[QStringLiteral("ActiveState")].toString(); 0135 if (activeState != QLatin1String("inactive") && activeState != QLatin1String("failed")) { 0136 return; 0137 } 0138 m_exited = true; 0139 0140 // ExecMainCode/Status correspond to si_code/si_status in the siginfo_t structure 0141 // ExecMainCode is the signal code: CLD_EXITED (1) means normal exit 0142 // ExecMainStatus is the process exit code in case of normal exit, otherwise it is the signal number 0143 const auto signalCode = properties[QStringLiteral("ExecMainCode")].value<qint32>(); 0144 const auto exitCodeOrSignalNumber = properties[QStringLiteral("ExecMainStatus")].value<qint32>(); 0145 const auto exitStatus = signalCode == CLD_EXITED ? QProcess::ExitStatus::NormalExit : QProcess::ExitStatus::CrashExit; 0146 0147 qCDebug(KIO_GUI) << m_serviceName << "pid=" << m_pid << "exitCode=" << exitCodeOrSignalNumber << "exitStatus=" << exitStatus; 0148 terminateStartupNotification(); 0149 deleteLater(); 0150 0151 systemd1::Unit unitInterface(systemdService, m_servicePath, QDBusConnection::sessionBus(), this); 0152 connect(new QDBusPendingCallWatcher(unitInterface.Unref(), this), &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { 0153 QDBusPendingReply<> reply = *watcher; 0154 watcher->deleteLater(); 0155 if (reply.isError()) { 0156 qCWarning(KIO_GUI) << "Failed to unref service:" << m_serviceName << reply.error().name() << reply.error().message(); 0157 return systemdError(reply.error().message()); 0158 } 0159 qCDebug(KIO_GUI) << "Successfully unref'd service:" << m_serviceName; 0160 }); 0161 } 0162 0163 void SystemdProcessRunner::handleUnitNew(const QString &newName, const QDBusObjectPath &newPath) 0164 { 0165 if (newName != m_serviceName) { 0166 return; 0167 } 0168 qCDebug(KIO_GUI) << "Successfully launched process as service:" << m_serviceName; 0169 0170 // Get PID (and possibly exit code) from systemd service properties 0171 m_servicePath = newPath.path(); 0172 m_serviceProperties = new DBus::Properties(systemdService, m_servicePath, QDBusConnection::sessionBus(), this); 0173 auto propReply = m_serviceProperties->GetAll(QString()); 0174 connect(new QDBusPendingCallWatcher(propReply, this), &QDBusPendingCallWatcher::finished, this, &SystemdProcessRunner::handleProperties); 0175 0176 // Watch for status change 0177 connect(m_serviceProperties, &DBus::Properties::PropertiesChanged, this, [this]() { 0178 if (m_exited) { 0179 return; 0180 } 0181 qCDebug(KIO_GUI) << "Got PropertiesChanged signal:" << m_serviceName; 0182 // We need to look at the full list of properties rather than only those which changed 0183 auto reply = m_serviceProperties->GetAll(QString()); 0184 connect(new QDBusPendingCallWatcher(reply, this), &QDBusPendingCallWatcher::finished, this, &SystemdProcessRunner::handleProperties); 0185 }); 0186 } 0187 0188 void SystemdProcessRunner::systemdError(const QString &message) 0189 { 0190 Q_EMIT error(message); 0191 deleteLater(); 0192 } 0193 0194 #include "moc_systemdprocessrunner_p.cpp"