7f04bb393f
...avoiding to have code in configmgr that knows about the details of the data stored in the configuration. (See the comments starting at <https://gerrit.libreoffice.org/c/core/+/139690/9#message-44703a2529c07bf1b0202ed3a232aa661784b159> "Migrating product name related color schemes between different versions" for details.) This reverts the dubious changes of583ea856f2
"Migrating product name related color schemes between different versions" in configmgr and offapi. (Also, this moves the computation of sMigratedProductName in MigrationImpl::copyConfig, desktop/source/migration/migration.cxx, to a saner location than in the middle of the "check if the shared registrymodifications.xcu file exists" block where that583ea856f2
had placed it.) Change-Id: I7ab3d57db19065c7c818e697300a2abd9e7f72bc Reviewed-on: https://gerrit.libreoffice.org/c/core/+/139963 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
895 lines
30 KiB
C++
895 lines
30 KiB
C++
/* -*- 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 <comphelper/diagnose_ex.hxx>
|
|
#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 + "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: */
|