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.
26 #ifdef HAVE_SYS_MMAN_H
29 #include <sys/types.h>
30 #ifdef HAVE_SYS_STAT_H
38 #include <qfileinfo.h>
39 #include <qtextcodec.h>
40 #include <qtextstream.h>
42 #include "kconfigbackend.h"
43 #include "kconfigbase.h"
46 #include <kstandarddirs.h>
47 #include <ksavefile.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.
56 ((*str
== ' ') || (*str
== '\t') || (*str
== '\r')))
61 // Strip trailing white-space.
63 ((str
[l
-1] == ' ') || (str
[l
-1] == '\t') || (str
[l
-1] == '\r')))
68 QCString
result(l
+ 1);
69 char *r
= result
.data();
71 for(int i
= 0; i
< l
;i
++, str
++)
76 if (i
>= l
) // End of line. (Line ends with single slash)
108 result
.truncate(r
-result
.data());
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
122 *r
++ = '\\'; *r
++ = 's';
132 *r
++ = '\\'; *r
++ = 'n';
136 *r
++ = '\\'; *r
++ = 't';
140 *r
++ = '\\'; *r
++ = 'r';
144 *r
++ = '\\'; *r
++ = '\\';
152 // Escape trailing space
155 *(r
-1) = '\\'; *r
++ = 's';
159 result
.truncate(r
- result
.data());
163 void KConfigBackEnd::changeFileName(const QString
&_fileName
,
164 const char * _resType
,
167 mfileName
= _fileName
;
169 useKDEGlobals
= _useKDEGlobals
;
170 if (mfileName
.isEmpty())
171 mLocalFileName
= QString::null
;
172 else if (mfileName
[0] == '/')
173 mLocalFileName
= mfileName
;
175 mLocalFileName
= KGlobal::dirs()->saveLocation(resType
) + mfileName
;
178 mGlobalFileName
= KGlobal::dirs()->saveLocation("config") +
179 QString::fromLatin1("kdeglobals");
181 mGlobalFileName
= QString::null
;
184 KConfigBackEnd::KConfigBackEnd(KConfigBase
*_config
,
185 const QString
&_fileName
,
186 const char * _resType
,
188 : pConfig(_config
), bFileImmutable(false), mConfigState(KConfigBase::NoAccess
), mFileMode(-1)
190 changeFileName(_fileName
, _resType
, _useKDEGlobals
);
193 void KConfigBackEnd::setFileWriteMode(int 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
;
210 // Create the containing dir, maybe it wasn't there
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
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
))
244 parseSingleConfigFile( aConfigFile
, 0L, true, (*it
!= mGlobalFileName
) );
251 bool bReadFile
= !mfileName
.isEmpty();
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
);
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
)
290 setLocaleString(currentLanguage
.utf8());
295 mConfigState
= KConfigBase::ReadOnly
;
302 static const char **mmap_pEof
;
304 static void mmap_sigbus_handler(int)
307 write(2, "SIGBUS\n", 7);
308 signal(SIGBUS
, mmap_sigbus_handler
);
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 ;->
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>");
332 unsigned int ll
= localeString
.length();
335 const char *map
= ( const char* ) mmap(0, rFile
.size(), PROT_READ
, MAP_PRIVATE
,
341 eof
= s
+ rFile
.size();
345 old_sighandler
= signal(SIGBUS
, mmap_sigbus_handler
);
352 data
= rFile
.readAll();
354 eof
= s
+ data
.size();
357 bool fileOptionImmutable
= false;
358 bool groupOptionImmutable
= false;
359 bool groupSkip
= false;
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'))
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
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
);
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;
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
;
409 if ((e
+2 < eof
) && (*e
++ == '[') && (*e
++ == '$')) // Option follows
413 groupOptionImmutable
= true;
417 KEntryKey
groupKey(aCurrentGroup
, 0);
418 KEntry entry
= pConfig
->lookupData(groupKey
);
419 groupSkip
= entry
.bImmutable
;
424 entry
.bImmutable
= groupOptionImmutable
;
425 pConfig
->putData(groupKey
, entry
, false);
429 // add the special group key indicator
430 (*pWriteBackMap
)[groupKey
] = entry
;
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
450 if (*s
== '[') //find the locale or options.
458 if ((s
>= eof
) || (*s
== '\n') || (*s
== '=')) {
459 fprintf(stderr
, "Invalid entry (missing ']') at %s:%d\n", rFile
.name().latin1(), line
);
470 fprintf(stderr
, "Invalid entry (second locale!?) at %s:%d\n", rFile
.name().latin1(), line
);
479 while (option
< eoption
)
483 optionImmutable
= true;
484 else if (*option
== 'e')
486 else if (*option
== 'd')
488 optionDeleted
= true;
491 else if (*option
== ']')
497 fprintf(stderr
, "Invalid entry (missing '=') at %s:%d\n", rFile
.name().latin1(), line
);
501 for (endOfKey
--; ; endOfKey
--)
503 if (endOfKey
< startLine
)
505 fprintf(stderr
, "Invalid entry (empty key) at %s:%d\n", rFile
.name().latin1(), line
);
508 if (!isspace(*endOfKey
))
512 const char *st
= ++s
;
513 while ((s
< eof
) && (*s
!= '\n')) s
++; // Search till end of line / end of file
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
524 continue; // We just ignore it
525 // We just store it as is to be able to write it back later.
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
;
543 aEntry
.bGlobal
= bGlobal
;
544 aEntry
.bImmutable
= optionImmutable
;
545 aEntry
.bDeleted
= optionDeleted
;
546 aEntry
.bExpand
= optionExpand
;
547 aEntry
.bNLS
= (locale
!= 0);
550 // don't insert into the config object but into the temporary
552 pWriteBackMap
->insert(aEntryKey
, aEntry
);
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;
566 munmap(( char* )map
, rFile
.size());
568 signal(SIGBUS
, old_sighandler
);
575 void KConfigINIBackEnd::sync(bool bMerge
)
577 // write-sync is only necessary if there are dirty entries
578 if (!pConfig
->isDirty())
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]=='/')
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
600 if (checkAccess(mLocalFileName
, W_OK
)) {
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
)
633 // Skip default values and group headers.
634 if ((key
.bDefault
) || key
.mKey
.isEmpty())
637 const KEntry
¤tEntry
= *aIt
;
639 KEntryMapConstIterator aTestIt
= aIt
;
641 bool hasDefault
= (aTestIt
!= entryMap
.end());
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
))
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.
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
)) {
669 fprintf(pStream
, "\n");
670 currentGroup
= key
.mGroup
;
671 fprintf(pStream
, "[%s]\n", currentGroup
.data());
675 // it is data for a group
676 fputs(key
.mKey
.data(), pStream
); // Key
678 if ( currentEntry
.bNLS
)
681 fputs(localeString
.data(), pStream
);
685 if (currentEntry
.bDeleted
)
687 fputs("[$d]\n", pStream
); // Deleted
691 if (currentEntry
.bImmutable
|| currentEntry
.bExpand
)
695 if (currentEntry
.bImmutable
)
697 if (currentEntry
.bExpand
)
703 fputs(stringToPrintable(currentEntry
.mValue
).data(), pStream
);
704 fputc('\n', pStream
);
709 bool KConfigINIBackEnd::writeConfigFile(QString filename
, bool bGlobal
,
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;
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 );
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
¤tEntry
= *aIt
;
741 if(aIt
.key().bDefault
)
743 aTempMap
.replace(aIt
.key(), currentEntry
);
747 if (!currentEntry
.bDirty
)
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
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);
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:
780 bool createNew
= true;
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.
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;
801 // File is not owned by user:
802 // Don't create new file but write to existing file instead.
807 KSaveFile
*pConfigFile
= 0;
812 pConfigFile
= new KSaveFile( filename
, 0600 );
814 if (pConfigFile
->status() != 0)
820 if (!bGlobal
&& (fileMode
== -1))
821 fileMode
= mFileMode
;
825 fchmod(pConfigFile
->handle(), fileMode
);
828 pStream
= pConfigFile
->fstream();
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
);
837 pStream
= fdopen( fd
, "w");
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
);
855 pConfigFile
->close();
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
); }