KConfig

kconfig.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
4 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
5 SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kconfig.h"
11#include "kconfig_p.h"
12
13#include "config-kconfig.h"
14#include "dbussanitizer_p.h"
15#include "kconfig_core_log_settings.h"
16
17#include <fcntl.h>
18
19#include "kconfiggroup.h"
20
21#include <QBasicMutex>
22#include <QByteArray>
23#include <QCache>
24#include <QCoreApplication>
25#include <QDir>
26#include <QFile>
27#include <QLocale>
28#include <QMutexLocker>
29#include <QProcess>
30#include <QSet>
31#include <QThreadStorage>
32#include <QTimeZone>
33
34#include <algorithm>
35#include <iterator>
36#include <set>
37
38#if KCONFIG_USE_DBUS
39#include <QDBusConnection>
40#include <QDBusMessage>
41#include <QDBusMetaType>
42#endif
43
44#ifdef Q_OS_WIN
45#include "registry_win_p.h"
46#endif
47
48bool KConfigPrivate::mappingsRegistered = false;
49
50// For caching purposes
51static bool s_wasTestModeEnabled = false;
52
53Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes.
54static QBasicMutex s_globalFilesMutex;
55Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals")))
56
57using ParseCacheKey = std::pair<QStringList, QString>;
58struct ParseCacheValue {
59 KEntryMap entries;
60 QDateTime parseTime;
61};
63
64#ifndef Q_OS_WIN
65static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive;
66#else
67static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive;
68#endif
69
70KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType)
71 : openFlags(flags)
72 , resourceType(resourceType)
73 , bDirty(false)
74 , bReadDefaults(false)
75 , bFileImmutable(false)
76 , bForceGlobal(false)
77 , bSuppressGlobal(false)
78 , configState(KConfigBase::NoAccess)
79{
80 const bool isTestMode = QStandardPaths::isTestModeEnabled();
81 // If sGlobalFileName was initialised and testMode has been toggled,
82 // sGlobalFileName may need to be updated to point to the correct kdeglobals file
83 if (sGlobalFileName.exists() && s_wasTestModeEnabled != isTestMode) {
84 s_wasTestModeEnabled = isTestMode;
86 }
87
88 static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1);
89 if (use_etc_kderc.loadRelaxed() < 0) {
90 use_etc_kderc.storeRelaxed(!qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests
91 }
92 if (use_etc_kderc.loadRelaxed()) {
93 etc_kderc =
94#ifdef Q_OS_WIN
95 QFile::decodeName(qgetenv("WINDIR") + "/kde5rc");
96#else
97 QStringLiteral("/etc/kde5rc");
98#endif
99 if (!QFileInfo(etc_kderc).isReadable()) {
100 use_etc_kderc.storeRelaxed(false);
101 etc_kderc.clear();
102 }
103 }
104
105 // if (!mappingsRegistered) {
106 // KEntryMap tmp;
107 // if (!etc_kderc.isEmpty()) {
108 // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(etc_kderc, QLatin1String("INI"));
109 // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults);
110 // }
111 // const QString kde5rc(QDir::home().filePath(".kde5rc"));
112 // if (KStandardDirs::checkAccess(kde5rc, R_OK)) {
113 // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(kde5rc, QLatin1String("INI"));
114 // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions());
115 // }
116 // KConfigBackend::registerMappings(tmp);
117 // mappingsRegistered = true;
118 // }
119
120 setLocale(QLocale().name());
121}
122
123bool KConfigPrivate::lockLocal()
124{
125 return mBackend.lock();
126}
127
128static bool isGroupOrSubGroupMatch(KEntryMapConstIterator entryMapIt, const QString &group)
129{
130 const QString &entryGroup = entryMapIt->first.mGroup;
131 Q_ASSERT_X(entryGroup.startsWith(group), Q_FUNC_INFO, "Precondition");
132 return entryGroup.size() == group.size() || entryGroup[group.size()] == QLatin1Char('\x1d');
133}
134
135void KConfigPrivate::copyGroup(const QString &source, const QString &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const
136{
137 KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap;
138 const bool sameName = (destination == source);
139
140 // we keep this bool outside the for loop so that if
141 // the group is empty, we don't end up marking the other config
142 // as dirty erroneously
143 bool dirtied = false;
144
145 entryMap.forEachEntryWhoseGroupStartsWith(source, [&source, &destination, flags, &otherMap, sameName, &dirtied](KEntryMapConstIterator entryMapIt) {
146 // don't copy groups that start with the same prefix, but are not sub-groups
147 if (!isGroupOrSubGroupMatch(entryMapIt, source)) {
148 return;
149 }
150
151 KEntryKey newKey = entryMapIt->first;
152
153 if (flags & KConfigBase::Localized) {
154 newKey.bLocal = true;
155 }
156
157 if (!sameName) {
158 newKey.mGroup.replace(0, source.size(), destination);
159 }
160
161 KEntry entry = entryMapIt->second;
162 dirtied = entry.bDirty = flags & KConfigBase::Persistent;
163
164 if (flags & KConfigBase::Global) {
165 entry.bGlobal = true;
166 }
167
168 if (flags & KConfigBase::Notify) {
169 entry.bNotify = true;
170 }
171
172 otherMap[newKey] = entry;
173 });
174
175 if (dirtied) {
176 otherGroup->config()->d_ptr->bDirty = true;
177 }
178}
179
180QString KConfigPrivate::expandString(const QString &value)
181{
182 QString aValue = value;
183
184 // check for environment variables and make necessary translations
185 int nDollarPos = aValue.indexOf(QLatin1Char('$'));
186 while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
187 // there is at least one $
188 if (aValue.at(nDollarPos + 1) != QLatin1Char('$')) {
189 int nEndPos = nDollarPos + 1;
190 // the next character is not $
191 QStringView aVarName;
192 if (aValue.at(nEndPos) == QLatin1Char('{')) {
193 while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) {
194 ++nEndPos;
195 }
196 ++nEndPos;
197 aVarName = QStringView(aValue).mid(nDollarPos + 2, nEndPos - nDollarPos - 3);
198 } else {
199 while (nEndPos < aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) {
200 ++nEndPos;
201 }
202 aVarName = QStringView(aValue).mid(nDollarPos + 1, nEndPos - nDollarPos - 1);
203 }
204 QString env;
205 if (!aVarName.isEmpty()) {
206#ifdef Q_OS_WIN
207 if (aVarName == QLatin1String("HOME")) {
208 env = QDir::homePath();
209 } else
210#endif
211 {
212 QByteArray pEnv = qgetenv(aVarName.toLatin1().constData());
213 if (!pEnv.isEmpty()) {
214 env = QString::fromLocal8Bit(pEnv.constData());
215 } else {
216 if (aVarName == QLatin1String("QT_DATA_HOME")) {
218 } else if (aVarName == QLatin1String("QT_CONFIG_HOME")) {
220 } else if (aVarName == QLatin1String("QT_CACHE_HOME")) {
222 }
223 }
224 }
225 aValue.replace(nDollarPos, nEndPos - nDollarPos, env);
226 nDollarPos += env.length();
227 } else {
228 aValue.remove(nDollarPos, nEndPos - nDollarPos);
229 }
230 } else {
231 // remove one of the dollar signs
232 aValue.remove(nDollarPos, 1);
233 ++nDollarPos;
234 }
235 nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos);
236 }
237
238 return aValue;
239}
240
242 : d_ptr(new KConfigPrivate(mode, resourceType))
243{
244 d_ptr->changeFileName(file); // set the local file name
245
246 // read initial information off disk
248}
249
250#if KCONFIGCORE_BUILD_DEPRECATED_SINCE(6, 3)
251KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType)
252 : d_ptr(new KConfigPrivate(SimpleConfig, resourceType))
253{
254 Q_UNUSED(backend);
255 d_ptr->changeFileName(file); // set the local file name
256
257 // read initial information off disk
259}
260#endif
261
262KConfig::KConfig(KConfigPrivate &d)
263 : d_ptr(&d)
264{
265}
266
267KConfig::~KConfig()
268{
269 Q_D(KConfig);
270 if (d->bDirty) {
271 sync();
272 }
273 delete d;
274}
275
276static bool isNonDeletedKey(KEntryMapConstIterator entryMapIt)
277{
278 return !entryMapIt->first.mKey.isNull() && !entryMapIt->second.bDeleted;
279}
280// is a key without default values and not deleted
281static bool isSetKey(KEntryMapConstIterator entryMapIt)
282{
283 return !entryMapIt->first.bDefault && !entryMapIt->second.bDeleted;
284}
285
286static int findFirstGroupEndPos(const QString &groupFullName, int from = 0)
287{
288 const auto index = groupFullName.indexOf(QLatin1Char('\x1d'), from);
289 return index == -1 ? groupFullName.size() : index;
290}
291
292static QStringList stringListFromStringViewCollection(const QSet<QStringView> &source)
293{
295 list.reserve(source.size());
296 std::transform(source.cbegin(), source.cend(), std::back_inserter(list), [](QStringView view) {
297 return view.toString();
298 });
299 return list;
300}
301
303{
304 Q_D(const KConfig);
305 QSet<QStringView> groups;
306
307 for (auto entryMapIt = d->entryMap.cbegin(); entryMapIt != d->entryMap.cend(); ++entryMapIt) {
308 const QString &group = entryMapIt->first.mGroup;
309 if (isNonDeletedKey(entryMapIt) && !group.isEmpty() && group != QStringLiteral("<default>") && group != QStringLiteral("$Version")) {
310 groups.insert(QStringView(group).left(findFirstGroupEndPos(group)));
311 }
312 }
313
314 return stringListFromStringViewCollection(groups);
315}
316
317QStringList KConfigPrivate::groupList(const QString &groupName) const
318{
319 const QString theGroup = groupName + QLatin1Char('\x1d');
320 QSet<QStringView> groups;
321
322 entryMap.forEachEntryWhoseGroupStartsWith(theGroup, [&theGroup, &groups](KEntryMapConstIterator entryMapIt) {
323 if (isNonDeletedKey(entryMapIt)) {
324 const QString &entryGroup = entryMapIt->first.mGroup;
325 const auto subgroupStartPos = theGroup.size();
326 const auto subgroupEndPos = findFirstGroupEndPos(entryGroup, subgroupStartPos);
327 groups.insert(QStringView(entryGroup).mid(subgroupStartPos, subgroupEndPos - subgroupStartPos));
328 }
329 });
330
331 return stringListFromStringViewCollection(groups);
332}
333
334/// Returns @p parentGroup itself, all its subgroups, subsubgroups, and so on, including deleted groups.
335QSet<QString> KConfigPrivate::allSubGroups(const QString &parentGroup) const
336{
337 QSet<QString> groups;
338
339 entryMap.forEachEntryWhoseGroupStartsWith(parentGroup, [&parentGroup, &groups](KEntryMapConstIterator entryMapIt) {
340 const KEntryKey &key = entryMapIt->first;
341 if (key.mKey.isNull() && isGroupOrSubGroupMatch(entryMapIt, parentGroup)) {
342 groups << key.mGroup;
343 }
344 });
345
346 return groups;
347}
348
349bool KConfigPrivate::hasNonDeletedEntries(const QString &group) const
350{
351 return entryMap.anyEntryWhoseGroupStartsWith(group, [&group](KEntryMapConstIterator entryMapIt) {
352 return isGroupOrSubGroupMatch(entryMapIt, group) && isNonDeletedKey(entryMapIt);
353 });
354}
355
356QList<QByteArray> KConfigPrivate::keyListImpl(const QString &theGroup) const
357{
358 std::set<QByteArray> tmp; // unique set, sorted for unittests
359
360 entryMap.forEachEntryOfGroup(theGroup, [&tmp](KEntryMapConstIterator it) {
361 if (isNonDeletedKey(it)) {
362 tmp.insert(it->first.mKey);
363 }
364 });
365
366 return QList<QByteArray>(tmp.begin(), tmp.end());
367}
368
369QStringList KConfigPrivate::usedKeyList(const QString &theGroup) const
370{
371 std::set<QString> tmp; // unique set, sorting as side-effect
372
373 entryMap.forEachEntryOfGroup(theGroup, [&tmp](KEntryMapConstIterator it) {
374 // leave the default values and deleted entries out, same as KConfig::entryMap()
375 if (isSetKey(it)) {
376 const QString key = QString::fromUtf8(it->first.mKey);
377 tmp.insert(key);
378 }
379 });
380
381 return QStringList(tmp.begin(), tmp.end());
382}
383
385{
386 Q_D(const KConfig);
388 const QString theGroup = aGroup.isEmpty() ? QStringLiteral("<default>") : aGroup;
389
390 d->entryMap.forEachEntryOfGroup(theGroup, [&theMap](KEntryMapConstIterator it) {
391 // leave the default values and deleted entries out
392 if (isSetKey(it)) {
393 const QString key = QString::fromUtf8(it->first.mKey.constData());
394 // the localized entry should come first, so don't overwrite it
395 // with the non-localized entry
396 if (!theMap.contains(key)) {
397 if (it->second.bExpand) {
398 theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->second.mValue.constData())));
399 } else {
400 theMap.insert(key, QString::fromUtf8(it->second.mValue.constData()));
401 }
402 }
403 }
404 });
405
406 return theMap;
407}
408
410{
411 Q_D(KConfig);
412
413 if (isImmutable() || name().isEmpty()) {
414 // can't write to an immutable or anonymous file.
415 return false;
416 }
417
418 QHash<QString, QByteArrayList> notifyGroupsLocal;
419 QHash<QString, QByteArrayList> notifyGroupsGlobal;
420
421 if (d->bDirty) {
422 const QByteArray utf8Locale(locale().toUtf8());
423
424 // Create the containing dir, maybe it wasn't there
425 d->mBackend.createEnclosing();
426
427 // lock the local file
428 if (d->configState == ReadWrite && !d->lockLocal()) {
429 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file";
430 return false;
431 }
432
433 // Rewrite global/local config only if there is a dirty entry in it.
434 bool writeGlobals = false;
435 bool writeLocals = false;
436
437 for (const auto &[key, e] : d->entryMap) {
438 if (e.bDirty) {
439 if (e.bGlobal) {
440 writeGlobals = true;
441 if (e.bNotify) {
442 notifyGroupsGlobal[key.mGroup] << key.mKey;
443 }
444 } else {
445 writeLocals = true;
446 if (e.bNotify) {
447 notifyGroupsLocal[key.mGroup] << key.mKey;
448 }
449 }
450 }
451 }
452
453 d->bDirty = false; // will revert to true if a config write fails
454
455 if (d->wantGlobals() && writeGlobals) {
456 KConfigIniBackend tmp;
457 tmp.setFilePath(*sGlobalFileName);
458 if (d->configState == ReadWrite && !tmp.lock()) {
459 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock global file";
460
461 // unlock the local config if we're returning early
462 if (d->mBackend.isLocked()) {
463 d->mBackend.unlock();
464 }
465
466 d->bDirty = true;
467 return false;
468 }
469 if (!tmp.writeConfig(utf8Locale, d->entryMap, KConfigIniBackend::WriteGlobal)) {
470 d->bDirty = true;
471 }
472 if (tmp.isLocked()) {
473 tmp.unlock();
474 }
475 }
476
477 if (writeLocals) {
478 if (!d->mBackend.writeConfig(utf8Locale, d->entryMap, KConfigIniBackend::WriteOptions())) {
479 d->bDirty = true;
480 }
481 }
482 if (d->mBackend.isLocked()) {
483 d->mBackend.unlock();
484 }
485 }
486
487 // Notifying absolute paths is not supported and also makes no sense.
488 const bool isAbsolutePath = name().at(0) == QLatin1Char('/');
489 if (!notifyGroupsLocal.isEmpty() && !isAbsolutePath) {
490 d->notifyClients(notifyGroupsLocal, kconfigDBusSanitizePath(QLatin1Char('/') + name()));
491 }
492 if (!notifyGroupsGlobal.isEmpty()) {
493 d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
494 }
495
496 return !d->bDirty;
497}
498
499void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
500{
501#if KCONFIG_USE_DBUS
502 qDBusRegisterMetaType<QByteArrayList>();
503
504 qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
505
506 QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"));
507 message.setArguments({QVariant::fromValue(changes)});
509#else
510 Q_UNUSED(changes)
511 Q_UNUSED(path)
512#endif
513}
514
516{
517 Q_D(KConfig);
518 d->bDirty = false;
519
520 // clear any dirty flags that entries might have set
521 for (auto &[_, entry] : d->entryMap) {
522 entry.bDirty = false;
523 entry.bNotify = false;
524 }
525}
526
528{
529 Q_D(const KConfig);
530 return d->bDirty;
531}
532
533void KConfig::checkUpdate(const QString &id, const QString &updateFile)
534{
535 const KConfigGroup cg(this, QStringLiteral("$Version"));
536 const QString cfg_id = updateFile + QLatin1Char(':') + id;
537 const QStringList ids = cg.readEntry("update_info", QStringList());
538 if (!ids.contains(cfg_id)) {
539 QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList{QStringLiteral("--check"), updateFile});
541 }
542}
543
544KConfig *KConfig::copyTo(const QString &file, KConfig *config) const
545{
546 Q_D(const KConfig);
547 if (!config) {
548 config = new KConfig(QString(), SimpleConfig, d->resourceType);
549 }
550 config->d_func()->changeFileName(file);
551 config->d_func()->entryMap = d->entryMap;
552 config->d_func()->bFileImmutable = false;
553
554 for (auto &[_, entry] : config->d_func()->entryMap) {
555 entry.bDirty = true;
556 }
557 config->d_ptr->bDirty = true;
558
559 return config;
560}
561
563{
564 Q_D(const KConfig);
565 return d->fileName;
566}
567
569{
570 Q_D(const KConfig);
571 return d->openFlags;
572}
573
574struct KConfigStaticData {
575 QString globalMainConfigName;
576 // Keep a copy so we can use it in global dtors, after qApp is gone
577 QStringList appArgs;
578};
579Q_GLOBAL_STATIC(KConfigStaticData, globalData)
580static QBasicMutex s_globalDataMutex;
581
583{
584 QMutexLocker locker(&s_globalDataMutex);
585 globalData()->globalMainConfigName = str;
586}
587
589{
590 QMutexLocker locker(&s_globalDataMutex);
591 KConfigStaticData *data = globalData();
592 if (data->appArgs.isEmpty()) {
593 data->appArgs = QCoreApplication::arguments();
594 }
595
596 // --config on the command line overrides everything else
597 const QStringList args = data->appArgs;
598 for (int i = 1; i < args.count(); ++i) {
599 if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
600 return args.at(i + 1);
601 }
602 }
603 const QString globalName = data->globalMainConfigName;
604 if (!globalName.isEmpty()) {
605 return globalName;
606 }
607
609 return appName + QLatin1String("rc");
610}
611
612void KConfigPrivate::changeFileName(const QString &name)
613{
614 fileName = name;
615
616 QString file;
617 if (name.isEmpty()) {
618 if (wantDefaults()) { // accessing default app-specific config "appnamerc"
619 fileName = KConfig::mainConfigName();
620 file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
621 } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
623 fileName = QStringLiteral("kdeglobals");
624 file = *sGlobalFileName;
625 } else {
626 // anonymous config
627 openFlags = KConfig::SimpleConfig;
628 return;
629 }
630 } else if (QDir::isAbsolutePath(fileName)) {
631 fileName = QFileInfo(fileName).canonicalFilePath();
632 if (fileName.isEmpty()) { // file doesn't exist (yet)
633 fileName = name;
634 }
635 file = fileName;
636 } else {
637 file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
638 }
639
640 Q_ASSERT(!file.isEmpty());
641
642 bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0);
643
644 mBackend.setFilePath(file);
645
646 configState = mBackend.accessMode();
647}
648
650{
651 Q_D(KConfig);
652 if (d->fileName.isEmpty()) {
653 return;
654 }
655
656 // Don't lose pending changes
657 if (!d->isReadOnly() && d->bDirty) {
658 sync();
659 }
660
661 d->entryMap.clear();
662
663 d->bFileImmutable = false;
664
665 {
666 QMutexLocker locker(&s_globalFilesMutex);
667 s_globalFiles()->clear();
668 }
669
670 // Parse all desired files from the least to the most specific.
671 if (d->wantGlobals()) {
672 d->parseGlobalFiles();
673 }
674
675#ifdef Q_OS_WIN
676 // Parse the windows registry defaults if desired
677 if (d->openFlags & ~KConfig::SimpleConfig) {
678 d->parseWindowsDefaults();
679 }
680#endif
681
682 d->parseConfigFiles();
683}
684
685QStringList KConfigPrivate::getGlobalFiles() const
686{
687 QMutexLocker locker(&s_globalFilesMutex);
688 if (s_globalFiles()->isEmpty()) {
689 const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
690 const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
691
692 const bool useEtcKderc = !etc_kderc.isEmpty();
693 s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
694
695 for (const QString &dir1 : paths1) {
696 s_globalFiles()->push_front(dir1);
697 }
698 for (const QString &dir2 : paths2) {
699 s_globalFiles()->push_front(dir2);
700 }
701
702 if (useEtcKderc) {
703 s_globalFiles()->push_front(etc_kderc);
704 }
705 }
706
707 return *s_globalFiles();
708}
709
710void KConfigPrivate::parseGlobalFiles()
711{
712 const QStringList globalFiles = getGlobalFiles();
713 // qDebug() << "parsing global files" << globalFiles;
714
715 Q_ASSERT(entryMap.empty());
716
717 const ParseCacheKey key = {globalFiles, locale};
718 auto data = sGlobalParse.localData().object(key);
719 QDateTime newest;
720 for (const auto &file : globalFiles) {
721 const auto fileDate = QFileInfo(file).lastModified(QTimeZone::UTC);
722 if (fileDate > newest) {
723 newest = fileDate;
724 }
725 }
726 if (data) {
727 if (data->parseTime < newest) {
728 data = nullptr;
729 } else {
730 entryMap = data->entries;
731 return;
732 }
733 }
734
735 const QByteArray utf8Locale = locale.toUtf8();
736 for (const QString &file : globalFiles) {
737 KConfigIniBackend::ParseOptions parseOpts = KConfigIniBackend::ParseGlobal | KConfigIniBackend::ParseExpansions;
738
739 if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) {
740 parseOpts |= KConfigIniBackend::ParseDefaults;
741 }
742
743 KConfigIniBackend backend;
744 backend.setFilePath(file);
745 if (backend.parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable) {
746 break;
747 }
748 }
749 sGlobalParse.localData().insert(key, new ParseCacheValue({entryMap, newest}));
750}
751
752#ifdef Q_OS_WIN
753void KConfigPrivate::parseWindowsDefaults()
754{
755 if (fileName.isEmpty() || QCoreApplication::organizationName().isEmpty()) {
756 return;
757 }
758 auto registryKey =
759 QStringLiteral("SOFTWARE\\%1\\%2")
760 .arg(QCoreApplication::organizationName(), fileName.endsWith(QStringLiteral("rc")) ? fileName.left(fileName.length() - 2) : fileName);
761 WindowsRegistry::parse(registryKey, entryMap);
762}
763#endif
764
765void KConfigPrivate::parseConfigFiles()
766{
767 // can only read the file if there is a backend and a file name
768 if (!fileName.isEmpty()) {
769 bFileImmutable = false;
770
771 QList<QString> files;
772 if (wantDefaults()) {
773 if (bSuppressGlobal) {
774 files = getGlobalFiles();
775 } else {
776 if (QDir::isAbsolutePath(fileName)) {
777 const QString canonicalFile = QFileInfo(fileName).canonicalFilePath();
778 if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
779 files << canonicalFile;
780 }
781 } else {
782 const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName);
783 for (const QString &f : localFilesPath) {
784 files.prepend(QFileInfo(f).canonicalFilePath());
785 }
786
787 // allow fallback to config files bundled in resources
788 const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
789 if (QFile::exists(resourceFile)) {
790 files.prepend(resourceFile);
791 }
792 }
793 }
794 } else {
795 files << mBackend.filePath();
796 }
797 if (!isSimple()) {
798 files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
799 }
800
801 // qDebug() << "parsing local files" << files;
802
803 const QByteArray utf8Locale = locale.toUtf8();
804 for (const QString &file : std::as_const(files)) {
805 if (file.compare(mBackend.filePath(), sPathCaseSensitivity) == 0) {
806 switch (mBackend.parseConfig(utf8Locale, entryMap, KConfigIniBackend::ParseExpansions)) {
807 case KConfigIniBackend::ParseOk:
808 break;
809 case KConfigIniBackend::ParseImmutable:
810 bFileImmutable = true;
811 break;
812 case KConfigIniBackend::ParseOpenError:
813 configState = KConfigBase::NoAccess;
814 break;
815 }
816 } else {
817 KConfigIniBackend backend;
818 backend.setFilePath(file);
819 constexpr auto parseOpts = KConfigIniBackend::ParseDefaults | KConfigIniBackend::ParseExpansions;
820 bFileImmutable = backend.parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable;
821 }
822
823 if (bFileImmutable) {
824 break;
825 }
826 }
827 }
828}
829
831{
832 Q_D(const KConfig);
833 return d->configState;
834}
835
837{
838 Q_D(KConfig);
839 for (const QString &file : files) {
840 d->extraFiles.push(file);
841 }
842
843 if (!files.isEmpty()) {
845 }
846}
847
849{
850 Q_D(const KConfig);
851 return d->extraFiles.toList();
852}
853
855{
856 Q_D(const KConfig);
857 return d->locale;
858}
859
860bool KConfigPrivate::setLocale(const QString &aLocale)
861{
862 if (aLocale != locale) {
863 locale = aLocale;
864 return true;
865 }
866 return false;
867}
868
870{
871 Q_D(KConfig);
872 if (d->setLocale(locale)) {
874 return true;
875 }
876 return false;
877}
878
880{
881 Q_D(KConfig);
882 d->bReadDefaults = b;
883}
884
886{
887 Q_D(const KConfig);
888 return d->bReadDefaults;
889}
890
892{
893 Q_D(const KConfig);
894 return d->bFileImmutable;
895}
896
897bool KConfig::isGroupImmutableImpl(const QString &aGroup) const
898{
899 Q_D(const KConfig);
900 return isImmutable() || d->entryMap.getEntryOption(aGroup, {}, {}, KEntryMap::EntryImmutable);
901}
902
903KConfigGroup KConfig::groupImpl(const QString &group)
904{
905 return KConfigGroup(this, group);
906}
907
908const KConfigGroup KConfig::groupImpl(const QString &group) const
909{
910 return KConfigGroup(this, group);
911}
912
913KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
914{
915 KEntryMap::EntryOptions options = {};
916
917 if (flags & KConfig::Persistent) {
918 options |= KEntryMap::EntryDirty;
919 }
920 if (flags & KConfig::Global) {
921 options |= KEntryMap::EntryGlobal;
922 }
923 if (flags & KConfig::Localized) {
924 options |= KEntryMap::EntryLocalized;
925 }
926 if (flags.testFlag(KConfig::Notify)) {
927 options |= KEntryMap::EntryNotify;
928 }
929 return options;
930}
931
933{
934 Q_D(KConfig);
935 KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
936
937 const QSet<QString> groups = d->allSubGroups(aGroup);
938 for (const QString &group : groups) {
939 const QList<QByteArray> keys = d->keyListImpl(group);
940 for (const QByteArray &key : keys) {
941 if (d->canWriteEntry(group, key)) {
942 d->entryMap.setEntry(group, key, QByteArray(), options);
943 d->bDirty = true;
944 }
945 }
946 }
947}
948
949bool KConfig::isConfigWritable(bool warnUser)
950{
951 Q_D(KConfig);
952 bool allWritable = d->mBackend.isWritable();
953
954 if (warnUser && !allWritable) {
955 QString errorMsg;
956 errorMsg = d->mBackend.nonWritableErrorMessage();
957
958 // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
959 errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator.");
960 QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
961 if (!cmdToExec.isEmpty()) {
962 QProcess::execute(cmdToExec, QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
963 }
964 }
965
966 d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
967
968 return allWritable;
969}
970
971bool KConfig::hasGroupImpl(const QString &aGroup) const
972{
973 Q_D(const KConfig);
974
975 // No need to look for the actual group entry anymore, or for subgroups:
976 // a group exists if it contains any non-deleted entry.
977
978 return d->hasNonDeletedEntries(aGroup);
979}
980
981bool KConfigPrivate::canWriteEntry(const QString &group, QAnyStringView key, bool isDefault) const
982{
983 if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) {
984 return isDefault;
985 }
986 return true;
987}
988
989void KConfigPrivate::putData(const QString &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
990{
991 KEntryMap::EntryOptions options = convertToOptions(flags);
992
993 if (bForceGlobal) {
994 options |= KEntryMap::EntryGlobal;
995 }
996 if (expand) {
997 options |= KEntryMap::EntryExpansion;
998 }
999
1000 if (value.isNull()) { // deleting entry
1001 options |= KEntryMap::EntryDeleted;
1002 }
1003
1004 bool dirtied = entryMap.setEntry(group, key, value, options);
1005 if (dirtied && (flags & KConfigBase::Persistent)) {
1006 bDirty = true;
1007 }
1008}
1009
1010void KConfigPrivate::revertEntry(const QString &group, QAnyStringView key, KConfigBase::WriteConfigFlags flags)
1011{
1012 KEntryMap::EntryOptions options = convertToOptions(flags);
1013
1014 bool dirtied = entryMap.revertEntry(group, key, options);
1015 if (dirtied) {
1016 bDirty = true;
1017 }
1018}
1019
1020QByteArray KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1021{
1022 return lookupInternalEntry(group, key, flags).mValue;
1023}
1024
1025KEntry KConfigPrivate::lookupInternalEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1026{
1027 if (bReadDefaults) {
1028 flags |= KEntryMap::SearchDefaults;
1029 }
1030 const auto it = entryMap.constFindEntry(group, key, flags);
1031 if (it == entryMap.cend()) {
1032 return {};
1033 }
1034 return it->second;
1035}
1036
1037QString KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags, bool *expand) const
1038{
1039 if (bReadDefaults) {
1040 flags |= KEntryMap::SearchDefaults;
1041 }
1042 return entryMap.getEntry(group, key, QString(), flags, expand);
1043}
1044
1046{
1047 Q_D(const KConfig);
1048 return d->resourceType;
1049}
1050
1051void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1052{
1053 /* nothing */
1054}
Interface to interact with configuration.
Definition kconfigbase.h:31
@ Notify
Notify remote KConfigWatchers of changes (requires DBus support) Implied persistent.
Definition kconfigbase.h:51
@ Persistent
Save this entry when saving the config object.
Definition kconfigbase.h:38
@ Global
Save the entry to the global KDE config file instead of the application specific config file.
Definition kconfigbase.h:42
@ Localized
Add the locale tag to the key when writing it.
Definition kconfigbase.h:47
AccessMode
Possible return values for accessMode().
QFlags< WriteConfigFlag > WriteConfigFlags
Stores a combination of WriteConfigFlag values.
Definition kconfigbase.h:67
KConfigGroup group(const QString &group)
Returns an object for the named subgroup.
A class for one specific group in a KConfig object.
T readEntry(const QString &key, const T &aDefault) const
Reads the value of an entry specified by pKey in the current group.
KConfig * config()
Return the config object that this group belongs to.
KConfig(const QString &file=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
Creates a KConfig object to manipulate a configuration file for the current application.
Definition kconfig.cpp:241
AccessMode accessMode() const override
Definition kconfig.cpp:830
void markAsClean() override
Definition kconfig.cpp:515
QStringList additionalConfigSources() const
Returns a list of the additional configuration sources used in this object.
Definition kconfig.cpp:848
QString locale() const
locales
Definition kconfig.cpp:854
void reparseConfiguration()
Updates the state of this object to match the persistent storage.
Definition kconfig.cpp:649
QMap< QString, QString > entryMap(const QString &aGroup=QString()) const
Returns a map (tree) of entries in a particular group.
Definition kconfig.cpp:384
void setReadDefaults(bool b)
defaults
Definition kconfig.cpp:879
bool sync() override
Definition kconfig.cpp:409
QString name() const
Returns the filename used to store the configuration.
Definition kconfig.cpp:562
bool hasGroupImpl(const QString &groupName) const override
Definition kconfig.cpp:971
void virtual_hook(int id, void *data) override
Virtual hook, used to add new "virtual" functions while maintaining binary compatibility.
Definition kconfig.cpp:1051
static QString mainConfigName()
Get the name of application config file.
Definition kconfig.cpp:588
bool readDefaults() const
Definition kconfig.cpp:885
bool isImmutable() const override
Definition kconfig.cpp:891
void checkUpdate(const QString &id, const QString &updateFile)
Ensures that the configuration file contains a certain update.
Definition kconfig.cpp:533
QStringList groupList() const override
Definition kconfig.cpp:302
KConfig * copyTo(const QString &file, KConfig *config=nullptr) const
Copies all entries from this config object to a new config object that will save itself to file.
Definition kconfig.cpp:544
static void setMainConfigName(const QString &str)
Sets the name of the application config file.
Definition kconfig.cpp:582
bool isGroupImmutableImpl(const QString &groupName) const override
Definition kconfig.cpp:897
KConfigGroup groupImpl(const QString &groupName) override
Definition kconfig.cpp:903
QStandardPaths::StandardLocation locationType() const
Returns the standard location enum passed to the constructor.
Definition kconfig.cpp:1045
OpenFlags openFlags() const
Definition kconfig.cpp:568
void addConfigSources(const QStringList &sources)
extra config files
Definition kconfig.cpp:836
bool isConfigWritable(bool warnUser)
Whether the configuration can be written to.
Definition kconfig.cpp:949
@ SimpleConfig
Just a single config file.
Definition kconfig.h:85
bool isDirty() const
Returns true if sync has any changes to write out.
Definition kconfig.cpp:527
void deleteGroupImpl(const QString &groupName, WriteConfigFlags flags=Normal) override
Definition kconfig.cpp:932
bool setLocale(const QString &aLocale)
Sets the locale to aLocale.
Definition kconfig.cpp:869
QFlags< OpenFlag > OpenFlags
Stores a combination of OpenFlag values.
Definition kconfig.h:93
QString name(const QVariant &location)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const char * constData() const const
bool isEmpty() const const
bool isNull() const const
QStringList arguments()
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
QString homePath()
bool isAbsolutePath(const QString &path)
QString decodeName(const QByteArray &localFileName)
bool exists() const const
bool testFlag(Enum flag) const const
bool isEmpty() const const
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
void prepend(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
int execute(const QString &program, const QStringList &arguments)
const_iterator cbegin() const const
const_iterator cend() const const
iterator insert(const T &value)
qsizetype size() const const
QString findExecutable(const QString &executableName, const QStringList &paths)
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QString writableLocation(StandardLocation type)
const QChar at(qsizetype position) const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString first(qsizetype n) const const
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringView mid(qsizetype start, qsizetype length) const const
bool isEmpty() const const
QByteArray toLatin1() const const
CaseSensitivity
QVariant fromValue(T &&value)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:49:40 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.