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