File indexing completed on 2024-04-21 14:54:07

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
0004     SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
0005     SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kconfig.h"
0011 #include "kconfig_p.h"
0012 
0013 #include "config-kconfig.h"
0014 #include "dbussanitizer_p.h"
0015 #include "kconfig_core_log_settings.h"
0016 
0017 #include <cstdlib>
0018 #include <fcntl.h>
0019 
0020 #include "kconfigbackend_p.h"
0021 #include "kconfiggroup.h"
0022 
0023 #include <QBasicMutex>
0024 #include <QByteArray>
0025 #include <QCache>
0026 #include <QCoreApplication>
0027 #include <QDir>
0028 #include <QFile>
0029 #include <QLocale>
0030 #include <QMutexLocker>
0031 #include <QProcess>
0032 #include <QSet>
0033 #include <QThreadStorage>
0034 
0035 #include <algorithm>
0036 #include <iterator>
0037 #include <set>
0038 #include <string_view>
0039 #include <unordered_set>
0040 
0041 #if KCONFIG_USE_DBUS
0042 #include <QDBusConnection>
0043 #include <QDBusMessage>
0044 #include <QDBusMetaType>
0045 #endif
0046 
0047 bool KConfigPrivate::mappingsRegistered = false;
0048 
0049 // For caching purposes
0050 static bool s_wasTestModeEnabled = false;
0051 
0052 Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes.
0053 static QBasicMutex s_globalFilesMutex;
0054 Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals")))
0055 
0056 using ParseCacheKey = std::pair<QStringList, QString>;
0057 struct ParseCacheValue {
0058     KEntryMap entries;
0059     QDateTime parseTime;
0060 };
0061 using ParseCache = QThreadStorage<QCache<ParseCacheKey, ParseCacheValue>>;
0062 Q_GLOBAL_STATIC(ParseCache, sGlobalParse)
0063 
0064 #ifndef Q_OS_WIN
0065 static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive;
0066 #else
0067 static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive;
0068 #endif
0069 
0070 KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType)
0071     : openFlags(flags)
0072     , resourceType(resourceType)
0073     , mBackend(nullptr)
0074     , bDynamicBackend(true)
0075     , bDirty(false)
0076     , bReadDefaults(false)
0077     , bFileImmutable(false)
0078     , bForceGlobal(false)
0079     , bSuppressGlobal(false)
0080     , configState(KConfigBase::NoAccess)
0081 {
0082     const bool isTestMode = QStandardPaths::isTestModeEnabled();
0083     // If sGlobalFileName was initialised and testMode has been toggled,
0084     // sGlobalFileName may need to be updated to point to the correct kdeglobals file
0085     if (sGlobalFileName.exists() && s_wasTestModeEnabled != isTestMode) {
0086         s_wasTestModeEnabled = isTestMode;
0087         *sGlobalFileName = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals");
0088     }
0089 
0090     static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1);
0091     if (use_etc_kderc.loadRelaxed() < 0) {
0092         use_etc_kderc.storeRelaxed(!qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests
0093     }
0094     if (use_etc_kderc.loadRelaxed()) {
0095         etc_kderc =
0096 #ifdef Q_OS_WIN
0097             QFile::decodeName(qgetenv("WINDIR") + "/kde5rc");
0098 #else
0099             QStringLiteral("/etc/kde5rc");
0100 #endif
0101         if (!QFileInfo(etc_kderc).isReadable()) {
0102             use_etc_kderc.storeRelaxed(false);
0103             etc_kderc.clear();
0104         }
0105     }
0106 
0107     //    if (!mappingsRegistered) {
0108     //        KEntryMap tmp;
0109     //        if (!etc_kderc.isEmpty()) {
0110     //            QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(etc_kderc, QLatin1String("INI"));
0111     //            backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults);
0112     //        }
0113     //        const QString kde5rc(QDir::home().filePath(".kde5rc"));
0114     //        if (KStandardDirs::checkAccess(kde5rc, R_OK)) {
0115     //            QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(kde5rc, QLatin1String("INI"));
0116     //            backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions());
0117     //        }
0118     //        KConfigBackend::registerMappings(tmp);
0119     //        mappingsRegistered = true;
0120     //    }
0121 
0122     setLocale(QLocale().name());
0123 }
0124 
0125 bool KConfigPrivate::lockLocal()
0126 {
0127     if (mBackend) {
0128         return mBackend->lock();
0129     }
0130     // anonymous object - pretend we locked it
0131     return true;
0132 }
0133 
0134 static bool isGroupOrSubGroupMatch(KEntryMapConstIterator entryMapIt, const QByteArray &group)
0135 {
0136     const QByteArray &entryGroup = entryMapIt.key().mGroup;
0137     Q_ASSERT_X(entryGroup.startsWith(group), Q_FUNC_INFO, "Precondition");
0138     return entryGroup.size() == group.size() || entryGroup[group.size()] == '\x1d';
0139 }
0140 
0141 void KConfigPrivate::copyGroup(const QByteArray &source, const QByteArray &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const
0142 {
0143     KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap;
0144     const bool sameName = (destination == source);
0145 
0146     // we keep this bool outside the for loop so that if
0147     // the group is empty, we don't end up marking the other config
0148     // as dirty erroneously
0149     bool dirtied = false;
0150 
0151     entryMap.forEachEntryWhoseGroupStartsWith(source, [&source, &destination, flags, &otherMap, sameName, &dirtied](KEntryMapConstIterator entryMapIt) {
0152         // don't copy groups that start with the same prefix, but are not sub-groups
0153         if (!isGroupOrSubGroupMatch(entryMapIt, source)) {
0154             return;
0155         }
0156 
0157         KEntryKey newKey = entryMapIt.key();
0158 
0159         if (flags & KConfigBase::Localized) {
0160             newKey.bLocal = true;
0161         }
0162 
0163         if (!sameName) {
0164             newKey.mGroup.replace(0, source.size(), destination);
0165         }
0166 
0167         KEntry entry = entryMapIt.value();
0168         dirtied = entry.bDirty = flags & KConfigBase::Persistent;
0169 
0170         if (flags & KConfigBase::Global) {
0171             entry.bGlobal = true;
0172         }
0173 
0174         if (flags & KConfigBase::Notify) {
0175             entry.bNotify = true;
0176         }
0177 
0178         otherMap[newKey] = entry;
0179     });
0180 
0181     if (dirtied) {
0182         otherGroup->config()->d_ptr->bDirty = true;
0183     }
0184 }
0185 
0186 QString KConfigPrivate::expandString(const QString &value)
0187 {
0188     QString aValue = value;
0189 
0190     // check for environment variables and make necessary translations
0191     int nDollarPos = aValue.indexOf(QLatin1Char('$'));
0192     while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
0193         // there is at least one $
0194         if (aValue.at(nDollarPos + 1) != QLatin1Char('$')) {
0195             int nEndPos = nDollarPos + 1;
0196             // the next character is not $
0197             QStringView aVarName;
0198             if (aValue.at(nEndPos) == QLatin1Char('{')) {
0199                 while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) {
0200                     ++nEndPos;
0201                 }
0202                 ++nEndPos;
0203                 aVarName = QStringView(aValue).mid(nDollarPos + 2, nEndPos - nDollarPos - 3);
0204             } else {
0205                 while (nEndPos < aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) {
0206                     ++nEndPos;
0207                 }
0208                 aVarName = QStringView(aValue).mid(nDollarPos + 1, nEndPos - nDollarPos - 1);
0209             }
0210             QString env;
0211             if (!aVarName.isEmpty()) {
0212 #ifdef Q_OS_WIN
0213                 if (aVarName == QLatin1String("HOME")) {
0214                     env = QDir::homePath();
0215                 } else
0216 #endif
0217                 {
0218                     QByteArray pEnv = qgetenv(aVarName.toLatin1().constData());
0219                     if (!pEnv.isEmpty()) {
0220                         env = QString::fromLocal8Bit(pEnv.constData());
0221                     } else {
0222                         if (aVarName == QLatin1String("QT_DATA_HOME")) {
0223                             env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
0224                         } else if (aVarName == QLatin1String("QT_CONFIG_HOME")) {
0225                             env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0226                         } else if (aVarName == QLatin1String("QT_CACHE_HOME")) {
0227                             env = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
0228                         }
0229                     }
0230                 }
0231                 aValue.replace(nDollarPos, nEndPos - nDollarPos, env);
0232                 nDollarPos += env.length();
0233             } else {
0234                 aValue.remove(nDollarPos, nEndPos - nDollarPos);
0235             }
0236         } else {
0237             // remove one of the dollar signs
0238             aValue.remove(nDollarPos, 1);
0239             ++nDollarPos;
0240         }
0241         nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos);
0242     }
0243 
0244     return aValue;
0245 }
0246 
0247 KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType)
0248     : d_ptr(new KConfigPrivate(mode, resourceType))
0249 {
0250     d_ptr->changeFileName(file); // set the local file name
0251 
0252     // read initial information off disk
0253     reparseConfiguration();
0254 }
0255 
0256 KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType)
0257     : d_ptr(new KConfigPrivate(SimpleConfig, resourceType))
0258 {
0259     d_ptr->mBackend = KConfigBackend::create(file, backend);
0260     d_ptr->bDynamicBackend = false;
0261     d_ptr->changeFileName(file); // set the local file name
0262 
0263     // read initial information off disk
0264     reparseConfiguration();
0265 }
0266 
0267 KConfig::KConfig(KConfigPrivate &d)
0268     : d_ptr(&d)
0269 {
0270 }
0271 
0272 KConfig::~KConfig()
0273 {
0274     Q_D(KConfig);
0275     if (d->bDirty && (d->mBackend && d->mBackend->ref.loadRelaxed() == 1)) {
0276         sync();
0277     }
0278     delete d;
0279 }
0280 
0281 static bool isNonDeletedKey(KEntryMapConstIterator entryMapIt)
0282 {
0283     return !entryMapIt.key().mKey.isNull() && !entryMapIt->bDeleted;
0284 }
0285 
0286 static int findFirstGroupEndPos(const QByteArray &groupFullName, int from = 0)
0287 {
0288     const auto index = groupFullName.indexOf('\x1d', from);
0289     return index == -1 ? groupFullName.size() : index;
0290 }
0291 
0292 // std::string_view is used because QByteArrayView does not exist in Qt 5.
0293 // std::unordered_set rather than QSet is used because there is no qHash() overload for std::string_view in Qt 5.
0294 using ByteArrayViewSet = std::unordered_set<std::string_view>;
0295 
0296 static QStringList stringListFromUtf8Collection(const ByteArrayViewSet &source)
0297 {
0298     QStringList list;
0299     list.reserve(source.size());
0300     std::transform(source.cbegin(), source.cend(), std::back_inserter(list), [](std::string_view view) {
0301         return QString::fromUtf8(view.data(), view.size());
0302     });
0303     return list;
0304 }
0305 
0306 QStringList KConfig::groupList() const
0307 {
0308     Q_D(const KConfig);
0309     ByteArrayViewSet groups;
0310 
0311     for (auto entryMapIt = d->entryMap.cbegin(); entryMapIt != d->entryMap.cend(); ++entryMapIt) {
0312         const QByteArray &group = entryMapIt.key().mGroup;
0313         if (isNonDeletedKey(entryMapIt) && !group.isEmpty() && group != "<default>" && group != "$Version") {
0314             groups.emplace(group.constData(), findFirstGroupEndPos(group));
0315         }
0316     }
0317 
0318     return stringListFromUtf8Collection(groups);
0319 }
0320 
0321 QStringList KConfigPrivate::groupList(const QByteArray &group) const
0322 {
0323     const QByteArray theGroup = group + '\x1d';
0324     ByteArrayViewSet groups;
0325 
0326     entryMap.forEachEntryWhoseGroupStartsWith(theGroup, [&theGroup, &groups](KEntryMapConstIterator entryMapIt) {
0327         if (isNonDeletedKey(entryMapIt)) {
0328             const QByteArray &entryGroup = entryMapIt.key().mGroup;
0329             const auto subgroupStartPos = theGroup.size();
0330             const auto subgroupEndPos = findFirstGroupEndPos(entryGroup, subgroupStartPos);
0331             groups.emplace(entryGroup.constData() + subgroupStartPos, subgroupEndPos - subgroupStartPos);
0332         }
0333     });
0334 
0335     return stringListFromUtf8Collection(groups);
0336 }
0337 
0338 /// Returns @p parentGroup itself, all its subgroups, subsubgroups, and so on, including deleted groups.
0339 QSet<QByteArray> KConfigPrivate::allSubGroups(const QByteArray &parentGroup) const
0340 {
0341     QSet<QByteArray> groups;
0342 
0343     entryMap.forEachEntryWhoseGroupStartsWith(parentGroup, [&parentGroup, &groups](KEntryMapConstIterator entryMapIt) {
0344         const KEntryKey &key = entryMapIt.key();
0345         if (key.mKey.isNull() && isGroupOrSubGroupMatch(entryMapIt, parentGroup)) {
0346             groups << key.mGroup;
0347         }
0348     });
0349 
0350     return groups;
0351 }
0352 
0353 bool KConfigPrivate::hasNonDeletedEntries(const QByteArray &group) const
0354 {
0355     return entryMap.anyEntryWhoseGroupStartsWith(group, [&group](KEntryMapConstIterator entryMapIt) {
0356         return isGroupOrSubGroupMatch(entryMapIt, group) && isNonDeletedKey(entryMapIt);
0357     });
0358 }
0359 
0360 QStringList KConfigPrivate::keyListImpl(const QByteArray &theGroup) const
0361 {
0362     QStringList keys;
0363 
0364     const auto theEnd = entryMap.constEnd();
0365     auto it = entryMap.constFindEntry(theGroup);
0366     if (it != theEnd) {
0367         ++it; // advance past the special group entry marker
0368 
0369         std::set<QString> tmp; // unique set, sorted for unittests
0370         for (; it != theEnd && it.key().mGroup == theGroup; ++it) {
0371             if (isNonDeletedKey(it)) {
0372                 tmp.insert(QString::fromUtf8(it.key().mKey));
0373             }
0374         }
0375         keys = QList<QString>(tmp.begin(), tmp.end());
0376     }
0377 
0378     return keys;
0379 }
0380 
0381 QMap<QString, QString> KConfig::entryMap(const QString &aGroup) const
0382 {
0383     Q_D(const KConfig);
0384     QMap<QString, QString> theMap;
0385     const QByteArray theGroup(aGroup.isEmpty() ? "<default>" : aGroup.toUtf8());
0386 
0387     const auto theEnd = d->entryMap.constEnd();
0388     auto it = d->entryMap.constFindEntry(theGroup, {}, {});
0389     if (it != theEnd) {
0390         ++it; // advance past the special group entry marker
0391 
0392         for (; it != theEnd && it.key().mGroup == theGroup; ++it) {
0393             // leave the default values and deleted entries out
0394             if (!it->bDeleted && !it.key().bDefault) {
0395                 const QString key = QString::fromUtf8(it.key().mKey.constData());
0396                 // the localized entry should come first, so don't overwrite it
0397                 // with the non-localized entry
0398                 if (!theMap.contains(key)) {
0399                     if (it->bExpand) {
0400                         theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->mValue.constData())));
0401                     } else {
0402                         theMap.insert(key, QString::fromUtf8(it->mValue.constData()));
0403                     }
0404                 }
0405             }
0406         }
0407     }
0408 
0409     return theMap;
0410 }
0411 
0412 bool KConfig::sync()
0413 {
0414     Q_D(KConfig);
0415 
0416     if (isImmutable() || name().isEmpty()) {
0417         // can't write to an immutable or anonymous file.
0418         return false;
0419     }
0420 
0421     QHash<QString, QByteArrayList> notifyGroupsLocal;
0422     QHash<QString, QByteArrayList> notifyGroupsGlobal;
0423 
0424     if (d->bDirty && d->mBackend) {
0425         const QByteArray utf8Locale(locale().toUtf8());
0426 
0427         // Create the containing dir, maybe it wasn't there
0428         d->mBackend->createEnclosing();
0429 
0430         // lock the local file
0431         if (d->configState == ReadWrite && !d->lockLocal()) {
0432             qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file";
0433             return false;
0434         }
0435 
0436         // Rewrite global/local config only if there is a dirty entry in it.
0437         bool writeGlobals = false;
0438         bool writeLocals = false;
0439 
0440         for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) {
0441             auto e = it.value();
0442             if (e.bDirty) {
0443                 if (e.bGlobal) {
0444                     writeGlobals = true;
0445                     if (e.bNotify) {
0446                         notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
0447                     }
0448                 } else {
0449                     writeLocals = true;
0450                     if (e.bNotify) {
0451                         notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
0452                     }
0453                 }
0454             }
0455         }
0456 
0457         d->bDirty = false; // will revert to true if a config write fails
0458 
0459         if (d->wantGlobals() && writeGlobals) {
0460             QExplicitlySharedDataPointer<KConfigBackend> tmp = KConfigBackend::create(*sGlobalFileName);
0461             if (d->configState == ReadWrite && !tmp->lock()) {
0462                 qCWarning(KCONFIG_CORE_LOG) << "couldn't lock global file";
0463 
0464                 // unlock the local config if we're returning early
0465                 if (d->mBackend->isLocked()) {
0466                     d->mBackend->unlock();
0467                 }
0468 
0469                 d->bDirty = true;
0470                 return false;
0471             }
0472             if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteGlobal)) {
0473                 d->bDirty = true;
0474             }
0475             if (tmp->isLocked()) {
0476                 tmp->unlock();
0477             }
0478         }
0479 
0480         if (writeLocals) {
0481             if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteOptions())) {
0482                 d->bDirty = true;
0483             }
0484         }
0485         if (d->mBackend->isLocked()) {
0486             d->mBackend->unlock();
0487         }
0488     }
0489 
0490     // Notifying absolute paths is not supported and also makes no sense.
0491     const bool isAbsolutePath = name().at(0) == QLatin1Char('/');
0492     if (!notifyGroupsLocal.isEmpty() && !isAbsolutePath) {
0493         d->notifyClients(notifyGroupsLocal, kconfigDBusSanitizePath(QLatin1Char('/') + name()));
0494     }
0495     if (!notifyGroupsGlobal.isEmpty()) {
0496         d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
0497     }
0498 
0499     return !d->bDirty;
0500 }
0501 
0502 void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
0503 {
0504 #if KCONFIG_USE_DBUS
0505     qDBusRegisterMetaType<QByteArrayList>();
0506 
0507     qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
0508 
0509     QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"));
0510     message.setArguments({QVariant::fromValue(changes)});
0511     QDBusConnection::sessionBus().send(message);
0512 #else
0513     Q_UNUSED(changes)
0514     Q_UNUSED(path)
0515 #endif
0516 }
0517 
0518 void KConfig::markAsClean()
0519 {
0520     Q_D(KConfig);
0521     d->bDirty = false;
0522 
0523     // clear any dirty flags that entries might have set
0524     const KEntryMapIterator theEnd = d->entryMap.end();
0525     for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) {
0526         it->bDirty = false;
0527         it->bNotify = false;
0528     }
0529 }
0530 
0531 bool KConfig::isDirty() const
0532 {
0533     Q_D(const KConfig);
0534     return d->bDirty;
0535 }
0536 
0537 void KConfig::checkUpdate(const QString &id, const QString &updateFile)
0538 {
0539     const KConfigGroup cg(this, "$Version");
0540     const QString cfg_id = updateFile + QLatin1Char(':') + id;
0541     const QStringList ids = cg.readEntry("update_info", QStringList());
0542     if (!ids.contains(cfg_id)) {
0543         QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList{QStringLiteral("--check"), updateFile});
0544         reparseConfiguration();
0545     }
0546 }
0547 
0548 KConfig *KConfig::copyTo(const QString &file, KConfig *config) const
0549 {
0550     Q_D(const KConfig);
0551     if (!config) {
0552         config = new KConfig(QString(), SimpleConfig, d->resourceType);
0553     }
0554     config->d_func()->changeFileName(file);
0555     config->d_func()->entryMap = d->entryMap;
0556     config->d_func()->bFileImmutable = false;
0557 
0558     const KEntryMapIterator theEnd = config->d_func()->entryMap.end();
0559     for (KEntryMapIterator it = config->d_func()->entryMap.begin(); it != theEnd; ++it) {
0560         it->bDirty = true;
0561     }
0562     config->d_ptr->bDirty = true;
0563 
0564     return config;
0565 }
0566 
0567 QString KConfig::name() const
0568 {
0569     Q_D(const KConfig);
0570     return d->fileName;
0571 }
0572 
0573 KConfig::OpenFlags KConfig::openFlags() const
0574 {
0575     Q_D(const KConfig);
0576     return d->openFlags;
0577 }
0578 
0579 struct KConfigStaticData {
0580     QString globalMainConfigName;
0581     // Keep a copy so we can use it in global dtors, after qApp is gone
0582     QStringList appArgs;
0583 };
0584 Q_GLOBAL_STATIC(KConfigStaticData, globalData)
0585 
0586 void KConfig::setMainConfigName(const QString &str)
0587 {
0588     globalData()->globalMainConfigName = str;
0589 }
0590 
0591 QString KConfig::mainConfigName()
0592 {
0593     KConfigStaticData *data = globalData();
0594     if (data->appArgs.isEmpty()) {
0595         data->appArgs = QCoreApplication::arguments();
0596     }
0597 
0598     // --config on the command line overrides everything else
0599     const QStringList args = data->appArgs;
0600     for (int i = 1; i < args.count(); ++i) {
0601         if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
0602             return args.at(i + 1);
0603         }
0604     }
0605     const QString globalName = data->globalMainConfigName;
0606     if (!globalName.isEmpty()) {
0607         return globalName;
0608     }
0609 
0610     QString appName = QCoreApplication::applicationName();
0611     return appName + QLatin1String("rc");
0612 }
0613 
0614 void KConfigPrivate::changeFileName(const QString &name)
0615 {
0616     fileName = name;
0617 
0618     QString file;
0619     if (name.isEmpty()) {
0620         if (wantDefaults()) { // accessing default app-specific config "appnamerc"
0621             fileName = KConfig::mainConfigName();
0622             file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
0623         } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
0624             resourceType = QStandardPaths::GenericConfigLocation;
0625             fileName = QStringLiteral("kdeglobals");
0626             file = *sGlobalFileName;
0627         } else {
0628             // anonymous config
0629             openFlags = KConfig::SimpleConfig;
0630             return;
0631         }
0632     } else if (QDir::isAbsolutePath(fileName)) {
0633         fileName = QFileInfo(fileName).canonicalFilePath();
0634         if (fileName.isEmpty()) { // file doesn't exist (yet)
0635             fileName = name;
0636         }
0637         file = fileName;
0638     } else {
0639         file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
0640     }
0641 
0642     Q_ASSERT(!file.isEmpty());
0643 
0644     bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0);
0645 
0646     if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend
0647         mBackend = KConfigBackend::create(file);
0648     } else {
0649         mBackend->setFilePath(file);
0650     }
0651 
0652     configState = mBackend->accessMode();
0653 }
0654 
0655 void KConfig::reparseConfiguration()
0656 {
0657     Q_D(KConfig);
0658     if (d->fileName.isEmpty()) {
0659         return;
0660     }
0661 
0662     // Don't lose pending changes
0663     if (!d->isReadOnly() && d->bDirty) {
0664         sync();
0665     }
0666 
0667     d->entryMap.clear();
0668 
0669     d->bFileImmutable = false;
0670 
0671     {
0672         QMutexLocker locker(&s_globalFilesMutex);
0673         s_globalFiles()->clear();
0674     }
0675 
0676     // Parse all desired files from the least to the most specific.
0677     if (d->wantGlobals()) {
0678         d->parseGlobalFiles();
0679     }
0680 
0681     d->parseConfigFiles();
0682 }
0683 
0684 QStringList KConfigPrivate::getGlobalFiles() const
0685 {
0686     QMutexLocker locker(&s_globalFilesMutex);
0687     if (s_globalFiles()->isEmpty()) {
0688         const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
0689         const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
0690 
0691         const bool useEtcKderc = !etc_kderc.isEmpty();
0692         s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
0693 
0694         for (const QString &dir1 : paths1) {
0695             s_globalFiles()->push_front(dir1);
0696         }
0697         for (const QString &dir2 : paths2) {
0698             s_globalFiles()->push_front(dir2);
0699         }
0700 
0701         if (useEtcKderc) {
0702             s_globalFiles()->push_front(etc_kderc);
0703         }
0704     }
0705 
0706     return *s_globalFiles();
0707 }
0708 
0709 void KConfigPrivate::parseGlobalFiles()
0710 {
0711     const QStringList globalFiles = getGlobalFiles();
0712     //    qDebug() << "parsing global files" << globalFiles;
0713 
0714     Q_ASSERT(entryMap.isEmpty());
0715     const ParseCacheKey key = {globalFiles, locale};
0716     auto data = sGlobalParse->localData().object(key);
0717     QDateTime newest;
0718     for (const auto &file : globalFiles) {
0719         const auto fileDate = QFileInfo(file).lastModified();
0720         if (fileDate > newest) {
0721             newest = fileDate;
0722         }
0723     }
0724     if (data) {
0725         if (data->parseTime < newest) {
0726             data = nullptr;
0727         } else {
0728             entryMap = data->entries;
0729             return;
0730         }
0731     }
0732 
0733     const QByteArray utf8Locale = locale.toUtf8();
0734     for (const QString &file : globalFiles) {
0735         KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions;
0736 
0737         if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) {
0738             parseOpts |= KConfigBackend::ParseDefaults;
0739         }
0740 
0741         QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(file);
0742         if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable) {
0743             break;
0744         }
0745     }
0746     sGlobalParse->localData().insert(key, new ParseCacheValue({entryMap, newest}));
0747 }
0748 
0749 void KConfigPrivate::parseConfigFiles()
0750 {
0751     // can only read the file if there is a backend and a file name
0752     if (mBackend && !fileName.isEmpty()) {
0753         bFileImmutable = false;
0754 
0755         QList<QString> files;
0756         if (wantDefaults()) {
0757             if (bSuppressGlobal) {
0758                 files = getGlobalFiles();
0759             } else {
0760                 if (QDir::isAbsolutePath(fileName)) {
0761                     const QString canonicalFile = QFileInfo(fileName).canonicalFilePath();
0762                     if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
0763                         files << canonicalFile;
0764                     }
0765                 } else {
0766                     const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName);
0767                     for (const QString &f : localFilesPath) {
0768                         files.prepend(QFileInfo(f).canonicalFilePath());
0769                     }
0770 
0771                     // allow fallback to config files bundled in resources
0772                     const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
0773                     if (QFile::exists(resourceFile)) {
0774                         files.prepend(resourceFile);
0775                     }
0776                 }
0777             }
0778         } else {
0779             files << mBackend->filePath();
0780         }
0781         if (!isSimple()) {
0782             files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
0783         }
0784 
0785         //        qDebug() << "parsing local files" << files;
0786 
0787         const QByteArray utf8Locale = locale.toUtf8();
0788         for (const QString &file : std::as_const(files)) {
0789             if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) {
0790                 switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseExpansions)) {
0791                 case KConfigBackend::ParseOk:
0792                     break;
0793                 case KConfigBackend::ParseImmutable:
0794                     bFileImmutable = true;
0795                     break;
0796                 case KConfigBackend::ParseOpenError:
0797                     configState = KConfigBase::NoAccess;
0798                     break;
0799                 }
0800             } else {
0801                 QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(file);
0802                 constexpr auto parseOpts = KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions;
0803                 bFileImmutable = backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable;
0804             }
0805 
0806             if (bFileImmutable) {
0807                 break;
0808             }
0809         }
0810     }
0811 }
0812 
0813 KConfig::AccessMode KConfig::accessMode() const
0814 {
0815     Q_D(const KConfig);
0816     return d->configState;
0817 }
0818 
0819 void KConfig::addConfigSources(const QStringList &files)
0820 {
0821     Q_D(KConfig);
0822     for (const QString &file : files) {
0823         d->extraFiles.push(file);
0824     }
0825 
0826     if (!files.isEmpty()) {
0827         reparseConfiguration();
0828     }
0829 }
0830 
0831 QStringList KConfig::additionalConfigSources() const
0832 {
0833     Q_D(const KConfig);
0834     return d->extraFiles.toList();
0835 }
0836 
0837 QString KConfig::locale() const
0838 {
0839     Q_D(const KConfig);
0840     return d->locale;
0841 }
0842 
0843 bool KConfigPrivate::setLocale(const QString &aLocale)
0844 {
0845     if (aLocale != locale) {
0846         locale = aLocale;
0847         return true;
0848     }
0849     return false;
0850 }
0851 
0852 bool KConfig::setLocale(const QString &locale)
0853 {
0854     Q_D(KConfig);
0855     if (d->setLocale(locale)) {
0856         reparseConfiguration();
0857         return true;
0858     }
0859     return false;
0860 }
0861 
0862 void KConfig::setReadDefaults(bool b)
0863 {
0864     Q_D(KConfig);
0865     d->bReadDefaults = b;
0866 }
0867 
0868 bool KConfig::readDefaults() const
0869 {
0870     Q_D(const KConfig);
0871     return d->bReadDefaults;
0872 }
0873 
0874 bool KConfig::isImmutable() const
0875 {
0876     Q_D(const KConfig);
0877     return d->bFileImmutable;
0878 }
0879 
0880 bool KConfig::isGroupImmutableImpl(const QByteArray &aGroup) const
0881 {
0882     Q_D(const KConfig);
0883     return isImmutable() || d->entryMap.getEntryOption(aGroup, {}, {}, KEntryMap::EntryImmutable);
0884 }
0885 
0886 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0)
0887 void KConfig::setForceGlobal(bool b)
0888 {
0889     Q_D(KConfig);
0890     d->bForceGlobal = b;
0891 }
0892 #endif
0893 
0894 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0)
0895 bool KConfig::forceGlobal() const
0896 {
0897     Q_D(const KConfig);
0898     return d->bForceGlobal;
0899 }
0900 #endif
0901 
0902 KConfigGroup KConfig::groupImpl(const QByteArray &group)
0903 {
0904     return KConfigGroup(this, group.constData());
0905 }
0906 
0907 const KConfigGroup KConfig::groupImpl(const QByteArray &group) const
0908 {
0909     return KConfigGroup(this, group.constData());
0910 }
0911 
0912 KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
0913 {
0914     KEntryMap::EntryOptions options = {};
0915 
0916     if (flags & KConfig::Persistent) {
0917         options |= KEntryMap::EntryDirty;
0918     }
0919     if (flags & KConfig::Global) {
0920         options |= KEntryMap::EntryGlobal;
0921     }
0922     if (flags & KConfig::Localized) {
0923         options |= KEntryMap::EntryLocalized;
0924     }
0925     if (flags.testFlag(KConfig::Notify)) {
0926         options |= KEntryMap::EntryNotify;
0927     }
0928     return options;
0929 }
0930 
0931 void KConfig::deleteGroupImpl(const QByteArray &aGroup, WriteConfigFlags flags)
0932 {
0933     Q_D(KConfig);
0934     KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
0935 
0936     const QSet<QByteArray> groups = d->allSubGroups(aGroup);
0937     for (const QByteArray &group : groups) {
0938         const QStringList keys = d->keyListImpl(group);
0939         for (const QString &_key : keys) {
0940             const QByteArray &key = _key.toUtf8();
0941             if (d->canWriteEntry(group, key.constData())) {
0942                 d->entryMap.setEntry(group, key, QByteArray(), options);
0943                 d->bDirty = true;
0944             }
0945         }
0946     }
0947 }
0948 
0949 bool KConfig::isConfigWritable(bool warnUser)
0950 {
0951     Q_D(KConfig);
0952     bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false);
0953 
0954     if (warnUser && !allWritable) {
0955         QString errorMsg;
0956         if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately
0957             errorMsg = d->mBackend->nonWritableErrorMessage();
0958         }
0959 
0960         // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
0961         errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator.");
0962         QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
0963         if (!cmdToExec.isEmpty()) {
0964             QProcess::execute(cmdToExec, QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
0965         }
0966     }
0967 
0968     d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
0969 
0970     return allWritable;
0971 }
0972 
0973 bool KConfig::hasGroupImpl(const QByteArray &aGroup) const
0974 {
0975     Q_D(const KConfig);
0976 
0977     // No need to look for the actual group entry anymore, or for subgroups:
0978     // a group exists if it contains any non-deleted entry.
0979 
0980     return d->hasNonDeletedEntries(aGroup);
0981 }
0982 
0983 bool KConfigPrivate::canWriteEntry(const QByteArray &group, const char *key, bool isDefault) const
0984 {
0985     if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) {
0986         return isDefault;
0987     }
0988     return true;
0989 }
0990 
0991 void KConfigPrivate::putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
0992 {
0993     KEntryMap::EntryOptions options = convertToOptions(flags);
0994 
0995     if (bForceGlobal) {
0996         options |= KEntryMap::EntryGlobal;
0997     }
0998     if (expand) {
0999         options |= KEntryMap::EntryExpansion;
1000     }
1001 
1002     if (value.isNull()) { // deleting entry
1003         options |= KEntryMap::EntryDeleted;
1004     }
1005 
1006     bool dirtied = entryMap.setEntry(group, key, value, options);
1007     if (dirtied && (flags & KConfigBase::Persistent)) {
1008         bDirty = true;
1009     }
1010 }
1011 
1012 void KConfigPrivate::revertEntry(const QByteArray &group, const char *key, KConfigBase::WriteConfigFlags flags)
1013 {
1014     KEntryMap::EntryOptions options = convertToOptions(flags);
1015 
1016     bool dirtied = entryMap.revertEntry(group, key, options);
1017     if (dirtied) {
1018         bDirty = true;
1019     }
1020 }
1021 
1022 QByteArray KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const
1023 {
1024     return lookupInternalEntry(group, key, flags).mValue;
1025 }
1026 
1027 KEntry KConfigPrivate::lookupInternalEntry(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags) const
1028 {
1029     if (bReadDefaults) {
1030         flags |= KEntryMap::SearchDefaults;
1031     }
1032     const auto it = entryMap.constFindEntry(group, key, flags);
1033     if (it == entryMap.constEnd()) {
1034         return {};
1035     }
1036     return it.value();
1037 }
1038 
1039 QString KConfigPrivate::lookupData(const QByteArray &group, const char *key, KEntryMap::SearchFlags flags, bool *expand) const
1040 {
1041     if (bReadDefaults) {
1042         flags |= KEntryMap::SearchDefaults;
1043     }
1044     return entryMap.getEntry(group, key, QString(), flags, expand);
1045 }
1046 
1047 QStandardPaths::StandardLocation KConfig::locationType() const
1048 {
1049     Q_D(const KConfig);
1050     return d->resourceType;
1051 }
1052 
1053 void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1054 {
1055     /* nothing */
1056 }