/* -*- 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 #include #include #include #include #include #include #include extern "C" { // // "common/dconf-changeset.h etc. lack extern "C" wrapper for C++" #include } #include #include #include #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: " 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 { namespace dconf { namespace { template 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, OUString(u'\0')); } else if (slash && string->match("2F", i + 1)) { *string = string->replaceAt(i, 3, "/"); } else if (string->match("5C", i + 1)) { *string = string->replaceAt(i + 1, 2, ""); } 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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long string value for key " << key); return false; } if (!rtl_convertStringToUString( &value->pData, p, static_cast(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 * 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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long hexbinary value for key " << key); return false; } value->realloc(static_cast(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 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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long boolean list for key " << key); return false; } css::uno::Sequence v(static_cast(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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long short list for key " << key); return false; } css::uno::Sequence v(static_cast(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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long int list for key " << key); return false; } css::uno::Sequence v(static_cast(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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long long list for key " << key); return false; } css::uno::Sequence v(static_cast(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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long double list for key " << key); return false; } css::uno::Sequence v(static_cast(n)); static_assert(std::is_same::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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long string list for key " << key); return false; } css::uno::Sequence v(static_cast(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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long hexbinary list for key " << key); return false; } css::uno::Sequence> v( static_cast(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 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 const & client, OString const & path, rtl::Reference const & node, int layer) { if (!dconf_client_is_writable(client.get(), path.getStr())) { node->setFinalized(layer); } } void readDir( Data & data, int layer, rtl::Reference const & node, NodeMap & members, GObjectHolder 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 > static_cast::type>( std::numeric_limits::max())) { SAL_WARN("configmgr.dconf", "too long dir/key in dir " << dir); continue; } OString s(*p, static_cast(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(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 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(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 prop( static_cast(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 locProp( static_cast(node.get())); css::uno::Any value; if (readValue( client, path, locProp->getStaticType(), locProp->isNillable(), false, &value) == ReadValue::Error) { continue; } static_cast(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 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())); break; case TYPE_SHORT: v.reset(g_variant_new_int16(value.get())); break; case TYPE_INT: v.reset(g_variant_new_int32(value.get())); break; case TYPE_LONG: v.reset(g_variant_new_int64(value.get())); break; case TYPE_DOUBLE: v.reset(g_variant_new_double(value.get())); break; case TYPE_STRING: v.reset( g_variant_new_string( encodeString(value.get()).getStr())); break; case TYPE_HEXBINARY: { css::uno::Sequence seq( value.get>()); 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 seq( value.get>()); 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 seq( value.get>()); 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 seq( value.get>()); 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 seq( value.get>()); 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 seq( value.get>()); 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: { css::uno::Sequence seq( value.get>()); std::vector vs; for (sal_Int32 i = 0; i != seq.getLength(); ++i) { children.emplace_front( g_variant_new_string(encodeString(seq[i]).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: { css::uno::Sequence> seq( value.get< css::uno::Sequence>>()); std::vector vs; for (sal_Int32 i = 0; i != seq.getLength(); ++i) { 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[i].getConstArray(), seq[i].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(), seq.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 const & parent, OString const & pathRepresentation, rtl::Reference const & node) { switch (node->kind()) { case Node::KIND_PROPERTY: { PropertyNode * prop = static_cast(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(parent.get()); if (!addProperty( changeset, pathRepresentation, locprop->getStaticType(), locprop->isNillable(), static_cast(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 const & parent, OUString const & nodeName, rtl::Reference 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 client(dconf_client_new()); if (client.get() == nullptr) { SAL_WARN("configmgr.dconf", "dconf_client_new failed"); return; } readDir( data, layer, rtl::Reference(), data.getComponents(), client, getRoot() + "/"); } void writeModifications(Components & components, Data & data) { GObjectHolder 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(), 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: */