File indexing completed on 2024-05-05 17:45:25
0001 /* 0002 * SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org> 0003 * SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org> 0004 * SPDX-FileCopyrightText: 1998 Luca Montecchiani <m.luca@usa.net> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 * 0008 */ 0009 0010 #include "helper.h" 0011 #include "config-kcm.h" 0012 0013 #include <algorithm> 0014 #include <chrono> 0015 0016 #include <QDebug> 0017 #include <QDir> 0018 #include <QFile> 0019 #include <QMimeDatabase> 0020 #include <QProcess> 0021 #include <QRegularExpression> 0022 0023 #include <KAuth/HelperSupport> 0024 0025 #include "ktar.h" 0026 #include "kzip.h" 0027 #include <KArchive> 0028 #include <KConfigGroup> 0029 #include <KLocalizedString> 0030 #include <KSharedConfig> 0031 0032 using namespace std::chrono_literals; 0033 0034 static QString updateAlternatives() 0035 { 0036 return QStringLiteral("update-alternatives"); 0037 } 0038 0039 static bool hasUpdateAlternatives() 0040 { 0041 return !QStandardPaths::findExecutable(updateAlternatives()).isEmpty(); 0042 } 0043 0044 static ActionReply updateAlternativesInstall(const QString &installPath) 0045 { 0046 QProcess process; 0047 process.start(updateAlternatives(), 0048 {QStringLiteral("--install"), 0049 QStringLiteral("/usr/share/plymouth/themes/default.plymouth"), 0050 QStringLiteral("default.plymouth"), 0051 installPath, 0052 QStringLiteral("100")}); 0053 0054 ActionReply reply = ActionReply::SuccessReply(); 0055 if (!process.waitForStarted()) { 0056 reply = ActionReply::BackendError; 0057 reply.setErrorDescription(i18n("Cannot start update-alternatives.")); 0058 return reply; 0059 } 0060 if (!process.waitForFinished()) { 0061 reply = ActionReply::BackendError; 0062 reply.setErrorDescription(i18n("update-alternatives failed to run.")); 0063 return reply; 0064 } 0065 0066 if (int ret = process.exitCode(); ret != 0) { 0067 reply = ActionReply(ActionReply::HelperErrorReply()); 0068 reply.setErrorCode(static_cast<ActionReply::Error>(ret)); 0069 reply.setErrorDescription(i18n("update-alternatives returned with error condition %1.", ret)); 0070 return reply; 0071 } 0072 return reply; 0073 } 0074 0075 ActionReply PlymouthHelper::save(const QVariantMap &args) 0076 { 0077 const QString theme = args.value(QStringLiteral("theme")).toString(); 0078 ActionReply reply; 0079 0080 if (theme.isEmpty()) { 0081 reply = ActionReply::BackendError; 0082 reply.setErrorDescription(i18n("No theme specified in helper parameters.")); 0083 return reply; 0084 } 0085 0086 { 0087 KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral(PLYMOUTH_CONFIG_PATH)), "Daemon"); 0088 cg.writeEntry("Theme", theme); 0089 } 0090 QFile configFile(QStringLiteral(PLYMOUTH_CONFIG_PATH)); 0091 configFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther); 0092 0093 // Special case: Ubuntu derivatives, which work different from everybody else 0094 if (hasUpdateAlternatives()) { 0095 // find the .plymouth file in the theme 0096 QDir dir(QStringLiteral(PLYMOUTH_THEMES_DIR) + theme); 0097 const QStringList themeFile = dir.entryList(QStringList() << QStringLiteral("*.plymouth")); 0098 if (themeFile.count() != 1) { 0099 reply = ActionReply::BackendError; 0100 reply.setErrorDescription(i18n("Theme corrupted: .plymouth file not found inside theme.")); 0101 return reply; 0102 } 0103 0104 QProcess checkProcess; 0105 QByteArray data; 0106 qDebug() << "Running update-alternatives --list default.plymouth now"; 0107 checkProcess.start(updateAlternatives(), {QStringLiteral("--list"), QStringLiteral("default.plymouth")}); 0108 if (!checkProcess.waitForStarted()) { 0109 reply = ActionReply::BackendError; 0110 reply.setErrorDescription(i18n("Cannot start update-alternatives.")); 0111 return reply; 0112 } 0113 if (!checkProcess.waitForFinished()) { 0114 reply = ActionReply::BackendError; 0115 reply.setErrorDescription(i18n("update-alternatives failed to run.")); 0116 return reply; 0117 } 0118 data = checkProcess.readAllStandardOutput(); 0119 0120 if (int ret = checkProcess.exitCode(); ret != 0) { 0121 reply = ActionReply(ActionReply::HelperErrorReply()); 0122 reply.setErrorCode(static_cast<ActionReply::Error>(ret)); 0123 reply.setErrorDescription(i18n("update-alternatives returned with error condition %1.", ret)); 0124 return reply; 0125 } 0126 const QString installFile = dir.path() + QLatin1Char('/') + themeFile.first(); 0127 if (!data.contains(installFile.toUtf8())) { 0128 qDebug() << "Plymouth file not found in update-alternatives. So install it"; 0129 if (auto reply = updateAlternativesInstall(installFile); reply.failed()) { 0130 return reply; 0131 } 0132 } else { 0133 qDebug() << "Running update-alternatives --set now"; 0134 QProcess process; 0135 process.start(QStringLiteral("update-alternatives"), QStringList() << QStringLiteral("--set") << QStringLiteral("default.plymouth") << installFile); 0136 if (!process.waitForStarted()) { 0137 reply = ActionReply::BackendError; 0138 reply.setErrorDescription(i18n("Cannot start update-alternatives.")); 0139 return reply; 0140 } 0141 if (!process.waitForFinished()) { 0142 reply = ActionReply::BackendError; 0143 reply.setErrorDescription(i18n("update-alternatives failed to run.")); 0144 return reply; 0145 } 0146 0147 if (int ret = process.exitCode(); ret != 0) { 0148 reply = ActionReply(ActionReply::HelperErrorReply()); 0149 reply.setErrorCode(static_cast<ActionReply::Error>(ret)); 0150 reply.setErrorDescription(i18n("update-alternatives returned with error condition %1.", ret)); 0151 return reply; 0152 } 0153 } 0154 } 0155 0156 QProcess process; 0157 qDebug() << "Running update-initramfs -u now"; 0158 process.start(QStringLiteral("/usr/sbin/update-initramfs"), QStringList() << QStringLiteral("-u")); 0159 if (!process.waitForStarted()) { 0160 reply = ActionReply::BackendError; 0161 reply.setErrorDescription(i18n("Cannot start initramfs.")); 0162 return reply; 0163 } 0164 // We don't know how long this will take. The helper will need to generate N=installed_kernels initrds. 0165 // Be very generous with the timeout! https://bugs.kde.org/show_bug.cgi?id=400641 0166 // NB: there is also a timeout in the KCM 0167 if (!process.waitForFinished(std::chrono::milliseconds(15min).count())) { 0168 reply = ActionReply::BackendError; 0169 reply.setErrorDescription(i18n("Initramfs failed to run.")); 0170 return reply; 0171 } 0172 0173 int ret = process.exitCode(); 0174 if (ret == 0) { 0175 return ActionReply::SuccessReply(); 0176 } 0177 reply = ActionReply(ActionReply::HelperErrorReply()); 0178 reply.setErrorCode(static_cast<ActionReply::Error>(ret)); 0179 reply.setErrorDescription(i18n("Initramfs returned with error condition %1.", ret)); 0180 return reply; 0181 } 0182 0183 ActionReply PlymouthHelper::install(const QVariantMap &args) 0184 { 0185 const QString themearchive = args.value(QStringLiteral("themearchive")).toString(); 0186 ActionReply reply; 0187 0188 if (themearchive.isEmpty()) { 0189 return ActionReply::BackendError; 0190 } 0191 0192 QDir basedir(QStringLiteral(PLYMOUTH_THEMES_DIR)); 0193 if (!basedir.exists()) { 0194 return ActionReply::BackendError; 0195 } 0196 0197 // this is weird but a decompression is not a single name, so take the path instead 0198 QString installpath = QStringLiteral(PLYMOUTH_THEMES_DIR); 0199 QMimeDatabase db; 0200 QMimeType mimeType = db.mimeTypeForFile(themearchive); 0201 qWarning() << "Postinstallation: uncompress the file"; 0202 0203 // FIXME: check for overwriting, malicious archive entries (../foo) etc. 0204 // FIXME: KArchive should provide "safe mode" for this! 0205 QScopedPointer<KArchive> archive; 0206 0207 static QVector<QString> tarTypes = { 0208 QStringLiteral("application/tar"), 0209 QStringLiteral("application/x-gzip"), 0210 QStringLiteral("application/x-bzip"), 0211 QStringLiteral("application/x-lzma"), 0212 QStringLiteral("application/x-xz"), 0213 QStringLiteral("application/x-bzip-compressed-tar"), 0214 QStringLiteral("application/x-compressed-tar"), 0215 }; 0216 0217 if (mimeType.inherits(QStringLiteral("application/zip"))) { 0218 archive.reset(new KZip(themearchive)); 0219 } else if (std::any_of(tarTypes.cbegin(), tarTypes.cend(), [&](const auto &type) { 0220 return mimeType.inherits(type); 0221 })) { 0222 archive.reset(new KTar(themearchive)); 0223 } else { 0224 qCritical() << "Could not determine type of archive file '" << themearchive << "'"; 0225 return ActionReply::BackendError; 0226 } 0227 0228 if (!archive->open(QIODevice::ReadOnly)) { 0229 qCritical() << "Cannot open archive file '" << themearchive << "'"; 0230 return ActionReply::BackendError; 0231 } 0232 0233 QString themeName; 0234 QString themePath; 0235 const KArchiveDirectory *dir = archive->directory(); 0236 // if there is more than an item in the file, 0237 // put contents in a subdirectory with the same name as the file 0238 if (dir->entries().count() > 1) { 0239 installpath += QLatin1Char('/') + QFileInfo(archive->fileName()).baseName(); 0240 themeName = QFileInfo(archive->fileName()).baseName(); 0241 themePath = installpath; 0242 } else { 0243 themeName = dir->entries().constFirst(); 0244 themePath = installpath + dir->entries().constFirst(); 0245 } 0246 dir->copyTo(installpath); 0247 0248 const QStringList themeFileList = dir->entries().filter(QRegularExpression(QStringLiteral("\\.plymouth$"))); 0249 0250 archive->close(); 0251 0252 // Special case: Ubuntu derivatives, which work different from everybody else 0253 if (hasUpdateAlternatives()) { 0254 // find the .plymouth file in the theme 0255 QDir dir(themePath); 0256 const QStringList themeFile = dir.entryList({QStringLiteral("*.plymouth")}); 0257 if (themeFile.count() != 1) { 0258 reply = ActionReply::BackendError; 0259 reply.setErrorDescription(i18n("Theme corrupted: .plymouth file not found inside theme.")); 0260 return reply; 0261 } 0262 0263 if (auto reply = updateAlternativesInstall(themePath + QLatin1Char('/') + themeFile.first()); reply.failed()) { 0264 return reply; 0265 } 0266 } 0267 0268 QVariantMap map; 0269 map[QStringLiteral("plugin")] = themeName; 0270 map[QStringLiteral("path")] = themePath; 0271 reply = ActionReply::SuccessReply(); 0272 reply.setData(map); 0273 return reply; 0274 } 0275 0276 ActionReply PlymouthHelper::uninstall(const QVariantMap &args) 0277 { 0278 const QString theme = args.value(QStringLiteral("theme")).toString(); 0279 ActionReply reply; 0280 0281 if (theme.isEmpty()) { 0282 qWarning() << "No theme specified."; 0283 return ActionReply::BackendError; 0284 } 0285 0286 QDir dir(QStringLiteral(PLYMOUTH_THEMES_DIR)); 0287 if (!dir.exists()) { 0288 reply = ActionReply::BackendError; 0289 reply.setErrorDescription(i18n("Theme folder %1 does not exist.", QStringLiteral(PLYMOUTH_THEMES_DIR))); 0290 return reply; 0291 } 0292 0293 if (!dir.cd(theme)) { 0294 reply = ActionReply::BackendError; 0295 reply.setErrorDescription(i18n("Theme %1 does not exist.", theme)); 0296 return reply; 0297 } 0298 0299 // Special case: Ubuntu derivatives, which work different from everybody else 0300 if (hasUpdateAlternatives()) { 0301 // find the .plymouth file in the theme 0302 const QStringList themeFile = dir.entryList(QStringList() << QStringLiteral("*.plymouth")); 0303 if (themeFile.count() != 1) { 0304 reply = ActionReply::BackendError; 0305 reply.setErrorDescription(i18n("Theme corrupted: .plymouth file not found inside theme.")); 0306 return reply; 0307 } 0308 int ret = 0; 0309 QProcess process; 0310 0311 process.start(QStringLiteral("update-alternatives"), 0312 {QStringLiteral("--remove"), QStringLiteral("default.plymouth"), dir.path() + QLatin1Char('/') + themeFile.first()}); 0313 if (!process.waitForStarted()) { 0314 reply = ActionReply::BackendError; 0315 reply.setErrorDescription(i18n("Cannot start update-alternatives.")); 0316 return reply; 0317 } 0318 if (!process.waitForFinished()) { 0319 reply = ActionReply::BackendError; 0320 reply.setErrorDescription(i18n("update-alternatives failed to run.")); 0321 return reply; 0322 } 0323 ret = process.exitCode(); 0324 0325 if (ret != 0) { 0326 reply = ActionReply(ActionReply::HelperErrorReply()); 0327 reply.setErrorCode(static_cast<ActionReply::Error>(ret)); 0328 reply.setErrorDescription(i18n("update-alternatives returned with error condition %1.", ret)); 0329 return reply; 0330 } 0331 } 0332 0333 if (dir.removeRecursively()) { 0334 return ActionReply::SuccessReply(); 0335 } 0336 return ActionReply::BackendError; 0337 } 0338 0339 KAUTH_HELPER_MAIN("org.kde.kcontrol.kcmplymouth", PlymouthHelper)