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 delete KSycoca::_self; 00209 } 00210 00211 KSycoca * KSycoca::self() 00212 { 00213 if (!_self) { 00214 qAddPostRoutine(delete_ksycoca_self); 00215 _self = new KSycoca(); 00216 } 00217 return _self; 00218 } 00219 00220 KSycoca::~KSycoca() 00221 { 00222 closeDatabase(); 00223 delete d; 00224 _self = 0L; 00225 } 00226 00227 void KSycoca::closeDatabase() 00228 { 00229 QIODevice *device = 0; 00230 if (m_str) 00231 device = m_str->device(); 00232 #ifdef HAVE_MMAP 00233 if (device && m_sycoca_mmap) 00234 { 00235 QBuffer *buf = (QBuffer *) device; 00236 buf->buffer().resetRawData(m_sycoca_mmap, m_sycoca_size); 00237 // Solaris has munmap(char*, size_t) and everything else should 00238 // be happy with a char* for munmap(void*, size_t) 00239 munmap((char*) m_sycoca_mmap, m_sycoca_size); 00240 m_sycoca_mmap = 0; 00241 } 00242 #endif 00243 00244 delete m_str; 00245 m_str = 0; 00246 delete device; 00247 if (d->database != device) 00248 delete d->database; 00249 device = 0; 00250 d->database = 0; 00251 // It is very important to delete all factories here 00252 // since they cache information about the database file 00253 delete m_lstFactories; 00254 m_lstFactories = 0L; 00255 } 00256 00257 void KSycoca::addFactory( KSycocaFactory *factory ) 00258 { 00259 assert(m_lstFactories); 00260 m_lstFactories->append(factory); 00261 } 00262 00263 bool KSycoca::isChanged(const char *type) 00264 { 00265 return self()->d->changeList.contains(type); 00266 } 00267 00268 void KSycoca::notifyDatabaseChanged(const QStringList &changeList) 00269 { 00270 d->changeList = changeList; 00271 //kdDebug(7011) << "got a notifyDatabaseChanged signal !" << endl; 00272 // kded tells us the database file changed 00273 // Close the database and forget all about what we knew 00274 // The next call to any public method will recreate 00275 // everything that's needed. 00276 closeDatabase(); 00277 00278 // Now notify applications 00279 emit databaseChanged(); 00280 } 00281 00282 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type) 00283 { 00284 if ( !m_str ) 00285 openDatabase(); 00286 //kdDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16) << endl; 00287 m_str->device()->at(offset); 00288 Q_INT32 aType; 00289 (*m_str) >> aType; 00290 type = (KSycocaType) aType; 00291 //kdDebug(7011) << QString("KSycoca::found type %1").arg(aType) << endl; 00292 return m_str; 00293 } 00294 00295 bool KSycoca::checkVersion(bool abortOnError) 00296 { 00297 if ( !m_str ) 00298 { 00299 if( !openDatabase(false /* don't open dummy db if not found */) ) 00300 return false; // No database found 00301 00302 // We should never get here... if a database was found then m_str shouldn't be 0L. 00303 assert(m_str); 00304 } 00305 m_str->device()->at(0); 00306 Q_INT32 aVersion; 00307 (*m_str) >> aVersion; 00308 if ( aVersion < KSYCOCA_VERSION ) 00309 { 00310 kdWarning(7011) << "Found version " << aVersion << ", expecting version " << KSYCOCA_VERSION << " or higher." << endl; 00311 if (!abortOnError) return false; 00312 kdError(7011) << "Outdated database ! Stop kded and restart it !" << endl; 00313 abort(); 00314 } 00315 return true; 00316 } 00317 00318 QDataStream * KSycoca::findFactory(KSycocaFactoryId id) 00319 { 00320 // The constructor found no database, but we want one 00321 if (bNoDatabase) 00322 { 00323 closeDatabase(); // close the dummy one 00324 // Check if new database already available 00325 if ( !openDatabase(false /* no dummy one*/) ) 00326 { 00327 static bool triedLaunchingKdeinit = false; 00328 if (!triedLaunchingKdeinit) // try only once 00329 { 00330 triedLaunchingKdeinit = true; 00331 kdDebug(7011) << "findFactory: we have no database.... launching kdeinit" << endl; 00332 KApplication::startKdeinit(); 00333 // Ok, the new database should be here now, open it. 00334 } 00335 if (!openDatabase(false)) 00336 return 0L; // Still no database - uh oh 00337 } 00338 } 00339 // rewind and check 00340 if (!checkVersion(false)) 00341 { 00342 kdWarning(7011) << "Outdated database found" << endl; 00343 return 0L; 00344 } 00345 Q_INT32 aId; 00346 Q_INT32 aOffset; 00347 while(true) 00348 { 00349 (*m_str) >> aId; 00350 //kdDebug(7011) << QString("KSycoca::findFactory : found factory %1").arg(aId) << endl; 00351 if (aId == 0) 00352 { 00353 kdError(7011) << "Error, KSycocaFactory (id = " << int(id) << ") not found!" << endl; 00354 break; 00355 } 00356 (*m_str) >> aOffset; 00357 if (aId == id) 00358 { 00359 //kdDebug(7011) << QString("KSycoca::findFactory(%1) offset %2").arg((int)id).arg(aOffset) << endl; 00360 m_str->device()->at(aOffset); 00361 return m_str; 00362 } 00363 } 00364 return 0; 00365 } 00366 00367 QString KSycoca::kfsstnd_prefixes() 00368 { 00369 if (bNoDatabase) return ""; 00370 if (!checkVersion(false)) return ""; 00371 Q_INT32 aId; 00372 Q_INT32 aOffset; 00373 // skip factories offsets 00374 while(true) 00375 { 00376 (*m_str) >> aId; 00377 if ( aId ) 00378 (*m_str) >> aOffset; 00379 else 00380 break; // just read 0 00381 } 00382 // We now point to the header 00383 QString prefixes; 00384 KSycocaEntry::read(*m_str, prefixes); 00385 (*m_str) >> m_timeStamp; 00386 KSycocaEntry::read(*m_str, d->language); 00387 (*m_str) >> d->updateSig; 00388 KSycocaEntry::read(*m_str, d->allResourceDirs); 00389 return prefixes; 00390 } 00391 00392 Q_UINT32 KSycoca::timeStamp() 00393 { 00394 if (!m_timeStamp) 00395 (void) kfsstnd_prefixes(); 00396 return m_timeStamp; 00397 } 00398 00399 Q_UINT32 KSycoca::updateSignature() 00400 { 00401 if (!m_timeStamp) 00402 (void) kfsstnd_prefixes(); 00403 return d->updateSig; 00404 } 00405 00406 QString KSycoca::language() 00407 { 00408 if (d->language.isEmpty()) 00409 (void) kfsstnd_prefixes(); 00410 return d->language; 00411 } 00412 00413 QStringList KSycoca::allResourceDirs() 00414 { 00415 if (!m_timeStamp) 00416 (void) kfsstnd_prefixes(); 00417 return d->allResourceDirs; 00418 } 00419 00420 QString KSycoca::determineRelativePath( const QString & _fullpath, const char *_resource ) 00421 { 00422 QString sRelativeFilePath; 00423 QStringList dirs = KGlobal::dirs()->resourceDirs( _resource ); 00424 QStringList::ConstIterator dirsit = dirs.begin(); 00425 for ( ; dirsit != dirs.end() && sRelativeFilePath.isEmpty(); ++dirsit ) { 00426 // might need canonicalPath() ... 00427 if ( _fullpath.find( *dirsit ) == 0 ) // path is dirs + relativePath 00428 sRelativeFilePath = _fullpath.mid( (*dirsit).length() ); // skip appsdirs 00429 } 00430 if ( sRelativeFilePath.isEmpty() ) 00431 kdFatal(7011) << QString("Couldn't find %1 in any %2 dir !!!").arg( _fullpath ).arg( _resource) << endl; 00432 //else 00433 // debug code 00434 //kdDebug(7011) << sRelativeFilePath << endl; 00435 return sRelativeFilePath; 00436 } 00437 00438 KSycoca * KSycoca::_self = 0L; 00439 00440 void KSycoca::flagError() 00441 { 00442 qWarning("ERROR: KSycoca database corruption!"); 00443 if (_self) 00444 { 00445 if (_self->d->readError) 00446 return; 00447 _self->d->readError = true; 00448 if (_self->d->autoRebuild) 00449 if(system("kbuildsycoca") < 0) // Rebuild the damned thing. 00450 qWarning("ERROR: Running KSycoca failed."); 00451 } 00452 } 00453 00454 void KSycoca::disableAutoRebuild() 00455 { 00456 d->autoRebuild = false; 00457 } 00458 00459 bool KSycoca::readError() 00460 { 00461 bool b = false; 00462 if (_self) 00463 { 00464 b = _self->d->readError; 00465 _self->d->readError = false; 00466 } 00467 return b; 00468 } 00469 00470 void KSycocaEntry::read( QDataStream &s, QString &str ) 00471 { 00472 Q_UINT32 bytes; 00473 s >> bytes; // read size of string 00474 if ( bytes > 8192 ) { // null string or too big 00475 if (bytes != 0xffffffff) 00476 KSycoca::flagError(); 00477 str = QString::null; 00478 } 00479 else if ( bytes > 0 ) { // not empty 00480 int bt = bytes/2; 00481 str.setLength( bt ); 00482 QChar* ch = (QChar *) str.unicode(); 00483 char t[8192]; 00484 char *b = t; 00485 s.readRawBytes( b, bytes ); 00486 while ( bt-- ) { 00487 *ch++ = (ushort) (((ushort)b[0])<<8) | (uchar)b[1]; 00488 b += 2; 00489 } 00490 } else { 00491 str = ""; 00492 } 00493 } 00494 00495 void KSycocaEntry::read( QDataStream &s, QStringList &list ) 00496 { 00497 list.clear(); 00498 Q_UINT32 count; 00499 s >> count; // read size of list 00500 if (count >= 1024) 00501 { 00502 KSycoca::flagError(); 00503 return; 00504 } 00505 for(Q_UINT32 i = 0; i < count; i++) 00506 { 00507 QString str; 00508 read(s, str); 00509 list.append( str ); 00510 if (s.atEnd()) 00511 { 00512 KSycoca::flagError(); 00513 return; 00514 } 00515 } 00516 } 00517 00518 void KSycoca::virtual_hook( int id, void* data ) 00519 { DCOPObject::virtual_hook( id, data ); } 00520 00521 void KSycocaEntry::virtual_hook( int, void* ) 00522 { /*BASE::virtual_hook( id, data );*/ } 00523 00524 #include "ksycoca.moc"

