summaryrefslogtreecommitdiffstats
path: root/tdecore/kconfigbackend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tdecore/kconfigbackend.cpp')
-rw-r--r--tdecore/kconfigbackend.cpp1186
1 files changed, 1186 insertions, 0 deletions
diff --git a/tdecore/kconfigbackend.cpp b/tdecore/kconfigbackend.cpp
new file mode 100644
index 000000000..633458d41
--- /dev/null
+++ b/tdecore/kconfigbackend.cpp
@@ -0,0 +1,1186 @@
+/*
+ This file is part of the KDE libraries
+ Copyright (c) 1999 Preston Brown <pbrown@kde.org>
+ Copyright (c) 1997-1999 Matthias Kalle Dalheimer <kalle@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 as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ 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 <unistd.h>
+#include <ctype.h>
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <fcntl.h>
+#include <signal.h>
+#include <setjmp.h>
+
+#include <tqdir.h>
+#include <tqfileinfo.h>
+#include <tqtextcodec.h>
+#include <tqtextstream.h>
+
+#include "kconfigbackend.h"
+#include "kconfigbase.h"
+#include <kapplication.h>
+#include <kglobal.h>
+#include <kprocess.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <ksavefile.h>
+#include <kurl.h>
+#include <kde_file.h>
+
+extern bool checkAccess(const TQString& pathname, int mode);
+/* translate escaped escape sequences to their actual values. */
+static TQCString printableToString(const char *str, int l)
+{
+ // Strip leading white-space.
+ while((l>0) &&
+ ((*str == ' ') || (*str == '\t') || (*str == '\r')))
+ {
+ str++; l--;
+ }
+
+ // Strip trailing white-space.
+ while((l>0) &&
+ ((str[l-1] == ' ') || (str[l-1] == '\t') || (str[l-1] == '\r')))
+ {
+ l--;
+ }
+
+ TQCString result(l + 1);
+ char *r = result.data();
+
+ for(int i = 0; i < l;i++, str++)
+ {
+ if (*str == '\\')
+ {
+ i++, str++;
+ if (i >= l) // End of line. (Line ends with single slash)
+ {
+ *r++ = '\\';
+ break;
+ }
+ switch(*str)
+ {
+ case 's':
+ *r++ = ' ';
+ break;
+ case 't':
+ *r++ = '\t';
+ break;
+ case 'n':
+ *r++ = '\n';
+ break;
+ case 'r':
+ *r++ = '\r';
+ break;
+ case '\\':
+ *r++ = '\\';
+ break;
+ default:
+ *r++ = '\\';
+ *r++ = *str;
+ }
+ }
+ else
+ {
+ *r++ = *str;
+ }
+ }
+ result.truncate(r-result.data());
+ return result;
+}
+
+static TQCString stringToPrintable(const TQCString& str){
+ TQCString result(str.length()*2); // Maximum 2x as long as source string
+ register char *r = const_cast<TQCString&>(result).data();
+ register char *s = const_cast<TQCString&>(str).data();
+
+ if (!s) return TQCString("");
+
+ // Escape leading space
+ if (*s == ' ')
+ {
+ *r++ = '\\'; *r++ = 's';
+ s++;
+ }
+
+ if (*s)
+ {
+ while(*s)
+ {
+ if (*s == '\n')
+ {
+ *r++ = '\\'; *r++ = 'n';
+ }
+ else if (*s == '\t')
+ {
+ *r++ = '\\'; *r++ = 't';
+ }
+ else if (*s == '\r')
+ {
+ *r++ = '\\'; *r++ = 'r';
+ }
+ else if (*s == '\\')
+ {
+ *r++ = '\\'; *r++ = '\\';
+ }
+ else
+ {
+ *r++ = *s;
+ }
+ s++;
+ }
+ // Escape trailing space
+ if (*(r-1) == ' ')
+ {
+ *(r-1) = '\\'; *r++ = 's';
+ }
+ }
+
+ result.truncate(r - result.data());
+ return result;
+}
+
+static TQCString decodeGroup(const char*s, int l)
+{
+ TQCString result(l);
+ register char *r = result.data();
+
+ l--; // Correct for trailing \0
+ while(l)
+ {
+ if ((*s == '[') && (l > 1))
+ {
+ if ((*(s+1) == '['))
+ {
+ l--;
+ s++;
+ }
+ }
+ if ((*s == ']') && (l > 1))
+ {
+ if ((*(s+1) == ']'))
+ {
+ l--;
+ s++;
+ }
+ }
+ *r++ = *s++;
+ l--;
+ }
+ result.truncate(r - result.data());
+ return result;
+}
+
+static TQCString encodeGroup(const TQCString &str)
+{
+ int l = str.length();
+ TQCString result(l*2+1);
+ register char *r = const_cast<TQCString&>(result).data();
+ register char *s = const_cast<TQCString&>(str).data();
+ while(l)
+ {
+ if ((*s == '[') || (*s == ']'))
+ *r++ = *s;
+ *r++ = *s++;
+ l--;
+ }
+ result.truncate(r - result.data());
+ return result;
+}
+
+static TQCString encodeKey(const char* key)
+{
+ TQCString newKey(key);
+
+ newKey.replace('[', "%5b");
+ newKey.replace(']', "%5d");
+
+ return newKey;
+}
+
+static TQCString decodeKey(const char* key)
+{
+ TQCString newKey(key);
+
+ newKey.replace("%5b", "[");
+ newKey.replace("%5d", "]");
+
+ return newKey;
+}
+
+class KConfigBackEnd::KConfigBackEndPrivate
+{
+public:
+ TQDateTime localLastModified;
+ uint localLastSize;
+ KLockFile::Ptr localLockFile;
+ KLockFile::Ptr globalLockFile;
+};
+
+void KConfigBackEnd::changeFileName(const TQString &_fileName,
+ const char * _resType,
+ bool _useKDEGlobals)
+{
+ mfileName = _fileName;
+ resType = _resType;
+ useKDEGlobals = _useKDEGlobals;
+ if (mfileName.isEmpty())
+ mLocalFileName = TQString::null;
+ else if (!TQDir::isRelativePath(mfileName))
+ mLocalFileName = mfileName;
+ else
+ mLocalFileName = KGlobal::dirs()->saveLocation(resType) + mfileName;
+
+ if (useKDEGlobals)
+ mGlobalFileName = KGlobal::dirs()->saveLocation("config") +
+ TQString::tqfromLatin1("kdeglobals");
+ else
+ mGlobalFileName = TQString::null;
+
+ d->localLastModified = TQDateTime();
+ d->localLastSize = 0;
+ d->localLockFile = 0;
+ d->globalLockFile = 0;
+}
+
+KLockFile::Ptr KConfigBackEnd::lockFile(bool bGlobal)
+{
+ if (bGlobal)
+ {
+ if (d->globalLockFile)
+ return d->globalLockFile;
+
+ if (!mGlobalFileName.isEmpty())
+ {
+ d->globalLockFile = new KLockFile(mGlobalFileName+".lock");
+ return d->globalLockFile;
+ }
+ }
+ else
+ {
+ if (d->localLockFile)
+ return d->localLockFile;
+
+ if (!mLocalFileName.isEmpty())
+ {
+ d->localLockFile = new KLockFile(mLocalFileName+".lock");
+ return d->localLockFile;
+ }
+ }
+ return 0;
+}
+
+KConfigBackEnd::KConfigBackEnd(KConfigBase *_config,
+ const TQString &_fileName,
+ const char * _resType,
+ bool _useKDEGlobals)
+ : pConfig(_config), bFileImmutable(false), mConfigState(KConfigBase::NoAccess), mFileMode(-1)
+{
+ d = new KConfigBackEndPrivate;
+ changeFileName(_fileName, _resType, _useKDEGlobals);
+}
+
+KConfigBackEnd::~KConfigBackEnd()
+{
+ delete d;
+}
+
+void KConfigBackEnd::setFileWriteMode(int mode)
+{
+ mFileMode = mode;
+}
+
+bool KConfigINIBackEnd::parseConfigFiles()
+{
+ // Check if we can write to the local file.
+ mConfigState = KConfigBase::ReadOnly;
+ if (!mLocalFileName.isEmpty() && !pConfig->isReadOnly())
+ {
+ if (checkAccess(mLocalFileName, W_OK))
+ {
+ mConfigState = KConfigBase::ReadWrite;
+ }
+ else
+ {
+ // Create the containing dir, maybe it wasn't there
+ KURL path;
+ path.setPath(mLocalFileName);
+ TQString dir=path.directory();
+ KStandardDirs::makeDir(dir);
+
+ if (checkAccess(mLocalFileName, W_OK))
+ {
+ mConfigState = KConfigBase::ReadWrite;
+ }
+ }
+ TQFileInfo info(mLocalFileName);
+ d->localLastModified = info.lastModified();
+ d->localLastSize = info.size();
+ }
+
+ // Parse all desired files from the least to the most specific.
+ bFileImmutable = false;
+
+ // Parse the general config files
+ if (useKDEGlobals) {
+ TQStringList kdercs = KGlobal::dirs()->
+ findAllResources("config", TQString::tqfromLatin1("kdeglobals"));
+
+#ifdef Q_WS_WIN
+ TQString etc_kderc = TQFile::decodeName( TQCString(getenv("WINDIR")) + "\\kderc" );
+#else
+ TQString etc_kderc = TQString::tqfromLatin1("/etc/kderc");
+#endif
+
+ if (checkAccess(etc_kderc, R_OK))
+ kdercs += etc_kderc;
+
+ kdercs += KGlobal::dirs()->
+ findAllResources("config", TQString::tqfromLatin1("system.kdeglobals"));
+
+ TQStringList::ConstIterator it;
+
+ for (it = kdercs.fromLast(); it != kdercs.end(); --it) {
+
+ TQFile aConfigFile( *it );
+ if (!aConfigFile.open( IO_ReadOnly ))
+ continue;
+ parseSingleConfigFile( aConfigFile, 0L, true, (*it != mGlobalFileName) );
+ aConfigFile.close();
+ if (bFileImmutable)
+ break;
+ }
+ }
+
+ bool bReadFile = !mfileName.isEmpty();
+ while(bReadFile) {
+ bReadFile = false;
+ TQString bootLanguage;
+ if (useKDEGlobals && localeString.isEmpty() && !KGlobal::_locale) {
+ // Boot strap language
+ bootLanguage = KLocale::_initLanguage(pConfig);
+ setLocaleString(bootLanguage.utf8());
+ }
+
+ bFileImmutable = false;
+ TQStringList list;
+ if ( !TQDir::isRelativePath(mfileName) )
+ list << mfileName;
+ else
+ list = KGlobal::dirs()->findAllResources(resType, mfileName);
+
+ TQStringList::ConstIterator it;
+
+ for (it = list.fromLast(); it != list.end(); --it) {
+
+ TQFile aConfigFile( *it );
+ // we can already be sure that this file exists
+ bool bIsLocal = (*it == mLocalFileName);
+ if (aConfigFile.open( IO_ReadOnly )) {
+ parseSingleConfigFile( aConfigFile, 0L, false, !bIsLocal );
+ aConfigFile.close();
+ if (bFileImmutable)
+ break;
+ }
+ }
+ if (KGlobal::dirs()->isRestrictedResource(resType, mfileName))
+ bFileImmutable = true;
+ TQString currentLanguage;
+ if (!bootLanguage.isEmpty())
+ {
+ currentLanguage = KLocale::_initLanguage(pConfig);
+ // If the file changed the language, we need to read the file again
+ // with the new language setting.
+ if (bootLanguage != currentLanguage)
+ {
+ bReadFile = true;
+ setLocaleString(currentLanguage.utf8());
+ }
+ }
+ }
+ if (bFileImmutable)
+ mConfigState = KConfigBase::ReadOnly;
+
+ return true;
+}
+
+#ifdef HAVE_MMAP
+#ifdef SIGBUS
+static sigjmp_buf mmap_jmpbuf;
+struct sigaction mmap_old_sigact;
+
+extern "C" {
+ static void mmap_sigbus_handler(int)
+ {
+ siglongjmp (mmap_jmpbuf, 1);
+ }
+}
+#endif
+#endif
+
+extern bool kde_kiosk_exception;
+
+void KConfigINIBackEnd::parseSingleConfigFile(TQFile &rFile,
+ KEntryMap *pWriteBackMap,
+ bool bGlobal, bool bDefault)
+{
+ const char *s; // May get clobbered by sigsetjump, but we don't use them afterwards.
+ const char *eof; // May get clobbered by sigsetjump, but we don't use them afterwards.
+ TQByteArray data;
+
+ if (!rFile.isOpen()) // come back, if you have real work for us ;->
+ return;
+
+ //using kdDebug() here leads to an infinite loop
+ //remove this for the release, aleXXX
+ //qWarning("Parsing %s, global = %s default = %s",
+ // rFile.name().latin1(), bGlobal ? "true" : "false", bDefault ? "true" : "false");
+
+ TQCString aCurrentGroup("<default>");
+
+ unsigned int ll = localeString.length();
+
+#ifdef HAVE_MMAP
+ static volatile const char *map;
+ map = ( const char* ) mmap(0, rFile.size(), PROT_READ, MAP_PRIVATE,
+ rFile.handle(), 0);
+
+ if ( map != MAP_FAILED )
+ {
+ s = (const char*) map;
+ eof = s + rFile.size();
+
+#ifdef SIGBUS
+ struct sigaction act;
+ act.sa_handler = mmap_sigbus_handler;
+ sigemptyset( &act.sa_mask );
+#ifdef SA_ONESHOT
+ act.sa_flags = SA_ONESHOT;
+#else
+ act.sa_flags = SA_RESETHAND;
+#endif
+ sigaction( SIGBUS, &act, &mmap_old_sigact );
+
+ if (sigsetjmp (mmap_jmpbuf, 1))
+ {
+qWarning("SIGBUS while reading %s", rFile.name().latin1());
+ munmap(( char* )map, rFile.size());
+ sigaction (SIGBUS, &mmap_old_sigact, 0);
+ return;
+ }
+#endif
+ }
+ else
+#endif
+ {
+ rFile.tqat(0);
+ data = rFile.readAll();
+ s = data.data();
+ eof = s + data.size();
+ }
+
+ bool fileOptionImmutable = false;
+ bool groupOptionImmutable = false;
+ bool groupSkip = false;
+ bool foundGettextDomain = false;
+ TQCString gettextDomain;
+
+ int line = 0;
+ for(; s < eof; s++)
+ {
+ line++;
+
+ while((s < eof) && isspace(*s) && (*s != '\n'))
+ s++; //skip leading whitespace, shouldn't happen too often
+
+ //skip empty lines, lines starting with #
+ if ((s < eof) && ((*s == '\n') || (*s == '#')))
+ {
+ sktoeol: //skip till end-of-line
+ while ((s < eof) && (*s != '\n'))
+ s++;
+ continue; // Empty or comment or no keyword
+ }
+ const char *startLine = s;
+
+ if (*s == '[') //group
+ {
+ // In a group [[ and ]] have a special meaning
+ while ((s < eof) && (*s != '\n'))
+ {
+ if (*s == ']')
+ {
+ if ((s+1 < eof) && (*(s+1) == ']'))
+ s++; // Skip "]]"
+ else
+ break;
+ }
+
+ s++; // Search till end of group
+ }
+ const char *e = s;
+ while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file
+ if ((e >= eof) || (*e != ']'))
+ {
+ fprintf(stderr, "Invalid group header at %s:%d\n", rFile.name().latin1(), line);
+ continue;
+ }
+ // group found; get the group name by taking everything in
+ // between the brackets
+ if ((e-startLine == 3) &&
+ (startLine[1] == '$') &&
+ (startLine[2] == 'i'))
+ {
+ if (!kde_kiosk_exception)
+ fileOptionImmutable = true;
+ continue;
+ }
+
+ aCurrentGroup = decodeGroup(startLine + 1, e - startLine);
+ //cout<<"found group ["<<aCurrentGroup<<"]"<<endl;
+
+ // Backwards compatibility
+ if (aCurrentGroup == "KDE Desktop Entry")
+ aCurrentGroup = "Desktop Entry";
+
+ groupOptionImmutable = fileOptionImmutable;
+
+ e++;
+ if ((e+2 < eof) && (*e++ == '[') && (*e++ == '$')) // Option follows
+ {
+ if ((*e == 'i') && !kde_kiosk_exception)
+ {
+ groupOptionImmutable = true;
+ }
+ }
+
+ KEntryKey groupKey(aCurrentGroup, 0);
+ KEntry entry = pConfig->lookupData(groupKey);
+ groupSkip = entry.bImmutable;
+
+ if (groupSkip && !bDefault)
+ continue;
+
+ entry.bImmutable |= groupOptionImmutable;
+ pConfig->putData(groupKey, entry, false);
+
+ if (pWriteBackMap)
+ {
+ // add the special group key indicator
+ (*pWriteBackMap)[groupKey] = entry;
+ }
+
+ continue;
+ }
+ if (groupSkip && !bDefault)
+ goto sktoeol; // Skip entry
+
+
+ bool optionImmutable = groupOptionImmutable;
+ bool optionDeleted = false;
+ bool optionExpand = false;
+ const char *endOfKey = 0, *locale = 0, *elocale = 0;
+ for (; (s < eof) && (*s != '\n'); s++)
+ {
+ if (*s == '=') //find the equal sign
+ {
+ if (!endOfKey)
+ endOfKey = s;
+ goto haveeq;
+ }
+ if (*s == '[') //find the locale or options.
+ {
+ const char *option;
+ const char *eoption;
+ endOfKey = s;
+ option = ++s;
+ for (;; s++)
+ {
+ if ((s >= eof) || (*s == '\n') || (*s == '=')) {
+ fprintf(stderr, "Invalid entry (missing ']') at %s:%d\n", rFile.name().latin1(), line);
+ goto sktoeol;
+ }
+ if (*s == ']')
+ break;
+ }
+ eoption = s;
+ if (*option != '$')
+ {
+ // Locale
+ if (locale) {
+ fprintf(stderr, "Invalid entry (second locale!?) at %s:%d\n", rFile.name().latin1(), line);
+ goto sktoeol;
+ }
+ locale = option;
+ elocale = eoption;
+ }
+ else
+ {
+ // Option
+ while (option < eoption)
+ {
+ option++;
+ if ((*option == 'i') && !kde_kiosk_exception)
+ optionImmutable = true;
+ else if (*option == 'e')
+ optionExpand = true;
+ else if (*option == 'd')
+ {
+ optionDeleted = true;
+ goto haveeq;
+ }
+ else if (*option == ']')
+ break;
+ }
+ }
+ }
+ }
+ fprintf(stderr, "Invalid entry (missing '=') at %s:%d\n", rFile.name().latin1(), line);
+ continue;
+
+ haveeq:
+ for (endOfKey--; ; endOfKey--)
+ {
+ if (endOfKey < startLine)
+ {
+ fprintf(stderr, "Invalid entry (empty key) at %s:%d\n", rFile.name().latin1(), line);
+ goto sktoeol;
+ }
+ if (!isspace(*endOfKey))
+ break;
+ }
+
+ const char *st = ++s;
+ while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file
+
+ if (locale) {
+ unsigned int cl = static_cast<unsigned int>(elocale - locale);
+ if ((ll != cl) || memcmp(locale, localeString.data(), ll))
+ {
+ // backward compatibility. C == en_US
+ if ( cl != 1 || ll != 5 || *locale != 'C' || memcmp(localeString.data(), "en_US", 5)) {
+ //cout<<"mismatched locale '"<<TQCString(locale, elocale-locale +1)<<"'"<<endl;
+ // We can ignore this one
+ if (!pWriteBackMap)
+ continue; // We just ignore it
+ // We just store it as is to be able to write it back later.
+ endOfKey = elocale;
+ locale = 0;
+ }
+ }
+ }
+
+ // insert the key/value line
+ TQCString key(startLine, endOfKey - startLine + 2);
+ TQCString val = printableToString(st, s - st);
+ //qDebug("found key '%s' with value '%s'", key.data(), val.data());
+
+ if (TQString(key.data()) == "X-Ubuntu-Gettext-Domain") {
+ gettextDomain = val.data();
+ foundGettextDomain = true;
+ }
+
+ KEntryKey aEntryKey(aCurrentGroup, decodeKey(key));
+ aEntryKey.bLocal = (locale != 0);
+ aEntryKey.bDefault = bDefault;
+
+ KEntry aEntry;
+ aEntry.mValue = val;
+ aEntry.bGlobal = bGlobal;
+ aEntry.bImmutable = optionImmutable;
+ aEntry.bDeleted = optionDeleted;
+ aEntry.bExpand = optionExpand;
+ aEntry.bNLS = (locale != 0);
+
+ if (pWriteBackMap) {
+ // don't insert into the config object but into the temporary
+ // scratchpad map
+ pWriteBackMap->insert(aEntryKey, aEntry);
+ } else {
+ // directly insert value into config object
+ // no need to specify localization; if the key we just
+ // retrieved was localized already, no need to localize it again.
+ pConfig->putData(aEntryKey, aEntry, false);
+ }
+ }
+ // Look up translations using KLocale
+ // https://launchpad.net/distros/ubuntu/+spec/langpacks-desktopfiles-kde
+ // This calls KLocale up to 10 times for each config file (and each KConfig has up to 4 files)
+ // so I'll see how much of a performance hit it is
+ // it also only acts on the last group in a file
+ // Ideas: only translate most important fields, only translate "Desktop Entry" files,
+ // do translation per KConfig not per single file
+ if (!pWriteBackMap) {
+ TQFile file("file.txt");
+ if (foundGettextDomain) {
+
+ KLocale locale(gettextDomain);
+
+ TQString language = locale.language();
+ translateKey(locale, aCurrentGroup, TQCString("Name"));
+ translateKey(locale, aCurrentGroup, TQCString("Comment"));
+ translateKey(locale, aCurrentGroup, TQCString("Language"));
+ translateKey(locale, aCurrentGroup, TQCString("Keywords"));
+ translateKey(locale, aCurrentGroup, TQCString("About"));
+ translateKey(locale, aCurrentGroup, TQCString("Description"));
+ translateKey(locale, aCurrentGroup, TQCString("GenericName"));
+ translateKey(locale, aCurrentGroup, TQCString("Query"));
+ translateKey(locale, aCurrentGroup, TQCString("ExtraNames"));
+ translateKey(locale, aCurrentGroup, TQCString("X-KDE-Submenu"));
+ }
+ }
+
+
+ if (fileOptionImmutable)
+ bFileImmutable = true;
+
+#ifdef HAVE_MMAP
+ if (map)
+ {
+ munmap(( char* )map, rFile.size());
+#ifdef SIGBUS
+ sigaction (SIGBUS, &mmap_old_sigact, 0);
+#endif
+ }
+#endif
+}
+
+void KConfigINIBackEnd::translateKey(KLocale& locale, TQCString currentGroup, TQCString key) {
+ KEntryKey entryKey = KEntryKey(currentGroup, key);
+ KEntry entry = pConfig->lookupData(entryKey);
+ if (TQString(entry.mValue) != "") {
+ TQString orig = key + "=" + entry.mValue;
+ TQString translate = locale.translate(key + "=" + entry.mValue);
+ if (TQString::compare(orig, translate) != 0) {
+ translate = translate.mid(key.length() + 1);
+ entry.mValue = translate.utf8();
+ entryKey.bLocal = true;
+ entry.bNLS = true;
+ pConfig->putData(entryKey, entry, false);
+ }
+ }
+}
+
+void KConfigINIBackEnd::sync(bool bMerge)
+{
+ // write-sync is only necessary if there are dirty entries
+ if (!pConfig->isDirty())
+ return;
+
+ bool bEntriesLeft = true;
+
+ // find out the file to write to (most specific writable file)
+ // try local app-specific file first
+
+ if (!mfileName.isEmpty()) {
+ // Create the containing dir if needed
+ if ((resType!="config") && !TQDir::isRelativePath(mLocalFileName))
+ {
+ KURL path;
+ path.setPath(mLocalFileName);
+ TQString dir=path.directory();
+ KStandardDirs::makeDir(dir);
+ }
+
+ // Can we allow the write? We can, if the program
+ // doesn't run SUID. But if it runs SUID, we must
+ // check if the user would be allowed to write if
+ // it wasn't SUID.
+ if (checkAccess(mLocalFileName, W_OK)) {
+ // File is writable
+ KLockFile::Ptr lf;
+
+ bool mergeLocalFile = bMerge;
+ // Check if the file has been updated since.
+ if (mergeLocalFile)
+ {
+ lf = lockFile(false); // Lock file for local file
+ if (lf && lf->isLocked())
+ lf = 0; // Already locked, we don't need to lock/unlock again
+
+ if (lf)
+ {
+ lf->lock( KLockFile::LockForce );
+ // But what if the locking failed? Ignore it for now...
+ }
+
+ TQFileInfo info(mLocalFileName);
+ if ((d->localLastSize == info.size()) &&
+ (d->localLastModified == info.lastModified()))
+ {
+ // Not changed, don't merge.
+ mergeLocalFile = false;
+ }
+ else
+ {
+ // Changed...
+ d->localLastModified = TQDateTime();
+ d->localLastSize = 0;
+ }
+ }
+
+ bEntriesLeft = writeConfigFile( mLocalFileName, false, mergeLocalFile );
+
+ // Only if we didn't have to merge anything can we use our in-memory state
+ // the next time around. Otherwise the config-file may contain entries
+ // that are different from our in-memory state which means we will have to
+ // do a merge from then on.
+ // We do not automatically update the in-memory state with the on-disk
+ // state when writing the config to disk. We only do so when
+ // KCOnfig::reparseConfiguration() is called.
+ // For KDE 4.0 we may wish to reconsider that.
+ if (!mergeLocalFile)
+ {
+ TQFileInfo info(mLocalFileName);
+ d->localLastModified = info.lastModified();
+ d->localLastSize = info.size();
+ }
+ if (lf) lf->unlock();
+ }
+ }
+
+ // only write out entries to the kdeglobals file if there are any
+ // entries marked global (indicated by bEntriesLeft) and
+ // the useKDEGlobals flag is set.
+ if (bEntriesLeft && useKDEGlobals) {
+
+ // can we allow the write? (see above)
+ if (checkAccess ( mGlobalFileName, W_OK )) {
+ KLockFile::Ptr lf = lockFile(true); // Lock file for global file
+ if (lf && lf->isLocked())
+ lf = 0; // Already locked, we don't need to lock/unlock again
+
+ if (lf)
+ {
+ lf->lock( KLockFile::LockForce );
+ // But what if the locking failed? Ignore it for now...
+ }
+ writeConfigFile( mGlobalFileName, true, bMerge ); // Always merge
+ if (lf) lf->unlock();
+ }
+ }
+
+}
+
+static void writeEntries(FILE *pStream, const KEntryMap& entryMap, bool defaultGroup, bool &firstEntry, const TQCString &localeString)
+{
+ // now write out all other groups.
+ TQCString currentGroup;
+ for (KEntryMapConstIterator aIt = entryMap.begin();
+ aIt != entryMap.end(); ++aIt)
+ {
+ const KEntryKey &key = aIt.key();
+
+ // Either proces the default group or all others
+ if ((key.mGroup != "<default>") == defaultGroup)
+ continue; // Skip
+
+ // Skip default values and group headers.
+ if ((key.bDefault) || key.mKey.isEmpty())
+ continue; // Skip
+
+ const KEntry &currentEntry = *aIt;
+
+ KEntryMapConstIterator aTestIt = aIt;
+ ++aTestIt;
+ bool hasDefault = (aTestIt != entryMap.end());
+ if (hasDefault)
+ {
+ const KEntryKey &defaultKey = aTestIt.key();
+ if ((!defaultKey.bDefault) ||
+ (defaultKey.mKey != key.mKey) ||
+ (defaultKey.mGroup != key.mGroup) ||
+ (defaultKey.bLocal != key.bLocal))
+ hasDefault = false;
+ }
+
+
+ if (hasDefault)
+ {
+ // Entry had a default value
+ if ((currentEntry.mValue == (*aTestIt).mValue) &&
+ (currentEntry.bDeleted == (*aTestIt).bDeleted))
+ continue; // Same as default, don't write.
+ }
+ else
+ {
+ // Entry had no default value.
+ if (currentEntry.bDeleted)
+ continue; // Don't write deleted entries if there is no default.
+ }
+
+ if (!defaultGroup && (currentGroup != key.mGroup)) {
+ if (!firstEntry)
+ fprintf(pStream, "\n");
+ currentGroup = key.mGroup;
+ fprintf(pStream, "[%s]\n", encodeGroup(currentGroup).data());
+ }
+
+ firstEntry = false;
+ // it is data for a group
+ fputs(encodeKey(key.mKey.data()), pStream); // Key
+
+ if ( currentEntry.bNLS )
+ {
+ fputc('[', pStream);
+ fputs(localeString.data(), pStream);
+ fputc(']', pStream);
+ }
+
+ if (currentEntry.bDeleted)
+ {
+ fputs("[$d]\n", pStream); // Deleted
+ }
+ else
+ {
+ if (currentEntry.bImmutable || currentEntry.bExpand)
+ {
+ fputc('[', pStream);
+ fputc('$', pStream);
+ if (currentEntry.bImmutable)
+ fputc('i', pStream);
+ if (currentEntry.bExpand)
+ fputc('e', pStream);
+
+ fputc(']', pStream);
+ }
+ fputc('=', pStream);
+ fputs(stringToPrintable(currentEntry.mValue).data(), pStream);
+ fputc('\n', pStream);
+ }
+ } // for loop
+}
+
+bool KConfigINIBackEnd::getEntryMap(KEntryMap &aTempMap, bool bGlobal,
+ TQFile *mergeFile)
+{
+ bool bEntriesLeft = false;
+ bFileImmutable = false;
+
+ // Read entries from disk
+ if (mergeFile && mergeFile->open(IO_ReadOnly))
+ {
+ // fill the temporary structure with entries from the file
+ parseSingleConfigFile(*mergeFile, &aTempMap, bGlobal, false );
+
+ if (bFileImmutable) // File has become immutable on disk
+ return bEntriesLeft;
+ }
+
+ KEntryMap aMap = pConfig->internalEntryMap();
+
+ // augment this structure with the dirty entries from the config object
+ for (KEntryMapIterator aIt = aMap.begin();
+ aIt != aMap.end(); ++aIt)
+ {
+ const KEntry &currentEntry = *aIt;
+ if(aIt.key().bDefault)
+ {
+ aTempMap.replace(aIt.key(), currentEntry);
+ continue;
+ }
+
+ if (mergeFile && !currentEntry.bDirty)
+ continue;
+
+ // only write back entries that have the same
+ // "globality" as the file
+ if (currentEntry.bGlobal != bGlobal)
+ {
+ // wrong "globality" - might have to be saved later
+ bEntriesLeft = true;
+ continue;
+ }
+
+ // put this entry from the config object into the
+ // temporary map, possibly replacing an existing entry
+ KEntryMapIterator aIt2 = aTempMap.find(aIt.key());
+ if (aIt2 != aTempMap.end() && (*aIt2).bImmutable)
+ continue; // Bail out if the on-disk entry is immutable
+
+ aTempMap.insert(aIt.key(), currentEntry, true);
+ } // loop
+
+ return bEntriesLeft;
+}
+
+/* antlarr: KDE 4.0: make the first parameter "const TQString &" */
+bool KConfigINIBackEnd::writeConfigFile(TQString filename, bool bGlobal,
+ bool bMerge)
+{
+ // is the config object read-only?
+ if (pConfig->isReadOnly())
+ return true; // pretend we wrote it
+
+ KEntryMap aTempMap;
+ TQFile *mergeFile = (bMerge ? new TQFile(filename) : 0);
+ bool bEntriesLeft = getEntryMap(aTempMap, bGlobal, mergeFile);
+ delete mergeFile;
+ if (bFileImmutable)
+ return true; // pretend we wrote it
+
+ // OK now the temporary map should be full of ALL entries.
+ // write it out to disk.
+
+ // Check if file exists:
+ int fileMode = -1;
+ bool createNew = true;
+
+ KDE_struct_stat buf;
+ if (KDE_stat(TQFile::encodeName(filename), &buf) == 0)
+ {
+ if (buf.st_uid == getuid())
+ {
+ // Preserve file mode if file exists and is owned by user.
+ fileMode = buf.st_mode & 0777;
+ }
+ else
+ {
+ // File is not owned by user:
+ // Don't create new file but write to existing file instead.
+ createNew = false;
+ }
+ }
+
+ KSaveFile *pConfigFile = 0;
+ FILE *pStream = 0;
+
+ if (createNew)
+ {
+ pConfigFile = new KSaveFile( filename, 0600 );
+
+ if (pConfigFile->status() != 0)
+ {
+ delete pConfigFile;
+ return bEntriesLeft;
+ }
+
+ if (!bGlobal && (fileMode == -1))
+ fileMode = mFileMode;
+
+ if (fileMode != -1)
+ {
+ fchmod(pConfigFile->handle(), fileMode);
+ }
+
+ pStream = pConfigFile->fstream();
+ }
+ else
+ {
+ // Open existing file.
+ // We use open() to ensure that we call without O_CREAT.
+ int fd = KDE_open( TQFile::encodeName(filename), O_WRONLY | O_TRUNC );
+ if (fd < 0)
+ {
+ return bEntriesLeft;
+ }
+ pStream = KDE_fdopen( fd, "w");
+ if (!pStream)
+ {
+ close(fd);
+ return bEntriesLeft;
+ }
+ }
+
+ writeEntries(pStream, aTempMap);
+
+ if (pConfigFile)
+ {
+ bool bEmptyFile = (ftell(pStream) == 0);
+ if ( bEmptyFile && ((fileMode == -1) || (fileMode == 0600)) )
+ {
+ // File is empty and doesn't have special permissions: delete it.
+ ::unlink(TQFile::encodeName(filename));
+ pConfigFile->abort();
+ }
+ else
+ {
+ // Normal case: Close the file
+ pConfigFile->close();
+ }
+ delete pConfigFile;
+ }
+ else
+ {
+ fclose(pStream);
+ }
+
+ return bEntriesLeft;
+}
+
+void KConfigINIBackEnd::writeEntries(FILE *pStream, const KEntryMap &aTempMap)
+{
+ bool firstEntry = true;
+
+ // Write default group
+ ::writeEntries(pStream, aTempMap, true, firstEntry, localeString);
+
+ // Write all other groups
+ ::writeEntries(pStream, aTempMap, false, firstEntry, localeString);
+}
+
+void KConfigBackEnd::virtual_hook( int, void* )
+{ /*BASE::virtual_hook( id, data );*/ }
+
+void KConfigINIBackEnd::virtual_hook( int id, void* data )
+{ KConfigBackEnd::virtual_hook( id, data ); }
+
+bool KConfigBackEnd::checkConfigFilesWritable(bool warnUser)
+{
+ // WARNING: Do NOT use the event loop as it may not exist at this time.
+ bool allWritable = true;
+ TQString errorMsg;
+ if ( !mLocalFileName.isEmpty() && !bFileImmutable && !checkAccess(mLocalFileName,W_OK) )
+ {
+ errorMsg = i18n("Will not save configuration.\n");
+ allWritable = false;
+ errorMsg += i18n("Configuration file \"%1\" not writable.\n").arg(mLocalFileName);
+ }
+ // We do not have an immutability flag for kdeglobals. However, making kdeglobals mutable while making
+ // the local config file immutable is senseless.
+ if ( !mGlobalFileName.isEmpty() && useKDEGlobals && !bFileImmutable && !checkAccess(mGlobalFileName,W_OK) )
+ {
+ if ( errorMsg.isEmpty() )
+ errorMsg = i18n("Will not save configuration.\n");
+ errorMsg += i18n("Configuration file \"%1\" not writable.\n").arg(mGlobalFileName);
+ allWritable = false;
+ }
+
+ if (warnUser && !allWritable)
+ {
+ // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
+ errorMsg += i18n("Please contact your system administrator.");
+ TQString cmdToExec = KStandardDirs::findExe(TQString("kdialog"));
+ KApplication *app = kapp;
+ if (!cmdToExec.isEmpty() && app)
+ {
+ KProcess lprocess;
+ lprocess << cmdToExec << "--title" << app->instanceName() << "--msgbox" << TQCString(errorMsg.local8Bit());
+ lprocess.start( KProcess::Block );
+ }
+ }
+ return allWritable;
+}