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"