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