1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
4 SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
5 SPDX-FileCopyrightText: 2024 Harald Sitter <sitter@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "ksharedconfig.h"
11#include "kconfig_core_log_settings.h"
12#include "kconfig_p.h"
13#include "kconfiggroup.h"
14#include <QCoreApplication>
15#include <QThread>
16#include <QThreadStorage>
17
18using namespace Qt::StringLiterals;
19
20void _k_globalMainConfigSync();
21
22using SharedConfigList = QList<KSharedConfig *>;
23
24class GlobalSharedConfig
25{
26public:
27 GlobalSharedConfig()
28 : wasTestModeEnabled(false)
29 {
30 if (!qApp || QThread::currentThread() == qApp->thread()) {
31 // We want to force the sync() before the QCoreApplication
32 // instance is gone. Otherwise we trigger a QLockFile::lock()
33 // after QCoreApplication is gone, calling qAppName() for a non
34 // existent app...
35 qAddPostRoutine(&_k_globalMainConfigSync);
36 }
37 // In other threads, QThreadStorage takes care of deleting the GlobalSharedConfigList when
38 // the thread exits.
39 }
40
41 SharedConfigList configList;
42 // in addition to the list, we need to hold the main config,
43 // so that it's not created and destroyed all the time.
44 KSharedConfigPtr mainConfig;
45 bool wasTestModeEnabled;
46};
47
48static QThreadStorage<GlobalSharedConfig *> s_storage;
49template<typename T>
50T *perThreadGlobalStatic()
51{
52 if (!s_storage.hasLocalData()) {
53 s_storage.setLocalData(new T);
54 }
55 return s_storage.localData();
56}
57
58// Q_GLOBAL_STATIC(GlobalSharedConfigList, globalSharedConfigList), but per thread:
59static GlobalSharedConfig *globalSharedConfig()
60{
61 return perThreadGlobalStatic<GlobalSharedConfig>();
62}
63
64namespace
65{
66[[nodiscard]] QString migrateStateRc(const QString &fileName)
67{
68 // Migrate from an old legacy path to new spec compliant ~/.local/state/
69 // https://gitlab.freedesktop.org/xdg/xdg-specs/-/blob/master/basedir/basedir-spec.xml
70 // TODO KF7: refactor openStateConfig so it always opens from XDG_STATE_HOME instead of the legacy when on an XDG platform
71
72 if (QFileInfo(fileName).isAbsolute()) {
73 return fileName;
74 }
75
76 const QString xdgStateHome = QStandardPaths::writableLocation(type: QStandardPaths::GenericStateLocation);
77 if (fileName.startsWith(s: xdgStateHome)) [[unlikely]] {
78 return fileName;
79 }
80
81 QString newPath = xdgStateHome + '/'_L1 + fileName; // intentionally not const so it can be move returned
82 QString oldPath = QStandardPaths::locate(type: QStandardPaths::AppDataLocation, fileName);
83 if (oldPath.isEmpty()) { // nothing to migrate
84 return newPath;
85 }
86 if (QFile::exists(fileName: oldPath) && QFile::exists(fileName: newPath)) {
87 qCDebug(KCONFIG_CORE_LOG) << "Old staterc and new staterc found. Not migrating! Using new path" << newPath;
88 return newPath;
89 }
90
91 if (QFile::exists(fileName: newPath)) { // already migrated
92 return newPath;
93 }
94
95 // Migrate legacy files.
96 // On failure we return the new path because we want higher level technology to surface the new path for read/write errors.
97 if (!QDir().exists(name: xdgStateHome)) {
98 if (!QDir().mkpath(dirPath: xdgStateHome)) {
99 qCWarning(KCONFIG_CORE_LOG) << "Failed to make state directory" << xdgStateHome;
100 return newPath;
101 }
102 }
103 qCInfo(KCONFIG_CORE_LOG) << "Migrating old staterc" << oldPath << "->" << newPath;
104 if (!QFile::rename(oldName: oldPath, newName: newPath)) {
105 qCWarning(KCONFIG_CORE_LOG) << "Failed to migrate" << oldPath << "->" << newPath;
106 return newPath;
107 }
108
109 return newPath;
110}
111} // namespace
112
113void _k_globalMainConfigSync()
114{
115 if (KSharedConfigPtr mainConfig = globalSharedConfig()->mainConfig) {
116 mainConfig->sync();
117 }
118}
119
120KSharedConfigPtr KSharedConfig::openConfig(const QString &_fileName, OpenFlags flags, QStandardPaths::StandardLocation resType)
121{
122 QString fileName(_fileName);
123 GlobalSharedConfig *global = globalSharedConfig();
124 if (fileName.isEmpty() && !flags.testFlag(flag: KConfig::SimpleConfig)) {
125 // Determine the config file name that KConfig will make up (see KConfigPrivate::changeFileName)
126 fileName = KConfig::mainConfigName();
127 }
128
129 if (!global->wasTestModeEnabled && QStandardPaths::isTestModeEnabled()) {
130 global->wasTestModeEnabled = true;
131 global->configList.clear();
132 global->mainConfig = nullptr;
133 }
134
135 for (auto *cfg : std::as_const(t&: global->configList)) {
136 if (cfg->name() == fileName && cfg->d_ptr->openFlags == flags && cfg->locationType() == resType) {
137 return KSharedConfigPtr(cfg);
138 }
139 }
140
141 KSharedConfigPtr ptr(new KSharedConfig(fileName, flags, resType));
142
143 if (_fileName.isEmpty() && flags == FullConfig && resType == QStandardPaths::GenericConfigLocation) {
144 global->mainConfig = ptr;
145
146 const bool isMainThread = !qApp || QThread::currentThread() == qApp->thread();
147 static bool userWarned = false;
148 if (isMainThread && !userWarned) {
149 userWarned = true;
150 const bool isReadOnly = qEnvironmentVariableIsEmpty(varName: "KDE_HOME_READONLY");
151 if (isReadOnly && QCoreApplication::applicationName() != QLatin1String("kdialog")) {
152 if (ptr->group(QStringLiteral("General")).readEntry(QStringLiteral("warn_unwritable_config"), aDefault: true)) {
153 ptr->isConfigWritable(warnUser: true);
154 }
155 }
156 }
157 }
158
159 return ptr;
160}
161
162KSharedConfig::Ptr KSharedConfig::openStateConfig(const QString &_fileName)
163{
164 QString fileName(_fileName);
165
166 if (fileName.isEmpty()) {
167 fileName = QCoreApplication::applicationName() + QLatin1String("staterc");
168 }
169
170 return openConfig(fileName: migrateStateRc(fileName), flags: SimpleConfig, resType: QStandardPaths::GenericStateLocation);
171}
172
173KSharedConfig::KSharedConfig(const QString &fileName, OpenFlags flags, QStandardPaths::StandardLocation resType)
174 : KConfig(fileName, flags, resType)
175{
176 globalSharedConfig()->configList.append(t: this);
177}
178
179KSharedConfig::~KSharedConfig()
180{
181 if (s_storage.hasLocalData()) {
182 globalSharedConfig()->configList.removeAll(t: this);
183 }
184}
185
186KConfigGroup KSharedConfig::groupImpl(const QString &groupName)
187{
188 KSharedConfigPtr ptr(this);
189 return KConfigGroup(ptr, groupName);
190}
191
192const KConfigGroup KSharedConfig::groupImpl(const QString &groupName) const
193{
194 const KSharedConfigPtr ptr(const_cast<KSharedConfig *>(this));
195 return KConfigGroup(ptr, groupName);
196}
197

source code of kconfig/src/core/ksharedconfig.cpp