Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / configmgr / source / components.cxx
blob939d226e964d29b277dad89649a3d45471baa8cf
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
22 #include <cassert>
23 #include <chrono>
24 #include <vector>
25 #include <set>
27 #include <com/sun/star/beans/Optional.hpp>
28 #include <com/sun/star/beans/UnknownPropertyException.hpp>
29 #include <com/sun/star/beans/XPropertySet.hpp>
30 #include <com/sun/star/container/NoSuchElementException.hpp>
31 #include <com/sun/star/lang/WrappedTargetException.hpp>
32 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
33 #include <com/sun/star/uno/Any.hxx>
34 #include <com/sun/star/uno/Exception.hpp>
35 #include <com/sun/star/uno/Reference.hxx>
36 #include <com/sun/star/uno/RuntimeException.hpp>
37 #include <com/sun/star/uno/XComponentContext.hpp>
38 #include <com/sun/star/uno/XInterface.hpp>
39 #include <cppuhelper/exc_hlp.hxx>
40 #include <config_dconf.h>
41 #include <config_folders.h>
42 #include <osl/conditn.hxx>
43 #include <osl/file.hxx>
44 #include <osl/mutex.hxx>
45 #include <rtl/bootstrap.hxx>
46 #include <rtl/ref.hxx>
47 #include <rtl/ustrbuf.hxx>
48 #include <rtl/ustring.hxx>
49 #include <sal/log.hxx>
50 #include <sal/types.h>
51 #include <salhelper/thread.hxx>
52 #include <tools/diagnose_ex.h>
53 #include <comphelper/backupfilehelper.hxx>
55 #include "additions.hxx"
56 #include "components.hxx"
57 #include "data.hxx"
58 #include "lock.hxx"
59 #include "modifications.hxx"
60 #include "node.hxx"
61 #include "nodemap.hxx"
62 #include "parsemanager.hxx"
63 #include "partial.hxx"
64 #include "rootaccess.hxx"
65 #include "writemodfile.hxx"
66 #include "xcdparser.hxx"
67 #include "xcuparser.hxx"
68 #include "xcsparser.hxx"
70 #if ENABLE_DCONF
71 #include "dconf.hxx"
72 #endif
74 #if defined(_WIN32)
75 #include "winreg.hxx"
76 #endif
78 namespace configmgr {
80 namespace {
82 struct UnresolvedVectorItem {
83 OUString name;
84 rtl::Reference< ParseManager > manager;
86 UnresolvedVectorItem(
87 OUString const & theName,
88 rtl::Reference< ParseManager > const & theManager):
89 name(theName), manager(theManager) {}
92 typedef std::vector< UnresolvedVectorItem > UnresolvedVector;
94 void parseXcsFile(
95 OUString const & url, int layer, Data & data, Partial const * partial,
96 Modifications * modifications, Additions * additions)
98 assert(partial == nullptr && modifications == nullptr && additions == nullptr);
99 (void) partial; (void) modifications; (void) additions;
100 bool ok = rtl::Reference< ParseManager >(
101 new ParseManager(url, new XcsParser(layer, data)))->parse(nullptr);
102 assert(ok);
103 (void) ok; // avoid warnings
106 void parseXcuFile(
107 OUString const & url, int layer, Data & data, Partial const * partial,
108 Modifications * modifications, Additions * additions)
110 bool ok = rtl::Reference< ParseManager >(
111 new ParseManager(
112 url,
113 new XcuParser(layer, data, partial, modifications, additions)))->
114 parse(nullptr);
115 assert(ok);
116 (void) ok; // avoid warnings
119 OUString expand(OUString const & str) {
120 OUString s(str);
121 rtl::Bootstrap::expandMacros(s); //TODO: detect failure
122 return s;
125 bool canRemoveFromLayer(int layer, rtl::Reference< Node > const & node) {
126 assert(node.is());
127 if (node->getLayer() > layer && node->getLayer() < Data::NO_LAYER) {
128 return false;
130 switch (node->kind()) {
131 case Node::KIND_LOCALIZED_PROPERTY:
132 case Node::KIND_GROUP:
133 for (auto const& member : node->getMembers())
135 if (!canRemoveFromLayer(layer, member.second)) {
136 return false;
139 return true;
140 case Node::KIND_SET:
141 return node->getMembers().empty();
142 default: // Node::KIND_PROPERTY, Node::KIND_LOCALIZED_VALUE
143 return true;
149 class Components::WriteThread: public salhelper::Thread {
150 public:
151 WriteThread(
152 rtl::Reference< WriteThread > * reference, Components & components,
153 OUString const & url, Data const & data);
155 void flush() { delay_.set(); }
157 private:
158 virtual ~WriteThread() override {}
160 virtual void execute() override;
162 rtl::Reference< WriteThread > * reference_;
163 Components & components_;
164 OUString url_;
165 Data const & data_;
166 osl::Condition delay_;
167 std::shared_ptr<osl::Mutex> lock_;
170 Components::WriteThread::WriteThread(
171 rtl::Reference< WriteThread > * reference, Components & components,
172 OUString const & url, Data const & data):
173 Thread("configmgrWriter"), reference_(reference), components_(components),
174 url_(url), data_(data),
175 lock_( lock() )
177 assert(reference != nullptr);
180 void Components::WriteThread::execute() {
181 delay_.wait(std::chrono::seconds(1)); // must not throw; result_error is harmless and ignored
182 osl::MutexGuard g(*lock_); // must not throw
183 try {
184 try {
185 writeModFile(components_, url_, data_);
186 } catch (css::uno::RuntimeException &) {
187 // Ignore write errors, instead of aborting:
188 TOOLS_WARN_EXCEPTION("configmgr", "error writing modifications");
190 } catch (...) {
191 reference_->clear();
192 throw;
194 reference_->clear();
197 Components & Components::getSingleton(
198 css::uno::Reference< css::uno::XComponentContext > const & context)
200 assert(context.is());
201 static Components singleton(context);
202 return singleton;
205 bool Components::allLocales(OUString const & locale) {
206 return locale == "*";
209 rtl::Reference< Node > Components::resolvePathRepresentation(
210 OUString const & pathRepresentation,
211 OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer)
212 const
214 return data_.resolvePathRepresentation(
215 pathRepresentation, canonicRepresentation, path, finalizedLayer);
218 rtl::Reference< Node > Components::getTemplate(OUString const & fullName) const
220 return data_.getTemplate(Data::NO_LAYER, fullName);
223 void Components::addRootAccess(rtl::Reference< RootAccess > const & access) {
224 roots_.insert(access.get());
227 void Components::removeRootAccess(RootAccess * access) {
228 roots_.erase(access);
231 void Components::initGlobalBroadcaster(
232 Modifications const & modifications,
233 rtl::Reference< RootAccess > const & exclude, Broadcaster * broadcaster)
235 //TODO: Iterate only over roots w/ listeners:
236 for (auto const& elemRoot : roots_)
238 rtl::Reference< RootAccess > root;
239 if (elemRoot->acquireCounting() > 1) {
240 root.set(elemRoot); // must not throw
242 elemRoot->releaseNondeleting();
243 if (root.is()) {
244 if (root != exclude) {
245 std::vector<OUString> path(root->getAbsolutePath());
246 Modifications::Node const * mods = &modifications.getRoot();
247 for (auto const& pathElem : path)
249 Modifications::Node::Children::const_iterator k(
250 mods->children.find(pathElem));
251 if (k == mods->children.end()) {
252 mods = nullptr;
253 break;
255 mods = &k->second;
257 //TODO: If the complete tree of which root is a part is deleted,
258 // or replaced, mods will be null, but some of the listeners
259 // from within root should probably fire nonetheless:
260 if (mods != nullptr) {
261 root->initBroadcaster(*mods, broadcaster);
268 void Components::addModification(std::vector<OUString> const & path) {
269 data_.modifications.add(path);
272 void Components::writeModifications() {
274 if (!data_.modifications.empty()) {
275 switch (modificationTarget_) {
276 case ModificationTarget::None:
277 break;
278 case ModificationTarget::File:
279 if (!writeThread_.is()) {
280 writeThread_ = new WriteThread(
281 &writeThread_, *this, modificationFileUrl_, data_);
282 writeThread_->launch();
284 break;
285 case ModificationTarget::Dconf:
286 #if ENABLE_DCONF
287 dconf::writeModifications(*this, data_);
288 #endif
289 break;
294 void Components::flushModifications() {
295 rtl::Reference< WriteThread > thread;
297 osl::MutexGuard g(*lock_);
298 thread = writeThread_;
300 if (thread.is()) {
301 thread->flush();
302 thread->join();
306 void Components::insertExtensionXcsFile(
307 bool shared, OUString const & fileUri)
309 int layer = getExtensionLayer(shared);
310 try {
311 parseXcsFile(fileUri, layer, data_, nullptr, nullptr, nullptr);
312 } catch (css::container::NoSuchElementException & e) {
313 throw css::uno::RuntimeException(
314 "insertExtensionXcsFile does not exist: " + e.Message);
318 void Components::insertExtensionXcuFile(
319 bool shared, OUString const & fileUri, Modifications * modifications)
321 assert(modifications != nullptr);
322 int layer = getExtensionLayer(shared) + 1;
323 Additions * adds = data_.addExtensionXcuAdditions(fileUri, layer);
324 try {
325 parseXcuFile(fileUri, layer, data_, nullptr, modifications, adds);
326 } catch (css::container::NoSuchElementException & e) {
327 data_.removeExtensionXcuAdditions(fileUri);
328 throw css::uno::RuntimeException(
329 "insertExtensionXcuFile does not exist: " + e.Message);
333 void Components::removeExtensionXcuFile(
334 OUString const & fileUri, Modifications * modifications)
336 //TODO: Ideally, exactly the data coming from the specified xcu file would
337 // be removed. However, not enough information is recorded in the in-memory
338 // data structures to do so. So, as a workaround, all those set elements
339 // that were freshly added by the xcu and have afterwards been left
340 // unchanged or have only had their properties changed in the user layer are
341 // removed (and nothing else). The heuristic to determine
342 // whether a node has been left unchanged is to check the layer ID (as
343 // usual) and additionally to check that the node does not recursively
344 // contain any non-empty sets (multiple extension xcu files are merged into
345 // one layer, so checking layer ID alone is not enough). Since
346 // item->additions records all additions of set members in textual order,
347 // the latter check works well when iterating through item->additions in
348 // reverse order.
349 assert(modifications != nullptr);
350 rtl::Reference< Data::ExtensionXcu > item(
351 data_.removeExtensionXcuAdditions(fileUri));
352 if (item.is()) {
353 for (Additions::reverse_iterator i(item->additions.rbegin());
354 i != item->additions.rend(); ++i)
356 rtl::Reference< Node > parent;
357 NodeMap const * map = &data_.getComponents();
358 rtl::Reference< Node > node;
359 for (auto const& j : *i)
361 parent = node;
362 node = map->findNode(Data::NO_LAYER, j);
363 if (!node.is()) {
364 break;
366 map = &node->getMembers();
368 if (node.is()) {
369 assert(parent.is());
370 if (parent->kind() == Node::KIND_SET) {
371 assert(
372 node->kind() == Node::KIND_GROUP ||
373 node->kind() == Node::KIND_SET);
374 if (canRemoveFromLayer(item->layer, node)) {
375 parent->getMembers().erase(i->back());
376 data_.modifications.remove(*i);
377 modifications->add(*i);
382 writeModifications();
386 void Components::insertModificationXcuFile(
387 OUString const & fileUri,
388 std::set< OUString > const & includedPaths,
389 std::set< OUString > const & excludedPaths,
390 Modifications * modifications)
392 assert(modifications != nullptr);
393 Partial part(includedPaths, excludedPaths);
394 try {
395 parseFileLeniently(
396 &parseXcuFile, fileUri, Data::NO_LAYER, &part, modifications, nullptr);
397 } catch (const css::container::NoSuchElementException &) {
398 TOOLS_WARN_EXCEPTION(
399 "configmgr",
400 "error inserting non-existing \"" << fileUri << "\"");
404 css::beans::Optional< css::uno::Any > Components::getExternalValue(
405 OUString const & descriptor)
407 sal_Int32 i = descriptor.indexOf(' ');
408 if (i <= 0) {
409 throw css::uno::RuntimeException(
410 "bad external value descriptor " + descriptor);
412 //TODO: Do not make calls with mutex locked:
413 OUString name(descriptor.copy(0, i));
414 ExternalServices::iterator j(externalServices_.find(name));
415 if (j == externalServices_.end()) {
416 css::uno::Reference< css::uno::XInterface > service;
417 try {
418 service = context_->getServiceManager()->createInstanceWithContext(
419 name, context_);
420 } catch (const css::uno::RuntimeException &) {
421 // Assuming these exceptions are real errors:
422 throw;
423 } catch (const css::uno::Exception &) {
424 // Assuming these exceptions indicate that the service is not
425 // installed:
426 TOOLS_WARN_EXCEPTION(
427 "configmgr",
428 "createInstance(" << name << ") failed");
430 css::uno::Reference< css::beans::XPropertySet > propset;
431 if (service.is()) {
432 propset.set( service, css::uno::UNO_QUERY_THROW);
434 j = externalServices_.emplace(name, propset).first;
436 css::beans::Optional< css::uno::Any > value;
437 if (j->second.is()) {
438 try {
439 if (!(j->second->getPropertyValue(descriptor.copy(i + 1)) >>=
440 value))
442 throw css::uno::RuntimeException(
443 "cannot obtain external value through " + descriptor);
445 } catch (css::beans::UnknownPropertyException & e) {
446 throw css::uno::RuntimeException(
447 "unknown external value descriptor ID: " + e.Message);
448 } catch (css::lang::WrappedTargetException & e) {
449 css::uno::Any anyEx = cppu::getCaughtException();
450 throw css::lang::WrappedTargetRuntimeException(
451 "cannot obtain external value: " + e.Message,
452 nullptr, anyEx );
455 return value;
458 Components::Components(
459 css::uno::Reference< css::uno::XComponentContext > const & context):
460 context_(context), sharedExtensionLayer_(-1), userExtensionLayer_(-1),
461 modificationTarget_(ModificationTarget::None)
463 assert(context.is());
464 lock_ = lock();
465 OUString conf(expand("${CONFIGURATION_LAYERS}"));
466 int layer = 0;
467 for (sal_Int32 i = 0;;) {
468 while (i != conf.getLength() && conf[i] == ' ') {
469 ++i;
471 if (i == conf.getLength()) {
472 break;
474 if (modificationTarget_ != ModificationTarget::None) {
475 throw css::uno::RuntimeException(
476 "CONFIGURATION_LAYERS: modification target layer followed by"
477 " further layers");
479 sal_Int32 c = i;
480 for (;; ++c) {
481 if (c == conf.getLength() || conf[c] == ' ') {
482 throw css::uno::RuntimeException(
483 "CONFIGURATION_LAYERS: missing ':' in \"" + conf + "\"");
485 if (conf[c] == ':') {
486 break;
489 sal_Int32 n = conf.indexOf(' ', c + 1);
490 if (n == -1) {
491 n = conf.getLength();
493 OUString type(conf.copy(i, c - i));
494 OUString url(conf.copy(c + 1, n - c - 1));
495 if (type == "xcsxcu") {
496 sal_uInt32 nStartTime = osl_getGlobalTimer();
497 parseXcsXcuLayer(layer, url);
498 SAL_INFO("configmgr", "parseXcsXcuLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
499 layer += 2; //TODO: overflow
500 } else if (type == "bundledext") {
501 parseXcsXcuIniLayer(layer, url, false);
502 layer += 2; //TODO: overflow
503 } else if (type == "sharedext") {
504 if (sharedExtensionLayer_ != -1) {
505 throw css::uno::RuntimeException(
506 "CONFIGURATION_LAYERS: multiple \"sharedext\" layers");
508 sharedExtensionLayer_ = layer;
509 parseXcsXcuIniLayer(layer, url, true);
510 layer += 2; //TODO: overflow
511 } else if (type == "userext") {
512 if (userExtensionLayer_ != -1) {
513 throw css::uno::RuntimeException(
514 "CONFIGURATION_LAYERS: multiple \"userext\" layers");
516 userExtensionLayer_ = layer;
517 parseXcsXcuIniLayer(layer, url, true);
518 layer += 2; //TODO: overflow
519 } else if (type == "module") {
520 parseModuleLayer(layer, url);
521 ++layer; //TODO: overflow
522 } else if (type == "res") {
523 sal_uInt32 nStartTime = osl_getGlobalTimer();
524 parseResLayer(layer, url);
525 SAL_INFO("configmgr", "parseResLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
526 ++layer; //TODO: overflow
527 #if ENABLE_DCONF
528 } else if (type == "dconf") {
529 if (url == "!") {
530 modificationTarget_ = ModificationTarget::Dconf;
531 dconf::readLayer(data_, Data::NO_LAYER);
532 } else if (url == "*") {
533 dconf::readLayer(data_, layer);
534 } else {
535 throw css::uno::RuntimeException(
536 "CONFIGURATION_LAYERS: unknown \"dconf\" kind \"" + url
537 + "\"");
539 ++layer; //TODO: overflow
540 #endif
541 #if defined(_WIN32)
542 } else if (type == "winreg") {
543 WinRegType eType;
544 if (url == "LOCAL_MACHINE" || url.isEmpty()/*backwards comp.*/) {
545 eType = WinRegType::LOCAL_MACHINE;
546 } else if (url == "CURRENT_USER") {
547 eType = WinRegType::CURRENT_USER;
548 } else {
549 throw css::uno::RuntimeException(
550 "CONFIGURATION_LAYERS: unknown \"winreg\" kind \"" + url
551 + "\"");
553 OUString aTempFileURL;
554 if (dumpWindowsRegistry(&aTempFileURL, eType)) {
555 parseFileLeniently(&parseXcuFile, aTempFileURL, layer, nullptr, nullptr, nullptr);
556 if (!getenv("SAL_CONFIG_WINREG_RETAIN_TMP"))
557 osl::File::remove(aTempFileURL);
559 ++layer; //TODO: overflow
560 #endif
561 } else if (type == "user") {
562 bool write;
563 if (url.startsWith("!", &url)) {
564 write = true;
565 } else if (url.startsWith("*", &url)) {
566 write = false;
567 } else {
568 write = true; // for backwards compatibility
570 if (url.isEmpty()) {
571 throw css::uno::RuntimeException(
572 "CONFIGURATION_LAYERS: empty \"user\" URL");
574 bool ignore = false;
575 #if ENABLE_DCONF
576 if (write) {
577 OUString token(
578 expand("${SYSUSERCONFIG}/libreoffice/dconfwrite"));
579 osl::DirectoryItem it;
580 osl::FileBase::RC e = osl::DirectoryItem::get(token, it);
581 ignore = e == osl::FileBase::E_None;
582 SAL_INFO(
583 "configmgr",
584 "dconf write (<" << token << "> " << +e << "): "
585 << int(ignore));
586 if (ignore) {
587 modificationTarget_ = ModificationTarget::Dconf;
590 #endif
591 if (!ignore) {
592 if (write) {
593 modificationTarget_ = ModificationTarget::File;
594 modificationFileUrl_ = url;
596 parseModificationLayer(write ? Data::NO_LAYER : layer, url);
598 ++layer; //TODO: overflow
599 } else {
600 throw css::uno::RuntimeException(
601 "CONFIGURATION_LAYERS: unknown layer type \"" + type + "\"");
603 i = n;
607 Components::~Components()
609 // get flag if _exit was already called which is a sign to not secure user config.
610 // this is used for win only currently where calling _exit() unfortunately still
611 // calls destructors (what is not wanted). May be needed for other systems, too
612 // (unknown yet) but can do no harm
613 const bool bExitWasCalled(comphelper::BackupFileHelper::getExitWasCalled());
615 #ifndef WNT
616 // we can add a SAL_WARN here for other systems where the destructor gets called after
617 // an _exit() call. Still safe - the getExitWasCalled() is used, but a hint that _exit
618 // behaves different on a system
619 SAL_WARN_IF(bExitWasCalled, "configmgr", "Components::~Components() called after _exit() call");
620 #endif
622 if (bExitWasCalled)
624 // do not write, re-join threads
625 osl::MutexGuard g(*lock_);
627 if (writeThread_.is())
629 writeThread_->join();
632 else
634 // write changes
635 flushModifications();
638 for (auto const& rootElem : roots_)
640 rootElem->setAlive(false);
644 void Components::parseFileLeniently(
645 FileParser * parseFile, OUString const & url, int layer,
646 Partial const * partial, Modifications * modifications,
647 Additions * additions)
649 assert(parseFile != nullptr);
650 try {
651 (*parseFile)(url, layer, data_, partial, modifications, additions);
652 } catch (const css::container::NoSuchElementException &) {
653 throw;
654 } catch (const css::uno::Exception &) { //TODO: more specific exception catching
655 // Ignore invalid XML files, instead of completely preventing OOo from
656 // starting:
657 TOOLS_WARN_EXCEPTION(
658 "configmgr",
659 "error reading \"" << url << "\"");
663 void Components::parseFiles(
664 int layer, OUString const & extension, FileParser * parseFile,
665 OUString const & url, bool recursive)
667 osl::Directory dir(url);
668 switch (dir.open()) {
669 case osl::FileBase::E_None:
670 break;
671 case osl::FileBase::E_NOENT:
672 if (!recursive) {
673 return;
675 [[fallthrough]];
676 default:
677 throw css::uno::RuntimeException(
678 "cannot open directory " + url);
680 for (;;) {
681 osl::DirectoryItem i;
682 osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
683 if (rc == osl::FileBase::E_NOENT) {
684 break;
686 if (rc != osl::FileBase::E_None) {
687 throw css::uno::RuntimeException(
688 "cannot iterate directory " + url);
690 osl::FileStatus stat(
691 osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
692 osl_FileStatus_Mask_FileURL);
693 if (i.getFileStatus(stat) != osl::FileBase::E_None) {
694 throw css::uno::RuntimeException(
695 "cannot stat in directory " + url);
697 if (stat.getFileType() == osl::FileStatus::Directory) { //TODO: symlinks
698 parseFiles(layer, extension, parseFile, stat.getFileURL(), true);
699 } else {
700 OUString file(stat.getFileName());
701 if (file.endsWith(extension)) {
702 try {
703 parseFileLeniently(
704 parseFile, stat.getFileURL(), layer, nullptr, nullptr, nullptr);
705 } catch (css::container::NoSuchElementException & e) {
706 if (stat.getFileType() == osl::FileStatus::Link) {
707 SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
708 continue;
710 throw css::uno::RuntimeException(
711 "stat'ed file does not exist: " + e.Message);
718 void Components::parseFileList(
719 int layer, FileParser * parseFile, OUString const & urls,
720 bool recordAdditions)
722 for (sal_Int32 i = 0;;) {
723 OUString url(urls.getToken(0, ' ', i));
724 if (!url.isEmpty()) {
725 Additions * adds = nullptr;
726 if (recordAdditions) {
727 adds = data_.addExtensionXcuAdditions(url, layer);
729 try {
730 parseFileLeniently(parseFile, url, layer, nullptr, nullptr, adds);
731 } catch (const css::container::NoSuchElementException &) {
732 TOOLS_WARN_EXCEPTION("configmgr", "file does not exist");
733 if (adds != nullptr) {
734 data_.removeExtensionXcuAdditions(url);
738 if (i == -1) {
739 break;
744 void Components::parseXcdFiles(int layer, OUString const & url) {
745 osl::Directory dir(url);
746 switch (dir.open()) {
747 case osl::FileBase::E_None:
748 break;
749 case osl::FileBase::E_NOENT:
750 return;
751 default:
752 throw css::uno::RuntimeException(
753 "cannot open directory " + url);
755 UnresolvedVector unres;
756 std::set< OUString > existingDeps;
757 std::set< OUString > processedDeps;
758 for (;;) {
759 osl::DirectoryItem i;
760 osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
761 if (rc == osl::FileBase::E_NOENT) {
762 break;
764 if (rc != osl::FileBase::E_None) {
765 throw css::uno::RuntimeException(
766 "cannot iterate directory " + url);
768 osl::FileStatus stat(
769 osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
770 osl_FileStatus_Mask_FileURL);
771 if (i.getFileStatus(stat) != osl::FileBase::E_None) {
772 throw css::uno::RuntimeException(
773 "cannot stat in directory " + url);
775 if (stat.getFileType() != osl::FileStatus::Directory) { //TODO: symlinks
776 OUString file(stat.getFileName());
777 OUString name;
778 if (file.endsWith(".xcd", &name)) {
779 existingDeps.insert(name);
780 rtl::Reference< ParseManager > manager;
781 try {
782 manager = new ParseManager(
783 stat.getFileURL(),
784 new XcdParser(layer, processedDeps, data_));
785 } catch (css::container::NoSuchElementException & e) {
786 if (stat.getFileType() == osl::FileStatus::Link) {
787 SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
788 continue;
790 throw css::uno::RuntimeException(
791 "stat'ed file does not exist: " + e.Message);
793 if (manager->parse(nullptr)) {
794 processedDeps.insert(name);
795 } else {
796 unres.emplace_back(name, manager);
801 while (!unres.empty()) {
802 bool isResolved = false;
803 for (UnresolvedVector::iterator i(unres.begin()); i != unres.end();) {
804 if (i->manager->parse(&existingDeps)) {
805 processedDeps.insert(i->name);
806 i = unres.erase(i);
807 isResolved = true;
808 } else {
809 ++i;
812 if (!isResolved) {
813 throw css::uno::RuntimeException(
814 "xcd: unresolved dependencies in " + url);
819 void Components::parseXcsXcuLayer(int layer, OUString const & url) {
820 parseXcdFiles(layer, url);
821 parseFiles(layer, ".xcs", &parseXcsFile, url + "/schema", false);
822 parseFiles(layer + 1, ".xcu", &parseXcuFile, url + "/data", false);
825 void Components::parseXcsXcuIniLayer(
826 int layer, OUString const & url, bool recordAdditions)
828 // Check if ini file exists (otherwise .override would still read global
829 // SCHEMA/DATA variables, which could interfere with unrelated environment
830 // variables):
831 if (rtl::Bootstrap(url).getHandle() != nullptr) {
832 OUStringBuffer prefix("${.override:");
833 for (sal_Int32 i = 0; i != url.getLength(); ++i) {
834 sal_Unicode c = url[i];
835 switch (c) {
836 case '$':
837 case ':':
838 case '\\':
839 prefix.append('\\');
840 [[fallthrough]];
841 default:
842 prefix.append(c);
845 prefix.append(':');
846 OUString urls(prefix.toString() + "SCHEMA}");
847 rtl::Bootstrap::expandMacros(urls);
848 if (!urls.isEmpty()) {
849 parseFileList(layer, &parseXcsFile, urls, false);
851 urls = prefix.makeStringAndClear() + "DATA}";
852 rtl::Bootstrap::expandMacros(urls);
853 if (!urls.isEmpty()) {
854 parseFileList(layer + 1, &parseXcuFile, urls, recordAdditions);
859 void Components::parseModuleLayer(int layer, OUString const & url) {
860 parseFiles(layer, ".xcu", &parseXcuFile, url, false);
863 void Components::parseResLayer(int layer, OUString const & url) {
864 OUString resUrl(url + "/res");
865 parseXcdFiles(layer, resUrl);
866 parseFiles(layer, ".xcu", &parseXcuFile, resUrl, false);
869 void Components::parseModificationLayer(int layer, OUString const & url) {
870 try {
871 parseFileLeniently(&parseXcuFile, url, layer, nullptr, nullptr, nullptr);
872 } catch (css::container::NoSuchElementException &) {
873 SAL_INFO(
874 "configmgr", "user registrymodifications.xcu does not (yet) exist");
875 // Migrate old user layer data (can be removed once migration is no
876 // longer relevant, probably OOo 4; also see hack for xsi namespace in
877 // xmlreader::XmlReader::registerNamespaceIri):
878 parseFiles(
879 layer, ".xcu", &parseXcuFile,
880 expand(
881 "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")
882 ":UserInstallation}/user/registry/data"),
883 false);
887 int Components::getExtensionLayer(bool shared) const {
888 int layer = shared ? sharedExtensionLayer_ : userExtensionLayer_;
889 if (layer == -1) {
890 throw css::uno::RuntimeException(
891 "insert extension xcs/xcu file into undefined layer");
893 return layer;
898 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */