KConfig

kconfig.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <[email protected]>
4  SPDX-FileCopyrightText: 1999 Preston Brown <[email protected]>
5  SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "kconfig.h"
11 #include "kconfig_p.h"
12 
13 #include "config-kconfig.h"
14 #include "dbussanitizer_p.h"
15 #include "kconfig_core_log_settings.h"
16 
17 #include <cstdlib>
18 #include <fcntl.h>
19 
20 #include "kconfigbackend_p.h"
21 #include "kconfiggroup.h"
22 
23 #include <QBasicMutex>
24 #include <QByteArray>
25 #include <QCache>
26 #include <QCoreApplication>
27 #include <QDir>
28 #include <QFile>
29 #include <QLocale>
30 #include <QMutexLocker>
31 #include <QProcess>
32 #include <QSet>
33 #include <QThreadStorage>
34 
35 #include <algorithm>
36 #include <iterator>
37 #include <set>
38 #include <string_view>
39 #include <unordered_set>
40 
41 #if KCONFIG_USE_DBUS
42 #include <QDBusConnection>
43 #include <QDBusMessage>
44 #include <QDBusMetaType>
45 #endif
46 
47 bool KConfigPrivate::mappingsRegistered = false;
48 
49 // For caching purposes
50 static bool s_wasTestModeEnabled = false;
51 
52 Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes.
53 static QBasicMutex s_globalFilesMutex;
54 Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals")))
55 
56 using ParseCacheKey = std::pair<QStringList, QString>;
57 struct ParseCacheValue {
58  KEntryMap entries;
59  QDateTime parseTime;
60 };
62 Q_GLOBAL_STATIC(ParseCache, sGlobalParse)
63 
64 #ifndef Q_OS_WIN
65 static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive;
66 #else
67 static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive;
68 #endif
69 
70 KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags, QStandardPaths::StandardLocation resourceType)
71  : openFlags(flags)
72  , resourceType(resourceType)
73  , mBackend(nullptr)
74  , bDynamicBackend(true)
75  , bDirty(false)
76  , bReadDefaults(false)
77  , bFileImmutable(false)
78  , bForceGlobal(false)
79  , bSuppressGlobal(false)
80  , configState(KConfigBase::NoAccess)
81 {
82  const bool isTestMode = QStandardPaths::isTestModeEnabled();
83  // If sGlobalFileName was initialised and testMode has been toggled,
84  // sGlobalFileName may need to be updated to point to the correct kdeglobals file
85  if (sGlobalFileName.exists() && s_wasTestModeEnabled != isTestMode) {
86  s_wasTestModeEnabled = isTestMode;
88  }
89 
90  static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1);
91  if (use_etc_kderc.loadRelaxed() < 0) {
92  use_etc_kderc.storeRelaxed(!qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests
93  }
94  if (use_etc_kderc.loadRelaxed()) {
95  etc_kderc =
96 #ifdef Q_OS_WIN
97  QFile::decodeName(qgetenv("WINDIR") + "/kde5rc");
98 #else
99  QStringLiteral("/etc/kde5rc");
100 #endif
101  if (!QFileInfo(etc_kderc).isReadable()) {
102  use_etc_kderc.storeRelaxed(false);
103  etc_kderc.clear();
104  }
105  }
106 
107  // if (!mappingsRegistered) {
108  // KEntryMap tmp;
109  // if (!etc_kderc.isEmpty()) {
110  // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(etc_kderc, QLatin1String("INI"));
111  // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseDefaults);
112  // }
113  // const QString kde5rc(QDir::home().filePath(".kde5rc"));
114  // if (KStandardDirs::checkAccess(kde5rc, R_OK)) {
115  // QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(kde5rc, QLatin1String("INI"));
116  // backend->parseConfig( "en_US", tmp, KConfigBackend::ParseOptions());
117  // }
118  // KConfigBackend::registerMappings(tmp);
119  // mappingsRegistered = true;
120  // }
121 
122  setLocale(QLocale().name());
123 }
124 
125 bool KConfigPrivate::lockLocal()
126 {
127  if (mBackend) {
128  return mBackend->lock();
129  }
130  // anonymous object - pretend we locked it
131  return true;
132 }
133 
134 static bool isGroupOrSubGroupMatch(KEntryMapConstIterator entryMapIt, const QByteArray &group)
135 {
136  const QByteArray &entryGroup = entryMapIt.key().mGroup;
137  Q_ASSERT_X(entryGroup.startsWith(group), Q_FUNC_INFO, "Precondition");
138  return entryGroup.size() == group.size() || entryGroup[group.size()] == '\x1d';
139 }
140 
141 void KConfigPrivate::copyGroup(const QByteArray &source, const QByteArray &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const
142 {
143  KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap;
144  const bool sameName = (destination == source);
145 
146  // we keep this bool outside the for loop so that if
147  // the group is empty, we don't end up marking the other config
148  // as dirty erroneously
149  bool dirtied = false;
150 
151  entryMap.forEachEntryWhoseGroupStartsWith(source, [&source, &destination, flags, &otherMap, sameName, &dirtied](KEntryMapConstIterator entryMapIt) {
152  // don't copy groups that start with the same prefix, but are not sub-groups
153  if (!isGroupOrSubGroupMatch(entryMapIt, source)) {
154  return;
155  }
156 
157  KEntryKey newKey = entryMapIt.key();
158 
159  if (flags & KConfigBase::Localized) {
160  newKey.bLocal = true;
161  }
162 
163  if (!sameName) {
164  newKey.mGroup.replace(0, source.size(), destination);
165  }
166 
167  KEntry entry = entryMapIt.value();
168  dirtied = entry.bDirty = flags & KConfigBase::Persistent;
169 
170  if (flags & KConfigBase::Global) {
171  entry.bGlobal = true;
172  }
173 
174  if (flags & KConfigBase::Notify) {
175  entry.bNotify = true;
176  }
177 
178  otherMap[newKey] = entry;
179  });
180 
181  if (dirtied) {
182  otherGroup->config()->d_ptr->bDirty = true;
183  }
184 }
185 
186 QString KConfigPrivate::expandString(const QString &value)
187 {
188  QString aValue = value;
189 
190  // check for environment variables and make necessary translations
191  int nDollarPos = aValue.indexOf(QLatin1Char('$'));
192  while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
193  // there is at least one $
194  if (aValue.at(nDollarPos + 1) != QLatin1Char('$')) {
195  int nEndPos = nDollarPos + 1;
196  // the next character is not $
197  QStringView aVarName;
198  if (aValue.at(nEndPos) == QLatin1Char('{')) {
199  while ((nEndPos <= aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) {
200  ++nEndPos;
201  }
202  ++nEndPos;
203  aVarName = QStringView(aValue).mid(nDollarPos + 2, nEndPos - nDollarPos - 3);
204  } else {
205  while (nEndPos < aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) {
206  ++nEndPos;
207  }
208  aVarName = QStringView(aValue).mid(nDollarPos + 1, nEndPos - nDollarPos - 1);
209  }
210  QString env;
211  if (!aVarName.isEmpty()) {
212 #ifdef Q_OS_WIN
213  if (aVarName == QLatin1String("HOME")) {
214  env = QDir::homePath();
215  } else
216 #endif
217  {
218  QByteArray pEnv = qgetenv(aVarName.toLatin1().constData());
219  if (!pEnv.isEmpty()) {
220  env = QString::fromLocal8Bit(pEnv.constData());
221  } else {
222  if (aVarName == QLatin1String("QT_DATA_HOME")) {
224  } else if (aVarName == QLatin1String("QT_CONFIG_HOME")) {
226  } else if (aVarName == QLatin1String("QT_CACHE_HOME")) {
228  }
229  }
230  }
231  aValue.replace(nDollarPos, nEndPos - nDollarPos, env);
232  nDollarPos += env.length();
233  } else {
234  aValue.remove(nDollarPos, nEndPos - nDollarPos);
235  }
236  } else {
237  // remove one of the dollar signs
238  aValue.remove(nDollarPos, 1);
239  ++nDollarPos;
240  }
241  nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos);
242  }
243 
244  return aValue;
245 }
246 
248  : d_ptr(new KConfigPrivate(mode, resourceType))
249 {
250  d_ptr->changeFileName(file); // set the local file name
251 
252  // read initial information off disk
254 }
255 
256 KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType)
257  : d_ptr(new KConfigPrivate(SimpleConfig, resourceType))
258 {
259  d_ptr->mBackend = KConfigBackend::create(file, backend);
260  d_ptr->bDynamicBackend = false;
261  d_ptr->changeFileName(file); // set the local file name
262 
263  // read initial information off disk
265 }
266 
267 KConfig::KConfig(KConfigPrivate &d)
268  : d_ptr(&d)
269 {
270 }
271 
272 KConfig::~KConfig()
273 {
274  Q_D(KConfig);
275  if (d->bDirty && (d->mBackend && d->mBackend->ref.loadRelaxed() == 1)) {
276  sync();
277  }
278  delete d;
279 }
280 
281 static bool isNonDeletedKey(KEntryMapConstIterator entryMapIt)
282 {
283  return !entryMapIt.key().mKey.isNull() && !entryMapIt->bDeleted;
284 }
285 
286 static int findFirstGroupEndPos(const QByteArray &groupFullName, int from = 0)
287 {
288  const auto index = groupFullName.indexOf('\x1d', from);
289  return index == -1 ? groupFullName.size() : index;
290 }
291 
292 // std::string_view is used because QByteArrayView does not exist in Qt 5.
293 // std::unordered_set rather than QSet is used because there is no qHash() overload for std::string_view in Qt 5.
294 using ByteArrayViewSet = std::unordered_set<std::string_view>;
295 
296 static QStringList stringListFromUtf8Collection(const ByteArrayViewSet &source)
297 {
299  list.reserve(source.size());
300  std::transform(source.cbegin(), source.cend(), std::back_inserter(list), [](std::string_view view) {
301  return QString::fromUtf8(view.data(), view.size());
302  });
303  return list;
304 }
305 
307 {
308  Q_D(const KConfig);
309  ByteArrayViewSet groups;
310 
311  for (auto entryMapIt = d->entryMap.cbegin(); entryMapIt != d->entryMap.cend(); ++entryMapIt) {
312  const QByteArray &group = entryMapIt.key().mGroup;
313  if (isNonDeletedKey(entryMapIt) && !group.isEmpty() && group != "<default>" && group != "$Version") {
314  groups.emplace(group.constData(), findFirstGroupEndPos(group));
315  }
316  }
317 
318  return stringListFromUtf8Collection(groups);
319 }
320 
321 QStringList KConfigPrivate::groupList(const QByteArray &group) const
322 {
323  const QByteArray theGroup = group + '\x1d';
324  ByteArrayViewSet groups;
325 
326  entryMap.forEachEntryWhoseGroupStartsWith(theGroup, [&theGroup, &groups](KEntryMapConstIterator entryMapIt) {
327  if (isNonDeletedKey(entryMapIt)) {
328  const QByteArray &entryGroup = entryMapIt.key().mGroup;
329  const auto subgroupStartPos = theGroup.size();
330  const auto subgroupEndPos = findFirstGroupEndPos(entryGroup, subgroupStartPos);
331  groups.emplace(entryGroup.constData() + subgroupStartPos, subgroupEndPos - subgroupStartPos);
332  }
333  });
334 
335  return stringListFromUtf8Collection(groups);
336 }
337 
338 /// Returns @p parentGroup itself, all its subgroups, subsubgroups, and so on, including deleted groups.
339 QSet<QByteArray> KConfigPrivate::allSubGroups(const QByteArray &parentGroup) const
340 {
341  QSet<QByteArray> groups;
342 
343  entryMap.forEachEntryWhoseGroupStartsWith(parentGroup, [&parentGroup, &groups](KEntryMapConstIterator entryMapIt) {
344  const KEntryKey &key = entryMapIt.key();
345  if (key.mKey.isNull() && isGroupOrSubGroupMatch(entryMapIt, parentGroup)) {
346  groups << key.mGroup;
347  }
348  });
349 
350  return groups;
351 }
352 
353 bool KConfigPrivate::hasNonDeletedEntries(const QByteArray &group) const
354 {
355  return entryMap.anyEntryWhoseGroupStartsWith(group, [&group](KEntryMapConstIterator entryMapIt) {
356  return isGroupOrSubGroupMatch(entryMapIt, group) && isNonDeletedKey(entryMapIt);
357  });
358 }
359 
360 QStringList KConfigPrivate::keyListImpl(const QByteArray &theGroup) const
361 {
362  QStringList keys;
363 
364  const auto theEnd = entryMap.constEnd();
365  auto it = entryMap.constFindEntry(theGroup);
366  if (it != theEnd) {
367  ++it; // advance past the special group entry marker
368 
369  std::set<QString> tmp; // unique set, sorted for unittests
370  for (; it != theEnd && it.key().mGroup == theGroup; ++it) {
371  if (isNonDeletedKey(it)) {
372  tmp.insert(QString::fromUtf8(it.key().mKey));
373  }
374  }
375  keys = QList<QString>(tmp.begin(), tmp.end());
376  }
377 
378  return keys;
379 }
380 
382 {
383  Q_D(const KConfig);
384  QMap<QString, QString> theMap;
385  const QByteArray theGroup(aGroup.isEmpty() ? "<default>" : aGroup.toUtf8());
386 
387  const auto theEnd = d->entryMap.constEnd();
388  auto it = d->entryMap.constFindEntry(theGroup, {}, {});
389  if (it != theEnd) {
390  ++it; // advance past the special group entry marker
391 
392  for (; it != theEnd && it.key().mGroup == theGroup; ++it) {
393  // leave the default values and deleted entries out
394  if (!it->bDeleted && !it.key().bDefault) {
395  const QString key = QString::fromUtf8(it.key().mKey.constData());
396  // the localized entry should come first, so don't overwrite it
397  // with the non-localized entry
398  if (!theMap.contains(key)) {
399  if (it->bExpand) {
400  theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->mValue.constData())));
401  } else {
402  theMap.insert(key, QString::fromUtf8(it->mValue.constData()));
403  }
404  }
405  }
406  }
407  }
408 
409  return theMap;
410 }
411 
413 {
414  Q_D(KConfig);
415 
416  if (isImmutable() || name().isEmpty()) {
417  // can't write to an immutable or anonymous file.
418  return false;
419  }
420 
421  QHash<QString, QByteArrayList> notifyGroupsLocal;
422  QHash<QString, QByteArrayList> notifyGroupsGlobal;
423 
424  if (d->bDirty && d->mBackend) {
425  const QByteArray utf8Locale(locale().toUtf8());
426 
427  // Create the containing dir, maybe it wasn't there
428  d->mBackend->createEnclosing();
429 
430  // lock the local file
431  if (d->configState == ReadWrite && !d->lockLocal()) {
432  qCWarning(KCONFIG_CORE_LOG) << "couldn't lock local file";
433  return false;
434  }
435 
436  // Rewrite global/local config only if there is a dirty entry in it.
437  bool writeGlobals = false;
438  bool writeLocals = false;
439 
440  for (auto it = d->entryMap.constBegin(); it != d->entryMap.constEnd(); ++it) {
441  auto e = it.value();
442  if (e.bDirty) {
443  if (e.bGlobal) {
444  writeGlobals = true;
445  if (e.bNotify) {
446  notifyGroupsGlobal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
447  }
448  } else {
449  writeLocals = true;
450  if (e.bNotify) {
451  notifyGroupsLocal[QString::fromUtf8(it.key().mGroup)] << it.key().mKey;
452  }
453  }
454  }
455  }
456 
457  d->bDirty = false; // will revert to true if a config write fails
458 
459  if (d->wantGlobals() && writeGlobals) {
460  QExplicitlySharedDataPointer<KConfigBackend> tmp = KConfigBackend::create(*sGlobalFileName);
461  if (d->configState == ReadWrite && !tmp->lock()) {
462  qCWarning(KCONFIG_CORE_LOG) << "couldn't lock global file";
463 
464  // unlock the local config if we're returning early
465  if (d->mBackend->isLocked()) {
466  d->mBackend->unlock();
467  }
468 
469  d->bDirty = true;
470  return false;
471  }
472  if (!tmp->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteGlobal)) {
473  d->bDirty = true;
474  }
475  if (tmp->isLocked()) {
476  tmp->unlock();
477  }
478  }
479 
480  if (writeLocals) {
481  if (!d->mBackend->writeConfig(utf8Locale, d->entryMap, KConfigBackend::WriteOptions())) {
482  d->bDirty = true;
483  }
484  }
485  if (d->mBackend->isLocked()) {
486  d->mBackend->unlock();
487  }
488  }
489 
490  // Notifying absolute paths is not supported and also makes no sense.
491  const bool isAbsolutePath = name().at(0) == QLatin1Char('/');
492  if (!notifyGroupsLocal.isEmpty() && !isAbsolutePath) {
493  d->notifyClients(notifyGroupsLocal, kconfigDBusSanitizePath(QLatin1Char('/') + name()));
494  }
495  if (!notifyGroupsGlobal.isEmpty()) {
496  d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
497  }
498 
499  return !d->bDirty;
500 }
501 
502 void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
503 {
504 #if KCONFIG_USE_DBUS
505  qDBusRegisterMetaType<QByteArrayList>();
506 
507  qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
508 
509  QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"));
510  message.setArguments({QVariant::fromValue(changes)});
512 #else
513  Q_UNUSED(changes)
514  Q_UNUSED(path)
515 #endif
516 }
517 
519 {
520  Q_D(KConfig);
521  d->bDirty = false;
522 
523  // clear any dirty flags that entries might have set
524  const KEntryMapIterator theEnd = d->entryMap.end();
525  for (KEntryMapIterator it = d->entryMap.begin(); it != theEnd; ++it) {
526  it->bDirty = false;
527  it->bNotify = false;
528  }
529 }
530 
531 bool KConfig::isDirty() const
532 {
533  Q_D(const KConfig);
534  return d->bDirty;
535 }
536 
537 void KConfig::checkUpdate(const QString &id, const QString &updateFile)
538 {
539  const KConfigGroup cg(this, "$Version");
540  const QString cfg_id = updateFile + QLatin1Char(':') + id;
541  const QStringList ids = cg.readEntry("update_info", QStringList());
542  if (!ids.contains(cfg_id)) {
543  QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList{QStringLiteral("--check"), updateFile});
545  }
546 }
547 
548 KConfig *KConfig::copyTo(const QString &file, KConfig *config) const
549 {
550  Q_D(const KConfig);
551  if (!config) {
552  config = new KConfig(QString(), SimpleConfig, d->resourceType);
553  }
554  config->d_func()->changeFileName(file);
555  config->d_func()->entryMap = d->entryMap;
556  config->d_func()->bFileImmutable = false;
557 
558  const KEntryMapIterator theEnd = config->d_func()->entryMap.end();
559  for (KEntryMapIterator it = config->d_func()->entryMap.begin(); it != theEnd; ++it) {
560  it->bDirty = true;
561  }
562  config->d_ptr->bDirty = true;
563 
564  return config;
565 }
566 
568 {
569  Q_D(const KConfig);
570  return d->fileName;
571 }
572 
574 {
575  Q_D(const KConfig);
576  return d->openFlags;
577 }
578 
579 struct KConfigStaticData {
580  QString globalMainConfigName;
581  // Keep a copy so we can use it in global dtors, after qApp is gone
582  QStringList appArgs;
583 };
584 Q_GLOBAL_STATIC(KConfigStaticData, globalData)
585 
586 void KConfig::setMainConfigName(const QString &str)
587 {
588  globalData()->globalMainConfigName = str;
589 }
590 
592 {
593  KConfigStaticData *data = globalData();
594  if (data->appArgs.isEmpty()) {
595  data->appArgs = QCoreApplication::arguments();
596  }
597 
598  // --config on the command line overrides everything else
599  const QStringList args = data->appArgs;
600  for (int i = 1; i < args.count(); ++i) {
601  if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
602  return args.at(i + 1);
603  }
604  }
605  const QString globalName = data->globalMainConfigName;
606  if (!globalName.isEmpty()) {
607  return globalName;
608  }
609 
611  return appName + QLatin1String("rc");
612 }
613 
614 void KConfigPrivate::changeFileName(const QString &name)
615 {
616  fileName = name;
617 
618  QString file;
619  if (name.isEmpty()) {
620  if (wantDefaults()) { // accessing default app-specific config "appnamerc"
621  fileName = KConfig::mainConfigName();
622  file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
623  } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
625  fileName = QStringLiteral("kdeglobals");
626  file = *sGlobalFileName;
627  } else {
628  // anonymous config
629  openFlags = KConfig::SimpleConfig;
630  return;
631  }
632  } else if (QDir::isAbsolutePath(fileName)) {
633  fileName = QFileInfo(fileName).canonicalFilePath();
634  if (fileName.isEmpty()) { // file doesn't exist (yet)
635  fileName = name;
636  }
637  file = fileName;
638  } else {
639  file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
640  }
641 
642  Q_ASSERT(!file.isEmpty());
643 
644  bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0);
645 
646  if (bDynamicBackend || !mBackend) { // allow dynamic changing of backend
647  mBackend = KConfigBackend::create(file);
648  } else {
649  mBackend->setFilePath(file);
650  }
651 
652  configState = mBackend->accessMode();
653 }
654 
656 {
657  Q_D(KConfig);
658  if (d->fileName.isEmpty()) {
659  return;
660  }
661 
662  // Don't lose pending changes
663  if (!d->isReadOnly() && d->bDirty) {
664  sync();
665  }
666 
667  d->entryMap.clear();
668 
669  d->bFileImmutable = false;
670 
671  {
672  QMutexLocker locker(&s_globalFilesMutex);
673  s_globalFiles()->clear();
674  }
675 
676  // Parse all desired files from the least to the most specific.
677  if (d->wantGlobals()) {
678  d->parseGlobalFiles();
679  }
680 
681  d->parseConfigFiles();
682 }
683 
684 QStringList KConfigPrivate::getGlobalFiles() const
685 {
686  QMutexLocker locker(&s_globalFilesMutex);
687  if (s_globalFiles()->isEmpty()) {
688  const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
689  const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
690 
691  const bool useEtcKderc = !etc_kderc.isEmpty();
692  s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
693 
694  for (const QString &dir1 : paths1) {
695  s_globalFiles()->push_front(dir1);
696  }
697  for (const QString &dir2 : paths2) {
698  s_globalFiles()->push_front(dir2);
699  }
700 
701  if (useEtcKderc) {
702  s_globalFiles()->push_front(etc_kderc);
703  }
704  }
705 
706  return *s_globalFiles();
707 }
708 
709 void KConfigPrivate::parseGlobalFiles()
710 {
711  const QStringList globalFiles = getGlobalFiles();
712  // qDebug() << "parsing global files" << globalFiles;
713 
714  Q_ASSERT(entryMap.isEmpty());
715  const ParseCacheKey key = {globalFiles, locale};
716  auto data = sGlobalParse->localData().object(key);
717  QDateTime newest;
718  for (const auto &file : globalFiles) {
719  const auto fileDate = QFileInfo(file).lastModified();
720  if (fileDate > newest) {
721  newest = fileDate;
722  }
723  }
724  if (data) {
725  if (data->parseTime < newest) {
726  data = nullptr;
727  } else {
728  entryMap = data->entries;
729  return;
730  }
731  }
732 
733  const QByteArray utf8Locale = locale.toUtf8();
734  for (const QString &file : globalFiles) {
735  KConfigBackend::ParseOptions parseOpts = KConfigBackend::ParseGlobal | KConfigBackend::ParseExpansions;
736 
737  if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) {
738  parseOpts |= KConfigBackend::ParseDefaults;
739  }
740 
741  QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(file);
742  if (backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable) {
743  break;
744  }
745  }
746  sGlobalParse->localData().insert(key, new ParseCacheValue({entryMap, newest}));
747 }
748 
749 void KConfigPrivate::parseConfigFiles()
750 {
751  // can only read the file if there is a backend and a file name
752  if (mBackend && !fileName.isEmpty()) {
753  bFileImmutable = false;
754 
755  QList<QString> files;
756  if (wantDefaults()) {
757  if (bSuppressGlobal) {
758  files = getGlobalFiles();
759  } else {
760  if (QDir::isAbsolutePath(fileName)) {
761  const QString canonicalFile = QFileInfo(fileName).canonicalFilePath();
762  if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
763  files << canonicalFile;
764  }
765  } else {
766  const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName);
767  for (const QString &f : localFilesPath) {
768  files.prepend(QFileInfo(f).canonicalFilePath());
769  }
770 
771  // allow fallback to config files bundled in resources
772  const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
773  if (QFile::exists(resourceFile)) {
774  files.prepend(resourceFile);
775  }
776  }
777  }
778  } else {
779  files << mBackend->filePath();
780  }
781  if (!isSimple()) {
782  files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
783  }
784 
785  // qDebug() << "parsing local files" << files;
786 
787  const QByteArray utf8Locale = locale.toUtf8();
788  for (const QString &file : std::as_const(files)) {
789  if (file.compare(mBackend->filePath(), sPathCaseSensitivity) == 0) {
790  switch (mBackend->parseConfig(utf8Locale, entryMap, KConfigBackend::ParseExpansions)) {
791  case KConfigBackend::ParseOk:
792  break;
793  case KConfigBackend::ParseImmutable:
794  bFileImmutable = true;
795  break;
796  case KConfigBackend::ParseOpenError:
797  configState = KConfigBase::NoAccess;
798  break;
799  }
800  } else {
801  QExplicitlySharedDataPointer<KConfigBackend> backend = KConfigBackend::create(file);
802  constexpr auto parseOpts = KConfigBackend::ParseDefaults | KConfigBackend::ParseExpansions;
803  bFileImmutable = backend->parseConfig(utf8Locale, entryMap, parseOpts) == KConfigBackend::ParseImmutable;
804  }
805 
806  if (bFileImmutable) {
807  break;
808  }
809  }
810  }
811 }
812 
814 {
815  Q_D(const KConfig);
816  return d->configState;
817 }
818 
820 {
821  Q_D(KConfig);
822  for (const QString &file : files) {
823  d->extraFiles.push(file);
824  }
825 
826  if (!files.isEmpty()) {
828  }
829 }
830 
832 {
833  Q_D(const KConfig);
834  return d->extraFiles.toList();
835 }
836 
838 {
839  Q_D(const KConfig);
840  return d->locale;
841 }
842 
843 bool KConfigPrivate::setLocale(const QString &aLocale)
844 {
845  if (aLocale != locale) {
846  locale = aLocale;
847  return true;
848  }
849  return false;
850 }
851 
852 bool KConfig::setLocale(const QString &locale)
853 {
854  Q_D(KConfig);
855  if (d->setLocale(locale)) {
857  return true;
858  }
859  return false;
860 }
861 
863 {
864  Q_D(KConfig);
865  d->bReadDefaults = b;
866 }
867 
869 {
870  Q_D(const KConfig);
871  return d->bReadDefaults;
872 }
873 
875 {
876  Q_D(const KConfig);
877  return d->bFileImmutable;
878 }
879 
880 bool KConfig::isGroupImmutableImpl(const QByteArray &aGroup) const
881 {
882  Q_D(const KConfig);
883  return isImmutable() || d->entryMap.getEntryOption(aGroup, {}, {}, KEntryMap::EntryImmutable);
884 }
885 
886 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0)
888 {
889  Q_D(KConfig);
890  d->bForceGlobal = b;
891 }
892 #endif
893 
894 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(4, 0)
896 {
897  Q_D(const KConfig);
898  return d->bForceGlobal;
899 }
900 #endif
901 
903 {
904  return KConfigGroup(this, group.constData());
905 }
906 
907 const KConfigGroup KConfig::groupImpl(const QByteArray &group) const
908 {
909  return KConfigGroup(this, group.constData());
910 }
911 
912 KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
913 {
914  KEntryMap::EntryOptions options = {};
915 
916  if (flags & KConfig::Persistent) {
917  options |= KEntryMap::EntryDirty;
918  }
919  if (flags & KConfig::Global) {
920  options |= KEntryMap::EntryGlobal;
921  }
922  if (flags & KConfig::Localized) {
923  options |= KEntryMap::EntryLocalized;
924  }
925  if (flags.testFlag(KConfig::Notify)) {
926  options |= KEntryMap::EntryNotify;
927  }
928  return options;
929 }
930 
932 {
933  Q_D(KConfig);
934  KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
935 
936  const QSet<QByteArray> groups = d->allSubGroups(aGroup);
937  for (const QByteArray &group : groups) {
938  const QStringList keys = d->keyListImpl(group);
939  for (const QString &_key : keys) {
940  const QByteArray &key = _key.toUtf8();
941  if (d->canWriteEntry(group, key.constData())) {
942  d->entryMap.setEntry(group, key, QByteArray(), options);
943  d->bDirty = true;
944  }
945  }
946  }
947 }
948 
949 bool KConfig::isConfigWritable(bool warnUser)
950 {
951  Q_D(KConfig);
952  bool allWritable = (d->mBackend ? d->mBackend->isWritable() : false);
953 
954  if (warnUser && !allWritable) {
955  QString errorMsg;
956  if (d->mBackend) { // TODO how can be it be null? Set errorMsg appropriately
957  errorMsg = d->mBackend->nonWritableErrorMessage();
958  }
959 
960  // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
961  errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator.");
962  QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
963  if (!cmdToExec.isEmpty()) {
964  QProcess::execute(cmdToExec, QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
965  }
966  }
967 
968  d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
969 
970  return allWritable;
971 }
972 
973 bool KConfig::hasGroupImpl(const QByteArray &aGroup) const
974 {
975  Q_D(const KConfig);
976 
977  // No need to look for the actual group entry anymore, or for subgroups:
978  // a group exists if it contains any non-deleted entry.
979 
980  return d->hasNonDeletedEntries(aGroup);
981 }
982 
983 bool KConfigPrivate::canWriteEntry(const QByteArray &group, const char *key, bool isDefault) const
984 {
985  if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) {
986  return isDefault;
987  }
988  return true;
989 }
990 
991 void KConfigPrivate::putData(const QByteArray &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
992 {
993  KEntryMap::EntryOptions options = convertToOptions(flags);
994 
995  if (bForceGlobal) {
996  options |= KEntryMap::EntryGlobal;
997  }
998  if (expand) {
999  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 
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 }
bool isNull() const const
AccessMode accessMode() const override
Definition: kconfig.cpp:813
bool contains(const Key &key) const const
Interface to interact with configuration.
Definition: kconfigbase.h:30
void virtual_hook(int id, void *data) override
Virtual hook, used to add new "virtual" functions while maintaining binary compatibility.
Definition: kconfig.cpp:1053
QByteArray toLatin1() const const
QString fromUtf8(const char *str, int size)
bool forceGlobal() const
Returns whether all entries are being written to kdeglobals.
Definition: kconfig.cpp:895
CaseSensitivity
QVariant fromValue(const T &value)
void deleteGroupImpl(const QByteArray &group, WriteConfigFlags flags=Normal) override
Definition: kconfig.cpp:931
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
int indexOf(char ch, int from) const const
void addConfigSources(const QStringList &sources)
extra config files
Definition: kconfig.cpp:819
int count(const T &value) const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
bool testFlag(Enum flag) const const
QString name() const
Returns the filename used to store the configuration.
Definition: kconfig.cpp:567
QStringView mid(qsizetype start) const const
KConfig * copyTo(const QString &file, KConfig *config=nullptr) const
Copies all entries from this config object to a new config object that will save itself to file.
Definition: kconfig.cpp:548
The central class of the KDE configuration data system.
Definition: kconfig.h:56
KConfigGroup groupImpl(const QByteArray &b) override
Definition: kconfig.cpp:902
void markAsClean() override
Definition: kconfig.cpp:518
@ Persistent
Save this entry when saving the config object.
Definition: kconfigbase.h:38
QStringList additionalConfigSources() const
Returns a list of the additional configuration sources used in this object.
Definition: kconfig.cpp:831
QString writableLocation(QStandardPaths::StandardLocation type)
void reparseConfiguration()
Updates the state of this object to match the persistent storage.
Definition: kconfig.cpp:655
QString homePath()
bool readDefaults() const
Definition: kconfig.cpp:868
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
bool sync() override
Definition: kconfig.cpp:412
bool setLocale(const QString &aLocale)
Sets the locale to aLocale.
Definition: kconfig.cpp:852
bool exists() const const
bool isDirty() const
Returns true if sync has any changes to write out.
Definition: kconfig.cpp:531
@ Notify
Notify remote KConfigWatchers of changes (requires DBus support) Implied persistent.
Definition: kconfigbase.h:51
@ Global
Save the entry to the global KDE config file instead of the application specific config file.
Definition: kconfigbase.h:42
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
bool send(const QDBusMessage &message) const const
QString findExecutable(const QString &executableName, const QStringList &paths)
void reserve(int alloc)
QMap::iterator insert(const Key &key, const T &value)
int size() const const
void prepend(const T &value)
void setReadDefaults(bool b)
defaults
Definition: kconfig.cpp:862
QDBusConnection sessionBus()
QString fromLocal8Bit(const char *str, int size)
QMap< QString, QString > entryMap(const QString &aGroup=QString()) const
Returns a map (tree) of entries in a particular group.
Definition: kconfig.cpp:381
QStringList groupList() const override
Definition: kconfig.cpp:306
bool isEmpty() const const
const Key key(const T &value) const const
int execute(const QString &program, const QStringList &arguments)
QByteArray toUtf8() const const
QCA_EXPORT QString appName()
int length() const const
const T & at(int i) const const
bool startsWith(const QByteArray &ba) const const
T readEntry(const QString &key, const T &aDefault) const
Reads the value of an entry specified by pKey in the current group.
Definition: kconfiggroup.h:258
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
KSharedConfigPtr config()
bool isAbsolutePath(const QString &path)
QString & replace(int position, int n, QChar after)
QString & remove(int position, int n)
LocaleWrapper locale()
bool isConfigWritable(bool warnUser)
Whether the configuration can be written to.
Definition: kconfig.cpp:949
QStandardPaths::StandardLocation locationType() const
Returns the standard location enum passed to the constructor.
Definition: kconfig.cpp:1047
bool isEmpty() const const
void setForceGlobal(bool force)
global
Definition: kconfig.cpp:887
bool isEmpty() const const
QList::const_iterator constEnd() const const
const char * constData() const const
AccessMode
Possible return values for accessMode().
Definition: kconfigbase.h:175
bool isEmpty() const const
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
const char * name(StandardAction id)
const QChar at(int position) const const
@ Localized
Add the locale tag to the key when writing it.
Definition: kconfigbase.h:47
bool isImmutable() const override
Definition: kconfig.cpp:874
KConfig * config()
Return the config object that this group belongs to.
int size() const const
KConfigGroup group(const QString &group)
Returns an object for the named subgroup.
Definition: kconfigbase.cpp:36
bool hasGroupImpl(const QByteArray &group) const override
Definition: kconfig.cpp:973
int compare(const QString &other, Qt::CaseSensitivity cs) const const
static QString mainConfigName()
Get the name of application config file.
Definition: kconfig.cpp:591
bool isGroupImmutableImpl(const QByteArray &aGroup) const override
Definition: kconfig.cpp:880
QString locale() const
locales
Definition: kconfig.cpp:837
QDateTime lastModified() const const
OpenFlags openFlags() const
Definition: kconfig.cpp:573
void checkUpdate(const QString &id, const QString &updateFile)
Ensures that the configuration file contains a certain update.
Definition: kconfig.cpp:537
@ SimpleConfig
Just a single config file.
Definition: kconfig.h:86
QStringList arguments()
QString message
KConfig(const QString &file=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
Creates a KConfig object to manipulate a configuration file for the current application.
Definition: kconfig.cpp:247
Q_D(Todo)
QString canonicalFilePath() const const
QString decodeName(const QByteArray &localFileName)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Dec 2 2023 04:11:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.