• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KDECore

ksycoca.cpp

Go to the documentation of this file.
00001 /*  This file is part of the KDE libraries
00002  *  Copyright (C) 1999-2000 Waldo Bastian <bastian@kde.org>
00003  *  Copyright (C) 2005-2009 David Faure <faure@kde.org>
00004  *  Copyright (C) 2008 Hamish Rodda <rodda@kde.org>
00005  *
00006  *  This library is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU Library General Public
00008  *  License version 2 as published by the Free Software Foundation;
00009  *
00010  *  This library is distributed in the hope that it will be useful,
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  *  Library General Public License for more details.
00014  *
00015  *  You should have received a copy of the GNU Library General Public License
00016  *  along with this library; see the file COPYING.LIB.  If not, write to
00017  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  *  Boston, MA 02110-1301, USA.
00019  **/
00020 
00021 #include "ksycoca.h"
00022 #include "ksycoca_p.h"
00023 #include "ksycocatype.h"
00024 #include "ksycocafactory.h"
00025 #include "ktoolinvocation.h"
00026 #include "kglobal.h"
00027 #include "kmemfile.h"
00028 #include "kde_file.h"
00029 #include "kconfiggroup.h"
00030 #include "ksharedconfig.h"
00031 
00032 #include "kdebug.h"
00033 #include "kstandarddirs.h"
00034 
00035 #include <QtCore/QDataStream>
00036 #include <QtCore/QCoreApplication>
00037 #include <QtCore/QFile>
00038 #include <QtCore/QBuffer>
00039 #include <QProcess>
00040 #include <QtDBus/QtDBus>
00041 
00042 #include <config.h>
00043 
00044 #include <stdlib.h>
00045 #include <fcntl.h>
00046 
00047 #include "ksycocadevices_p.h"
00048 
00049 // TODO: remove mmap() from kdewin32 and use QFile::mmap() when needed
00050 #ifdef Q_WS_WIN
00051 #undef HAVE_MMAP
00052 #endif
00053 
00058 #define KSYCOCA_VERSION 202
00059 
00063 #define KSYCOCA_FILENAME "ksycoca4"
00064 
00065 #if HAVE_MADVISE
00066 #include <sys/mman.h> // This #include was checked when looking for posix_madvise
00067 #endif
00068 
00069 #ifndef MAP_FAILED
00070 #define MAP_FAILED ((void *) -1)
00071 #endif
00072 
00073 static bool s_autoRebuild = true;
00074 
00075 // The following limitations are in place:
00076 // Maximum length of a single string: 8192 bytes
00077 // Maximum length of a string list: 1024 strings
00078 // Maximum number of entries: 8192
00079 //
00080 // The purpose of these limitations is to limit the impact
00081 // of database corruption.
00082 
00083 
00084 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
00085 
00086 KSycocaPrivate::KSycocaPrivate()
00087     : databaseStatus( DatabaseNotOpen ),
00088       readError( false ),
00089       timeStamp( 0 ),
00090       m_databasePath(),
00091       updateSig( 0 ),
00092       sycoca_size(0),
00093       sycoca_mmap(0),
00094       m_mmapFile(0),
00095       m_device(0)
00096 {
00097 #ifdef Q_OS_WIN
00098     /*
00099       on windows we use KMemFile (QSharedMemory) to avoid problems
00100       with mmap (can't delete a mmap'd file)
00101     */
00102     m_sycocaStrategy = StrategyMemFile;
00103 #else
00104     m_sycocaStrategy = StrategyMmap;
00105 #endif
00106     KConfigGroup config(KGlobal::config(), "KSycoca");
00107     setStrategyFromString(config.readEntry("strategy"));
00108 }
00109 
00110 void KSycocaPrivate::setStrategyFromString(const QString& strategy) {
00111     if (strategy == QLatin1String("mmap"))
00112         m_sycocaStrategy = StrategyMmap;
00113     else if (strategy == QLatin1String("file"))
00114         m_sycocaStrategy = StrategyFile;
00115     else if (strategy == QLatin1String("sharedmem"))
00116         m_sycocaStrategy = StrategyMemFile;
00117     else if (!strategy.isEmpty())
00118         kWarning(7011) << "Unknown sycoca strategy:" << strategy;
00119 }
00120 
00121 bool KSycocaPrivate::tryMmap()
00122 {
00123 #ifdef HAVE_MMAP
00124     Q_ASSERT(!m_databasePath.isEmpty());
00125     m_mmapFile = new QFile(m_databasePath);
00126     const bool canRead = m_mmapFile->open(QIODevice::ReadOnly);
00127     Q_ASSERT(canRead);
00128     Q_UNUSED(canRead); // no compiler warning in release builds.
00129     fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
00130     sycoca_size = m_mmapFile->size();
00131     sycoca_mmap = (const char *) mmap(0, sycoca_size,
00132                                       PROT_READ, MAP_SHARED,
00133                                       m_mmapFile->handle(), 0);
00134     /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
00135        null pointer too.  */
00136     if (sycoca_mmap == (const char*) MAP_FAILED || sycoca_mmap == 0) {
00137         kDebug(7011) << "mmap failed. (length = " << sycoca_size << ")";
00138         sycoca_mmap = 0;
00139         return false;
00140     } else {
00141 #ifdef HAVE_MADVISE
00142         (void) posix_madvise((void*)sycoca_mmap, sycoca_size, POSIX_MADV_WILLNEED);
00143 #endif // HAVE_MADVISE
00144         return true;
00145     }
00146 #endif // HAVE_MMAP
00147     return false;
00148 }
00149 
00150 int KSycoca::version()
00151 {
00152    return KSYCOCA_VERSION;
00153 }
00154 
00155 class KSycocaSingleton
00156 {
00157 public:
00158     KSycocaSingleton() { }
00159     ~KSycocaSingleton() { }
00160 
00161     bool hasSycoca() const {
00162         return m_threadSycocas.hasLocalData();
00163     }
00164     KSycoca* sycoca() {
00165         if (!m_threadSycocas.hasLocalData())
00166             m_threadSycocas.setLocalData(new KSycoca);
00167         return m_threadSycocas.localData();
00168     }
00169     void setSycoca(KSycoca* s) {
00170         m_threadSycocas.setLocalData(s);
00171     }
00172 
00173 private:
00174     QThreadStorage<KSycoca*> m_threadSycocas;
00175 };
00176 
00177 K_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
00178 
00179 // Read-only constructor
00180 KSycoca::KSycoca()
00181   : d(new KSycocaPrivate)
00182 {
00183     QDBusConnection::sessionBus().connect(QString(), QString(),
00184                                           QString::fromLatin1("org.kde.KSycoca"),
00185                                           QString::fromLatin1("notifyDatabaseChanged"),
00186                                           this, SLOT(notifyDatabaseChanged(QStringList)));
00187 }
00188 
00189 bool KSycocaPrivate::openDatabase(bool openDummyIfNotFound)
00190 {
00191     Q_ASSERT(databaseStatus == DatabaseNotOpen);
00192 
00193     delete m_device; m_device = 0;
00194     QString path = KSycoca::absoluteFilePath();
00195 
00196     bool canRead = KDE::access(path, R_OK) == 0;
00197     kDebug(7011) << "Trying to open ksycoca from" << path;
00198     if (!canRead) {
00199         path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase);
00200         if (!path.isEmpty()) {
00201             kDebug(7011) << "Trying to open global ksycoca from " << path;
00202             canRead = KDE::access(path, R_OK) == 0;
00203         }
00204     }
00205 
00206     bool result = true;
00207     if (canRead) {
00208         m_databasePath = path;
00209         checkVersion();
00210     } else { // No database file
00211         kDebug(7011) << "Could not open ksycoca";
00212         m_databasePath.clear();
00213         databaseStatus = NoDatabase;
00214         if (openDummyIfNotFound) {
00215             // We open a dummy database instead.
00216             //kDebug(7011) << "No database, opening a dummy one.";
00217 
00218             m_sycocaStrategy = StrategyDummyBuffer;
00219             QDataStream* str = stream();
00220             *str << qint32(KSYCOCA_VERSION);
00221             *str << qint32(0);
00222         } else {
00223             result = false;
00224         }
00225     }
00226     return result;
00227 }
00228 
00229 KSycocaAbstractDevice* KSycocaPrivate::device()
00230 {
00231     if (m_device)
00232         return m_device;
00233 
00234     Q_ASSERT(!m_databasePath.isEmpty());
00235 
00236     KSycocaAbstractDevice* device = m_device;
00237     if (m_sycocaStrategy == StrategyDummyBuffer) {
00238         device = new KSycocaBufferDevice;
00239         device->device()->open(QIODevice::ReadOnly); // can't fail
00240     } else {
00241 #ifdef HAVE_MMAP
00242         if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
00243             device = new KSycocaMmapDevice(sycoca_mmap,
00244                                            sycoca_size);
00245             if (!device->device()->open(QIODevice::ReadOnly)) {
00246                 delete device; device = 0;
00247             }
00248         }
00249 #endif
00250 #ifndef QT_NO_SHAREDMEMORY
00251         if (!device && m_sycocaStrategy == StrategyMemFile) {
00252             device = new KSycocaMemFileDevice(m_databasePath);
00253             if (!device->device()->open(QIODevice::ReadOnly)) {
00254                 delete device; device = 0;
00255             }
00256         }
00257 #endif
00258         if (!device) {
00259             device = new KSycocaFileDevice(m_databasePath);
00260             if (!device->device()->open(QIODevice::ReadOnly)) {
00261                 kError(7011) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
00262                 //delete device; device = 0; // this would crash in the return statement...
00263             }
00264         }
00265     }
00266     if (device) {
00267         m_device = device;
00268     }
00269     return m_device;
00270 }
00271 
00272 QDataStream*& KSycocaPrivate::stream()
00273 {
00274     if (!m_device) {
00275         if (databaseStatus == DatabaseNotOpen) {
00276             checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy);
00277         }
00278 
00279         device(); // create m_device
00280     }
00281 
00282     return m_device->stream();
00283 }
00284 
00285 // Read-write constructor - only for KBuildSycoca
00286 KSycoca::KSycoca( bool /* dummy */ )
00287   : d(new KSycocaPrivate)
00288 {
00289     // This instance was not created by the singleton, but by a direct call to new!
00290     ksycocaInstance->setSycoca(this);
00291 }
00292 
00293 KSycoca * KSycoca::self()
00294 {
00295     KSycoca* s = ksycocaInstance->sycoca();
00296     Q_ASSERT(s);
00297     return s;
00298 }
00299 
00300 KSycoca::~KSycoca()
00301 {
00302     d->closeDatabase();
00303     delete d;
00304     //if (ksycocaInstance.exists()
00305     //    && ksycocaInstance->self == this)
00306     //    ksycocaInstance->self = 0;
00307 }
00308 
00309 bool KSycoca::isAvailable()
00310 {
00311     return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing/* don't open dummy db if not found */);
00312 }
00313 
00314 void KSycocaPrivate::closeDatabase()
00315 {
00316     delete m_device;
00317     m_device = 0;
00318 
00319     // It is very important to delete all factories here
00320     // since they cache information about the database file
00321     // But other threads might be using them, so this class is
00322     // refcounted, and deleted when the last thread is done with them
00323     qDeleteAll(m_factories);
00324     m_factories.clear();
00325 #ifdef HAVE_MMAP
00326     if (sycoca_mmap) {
00327         //QBuffer *buf = static_cast<QBuffer*>(device);
00328         //buf->buffer().clear();
00329         // Solaris has munmap(char*, size_t) and everything else should
00330         // be happy with a char* for munmap(void*, size_t)
00331         munmap(const_cast<char*>(sycoca_mmap), sycoca_size);
00332         sycoca_mmap = 0;
00333     }
00334     delete m_mmapFile; m_mmapFile = 0;
00335 #endif
00336 
00337     databaseStatus = DatabaseNotOpen;
00338     timeStamp = 0;
00339 }
00340 
00341 void KSycoca::addFactory( KSycocaFactory *factory )
00342 {
00343     d->addFactory(factory);
00344 }
00345 
00346 #ifndef KDE_NO_DEPRECATED
00347 bool KSycoca::isChanged(const char *type)
00348 {
00349     return self()->d->changeList.contains(QString::fromLatin1(type));
00350 }
00351 #endif
00352 
00353 void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
00354 {
00355     d->changeList = changeList;
00356     //kDebug(7011) << QThread::currentThread() << "got a notifyDatabaseChanged signal" << changeList;
00357     // kbuildsycoca tells us the database file changed
00358     // Close the database and forget all about what we knew
00359     // The next call to any public method will recreate
00360     // everything that's needed.
00361     d->closeDatabase();
00362 
00363     // Now notify applications
00364 #ifndef KDE_NO_DEPRECATED
00365     emit databaseChanged();
00366 #endif
00367     emit databaseChanged(changeList);
00368 }
00369 
00370 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
00371 {
00372    QDataStream* str = stream();
00373    Q_ASSERT(str);
00374    //kDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
00375    str->device()->seek(offset);
00376    qint32 aType;
00377    *str >> aType;
00378    type = KSycocaType(aType);
00379    //kDebug(7011) << QString("KSycoca::found type %1").arg(aType);
00380    return str;
00381 }
00382 
00383 KSycocaFactoryList* KSycoca::factories()
00384 {
00385     return d->factories();
00386 }
00387 
00388 // Warning, checkVersion rewinds to the beginning of stream().
00389 bool KSycocaPrivate::checkVersion()
00390 {
00391     QDataStream *m_str = device()->stream();
00392     Q_ASSERT(m_str);
00393     m_str->device()->seek(0);
00394     qint32 aVersion;
00395     *m_str >> aVersion;
00396     if ( aVersion < KSYCOCA_VERSION ) {
00397         kWarning(7011) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
00398         databaseStatus = BadVersion;
00399         return false;
00400     } else {
00401         databaseStatus = DatabaseOK;
00402         return true;
00403     }
00404 }
00405 
00406 // If it returns true, we have a valid database and the stream has rewinded to the beginning
00407 // and past the version number.
00408 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
00409 {
00410     if (databaseStatus == DatabaseOK) {
00411         if (checkVersion()) // we know the version is ok, but we must rewind the stream anyway
00412             return true;
00413     }
00414 
00415     closeDatabase(); // close the dummy one
00416 
00417     // We can only use the installed ksycoca file if kdeinit+klauncher+kded are running,
00418     // since kded is what keeps the file uptodate.
00419     const bool kdeinitRunning = QDBusConnection::sessionBus().interface()->isServiceRegistered(QString::fromLatin1("org.kde.klauncher"));
00420 
00421     // Check if new database already available
00422     if (kdeinitRunning && openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
00423         if (checkVersion()) {
00424             // Database exists, and version is ok.
00425             return true;
00426         }
00427     }
00428 
00429     if (ifNotFound & IfNotFoundRecreate) {
00430         // Well, if kdeinit is not running we need to launch it,
00431         // but otherwise we simply need to run kbuildsycoca to recreate the sycoca file.
00432         if (!kdeinitRunning) {
00433             kDebug(7011) << "We have no database.... launching kdeinit";
00434             KToolInvocation::klauncher(); // this calls startKdeinit, and blocks until it returns
00435             // and since kdeinit4 only returns after kbuildsycoca4 is done, we can proceed.
00436         } else {
00437             kDebug(7011) << QThread::currentThread() << "We have no database.... launching" << KBUILDSYCOCA_EXENAME;
00438             if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0)
00439                 qWarning("ERROR: Running KSycoca failed.");
00440         }
00441 
00442         closeDatabase(); // close the dummy one
00443 
00444         // Ok, the new database should be here now, open it.
00445         if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
00446             kDebug(7011) << "Still no database...";
00447             return false; // Still no database - uh oh
00448         }
00449         if (!checkVersion()) {
00450             kDebug(7011) << "Still outdated...";
00451             return false; // Still outdated - uh oh
00452         }
00453         return true;
00454     }
00455 
00456     return false;
00457 }
00458 
00459 QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
00460 {
00461     // Ensure we have a valid database (right version, and rewinded to beginning)
00462     if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
00463         return 0;
00464     }
00465 
00466     QDataStream* str = stream();
00467     Q_ASSERT(str);
00468 
00469     qint32 aId;
00470     qint32 aOffset;
00471     while(true) {
00472         *str >> aId;
00473         if (aId == 0) {
00474             kError(7011) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
00475             break;
00476         }
00477         *str >> aOffset;
00478         if (aId == id) {
00479             //kDebug(7011) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
00480             str->device()->seek(aOffset);
00481             return str;
00482         }
00483     }
00484     return 0;
00485 }
00486 
00487 QString KSycoca::kfsstnd_prefixes()
00488 {
00489     // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
00490    if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing))
00491        return QString();
00492    QDataStream* str = stream();
00493    Q_ASSERT(str);
00494    qint32 aId;
00495    qint32 aOffset;
00496    // skip factories offsets
00497    while(true)
00498    {
00499       *str >> aId;
00500       if ( aId )
00501         *str >> aOffset;
00502       else
00503         break; // just read 0
00504    }
00505    // We now point to the header
00506    QString prefixes;
00507    KSycocaEntry::read(*str, prefixes);
00508    *str >> d->timeStamp;
00509    KSycocaEntry::read(*str, d->language);
00510    *str >> d->updateSig;
00511    KSycocaEntry::read(*str, d->allResourceDirs);
00512    return prefixes;
00513 }
00514 
00515 quint32 KSycoca::timeStamp()
00516 {
00517    if (!d->timeStamp)
00518       (void) kfsstnd_prefixes();
00519    return d->timeStamp;
00520 }
00521 
00522 quint32 KSycoca::updateSignature()
00523 {
00524    if (!d->timeStamp)
00525       (void) kfsstnd_prefixes();
00526    return d->updateSig;
00527 }
00528 
00529 QString KSycoca::absoluteFilePath(DatabaseType type)
00530 {
00531     if (type == GlobalDatabase)
00532         return KGlobal::dirs()->saveLocation("services") + QString::fromLatin1(KSYCOCA_FILENAME);
00533 
00534     const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
00535     if (ksycoca_env.isEmpty())
00536         return KGlobal::dirs()->saveLocation("cache") + QString::fromLatin1(KSYCOCA_FILENAME);
00537     else
00538         return QFile::decodeName(ksycoca_env);
00539 }
00540 
00541 QString KSycoca::language()
00542 {
00543    if (d->language.isEmpty())
00544       (void) kfsstnd_prefixes();
00545    return d->language;
00546 }
00547 
00548 QStringList KSycoca::allResourceDirs()
00549 {
00550    if (!d->timeStamp)
00551       (void) kfsstnd_prefixes();
00552    return d->allResourceDirs;
00553 }
00554 
00555 void KSycoca::flagError()
00556 {
00557     kWarning(7011) << "ERROR: KSycoca database corruption!";
00558     KSycocaPrivate* d = ksycocaInstance->sycoca()->d;
00559     if (d->readError)
00560         return;
00561     d->readError = true;
00562     if (s_autoRebuild) {
00563         // Rebuild the damned thing.
00564         if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0)
00565             qWarning("ERROR: Running %s failed", KBUILDSYCOCA_EXENAME);
00566         // Old comment, maybe not true anymore:
00567         // Do not wait until the DBUS signal from kbuildsycoca here.
00568         // It deletes m_str which is a problem when flagError is called during the KSycocaFactory ctor...
00569     }
00570 }
00571 
00572 #ifndef KDE_NO_DEPRECATED
00573 bool KSycoca::readError() // KDE5: remove
00574 {
00575     return false;
00576 }
00577 #endif
00578 
00579 bool KSycoca::isBuilding()
00580 {
00581     return false;
00582 }
00583 
00584 void KSycoca::disableAutoRebuild()
00585 {
00586    s_autoRebuild = false;
00587 }
00588 
00589 QDataStream*& KSycoca::stream()
00590 {
00591     return d->stream();
00592 }
00593 
00594 void KSycoca::clearCaches()
00595 {
00596     if (ksycocaInstance.exists() && ksycocaInstance->hasSycoca())
00597         ksycocaInstance->sycoca()->d->closeDatabase();
00598 }
00599 
00600 #include "ksycoca.moc"

KDECore

Skip menu "KDECore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal