fix logic
[personal-kdelibs.git] / kdecore / sycoca / ksycoca.cpp
blobda79952475b7c92daacf52bf4ed6fc7446786d8a
1 /* This file is part of the KDE libraries
2 * Copyright (C) 1999-2000 Waldo Bastian <bastian@kde.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License version 2 as published by the Free Software Foundation;
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Library General Public License for more details.
13 * You should have received a copy of the GNU Library General Public License
14 * along with this library; see the file COPYING.LIB. If not, write to
15 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 **/
19 #include "ksycoca.h"
20 #include "ksycocatype.h"
21 #include "ksycocafactory.h"
22 #include "ktoolinvocation.h"
23 #include "kglobal.h"
24 #include "kmemfile.h"
26 #include "kdebug.h"
27 #include "kstandarddirs.h"
29 #include <QtCore/QDataStream>
30 #include <QtCore/QCoreApplication>
31 #include <QtCore/QFile>
32 #include <QtCore/QBuffer>
33 #include <QProcess>
34 #include <QtDBus/QtDBus>
36 #include <config.h>
38 #include <stdlib.h>
39 #include <fcntl.h>
41 /**
42 * Sycoca file version number.
43 * If the existing file is outdated, it will not get read
44 * but instead we'll ask kded to regenerate a new one...
46 #define KSYCOCA_VERSION 141
48 /**
49 * Sycoca file name, used internally (by kbuildsycoca)
51 #define KSYCOCA_FILENAME "ksycoca4"
53 #ifdef Q_OS_WIN
55 on windows we use KMemFile (QSharedMemory) to avoid problems
56 with mmap (can't delete a mmap'd file)
58 #undef HAVE_MMAP
59 #endif
61 #ifdef HAVE_SYS_MMAN_H
62 #include <sys/mman.h>
63 #endif
65 #ifdef Q_OS_SOLARIS
66 extern "C" int madvise(caddr_t, size_t, int);
67 #endif
69 #ifndef MAP_FAILED
70 #define MAP_FAILED ((void *) -1)
71 #endif
73 // The following limitations are in place:
74 // Maximum length of a single string: 8192 bytes
75 // Maximum length of a string list: 1024 strings
76 // Maximum number of entries: 8192
78 // The purpose of these limitations is to limit the impact
79 // of database corruption.
81 class KSycocaPrivate
83 public:
84 KSycocaPrivate()
85 : databaseStatus( DatabaseNotOpen ),
86 readError( false ),
87 autoRebuild( true ),
88 sycoca_size( 0 ),
89 sycoca_mmap( 0 ),
90 timeStamp( 0 ),
91 m_database( 0 ),
92 m_dummyBuffer(0),
93 updateSig( 0 ),
94 lstFactories( 0 )
98 static void delete_ksycoca_self() {
99 delete _self;
100 _self = 0;
103 bool checkVersion();
104 bool openDatabase(bool openDummyIfNotFound=true);
105 enum BehaviorIfNotFound {
106 IfNotFoundDoNothing = 0,
107 IfNotFoundOpenDummy = 1,
108 IfNotFoundRecreate = 2
110 Q_DECLARE_FLAGS(BehaviorsIfNotFound, BehaviorIfNotFound)
111 bool checkDatabase(BehaviorsIfNotFound ifNotFound);
112 void closeDatabase();
114 enum {
115 DatabaseNotOpen, // m_str is 0, openDatabase must be called
116 NoDatabase, // not found, so we opened a dummy one instead
117 BadVersion, // it's opened, but it's not useable
118 DatabaseOK } databaseStatus;
119 bool readError;
120 bool autoRebuild;
121 size_t sycoca_size;
122 const char *sycoca_mmap;
123 quint32 timeStamp;
124 #ifdef Q_OS_WIN
125 KMemFile *m_database;
126 #else
127 QFile *m_database;
128 #endif
129 QBuffer* m_dummyBuffer;
130 QStringList changeList;
131 QString language;
132 quint32 updateSig;
133 QStringList allResourceDirs;
134 KSycocaFactoryList *lstFactories;
135 static KSycoca *_self;
137 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
139 KSycoca * KSycocaPrivate::_self = 0L;
141 int KSycoca::version()
143 return KSYCOCA_VERSION;
146 // Read-only constructor
147 KSycoca::KSycoca()
148 : m_str(0),
149 d(new KSycocaPrivate)
151 QDBusConnection::sessionBus().connect(QString(), QString(), "org.kde.KSycoca", "notifyDatabaseChanged",
152 this, SLOT(notifyDatabaseChanged(QStringList)));
153 KSycocaPrivate::_self = this;
154 // We register with D-Bus _before_ we try to open the database.
155 // This way we can be relatively sure that the KDE framework is
156 // up and running (kdeinit, klauncher, kded) and
157 // that the database is up to date.
159 // -> huh? -thiago
160 // This is because dcopserver was autostarted (via kdeinit) when trying to register to dcop. - David
161 // But the "launching kdeinit" case below takes care of it.
162 d->openDatabase();
165 bool KSycocaPrivate::openDatabase( bool openDummyIfNotFound )
167 bool result = true;
169 sycoca_mmap = 0;
170 QDataStream* &m_str = KSycocaPrivate::_self->m_str;
171 m_str = 0;
172 delete m_dummyBuffer;
173 m_dummyBuffer = 0;
174 QString path = KSycoca::absoluteFilePath();
176 kDebug(7011) << "Trying to open ksycoca from " << path;
177 #ifdef Q_OS_WIN
178 m_database = new KMemFile(path);
179 #else
180 m_database = new QFile(path);
181 #endif
182 bool bOpen = m_database->open( QIODevice::ReadOnly );
183 if (!bOpen)
185 path = KSycoca::absoluteFilePath(KSycoca::GlobalDatabase);
186 if (!path.isEmpty())
188 kDebug(7011) << "Trying to open global ksycoca from " << path;
189 delete m_database;
190 #ifdef Q_OS_WIN
191 m_database = new KMemFile(path);
192 #else
193 m_database = new QFile(path);
194 #endif
195 bOpen = m_database->open( QIODevice::ReadOnly );
199 if (bOpen)
201 #ifdef Q_OS_WIN
202 m_str = new QDataStream(m_database);
203 m_str->setVersion(QDataStream::Qt_3_1);
204 sycoca_mmap = 0;
205 #else // Q_OS_WIN
206 fcntl(m_database->handle(), F_SETFD, FD_CLOEXEC);
207 sycoca_size = m_database->size();
208 #ifdef HAVE_MMAP
209 sycoca_mmap = (const char *) mmap(0, sycoca_size,
210 PROT_READ, MAP_SHARED,
211 m_database->handle(), 0);
212 /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
213 null pointer too. */
214 if (sycoca_mmap == (const char*) MAP_FAILED || sycoca_mmap == 0)
216 kDebug(7011) << "mmap failed. (length = " << sycoca_size << ")";
217 #endif // HAVE_MMAP
218 m_str = new QDataStream(m_database);
219 m_str->setVersion(QDataStream::Qt_3_1);
220 sycoca_mmap = 0;
221 #ifdef HAVE_MMAP
223 else
225 #ifdef HAVE_MADVISE
226 (void) madvise((char*)sycoca_mmap, sycoca_size, MADV_WILLNEED);
227 #endif // HAVE_MADVISE
228 m_dummyBuffer = new QBuffer;
229 m_dummyBuffer->setData(QByteArray::fromRawData(sycoca_mmap, sycoca_size));
230 m_dummyBuffer->open(QIODevice::ReadOnly);
231 m_str = new QDataStream(m_dummyBuffer);
232 m_str->setVersion(QDataStream::Qt_3_1);
234 #endif // HAVE_MMAP
235 #endif // !Q_OS_WIN
236 checkVersion();
238 else
240 kDebug(7011) << "Could not open ksycoca";
242 // No database file
243 delete m_database;
244 m_database = 0;
246 databaseStatus = NoDatabase;
247 if (openDummyIfNotFound)
249 // We open a dummy database instead.
250 //kDebug(7011) << "No database, opening a dummy one.";
251 m_dummyBuffer = new QBuffer;
252 m_dummyBuffer->open(QIODevice::ReadWrite);
253 m_str = new QDataStream(m_dummyBuffer);
254 m_str->setVersion(QDataStream::Qt_3_1);
255 *m_str << qint32(KSYCOCA_VERSION);
256 *m_str << qint32(0);
258 else
260 result = false;
263 lstFactories = new KSycocaFactoryList;
264 return result;
267 // Read-write constructor - only for KBuildSycoca
268 KSycoca::KSycoca( bool /* dummy */ )
269 : m_str(0),
270 d(new KSycocaPrivate)
272 QDBusConnection::sessionBus().registerObject("/ksycoca_building", this, QDBusConnection::ExportScriptableSlots);
273 d->lstFactories = new KSycocaFactoryList;
274 KSycocaPrivate::_self = this;
277 KSycoca * KSycoca::self()
279 if (!KSycocaPrivate::_self) {
280 qAddPostRoutine(KSycocaPrivate::delete_ksycoca_self);
281 KSycocaPrivate::_self = new KSycoca;
283 return KSycocaPrivate::_self;
286 KSycoca::~KSycoca()
288 d->closeDatabase();
289 delete d;
290 KSycocaPrivate::_self = 0L;
293 bool KSycoca::isAvailable()
295 return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing/* don't open dummy db if not found */);
298 void KSycocaPrivate::closeDatabase()
300 QDataStream* &m_str = KSycocaPrivate::_self->m_str;
301 QIODevice *device = 0;
302 if (m_str)
303 device = m_str->device();
304 #ifdef HAVE_MMAP
305 if (device && sycoca_mmap)
307 QBuffer *buf = static_cast<QBuffer*>(device);
308 buf->buffer().clear();
309 // Solaris has munmap(char*, size_t) and everything else should
310 // be happy with a char* for munmap(void*, size_t)
311 munmap(const_cast<char*>(sycoca_mmap), sycoca_size);
312 sycoca_mmap = 0;
314 #endif
316 delete m_dummyBuffer;
317 m_dummyBuffer = 0;
318 if (m_database != device)
319 delete m_database;
320 device = 0;
321 m_database = 0;
322 delete m_str;
323 m_str = 0;
324 // It is very important to delete all factories here
325 // since they cache information about the database file
326 if ( lstFactories )
327 qDeleteAll( *lstFactories );
328 delete lstFactories;
329 lstFactories = 0;
330 databaseStatus = DatabaseNotOpen;
333 void KSycoca::addFactory( KSycocaFactory *factory )
335 Q_ASSERT(d->lstFactories != 0);
336 d->lstFactories->append(factory);
339 bool KSycoca::isChanged(const char *type)
341 return self()->d->changeList.contains(type);
344 void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
346 d->changeList = changeList;
347 //kDebug() << "got a notifyDatabaseChanged signal" << changeList;
348 // kded tells us the database file changed
349 // Close the database and forget all about what we knew
350 // The next call to any public method will recreate
351 // everything that's needed.
352 d->closeDatabase();
354 // Now notify applications
355 emit databaseChanged();
358 QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
360 if ( !m_str )
361 d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate | KSycocaPrivate::IfNotFoundOpenDummy);
362 Q_ASSERT(m_str);
363 //kDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
364 m_str->device()->seek(offset);
365 qint32 aType;
366 *m_str >> aType;
367 type = KSycocaType(aType);
368 //kDebug(7011) << QString("KSycoca::found type %1").arg(aType);
369 return m_str;
372 KSycocaFactoryList* KSycoca::factories()
374 return d->lstFactories;
377 // Warning, checkVersion rewinds to the beginning of m_str.
378 bool KSycocaPrivate::checkVersion()
380 QDataStream *m_str = KSycocaPrivate::_self->m_str;
381 Q_ASSERT(m_str);
382 m_str->device()->seek(0);
383 qint32 aVersion;
384 *m_str >> aVersion;
385 if ( aVersion < KSYCOCA_VERSION ) {
386 kWarning(7011) << "Found version " << aVersion << ", expecting version " << KSYCOCA_VERSION << " or higher.";
387 databaseStatus = BadVersion;
388 return false;
389 } else {
390 databaseStatus = DatabaseOK;
391 return true;
395 // If it returns true, we have a valid database and the stream has rewinded to the beginning
396 // and past the version number.
397 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
399 QDataStream* &m_str = KSycocaPrivate::_self->m_str;
400 if (databaseStatus == DatabaseOK) {
401 Q_ASSERT(m_str);
402 if (checkVersion()) // we know the version is ok, but we must rewind the stream anyway
403 return true;
406 closeDatabase(); // close the dummy one
407 // Check if new database already available
408 if( openDatabase(ifNotFound & IfNotFoundOpenDummy) ) {
409 Q_ASSERT(m_str); // if a database was found then m_str shouldn't be 0
410 if (checkVersion()) {
411 // Database exists, and version is ok.
412 return true;
416 static bool triedLaunchingKdeinit = false;
417 if ((ifNotFound & IfNotFoundRecreate) && !triedLaunchingKdeinit) { // try only once
418 triedLaunchingKdeinit = true;
419 // Well, if kdeinit is not running we need to launch it,
420 // but otherwise we simply need to run kbuildsycoca to recreate the sycoca file.
421 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.klauncher")) {
422 kDebug(7011) << "We have no database.... launching kdeinit";
423 KToolInvocation::klauncher(); // this calls startKdeinit
424 } else {
425 kDebug(7011) << "We have no database.... launching " << KBUILDSYCOCA_EXENAME;
426 if (QProcess::execute(KStandardDirs::findExe(KBUILDSYCOCA_EXENAME)) != 0)
427 qWarning("ERROR: Running KSycoca failed.");
430 // Wait until the DBUS signal from kbuildsycoca
431 QEventLoop eventLoop;
432 QObject::connect(KSycoca::self(), SIGNAL(databaseChanged()), &eventLoop, SLOT(quit()));
433 eventLoop.exec( QEventLoop::ExcludeUserInputEvents );
435 // Ok, the new database should be here now, open it.
436 if (!openDatabase(ifNotFound & IfNotFoundOpenDummy)) {
437 kDebug(7011) << "Still no database...";
438 return false; // Still no database - uh oh
440 if (!checkVersion()) {
441 kDebug(7011) << "Still outdated...";
442 return false; // Still outdated - uh oh
444 return true;
447 return false;
450 QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
452 // Ensure we have a valid database (right version, and rewinded to beginning)
453 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
454 return 0;
457 qint32 aId;
458 qint32 aOffset;
459 while(true) {
460 *m_str >> aId;
461 if (aId == 0) {
462 kError(7011) << "Error, KSycocaFactory (id = " << int(id) << ") not found!" << endl;
463 break;
465 *m_str >> aOffset;
466 if (aId == id) {
467 //kDebug(7011) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
468 m_str->device()->seek(aOffset);
469 return m_str;
472 return 0;
475 QString KSycoca::kfsstnd_prefixes()
477 // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
478 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) return "";
479 qint32 aId;
480 qint32 aOffset;
481 // skip factories offsets
482 while(true)
484 *m_str >> aId;
485 if ( aId )
486 *m_str >> aOffset;
487 else
488 break; // just read 0
490 // We now point to the header
491 QString prefixes;
492 KSycocaEntry::read(*m_str, prefixes);
493 *m_str >> d->timeStamp;
494 KSycocaEntry::read(*m_str, d->language);
495 *m_str >> d->updateSig;
496 KSycocaEntry::read(*m_str, d->allResourceDirs);
497 return prefixes;
500 quint32 KSycoca::timeStamp()
502 if (!d->timeStamp)
503 (void) kfsstnd_prefixes();
504 return d->timeStamp;
507 quint32 KSycoca::updateSignature()
509 if (!d->timeStamp)
510 (void) kfsstnd_prefixes();
511 return d->updateSig;
514 QString KSycoca::absoluteFilePath(DatabaseType type)
516 if (type == GlobalDatabase)
517 return KStandardDirs::locate("services", KSYCOCA_FILENAME);
519 const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
520 if (ksycoca_env.isEmpty())
521 return KGlobal::dirs()->saveLocation("cache") + KSYCOCA_FILENAME;
522 else
523 return QFile::decodeName(ksycoca_env);
526 QString KSycoca::language()
528 if (d->language.isEmpty())
529 (void) kfsstnd_prefixes();
530 return d->language;
533 QStringList KSycoca::allResourceDirs()
535 if (!d->timeStamp)
536 (void) kfsstnd_prefixes();
537 return d->allResourceDirs;
540 #if 0
541 QString KSycoca::determineRelativePath( const QString & _fullpath, const char *_resource )
543 QString sRelativeFilePath;
544 QStringList dirs = KGlobal::dirs()->resourceDirs( _resource );
545 QStringList::ConstIterator dirsit = dirs.begin();
546 for ( ; dirsit != dirs.end() && sRelativeFilePath.isEmpty(); ++dirsit ) {
547 // might need canonicalPath() ...
548 if ( _fullpath.indexOf( *dirsit ) == 0 ) // path is dirs + relativePath
549 sRelativeFilePath = _fullpath.mid( (*dirsit).length() ); // skip appsdirs
551 if ( sRelativeFilePath.isEmpty() )
552 kFatal(7011) << QString("Couldn't find %1 in any %2 dir !!!").arg( _fullpath ).arg( _resource);
553 //else
554 // debug code
555 //kDebug(7011) << sRelativeFilePath;
556 return sRelativeFilePath;
558 #endif
560 void KSycoca::flagError()
562 kWarning(7011) << "ERROR: KSycoca database corruption!";
563 if (KSycocaPrivate::_self)
565 if (KSycocaPrivate::_self->d->readError)
566 return;
567 KSycocaPrivate::_self->d->readError = true;
568 if (KSycocaPrivate::_self->d->autoRebuild) {
569 // Rebuild the damned thing.
570 if (QProcess::execute(KStandardDirs::findExe(KBUILDSYCOCA_EXENAME)) != 0)
571 qWarning("ERROR: Running %s failed", KBUILDSYCOCA_EXENAME);
572 // Do not wait until the DBUS signal from kbuildsycoca here.
573 // It deletes m_str which is a problem when flagError is called during the KSycocaFactory ctor...
578 bool KSycoca::isBuilding()
580 return false;
583 void KSycoca::disableAutoRebuild()
585 d->autoRebuild = false;
588 bool KSycoca::readError()
590 bool b = false;
591 if (KSycocaPrivate::_self)
593 b = KSycocaPrivate::_self->d->readError;
594 KSycocaPrivate::_self->d->readError = false;
596 return b;
599 #include "ksycoca.moc"