File indexing completed on 2024-04-14 03:51:04

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