KDECore
ksycoca.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 * Copyright (C) 1999-2000 Waldo Bastian <bastian@kde.org> 00003 * Copyright (C) 2005-2009 David Faure <faure@kde.org> 00004 * Copyright (C) 2008 Hamish Rodda <rodda@kde.org> 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License version 2 as published by the Free Software Foundation; 00009 * 00010 * This library is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 * Library General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU Library General Public License 00016 * along with this library; see the file COPYING.LIB. If not, write to 00017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 * Boston, MA 02110-1301, USA. 00019 **/ 00020 00021 #include "ksycoca.h" 00022 #include "ksycoca_p.h" 00023 #include "ksycocatype.h" 00024 #include "ksycocafactory.h" 00025 #include "ktoolinvocation.h" 00026 #include "kglobal.h" 00027 #include "kmemfile.h" 00028 #include "kde_file.h" 00029 #include "kconfiggroup.h" 00030 #include "ksharedconfig.h" 00031 00032 #include "kdebug.h" 00033 #include "kstandarddirs.h" 00034 00035 #include <QtCore/QDataStream> 00036 #include <QtCore/QCoreApplication> 00037 #include <QtCore/QFile> 00038 #include <QtCore/QBuffer> 00039 #include <QProcess> 00040 #include <QtDBus/QtDBus> 00041 00042 #include <config.h> 00043 00044 #include <stdlib.h> 00045 #include <fcntl.h> 00046 00047 #include "ksycocadevices_p.h" 00048 00049 // TODO: remove mmap() from kdewin32 and use QFile::mmap() when needed 00050 #ifdef Q_WS_WIN 00051 #undef HAVE_MMAP 00052 #endif 00053 00058 #define KSYCOCA_VERSION 202 00059 00063 #define KSYCOCA_FILENAME "ksycoca4" 00064 00065 #if HAVE_MADVISE 00066 #include <sys/mman.h> // This #include was checked when looking for posix_madvise 00067 #endif 00068 00069 #ifndef MAP_FAILED 00070 #define MAP_FAILED ((void *) -1) 00071 #endif 00072 00073 static bool s_autoRebuild = true; 00074 00075 // The following limitations are in place: 00076 // Maximum length of a single string: 8192 bytes 00077 // Maximum length of a string list: 1024 strings 00078 // Maximum number of entries: 8192 00079 // 00080 // The purpose of these limitations is to limit the impact 00081 // of database corruption. 00082 00083 00084 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound) 00085 00086 KSycocaPrivate::KSycocaPrivate() 00087 : databaseStatus( DatabaseNotOpen ), 00088 readError( false ), 00089 timeStamp( 0 ), 00090 m_databasePath(), 00091 updateSig( 0 ), 00092 sycoca_size(0), 00093 sycoca_mmap(0), 00094 m_mmapFile(0), 00095 m_device(0) 00096 { 00097 #ifdef Q_OS_WIN 00098 /* 00099 on windows we use KMemFile (QSharedMemory) to avoid problems 00100 with mmap (can't delete a mmap'd file) 00101 */ 00102 m_sycocaStrategy = StrategyMemFile; 00103 #else 00104 m_sycocaStrategy = StrategyMmap; 00105 #endif 00106 KConfigGroup config(KGlobal::config(), "KSycoca"); 00107 setStrategyFromString(config.readEntry("strategy")); 00108 } 00109 00110 void KSycocaPrivate::setStrategyFromString(const QString& strategy) { 00111 if (strategy == QLatin1String("mmap")) 00112 m_sycocaStrategy = StrategyMmap; 00113 else if (strategy == QLatin1String("file")) 00114 m_sycocaStrategy = StrategyFile; 00115 else if (strategy == QLatin1String("sharedmem")) 00116 m_sycocaStrategy = StrategyMemFile; 00117 else if (!strategy.isEmpty()) 00118 kWarning(7011) << "Unknown sycoca strategy:" << strategy; 00119 } 00120 00121 bool KSycocaPrivate::tryMmap() 00122 { 00123 #ifdef HAVE_MMAP 00124 Q_ASSERT(!m_databasePath.isEmpty()); 00125 m_mmapFile = new QFile(m_databasePath); 00126 const bool canRead = m_mmapFile->open(QIODevice::ReadOnly); 00127 Q_ASSERT(canRead); 00128 Q_UNUSED(canRead); // no compiler warning in release builds. 00129 fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC); 00130 sycoca_size = m_mmapFile->size(); 00131 sycoca_mmap = (const char *) mmap(0, sycoca_size, 00132 PROT_READ, MAP_SHARED, 00133 m_mmapFile->handle(), 0); 00134 /* POSIX mandates only MAP_FAILED, but we are paranoid so check for 00135 null pointer too. */ 00136 if (sycoca_mmap == (const char*) MAP_FAILED || sycoca_mmap == 0) { 00137 kDebug(7011) << "mmap failed. (length = " << sycoca_size << ")"; 00138 sycoca_mmap = 0; 00139 return false; 00140 } else { 00141 #ifdef HAVE_MADVISE 00142 (void) posix_madvise((void*)sycoca_mmap, sycoca_size, POSIX_MADV_WILLNEED); 00143 #endif // HAVE_MADVISE 00144 return true; 00145 } 00146 #endif // HAVE_MMAP 00147 return false; 00148 } 00149 00150 int KSycoca::version() 00151 { 00152 return KSYCOCA_VERSION; 00153 } 00154 00155 class KSycocaSingleton 00156 { 00157 public: 00158 KSycocaSingleton() { } 00159 ~KSycocaSingleton() { } 00160 00161 bool hasSycoca() const { 00162 return m_threadSycocas.hasLocalData(); 00163 } 00164 KSycoca* sycoca() { 00165 if (!m_threadSycocas.hasLocalData()) 00166 m_threadSycocas.setLocalData(new KSycoca); 00167 return m_threadSycocas.localData(); 00168 } 00169 void setSycoca(KSycoca* s) { 00170 m_threadSycocas.setLocalData(s); 00171 } 00172 00173 private: 00174 QThreadStorage<KSycoca*> m_threadSycocas; 00175 }; 00176 00177 K_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance) 00178 00179 // Read-only constructor 00180 KSycoca::KSycoca() 00181 : d(new KSycocaPrivate) 00182 { 00183 QDBusConnection::sessionBus().connect(QString(), QString(), 00184 QString::fromLatin1("org.kde.KSycoca"), 00185 QString::fromLatin1("notifyDatabaseChanged"), 00186 this, SLOT(notifyDatabaseChanged(QStringList))); 00187 } 00188 00189 bool KSycocaPrivate::openDatabase(bool openDummyIfNotFound) 00190 { 00191 Q_ASSERT(databaseStatus == DatabaseNotOpen); 00192 00193 delete m_device; m_device = 0; 00194 QString path = KSycoca::absoluteFilePath(); 00195 00196 bool canRead = KDE::access(path, R_OK) == 0; 00197 kDebug(7011) << "Trying to open ksycoca from" << path; 00198 if (!canRead) { 00199 path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase); 00200 if (!path.isEmpty()) { 00201 kDebug(7011) << "Trying to open global ksycoca from " << path; 00202 canRead = KDE::access(path, R_OK) == 0; 00203 } 00204 } 00205 00206 bool result = true; 00207 if (canRead) { 00208 m_databasePath = path; 00209 checkVersion(); 00210 } else { // No database file 00211 kDebug(7011) << "Could not open ksycoca"; 00212 m_databasePath.clear(); 00213 databaseStatus = NoDatabase; 00214 if (openDummyIfNotFound) { 00215 // We open a dummy database instead. 00216 //kDebug(7011) << "No database, opening a dummy one."; 00217 00218 m_sycocaStrategy = StrategyDummyBuffer; 00219 QDataStream* str = stream(); 00220 *str << qint32(KSYCOCA_VERSION); 00221 *str << qint32(0); 00222 } else { 00223 result = false; 00224 } 00225 } 00226 return result; 00227 } 00228 00229 KSycocaAbstractDevice* KSycocaPrivate::device() 00230 { 00231 if (m_device) 00232 return m_device; 00233 00234 Q_ASSERT(!m_databasePath.isEmpty()); 00235 00236 KSycocaAbstractDevice* device = m_device; 00237 if (m_sycocaStrategy == StrategyDummyBuffer) { 00238 device = new KSycocaBufferDevice; 00239 device->device()->open(QIODevice::ReadOnly); // can't fail 00240 } else { 00241 #ifdef HAVE_MMAP 00242 if (m_sycocaStrategy == StrategyMmap && tryMmap()) { 00243 device = new KSycocaMmapDevice(sycoca_mmap, 00244 sycoca_size); 00245 if (!device->device()->open(QIODevice::ReadOnly)) { 00246 delete device; device = 0; 00247 } 00248 } 00249 #endif 00250 #ifndef QT_NO_SHAREDMEMORY 00251 if (!device && m_sycocaStrategy == StrategyMemFile) { 00252 device = new KSycocaMemFileDevice(m_databasePath); 00253 if (!device->device()->open(QIODevice::ReadOnly)) { 00254 delete device; device = 0; 00255 } 00256 } 00257 #endif 00258 if (!device) { 00259 device = new KSycocaFileDevice(m_databasePath); 00260 if (!device->device()->open(QIODevice::ReadOnly)) { 00261 kError(7011) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible."; 00262 //delete device; device = 0; // this would crash in the return statement... 00263 } 00264 } 00265 } 00266 if (device) { 00267 m_device = device; 00268 } 00269 return m_device; 00270 } 00271 00272 QDataStream*& KSycocaPrivate::stream() 00273 { 00274 if (!m_device) { 00275 if (databaseStatus == DatabaseNotOpen) { 00276 checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy); 00277 } 00278 00279 device(); // create m_device 00280 } 00281 00282 return m_device->stream(); 00283 } 00284 00285 // Read-write constructor - only for KBuildSycoca 00286 KSycoca::KSycoca( bool /* dummy */ ) 00287 : d(new KSycocaPrivate) 00288 { 00289 // This instance was not created by the singleton, but by a direct call to new! 00290 ksycocaInstance->setSycoca(this); 00291 } 00292 00293 KSycoca * KSycoca::self() 00294 { 00295 KSycoca* s = ksycocaInstance->sycoca(); 00296 Q_ASSERT(s); 00297 return s; 00298 } 00299 00300 KSycoca::~KSycoca() 00301 { 00302 d->closeDatabase(); 00303 delete d; 00304 //if (ksycocaInstance.exists() 00305 // && ksycocaInstance->self == this) 00306 // ksycocaInstance->self = 0; 00307 } 00308 00309 bool KSycoca::isAvailable() 00310 { 00311 return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing/* don't open dummy db if not found */); 00312 } 00313 00314 void KSycocaPrivate::closeDatabase() 00315 { 00316 delete m_device; 00317 m_device = 0; 00318 00319 // It is very important to delete all factories here 00320 // since they cache information about the database file 00321 // But other threads might be using them, so this class is 00322 // refcounted, and deleted when the last thread is done with them 00323 qDeleteAll(m_factories); 00324 m_factories.clear(); 00325 #ifdef HAVE_MMAP 00326 if (sycoca_mmap) { 00327 //QBuffer *buf = static_cast<QBuffer*>(device); 00328 //buf->buffer().clear(); 00329 // Solaris has munmap(char*, size_t) and everything else should 00330 // be happy with a char* for munmap(void*, size_t) 00331 munmap(const_cast<char*>(sycoca_mmap), sycoca_size); 00332 sycoca_mmap = 0; 00333 } 00334 delete m_mmapFile; m_mmapFile = 0; 00335 #endif 00336 00337 databaseStatus = DatabaseNotOpen; 00338 timeStamp = 0; 00339 } 00340 00341 void KSycoca::addFactory( KSycocaFactory *factory ) 00342 { 00343 d->addFactory(factory); 00344 } 00345 00346 #ifndef KDE_NO_DEPRECATED 00347 bool KSycoca::isChanged(const char *type) 00348 { 00349 return self()->d->changeList.contains(QString::fromLatin1(type)); 00350 } 00351 #endif 00352 00353 void KSycoca::notifyDatabaseChanged(const QStringList &changeList) 00354 { 00355 d->changeList = changeList; 00356 //kDebug(7011) << QThread::currentThread() << "got a notifyDatabaseChanged signal" << changeList; 00357 // kbuildsycoca tells us the database file changed 00358 // Close the database and forget all about what we knew 00359 // The next call to any public method will recreate 00360 // everything that's needed. 00361 d->closeDatabase(); 00362 00363 // Now notify applications 00364 #ifndef KDE_NO_DEPRECATED 00365 emit databaseChanged(); 00366 #endif 00367 emit databaseChanged(changeList); 00368 } 00369 00370 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type) 00371 { 00372 QDataStream* str = stream(); 00373 Q_ASSERT(str); 00374 //kDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16); 00375 str->device()->seek(offset); 00376 qint32 aType; 00377 *str >> aType; 00378 type = KSycocaType(aType); 00379 //kDebug(7011) << QString("KSycoca::found type %1").arg(aType); 00380 return str; 00381 } 00382 00383 KSycocaFactoryList* KSycoca::factories() 00384 { 00385 return d->factories(); 00386 } 00387 00388 // Warning, checkVersion rewinds to the beginning of stream(). 00389 bool KSycocaPrivate::checkVersion() 00390 { 00391 QDataStream *m_str = device()->stream(); 00392 Q_ASSERT(m_str); 00393 m_str->device()->seek(0); 00394 qint32 aVersion; 00395 *m_str >> aVersion; 00396 if ( aVersion < KSYCOCA_VERSION ) { 00397 kWarning(7011) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher."; 00398 databaseStatus = BadVersion; 00399 return false; 00400 } else { 00401 databaseStatus = DatabaseOK; 00402 return true; 00403 } 00404 } 00405 00406 // If it returns true, we have a valid database and the stream has rewinded to the beginning 00407 // and past the version number. 00408 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound) 00409 { 00410 if (databaseStatus == DatabaseOK) { 00411 if (checkVersion()) // we know the version is ok, but we must rewind the stream anyway 00412 return true; 00413 } 00414 00415 closeDatabase(); // close the dummy one 00416 00417 // We can only use the installed ksycoca file if kdeinit+klauncher+kded are running, 00418 // since kded is what keeps the file uptodate. 00419 const bool kdeinitRunning = QDBusConnection::sessionBus().interface()->isServiceRegistered(QString::fromLatin1("org.kde.klauncher")); 00420 00421 // Check if new database already available 00422 if (kdeinitRunning && openDatabase(ifNotFound & IfNotFoundOpenDummy)) { 00423 if (checkVersion()) { 00424 // Database exists, and version is ok. 00425 return true; 00426 } 00427 } 00428 00429 if (ifNotFound & IfNotFoundRecreate) { 00430 // Well, if kdeinit is not running we need to launch it, 00431 // but otherwise we simply need to run kbuildsycoca to recreate the sycoca file. 00432 if (!kdeinitRunning) { 00433 kDebug(7011) << "We have no database.... launching kdeinit"; 00434 KToolInvocation::klauncher(); // this calls startKdeinit, and blocks until it returns 00435 // and since kdeinit4 only returns after kbuildsycoca4 is done, we can proceed. 00436 } else { 00437 kDebug(7011) << QThread::currentThread() << "We have no database.... launching" << KBUILDSYCOCA_EXENAME; 00438 if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0) 00439 qWarning("ERROR: Running KSycoca failed."); 00440 } 00441 00442 closeDatabase(); // close the dummy one 00443 00444 // Ok, the new database should be here now, open it. 00445 if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) { 00446 kDebug(7011) << "Still no database..."; 00447 return false; // Still no database - uh oh 00448 } 00449 if (!checkVersion()) { 00450 kDebug(7011) << "Still outdated..."; 00451 return false; // Still outdated - uh oh 00452 } 00453 return true; 00454 } 00455 00456 return false; 00457 } 00458 00459 QDataStream * KSycoca::findFactory(KSycocaFactoryId id) 00460 { 00461 // Ensure we have a valid database (right version, and rewinded to beginning) 00462 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) { 00463 return 0; 00464 } 00465 00466 QDataStream* str = stream(); 00467 Q_ASSERT(str); 00468 00469 qint32 aId; 00470 qint32 aOffset; 00471 while(true) { 00472 *str >> aId; 00473 if (aId == 0) { 00474 kError(7011) << "Error, KSycocaFactory (id =" << int(id) << ") not found!"; 00475 break; 00476 } 00477 *str >> aOffset; 00478 if (aId == id) { 00479 //kDebug(7011) << "KSycoca::findFactory(" << id << ") offset " << aOffset; 00480 str->device()->seek(aOffset); 00481 return str; 00482 } 00483 } 00484 return 0; 00485 } 00486 00487 QString KSycoca::kfsstnd_prefixes() 00488 { 00489 // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca. 00490 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) 00491 return QString(); 00492 QDataStream* str = stream(); 00493 Q_ASSERT(str); 00494 qint32 aId; 00495 qint32 aOffset; 00496 // skip factories offsets 00497 while(true) 00498 { 00499 *str >> aId; 00500 if ( aId ) 00501 *str >> aOffset; 00502 else 00503 break; // just read 0 00504 } 00505 // We now point to the header 00506 QString prefixes; 00507 KSycocaEntry::read(*str, prefixes); 00508 *str >> d->timeStamp; 00509 KSycocaEntry::read(*str, d->language); 00510 *str >> d->updateSig; 00511 KSycocaEntry::read(*str, d->allResourceDirs); 00512 return prefixes; 00513 } 00514 00515 quint32 KSycoca::timeStamp() 00516 { 00517 if (!d->timeStamp) 00518 (void) kfsstnd_prefixes(); 00519 return d->timeStamp; 00520 } 00521 00522 quint32 KSycoca::updateSignature() 00523 { 00524 if (!d->timeStamp) 00525 (void) kfsstnd_prefixes(); 00526 return d->updateSig; 00527 } 00528 00529 QString KSycoca::absoluteFilePath(DatabaseType type) 00530 { 00531 if (type == GlobalDatabase) 00532 return KGlobal::dirs()->saveLocation("services") + QString::fromLatin1(KSYCOCA_FILENAME); 00533 00534 const QByteArray ksycoca_env = qgetenv("KDESYCOCA"); 00535 if (ksycoca_env.isEmpty()) 00536 return KGlobal::dirs()->saveLocation("cache") + QString::fromLatin1(KSYCOCA_FILENAME); 00537 else 00538 return QFile::decodeName(ksycoca_env); 00539 } 00540 00541 QString KSycoca::language() 00542 { 00543 if (d->language.isEmpty()) 00544 (void) kfsstnd_prefixes(); 00545 return d->language; 00546 } 00547 00548 QStringList KSycoca::allResourceDirs() 00549 { 00550 if (!d->timeStamp) 00551 (void) kfsstnd_prefixes(); 00552 return d->allResourceDirs; 00553 } 00554 00555 void KSycoca::flagError() 00556 { 00557 kWarning(7011) << "ERROR: KSycoca database corruption!"; 00558 KSycocaPrivate* d = ksycocaInstance->sycoca()->d; 00559 if (d->readError) 00560 return; 00561 d->readError = true; 00562 if (s_autoRebuild) { 00563 // Rebuild the damned thing. 00564 if (QProcess::execute(KStandardDirs::findExe(QString::fromLatin1(KBUILDSYCOCA_EXENAME))) != 0) 00565 qWarning("ERROR: Running %s failed", KBUILDSYCOCA_EXENAME); 00566 // Old comment, maybe not true anymore: 00567 // Do not wait until the DBUS signal from kbuildsycoca here. 00568 // It deletes m_str which is a problem when flagError is called during the KSycocaFactory ctor... 00569 } 00570 } 00571 00572 #ifndef KDE_NO_DEPRECATED 00573 bool KSycoca::readError() // KDE5: remove 00574 { 00575 return false; 00576 } 00577 #endif 00578 00579 bool KSycoca::isBuilding() 00580 { 00581 return false; 00582 } 00583 00584 void KSycoca::disableAutoRebuild() 00585 { 00586 s_autoRebuild = false; 00587 } 00588 00589 QDataStream*& KSycoca::stream() 00590 { 00591 return d->stream(); 00592 } 00593 00594 void KSycoca::clearCaches() 00595 { 00596 if (ksycocaInstance.exists() && ksycocaInstance->hasSycoca()) 00597 ksycocaInstance->sycoca()->d->closeDatabase(); 00598 } 00599 00600 #include "ksycoca.moc"
KDE 4.6 API Reference