File indexing completed on 2020-07-25 10:42:55

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