Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kconfig/src/core/kconfig.cpp
Line
Count
Source
1
/*
2
    This file is part of the KDE libraries
3
    SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
4
    SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
5
    SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <kalle@kde.org>
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 <fcntl.h>
18
19
#include "kconfiggroup.h"
20
21
#include <QBasicMutex>
22
#include <QByteArray>
23
#include <QCache>
24
#include <QCoreApplication>
25
#include <QDir>
26
#include <QFile>
27
#include <QLocale>
28
#include <QMutexLocker>
29
#include <QProcess>
30
#include <QSet>
31
#include <QThreadStorage>
32
#include <QTimeZone>
33
34
#include <algorithm>
35
#include <iterator>
36
#include <set>
37
38
#if KCONFIG_USE_DBUS
39
#include <QDBusConnection>
40
#include <QDBusMessage>
41
#include <QDBusMetaType>
42
#endif
43
44
#ifdef Q_OS_WIN
45
#include "registry_win_p.h"
46
#endif
47
48
bool KConfigPrivate::mappingsRegistered = false;
49
50
// For caching purposes
51
static bool s_wasTestModeEnabled = false;
52
53
Q_GLOBAL_STATIC(QStringList, s_globalFiles) // For caching purposes.
54
static QBasicMutex s_globalFilesMutex;
55
Q_GLOBAL_STATIC_WITH_ARGS(QString, sGlobalFileName, (QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals")))
56
57
using ParseCacheKey = std::pair<QStringList, QString>;
58
using ParseCacheTimestamp = QList<qint64>;
59
struct ParseCacheValue {
60
    KEntryMap entries;
61
    ParseCacheTimestamp timestamp;
62
};
63
QThreadStorage<QCache<ParseCacheKey, ParseCacheValue>> sGlobalParse;
64
65
#ifndef Q_OS_WIN
66
static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseSensitive;
67
#else
68
static const Qt::CaseSensitivity sPathCaseSensitivity = Qt::CaseInsensitive;
69
#endif
70
71
static QString getDefaultLocaleName()
72
121k
{
73
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
74
    if (QLocale() == QLocale::system()) {
75
        // If the default locale hasn't been changed then
76
        // On Windows and Apple OSs, we cannot use QLocale::system() if an application-specific
77
        // language was set by kxmlgui because Qt ignores LANGUAGE on Windows and Apple OSs.
78
        if (const auto firstLanguage = qEnvironmentVariable("LANGUAGE").section(u':', 0, 0, QString::SectionSkipEmpty); !firstLanguage.isEmpty()) {
79
            return firstLanguage;
80
        }
81
        // Also prefer the configured display language over the system language
82
        if (const auto languages = QLocale::system().uiLanguages(); !languages.isEmpty()) {
83
            // uiLanguages() uses dashes as separator, but KConfig assumes underscores
84
            return languages.value(0).replace(u'-', u'_');
85
        }
86
    }
87
#endif
88
121k
    return QLocale().name();
89
121k
}
90
91
KConfigPrivate::KConfigPrivate(KConfig::OpenFlags flags,
92
                               QStandardPaths::StandardLocation resourceType,
93
                               std::unique_ptr<KConfigIniBackendAbstractDevice> backend)
94
121k
    : openFlags(flags)
95
121k
    , resourceType(resourceType)
96
121k
    , mBackend(std::move(backend))
97
121k
    , bDirty(false)
98
121k
    , bReadDefaults(false)
99
121k
    , bFileImmutable(false)
100
121k
    , bForceGlobal(false)
101
121k
    , bSuppressGlobal(false)
102
121k
    , configState(KConfigBase::NoAccess)
103
121k
{
104
121k
    const bool isTestMode = QStandardPaths::isTestModeEnabled();
105
    // If sGlobalFileName was initialised and testMode has been toggled,
106
    // sGlobalFileName may need to be updated to point to the correct kdeglobals file
107
121k
    if (sGlobalFileName.exists() && s_wasTestModeEnabled != isTestMode) {
108
0
        s_wasTestModeEnabled = isTestMode;
109
0
        *sGlobalFileName = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdeglobals");
110
0
    }
111
112
121k
    static QBasicAtomicInt use_etc_kderc = Q_BASIC_ATOMIC_INITIALIZER(-1);
113
121k
    if (use_etc_kderc.loadRelaxed() < 0) {
114
15
        use_etc_kderc.storeRelaxed(!qEnvironmentVariableIsSet("KDE_SKIP_KDERC")); // for unit tests
115
15
    }
116
121k
    if (use_etc_kderc.loadRelaxed()) {
117
15
        etc_kderc =
118
#ifdef Q_OS_WIN
119
            QFile::decodeName(qgetenv("WINDIR") + "/kde5rc");
120
#else
121
15
            QStringLiteral("/etc/kde5rc");
122
15
#endif
123
15
        if (!QFileInfo(etc_kderc).isReadable()) {
124
15
            use_etc_kderc.storeRelaxed(false);
125
15
            etc_kderc.clear();
126
15
        }
127
15
    }
128
129
121k
    setLocale(getDefaultLocaleName());
130
121k
}
131
132
bool KConfigPrivate::lockLocal()
133
1
{
134
1
    return mBackend.lock();
135
1
}
136
137
static bool isGroupOrSubGroupMatch(KEntryMapConstIterator entryMapIt, const QString &group)
138
0
{
139
0
    const QString &entryGroup = entryMapIt->first.mGroup;
140
0
    Q_ASSERT_X(entryGroup.startsWith(group), Q_FUNC_INFO, "Precondition");
141
0
    return entryGroup.size() == group.size() || entryGroup[group.size()] == QLatin1Char('\x1d');
142
0
}
143
144
void KConfigPrivate::copyGroup(const QString &source, const QString &destination, KConfigGroup *otherGroup, KConfigBase::WriteConfigFlags flags) const
145
0
{
146
0
    KEntryMap &otherMap = otherGroup->config()->d_ptr->entryMap;
147
0
    const bool sameName = (destination == source);
148
149
    // we keep this bool outside the for loop so that if
150
    // the group is empty, we don't end up marking the other config
151
    // as dirty erroneously
152
0
    bool dirtied = false;
153
154
0
    entryMap.forEachEntryWhoseGroupStartsWith(source, [&source, &destination, flags, &otherMap, sameName, &dirtied](KEntryMapConstIterator entryMapIt) {
155
        // don't copy groups that start with the same prefix, but are not sub-groups
156
0
        if (!isGroupOrSubGroupMatch(entryMapIt, source)) {
157
0
            return;
158
0
        }
159
160
0
        KEntryKey newKey = entryMapIt->first;
161
162
0
        if (flags & KConfigBase::Localized) {
163
0
            newKey.bLocal = true;
164
0
        }
165
166
0
        if (!sameName) {
167
0
            newKey.mGroup.replace(0, source.size(), destination);
168
0
        }
169
170
0
        KEntry entry = entryMapIt->second;
171
0
        dirtied = entry.bDirty = flags & KConfigBase::Persistent;
172
173
0
        if (flags & KConfigBase::Global) {
174
0
            entry.bGlobal = true;
175
0
        }
176
177
0
        if (flags & KConfigBase::Notify) {
178
0
            entry.bNotify = true;
179
0
        }
180
181
0
        otherMap[newKey] = entry;
182
0
    });
183
184
0
    if (dirtied) {
185
0
        otherGroup->config()->d_ptr->bDirty = true;
186
0
    }
187
0
}
188
189
QString KConfigPrivate::expandString(const QString &value)
190
0
{
191
0
    QString aValue = value;
192
193
    // check for environment variables and make necessary translations
194
0
    int nDollarPos = aValue.indexOf(QLatin1Char('$'));
195
0
    while (nDollarPos != -1 && nDollarPos + 1 < aValue.length()) {
196
        // there is at least one $
197
0
        if (aValue.at(nDollarPos + 1) != QLatin1Char('$')) {
198
0
            int nEndPos = nDollarPos + 1;
199
            // the next character is not $
200
0
            QStringView aVarName;
201
0
            if (aValue.at(nEndPos) == QLatin1Char('{')) {
202
0
                while ((nEndPos < aValue.length()) && (aValue[nEndPos] != QLatin1Char('}'))) {
203
0
                    ++nEndPos;
204
0
                }
205
0
                ++nEndPos;
206
0
                aVarName = QStringView(aValue).mid(nDollarPos + 2, nEndPos - nDollarPos - 3);
207
0
            } else {
208
0
                while (nEndPos < aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos] == QLatin1Char('_'))) {
209
0
                    ++nEndPos;
210
0
                }
211
0
                aVarName = QStringView(aValue).mid(nDollarPos + 1, nEndPos - nDollarPos - 1);
212
0
            }
213
0
            QString env;
214
0
            if (!aVarName.isEmpty()) {
215
#ifdef Q_OS_WIN
216
                if (aVarName == QLatin1String("HOME")) {
217
                    env = QDir::homePath();
218
                } else
219
#endif
220
0
                {
221
0
                    QByteArray pEnv = qgetenv(aVarName.toLatin1().constData());
222
0
                    if (!pEnv.isEmpty()) {
223
0
                        env = QString::fromLocal8Bit(pEnv.constData());
224
0
                    } else {
225
0
                        if (aVarName == QLatin1String("QT_DATA_HOME")) {
226
0
                            env = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
227
0
                        } else if (aVarName == QLatin1String("QT_CONFIG_HOME")) {
228
0
                            env = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
229
0
                        } else if (aVarName == QLatin1String("QT_CACHE_HOME")) {
230
0
                            env = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
231
0
                        }
232
0
                    }
233
0
                }
234
0
                aValue.replace(nDollarPos, nEndPos - nDollarPos, env);
235
0
                nDollarPos += env.length();
236
0
            } else {
237
0
                aValue.remove(nDollarPos, nEndPos - nDollarPos);
238
0
            }
239
0
        } else {
240
            // remove one of the dollar signs
241
0
            aValue.remove(nDollarPos, 1);
242
0
            ++nDollarPos;
243
0
        }
244
0
        nDollarPos = aValue.indexOf(QLatin1Char('$'), nDollarPos);
245
0
    }
246
247
0
    return aValue;
248
0
}
249
250
KConfig::KConfig(const QString &file, OpenFlags mode, QStandardPaths::StandardLocation resourceType)
251
121k
    : d_ptr(new KConfigPrivate(mode, resourceType))
252
121k
{
253
121k
    d_ptr->changeFileName(file); // set the local file name
254
255
    // read initial information off disk
256
121k
    reparseConfiguration();
257
121k
}
258
259
KConfig::KConfig(const std::shared_ptr<QIODevice> &device, OpenFlags mode)
260
0
    : d_ptr(new KConfigPrivate(mode,
261
0
                               QStandardPaths::StandardLocation::GenericConfigLocation // a default location type
262
0
                               ,
263
0
                               std::make_unique<KConfigIniBackendQIODevice>(device)))
264
0
{
265
0
    d_ptr->configState = d_ptr->mBackend.accessMode();
266
267
    // read initial information off device
268
0
    reparseConfiguration();
269
0
}
270
271
#if KCONFIGCORE_BUILD_DEPRECATED_SINCE(6, 3)
272
KConfig::KConfig(const QString &file, const QString &backend, QStandardPaths::StandardLocation resourceType)
273
0
    : d_ptr(new KConfigPrivate(SimpleConfig, resourceType))
274
0
{
275
0
    Q_UNUSED(backend);
276
0
    d_ptr->changeFileName(file); // set the local file name
277
278
    // read initial information off disk
279
0
    reparseConfiguration();
280
0
}
281
#endif
282
283
KConfig::KConfig(KConfigPrivate &d)
284
0
    : d_ptr(&d)
285
0
{
286
0
}
287
288
KConfig::~KConfig()
289
121k
{
290
121k
    Q_D(KConfig);
291
121k
    if (d->bDirty) {
292
0
        sync();
293
0
    }
294
121k
    delete d;
295
121k
}
296
297
static bool isNonDeletedKey(KEntryMapConstIterator entryMapIt)
298
0
{
299
0
    return !entryMapIt->first.mKey.isNull() && !entryMapIt->second.bDeleted;
300
0
}
301
// is a key without default values and not deleted
302
static bool isSetKey(KEntryMapConstIterator entryMapIt)
303
0
{
304
0
    return !entryMapIt->first.bDefault && !entryMapIt->second.bDeleted;
305
0
}
306
307
static int findFirstGroupEndPos(const QString &groupFullName, int from = 0)
308
0
{
309
0
    const auto index = groupFullName.indexOf(QLatin1Char('\x1d'), from);
310
0
    return index == -1 ? groupFullName.size() : index;
311
0
}
312
313
static QStringList stringListFromStringViewCollection(const QSet<QStringView> &source)
314
0
{
315
0
    QStringList list;
316
0
    list.reserve(source.size());
317
0
    std::transform(source.cbegin(), source.cend(), std::back_inserter(list), [](QStringView view) {
318
0
        return view.toString();
319
0
    });
320
0
    return list;
321
0
}
322
323
QStringList KConfig::groupList() const
324
0
{
325
0
    Q_D(const KConfig);
326
0
    QSet<QStringView> groups;
327
328
0
    for (auto entryMapIt = d->entryMap.cbegin(); entryMapIt != d->entryMap.cend(); ++entryMapIt) {
329
0
        const QString &group = entryMapIt->first.mGroup;
330
0
        if (isNonDeletedKey(entryMapIt) && !group.isEmpty() && group != QStringLiteral("<default>") && group != QStringLiteral("$Version")) {
331
0
            groups.insert(QStringView(group).left(findFirstGroupEndPos(group)));
332
0
        }
333
0
    }
334
335
0
    return stringListFromStringViewCollection(groups);
336
0
}
337
338
QStringList KConfigPrivate::groupList(const QString &groupName) const
339
0
{
340
0
    const QString theGroup = groupName + QLatin1Char('\x1d');
341
0
    QSet<QStringView> groups;
342
343
0
    entryMap.forEachEntryWhoseGroupStartsWith(theGroup, [&theGroup, &groups](KEntryMapConstIterator entryMapIt) {
344
0
        if (isNonDeletedKey(entryMapIt)) {
345
0
            const QString &entryGroup = entryMapIt->first.mGroup;
346
0
            const auto subgroupStartPos = theGroup.size();
347
0
            const auto subgroupEndPos = findFirstGroupEndPos(entryGroup, subgroupStartPos);
348
0
            groups.insert(QStringView(entryGroup).mid(subgroupStartPos, subgroupEndPos - subgroupStartPos));
349
0
        }
350
0
    });
351
352
0
    return stringListFromStringViewCollection(groups);
353
0
}
354
355
/// Returns @p parentGroup itself, all its subgroups, subsubgroups, and so on, including deleted groups.
356
QSet<QString> KConfigPrivate::allSubGroups(const QString &parentGroup) const
357
0
{
358
0
    QSet<QString> groups;
359
360
0
    entryMap.forEachEntryWhoseGroupStartsWith(parentGroup, [&parentGroup, &groups](KEntryMapConstIterator entryMapIt) {
361
0
        const KEntryKey &key = entryMapIt->first;
362
0
        if (key.mKey.isNull() && isGroupOrSubGroupMatch(entryMapIt, parentGroup)) {
363
0
            groups << key.mGroup;
364
0
        }
365
0
    });
366
367
0
    return groups;
368
0
}
369
370
bool KConfigPrivate::hasNonDeletedEntries(const QString &group) const
371
0
{
372
0
    return entryMap.anyEntryWhoseGroupStartsWith(group, [&group](KEntryMapConstIterator entryMapIt) {
373
0
        return isGroupOrSubGroupMatch(entryMapIt, group) && isNonDeletedKey(entryMapIt);
374
0
    });
375
0
}
376
377
QList<QByteArray> KConfigPrivate::keyListImpl(const QString &theGroup) const
378
0
{
379
0
    std::set<QByteArray> tmp; // unique set, sorted for unittests
380
381
0
    entryMap.forEachEntryOfGroup(theGroup, [&tmp](KEntryMapConstIterator it) {
382
0
        if (isNonDeletedKey(it)) {
383
0
            tmp.insert(it->first.mKey);
384
0
        }
385
0
    });
386
387
0
    return QList<QByteArray>(tmp.begin(), tmp.end());
388
0
}
389
390
QStringList KConfigPrivate::usedKeyList(const QString &theGroup) const
391
0
{
392
0
    std::set<QString> tmp; // unique set, sorting as side-effect
393
394
0
    entryMap.forEachEntryOfGroup(theGroup, [&tmp](KEntryMapConstIterator it) {
395
        // leave the default values and deleted entries out, same as KConfig::entryMap()
396
0
        if (isSetKey(it)) {
397
0
            const QString key = QString::fromUtf8(it->first.mKey);
398
0
            tmp.insert(key);
399
0
        }
400
0
    });
401
402
0
    return QStringList(tmp.begin(), tmp.end());
403
0
}
404
405
QMap<QString, QString> KConfig::entryMap(const QString &aGroup) const
406
0
{
407
0
    Q_D(const KConfig);
408
0
    QMap<QString, QString> theMap;
409
0
    const QString theGroup = aGroup.isEmpty() ? QStringLiteral("<default>") : aGroup;
410
411
0
    d->entryMap.forEachEntryOfGroup(theGroup, [&theMap](KEntryMapConstIterator it) {
412
        // leave the default values and deleted entries out
413
0
        if (isSetKey(it)) {
414
0
            const QString key = QString::fromUtf8(it->first.mKey.constData());
415
            // the localized entry should come first, so don't overwrite it
416
            // with the non-localized entry
417
0
            if (!theMap.contains(key)) {
418
0
                if (it->second.bExpand) {
419
0
                    theMap.insert(key, KConfigPrivate::expandString(QString::fromUtf8(it->second.mValue.constData())));
420
0
                } else {
421
0
                    theMap.insert(key, QString::fromUtf8(it->second.mValue.constData()));
422
0
                }
423
0
            }
424
0
        }
425
0
    });
426
427
0
    return theMap;
428
0
}
429
430
bool KConfig::sync()
431
121k
{
432
121k
    Q_D(KConfig);
433
434
121k
    if (isImmutable() || !d->mBackend.isWritable()) {
435
        // can't write to an immutable or anonymous file.
436
0
        return false;
437
0
    }
438
439
121k
    QHash<QString, QByteArrayList> notifyGroupsLocal;
440
121k
    QHash<QString, QByteArrayList> notifyGroupsGlobal;
441
442
121k
    if (d->bDirty) {
443
1
        const QByteArray utf8Locale(locale().toUtf8());
444
445
        // Create the containing dir, maybe it wasn't there
446
1
        d->mBackend.createEnclosing();
447
448
        // lock the local file
449
1
        if (d->configState == ReadWrite && !d->lockLocal()) {
450
0
            qCWarning(KCONFIG_CORE_LOG) << "Couldn't lock local file:" << d->mBackend.backingDevicePath();
451
0
            return false;
452
0
        }
453
454
        // Rewrite global/local config only if there is a dirty entry in it.
455
1
        bool writeGlobals = false;
456
1
        bool writeLocals = false;
457
458
2
        for (const auto &[key, e] : d->entryMap) {
459
2
            if (e.bDirty) {
460
1
                if (e.bGlobal) {
461
0
                    writeGlobals = true;
462
0
                    if (e.bNotify) {
463
0
                        notifyGroupsGlobal[key.mGroup] << key.mKey;
464
0
                    }
465
1
                } else {
466
1
                    writeLocals = true;
467
1
                    if (e.bNotify) {
468
0
                        notifyGroupsLocal[key.mGroup] << key.mKey;
469
0
                    }
470
1
                }
471
1
            }
472
2
        }
473
474
1
        d->bDirty = false; // will revert to true if a config write fails
475
476
1
        if (d->wantGlobals() && writeGlobals) {
477
0
            KConfigIniBackend tmp(std::make_unique<KConfigIniBackendPathDevice>(*sGlobalFileName));
478
0
            if (d->configState == ReadWrite && !tmp.lock()) {
479
0
                qCWarning(KCONFIG_CORE_LOG) << "Couldn't lock global file:" << d->mBackend.backingDevicePath();
480
481
                // unlock the local config if we're returning early
482
0
                if (d->mBackend.isLocked()) {
483
0
                    d->mBackend.unlock();
484
0
                }
485
486
0
                d->bDirty = true;
487
0
                return false;
488
0
            }
489
0
            if (!tmp.writeConfig(utf8Locale, d->entryMap, KConfigIniBackend::WriteGlobal)) {
490
0
                d->bDirty = true;
491
0
            }
492
0
            if (tmp.isLocked()) {
493
0
                tmp.unlock();
494
0
            }
495
0
        }
496
497
1
        if (writeLocals) {
498
1
            if (!d->mBackend.writeConfig(utf8Locale, d->entryMap, KConfigIniBackend::WriteOptions())) {
499
0
                qCWarning(KCONFIG_CORE_LOG) << "Couldn't write to config:" << d->mBackend.backingDevicePath();
500
0
                d->bDirty = true;
501
0
            }
502
1
        }
503
1
        if (d->mBackend.isLocked()) {
504
1
            d->mBackend.unlock();
505
1
        }
506
1
    }
507
508
    // Notifying absolute paths is not supported and also makes no sense.
509
121k
    const bool isAbsolutePath = !name().isEmpty() && name().at(0) == QLatin1Char('/');
510
121k
    if (!notifyGroupsLocal.isEmpty() && !isAbsolutePath) {
511
0
        d->notifyClients(notifyGroupsLocal, kconfigDBusSanitizePath(QLatin1Char('/') + name()));
512
0
    }
513
121k
    if (!notifyGroupsGlobal.isEmpty()) {
514
0
        d->notifyClients(notifyGroupsGlobal, QStringLiteral("/kdeglobals"));
515
0
    }
516
517
121k
    return !d->bDirty;
518
121k
}
519
520
void KConfigPrivate::notifyClients(const QHash<QString, QByteArrayList> &changes, const QString &path)
521
0
{
522
#if KCONFIG_USE_DBUS
523
    qDBusRegisterMetaType<QByteArrayList>();
524
525
    qDBusRegisterMetaType<QHash<QString, QByteArrayList>>();
526
527
    QDBusMessage message = QDBusMessage::createSignal(path, QStringLiteral("org.kde.kconfig.notify"), QStringLiteral("ConfigChanged"));
528
    message.setArguments({QVariant::fromValue(changes)});
529
    QDBusConnection::sessionBus().send(message);
530
#else
531
0
    Q_UNUSED(changes)
532
0
    Q_UNUSED(path)
533
0
#endif
534
0
}
535
536
void KConfig::markAsClean()
537
0
{
538
0
    Q_D(KConfig);
539
0
    d->bDirty = false;
540
541
    // clear any dirty flags that entries might have set
542
0
    for (auto &[_, entry] : d->entryMap) {
543
0
        entry.bDirty = false;
544
0
        entry.bNotify = false;
545
0
    }
546
0
}
547
548
bool KConfig::isDirty() const
549
0
{
550
0
    Q_D(const KConfig);
551
0
    return d->bDirty;
552
0
}
553
554
void KConfig::checkUpdate(const QString &id, const QString &updateFile)
555
0
{
556
0
    const KConfigGroup cg(this, QStringLiteral("$Version"));
557
0
    const QString cfg_id = updateFile + QLatin1Char(':') + id;
558
0
    const QStringList ids = cg.readEntry("update_info", QStringList());
559
0
    if (!ids.contains(cfg_id)) {
560
0
        QProcess::execute(QStringLiteral(KCONF_UPDATE_INSTALL_LOCATION), QStringList{QStringLiteral("--check"), updateFile});
561
0
        reparseConfiguration();
562
0
    }
563
0
}
564
565
KConfig *KConfig::copyTo(const QString &file, KConfig *config) const
566
0
{
567
0
    Q_D(const KConfig);
568
0
    if (!config) {
569
0
        config = new KConfig(QString(), SimpleConfig, d->resourceType);
570
0
    }
571
0
    config->d_func()->changeFileName(file);
572
0
    config->d_func()->entryMap = d->entryMap;
573
0
    config->d_func()->bFileImmutable = false;
574
575
0
    for (auto &[_, entry] : config->d_func()->entryMap) {
576
0
        entry.bDirty = true;
577
0
    }
578
0
    config->d_ptr->bDirty = true;
579
580
0
    return config;
581
0
}
582
583
void KConfig::copyFrom(const KConfig &config) const
584
0
{
585
0
    Q_D(const KConfig);
586
0
    d_ptr->entryMap = config.d_func()->entryMap;
587
0
    d_ptr->bFileImmutable = false;
588
589
0
    for (auto &[_, entry] : d_ptr->entryMap) {
590
0
        entry.bDirty = true;
591
0
    }
592
0
    d_ptr->bDirty = true;
593
0
}
594
595
// TODO KF7 remove, expose QIODevice instead
596
// have a separate static funtion for relative filename config files
597
QString KConfig::name() const
598
432k
{
599
432k
    Q_D(const KConfig);
600
432k
    return d->fileName;
601
432k
}
602
603
KConfig::OpenFlags KConfig::openFlags() const
604
0
{
605
0
    Q_D(const KConfig);
606
0
    return d->openFlags;
607
0
}
608
609
struct KConfigStaticData {
610
    QString globalMainConfigName;
611
    // Keep a copy so we can use it in global dtors, after qApp is gone
612
    QStringList appArgs;
613
};
614
Q_GLOBAL_STATIC(KConfigStaticData, globalData)
615
static QBasicMutex s_globalDataMutex;
616
617
void KConfig::setMainConfigName(const QString &str)
618
0
{
619
0
    QMutexLocker locker(&s_globalDataMutex);
620
0
    globalData()->globalMainConfigName = str;
621
0
}
622
623
QString KConfig::mainConfigName()
624
135k
{
625
135k
    QMutexLocker locker(&s_globalDataMutex);
626
135k
    KConfigStaticData *data = globalData();
627
135k
    if (data->appArgs.isEmpty()) {
628
135k
        data->appArgs = QCoreApplication::arguments();
629
135k
    }
630
631
    // --config on the command line overrides everything else
632
135k
    const QStringList args = data->appArgs;
633
135k
    for (int i = 1; i < args.count(); ++i) {
634
0
        if (args.at(i) == QLatin1String("--config") && i < args.count() - 1) {
635
0
            return args.at(i + 1);
636
0
        }
637
0
    }
638
135k
    const QString globalName = data->globalMainConfigName;
639
135k
    if (!globalName.isEmpty()) {
640
0
        return globalName;
641
0
    }
642
643
135k
    QString appName = QCoreApplication::applicationName();
644
135k
    return appName + QLatin1String("rc");
645
135k
}
646
647
void KConfigPrivate::changeFileName(const QString &name)
648
121k
{
649
121k
    fileName = name;
650
651
121k
    QString file;
652
121k
    if (name.isEmpty()) {
653
0
        if (wantDefaults()) { // accessing default app-specific config "appnamerc"
654
0
            fileName = KConfig::mainConfigName();
655
0
            file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
656
0
        } else if (wantGlobals()) { // accessing "kdeglobals" by specifying no filename and NoCascade - XXX used anywhere?
657
0
            resourceType = QStandardPaths::GenericConfigLocation;
658
0
            fileName = QStringLiteral("kdeglobals");
659
0
            file = *sGlobalFileName;
660
0
        } else {
661
            // anonymous config
662
0
            openFlags = KConfig::SimpleConfig;
663
0
            return;
664
0
        }
665
121k
    } else if (QDir::isAbsolutePath(fileName)) {
666
0
        fileName = QFileInfo(fileName).canonicalFilePath();
667
0
        if (fileName.isEmpty()) { // file doesn't exist (yet)
668
0
            fileName = name;
669
0
        }
670
0
        file = fileName;
671
121k
    } else {
672
121k
        file = QStandardPaths::writableLocation(resourceType) + QLatin1Char('/') + fileName;
673
121k
    }
674
675
121k
    Q_ASSERT(!file.isEmpty());
676
677
121k
    bSuppressGlobal = (file.compare(*sGlobalFileName, sPathCaseSensitivity) == 0);
678
679
121k
    mBackend.setDeviceInterface(std::make_unique<KConfigIniBackendPathDevice>(file));
680
681
121k
    configState = mBackend.accessMode();
682
121k
}
683
684
void KConfig::reparseConfiguration()
685
160k
{
686
160k
    Q_D(KConfig);
687
160k
    if (!d->mBackend.hasOpenableDeviceInterface()) {
688
0
        return;
689
0
    }
690
691
    // Don't lose pending changes
692
160k
    if (!d->isReadOnly() && d->bDirty) {
693
0
        sync();
694
0
    }
695
696
160k
    d->entryMap.clear();
697
698
160k
    d->bFileImmutable = false;
699
700
160k
    {
701
160k
        QMutexLocker locker(&s_globalFilesMutex);
702
160k
        s_globalFiles()->clear();
703
160k
    }
704
705
    // Parse all desired files from the least to the most specific.
706
160k
    if (d->wantGlobals()) {
707
160k
        d->parseGlobalFiles();
708
160k
    }
709
710
#ifdef Q_OS_WIN
711
    // Parse the windows registry defaults if desired
712
    if (d->openFlags & ~KConfig::SimpleConfig) {
713
        d->parseWindowsDefaults();
714
    }
715
#endif
716
717
160k
    d->parseConfigFiles();
718
160k
}
719
720
QStringList KConfigPrivate::getGlobalFiles() const
721
160k
{
722
160k
    QMutexLocker locker(&s_globalFilesMutex);
723
160k
    if (s_globalFiles()->isEmpty()) {
724
160k
        const QStringList paths1 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
725
160k
        const QStringList paths2 = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("system.kdeglobals"));
726
727
160k
        const bool useEtcKderc = !etc_kderc.isEmpty();
728
160k
        s_globalFiles()->reserve(paths1.size() + paths2.size() + (useEtcKderc ? 1 : 0));
729
730
160k
        for (const QString &dir1 : paths1) {
731
0
            s_globalFiles()->push_front(dir1);
732
0
        }
733
160k
        for (const QString &dir2 : paths2) {
734
0
            s_globalFiles()->push_front(dir2);
735
0
        }
736
737
160k
        if (useEtcKderc) {
738
0
            s_globalFiles()->push_front(etc_kderc);
739
0
        }
740
160k
    }
741
742
160k
    return *s_globalFiles();
743
160k
}
744
745
void KConfigPrivate::parseGlobalFiles()
746
160k
{
747
160k
    const QStringList globalFiles = getGlobalFiles();
748
    //    qDebug() << "parsing global files" << globalFiles;
749
750
160k
    Q_ASSERT(entryMap.empty());
751
752
160k
    ParseCacheTimestamp timestamp;
753
160k
    timestamp.reserve(globalFiles.count());
754
160k
    for (const auto &file : globalFiles) {
755
0
        timestamp << QFileInfo(file).lastModified(QTimeZone::UTC).toMSecsSinceEpoch();
756
0
    }
757
758
160k
    const ParseCacheKey key = {globalFiles, locale};
759
160k
    auto data = sGlobalParse.localData().object(key);
760
160k
    if (data) {
761
38.8k
        if (data->timestamp != timestamp) {
762
0
            data = nullptr;
763
38.8k
        } else {
764
38.8k
            entryMap = data->entries;
765
38.8k
            return;
766
38.8k
        }
767
38.8k
    }
768
769
121k
    const QByteArray utf8Locale = locale.toUtf8();
770
121k
    for (const QString &file : globalFiles) {
771
0
        KConfigIniBackend::ParseOptions parseOpts = KConfigIniBackend::ParseGlobal | KConfigIniBackend::ParseExpansions;
772
773
0
        if (file.compare(*sGlobalFileName, sPathCaseSensitivity) != 0) {
774
0
            parseOpts |= KConfigIniBackend::ParseDefaults;
775
0
        }
776
777
0
        KConfigIniBackend backend(std::make_unique<KConfigIniBackendPathDevice>(file));
778
0
        if (backend.parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable) {
779
0
            break;
780
0
        }
781
0
    }
782
121k
    sGlobalParse.localData().insert(key, new ParseCacheValue({entryMap, timestamp}));
783
121k
}
784
785
#ifdef Q_OS_WIN
786
void KConfigPrivate::parseWindowsDefaults()
787
{
788
    if (QCoreApplication::organizationName().isEmpty() || fileName.isEmpty() || QFileInfo(fileName).isAbsolute()) {
789
        return;
790
    }
791
    const QString registryKey = QCoreApplication::organizationName() + u'/' + (fileName.endsWith(QStringLiteral("rc")) ? fileName.chopped(2) : fileName);
792
    WindowsRegistry::parse(registryKey, entryMap);
793
}
794
#endif
795
796
void KConfigPrivate::parseConfigFiles()
797
160k
{
798
    // can only read the file if there is a backend and a file name
799
160k
    if (!mBackend.hasOpenableDeviceInterface()) {
800
0
        return;
801
0
    }
802
803
160k
    const auto backingDevicePath = mBackend.backingDevicePath();
804
160k
    bFileImmutable = false;
805
806
160k
    QList<QString> files;
807
160k
    if (wantDefaults()) {
808
160k
        if (bSuppressGlobal) {
809
0
            files = getGlobalFiles();
810
160k
        } else {
811
160k
            if (QDir::isAbsolutePath(fileName)) {
812
0
                const QString canonicalFile = QFileInfo(backingDevicePath).canonicalFilePath();
813
0
                if (!canonicalFile.isEmpty()) { // empty if it doesn't exist
814
0
                    files << canonicalFile;
815
0
                }
816
160k
            } else {
817
160k
                const QStringList localFilesPath = QStandardPaths::locateAll(resourceType, fileName);
818
160k
                for (const QString &f : localFilesPath) {
819
121k
                    files.prepend(QFileInfo(f).canonicalFilePath());
820
121k
                }
821
822
                // allow fallback to config files bundled in resources
823
160k
                const QString resourceFile(QStringLiteral(":/kconfig/") + fileName);
824
160k
                if (QFile::exists(resourceFile)) {
825
0
                    files.prepend(resourceFile);
826
0
                }
827
160k
            }
828
160k
        }
829
160k
    } else {
830
        // this also handles anonymous config case (backingDevicePath == "") and not file QIODevice case
831
0
        files << backingDevicePath;
832
0
    }
833
160k
    if (!isSimple()) {
834
160k
        files = QList<QString>(extraFiles.cbegin(), extraFiles.cend()) + files;
835
160k
    }
836
837
160k
    const QByteArray utf8Locale = locale.toUtf8();
838
160k
    for (const QString &file : std::as_const(files)) {
839
121k
        if (file.compare(backingDevicePath, sPathCaseSensitivity) == 0) {
840
121k
            switch (mBackend.parseConfig(utf8Locale, entryMap, KConfigIniBackend::ParseExpansions)) {
841
121k
            case KConfigIniBackend::ParseOk:
842
121k
                break;
843
0
            case KConfigIniBackend::ParseImmutable:
844
0
                bFileImmutable = true;
845
0
                break;
846
0
            case KConfigIniBackend::ParseOpenError:
847
0
                configState = KConfigBase::NoAccess;
848
0
                break;
849
121k
            }
850
121k
        } else {
851
0
            KConfigIniBackend backend(std::make_unique<KConfigIniBackendPathDevice>(file));
852
0
            constexpr auto parseOpts = KConfigIniBackend::ParseDefaults | KConfigIniBackend::ParseExpansions;
853
0
            bFileImmutable = backend.parseConfig(utf8Locale, entryMap, parseOpts) == KConfigIniBackend::ParseImmutable;
854
0
        }
855
856
121k
        if (bFileImmutable) {
857
0
            break;
858
0
        }
859
121k
    }
860
160k
}
861
862
KConfig::AccessMode KConfig::accessMode() const
863
174k
{
864
174k
    Q_D(const KConfig);
865
174k
    return d->configState;
866
174k
}
867
868
void KConfig::addConfigSources(const QStringList &files)
869
0
{
870
0
    Q_D(KConfig);
871
0
    for (const QString &file : files) {
872
0
        d->extraFiles.push(file);
873
0
    }
874
875
0
    if (!files.isEmpty()) {
876
0
        reparseConfiguration();
877
0
    }
878
0
}
879
880
QStringList KConfig::additionalConfigSources() const
881
0
{
882
0
    Q_D(const KConfig);
883
0
    return d->extraFiles.toList();
884
0
}
885
886
QString KConfig::locale() const
887
1
{
888
1
    Q_D(const KConfig);
889
1
    return d->locale;
890
1
}
891
892
bool KConfigPrivate::setLocale(const QString &aLocale)
893
121k
{
894
121k
    if (aLocale != locale) {
895
121k
        locale = aLocale;
896
121k
        return true;
897
121k
    }
898
0
    return false;
899
121k
}
900
901
bool KConfig::setLocale(const QString &locale)
902
0
{
903
0
    Q_D(KConfig);
904
0
    if (d->setLocale(locale)) {
905
0
        reparseConfiguration();
906
0
        return true;
907
0
    }
908
0
    return false;
909
0
}
910
911
void KConfig::setReadDefaults(bool b)
912
8
{
913
8
    Q_D(KConfig);
914
8
    d->bReadDefaults = b;
915
8
}
916
917
bool KConfig::readDefaults() const
918
38.8k
{
919
38.8k
    Q_D(const KConfig);
920
38.8k
    return d->bReadDefaults;
921
38.8k
}
922
923
bool KConfig::isImmutable() const
924
296k
{
925
296k
    Q_D(const KConfig);
926
296k
    return d->bFileImmutable;
927
296k
}
928
929
bool KConfig::isGroupImmutableImpl(const QString &aGroup) const
930
174k
{
931
174k
    Q_D(const KConfig);
932
174k
    return isImmutable() || d->entryMap.getEntryOption(aGroup, {}, {}, KEntryMap::EntryImmutable);
933
174k
}
934
935
KConfigGroup KConfig::groupImpl(const QString &group)
936
0
{
937
0
    return KConfigGroup(this, group);
938
0
}
939
940
const KConfigGroup KConfig::groupImpl(const QString &group) const
941
0
{
942
0
    return KConfigGroup(this, group);
943
0
}
944
945
KEntryMap::EntryOptions convertToOptions(KConfig::WriteConfigFlags flags)
946
121k
{
947
121k
    KEntryMap::EntryOptions options = {};
948
949
121k
    if (flags & KConfig::Persistent) {
950
121k
        options |= KEntryMap::EntryDirty;
951
121k
    }
952
121k
    if (flags & KConfig::Global) {
953
0
        options |= KEntryMap::EntryGlobal;
954
0
    }
955
121k
    if (flags & KConfig::Localized) {
956
0
        options |= KEntryMap::EntryLocalized;
957
0
    }
958
121k
    if (flags.testFlag(KConfig::Notify)) {
959
0
        options |= KEntryMap::EntryNotify;
960
0
    }
961
121k
    return options;
962
121k
}
963
964
void KConfig::deleteGroupImpl(const QString &aGroup, WriteConfigFlags flags)
965
0
{
966
0
    Q_D(KConfig);
967
0
    KEntryMap::EntryOptions options = convertToOptions(flags) | KEntryMap::EntryDeleted;
968
969
0
    const QSet<QString> groups = d->allSubGroups(aGroup);
970
0
    for (const QString &group : groups) {
971
0
        const QList<QByteArray> keys = d->keyListImpl(group);
972
0
        for (const QByteArray &key : keys) {
973
0
            if (d->canWriteEntry(group, key)) {
974
0
                d->entryMap.setEntry(group, key, QByteArray(), options);
975
0
                d->bDirty = true;
976
0
            }
977
0
        }
978
0
    }
979
0
}
980
981
bool KConfig::isConfigWritable(bool warnUser)
982
14
{
983
14
    Q_D(KConfig);
984
14
    bool allWritable = d->mBackend.isWritable();
985
986
14
    if (warnUser && !allWritable) {
987
0
        QString errorMsg;
988
0
        errorMsg = d->mBackend.nonWritableErrorMessage();
989
990
        // Note: We don't ask the user if we should not ask this question again because we can't save the answer.
991
0
        errorMsg += QCoreApplication::translate("KConfig", "Please contact your system administrator.");
992
0
        QString cmdToExec = QStandardPaths::findExecutable(QStringLiteral("kdialog"));
993
0
        if (!cmdToExec.isEmpty()) {
994
0
            QProcess::execute(cmdToExec, QStringList{QStringLiteral("--title"), QCoreApplication::applicationName(), QStringLiteral("--msgbox"), errorMsg});
995
0
        }
996
0
    }
997
998
14
    d->configState = allWritable ? ReadWrite : ReadOnly; // update the read/write status
999
1000
14
    return allWritable;
1001
14
}
1002
1003
bool KConfig::hasGroupImpl(const QString &aGroup) const
1004
0
{
1005
0
    Q_D(const KConfig);
1006
1007
    // No need to look for the actual group entry anymore, or for subgroups:
1008
    // a group exists if it contains any non-deleted entry.
1009
1010
0
    return d->hasNonDeletedEntries(aGroup);
1011
0
}
1012
1013
bool KConfigPrivate::canWriteEntry(const QString &group, QAnyStringView key, bool isDefault) const
1014
38.8k
{
1015
38.8k
    if (bFileImmutable || entryMap.getEntryOption(group, key, KEntryMap::SearchLocalized, KEntryMap::EntryImmutable)) {
1016
0
        return isDefault;
1017
0
    }
1018
38.8k
    return true;
1019
38.8k
}
1020
1021
void KConfigPrivate::putData(const QString &group, const char *key, const QByteArray &value, KConfigBase::WriteConfigFlags flags, bool expand)
1022
121k
{
1023
121k
    KEntryMap::EntryOptions options = convertToOptions(flags);
1024
1025
121k
    if (bForceGlobal) {
1026
0
        options |= KEntryMap::EntryGlobal;
1027
0
    }
1028
121k
    if (expand) {
1029
0
        options |= KEntryMap::EntryExpansion;
1030
0
    }
1031
1032
121k
    if (value.isNull()) { // deleting entry
1033
0
        options |= KEntryMap::EntryDeleted;
1034
0
    }
1035
1036
121k
    bool dirtied = entryMap.setEntry(group, key, value, options);
1037
121k
    if (dirtied && (flags & KConfigBase::Persistent)) {
1038
1
        bDirty = true;
1039
1
    }
1040
121k
}
1041
1042
void KConfigPrivate::revertEntry(const QString &group, QAnyStringView key, KConfigBase::WriteConfigFlags flags)
1043
0
{
1044
0
    KEntryMap::EntryOptions options = convertToOptions(flags);
1045
1046
0
    bool dirtied = entryMap.revertEntry(group, key, options);
1047
0
    if (dirtied) {
1048
0
        bDirty = true;
1049
0
    }
1050
0
}
1051
1052
QByteArray KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1053
52.7k
{
1054
52.7k
    return lookupInternalEntry(group, key, flags).mValue;
1055
52.7k
}
1056
1057
KEntry KConfigPrivate::lookupInternalEntry(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags) const
1058
52.7k
{
1059
52.7k
    if (bReadDefaults) {
1060
4
        flags |= KEntryMap::SearchDefaults;
1061
4
    }
1062
52.7k
    const auto it = entryMap.constFindEntry(group, key, flags);
1063
52.7k
    if (it == entryMap.cend()) {
1064
38.8k
        return {};
1065
38.8k
    }
1066
13.9k
    return it->second;
1067
52.7k
}
1068
1069
QString KConfigPrivate::lookupData(const QString &group, QAnyStringView key, KEntryMap::SearchFlags flags, bool *expand) const
1070
0
{
1071
0
    if (bReadDefaults) {
1072
0
        flags |= KEntryMap::SearchDefaults;
1073
0
    }
1074
0
    return entryMap.getEntry(group, key, QString(), flags, expand);
1075
0
}
1076
1077
QStandardPaths::StandardLocation KConfig::locationType() const
1078
13.9k
{
1079
13.9k
    Q_D(const KConfig);
1080
13.9k
    return d->resourceType;
1081
13.9k
}
1082
1083
void KConfig::virtual_hook(int /*id*/, void * /*data*/)
1084
0
{
1085
    /* nothing */
1086
0
}