[gnome-terminal/gnome-3-40] all: Add settings schema verifier



commit 417a3ada79e042a119a4c9c99adc866de127bfd5
Author: Christian Persch <chpe src gnome org>
Date:   Fri Jun 4 20:14:42 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.

 src/Makefile.am             |  16 ++
 src/external.gschema.xml    | 143 +++++++++++++++
 src/terminal-app.c          |  11 +-
 src/terminal-client-utils.c |   1 +
 src/terminal-util.c         | 421 +++++++++++++++++++++++++++++++++++++++-----
 src/terminal-util.h         |   5 +-
 6 files changed, 544 insertions(+), 53 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 4583cfcb..1f43873b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -99,6 +99,7 @@ gnome_terminal_server_CPPFLAGS = \
        -DTERMINAL_COMPILATION \
        -DVTE_DISABLE_DEPRECATION_WARNINGS \
        -DTERM_LOCALEDIR="\"$(datadir)/locale\"" \
+       -DTERM_PKGLIBDIR="\"$(pkglibdir)\"" \
        $(AM_CPPFLAGS)
 
 gnome_terminal_server_CFLAGS = \
@@ -241,6 +242,7 @@ gnome_terminal_CPPFLAGS = \
        -DTERM_DATADIR="\"$(datadir)\"" \
        -DTERM_LOCALEDIR="\"$(datadir)/locale\"" \
        -DTERM_PKGDATADIR="\"$(pkgdatadir)\"" \
+       -DTERM_PKGLIBDIR="\"$(pkglibdir)\"" \
        $(AM_CPPFLAGS)
 
 gnome_terminal_CFLAGS = \
@@ -320,12 +322,25 @@ gsettings_SCHEMAS = \
        org.gnome.Terminal.gschema.xml \
        $(NULL)
 
+external_gsettings_schemas = \
+       external.gschema.xml \
+       $(NULL)
+
+referenceschemadir = $(pkglibdir)
+referenceschema_DATA = \
+       gschemas.compiled \
+       $(NULL)
+
+gschemas.compiled: $(gsettings_SCHEMAS) $(external_gsettings_schemas)
+       $(AM_V_GEN)$(GLIB_COMPILE_SCHEMAS) $(patsubst %,--schema-file=%,$^) --targetdir=$(builddir)
+
 CLEANFILES = \
        stamp-terminal-type-builtins.h \
        gnome-terminal.schemas \
        stamp-terminal-type-builtins.h \
        org.gnome.Terminal.service \
        gnome-terminal-server.service \
+       gschemas.compiled \
        $(BUILT_SOURCES)
 
 EXTRA_DIST = \
@@ -347,6 +362,7 @@ EXTRA_DIST = \
        $(about_DATA) \
        $(builder_DATA) \
        $(gsettings_SCHEMAS) \
+       $(external_gsettings_schemas) \
        $(NULL)
 
 @GSETTINGS_RULES@
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/terminal-app.c b/src/terminal-app.c
index 06599acd..cf790fb4 100644
--- a/src/terminal-app.c
+++ b/src/terminal-app.c
@@ -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
@@ -807,7 +805,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,
@@ -842,10 +840,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.c b/src/terminal-client-utils.c
index 5bca3399..51118cfa 100644
--- a/src/terminal-client-utils.c
+++ b/src/terminal-client-utils.c
@@ -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,
                              NULL /* default backend */,
diff --git a/src/terminal-util.c b/src/terminal-util.c
index b048af1d..b8b6f5a8 100644
--- a/src/terminal-util.c
+++ b/src/terminal-util.c
@@ -775,48 +775,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-%NULL) with the value type
- * @mandatory_key_type.
- *
- * Returns: (transfer full): a new #GSettings, or %NULL
- */
-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 == NULL)
-    return NULL;
-
-  if (mandatory_key) {
-    gs_unref_settings_schema_key GSettingsSchemaKey *key;
-
-    key = g_settings_schema_get_key (schema, mandatory_key);
-    if (key == NULL)
-      return NULL;
-
-    if (!g_variant_type_equal (g_settings_schema_key_get_value_type (key),
-                               mandatory_key_type))
-      return NULL;
-  }
-
-  return g_settings_new_full (schema, NULL, NULL);
-}
-
 /**
  * terminal_g_settings_get_rgba:
  * @settings: a #GSettings
@@ -1607,3 +1565,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 == NULL)
+    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 = NULL;
+  gs_unref_variant GVariant* source_data = NULL;
+  g_variant_get(source_range, "(&sv)", &source_type, &source_data);
+
+  char const* reference_type = NULL;
+  gs_unref_variant GVariant* reference_data = NULL;
+  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 = NULL;
+  gs_strfreev char** nonreloc_schemas = NULL;
+
+  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 = NULL;
+  GSettingsSchemaSource* reference_source =
+    g_settings_schema_source_new_from_directory(TERM_PKGLIBDIR,
+                                                NULL /* 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.h b/src/terminal-util.h
index 888a0fb0..f4b7292b 100644
--- a/src/terminal-util.h
+++ b/src/terminal-util.h
@@ -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]