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