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 "kconfigbackend_p.h"
20#include "kconfiggroup.h"
21
22#include <QBasicMutex>
23#include <QByteArray>
24#include <QCache>
25#include <QCoreApplication>
26#include <QDir>
27#include <QFile>
28#include <QLocale>
29#include <QMutexLocker>
30#include <QProcess>
31#include <QSet>
32#include <QThreadStorage>
33#include <QTimeZone>
34
35#include <algorithm>
36#include <iterator>
37#include <set>
38
39#if KCONFIG_USE_DBUS
40#include <QDBusConnection>
41#include <QDBusMessage>
42#include <QDBusMetaType>
43#endif
44
45bool KConfigPrivate::mappingsRegistered = false;
46
47// For caching purposes
48static bool s_wasTestModeEnabled = false;
49
50Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes.
51static QBasicMutex s_globalFilesMutex;
52Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals")))
53
54using ParseCacheKey = std::pair<QStringList, QString>;
55struct ParseCacheValue {
56 KEntryMap entries;
57 QDateTime parseTime;
58};
59using ParseCache = QThreadStorage<QCache<ParseCacheKey, ParseCacheValue>>;
60Q_GLOBAL_STATIC(ParseCache, sGlobalParse)
61
62#ifndef Q_OS_WIN
63static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive;
64#else
65static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive;
66#endif
67
68KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType)
69 : openFlags(flags)
70 , resourceType(resourceType)
71 , mBackend(nullptr)
72 , bDynamicBackend(true)
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;
85 *sGlobalFileName = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals");
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(newValue: !qEnvironmentVariableIsSet(varName: "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(newValue: 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 if (mBackend) {
126 return mBackend->lock();
127 }
128 // anonymous object - pretend we locked it
129 return true;
130}
131
132static bool isGroupOrSubGroupMatch(KEntryMapConstIterator entryMapIt, const QString &group)
133{
134 const QString &entryGroup = entryMapIt->first.mGroup;
135 Q_ASSERT_X(entryGroup.startsWith(group), Q_FUNC_INFO, "Precondition");
136 return entryGroup.size() == group.size() || entryGroup[group.size()] == QLatin1Char('\x1d');
137}
138
139void KConfigPrivate::copyGroup(const QString &source, const QString &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const
140{
141 KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap;
142 const bool sameName = (destination == source);
143
144 // we keep this bool outside the for loop so that if
145 // the group is empty, we don't end up marking the other config
146 // as dirty erroneously
147 bool dirtied = false;
148
149 entryMap.forEachEntryWhoseGroupStartsWith(groupPrefix: source, callback: [&source, &destination, flags, &otherMap, sameName, &dirtied](KEntryMapConstIterator entryMapIt) {
150 // don't copy groups that start with the same prefix, but are not sub-groups
151 if (!isGroupOrSubGroupMatch(entryMapIt, group: source)) {
152 return;
153 }
154
155 KEntryKey newKey = entryMapIt->first;
156
157 if (flags & KConfigBase::Localized) {
158 newKey.bLocal = true;
159 }
160
161 if (!sameName) {
162 newKey.mGroup.replace(i: 0, len: source.size(), after: destination);
163 }
164
165 KEntry entry = entryMapIt->second;
166 dirtied = entry.bDirty = flags & KConfigBase::Persistent;
167
168 if (flags & KConfigBase::Global) {
169 entry.bGlobal = true;
170 }
171
172 if (flags & KConfigBase::Notify) {
173 entry.bNotify = true;
174 }
175
176 otherMap[newKey] = entry;
177 });
178
179 if (dirtied) {
180 otherGroup->config()->d_ptr->bDirty = true;
181 }
182}
183
184QString KConfigPrivate::expandString(const QString &value)
185{
186 QString aValue = value;
187
188 // check for environment variables and make necessary translations
189 int nDollarPos = aValue.indexOf(c: QLatin1Char('$'));
190 while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
191 // there is at least one $
192 if (aValue.at(i: nDollarPos + 1) != QLatin1Char('$')) {
193 int nEndPos = nDollarPos + 1;
194 // the next character is not $
195 QStringView aVarName;
196 if (aValue.at(i: nEndPos) == QLatin1Char('{')) {
197 while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) {
198 ++nEndPos;
199 }
200 ++nEndPos;
201 aVarName = QStringView(aValue).mid(pos: nDollarPos + 2, n: nEndPos - nDollarPos - 3);
202 } else {
203 while (nEndPos < aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) {
204 ++nEndPos;
205 }
206 aVarName = QStringView(aValue).mid(pos: nDollarPos + 1, n: nEndPos - nDollarPos - 1);
207 }
208 QString env;
209 if (!aVarName.isEmpty()) {
210#ifdef Q_OS_WIN
211 if (aVarName == QLatin1String("HOME")) {
212 env = QDir::homePath();
213 } else
214#endif
215 {
216 QByteArray pEnv = qgetenv(varName: aVarName.toLatin1().constData());
217 if (!pEnv.isEmpty()) {
218 env = QString::fromLocal8Bit(ba: pEnv.constData());
219 } else {
220 if (aVarName == QLatin1String("QT_DATA_HOME")) {
221 env = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation);
222 } else if (aVarName == QLatin1String("QT_CONFIG_HOME")) {
223 env = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation);
224 } else if (aVarName == QLatin1String("QT_CACHE_HOME")) {
225 env = QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation);
226 }
227 }
228 }
229 aValue.replace(i: nDollarPos, len: nEndPos - nDollarPos, after: env);
230 nDollarPos += env.length();
231 } else {
232 aValue.remove(i: nDollarPos, len: nEndPos - nDollarPos);
233 }
234 } else {
235 // remove one of the dollar signs
236 aValue.remove(i: nDollarPos, len: 1);
237 ++nDollarPos;
238 }
239 nDollarPos = aValue.indexOf(c: QLatin1Char('$'), from: nDollarPos);
240 }
241
242 return aValue;
243}
244
245KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType)
246 : d_ptr(new KConfigPrivate(mode, resourceType))
247{
248 d_ptr->changeFileName(fileName: file); // set the local file name
249
250 // read initial information off disk
251 reparseConfiguration();
252}
253
254KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType)
255 : d_ptr(new KConfigPrivate(SimpleConfig, resourceType))
256{
257 d_ptr->mBackend = KConfigBackend::create(fileName: file, system: backend);
258 d_ptr->bDynamicBackend = false;
259 d_ptr->changeFileName(fileName: file); // set the local file name
260
261 // read initial information off disk
262 reparseConfiguration();
263}
264
265KConfig::KConfig(KConfigPrivate &d)
266 : d_ptr(&d)
267{
268}
269
270KConfig::~KConfig()
271{
272 Q_D(KConfig);
273 if (d->bDirty && (d->mBackend && d->mBackend->ref.loadRelaxed() == 1)) {
274 sync();
275 }
276 delete d;
277}
278
279static bool isNonDeletedKey(KEntryMapConstIterator entryMapIt)
280{
281 return !entryMapIt->first.mKey.isNull() && !entryMapIt->second.bDeleted;
282}
283// is a key without default values and not deleted
284static bool isSetKey(KEntryMapConstIterator entryMapIt)
285{
286 return !entryMapIt->first.bDefault && !entryMapIt->second.bDeleted;
287}
288
289static int findFirstGroupEndPos(const QString &groupFullName, int from = 0)
290{
291 const auto index = groupFullName.indexOf(c: QLatin1Char('\x1d'), from);
292 return index == -1 ? groupFullName.size() : index;
293}
294
295static QStringList stringListFromStringViewCollection(const QSet<QStringView> &source)
296{
297 QStringList list;
298 list.reserve(asize: source.size());
299 std::transform(first: source.cbegin(), last: source.cend(), result: std::back_inserter(x&: list), unary_op: [](QStringView view) {
300 return view.toString();
301 });
302 return list;
303}
304
305QStringList KConfig::groupList() const
306{
307 Q_D(const KConfig);
308 QSet<QStringView> groups;
309
310 for (auto entryMapIt = d->entryMap.cbegin(); entryMapIt != d->entryMap.cend(); ++entryMapIt) {
311 const QString &group = entryMapIt->first.mGroup;
312 if (isNonDeletedKey(entryMapIt) && !group.isEmpty() && group != QStringLiteral("<default>") && group != QStringLiteral("$Version")) {
313 groups.insert(value: QStringView(group).left(n: findFirstGroupEndPos(groupFullName: group)));
314 }
315 }
316
317 return stringListFromStringViewCollection(source: groups);
318}
319
320QStringList KConfigPrivate::groupList(const QString &groupName) const
321{
322 const QString theGroup = groupName + QLatin1Char('\x1d');
323 QSet<QStringView> groups;
324
325 entryMap.forEachEntryWhoseGroupStartsWith(groupPrefix: theGroup, callback: [&theGroup, &groups](KEntryMapConstIterator entryMapIt) {
326 if (isNonDeletedKey(entryMapIt)) {
327 const QString &entryGroup = entryMapIt->first.mGroup;
328 const auto subgroupStartPos = theGroup.size();
329 const auto subgroupEndPos = findFirstGroupEndPos(groupFullName: entryGroup, from: subgroupStartPos);
330 groups.insert(value: QStringView(entryGroup).mid(pos: subgroupStartPos, n: subgroupEndPos - subgroupStartPos));
331 }
332 });
333
334 return stringListFromStringViewCollection(source: groups);
335}
336
337/// Returns @p parentGroup itself, all its subgroups, subsubgroups, and so on, including deleted groups.
338QSet<QString> KConfigPrivate::allSubGroups(const QString &parentGroup) const
339{
340 QSet<QString> groups;
341
342 entryMap.forEachEntryWhoseGroupStartsWith(groupPrefix: parentGroup, callback: [&parentGroup, &groups](KEntryMapConstIterator entryMapIt) {
343 const KEntryKey &key = entryMapIt->first;
344 if (key.mKey.isNull() && isGroupOrSubGroupMatch(entryMapIt, group: parentGroup)) {
345 groups << key.mGroup;
346 }
347 });
348
349 return groups;
350}
351
352bool KConfigPrivate::hasNonDeletedEntries(const QString &group) const
353{
354 return entryMap.anyEntryWhoseGroupStartsWith(groupPrefix: group, predicate: [&group](KEntryMapConstIterator entryMapIt) {
355 return isGroupOrSubGroupMatch(entryMapIt, group) && isNonDeletedKey(entryMapIt);
356 });
357}
358
359QList<QByteArray> KConfigPrivate::keyListImpl(const QString &theGroup) const
360{
361 std::set<QByteArray> tmp; // unique set, sorted for unittests
362
363 entryMap.forEachEntryOfGroup(theGroup, callback: [&tmp](KEntryMapConstIterator it) {
364 if (isNonDeletedKey(entryMapIt: it)) {
365 tmp.insert(x: it->first.mKey);
366 }
367 });
368
369 return QList<QByteArray>(tmp.begin(), tmp.end());
370}
371
372QStringList KConfigPrivate::usedKeyList(const QString &theGroup) const
373{
374 std::set<QString> tmp; // unique set, sorting as side-effect
375
376 entryMap.forEachEntryOfGroup(theGroup, callback: [&tmp](KEntryMapConstIterator it) {
377 // leave the default values and deleted entries out, same as KConfig::entryMap()
378 if (isSetKey(entryMapIt: it)) {
379 const QString key = QString::fromUtf8(ba: it->first.mKey);
380 tmp.insert(x: key);
381 }
382 });
383
384 return QStringList(tmp.begin(), tmp.end());
385}
386
387QMap<QString, QString> KConfig::entryMap(const QString &aGroup) const
388{
389 Q_D(const KConfig);
390 QMap<QString, QString> theMap;
391 const QString theGroup = aGroup.isEmpty() ? QStringLiteral("<default>") : aGroup;
392
393 d->entryMap.forEachEntryOfGroup(theGroup, callback: [&theMap](KEntryMapConstIterator it) {
394 // leave the default values and deleted entries out
395 if (isSetKey(entryMapIt: it)) {
396 const QString key = QString::fromUtf8(utf8: it->first.mKey.constData());
397 // the localized entry should come first, so don't overwrite it
398 // with the non-localized entry
399 if (!theMap.contains(key)) {
400 if (it->second.bExpand) {
401 theMap.insert(key, value: KConfigPrivate::expandString(value: QString::fromUtf8(utf8: it->second.mValue.constData())));
402 } else {
403 theMap.insert(key, value: QString::fromUtf8(utf8: it->second.mValue.constData()));
404 }
405 }
406 }
407 });
408
409 return theMap;
410}
411
412bool KConfig::sync()
413{
414 Q_D(KConfig);
415
416 if (isImmutable() || name().isEmpty()) {
417 // can't write to an immutable or anonymous file.
418 return false;
419 }
420
421 QHash<QString, QByteArrayList> notifyGroupsLocal;
422 QHash<QString, QByteArrayList> notifyGroupsGlobal;
423
424 if (d->bDirty && d->mBackend) {
425 const QByteArray utf8Locale(locale().toUtf8());
426
427 // Create the containing dir, maybe it wasn't there
428 d->mBackend->createEnclosing();
429
430 // lock the local file
431 if (d->configState == ReadWrite && !d->lockLocal()) {
432 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file";
433 return false;
434 }
435
436 // Rewrite global/local config only if there is a dirty entry in it.
437 bool writeGlobals = false;
438 bool writeLocals = false;
439
440 for (const auto &[key, e] : d->entryMap) {
441 if (e.bDirty) {
442 if (e.bGlobal) {
443 writeGlobals = true;
444 if (e.bNotify) {
445 notifyGroupsGlobal[key.mGroup] << key.mKey;
446 }
447 } else {
448 writeLocals = true;
449 if (e.bNotify) {
450 notifyGroupsLocal[key.mGroup] << key.mKey;
451 }
452 }
453 }
454 }
455
456 d->bDirty = false; // will revert to true if a config write fails
457
458 if (d->wantGlobals() && writeGlobals) {
459 QExplicitlySharedDataPointer<KConfigBackend> tmp = KConfigBackend::create(fileName: *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(locale: utf8Locale, entryMap&: d->entryMap, options: KConfigBackend::WriteGlobal)) {
472 d->bDirty = true;
473 }
474 if (tmp->isLocked()) {
475 tmp->unlock();
476 }
477 }
478
479 if (writeLocals) {
480 if (!d->mBackend->writeConfig(locale: utf8Locale, entryMap&: d->entryMap, options: KConfigBackend::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(i: 0) == QLatin1Char('/');
491 if (!notifyGroupsLocal.isEmpty() && !isAbsolutePath) {
492 d->notifyClients(changes: notifyGroupsLocal, path: kconfigDBusSanitizePath(path: QLatin1Char('/') + name()));
493 }
494 if (!notifyGroupsGlobal.isEmpty()) {
495 d->notifyClients(changes: 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(value: changes)});
510 QDBusConnection::sessionBus().send(message);
511#else
512 Q_UNUSED(changes)
513 Q_UNUSED(path)
514#endif
515}
516
517void KConfig::markAsClean()
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
529bool KConfig::isDirty() const
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(key: "update_info", aDefault: QStringList());
540 if (!ids.contains(str: cfg_id)) {
541 QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), arguments: QStringList{QStringLiteral("--check"), updateFile});
542 reparseConfiguration();
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(fileName: 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
564QString KConfig::name() const
565{
566 Q_D(const KConfig);
567 return d->fileName;
568}
569
570KConfig::OpenFlags KConfig::openFlags() const
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)
582
583void KConfig::setMainConfigName(const QString &str)
584{
585 globalData()->globalMainConfigName = str;
586}
587
588QString KConfig::mainConfigName()
589{
590 KConfigStaticData *data = globalData();
591 if (data->appArgs.isEmpty()) {
592 data->appArgs = QCoreApplication::arguments();
593 }
594
595 // --config on the command line overrides everything else
596 const QStringList args = data->appArgs;
597 for (int i = 1; i < args.count(); ++i) {
598 if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
599 return args.at(i: i + 1);
600 }
601 }
602 const QString globalName = data->globalMainConfigName;
603 if (!globalName.isEmpty()) {
604 return globalName;
605 }
606
607 QString appName = QCoreApplication::applicationName();
608 return appName + QLatin1String("rc");
609}
610
611void KConfigPrivate::changeFileName(const QString &name)
612{
613 fileName = name;
614
615 QString file;
616 if (name.isEmpty()) {
617 if (wantDefaults()) { // accessing default app-specific config "appnamerc"
618 fileName = KConfig::mainConfigName();
619 file = QStandardPaths::writableLocation(type: resourceType) + QLatin1Char('/') + fileName;
620 } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
621 resourceType = QStandardPaths::GenericConfigLocation;
622 fileName = QStringLiteral("kdeglobals");
623 file = *sGlobalFileName;
624 } else {
625 // anonymous config
626 openFlags = KConfig::SimpleConfig;
627 return;
628 }
629 } else if (QDir::isAbsolutePath(path: fileName)) {
630 fileName = QFileInfo(fileName).canonicalFilePath();
631 if (fileName.isEmpty()) { // file doesn't exist (yet)
632 fileName = name;
633 }
634 file = fileName;
635 } else {
636 file = QStandardPaths::writableLocation(type: resourceType) + QLatin1Char('/') + fileName;
637 }
638
639 Q_ASSERT(!file.isEmpty());
640
641 bSuppressGlobal = (file.compare(s: *sGlobalFileName, cs: sPathCaseSensitivity) == 0);
642
643 if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend
644 mBackend = KConfigBackend::create(fileName: file);
645 } else {
646 mBackend->setFilePath(file);
647 }
648
649 configState = mBackend->accessMode();
650}
651
652void KConfig::reparseConfiguration()
653{
654 Q_D(KConfig);
655 if (d->fileName.isEmpty()) {
656 return;
657 }
658
659 // Don't lose pending changes
660 if (!d->isReadOnly() && d->bDirty) {
661 sync();
662 }
663
664 d->entryMap.clear();
665
666 d->bFileImmutable = false;
667
668 {
669 QMutexLocker locker(&s_globalFilesMutex);
670 s_globalFiles()->clear();
671 }
672
673 // Parse all desired files from the least to the most specific.
674 if (d->wantGlobals()) {
675 d->parseGlobalFiles();
676 }
677
678 d->parseConfigFiles();
679}
680
681QStringList KConfigPrivate::getGlobalFiles() const
682{
683 QMutexLocker locker(&s_globalFilesMutex);
684 if (s_globalFiles()->isEmpty()) {
685 const QStringList paths1 = QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
686 const QStringList paths2 = QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
687
688 const bool useEtcKderc = !etc_kderc.isEmpty();
689 s_globalFiles()->reserve(asize: paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
690
691 for (const QString &dir1 : paths1) {
692 s_globalFiles()->push_front(t: dir1);
693 }
694 for (const QString &dir2 : paths2) {
695 s_globalFiles()->push_front(t: dir2);
696 }
697
698 if (useEtcKderc) {
699 s_globalFiles()->push_front(t: etc_kderc);
700 }
701 }
702
703 return *s_globalFiles();
704}
705
706void KConfigPrivate::parseGlobalFiles()
707{
708 const QStringList globalFiles = getGlobalFiles();
709 // qDebug() << "parsing global files" << globalFiles;
710
711 Q_ASSERT(entryMap.empty());
712 const ParseCacheKey key = {globalFiles, locale};
713 auto data = sGlobalParse->localData().object(key);
714 QDateTime newest;
715 for (const auto &file : globalFiles) {
716#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
717 const auto fileDate = QFileInfo(file).lastModified(tz: QTimeZone::UTC);
718#else
719 const auto fileDate = QFileInfo(file).lastModified();
720#endif
721 if (fileDate > newest) {
722 newest = fileDate;
723 }
724 }
725 if (data) {
726 if (data->parseTime < newest) {
727 data = nullptr;
728 } else {
729 entryMap = data->entries;
730 return;
731 }
732 }
733
734 const QByteArray utf8Locale = locale.toUtf8();
735 for (const QString &file : globalFiles) {
736 KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions;
737
738 if (file.compare(s: *sGlobalFileName, cs: sPathCaseSensitivity) != 0) {
739 parseOpts |= KConfigBackend::ParseDefaults;
740 }
741
742 QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(fileName: file);
743 if (backend->parseConfig(locale: utf8Locale, pWriteBackMap&: entryMap, options: parseOpts) == KConfigBackend::ParseImmutable) {
744 break;
745 }
746 }
747 sGlobalParse->localData().insert(key, object: new ParseCacheValue({.entries: entryMap, .parseTime: newest}));
748}
749
750void KConfigPrivate::parseConfigFiles()
751{
752 // can only read the file if there is a backend and a file name
753 if (mBackend && !fileName.isEmpty()) {
754 bFileImmutable = false;
755
756 QList<QString> files;
757 if (wantDefaults()) {
758 if (bSuppressGlobal) {
759 files = getGlobalFiles();
760 } else {
761 if (QDir::isAbsolutePath(path: fileName)) {
762 const QString canonicalFile = QFileInfo(fileName).canonicalFilePath();
763 if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
764 files << canonicalFile;
765 }
766 } else {
767 const QStringList localFilesPath = QStandardPaths::locateAll(type: resourceType, fileName);
768 for (const QString &f : localFilesPath) {
769 files.prepend(t: QFileInfo(f).canonicalFilePath());
770 }
771
772 // allow fallback to config files bundled in resources
773 const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
774 if (QFile::exists(fileName: resourceFile)) {
775 files.prepend(t: resourceFile);
776 }
777 }
778 }
779 } else {
780 files << mBackend->filePath();
781 }
782 if (!isSimple()) {
783 files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
784 }
785
786 // qDebug() << "parsing local files" << files;
787
788 const QByteArray utf8Locale = locale.toUtf8();
789 for (const QString &file : std::as_const(t&: files)) {
790 if (file.compare(s: mBackend->filePath(), cs: sPathCaseSensitivity) == 0) {
791 switch (mBackend->parseConfig(locale: utf8Locale, pWriteBackMap&: entryMap, options: KConfigBackend::ParseExpansions)) {
792 case KConfigBackend::ParseOk:
793 break;
794 case KConfigBackend::ParseImmutable:
795 bFileImmutable = true;
796 break;
797 case KConfigBackend::ParseOpenError:
798 configState = KConfigBase::NoAccess;
799 break;
800 }
801 } else {
802 QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(fileName: file);
803 constexpr auto parseOpts = KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions;
804 bFileImmutable = backend->parseConfig(locale: utf8Locale, pWriteBackMap&: entryMap, options: parseOpts) == KConfigBackend::ParseImmutable;
805 }
806
807 if (bFileImmutable) {
808 break;
809 }
810 }
811 }
812}
813
814KConfig::AccessMode KConfig::accessMode() const
815{
816 Q_D(const KConfig);
817 return d->configState;
818}
819
820void KConfig::addConfigSources(const QStringList &files)
821{
822 Q_D(KConfig);
823 for (const QString &file : files) {
824 d->extraFiles.push(t: file);
825 }
826
827 if (!files.isEmpty()) {
828 reparseConfiguration();
829 }
830}
831
832QStringList KConfig::additionalConfigSources() const
833{
834 Q_D(const KConfig);
835 return d->extraFiles.toList();
836}
837
838QString KConfig::locale() const
839{
840 Q_D(const KConfig);
841 return d->locale;
842}
843
844bool KConfigPrivate::setLocale(const QString &aLocale)
845{
846 if (aLocale != locale) {
847 locale = aLocale;
848 return true;
849 }
850 return false;
851}
852
853bool KConfig::setLocale(const QString &locale)
854{
855 Q_D(KConfig);
856 if (d->setLocale(locale)) {
857 reparseConfiguration();
858 return true;
859 }
860 return false;
861}
862
863void KConfig::setReadDefaults(bool b)
864{
865 Q_D(KConfig);
866 d->bReadDefaults = b;
867}
868
869bool KConfig::readDefaults() const
870{
871 Q_D(const KConfig);
872 return d->bReadDefaults;
873}
874
875bool KConfig::isImmutable() const
876{
877 Q_D(const KConfig);
878 return d->bFileImmutable;
879}
880
881bool KConfig::isGroupImmutableImpl(const QString &aGroup) const
882{
883 Q_D(const KConfig);
884 return isImmutable() || d->entryMap.getEntryOption(group: aGroup, key: {}, flags: {}, option: KEntryMap::EntryImmutable);
885}
886
887KConfigGroup KConfig::groupImpl(const QString &group)
888{
889 return KConfigGroup(this, group);
890}
891
892const KConfigGroup KConfig::groupImpl(const QString &group) const
893{
894 return KConfigGroup(this, group);
895}
896
897KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
898{
899 KEntryMap::EntryOptions options = {};
900
901 if (flags & KConfig::Persistent) {
902 options |= KEntryMap::EntryDirty;
903 }
904 if (flags & KConfig::Global) {
905 options |= KEntryMap::EntryGlobal;
906 }
907 if (flags & KConfig::Localized) {
908 options |= KEntryMap::EntryLocalized;
909 }
910 if (flags.testFlag(flag: KConfig::Notify)) {
911 options |= KEntryMap::EntryNotify;
912 }
913 return options;
914}
915
916void KConfig::deleteGroupImpl(const QString &aGroup, WriteConfigFlags flags)
917{
918 Q_D(KConfig);
919 KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
920
921 const QSet<QString> groups = d->allSubGroups(parentGroup: aGroup);
922 for (const QString &group : groups) {
923 const QList<QByteArray> keys = d->keyListImpl(theGroup: group);
924 for (const QByteArray &key : keys) {
925 if (d->canWriteEntry(group, key)) {
926 d->entryMap.setEntry(group, key, value: QByteArray(), options);
927 d->bDirty = true;
928 }
929 }
930 }
931}
932
933bool KConfig::isConfigWritable(bool warnUser)
934{
935 Q_D(KConfig);
936 bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false);
937
938 if (warnUser && !allWritable) {
939 QString errorMsg;
940 if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately
941 errorMsg = d->mBackend->nonWritableErrorMessage();
942 }
943
944 // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
945 errorMsg += QCoreApplication::translate(context: "KConfig", key: "Please contact your system administrator.");
946 QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
947 if (!cmdToExec.isEmpty()) {
948 QProcess::execute(program: cmdToExec, arguments: QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
949 }
950 }
951
952 d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
953
954 return allWritable;
955}
956
957bool KConfig::hasGroupImpl(const QString &aGroup) const
958{
959 Q_D(const KConfig);
960
961 // No need to look for the actual group entry anymore, or for subgroups:
962 // a group exists if it contains any non-deleted entry.
963
964 return d->hasNonDeletedEntries(group: aGroup);
965}
966
967bool KConfigPrivate::canWriteEntry(const QString &group, QAnyStringView key, bool isDefault) const
968{
969 if (bFileImmutable || entryMap.getEntryOption(group, key, flags: KEntryMap::SearchLocalized, option: KEntryMap::EntryImmutable)) {
970 return isDefault;
971 }
972 return true;
973}
974
975void KConfigPrivate::putData(const QString &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
976{
977 KEntryMap::EntryOptions options = convertToOptions(flags);
978
979 if (bForceGlobal) {
980 options |= KEntryMap::EntryGlobal;
981 }
982 if (expand) {
983 options |= KEntryMap::EntryExpansion;
984 }
985
986 if (value.isNull()) { // deleting entry
987 options |= KEntryMap::EntryDeleted;
988 }
989
990 bool dirtied = entryMap.setEntry(group, key, value, options);
991 if (dirtied && (flags & KConfigBase::Persistent)) {
992 bDirty = true;
993 }
994}
995
996void KConfigPrivate::revertEntry(const QString &group, QAnyStringView key, KConfigBase::WriteConfigFlags flags)
997{
998 KEntryMap::EntryOptions options = convertToOptions(flags);
999
1000 bool dirtied = entryMap.revertEntry(group, key, options);
1001 if (dirtied) {
1002 bDirty = true;
1003 }
1004}
1005
1006QByteArray KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1007{
1008 return lookupInternalEntry(group, key, flags).mValue;
1009}
1010
1011KEntry KConfigPrivate::lookupInternalEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1012{
1013 if (bReadDefaults) {
1014 flags |= KEntryMap::SearchDefaults;
1015 }
1016 const auto it = entryMap.constFindEntry(group, key, flags);
1017 if (it == entryMap.cend()) {
1018 return {};
1019 }
1020 return it->second;
1021}
1022
1023QString KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags, bool *expand) const
1024{
1025 if (bReadDefaults) {
1026 flags |= KEntryMap::SearchDefaults;
1027 }
1028 return entryMap.getEntry(group, key, defaultValue: QString(), flags, expand);
1029}
1030
1031QStandardPaths::StandardLocation KConfig::locationType() const
1032{
1033 Q_D(const KConfig);
1034 return d->resourceType;
1035}
1036
1037void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1038{
1039 /* nothing */
1040}
1041

source code of kconfig/src/core/kconfig.cpp