diff options
Diffstat (limited to 'configmgr/source/dconf.cxx')
-rw-r--r-- | configmgr/source/dconf.cxx | 1598 |
1 files changed, 1598 insertions, 0 deletions
diff --git a/configmgr/source/dconf.cxx b/configmgr/source/dconf.cxx new file mode 100644 index 000000000..9db51fa17 --- /dev/null +++ b/configmgr/source/dconf.cxx @@ -0,0 +1,1598 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <cassert> +#include <cstddef> +#include <cstring> +#include <forward_list> +#include <limits> +#include <vector> + +extern "C" { + //TODO: <https://bugzilla.gnome.org/show_bug.cgi?id=754245> + // "common/dconf-changeset.h etc. lack extern "C" wrapper for C++", fixed on current dconf + // master (towards 0.40?) now with + // <https://gitlab.gnome.org/GNOME/dconf/-/commit/db3d4df6d1a763698f27b013dc42da8d4ae02639> + // "Merge branch 'wip/issue-23' into 'master'" +#include <dconf/dconf.h> +} + +#include <com/sun/star/uno/Sequence.hxx> +#include <o3tl/safeint.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include "data.hxx" +#include "dconf.hxx" +#include "groupnode.hxx" +#include "localizedpropertynode.hxx" +#include "localizedvaluenode.hxx" +#include "nodemap.hxx" +#include "propertynode.hxx" +#include "setnode.hxx" + +// component-data is encoded in dconf as follows: +// +// * The node hierarchy (starting at component nodes with names like +// "org.openoffice.Setup") maps to dconf paths underneath +// "/org/libreoffice/registry/". +// +// * Component, group, set, and localized property nodes map to dconf dirs, +// while property and localized value nodes map to dconf keys. +// +// * The names of nodes that are not set elements are used directly as dconf +// path segments. (The syntax for node names is any non-empty sequences of +// any Unicode scalar values except U+0000--0008, U+000B--000C, U+000E--001F, +// U+002F SOLIDUS, and U+FFFE--FFFF. TODO: "<aruiz> sberg, in general I think +// it'd be nice if you used path separators instead of dots though, they have +// meaning in dconf/gvdb world :-)"?) +// +// * The names of set element nodes are encoded as dconf path segments as +// follows: each occurrence of U+0000 NULL is replace by the three characters +// "\00", each occurrence of U+002F SOLIDUS is replaced by the three +// characters "\2F", and each occurrence of U+005C REVERSE SOLIDUS is replaced +// by the three characters "\5C". +// +// * Set elements (which must themselves be either sets or groups) map to +// "indirection" dconf dirs as follows: +// +// ** The dir must contain a key named "op" of string type, with a value of +// "fuse", "replace", or "remove". +// +// ** If "op" is "fuse" or "replace", the dir must contain exactly the following +// further keys and dirs: +// +// *** The dir must contain a key named "template" of string type, containing +// the full template name, encoded as follows: each occurrence of U+0000 +// NULL is replace by the three characters "\00" and each occurrence of +// U+005C REVERSE SOLIDUS is replaced by the three characters "\5C". +// +// *** The dir must contain a dir named "content" that contains the set +// element's (i.e., set or group node's) real content. +// +// ** If "op" is "remove", the dir must contain no further keys or dirs. +// +// * Property and localized property value "fuse" operations map to GVariant +// instances as follows: +// +// ** Non-nillable boolean values map to GVariant boolean instances. +// +// ** Non-nillable short values map to GVariant int16 instances. +// +// ** Non-nillable int values map to GVariant int32 instances. +// +// ** Non-nillable long values map to GVariant int64 instances. +// +// ** Non-nillable double values map to GVariant double instances. +// +// ** Non-nillable string values map to GVariant string instances, with the +// following encoding: each occurrence of U+0000 NULL is replace by the three +// characters "\00" and each occurrence of U+005C REVERSE SOLIDUS is replaced +// by the three characters "\5C". +// +// ** Non-nillable hexbinary values map to GVariant byte array instances. +// +// ** Non-nillable list values recursively map to GVariant array instances. +// +// ** Nillable values recursively map to GVariant maybe instances. +// +// * Property "remove" operations map to GVariant instances of empty tuple type. +// +// Finalization: The component-update.dtd allows for finalization of +// oor:component-data, node, and prop elements, while dconf allows for locking +// of individual keys. That does not match, but just mark the individual Node +// instances that correspond to individual dconf keys as finalized for +// non-writable dconf keys. +// +// TODO: support "mandatory" and "external"? + +namespace configmgr::dconf { + +namespace { + +template<typename T> class GObjectHolder { +public: + explicit GObjectHolder(T * object): object_(object) {} + + ~GObjectHolder() { + if (object_ != nullptr) { + g_object_unref(object_); + } + } + + T * get() const { return object_; } + +private: + GObjectHolder(GObjectHolder const &) = delete; + GObjectHolder& operator =(GObjectHolder const &) = delete; + + T * object_; +}; + +class GVariantHolder { +public: + explicit GVariantHolder(GVariant * variant = nullptr): variant_(variant) {} + + ~GVariantHolder() { unref(); } + + void reset(GVariant * variant) { + unref(); + variant_ = variant; + } + + void release() { variant_ = nullptr; } + + GVariant * get() const { return variant_; } + +private: + GVariantHolder(GVariantHolder const &) = delete; + GVariantHolder& operator =(GVariantHolder const &) = delete; + + void unref() { + if (variant_ != nullptr) { + g_variant_unref(variant_); + } + } + + GVariant * variant_; +}; + +class GVariantTypeHolder { +public: + explicit GVariantTypeHolder(GVariantType * type): type_(type) {} + + ~GVariantTypeHolder() { + if (type_ != nullptr) { + g_variant_type_free(type_); + } + } + + GVariantType * get() const { return type_; } + +private: + GVariantTypeHolder(GVariantTypeHolder const &) = delete; + GVariantTypeHolder& operator =(GVariantTypeHolder const &) = delete; + + GVariantType * type_; +}; + +class StringArrayHolder { +public: + explicit StringArrayHolder(gchar ** array): array_(array) {} + + ~StringArrayHolder() { g_strfreev(array_); } + + gchar ** get() const { return array_; } + +private: + StringArrayHolder(StringArrayHolder const &) = delete; + StringArrayHolder& operator =(StringArrayHolder const &) = delete; + + gchar ** array_; +}; + +class ChangesetHolder { +public: + explicit ChangesetHolder(DConfChangeset * changeset): + changeset_(changeset) + {} + + ~ChangesetHolder() { + if (changeset_ != nullptr) { + dconf_changeset_unref(changeset_); + } + } + + DConfChangeset * get() const { return changeset_; } + +private: + ChangesetHolder(ChangesetHolder const &) = delete; + ChangesetHolder& operator =(ChangesetHolder const &) = delete; + + DConfChangeset * changeset_; +}; + +OString getRoot() { + return "/org/libreoffice/registry"; +} + +bool decode(OUString * string, bool slash) { + for (sal_Int32 i = 0;; ++i) { + i = string->indexOf('\\', i); + if (i == -1) { + return true; + } + if (string->match("00", i + 1)) { + *string = string->replaceAt(i, 3, OUStringChar(u'\0')); + } else if (slash && string->match("2F", i + 1)) { + *string = string->replaceAt(i, 3, u"/"); + } else if (string->match("5C", i + 1)) { + *string = string->replaceAt(i + 1, 2, u""); + } else { + SAL_WARN("configmgr.dconf", "bad escape in " << *string); + return false; + } + } +} + +bool getBoolean( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_BOOLEAN)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match boolean property"); + return false; + } + *value <<= bool(g_variant_get_boolean(variant.get())); + return true; +} + +bool getShort( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT16)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match short property"); + return false; + } + *value <<= sal_Int16(g_variant_get_int16(variant.get())); + return true; +} + +bool getInt( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT32)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match int property"); + return false; + } + *value <<= sal_Int32(g_variant_get_int32(variant.get())); + return true; +} + +bool getLong( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_INT64)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match long property"); + return false; + } + *value <<= sal_Int64(g_variant_get_int64(variant.get())); + return true; +} + +bool getDouble( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_DOUBLE)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match double property"); + return false; + } + *value <<= double(g_variant_get_double(variant.get())); + return true; +} + +bool getStringValue( + OString const & key, GVariantHolder const & variant, OUString * value) +{ + assert(value != nullptr); + if (!g_variant_is_of_type(variant.get(), G_VARIANT_TYPE_STRING)) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match string property"); + return false; + } + gsize n; + char const * p = g_variant_get_string(variant.get(), &n); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long string value for key " << key); + return false; + } + if (!rtl_convertStringToUString( + &value->pData, p, static_cast<sal_Int32>(n), RTL_TEXTENCODING_UTF8, + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) + { + SAL_WARN("configmgr.dconf", "non--UTF-8 string value for key " << key); + return false; + } + return decode(value, false); +} + +bool getString( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + OUString v; + if (!getStringValue(key, variant, &v)) { + return false; + } + *value <<= v; + return true; +} + +bool getHexbinaryValue( + OString const & key, GVariantHolder const & variant, + css::uno::Sequence<sal_Int8> * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ay") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match hexbinary property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (guchar)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long hexbinary value for key " << key); + return false; + } + value->realloc(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int8) == sizeof (guchar), "size mismatch"); + std::memcpy(value->getArray(), p, n * sizeof (guchar)); + // assuming that n * sizeof (guchar) is small enough for std::size_t + return true; +} + +bool getHexbinary( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + css::uno::Sequence<sal_Int8> v; + if (!getHexbinaryValue(key, variant, &v)) { + return false; + } + *value <<= v; + return true; +} + +bool getBooleanList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ab") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match boolean list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (guchar)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long boolean list for key " << key); + return false; + } + css::uno::Sequence<sal_Bool> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Bool) == sizeof (guchar), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (guchar)); + // assuming that n * sizeof (guchar) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getShortList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "an") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match short list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gint16)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long short list for key " << key); + return false; + } + css::uno::Sequence<sal_Int16> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int16) == sizeof (gint16), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gint16)); + // assuming that n * sizeof (gint16) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getIntList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ai") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match int list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gint32)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long int list for key " << key); + return false; + } + css::uno::Sequence<sal_Int32> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int32) == sizeof (gint32), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gint32)); + // assuming that n * sizeof (gint32) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getLongList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ax") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match long list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gint64)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long long list for key " << key); + return false; + } + css::uno::Sequence<sal_Int64> v(static_cast<sal_Int32>(n)); + static_assert(sizeof (sal_Int64) == sizeof (gint64), "size mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gint64)); + // assuming that n * sizeof (gint64) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getDoubleList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "ad") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match double list property"); + return false; + } + gsize n; + gconstpointer p = g_variant_get_fixed_array( + variant.get(), &n, sizeof (gdouble)); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long double list for key " << key); + return false; + } + css::uno::Sequence<double> v(static_cast<sal_Int32>(n)); + static_assert(std::is_same<double, gdouble>::value, "type mismatch"); + std::memcpy(v.getArray(), p, n * sizeof (gdouble)); + // assuming that n * sizeof (gdouble) is small enough for std::size_t + *value <<= v; + return true; +} + +bool getStringList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "as") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match string list property"); + return false; + } + gsize n = g_variant_n_children(variant.get()); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long string list for key " << key); + return false; + } + css::uno::Sequence<OUString> v(static_cast<sal_Int32>(n)); + for (gsize i = 0; i != n; ++i) { + GVariantHolder c(g_variant_get_child_value(variant.get(), i)); + if (!getStringValue(key, c, v.getArray() + i)) { + return false; + } + } + *value <<= v; + return true; +} + +bool getHexbinaryList( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + assert(value != nullptr); + if (std::strcmp(g_variant_get_type_string(variant.get()), "aay") != 0) { + SAL_WARN( + "configmgr.dconf", + "bad key " << key << " does not match hexbinary list property"); + return false; + } + gsize n = g_variant_n_children(variant.get()); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long hexbinary list for key " << key); + return false; + } + css::uno::Sequence<css::uno::Sequence<sal_Int8>> v( + static_cast<sal_Int32>(n)); + for (gsize i = 0; i != n; ++i) { + GVariantHolder c(g_variant_get_child_value(variant.get(), i)); + if (!getHexbinaryValue(key, c, v.getArray() + i)) { + return false; + } + } + *value <<= v; + return true; +} + +bool getAny( + OString const & key, GVariantHolder const & variant, css::uno::Any * value) +{ + char const * t = g_variant_get_type_string(variant.get()); + if (std::strcmp(t, "b") == 0) { + return getBoolean(key, variant, value); + } + if (std::strcmp(t, "n") == 0) { + return getShort(key, variant, value); + } + if (std::strcmp(t, "i") == 0) { + return getInt(key, variant, value); + } + if (std::strcmp(t, "x") == 0) { + return getLong(key, variant, value); + } + if (std::strcmp(t, "d") == 0) { + return getDouble(key, variant, value); + } + if (std::strcmp(t, "s") == 0) { + return getString(key, variant, value); + } + if (std::strcmp(t, "ay") == 0) { + return getHexbinary(key, variant, value); + } + if (std::strcmp(t, "ab") == 0) { + return getBooleanList(key, variant, value); + } + if (std::strcmp(t, "an") == 0) { + return getShortList(key, variant, value); + } + if (std::strcmp(t, "ai") == 0) { + return getIntList(key, variant, value); + } + if (std::strcmp(t, "ax") == 0) { + return getLongList(key, variant, value); + } + if (std::strcmp(t, "ad") == 0) { + return getDoubleList(key, variant, value); + } + if (std::strcmp(t, "as") == 0) { + return getStringList(key, variant, value); + } + if (std::strcmp(t, "aay") == 0) { + return getHexbinaryList(key, variant, value); + } + SAL_WARN( + "configmgr.dconf", "bad key " << key << " does not match any property"); + return false; +} + +enum class ReadValue { Error, Value, Remove }; + +ReadValue readValue( + GObjectHolder<DConfClient> const & client, OString const & path, Type type, + bool nillable, bool removable, css::uno::Any * value) +{ + assert(value != nullptr); + assert(!value->hasValue()); + assert(!path.endsWith("/")); + GVariantHolder v(dconf_client_read(client.get(), path.getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "cannot read key " << path); + return ReadValue::Error; + } + if (removable && std::strcmp(g_variant_get_type_string(v.get()), "()") == 0) + { + return ReadValue::Remove; + } + bool nil; + if (nillable) { + if (g_variant_classify(v.get()) != G_VARIANT_CLASS_MAYBE) { + SAL_WARN( + "configmgr.dconf", + "bad key " << path << " does not match nillable property"); + } + v.reset(g_variant_get_maybe(v.get())); + nil = v.get() == nullptr; + } else { + nil = false; + } + if (!nil) { + switch (type) { + case TYPE_ANY: + if (!getAny(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_BOOLEAN: + if (!getBoolean(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_SHORT: + if (!getShort(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_INT: + if (!getInt(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_LONG: + if (!getLong(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_DOUBLE: + if (!getDouble(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_STRING: + if (!getString(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_HEXBINARY: + if (!getHexbinary(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_BOOLEAN_LIST: + if (!getBooleanList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_SHORT_LIST: + if (!getShortList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_INT_LIST: + if (!getIntList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_LONG_LIST: + if (!getLongList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_DOUBLE_LIST: + if (!getDoubleList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_STRING_LIST: + if (!getStringList(path, v, value)) { + return ReadValue::Error; + } + break; + case TYPE_HEXBINARY_LIST: + if (!getHexbinaryList(path, v, value)) { + return ReadValue::Error; + } + break; + default: + assert(false); // cannot happen + } + } + return ReadValue::Value; +} + +void finalize( + GObjectHolder<DConfClient> const & client, OString const & path, + rtl::Reference<Node> const & node, int layer) +{ + if (!dconf_client_is_writable(client.get(), path.getStr())) { + node->setFinalized(layer); + } +} + +void readDir( + Data & data, int layer, rtl::Reference<Node> const & node, + NodeMap & members, GObjectHolder<DConfClient> const & client, + OString const & dir) +{ + StringArrayHolder a(dconf_client_list(client.get(), dir.getStr(), nullptr)); + for (char const * const * p = a.get(); *p != nullptr; ++p) { + std::size_t n = std::strlen(*p); + if (n > o3tl::make_unsigned( + std::numeric_limits<sal_Int32>::max())) + { + SAL_WARN("configmgr.dconf", "too long dir/key in dir " << dir); + continue; + } + OString s(*p, static_cast<sal_Int32>(n)); + OString path(dir + s); + OUString name; + if (!rtl_convertStringToUString( + &name.pData, s.getStr(), s.getLength(), RTL_TEXTENCODING_UTF8, + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) + { + SAL_WARN("configmgr.dconf", "non--UTF-8 dir/key in dir " << dir); + continue; + } + bool isDir = name.endsWith("/", &name); + OUString templ; + bool remove; + bool replace; + if (node.is() && node->kind() == Node::KIND_SET) { + if (!isDir) { + SAL_WARN( + "configmgr.dconf", + "bad key " << path << " does not match set element"); + continue; + } + if (!decode(&name, true)) { + continue; + } + enum class Op { None, Fuse, Replace, Remove }; + Op op = Op::None; + bool content = false; + bool bad = false; + StringArrayHolder a2( + dconf_client_list(client.get(), path.getStr(), nullptr)); + for (char const * const * p2 = a2.get(); *p2 != nullptr; ++p2) { + if (std::strcmp(*p2, "op") == 0) { + OString path2(path + "op"); + GVariantHolder v( + dconf_client_read(client.get(), path2.getStr())); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "cannot read key " << path2); + bad = true; + break; + } + OUString ops; + if (!getStringValue(path2, v, &ops)) { + bad = true; + break; + } + if (ops == "fuse") { + op = Op::Fuse; + } else if (ops == "replace") { + op = Op::Replace; + } else if (ops == "remove") { + op = Op::Remove; + } else { + SAL_WARN( + "configmgr.dconf", + "bad key " << path2 << " value " << ops); + bad = true; + break; + } + } else if (std::strcmp(*p2, "template") == 0) { + OString path2(path + "template"); + GVariantHolder v( + dconf_client_read(client.get(), path2.getStr())); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "cannot read key " << path2); + bad = true; + break; + } + if (!getStringValue(path2, v, &templ)) { + bad = true; + break; + } + if (!static_cast<SetNode *>(node.get())-> + isValidTemplate(templ)) + { + SAL_WARN( + "configmgr.dconf", + "bad key " << path2 << " value " << templ + << " denotes unsupported set element template"); + bad = true; + break; + } + } else if (std::strcmp(*p2, "content/") == 0) { + content = true; + } else { + SAL_WARN( + "configmgr.dconf", + "bad dir/key " << p2 + << " in set element indirection dir " << path); + bad = true; + break; + } + } + if (bad) { + continue; + } + switch (op) { + default: // case Op::None: + SAL_WARN( + "configmgr.dconf", + "bad set element indirection dir " << path + << " missing \"op\" key"); + continue; + case Op::Fuse: + case Op::Replace: + if (templ.isEmpty() || !content) { + SAL_WARN( + "configmgr.dconf", + "missing \"content\" and/or \"template\" dir/key in " + "\"op\" = \"fuse\"/\"remove\" set element" + " indirection dir " << path); + continue; + } + path += "content/"; + remove = false; + replace = op == Op::Replace; + break; + case Op::Remove: + if (!templ.isEmpty() || content) { + SAL_WARN( + "configmgr.dconf", + "bad \"content\" and/or \"template\" dir/key in \"op\" " + "= \"remove\" set element indirection dir " + << path); + continue; + } + remove = true; + replace = false; + break; + } + } else { + remove = false; + replace = false; + } + rtl::Reference<Node> member(members.findNode(layer, name)); + bool insert = !member.is(); + if (!remove) { + if (replace || insert) { + if (!node.is()) { + SAL_WARN("configmgr.dconf", "bad unmatched " << path); + continue; + } + switch (node->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + member.set(new LocalizedValueNode(layer)); + break; + case Node::KIND_GROUP: + if (!static_cast<GroupNode *>(node.get())->isExtensible()) { + SAL_WARN("configmgr.dconf", "bad unmatched " << path); + continue; + } + member.set( + new PropertyNode( + layer, TYPE_ANY, true, css::uno::Any(), true)); + break; + case Node::KIND_SET: + assert(!templ.isEmpty()); + member = data.getTemplate(layer, templ); + if (!member.is()) { + SAL_WARN( + "configmgr.dconf", + "bad " << path << " denoting undefined template " + << templ); + continue; + } + member = member->clone(true); + break; + default: + assert(false); // cannot happen + } + } else if (!templ.isEmpty() && templ != member->getTemplateName()) { + SAL_WARN( + "configmgr.dconf", + "bad " << path + << " denoting set element of non-matching template " + << member->getTemplateName()); + continue; + } + } + if (member.is()) { + if (member->getFinalized() < layer) { + continue; + } + switch (member->kind()) { + case Node::KIND_PROPERTY: + { + if (isDir) { + SAL_WARN( + "configmgr.dconf", + "bad dir " << path << " does not match property"); + continue; + } + rtl::Reference<PropertyNode> prop( + static_cast<PropertyNode *>(member.get())); + css::uno::Any value; + switch (readValue( + client, path, prop->getStaticType(), + prop->isNillable(), prop->isExtension(), + &value)) + { + case ReadValue::Error: + continue; + case ReadValue::Value: + prop->setValue(layer, value); + finalize(client, path, member, layer); + break; + case ReadValue::Remove: + remove = true; + break; + } + break; + } + case Node::KIND_LOCALIZED_VALUE: + { + if (isDir) { + SAL_WARN( + "configmgr.dconf", + "bad dir " << path + << " does not match localized value"); + continue; + } + assert( + node.is() + && node->kind() == Node::KIND_LOCALIZED_PROPERTY); + rtl::Reference<LocalizedPropertyNode> locProp( + static_cast<LocalizedPropertyNode *>(node.get())); + css::uno::Any value; + if (readValue( + client, path, locProp->getStaticType(), + locProp->isNillable(), false, &value) + == ReadValue::Error) + { + continue; + } + static_cast<LocalizedValueNode *>(member.get())->setValue( + layer, value); + finalize(client, path, member, layer); + break; + } + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + case Node::KIND_SET: + if (!isDir) { + SAL_WARN( + "configmgr.dconf", + "bad key " << path + << " does not match localized property, group, or" + " set, respectively"); + continue; + } + assert(path.endsWith("/")); + readDir( + data, layer, member, member->getMembers(), client, path); + break; + default: + assert(false); // cannot happen + } + } + if (remove) { + if (!(member.is() && member->getMandatory())) { + members.erase(name); + } + } else if (replace) { + members.erase(name); + members.insert(NodeMap::value_type(name, member)); + } else if (insert) { + members.insert(NodeMap::value_type(name, member)); + } + } +} + +OString encodeSegment(OUString const & name, bool setElement) { + if (!setElement) { + return name.toUtf8(); + } + OUStringBuffer buf; + for (sal_Int32 i = 0; i != name.getLength(); ++i) { + sal_Unicode c = name[i]; + switch (c) { + case '\0': + buf.append("\\00"); + break; + case '/': + buf.append("\\2F"); + break; + case '\\': + buf.append("\\5C"); + break; + default: + buf.append(c); + } + } + return buf.makeStringAndClear().toUtf8(); +} + +OString encodeString(OUString const & value) { + OUStringBuffer buf; + for (sal_Int32 i = 0; i != value.getLength(); ++i) { + sal_Unicode c = value[i]; + switch (c) { + case '\0': + buf.append("\\00"); + break; + case '\\': + buf.append("\\5C"); + break; + default: + buf.append(c); + } + } + return buf.makeStringAndClear().toUtf8(); +} + +bool addProperty( + ChangesetHolder const & changeset, OString const & pathRepresentation, + Type type, bool nillable, css::uno::Any const & value) +{ + Type dynType = getDynamicType(value); + assert(dynType != TYPE_ERROR); + if (type == TYPE_ANY) { + type = dynType; + } + GVariantHolder v; + std::forward_list<GVariantHolder> children; + if (dynType == TYPE_NIL) { + switch (type) { + case TYPE_BOOLEAN: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_BOOLEAN, nullptr)); + break; + case TYPE_SHORT: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT16, nullptr)); + break; + case TYPE_INT: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT32, nullptr)); + break; + case TYPE_LONG: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_INT64, nullptr)); + break; + case TYPE_DOUBLE: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_DOUBLE, nullptr)); + break; + case TYPE_STRING: + v.reset(g_variant_new_maybe(G_VARIANT_TYPE_STRING, nullptr)); + break; + case TYPE_HEXBINARY: + case TYPE_BOOLEAN_LIST: + case TYPE_SHORT_LIST: + case TYPE_INT_LIST: + case TYPE_LONG_LIST: + case TYPE_DOUBLE_LIST: + case TYPE_STRING_LIST: + case TYPE_HEXBINARY_LIST: + { + static char const * const typeString[ + TYPE_HEXBINARY_LIST - TYPE_HEXBINARY + 1] + = { "ay", "ab", "an", "ai", "ax", "ad", "as", "aay" }; + GVariantTypeHolder ty( + g_variant_type_new(typeString[type - TYPE_HEXBINARY])); + if (ty.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_type_new failed"); + return false; + } + v.reset(g_variant_new_maybe(ty.get(), nullptr)); + break; + } + default: + assert(false); // this cannot happen + break; + } + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_maybe failed"); + return false; + } + } else { + switch (type) { + case TYPE_BOOLEAN: + v.reset(g_variant_new_boolean(value.get<bool>())); + break; + case TYPE_SHORT: + v.reset(g_variant_new_int16(value.get<sal_Int16>())); + break; + case TYPE_INT: + v.reset(g_variant_new_int32(value.get<sal_Int32>())); + break; + case TYPE_LONG: + v.reset(g_variant_new_int64(value.get<sal_Int64>())); + break; + case TYPE_DOUBLE: + v.reset(g_variant_new_double(value.get<double>())); + break; + case TYPE_STRING: + v.reset( + g_variant_new_string( + encodeString(value.get<OUString>()).getStr())); + break; + case TYPE_HEXBINARY: + { + css::uno::Sequence<sal_Int8> seq( + value.get<css::uno::Sequence<sal_Int8>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int8) == sizeof (guchar), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int8))); + break; + } + case TYPE_BOOLEAN_LIST: + { + css::uno::Sequence<sal_Bool> seq( + value.get<css::uno::Sequence<sal_Bool>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert(sizeof (sal_Bool) == 1, "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_BOOLEAN, seq.getConstArray(), + seq.getLength(), sizeof (sal_Bool))); + break; + } + case TYPE_SHORT_LIST: + { + css::uno::Sequence<sal_Int16> seq( + value.get<css::uno::Sequence<sal_Int16>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int16) == sizeof (gint16), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_INT16, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int16))); + //TODO: endian-ness? + break; + } + case TYPE_INT_LIST: + { + css::uno::Sequence<sal_Int32> seq( + value.get<css::uno::Sequence<sal_Int32>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int32) == sizeof (gint32), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_INT32, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int32))); + //TODO: endian-ness? + break; + } + case TYPE_LONG_LIST: + { + css::uno::Sequence<sal_Int64> seq( + value.get<css::uno::Sequence<sal_Int64>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int64) == sizeof (gint64), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_INT64, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int64))); + //TODO: endian-ness? + break; + } + case TYPE_DOUBLE_LIST: + { + css::uno::Sequence<double> seq( + value.get<css::uno::Sequence<double>>()); + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (double) == sizeof (gdouble), "size mismatch"); + v.reset( + g_variant_new_fixed_array( + G_VARIANT_TYPE_DOUBLE, seq.getConstArray(), + seq.getLength(), sizeof (double))); + //TODO: endian-ness? + break; + } + case TYPE_STRING_LIST: + { + const css::uno::Sequence<OUString> seq( + value.get<css::uno::Sequence<OUString>>()); + std::vector<GVariant *> vs; + for (OUString const & s : seq) { + children.emplace_front( + g_variant_new_string(encodeString(s).getStr())); + if (children.front().get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "g_variant_new_string failed"); + return false; + } + vs.push_back(children.front().get()); + } + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + v.reset( + g_variant_new_array( + G_VARIANT_TYPE_STRING, vs.data(), seq.getLength())); + break; + } + case TYPE_HEXBINARY_LIST: + { + const css::uno::Sequence<css::uno::Sequence<sal_Int8>> seqSeq( + value.get< + css::uno::Sequence<css::uno::Sequence<sal_Int8>>>()); + std::vector<GVariant *> vs; + for (css::uno::Sequence<sal_Int8> const & seq : seqSeq) { + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + static_assert( + sizeof (sal_Int8) == sizeof (guchar), "size mismatch"); + children.emplace_front( + g_variant_new_fixed_array( + G_VARIANT_TYPE_BYTE, seq.getConstArray(), + seq.getLength(), sizeof (sal_Int8))); + if (children.front().get() == nullptr) { + SAL_WARN( + "configmgr.dconf", + "g_variant_new_fixed_array failed"); + return false; + } + vs.push_back(children.front().get()); + } + GVariantTypeHolder ty(g_variant_type_new("aay")); + if (ty.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_type_new failed"); + return false; + } + static_assert( + sizeof(sal_Int32) <= sizeof(gsize), + "G_MAXSIZE too small"); + v.reset( + g_variant_new_array(ty.get(), vs.data(), seqSeq.getLength())); + break; + } + default: + assert(false); // this cannot happen + break; + } + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "GVariant creation failed"); + return false; + } + if (nillable) { + GVariantHolder v1(g_variant_new_maybe(nullptr, v.get())); + if (v1.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_maybe failed"); + return false; + } + v.release(); + v.reset(v1.get()); + v1.release(); + } + } + dconf_changeset_set( + changeset.get(), pathRepresentation.getStr(), v.get()); + for (auto & i: children) { + i.release(); + } + v.release(); + return true; +} + +bool addNode( + Components & components, ChangesetHolder const & changeset, + rtl::Reference<Node> const & parent, OString const & pathRepresentation, + rtl::Reference<Node> const & node) +{ + switch (node->kind()) { + case Node::KIND_PROPERTY: + { + PropertyNode * prop = static_cast<PropertyNode *>(node.get()); + if (!addProperty( + changeset, pathRepresentation, prop->getStaticType(), + prop->isNillable(), prop->getValue(components))) + { + return false; + } + break; + } + case Node::KIND_LOCALIZED_VALUE: + { + //TODO: name.isEmpty()? + LocalizedPropertyNode * locprop + = static_cast<LocalizedPropertyNode *>(parent.get()); + if (!addProperty( + changeset, pathRepresentation, + locprop->getStaticType(), locprop->isNillable(), + static_cast<LocalizedValueNode *>(node.get())->getValue())) + { + return false; + } + break; + } + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + case Node::KIND_SET: + for (auto const & i: node->getMembers()) { + OUString templ(i.second->getTemplateName()); + OString path( + pathRepresentation + "/" + + encodeSegment(i.first, !templ.isEmpty())); + if (!templ.isEmpty()) { + path += "/"; + GVariantHolder v(g_variant_new_string("replace")); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), v.get()); + v.release(); + v.reset(g_variant_new_string(encodeString(templ).getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), + v.get()); + v.release(); + path += "content"; + } + if (!addNode(components, changeset, parent, path, i.second)) { + return false; + } + } + break; + case Node::KIND_ROOT: + assert(false); // this cannot happen + break; + } + return true; +} + +bool addModifications( + Components & components, ChangesetHolder const & changeset, + OString const & parentPathRepresentation, + rtl::Reference<Node> const & parent, OUString const & nodeName, + rtl::Reference<Node> const & node, + Modifications::Node const & modifications) +{ + // It is never necessary to write oor:finalized or oor:mandatory attributes, + // as they cannot be set via the UNO API. + if (modifications.children.empty()) { + assert(parent.is()); + // components themselves have no parent but must have children + if (node.is()) { + OUString templ(node->getTemplateName()); + OString path( + parentPathRepresentation + "/" + + encodeSegment(nodeName, !templ.isEmpty())); + if (!templ.isEmpty()) { + path += "/"; + GVariantHolder v(g_variant_new_string("replace")); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), v.get()); + v.release(); + v.reset(g_variant_new_string(encodeString(templ).getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), + v.get()); + v.release(); + path += "content"; + } + if (!addNode(components, changeset, parent, path, node)) { + return false; + } + } else { + switch (parent->kind()) { + case Node::KIND_LOCALIZED_PROPERTY: + case Node::KIND_GROUP: + { + GVariantHolder v(g_variant_new_tuple(nullptr, 0)); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "g_variant_new_tuple failed"); + return false; + } + OString path(parentPathRepresentation); + if (!nodeName.isEmpty()) { // KIND_LOCALIZED_PROPERTY + path += "/" + encodeSegment(nodeName, false); + } + dconf_changeset_set( + changeset.get(), path.getStr(), v.get()); + v.release(); + break; + } + case Node::KIND_SET: + { + OString path( + parentPathRepresentation + "/" + + encodeSegment(nodeName, true) + "/"); + GVariantHolder v(g_variant_new_string("remove")); + if (v.get() == nullptr) { + SAL_WARN( + "configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), + v.get()); + v.release(); + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), + nullptr); + dconf_changeset_set( + changeset.get(), OString(path + "content/").getStr(), + nullptr); + break; + } + default: + assert(false); // this cannot happen + break; + } + } + } else { + assert(node.is()); + OUString templ(node->getTemplateName()); + OString path( + parentPathRepresentation + "/" + + encodeSegment(nodeName, !templ.isEmpty())); + if (!templ.isEmpty()) { + path += "/"; + GVariantHolder v(g_variant_new_string("fuse")); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "op").getStr(), v.get()); + v.release(); + v.reset(g_variant_new_string(encodeString(templ).getStr())); + if (v.get() == nullptr) { + SAL_WARN("configmgr.dconf", "g_variant_new_string failed"); + return false; + } + dconf_changeset_set( + changeset.get(), OString(path + "template").getStr(), v.get()); + v.release(); + path += "content"; + } + for (auto const & i: modifications.children) { + if (!addModifications( + components, changeset, path, node, i.first, + node->getMember(i.first), i.second)) + { + return false; + } + } + } + return true; +} + +} + +void readLayer(Data & data, int layer) { + GObjectHolder<DConfClient> client(dconf_client_new()); + if (client.get() == nullptr) { + SAL_WARN("configmgr.dconf", "dconf_client_new failed"); + return; + } + readDir( + data, layer, rtl::Reference<Node>(), data.getComponents(), client, + getRoot() + "/"); +} + +void writeModifications(Components & components, Data & data) { + GObjectHolder<DConfClient> client(dconf_client_new()); + if (client.get() == nullptr) { + SAL_WARN("configmgr.dconf", "dconf_client_new failed"); + } + ChangesetHolder cs(dconf_changeset_new()); + if (cs.get() == nullptr) { + SAL_WARN("configmgr.dconf", "dconf_changeset_new failed"); + return; + } + for (auto const & i: data.modifications.getRoot().children) { + if (!addModifications( + components, cs, getRoot(), rtl::Reference<Node>(), i.first, + data.getComponents().findNode(Data::NO_LAYER, i.first), + i.second)) + { + return; + } + } + if (!dconf_client_change_sync( + client.get(), cs.get(), nullptr, nullptr, nullptr)) + { + //TODO: GError + SAL_WARN("configmgr.dconf", "dconf_client_change_sync failed"); + return; + } + data.modifications.clear(); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |