summaryrefslogtreecommitdiffstats
path: root/configmgr/source/components.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /configmgr/source/components.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'configmgr/source/components.cxx')
-rw-r--r--configmgr/source/components.cxx895
1 files changed, 895 insertions, 0 deletions
diff --git a/configmgr/source/components.cxx b/configmgr/source/components.cxx
new file mode 100644
index 000000000..0693ec26b
--- /dev/null
+++ b/configmgr/source/components.cxx
@@ -0,0 +1,895 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cassert>
+#include <chrono>
+#include <utility>
+#include <vector>
+#include <set>
+
+#include <com/sun/star/beans/Optional.hpp>
+#include <com/sun/star/beans/UnknownPropertyException.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/lang/WrappedTargetException.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/uno/XInterface.hpp>
+#include <cppuhelper/exc_hlp.hxx>
+#include <config_dconf.h>
+#include <config_folders.h>
+#include <osl/conditn.hxx>
+#include <osl/file.hxx>
+#include <osl/mutex.hxx>
+#include <rtl/bootstrap.hxx>
+#include <rtl/ref.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <salhelper/thread.hxx>
+#include <tools/diagnose_ex.h>
+#include <comphelper/backupfilehelper.hxx>
+#include <o3tl/string_view.hxx>
+
+#include "additions.hxx"
+#include "components.hxx"
+#include "data.hxx"
+#include "lock.hxx"
+#include "modifications.hxx"
+#include "node.hxx"
+#include "nodemap.hxx"
+#include "parsemanager.hxx"
+#include "partial.hxx"
+#include "rootaccess.hxx"
+#include "writemodfile.hxx"
+#include "xcdparser.hxx"
+#include "xcuparser.hxx"
+#include "xcsparser.hxx"
+
+#if ENABLE_DCONF
+#include "dconf.hxx"
+#endif
+
+#if defined(_WIN32)
+#include "winreg.hxx"
+#endif
+
+namespace configmgr {
+
+namespace {
+
+struct UnresolvedVectorItem {
+ OUString name;
+ rtl::Reference< ParseManager > manager;
+
+ UnresolvedVectorItem(
+ OUString theName,
+ rtl::Reference< ParseManager > theManager):
+ name(std::move(theName)), manager(std::move(theManager)) {}
+};
+
+typedef std::vector< UnresolvedVectorItem > UnresolvedVector;
+
+void parseXcsFile(
+ OUString const & url, int layer, Data & data, Partial const * partial,
+ Modifications * modifications, Additions * additions)
+{
+ assert(partial == nullptr && modifications == nullptr && additions == nullptr);
+ (void) partial; (void) modifications; (void) additions;
+ bool ok = rtl::Reference< ParseManager >(
+ new ParseManager(url, new XcsParser(layer, data)))->parse(nullptr);
+ assert(ok);
+ (void) ok; // avoid warnings
+}
+
+void parseXcuFile(
+ OUString const & url, int layer, Data & data, Partial const * partial,
+ Modifications * modifications, Additions * additions)
+{
+ bool ok = rtl::Reference< ParseManager >(
+ new ParseManager(
+ url,
+ new XcuParser(layer, data, partial, modifications, additions)))->
+ parse(nullptr);
+ assert(ok);
+ (void) ok; // avoid warnings
+}
+
+OUString expand(OUString const & str) {
+ OUString s(str);
+ rtl::Bootstrap::expandMacros(s); //TODO: detect failure
+ return s;
+}
+
+bool canRemoveFromLayer(int layer, rtl::Reference< Node > const & node) {
+ assert(node.is());
+ if (node->getLayer() > layer && node->getLayer() < Data::NO_LAYER) {
+ return false;
+ }
+ switch (node->kind()) {
+ case Node::KIND_LOCALIZED_PROPERTY:
+ case Node::KIND_GROUP:
+ for (auto const& member : node->getMembers())
+ {
+ if (!canRemoveFromLayer(layer, member.second)) {
+ return false;
+ }
+ }
+ return true;
+ case Node::KIND_SET:
+ return node->getMembers().empty();
+ default: // Node::KIND_PROPERTY, Node::KIND_LOCALIZED_VALUE
+ return true;
+ }
+}
+
+}
+
+class Components::WriteThread: public salhelper::Thread {
+public:
+ WriteThread(
+ rtl::Reference< WriteThread > * reference, Components & components,
+ OUString url, Data const & data);
+
+ void flush() { delay_.set(); }
+
+private:
+ virtual ~WriteThread() override {}
+
+ virtual void execute() override;
+
+ rtl::Reference< WriteThread > * reference_;
+ Components & components_;
+ OUString url_;
+ Data const & data_;
+ osl::Condition delay_;
+ std::shared_ptr<osl::Mutex> lock_;
+};
+
+Components::WriteThread::WriteThread(
+ rtl::Reference< WriteThread > * reference, Components & components,
+ OUString url, Data const & data):
+ Thread("configmgrWriter"), reference_(reference), components_(components),
+ url_(std::move(url)), data_(data),
+ lock_( lock() )
+{
+ assert(reference != nullptr);
+}
+
+void Components::WriteThread::execute() {
+ delay_.wait(std::chrono::seconds(1)); // must not throw; result_error is harmless and ignored
+ osl::MutexGuard g(*lock_); // must not throw
+ try {
+ try {
+ writeModFile(components_, url_, data_);
+ } catch (css::uno::RuntimeException &) {
+ // Ignore write errors, instead of aborting:
+ TOOLS_WARN_EXCEPTION("configmgr", "error writing modifications");
+ }
+ } catch (...) {
+ reference_->clear();
+ throw;
+ }
+ reference_->clear();
+}
+
+Components & Components::getSingleton(
+ css::uno::Reference< css::uno::XComponentContext > const & context)
+{
+ assert(context.is());
+ static Components singleton(context);
+ return singleton;
+}
+
+bool Components::allLocales(std::u16string_view locale) {
+ return locale == u"*";
+}
+
+rtl::Reference< Node > Components::resolvePathRepresentation(
+ OUString const & pathRepresentation,
+ OUString * canonicRepresentation, std::vector<OUString> * path, int * finalizedLayer)
+ const
+{
+ return data_.resolvePathRepresentation(
+ pathRepresentation, canonicRepresentation, path, finalizedLayer);
+}
+
+rtl::Reference< Node > Components::getTemplate(OUString const & fullName) const
+{
+ return data_.getTemplate(Data::NO_LAYER, fullName);
+}
+
+void Components::addRootAccess(rtl::Reference< RootAccess > const & access) {
+ roots_.insert(access.get());
+}
+
+void Components::removeRootAccess(RootAccess * access) {
+ roots_.erase(access);
+}
+
+void Components::initGlobalBroadcaster(
+ Modifications const & modifications,
+ rtl::Reference< RootAccess > const & exclude, Broadcaster * broadcaster)
+{
+ //TODO: Iterate only over roots w/ listeners:
+ for (auto const& elemRoot : roots_)
+ {
+ rtl::Reference< RootAccess > root;
+ if (elemRoot->acquireCounting() > 1) {
+ root.set(elemRoot); // must not throw
+ }
+ elemRoot->releaseNondeleting();
+ if (root.is()) {
+ if (root != exclude) {
+ std::vector<OUString> path(root->getAbsolutePath());
+ Modifications::Node const * mods = &modifications.getRoot();
+ for (auto const& pathElem : path)
+ {
+ Modifications::Node::Children::const_iterator k(
+ mods->children.find(pathElem));
+ if (k == mods->children.end()) {
+ mods = nullptr;
+ break;
+ }
+ mods = &k->second;
+ }
+ //TODO: If the complete tree of which root is a part is deleted,
+ // or replaced, mods will be null, but some of the listeners
+ // from within root should probably fire nonetheless:
+ if (mods != nullptr) {
+ root->initBroadcaster(*mods, broadcaster);
+ }
+ }
+ }
+ }
+}
+
+void Components::addModification(std::vector<OUString> const & path) {
+ data_.modifications.add(path);
+}
+
+void Components::writeModifications() {
+
+ if (data_.modifications.empty())
+ return;
+
+ switch (modificationTarget_) {
+ case ModificationTarget::None:
+ break;
+ case ModificationTarget::File:
+ if (!writeThread_.is()) {
+ writeThread_ = new WriteThread(
+ &writeThread_, *this, modificationFileUrl_, data_);
+ writeThread_->launch();
+ }
+ break;
+ case ModificationTarget::Dconf:
+#if ENABLE_DCONF
+ dconf::writeModifications(*this, data_);
+#endif
+ break;
+ }
+}
+
+void Components::flushModifications() {
+ rtl::Reference< WriteThread > thread;
+ {
+ osl::MutexGuard g(*lock_);
+ thread = writeThread_;
+ }
+ if (thread.is()) {
+ thread->flush();
+ thread->join();
+ }
+}
+
+void Components::insertExtensionXcsFile(
+ bool shared, OUString const & fileUri)
+{
+ int layer = getExtensionLayer(shared);
+ try {
+ parseXcsFile(fileUri, layer, data_, nullptr, nullptr, nullptr);
+ } catch (css::container::NoSuchElementException & e) {
+ throw css::uno::RuntimeException(
+ "insertExtensionXcsFile does not exist: " + e.Message);
+ }
+}
+
+void Components::insertExtensionXcuFile(
+ bool shared, OUString const & fileUri, Modifications * modifications)
+{
+ assert(modifications != nullptr);
+ int layer = getExtensionLayer(shared) + 1;
+ Additions * adds = data_.addExtensionXcuAdditions(fileUri, layer);
+ try {
+ parseXcuFile(fileUri, layer, data_, nullptr, modifications, adds);
+ } catch (css::container::NoSuchElementException & e) {
+ data_.removeExtensionXcuAdditions(fileUri);
+ throw css::uno::RuntimeException(
+ "insertExtensionXcuFile does not exist: " + e.Message);
+ }
+}
+
+void Components::removeExtensionXcuFile(
+ OUString const & fileUri, Modifications * modifications)
+{
+ //TODO: Ideally, exactly the data coming from the specified xcu file would
+ // be removed. However, not enough information is recorded in the in-memory
+ // data structures to do so. So, as a workaround, all those set elements
+ // that were freshly added by the xcu and have afterwards been left
+ // unchanged or have only had their properties changed in the user layer are
+ // removed (and nothing else). The heuristic to determine
+ // whether a node has been left unchanged is to check the layer ID (as
+ // usual) and additionally to check that the node does not recursively
+ // contain any non-empty sets (multiple extension xcu files are merged into
+ // one layer, so checking layer ID alone is not enough). Since
+ // item->additions records all additions of set members in textual order,
+ // the latter check works well when iterating through item->additions in
+ // reverse order.
+ assert(modifications != nullptr);
+ rtl::Reference< Data::ExtensionXcu > item(
+ data_.removeExtensionXcuAdditions(fileUri));
+ if (!item.is())
+ return;
+
+ for (Additions::reverse_iterator i(item->additions.rbegin());
+ i != item->additions.rend(); ++i)
+ {
+ rtl::Reference< Node > parent;
+ NodeMap const * map = &data_.getComponents();
+ rtl::Reference< Node > node;
+ for (auto const& j : *i)
+ {
+ parent = node;
+ node = map->findNode(Data::NO_LAYER, j);
+ if (!node.is()) {
+ break;
+ }
+ map = &node->getMembers();
+ }
+ if (node.is()) {
+ assert(parent.is());
+ if (parent->kind() == Node::KIND_SET) {
+ assert(
+ node->kind() == Node::KIND_GROUP ||
+ node->kind() == Node::KIND_SET);
+ if (canRemoveFromLayer(item->layer, node)) {
+ parent->getMembers().erase(i->back());
+ data_.modifications.remove(*i);
+ modifications->add(*i);
+ }
+ }
+ }
+ }
+ writeModifications();
+}
+
+void Components::insertModificationXcuFile(
+ OUString const & fileUri,
+ std::set< OUString > const & includedPaths,
+ std::set< OUString > const & excludedPaths,
+ Modifications * modifications)
+{
+ assert(modifications != nullptr);
+ Partial part(includedPaths, excludedPaths);
+ try {
+ parseFileLeniently(
+ &parseXcuFile, fileUri, Data::NO_LAYER, &part, modifications, nullptr);
+ } catch (const css::container::NoSuchElementException &) {
+ TOOLS_WARN_EXCEPTION(
+ "configmgr",
+ "error inserting non-existing \"" << fileUri << "\"");
+ }
+}
+
+css::beans::Optional< css::uno::Any > Components::getExternalValue(
+ std::u16string_view descriptor)
+{
+ size_t i = descriptor.find(' ');
+ if (i == 0 || i == std::u16string_view::npos) {
+ throw css::uno::RuntimeException(
+ OUString::Concat("bad external value descriptor ") + descriptor);
+ }
+ //TODO: Do not make calls with mutex locked:
+ OUString name(descriptor.substr(0, i));
+ ExternalServices::iterator j(externalServices_.find(name));
+ if (j == externalServices_.end()) {
+ css::uno::Reference< css::uno::XInterface > service;
+ try {
+ service = context_->getServiceManager()->createInstanceWithContext(
+ name, context_);
+ } catch (const css::uno::RuntimeException &) {
+ // Assuming these exceptions are real errors:
+ throw;
+ } catch (const css::uno::Exception &) {
+ // Assuming these exceptions indicate that the service is not
+ // installed:
+ TOOLS_WARN_EXCEPTION(
+ "configmgr",
+ "createInstance(" << name << ") failed");
+ }
+ css::uno::Reference< css::beans::XPropertySet > propset;
+ if (service.is()) {
+ propset.set( service, css::uno::UNO_QUERY_THROW);
+ }
+ j = externalServices_.emplace(name, propset).first;
+ }
+ css::beans::Optional< css::uno::Any > value;
+ if (j->second.is()) {
+ try {
+ if (!(j->second->getPropertyValue(OUString(descriptor.substr(i + 1))) >>=
+ value))
+ {
+ throw css::uno::RuntimeException(
+ OUString::Concat("cannot obtain external value through ") + descriptor);
+ }
+ } catch (css::beans::UnknownPropertyException & e) {
+ throw css::uno::RuntimeException(
+ "unknown external value descriptor ID: " + e.Message);
+ } catch (css::lang::WrappedTargetException & e) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "cannot obtain external value: " + e.Message,
+ nullptr, anyEx );
+ }
+ }
+ return value;
+}
+
+Components::Components(
+ css::uno::Reference< css::uno::XComponentContext > const & context):
+ context_(context), sharedExtensionLayer_(-1), userExtensionLayer_(-1),
+ modificationTarget_(ModificationTarget::None)
+{
+ assert(context.is());
+ lock_ = lock();
+ OUString conf(expand("${CONFIGURATION_LAYERS}"));
+ int layer = 0;
+ for (sal_Int32 i = 0;;) {
+ while (i != conf.getLength() && conf[i] == ' ') {
+ ++i;
+ }
+ if (i == conf.getLength()) {
+ break;
+ }
+ if (modificationTarget_ != ModificationTarget::None) {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: modification target layer followed by"
+ " further layers");
+ }
+ sal_Int32 c = i;
+ for (;; ++c) {
+ if (c == conf.getLength() || conf[c] == ' ') {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: missing ':' in \"" + conf + "\"");
+ }
+ if (conf[c] == ':') {
+ break;
+ }
+ }
+ sal_Int32 n = conf.indexOf(' ', c + 1);
+ if (n == -1) {
+ n = conf.getLength();
+ }
+ OUString type(conf.copy(i, c - i));
+ OUString url(conf.copy(c + 1, n - c - 1));
+ if (type == "xcsxcu") {
+ sal_uInt32 nStartTime = osl_getGlobalTimer();
+ parseXcsXcuLayer(layer, url);
+ SAL_INFO("configmgr", "parseXcsXcuLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
+ layer += 2; //TODO: overflow
+ } else if (type == "bundledext") {
+ parseXcsXcuIniLayer(layer, url, false);
+ layer += 2; //TODO: overflow
+ } else if (type == "sharedext") {
+ if (sharedExtensionLayer_ != -1) {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: multiple \"sharedext\" layers");
+ }
+ sharedExtensionLayer_ = layer;
+ parseXcsXcuIniLayer(layer, url, true);
+ layer += 2; //TODO: overflow
+ } else if (type == "userext") {
+ if (userExtensionLayer_ != -1) {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: multiple \"userext\" layers");
+ }
+ userExtensionLayer_ = layer;
+ parseXcsXcuIniLayer(layer, url, true);
+ layer += 2; //TODO: overflow
+ } else if (type == "res") {
+ sal_uInt32 nStartTime = osl_getGlobalTimer();
+ parseResLayer(layer, url);
+ SAL_INFO("configmgr", "parseResLayer() took " << (osl_getGlobalTimer() - nStartTime) << " ms");
+ ++layer; //TODO: overflow
+#if ENABLE_DCONF
+ } else if (type == "dconf") {
+ if (url == "!") {
+ modificationTarget_ = ModificationTarget::Dconf;
+ dconf::readLayer(data_, Data::NO_LAYER);
+ } else if (url == "*") {
+ dconf::readLayer(data_, layer);
+ } else {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: unknown \"dconf\" kind \"" + url
+ + "\"");
+ }
+ ++layer; //TODO: overflow
+#endif
+#if defined(_WIN32)
+ } else if (type == "winreg") {
+ WinRegType eType;
+ if (url == "LOCAL_MACHINE" || url.isEmpty()/*backwards comp.*/) {
+ eType = WinRegType::LOCAL_MACHINE;
+ } else if (url == "CURRENT_USER") {
+ eType = WinRegType::CURRENT_USER;
+ } else {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: unknown \"winreg\" kind \"" + url
+ + "\"");
+ }
+ OUString aTempFileURL;
+ if (dumpWindowsRegistry(&aTempFileURL, eType)) {
+ parseFileLeniently(&parseXcuFile, aTempFileURL, layer, nullptr, nullptr, nullptr);
+ if (!getenv("SAL_CONFIG_WINREG_RETAIN_TMP"))
+ osl::File::remove(aTempFileURL);
+ }
+ ++layer; //TODO: overflow
+#endif
+ } else if (type == "user") {
+ bool write;
+ if (url.startsWith("!", &url)) {
+ write = true;
+ } else if (url.startsWith("*", &url)) {
+ write = false;
+ } else {
+ write = true; // for backwards compatibility
+ }
+ if (url.isEmpty()) {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: empty \"user\" URL");
+ }
+ bool ignore = false;
+#if ENABLE_DCONF
+ if (write) {
+ OUString token(
+ expand("${SYSUSERCONFIG}/libreoffice/dconfwrite"));
+ osl::DirectoryItem it;
+ osl::FileBase::RC e = osl::DirectoryItem::get(token, it);
+ ignore = e == osl::FileBase::E_None;
+ SAL_INFO(
+ "configmgr",
+ "dconf write (<" << token << "> " << +e << "): "
+ << int(ignore));
+ if (ignore) {
+ modificationTarget_ = ModificationTarget::Dconf;
+ }
+ }
+#endif
+ if (!ignore) {
+ if (write) {
+ modificationTarget_ = ModificationTarget::File;
+ modificationFileUrl_ = url;
+ }
+ parseModificationLayer(write ? Data::NO_LAYER : layer, url);
+ }
+ ++layer; //TODO: overflow
+ } else {
+ throw css::uno::RuntimeException(
+ "CONFIGURATION_LAYERS: unknown layer type \"" + type + "\"");
+ }
+ i = n;
+ }
+}
+
+Components::~Components()
+{
+ // get flag if _exit was already called which is a sign to not secure user config.
+ // this is used for win only currently where calling _exit() unfortunately still
+ // calls destructors (what is not wanted). May be needed for other systems, too
+ // (unknown yet) but can do no harm
+ const bool bExitWasCalled(comphelper::BackupFileHelper::getExitWasCalled());
+
+#ifndef _WIN32
+ // we can add a SAL_WARN here for other systems where the destructor gets called after
+ // an _exit() call. Still safe - the getExitWasCalled() is used, but a hint that _exit
+ // behaves different on a system
+ SAL_WARN_IF(bExitWasCalled, "configmgr", "Components::~Components() called after _exit() call");
+#endif
+
+ if (bExitWasCalled)
+ {
+ // do not write, re-join threads
+ osl::MutexGuard g(*lock_);
+
+ if (writeThread_.is())
+ {
+ writeThread_->join();
+ }
+ }
+ else
+ {
+ // write changes
+ flushModifications();
+ }
+
+ for (auto const& rootElem : roots_)
+ {
+ rootElem->setAlive(false);
+ }
+}
+
+void Components::parseFileLeniently(
+ FileParser * parseFile, OUString const & url, int layer,
+ Partial const * partial, Modifications * modifications,
+ Additions * additions)
+{
+ assert(parseFile != nullptr);
+ try {
+ (*parseFile)(url, layer, data_, partial, modifications, additions);
+ } catch (const css::container::NoSuchElementException &) {
+ throw;
+ } catch (const css::uno::Exception &) { //TODO: more specific exception catching
+ // Ignore invalid XML files, instead of completely preventing OOo from
+ // starting:
+ TOOLS_WARN_EXCEPTION(
+ "configmgr",
+ "error reading \"" << url << "\"");
+ }
+}
+
+void Components::parseFiles(
+ int layer, OUString const & extension, FileParser * parseFile,
+ OUString const & url, bool recursive)
+{
+ osl::Directory dir(url);
+ switch (dir.open()) {
+ case osl::FileBase::E_None:
+ break;
+ case osl::FileBase::E_NOENT:
+ if (!recursive) {
+ return;
+ }
+ [[fallthrough]];
+ default:
+ throw css::uno::RuntimeException(
+ "cannot open directory " + url);
+ }
+ for (;;) {
+ osl::DirectoryItem i;
+ osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
+ if (rc == osl::FileBase::E_NOENT) {
+ break;
+ }
+ if (rc != osl::FileBase::E_None) {
+ throw css::uno::RuntimeException(
+ "cannot iterate directory " + url);
+ }
+ osl::FileStatus stat(
+ osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
+ osl_FileStatus_Mask_FileURL);
+ if (i.getFileStatus(stat) != osl::FileBase::E_None) {
+ throw css::uno::RuntimeException(
+ "cannot stat in directory " + url);
+ }
+ if (stat.getFileType() == osl::FileStatus::Directory) { //TODO: symlinks
+ parseFiles(layer, extension, parseFile, stat.getFileURL(), true);
+ } else {
+ OUString file(stat.getFileName());
+ if (file.endsWith(extension)) {
+ try {
+ parseFileLeniently(
+ parseFile, stat.getFileURL(), layer, nullptr, nullptr, nullptr);
+ } catch (css::container::NoSuchElementException & e) {
+ if (stat.getFileType() == osl::FileStatus::Link) {
+ SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
+ continue;
+ }
+ throw css::uno::RuntimeException(
+ "stat'ed file does not exist: " + e.Message);
+ }
+ }
+ }
+ }
+}
+
+void Components::parseFileList(
+ int layer, FileParser * parseFile, std::u16string_view urls,
+ bool recordAdditions)
+{
+ for (sal_Int32 i = 0;;) {
+ OUString url(o3tl::getToken(urls, 0, ' ', i));
+ if (!url.isEmpty()) {
+ Additions * adds = nullptr;
+ if (recordAdditions) {
+ adds = data_.addExtensionXcuAdditions(url, layer);
+ }
+ try {
+ parseFileLeniently(parseFile, url, layer, nullptr, nullptr, adds);
+ } catch (const css::container::NoSuchElementException &) {
+ TOOLS_WARN_EXCEPTION("configmgr", "file does not exist");
+ if (adds != nullptr) {
+ data_.removeExtensionXcuAdditions(url);
+ }
+ }
+ }
+ if (i == -1) {
+ break;
+ }
+ }
+}
+
+void Components::parseXcdFiles(int layer, OUString const & url) {
+ osl::Directory dir(url);
+ switch (dir.open()) {
+ case osl::FileBase::E_None:
+ break;
+ case osl::FileBase::E_NOENT:
+ return;
+ default:
+ throw css::uno::RuntimeException(
+ "cannot open directory " + url);
+ }
+ UnresolvedVector unres;
+ std::set< OUString > existingDeps;
+ std::set< OUString > processedDeps;
+ for (;;) {
+ osl::DirectoryItem i;
+ osl::FileBase::RC rc = dir.getNextItem(i, SAL_MAX_UINT32);
+ if (rc == osl::FileBase::E_NOENT) {
+ break;
+ }
+ if (rc != osl::FileBase::E_None) {
+ throw css::uno::RuntimeException(
+ "cannot iterate directory " + url);
+ }
+ osl::FileStatus stat(
+ osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileName |
+ osl_FileStatus_Mask_FileURL);
+ if (i.getFileStatus(stat) != osl::FileBase::E_None) {
+ throw css::uno::RuntimeException(
+ "cannot stat in directory " + url);
+ }
+ if (stat.getFileType() != osl::FileStatus::Directory) { //TODO: symlinks
+ OUString file(stat.getFileName());
+ OUString name;
+ if (file.endsWith(".xcd", &name)) {
+ existingDeps.insert(name);
+ rtl::Reference< ParseManager > manager;
+ try {
+ manager = new ParseManager(
+ stat.getFileURL(),
+ new XcdParser(layer, processedDeps, data_));
+ } catch (css::container::NoSuchElementException & e) {
+ if (stat.getFileType() == osl::FileStatus::Link) {
+ SAL_WARN("configmgr", "dangling link <" << stat.getFileURL() << ">");
+ continue;
+ }
+ throw css::uno::RuntimeException(
+ "stat'ed file does not exist: " + e.Message);
+ }
+ if (manager->parse(nullptr)) {
+ processedDeps.insert(name);
+ } else {
+ unres.emplace_back(name, manager);
+ }
+ }
+ }
+ }
+ while (!unres.empty()) {
+ bool resolved = false;
+ for (UnresolvedVector::iterator i(unres.begin()); i != unres.end();) {
+ if (i->manager->parse(&existingDeps)) {
+ processedDeps.insert(i->name);
+ i = unres.erase(i);
+ resolved = true;
+ } else {
+ ++i;
+ }
+ }
+ if (!resolved) {
+ throw css::uno::RuntimeException(
+ "xcd: unresolved dependencies in " + url);
+ }
+ }
+}
+
+void Components::parseXcsXcuLayer(int layer, OUString const & url) {
+ parseXcdFiles(layer, url);
+ parseFiles(layer, ".xcs", &parseXcsFile, url + "/schema", false);
+ parseFiles(layer + 1, ".xcu", &parseXcuFile, url + "/data", false);
+}
+
+void Components::parseXcsXcuIniLayer(
+ int layer, OUString const & url, bool recordAdditions)
+{
+ // Check if ini file exists (otherwise .override would still read global
+ // SCHEMA/DATA variables, which could interfere with unrelated environment
+ // variables):
+ if (rtl::Bootstrap(url).getHandle() == nullptr) return;
+
+ OUStringBuffer prefix("${.override:");
+ for (sal_Int32 i = 0; i != url.getLength(); ++i) {
+ sal_Unicode c = url[i];
+ switch (c) {
+ case '$':
+ case ':':
+ case '\\':
+ prefix.append('\\');
+ [[fallthrough]];
+ default:
+ prefix.append(c);
+ }
+ }
+ prefix.append(':');
+ OUString urls(prefix + "SCHEMA}");
+ rtl::Bootstrap::expandMacros(urls);
+ if (!urls.isEmpty()) {
+ parseFileList(layer, &parseXcsFile, urls, false);
+ }
+ urls = prefix.makeStringAndClear() + "DATA}";
+ rtl::Bootstrap::expandMacros(urls);
+ if (!urls.isEmpty()) {
+ parseFileList(layer + 1, &parseXcuFile, urls, recordAdditions);
+ }
+}
+
+void Components::parseResLayer(int layer, std::u16string_view url) {
+ OUString resUrl(OUString::Concat(url) + "/res");
+ parseXcdFiles(layer, resUrl);
+ parseFiles(layer, ".xcu", &parseXcuFile, resUrl, false);
+}
+
+void Components::parseModificationLayer(int layer, OUString const & url) {
+ try {
+ parseFileLeniently(&parseXcuFile, url, layer, nullptr, nullptr, nullptr);
+ } catch (css::container::NoSuchElementException &) {
+ SAL_INFO(
+ "configmgr", "user registrymodifications.xcu does not (yet) exist");
+ // Migrate old user layer data (can be removed once migration is no
+ // longer relevant, probably OOo 4; also see hack for xsi namespace in
+ // xmlreader::XmlReader::registerNamespaceIri):
+ parseFiles(
+ layer, ".xcu", &parseXcuFile,
+ expand(
+ "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")
+ ":UserInstallation}/user/registry/data"),
+ false);
+ }
+}
+
+int Components::getExtensionLayer(bool shared) const {
+ int layer = shared ? sharedExtensionLayer_ : userExtensionLayer_;
+ if (layer == -1) {
+ throw css::uno::RuntimeException(
+ "insert extension xcs/xcu file into undefined layer");
+ }
+ return layer;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */