WvDBusMsg::is_reply() had an unnecessary hack for message #1.
[wvapps.git] / unity / kconfig / kconfigbackend.cpp
blob8334811e077b462ad28d768ae472c6ee416134ad
1 /*
2 This file is part of the KDE libraries
3 Copyright (c) 1999 Preston Brown <pbrown@kde.org>
4 Copyright (c) 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA.
22 #include <config.h>
24 #include <unistd.h>
25 #include <ctype.h>
26 #ifdef HAVE_SYS_MMAN_H
27 #include <sys/mman.h>
28 #endif
29 #include <sys/types.h>
30 #ifdef HAVE_SYS_STAT_H
31 #include <sys/stat.h>
32 #endif
33 #include <fcntl.h>
34 #include <signal.h>
36 #undef Unsorted
37 #include <qdir.h>
38 #include <qfileinfo.h>
39 #include <qtextcodec.h>
40 #include <qtextstream.h>
42 #include "kconfigbackend.h"
43 #include "kconfigbase.h"
44 #include <kglobal.h>
45 #include <klocale.h>
46 #include <kstandarddirs.h>
47 #include <ksavefile.h>
48 #include <kurl.h>
50 extern bool checkAccess(const QString& pathname, int mode);
51 /* translate escaped escape sequences to their actual values. */
52 static QCString printableToString(const char *str, int l)
54 // Strip leading white-space.
55 while((l>0) &&
56 ((*str == ' ') || (*str == '\t') || (*str == '\r')))
58 str++; l--;
61 // Strip trailing white-space.
62 while((l>0) &&
63 ((str[l-1] == ' ') || (str[l-1] == '\t') || (str[l-1] == '\r')))
65 l--;
68 QCString result(l + 1);
69 char *r = result.data();
71 for(int i = 0; i < l;i++, str++)
73 if (*str == '\\')
75 i++, str++;
76 if (i >= l) // End of line. (Line ends with single slash)
78 *r++ = '\\';
79 break;
81 switch(*str)
83 case 's':
84 *r++ = ' ';
85 break;
86 case 't':
87 *r++ = '\t';
88 break;
89 case 'n':
90 *r++ = '\n';
91 break;
92 case 'r':
93 *r++ = '\r';
94 break;
95 case '\\':
96 *r++ = '\\';
97 break;
98 default:
99 *r++ = '\\';
100 *r++ = *str;
103 else
105 *r++ = *str;
108 result.truncate(r-result.data());
109 return result;
112 static QCString stringToPrintable(const QCString& str){
113 QCString result(str.length()*2); // Maximum 2x as long as source string
114 register char *r = result.data();
115 register char *s = str.data();
117 if (!s) return QCString("");
119 // Escape leading space
120 if (*s == ' ')
122 *r++ = '\\'; *r++ = 's';
123 s++;
126 if (*s)
128 while(*s)
130 if (*s == '\n')
132 *r++ = '\\'; *r++ = 'n';
134 else if (*s == '\t')
136 *r++ = '\\'; *r++ = 't';
138 else if (*s == '\r')
140 *r++ = '\\'; *r++ = 'r';
142 else if (*s == '\\')
144 *r++ = '\\'; *r++ = '\\';
146 else
148 *r++ = *s;
150 s++;
152 // Escape trailing space
153 if (*(r-1) == ' ')
155 *(r-1) = '\\'; *r++ = 's';
159 result.truncate(r - result.data());
160 return result;
163 void KConfigBackEnd::changeFileName(const QString &_fileName,
164 const char * _resType,
165 bool _useKDEGlobals)
167 mfileName = _fileName;
168 resType = _resType;
169 useKDEGlobals = _useKDEGlobals;
170 if (mfileName.isEmpty())
171 mLocalFileName = QString::null;
172 else if (mfileName[0] == '/')
173 mLocalFileName = mfileName;
174 else
175 mLocalFileName = KGlobal::dirs()->saveLocation(resType) + mfileName;
177 if (useKDEGlobals)
178 mGlobalFileName = KGlobal::dirs()->saveLocation("config") +
179 QString::fromLatin1("kdeglobals");
180 else
181 mGlobalFileName = QString::null;
184 KConfigBackEnd::KConfigBackEnd(KConfigBase *_config,
185 const QString &_fileName,
186 const char * _resType,
187 bool _useKDEGlobals)
188 : pConfig(_config), bFileImmutable(false), mConfigState(KConfigBase::NoAccess), mFileMode(-1)
190 changeFileName(_fileName, _resType, _useKDEGlobals);
193 void KConfigBackEnd::setFileWriteMode(int mode)
195 mFileMode = mode;
198 bool KConfigINIBackEnd::parseConfigFiles()
200 // Check if we can write to the local file.
201 mConfigState = KConfigBase::ReadOnly;
202 if (!mLocalFileName.isEmpty() && !pConfig->isReadOnly())
204 if (checkAccess(mLocalFileName, W_OK))
206 mConfigState = KConfigBase::ReadWrite;
208 else
210 // Create the containing dir, maybe it wasn't there
211 KURL path;
212 path.setPath(mLocalFileName);
213 QString dir=path.directory();
214 KStandardDirs::makeDir(dir);
216 if (checkAccess(mLocalFileName, W_OK))
218 mConfigState = KConfigBase::ReadWrite;
223 // Parse all desired files from the least to the most specific.
224 bFileImmutable = false;
226 // Parse the general config files
227 if (useKDEGlobals) {
228 QStringList kdercs = KGlobal::dirs()->
229 findAllResources("config", QString::fromLatin1("kdeglobals"));
231 if (checkAccess(QString::fromLatin1("/etc/kderc"), R_OK))
232 kdercs += QString::fromLatin1("/etc/kderc");
234 kdercs += KGlobal::dirs()->
235 findAllResources("config", QString::fromLatin1("system.kdeglobals"));
237 QStringList::ConstIterator it;
239 for (it = kdercs.fromLast(); it != kdercs.end(); --it) {
241 QFile aConfigFile( *it );
242 if (!aConfigFile.open( IO_ReadOnly ))
243 continue;
244 parseSingleConfigFile( aConfigFile, 0L, true, (*it != mGlobalFileName) );
245 aConfigFile.close();
246 if (bFileImmutable)
247 break;
251 bool bReadFile = !mfileName.isEmpty();
252 while(bReadFile) {
253 bReadFile = false;
254 QString bootLanguage;
255 if (useKDEGlobals && localeString.isEmpty() && !KGlobal::_locale) {
256 // Boot strap language
257 bootLanguage = KLocale::_initLanguage(pConfig);
258 setLocaleString(bootLanguage.utf8());
261 bFileImmutable = false;
262 QStringList list = KGlobal::dirs()->
263 findAllResources(resType, mfileName);
265 QStringList::ConstIterator it;
267 for (it = list.fromLast(); it != list.end(); --it) {
269 QFile aConfigFile( *it );
270 // we can already be sure that this file exists
271 bool bIsLocal = (*it == mLocalFileName);
272 if (aConfigFile.open( IO_ReadOnly )) {
273 parseSingleConfigFile( aConfigFile, 0L, false, !bIsLocal );
274 aConfigFile.close();
275 if (bFileImmutable)
276 break;
279 if (KGlobal::dirs()->isRestrictedResource(resType, mfileName))
280 bFileImmutable = true;
281 QString currentLanguage;
282 if (!bootLanguage.isEmpty())
284 currentLanguage = KLocale::_initLanguage(pConfig);
285 // If the file changed the language, we need to read the file again
286 // with the new language setting.
287 if (bootLanguage != currentLanguage)
289 bReadFile = true;
290 setLocaleString(currentLanguage.utf8());
294 if (bFileImmutable)
295 mConfigState = KConfigBase::ReadOnly;
297 return true;
300 #ifdef HAVE_MMAP
301 #ifdef SIGBUS
302 static const char **mmap_pEof;
304 static void mmap_sigbus_handler(int)
306 *mmap_pEof = 0;
307 write(2, "SIGBUS\n", 7);
308 signal(SIGBUS, mmap_sigbus_handler);
310 #endif
311 #endif
313 void KConfigINIBackEnd::parseSingleConfigFile(QFile &rFile,
314 KEntryMap *pWriteBackMap,
315 bool bGlobal, bool bDefault)
317 void (*old_sighandler)(int) = 0;
319 if (!rFile.isOpen()) // come back, if you have real work for us ;->
320 return;
322 //using kdDebug() here leads to an infinite loop
323 //remove this for the release, aleXXX
324 //qWarning("Parsing %s, global = %s default = %s",
325 // rFile.name().latin1(), bGlobal ? "true" : "false", bDefault ? "true" : "false");
327 QCString aCurrentGroup("<default>");
329 const char *s, *eof;
330 QByteArray data;
332 unsigned int ll = localeString.length();
334 #ifdef HAVE_MMAP
335 const char *map = ( const char* ) mmap(0, rFile.size(), PROT_READ, MAP_PRIVATE,
336 rFile.handle(), 0);
338 if (map)
340 s = map;
341 eof = s + rFile.size();
343 #ifdef SIGBUS
344 mmap_pEof = &eof;
345 old_sighandler = signal(SIGBUS, mmap_sigbus_handler);
346 #endif
348 else
349 #endif
351 rFile.at(0);
352 data = rFile.readAll();
353 s = data.data();
354 eof = s + data.size();
357 bool fileOptionImmutable = false;
358 bool groupOptionImmutable = false;
359 bool groupSkip = false;
361 int line = 0;
362 for(; s < eof; s++)
364 line++;
366 while((s < eof) && isspace(*s) && (*s != '\n'))
367 s++; //skip leading whitespace, shouldn't happen too often
369 //skip empty lines, lines starting with #
370 if ((s < eof) && ((*s == '\n') || (*s == '#')))
372 sktoeol: //skip till end-of-line
373 while ((s < eof) && (*s != '\n'))
374 s++;
375 continue; // Empty or comment or no keyword
377 const char *startLine = s;
379 if (*s == '[') //group
381 while ((s < eof) && (*s != '\n') && (*s != ']')) s++; // Search till end of group
382 const char *e = s;
383 while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file
384 if ((e >= eof) || (*e != ']'))
386 fprintf(stderr, "Invalid group header at %s:%d\n", rFile.name().latin1(), line);
387 continue;
389 // group found; get the group name by taking everything in
390 // between the brackets
391 if ((e-startLine == 3) &&
392 (startLine[1] == '$') &&
393 (startLine[2] == 'i'))
395 fileOptionImmutable = true;
396 continue;
399 aCurrentGroup = QCString(startLine + 1, e - startLine);
400 //cout<<"found group ["<<aCurrentGroup<<"]"<<endl;
402 // Backwards compatibility
403 if (aCurrentGroup == "KDE Desktop Entry")
404 aCurrentGroup = "Desktop Entry";
406 groupOptionImmutable = fileOptionImmutable;
408 e++;
409 if ((e+2 < eof) && (*e++ == '[') && (*e++ == '$')) // Option follows
411 if (*e == 'i')
413 groupOptionImmutable = true;
417 KEntryKey groupKey(aCurrentGroup, 0);
418 KEntry entry = pConfig->lookupData(groupKey);
419 groupSkip = entry.bImmutable;
421 if (groupSkip)
422 continue;
424 entry.bImmutable = groupOptionImmutable;
425 pConfig->putData(groupKey, entry, false);
427 if (pWriteBackMap)
429 // add the special group key indicator
430 (*pWriteBackMap)[groupKey] = entry;
433 continue;
435 if (groupSkip)
436 goto sktoeol; // Skip entry
438 bool optionImmutable = groupOptionImmutable;
439 bool optionDeleted = false;
440 bool optionExpand = false;
441 const char *endOfKey = 0, *locale = 0, *elocale = 0;
442 for (; (s < eof) && (*s != '\n'); s++)
444 if (*s == '=') //find the equal sign
446 if (!endOfKey)
447 endOfKey = s;
448 goto haveeq;
450 if (*s == '[') //find the locale or options.
452 const char *option;
453 const char *eoption;
454 endOfKey = s;
455 option = ++s;
456 for (;; s++)
458 if ((s >= eof) || (*s == '\n') || (*s == '=')) {
459 fprintf(stderr, "Invalid entry (missing ']') at %s:%d\n", rFile.name().latin1(), line);
460 goto sktoeol;
462 if (*s == ']')
463 break;
465 eoption = s;
466 if (*option != '$')
468 // Locale
469 if (locale) {
470 fprintf(stderr, "Invalid entry (second locale!?) at %s:%d\n", rFile.name().latin1(), line);
471 goto sktoeol;
473 locale = option;
474 elocale = eoption;
476 else
478 // Option
479 while (option < eoption)
481 option++;
482 if (*option == 'i')
483 optionImmutable = true;
484 else if (*option == 'e')
485 optionExpand = true;
486 else if (*option == 'd')
488 optionDeleted = true;
489 goto haveeq;
491 else if (*option == ']')
492 break;
497 fprintf(stderr, "Invalid entry (missing '=') at %s:%d\n", rFile.name().latin1(), line);
498 continue;
500 haveeq:
501 for (endOfKey--; ; endOfKey--)
503 if (endOfKey < startLine)
505 fprintf(stderr, "Invalid entry (empty key) at %s:%d\n", rFile.name().latin1(), line);
506 goto sktoeol;
508 if (!isspace(*endOfKey))
509 break;
512 const char *st = ++s;
513 while ((s < eof) && (*s != '\n')) s++; // Search till end of line / end of file
515 if (locale) {
516 unsigned int cl = static_cast<unsigned int>(elocale - locale);
517 if ((ll != cl) || memcmp(locale, localeString.data(), ll))
519 // backward compatibility. C == en_US
520 if ( cl != 1 || ll != 5 || memcmp(locale, "C", 1) || memcmp(localeString.data(), "en_US", 5)) {
521 //cout<<"mismatched locale '"<<QCString(locale, elocale-locale +1)<<"'"<<endl;
522 // We can ignore this one
523 if (!pWriteBackMap)
524 continue; // We just ignore it
525 // We just store it as is to be able to write it back later.
526 endOfKey = elocale;
527 locale = 0;
532 // insert the key/value line
533 QCString key(startLine, endOfKey - startLine + 2);
534 QCString val = printableToString(st, s - st);
535 //qDebug("found key '%s' with value '%s'", key.data(), val.data());
537 KEntryKey aEntryKey(aCurrentGroup, key);
538 aEntryKey.bLocal = (locale != 0);
539 aEntryKey.bDefault = bDefault;
541 KEntry aEntry;
542 aEntry.mValue = val;
543 aEntry.bGlobal = bGlobal;
544 aEntry.bImmutable = optionImmutable;
545 aEntry.bDeleted = optionDeleted;
546 aEntry.bExpand = optionExpand;
547 aEntry.bNLS = (locale != 0);
549 if (pWriteBackMap) {
550 // don't insert into the config object but into the temporary
551 // scratchpad map
552 pWriteBackMap->insert(aEntryKey, aEntry);
553 } else {
554 // directly insert value into config object
555 // no need to specify localization; if the key we just
556 // retrieved was localized already, no need to localize it again.
557 pConfig->putData(aEntryKey, aEntry, false);
560 if (fileOptionImmutable)
561 bFileImmutable = true;
563 #ifdef HAVE_MMAP
564 if (map)
566 munmap(( char* )map, rFile.size());
567 #ifdef SIGBUS
568 signal(SIGBUS, old_sighandler);
569 #endif
571 #endif
575 void KConfigINIBackEnd::sync(bool bMerge)
577 // write-sync is only necessary if there are dirty entries
578 if (!pConfig->isDirty())
579 return;
581 bool bEntriesLeft = true;
583 // find out the file to write to (most specific writable file)
584 // try local app-specific file first
586 if (!mfileName.isEmpty()) {
587 // Create the containing dir if needed
588 if ((resType!="config") && mLocalFileName[0]=='/')
590 KURL path;
591 path.setPath(mLocalFileName);
592 QString dir=path.directory();
593 KStandardDirs::makeDir(dir);
596 // Can we allow the write? We can, if the program
597 // doesn't run SUID. But if it runs SUID, we must
598 // check if the user would be allowed to write if
599 // it wasn't SUID.
600 if (checkAccess(mLocalFileName, W_OK)) {
601 // is it writable?
602 bEntriesLeft = writeConfigFile( mLocalFileName, false, bMerge );
606 // only write out entries to the kdeglobals file if there are any
607 // entries marked global (indicated by bEntriesLeft) and
608 // the useKDEGlobals flag is set.
609 if (bEntriesLeft && useKDEGlobals) {
612 // can we allow the write? (see above)
613 if (checkAccess ( mGlobalFileName, W_OK )) {
614 writeConfigFile( mGlobalFileName, true, bMerge );
620 static void writeEntries(FILE *pStream, const KEntryMap& entryMap, bool defaultGroup, bool &firstEntry, const QCString &localeString)
622 // now write out all other groups.
623 QCString currentGroup;
624 for (KEntryMapConstIterator aIt = entryMap.begin();
625 aIt != entryMap.end(); ++aIt)
627 const KEntryKey &key = aIt.key();
629 // Either proces the default group or all others
630 if ((key.mGroup != "<default>") == defaultGroup)
631 continue; // Skip
633 // Skip default values and group headers.
634 if ((key.bDefault) || key.mKey.isEmpty())
635 continue; // Skip
637 const KEntry &currentEntry = *aIt;
639 KEntryMapConstIterator aTestIt = aIt;
640 ++aTestIt;
641 bool hasDefault = (aTestIt != entryMap.end());
642 if (hasDefault)
644 const KEntryKey &defaultKey = aTestIt.key();
645 if ((!defaultKey.bDefault) ||
646 (defaultKey.mKey != key.mKey) ||
647 (defaultKey.mGroup != key.mGroup) ||
648 (defaultKey.bLocal != key.bLocal))
649 hasDefault = false;
653 if (hasDefault)
655 // Entry had a default value
656 if ((currentEntry.mValue == (*aTestIt).mValue) &&
657 (currentEntry.bDeleted == (*aTestIt).bDeleted))
658 continue; // Same as default, don't write.
660 else
662 // Entry had no default value.
663 if (currentEntry.bDeleted)
664 continue; // Don't write deleted entries if there is no default.
667 if (!defaultGroup && (currentGroup != key.mGroup)) {
668 if (!firstEntry)
669 fprintf(pStream, "\n");
670 currentGroup = key.mGroup;
671 fprintf(pStream, "[%s]\n", currentGroup.data());
674 firstEntry = false;
675 // it is data for a group
676 fputs(key.mKey.data(), pStream); // Key
678 if ( currentEntry.bNLS )
680 fputc('[', pStream);
681 fputs(localeString.data(), pStream);
682 fputc(']', pStream);
685 if (currentEntry.bDeleted)
687 fputs("[$d]\n", pStream); // Deleted
689 else
691 if (currentEntry.bImmutable || currentEntry.bExpand)
693 fputc('[', pStream);
694 fputc('$', pStream);
695 if (currentEntry.bImmutable)
696 fputc('i', pStream);
697 if (currentEntry.bExpand)
698 fputc('e', pStream);
700 fputc(']', pStream);
702 fputc('=', pStream);
703 fputs(stringToPrintable(currentEntry.mValue).data(), pStream);
704 fputc('\n', pStream);
706 } // for loop
709 bool KConfigINIBackEnd::writeConfigFile(QString filename, bool bGlobal,
710 bool bMerge)
712 KEntryMap aTempMap;
713 bool bEntriesLeft = false;
715 // is the config object read-only?
716 if (pConfig->isReadOnly())
717 return true; // pretend we wrote it
719 bFileImmutable = false;
720 if (bMerge)
722 // Read entries from disk
723 QFile rConfigFile( filename );
724 if (rConfigFile.open(IO_ReadOnly))
726 // fill the temporary structure with entries from the file
727 parseSingleConfigFile( rConfigFile, &aTempMap, bGlobal, false );
728 rConfigFile.close();
730 if (bFileImmutable) // File has become immutable on disk
731 return true; // pretend we wrote it
734 KEntryMap aMap = pConfig->internalEntryMap();
736 // augment this structure with the dirty entries from the config object
737 for (KEntryMapIterator aIt = aMap.begin();
738 aIt != aMap.end(); ++aIt)
740 const KEntry &currentEntry = *aIt;
741 if(aIt.key().bDefault)
743 aTempMap.replace(aIt.key(), currentEntry);
744 continue;
747 if (!currentEntry.bDirty)
748 continue;
750 // only write back entries that have the same
751 // "globality" as the file
752 if (currentEntry.bGlobal != bGlobal)
754 // wrong "globality" - might have to be saved later
755 bEntriesLeft = true;
756 continue;
759 // put this entry from the config object into the
760 // temporary map, possibly replacing an existing entry
761 KEntryMapIterator aIt2 = aTempMap.find(aIt.key());
762 if (aIt2 != aTempMap.end() && (*aIt2).bImmutable)
763 continue; // Bail out if the on-disk entry is immutable
765 aTempMap.insert(aIt.key(), currentEntry, true);
766 } // loop
768 else
770 // don't merge, just get the regular entry map and use that.
771 aTempMap = pConfig->internalEntryMap();
772 bEntriesLeft = true; // maybe not true, but we aren't sure
775 // OK now the temporary map should be full of ALL entries.
776 // write it out to disk.
778 // Check if file exists:
779 int fileMode = -1;
780 bool createNew = true;
782 struct stat buf;
783 if (lstat(QFile::encodeName(filename), &buf) == 0)
785 if (S_ISLNK(buf.st_mode))
787 // File is a symlink:
788 if (stat(QFile::encodeName(filename), &buf) == 0)
790 // Don't create new file but write to existing file instead.
791 createNew = false;
794 else if (buf.st_uid == getuid())
796 // Preserve file mode if file exists and is owned by user.
797 fileMode = buf.st_mode & 0777;
799 else
801 // File is not owned by user:
802 // Don't create new file but write to existing file instead.
803 createNew = false;
807 KSaveFile *pConfigFile = 0;
808 FILE *pStream = 0;
810 if (createNew)
812 pConfigFile = new KSaveFile( filename, 0600 );
814 if (pConfigFile->status() != 0)
816 delete pConfigFile;
817 return bEntriesLeft;
820 if (!bGlobal && (fileMode == -1))
821 fileMode = mFileMode;
823 if (fileMode != -1)
825 fchmod(pConfigFile->handle(), fileMode);
828 pStream = pConfigFile->fstream();
830 else
832 // Open existing file.
833 // We use open() to ensure that we call without O_CREAT.
834 int fd = open( QFile::encodeName(filename), O_WRONLY | O_TRUNC);
835 if (fd < 0)
836 return bEntriesLeft;
837 pStream = fdopen( fd, "w");
838 if (!pStream)
840 close(fd);
841 return bEntriesLeft;
845 bool firstEntry = true;
847 // Write default group
848 writeEntries(pStream, aTempMap, true, firstEntry, localeString);
850 // Write all other groups
851 writeEntries(pStream, aTempMap, false, firstEntry, localeString);
853 if (pConfigFile)
855 pConfigFile->close();
856 delete pConfigFile;
858 else
860 fclose(pStream);
863 return bEntriesLeft;
867 void KConfigBackEnd::virtual_hook( int, void* )
868 { /*BASE::virtual_hook( id, data );*/ }
870 void KConfigINIBackEnd::virtual_hook( int id, void* data )
871 { KConfigBackEnd::virtual_hook( id, data ); }