You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1191 lines
32 KiB
1191 lines
32 KiB
/*
|
|
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
|
|
char *r = const_cast<TQCString&>(result).data();
|
|
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);
|
|
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);
|
|
char *r = const_cast<TQCString&>(result).data();
|
|
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, TQString(), false) + mfileName;
|
|
}
|
|
|
|
if (useKDEGlobals) {
|
|
mGlobalFileName = KGlobal::dirs()->saveLocation("config", TQString(), false) + TQString::fromLatin1("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::fromLatin1("kdeglobals"));
|
|
|
|
#ifdef Q_WS_WIN
|
|
TQString etc_kderc = TQFile::decodeName( TQCString(getenv("WINDIR")) + "\\kderc" );
|
|
#else
|
|
TQString etc_kderc = TQString::fromLatin1("/etc/kderc");
|
|
#endif
|
|
|
|
if (checkAccess(etc_kderc, R_OK))
|
|
kdercs += etc_kderc;
|
|
|
|
kdercs += KGlobal::dirs()->
|
|
findAllResources("config", TQString::fromLatin1("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.at(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 ¤tEntry = *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 ¤tEntry = *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;
|
|
}
|