Bump version to 6.0-36
[LibreOffice.git] / configmgr / source / components.cxx
blobdc4374d60ec6884febdc1abb42f9f30b1f348c74
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 <algorithm>
23 #include <cassert>
24 #include <chrono>
25 #include <cstddef>
26 #include <vector>
27 #include <set>
29 #include <com/sun/star/beans/Optional.hpp>
30 #include <com/sun/star/beans/UnknownPropertyException.hpp>
31 #include <com/sun/star/beans/XPropertySet.hpp>
32 #include <com/sun/star/container/NoSuchElementException.hpp>
33 #include <com/sun/star/lang/WrappedTargetException.hpp>
34 #include <com/sun/star/uno/Any.hxx>
35 #include <com/sun/star/uno/Exception.hpp>
36 #include <com/sun/star/uno/Reference.hxx>
37 #include <com/sun/star/uno/RuntimeException.hpp>
38 #include <com/sun/star/uno/XComponentContext.hpp>
39 #include <com/sun/star/uno/XInterface.hpp>
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 <rtl/instance.hxx>
50 #include <sal/log.hxx>
51 #include <sal/types.h>
52 #include <salhelper/thread.hxx>
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 (NodeMap::const_iterator i(node->getMembers().begin());
134 i != node->getMembers().end(); ++i)
136 if (!canRemoveFromLayer(layer, i->second)) {
137 return false;
140 return true;
141 case Node::KIND_SET:
142 return node->getMembers().empty();
143 default: // Node::KIND_PROPERTY, Node::KIND_LOCALIZED_VALUE
144 return true;
150 class Components::WriteThread: public salhelper::Thread {
151 public:
152 WriteThread(
153 rtl::Reference< WriteThread > * reference, Components & components,
154 OUString const & url, Data const & data);
156 void flush() { delay_.set(); }
158 private:
159 virtual ~WriteThread() override {}
161 virtual void execute() override;
163 rtl::Reference< WriteThread > * reference_;
164 Components & components_;
165 OUString url_;
166 Data const & data_;
167 osl::Condition delay_;
168 std::shared_ptr<osl::Mutex> lock_;
171 Components::WriteThread::WriteThread(
172 rtl::Reference< WriteThread > * reference, Components & components,
173 OUString const & url, Data const & data):
174 Thread("configmgrWriter"), reference_(reference), components_(components),
175 url_(url), data_(data),
176 lock_( lock() )
178 assert(reference != nullptr);
181 void Components::WriteThread::execute() {
182 delay_.wait(std::chrono::seconds(1)); // must not throw; result_error is harmless and ignored
183 osl::MutexGuard g(*lock_); // must not throw
184 try {
185 try {
186 writeModFile(components_, url_, data_);
187 } catch (css::uno::RuntimeException & e) {
188 // Ignore write errors, instead of aborting:
189 SAL_WARN("configmgr", "error writing modifications: " << e);
191 } catch (...) {
192 reference_->clear();
193 throw;
195 reference_->clear();
198 class theComponentsSingleton :
199 public rtl::StaticWithArg<
200 Components,
201 css::uno::Reference< css::uno::XComponentContext >,
202 theComponentsSingleton>
206 Components & Components::getSingleton(
207 css::uno::Reference< css::uno::XComponentContext > const & context)
209 assert(context.is());
210 return theComponentsSingleton::get(context);
213 bool Components::allLocales(OUString const & locale) {
214 return locale == "*";
217 rtl::Reference< Node > Components::resolvePathRepresentation(
218 OUString const & pathRepresentation,
219 OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer)
220 const
222 return data_.resolvePathRepresentation(
223 pathRepresentation, canonicRepresentation, path, finalizedLayer);
226 rtl::Reference< Node > Components::getTemplate(OUString const & fullName) const
228 return data_.getTemplate(Data::NO_LAYER, fullName);
231 void Components::addRootAccess(rtl::Reference< RootAccess > const & access) {
232 roots_.insert(access.get());
235 void Components::removeRootAccess(RootAccess * access) {
236 roots_.erase(access);
239 void Components::initGlobalBroadcaster(
240 Modifications const & modifications,
241 rtl::Reference< RootAccess > const & exclude, Broadcaster * broadcaster)
243 //TODO: Iterate only over roots w/ listeners:
244 for (WeakRootSet::iterator i(roots_.begin()); i != roots_.end(); ++i) {
245 rtl::Reference< RootAccess > root;
246 if ((*i)->acquireCounting() > 1) {
247 root.set(*i); // must not throw
249 (*i)->releaseNondeleting();
250 if (root.is()) {
251 if (root != exclude) {
252 std::vector<OUString> path(root->getAbsolutePath());
253 Modifications::Node const * mods = &modifications.getRoot();
254 for (auto j(path.begin()); j != path.end(); ++j) {
255 Modifications::Node::Children::const_iterator k(
256 mods->children.find(*j));
257 if (k == mods->children.end()) {
258 mods = nullptr;
259 break;
261 mods = &k->second;
263 //TODO: If the complete tree of which root is a part is deleted,
264 // or replaced, mods will be null, but some of the listeners
265 // from within root should probably fire nonetheless:
266 if (mods != nullptr) {
267 root->initBroadcaster(*mods, broadcaster);
274 void Components::addModification(std::vector<OUString> const & path) {
275 data_.modifications.add(path);
278 void Components::writeModifications() {
280 if (!data_.modifications.empty()) {
281 switch (modificationTarget_) {
282 case ModificationTarget::None:
283 break;
284 case ModificationTarget::File:
285 if (!writeThread_.is()) {
286 writeThread_ = new WriteThread(
287 &writeThread_, *this, modificationFileUrl_, data_);
288 writeThread_->launch();
290 break;
291 case ModificationTarget::Dconf:
292 #if ENABLE_DCONF
293 dconf::writeModifications(*this, data_);
294 #endif
295 break;
300 void Components::flushModifications() {
301 rtl::Reference< WriteThread > thread;
303 osl::MutexGuard g(*lock_);
304 thread = writeThread_;
306 if (thread.is()) {
307 thread->flush();
308 thread->join();
312 void Components::insertExtensionXcsFile(
313 bool shared, OUString const & fileUri)
315 int layer = getExtensionLayer(shared);
316 try {
317 parseXcsFile(fileUri, layer, data_, nullptr, nullptr, nullptr);
318 } catch (css::container::NoSuchElementException & e) {
319 throw css::uno::RuntimeException(
320 "insertExtensionXcsFile does not exist: " + e.Message);
324 void Components::insertExtensionXcuFile(
325 bool shared, OUString const & fileUri, Modifications * modifications)
327 assert(modifications != nullptr);
328 int layer = getExtensionLayer(shared) + 1;
329 Additions * adds = data_.addExtensionXcuAdditions(fileUri, layer);
330 try {
331 parseXcuFile(fileUri, layer, data_, nullptr, modifications, adds);
332 } catch (css::container::NoSuchElementException & e) {
333 data_.removeExtensionXcuAdditions(fileUri);
334 throw css::uno::RuntimeException(
335 "insertExtensionXcuFile does not exist: " + e.Message);
339 void Components::removeExtensionXcuFile(
340 OUString const & fileUri, Modifications * modifications)
342 //TODO: Ideally, exactly the data coming from the specified xcu file would
343 // be removed. However, not enough information is recorded in the in-memory
344 // data structures to do so. So, as a workaround, all those set elements
345 // that were freshly added by the xcu and have afterwards been left
346 // unchanged or have only had their properties changed in the user layer are
347 // removed (and nothing else). The heuristic to determine
348 // whether a node has been left unchanged is to check the layer ID (as
349 // usual) and additionally to check that the node does not recursively
350 // contain any non-empty sets (multiple extension xcu files are merged into
351 // one layer, so checking layer ID alone is not enough). Since
352 // item->additions records all additions of set members in textual order,
353 // the latter check works well when iterating through item->additions in
354 // reverse order.
355 assert(modifications != nullptr);
356 rtl::Reference< Data::ExtensionXcu > item(
357 data_.removeExtensionXcuAdditions(fileUri));
358 if (item.is()) {
359 for (Additions::reverse_iterator i(item->additions.rbegin());
360 i != item->additions.rend(); ++i)
362 rtl::Reference< Node > parent;
363 NodeMap const * map = &data_.getComponents();
364 rtl::Reference< Node > node;
365 for (auto j(i->begin()); j != i->end(); ++j) {
366 parent = node;
367 node = map->findNode(Data::NO_LAYER, *j);
368 if (!node.is()) {
369 break;
371 map = &node->getMembers();
373 if (node.is()) {
374 assert(parent.is());
375 if (parent->kind() == Node::KIND_SET) {
376 assert(
377 node->kind() == Node::KIND_GROUP ||
378 node->kind() == Node::KIND_SET);
379 if (canRemoveFromLayer(item->layer, node)) {
380 parent->getMembers().erase(i->back());
381 data_.modifications.remove(*i);
382 modifications->add(*i);
387 writeModifications();
391 void Components::insertModificationXcuFile(
392 OUString const & fileUri,
393 std::set< OUString > const & includedPaths,
394 std::set< OUString > const & excludedPaths,
395 Modifications * modifications)
397 assert(modifications != nullptr);
398 Partial part(includedPaths, excludedPaths);
399 try {
400 parseFileLeniently(
401 &parseXcuFile, fileUri, Data::NO_LAYER, &part, modifications, nullptr);
402 } catch (css::container::NoSuchElementException & e) {
403 SAL_WARN(
404 "configmgr",
405 "error inserting non-existing \"" << fileUri << "\": " << e);
409 css::beans::Optional< css::uno::Any > Components::getExternalValue(
410 OUString const & descriptor)
412 sal_Int32 i = descriptor.indexOf(' ');
413 if (i <= 0) {
414 throw css::uno::RuntimeException(
415 "bad external value descriptor " + descriptor);
417 //TODO: Do not make calls with mutex locked:
418 OUString name(descriptor.copy(0, i));
419 ExternalServices::iterator j(externalServices_.find(name));
420 if (j == externalServices_.end()) {
421 css::uno::Reference< css::uno::XInterface > service;
422 try {
423 service = context_->getServiceManager()->createInstanceWithContext(
424 name, context_);
425 } catch (css::uno::RuntimeException &) {
426 // Assuming these exceptions are real errors:
427 throw;
428 } catch (css::uno::Exception & e) {
429 // Assuming these exceptions indicate that the service is not
430 // installed:
431 SAL_WARN(
432 "configmgr",
433 "createInstance(" << name << ") failed with " << e);
435 css::uno::Reference< css::beans::XPropertySet > propset;
436 if (service.is()) {
437 propset.set( service, css::uno::UNO_QUERY_THROW);
439 j = externalServices_.emplace(name, propset).first;
441 css::beans::Optional< css::uno::Any > value;
442 if (j->second.is()) {
443 try {
444 if (!(j->second->getPropertyValue(descriptor.copy(i + 1)) >>=
445 value))
447 throw css::uno::RuntimeException(
448 "cannot obtain external value through " + descriptor);
450 } catch (css::beans::UnknownPropertyException & e) {
451 throw css::uno::RuntimeException(
452 "unknown external value descriptor ID: " + e.Message);
453 } catch (css::lang::WrappedTargetException & e) {
454 throw css::uno::RuntimeException(
455 "cannot obtain external value: " + e.Message);
458 return value;
461 Components::Components(
462 css::uno::Reference< css::uno::XComponentContext > const & context):
463 context_(context), sharedExtensionLayer_(-1), userExtensionLayer_(-1),
464 modificationTarget_(ModificationTarget::None)
466 assert(context.is());
467 lock_ = lock();
468 OUString conf(expand("${CONFIGURATION_LAYERS}"));
469 int layer = 0;
470 for (sal_Int32 i = 0;;) {
471 while (i != conf.getLength() && conf[i] == ' ') {
472 ++i;
474 if (i == conf.getLength()) {
475 break;
477 if (modificationTarget_ != ModificationTarget::None) {
478 throw css::uno::RuntimeException(
479 "CONFIGURATION_LAYERS: modification target layer followed by"
480 " further layers");
482 sal_Int32 c = i;
483 for (;; ++c) {
484 if (c == conf.getLength() || conf[c] == ' ') {
485 throw css::uno::RuntimeException(
486 "CONFIGURATION_LAYERS: missing ':' in \"" + conf + "\"");
488 if (conf[c] == ':') {
489 break;
492 sal_Int32 n = conf.indexOf(' ', c + 1);
493 if (n == -1) {
494 n = conf.getLength();
496 OUString type(conf.copy(i, c - i));
497 OUString url(conf.copy(c + 1, n - c - 1));
498 if (type == "xcsxcu") {
499 sal_uInt32 nStartTime = osl_getGlobalTimer();
500 parseXcsXcuLayer(layer, url);
501 SAL_INFO("configmgr", "parseXcsXcuLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
502 layer += 2; //TODO: overflow
503 } else if (type == "bundledext") {
504 parseXcsXcuIniLayer(layer, url, false);
505 layer += 2; //TODO: overflow
506 } else if (type == "sharedext") {
507 if (sharedExtensionLayer_ != -1) {
508 throw css::uno::RuntimeException(
509 "CONFIGURATION_LAYERS: multiple \"sharedext\" layers");
511 sharedExtensionLayer_ = layer;
512 parseXcsXcuIniLayer(layer, url, true);
513 layer += 2; //TODO: overflow
514 } else if (type == "userext") {
515 if (userExtensionLayer_ != -1) {
516 throw css::uno::RuntimeException(
517 "CONFIGURATION_LAYERS: multiple \"userext\" layers");
519 userExtensionLayer_ = layer;
520 parseXcsXcuIniLayer(layer, url, true);
521 layer += 2; //TODO: overflow
522 } else if (type == "module") {
523 parseModuleLayer(layer, url);
524 ++layer; //TODO: overflow
525 } else if (type == "res") {
526 sal_uInt32 nStartTime = osl_getGlobalTimer();
527 parseResLayer(layer, url);
528 SAL_INFO("configmgr", "parseResLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
529 ++layer; //TODO: overflow
530 #if ENABLE_DCONF
531 } else if (type == "dconf") {
532 if (url == "!") {
533 modificationTarget_ = ModificationTarget::Dconf;
534 dconf::readLayer(data_, Data::NO_LAYER);
535 } else if (url == "*") {
536 dconf::readLayer(data_, layer);
537 } else {
538 throw css::uno::RuntimeException(
539 "CONFIGURATION_LAYERS: unknown \"dconf\" kind \"" + url
540 + "\"");
542 ++layer; //TODO: overflow
543 #endif
544 #if defined(_WIN32)
545 } else if (type == "winreg") {
546 WinRegType eType;
547 if (url == "LOCAL_MACHINE" || url.isEmpty()/*backwards comp.*/) {
548 eType = WinRegType::LOCAL_MACHINE;
549 } else if (url == "CURRENT_USER") {
550 eType = WinRegType::CURRENT_USER;
551 } else {
552 throw css::uno::RuntimeException(
553 "CONFIGURATION_LAYERS: unknown \"winreg\" kind \"" + url
554 + "\"");
556 OUString aTempFileURL;
557 if (dumpWindowsRegistry(&aTempFileURL, eType)) {
558 parseFileLeniently(&parseXcuFile, aTempFileURL, layer, nullptr, nullptr, nullptr);
559 if (!getenv("SAL_CONFIG_WINREG_RETAIN_TMP"))
560 osl::File::remove(aTempFileURL);
562 ++layer; //TODO: overflow
563 #endif
564 } else if (type == "user") {
565 bool write;
566 if (url.startsWith("!", &url)) {
567 write = true;
568 } else if (url.startsWith("*", &url)) {
569 write = false;
570 } else {
571 write = true; // for backwards compatibility
573 if (url.isEmpty()) {
574 throw css::uno::RuntimeException(
575 "CONFIGURATION_LAYERS: empty \"user\" URL");
577 bool ignore = false;
578 #if ENABLE_DCONF
579 if (write) {
580 OUString token(
581 expand("${SYSUSERCONFIG}/libreoffice/dconfwrite"));
582 osl::DirectoryItem it;
583 osl::FileBase::RC e = osl::DirectoryItem::get(token, it);
584 ignore = e == osl::FileBase::E_None;
585 SAL_INFO(
586 "configmgr",
587 "dconf write (<" << token << "> " << +e << "): "
588 << int(ignore));
589 if (ignore) {
590 modificationTarget_ = ModificationTarget::Dconf;
593 #endif
594 if (!ignore) {
595 if (write) {
596 modificationTarget_ = ModificationTarget::File;
597 modificationFileUrl_ = url;
599 parseModificationLayer(write ? Data::NO_LAYER : layer, url);
601 ++layer; //TODO: overflow
602 } else {
603 throw css::uno::RuntimeException(
604 "CONFIGURATION_LAYERS: unknown layer type \"" + type + "\"");
606 i = n;
610 Components::~Components()
612 // get flag if _exit was already called which is a sign to not secure user config.
613 // this is used for win only currently where calling _exit() unfortunately still
614 // calls destructors (what is not wanted). May be needed for other systems, too
615 // (unknown yet) but can do no harm
616 const bool bExitWasCalled(comphelper::BackupFileHelper::getExitWasCalled());
618 #ifndef WNT
619 // we can add a SAL_WARN here for other systems where the destructor gets called after
620 // an _exit() call. Still safe - the getExitWasCalled() is used, but a hint that _exit
621 // behaves different on a system
622 SAL_WARN_IF(bExitWasCalled, "configmgr", "Components::~Components() called after _exit() call");
623 #endif
625 if (bExitWasCalled)
627 // do not write, re-join threads
628 osl::MutexGuard g(*lock_);
630 if (writeThread_.is())
632 writeThread_->join();
635 else
637 // write changes
638 flushModifications();
641 for (WeakRootSet::iterator i(roots_.begin()); i != roots_.end(); ++i) {
642 (*i)->setAlive(false);
646 void Components::parseFileLeniently(
647 FileParser * parseFile, OUString const & url, int layer,
648 Partial const * partial, Modifications * modifications,
649 Additions * additions)
651 assert(parseFile != nullptr);
652 try {
653 (*parseFile)(url, layer, data_, partial, modifications, additions);
654 } catch (css::container::NoSuchElementException &) {
655 throw;
656 } catch (css::uno::Exception & e) { //TODO: more specific exception catching
657 // Ignore invalid XML files, instead of completely preventing OOo from
658 // starting:
659 SAL_WARN(
660 "configmgr",
661 "error reading \"" << url << "\": " << e);
665 void Components::parseFiles(
666 int layer, OUString const & extension, FileParser * parseFile,
667 OUString const & url, bool recursive)
669 osl::Directory dir(url);
670 switch (dir.open()) {
671 case osl::FileBase::E_None:
672 break;
673 case osl::FileBase::E_NOENT:
674 if (!recursive) {
675 return;
677 SAL_FALLTHROUGH;
678 default:
679 throw css::uno::RuntimeException(
680 "cannot open directory " + url);
682 for (;;) {
683 osl::DirectoryItem i;
684 osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
685 if (rc == osl::FileBase::E_NOENT) {
686 break;
688 if (rc != osl::FileBase::E_None) {
689 throw css::uno::RuntimeException(
690 "cannot iterate directory " + url);
692 osl::FileStatus stat(
693 osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
694 osl_FileStatus_Mask_FileURL);
695 if (i.getFileStatus(stat) != osl::FileBase::E_None) {
696 throw css::uno::RuntimeException(
697 "cannot stat in directory " + url);
699 if (stat.getFileType() == osl::FileStatus::Directory) { //TODO: symlinks
700 parseFiles(layer, extension, parseFile, stat.getFileURL(), true);
701 } else {
702 OUString file(stat.getFileName());
703 if (file.endsWith(extension)) {
704 try {
705 parseFileLeniently(
706 parseFile, stat.getFileURL(), layer, nullptr, nullptr, nullptr);
707 } catch (css::container::NoSuchElementException & e) {
708 if (stat.getFileType() == osl::FileStatus::Link) {
709 SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
710 continue;
712 throw css::uno::RuntimeException(
713 "stat'ed file does not exist: " + e.Message);
720 void Components::parseFileList(
721 int layer, FileParser * parseFile, OUString const & urls,
722 bool recordAdditions)
724 for (sal_Int32 i = 0;;) {
725 OUString url(urls.getToken(0, ' ', i));
726 if (!url.isEmpty()) {
727 Additions * adds = nullptr;
728 if (recordAdditions) {
729 adds = data_.addExtensionXcuAdditions(url, layer);
731 try {
732 parseFileLeniently(parseFile, url, layer, nullptr, nullptr, adds);
733 } catch (css::container::NoSuchElementException & e) {
734 SAL_WARN("configmgr", "file does not exist: " << e);
735 if (adds != nullptr) {
736 data_.removeExtensionXcuAdditions(url);
740 if (i == -1) {
741 break;
746 void Components::parseXcdFiles(int layer, OUString const & url) {
747 osl::Directory dir(url);
748 switch (dir.open()) {
749 case osl::FileBase::E_None:
750 break;
751 case osl::FileBase::E_NOENT:
752 return;
753 default:
754 throw css::uno::RuntimeException(
755 "cannot open directory " + url);
757 UnresolvedVector unres;
758 std::set< OUString > existingDeps;
759 std::set< OUString > processedDeps;
760 for (;;) {
761 osl::DirectoryItem i;
762 osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
763 if (rc == osl::FileBase::E_NOENT) {
764 break;
766 if (rc != osl::FileBase::E_None) {
767 throw css::uno::RuntimeException(
768 "cannot iterate directory " + url);
770 osl::FileStatus stat(
771 osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
772 osl_FileStatus_Mask_FileURL);
773 if (i.getFileStatus(stat) != osl::FileBase::E_None) {
774 throw css::uno::RuntimeException(
775 "cannot stat in directory " + url);
777 if (stat.getFileType() != osl::FileStatus::Directory) { //TODO: symlinks
778 OUString file(stat.getFileName());
779 OUString name;
780 if (file.endsWith(".xcd", &name)) {
781 existingDeps.insert(name);
782 rtl::Reference< ParseManager > manager;
783 try {
784 manager = new ParseManager(
785 stat.getFileURL(),
786 new XcdParser(layer, processedDeps, data_));
787 } catch (css::container::NoSuchElementException & e) {
788 if (stat.getFileType() == osl::FileStatus::Link) {
789 SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
790 continue;
792 throw css::uno::RuntimeException(
793 "stat'ed file does not exist: " + e.Message);
795 if (manager->parse(nullptr)) {
796 processedDeps.insert(name);
797 } else {
798 unres.emplace_back(name, manager);
803 while (!unres.empty()) {
804 bool isResolved = false;
805 for (UnresolvedVector::iterator i(unres.begin()); i != unres.end();) {
806 if (i->manager->parse(&existingDeps)) {
807 processedDeps.insert(i->name);
808 i = unres.erase(i);
809 isResolved = true;
810 } else {
811 ++i;
814 if (!isResolved) {
815 throw css::uno::RuntimeException(
816 "xcd: unresolved dependencies in " + url);
821 void Components::parseXcsXcuLayer(int layer, OUString const & url) {
822 parseXcdFiles(layer, url);
823 parseFiles(layer, ".xcs", &parseXcsFile, url + "/schema", false);
824 parseFiles(layer + 1, ".xcu", &parseXcuFile, url + "/data", false);
827 void Components::parseXcsXcuIniLayer(
828 int layer, OUString const & url, bool recordAdditions)
830 // Check if ini file exists (otherwise .override would still read global
831 // SCHEMA/DATA variables, which could interfere with unrelated environment
832 // variables):
833 if (rtl::Bootstrap(url).getHandle() != nullptr) {
834 OUStringBuffer prefix("${.override:");
835 for (sal_Int32 i = 0; i != url.getLength(); ++i) {
836 sal_Unicode c = url[i];
837 switch (c) {
838 case '$':
839 case ':':
840 case '\\':
841 prefix.append('\\');
842 SAL_FALLTHROUGH;
843 default:
844 prefix.append(c);
847 prefix.append(':');
848 OUString urls(prefix.toString() + "SCHEMA}");
849 rtl::Bootstrap::expandMacros(urls);
850 if (!urls.isEmpty()) {
851 parseFileList(layer, &parseXcsFile, urls, false);
853 urls = prefix.makeStringAndClear() + "DATA}";
854 rtl::Bootstrap::expandMacros(urls);
855 if (!urls.isEmpty()) {
856 parseFileList(layer + 1, &parseXcuFile, urls, recordAdditions);
861 void Components::parseModuleLayer(int layer, OUString const & url) {
862 parseFiles(layer, ".xcu", &parseXcuFile, url, false);
865 void Components::parseResLayer(int layer, OUString const & url) {
866 OUString resUrl(url + "/res");
867 parseXcdFiles(layer, resUrl);
868 parseFiles(layer, ".xcu", &parseXcuFile, resUrl, false);
871 void Components::parseModificationLayer(int layer, OUString const & url) {
872 try {
873 parseFileLeniently(&parseXcuFile, url, layer, nullptr, nullptr, nullptr);
874 } catch (css::container::NoSuchElementException &) {
875 SAL_INFO(
876 "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 parseFiles(
881 layer, ".xcu", &parseXcuFile,
882 expand(
883 "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")
884 ":UserInstallation}/user/registry/data"),
885 false);
889 int Components::getExtensionLayer(bool shared) const {
890 int layer = shared ? sharedExtensionLayer_ : userExtensionLayer_;
891 if (layer == -1) {
892 throw css::uno::RuntimeException(
893 "insert extension xcs/xcu file into undefined layer");
895 return layer;
900 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */