gsettingsschema: Use the trusted parameter
[platform/upstream/glib.git] / gio / gsettingsschema.c
index 51567c1..235349a 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright © 2010 Codethink Limited
+ * Copyright © 2011 Canonical Limited
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
 
 #include "config.h"
 
-#include "gsettingsschema.h"
+#include "gsettingsschema-internal.h"
+#include "gsettings.h"
+
 #include "gvdb/gvdb-reader.h"
+#include "strinfo.c"
 
 #include <glibintl.h>
+#include <string.h>
 
-G_DEFINE_TYPE (GSettingsSchema, g_settings_schema, G_TYPE_OBJECT)
-
-struct _GSettingsSchemaPrivate
+struct _GSettingsSchema
 {
   const gchar *gettext_domain;
   const gchar *path;
   GQuark *items;
   gint n_items;
   GvdbTable *table;
-  gchar *name;
+  gchar *id;
+
+  gint ref_count;
 };
 
-static GSList *schema_sources;
+typedef struct _GSettingsSchemaSource GSettingsSchemaSource;
+
+G_DEFINE_BOXED_TYPE (GSettingsSchemaSource, g_settings_schema_source, g_settings_schema_source_ref, g_settings_schema_source_unref)
+G_DEFINE_BOXED_TYPE (GSettingsSchema, g_settings_schema, g_settings_schema_ref, g_settings_schema_unref)
+
+struct _GSettingsSchemaSource
+{
+  GSettingsSchemaSource *parent;
+  GvdbTable *table;
+
+  gint ref_count;
+};
+
+static GSettingsSchemaSource *schema_sources;
+
+static void
+prepend_schema_table (GvdbTable *table)
+{
+  GSettingsSchemaSource *source;
+
+  /* we steal the reference from 'schema_sources' for our ->parent */
+  source = g_slice_new (GSettingsSchemaSource);
+  source->parent = schema_sources;
+  source->table = table;
+  source->ref_count = 1;
+
+  schema_sources = source;
+}
+
+GSettingsSchemaSource *
+g_settings_schema_source_ref (GSettingsSchemaSource *source)
+{
+  g_atomic_int_inc (&source->ref_count);
+
+  return source;
+}
+
+void
+g_settings_schema_source_unref (GSettingsSchemaSource *source)
+{
+  if (g_atomic_int_dec_and_test (&source->ref_count))
+    {
+      if (source == schema_sources)
+        g_error ("g_settings_schema_source_unref() called too many times on the default schema source");
+
+      if (source->parent)
+        g_settings_schema_source_unref (source->parent);
+      gvdb_table_unref (source->table);
+
+      g_slice_free (GSettingsSchemaSource, source);
+    }
+}
+
+GSettingsSchemaSource *
+g_settings_schema_source_new_from_directory (GSettingsSchemaSource  *parent,
+                                             const gchar            *directory,
+                                             gboolean                trusted,
+                                             GError                **error)
+{
+  GSettingsSchemaSource *source;
+  GvdbTable *table;
+  gchar *filename;
+
+  filename = g_build_filename (directory, "gschemas.compiled", NULL);
+  table = gvdb_table_new (filename, trusted, error);
+  g_free (filename);
+
+  if (table == NULL)
+    return NULL;
+
+  source = g_slice_new (GSettingsSchemaSource);
+  source->parent = parent ? g_settings_schema_source_ref (parent) : NULL;
+  source->table = table;
+  source->ref_count = 1;
+
+  return source;
+}
 
 static void
 initialise_schema_sources (void)
 {
   static gsize initialised;
 
+  /* need a separate variable because 'schema_sources' may legitimately
+   * be null if we have zero valid schema sources
+   */
   if G_UNLIKELY (g_once_init_enter (&initialised))
     {
-      const gchar * const *dir;
+      const gchar * const *dirs;
       const gchar *path;
+      gint i;
+
+      /* iterate in reverse: count up, then count down */
+      dirs = g_get_system_data_dirs ();
+      for (i = 0; dirs[i]; i++);
 
-      for (dir = g_get_system_data_dirs (); *dir; dir++)
+      while (i--)
         {
           gchar *filename;
           GvdbTable *table;
 
-          filename = g_build_filename (*dir, "glib-2.0", "schemas",
-                                       "gschemas.compiled", NULL);
+          filename = g_build_filename (dirs[i], "glib-2.0", "schemas", "gschemas.compiled", NULL);
           table = gvdb_table_new (filename, TRUE, NULL);
 
           if (table != NULL)
-            schema_sources = g_slist_prepend (schema_sources, table);
+            prepend_schema_table (table);
 
           g_free (filename);
         }
 
-      schema_sources = g_slist_reverse (schema_sources);
-
       if ((path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL)
         {
           gchar *filename;
@@ -74,7 +160,7 @@ initialise_schema_sources (void)
           table = gvdb_table_new (filename, TRUE, NULL);
 
           if (table != NULL)
-            schema_sources = g_slist_prepend (schema_sources, table);
+            prepend_schema_table (table);
 
           g_free (filename);
         }
@@ -83,6 +169,48 @@ initialise_schema_sources (void)
     }
 }
 
+GSettingsSchemaSource *
+g_settings_schema_source_get_default (void)
+{
+  initialise_schema_sources ();
+
+  return schema_sources;
+}
+
+GSettingsSchema *
+g_settings_schema_source_lookup (GSettingsSchemaSource *source,
+                                 const gchar           *schema_id,
+                                 gboolean               recursive)
+{
+  GSettingsSchema *schema;
+  GvdbTable *table;
+
+  g_return_val_if_fail (source != NULL, NULL);
+  g_return_val_if_fail (schema_id != NULL, NULL);
+
+  table = gvdb_table_get_table (source->table, schema_id);
+
+  if (table == NULL && recursive)
+    for (source = source->parent; source; source = source->parent)
+      if ((table = gvdb_table_get_table (source->table, schema_id)))
+        break;
+
+  if (table == NULL)
+    return NULL;
+
+  schema = g_slice_new0 (GSettingsSchema);
+  schema->ref_count = 1;
+  schema->id = g_strdup (schema_id);
+  schema->table = table;
+  schema->path = g_settings_schema_get_string (schema, ".path");
+  schema->gettext_domain = g_settings_schema_get_string (schema, ".gettext-domain");
+
+  if (schema->gettext_domain)
+    bind_textdomain_codeset (schema->gettext_domain, "UTF-8");
+
+  return schema;
+}
+
 static gboolean
 steal_item (gpointer key,
             gpointer value,
@@ -104,9 +232,9 @@ ensure_schema_lists (void)
 {
   if (g_once_init_enter (&schema_lists_initialised))
     {
+      GSettingsSchemaSource *source;
       GHashTable *single, *reloc;
       const gchar **ptr;
-      GSList *source;
       gchar **list;
       gint i;
 
@@ -118,9 +246,9 @@ ensure_schema_lists (void)
       single = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
       reloc = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
-      for (source = schema_sources; source; source = source->next)
+      for (source = schema_sources; source; source = source->parent)
         {
-          list = gvdb_table_list (source->data, "");
+          list = gvdb_table_list (source->table, "");
 
           g_assert (list != NULL);
 
@@ -131,13 +259,15 @@ ensure_schema_lists (void)
                 {
                   GvdbTable *table;
 
-                  table = gvdb_table_get_table (source->data, list[i]);
+                  table = gvdb_table_get_table (source->table, list[i]);
                   g_assert (table != NULL);
 
                   if (gvdb_table_has_value (table, ".path"))
                     g_hash_table_insert (single, g_strdup (list[i]), NULL);
                   else
                     g_hash_table_insert (reloc, g_strdup (list[i]), NULL);
+
+                  gvdb_table_unref (table);
                 }
             }
 
@@ -212,34 +342,25 @@ g_settings_list_relocatable_schemas (void)
   return relocatable_schema_list;
 }
 
-static void
-g_settings_schema_finalize (GObject *object)
+GSettingsSchema *
+g_settings_schema_ref (GSettingsSchema *schema)
 {
-  GSettingsSchema *schema = G_SETTINGS_SCHEMA (object);
-
-  gvdb_table_unref (schema->priv->table);
-  g_free (schema->priv->items);
-  g_free (schema->priv->name);
-
-  G_OBJECT_CLASS (g_settings_schema_parent_class)
-    ->finalize (object);
-}
+  g_atomic_int_inc (&schema->ref_count);
 
-static void
-g_settings_schema_init (GSettingsSchema *schema)
-{
-  schema->priv = G_TYPE_INSTANCE_GET_PRIVATE (schema, G_TYPE_SETTINGS_SCHEMA,
-                                              GSettingsSchemaPrivate);
+  return schema;
 }
 
-static void
-g_settings_schema_class_init (GSettingsSchemaClass *class)
+void
+g_settings_schema_unref (GSettingsSchema *schema)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (class);
-
-  object_class->finalize = g_settings_schema_finalize;
+  if (g_atomic_int_dec_and_test (&schema->ref_count))
+    {
+      gvdb_table_unref (schema->table);
+      g_free (schema->items);
+      g_free (schema->id);
 
-  g_type_class_add_private (class, sizeof (GSettingsSchemaPrivate));
+      g_slice_free (GSettingsSchema, schema);
+    }
 }
 
 const gchar *
@@ -249,7 +370,7 @@ g_settings_schema_get_string (GSettingsSchema *schema,
   const gchar *result = NULL;
   GVariant *value;
 
-  if ((value = gvdb_table_get_raw_value (schema->priv->table, key)))
+  if ((value = gvdb_table_get_raw_value (schema->table, key)))
     {
       result = g_variant_get_string (value, NULL);
       g_variant_unref (value);
@@ -258,40 +379,6 @@ g_settings_schema_get_string (GSettingsSchema *schema,
   return result;
 }
 
-GSettingsSchema *
-g_settings_schema_new (const gchar *name)
-{
-  GSettingsSchema *schema;
-  GvdbTable *table = NULL;
-  GSList *source;
-
-  initialise_schema_sources ();
-
-  for (source = schema_sources; source; source = source->next)
-    {
-      GvdbTable *file = source->data;
-
-      if ((table = gvdb_table_get_table (file, name)))
-        break;
-    }
-
-  if (table == NULL)
-    g_error ("Settings schema '%s' is not installed\n", name);
-
-  schema = g_object_new (G_TYPE_SETTINGS_SCHEMA, NULL);
-  schema->priv->name = g_strdup (name);
-  schema->priv->table = table;
-  schema->priv->path =
-    g_settings_schema_get_string (schema, ".path");
-  schema->priv->gettext_domain =
-    g_settings_schema_get_string (schema, ".gettext-domain");
-
-  if (schema->priv->gettext_domain)
-    bind_textdomain_codeset (schema->priv->gettext_domain, "UTF-8");
-
-  return schema;
-}
-
 GVariantIter *
 g_settings_schema_get_value (GSettingsSchema *schema,
                              const gchar     *key)
@@ -299,10 +386,10 @@ g_settings_schema_get_value (GSettingsSchema *schema,
   GVariantIter *iter;
   GVariant *value;
 
-  value = gvdb_table_get_raw_value (schema->priv->table, key);
+  value = gvdb_table_get_raw_value (schema->table, key);
 
   if G_UNLIKELY (value == NULL)
-    g_error ("schema does not contain a key named '%s'", key);
+    g_error ("Settings schema '%s' does not contain a key named '%s'", schema->id, key);
 
   iter = g_variant_iter_new (value);
   g_variant_unref (value);
@@ -313,20 +400,20 @@ g_settings_schema_get_value (GSettingsSchema *schema,
 const gchar *
 g_settings_schema_get_path (GSettingsSchema *schema)
 {
-  return schema->priv->path;
+  return schema->path;
 }
 
 const gchar *
 g_settings_schema_get_gettext_domain (GSettingsSchema *schema)
 {
-  return schema->priv->gettext_domain;
+  return schema->gettext_domain;
 }
 
 gboolean
 g_settings_schema_has_key (GSettingsSchema *schema,
                            const gchar     *key)
 {
-  return gvdb_table_has_value (schema->priv->table, key);
+  return gvdb_table_has_value (schema->table, key);
 }
 
 const GQuark *
@@ -335,25 +422,341 @@ g_settings_schema_list (GSettingsSchema *schema,
 {
   gint i, j;
 
-  if (schema->priv->items == NULL)
+  if (schema->items == NULL)
     {
       gchar **list;
       gint len;
 
-      list = gvdb_table_list (schema->priv->table, "");
-      len = g_strv_length (list);
+      list = gvdb_table_list (schema->table, "");
+      len = list ? g_strv_length (list) : 0;
 
-      schema->priv->items = g_new (GQuark, len);
+      schema->items = g_new (GQuark, len);
       j = 0;
 
       for (i = 0; i < len; i++)
         if (list[i][0] != '.')
-          schema->priv->items[j++] = g_quark_from_string (list[i]);
-      schema->priv->n_items = j;
+          schema->items[j++] = g_quark_from_string (list[i]);
+      schema->n_items = j;
 
       g_strfreev (list);
     }
 
-  *n_items = schema->priv->n_items;
-  return schema->priv->items;
+  *n_items = schema->n_items;
+  return schema->items;
+}
+
+const gchar *
+g_settings_schema_get_id (GSettingsSchema *schema)
+{
+  return schema->id;
+}
+
+static inline void
+endian_fixup (GVariant **value)
+{
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+  GVariant *tmp;
+
+  tmp = g_variant_byteswap (*value);
+  g_variant_unref (*value);
+  *value = tmp;
+#endif
+}
+
+void
+g_settings_schema_key_init (GSettingsSchemaKey *key,
+                            GSettingsSchema    *schema,
+                            const gchar        *name)
+{
+  GVariantIter *iter;
+  GVariant *data;
+  guchar code;
+
+  memset (key, 0, sizeof *key);
+
+  iter = g_settings_schema_get_value (schema, name);
+
+  key->schema = g_settings_schema_ref (schema);
+  key->default_value = g_variant_iter_next_value (iter);
+  endian_fixup (&key->default_value);
+  key->type = g_variant_get_type (key->default_value);
+  key->name = g_intern_string (name);
+
+  while (g_variant_iter_next (iter, "(y*)", &code, &data))
+    {
+      switch (code)
+        {
+        case 'l':
+          /* translation requested */
+          g_variant_get (data, "(y&s)", &key->lc_char, &key->unparsed);
+          break;
+
+        case 'e':
+          /* enumerated types... */
+          key->is_enum = TRUE;
+          goto choice;
+
+        case 'f':
+          /* flags... */
+          key->is_flags = TRUE;
+          goto choice;
+
+        choice: case 'c':
+          /* ..., choices, aliases */
+          key->strinfo = g_variant_get_fixed_array (data, &key->strinfo_length, sizeof (guint32));
+          break;
+
+        case 'r':
+          g_variant_get (data, "(**)", &key->minimum, &key->maximum);
+          endian_fixup (&key->minimum);
+          endian_fixup (&key->maximum);
+          break;
+
+        default:
+          g_warning ("unknown schema extension '%c'", code);
+          break;
+        }
+
+      g_variant_unref (data);
+    }
+
+  g_variant_iter_free (iter);
+}
+
+void
+g_settings_schema_key_clear (GSettingsSchemaKey *key)
+{
+  if (key->minimum)
+    g_variant_unref (key->minimum);
+
+  if (key->maximum)
+    g_variant_unref (key->maximum);
+
+  g_variant_unref (key->default_value);
+
+  g_settings_schema_unref (key->schema);
+}
+
+gboolean
+g_settings_schema_key_type_check (GSettingsSchemaKey *key,
+                                  GVariant           *value)
+{
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  return g_variant_is_of_type (value, key->type);
+}
+
+gboolean
+g_settings_schema_key_range_check (GSettingsSchemaKey *key,
+                                   GVariant           *value)
+{
+  if (key->minimum == NULL && key->strinfo == NULL)
+    return TRUE;
+
+  if (g_variant_is_container (value))
+    {
+      gboolean ok = TRUE;
+      GVariantIter iter;
+      GVariant *child;
+
+      g_variant_iter_init (&iter, value);
+      while (ok && (child = g_variant_iter_next_value (&iter)))
+        {
+          ok = g_settings_schema_key_range_check (key, child);
+          g_variant_unref (child);
+        }
+
+      return ok;
+    }
+
+  if (key->minimum)
+    {
+      return g_variant_compare (key->minimum, value) <= 0 &&
+             g_variant_compare (value, key->maximum) <= 0;
+    }
+
+  return strinfo_is_string_valid (key->strinfo, key->strinfo_length,
+                                  g_variant_get_string (value, NULL));
+}
+
+GVariant *
+g_settings_schema_key_range_fixup (GSettingsSchemaKey *key,
+                                   GVariant           *value)
+{
+  const gchar *target;
+
+  if (g_settings_schema_key_range_check (key, value))
+    return g_variant_ref (value);
+
+  if (key->strinfo == NULL)
+    return NULL;
+
+  if (g_variant_is_container (value))
+    {
+      GVariantBuilder builder;
+      GVariantIter iter;
+      GVariant *child;
+
+      g_variant_iter_init (&iter, value);
+      g_variant_builder_init (&builder, g_variant_get_type (value));
+
+      while ((child = g_variant_iter_next_value (&iter)))
+        {
+          GVariant *fixed;
+
+          fixed = g_settings_schema_key_range_fixup (key, child);
+          g_variant_unref (child);
+
+          if (fixed == NULL)
+            {
+              g_variant_builder_clear (&builder);
+              return NULL;
+            }
+
+          g_variant_builder_add_value (&builder, fixed);
+          g_variant_unref (fixed);
+        }
+
+      return g_variant_ref_sink (g_variant_builder_end (&builder));
+    }
+
+  target = strinfo_string_from_alias (key->strinfo, key->strinfo_length,
+                                      g_variant_get_string (value, NULL));
+  return target ? g_variant_ref_sink (g_variant_new_string (target)) : NULL;
+}
+
+
+GVariant *
+g_settings_schema_key_get_translated_default (GSettingsSchemaKey *key)
+{
+  const gchar *translated;
+  GError *error = NULL;
+  const gchar *domain;
+  GVariant *value;
+
+  domain = g_settings_schema_get_gettext_domain (key->schema);
+
+  if (key->lc_char == '\0')
+    /* translation not requested for this key */
+    return NULL;
+
+  if (key->lc_char == 't')
+    translated = g_dcgettext (domain, key->unparsed, LC_TIME);
+  else
+    translated = g_dgettext (domain, key->unparsed);
+
+  if (translated == key->unparsed)
+    /* the default value was not translated */
+    return NULL;
+
+  /* try to parse the translation of the unparsed default */
+  value = g_variant_parse (key->type, translated, NULL, NULL, &error);
+
+  if (value == NULL)
+    {
+      g_warning ("Failed to parse translated string `%s' for "
+                 "key `%s' in schema `%s': %s", key->unparsed, key->name,
+                 g_settings_schema_get_id (key->schema), error->message);
+      g_warning ("Using untranslated default instead.");
+      g_error_free (error);
+    }
+
+  else if (!g_settings_schema_key_range_check (key, value))
+    {
+      g_warning ("Translated default `%s' for key `%s' in schema `%s' "
+                 "is outside of valid range", key->unparsed, key->name,
+                 g_settings_schema_get_id (key->schema));
+      g_variant_unref (value);
+      value = NULL;
+    }
+
+  return value;
+}
+
+gint
+g_settings_schema_key_to_enum (GSettingsSchemaKey *key,
+                               GVariant           *value)
+{
+  gboolean it_worked;
+  guint result;
+
+  it_worked = strinfo_enum_from_string (key->strinfo, key->strinfo_length,
+                                        g_variant_get_string (value, NULL),
+                                        &result);
+
+  /* 'value' can only come from the backend after being filtered for validity,
+   * from the translation after being filtered for validity, or from the schema
+   * itself (which the schema compiler checks for validity).  If this assertion
+   * fails then it's really a bug in GSettings or the schema compiler...
+   */
+  g_assert (it_worked);
+
+  return result;
+}
+
+GVariant *
+g_settings_schema_key_from_enum (GSettingsSchemaKey *key,
+                                 gint                value)
+{
+  const gchar *string;
+
+  string = strinfo_string_from_enum (key->strinfo, key->strinfo_length, value);
+
+  if (string == NULL)
+    return NULL;
+
+  return g_variant_new_string (string);
+}
+
+guint
+g_settings_schema_key_to_flags (GSettingsSchemaKey *key,
+                                GVariant           *value)
+{
+  GVariantIter iter;
+  const gchar *flag;
+  guint result;
+
+  result = 0;
+  g_variant_iter_init (&iter, value);
+  while (g_variant_iter_next (&iter, "&s", &flag))
+    {
+      gboolean it_worked;
+      guint flag_value;
+
+      it_worked = strinfo_enum_from_string (key->strinfo, key->strinfo_length, flag, &flag_value);
+      /* as in g_settings_to_enum() */
+      g_assert (it_worked);
+
+      result |= flag_value;
+    }
+
+  return result;
+}
+
+GVariant *
+g_settings_schema_key_from_flags (GSettingsSchemaKey *key,
+                                  guint               value)
+{
+  GVariantBuilder builder;
+  gint i;
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+
+  for (i = 0; i < 32; i++)
+    if (value & (1u << i))
+      {
+        const gchar *string;
+
+        string = strinfo_string_from_enum (key->strinfo, key->strinfo_length, 1u << i);
+
+        if (string == NULL)
+          {
+            g_variant_builder_clear (&builder);
+            return NULL;
+          }
+
+        g_variant_builder_add (&builder, "s", string);
+      }
+
+  return g_variant_builder_end (&builder);
 }