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

KDE's Doxygen guidelines are available online.