File indexing completed on 2024-04-28 15:29:54

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 2005-2009 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2020-2022 Harald Sitter <sitter@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 #include "ksycoca.h"
0012 #include "ksycoca_p.h"
0013 #include "ksycocafactory_p.h"
0014 #include "ksycocatype.h"
0015 #include "ksycocautils_p.h"
0016 #include "sycocadebug.h"
0017 #include <KConfigGroup>
0018 #include <KSandbox>
0019 #include <KSharedConfig>
0020 
0021 #include <QCoreApplication>
0022 #include <QDataStream>
0023 #include <QFile>
0024 #include <QFileInfo>
0025 #include <QMetaMethod>
0026 #include <QStandardPaths>
0027 #include <QThread>
0028 #include <QThreadStorage>
0029 
0030 #include <QCryptographicHash>
0031 #include <fcntl.h>
0032 #include <kmimetypefactory_p.h>
0033 #include <kservicefactory_p.h>
0034 #include <kservicegroupfactory_p.h>
0035 #include <kservicetypefactory_p.h>
0036 #include <stdlib.h>
0037 
0038 #include "kbuildsycoca_p.h"
0039 #include "ksycocadevices_p.h"
0040 
0041 #ifdef Q_OS_UNIX
0042 #include <sys/time.h>
0043 #include <utime.h>
0044 #endif
0045 
0046 /**
0047  * Sycoca file version number.
0048  * If the existing file is outdated, it will not get read
0049  * but instead we'll regenerate a new one.
0050  * However running apps should still be able to read it, so
0051  * only add to the data, never remove/modify.
0052  */
0053 #define KSYCOCA_VERSION 305
0054 
0055 #if HAVE_MADVISE || HAVE_MMAP
0056 #include <sys/mman.h> // This #include was checked when looking for posix_madvise
0057 #endif
0058 
0059 #ifndef MAP_FAILED
0060 #define MAP_FAILED ((void *)-1)
0061 #endif
0062 
0063 QDataStream &operator>>(QDataStream &in, KSycocaHeader &h)
0064 {
0065     in >> h.prefixes >> h.timeStamp >> h.language >> h.updateSignature;
0066     return in;
0067 }
0068 
0069 // The following limitations are in place:
0070 // Maximum length of a single string: 8192 bytes
0071 // Maximum length of a string list: 1024 strings
0072 // Maximum number of entries: 8192
0073 //
0074 // The purpose of these limitations is to limit the impact
0075 // of database corruption.
0076 
0077 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
0078 
0079 KSycocaPrivate::KSycocaPrivate(KSycoca *qq)
0080     : databaseStatus(DatabaseNotOpen)
0081     , readError(false)
0082     , timeStamp(0)
0083     , m_databasePath()
0084     , updateSig(0)
0085     , m_fileWatcher(new KDirWatch)
0086     , m_haveListeners(false)
0087     , q(qq)
0088     , sycoca_size(0)
0089     , sycoca_mmap(nullptr)
0090     , m_mmapFile(nullptr)
0091     , m_device(nullptr)
0092     , m_mimeTypeFactory(nullptr)
0093     , m_serviceTypeFactory(nullptr)
0094     , m_serviceFactory(nullptr)
0095     , m_serviceGroupFactory(nullptr)
0096 {
0097 #ifdef Q_OS_WIN
0098     /*
0099       on windows we use KMemFile (QSharedMemory) to avoid problems
0100       with mmap (can't delete a mmap'd file)
0101     */
0102     m_sycocaStrategy = StrategyMemFile;
0103 #else
0104     m_sycocaStrategy = StrategyMmap;
0105 #endif
0106     KConfigGroup config(KSharedConfig::openConfig(), "KSycoca");
0107     setStrategyFromString(config.readEntry("strategy"));
0108 }
0109 
0110 void KSycocaPrivate::setStrategyFromString(const QString &strategy)
0111 {
0112     if (strategy == QLatin1String("mmap")) {
0113         m_sycocaStrategy = StrategyMmap;
0114     } else if (strategy == QLatin1String("file")) {
0115         m_sycocaStrategy = StrategyFile;
0116     } else if (strategy == QLatin1String("sharedmem")) {
0117         m_sycocaStrategy = StrategyMemFile;
0118     } else if (!strategy.isEmpty()) {
0119         qCWarning(SYCOCA) << "Unknown sycoca strategy:" << strategy;
0120     }
0121 }
0122 
0123 bool KSycocaPrivate::tryMmap()
0124 {
0125 #if HAVE_MMAP
0126     Q_ASSERT(!m_databasePath.isEmpty());
0127     m_mmapFile = new QFile(m_databasePath);
0128     const bool canRead = m_mmapFile->open(QIODevice::ReadOnly);
0129     Q_ASSERT(canRead);
0130     if (!canRead) {
0131         return false;
0132     }
0133     fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
0134     sycoca_size = m_mmapFile->size();
0135     void *mmapRet = mmap(nullptr, sycoca_size, PROT_READ, MAP_SHARED, m_mmapFile->handle(), 0);
0136     /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
0137        null pointer too.  */
0138     if (mmapRet == MAP_FAILED || mmapRet == nullptr) {
0139         qCDebug(SYCOCA).nospace() << "mmap failed. (length = " << sycoca_size << ")";
0140         sycoca_mmap = nullptr;
0141         return false;
0142     } else {
0143         sycoca_mmap = static_cast<const char *>(mmapRet);
0144 #if HAVE_MADVISE
0145         (void)posix_madvise(mmapRet, sycoca_size, POSIX_MADV_WILLNEED);
0146 #endif // HAVE_MADVISE
0147         return true;
0148     }
0149 #else
0150     return false;
0151 #endif // HAVE_MMAP
0152 }
0153 
0154 int KSycoca::version()
0155 {
0156     return KSYCOCA_VERSION;
0157 }
0158 
0159 class KSycocaSingleton
0160 {
0161 public:
0162     KSycocaSingleton()
0163     {
0164     }
0165     ~KSycocaSingleton()
0166     {
0167     }
0168 
0169     bool hasSycoca() const
0170     {
0171         return m_threadSycocas.hasLocalData();
0172     }
0173     KSycoca *sycoca()
0174     {
0175         if (!m_threadSycocas.hasLocalData()) {
0176             m_threadSycocas.setLocalData(new KSycoca);
0177         }
0178         return m_threadSycocas.localData();
0179     }
0180     void setSycoca(KSycoca *s)
0181     {
0182         m_threadSycocas.setLocalData(s);
0183     }
0184 
0185 private:
0186     QThreadStorage<KSycoca *> m_threadSycocas;
0187 };
0188 
0189 Q_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
0190 
0191 QString KSycocaPrivate::findDatabase()
0192 {
0193     Q_ASSERT(databaseStatus == DatabaseNotOpen);
0194 
0195     const QString path = KSycoca::absoluteFilePath();
0196     const QFileInfo info(path);
0197     if (info.isReadable()) {
0198         if (m_haveListeners && m_fileWatcher) {
0199             m_fileWatcher->addFile(path);
0200         }
0201         return path;
0202     }
0203     // Let's be notified when it gets created - by another process or by ourselves
0204     if (m_fileWatcher) {
0205         m_fileWatcher->addFile(path);
0206     }
0207     return QString();
0208 }
0209 
0210 // Read-only constructor
0211 // One instance per thread
0212 KSycoca::KSycoca()
0213     : d(new KSycocaPrivate(this))
0214 {
0215     if (d->m_fileWatcher) {
0216         // We always delete and recreate the DB, so KDirWatch normally emits created
0217         connect(d->m_fileWatcher.get(), &KDirWatch::created, this, [this]() {
0218             d->slotDatabaseChanged();
0219         });
0220         // In some cases, KDirWatch only thinks the file was modified though
0221         connect(d->m_fileWatcher.get(), &KDirWatch::dirty, this, [this]() {
0222             d->slotDatabaseChanged();
0223         });
0224     }
0225 }
0226 
0227 bool KSycocaPrivate::openDatabase()
0228 {
0229     Q_ASSERT(databaseStatus == DatabaseNotOpen);
0230 
0231     delete m_device;
0232     m_device = nullptr;
0233 
0234     if (m_databasePath.isEmpty()) {
0235         m_databasePath = findDatabase();
0236     }
0237 
0238     bool result = true;
0239     if (!m_databasePath.isEmpty()) {
0240         static bool firstTime = true;
0241         if (firstTime) {
0242             firstTime = false;
0243             if (KSandbox::isFlatpak()) {
0244                 // We're running inside flatpak, which sets all times to 1970
0245                 // So the first very time, don't use an existing database, recreate it
0246                 qCDebug(SYCOCA) << "flatpak detected, ignoring" << m_databasePath;
0247                 return false;
0248             }
0249         }
0250 
0251         qCDebug(SYCOCA) << "Opening ksycoca from" << m_databasePath;
0252         m_dbLastModified = QFileInfo(m_databasePath).lastModified();
0253         result = checkVersion();
0254     } else { // No database file
0255         // qCDebug(SYCOCA) << "Could not open ksycoca";
0256         result = false;
0257     }
0258     return result;
0259 }
0260 
0261 KSycocaAbstractDevice *KSycocaPrivate::device()
0262 {
0263     if (m_device) {
0264         return m_device;
0265     }
0266 
0267     KSycocaAbstractDevice *device = m_device;
0268     Q_ASSERT(!m_databasePath.isEmpty());
0269 #if HAVE_MMAP
0270     if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
0271         device = new KSycocaMmapDevice(sycoca_mmap, sycoca_size);
0272         if (!device->device()->open(QIODevice::ReadOnly)) {
0273             delete device;
0274             device = nullptr;
0275         }
0276     }
0277 #endif
0278 #ifndef QT_NO_SHAREDMEMORY
0279     if (!device && m_sycocaStrategy == StrategyMemFile) {
0280         device = new KSycocaMemFileDevice(m_databasePath);
0281         if (!device->device()->open(QIODevice::ReadOnly)) {
0282             delete device;
0283             device = nullptr;
0284         }
0285     }
0286 #endif
0287     if (!device) {
0288         device = new KSycocaFileDevice(m_databasePath);
0289         if (!device->device()->open(QIODevice::ReadOnly)) {
0290             qCWarning(SYCOCA) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
0291             // delete device; device = 0; // this would crash in the return statement...
0292         }
0293     }
0294     if (device) {
0295         m_device = device;
0296     }
0297     return m_device;
0298 }
0299 
0300 QDataStream *&KSycocaPrivate::stream()
0301 {
0302     if (!m_device) {
0303         if (databaseStatus == DatabaseNotOpen) {
0304             checkDatabase(KSycocaPrivate::IfNotFoundRecreate);
0305         }
0306 
0307         device(); // create m_device
0308     }
0309 
0310     return m_device->stream();
0311 }
0312 
0313 void KSycocaPrivate::slotDatabaseChanged()
0314 {
0315     // We don't have information anymore on what resources changed, so emit them all
0316     changeList = QStringList() << QStringLiteral("services") << QStringLiteral("servicetypes") << QStringLiteral("xdgdata-mime") << QStringLiteral("apps");
0317 
0318     qCDebug(SYCOCA) << QThread::currentThread() << "got a notifyDatabaseChanged signal";
0319     // KDirWatch tells us the database file changed
0320     // We would have found out in the next call to ensureCacheValid(), but for
0321     // now keep the call to closeDatabase, to help refcounting to 0 the old mmapped file earlier.
0322     closeDatabase();
0323     // Start monitoring the new file right away
0324     m_databasePath = findDatabase();
0325 
0326     // Now notify applications
0327     Q_EMIT q->databaseChanged();
0328 
0329 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 80)
0330     Q_EMIT q->databaseChanged(changeList);
0331 #endif
0332 }
0333 
0334 KMimeTypeFactory *KSycocaPrivate::mimeTypeFactory()
0335 {
0336     if (!m_mimeTypeFactory) {
0337         m_mimeTypeFactory = new KMimeTypeFactory(q);
0338     }
0339     return m_mimeTypeFactory;
0340 }
0341 
0342 KServiceTypeFactory *KSycocaPrivate::serviceTypeFactory()
0343 {
0344     if (!m_serviceTypeFactory) {
0345         m_serviceTypeFactory = new KServiceTypeFactory(q);
0346     }
0347     return m_serviceTypeFactory;
0348 }
0349 
0350 KServiceFactory *KSycocaPrivate::serviceFactory()
0351 {
0352     if (!m_serviceFactory) {
0353         m_serviceFactory = new KServiceFactory(q);
0354     }
0355     return m_serviceFactory;
0356 }
0357 
0358 KServiceGroupFactory *KSycocaPrivate::serviceGroupFactory()
0359 {
0360     if (!m_serviceGroupFactory) {
0361         m_serviceGroupFactory = new KServiceGroupFactory(q);
0362     }
0363     return m_serviceGroupFactory;
0364 }
0365 
0366 // Add local paths to the list of dirs we got from the global database
0367 void KSycocaPrivate::addLocalResourceDir(const QString &path)
0368 {
0369     // If any local path is more recent than the time the global sycoca was created, build a local sycoca.
0370     allResourceDirs.insert(path, timeStamp);
0371 }
0372 
0373 // Read-write constructor - only for KBuildSycoca
0374 KSycoca::KSycoca(bool /* dummy */)
0375     : d(new KSycocaPrivate(this))
0376 {
0377 }
0378 
0379 KSycoca *KSycoca::self()
0380 {
0381     KSycoca *s = ksycocaInstance()->sycoca();
0382     Q_ASSERT(s);
0383     return s;
0384 }
0385 
0386 KSycoca::~KSycoca()
0387 {
0388     d->closeDatabase();
0389     delete d;
0390     // if (ksycocaInstance.exists()
0391     //    && ksycocaInstance->self == this)
0392     //    ksycocaInstance->self = 0;
0393 }
0394 
0395 bool KSycoca::isAvailable() // TODO KF6: make it non-static (mostly useful for unittests)
0396 {
0397     return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
0398 }
0399 
0400 void KSycocaPrivate::closeDatabase()
0401 {
0402     delete m_device;
0403     m_device = nullptr;
0404 
0405     // It is very important to delete all factories here
0406     // since they cache information about the database file
0407     // But other threads might be using them, so this class is
0408     // refcounted, and deleted when the last thread is done with them
0409     qDeleteAll(m_factories);
0410     m_factories.clear();
0411 
0412     m_mimeTypeFactory = nullptr;
0413     m_serviceFactory = nullptr;
0414     m_serviceTypeFactory = nullptr;
0415     m_serviceGroupFactory = nullptr;
0416 
0417 #if HAVE_MMAP
0418     if (sycoca_mmap) {
0419         // Solaris has munmap(char*, size_t) and everything else should
0420         // be happy with a char* for munmap(void*, size_t)
0421         munmap(const_cast<char *>(sycoca_mmap), sycoca_size);
0422         sycoca_mmap = nullptr;
0423     }
0424     delete m_mmapFile;
0425     m_mmapFile = nullptr;
0426 #endif
0427 
0428     databaseStatus = DatabaseNotOpen;
0429     m_databasePath.clear();
0430     timeStamp = 0;
0431 }
0432 
0433 void KSycoca::addFactory(KSycocaFactory *factory)
0434 {
0435     d->addFactory(factory);
0436 }
0437 
0438 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
0439 bool KSycoca::isChanged(const char *type)
0440 {
0441     return self()->d->changeList.contains(QString::fromLatin1(type));
0442 }
0443 #endif
0444 
0445 QDataStream *KSycoca::findEntry(int offset, KSycocaType &type)
0446 {
0447     QDataStream *str = stream();
0448     Q_ASSERT(str);
0449     // qCDebug(SYCOCA) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
0450     str->device()->seek(offset);
0451     qint32 aType;
0452     *str >> aType;
0453     type = KSycocaType(aType);
0454     // qCDebug(SYCOCA) << QString("KSycoca::found type %1").arg(aType);
0455     return str;
0456 }
0457 
0458 KSycocaFactoryList *KSycoca::factories()
0459 {
0460     return d->factories();
0461 }
0462 
0463 // Warning, checkVersion rewinds to the beginning of stream().
0464 bool KSycocaPrivate::checkVersion()
0465 {
0466     QDataStream *m_str = device()->stream();
0467     Q_ASSERT(m_str);
0468     m_str->device()->seek(0);
0469     qint32 aVersion;
0470     *m_str >> aVersion;
0471     if (aVersion < KSYCOCA_VERSION) {
0472         qCDebug(SYCOCA) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
0473         databaseStatus = BadVersion;
0474         return false;
0475     } else {
0476         databaseStatus = DatabaseOK;
0477         return true;
0478     }
0479 }
0480 
0481 // This is now completely useless. KF6: remove
0482 extern KSERVICE_EXPORT bool kservice_require_kded;
0483 KSERVICE_EXPORT bool kservice_require_kded = true;
0484 
0485 // If it returns true, we have a valid database and the stream has rewinded to the beginning
0486 // and past the version number.
0487 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
0488 {
0489     if (databaseStatus == DatabaseOK) {
0490         if (checkVersion()) { // we know the version is ok, but we must rewind the stream anyway
0491             return true;
0492         }
0493     }
0494 
0495     closeDatabase(); // close the dummy one
0496 
0497     // Check if new database already available
0498     if (openDatabase()) {
0499         // Database exists, and version is ok, we can read it.
0500 
0501         if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && ifNotFound != IfNotFoundDoNothing) {
0502             // Ensure it's up-to-date, rebuild if needed
0503             checkDirectories();
0504 
0505             // Don't check again for some time
0506             m_lastCheck.start();
0507         }
0508 
0509         return true;
0510     }
0511 
0512     if (ifNotFound & IfNotFoundRecreate) {
0513         return buildSycoca();
0514     }
0515 
0516     return false;
0517 }
0518 
0519 QDataStream *KSycoca::findFactory(KSycocaFactoryId id)
0520 {
0521     // Ensure we have a valid database (right version, and rewinded to beginning)
0522     if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
0523         return nullptr;
0524     }
0525 
0526     QDataStream *str = stream();
0527     Q_ASSERT(str);
0528 
0529     qint32 aId;
0530     qint32 aOffset;
0531     while (true) {
0532         *str >> aId;
0533         if (aId == 0) {
0534             qCWarning(SYCOCA) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
0535             break;
0536         }
0537         *str >> aOffset;
0538         if (aId == id) {
0539             // qCDebug(SYCOCA) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
0540             str->device()->seek(aOffset);
0541             return str;
0542         }
0543     }
0544     return nullptr;
0545 }
0546 
0547 bool KSycoca::needsRebuild()
0548 {
0549     return d->needsRebuild();
0550 }
0551 
0552 KSycocaHeader KSycocaPrivate::readSycocaHeader()
0553 {
0554     KSycocaHeader header;
0555     // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
0556     if (!checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) {
0557         return header;
0558     }
0559     QDataStream *str = stream();
0560     qint64 oldPos = str->device()->pos();
0561 
0562     Q_ASSERT(str);
0563     qint32 aId;
0564     qint32 aOffset;
0565     // skip factories offsets
0566     while (true) {
0567         *str >> aId;
0568         if (aId) {
0569             *str >> aOffset;
0570         } else {
0571             break; // just read 0
0572         }
0573     }
0574     // We now point to the header
0575     QStringList directoryList;
0576     *str >> header >> directoryList;
0577     allResourceDirs.clear();
0578     for (int i = 0; i < directoryList.count(); ++i) {
0579         qint64 mtime;
0580         *str >> mtime;
0581         allResourceDirs.insert(directoryList.at(i), mtime);
0582     }
0583 
0584     QStringList fileList;
0585     *str >> fileList;
0586     extraFiles.clear();
0587     for (const auto &fileName : std::as_const(fileList)) {
0588         qint64 mtime;
0589         *str >> mtime;
0590         extraFiles.insert(fileName, mtime);
0591     }
0592 
0593     str->device()->seek(oldPos);
0594 
0595     timeStamp = header.timeStamp;
0596 
0597     // for the useless public accessors. KF6: remove these two lines, the accessors and the vars.
0598     language = header.language;
0599     updateSig = header.updateSignature;
0600 
0601     return header;
0602 }
0603 
0604 class TimestampChecker
0605 {
0606 public:
0607     TimestampChecker()
0608         : m_now(QDateTime::currentDateTime())
0609     {
0610     }
0611 
0612     // Check times of last modification of all directories on which ksycoca depends,
0613     // If none of them is newer than the mtime we stored for that directory at the
0614     // last rebuild, this means that there's no need to rebuild ksycoca.
0615     bool checkDirectoriesTimestamps(const QMap<QString, qint64> &dirs) const
0616     {
0617         Q_ASSERT(!dirs.isEmpty());
0618         // qCDebug(SYCOCA) << "checking file timestamps";
0619         for (auto it = dirs.begin(); it != dirs.end(); ++it) {
0620             const QString dir = it.key();
0621             const qint64 lastStamp = it.value();
0622 
0623             auto visitor = [&](const QFileInfo &fi) {
0624                 const QDateTime mtime = fi.lastModified();
0625                 if (mtime.toMSecsSinceEpoch() > lastStamp) {
0626                     if (mtime > m_now) {
0627                         qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
0628                     }
0629                     qCDebug(SYCOCA) << "dir timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
0630                     // no need to continue search
0631                     return false;
0632                 }
0633 
0634                 return true;
0635             };
0636 
0637             if (!KSycocaUtilsPrivate::visitResourceDirectory(dir, visitor)) {
0638                 return false;
0639             }
0640         }
0641         return true;
0642     }
0643 
0644     bool checkFilesTimestamps(const QMap<QString, qint64> &files) const
0645     {
0646         for (auto it = files.begin(); it != files.end(); ++it) {
0647             const QString fileName = it.key();
0648             const qint64 lastStamp = it.value();
0649 
0650             QFileInfo fi(fileName);
0651             if (!fi.exists()) {
0652                 return false;
0653             }
0654             const QDateTime mtime = fi.lastModified();
0655             if (mtime.toMSecsSinceEpoch() > lastStamp) {
0656                 if (mtime > m_now) {
0657                     qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
0658                 }
0659                 qCDebug(SYCOCA) << "file timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
0660                 return false;
0661             }
0662         }
0663         return true;
0664     }
0665 
0666 private:
0667     QDateTime m_now;
0668 };
0669 
0670 void KSycocaPrivate::checkDirectories()
0671 {
0672     if (needsRebuild()) {
0673         buildSycoca();
0674     }
0675 }
0676 
0677 bool KSycocaPrivate::needsRebuild()
0678 {
0679     if (!timeStamp && databaseStatus == DatabaseOK) {
0680         (void)readSycocaHeader();
0681     }
0682     // these days timeStamp is really a "bool headerFound", the value itself doesn't matter...
0683     // KF6: replace it with bool.
0684     const auto timestampChecker = TimestampChecker();
0685     bool ret = timeStamp != 0
0686         && (!timestampChecker.checkDirectoriesTimestamps(allResourceDirs) //
0687             || !timestampChecker.checkFilesTimestamps(extraFiles));
0688     if (ret) {
0689         return true;
0690     }
0691     auto files = KBuildSycoca::factoryExtraFiles();
0692     // ensure files are ordered so next comparison works
0693     files.sort();
0694     // to cover cases when extra files were added
0695     return extraFiles.keys() != files;
0696 }
0697 
0698 bool KSycocaPrivate::buildSycoca()
0699 {
0700     KBuildSycoca builder;
0701     if (!builder.recreate()) {
0702         return false; // error
0703     }
0704 
0705     closeDatabase(); // close the dummy one
0706 
0707     // Ok, the new database should be here now, open it.
0708     if (!openDatabase()) {
0709         qCDebug(SYCOCA) << "Still no database...";
0710         return false;
0711     }
0712     return true;
0713 }
0714 
0715 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
0716 quint32 KSycoca::timeStamp()
0717 {
0718     if (!d->timeStamp) {
0719         (void)d->readSycocaHeader();
0720     }
0721     return d->timeStamp / 1000; // from ms to s
0722 }
0723 #endif
0724 
0725 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
0726 quint32 KSycoca::updateSignature()
0727 {
0728     if (!d->timeStamp) {
0729         (void)d->readSycocaHeader();
0730     }
0731     return d->updateSig;
0732 }
0733 #endif
0734 
0735 QString KSycoca::absoluteFilePath(DatabaseType type)
0736 {
0737     Q_UNUSED(type); // GlobalDatabase concept removed in 5.61
0738     const QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0739     QString suffix = QLatin1Char('_') + QLocale().bcp47Name();
0740 
0741     const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
0742     if (ksycoca_env.isEmpty()) {
0743         const QByteArray pathHash = QCryptographicHash::hash(paths.join(QLatin1Char(':')).toUtf8(), QCryptographicHash::Sha1);
0744         suffix += QLatin1Char('_') + QString::fromLatin1(pathHash.toBase64());
0745         suffix.replace(QLatin1Char('/'), QLatin1Char('_'));
0746 #ifdef Q_OS_WIN
0747         suffix.replace(QLatin1Char(':'), QLatin1Char('_'));
0748 #endif
0749         const QString fileName = QLatin1String("ksycoca5") + suffix;
0750         return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + fileName;
0751     } else {
0752         return QFile::decodeName(ksycoca_env);
0753     }
0754 }
0755 
0756 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
0757 QString KSycoca::language()
0758 {
0759     if (d->language.isEmpty()) {
0760         (void)d->readSycocaHeader();
0761     }
0762     return d->language;
0763 }
0764 #endif
0765 
0766 QStringList KSycoca::allResourceDirs()
0767 {
0768     if (!d->timeStamp) {
0769         (void)d->readSycocaHeader();
0770     }
0771     return d->allResourceDirs.keys();
0772 }
0773 
0774 void KSycoca::flagError()
0775 {
0776     qCWarning(SYCOCA) << "ERROR: KSycoca database corruption!";
0777     KSycoca *sycoca = self();
0778     if (sycoca->d->readError) {
0779         return;
0780     }
0781     sycoca->d->readError = true;
0782     if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && !sycoca->isBuilding()) {
0783         // Rebuild the damned thing.
0784         KBuildSycoca builder;
0785         (void)builder.recreate();
0786     }
0787 }
0788 
0789 bool KSycoca::isBuilding()
0790 {
0791     return false;
0792 }
0793 
0794 void KSycoca::disableAutoRebuild()
0795 {
0796     ksycocaInstance->sycoca()->d->m_fileWatcher = nullptr;
0797 }
0798 
0799 QDataStream *&KSycoca::stream()
0800 {
0801     return d->stream();
0802 }
0803 
0804 void KSycoca::connectNotify(const QMetaMethod &signal)
0805 {
0806     if (signal.name() == "databaseChanged" && !d->m_haveListeners) {
0807         d->m_haveListeners = true;
0808         if (d->m_databasePath.isEmpty()) {
0809             d->m_databasePath = d->findDatabase();
0810         } else if (d->m_fileWatcher) {
0811             d->m_fileWatcher->addFile(d->m_databasePath);
0812         }
0813     }
0814 }
0815 
0816 void KSycoca::clearCaches()
0817 {
0818     if (ksycocaInstance.exists() && ksycocaInstance()->hasSycoca()) {
0819         ksycocaInstance()->sycoca()->d->closeDatabase();
0820     }
0821 }
0822 
0823 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
0824 KSERVICE_EXPORT int ksycoca_ms_between_checks = 1500;
0825 
0826 void KSycoca::ensureCacheValid()
0827 {
0828     if (qAppName() == QLatin1String(KBUILDSYCOCA_EXENAME)) {
0829         return;
0830     }
0831 
0832     if (d->databaseStatus != KSycocaPrivate::DatabaseOK) {
0833         if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
0834             return;
0835         }
0836     }
0837 
0838     if (d->m_lastCheck.isValid() && d->m_lastCheck.elapsed() < ksycoca_ms_between_checks) {
0839         return;
0840     }
0841     d->m_lastCheck.start();
0842 
0843     // Check if the file on disk was modified since we last checked it.
0844     QFileInfo info(d->m_databasePath);
0845     if (info.lastModified() == d->m_dbLastModified) {
0846         // Check if the watched directories were modified, then the cache needs a rebuild.
0847         d->checkDirectories();
0848         return;
0849     }
0850 
0851     // Close the database and forget all about what we knew.
0852     // The next call to any public method will recreate
0853     // everything that's needed.
0854     d->closeDatabase();
0855 }
0856 
0857 #include "moc_ksycoca.cpp"