1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
4     SPDX-FileCopyrightText: 2005-2009 David Faure <faure@kde.org>
5     SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
6     SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
7 
8     SPDX-License-Identifier: LGPL-2.0-only
9 */
10 
11 #include "ksycoca.h"
12 #include "ksycoca_p.h"
13 #include "ksycocafactory_p.h"
14 #include "ksycocatype.h"
15 #include "ksycocautils_p.h"
16 #include "sycocadebug.h"
17 #include <KConfigGroup>
18 #include <KSharedConfig>
19 
20 #include <QCoreApplication>
21 #include <QDataStream>
22 #include <QFile>
23 #include <QFileInfo>
24 #include <QMetaMethod>
25 #include <QStandardPaths>
26 #include <QThread>
27 #include <QThreadStorage>
28 
29 #include <QCryptographicHash>
30 #include <fcntl.h>
31 #include <kmimetypefactory_p.h>
32 #include <kservicefactory_p.h>
33 #include <kservicegroupfactory_p.h>
34 #include <kservicetypefactory_p.h>
35 #include <stdlib.h>
36 
37 #include "kbuildsycoca_p.h"
38 #include "ksycocadevices_p.h"
39 
40 #ifdef Q_OS_UNIX
41 #include <sys/time.h>
42 #include <utime.h>
43 #endif
44 
45 /**
46  * Sycoca file version number.
47  * If the existing file is outdated, it will not get read
48  * but instead we'll regenerate a new one.
49  * However running apps should still be able to read it, so
50  * only add to the data, never remove/modify.
51  */
52 #define KSYCOCA_VERSION 304
53 
54 #if HAVE_MADVISE || HAVE_MMAP
55 #include <sys/mman.h> // This #include was checked when looking for posix_madvise
56 #endif
57 
58 #ifndef MAP_FAILED
59 #define MAP_FAILED ((void *)-1)
60 #endif
61 
operator >>(QDataStream & in,KSycocaHeader & h)62 QDataStream &operator>>(QDataStream &in, KSycocaHeader &h)
63 {
64     in >> h.prefixes >> h.timeStamp >> h.language >> h.updateSignature;
65     return in;
66 }
67 
68 // The following limitations are in place:
69 // Maximum length of a single string: 8192 bytes
70 // Maximum length of a string list: 1024 strings
71 // Maximum number of entries: 8192
72 //
73 // The purpose of these limitations is to limit the impact
74 // of database corruption.
75 
Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)76 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
77 
78 KSycocaPrivate::KSycocaPrivate(KSycoca *qq)
79     : databaseStatus(DatabaseNotOpen)
80     , readError(false)
81     , timeStamp(0)
82     , m_databasePath()
83     , updateSig(0)
84     , m_fileWatcher(new KDirWatch)
85     , m_haveListeners(false)
86     , q(qq)
87     , sycoca_size(0)
88     , sycoca_mmap(nullptr)
89     , m_mmapFile(nullptr)
90     , m_device(nullptr)
91     , m_mimeTypeFactory(nullptr)
92     , m_serviceTypeFactory(nullptr)
93     , m_serviceFactory(nullptr)
94     , m_serviceGroupFactory(nullptr)
95 {
96 #ifdef Q_OS_WIN
97     /*
98       on windows we use KMemFile (QSharedMemory) to avoid problems
99       with mmap (can't delete a mmap'd file)
100     */
101     m_sycocaStrategy = StrategyMemFile;
102 #else
103     m_sycocaStrategy = StrategyMmap;
104 #endif
105     KConfigGroup config(KSharedConfig::openConfig(), "KSycoca");
106     setStrategyFromString(config.readEntry("strategy"));
107 }
108 
setStrategyFromString(const QString & strategy)109 void KSycocaPrivate::setStrategyFromString(const QString &strategy)
110 {
111     if (strategy == QLatin1String("mmap")) {
112         m_sycocaStrategy = StrategyMmap;
113     } else if (strategy == QLatin1String("file")) {
114         m_sycocaStrategy = StrategyFile;
115     } else if (strategy == QLatin1String("sharedmem")) {
116         m_sycocaStrategy = StrategyMemFile;
117     } else if (!strategy.isEmpty()) {
118         qCWarning(SYCOCA) << "Unknown sycoca strategy:" << strategy;
119     }
120 }
121 
tryMmap()122 bool KSycocaPrivate::tryMmap()
123 {
124 #if HAVE_MMAP
125     Q_ASSERT(!m_databasePath.isEmpty());
126     m_mmapFile = new QFile(m_databasePath);
127     const bool canRead = m_mmapFile->open(QIODevice::ReadOnly);
128     Q_ASSERT(canRead);
129     if (!canRead) {
130         return false;
131     }
132     fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
133     sycoca_size = m_mmapFile->size();
134     void *mmapRet = mmap(nullptr, sycoca_size, PROT_READ, MAP_SHARED, m_mmapFile->handle(), 0);
135     /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
136        null pointer too.  */
137     if (mmapRet == MAP_FAILED || mmapRet == nullptr) {
138         qCDebug(SYCOCA).nospace() << "mmap failed. (length = " << sycoca_size << ")";
139         sycoca_mmap = nullptr;
140         return false;
141     } else {
142         sycoca_mmap = static_cast<const char *>(mmapRet);
143 #if HAVE_MADVISE
144         (void)posix_madvise(mmapRet, sycoca_size, POSIX_MADV_WILLNEED);
145 #endif // HAVE_MADVISE
146         return true;
147     }
148 #else
149     return false;
150 #endif // HAVE_MMAP
151 }
152 
version()153 int KSycoca::version()
154 {
155     return KSYCOCA_VERSION;
156 }
157 
158 class KSycocaSingleton
159 {
160 public:
KSycocaSingleton()161     KSycocaSingleton()
162     {
163     }
~KSycocaSingleton()164     ~KSycocaSingleton()
165     {
166     }
167 
hasSycoca() const168     bool hasSycoca() const
169     {
170         return m_threadSycocas.hasLocalData();
171     }
sycoca()172     KSycoca *sycoca()
173     {
174         if (!m_threadSycocas.hasLocalData()) {
175             m_threadSycocas.setLocalData(new KSycoca);
176         }
177         return m_threadSycocas.localData();
178     }
setSycoca(KSycoca * s)179     void setSycoca(KSycoca *s)
180     {
181         m_threadSycocas.setLocalData(s);
182     }
183 
184 private:
185     QThreadStorage<KSycoca *> m_threadSycocas;
186 };
187 
Q_GLOBAL_STATIC(KSycocaSingleton,ksycocaInstance)188 Q_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
189 
190 QString KSycocaPrivate::findDatabase()
191 {
192     Q_ASSERT(databaseStatus == DatabaseNotOpen);
193 
194     const QString path = KSycoca::absoluteFilePath();
195     const QFileInfo info(path);
196     if (info.isReadable()) {
197         if (m_haveListeners && m_fileWatcher) {
198             m_fileWatcher->addFile(path);
199         }
200         return path;
201     }
202     // Let's be notified when it gets created - by another process or by ourselves
203     if (m_fileWatcher) {
204         m_fileWatcher->addFile(path);
205     }
206     return QString();
207 }
208 
209 // Read-only constructor
210 // One instance per thread
KSycoca()211 KSycoca::KSycoca()
212     : d(new KSycocaPrivate(this))
213 {
214     if (d->m_fileWatcher) {
215         // We always delete and recreate the DB, so KDirWatch normally emits created
216         connect(d->m_fileWatcher.get(), &KDirWatch::created, this, [this]() {
217             d->slotDatabaseChanged();
218         });
219         // In some cases, KDirWatch only thinks the file was modified though
220         connect(d->m_fileWatcher.get(), &KDirWatch::dirty, this, [this]() {
221             d->slotDatabaseChanged();
222         });
223     }
224 }
225 
openDatabase()226 bool KSycocaPrivate::openDatabase()
227 {
228     Q_ASSERT(databaseStatus == DatabaseNotOpen);
229 
230     delete m_device;
231     m_device = nullptr;
232 
233     if (m_databasePath.isEmpty()) {
234         m_databasePath = findDatabase();
235     }
236 
237     bool result = true;
238     if (!m_databasePath.isEmpty()) {
239         static bool firstTime = true;
240         if (firstTime) {
241             firstTime = false;
242             if (QFileInfo::exists(QStringLiteral("/.flatpak-info"))) {
243                 // We're running inside flatpak, which sets all times to 1970
244                 // So the first very time, don't use an existing database, recreate it
245                 qCDebug(SYCOCA) << "flatpak detected, ignoring" << m_databasePath;
246                 return false;
247             }
248         }
249 
250         qCDebug(SYCOCA) << "Opening ksycoca from" << m_databasePath;
251         m_dbLastModified = QFileInfo(m_databasePath).lastModified();
252         result = checkVersion();
253     } else { // No database file
254         // qCDebug(SYCOCA) << "Could not open ksycoca";
255         result = false;
256     }
257     return result;
258 }
259 
device()260 KSycocaAbstractDevice *KSycocaPrivate::device()
261 {
262     if (m_device) {
263         return m_device;
264     }
265 
266     KSycocaAbstractDevice *device = m_device;
267     Q_ASSERT(!m_databasePath.isEmpty());
268 #if HAVE_MMAP
269     if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
270         device = new KSycocaMmapDevice(sycoca_mmap, sycoca_size);
271         if (!device->device()->open(QIODevice::ReadOnly)) {
272             delete device;
273             device = nullptr;
274         }
275     }
276 #endif
277 #ifndef QT_NO_SHAREDMEMORY
278     if (!device && m_sycocaStrategy == StrategyMemFile) {
279         device = new KSycocaMemFileDevice(m_databasePath);
280         if (!device->device()->open(QIODevice::ReadOnly)) {
281             delete device;
282             device = nullptr;
283         }
284     }
285 #endif
286     if (!device) {
287         device = new KSycocaFileDevice(m_databasePath);
288         if (!device->device()->open(QIODevice::ReadOnly)) {
289             qCWarning(SYCOCA) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
290             // delete device; device = 0; // this would crash in the return statement...
291         }
292     }
293     if (device) {
294         m_device = device;
295     }
296     return m_device;
297 }
298 
stream()299 QDataStream *&KSycocaPrivate::stream()
300 {
301     if (!m_device) {
302         if (databaseStatus == DatabaseNotOpen) {
303             checkDatabase(KSycocaPrivate::IfNotFoundRecreate);
304         }
305 
306         device(); // create m_device
307     }
308 
309     return m_device->stream();
310 }
311 
slotDatabaseChanged()312 void KSycocaPrivate::slotDatabaseChanged()
313 {
314     // We don't have information anymore on what resources changed, so emit them all
315     changeList = QStringList() << QStringLiteral("services") << QStringLiteral("servicetypes") << QStringLiteral("xdgdata-mime") << QStringLiteral("apps");
316 
317     qCDebug(SYCOCA) << QThread::currentThread() << "got a notifyDatabaseChanged signal";
318     // KDirWatch tells us the database file changed
319     // We would have found out in the next call to ensureCacheValid(), but for
320     // now keep the call to closeDatabase, to help refcounting to 0 the old mmapped file earlier.
321     closeDatabase();
322     // Start monitoring the new file right away
323     m_databasePath = findDatabase();
324 
325     // Now notify applications
326     Q_EMIT q->databaseChanged();
327 
328 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 80)
329     Q_EMIT q->databaseChanged(changeList);
330 #endif
331 }
332 
mimeTypeFactory()333 KMimeTypeFactory *KSycocaPrivate::mimeTypeFactory()
334 {
335     if (!m_mimeTypeFactory) {
336         m_mimeTypeFactory = new KMimeTypeFactory(q);
337     }
338     return m_mimeTypeFactory;
339 }
340 
serviceTypeFactory()341 KServiceTypeFactory *KSycocaPrivate::serviceTypeFactory()
342 {
343     if (!m_serviceTypeFactory) {
344         m_serviceTypeFactory = new KServiceTypeFactory(q);
345     }
346     return m_serviceTypeFactory;
347 }
348 
serviceFactory()349 KServiceFactory *KSycocaPrivate::serviceFactory()
350 {
351     if (!m_serviceFactory) {
352         m_serviceFactory = new KServiceFactory(q);
353     }
354     return m_serviceFactory;
355 }
356 
serviceGroupFactory()357 KServiceGroupFactory *KSycocaPrivate::serviceGroupFactory()
358 {
359     if (!m_serviceGroupFactory) {
360         m_serviceGroupFactory = new KServiceGroupFactory(q);
361     }
362     return m_serviceGroupFactory;
363 }
364 
365 // Add local paths to the list of dirs we got from the global database
addLocalResourceDir(const QString & path)366 void KSycocaPrivate::addLocalResourceDir(const QString &path)
367 {
368     // If any local path is more recent than the time the global sycoca was created, build a local sycoca.
369     allResourceDirs.insert(path, timeStamp);
370 }
371 
372 // Read-write constructor - only for KBuildSycoca
KSycoca(bool)373 KSycoca::KSycoca(bool /* dummy */)
374     : d(new KSycocaPrivate(this))
375 {
376 }
377 
self()378 KSycoca *KSycoca::self()
379 {
380     KSycoca *s = ksycocaInstance()->sycoca();
381     Q_ASSERT(s);
382     return s;
383 }
384 
~KSycoca()385 KSycoca::~KSycoca()
386 {
387     d->closeDatabase();
388     delete d;
389     // if (ksycocaInstance.exists()
390     //    && ksycocaInstance->self == this)
391     //    ksycocaInstance->self = 0;
392 }
393 
isAvailable()394 bool KSycoca::isAvailable() // TODO KF6: make it non-static (mostly useful for unittests)
395 {
396     return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
397 }
398 
closeDatabase()399 void KSycocaPrivate::closeDatabase()
400 {
401     delete m_device;
402     m_device = nullptr;
403 
404     // It is very important to delete all factories here
405     // since they cache information about the database file
406     // But other threads might be using them, so this class is
407     // refcounted, and deleted when the last thread is done with them
408     qDeleteAll(m_factories);
409     m_factories.clear();
410 
411     m_mimeTypeFactory = nullptr;
412     m_serviceFactory = nullptr;
413     m_serviceTypeFactory = nullptr;
414     m_serviceGroupFactory = nullptr;
415 
416 #if HAVE_MMAP
417     if (sycoca_mmap) {
418         // Solaris has munmap(char*, size_t) and everything else should
419         // be happy with a char* for munmap(void*, size_t)
420         munmap(const_cast<char *>(sycoca_mmap), sycoca_size);
421         sycoca_mmap = nullptr;
422     }
423     delete m_mmapFile;
424     m_mmapFile = nullptr;
425 #endif
426 
427     databaseStatus = DatabaseNotOpen;
428     m_databasePath.clear();
429     timeStamp = 0;
430 }
431 
addFactory(KSycocaFactory * factory)432 void KSycoca::addFactory(KSycocaFactory *factory)
433 {
434     d->addFactory(factory);
435 }
436 
437 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
isChanged(const char * type)438 bool KSycoca::isChanged(const char *type)
439 {
440     return self()->d->changeList.contains(QString::fromLatin1(type));
441 }
442 #endif
443 
findEntry(int offset,KSycocaType & type)444 QDataStream *KSycoca::findEntry(int offset, KSycocaType &type)
445 {
446     QDataStream *str = stream();
447     Q_ASSERT(str);
448     // qCDebug(SYCOCA) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
449     str->device()->seek(offset);
450     qint32 aType;
451     *str >> aType;
452     type = KSycocaType(aType);
453     // qCDebug(SYCOCA) << QString("KSycoca::found type %1").arg(aType);
454     return str;
455 }
456 
factories()457 KSycocaFactoryList *KSycoca::factories()
458 {
459     return d->factories();
460 }
461 
462 // Warning, checkVersion rewinds to the beginning of stream().
checkVersion()463 bool KSycocaPrivate::checkVersion()
464 {
465     QDataStream *m_str = device()->stream();
466     Q_ASSERT(m_str);
467     m_str->device()->seek(0);
468     qint32 aVersion;
469     *m_str >> aVersion;
470     if (aVersion < KSYCOCA_VERSION) {
471         qCDebug(SYCOCA) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
472         databaseStatus = BadVersion;
473         return false;
474     } else {
475         databaseStatus = DatabaseOK;
476         return true;
477     }
478 }
479 
480 // This is now completely useless. KF6: remove
481 extern KSERVICE_EXPORT bool kservice_require_kded;
482 KSERVICE_EXPORT bool kservice_require_kded = true;
483 
484 // If it returns true, we have a valid database and the stream has rewinded to the beginning
485 // and past the version number.
checkDatabase(BehaviorsIfNotFound ifNotFound)486 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
487 {
488     if (databaseStatus == DatabaseOK) {
489         if (checkVersion()) { // we know the version is ok, but we must rewind the stream anyway
490             return true;
491         }
492     }
493 
494     closeDatabase(); // close the dummy one
495 
496     // Check if new database already available
497     if (openDatabase()) {
498         // Database exists, and version is ok, we can read it.
499 
500         if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && ifNotFound != IfNotFoundDoNothing) {
501             // Ensure it's up-to-date, rebuild if needed
502             checkDirectories();
503 
504             // Don't check again for some time
505             m_lastCheck.start();
506         }
507 
508         return true;
509     }
510 
511     if (ifNotFound & IfNotFoundRecreate) {
512         return buildSycoca();
513     }
514 
515     return false;
516 }
517 
findFactory(KSycocaFactoryId id)518 QDataStream *KSycoca::findFactory(KSycocaFactoryId id)
519 {
520     // Ensure we have a valid database (right version, and rewinded to beginning)
521     if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
522         return nullptr;
523     }
524 
525     QDataStream *str = stream();
526     Q_ASSERT(str);
527 
528     qint32 aId;
529     qint32 aOffset;
530     while (true) {
531         *str >> aId;
532         if (aId == 0) {
533             qCWarning(SYCOCA) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
534             break;
535         }
536         *str >> aOffset;
537         if (aId == id) {
538             // qCDebug(SYCOCA) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
539             str->device()->seek(aOffset);
540             return str;
541         }
542     }
543     return nullptr;
544 }
545 
needsRebuild()546 bool KSycoca::needsRebuild()
547 {
548     return d->needsRebuild();
549 }
550 
readSycocaHeader()551 KSycocaHeader KSycocaPrivate::readSycocaHeader()
552 {
553     KSycocaHeader header;
554     // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
555     if (!checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) {
556         return header;
557     }
558     QDataStream *str = stream();
559     qint64 oldPos = str->device()->pos();
560 
561     Q_ASSERT(str);
562     qint32 aId;
563     qint32 aOffset;
564     // skip factories offsets
565     while (true) {
566         *str >> aId;
567         if (aId) {
568             *str >> aOffset;
569         } else {
570             break; // just read 0
571         }
572     }
573     // We now point to the header
574     QStringList directoryList;
575     *str >> header >> directoryList;
576     allResourceDirs.clear();
577     for (int i = 0; i < directoryList.count(); ++i) {
578         qint64 mtime;
579         *str >> mtime;
580         allResourceDirs.insert(directoryList.at(i), mtime);
581     }
582 
583     QStringList fileList;
584     *str >> fileList;
585     extraFiles.clear();
586     for (const auto &fileName : std::as_const(fileList)) {
587         qint64 mtime;
588         *str >> mtime;
589         extraFiles.insert(fileName, mtime);
590     }
591 
592     str->device()->seek(oldPos);
593 
594     timeStamp = header.timeStamp;
595 
596     // for the useless public accessors. KF6: remove these two lines, the accessors and the vars.
597     language = header.language;
598     updateSig = header.updateSignature;
599 
600     return header;
601 }
602 
603 class TimestampChecker
604 {
605 public:
TimestampChecker()606     TimestampChecker()
607         : m_now(QDateTime::currentDateTime())
608     {
609     }
610 
611     // Check times of last modification of all directories on which ksycoca depends,
612     // If none of them is newer than the mtime we stored for that directory at the
613     // last rebuild, this means that there's no need to rebuild ksycoca.
checkDirectoriesTimestamps(const QMap<QString,qint64> & dirs) const614     bool checkDirectoriesTimestamps(const QMap<QString, qint64> &dirs) const
615     {
616         Q_ASSERT(!dirs.isEmpty());
617         // qCDebug(SYCOCA) << "checking file timestamps";
618         for (auto it = dirs.begin(); it != dirs.end(); ++it) {
619             const QString dir = it.key();
620             const qint64 lastStamp = it.value();
621 
622             auto visitor = [&](const QFileInfo &fi) {
623                 const QDateTime mtime = fi.lastModified();
624                 if (mtime.toMSecsSinceEpoch() > lastStamp) {
625                     if (mtime > m_now) {
626                         qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
627                     }
628                     qCDebug(SYCOCA) << "dir timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
629                     // no need to continue search
630                     return false;
631                 }
632 
633                 return true;
634             };
635 
636             if (!KSycocaUtilsPrivate::visitResourceDirectory(dir, visitor)) {
637                 return false;
638             }
639         }
640         return true;
641     }
642 
checkFilesTimestamps(const QMap<QString,qint64> & files) const643     bool checkFilesTimestamps(const QMap<QString, qint64> &files) const
644     {
645         for (auto it = files.begin(); it != files.end(); ++it) {
646             const QString fileName = it.key();
647             const qint64 lastStamp = it.value();
648 
649             QFileInfo fi(fileName);
650             if (!fi.exists()) {
651                 return false;
652             }
653             const QDateTime mtime = fi.lastModified();
654             if (mtime.toMSecsSinceEpoch() > lastStamp) {
655                 if (mtime > m_now) {
656                     qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
657                 }
658                 qCDebug(SYCOCA) << "file timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
659                 return false;
660             }
661         }
662         return true;
663     }
664 
665 private:
666     QDateTime m_now;
667 };
668 
checkDirectories()669 void KSycocaPrivate::checkDirectories()
670 {
671     if (needsRebuild()) {
672         buildSycoca();
673     }
674 }
675 
needsRebuild()676 bool KSycocaPrivate::needsRebuild()
677 {
678     if (!timeStamp && databaseStatus == DatabaseOK) {
679         (void)readSycocaHeader();
680     }
681     // these days timeStamp is really a "bool headerFound", the value itself doesn't matter...
682     // KF6: replace it with bool.
683     const auto timestampChecker = TimestampChecker();
684     bool ret = timeStamp != 0
685         && (!timestampChecker.checkDirectoriesTimestamps(allResourceDirs) //
686             || !timestampChecker.checkFilesTimestamps(extraFiles));
687     if (ret) {
688         return true;
689     }
690     auto files = KBuildSycoca::factoryExtraFiles();
691     // ensure files are ordered so next comparison works
692     files.sort();
693     // to cover cases when extra files were added
694     return extraFiles.keys() != files;
695 }
696 
buildSycoca()697 bool KSycocaPrivate::buildSycoca()
698 {
699     KBuildSycoca builder;
700     if (!builder.recreate()) {
701         return false; // error
702     }
703 
704     closeDatabase(); // close the dummy one
705 
706     // Ok, the new database should be here now, open it.
707     if (!openDatabase()) {
708         qCDebug(SYCOCA) << "Still no database...";
709         return false;
710     }
711     return true;
712 }
713 
714 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
timeStamp()715 quint32 KSycoca::timeStamp()
716 {
717     if (!d->timeStamp) {
718         (void)d->readSycocaHeader();
719     }
720     return d->timeStamp / 1000; // from ms to s
721 }
722 #endif
723 
724 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
updateSignature()725 quint32 KSycoca::updateSignature()
726 {
727     if (!d->timeStamp) {
728         (void)d->readSycocaHeader();
729     }
730     return d->updateSig;
731 }
732 #endif
733 
absoluteFilePath(DatabaseType type)734 QString KSycoca::absoluteFilePath(DatabaseType type)
735 {
736     Q_UNUSED(type); // GlobalDatabase concept removed in 5.61
737     const QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
738     QString suffix = QLatin1Char('_') + QLocale().bcp47Name();
739 
740     const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
741     if (ksycoca_env.isEmpty()) {
742         const QByteArray pathHash = QCryptographicHash::hash(paths.join(QLatin1Char(':')).toUtf8(), QCryptographicHash::Sha1);
743         suffix += QLatin1Char('_') + QString::fromLatin1(pathHash.toBase64());
744         suffix.replace(QLatin1Char('/'), QLatin1Char('_'));
745 #ifdef Q_OS_WIN
746         suffix.replace(QLatin1Char(':'), QLatin1Char('_'));
747 #endif
748         const QString fileName = QLatin1String("ksycoca5") + suffix;
749         return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + fileName;
750     } else {
751         return QFile::decodeName(ksycoca_env);
752     }
753 }
754 
755 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
language()756 QString KSycoca::language()
757 {
758     if (d->language.isEmpty()) {
759         (void)d->readSycocaHeader();
760     }
761     return d->language;
762 }
763 #endif
764 
allResourceDirs()765 QStringList KSycoca::allResourceDirs()
766 {
767     if (!d->timeStamp) {
768         (void)d->readSycocaHeader();
769     }
770     return d->allResourceDirs.keys();
771 }
772 
flagError()773 void KSycoca::flagError()
774 {
775     qCWarning(SYCOCA) << "ERROR: KSycoca database corruption!";
776     KSycoca *sycoca = self();
777     if (sycoca->d->readError) {
778         return;
779     }
780     sycoca->d->readError = true;
781     if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && !sycoca->isBuilding()) {
782         // Rebuild the damned thing.
783         KBuildSycoca builder;
784         (void)builder.recreate();
785     }
786 }
787 
isBuilding()788 bool KSycoca::isBuilding()
789 {
790     return false;
791 }
792 
disableAutoRebuild()793 void KSycoca::disableAutoRebuild()
794 {
795     ksycocaInstance->sycoca()->d->m_fileWatcher = nullptr;
796 }
797 
stream()798 QDataStream *&KSycoca::stream()
799 {
800     return d->stream();
801 }
802 
connectNotify(const QMetaMethod & signal)803 void KSycoca::connectNotify(const QMetaMethod &signal)
804 {
805     if (signal.name() == "databaseChanged" && !d->m_haveListeners) {
806         d->m_haveListeners = true;
807         if (d->m_databasePath.isEmpty()) {
808             d->m_databasePath = d->findDatabase();
809         } else if (d->m_fileWatcher) {
810             d->m_fileWatcher->addFile(d->m_databasePath);
811         }
812     }
813 }
814 
clearCaches()815 void KSycoca::clearCaches()
816 {
817     if (ksycocaInstance.exists() && ksycocaInstance()->hasSycoca()) {
818         ksycocaInstance()->sycoca()->d->closeDatabase();
819     }
820 }
821 
822 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
823 KSERVICE_EXPORT int ksycoca_ms_between_checks = 1500;
824 
ensureCacheValid()825 void KSycoca::ensureCacheValid()
826 {
827     if (qAppName() == QLatin1String(KBUILDSYCOCA_EXENAME)) {
828         return;
829     }
830 
831     if (d->databaseStatus != KSycocaPrivate::DatabaseOK) {
832         if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
833             return;
834         }
835     }
836 
837     if (d->m_lastCheck.isValid() && d->m_lastCheck.elapsed() < ksycoca_ms_between_checks) {
838         return;
839     }
840     d->m_lastCheck.start();
841 
842     // Check if the file on disk was modified since we last checked it.
843     QFileInfo info(d->m_databasePath);
844     if (info.lastModified() == d->m_dbLastModified) {
845         // Check if the watched directories were modified, then the cache needs a rebuild.
846         d->checkDirectories();
847         return;
848     }
849 
850     // Close the database and forget all about what we knew.
851     // The next call to any public method will recreate
852     // everything that's needed.
853     d->closeDatabase();
854 }
855