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