• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • 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 143
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 == "mmap")
00112         m_sycocaStrategy = StrategyMmap;
00113     else if (strategy == "file")
00114         m_sycocaStrategy = StrategyFile;
00115     else if (strategy == "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     fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
00129     sycoca_size = m_mmapFile->size();
00130     sycoca_mmap = (const char *) mmap(0, sycoca_size,
00131                                       PROT_READ, MAP_SHARED,
00132                                       m_mmapFile->handle(), 0);
00133     /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
00134        null pointer too.  */
00135     if (sycoca_mmap == (const char*) MAP_FAILED || sycoca_mmap == 0) {
00136         kDebug(7011) << "mmap failed. (length = " << sycoca_size << ")";
00137         sycoca_mmap = 0;
00138         return false;
00139     } else {
00140 #ifdef HAVE_MADVISE
00141         (void) posix_madvise((void*)sycoca_mmap, sycoca_size, POSIX_MADV_WILLNEED);
00142 #endif // HAVE_MADVISE
00143         return true;
00144     }
00145 #endif // HAVE_MMAP
00146     return false;
00147 }
00148 
00149 int KSycoca::version()
00150 {
00151    return KSYCOCA_VERSION;
00152 }
00153 
00154 class KSycocaSingleton
00155 {
00156 public:
00157     KSycocaSingleton() { }
00158     ~KSycocaSingleton() { }
00159 
00160     bool hasSycoca() const {
00161         return m_threadSycocas.hasLocalData();
00162     }
00163     KSycoca* sycoca() {
00164         if (!m_threadSycocas.hasLocalData())
00165             m_threadSycocas.setLocalData(new KSycoca);
00166         return m_threadSycocas.localData();
00167     }
00168     void setSycoca(KSycoca* s) {
00169         m_threadSycocas.setLocalData(s);
00170     }
00171 
00172 private:
00173     QThreadStorage<KSycoca*> m_threadSycocas;
00174 };
00175 
00176 K_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
00177 
00178 // Read-only constructor
00179 KSycoca::KSycoca()
00180   : d(new KSycocaPrivate)
00181 {
00182     QDBusConnection::sessionBus().connect(QString(), QString(),
00183                                           "org.kde.KSycoca", "notifyDatabaseChanged",
00184                                           this, SLOT(notifyDatabaseChanged(QStringList)));
00185 }
00186 
00187 bool KSycocaPrivate::openDatabase(bool openDummyIfNotFound)
00188 {
00189     Q_ASSERT(databaseStatus == DatabaseNotOpen);
00190 
00191     delete m_device; m_device = 0;
00192     QString path = KSycoca::absoluteFilePath();
00193 
00194     bool canRead = KDE::access(path, R_OK) == 0;
00195     kDebug(7011) << "Trying to open ksycoca from " << path;
00196     if (!canRead) {
00197         path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase);
00198         if (!path.isEmpty()) {
00199             kDebug(7011) << "Trying to open global ksycoca from " << path;
00200             canRead = KDE::access(path, R_OK) == 0;
00201         }
00202     }
00203 
00204     bool result = true;
00205     if (canRead) {
00206         m_databasePath = path;
00207         checkVersion();
00208     } else { // No database file
00209         kDebug(7011) << "Could not open ksycoca";
00210         m_databasePath.clear();
00211         databaseStatus = NoDatabase;
00212         if (openDummyIfNotFound) {
00213             // We open a dummy database instead.
00214             //kDebug(7011) << "No database, opening a dummy one.";
00215 
00216             m_sycocaStrategy = StrategyDummyBuffer;
00217             QDataStream* str = stream();
00218             *str << qint32(KSYCOCA_VERSION);
00219             *str << qint32(0);
00220         } else {
00221             result = false;
00222         }
00223     }
00224     return result;
00225 }
00226 
00227 KSycocaAbstractDevice* KSycocaPrivate::device()
00228 {
00229     if (m_device)
00230         return m_device;
00231 
00232     Q_ASSERT(!m_databasePath.isEmpty());
00233 
00234     KSycocaAbstractDevice* device = m_device;
00235     if (m_sycocaStrategy == StrategyDummyBuffer) {
00236         device = new KSycocaBufferDevice;
00237         device->device()->open(QIODevice::ReadOnly); // can't fail
00238     } else {
00239 #ifdef HAVE_MMAP
00240         if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
00241             device = new KSycocaMmapDevice(sycoca_mmap,
00242                                            sycoca_size);
00243             if (!device->device()->open(QIODevice::ReadOnly)) {
00244                 delete device; device = 0;
00245             }
00246         }
00247 #endif
00248         if (!device && m_sycocaStrategy == StrategyMemFile) {
00249             device = new KSycocaMemFileDevice(m_databasePath);
00250             if (!device->device()->open(QIODevice::ReadOnly)) {
00251                 delete device; device = 0;
00252             }
00253         }
00254 
00255         if (!device) {
00256             device = new KSycocaFileDevice(m_databasePath);
00257             if (!device->device()->open(QIODevice::ReadOnly)) {
00258                 kError(7011) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
00259                 //delete device; device = 0; // this would crash in the return statement...
00260             }
00261         }
00262     }
00263     if (device) {
00264         m_device = device;
00265     }
00266     return m_device;
00267 }
00268 
00269 QDataStream*& KSycocaPrivate::stream()
00270 {
00271     if (!m_device) {
00272         if (databaseStatus == DatabaseNotOpen) {
00273             checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy);
00274         }
00275 
00276         device(); // create m_device
00277     }
00278 
00279     return m_device->stream();
00280 }
00281 
00282 // Read-write constructor - only for KBuildSycoca
00283 KSycoca::KSycoca( bool /* dummy */ )
00284   : d(new KSycocaPrivate)
00285 {
00286     // This instance was not created by the singleton, but by a direct call to new!
00287     ksycocaInstance->setSycoca(this);
00288 }
00289 
00290 KSycoca * KSycoca::self()
00291 {
00292     KSycoca* s = ksycocaInstance->sycoca();
00293     Q_ASSERT(s);
00294     return s;
00295 }
00296 
00297 KSycoca::~KSycoca()
00298 {
00299     d->closeDatabase();
00300     delete d;
00301     //if (ksycocaInstance.exists()
00302     //    && ksycocaInstance->self == this)
00303     //    ksycocaInstance->self = 0;
00304 }
00305 
00306 bool KSycoca::isAvailable()
00307 {
00308     return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing/* don't open dummy db if not found */);
00309 }
00310 
00311 void KSycocaPrivate::closeDatabase()
00312 {
00313     delete m_device;
00314     m_device = 0;
00315 
00316     // It is very important to delete all factories here
00317     // since they cache information about the database file
00318     // But other threads might be using them, so this class is
00319     // refcounted, and deleted when the last thread is done with them
00320     qDeleteAll(m_factories);
00321     m_factories.clear();
00322 #ifdef HAVE_MMAP
00323     if (sycoca_mmap) {
00324         //QBuffer *buf = static_cast<QBuffer*>(device);
00325         //buf->buffer().clear();
00326         // Solaris has munmap(char*, size_t) and everything else should
00327         // be happy with a char* for munmap(void*, size_t)
00328         munmap(const_cast<char*>(sycoca_mmap), sycoca_size);
00329         sycoca_mmap = 0;
00330     }
00331     delete m_mmapFile; m_mmapFile = 0;
00332 #endif
00333 
00334     databaseStatus = DatabaseNotOpen;
00335     timeStamp = 0;
00336 }
00337 
00338 void KSycoca::addFactory( KSycocaFactory *factory )
00339 {
00340     d->addFactory(factory);
00341 }
00342 
00343 bool KSycoca::isChanged(const char *type)
00344 {
00345     return self()->d->changeList.contains(type);
00346 }
00347 
00348 void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
00349 {
00350     d->changeList = changeList;
00351     //kDebug(7011) << QThread::currentThread() << "got a notifyDatabaseChanged signal" << changeList;
00352     // kbuildsycoca tells us the database file changed
00353     // Close the database and forget all about what we knew
00354     // The next call to any public method will recreate
00355     // everything that's needed.
00356     d->closeDatabase();
00357 
00358     // Now notify applications
00359     emit databaseChanged();
00360     emit databaseChanged(changeList);
00361 }
00362 
00363 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
00364 {
00365    QDataStream* str = stream();
00366    Q_ASSERT(str);
00367    //kDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
00368    str->device()->seek(offset);
00369    qint32 aType;
00370    *str >> aType;
00371    type = KSycocaType(aType);
00372    //kDebug(7011) << QString("KSycoca::found type %1").arg(aType);
00373    return str;
00374 }
00375 
00376 KSycocaFactoryList* KSycoca::factories()
00377 {
00378     return d->factories();
00379 }
00380 
00381 // Warning, checkVersion rewinds to the beginning of stream().
00382 bool KSycocaPrivate::checkVersion()
00383 {
00384     QDataStream *m_str = device()->stream();
00385     Q_ASSERT(m_str);
00386     m_str->device()->seek(0);
00387     qint32 aVersion;
00388     *m_str >> aVersion;
00389     if ( aVersion < KSYCOCA_VERSION ) {
00390         kWarning(7011) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
00391         databaseStatus = BadVersion;
00392         return false;
00393     } else {
00394         databaseStatus = DatabaseOK;
00395         return true;
00396     }
00397 }
00398 
00399 // If it returns true, we have a valid database and the stream has rewinded to the beginning
00400 // and past the version number.
00401 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
00402 {
00403     if (databaseStatus == DatabaseOK) {
00404         if (checkVersion()) // we know the version is ok, but we must rewind the stream anyway
00405             return true;
00406     }
00407 
00408     closeDatabase(); // close the dummy one
00409 
00410     // We can only use the installed ksycoca file if kdeinit+klauncher+kded are running,
00411     // since kded is what keeps the file uptodate.
00412     const bool kdeinitRunning = QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.klauncher");
00413 
00414     // Check if new database already available
00415     if (kdeinitRunning && openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
00416         if (checkVersion()) {
00417             // Database exists, and version is ok.
00418             return true;
00419         }
00420     }
00421 
00422     if (ifNotFound & IfNotFoundRecreate) {
00423         // Well, if kdeinit is not running we need to launch it,
00424         // but otherwise we simply need to run kbuildsycoca to recreate the sycoca file.
00425         if (!kdeinitRunning) {
00426             kDebug(7011) << "We have no database.... launching kdeinit";
00427             KToolInvocation::klauncher(); // this calls startKdeinit, and blocks until it returns
00428             // and since kdeinit4 only returns after kbuildsycoca4 is done, we can proceed.
00429         } else {
00430             kDebug(7011) << QThread::currentThread() << "We have no database.... launching" << KBUILDSYCOCA_EXENAME;
00431             if (QProcess::execute(KStandardDirs::findExe(KBUILDSYCOCA_EXENAME)) != 0)
00432                 qWarning("ERROR: Running KSycoca failed.");
00433         }
00434 
00435         closeDatabase(); // close the dummy one
00436 
00437         // Ok, the new database should be here now, open it.
00438         if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
00439             kDebug(7011) << "Still no database...";
00440             return false; // Still no database - uh oh
00441         }
00442         if (!checkVersion()) {
00443             kDebug(7011) << "Still outdated...";
00444             return false; // Still outdated - uh oh
00445         }
00446         return true;
00447     }
00448 
00449     return false;
00450 }
00451 
00452 QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
00453 {
00454     // Ensure we have a valid database (right version, and rewinded to beginning)
00455     if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
00456         return 0;
00457     }
00458 
00459     QDataStream* str = stream();
00460     Q_ASSERT(str);
00461 
00462     qint32 aId;
00463     qint32 aOffset;
00464     while(true) {
00465         *str >> aId;
00466         if (aId == 0) {
00467             kError(7011) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
00468             break;
00469         }
00470         *str >> aOffset;
00471         if (aId == id) {
00472             //kDebug(7011) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
00473             str->device()->seek(aOffset);
00474             return str;
00475         }
00476     }
00477     return 0;
00478 }
00479 
00480 QString KSycoca::kfsstnd_prefixes()
00481 {
00482     // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
00483    if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing))
00484        return QString();
00485    QDataStream* str = stream();
00486    Q_ASSERT(str);
00487    qint32 aId;
00488    qint32 aOffset;
00489    // skip factories offsets
00490    while(true)
00491    {
00492       *str >> aId;
00493       if ( aId )
00494         *str >> aOffset;
00495       else
00496         break; // just read 0
00497    }
00498    // We now point to the header
00499    QString prefixes;
00500    KSycocaEntry::read(*str, prefixes);
00501    *str >> d->timeStamp;
00502    KSycocaEntry::read(*str, d->language);
00503    *str >> d->updateSig;
00504    KSycocaEntry::read(*str, d->allResourceDirs);
00505    return prefixes;
00506 }
00507 
00508 quint32 KSycoca::timeStamp()
00509 {
00510    if (!d->timeStamp)
00511       (void) kfsstnd_prefixes();
00512    return d->timeStamp;
00513 }
00514 
00515 quint32 KSycoca::updateSignature()
00516 {
00517    if (!d->timeStamp)
00518       (void) kfsstnd_prefixes();
00519    return d->updateSig;
00520 }
00521 
00522 QString KSycoca::absoluteFilePath(DatabaseType type)
00523 {
00524    if (type == GlobalDatabase)
00525       return KStandardDirs::locate("services", KSYCOCA_FILENAME);
00526 
00527    const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
00528    if (ksycoca_env.isEmpty())
00529       return KGlobal::dirs()->saveLocation("cache") + KSYCOCA_FILENAME;
00530    else
00531       return QFile::decodeName(ksycoca_env);
00532 }
00533 
00534 QString KSycoca::language()
00535 {
00536    if (d->language.isEmpty())
00537       (void) kfsstnd_prefixes();
00538    return d->language;
00539 }
00540 
00541 QStringList KSycoca::allResourceDirs()
00542 {
00543    if (!d->timeStamp)
00544       (void) kfsstnd_prefixes();
00545    return d->allResourceDirs;
00546 }
00547 
00548 void KSycoca::flagError()
00549 {
00550     kWarning(7011) << "ERROR: KSycoca database corruption!";
00551     KSycocaPrivate* d = ksycocaInstance->sycoca()->d;
00552     if (d->readError)
00553         return;
00554     d->readError = true;
00555     if (s_autoRebuild) {
00556         // Rebuild the damned thing.
00557         if (QProcess::execute(KStandardDirs::findExe(KBUILDSYCOCA_EXENAME)) != 0)
00558             qWarning("ERROR: Running %s failed", KBUILDSYCOCA_EXENAME);
00559         // Old comment, maybe not true anymore:
00560         // Do not wait until the DBUS signal from kbuildsycoca here.
00561         // It deletes m_str which is a problem when flagError is called during the KSycocaFactory ctor...
00562     }
00563 }
00564 
00565 bool KSycoca::readError() // KDE5: remove
00566 {
00567     return false;
00568 }
00569 
00570 bool KSycoca::isBuilding()
00571 {
00572     return false;
00573 }
00574 
00575 void KSycoca::disableAutoRebuild()
00576 {
00577    s_autoRebuild = false;
00578 }
00579 
00580 QDataStream*& KSycoca::stream()
00581 {
00582     return d->stream();
00583 }
00584 
00585 void KSycoca::clearCaches()
00586 {
00587     if (ksycocaInstance.exists() && ksycocaInstance->hasSycoca())
00588         ksycocaInstance->sycoca()->d->closeDatabase();
00589 }
00590 
00591 #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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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