File indexing completed on 2024-04-14 05:38:14

0001 /*
0002     SPDX-FileCopyrightText: 2018-2020 Harald Sitter <sitter@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "upgraderprocess.h"
0007 
0008 #include <KLocalizedString>
0009 #include <KOSRelease>
0010 #include <KTitleWidget>
0011 
0012 #include <QDialog>
0013 #include <QDialogButtonBox>
0014 #include <QIcon>
0015 #include <QProcess>
0016 #include <QTextEdit>
0017 #include <QVBoxLayout>
0018 
0019 #include "debug.h"
0020 #include "upgraderwatcher.h"
0021 
0022 void UpgraderProcess::setUseDevel(bool useDevel)
0023 {
0024     m_useDevel = useDevel;
0025 }
0026 
0027 void UpgraderProcess::run()
0028 {
0029     auto process = new QProcess(this);
0030     connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
0031             this, [this](){
0032         m_waiting = false;
0033         emit notPending();
0034         deleteLater();
0035     });
0036 
0037     process->setProcessChannelMode(QProcess::MergedChannels);
0038     connect(process, &QProcess::readyReadStandardOutput,
0039             this, [this, process]() {
0040         if (!NOTIFIER().isDebugEnabled() && !m_waiting) {
0041             return;
0042         }
0043 
0044         const QString newOutput = QString::fromUtf8(process->readAllStandardOutput());
0045         // route this through format string so newlines are preserved
0046         qCDebug(NOTIFIER, "do-release-upgrader: %s\n", newOutput.toUtf8().constData());
0047         if (m_waiting) {
0048             m_output += newOutput;
0049         }
0050     });
0051 
0052     // Monitor dbus for the higher level UIs to appear.
0053     // If the proc finishes before the dbus magic happens it likely crapped out in early
0054     // checks which have zero UI backing, meaning we need to display stdout in a dialog.
0055     auto unexpectedConnection = connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
0056                                         this, &UpgraderProcess::onUnexpectedFinish);
0057     connect(UpgraderWatcher::self(), &UpgraderWatcher::upgraderRunning,
0058             this, [this, unexpectedConnection]() {
0059         m_waiting = false;
0060         emit notPending();
0061         disconnect(unexpectedConnection);
0062     });
0063 
0064     // pkexec is being difficult. It will refuse to auth a startDetached service
0065     // because it won't have a parent and parentless commands are not allowed
0066     // to auth.
0067     // Instead hold on to the process.
0068     // For future reference: another approach is to sh -c and hold
0069     // do-release-upgrade as a fork of that sh.
0070     auto args = QStringList({
0071                                 QStringLiteral("-m"), QStringLiteral("desktop"),
0072                                 QStringLiteral("-f"), QStringLiteral("DistUpgradeViewKDE")
0073                             });
0074     if (m_useDevel) {
0075         args << QStringLiteral("--devel-release");
0076     }
0077 
0078     qCDebug(NOTIFIER) << "Starting do-release-upgrade";
0079     process->start(QStringLiteral("do-release-upgrade"), args);
0080 }
0081 
0082 void UpgraderProcess::onUnexpectedFinish(int code)
0083 {
0084     // If the process finished within some seconds something is probably broken
0085     // in the bootstrap. Display the output.
0086     // Notably the upgrader exit(1) when it detects pending updates on apt and will only
0087     // inform the user through stdout. It's very crappy UX.
0088 
0089     if (code == 0) {
0090         qCWarning(NOTIFIER) << "Unexpected early exit but ignoring because it was code 0!";
0091         return;
0092     }
0093 
0094     qCDebug(NOTIFIER) << "Probable failure" << code;
0095 
0096     QDialog dialog;
0097     dialog.setWindowIcon(QIcon::fromTheme(QStringLiteral("system-software-update")));
0098     dialog.setWindowTitle(i18nc("@title/window upgrade failure dialog", "Upgrade Failed"));
0099     auto layout = new QVBoxLayout;
0100     dialog.setLayout(layout);
0101 
0102     auto title = new KTitleWidget(&dialog);
0103     title->setText(i18nc("@title title widget above process output",
0104                          "Upgrade failed with the following output:"));
0105     layout->addWidget(title);
0106 
0107     auto editor = new QTextEdit(&dialog);
0108     editor->setReadOnly(true);
0109     editor->setText(m_output.replace(QStringLiteral("ubuntu"), QStringLiteral("neon"), Qt::CaseInsensitive));
0110     layout->addWidget(editor);
0111 
0112     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
0113     layout->addWidget(buttonBox);
0114     connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
0115 
0116     dialog.exec();
0117 }