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.
20 #include "ksycocatype.h"
21 #include "ksycocafactory.h"
22 #include "ktoolinvocation.h"
27 #include "kstandarddirs.h"
29 #include <QtCore/QDataStream>
30 #include <QtCore/QCoreApplication>
31 #include <QtCore/QFile>
32 #include <QtCore/QBuffer>
34 #include <QtDBus/QtDBus>
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
49 * Sycoca file name, used internally (by kbuildsycoca)
51 #define KSYCOCA_FILENAME "ksycoca4"
55 on windows we use KMemFile (QSharedMemory) to avoid problems
56 with mmap (can't delete a mmap'd file)
61 #ifdef HAVE_SYS_MMAN_H
66 extern "C" int madvise(caddr_t
, size_t, int);
70 #define MAP_FAILED ((void *) -1)
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.
85 : databaseStatus( DatabaseNotOpen
),
98 static void delete_ksycoca_self() {
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();
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
;
122 const char *sycoca_mmap
;
125 KMemFile
*m_database
;
129 QBuffer
* m_dummyBuffer
;
130 QStringList changeList
;
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
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.
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.
165 bool KSycocaPrivate::openDatabase( bool openDummyIfNotFound
)
170 QDataStream
* &m_str
= KSycocaPrivate::_self
->m_str
;
172 delete m_dummyBuffer
;
174 QString path
= KSycoca::absoluteFilePath();
176 kDebug(7011) << "Trying to open ksycoca from " << path
;
178 m_database
= new KMemFile(path
);
180 m_database
= new QFile(path
);
182 bool bOpen
= m_database
->open( QIODevice::ReadOnly
);
185 path
= KSycoca::absoluteFilePath(KSycoca::GlobalDatabase
);
188 kDebug(7011) << "Trying to open global ksycoca from " << path
;
191 m_database
= new KMemFile(path
);
193 m_database
= new QFile(path
);
195 bOpen
= m_database
->open( QIODevice::ReadOnly
);
202 m_str
= new QDataStream(m_database
);
203 m_str
->setVersion(QDataStream::Qt_3_1
);
206 fcntl(m_database
->handle(), F_SETFD
, FD_CLOEXEC
);
207 sycoca_size
= m_database
->size();
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
214 if (sycoca_mmap
== (const char*) MAP_FAILED
|| sycoca_mmap
== 0)
216 kDebug(7011) << "mmap failed. (length = " << sycoca_size
<< ")";
218 m_str
= new QDataStream(m_database
);
219 m_str
->setVersion(QDataStream::Qt_3_1
);
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
);
240 kDebug(7011) << "Could not open ksycoca";
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
);
263 lstFactories
= new KSycocaFactoryList
;
267 // Read-write constructor - only for KBuildSycoca
268 KSycoca::KSycoca( bool /* dummy */ )
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
;
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;
303 device
= m_str
->device();
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
);
316 delete m_dummyBuffer
;
318 if (m_database
!= device
)
324 // It is very important to delete all factories here
325 // since they cache information about the database file
327 qDeleteAll( *lstFactories
);
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.
354 // Now notify applications
355 emit
databaseChanged();
358 QDataStream
* KSycoca::findEntry(int offset
, KSycocaType
&type
)
361 d
->checkDatabase(KSycocaPrivate::IfNotFoundRecreate
| KSycocaPrivate::IfNotFoundOpenDummy
);
363 //kDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
364 m_str
->device()->seek(offset
);
367 type
= KSycocaType(aType
);
368 //kDebug(7011) << QString("KSycoca::found type %1").arg(aType);
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
;
382 m_str
->device()->seek(0);
385 if ( aVersion
< KSYCOCA_VERSION
) {
386 kWarning(7011) << "Found version " << aVersion
<< ", expecting version " << KSYCOCA_VERSION
<< " or higher.";
387 databaseStatus
= BadVersion
;
390 databaseStatus
= DatabaseOK
;
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
) {
402 if (checkVersion()) // we know the version is ok, but we must rewind the stream anyway
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.
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
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
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
)) {
462 kError(7011) << "Error, KSycocaFactory (id = " << int(id
) << ") not found!" << endl
;
467 //kDebug(7011) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
468 m_str
->device()->seek(aOffset
);
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 "";
481 // skip factories offsets
488 break; // just read 0
490 // We now point to the header
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
);
500 quint32
KSycoca::timeStamp()
503 (void) kfsstnd_prefixes();
507 quint32
KSycoca::updateSignature()
510 (void) kfsstnd_prefixes();
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
;
523 return QFile::decodeName(ksycoca_env
);
526 QString
KSycoca::language()
528 if (d
->language
.isEmpty())
529 (void) kfsstnd_prefixes();
533 QStringList
KSycoca::allResourceDirs()
536 (void) kfsstnd_prefixes();
537 return d
->allResourceDirs
;
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
);
555 //kDebug(7011) << sRelativeFilePath;
556 return sRelativeFilePath
;
560 void KSycoca::flagError()
562 kWarning(7011) << "ERROR: KSycoca database corruption!";
563 if (KSycocaPrivate::_self
)
565 if (KSycocaPrivate::_self
->d
->readError
)
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()
583 void KSycoca::disableAutoRebuild()
585 d
->autoRebuild
= false;
588 bool KSycoca::readError()
591 if (KSycocaPrivate::_self
)
593 b
= KSycocaPrivate::_self
->d
->readError
;
594 KSycocaPrivate::_self
->d
->readError
= false;
599 #include "ksycoca.moc"