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