File indexing completed on 2020-03-30 10:49:30

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