File indexing completed on 2024-11-17 04:55:40

0001 /*
0002  *   SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0003  *   SPDX-FileCopyrightText: 2018 Abhijeet Sharma <sharma.abhijeet2096@gmail.com>
0004  *
0005  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "FwupdBackend.h"
0009 #include "../DiscoverVersion.h"
0010 #include "FwupdResource.h"
0011 #include "FwupdSourcesBackend.h"
0012 #include "FwupdTransaction.h"
0013 #include <Transaction/Transaction.h>
0014 #include <resources/SourcesModel.h>
0015 #include <resources/StandardBackendUpdater.h>
0016 
0017 #include <KAboutData>
0018 #include <KConfigGroup>
0019 #include <KLocalizedString>
0020 #include <KPluginFactory>
0021 #include <KSharedConfig>
0022 #include <QCoreApplication>
0023 
0024 DISCOVER_BACKEND_PLUGIN(FwupdBackend)
0025 
0026 FwupdBackend::FwupdBackend(QObject *parent)
0027     : AbstractResourcesBackend(parent)
0028     , client(fwupd_client_new())
0029     , m_updater(new StandardBackendUpdater(this))
0030     , m_cancellable(g_cancellable_new())
0031 {
0032     g_autoptr(GError) error = nullptr;
0033     if (!fwupd_client_connect(client, m_cancellable, &error)) {
0034         handleError(error);
0035         m_isValid = false;
0036         return;
0037     }
0038     fwupd_client_set_user_agent_for_package(client, "plasma-discover", version.data());
0039     connect(m_updater, &StandardBackendUpdater::updatesCountChanged, this, &FwupdBackend::updatesCountChanged);
0040 
0041     SourcesModel::global()->addSourcesBackend(new FwupdSourcesBackend(this));
0042     QTimer::singleShot(0, this, &FwupdBackend::checkForUpdates);
0043 }
0044 
0045 QMap<GChecksumType, QCryptographicHash::Algorithm> FwupdBackend::gchecksumToQChryptographicHash()
0046 {
0047     static QMap<GChecksumType, QCryptographicHash::Algorithm> map;
0048     if (map.isEmpty()) {
0049         map.insert(G_CHECKSUM_SHA1, QCryptographicHash::Sha1);
0050         map.insert(G_CHECKSUM_SHA256, QCryptographicHash::Sha256);
0051         map.insert(G_CHECKSUM_SHA512, QCryptographicHash::Sha512);
0052         map.insert(G_CHECKSUM_MD5, QCryptographicHash::Md5);
0053     }
0054     return map;
0055 }
0056 
0057 FwupdBackend::~FwupdBackend()
0058 {
0059     g_cancellable_cancel(m_cancellable);
0060     g_object_unref(m_cancellable);
0061 
0062     g_object_unref(client);
0063 }
0064 
0065 void FwupdBackend::addResource(FwupdResource *res)
0066 {
0067     res->setParent(this);
0068     auto &r = m_resources[res->packageName()];
0069     if (r) {
0070         Q_EMIT resourceRemoved(r);
0071         delete r;
0072     }
0073     r = res;
0074     Q_ASSERT(m_resources.value(res->packageName()) == res);
0075 }
0076 
0077 FwupdResource *FwupdBackend::createRelease(FwupdDevice *device)
0078 {
0079     FwupdRelease *release = fwupd_device_get_release_default(device);
0080     FwupdResource *res = new FwupdResource(device, QString::fromUtf8(fwupd_release_get_appstream_id(release)), this);
0081     res->setReleaseDetails(release);
0082 
0083     /* the same as we have already */
0084     if (qstrcmp(fwupd_device_get_version(device), fwupd_release_get_version(release)) == 0) {
0085         qWarning() << "Fwupd Error: same firmware version as installed";
0086     }
0087 
0088     return res;
0089 }
0090 
0091 void FwupdBackend::addUpdates()
0092 {
0093     g_autoptr(GError) error = nullptr;
0094     g_autoptr(GPtrArray) devices = fwupd_client_get_devices(client, m_cancellable, &error);
0095 
0096     if (!devices) {
0097         if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO))
0098             qDebug() << "Fwupd Info: No Devices Found";
0099         else
0100             handleError(error);
0101         return;
0102     }
0103 
0104     for (uint i = 0; i < devices->len && !g_cancellable_is_cancelled(m_cancellable); i++) {
0105         FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i);
0106 
0107         if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED))
0108             continue;
0109 
0110         if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_LOCKED))
0111             continue;
0112 
0113         if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE))
0114             continue;
0115 
0116         g_autoptr(GError) error2 = nullptr;
0117         g_autoptr(GPtrArray) rels = fwupd_client_get_upgrades(client, fwupd_device_get_id(device), m_cancellable, &error2);
0118         if (rels) {
0119             fwupd_device_add_release(device, (FwupdRelease *)g_ptr_array_index(rels, 0));
0120             auto res = createApp(device);
0121             if (!res) {
0122                 qWarning() << "Fwupd Error: Cannot Create App From Device" << fwupd_device_get_name(device);
0123             } else {
0124                 QString longdescription;
0125                 for (uint j = 0; j < rels->len; j++) {
0126                     FwupdRelease *release = (FwupdRelease *)g_ptr_array_index(rels, j);
0127                     if (!fwupd_release_get_description(release))
0128                         continue;
0129                     if (rels->len > 1) {
0130                         longdescription += QStringLiteral("Version %1\n").arg(QString::fromUtf8(fwupd_release_get_version(release)));
0131                     }
0132                     longdescription += QString::fromUtf8(fwupd_release_get_description(release));
0133                     if (rels->len > 1) {
0134                         longdescription += QLatin1Char('\n');
0135                     }
0136                 }
0137                 res->setDescription(longdescription);
0138 
0139                 // Make sure to set the installed version of the current thing so
0140                 // they can both be shown in the update page UI
0141                 auto installedResource = m_resources[res->packageName()];
0142                 if (installedResource) {
0143                     res->setInstalledVersion(installedResource->availableVersion());
0144                 }
0145                 addResource(res);
0146             }
0147         } else {
0148             if (g_error_matches(error2, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
0149                 qWarning() << "fwupd: Device not supported:" << fwupd_device_get_name(device);
0150             } else if (!g_error_matches(error2, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) {
0151                 handleError(error2);
0152             }
0153         }
0154     }
0155 }
0156 
0157 QByteArray FwupdBackend::getChecksum(const QString &filename, QCryptographicHash::Algorithm hashAlgorithm)
0158 {
0159     QFile f(filename);
0160     if (!f.open(QFile::ReadOnly)) {
0161         qWarning() << "could not open to check" << filename;
0162         return {};
0163     }
0164 
0165     QCryptographicHash hash(hashAlgorithm);
0166     if (!hash.addData(&f)) {
0167         qWarning() << "could not read to check" << filename;
0168         return {};
0169     }
0170 
0171     return hash.result().toHex();
0172 }
0173 
0174 FwupdResource *FwupdBackend::createApp(FwupdDevice *device)
0175 {
0176     FwupdRelease *release = fwupd_device_get_release_default(device);
0177     std::unique_ptr<FwupdResource> app(createRelease(device));
0178 
0179     if (!app->isLiveUpdatable()) {
0180         qWarning() << "Fwupd Error: " << app->name() << "[" << app->id() << "]"
0181                    << "cannot be updated";
0182         return nullptr;
0183     }
0184 
0185     if (app->id().isNull()) {
0186         qWarning() << "Fwupd Error: No id for firmware";
0187         return nullptr;
0188     }
0189 
0190     if (app->availableVersion().isNull()) {
0191         qWarning() << "Fwupd Error: No version! for " << app->id();
0192         return nullptr;
0193     }
0194 
0195     GPtrArray *checksums = fwupd_release_get_checksums(release);
0196     if (checksums->len == 0) {
0197         qWarning() << "Fwupd Error: " << app->name() << "[" << app->id() << "] has no checksums, ignoring as unsafe";
0198         return nullptr;
0199     }
0200 
0201 #if FWUPD_CHECK_VERSION(1, 5, 6)
0202     GPtrArray *locations = fwupd_release_get_locations(release);
0203     const QUrl update_uri(locations->len == 0 ? QString::fromUtf8("") : QString::fromUtf8((const gchar *)g_ptr_array_index(locations, 0)));
0204 #else
0205     const QUrl update_uri(QString::fromUtf8(fwupd_release_get_uri(release)));
0206 #endif
0207     if (!update_uri.isValid()) {
0208         qWarning() << "Fwupd Error: No Update URI available for" << app->name() << "[" << app->id() << "]";
0209         return nullptr;
0210     }
0211 
0212     /* Checking for firmware in the cache? */
0213     const QString filename_cache = app->cacheFile();
0214     if (QFile::exists(filename_cache)) {
0215         /* Currently LVFS supports SHA1 only*/
0216         const QByteArray checksum_tmp(fwupd_checksum_get_by_kind(checksums, G_CHECKSUM_SHA1));
0217         const QByteArray checksum = getChecksum(filename_cache, QCryptographicHash::Sha1);
0218         if (checksum_tmp != checksum) {
0219             QFile::remove(filename_cache);
0220         }
0221     }
0222 
0223     app->setState(AbstractResource::Upgradeable);
0224     return app.release();
0225 }
0226 
0227 void FwupdBackend::handleError(GError *perror)
0228 {
0229     // TODO: localise the error message
0230     if (perror && !g_error_matches(perror, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE) && !g_error_matches(perror, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) {
0231         const QString msg = QString::fromUtf8(perror->message);
0232         QTimer::singleShot(0, this, [this, msg]() {
0233             Q_EMIT passiveMessage(msg);
0234         });
0235         qWarning() << "Fwupd Error" << perror->code << perror->message;
0236     }
0237     // else
0238     //     qDebug() << "Fwupd skipped" << perror->code << perror->message;
0239 }
0240 
0241 QString FwupdBackend::cacheFile(const QString &kind, const QString &basename)
0242 {
0243     const QDir cacheDir(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation));
0244     const QString cacheDirFile = cacheDir.filePath(kind);
0245 
0246     if (!QFileInfo::exists(cacheDirFile) && !cacheDir.mkpath(kind)) {
0247         qWarning() << "Fwupd Error: cannot make  cache directory!";
0248         return {};
0249     }
0250 
0251     return cacheDir.filePath(kind + QLatin1Char('/') + basename);
0252 }
0253 
0254 static void fwupd_client_get_devices_cb(GObject * /*source*/, GAsyncResult *res, gpointer user_data)
0255 {
0256     FwupdBackend *helper = (FwupdBackend *)user_data;
0257     g_autoptr(GError) error = nullptr;
0258     auto array = fwupd_client_get_devices_finish(helper->client, res, &error);
0259     if (!error)
0260         helper->setDevices(array);
0261     else
0262         helper->handleError(error);
0263 }
0264 
0265 void FwupdBackend::setDevices(GPtrArray *devices)
0266 {
0267     for (uint i = 0; devices && i < devices->len; i++) {
0268         FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i);
0269 
0270         if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED))
0271             continue;
0272 
0273         g_autoptr(GError) error = nullptr;
0274         g_autoptr(GPtrArray) releases = fwupd_client_get_releases(client, fwupd_device_get_id(device), m_cancellable, &error);
0275 
0276         if (error) {
0277             if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
0278                 qWarning() << "fwupd: Device not supported:" << fwupd_device_get_name(device) << error->message;
0279                 continue;
0280             }
0281             if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) {
0282                 continue;
0283             }
0284 
0285             handleError(error);
0286         }
0287 
0288         auto res = new FwupdResource(device, this);
0289         for (uint i = 0; releases && i < releases->len; ++i) {
0290             FwupdRelease *release = (FwupdRelease *)g_ptr_array_index(releases, i);
0291             if (res->installedVersion().toUtf8() == fwupd_release_get_version(release)) {
0292                 res->setReleaseDetails(release);
0293                 break;
0294             }
0295         }
0296         addResource(res);
0297     }
0298     g_ptr_array_unref(devices);
0299 
0300     addUpdates();
0301 
0302     m_fetching = false;
0303     Q_EMIT fetchingChanged();
0304     Q_EMIT initialized();
0305 }
0306 
0307 static void fwupd_client_get_remotes_cb(GObject * /*source*/, GAsyncResult *res, gpointer user_data)
0308 {
0309     FwupdBackend *helper = (FwupdBackend *)user_data;
0310     g_autoptr(GError) error = nullptr;
0311     auto array = fwupd_client_get_remotes_finish(helper->client, res, &error);
0312     if (!error)
0313         helper->setRemotes(array);
0314     else
0315         helper->handleError(error);
0316 }
0317 
0318 static void fwupd_client_refresh_remote_cb(GObject * /*source*/, GAsyncResult *res, gpointer user_data)
0319 {
0320     FwupdBackend *helper = (FwupdBackend *)user_data;
0321     g_autoptr(GError) error = nullptr;
0322     const bool successful = fwupd_client_refresh_remote_finish(helper->client, res, &error);
0323     if (!successful)
0324         helper->handleError(error);
0325 }
0326 
0327 void FwupdBackend::setRemotes(GPtrArray *remotes)
0328 {
0329     for (uint i = 0; remotes && i < remotes->len; i++) {
0330         FwupdRemote *remote = (FwupdRemote *)g_ptr_array_index(remotes, i);
0331         if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED))
0332             continue;
0333 
0334         if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL
0335             || fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DIRECTORY) {
0336             continue;
0337         }
0338 
0339         fwupd_client_refresh_remote2_async(client, remote, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, m_cancellable, fwupd_client_refresh_remote_cb, this);
0340     }
0341 }
0342 
0343 void FwupdBackend::checkForUpdates()
0344 {
0345     if (m_fetching)
0346         return;
0347 
0348     m_fetching = true;
0349     Q_EMIT fetchingChanged();
0350 
0351     fwupd_client_get_devices_async(client, m_cancellable, fwupd_client_get_devices_cb, this);
0352     fwupd_client_get_remotes_async(client, m_cancellable, fwupd_client_get_remotes_cb, this);
0353 }
0354 
0355 int FwupdBackend::updatesCount() const
0356 {
0357     return m_updater->updatesCount();
0358 }
0359 
0360 ResultsStream *FwupdBackend::search(const AbstractResourcesBackend::Filters &filter)
0361 {
0362     if (!filter.resourceUrl.isEmpty()) {
0363         if (filter.resourceUrl.scheme() == QLatin1String("fwupd")) {
0364             return findResourceByPackageName(filter.resourceUrl);
0365         } else if (filter.resourceUrl.isLocalFile()) {
0366             return resourceForFile(filter.resourceUrl);
0367         }
0368         return new ResultsStream(QStringLiteral("FwupdStream-empty"), {});
0369     }
0370 
0371     auto stream = new ResultsStream(QStringLiteral("FwupdStream"));
0372     auto f = [this, stream, filter]() {
0373         QVector<StreamResult> ret;
0374         for (AbstractResource *r : std::as_const(m_resources)) {
0375             if (r->state() < filter.state)
0376                 continue;
0377 
0378             if (filter.search.isEmpty() || r->name().contains(filter.search, Qt::CaseInsensitive)
0379                 || r->comment().contains(filter.search, Qt::CaseInsensitive)) {
0380                 ret += r;
0381             }
0382         }
0383         if (!ret.isEmpty())
0384             Q_EMIT stream->resourcesFound(ret);
0385         stream->finish();
0386     };
0387     if (isFetching()) {
0388         connect(this, &FwupdBackend::initialized, stream, f);
0389     } else {
0390         QTimer::singleShot(0, this, f);
0391     }
0392     return stream;
0393 }
0394 
0395 ResultsStream *FwupdBackend::findResourceByPackageName(const QUrl &search)
0396 {
0397     auto res = search.scheme() == QLatin1String("fwupd") ? m_resources.value(search.host().replace(QLatin1Char('.'), QLatin1Char(' '))) : nullptr;
0398     if (!res) {
0399         return new ResultsStream(QStringLiteral("FwupdStream"), {});
0400     } else
0401         return new ResultsStream(QStringLiteral("FwupdStream"), {res});
0402 }
0403 
0404 AbstractBackendUpdater *FwupdBackend::backendUpdater() const
0405 {
0406     return m_updater;
0407 }
0408 
0409 AbstractReviewsBackend *FwupdBackend::reviewsBackend() const
0410 {
0411     return nullptr;
0412 }
0413 
0414 Transaction *FwupdBackend::installApplication(AbstractResource *app, const AddonList &addons)
0415 {
0416     Q_ASSERT(addons.isEmpty());
0417     return installApplication(app);
0418 }
0419 
0420 Transaction *FwupdBackend::installApplication(AbstractResource *app)
0421 {
0422     return new FwupdTransaction(qobject_cast<FwupdResource *>(app), this);
0423 }
0424 
0425 Transaction *FwupdBackend::removeApplication(AbstractResource * /*app*/)
0426 {
0427     qWarning() << "should not have reached here, it's not possible to uninstall a firmware";
0428     return nullptr;
0429 }
0430 
0431 ResultsStream *FwupdBackend::resourceForFile(const QUrl &path)
0432 {
0433     if (!path.isLocalFile())
0434         return new ResultsStream(QStringLiteral("FwupdStream-void"), {});
0435 
0436     g_autoptr(GError) error = nullptr;
0437 
0438     const QString fileName = path.fileName();
0439     QMimeDatabase db;
0440     QMimeType type = db.mimeTypeForFile(fileName);
0441     FwupdResource *app = nullptr;
0442 
0443     if (type.isValid() && type.inherits(QStringLiteral("application/vnd.ms-cab-compressed"))) {
0444         g_autofree gchar *filename = fileName.toUtf8().data();
0445         g_autoptr(GPtrArray) devices = fwupd_client_get_details(client, filename, nullptr, &error);
0446 
0447         if (devices) {
0448             FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, 0);
0449             app = createRelease(device);
0450             app->setState(AbstractResource::None);
0451             for (uint i = 1; i < devices->len; i++) {
0452                 FwupdDevice *device = (FwupdDevice *)g_ptr_array_index(devices, i);
0453                 FwupdResource *app_ = createRelease(device);
0454                 app_->setState(AbstractResource::None);
0455             }
0456             addResource(app);
0457             connect(app, &FwupdResource::stateChanged, this, &FwupdBackend::updatesCountChanged);
0458             return new ResultsStream(QStringLiteral("FwupdStream-file"), {app});
0459         } else {
0460             handleError(error);
0461         }
0462     }
0463     return new ResultsStream(QStringLiteral("FwupdStream-void"), {});
0464 }
0465 
0466 QString FwupdBackend::displayName() const
0467 {
0468     return i18n("Firmware Updates");
0469 }
0470 
0471 bool FwupdBackend::hasApplications() const
0472 {
0473     return false;
0474 }
0475 
0476 #include "FwupdBackend.moc"