summaryrefslogtreecommitdiff
path: root/kdecore/sycoca/ksycoca.cpp
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-25 18:42:22 (GMT)
committerChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-25 18:42:22 (GMT)
commit43d35cfb36c924eede624f4a5c2fcb0dd7d698b6 (patch)
treeb7ac13bcf009d94cabb4c5afeee0825a4ce9da67 /kdecore/sycoca/ksycoca.cpp
parentbf385f44f99d73263b1787daac7423599e69f024 (diff)
downloadlibcalendaring-43d35cfb36c924eede624f4a5c2fcb0dd7d698b6.tar.gz
kdecore import
commit 557c1267cbb5e352e1487c3dd468cd4a4cb63254 Merge: aed99b0 387e3c3 Author: David Faure <faure@kde.org> Date: Thu Jun 14 17:56:59 2012 +0200 Merge remote-tracking branch 'origin/KDE/4.8'
Diffstat (limited to 'kdecore/sycoca/ksycoca.cpp')
-rw-r--r--kdecore/sycoca/ksycoca.cpp604
1 files changed, 604 insertions, 0 deletions
diff --git a/kdecore/sycoca/ksycoca.cpp b/kdecore/sycoca/ksycoca.cpp
new file mode 100644
index 0000000..bba13d8
--- /dev/null
+++ b/kdecore/sycoca/ksycoca.cpp
@@ -0,0 +1,604 @@
+/* This file is part of the KDE libraries
+ * Copyright (C) 1999-2000 Waldo Bastian <bastian@kde.org>
+ * Copyright (C) 2005-2009 David Faure <faure@kde.org>
+ * Copyright (C) 2008 Hamish Rodda <rodda@kde.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License version 2 as published by the Free Software Foundation;
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ **/
+
+#include "ksycoca.h"
+#include "ksycoca_p.h"
+#include "ksycocatype.h"
+#include "ksycocafactory.h"
+#include "ktoolinvocation.h"
+#include "kglobal.h"
+#include "kmemfile.h"
+#include "kde_file.h"
+#include "kconfiggroup.h"
+#include "ksharedconfig.h"
+
+#include "kdebug.h"
+#include "kstandarddirs.h"
+
+#include <QtCore/QDataStream>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QFile>
+#include <QtCore/QBuffer>
+#include <QProcess>
+#include <QtDBus/QtDBus>
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include "ksycocadevices_p.h"
+
+// TODO: remove mmap() from kdewin32 and use QFile::mmap() when needed
+#ifdef Q_WS_WIN
+#undef HAVE_MMAP
+#endif
+/**
+ * Sycoca file version number.
+ * If the existing file is outdated, it will not get read
+ * but instead we'll ask kded to regenerate a new one...
+ */
+#define KSYCOCA_VERSION 205
+
+/**
+ * Sycoca file name, used internally (by kbuildsycoca)
+ */
+#define KSYCOCA_FILENAME "ksycoca4"
+
+#if HAVE_MADVISE
+#include <sys/mman.h> // This #include was checked when looking for posix_madvise
+#endif
+
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void *) -1)
+#endif
+
+static bool s_autoRebuild = true;
+
+// The following limitations are in place:
+// Maximum length of a single string: 8192 bytes
+// Maximum length of a string list: 1024 strings
+// Maximum number of entries: 8192
+//
+// The purpose of these limitations is to limit the impact
+// of database corruption.
+
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
+
+KSycocaPrivate::KSycocaPrivate()
+ : databaseStatus( DatabaseNotOpen ),
+ readError( false ),
+ timeStamp( 0 ),
+ m_databasePath(),
+ updateSig( 0 ),
+ sycoca_size(0),
+ sycoca_mmap(0),
+ m_mmapFile(0),
+ m_device(0)
+{
+#ifdef Q_OS_WIN
+ /*
+ on windows we use KMemFile (QSharedMemory) to avoid problems
+ with mmap (can't delete a mmap'd file)
+ */
+ m_sycocaStrategy = StrategyMemFile;
+#else
+ m_sycocaStrategy = StrategyMmap;
+#endif
+ KConfigGroup config(KGlobal::config(), "KSycoca");
+ setStrategyFromString(config.readEntry("strategy"));
+}
+
+void KSycocaPrivate::setStrategyFromString(const QString& strategy) {
+ if (strategy == QLatin1String("mmap"))
+ m_sycocaStrategy = StrategyMmap;
+ else if (strategy == QLatin1String("file"))
+ m_sycocaStrategy = StrategyFile;
+ else if (strategy == QLatin1String("sharedmem"))
+ m_sycocaStrategy = StrategyMemFile;
+ else if (!strategy.isEmpty())
+ kWarning(7011) << "Unknown sycoca strategy:" << strategy;
+}
+
+bool KSycocaPrivate::tryMmap()
+{
+#ifdef HAVE_MMAP
+ Q_ASSERT(!m_databasePath.isEmpty());
+ m_mmapFile = new QFile(m_databasePath);
+ const bool canRead = m_mmapFile->open(QIODevice::ReadOnly);
+ Q_ASSERT(canRead);
+ Q_UNUSED(canRead); // no compiler warning in release builds.
+ fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
+ sycoca_size = m_mmapFile->size();
+ sycoca_mmap = (const char *) mmap(0, sycoca_size,
+ PROT_READ, MAP_SHARED,
+ m_mmapFile->handle(), 0);
+ /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
+ null pointer too. */
+ if (sycoca_mmap == (const char*) MAP_FAILED || sycoca_mmap == 0) {
+ kDebug(7011) << "mmap failed. (length = " << sycoca_size << ")";
+ sycoca_mmap = 0;
+ return false;
+ } else {
+#ifdef HAVE_MADVISE
+ (void) posix_madvise((void*)sycoca_mmap, sycoca_size, POSIX_MADV_WILLNEED);
+#endif // HAVE_MADVISE
+ return true;
+ }
+#endif // HAVE_MMAP
+ return false;
+}
+
+int KSycoca::version()
+{
+ return KSYCOCA_VERSION;
+}
+
+class KSycocaSingleton
+{
+public:
+ KSycocaSingleton() { }
+ ~KSycocaSingleton() { }
+
+ bool hasSycoca() const {
+ return m_threadSycocas.hasLocalData();
+ }
+ KSycoca* sycoca() {
+ if (!m_threadSycocas.hasLocalData())
+ m_threadSycocas.setLocalData(new KSycoca);
+ return m_threadSycocas.localData();
+ }
+ void setSycoca(KSycoca* s) {
+ m_threadSycocas.setLocalData(s);
+ }
+
+private:
+ QThreadStorage<KSycoca*> m_threadSycocas;
+};
+
+K_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
+
+// Read-only constructor
+KSycoca::KSycoca()
+ : d(new KSycocaPrivate)
+{
+ QDBusConnection::sessionBus().connect(QString(), QString(),
+ QString::fromLatin1("org.kde.KSycoca"),
+ QString::fromLatin1("notifyDatabaseChanged"),
+ this, SLOT(notifyDatabaseChanged(QStringList)));
+}
+
+bool KSycocaPrivate::openDatabase(bool openDummyIfNotFound)
+{
+ Q_ASSERT(databaseStatus == DatabaseNotOpen);
+
+ delete m_device; m_device = 0;
+ QString path = KSycoca::absoluteFilePath();
+
+ bool canRead = KDE::access(path, R_OK) == 0;
+ kDebug(7011) << "Trying to open ksycoca from" << path;
+ if (!canRead) {
+ path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase);
+ if (!path.isEmpty()) {
+ kDebug(7011) << "Trying to open global ksycoca from " << path;
+ canRead = KDE::access(path, R_OK) == 0;
+ }
+ }
+
+ bool result = true;
+ if (canRead) {
+ m_databasePath = path;
+ checkVersion();
+ } else { // No database file
+ kDebug(7011) << "Could not open ksycoca";
+ m_databasePath.clear();
+ databaseStatus = NoDatabase;
+ if (openDummyIfNotFound) {
+ // We open a dummy database instead.
+ //kDebug(7011) << "No database, opening a dummy one.";
+
+ m_sycocaStrategy = StrategyDummyBuffer;
+ QDataStream* str = stream();
+ *str << qint32(KSYCOCA_VERSION);
+ *str << qint32(0);
+ } else {
+ result = false;
+ }
+ }
+ return result;
+}
+
+KSycocaAbstractDevice* KSycocaPrivate::device()
+{
+ if (m_device)
+ return m_device;
+
+ Q_ASSERT(!m_databasePath.isEmpty());
+
+ KSycocaAbstractDevice* device = m_device;
+ if (m_sycocaStrategy == StrategyDummyBuffer) {
+ device = new KSycocaBufferDevice;
+ device->device()->open(QIODevice::ReadOnly); // can't fail
+ } else {
+#ifdef HAVE_MMAP
+ if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
+ device = new KSycocaMmapDevice(sycoca_mmap,
+ sycoca_size);
+ if (!device->device()->open(QIODevice::ReadOnly)) {
+ delete device; device = 0;
+ }
+ }
+#endif
+#ifndef QT_NO_SHAREDMEMORY
+ if (!device && m_sycocaStrategy == StrategyMemFile) {
+ device = new KSycocaMemFileDevice(m_databasePath);
+ if (!device->device()->open(QIODevice::ReadOnly)) {
+ delete device; device = 0;
+ }
+ }
+#endif
+ if (!device) {
+ device = new KSycocaFileDevice(m_databasePath);
+ if (!device->device()->open(QIODevice::ReadOnly)) {
+ kError(7011) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
+ //delete device; device = 0; // this would crash in the return statement...
+ }
+ }
+ }
+ if (device) {
+ m_device = device;
+ }
+ return m_device;
+}
+
+QDataStream*& KSycocaPrivate::stream()
+{
+ if (!m_device) {
+ if (databaseStatus == DatabaseNotOpen) {
+ checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy);
+ }
+
+ device(); // create m_device
+ }
+
+ return m_device->stream();
+}
+
+// Read-write constructor - only for KBuildSycoca
+KSycoca::KSycoca( bool /* dummy */ )
+ : d(new KSycocaPrivate)
+{
+ // This instance was not created by the singleton, but by a direct call to new!
+ ksycocaInstance->setSycoca(this);
+}
+
+KSycoca * KSycoca::self()
+{
+ KSycoca* s = ksycocaInstance->sycoca();
+ Q_ASSERT(s);
+ return s;
+}
+
+KSycoca::~KSycoca()
+{
+ d->closeDatabase();
+ delete d;
+ //if (ksycocaInstance.exists()
+ // && ksycocaInstance->self == this)
+ // ksycocaInstance->self = 0;
+}
+
+bool KSycoca::isAvailable()
+{
+ return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing/* don't open dummy db if not found */);
+}
+
+void KSycocaPrivate::closeDatabase()
+{
+ delete m_device;
+ m_device = 0;
+
+ // It is very important to delete all factories here
+ // since they cache information about the database file
+ // But other threads might be using them, so this class is
+ // refcounted, and deleted when the last thread is done with them
+ qDeleteAll(m_factories);
+ m_factories.clear();
+#ifdef HAVE_MMAP
+ if (sycoca_mmap) {
+ //QBuffer *buf = static_cast<QBuffer*>(device);
+ //buf->buffer().clear();
+ // Solaris has munmap(char*, size_t) and everything else should
+ // be happy with a char* for munmap(void*, size_t)
+ munmap(const_cast<char*>(sycoca_mmap), sycoca_size);
+ sycoca_mmap = 0;
+ }
+ delete m_mmapFile; m_mmapFile = 0;
+#endif
+
+ databaseStatus = DatabaseNotOpen;
+ timeStamp = 0;
+}
+
+void KSycoca::addFactory( KSycocaFactory *factory )
+{
+ d->addFactory(factory);
+}
+
+#ifndef KDE_NO_DEPRECATED
+bool KSycoca::isChanged(const char *type)
+{
+ return self()->d->changeList.contains(QString::fromLatin1(type));
+}
+#endif
+
+void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
+{
+ d->changeList = changeList;
+ //kDebug(7011) << QThread::currentThread() << "got a notifyDatabaseChanged signal" << changeList;
+ // kbuildsycoca tells us the database file changed
+ // Close the database and forget all about what we knew
+ // The next call to any public method will recreate
+ // everything that's needed.
+ d->closeDatabase();
+
+ // Now notify applications
+#ifndef KDE_NO_DEPRECATED
+ emit databaseChanged();
+#endif
+ emit databaseChanged(changeList);
+}
+
+QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
+{
+ QDataStream* str = stream();
+ Q_ASSERT(str);
+ //kDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
+ str->device()->seek(offset);
+ qint32 aType;
+ *str >> aType;
+ type = KSycocaType(aType);
+ //kDebug(7011) << QString("KSycoca::found type %1").arg(aType);
+ return str;
+}
+
+KSycocaFactoryList* KSycoca::factories()
+{
+ return d->factories();
+}
+
+// Warning, checkVersion rewinds to the beginning of stream().
+bool KSycocaPrivate::checkVersion()
+{
+ QDataStream *m_str = device()->stream();
+ Q_ASSERT(m_str);
+ m_str->device()->seek(0);
+ qint32 aVersion;
+ *m_str >> aVersion;
+ if ( aVersion < KSYCOCA_VERSION ) {
+ kWarning(7011) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
+ databaseStatus = BadVersion;
+ return false;
+ } else {
+ databaseStatus = DatabaseOK;
+ return true;
+ }
+}
+
+// If it returns true, we have a valid database and the stream has rewinded to the beginning
+// and past the version number.
+bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
+{
+ if (databaseStatus == DatabaseOK) {
+ if (checkVersion()) // we know the version is ok, but we must rewind the stream anyway
+ return true;
+ }
+
+ closeDatabase(); // close the dummy one
+
+ // We can only use the installed ksycoca file if kdeinit+klauncher+kded are running,
+ // since kded is what keeps the file uptodate.
+ const bool kdeinitRunning = QDBusConnection::sessionBus().interface()->isServiceRegistered(QString::fromLatin1("org.kde.klauncher"));
+
+ // Check if new database already available
+ if (kdeinitRunning && openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
+ if (checkVersion()) {
+ // Database exists, and version is ok.
+ return true;
+ }
+ }
+
+ if (ifNotFound & IfNotFoundRecreate) {
+ // Well, if kdeinit is not running we need to launch it,
+ // but otherwise we simply need to run kbuildsycoca to recreate the sycoca file.
+ if (!kdeinitRunning) {
+ kDebug(7011) << "We have no database.... launching kdeinit";
+ KToolInvocation::klauncher(); // this calls startKdeinit, and blocks until it returns
+ // and since kdeinit4 only returns after kbuildsycoca4 is done, we can proceed.
+ } else {
+ kDebug(7011) << QThread::currentThread() << "We have no database.... launching" << KBUILDSYCOCA_EXENAME;
+ if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0)
+ qWarning("ERROR: Running KSycoca failed.");
+ }
+
+ closeDatabase(); // close the dummy one
+
+ // Ok, the new database should be here now, open it.
+ if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
+ kDebug(7011) << "Still no database...";
+ return false; // Still no database - uh oh
+ }
+ if (!checkVersion()) {
+ kDebug(7011) << "Still outdated...";
+ return false; // Still outdated - uh oh
+ }
+ return true;
+ }
+
+ return false;
+}
+
+QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
+{
+ // Ensure we have a valid database (right version, and rewinded to beginning)
+ if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
+ return 0;
+ }
+
+ QDataStream* str = stream();
+ Q_ASSERT(str);
+
+ qint32 aId;
+ qint32 aOffset;
+ while(true) {
+ *str >> aId;
+ if (aId == 0) {
+ kError(7011) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
+ break;
+ }
+ *str >> aOffset;
+ if (aId == id) {
+ //kDebug(7011) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
+ str->device()->seek(aOffset);
+ return str;
+ }
+ }
+ return 0;
+}
+
+QString KSycoca::kfsstnd_prefixes()
+{
+ // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
+ if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing))
+ return QString();
+ QDataStream* str = stream();
+ Q_ASSERT(str);
+ qint32 aId;
+ qint32 aOffset;
+ // skip factories offsets
+ while(true)
+ {
+ *str >> aId;
+ if ( aId )
+ *str >> aOffset;
+ else
+ break; // just read 0
+ }
+ // We now point to the header
+ QString prefixes;
+ KSycocaEntry::read(*str, prefixes);
+ *str >> d->timeStamp;
+ KSycocaEntry::read(*str, d->language);
+ *str >> d->updateSig;
+ KSycocaEntry::read(*str, d->allResourceDirs);
+ return prefixes;
+}
+
+quint32 KSycoca::timeStamp()
+{
+ if (!d->timeStamp)
+ (void) kfsstnd_prefixes();
+ return d->timeStamp;
+}
+
+quint32 KSycoca::updateSignature()
+{
+ if (!d->timeStamp)
+ (void) kfsstnd_prefixes();
+ return d->updateSig;
+}
+
+QString KSycoca::absoluteFilePath(DatabaseType type)
+{
+ if (type == GlobalDatabase) {
+ QString path = KGlobal::dirs()->findResource("services", QString::fromLatin1(KSYCOCA_FILENAME));
+ if (path.isEmpty())
+ return KGlobal::dirs()->saveLocation("services") + QString::fromLatin1(KSYCOCA_FILENAME);
+ return path;
+ }
+
+ const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
+ if (ksycoca_env.isEmpty())
+ return KGlobal::dirs()->saveLocation("cache") + QString::fromLatin1(KSYCOCA_FILENAME);
+ else
+ return QFile::decodeName(ksycoca_env);
+}
+
+QString KSycoca::language()
+{
+ if (d->language.isEmpty())
+ (void) kfsstnd_prefixes();
+ return d->language;
+}
+
+QStringList KSycoca::allResourceDirs()
+{
+ if (!d->timeStamp)
+ (void) kfsstnd_prefixes();
+ return d->allResourceDirs;
+}
+
+void KSycoca::flagError()
+{
+ kWarning(7011) << "ERROR: KSycoca database corruption!";
+ KSycocaPrivate* d = ksycocaInstance->sycoca()->d;
+ if (d->readError)
+ return;
+ d->readError = true;
+ if (s_autoRebuild) {
+ // Rebuild the damned thing.
+ if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0)
+ qWarning("ERROR: Running %s failed", KBUILDSYCOCA_EXENAME);
+ // Old comment, maybe not true anymore:
+ // Do not wait until the DBUS signal from kbuildsycoca here.
+ // It deletes m_str which is a problem when flagError is called during the KSycocaFactory ctor...
+ }
+}
+
+#ifndef KDE_NO_DEPRECATED
+bool KSycoca::readError() // KDE5: remove
+{
+ return false;
+}
+#endif
+
+bool KSycoca::isBuilding()
+{
+ return false;
+}
+
+void KSycoca::disableAutoRebuild()
+{
+ s_autoRebuild = false;
+}
+
+QDataStream*& KSycoca::stream()
+{
+ return d->stream();
+}
+
+void KSycoca::clearCaches()
+{
+ if (ksycocaInstance.exists() && ksycocaInstance->hasSycoca())
+ ksycocaInstance->sycoca()->d->closeDatabase();
+}
+
+#include "ksycoca.moc"