kdecore Library API Documentation

ksycoca.cpp

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., 59 Temple Place - Suite 330,
00016  *  Boston, MA 02111-1307, USA.
00017  **/
00018 
00019 #include "config.h"
00020 
00021 #include "ksycoca.h"
00022 #include "ksycocatype.h"
00023 #include "ksycocafactory.h"
00024 
00025 #include <qdatastream.h>
00026 #include <qfile.h>
00027 #include <qbuffer.h>
00028 
00029 #include <kapplication.h>
00030 #include <dcopclient.h>
00031 #include <kglobal.h>
00032 #include <kdebug.h>
00033 #include <kprocess.h>
00034 #include <kstandarddirs.h>
00035 
00036 #include <assert.h>
00037 #include <stdlib.h>
00038 #include <unistd.h>
00039 #include <fcntl.h>
00040               
00041 #ifdef HAVE_SYS_MMAN_H
00042 #include <sys/mman.h>
00043 #endif
00044 
00045 #ifndef MAP_FAILED
00046 #define MAP_FAILED ((void *) -1)
00047 #endif
00048 
00049 template class QPtrList<KSycocaFactory>;
00050 
00051 // The following limitations are in place:
00052 // Maximum length of a single string: 8192 bytes
00053 // Maximum lenght of a string list: 1024 strings
00054 // Maximum number of entries: 8192
00055 //
00056 // The purpose of these limitations is to limit the impact
00057 // of database corruption.
00058 
00059 struct KSycocaPrivate {
00060     KSycocaPrivate() {
00061         database = 0;
00062         readError = false;
00063         updateSig = 0;
00064         autoRebuild = true;
00065     }
00066     QFile *database;
00067     QStringList changeList;
00068     QString language;
00069     bool readError;
00070     bool autoRebuild;
00071     Q_UINT32 updateSig;
00072 };
00073 
00074 // Read-only constructor
00075 KSycoca::KSycoca()
00076   : DCOPObject("ksycoca"), m_lstFactories(0), m_str(0), bNoDatabase(false),
00077     m_sycoca_size(0), m_sycoca_mmap(0), m_timeStamp(0)
00078 {
00079    d = new KSycocaPrivate;
00080    // Register app as able to receive DCOP messages
00081    if (kapp && !kapp->dcopClient()->isAttached())
00082    {
00083       kapp->dcopClient()->attach();
00084    }
00085    // We register with DCOP _before_ we try to open the database.
00086    // This way we can be relative sure that the KDE framework is
00087    // up and running (kdeinit, dcopserver, klaucnher, kded) and
00088    // that the database is up to date.
00089    openDatabase();
00090    _self = this;
00091 }
00092 
00093 bool KSycoca::openDatabase( bool openDummyIfNotFound )
00094 {
00095    bool result = true;
00096   
00097    m_sycoca_mmap = 0;
00098    m_str = 0;
00099    QString path;
00100    QCString ksycoca_env = getenv("KDESYCOCA");
00101    if (ksycoca_env.isEmpty())
00102       path = KGlobal::dirs()->saveLocation("tmp") + "ksycoca";
00103    else
00104       path = QFile::decodeName(ksycoca_env);
00105    //kdDebug(7011) << "Trying to open ksycoca from " << path << endl;
00106    QFile *database = new QFile(path);
00107    if (database->open( IO_ReadOnly ))
00108    {
00109      fcntl(database->handle(), F_SETFD, FD_CLOEXEC);
00110      m_sycoca_size = database->size();
00111 #ifdef HAVE_MMAP
00112      m_sycoca_mmap = (const char *) mmap(0, m_sycoca_size,
00113                                 PROT_READ, MAP_SHARED,
00114                                 database->handle(), 0);
00115      /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
00116         null pointer too.  */
00117      if (m_sycoca_mmap == (const char*) MAP_FAILED || m_sycoca_mmap == 0)
00118      {
00119         kdDebug(7011) << "mmap failed. (length = " << m_sycoca_size << ")" << endl;
00120 #endif
00121         m_str = new QDataStream(database);
00122 #ifdef HAVE_MMAP
00123      }
00124      else
00125      {
00126         QByteArray b_array;
00127         b_array.setRawData(m_sycoca_mmap, m_sycoca_size);
00128         QBuffer *buffer = new QBuffer( b_array );
00129         buffer->open(IO_ReadWrite);
00130         m_str = new QDataStream( buffer);
00131      }
00132 #endif
00133      bNoDatabase = false;
00134    }
00135    else
00136    {
00137      // No database file
00138      delete database;
00139      database = 0;
00140 
00141      bNoDatabase = true;
00142      if (openDummyIfNotFound)
00143      {
00144         // We open a dummy database instead.
00145         //kdDebug(7011) << "No database, opening a dummy one." << endl;
00146         QBuffer *buffer = new QBuffer( QByteArray() );
00147         buffer->open(IO_ReadWrite);
00148         m_str = new QDataStream( buffer);
00149         (*m_str) << (Q_INT32) KSYCOCA_VERSION;
00150         (*m_str) << (Q_INT32) 0;
00151      }
00152      else
00153      {
00154         result = false;
00155      }
00156    }
00157    m_lstFactories = new KSycocaFactoryList();
00158    m_lstFactories->setAutoDelete( true );
00159    d->database = database;
00160    return result;
00161 }
00162 
00163 // Read-write constructor - only for KBuildSycoca
00164 KSycoca::KSycoca( bool /* dummy */ )
00165   : DCOPObject("ksycoca_building"), m_lstFactories(0), m_str(0), bNoDatabase(false),
00166     m_sycoca_size(0), m_sycoca_mmap(0)
00167 {
00168    d = new KSycocaPrivate;
00169    m_lstFactories = new KSycocaFactoryList();
00170    m_lstFactories->setAutoDelete( true );
00171    _self = this;
00172 }
00173 
00174 static void delete_ksycoca_self() {
00175   if (KSycoca::_checkSelf())
00176      delete KSycoca::_self;
00177   
00178 }
00179 
00180 bool KSycoca::_checkSelf() {
00181   return (_self ? true : false);
00182 }
00183     
00184 KSycoca * KSycoca::self()
00185 {
00186     if (!_self) {
00187         qAddPostRoutine(delete_ksycoca_self);
00188         _self = new KSycoca();
00189     }
00190   return _self;
00191 }
00192 
00193 KSycoca::~KSycoca()
00194 {
00195    closeDatabase();
00196    delete d;
00197    _self = 0L;
00198 }
00199 
00200 void KSycoca::closeDatabase()
00201 {
00202    QIODevice *device = 0;
00203    if (m_str)
00204       device = m_str->device();
00205 #ifdef HAVE_MMAP
00206    if (device && m_sycoca_mmap)
00207    {
00208       QBuffer *buf = (QBuffer *) device;
00209       buf->buffer().resetRawData(m_sycoca_mmap, m_sycoca_size);
00210       // Solaris has munmap(char*, size_t) and everything else should
00211       // be happy with a char* for munmap(void*, size_t)
00212       munmap((char*) m_sycoca_mmap, m_sycoca_size);
00213       m_sycoca_mmap = 0;
00214    }
00215 #endif
00216 
00217    delete m_str;
00218    m_str = 0;
00219    delete device;
00220    if (d->database != device)
00221       delete d->database;
00222    device = 0;
00223    d->database = 0;
00224    // It is very important to delete all factories here
00225    // since they cache information about the database file
00226    delete m_lstFactories;
00227    m_lstFactories = 0L;
00228 }
00229 
00230 void KSycoca::addFactory( KSycocaFactory *factory )
00231 {
00232    assert(m_lstFactories);
00233    m_lstFactories->append(factory);
00234 }
00235 
00236 bool KSycoca::isChanged(const char *type)
00237 {
00238     return self()->d->changeList.contains(type);
00239 }
00240 
00241 void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
00242 {
00243     d->changeList = changeList;
00244     //kdDebug(7011) << "got a notifyDatabaseChanged signal !" << endl;
00245     // kded tells us the database file changed
00246     // Close the database and forget all about what we knew
00247     // The next call to any public method will recreate
00248     // everything that's needed.
00249     closeDatabase();
00250 
00251     // Now notify applications
00252     emit databaseChanged();
00253 }
00254 
00255 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
00256 {
00257    if ( !m_str )
00258       openDatabase();
00259    //kdDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16) << endl;
00260    m_str->device()->at(offset);
00261    Q_INT32 aType;
00262    (*m_str) >> aType;
00263    type = (KSycocaType) aType;
00264    //kdDebug(7011) << QString("KSycoca::found type %1").arg(aType) << endl;
00265    return m_str;
00266 }
00267 
00268 bool KSycoca::checkVersion(bool abortOnError)
00269 {
00270    if ( !m_str )
00271    {
00272       if( !openDatabase(false /* don't open dummy db if not found */) )
00273         return false; // No database found
00274 
00275       // We should never get here... if a database was found then m_str shouldn't be 0L.
00276       assert(m_str);
00277    }
00278    m_str->device()->at(0);
00279    Q_INT32 aVersion;
00280    (*m_str) >> aVersion;
00281    if ( aVersion < KSYCOCA_VERSION )
00282    {
00283       kdWarning(7011) << "Found version " << aVersion << ", expecting version " << KSYCOCA_VERSION << " or higher." << endl;
00284       if (!abortOnError) return false;
00285       kdError(7011) << "Outdated database ! Stop kded and restart it !" << endl;
00286       abort();
00287    }
00288    return true;
00289 }
00290 
00291 QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
00292 {
00293    // The constructor found no database, but we want one
00294    if (bNoDatabase)
00295    {
00296       closeDatabase(); // close the dummy one
00297       // Check if new database already available
00298       if ( !openDatabase(false /* no dummy one*/) )
00299       {
00300          static bool triedLaunchingKdeinit = false;
00301          if (!triedLaunchingKdeinit) // try only once
00302          {
00303            triedLaunchingKdeinit = true;
00304            kdDebug(7011) << "findFactory: we have no database.... launching kdeinit" << endl;
00305            KApplication::startKdeinit();
00306            // Ok, the new database should be here now, open it.
00307          }
00308          if (!openDatabase(false))
00309             return 0L; // Still no database - uh oh
00310       }
00311    }
00312    // rewind and check
00313    if (!checkVersion(false))
00314    {
00315      kdWarning(7011) << "Outdated database found" << endl;
00316      return 0L;
00317    }
00318    Q_INT32 aId;
00319    Q_INT32 aOffset;
00320    while(true)
00321    {
00322       (*m_str) >> aId;
00323       //kdDebug(7011) << QString("KSycoca::findFactory : found factory %1").arg(aId) << endl;
00324       if (aId == 0)
00325       {
00326          kdError(7011) << "Error, KSycocaFactory (id = " << int(id) << ") not found!" << endl;
00327          break;
00328       }
00329       (*m_str) >> aOffset;
00330       if (aId == id)
00331       {
00332          //kdDebug(7011) << QString("KSycoca::findFactory(%1) offset %2").arg((int)id).arg(aOffset) << endl;
00333          m_str->device()->at(aOffset);
00334          return m_str;
00335       }
00336    }
00337    return 0;
00338 }
00339 
00340 QString KSycoca::kfsstnd_prefixes()
00341 {
00342    if (bNoDatabase) return "";
00343    if (!checkVersion(false)) return "";
00344    Q_INT32 aId;
00345    Q_INT32 aOffset;
00346    // skip factories offsets
00347    while(true)
00348    {
00349       (*m_str) >> aId;
00350       if ( aId )
00351         (*m_str) >> aOffset;
00352       else
00353         break; // just read 0
00354    }
00355    // We now point to the header
00356    QString prefixes;
00357    KSycocaEntry::read(*m_str, prefixes);
00358    (*m_str) >> m_timeStamp;
00359    KSycocaEntry::read(*m_str, d->language);
00360    (*m_str) >> d->updateSig;
00361    return prefixes;
00362 }
00363 
00364 Q_UINT32 KSycoca::timeStamp()
00365 {
00366    if (!m_timeStamp)
00367       (void) kfsstnd_prefixes();
00368    return m_timeStamp;
00369 }
00370 
00371 Q_UINT32 KSycoca::updateSignature()
00372 {
00373    if (!m_timeStamp)
00374       (void) kfsstnd_prefixes();
00375    return d->updateSig;
00376 }
00377 
00378 QString KSycoca::language()
00379 {
00380    if (d->language.isEmpty())
00381       (void) kfsstnd_prefixes();
00382    return d->language;
00383 }
00384 
00385 QString KSycoca::determineRelativePath( const QString & _fullpath, const char *_resource )
00386 {
00387   QString sRelativeFilePath;
00388   QStringList dirs = KGlobal::dirs()->resourceDirs( _resource );
00389   QStringList::ConstIterator dirsit = dirs.begin();
00390   for ( ; dirsit != dirs.end() && sRelativeFilePath.isEmpty(); ++dirsit ) {
00391     // might need canonicalPath() ...
00392     if ( _fullpath.find( *dirsit ) == 0 ) // path is dirs + relativePath
00393       sRelativeFilePath = _fullpath.mid( (*dirsit).length() ); // skip appsdirs
00394   }
00395   if ( sRelativeFilePath.isEmpty() )
00396     kdFatal(7011) << QString("Couldn't find %1 in any %2 dir !!!").arg( _fullpath ).arg( _resource) << endl;
00397   //else
00398     // debug code
00399     //kdDebug(7011) << sRelativeFilePath << endl;
00400   return sRelativeFilePath;
00401 }
00402 
00403 KSycoca * KSycoca::_self = 0L;
00404 
00405 void KSycoca::flagError()
00406 {
00407    qWarning("ERROR: KSycoca database corruption!");
00408    if (_self)
00409    {
00410       if (_self->d->readError)
00411          return;
00412       _self->d->readError = true;
00413       if (_self->d->autoRebuild)
00414          system("kbuildsycoca"); // Rebuild the damned thing.
00415    }
00416 }
00417 
00418 void KSycoca::disableAutoRebuild()
00419 {
00420    d->autoRebuild = false;
00421 }
00422 
00423 bool KSycoca::readError()
00424 {
00425    bool b = false;
00426    if (_self)
00427    {
00428       b = _self->d->readError;
00429       _self->d->readError = false;
00430    }
00431    return b;
00432 }
00433 
00434 void KSycocaEntry::read( QDataStream &s, QString &str )
00435 {
00436   Q_UINT32 bytes;
00437   s >> bytes;                          // read size of string
00438   if ( bytes > 8192 ) {                // null string or too big
00439       if (bytes != 0xffffffff)
00440          KSycoca::flagError();
00441       str = QString::null;
00442   } 
00443   else if ( bytes > 0 ) {              // not empty
00444       int bt = bytes/2;
00445       str.setLength( bt );
00446       QChar* ch = (QChar *) str.unicode();
00447       char t[8192];
00448       char *b = t;
00449       s.readRawBytes( b, bytes );
00450       while ( bt-- ) {
00451           *ch++ = (ushort) (((ushort)b[0])<<8) | (uchar)b[1];
00452       b += 2;
00453       }
00454   } else {
00455       str = "";
00456   }
00457 }
00458 
00459 void KSycocaEntry::read( QDataStream &s, QStringList &list )
00460 {
00461   list.clear();
00462   Q_UINT32 count;
00463   s >> count;                          // read size of list
00464   if (count >= 1024)
00465   {
00466      KSycoca::flagError();
00467      return;
00468   }
00469   for(Q_UINT32 i = 0; i < count; i++)
00470   {
00471      QString str;
00472      read(s, str);
00473      list.append( str );
00474      if (s.atEnd())
00475      {
00476         KSycoca::flagError();
00477         return;
00478      }
00479   }
00480 }
00481 
00482 void KSycoca::virtual_hook( int id, void* data )
00483 { DCOPObject::virtual_hook( id, data ); }
00484 
00485 void KSycocaEntry::virtual_hook( int, void* )
00486 { /*BASE::virtual_hook( id, data );*/ }
00487 
00488 #include "ksycoca.moc"
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.4.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Sun Feb 27 22:14:48 2005 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001