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>;
58using ParseCacheTimestamp = QList<qint64>;
59struct ParseCacheValue {
60 KEntryMap entries;
61 ParseCacheTimestamp timestamp;
62};
63QThreadStorage<QCache<ParseCacheKey, ParseCacheValue>> 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 , bDirty(false)
75 , bReadDefaults(false)
76 , bFileImmutable(false)
77 , bForceGlobal(false)
78 , bSuppressGlobal(false)
79 , configState(KConfigBase::NoAccess)
80{
81 const bool isTestMode = QStandardPaths::isTestModeEnabled();
82 // If sGlobalFileName was initialised and testMode has been toggled,
83 // sGlobalFileName may need to be updated to point to the correct kdeglobals file
84 if (sGlobalFileName.exists() && s_wasTestModeEnabled != isTestMode) {
85 s_wasTestModeEnabled = isTestMode;
86 *sGlobalFileName = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals");
87 }
88
89 static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1);
90 if (use_etc_kderc.loadRelaxed() < 0) {
91 use_etc_kderc.storeRelaxed(newValue: !qEnvironmentVariableIsSet(varName: "KDE_SKIP_KDERC")); // for unit tests
92 }
93 if (use_etc_kderc.loadRelaxed()) {
94 etc_kderc =
95#ifdef Q_OS_WIN
96 QFile::decodeName(qgetenv("WINDIR") + "/kde5rc");
97#else
98 QStringLiteral("/etc/kde5rc");
99#endif
100 if (!QFileInfo(etc_kderc).isReadable()) {
101 use_etc_kderc.storeRelaxed(newValue: false);
102 etc_kderc.clear();
103 }
104 }
105
106 // if (!mappingsRegistered) {
107 // KEntryMap tmp;
108 // if (!etc_kderc.isEmpty()) {
109 // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(etc_kderc, QLatin1String("INI"));
110 // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults);
111 // }
112 // const QString kde5rc(QDir::home().filePath(".kde5rc"));
113 // if (KStandardDirs::checkAccess(kde5rc, R_OK)) {
114 // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(kde5rc, QLatin1String("INI"));
115 // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions());
116 // }
117 // KConfigBackend::registerMappings(tmp);
118 // mappingsRegistered = true;
119 // }
120
121 setLocale(QLocale().name());
122}
123
124bool KConfigPrivate::lockLocal()
125{
126 return mBackend.lock();
127}
128
129static bool isGroupOrSubGroupMatch(KEntryMapConstIterator entryMapIt, const QString &group)
130{
131 const QString &entryGroup = entryMapIt->first.mGroup;
132 Q_ASSERT_X(entryGroup.startsWith(group), Q_FUNC_INFO, "Precondition");
133 return entryGroup.size() == group.size() || entryGroup[group.size()] == QLatin1Char('\x1d');
134}
135
136void KConfigPrivate::copyGroup(const QString &source, const QString &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const
137{
138 KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap;
139 const bool sameName = (destination == source);
140
141 // we keep this bool outside the for loop so that if
142 // the group is empty, we don't end up marking the other config
143 // as dirty erroneously
144 bool dirtied = false;
145
146 entryMap.forEachEntryWhoseGroupStartsWith(groupPrefix: source, callback: [&source, &destination, flags, &otherMap, sameName, &dirtied](KEntryMapConstIterator entryMapIt) {
147 // don't copy groups that start with the same prefix, but are not sub-groups
148 if (!isGroupOrSubGroupMatch(entryMapIt, group: source)) {
149 return;
150 }
151
152 KEntryKey newKey = entryMapIt->first;
153
154 if (flags & KConfigBase::Localized) {
155 newKey.bLocal = true;
156 }
157
158 if (!sameName) {
159 newKey.mGroup.replace(i: 0, len: source.size(), after: destination);
160 }
161
162 KEntry entry = entryMapIt->second;
163 dirtied = entry.bDirty = flags & KConfigBase::Persistent;
164
165 if (flags & KConfigBase::Global) {
166 entry.bGlobal = true;
167 }
168
169 if (flags & KConfigBase::Notify) {
170 entry.bNotify = true;
171 }
172
173 otherMap[newKey] = entry;
174 });
175
176 if (dirtied) {
177 otherGroup->config()->d_ptr->bDirty = true;
178 }
179}
180
181QString KConfigPrivate::expandString(const QString &value)
182{
183 QString aValue = value;
184
185 // check for environment variables and make necessary translations
186 int nDollarPos = aValue.indexOf(ch: QLatin1Char('$'));
187 while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
188 // there is at least one $
189 if (aValue.at(i: nDollarPos + 1) != QLatin1Char('$')) {
190 int nEndPos = nDollarPos + 1;
191 // the next character is not $
192 QStringView aVarName;
193 if (aValue.at(i: nEndPos) == QLatin1Char('{')) {
194 while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) {
195 ++nEndPos;
196 }
197 ++nEndPos;
198 aVarName = QStringView(aValue).mid(pos: nDollarPos + 2, n: nEndPos - nDollarPos - 3);
199 } else {
200 while (nEndPos < aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) {
201 ++nEndPos;
202 }
203 aVarName = QStringView(aValue).mid(pos: nDollarPos + 1, n: nEndPos - nDollarPos - 1);
204 }
205 QString env;
206 if (!aVarName.isEmpty()) {
207#ifdef Q_OS_WIN
208 if (aVarName == QLatin1String("HOME")) {
209 env = QDir::homePath();
210 } else
211#endif
212 {
213 QByteArray pEnv = qgetenv(varName: aVarName.toLatin1().constData());
214 if (!pEnv.isEmpty()) {
215 env = QString::fromLocal8Bit(ba: pEnv.constData());
216 } else {
217 if (aVarName == QLatin1String("QT_DATA_HOME")) {
218 env = QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation);
219 } else if (aVarName == QLatin1String("QT_CONFIG_HOME")) {
220 env = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation);
221 } else if (aVarName == QLatin1String("QT_CACHE_HOME")) {
222 env = QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation);
223 }
224 }
225 }
226 aValue.replace(i: nDollarPos, len: nEndPos - nDollarPos, after: env);
227 nDollarPos += env.length();
228 } else {
229 aValue.remove(i: nDollarPos, len: nEndPos - nDollarPos);
230 }
231 } else {
232 // remove one of the dollar signs
233 aValue.remove(i: nDollarPos, len: 1);
234 ++nDollarPos;
235 }
236 nDollarPos = aValue.indexOf(ch: QLatin1Char('$'), from: nDollarPos);
237 }
238
239 return aValue;
240}
241
242KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType)
243 : d_ptr(new KConfigPrivate(mode, resourceType))
244{
245 d_ptr->changeFileName(fileName: file); // set the local file name
246
247 // read initial information off disk
248 reparseConfiguration();
249}
250
251#if KCONFIGCORE_BUILD_DEPRECATED_SINCE(6, 3)
252KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType)
253 : d_ptr(new KConfigPrivate(SimpleConfig, resourceType))
254{
255 Q_UNUSED(backend);
256 d_ptr->changeFileName(fileName: file); // set the local file name
257
258 // read initial information off disk
259 reparseConfiguration();
260}
261#endif
262
263KConfig::KConfig(KConfigPrivate &d)
264 : d_ptr(&d)
265{
266}
267
268KConfig::~KConfig()
269{
270 Q_D(KConfig);
271 if (d->bDirty) {
272 sync();
273 }
274 delete d;
275}
276
277static bool isNonDeletedKey(KEntryMapConstIterator entryMapIt)
278{
279 return !entryMapIt->first.mKey.isNull() && !entryMapIt->second.bDeleted;
280}
281// is a key without default values and not deleted
282static bool isSetKey(KEntryMapConstIterator entryMapIt)
283{
284 return !entryMapIt->first.bDefault && !entryMapIt->second.bDeleted;
285}
286
287static int findFirstGroupEndPos(const QString &groupFullName, int from = 0)
288{
289 const auto index = groupFullName.indexOf(ch: QLatin1Char('\x1d'), from);
290 return index == -1 ? groupFullName.size() : index;
291}
292
293static QStringList stringListFromStringViewCollection(const QSet<QStringView> &source)
294{
295 QStringList list;
296 list.reserve(asize: source.size());
297 std::transform(first: source.cbegin(), last: source.cend(), result: std::back_inserter(x&: list), unary_op: [](QStringView view) {
298 return view.toString();
299 });
300 return list;
301}
302
303QStringList KConfig::groupList() const
304{
305 Q_D(const KConfig);
306 QSet<QStringView> groups;
307
308 for (auto entryMapIt = d->entryMap.cbegin(); entryMapIt != d->entryMap.cend(); ++entryMapIt) {
309 const QString &group = entryMapIt->first.mGroup;
310 if (isNonDeletedKey(entryMapIt) && !group.isEmpty() && group != QStringLiteral("<default>") && group != QStringLiteral("$Version")) {
311 groups.insert(value: QStringView(group).left(n: findFirstGroupEndPos(groupFullName: group)));
312 }
313 }
314
315 return stringListFromStringViewCollection(source: groups);
316}
317
318QStringList KConfigPrivate::groupList(const QString &groupName) const
319{
320 const QString theGroup = groupName + QLatin1Char('\x1d');
321 QSet<QStringView> groups;
322
323 entryMap.forEachEntryWhoseGroupStartsWith(groupPrefix: theGroup, callback: [&theGroup, &groups](KEntryMapConstIterator entryMapIt) {
324 if (isNonDeletedKey(entryMapIt)) {
325 const QString &entryGroup = entryMapIt->first.mGroup;
326 const auto subgroupStartPos = theGroup.size();
327 const auto subgroupEndPos = findFirstGroupEndPos(groupFullName: entryGroup, from: subgroupStartPos);
328 groups.insert(value: QStringView(entryGroup).mid(pos: subgroupStartPos, n: subgroupEndPos - subgroupStartPos));
329 }
330 });
331
332 return stringListFromStringViewCollection(source: groups);
333}
334
335/// Returns @p parentGroup itself, all its subgroups, subsubgroups, and so on, including deleted groups.
336QSet<QString> KConfigPrivate::allSubGroups(const QString &parentGroup) const
337{
338 QSet<QString> groups;
339
340 entryMap.forEachEntryWhoseGroupStartsWith(groupPrefix: parentGroup, callback: [&parentGroup, &groups](KEntryMapConstIterator entryMapIt) {
341 const KEntryKey &key = entryMapIt->first;
342 if (key.mKey.isNull() && isGroupOrSubGroupMatch(entryMapIt, group: parentGroup)) {
343 groups << key.mGroup;
344 }
345 });
346
347 return groups;
348}
349
350bool KConfigPrivate::hasNonDeletedEntries(const QString &group) const
351{
352 return entryMap.anyEntryWhoseGroupStartsWith(groupPrefix: group, predicate: [&group](KEntryMapConstIterator entryMapIt) {
353 return isGroupOrSubGroupMatch(entryMapIt, group) && isNonDeletedKey(entryMapIt);
354 });
355}
356
357QList<QByteArray> KConfigPrivate::keyListImpl(const QString &theGroup) const
358{
359 std::set<QByteArray> tmp; // unique set, sorted for unittests
360
361 entryMap.forEachEntryOfGroup(theGroup, callback: [&tmp](KEntryMapConstIterator it) {
362 if (isNonDeletedKey(entryMapIt: it)) {
363 tmp.insert(x: it->first.mKey);
364 }
365 });
366
367 return QList<QByteArray>(tmp.begin(), tmp.end());
368}
369
370QStringList KConfigPrivate::usedKeyList(const QString &theGroup) const
371{
372 std::set<QString> tmp; // unique set, sorting as side-effect
373
374 entryMap.forEachEntryOfGroup(theGroup, callback: [&tmp](KEntryMapConstIterator it) {
375 // leave the default values and deleted entries out, same as KConfig::entryMap()
376 if (isSetKey(entryMapIt: it)) {
377 const QString key = QString::fromUtf8(ba: it->first.mKey);
378 tmp.insert(x: key);
379 }
380 });
381
382 return QStringList(tmp.begin(), tmp.end());
383}
384
385QMap<QString, QString> KConfig::entryMap(const QString &aGroup) const
386{
387 Q_D(const KConfig);
388 QMap<QString, QString> theMap;
389 const QString theGroup = aGroup.isEmpty() ? QStringLiteral("<default>") : aGroup;
390
391 d->entryMap.forEachEntryOfGroup(theGroup, callback: [&theMap](KEntryMapConstIterator it) {
392 // leave the default values and deleted entries out
393 if (isSetKey(entryMapIt: it)) {
394 const QString key = QString::fromUtf8(utf8: it->first.mKey.constData());
395 // the localized entry should come first, so don't overwrite it
396 // with the non-localized entry
397 if (!theMap.contains(key)) {
398 if (it->second.bExpand) {
399 theMap.insert(key, value: KConfigPrivate::expandString(value: QString::fromUtf8(utf8: it->second.mValue.constData())));
400 } else {
401 theMap.insert(key, value: QString::fromUtf8(utf8: it->second.mValue.constData()));
402 }
403 }
404 }
405 });
406
407 return theMap;
408}
409
410bool KConfig::sync()
411{
412 Q_D(KConfig);
413
414 if (isImmutable() || name().isEmpty()) {
415 // can't write to an immutable or anonymous file.
416 return false;
417 }
418
419 QHash<QString, QByteArrayList> notifyGroupsLocal;
420 QHash<QString, QByteArrayList> notifyGroupsGlobal;
421
422 if (d->bDirty) {
423 const QByteArray utf8Locale(locale().toUtf8());
424
425 // Create the containing dir, maybe it wasn't there
426 d->mBackend.createEnclosing();
427
428 // lock the local file
429 if (d->configState == ReadWrite && !d->lockLocal()) {
430 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file";
431 return false;
432 }
433
434 // Rewrite global/local config only if there is a dirty entry in it.
435 bool writeGlobals = false;
436 bool writeLocals = false;
437
438 for (const auto &[key, e] : d->entryMap) {
439 if (e.bDirty) {
440 if (e.bGlobal) {
441 writeGlobals = true;
442 if (e.bNotify) {
443 notifyGroupsGlobal[key.mGroup] << key.mKey;
444 }
445 } else {
446 writeLocals = true;
447 if (e.bNotify) {
448 notifyGroupsLocal[key.mGroup] << key.mKey;
449 }
450 }
451 }
452 }
453
454 d->bDirty = false; // will revert to true if a config write fails
455
456 if (d->wantGlobals() && writeGlobals) {
457 KConfigIniBackend tmp;
458 tmp.setFilePath(*sGlobalFileName);
459 if (d->configState == ReadWrite && !tmp.lock()) {
460 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock global file";
461
462 // unlock the local config if we're returning early
463 if (d->mBackend.isLocked()) {
464 d->mBackend.unlock();
465 }
466
467 d->bDirty = true;
468 return false;
469 }
470 if (!tmp.writeConfig(locale: utf8Locale, entryMap&: d->entryMap, options: KConfigIniBackend::WriteGlobal)) {
471 d->bDirty = true;
472 }
473 if (tmp.isLocked()) {
474 tmp.unlock();
475 }
476 }
477
478 if (writeLocals) {
479 if (!d->mBackend.writeConfig(locale: utf8Locale, entryMap&: d->entryMap, options: KConfigIniBackend::WriteOptions())) {
480 d->bDirty = true;
481 }
482 }
483 if (d->mBackend.isLocked()) {
484 d->mBackend.unlock();
485 }
486 }
487
488 // Notifying absolute paths is not supported and also makes no sense.
489 const bool isAbsolutePath = name().at(i: 0) == QLatin1Char('/');
490 if (!notifyGroupsLocal.isEmpty() && !isAbsolutePath) {
491 d->notifyClients(changes: notifyGroupsLocal, path: kconfigDBusSanitizePath(path: QLatin1Char('/') + name()));
492 }
493 if (!notifyGroupsGlobal.isEmpty()) {
494 d->notifyClients(changes: notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
495 }
496
497 return !d->bDirty;
498}
499
500void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
501{
502#if KCONFIG_USE_DBUS
503 qDBusRegisterMetaType<QByteArrayList>();
504
505 qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
506
507 QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"));
508 message.setArguments({QVariant::fromValue(value: changes)});
509 QDBusConnection::sessionBus().send(message);
510#else
511 Q_UNUSED(changes)
512 Q_UNUSED(path)
513#endif
514}
515
516void KConfig::markAsClean()
517{
518 Q_D(KConfig);
519 d->bDirty = false;
520
521 // clear any dirty flags that entries might have set
522 for (auto &[_, entry] : d->entryMap) {
523 entry.bDirty = false;
524 entry.bNotify = false;
525 }
526}
527
528bool KConfig::isDirty() const
529{
530 Q_D(const KConfig);
531 return d->bDirty;
532}
533
534void KConfig::checkUpdate(const QString &id, const QString &updateFile)
535{
536 const KConfigGroup cg(this, QStringLiteral("$Version"));
537 const QString cfg_id = updateFile + QLatin1Char(':') + id;
538 const QStringList ids = cg.readEntry(key: "update_info", aDefault: QStringList());
539 if (!ids.contains(str: cfg_id)) {
540 QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), arguments: QStringList{QStringLiteral("--check"), updateFile});
541 reparseConfiguration();
542 }
543}
544
545KConfig *KConfig::copyTo(const QString &file, KConfig *config) const
546{
547 Q_D(const KConfig);
548 if (!config) {
549 config = new KConfig(QString(), SimpleConfig, d->resourceType);
550 }
551 config->d_func()->changeFileName(fileName: file);
552 config->d_func()->entryMap = d->entryMap;
553 config->d_func()->bFileImmutable = false;
554
555 for (auto &[_, entry] : config->d_func()->entryMap) {
556 entry.bDirty = true;
557 }
558 config->d_ptr->bDirty = true;
559
560 return config;
561}
562
563QString KConfig::name() const
564{
565 Q_D(const KConfig);
566 return d->fileName;
567}
568
569KConfig::OpenFlags KConfig::openFlags() const
570{
571 Q_D(const KConfig);
572 return d->openFlags;
573}
574
575struct KConfigStaticData {
576 QString globalMainConfigName;
577 // Keep a copy so we can use it in global dtors, after qApp is gone
578 QStringList appArgs;
579};
580Q_GLOBAL_STATIC(KConfigStaticData, globalData)
581static QBasicMutex s_globalDataMutex;
582
583void KConfig::setMainConfigName(const QString &str)
584{
585 QMutexLocker locker(&s_globalDataMutex);
586 globalData()->globalMainConfigName = str;
587}
588
589QString KConfig::mainConfigName()
590{
591 QMutexLocker locker(&s_globalDataMutex);
592 KConfigStaticData *data = globalData();
593 if (data->appArgs.isEmpty()) {
594 data->appArgs = QCoreApplication::arguments();
595 }
596
597 // --config on the command line overrides everything else
598 const QStringList args = data->appArgs;
599 for (int i = 1; i < args.count(); ++i) {
600 if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
601 return args.at(i: i + 1);
602 }
603 }
604 const QString globalName = data->globalMainConfigName;
605 if (!globalName.isEmpty()) {
606 return globalName;
607 }
608
609 QString appName = QCoreApplication::applicationName();
610 return appName + QLatin1String("rc");
611}
612
613void KConfigPrivate::changeFileName(const QString &name)
614{
615 fileName = name;
616
617 QString file;
618 if (name.isEmpty()) {
619 if (wantDefaults()) { // accessing default app-specific config "appnamerc"
620 fileName = KConfig::mainConfigName();
621 file = QStandardPaths::writableLocation(type: resourceType) + QLatin1Char('/') + fileName;
622 } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
623 resourceType = QStandardPaths::GenericConfigLocation;
624 fileName = QStringLiteral("kdeglobals");
625 file = *sGlobalFileName;
626 } else {
627 // anonymous config
628 openFlags = KConfig::SimpleConfig;
629 return;
630 }
631 } else if (QDir::isAbsolutePath(path: fileName)) {
632 fileName = QFileInfo(fileName).canonicalFilePath();
633 if (fileName.isEmpty()) { // file doesn't exist (yet)
634 fileName = name;
635 }
636 file = fileName;
637 } else {
638 file = QStandardPaths::writableLocation(type: resourceType) + QLatin1Char('/') + fileName;
639 }
640
641 Q_ASSERT(!file.isEmpty());
642
643 bSuppressGlobal = (file.compare(s: *sGlobalFileName, cs: sPathCaseSensitivity) == 0);
644
645 mBackend.setFilePath(file);
646
647 configState = mBackend.accessMode();
648}
649
650void KConfig::reparseConfiguration()
651{
652 Q_D(KConfig);
653 if (d->fileName.isEmpty()) {
654 return;
655 }
656
657 // Don't lose pending changes
658 if (!d->isReadOnly() && d->bDirty) {
659 sync();
660 }
661
662 d->entryMap.clear();
663
664 d->bFileImmutable = false;
665
666 {
667 QMutexLocker locker(&s_globalFilesMutex);
668 s_globalFiles()->clear();
669 }
670
671 // Parse all desired files from the least to the most specific.
672 if (d->wantGlobals()) {
673 d->parseGlobalFiles();
674 }
675
676#ifdef Q_OS_WIN
677 // Parse the windows registry defaults if desired
678 if (d->openFlags & ~KConfig::SimpleConfig) {
679 d->parseWindowsDefaults();
680 }
681#endif
682
683 d->parseConfigFiles();
684}
685
686QStringList KConfigPrivate::getGlobalFiles() const
687{
688 QMutexLocker locker(&s_globalFilesMutex);
689 if (s_globalFiles()->isEmpty()) {
690 const QStringList paths1 = QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
691 const QStringList paths2 = QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
692
693 const bool useEtcKderc = !etc_kderc.isEmpty();
694 s_globalFiles()->reserve(asize: paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
695
696 for (const QString &dir1 : paths1) {
697 s_globalFiles()->push_front(t: dir1);
698 }
699 for (const QString &dir2 : paths2) {
700 s_globalFiles()->push_front(t: dir2);
701 }
702
703 if (useEtcKderc) {
704 s_globalFiles()->push_front(t: etc_kderc);
705 }
706 }
707
708 return *s_globalFiles();
709}
710
711void KConfigPrivate::parseGlobalFiles()
712{
713 const QStringList globalFiles = getGlobalFiles();
714 // qDebug() << "parsing global files" << globalFiles;
715
716 Q_ASSERT(entryMap.empty());
717
718 ParseCacheTimestamp timestamp;
719 timestamp.reserve(asize: globalFiles.count());
720 for (const auto &file : globalFiles) {
721 timestamp << QFileInfo(file).lastModified(tz: QTimeZone::UTC).toMSecsSinceEpoch();
722 }
723
724 const ParseCacheKey key = {globalFiles, locale};
725 auto data = sGlobalParse.localData().object(key);
726 if (data) {
727 if (data->timestamp != timestamp) {
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(s: *sGlobalFileName, cs: sPathCaseSensitivity) != 0) {
740 parseOpts |= KConfigIniBackend::ParseDefaults;
741 }
742
743 KConfigIniBackend backend;
744 backend.setFilePath(file);
745 if (backend.parseConfig(locale: utf8Locale, entryMap, options: parseOpts) == KConfigIniBackend::ParseImmutable) {
746 break;
747 }
748 }
749 sGlobalParse.localData().insert(key, object: new ParseCacheValue({.entries: entryMap, .timestamp: timestamp}));
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(path: 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(type: resourceType, fileName);
783 for (const QString &f : localFilesPath) {
784 files.prepend(t: 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(fileName: resourceFile)) {
790 files.prepend(t: 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(t&: files)) {
805 if (file.compare(s: mBackend.filePath(), cs: sPathCaseSensitivity) == 0) {
806 switch (mBackend.parseConfig(locale: utf8Locale, entryMap, options: 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(locale: utf8Locale, entryMap, options: parseOpts) == KConfigIniBackend::ParseImmutable;
821 }
822
823 if (bFileImmutable) {
824 break;
825 }
826 }
827 }
828}
829
830KConfig::AccessMode KConfig::accessMode() const
831{
832 Q_D(const KConfig);
833 return d->configState;
834}
835
836void KConfig::addConfigSources(const QStringList &files)
837{
838 Q_D(KConfig);
839 for (const QString &file : files) {
840 d->extraFiles.push(t: file);
841 }
842
843 if (!files.isEmpty()) {
844 reparseConfiguration();
845 }
846}
847
848QStringList KConfig::additionalConfigSources() const
849{
850 Q_D(const KConfig);
851 return d->extraFiles.toList();
852}
853
854QString KConfig::locale() const
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
869bool KConfig::setLocale(const QString &locale)
870{
871 Q_D(KConfig);
872 if (d->setLocale(locale)) {
873 reparseConfiguration();
874 return true;
875 }
876 return false;
877}
878
879void KConfig::setReadDefaults(bool b)
880{
881 Q_D(KConfig);
882 d->bReadDefaults = b;
883}
884
885bool KConfig::readDefaults() const
886{
887 Q_D(const KConfig);
888 return d->bReadDefaults;
889}
890
891bool KConfig::isImmutable() const
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(group: aGroup, key: {}, flags: {}, option: 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(flag: KConfig::Notify)) {
927 options |= KEntryMap::EntryNotify;
928 }
929 return options;
930}
931
932void KConfig::deleteGroupImpl(const QString &aGroup, WriteConfigFlags flags)
933{
934 Q_D(KConfig);
935 KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
936
937 const QSet<QString> groups = d->allSubGroups(parentGroup: aGroup);
938 for (const QString &group : groups) {
939 const QList<QByteArray> keys = d->keyListImpl(theGroup: group);
940 for (const QByteArray &key : keys) {
941 if (d->canWriteEntry(group, key)) {
942 d->entryMap.setEntry(group, key, value: 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(context: "KConfig", key: "Please contact your system administrator.");
960 QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
961 if (!cmdToExec.isEmpty()) {
962 QProcess::execute(program: cmdToExec, arguments: 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(group: aGroup);
979}
980
981bool KConfigPrivate::canWriteEntry(const QString &group, QAnyStringView key, bool isDefault) const
982{
983 if (bFileImmutable || entryMap.getEntryOption(group, key, flags: KEntryMap::SearchLocalized, option: 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, defaultValue: QString(), flags, expand);
1043}
1044
1045QStandardPaths::StandardLocation KConfig::locationType() const
1046{
1047 Q_D(const KConfig);
1048 return d->resourceType;
1049}
1050
1051void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1052{
1053 /* nothing */
1054}
1055

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