00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "ksycoca.h"
00020 #include "ksycocatype.h"
00021 #include "ksycocafactory.h"
00022 #include "ktoolinvocation.h"
00023 #include "kglobal.h"
00024 #include "kmemfile.h"
00025
00026 #include "kdebug.h"
00027 #include "kstandarddirs.h"
00028
00029 #include <QtCore/QDataStream>
00030 #include <QtCore/QCoreApplication>
00031 #include <QtCore/QFile>
00032 #include <QtCore/QBuffer>
00033 #include <QProcess>
00034 #include <QtDBus/QtDBus>
00035
00036 #include <config.h>
00037
00038 #include <stdlib.h>
00039 #include <fcntl.h>
00040
00041 #ifdef Q_OS_WIN
00042
00043
00044
00045
00046 #undef HAVE_MMAP
00047 #endif
00048
00049 #ifdef HAVE_SYS_MMAN_H
00050 #include <sys/mman.h>
00051 #endif
00052
00053 #ifdef Q_OS_SOLARIS
00054 extern "C" int madvise(caddr_t, size_t, int);
00055 #endif
00056
00057 #ifndef MAP_FAILED
00058 #define MAP_FAILED ((void *) -1)
00059 #endif
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069 class KSycocaPrivate
00070 {
00071 public:
00072 KSycocaPrivate()
00073 : databaseStatus( DatabaseNotOpen ),
00074 readError( false ),
00075 autoRebuild( true ),
00076 sycoca_size( 0 ),
00077 sycoca_mmap( 0 ),
00078 timeStamp( 0 ),
00079 m_database( 0 ),
00080 m_dummyBuffer(0),
00081 updateSig( 0 ),
00082 lstFactories( 0 )
00083 {
00084 }
00085
00086 static void delete_ksycoca_self() {
00087 delete _self;
00088 _self = 0;
00089 }
00090
00091 bool checkVersion();
00092 bool openDatabase(bool openDummyIfNotFound=true);
00093 enum BehaviorIfNotFound {
00094 IfNotFoundDoNothing = 0,
00095 IfNotFoundOpenDummy = 1,
00096 IfNotFoundRecreate = 2
00097 };
00098 Q_DECLARE_FLAGS(BehaviorsIfNotFound, BehaviorIfNotFound)
00099 bool checkDatabase(BehaviorsIfNotFound ifNotFound);
00100 void closeDatabase();
00101
00102 enum {
00103 DatabaseNotOpen,
00104 NoDatabase,
00105 BadVersion,
00106 DatabaseOK } databaseStatus;
00107 bool readError;
00108 bool autoRebuild;
00109 size_t sycoca_size;
00110 const char *sycoca_mmap;
00111 quint32 timeStamp;
00112 #ifdef Q_OS_WIN
00113 KMemFile *m_database;
00114 #else
00115 QFile *m_database;
00116 #endif
00117 QBuffer* m_dummyBuffer;
00118 QStringList changeList;
00119 QString language;
00120 quint32 updateSig;
00121 QStringList allResourceDirs;
00122 KSycocaFactoryList *lstFactories;
00123 static KSycoca *_self;
00124 };
00125 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
00126
00127 KSycoca * KSycocaPrivate::_self = 0L;
00128
00129 int KSycoca::version()
00130 {
00131 return KSYCOCA_VERSION;
00132 }
00133
00134
00135 KSycoca::KSycoca()
00136 : m_str(0),
00137 d(new KSycocaPrivate)
00138 {
00139 QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KSycoca", "notifyDatabaseChanged",
00140 this, SLOT(notifyDatabaseChanged(QStringList)));
00141 KSycocaPrivate::_self = this;
00142
00143
00144
00145
00146
00147
00148
00149
00150 d->openDatabase();
00151 }
00152
00153 bool KSycocaPrivate::openDatabase( bool openDummyIfNotFound )
00154 {
00155 bool result = true;
00156
00157 sycoca_mmap = 0;
00158 QDataStream* &m_str = KSycocaPrivate::_self->m_str;
00159 m_str = 0;
00160 delete m_dummyBuffer;
00161 m_dummyBuffer = 0;
00162 QString path = KSycoca::absoluteFilePath();
00163
00164 kDebug(7011) << "Trying to open ksycoca from " << path;
00165 #ifdef Q_OS_WIN
00166 m_database = new KMemFile(path);
00167 #else
00168 m_database = new QFile(path);
00169 #endif
00170 bool bOpen = m_database->open( QIODevice::ReadOnly );
00171 if (!bOpen)
00172 {
00173 path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase);
00174 if (!path.isEmpty())
00175 {
00176 kDebug(7011) << "Trying to open global ksycoca from " << path;
00177 delete m_database;
00178 #ifdef Q_OS_WIN
00179 m_database = new KMemFile(path);
00180 #else
00181 m_database = new QFile(path);
00182 #endif
00183 bOpen = m_database->open( QIODevice::ReadOnly );
00184 }
00185 }
00186
00187 if (bOpen)
00188 {
00189 #ifdef Q_OS_WIN
00190 m_str = new QDataStream(m_database);
00191 m_str->setVersion(QDataStream::Qt_3_1);
00192 sycoca_mmap = 0;
00193 #else // Q_OS_WIN
00194 fcntl(m_database->handle(), F_SETFD, FD_CLOEXEC);
00195 sycoca_size = m_database->size();
00196 #ifdef HAVE_MMAP
00197 sycoca_mmap = (const char *) mmap(0, sycoca_size,
00198 PROT_READ, MAP_SHARED,
00199 m_database->handle(), 0);
00200
00201
00202 if (sycoca_mmap == (const char*) MAP_FAILED || sycoca_mmap == 0)
00203 {
00204 kDebug(7011) << "mmap failed. (length = " << sycoca_size << ")";
00205 #endif // HAVE_MMAP
00206 m_str = new QDataStream(m_database);
00207 m_str->setVersion(QDataStream::Qt_3_1);
00208 sycoca_mmap = 0;
00209 #ifdef HAVE_MMAP
00210 }
00211 else
00212 {
00213 #ifdef HAVE_MADVISE
00214 (void) madvise((char*)sycoca_mmap, sycoca_size, MADV_WILLNEED);
00215 #endif // HAVE_MADVISE
00216 m_dummyBuffer = new QBuffer;
00217 m_dummyBuffer->setData(QByteArray::fromRawData(sycoca_mmap, sycoca_size));
00218 m_dummyBuffer->open(QIODevice::ReadOnly);
00219 m_str = new QDataStream(m_dummyBuffer);
00220 m_str->setVersion(QDataStream::Qt_3_1);
00221 }
00222 #endif // HAVE_MMAP
00223 #endif // !Q_OS_WIN
00224 checkVersion();
00225 }
00226 else
00227 {
00228 kDebug(7011) << "Could not open ksycoca";
00229
00230
00231 delete m_database;
00232 m_database = 0;
00233
00234 databaseStatus = NoDatabase;
00235 if (openDummyIfNotFound)
00236 {
00237
00238
00239 m_dummyBuffer = new QBuffer;
00240 m_dummyBuffer->open(QIODevice::ReadWrite);
00241 m_str = new QDataStream(m_dummyBuffer);
00242 m_str->setVersion(QDataStream::Qt_3_1);
00243 *m_str << qint32(KSYCOCA_VERSION);
00244 *m_str << qint32(0);
00245 }
00246 else
00247 {
00248 result = false;
00249 }
00250 }
00251 lstFactories = new KSycocaFactoryList;
00252 return result;
00253 }
00254
00255
00256 KSycoca::KSycoca( bool )
00257 : m_str(0),
00258 d(new KSycocaPrivate)
00259 {
00260 QDBusConnection::sessionBus().registerObject("/ksycoca_building", this, QDBusConnection::ExportScriptableSlots);
00261 d->lstFactories = new KSycocaFactoryList;
00262 KSycocaPrivate::_self = this;
00263 }
00264
00265 KSycoca * KSycoca::self()
00266 {
00267 if (!KSycocaPrivate::_self) {
00268 qAddPostRoutine(KSycocaPrivate::delete_ksycoca_self);
00269 KSycocaPrivate::_self = new KSycoca;
00270 }
00271 return KSycocaPrivate::_self;
00272 }
00273
00274 KSycoca::~KSycoca()
00275 {
00276 d->closeDatabase();
00277 delete d;
00278 KSycocaPrivate::_self = 0L;
00279 }
00280
00281 bool KSycoca::isAvailable()
00282 {
00283 return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
00284 }
00285
00286 void KSycocaPrivate::closeDatabase()
00287 {
00288 QDataStream* &m_str = KSycocaPrivate::_self->m_str;
00289 QIODevice *device = 0;
00290 #ifdef HAVE_MMAP
00291 if (device && sycoca_mmap)
00292 {
00293 QBuffer *buf = static_cast<QBuffer*>(device);
00294 buf->buffer().clear();
00295
00296
00297 munmap(const_cast<char*>(sycoca_mmap), sycoca_size);
00298 sycoca_mmap = 0;
00299 }
00300 #endif
00301
00302 delete m_str;
00303 m_str = 0;
00304 delete m_dummyBuffer;
00305 m_dummyBuffer = 0;
00306 delete device;
00307 if (m_database != device)
00308 delete m_database;
00309 device = 0;
00310 m_database = 0;
00311
00312
00313 if ( lstFactories )
00314 qDeleteAll( *lstFactories );
00315 delete lstFactories;
00316 lstFactories = 0;
00317 databaseStatus = DatabaseNotOpen;
00318 }
00319
00320 void KSycoca::addFactory( KSycocaFactory *factory )
00321 {
00322 Q_ASSERT(d->lstFactories != 0);
00323 d->lstFactories->append(factory);
00324 }
00325
00326 bool KSycoca::isChanged(const char *type)
00327 {
00328 return self()->d->changeList.contains(type);
00329 }
00330
00331 void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
00332 {
00333 d->changeList = changeList;
00334
00335
00336
00337
00338
00339 d->closeDatabase();
00340
00341
00342 emit databaseChanged();
00343 }
00344
00345 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
00346 {
00347 if ( !m_str )
00348 d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy);
00349 Q_ASSERT(m_str);
00350
00351 m_str->device()->seek(offset);
00352 qint32 aType;
00353 *m_str >> aType;
00354 type = KSycocaType(aType);
00355
00356 return m_str;
00357 }
00358
00359 KSycocaFactoryList* KSycoca::factories()
00360 {
00361 return d->lstFactories;
00362 }
00363
00364
00365 bool KSycocaPrivate::checkVersion()
00366 {
00367 QDataStream *m_str = KSycocaPrivate::_self->m_str;
00368 Q_ASSERT(m_str);
00369 m_str->device()->seek(0);
00370 qint32 aVersion;
00371 *m_str >> aVersion;
00372 if ( aVersion < KSYCOCA_VERSION ) {
00373 kWarning(7011) << "Found version " << aVersion << ", expecting version " << KSYCOCA_VERSION << " or higher.";
00374 databaseStatus = BadVersion;
00375 return false;
00376 } else {
00377 databaseStatus = DatabaseOK;
00378 return true;
00379 }
00380 }
00381
00382
00383
00384 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
00385 {
00386 QDataStream* &m_str = KSycocaPrivate::_self->m_str;
00387 if (databaseStatus == DatabaseOK) {
00388 Q_ASSERT(m_str);
00389 if (checkVersion())
00390 return true;
00391 }
00392
00393 closeDatabase();
00394
00395 if( openDatabase(ifNotFound & IfNotFoundOpenDummy) ) {
00396 Q_ASSERT(m_str);
00397 if (checkVersion()) {
00398
00399 return true;
00400 }
00401 }
00402
00403 static bool triedLaunchingKdeinit = false;
00404 if ((ifNotFound & IfNotFoundRecreate) && !triedLaunchingKdeinit) {
00405 triedLaunchingKdeinit = true;
00406
00407
00408 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.klauncher")) {
00409 kDebug(7011) << "We have no database.... launching kdeinit";
00410 KToolInvocation::klauncher();
00411 } else {
00412 kDebug(7011) << "We have no database.... launching " << KBUILDSYCOCA_EXENAME;
00413 if (QProcess::execute(KStandardDirs::findExe(KBUILDSYCOCA_EXENAME)) != 0)
00414 qWarning("ERROR: Running KSycoca failed.");
00415 }
00416
00417
00418 QEventLoop eventLoop;
00419 QObject::connect(KSycoca::self(), SIGNAL(databaseChanged()), &eventLoop, SLOT(quit()));
00420 eventLoop.exec( QEventLoop::ExcludeUserInputEvents );
00421
00422
00423 if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
00424 kDebug(7011) << "Still no database...";
00425 return false;
00426 }
00427 if (!checkVersion()) {
00428 kDebug(7011) << "Still outdated...";
00429 return false;
00430 }
00431 return true;
00432 }
00433
00434 return false;
00435 }
00436
00437 QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
00438 {
00439
00440 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
00441 return 0;
00442 }
00443
00444 qint32 aId;
00445 qint32 aOffset;
00446 while(true) {
00447 *m_str >> aId;
00448 if (aId == 0) {
00449 kError(7011) << "Error, KSycocaFactory (id = " << int(id) << ") not found!" << endl;
00450 break;
00451 }
00452 *m_str >> aOffset;
00453 if (aId == id) {
00454
00455 m_str->device()->seek(aOffset);
00456 return m_str;
00457 }
00458 }
00459 return 0;
00460 }
00461
00462 QString KSycoca::kfsstnd_prefixes()
00463 {
00464
00465 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) return "";
00466 qint32 aId;
00467 qint32 aOffset;
00468
00469 while(true)
00470 {
00471 *m_str >> aId;
00472 if ( aId )
00473 *m_str >> aOffset;
00474 else
00475 break;
00476 }
00477
00478 QString prefixes;
00479 KSycocaEntry::read(*m_str, prefixes);
00480 *m_str >> d->timeStamp;
00481 KSycocaEntry::read(*m_str, d->language);
00482 *m_str >> d->updateSig;
00483 KSycocaEntry::read(*m_str, d->allResourceDirs);
00484 return prefixes;
00485 }
00486
00487 quint32 KSycoca::timeStamp()
00488 {
00489 if (!d->timeStamp)
00490 (void) kfsstnd_prefixes();
00491 return d->timeStamp;
00492 }
00493
00494 quint32 KSycoca::updateSignature()
00495 {
00496 if (!d->timeStamp)
00497 (void) kfsstnd_prefixes();
00498 return d->updateSig;
00499 }
00500
00501 QString KSycoca::absoluteFilePath(DatabaseType type)
00502 {
00503 if (type == GlobalDatabase)
00504 return KStandardDirs::locate("services", KSYCOCA_FILENAME);
00505
00506 const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
00507 if (ksycoca_env.isEmpty())
00508 return KGlobal::dirs()->saveLocation("cache") + KSYCOCA_FILENAME;
00509 else
00510 return QFile::decodeName(ksycoca_env);
00511 }
00512
00513 QString KSycoca::language()
00514 {
00515 if (d->language.isEmpty())
00516 (void) kfsstnd_prefixes();
00517 return d->language;
00518 }
00519
00520 QStringList KSycoca::allResourceDirs()
00521 {
00522 if (!d->timeStamp)
00523 (void) kfsstnd_prefixes();
00524 return d->allResourceDirs;
00525 }
00526
00527 #if 0
00528 QString KSycoca::determineRelativePath( const QString & _fullpath, const char *_resource )
00529 {
00530 QString sRelativeFilePath;
00531 QStringList dirs = KGlobal::dirs()->resourceDirs( _resource );
00532 QStringList::ConstIterator dirsit = dirs.begin();
00533 for ( ; dirsit != dirs.end() && sRelativeFilePath.isEmpty(); ++dirsit ) {
00534
00535 if ( _fullpath.indexOf( *dirsit ) == 0 )
00536 sRelativeFilePath = _fullpath.mid( (*dirsit).length() );
00537 }
00538 if ( sRelativeFilePath.isEmpty() )
00539 kFatal(7011) << QString("Couldn't find %1 in any %2 dir !!!").arg( _fullpath ).arg( _resource);
00540
00541
00542
00543 return sRelativeFilePath;
00544 }
00545 #endif
00546
00547 void KSycoca::flagError()
00548 {
00549 kWarning(7011) << "ERROR: KSycoca database corruption!";
00550 if (KSycocaPrivate::_self)
00551 {
00552 if (KSycocaPrivate::_self->d->readError)
00553 return;
00554 KSycocaPrivate::_self->d->readError = true;
00555 if (KSycocaPrivate::_self->d->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::isBuilding()
00566 {
00567 return false;
00568 }
00569
00570 void KSycoca::disableAutoRebuild()
00571 {
00572 d->autoRebuild = false;
00573 }
00574
00575 bool KSycoca::readError()
00576 {
00577 bool b = false;
00578 if (KSycocaPrivate::_self)
00579 {
00580 b = KSycocaPrivate::_self->d->readError;
00581 KSycocaPrivate::_self->d->readError = false;
00582 }
00583 return b;
00584 }
00585
00586 #include "ksycoca.moc"