Coverage Report

Created: 2026-04-29 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kconfig/src/core/ksharedconfig.cpp
Line
Count
Source
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
18
using namespace Qt::StringLiterals;
19
20
void _k_globalMainConfigSync();
21
22
using SharedConfigList = QList<KSharedConfig *>;
23
24
class GlobalSharedConfig
25
{
26
public:
27
    GlobalSharedConfig()
28
113k
        : wasTestModeEnabled(false)
29
113k
    {
30
113k
        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
113k
            qAddPostRoutine(&_k_globalMainConfigSync);
36
113k
        }
37
        // In other threads, QThreadStorage takes care of deleting the GlobalSharedConfigList when
38
        // the thread exits.
39
113k
    }
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
48
static QThreadStorage<GlobalSharedConfig *> s_storage;
49
template<typename T>
50
T *perThreadGlobalStatic()
51
355k
{
52
355k
    if (!s_storage.hasLocalData()) {
53
113k
        s_storage.setLocalData(new T);
54
113k
    }
55
355k
    return s_storage.localData();
56
355k
}
57
58
// Q_GLOBAL_STATIC(GlobalSharedConfigList, globalSharedConfigList), but per thread:
59
static GlobalSharedConfig *globalSharedConfig()
60
355k
{
61
355k
    return perThreadGlobalStatic<GlobalSharedConfig>();
62
355k
}
63
64
namespace
65
{
66
[[nodiscard]] QString migrateStateRc(const QString &fileName)
67
0
{
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
0
    if (QFileInfo(fileName).isAbsolute()) {
73
0
        return fileName;
74
0
    }
75
76
0
    const QString xdgStateHome = QStandardPaths::writableLocation(QStandardPaths::GenericStateLocation);
77
0
    if (fileName.startsWith(xdgStateHome)) [[unlikely]] {
78
0
        return fileName;
79
0
    }
80
81
0
    QString newPath = xdgStateHome + '/'_L1 + fileName; // intentionally not const so it can be move returned
82
0
    QString oldPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName);
83
0
    if (oldPath.isEmpty()) { // nothing to migrate
84
0
        return newPath;
85
0
    }
86
0
    if (QFile::exists(oldPath) && QFile::exists(newPath)) {
87
0
        qCDebug(KCONFIG_CORE_LOG) << "Old staterc and new staterc found. Not migrating! Using new path" << newPath;
88
0
        return newPath;
89
0
    }
90
91
0
    if (QFile::exists(newPath)) { // already migrated
92
0
        return newPath;
93
0
    }
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
0
    if (!QDir().exists(xdgStateHome)) {
98
0
        if (!QDir().mkpath(xdgStateHome)) {
99
0
            qCWarning(KCONFIG_CORE_LOG) << "Failed to make state directory" << xdgStateHome;
100
0
            return newPath;
101
0
        }
102
0
    }
103
0
    qCInfo(KCONFIG_CORE_LOG) << "Migrating old staterc" << oldPath << "->" << newPath;
104
0
    if (!QFile::rename(oldPath, newPath)) {
105
0
        qCWarning(KCONFIG_CORE_LOG) << "Failed to migrate" << oldPath << "->" << newPath;
106
0
        return newPath;
107
0
    }
108
109
0
    return newPath;
110
0
}
111
} // namespace
112
113
void _k_globalMainConfigSync()
114
113k
{
115
113k
    if (KSharedConfigPtr mainConfig = globalSharedConfig()->mainConfig) {
116
113k
        mainConfig->sync();
117
113k
    }
118
113k
}
119
120
KSharedConfigPtr KSharedConfig::openConfig(const QString &_fileName, OpenFlags flags, QStandardPaths::StandardLocation resType)
121
127k
{
122
127k
    QString fileName(_fileName);
123
127k
    GlobalSharedConfig *global = globalSharedConfig();
124
127k
    if (fileName.isEmpty() && !flags.testFlag(KConfig::SimpleConfig)) {
125
        // Determine the config file name that KConfig will make up (see KConfigPrivate::changeFileName)
126
127k
        fileName = KConfig::mainConfigName();
127
127k
    }
128
129
127k
    if (!global->wasTestModeEnabled && QStandardPaths::isTestModeEnabled()) {
130
0
        global->wasTestModeEnabled = true;
131
0
        global->configList.clear();
132
0
        global->mainConfig = nullptr;
133
0
    }
134
135
127k
    for (auto *cfg : std::as_const(global->configList)) {
136
13.9k
        if (cfg->name() == fileName && cfg->d_ptr->openFlags == flags && cfg->locationType() == resType) {
137
13.9k
            return KSharedConfigPtr(cfg);
138
13.9k
        }
139
13.9k
    }
140
141
113k
    KSharedConfigPtr ptr(new KSharedConfig(fileName, flags, resType));
142
143
113k
    if (_fileName.isEmpty() && flags == FullConfig && resType == QStandardPaths::GenericConfigLocation) {
144
113k
        global->mainConfig = ptr;
145
146
113k
        const bool isMainThread = !qApp || QThread::currentThread() == qApp->thread();
147
113k
        static bool userWarned = false;
148
113k
        if (isMainThread && !userWarned) {
149
14
            userWarned = true;
150
14
            const bool isReadOnly = qEnvironmentVariableIsEmpty("KDE_HOME_READONLY");
151
14
            if (isReadOnly && QCoreApplication::applicationName() != QLatin1String("kdialog")) {
152
14
                if (ptr->group(QStringLiteral("General")).readEntry(QStringLiteral("warn_unwritable_config"), true)) {
153
14
                    ptr->isConfigWritable(true);
154
14
                }
155
14
            }
156
14
        }
157
113k
    }
158
159
113k
    return ptr;
160
127k
}
161
162
KSharedConfig::Ptr KSharedConfig::openStateConfig(const QString &_fileName)
163
0
{
164
0
    QString fileName(_fileName);
165
166
0
    if (fileName.isEmpty()) {
167
0
        fileName = QCoreApplication::applicationName() + QLatin1String("staterc");
168
0
    }
169
170
0
    return openConfig(migrateStateRc(fileName), SimpleConfig, QStandardPaths::GenericStateLocation);
171
0
}
172
173
KSharedConfig::KSharedConfig(const QString &fileName, OpenFlags flags, QStandardPaths::StandardLocation resType)
174
113k
    : KConfig(fileName, flags, resType)
175
113k
{
176
113k
    globalSharedConfig()->configList.append(this);
177
113k
}
178
179
KSharedConfig::~KSharedConfig()
180
113k
{
181
113k
    if (s_storage.hasLocalData()) {
182
0
        globalSharedConfig()->configList.removeAll(this);
183
0
    }
184
113k
}
185
186
KConfigGroup KSharedConfig::groupImpl(const QString &groupName)
187
14
{
188
14
    KSharedConfigPtr ptr(this);
189
14
    return KConfigGroup(ptr, groupName);
190
14
}
191
192
const KConfigGroup KSharedConfig::groupImpl(const QString &groupName) const
193
0
{
194
0
    const KSharedConfigPtr ptr(const_cast<KSharedConfig *>(this));
195
0
    return KConfigGroup(ptr, groupName);
196
0
}