/* This file is part of the KDE libraries * Copyright (C) 1999-2000 Waldo Bastian * Copyright (C) 2005-2009 David Faure * Copyright (C) 2008 Hamish Rodda * * 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 "kde_file.h" #include "kconfiggroup.h" #include "ksharedconfig.h" #include "kdebug.h" #include "kstandarddirs.h" #include "kmemfile.h" #include #include #include #include #include #include #include #include #include #include #include "ksycocadevices_p.h" /** * 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 242 /** * Sycoca file name, used internally (by kbuildsycoca) */ #define KSYCOCA_FILENAME "ksycoca4" 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 ), m_device(0) { m_sycocaStrategy = StrategyMemFile; KConfigGroup config(KGlobal::config(), "KSycoca"); setStrategyFromString(config.readEntry("strategy")); } void KSycocaPrivate::setStrategyFromString(const QString& strategy) { 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; } int KSycoca::version() { return KSYCOCA_VERSION; } class KSycocaSingleton { public: KSycocaSingleton() { } ~KSycocaSingleton() { if (m_threadSycocas) { delete m_threadSycocas; m_threadSycocas = 0; } } bool hasSycoca() const { return (m_threadSycocas != 0); } KSycoca* sycoca() { if (!m_threadSycocas) m_threadSycocas = new KSycoca(); return m_threadSycocas; } void setSycoca(KSycoca* s) { m_threadSycocas = s; } private: static thread_local KSycoca* m_threadSycocas; }; thread_local KSycoca* KSycocaSingleton::m_threadSycocas = 0; 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 { #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(); databaseStatus = DatabaseNotOpen; timeStamp = 0; } void KSycoca::addFactory( KSycocaFactory *factory ) { d->addFactory(factory); } 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 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... } } 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 "moc_ksycoca.cpp"