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