File indexing completed on 2024-04-28 05:36:11
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)), QStringLiteral("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 if (QFileInfo::exists(QStringLiteral("/usr/sbin/update-initramfs"))) { 0158 qDebug() << "Running update-initramfs -u now"; 0159 process.start(QStringLiteral("/usr/sbin/update-initramfs"), QStringList() << QStringLiteral("-u")); 0160 if (!process.waitForStarted()) { 0161 reply = ActionReply::BackendError; 0162 reply.setErrorDescription(i18n("Cannot start initramfs.")); 0163 return reply; 0164 } 0165 } 0166 if (QFileInfo::exists(QStringLiteral("/usr/bin/mkinitcpio"))) { 0167 qDebug() << "Running mkinitcpio -P now"; 0168 process.start(QStringLiteral("/usr/bin/mkinitcpio"), QStringList() << QStringLiteral("-P")); 0169 if (!process.waitForStarted()) { 0170 reply = ActionReply::BackendError; 0171 reply.setErrorDescription(i18n("Cannot start mkinitcpio.")); 0172 return reply; 0173 } 0174 } 0175 // We don't know how long this will take. The helper will need to generate N=installed_kernels initrds. 0176 // Be very generous with the timeout! https://bugs.kde.org/show_bug.cgi?id=400641 0177 // NB: there is also a timeout in the KCM 0178 if (!process.waitForFinished(std::chrono::milliseconds(15min).count())) { 0179 reply = ActionReply::BackendError; 0180 reply.setErrorDescription(i18n("Initramfs failed to run.")); 0181 return reply; 0182 } 0183 0184 int ret = process.exitCode(); 0185 if (ret == 0) { 0186 return ActionReply::SuccessReply(); 0187 } 0188 reply = ActionReply(ActionReply::HelperErrorReply()); 0189 reply.setErrorCode(static_cast<ActionReply::Error>(ret)); 0190 reply.setErrorDescription(i18n("Initramfs returned with error condition %1.", ret)); 0191 return reply; 0192 } 0193 0194 ActionReply PlymouthHelper::install(const QVariantMap &args) 0195 { 0196 const QString themearchive = args.value(QStringLiteral("themearchive")).toString(); 0197 ActionReply reply; 0198 0199 if (themearchive.isEmpty()) { 0200 return ActionReply::BackendError; 0201 } 0202 0203 QDir basedir(QStringLiteral(PLYMOUTH_THEMES_DIR)); 0204 if (!basedir.exists()) { 0205 return ActionReply::BackendError; 0206 } 0207 0208 // this is weird but a decompression is not a single name, so take the path instead 0209 QString installpath = QStringLiteral(PLYMOUTH_THEMES_DIR); 0210 QMimeDatabase db; 0211 QMimeType mimeType = db.mimeTypeForFile(themearchive); 0212 qWarning() << "Postinstallation: uncompress the file"; 0213 0214 // FIXME: check for overwriting, malicious archive entries (../foo) etc. 0215 // FIXME: KArchive should provide "safe mode" for this! 0216 QScopedPointer<KArchive> archive; 0217 0218 static QList<QString> tarTypes = { 0219 QStringLiteral("application/tar"), 0220 QStringLiteral("application/x-gzip"), 0221 QStringLiteral("application/x-bzip"), 0222 QStringLiteral("application/x-lzma"), 0223 QStringLiteral("application/x-xz"), 0224 QStringLiteral("application/x-bzip-compressed-tar"), 0225 QStringLiteral("application/x-compressed-tar"), 0226 }; 0227 0228 if (mimeType.inherits(QStringLiteral("application/zip"))) { 0229 archive.reset(new KZip(themearchive)); 0230 } else if (std::any_of(tarTypes.cbegin(), tarTypes.cend(), [&](const auto &type) { 0231 return mimeType.inherits(type); 0232 })) { 0233 archive.reset(new KTar(themearchive)); 0234 } else { 0235 qCritical() << "Could not determine type of archive file '" << themearchive << "'"; 0236 return ActionReply::BackendError; 0237 } 0238 0239 if (!archive->open(QIODevice::ReadOnly)) { 0240 qCritical() << "Cannot open archive file '" << themearchive << "'"; 0241 return ActionReply::BackendError; 0242 } 0243 0244 QString themeName; 0245 QString themePath; 0246 const KArchiveDirectory *dir = archive->directory(); 0247 // if there is more than an item in the file, 0248 // put contents in a subdirectory with the same name as the file 0249 if (dir->entries().count() > 1) { 0250 installpath += QLatin1Char('/') + QFileInfo(archive->fileName()).baseName(); 0251 themeName = QFileInfo(archive->fileName()).baseName(); 0252 themePath = installpath; 0253 } else { 0254 themeName = dir->entries().constFirst(); 0255 themePath = installpath + dir->entries().constFirst(); 0256 } 0257 dir->copyTo(installpath); 0258 0259 archive->close(); 0260 0261 // Special case: Ubuntu derivatives, which work different from everybody else 0262 if (hasUpdateAlternatives()) { 0263 // find the .plymouth file in the theme 0264 QDir dir(themePath); 0265 const QStringList themeFile = dir.entryList({QStringLiteral("*.plymouth")}); 0266 if (themeFile.count() != 1) { 0267 reply = ActionReply::BackendError; 0268 reply.setErrorDescription(i18n("Theme corrupted: .plymouth file not found inside theme.")); 0269 return reply; 0270 } 0271 0272 if (auto reply = updateAlternativesInstall(themePath + QLatin1Char('/') + themeFile.first()); reply.failed()) { 0273 return reply; 0274 } 0275 } 0276 0277 QVariantMap map; 0278 map[QStringLiteral("plugin")] = themeName; 0279 map[QStringLiteral("path")] = themePath; 0280 reply = ActionReply::SuccessReply(); 0281 reply.setData(map); 0282 return reply; 0283 } 0284 0285 ActionReply PlymouthHelper::uninstall(const QVariantMap &args) 0286 { 0287 const QString theme = args.value(QStringLiteral("theme")).toString(); 0288 ActionReply reply; 0289 0290 if (theme.isEmpty()) { 0291 qWarning() << "No theme specified."; 0292 return ActionReply::BackendError; 0293 } 0294 0295 QDir dir(QStringLiteral(PLYMOUTH_THEMES_DIR)); 0296 if (!dir.exists()) { 0297 reply = ActionReply::BackendError; 0298 reply.setErrorDescription(i18n("Theme folder %1 does not exist.", QStringLiteral(PLYMOUTH_THEMES_DIR))); 0299 return reply; 0300 } 0301 0302 if (!dir.cd(theme)) { 0303 reply = ActionReply::BackendError; 0304 reply.setErrorDescription(i18n("Theme %1 does not exist.", theme)); 0305 return reply; 0306 } 0307 0308 // Special case: Ubuntu derivatives, which work different from everybody else 0309 if (hasUpdateAlternatives()) { 0310 // find the .plymouth file in the theme 0311 const QStringList themeFile = dir.entryList(QStringList() << QStringLiteral("*.plymouth")); 0312 if (themeFile.count() != 1) { 0313 reply = ActionReply::BackendError; 0314 reply.setErrorDescription(i18n("Theme corrupted: .plymouth file not found inside theme.")); 0315 return reply; 0316 } 0317 int ret = 0; 0318 QProcess process; 0319 0320 process.start(QStringLiteral("update-alternatives"), 0321 {QStringLiteral("--remove"), QStringLiteral("default.plymouth"), dir.path() + QLatin1Char('/') + themeFile.first()}); 0322 if (!process.waitForStarted()) { 0323 reply = ActionReply::BackendError; 0324 reply.setErrorDescription(i18n("Cannot start update-alternatives.")); 0325 return reply; 0326 } 0327 if (!process.waitForFinished()) { 0328 reply = ActionReply::BackendError; 0329 reply.setErrorDescription(i18n("update-alternatives failed to run.")); 0330 return reply; 0331 } 0332 ret = process.exitCode(); 0333 0334 if (ret != 0) { 0335 reply = ActionReply(ActionReply::HelperErrorReply()); 0336 reply.setErrorCode(static_cast<ActionReply::Error>(ret)); 0337 reply.setErrorDescription(i18n("update-alternatives returned with error condition %1.", ret)); 0338 return reply; 0339 } 0340 } 0341 0342 if (dir.removeRecursively()) { 0343 return ActionReply::SuccessReply(); 0344 } 0345 return ActionReply::BackendError; 0346 } 0347 0348 KAUTH_HELPER_MAIN("org.kde.kcontrol.kcmplymouth", PlymouthHelper) 0349 0350 #include "moc_helper.cpp"