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

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