summaryrefslogtreecommitdiffstats
path: root/kdecore/ksycoca.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kdecore/ksycoca.cpp')
-rw-r--r--kdecore/ksycoca.cpp524
1 files changed, 524 insertions, 0 deletions
diff --git a/kdecore/ksycoca.cpp b/kdecore/ksycoca.cpp
new file mode 100644
index 000000000..b4a3533fe
--- /dev/null
+++ b/kdecore/ksycoca.cpp
@@ -0,0 +1,524 @@
+/* This file is part of the KDE libraries
+ * Copyright (C) 1999-2000 Waldo Bastian <bastian@kde.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License version 2 as published by the Free Software Foundation;
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ **/
+
+#include "config.h"
+
+#include "ksycoca.h"
+#include "ksycocatype.h"
+#include "ksycocafactory.h"
+
+#include <qdatastream.h>
+#include <qfile.h>
+#include <qbuffer.h>
+
+#include <kapplication.h>
+#include <dcopclient.h>
+#include <kglobal.h>
+#include <kdebug.h>
+#include <kprocess.h>
+#include <kstandarddirs.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#ifdef Q_OS_SOLARIS
+extern "C"
+{
+ extern int madvise(caddr_t, size_t, int);
+}
+#endif
+
+#ifndef MAP_FAILED
+#define MAP_FAILED ((void *) -1)
+#endif
+
+template class QPtrList<KSycocaFactory>;
+
+// The following limitations are in place:
+// Maximum length of a single string: 8192 bytes
+// Maximum length of a string list: 1024 strings
+// Maximum number of entries: 8192
+//
+// The purpose of these limitations is to limit the impact
+// of database corruption.
+
+class KSycocaPrivate {
+public:
+ KSycocaPrivate() {
+ database = 0;
+ readError = false;
+ updateSig = 0;
+ autoRebuild = true;
+ }
+ QFile *database;
+ QStringList changeList;
+ QString language;
+ bool readError;
+ bool autoRebuild;
+ Q_UINT32 updateSig;
+ QStringList allResourceDirs;
+};
+
+int KSycoca::version()
+{
+ return KSYCOCA_VERSION;
+}
+
+// Read-only constructor
+KSycoca::KSycoca()
+ : DCOPObject("ksycoca"), m_lstFactories(0), m_str(0), bNoDatabase(false),
+ m_sycoca_size(0), m_sycoca_mmap(0), m_timeStamp(0)
+{
+ d = new KSycocaPrivate;
+ // Register app as able to receive DCOP messages
+ if (kapp && !kapp->dcopClient()->isAttached())
+ {
+ kapp->dcopClient()->attach();
+ }
+ // We register with DCOP _before_ we try to open the database.
+ // This way we can be relative sure that the KDE framework is
+ // up and running (kdeinit, dcopserver, klaucnher, kded) and
+ // that the database is up to date.
+ openDatabase();
+ _self = this;
+}
+
+bool KSycoca::openDatabase( bool openDummyIfNotFound )
+{
+ bool result = true;
+
+ m_sycoca_mmap = 0;
+ m_str = 0;
+ QString path;
+ QCString ksycoca_env = getenv("KDESYCOCA");
+ if (ksycoca_env.isEmpty())
+ path = KGlobal::dirs()->saveLocation("cache") + "ksycoca";
+ else
+ path = QFile::decodeName(ksycoca_env);
+
+ kdDebug(7011) << "Trying to open ksycoca from " << path << endl;
+ QFile *database = new QFile(path);
+ bool bOpen = database->open( IO_ReadOnly );
+ if (!bOpen)
+ {
+ path = locate("services", "ksycoca");
+ if (!path.isEmpty())
+ {
+ kdDebug(7011) << "Trying to open global ksycoca from " << path << endl;
+ delete database;
+ database = new QFile(path);
+ bOpen = database->open( IO_ReadOnly );
+ }
+ }
+
+ if (bOpen)
+ {
+ fcntl(database->handle(), F_SETFD, FD_CLOEXEC);
+ m_sycoca_size = database->size();
+#ifdef HAVE_MMAP
+ m_sycoca_mmap = (const char *) mmap(0, m_sycoca_size,
+ PROT_READ, MAP_SHARED,
+ database->handle(), 0);
+ /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
+ null pointer too. */
+ if (m_sycoca_mmap == (const char*) MAP_FAILED || m_sycoca_mmap == 0)
+ {
+ kdDebug(7011) << "mmap failed. (length = " << m_sycoca_size << ")" << endl;
+#endif
+ m_str = new QDataStream(database);
+#ifdef HAVE_MMAP
+ }
+ else
+ {
+#ifdef HAVE_MADVISE
+ (void) madvise((char*)m_sycoca_mmap, m_sycoca_size, MADV_WILLNEED);
+#endif
+ QByteArray b_array;
+ b_array.setRawData(m_sycoca_mmap, m_sycoca_size);
+ QBuffer *buffer = new QBuffer( b_array );
+ buffer->open(IO_ReadWrite);
+ m_str = new QDataStream( buffer);
+ }
+#endif
+ bNoDatabase = false;
+ }
+ else
+ {
+ kdDebug(7011) << "Could not open ksycoca" << endl;
+
+ // No database file
+ delete database;
+ database = 0;
+
+ bNoDatabase = true;
+ if (openDummyIfNotFound)
+ {
+ // We open a dummy database instead.
+ //kdDebug(7011) << "No database, opening a dummy one." << endl;
+ QBuffer *buffer = new QBuffer( QByteArray() );
+ buffer->open(IO_ReadWrite);
+ m_str = new QDataStream( buffer);
+ (*m_str) << (Q_INT32) KSYCOCA_VERSION;
+ (*m_str) << (Q_INT32) 0;
+ }
+ else
+ {
+ result = false;
+ }
+ }
+ m_lstFactories = new KSycocaFactoryList();
+ m_lstFactories->setAutoDelete( true );
+ d->database = database;
+ return result;
+}
+
+// Read-write constructor - only for KBuildSycoca
+KSycoca::KSycoca( bool /* dummy */ )
+ : DCOPObject("ksycoca_building"), m_lstFactories(0), m_str(0), bNoDatabase(false),
+ m_sycoca_size(0), m_sycoca_mmap(0)
+{
+ d = new KSycocaPrivate;
+ m_lstFactories = new KSycocaFactoryList();
+ m_lstFactories->setAutoDelete( true );
+ _self = this;
+}
+
+static void delete_ksycoca_self() {
+ delete KSycoca::_self;
+}
+
+KSycoca * KSycoca::self()
+{
+ if (!_self) {
+ qAddPostRoutine(delete_ksycoca_self);
+ _self = new KSycoca();
+ }
+ return _self;
+}
+
+KSycoca::~KSycoca()
+{
+ closeDatabase();
+ delete d;
+ _self = 0L;
+}
+
+void KSycoca::closeDatabase()
+{
+ QIODevice *device = 0;
+ if (m_str)
+ device = m_str->device();
+#ifdef HAVE_MMAP
+ if (device && m_sycoca_mmap)
+ {
+ QBuffer *buf = (QBuffer *) device;
+ buf->buffer().resetRawData(m_sycoca_mmap, m_sycoca_size);
+ // Solaris has munmap(char*, size_t) and everything else should
+ // be happy with a char* for munmap(void*, size_t)
+ munmap((char*) m_sycoca_mmap, m_sycoca_size);
+ m_sycoca_mmap = 0;
+ }
+#endif
+
+ delete m_str;
+ m_str = 0;
+ delete device;
+ if (d->database != device)
+ delete d->database;
+ device = 0;
+ d->database = 0;
+ // It is very important to delete all factories here
+ // since they cache information about the database file
+ delete m_lstFactories;
+ m_lstFactories = 0L;
+}
+
+void KSycoca::addFactory( KSycocaFactory *factory )
+{
+ assert(m_lstFactories);
+ m_lstFactories->append(factory);
+}
+
+bool KSycoca::isChanged(const char *type)
+{
+ return self()->d->changeList.contains(type);
+}
+
+void KSycoca::notifyDatabaseChanged(const QStringList &changeList)
+{
+ d->changeList = changeList;
+ //kdDebug(7011) << "got a notifyDatabaseChanged signal !" << endl;
+ // kded tells us the database file changed
+ // Close the database and forget all about what we knew
+ // The next call to any public method will recreate
+ // everything that's needed.
+ closeDatabase();
+
+ // Now notify applications
+ emit databaseChanged();
+}
+
+QDataStream * KSycoca::findEntry(int offset, KSycocaType &type)
+{
+ if ( !m_str )
+ openDatabase();
+ //kdDebug(7011) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16) << endl;
+ m_str->device()->at(offset);
+ Q_INT32 aType;
+ (*m_str) >> aType;
+ type = (KSycocaType) aType;
+ //kdDebug(7011) << QString("KSycoca::found type %1").arg(aType) << endl;
+ return m_str;
+}
+
+bool KSycoca::checkVersion(bool abortOnError)
+{
+ if ( !m_str )
+ {
+ if( !openDatabase(false /* don't open dummy db if not found */) )
+ return false; // No database found
+
+ // We should never get here... if a database was found then m_str shouldn't be 0L.
+ assert(m_str);
+ }
+ m_str->device()->at(0);
+ Q_INT32 aVersion;
+ (*m_str) >> aVersion;
+ if ( aVersion < KSYCOCA_VERSION )
+ {
+ kdWarning(7011) << "Found version " << aVersion << ", expecting version " << KSYCOCA_VERSION << " or higher." << endl;
+ if (!abortOnError) return false;
+ kdError(7011) << "Outdated database ! Stop kded and restart it !" << endl;
+ abort();
+ }
+ return true;
+}
+
+QDataStream * KSycoca::findFactory(KSycocaFactoryId id)
+{
+ // The constructor found no database, but we want one
+ if (bNoDatabase)
+ {
+ closeDatabase(); // close the dummy one
+ // Check if new database already available
+ if ( !openDatabase(false /* no dummy one*/) )
+ {
+ static bool triedLaunchingKdeinit = false;
+ if (!triedLaunchingKdeinit) // try only once
+ {
+ triedLaunchingKdeinit = true;
+ kdDebug(7011) << "findFactory: we have no database.... launching kdeinit" << endl;
+ KApplication::startKdeinit();
+ // Ok, the new database should be here now, open it.
+ }
+ if (!openDatabase(false))
+ return 0L; // Still no database - uh oh
+ }
+ }
+ // rewind and check
+ if (!checkVersion(false))
+ {
+ kdWarning(7011) << "Outdated database found" << endl;
+ return 0L;
+ }
+ Q_INT32 aId;
+ Q_INT32 aOffset;
+ while(true)
+ {
+ (*m_str) >> aId;
+ //kdDebug(7011) << QString("KSycoca::findFactory : found factory %1").arg(aId) << endl;
+ if (aId == 0)
+ {
+ kdError(7011) << "Error, KSycocaFactory (id = " << int(id) << ") not found!" << endl;
+ break;
+ }
+ (*m_str) >> aOffset;
+ if (aId == id)
+ {
+ //kdDebug(7011) << QString("KSycoca::findFactory(%1) offset %2").arg((int)id).arg(aOffset) << endl;
+ m_str->device()->at(aOffset);
+ return m_str;
+ }
+ }
+ return 0;
+}
+
+QString KSycoca::kfsstnd_prefixes()
+{
+ if (bNoDatabase) return "";
+ if (!checkVersion(false)) return "";
+ Q_INT32 aId;
+ Q_INT32 aOffset;
+ // skip factories offsets
+ while(true)
+ {
+ (*m_str) >> aId;
+ if ( aId )
+ (*m_str) >> aOffset;
+ else
+ break; // just read 0
+ }
+ // We now point to the header
+ QString prefixes;
+ KSycocaEntry::read(*m_str, prefixes);
+ (*m_str) >> m_timeStamp;
+ KSycocaEntry::read(*m_str, d->language);
+ (*m_str) >> d->updateSig;
+ KSycocaEntry::read(*m_str, d->allResourceDirs);
+ return prefixes;
+}
+
+Q_UINT32 KSycoca::timeStamp()
+{
+ if (!m_timeStamp)
+ (void) kfsstnd_prefixes();
+ return m_timeStamp;
+}
+
+Q_UINT32 KSycoca::updateSignature()
+{
+ if (!m_timeStamp)
+ (void) kfsstnd_prefixes();
+ return d->updateSig;
+}
+
+QString KSycoca::language()
+{
+ if (d->language.isEmpty())
+ (void) kfsstnd_prefixes();
+ return d->language;
+}
+
+QStringList KSycoca::allResourceDirs()
+{
+ if (!m_timeStamp)
+ (void) kfsstnd_prefixes();
+ return d->allResourceDirs;
+}
+
+QString KSycoca::determineRelativePath( const QString & _fullpath, const char *_resource )
+{
+ QString sRelativeFilePath;
+ QStringList dirs = KGlobal::dirs()->resourceDirs( _resource );
+ QStringList::ConstIterator dirsit = dirs.begin();
+ for ( ; dirsit != dirs.end() && sRelativeFilePath.isEmpty(); ++dirsit ) {
+ // might need canonicalPath() ...
+ if ( _fullpath.find( *dirsit ) == 0 ) // path is dirs + relativePath
+ sRelativeFilePath = _fullpath.mid( (*dirsit).length() ); // skip appsdirs
+ }
+ if ( sRelativeFilePath.isEmpty() )
+ kdFatal(7011) << QString("Couldn't find %1 in any %2 dir !!!").arg( _fullpath ).arg( _resource) << endl;
+ //else
+ // debug code
+ //kdDebug(7011) << sRelativeFilePath << endl;
+ return sRelativeFilePath;
+}
+
+KSycoca * KSycoca::_self = 0L;
+
+void KSycoca::flagError()
+{
+ qWarning("ERROR: KSycoca database corruption!");
+ if (_self)
+ {
+ if (_self->d->readError)
+ return;
+ _self->d->readError = true;
+ if (_self->d->autoRebuild)
+ if(system("kbuildsycoca") < 0) // Rebuild the damned thing.
+ qWarning("ERROR: Running KSycoca failed.");
+ }
+}
+
+void KSycoca::disableAutoRebuild()
+{
+ d->autoRebuild = false;
+}
+
+bool KSycoca::readError()
+{
+ bool b = false;
+ if (_self)
+ {
+ b = _self->d->readError;
+ _self->d->readError = false;
+ }
+ return b;
+}
+
+void KSycocaEntry::read( QDataStream &s, QString &str )
+{
+ Q_UINT32 bytes;
+ s >> bytes; // read size of string
+ if ( bytes > 8192 ) { // null string or too big
+ if (bytes != 0xffffffff)
+ KSycoca::flagError();
+ str = QString::null;
+ }
+ else if ( bytes > 0 ) { // not empty
+ int bt = bytes/2;
+ str.setLength( bt );
+ QChar* ch = (QChar *) str.unicode();
+ char t[8192];
+ char *b = t;
+ s.readRawBytes( b, bytes );
+ while ( bt-- ) {
+ *ch++ = (ushort) (((ushort)b[0])<<8) | (uchar)b[1];
+ b += 2;
+ }
+ } else {
+ str = "";
+ }
+}
+
+void KSycocaEntry::read( QDataStream &s, QStringList &list )
+{
+ list.clear();
+ Q_UINT32 count;
+ s >> count; // read size of list
+ if (count >= 1024)
+ {
+ KSycoca::flagError();
+ return;
+ }
+ for(Q_UINT32 i = 0; i < count; i++)
+ {
+ QString str;
+ read(s, str);
+ list.append( str );
+ if (s.atEnd())
+ {
+ KSycoca::flagError();
+ return;
+ }
+ }
+}
+
+void KSycoca::virtual_hook( int id, void* data )
+{ DCOPObject::virtual_hook( id, data ); }
+
+void KSycocaEntry::virtual_hook( int, void* )
+{ /*BASE::virtual_hook( id, data );*/ }
+
+#include "ksycoca.moc"