Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/configmgr/source/components.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
22
#include <cassert>
23
#include <chrono>
24
#include <condition_variable>
25
#include <mutex>
26
#include <utility>
27
#include <vector>
28
#include <set>
29
30
#include <com/sun/star/beans/Optional.hpp>
31
#include <com/sun/star/beans/UnknownPropertyException.hpp>
32
#include <com/sun/star/beans/XPropertySet.hpp>
33
#include <com/sun/star/container/NoSuchElementException.hpp>
34
#include <com/sun/star/lang/WrappedTargetException.hpp>
35
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
36
#include <com/sun/star/uno/Any.hxx>
37
#include <com/sun/star/uno/Exception.hpp>
38
#include <com/sun/star/uno/Reference.hxx>
39
#include <com/sun/star/uno/RuntimeException.hpp>
40
#include <com/sun/star/uno/XComponentContext.hpp>
41
#include <com/sun/star/uno/XInterface.hpp>
42
#include <cppuhelper/exc_hlp.hxx>
43
#include <config_dconf.h>
44
#include <config_folders.h>
45
#include <osl/conditn.hxx>
46
#include <osl/file.hxx>
47
#include <osl/mutex.hxx>
48
#include <rtl/bootstrap.hxx>
49
#include <rtl/ref.hxx>
50
#include <rtl/ustrbuf.hxx>
51
#include <rtl/ustring.hxx>
52
#include <sal/log.hxx>
53
#include <sal/types.h>
54
#include <salhelper/thread.hxx>
55
#include <comphelper/diagnose_ex.hxx>
56
#include <o3tl/string_view.hxx>
57
58
#include "additions.hxx"
59
#include "components.hxx"
60
#include "data.hxx"
61
#include "lock.hxx"
62
#include "modifications.hxx"
63
#include "node.hxx"
64
#include "nodemap.hxx"
65
#include "parsemanager.hxx"
66
#include "partial.hxx"
67
#include "rootaccess.hxx"
68
#include "writemodfile.hxx"
69
#include "xcdparser.hxx"
70
#include "xcuparser.hxx"
71
#include "xcsparser.hxx"
72
73
#if ENABLE_DCONF
74
#include "dconf.hxx"
75
#endif
76
77
#if defined(_WIN32)
78
#include "winreg.hxx"
79
#endif
80
81
namespace configmgr {
82
83
namespace {
84
85
struct UnresolvedVectorItem {
86
    OUString name;
87
    rtl::Reference< ParseManager > manager;
88
89
    UnresolvedVectorItem(
90
        OUString  theName,
91
        rtl::Reference< ParseManager > theManager):
92
0
        name(std::move(theName)), manager(std::move(theManager)) {}
93
};
94
95
typedef std::vector< UnresolvedVectorItem > UnresolvedVector;
96
97
void parseXcsFile(
98
    OUString const & url, int layer, Data & data, Partial const * partial,
99
    Modifications * modifications, Additions * additions)
100
0
{
101
0
    assert(partial == nullptr && modifications == nullptr && additions == nullptr);
102
0
    (void) partial; (void) modifications; (void) additions;
103
0
    bool ok = rtl::Reference< ParseManager >(
104
0
        new ParseManager(url, new XcsParser(layer, data)))->parse(nullptr);
105
0
    assert(ok);
106
0
    (void) ok; // avoid warnings
107
0
}
108
109
void parseXcuFile(
110
    OUString const & url, int layer, Data & data, Partial const * partial,
111
    Modifications * modifications, Additions * additions)
112
0
{
113
0
    bool ok = rtl::Reference< ParseManager >(
114
0
        new ParseManager(
115
0
            url,
116
0
            new XcuParser(layer, data, partial, modifications, additions)))->
117
0
        parse(nullptr);
118
0
    assert(ok);
119
0
    (void) ok; // avoid warnings
120
0
}
121
122
13
OUString expand(OUString const & str) {
123
13
    OUString s(str);
124
13
    rtl::Bootstrap::expandMacros(s); //TODO: detect failure
125
13
    return s;
126
13
}
127
128
0
bool canRemoveFromLayer(int layer, rtl::Reference< Node > const & node) {
129
0
    assert(node.is());
130
0
    if (node->getLayer() > layer && node->getLayer() < Data::NO_LAYER) {
131
0
        return false;
132
0
    }
133
0
    switch (node->kind()) {
134
0
    case Node::KIND_LOCALIZED_PROPERTY:
135
0
    case Node::KIND_GROUP:
136
0
        for (auto const& member : node->getMembers())
137
0
        {
138
0
            if (!canRemoveFromLayer(layer, member.second)) {
139
0
                return false;
140
0
            }
141
0
        }
142
0
        return true;
143
0
    case Node::KIND_SET:
144
0
        return node->getMembers().empty();
145
0
    default: // Node::KIND_PROPERTY, Node::KIND_LOCALIZED_VALUE
146
0
        return true;
147
0
    }
148
0
}
149
150
}
151
152
class Components::WriteThread: public salhelper::Thread {
153
public:
154
    WriteThread(
155
        rtl::Reference< WriteThread > * reference, Components & components,
156
        OUString url, Data const & data);
157
158
0
    void trigger() {
159
0
        std::scoped_lock l(triggerMutex_);
160
0
        triggered_ = true;
161
0
        triggerCondition_.notify_all();
162
0
    }
163
164
0
    void flush() {
165
0
        delayOrTerminate_.set();
166
0
        trigger();
167
0
    }
168
169
private:
170
0
    virtual ~WriteThread() override {}
171
172
    virtual void execute() override;
173
174
    rtl::Reference< WriteThread > * reference_;
175
    Components & components_;
176
    OUString url_;
177
    Data const & data_;
178
    osl::Condition delayOrTerminate_;
179
    std::mutex triggerMutex_;
180
    std::condition_variable triggerCondition_;
181
    bool triggered_;
182
};
183
184
Components::WriteThread::WriteThread(
185
    rtl::Reference< WriteThread > * reference, Components & components,
186
    OUString url, Data const & data):
187
0
    Thread("configmgrWriter"), reference_(reference), components_(components),
188
0
    url_(std::move(url)), data_(data),
189
0
    triggered_(false)
190
0
{
191
0
    assert(reference != nullptr);
192
0
}
193
194
0
void Components::WriteThread::execute() {
195
0
    for (;;) {
196
0
        {
197
0
            std::unique_lock l(triggerMutex_);
198
0
            while (!triggered_) {
199
0
                triggerCondition_.wait(l);
200
0
            }
201
0
            triggered_ = false;
202
0
        }
203
0
        delayOrTerminate_.wait(std::chrono::seconds(1));
204
            // must not throw; result_error is harmless and ignored
205
0
        try {
206
0
            try {
207
0
                writeModFile(components_, url_, data_);
208
0
            } catch (css::uno::RuntimeException &) {
209
                // Ignore write errors, instead of aborting:
210
0
                TOOLS_WARN_EXCEPTION("configmgr", "error writing modifications");
211
0
            }
212
0
        } catch (...) {
213
0
            reference_->clear();
214
0
            throw;
215
0
        }
216
0
        if (!delayOrTerminate_.check()) {
217
0
            continue;
218
0
        }
219
0
        reference_->clear();
220
0
        break;
221
0
    }
222
0
}
223
224
Components & Components::getSingleton(
225
    css::uno::Reference< css::uno::XComponentContext > const & context)
226
81.7k
{
227
81.7k
    assert(context.is());
228
81.7k
    static Components singleton(context);
229
81.7k
    return singleton;
230
81.7k
}
231
232
0
bool Components::allLocales(std::u16string_view locale) {
233
0
    return locale == u"*";
234
0
}
235
236
rtl::Reference< Node > Components::resolvePathRepresentation(
237
    OUString const & pathRepresentation,
238
    OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer)
239
    const
240
81.6k
{
241
81.6k
    return data_.resolvePathRepresentation(
242
81.6k
        pathRepresentation, canonicRepresentation, path, finalizedLayer);
243
81.6k
}
244
245
rtl::Reference< Node > Components::getTemplate(OUString const & fullName) const
246
0
{
247
0
    return data_.getTemplate(Data::NO_LAYER, fullName);
248
0
}
249
250
45.8k
void Components::addRootAccess(rtl::Reference< RootAccess > const & access) {
251
45.8k
    roots_.insert(access.get());
252
45.8k
}
253
254
81.7k
void Components::removeRootAccess(RootAccess * access) {
255
81.7k
    roots_.erase(access);
256
81.7k
}
257
258
void Components::initGlobalBroadcaster(
259
    Modifications const & modifications,
260
    rtl::Reference< RootAccess > const & exclude, Broadcaster * broadcaster)
261
0
{
262
    //TODO: Iterate only over roots w/ listeners:
263
0
    for (auto const& elemRoot : roots_)
264
0
    {
265
0
        rtl::Reference< RootAccess > root;
266
0
        if (elemRoot->acquireCounting() > 1) {
267
0
            root.set(elemRoot); // must not throw
268
0
        }
269
0
        elemRoot->releaseNondeleting();
270
0
        if (root.is()) {
271
0
            if (root != exclude) {
272
0
                std::vector<OUString> path(root->getAbsolutePath());
273
0
                Modifications::Node const * mods = &modifications.getRoot();
274
0
                for (auto const& pathElem : path)
275
0
                {
276
0
                    Modifications::Node::Children::const_iterator k(
277
0
                        mods->children.find(pathElem));
278
0
                    if (k == mods->children.end()) {
279
0
                        mods = nullptr;
280
0
                        break;
281
0
                    }
282
0
                    mods = &k->second;
283
0
                }
284
                //TODO: If the complete tree of which root is a part is deleted,
285
                // or replaced, mods will be null, but some of the listeners
286
                // from within root should probably fire nonetheless:
287
0
                if (mods != nullptr) {
288
0
                    root->initBroadcaster(*mods, broadcaster);
289
0
                }
290
0
            }
291
0
        }
292
0
    }
293
0
}
294
295
0
void Components::addModification(std::vector<OUString> const & path) {
296
0
    data_.modifications.add(path);
297
0
}
298
299
0
void Components::writeModifications() {
300
301
0
    if (data_.modifications.empty())
302
0
        return;
303
304
0
    switch (modificationTarget_) {
305
0
    case ModificationTarget::None:
306
0
        break;
307
0
    case ModificationTarget::File:
308
0
        if (!writeThread_.is()) {
309
0
            writeThread_ = new WriteThread(
310
0
                &writeThread_, *this, modificationFileUrl_, data_);
311
0
            writeThread_->launch();
312
0
        }
313
0
        writeThread_->trigger();
314
0
        break;
315
0
    case ModificationTarget::Dconf:
316
#if ENABLE_DCONF
317
        dconf::writeModifications(*this, data_);
318
#endif
319
0
        break;
320
0
    }
321
0
}
322
323
13
void Components::flushModifications() {
324
13
    rtl::Reference< WriteThread > thread;
325
13
    {
326
13
        osl::MutexGuard g(*lock_);
327
13
        thread = writeThread_;
328
13
    }
329
13
    if (thread.is()) {
330
0
        thread->flush();
331
0
        thread->join();
332
0
    }
333
13
}
334
335
void Components::insertExtensionXcsFile(
336
    bool shared, OUString const & fileUri)
337
0
{
338
0
    int layer = getExtensionLayer(shared);
339
0
    try {
340
0
        parseXcsFile(fileUri, layer, data_, nullptr, nullptr, nullptr);
341
0
    } catch (css::container::NoSuchElementException & e) {
342
0
        throw css::uno::RuntimeException(
343
0
            "insertExtensionXcsFile does not exist: " + e.Message);
344
0
    }
345
0
}
346
347
void Components::insertExtensionXcuFile(
348
    bool shared, OUString const & fileUri, Modifications * modifications)
349
0
{
350
0
    assert(modifications != nullptr);
351
0
    int layer = getExtensionLayer(shared) + 1;
352
0
    Additions * adds = data_.addExtensionXcuAdditions(fileUri, layer);
353
0
    try {
354
0
        parseXcuFile(fileUri, layer, data_, nullptr, modifications, adds);
355
0
    } catch (css::container::NoSuchElementException & e) {
356
0
        data_.removeExtensionXcuAdditions(fileUri);
357
0
        throw css::uno::RuntimeException(
358
0
            "insertExtensionXcuFile does not exist: " + e.Message);
359
0
    }
360
0
}
361
362
void Components::removeExtensionXcuFile(
363
    OUString const & fileUri, Modifications * modifications)
364
0
{
365
    //TODO: Ideally, exactly the data coming from the specified xcu file would
366
    // be removed.  However, not enough information is recorded in the in-memory
367
    // data structures to do so.  So, as a workaround, all those set elements
368
    // that were freshly added by the xcu and have afterwards been left
369
    // unchanged or have only had their properties changed in the user layer are
370
    // removed (and nothing else).  The heuristic to determine
371
    // whether a node has been left unchanged is to check the layer ID (as
372
    // usual) and additionally to check that the node does not recursively
373
    // contain any non-empty sets (multiple extension xcu files are merged into
374
    // one layer, so checking layer ID alone is not enough).  Since
375
    // item->additions records all additions of set members in textual order,
376
    // the latter check works well when iterating through item->additions in
377
    // reverse order.
378
0
    assert(modifications != nullptr);
379
0
    rtl::Reference< Data::ExtensionXcu > item(
380
0
        data_.removeExtensionXcuAdditions(fileUri));
381
0
    if (!item.is())
382
0
        return;
383
384
0
    for (Additions::reverse_iterator i(item->additions.rbegin());
385
0
         i != item->additions.rend(); ++i)
386
0
    {
387
0
        rtl::Reference< Node > parent;
388
0
        NodeMap const * map = &data_.getComponents();
389
0
        rtl::Reference< Node > node;
390
0
        for (auto const& j : *i)
391
0
        {
392
0
            parent = node;
393
0
            node = map->findNode(Data::NO_LAYER, j);
394
0
            if (!node.is()) {
395
0
                break;
396
0
            }
397
0
            map = &node->getMembers();
398
0
        }
399
0
        if (node.is()) {
400
0
            assert(parent.is());
401
0
            if (parent->kind() == Node::KIND_SET) {
402
0
                assert(
403
0
                    node->kind() == Node::KIND_GROUP ||
404
0
                    node->kind() == Node::KIND_SET);
405
0
                if (canRemoveFromLayer(item->layer, node)) {
406
0
                    parent->getMembers().erase(i->back());
407
0
                    data_.modifications.remove(*i);
408
0
                    modifications->add(*i);
409
0
                }
410
0
            }
411
0
        }
412
0
    }
413
0
    writeModifications();
414
0
}
415
416
void Components::insertModificationXcuFile(
417
    OUString const & fileUri,
418
    css::uno::Sequence< OUString > const & includedPaths,
419
    css::uno::Sequence< OUString > const & excludedPaths,
420
    Modifications * modifications)
421
0
{
422
0
    assert(modifications != nullptr);
423
0
    Partial part(includedPaths, excludedPaths);
424
0
    try {
425
0
        parseFileLeniently(
426
0
            &parseXcuFile, fileUri, Data::NO_LAYER, &part, modifications, nullptr);
427
0
    } catch (const css::container::NoSuchElementException &) {
428
0
        TOOLS_WARN_EXCEPTION(
429
0
            "configmgr",
430
0
            "error inserting non-existing \"" << fileUri << "\"");
431
0
    }
432
0
}
433
434
css::beans::Optional< css::uno::Any > Components::getExternalValue(
435
    std::u16string_view descriptor)
436
0
{
437
0
    size_t i = descriptor.find(' ');
438
0
    if (i == 0 || i == std::u16string_view::npos) {
439
0
        throw css::uno::RuntimeException(
440
0
            OUString::Concat("bad external value descriptor ") + descriptor);
441
0
    }
442
    //TODO: Do not make calls with mutex locked:
443
0
    OUString name(descriptor.substr(0, i));
444
0
    ExternalServices::iterator j(externalServices_.find(name));
445
0
    if (j == externalServices_.end()) {
446
0
        css::uno::Reference< css::uno::XInterface > service;
447
0
        try {
448
0
            service = context_->getServiceManager()->createInstanceWithContext(
449
0
                name, context_);
450
0
        } catch (const css::uno::RuntimeException &) {
451
            // Assuming these exceptions are real errors:
452
0
            throw;
453
0
        } catch (const css::uno::Exception &)  {
454
            // Assuming these exceptions indicate that the service is not
455
            // installed:
456
0
            TOOLS_WARN_EXCEPTION(
457
0
                "configmgr",
458
0
                "createInstance(" << name << ") failed");
459
0
        }
460
0
        css::uno::Reference< css::beans::XPropertySet > propset;
461
0
        if (service.is()) {
462
0
            propset.set( service, css::uno::UNO_QUERY_THROW);
463
0
        }
464
0
        j = externalServices_.emplace(name, propset).first;
465
0
    }
466
0
    css::beans::Optional< css::uno::Any > value;
467
0
    if (j->second.is()) {
468
0
        try {
469
0
            if (!(j->second->getPropertyValue(OUString(descriptor.substr(i + 1))) >>=
470
0
                  value))
471
0
            {
472
0
                throw css::uno::RuntimeException(
473
0
                    OUString::Concat("cannot obtain external value through ") + descriptor);
474
0
            }
475
0
        } catch (css::beans::UnknownPropertyException & e) {
476
0
            throw css::uno::RuntimeException(
477
0
                "unknown external value descriptor ID: " + e.Message);
478
0
        } catch (css::lang::WrappedTargetException & e) {
479
0
            css::uno::Any anyEx = cppu::getCaughtException();
480
0
            throw css::lang::WrappedTargetRuntimeException(
481
0
                "cannot obtain external value: " + e.Message,
482
0
                nullptr, anyEx );
483
0
        }
484
0
    }
485
0
    return value;
486
0
}
487
488
Components::Components(
489
    css::uno::Reference< css::uno::XComponentContext > const & context):
490
13
    context_(context), sharedExtensionLayer_(-1), userExtensionLayer_(-1),
491
13
    modificationTarget_(ModificationTarget::None)
492
13
{
493
13
    assert(context.is());
494
13
    lock_ = lock();
495
496
13
    bool staticize = !!getenv("SAL_CONFIG_STATICIZE");
497
13
    Node::setStaticizedFlag(staticize);
498
499
13
    OUString conf(expand(u"${CONFIGURATION_LAYERS}"_ustr));
500
13
    int layer = 0;
501
13
    for (sal_Int32 i = 0;;) {
502
13
        while (i != conf.getLength() && conf[i] == ' ') {
503
0
            ++i;
504
0
        }
505
13
        if (i == conf.getLength()) {
506
13
            break;
507
13
        }
508
0
        if (modificationTarget_ != ModificationTarget::None) {
509
0
            throw css::uno::RuntimeException(
510
0
                u"CONFIGURATION_LAYERS: modification target layer followed by"
511
0
                " further layers"_ustr);
512
0
        }
513
0
        sal_Int32 c = i;
514
0
        for (;; ++c) {
515
0
            if (c == conf.getLength() || conf[c] == ' ') {
516
0
                throw css::uno::RuntimeException(
517
0
                    "CONFIGURATION_LAYERS: missing ':' in \"" + conf + "\"");
518
0
            }
519
0
            if (conf[c] == ':') {
520
0
                break;
521
0
            }
522
0
        }
523
0
        sal_Int32 n = conf.indexOf(' ', c + 1);
524
0
        if (n == -1) {
525
0
            n = conf.getLength();
526
0
        }
527
0
        OUString type(conf.copy(i, c - i));
528
0
        OUString url(conf.copy(c + 1, n - c - 1));
529
0
        if (type == "xcsxcu") {
530
0
            sal_uInt32 nStartTime = osl_getGlobalTimer();
531
0
            parseXcsXcuLayer(layer, url);
532
0
            SAL_INFO("configmgr", "parseXcsXcuLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
533
0
            layer += 2;
534
0
        } else if (type == "bundledext") {
535
0
            parseXcsXcuIniLayer(layer, url, false);
536
0
            layer += 2;
537
0
        } else if (type == "sharedext") {
538
0
            if (sharedExtensionLayer_ != -1) {
539
0
                throw css::uno::RuntimeException(
540
0
                    u"CONFIGURATION_LAYERS: multiple \"sharedext\" layers"_ustr);
541
0
            }
542
0
            sharedExtensionLayer_ = layer;
543
0
            parseXcsXcuIniLayer(layer, url, true);
544
0
            layer += 2;
545
0
        } else if (type == "userext") {
546
0
            if (userExtensionLayer_ != -1) {
547
0
                throw css::uno::RuntimeException(
548
0
                    u"CONFIGURATION_LAYERS: multiple \"userext\" layers"_ustr);
549
0
            }
550
0
            userExtensionLayer_ = layer;
551
0
            parseXcsXcuIniLayer(layer, url, true);
552
0
            layer += 2;
553
0
        } else if (type == "res") {
554
0
            sal_uInt32 nStartTime = osl_getGlobalTimer();
555
0
            parseResLayer(layer, url);
556
0
            SAL_INFO("configmgr", "parseResLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
557
0
            ++layer;
558
#if ENABLE_DCONF
559
        } else if (type == "dconf") {
560
            if (url == "!") {
561
                modificationTarget_ = ModificationTarget::Dconf;
562
                dconf::readLayer(data_, Data::NO_LAYER);
563
            } else if (url == "*") {
564
                dconf::readLayer(data_, layer);
565
            } else {
566
                throw css::uno::RuntimeException(
567
                    "CONFIGURATION_LAYERS: unknown \"dconf\" kind \"" + url
568
                    + "\"");
569
            }
570
            ++layer;
571
#endif
572
#if defined(_WIN32)
573
        } else if (type == "winreg") {
574
            WinRegType eType;
575
            if (url == "LOCAL_MACHINE" || url.isEmpty()/*backwards comp.*/) {
576
                eType = WinRegType::LOCAL_MACHINE;
577
            } else if (url == "CURRENT_USER") {
578
                eType = WinRegType::CURRENT_USER;
579
            } else {
580
                throw css::uno::RuntimeException(
581
                    "CONFIGURATION_LAYERS: unknown \"winreg\" kind \"" + url
582
                    + "\"");
583
            }
584
            OUString aTempFileURL;
585
            if (dumpWindowsRegistry(&aTempFileURL, eType)) {
586
                parseFileLeniently(&parseXcuFile, aTempFileURL, layer, nullptr, nullptr, nullptr);
587
                if (!getenv("SAL_CONFIG_WINREG_RETAIN_TMP"))
588
                    osl::File::remove(aTempFileURL);
589
            }
590
            ++layer;
591
#endif
592
0
        } else if (type == "user") {
593
0
            bool write;
594
0
            if (url.startsWith("!", &url)) {
595
0
                write = true;
596
0
            } else if (url.startsWith("*", &url)) {
597
0
                write = false;
598
0
            } else {
599
0
                write = true; // for backwards compatibility
600
0
            }
601
0
            if (url.isEmpty()) {
602
0
                throw css::uno::RuntimeException(
603
0
                    u"CONFIGURATION_LAYERS: empty \"user\" URL"_ustr);
604
0
            }
605
0
            bool ignore = false;
606
#if ENABLE_DCONF
607
            if (write) {
608
                OUString token(
609
                    expand("${SYSUSERCONFIG}/libreoffice/dconfwrite"));
610
                osl::DirectoryItem it;
611
                osl::FileBase::RC e = osl::DirectoryItem::get(token, it);
612
                ignore = e == osl::FileBase::E_None;
613
                SAL_INFO(
614
                    "configmgr",
615
                    "dconf write (<" << token << "> " << +e << "): "
616
                        << int(ignore));
617
                if (ignore) {
618
                    modificationTarget_ = ModificationTarget::Dconf;
619
                }
620
            }
621
#endif
622
0
            if (!ignore) {
623
0
                if (write) {
624
0
                    modificationTarget_ = ModificationTarget::File;
625
0
                    modificationFileUrl_ = url;
626
0
                }
627
0
                parseModificationLayer(write ? Data::NO_LAYER : layer, url);
628
0
            }
629
0
            ++layer;
630
0
        } else {
631
0
            throw css::uno::RuntimeException(
632
0
                "CONFIGURATION_LAYERS: unknown layer type \"" + type + "\"");
633
0
        }
634
0
        i = n;
635
0
    }
636
637
13
    Node::setStaticizedFlag(false);
638
13
}
639
640
Components::~Components()
641
13
{
642
13
    flushModifications();
643
644
13
    for (auto const& rootElem : roots_)
645
0
    {
646
0
        rootElem->setAlive(false);
647
0
    }
648
13
}
649
650
void Components::parseFileLeniently(
651
    FileParser * parseFile, OUString const & url, int layer,
652
    Partial const * partial, Modifications * modifications,
653
    Additions * additions)
654
0
{
655
0
    assert(parseFile != nullptr);
656
0
    try {
657
0
        (*parseFile)(url, layer, data_, partial, modifications, additions);
658
0
    } catch (const css::container::NoSuchElementException &) {
659
0
        throw;
660
0
    } catch (const css::uno::Exception &) { //TODO: more specific exception catching
661
        // Ignore invalid XML files, instead of completely preventing OOo from
662
        // starting:
663
0
        TOOLS_WARN_EXCEPTION(
664
0
            "configmgr",
665
0
            "error reading \"" << url << "\"");
666
0
    }
667
0
}
668
669
void Components::parseFiles(
670
    int layer, OUString const & extension, FileParser * parseFile,
671
    OUString const & url, bool recursive)
672
0
{
673
0
    osl::Directory dir(url);
674
0
    switch (dir.open()) {
675
0
    case osl::FileBase::E_None:
676
0
        break;
677
0
    case osl::FileBase::E_NOENT:
678
0
        if (!recursive) {
679
0
            return;
680
0
        }
681
0
        [[fallthrough]];
682
0
    default:
683
0
        throw css::uno::RuntimeException(
684
0
            "cannot open directory " + url);
685
0
    }
686
0
    for (;;) {
687
0
        osl::DirectoryItem i;
688
0
        osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
689
0
        if (rc == osl::FileBase::E_NOENT) {
690
0
            break;
691
0
        }
692
0
        if (rc != osl::FileBase::E_None) {
693
0
            throw css::uno::RuntimeException(
694
0
                "cannot iterate directory " + url);
695
0
        }
696
0
        osl::FileStatus stat(
697
0
            osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
698
0
            osl_FileStatus_Mask_FileURL);
699
0
        if (i.getFileStatus(stat) != osl::FileBase::E_None) {
700
0
            throw css::uno::RuntimeException(
701
0
                "cannot stat in directory " + url);
702
0
        }
703
0
        if (stat.getFileType() == osl::FileStatus::Directory) { //TODO: symlinks
704
0
            parseFiles(layer, extension, parseFile, stat.getFileURL(), true);
705
0
        } else {
706
0
            OUString file(stat.getFileName());
707
0
            if (file.endsWith(extension)) {
708
0
                try {
709
0
                    parseFileLeniently(
710
0
                        parseFile, stat.getFileURL(), layer, nullptr, nullptr, nullptr);
711
0
                } catch (css::container::NoSuchElementException & e) {
712
0
                    if (stat.getFileType() == osl::FileStatus::Link) {
713
0
                        SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
714
0
                        continue;
715
0
                    }
716
0
                    throw css::uno::RuntimeException(
717
0
                        "stat'ed file does not exist: " + e.Message);
718
0
                }
719
0
            }
720
0
        }
721
0
    }
722
0
}
723
724
void Components::parseFileList(
725
    int layer, FileParser * parseFile, std::u16string_view urls,
726
    bool recordAdditions)
727
0
{
728
0
    for (sal_Int32 i = 0;;) {
729
0
        OUString url(o3tl::getToken(urls, 0, ' ', i));
730
0
        if (!url.isEmpty()) {
731
0
            Additions * adds = nullptr;
732
0
            if (recordAdditions) {
733
0
                adds = data_.addExtensionXcuAdditions(url, layer);
734
0
            }
735
0
            try {
736
0
                parseFileLeniently(parseFile, url, layer, nullptr, nullptr, adds);
737
0
            } catch (const css::container::NoSuchElementException &) {
738
0
                TOOLS_WARN_EXCEPTION("configmgr", "file does not exist");
739
0
                if (adds != nullptr) {
740
0
                    data_.removeExtensionXcuAdditions(url);
741
0
                }
742
0
            }
743
0
        }
744
0
        if (i == -1) {
745
0
            break;
746
0
        }
747
0
    }
748
0
}
749
750
0
void Components::parseXcdFiles(int layer, OUString const & url) {
751
0
    osl::Directory dir(url);
752
0
    switch (dir.open()) {
753
0
    case osl::FileBase::E_None:
754
0
        break;
755
0
    case osl::FileBase::E_NOENT:
756
0
        return;
757
0
    default:
758
0
        throw css::uno::RuntimeException(
759
0
            "cannot open directory " + url);
760
0
    }
761
0
    UnresolvedVector unres;
762
0
    std::set< OUString > existingDeps;
763
0
    std::set< OUString > processedDeps;
764
0
    for (;;) {
765
0
        osl::DirectoryItem i;
766
0
        osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
767
0
        if (rc == osl::FileBase::E_NOENT) {
768
0
            break;
769
0
        }
770
0
        if (rc != osl::FileBase::E_None) {
771
0
            throw css::uno::RuntimeException(
772
0
                "cannot iterate directory " + url);
773
0
        }
774
0
        osl::FileStatus stat(
775
0
            osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
776
0
            osl_FileStatus_Mask_FileURL);
777
0
        if (i.getFileStatus(stat) != osl::FileBase::E_None) {
778
0
            throw css::uno::RuntimeException(
779
0
                "cannot stat in directory " + url);
780
0
        }
781
0
        if (stat.getFileType() != osl::FileStatus::Directory) { //TODO: symlinks
782
0
            OUString file(stat.getFileName());
783
0
            OUString name;
784
0
            if (file.endsWith(".xcd", &name)) {
785
0
                existingDeps.insert(name);
786
0
                rtl::Reference< ParseManager > manager;
787
0
                try {
788
0
                    manager = new ParseManager(
789
0
                        stat.getFileURL(),
790
0
                        new XcdParser(layer, processedDeps, data_));
791
0
                } catch (css::container::NoSuchElementException & e) {
792
0
                    if (stat.getFileType() == osl::FileStatus::Link) {
793
0
                        SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
794
0
                        continue;
795
0
                    }
796
0
                    throw css::uno::RuntimeException(
797
0
                        "stat'ed file does not exist: " + e.Message);
798
0
                }
799
0
                if (manager->parse(nullptr)) {
800
0
                    processedDeps.insert(name);
801
0
                } else {
802
0
                    unres.emplace_back(name, manager);
803
0
                }
804
0
            }
805
0
        }
806
0
    }
807
0
    while (!unres.empty()) {
808
0
        bool resolved = false;
809
0
        for (UnresolvedVector::iterator i(unres.begin()); i != unres.end();) {
810
0
            if (i->manager->parse(&existingDeps)) {
811
0
                processedDeps.insert(i->name);
812
0
                i = unres.erase(i);
813
0
                resolved = true;
814
0
            } else {
815
0
                ++i;
816
0
            }
817
0
        }
818
0
        if (!resolved) {
819
0
            throw css::uno::RuntimeException(
820
0
                "xcd: unresolved dependencies in " + url);
821
0
        }
822
0
    }
823
0
}
824
825
0
void Components::parseXcsXcuLayer(int layer, OUString const & url) {
826
0
    parseXcdFiles(layer, url);
827
0
    parseFiles(layer, u".xcs"_ustr, &parseXcsFile, url + "/schema", false);
828
0
    parseFiles(layer + 1, u".xcu"_ustr, &parseXcuFile, url + "/data", false);
829
0
}
830
831
void Components::parseXcsXcuIniLayer(
832
    int layer, OUString const & url, bool recordAdditions)
833
0
{
834
    // Check if ini file exists (otherwise .override would still read global
835
    // SCHEMA/DATA variables, which could interfere with unrelated environment
836
    // variables):
837
0
    if (rtl::Bootstrap(url).getHandle() == nullptr)        return;
838
839
0
    OUStringBuffer prefix("${.override:");
840
0
    for (sal_Int32 i = 0; i != url.getLength(); ++i) {
841
0
        sal_Unicode c = url[i];
842
0
        switch (c) {
843
0
        case '$':
844
0
        case ':':
845
0
        case '\\':
846
0
            prefix.append('\\');
847
0
            [[fallthrough]];
848
0
        default:
849
0
            prefix.append(c);
850
0
        }
851
0
    }
852
0
    prefix.append(':');
853
0
    OUString urls(prefix + "SCHEMA}");
854
0
    rtl::Bootstrap::expandMacros(urls);
855
0
    if (!urls.isEmpty()) {
856
0
        parseFileList(layer, &parseXcsFile, urls, false);
857
0
    }
858
0
    urls = prefix + "DATA}";
859
0
    rtl::Bootstrap::expandMacros(urls);
860
0
    if (!urls.isEmpty()) {
861
0
        parseFileList(layer + 1, &parseXcuFile, urls, recordAdditions);
862
0
    }
863
0
}
864
865
0
void Components::parseResLayer(int layer, std::u16string_view url) {
866
0
    OUString resUrl(OUString::Concat(url) + "/res");
867
0
    parseXcdFiles(layer, resUrl);
868
0
    parseFiles(layer, u".xcu"_ustr, &parseXcuFile, resUrl, false);
869
0
}
870
871
0
void Components::parseModificationLayer(int layer, OUString const & url) {
872
0
    try {
873
0
        parseFileLeniently(&parseXcuFile, url, layer, nullptr, nullptr, nullptr);
874
0
    } catch (css::container::NoSuchElementException &) {
875
0
        SAL_INFO(
876
0
            "configmgr", "user registrymodifications.xcu does not (yet) exist");
877
        // Migrate old user layer data (can be removed once migration is no
878
        // longer relevant, probably OOo 4; also see hack for xsi namespace in
879
        // xmlreader::XmlReader::registerNamespaceIri):
880
0
        parseFiles(
881
0
            layer, u".xcu"_ustr, &parseXcuFile,
882
0
            expand(
883
0
                u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")
884
0
                ":UserInstallation}/user/registry/data"_ustr),
885
0
            false);
886
0
    }
887
0
}
888
889
0
int Components::getExtensionLayer(bool shared) const {
890
0
    int layer = shared ? sharedExtensionLayer_ : userExtensionLayer_;
891
0
    if (layer == -1) {
892
0
        throw css::uno::RuntimeException(
893
0
            u"insert extension xcs/xcu file into undefined layer"_ustr);
894
0
    }
895
0
    return layer;
896
0
}
897
898
}
899
900
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */