[gnome-terminal] all: Add settings schema verifier



commit cfa851b9e4851805cbcdd477961a6c3b11c4bee5
Author: Christian Persch <chpe src gnome org>
Date:   Fri Jun 4 20:13:18 2021 +0200

    all: Add settings schema verifier
    
    Add external schemas, and build a reference gschema.compiled. At
    startup, verify that the installed compiled schemas conform to that
    reference schema; if not, use the reference schema as schema source
    instead.
    
    This fixes the problem where, on upgrade, or through an otherwise failed
    recompile of the gschemas.compiled cache, where the upgrade to
    gnome-terminal added ettings keys, the server will abort when trying to get
    that new key.
    
    E.g. https://bugzilla.redhat.com/show_bug.cgi?id=1961775 which is just
    the latest instance of this problem occurring.

 meson.build                  |   2 +
 src/external.gschema.xml     | 143 +++++++++++++++
 src/meson.build              |  15 ++
 src/terminal-app.cc          |  11 +-
 src/terminal-client-utils.cc |   1 +
 src/terminal-util.cc         | 421 ++++++++++++++++++++++++++++++++++++++-----
 src/terminal-util.hh         |   5 +-
 7 files changed, 545 insertions(+), 53 deletions(-)
---
diff --git a/meson.build b/meson.build
index 3a430262..d09893ed 100644
--- a/meson.build
+++ b/meson.build
@@ -80,6 +80,7 @@ gt_localedir  = get_option('localedir')
 gt_prefix     = get_option('prefix')
 
 gt_pkgdatadir = gt_datadir / gt_name
+gt_pkglibdir  = gt_libdir  / gt_name
 
 gt_schemadir = gt_datadir / 'glib-2.0' / 'schemas'
 
@@ -327,6 +328,7 @@ configure_file(
 
 # Utilities
 
+glib_compile_schemas = find_program('glib-compile-schemas')
 xsltproc = find_program('xsltproc')
 
 # Subdirs
diff --git a/src/external.gschema.xml b/src/external.gschema.xml
new file mode 100644
index 00000000..79c31664
--- /dev/null
+++ b/src/external.gschema.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright © 2021 Christian Persch
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<schemalist>
+
+  <!-- From gsettings-desktop-schemas -->
+
+  <schema id="org.gnome.desktop.interface"
+          gettext-domain="gsettings-desktop-schemas"
+          path="/org/gnome/desktop/interface/">
+    <key name="monospace-font-name"
+         type="s">
+      <default>'DejaVu Sans Mono 10'</default>
+    </key>
+  </schema>
+
+  <enum id="org.gnome.desktop.GDesktopProxyMode">
+    <value nick="none"
+           value="0"/>
+    <value nick="manual"
+           value="1"/>
+    <value nick="auto"
+           value="2"/>
+  </enum>
+
+  <schema id="org.gnome.system.proxy.http"
+          gettext-domain="gsettings-desktop-schemas"
+          path="/system/proxy/http/">
+    <key name="host"
+         type="s">
+      <default>''</default>
+    </key>
+    <key name="port"
+         type="i">
+      <range min="0"
+             max="65535" />
+      <default>8080</default>
+    </key>
+    <key name="use-authentication"
+         type="b">
+      <default>false</default>
+    </key>
+    <key name="authentication-user"
+         type="s">
+      <default>''</default>
+    </key>
+    <key name="authentication-password"
+         type="s">
+      <default>''</default>
+    </key>
+  </schema>
+
+  <schema id="org.gnome.system.proxy.https"
+          gettext-domain="gsettings-desktop-schemas"
+          path="/system/proxy/https/">
+    <key name="host"
+         type="s">
+      <default>''</default>
+    </key>
+    <key name="port"
+         type="i">
+      <range min="0"
+             max="65535" />
+      <default>0</default>
+    </key>
+  </schema>
+
+  <schema id="org.gnome.system.proxy.ftp"
+          gettext-domain="gsettings-desktop-schemas"
+          path="/system/proxy/ftp/">
+    <key name="host"
+         type="s">
+      <default>''</default>
+    </key>
+    <key name="port"
+         type="i">
+      <range min="0"
+             max="65535" />
+      <default>0</default>
+    </key>
+  </schema>
+
+  <schema id="org.gnome.system.proxy.socks"
+          gettext-domain="gsettings-desktop-schemas"
+          path="/system/proxy/socks/">
+    <key name="host"
+         type="s">
+      <default>''</default>
+    </key>
+    <key name="port"
+         type="i">
+      <range min="0"
+             max="65535" />
+      <default>0</default>
+    </key>
+  </schema>
+
+  <schema id="org.gnome.system.proxy"
+          gettext-domain="gsettings-desktop-schemas"
+          path="/system/proxy/">
+    <child name="http"
+           schema="org.gnome.system.proxy.http" />
+    <child name="https"
+           schema="org.gnome.system.proxy.https" />
+    <child name="ftp"
+           schema="org.gnome.system.proxy.ftp" />
+    <child name="socks"
+           schema="org.gnome.system.proxy.socks" />
+    <key name="mode"
+         enum="org.gnome.desktop.GDesktopProxyMode">
+      <default>'none'</default>
+    </key>
+    <key name="ignore-hosts"
+         type="as">
+      <default>['localhost', '127.0.0.0/8', '::1']</default>
+    </key>
+  </schema>
+
+  <!-- From gtk+ -->
+
+  <schema id="org.gtk.Settings.Debug"
+          path="/org/gtk/settings/debug/">
+    <key name="enable-inspector-keybinding"
+         type="b">
+      <default>false</default>
+    </key>
+  </schema>
+
+</schemalist>
diff --git a/src/meson.build b/src/meson.build
index fe0c77ce..5491ba5f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -208,6 +208,7 @@ server_cxxflags = [
   '-DTERMINAL_COMPILATION',
   '-DVTE_DISABLE_DEPRECATION_WARNINGS',
   '-DTERM_LOCALEDIR="@0@"'.format(gt_prefix / gt_localedir),
+  '-DTERM_PKGLIBDIR="@0@"'.format(gt_prefix / gt_pkglibdir),
 ]
 
 server_deps = [
@@ -293,6 +294,7 @@ client_cxxflags = [
   '-DTERM_DATADIR="@0@"'.format(gt_prefix / gt_datadir),
   '-DTERM_LOCALEDIR="@0@"'.format(gt_prefix / gt_localedir),
   '-DTERM_PKGDATADIR="@0@"'.format(gt_prefix / gt_pkgdatadir),
+  '-DTERM_PKGLIBDIR="@0@"'.format(gt_prefix / gt_pkglibdir),
 ]
 
 client_deps = [
@@ -327,6 +329,19 @@ meson.add_install_script(
   gt_prefix / gt_schemadir,
 )
 
+reference_schemas = custom_target(
+  'gschemas.compiled',
+  command: [
+    glib_compile_schemas,
+    '--targetdir', meson.current_build_dir(),
+    '--schema-file', files(gt_dns_name + '.gschema.xml'),
+    '--schema-file', files('external.gschema.xml'),
+  ],
+  install: true,
+  install_dir: gt_prefix / gt_pkglibdir,
+  output: 'gschemas.compiled',
+)
+
 # Nautilus extension
 
 if get_option('nautilus_extension')
diff --git a/src/terminal-app.cc b/src/terminal-app.cc
index 3f456a2e..39d3da16 100644
--- a/src/terminal-app.cc
+++ b/src/terminal-app.cc
@@ -70,8 +70,6 @@
 #define GTK_SETTING_PREFER_DARK_THEME           "gtk-application-prefer-dark-theme"
 
 #define GTK_DEBUG_SETTING_SCHEMA                "org.gtk.Settings.Debug"
-#define GTK_DEBUG_ENABLE_INSPECTOR_KEY          "enable-inspector-keybinding"
-#define GTK_DEBUG_ENABLE_INSPECTOR_TYPE         G_VARIANT_TYPE_BOOLEAN
 
 #ifdef DISUNIFY_NEW_TERMINAL_SECTION
 #error Use a gsettings override instead
@@ -810,7 +808,7 @@ terminal_app_init (TerminalApp *app)
 
   gtk_window_set_default_icon_name (GNOME_TERMINAL_ICON_NAME);
 
-  app->schema_source = g_settings_schema_source_get_default();
+  app->schema_source = terminal_g_settings_schema_source_get_default();
 
   /* Desktop proxy settings */
   app->system_proxy_settings = terminal_g_settings_new(app->schema_source,
@@ -845,10 +843,9 @@ terminal_app_init (TerminalApp *app)
                                                  TERMINAL_SETTING_SCHEMA);
 
   /* Gtk debug settings */
-  app->gtk_debug_settings = terminal_g_settings_new_checked(app->schema_source,
-                                                            GTK_DEBUG_SETTING_SCHEMA,
-                                                            GTK_DEBUG_ENABLE_INSPECTOR_KEY,
-                                                            GTK_DEBUG_ENABLE_INSPECTOR_TYPE);
+  app->gtk_debug_settings = terminal_g_settings_new(app->schema_source,
+                                                    GTK_DEBUG_SETTING_SCHEMA);
+;
 
   /* These are internal settings that exists only for distributions
    * to override, so we cache them on startup and don't react to changes.
diff --git a/src/terminal-client-utils.cc b/src/terminal-client-utils.cc
index 4d7ed70d..122ea3b8 100644
--- a/src/terminal-client-utils.cc
+++ b/src/terminal-client-utils.cc
@@ -294,6 +294,7 @@ terminal_g_settings_new_with_path (GSettingsSchemaSource* source,
     g_settings_schema_source_lookup(source,
                                     schema_id,
                                     TRUE /* recursive */);
+  g_assert_nonnull(schema);
 
   return g_settings_new_full(schema,
                              nullptr /* default backend */,
diff --git a/src/terminal-util.cc b/src/terminal-util.cc
index 7fbb1f78..e6f49583 100644
--- a/src/terminal-util.cc
+++ b/src/terminal-util.cc
@@ -773,48 +773,6 @@ s_to_rgba (GVariant *variant,
   return TRUE;
 }
 
-/**
- * terminal_g_settings_new_checked:
- * @schema_source: a #GSettingsSchemaSource
- * @schema_id: a settings schema ID
- * @mandatory_key: the name of a key that must exist in the schema
- * @mandatory_key_type: the expected value type of @mandatory_key
- *
- * Creates a #GSettings for @schema_id, if this schema exists and
- * has a key named @mandatory_key (if non-%nullptr) with the value type
- * @mandatory_key_type.
- *
- * Returns: (transfer full): a new #GSettings, or %nullptr
- */
-GSettings *
-terminal_g_settings_new_checked(GSettingsSchemaSource* schema_source,
-                                const char *schema_id,
-                                const char *mandatory_key,
-                                const GVariantType *mandatory_key_type)
-{
-  gs_unref_settings_schema GSettingsSchema *schema;
-
-  schema = g_settings_schema_source_lookup(schema_source,
-                                           schema_id,
-                                           TRUE);
-  if (schema == nullptr)
-    return nullptr;
-
-  if (mandatory_key) {
-    gs_unref_settings_schema_key GSettingsSchemaKey *key;
-
-    key = g_settings_schema_get_key (schema, mandatory_key);
-    if (key == nullptr)
-      return nullptr;
-
-    if (!g_variant_type_equal (g_settings_schema_key_get_value_type (key),
-                               mandatory_key_type))
-      return nullptr;
-  }
-
-  return g_settings_new_full (schema, nullptr, nullptr);
-}
-
 /**
  * terminal_g_settings_get_rgba:
  * @settings: a #GSettings
@@ -1605,3 +1563,382 @@ terminal_util_check_envv(char const* const* strv)
 
   return TRUE;
 }
+
+#define TERMINAL_SCHEMA_VERIFIER_ERROR (g_quark_from_static_string("TerminalSchemaVerifier"))
+
+typedef enum {
+  TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
+  TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
+  TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
+  TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
+  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
+  TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
+} TerminalSchemaVerifierError;
+
+static gboolean
+strv_contains(char const* const* strv,
+              char const* str)
+{
+  if (strv == nullptr)
+    return FALSE;
+
+  for (size_t i = 0; strv[i]; i++) {
+    if (g_str_equal (strv[i], str))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+static gboolean
+schema_key_range_compatible(GSettingsSchema* source_schema,
+                            GSettingsSchemaKey* source_key,
+                            char const* key,
+                            GSettingsSchemaKey* reference_key,
+                            GError** error)
+{
+  gs_unref_variant GVariant* source_range =
+    g_settings_schema_key_get_range(source_key);
+  gs_unref_variant GVariant* reference_range =
+    g_settings_schema_key_get_range(reference_key);
+
+  char const* source_type = nullptr;
+  gs_unref_variant GVariant* source_data = nullptr;
+  g_variant_get(source_range, "(&sv)", &source_type, &source_data);
+
+  char const* reference_type = nullptr;
+  gs_unref_variant GVariant* reference_data = nullptr;
+  g_variant_get(reference_range, "(&sv)", &reference_type, &reference_data);
+
+  if (!g_str_equal(source_type, reference_type)) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE,
+                "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                key, source_type, reference_type);
+    return FALSE;
+  }
+
+  if (g_str_equal(reference_type, "type"))
+    ; /* no constraints; this is fine */
+  else if (g_str_equal(reference_type, "enum")) {
+    size_t source_values_len = 0;
+    gs_free char const** source_values = g_variant_get_strv(source_data, &source_values_len);
+
+    size_t reference_values_len = 0;
+    gs_free char const** reference_values = g_variant_get_strv(reference_data, &reference_values_len);
+
+    /* Check that every enum value in source is valid according to the reference */
+    for (size_t i = 0; i < source_values_len; ++i) {
+      if (!strv_contains(reference_values, source_values[i])) {
+        g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                    TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_ENUM_VALUE,
+                    "Schema \"%s\" key \"%s\" has enum value \"%s\" not in reference schema",
+                    g_settings_schema_get_id(source_schema),
+                    key, source_values[i]);
+        return FALSE;
+      }
+    }
+  } else if (g_str_equal(reference_type, "flags")) {
+    /* Our schemas don't use flags. If that changes, need to implement this! */
+    g_assert_not_reached();
+  } else if (g_str_equal(reference_type, "range")) {
+    if (!g_variant_is_of_type(source_data,
+                              g_variant_get_type(reference_data))) {
+      char const* source_type_str = g_variant_get_type_string(source_data);
+      char const* reference_type_str = g_variant_get_type_string(reference_data);
+      g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_MISMATCH,
+                  "Schema \"%s\" key \"%s\" has range type \"%s\" but reference range type is \"%s\"",
+                  g_settings_schema_get_id(source_schema),
+                  key, source_type_str, reference_type_str);
+      return FALSE;
+    }
+
+    /* The source interval must be contained within the reference interval */
+    gboolean ok = FALSE;
+
+    if (g_variant_is_of_type(reference_data, G_VARIANT_TYPE("(ii)"))) {
+      int source_min, source_max;
+      g_variant_get(source_data, "(ii)", &source_min, &source_max);
+
+      int reference_min, reference_max;
+      g_variant_get(reference_data, "(ii)", &reference_min, &reference_max);
+
+      ok = (source_min >= reference_min && source_max <= reference_max);
+    } else if (g_variant_is_of_type(reference_data, G_VARIANT_TYPE("(dd)"))) {
+      double source_min, source_max;
+      g_variant_get(source_data, "(dd)", &source_min, &source_max);
+
+      double reference_min, reference_max;
+      g_variant_get(reference_data, "(dd)", &reference_min, &reference_max);
+
+      ok = (source_min >= reference_min && source_max <= reference_max);
+    } else {
+      /* Our schemas don't use this. If that changes, need to implement this! */
+      g_assert_not_reached();
+    }
+
+    if (!ok) {
+      g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                  TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_INTERVAL,
+                  "Schema \"%s\" key \"%s\" has range interval not contained in reference range interval",
+                  g_settings_schema_get_id(source_schema), key);
+        return FALSE;
+    }
+  } else {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_RANGE_TYPE_UNKNOWN,
+                "Schema \"%s\" key \"%s\" has unknown range type \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                key, reference_type);
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+schema_verify_key(GSettingsSchema* source_schema,
+                  char const* key,
+                  GSettingsSchema* reference_schema,
+                  GError** error)
+{
+  if (!g_settings_schema_has_key(source_schema, key)) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_MISSING,
+                "Schema \"%s\" has missing key \"%s\"",
+                g_settings_schema_get_id(source_schema), key);
+    return FALSE;
+  }
+
+  gs_unref_settings_schema_key GSettingsSchemaKey* source_key =
+    g_settings_schema_get_key(source_schema, key);
+  g_assert_nonnull(source_key);
+
+  gs_unref_settings_schema_key GSettingsSchemaKey* reference_key =
+    g_settings_schema_get_key(reference_schema, key);
+  g_assert_nonnull(reference_key);
+
+  GVariantType const* source_type = g_settings_schema_key_get_value_type(source_key);
+  GVariantType const* reference_type = g_settings_schema_key_get_value_type(reference_key);
+  if (!g_variant_type_equal(source_type, reference_type)) {
+    gs_free char* source_type_str = g_variant_type_dup_string(source_type);
+    gs_free char* reference_type_str = g_variant_type_dup_string(reference_type);
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_TYPE,
+                "Schema \"%s\" has type \"%s\" but reference type is \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                source_type_str, reference_type_str);
+    return FALSE;
+  }
+
+  gs_unref_variant GVariant* source_default = g_settings_schema_key_get_default_value(source_key);
+  if (!g_settings_schema_key_range_check(reference_key, source_default)) {
+    gs_free char* source_value_str = g_variant_print(source_default, TRUE);
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_KEY_DEFAULT,
+                "Schema \"%s\" default value \"%s\" does not conform to reference schema",
+                g_settings_schema_get_id(source_schema), source_value_str);
+    return FALSE;
+  }
+
+  if (!schema_key_range_compatible(source_schema,
+                                   source_key,
+                                   key,
+                                   reference_key,
+                                   error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+schema_verify_child(GSettingsSchema* source_schema,
+                    char const* child_name,
+                    GSettingsSchema* reference_schema,
+                    GError** error)
+{
+  /* Should verify the child's schema ID is as expected and exists in
+   * the source, but there appears to be no API to get the schema ID of
+   * the child.
+   *
+   * We work around this missing verification by never calling
+   * g_settings_get_child() and instead always constructing the child
+   * GSettings directly; and the existence and correctness of that
+   * schema is verified by the per-schema checks.
+   */
+
+  return TRUE;
+}
+
+static gboolean
+schema_verify(GSettingsSchema* source_schema,
+              GSettingsSchema* reference_schema,
+              GError** error)
+{
+  /* Verify path */
+  char const* source_path = g_settings_schema_get_path(source_schema);
+  char const* reference_path = g_settings_schema_get_path(reference_schema);
+  if (g_strcmp0(source_path, reference_path) != 0) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_SCHEMA_PATH,
+                "Schema \"%s\" has path \"%s\" but reference path is \"%s\"",
+                g_settings_schema_get_id(source_schema),
+                source_path ? source_path : "(null)",
+                reference_path ? reference_path : "(null)");
+    return FALSE;
+  }
+
+  /* Verify keys */
+  gs_strfreev char** keys = g_settings_schema_list_keys(reference_schema);
+  if (keys) {
+    for (int i = 0; keys[i]; ++i) {
+      if (!schema_verify_key(source_schema,
+                             keys[i],
+                             reference_schema,
+                             error))
+        return FALSE;
+    }
+  }
+
+  /* Verify child schemas */
+  gs_strfreev char** source_children = g_settings_schema_list_children(source_schema);
+  gs_strfreev char** reference_children = g_settings_schema_list_children(reference_schema);
+  if (reference_children) {
+    for (size_t i = 0; reference_children[i]; ++i) {
+      if (!strv_contains((char const* const*)source_children, reference_children[i])) {
+        g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                    TERMINAL_SCHEMA_VERIFIER_CHILD_MISSING,
+                    "Schema \"%s\" has missing child \"%s\"",
+                    g_settings_schema_get_id(source_schema),
+                    reference_children[i]);
+        return FALSE;
+      }
+
+      if (!schema_verify_child(source_schema,
+                               reference_children[i],
+                               reference_schema,
+                               error))
+          return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+static gboolean
+schemas_source_verify_schema_by_name(GSettingsSchemaSource* source,
+                                     char const* schema_name,
+                                     GSettingsSchemaSource* reference_source,
+                                     GError** error)
+{
+  gs_unref_settings_schema GSettingsSchema* source_schema =
+    g_settings_schema_source_lookup(source, schema_name, TRUE /* recursive */);
+
+  if (!source_schema) {
+    g_set_error(error, TERMINAL_SCHEMA_VERIFIER_ERROR,
+                TERMINAL_SCHEMA_VERIFIER_SCHEMA_MISSING,
+                "Schema \"%s\" is missing", schema_name);
+    return FALSE;
+  }
+
+  gs_unref_settings_schema GSettingsSchema* reference_schema =
+    g_settings_schema_source_lookup(reference_source,
+                                    schema_name,
+                                    FALSE /* recursive */);
+  g_assert_nonnull(reference_schema);
+
+  return schema_verify(source_schema,
+                       reference_schema,
+                       error);
+}
+
+static gboolean
+schemas_source_verify_schemas(GSettingsSchemaSource* source,
+                              char const* const* schemas,
+                              GSettingsSchemaSource* reference_source,
+                              GError** error)
+{
+  if (!schemas)
+    return TRUE;
+
+  for (int i = 0; schemas[i]; ++i) {
+    if (!schemas_source_verify_schema_by_name(source,
+                                              schemas[i],
+                                              reference_source,
+                                              error))
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+schemas_source_verify(GSettingsSchemaSource* source,
+                      GSettingsSchemaSource* reference_source,
+                      GError** error)
+{
+  gs_strfreev char** reloc_schemas = nullptr;
+  gs_strfreev char** nonreloc_schemas = nullptr;
+
+  g_settings_schema_source_list_schemas(reference_source,
+                                        FALSE /* recursive */,
+                                        &reloc_schemas,
+                                        &nonreloc_schemas);
+
+  if (!schemas_source_verify_schemas(source,
+                                     (char const* const*)reloc_schemas,
+                                     reference_source,
+                                     error))
+    return FALSE;
+
+  if (!schemas_source_verify_schemas(source,
+                                     (char const* const*)nonreloc_schemas,
+                                     reference_source,
+                                     error))
+    return FALSE;
+
+  return TRUE;
+}
+
+
+GSettingsSchemaSource*
+terminal_g_settings_schema_source_get_default(void)
+{
+  GSettingsSchemaSource* default_source = g_settings_schema_source_get_default();
+
+  gs_free_error GError* error = nullptr;
+  GSettingsSchemaSource* reference_source =
+    g_settings_schema_source_new_from_directory(TERM_PKGLIBDIR,
+                                                nullptr /* parent source */,
+                                                FALSE /* trusted */,
+                                                &error);
+  if (!reference_source)  {
+    /* Can only use the installed schemas, or abort here. */
+    g_printerr("Failed to load reference schemas: %s\n"
+               "Using unverified installed schemas.\n",
+               error->message);
+
+    g_settings_schema_source_unref(reference_source);
+    return g_settings_schema_source_ref(default_source);
+  }
+
+  if (!schemas_source_verify(default_source, reference_source, &error)) {
+    g_printerr("Installed schemas failed verification: %s\n"
+               "Falling back to built-in reference schemas.\n",
+               error->message);
+
+    return reference_source; /* transfer */
+  }
+
+  /* Installed schemas verified; use them. */
+  g_settings_schema_source_unref(reference_source);
+  return g_settings_schema_source_ref(default_source);
+}
diff --git a/src/terminal-util.hh b/src/terminal-util.hh
index 888a0fb0..f4b7292b 100644
--- a/src/terminal-util.hh
+++ b/src/terminal-util.hh
@@ -71,10 +71,7 @@ char **terminal_util_get_etc_shells (void);
 
 gboolean terminal_util_get_is_shell (const char *command);
 
-GSettings *terminal_g_settings_new_checked(GSettingsSchemaSource* schema_source,
-                                           const char *schema_id,
-                                           const char *mandatory_key,
-                                           const GVariantType *mandatory_key_type);
+GSettingsSchemaSource* terminal_g_settings_schema_source_get_default(void);
 
 const GdkRGBA *terminal_g_settings_get_rgba (GSettings  *settings,
                                              const char *key,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]