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