KDECore
ksycoca.cpp
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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
00050 #ifdef Q_WS_WIN
00051 #undef HAVE_MMAP
00052 #endif
00053
00058 #define KSYCOCA_VERSION 143
00059
00063 #define KSYCOCA_FILENAME "ksycoca4"
00064
00065 #if HAVE_MADVISE
00066 #include <sys/mman.h>
00067 #endif
00068
00069 #ifndef MAP_FAILED
00070 #define MAP_FAILED ((void *) -1)
00071 #endif
00072
00073 static bool s_autoRebuild = true;
00074
00075
00076
00077
00078
00079
00080
00081
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
00100
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 == "mmap")
00112 m_sycocaStrategy = StrategyMmap;
00113 else if (strategy == "file")
00114 m_sycocaStrategy = StrategyFile;
00115 else if (strategy == "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 fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
00129 sycoca_size = m_mmapFile->size();
00130 sycoca_mmap = (const char *) mmap(0, sycoca_size,
00131 PROT_READ, MAP_SHARED,
00132 m_mmapFile->handle(), 0);
00133
00134
00135 if (sycoca_mmap == (const char*) MAP_FAILED || sycoca_mmap == 0) {
00136 kDebug(7011) << "mmap failed. (length = " << sycoca_size << ")";
00137 sycoca_mmap = 0;
00138 return false;
00139 } else {
00140 #ifdef HAVE_MADVISE
00141 (void) posix_madvise((void*)sycoca_mmap, sycoca_size, POSIX_MADV_WILLNEED);
00142 #endif // HAVE_MADVISE
00143 return true;
00144 }
00145 #endif // HAVE_MMAP
00146 return false;
00147 }
00148
00149 int KSycoca::version()
00150 {
00151 return KSYCOCA_VERSION;
00152 }
00153
00154 class KSycocaSingleton
00155 {
00156 public:
00157 KSycocaSingleton() { }
00158 ~KSycocaSingleton() { }
00159
00160 bool hasSycoca() const {
00161 return m_threadSycocas.hasLocalData();
00162 }
00163 KSycoca* sycoca() {
00164 if (!m_threadSycocas.hasLocalData())
00165 m_threadSycocas.setLocalData(new KSycoca);
00166 return m_threadSycocas.localData();
00167 }
00168 void setSycoca(KSycoca* s) {
00169 m_threadSycocas.setLocalData(s);
00170 }
00171
00172 private:
00173 QThreadStorage<KSycoca*> m_threadSycocas;
00174 };
00175
00176 K_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
00177
00178
00179 KSycoca::KSycoca()
00180 : d(new KSycocaPrivate)
00181 {
00182 QDBusConnection::sessionBus().connect(QString(), QString(),
00183 "org.kde.KSycoca", "notifyDatabaseChanged",
00184 this, SLOT(notifyDatabaseChanged(QStringList)));
00185 }
00186
00187 bool KSycocaPrivate::openDatabase(bool openDummyIfNotFound)
00188 {
00189 Q_ASSERT(databaseStatus == DatabaseNotOpen);
00190
00191 delete m_device; m_device = 0;
00192 QString path = KSycoca::absoluteFilePath();
00193
00194 bool canRead = KDE::access(path, R_OK) == 0;
00195 kDebug(7011) << "Trying to open ksycoca from " << path;
00196 if (!canRead) {
00197 path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase);
00198 if (!path.isEmpty()) {
00199 kDebug(7011) << "Trying to open global ksycoca from " << path;
00200 canRead = KDE::access(path, R_OK) == 0;
00201 }
00202 }
00203
00204 bool result = true;
00205 if (canRead) {
00206 m_databasePath = path;
00207 checkVersion();
00208 } else {
00209 kDebug(7011) << "Could not open ksycoca";
00210 m_databasePath.clear();
00211 databaseStatus = NoDatabase;
00212 if (openDummyIfNotFound) {
00213
00214
00215
00216 m_sycocaStrategy = StrategyDummyBuffer;
00217 QDataStream* str = stream();
00218 *str << qint32(KSYCOCA_VERSION);
00219 *str << qint32(0);
00220 } else {
00221 result = false;
00222 }
00223 }
00224 return result;
00225 }
00226
00227 KSycocaAbstractDevice* KSycocaPrivate::device()
00228 {
00229 if (m_device)
00230 return m_device;
00231
00232 Q_ASSERT(!m_databasePath.isEmpty());
00233
00234 KSycocaAbstractDevice* device = m_device;
00235 if (m_sycocaStrategy == StrategyDummyBuffer) {
00236 device = new KSycocaBufferDevice;
00237 device->device()->open(QIODevice::ReadOnly);
00238 } else {
00239 #ifdef HAVE_MMAP
00240 if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
00241 device = new KSycocaMmapDevice(sycoca_mmap,
00242 sycoca_size);
00243 if (!device->device()->open(QIODevice::ReadOnly)) {
00244 delete device; device = 0;
00245 }
00246 }
00247 #endif
00248 if (!device && m_sycocaStrategy == StrategyMemFile) {
00249 device = new KSycocaMemFileDevice(m_databasePath);
00250 if (!device->device()->open(QIODevice::ReadOnly)) {
00251 delete device; device = 0;
00252 }
00253 }
00254
00255 if (!device) {
00256 device = new KSycocaFileDevice(m_databasePath);
00257 if (!device->device()->open(QIODevice::ReadOnly)) {
00258 kError(7011) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
00259
00260 }
00261 }
00262 }
00263 if (device) {
00264 m_device = device;
00265 }
00266 return m_device;
00267 }
00268
00269 QDataStream*& KSycocaPrivate::stream()
00270 {
00271 if (!m_device) {
00272 if (databaseStatus == DatabaseNotOpen) {
00273 checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy);
00274 }
00275
00276 device();
00277 }
00278
00279 return m_device->stream();
00280 }
00281
00282
00283 KSycoca::KSycoca( bool )
00284 : d(new KSycocaPrivate)
00285 {
00286
00287 ksycocaInstance->setSycoca(this);
00288 }
00289
00290 KSycoca * KSycoca::self()
00291 {
00292 KSycoca* s = ksycocaInstance->sycoca();
00293 Q_ASSERT(s);
00294 return s;
00295 }
00296
00297 KSycoca::~KSycoca()
00298 {
00299 d->closeDatabase();
00300 delete d;
00301
00302
00303
00304 }
00305
00306 bool KSycoca::isAvailable()
00307 {
00308 return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
00309 }
00310
00311 void KSycocaPrivate::closeDatabase()
00312 {
00313 delete m_device;
00314 m_device = 0;
00315
00316
00317
00318
00319
00320 qDeleteAll(m_factories);
00321 m_factories.clear();
00322 #ifdef HAVE_MMAP
00323 if (sycoca_mmap) {
00324
00325
00326
00327
00328 munmap(const_cast<char*>(sycoca_mmap), sycoca_size);
00329 sycoca_mmap = 0;
00330 }
00331 delete m_mmapFile; m_mmapFile = 0;
00332 #endif
00333
00334 databaseStatus = DatabaseNotOpen;
00335 timeStamp = 0;
00336 }
00337
00338 void KSycoca::addFactory( KSycocaFactory *factory )
00339 {
00340 d->addFactory(factory);
00341 }
00342
00343 bool KSycoca::isChanged(const char *type)
00344 {
00345 return self()->d->changeList.contains(type);
00346 }
00347
00348 void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
00349 {
00350 d->changeList = changeList;
00351
00352
00353
00354
00355
00356 d->closeDatabase();
00357
00358
00359 emit databaseChanged();
00360 emit databaseChanged(changeList);
00361 }
00362
00363 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
00364 {
00365 QDataStream* str = stream();
00366 Q_ASSERT(str);
00367
00368 str->device()->seek(offset);
00369 qint32 aType;
00370 *str >> aType;
00371 type = KSycocaType(aType);
00372
00373 return str;
00374 }
00375
00376 KSycocaFactoryList* KSycoca::factories()
00377 {
00378 return d->factories();
00379 }
00380
00381
00382 bool KSycocaPrivate::checkVersion()
00383 {
00384 QDataStream *m_str = device()->stream();
00385 Q_ASSERT(m_str);
00386 m_str->device()->seek(0);
00387 qint32 aVersion;
00388 *m_str >> aVersion;
00389 if ( aVersion < KSYCOCA_VERSION ) {
00390 kWarning(7011) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
00391 databaseStatus = BadVersion;
00392 return false;
00393 } else {
00394 databaseStatus = DatabaseOK;
00395 return true;
00396 }
00397 }
00398
00399
00400
00401 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
00402 {
00403 if (databaseStatus == DatabaseOK) {
00404 if (checkVersion())
00405 return true;
00406 }
00407
00408 closeDatabase();
00409
00410
00411
00412 const bool kdeinitRunning = QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.klauncher");
00413
00414
00415 if (kdeinitRunning && openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
00416 if (checkVersion()) {
00417
00418 return true;
00419 }
00420 }
00421
00422 if (ifNotFound & IfNotFoundRecreate) {
00423
00424
00425 if (!kdeinitRunning) {
00426 kDebug(7011) << "We have no database.... launching kdeinit";
00427 KToolInvocation::klauncher();
00428
00429 } else {
00430 kDebug(7011) << QThread::currentThread() << "We have no database.... launching" << KBUILDSYCOCA_EXENAME;
00431 if (QProcess::execute(KStandardDirs::findExe(KBUILDSYCOCA_EXENAME)) != 0)
00432 qWarning("ERROR: Running KSycoca failed.");
00433 }
00434
00435 closeDatabase();
00436
00437
00438 if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
00439 kDebug(7011) << "Still no database...";
00440 return false;
00441 }
00442 if (!checkVersion()) {
00443 kDebug(7011) << "Still outdated...";
00444 return false;
00445 }
00446 return true;
00447 }
00448
00449 return false;
00450 }
00451
00452 QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
00453 {
00454
00455 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
00456 return 0;
00457 }
00458
00459 QDataStream* str = stream();
00460 Q_ASSERT(str);
00461
00462 qint32 aId;
00463 qint32 aOffset;
00464 while(true) {
00465 *str >> aId;
00466 if (aId == 0) {
00467 kError(7011) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
00468 break;
00469 }
00470 *str >> aOffset;
00471 if (aId == id) {
00472
00473 str->device()->seek(aOffset);
00474 return str;
00475 }
00476 }
00477 return 0;
00478 }
00479
00480 QString KSycoca::kfsstnd_prefixes()
00481 {
00482
00483 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing))
00484 return QString();
00485 QDataStream* str = stream();
00486 Q_ASSERT(str);
00487 qint32 aId;
00488 qint32 aOffset;
00489
00490 while(true)
00491 {
00492 *str >> aId;
00493 if ( aId )
00494 *str >> aOffset;
00495 else
00496 break;
00497 }
00498
00499 QString prefixes;
00500 KSycocaEntry::read(*str, prefixes);
00501 *str >> d->timeStamp;
00502 KSycocaEntry::read(*str, d->language);
00503 *str >> d->updateSig;
00504 KSycocaEntry::read(*str, d->allResourceDirs);
00505 return prefixes;
00506 }
00507
00508 quint32 KSycoca::timeStamp()
00509 {
00510 if (!d->timeStamp)
00511 (void) kfsstnd_prefixes();
00512 return d->timeStamp;
00513 }
00514
00515 quint32 KSycoca::updateSignature()
00516 {
00517 if (!d->timeStamp)
00518 (void) kfsstnd_prefixes();
00519 return d->updateSig;
00520 }
00521
00522 QString KSycoca::absoluteFilePath(DatabaseType type)
00523 {
00524 if (type == GlobalDatabase)
00525 return KStandardDirs::locate("services", KSYCOCA_FILENAME);
00526
00527 const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
00528 if (ksycoca_env.isEmpty())
00529 return KGlobal::dirs()->saveLocation("cache") + KSYCOCA_FILENAME;
00530 else
00531 return QFile::decodeName(ksycoca_env);
00532 }
00533
00534 QString KSycoca::language()
00535 {
00536 if (d->language.isEmpty())
00537 (void) kfsstnd_prefixes();
00538 return d->language;
00539 }
00540
00541 QStringList KSycoca::allResourceDirs()
00542 {
00543 if (!d->timeStamp)
00544 (void) kfsstnd_prefixes();
00545 return d->allResourceDirs;
00546 }
00547
00548 void KSycoca::flagError()
00549 {
00550 kWarning(7011) << "ERROR: KSycoca database corruption!";
00551 KSycocaPrivate* d = ksycocaInstance->sycoca()->d;
00552 if (d->readError)
00553 return;
00554 d->readError = true;
00555 if (s_autoRebuild) {
00556
00557 if (QProcess::execute(KStandardDirs::findExe(KBUILDSYCOCA_EXENAME)) != 0)
00558 qWarning("ERROR: Running %s failed", KBUILDSYCOCA_EXENAME);
00559
00560
00561
00562 }
00563 }
00564
00565 bool KSycoca::readError()
00566 {
00567 return false;
00568 }
00569
00570 bool KSycoca::isBuilding()
00571 {
00572 return false;
00573 }
00574
00575 void KSycoca::disableAutoRebuild()
00576 {
00577 s_autoRebuild = false;
00578 }
00579
00580 QDataStream*& KSycoca::stream()
00581 {
00582 return d->stream();
00583 }
00584
00585 void KSycoca::clearCaches()
00586 {
00587 if (ksycocaInstance.exists() && ksycocaInstance->hasSycoca())
00588 ksycocaInstance->sycoca()->d->closeDatabase();
00589 }
00590
00591 #include "ksycoca.moc"