Merge branch 'fix-gdbus-unix-address' into 'master'

Fix failing tests when G_MESSAGES_DEBUG is set (again)

See merge request GNOME/glib!247
diff --git a/gio/glib-compile-schemas.c b/gio/glib-compile-schemas.c
index d4340d4..5e1bebb 100644
--- a/gio/glib-compile-schemas.c
+++ b/gio/glib-compile-schemas.c
@@ -2112,6 +2112,7 @@
         }
 
       g_strfreev (groups);
+      g_key_file_free (key_file);
     }
 
   return TRUE;
diff --git a/gio/gresource.c b/gio/gresource.c
index bf54f1d..2844f48 100644
--- a/gio/gresource.c
+++ b/gio/gresource.c
@@ -489,7 +489,7 @@
 {
   if (g_atomic_int_dec_and_test (&resource->ref_count))
     {
-      gvdb_table_unref (resource->table);
+      gvdb_table_free (resource->table);
       g_free (resource);
     }
 }
@@ -512,6 +512,19 @@
   return resource;
 }
 
+static void
+g_resource_error_from_gvdb_table_error (GError **g_resource_error,
+                                        GError  *gvdb_table_error  /* (transfer full) */)
+{
+  if (g_error_matches (gvdb_table_error, G_FILE_ERROR, G_FILE_ERROR_INVAL))
+    g_set_error_literal (g_resource_error,
+                         G_RESOURCE_ERROR, G_RESOURCE_ERROR_INTERNAL,
+                         gvdb_table_error->message);
+  else
+    g_propagate_error (g_resource_error, g_steal_pointer (&gvdb_table_error));
+  g_clear_error (&gvdb_table_error);
+}
+
 /**
  * g_resource_new_from_data:
  * @data: A #GBytes
@@ -528,6 +541,8 @@
  * Otherwise this function will internally create a copy of the memory since
  * GLib 2.56, or in older versions fail and exit the process.
  *
+ * If @data is empty or corrupt, %G_RESOURCE_ERROR_INTERNAL will be returned.
+ *
  * Returns: (transfer full): a new #GResource, or %NULL on error
  *
  * Since: 2.32
@@ -538,6 +553,7 @@
 {
   GvdbTable *table;
   gboolean unref_data = FALSE;
+  GError *local_error = NULL;
 
   if (((guintptr) g_bytes_get_data (data, NULL)) % sizeof (gpointer) != 0)
     {
@@ -546,19 +562,16 @@
       unref_data = TRUE;
     }
 
-  table = gvdb_table_new_from_data (g_bytes_get_data (data, NULL),
-                                    g_bytes_get_size (data),
-                                    TRUE,
-                                    g_bytes_ref (data),
-                                    (GvdbRefFunc)g_bytes_ref,
-                                    (GDestroyNotify)g_bytes_unref,
-                                    error);
+  table = gvdb_table_new_from_bytes (data, TRUE, &local_error);
 
   if (unref_data)
     g_bytes_unref (data);
 
   if (table == NULL)
-    return NULL;
+    {
+      g_resource_error_from_gvdb_table_error (error, g_steal_pointer (&local_error));
+      return NULL;
+    }
 
   return g_resource_new_from_table (table);
 }
@@ -574,6 +587,11 @@
  * If you want to use this resource in the global resource namespace you need
  * to register it with g_resources_register().
  *
+ * If @filename is empty or the data in it is corrupt,
+ * %G_RESOURCE_ERROR_INTERNAL will be returned. If @filename doesn’t exist, or
+ * there is an error in reading it, an error from g_mapped_file_new() will be
+ * returned.
+ *
  * Returns: (transfer full): a new #GResource, or %NULL on error
  *
  * Since: 2.32
@@ -583,10 +601,14 @@
                  GError      **error)
 {
   GvdbTable *table;
+  GError *local_error = NULL;
 
-  table = gvdb_table_new (filename, FALSE, error);
+  table = gvdb_table_new (filename, FALSE, &local_error);
   if (table == NULL)
-    return NULL;
+    {
+      g_resource_error_from_gvdb_table_error (error, g_steal_pointer (&local_error));
+      return NULL;
+    }
 
   return g_resource_new_from_table (table);
 }
diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c
index 17b7e3b..38c9d78 100644
--- a/gio/gsettingsschema.c
+++ b/gio/gsettingsschema.c
@@ -231,7 +231,7 @@
 
       if (source->parent)
         g_settings_schema_source_unref (source->parent);
-      gvdb_table_unref (source->table);
+      gvdb_table_free (source->table);
       g_free (source->directory);
 
       if (source->text_tables)
@@ -267,6 +267,9 @@
  * Generally, you should set @trusted to %TRUE for files installed by the
  * system and to %FALSE for files in the home directory.
  *
+ * In either case, an empty file or some types of corruption in the file will
+ * result in %G_FILE_ERROR_INVAL being returned.
+ *
  * If @parent is non-%NULL then there are two effects.
  *
  * First, if g_settings_schema_source_lookup() is called with the
@@ -802,7 +805,7 @@
               else
                 g_hash_table_add (reloc, schema);
 
-              gvdb_table_unref (table);
+              gvdb_table_free (table);
             }
         }
 
@@ -928,7 +931,7 @@
         g_settings_schema_unref (schema->extends);
 
       g_settings_schema_source_unref (schema->source);
-      gvdb_table_unref (schema->table);
+      gvdb_table_free (schema->table);
       g_free (schema->items);
       g_free (schema->id);
 
@@ -1188,7 +1191,7 @@
                   g_hash_table_iter_remove (&iter);
               }
 
-            gvdb_table_unref (child_table);
+            gvdb_table_free (child_table);
           }
 
       /* Now create the list */
diff --git a/gio/gvdb/gvdb-builder.c b/gio/gvdb/gvdb-builder.c
index 8b9baa0..2383e60 100644
--- a/gio/gvdb/gvdb-builder.c
+++ b/gio/gvdb/gvdb-builder.c
@@ -339,6 +339,13 @@
 #undef chunk
 
   memset (*bloom_filter, 0, n_bloom_words * sizeof (guint32_le));
+
+  /* NOTE - the code to actually fill in the bloom filter here is missing.
+   * Patches welcome! 
+   *
+   * http://en.wikipedia.org/wiki/Bloom_filter
+   * http://0pointer.de/blog/projects/bloom.html
+   */
 }
 
 static void
diff --git a/gio/gvdb/gvdb-reader.c b/gio/gvdb/gvdb-reader.c
index 510eba2..aa3154f 100644
--- a/gio/gvdb/gvdb-reader.c
+++ b/gio/gvdb/gvdb-reader.c
@@ -23,15 +23,11 @@
 #include <string.h>
 
 struct _GvdbTable {
-  gint ref_count;
+  GBytes *bytes;
 
   const gchar *data;
   gsize size;
 
-  gpointer user_data;
-  GvdbRefFunc ref_user_data;
-  GDestroyNotify unref_user_data;
-
   gboolean byteswapped;
   gboolean trusted;
 
@@ -124,80 +120,81 @@
   file->n_hash_items = size / sizeof (struct gvdb_hash_item);
 }
 
-static GvdbTable *
-new_from_data (const void    *data,
-	       gsize          data_len,
-	       gboolean       trusted,
-	       gpointer       user_data,
-	       GvdbRefFunc    ref,
-	       GDestroyNotify unref,
-	       const char    *filename,
-	       GError       **error)
+/**
+ * gvdb_table_new_from_bytes:
+ * @bytes: the #GBytes with the data
+ * @trusted: if the contents of @bytes are trusted
+ * @error: %NULL, or a pointer to a %NULL #GError
+ *
+ * Creates a new #GvdbTable from the contents of @bytes.
+ *
+ * This call can fail if the header contained in @bytes is invalid or if @bytes
+ * is empty; if so, %G_FILE_ERROR_INVAL will be returned.
+ *
+ * You should call gvdb_table_free() on the return result when you no
+ * longer require it.
+ *
+ * Returns: a new #GvdbTable
+ **/
+GvdbTable *
+gvdb_table_new_from_bytes (GBytes    *bytes,
+                           gboolean   trusted,
+                           GError   **error)
 {
+  const struct gvdb_header *header;
   GvdbTable *file;
 
   file = g_slice_new0 (GvdbTable);
-  file->data = data;
-  file->size = data_len;
+  file->bytes = g_bytes_ref (bytes);
+  file->data = g_bytes_get_data (bytes, &file->size);
   file->trusted = trusted;
-  file->ref_count = 1;
-  file->ref_user_data = ref;
-  file->unref_user_data = unref;
-  file->user_data = user_data;
 
-  if (sizeof (struct gvdb_header) <= file->size)
-    {
-      const struct gvdb_header *header = (gpointer) file->data;
+  if (file->size < sizeof (struct gvdb_header))
+    goto invalid;
 
-      if (header->signature[0] == GVDB_SIGNATURE0 &&
-          header->signature[1] == GVDB_SIGNATURE1 &&
-          guint32_from_le (header->version) == 0)
-        file->byteswapped = FALSE;
+  header = (gpointer) file->data;
 
-      else if (header->signature[0] == GVDB_SWAPPED_SIGNATURE0 &&
-               header->signature[1] == GVDB_SWAPPED_SIGNATURE1 &&
-               guint32_from_le (header->version) == 0)
-        file->byteswapped = TRUE;
+  if (header->signature[0] == GVDB_SIGNATURE0 &&
+      header->signature[1] == GVDB_SIGNATURE1 &&
+      guint32_from_le (header->version) == 0)
+    file->byteswapped = FALSE;
 
-      else
-        {
-	  if (filename)
-	    g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
-			 "%s: invalid header", filename);
-	  else
-	    g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
-			 "invalid gvdb header");
-          g_slice_free (GvdbTable, file);
-	  if (unref)
-	    unref (user_data);
+  else if (header->signature[0] == GVDB_SWAPPED_SIGNATURE0 &&
+           header->signature[1] == GVDB_SWAPPED_SIGNATURE1 &&
+           guint32_from_le (header->version) == 0)
+    file->byteswapped = TRUE;
 
-          return NULL;
-        }
+  else
+    goto invalid;
 
-      gvdb_table_setup_root (file, &header->root);
-    }
+  gvdb_table_setup_root (file, &header->root);
 
   return file;
+
+invalid:
+  g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "invalid gvdb header");
+
+  g_bytes_unref (file->bytes);
+
+  g_slice_free (GvdbTable, file);
+
+  return NULL;
 }
 
 /**
  * gvdb_table_new:
- * @filename: the path to the hash file
- * @trusted: if the contents of @filename are trusted
+ * @filename: a filename
+ * @trusted: if the contents of @bytes are trusted
  * @error: %NULL, or a pointer to a %NULL #GError
  *
- * Creates a new #GvdbTable from the contents of the file found at
- * @filename.
+ * Creates a new #GvdbTable using the #GMappedFile for @filename as the
+ * #GBytes.
  *
- * The only time this function fails is if the file cannot be opened.
+ * This function will fail if the file cannot be opened.
  * In that case, the #GError that is returned will be an error from
  * g_mapped_file_new().
  *
- * An empty or otherwise corrupted file is considered to be a valid
- * #GvdbTable with no entries.
- *
- * You should call gvdb_table_unref() on the return result when you no
- * longer require it.
+ * An empty or corrupt file will result in %G_FILE_ERROR_INVAL.
  *
  * Returns: a new #GvdbTable
  **/
@@ -207,53 +204,21 @@
                 GError      **error)
 {
   GMappedFile *mapped;
+  GvdbTable *table;
+  GBytes *bytes;
 
-  if ((mapped = g_mapped_file_new (filename, FALSE, error)) == NULL)
+  mapped = g_mapped_file_new (filename, FALSE, error);
+  if (!mapped)
     return NULL;
 
-  return new_from_data (g_mapped_file_get_contents (mapped),
-			g_mapped_file_get_length (mapped),
-			trusted,
-			mapped,
-			(GvdbRefFunc)g_mapped_file_ref,
-			(GDestroyNotify)g_mapped_file_unref,
-			filename,
-			error);
-}
+  bytes = g_mapped_file_get_bytes (mapped);
+  table = gvdb_table_new_from_bytes (bytes, trusted, error);
+  g_mapped_file_unref (mapped);
+  g_bytes_unref (bytes);
 
-/**
- * gvdb_table_new_from_data:
- * @data: the data
- * @data_len: the length of @data in bytes
- * @trusted: if the contents of @data are trusted
- * @user_data: User supplied data that owns @data
- * @ref: Ref function for @user_data
- * @unref: Unref function for @user_data
- *
- * Creates a new #GvdbTable from the data in @data.
- *
- * An empty or otherwise corrupted data is considered to be a valid
- * #GvdbTable with no entries.
- *
- * You should call gvdb_table_unref() on the return result when you no
- * longer require it.
- *
- * Returns: a new #GvdbTable
- **/
-GvdbTable *
-gvdb_table_new_from_data (const void    *data,
-			  gsize          data_len,
-			  gboolean       trusted,
-			  gpointer       user_data,
-			  GvdbRefFunc    ref,
-			  GDestroyNotify unref,
-			  GError        **error)
-{
-  return new_from_data (data, data_len,
-			trusted,
-			user_data, ref, unref,
-			NULL,
-			error);
+  g_prefix_error (error, "%s: ", filename);
+
+  return table;
 }
 
 static gboolean
@@ -346,18 +311,6 @@
   return NULL;
 }
 
-static const struct gvdb_hash_item *
-gvdb_table_get_item (GvdbTable  *table,
-                     guint32_le  item_no)
-{
-  guint32 item_no_native = guint32_from_le (item_no);
-
-  if G_LIKELY (item_no_native < table->n_hash_items)
-    return table->hash_items + item_no_native;
-
-  return NULL;
-}
-
 static gboolean
 gvdb_table_list_from_item (GvdbTable                    *table,
                            const struct gvdb_hash_item  *item,
@@ -376,6 +329,155 @@
   return TRUE;
 }
 
+/**
+ * gvdb_table_get_names:
+ * @table: a #GvdbTable
+ * @length: the number of items returned, or %NULL
+ *
+ * Gets a list of all names contained in @table.
+ *
+ * No call to gvdb_table_get_table(), gvdb_table_list() or
+ * gvdb_table_get_value() will succeed unless it is for one of the
+ * names returned by this function.
+ *
+ * Note that some names that are returned may still fail for all of the
+ * above calls in the case of the corrupted file.  Note also that the
+ * returned strings may not be utf8.
+ *
+ * Returns: a %NULL-terminated list of strings, of length @length
+ **/
+gchar **
+gvdb_table_get_names (GvdbTable *table,
+                      gint      *length)
+{
+  gchar **names;
+  gint n_names;
+  gint filled;
+  gint total;
+  gint i;
+
+  /* We generally proceed by iterating over the list of items in the
+   * hash table (in order of appearance) recording them into an array.
+   *
+   * Each item has a parent item (except root items).  The parent item
+   * forms part of the name of the item.  We could go fetching the
+   * parent item chain at the point that we encounter each item but then
+   * we would need to implement some sort of recursion along with checks
+   * for self-referential items.
+   *
+   * Instead, we do a number of passes.  Each pass will build up one
+   * level of names (starting from the root).  We continue to do passes
+   * until no more items are left.  The first pass will only add root
+   * items and each further pass will only add items whose direct parent
+   * is an item added in the immediately previous pass.  It's also
+   * possible that items get filled if they follow their parent within a
+   * particular pass.
+   *
+   * At most we will have a number of passes equal to the depth of the
+   * tree.  Self-referential items will never be filled in (since their
+   * parent will have never been filled in).  We continue until we have
+   * a pass that fills in no additional items.
+   *
+   * This takes an O(n) algorithm and turns it into O(n*m) where m is
+   * the depth of the tree, but in all sane cases the tree won't be very
+   * deep and the constant factor of this algorithm is lower (and the
+   * complexity of coding it, as well).
+   */
+
+  n_names = table->n_hash_items;
+  names = g_new0 (gchar *, n_names + 1);
+
+  /* 'names' starts out all-NULL.  On each pass we record the number
+   * of items changed from NULL to non-NULL in 'filled' so we know if we
+   * should repeat the loop.  'total' counts the total number of items
+   * filled.  If 'total' ends up equal to 'n_names' then we know that
+   * 'names' has been completely filled.
+   */
+
+  total = 0;
+  do
+    {
+      /* Loop until we have filled no more entries */
+      filled = 0;
+
+      for (i = 0; i < n_names; i++)
+        {
+          const struct gvdb_hash_item *item = &table->hash_items[i];
+          const gchar *name;
+          gsize name_length;
+          guint32 parent;
+
+          /* already got it on a previous pass */
+          if (names[i] != NULL)
+            continue;
+
+          parent = guint32_from_le (item->parent);
+
+          if (parent == 0xffffffffu)
+            {
+              /* it's a root item */
+              name = gvdb_table_item_get_key (table, item, &name_length);
+
+              if (name != NULL)
+                {
+                  names[i] = g_strndup (name, name_length);
+                  filled++;
+                }
+            }
+
+          else if (parent < n_names && names[parent] != NULL)
+            {
+              /* It's a non-root item whose parent was filled in already.
+               *
+               * Calculate the name of this item by combining it with
+               * its parent name.
+               */
+              name = gvdb_table_item_get_key (table, item, &name_length);
+
+              if (name != NULL)
+                {
+                  const gchar *parent_name = names[parent];
+                  gsize parent_length;
+                  gchar *fullname;
+
+                  parent_length = strlen (parent_name);
+                  fullname = g_malloc (parent_length + name_length + 1);
+                  memcpy (fullname, parent_name, parent_length);
+                  memcpy (fullname + parent_length, name, name_length);
+                  fullname[parent_length + name_length] = '\0';
+                  names[i] = fullname;
+                  filled++;
+                }
+            }
+        }
+
+      total += filled;
+    }
+  while (filled && total < n_names);
+
+  /* If the table was corrupted then 'names' may have holes in it.
+   * Collapse those.
+   */
+  if G_UNLIKELY (total != n_names)
+    {
+      GPtrArray *fixed_names;
+
+      fixed_names = g_ptr_array_new ();
+      for (i = 0; i < n_names; i++)
+        if (names[i] != NULL)
+          g_ptr_array_add (fixed_names, names[i]);
+
+      g_free (names);
+      n_names = fixed_names->len;
+      g_ptr_array_add (fixed_names, NULL);
+      names = (gchar **) g_ptr_array_free (fixed_names, FALSE);
+    }
+
+  if (length)
+    *length = n_names;
+
+  return names;
+}
 
 /**
  * gvdb_table_list:
@@ -457,7 +559,15 @@
 gvdb_table_has_value (GvdbTable    *file,
                       const gchar  *key)
 {
-  return gvdb_table_lookup (file, key, 'v') != NULL;
+  static const struct gvdb_hash_item *item;
+  gsize size;
+
+  item = gvdb_table_lookup (file, key, 'v');
+
+  if (item == NULL)
+    return FALSE;
+
+  return gvdb_table_dereference (file, &item->value.pointer, 8, &size) != NULL;
 }
 
 static GVariant *
@@ -466,6 +576,7 @@
 {
   GVariant *variant, *value;
   gconstpointer data;
+  GBytes *bytes;
   gsize size;
 
   data = gvdb_table_dereference (table, &item->value.pointer, 8, &size);
@@ -473,12 +584,11 @@
   if G_UNLIKELY (data == NULL)
     return NULL;
 
-  variant = g_variant_new_from_data (G_VARIANT_TYPE_VARIANT,
-                                     data, size, table->trusted,
-                                     table->unref_user_data,
-                                     table->ref_user_data ? table->ref_user_data (table->user_data) : table->user_data);
+  bytes = g_bytes_new_from_bytes (table->bytes, ((gchar *) data) - table->data, size);
+  variant = g_variant_new_from_bytes (G_VARIANT_TYPE_VARIANT, bytes, table->trusted);
   value = g_variant_get_variant (variant);
   g_variant_unref (variant);
+  g_bytes_unref (bytes);
 
   return value;
 }
@@ -562,7 +672,7 @@
  * contained in the file.  This newly-created #GvdbTable does not depend
  * on the continued existence of @file.
  *
- * You should call gvdb_table_unref() on the return result when you no
+ * You should call gvdb_table_free() on the return result when you no
  * longer require it.
  *
  * Returns: a new #GvdbTable, or %NULL
@@ -580,14 +690,11 @@
     return NULL;
 
   new = g_slice_new0 (GvdbTable);
-  new->user_data = file->ref_user_data ? file->ref_user_data (file->user_data) : file->user_data;
-  new->ref_user_data = file->ref_user_data;
-  new->unref_user_data = file->unref_user_data;
+  new->bytes = g_bytes_ref (file->bytes);
   new->byteswapped = file->byteswapped;
   new->trusted = file->trusted;
   new->data = file->data;
   new->size = file->size;
-  new->ref_count = 1;
 
   gvdb_table_setup_root (new, &item->value.pointer);
 
@@ -595,38 +702,16 @@
 }
 
 /**
- * gvdb_table_ref:
+ * gvdb_table_free:
  * @file: a #GvdbTable
  *
- * Increases the reference count on @file.
- *
- * Returns: a new reference on @file
- **/
-GvdbTable *
-gvdb_table_ref (GvdbTable *file)
-{
-  g_atomic_int_inc (&file->ref_count);
-
-  return file;
-}
-
-/**
- * gvdb_table_unref:
- * @file: a #GvdbTable
- *
- * Decreases the reference count on @file, possibly freeing it.
- *
- * Since: 2.26
+ * Frees @file.
  **/
 void
-gvdb_table_unref (GvdbTable *file)
+gvdb_table_free (GvdbTable *file)
 {
-  if (g_atomic_int_dec_and_test (&file->ref_count))
-    {
-      if (file->unref_user_data)
-	file->unref_user_data (file->user_data);
-      g_slice_free (GvdbTable, file);
-    }
+  g_bytes_unref (file->bytes);
+  g_slice_free (GvdbTable, file);
 }
 
 /**
@@ -646,105 +731,3 @@
 {
   return !!*table->data;
 }
-
-/**
- * gvdb_table_walk:
- * @table: a #GvdbTable
- * @key: a key corresponding to a list
- * @open_func: the #GvdbWalkOpenFunc
- * @value_func: the #GvdbWalkValueFunc
- * @close_func: the #GvdbWalkCloseFunc
- * @user_data: data to pass to the callbacks
- *
- * Looks up the list at @key and iterate over the items in it.
- *
- * First, @open_func is called to signal that we are starting to iterate over
- * the list.  Then the list is iterated.  When all items in the list have been
- * iterated over, the @close_func is called.
- *
- * When iterating, if a given item in the list is a value then @value_func is
- * called.
- *
- * If a given item in the list is itself a list then @open_func is called.  If
- * that function returns %TRUE then the walk begins iterating the items in the
- * sublist, until there are no more items, at which point a matching
- * @close_func call is made.  If @open_func returns %FALSE then no iteration of
- * the sublist occurs and no corresponding @close_func call is made.
- **/
-void
-gvdb_table_walk (GvdbTable         *table,
-                 const gchar       *key,
-                 GvdbWalkOpenFunc   open_func,
-                 GvdbWalkValueFunc  value_func,
-                 GvdbWalkCloseFunc  close_func,
-                 gpointer           user_data)
-{
-  const struct gvdb_hash_item *item;
-  const guint32_le *pointers[64];
-  const guint32_le *enders[64];
-  gsize name_lengths[64];
-  gint index = 0;
-
-  item = gvdb_table_lookup (table, key, 'L');
-  name_lengths[0] = 0;
-  pointers[0] = NULL;
-  enders[0] = NULL;
-  goto start_here;
-
-  while (index)
-    {
-      close_func (name_lengths[index], user_data);
-      index--;
-
-      while (pointers[index] < enders[index])
-        {
-          const gchar *name;
-          gsize name_len;
-
-          item = gvdb_table_get_item (table, *pointers[index]++);
- start_here:
-
-          if (item != NULL &&
-              (name = gvdb_table_item_get_key (table, item, &name_len)))
-            {
-              if (item->type == 'L')
-                {
-                  if (open_func (name, name_len, user_data))
-                    {
-                      guint length = 0;
-
-                      index++;
-                      g_assert (index < 64);
-
-                      gvdb_table_list_from_item (table, item,
-                                                 &pointers[index],
-                                                 &length);
-                      enders[index] = pointers[index] + length;
-                      name_lengths[index] = name_len;
-                    }
-                }
-              else if (item->type == 'v')
-                {
-                  GVariant *value;
-
-                  value = gvdb_table_value_from_item (table, item);
-
-                  if (value != NULL)
-                    {
-                      if (table->byteswapped)
-                        {
-                          GVariant *tmp;
-
-                          tmp = g_variant_byteswap (value);
-                          g_variant_unref (value);
-                          value = tmp;
-                        }
-
-                      value_func (name, name_len, value, user_data);
-                      g_variant_unref (value);
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/gio/gvdb/gvdb-reader.h b/gio/gvdb/gvdb-reader.h
index 449241e..3982773 100644
--- a/gio/gvdb/gvdb-reader.h
+++ b/gio/gvdb/gvdb-reader.h
@@ -24,27 +24,21 @@
 
 typedef struct _GvdbTable GvdbTable;
 
-typedef gpointer (*GvdbRefFunc) (gpointer data);
-
 G_BEGIN_DECLS
 
 G_GNUC_INTERNAL
+GvdbTable *             gvdb_table_new_from_bytes                       (GBytes       *bytes,
+                                                                         gboolean      trusted,
+                                                                         GError      **error);
+G_GNUC_INTERNAL
 GvdbTable *             gvdb_table_new                                  (const gchar  *filename,
                                                                          gboolean      trusted,
                                                                          GError      **error);
 G_GNUC_INTERNAL
-GvdbTable *             gvdb_table_new_from_data                        (const void   *data,
-									 gsize         data_len,
-                                                                         gboolean      trusted,
-									 gpointer      user_data,
-									 GvdbRefFunc   ref,
-									 GDestroyNotify unref,
-									 GError      **error);
+void                    gvdb_table_free                                 (GvdbTable    *table);
 G_GNUC_INTERNAL
-GvdbTable *             gvdb_table_ref                                  (GvdbTable    *table);
-G_GNUC_INTERNAL
-void                    gvdb_table_unref                                (GvdbTable    *table);
-
+gchar **                gvdb_table_get_names                            (GvdbTable    *table,
+                                                                         gint         *length);
 G_GNUC_INTERNAL
 gchar **                gvdb_table_list                                 (GvdbTable    *table,
                                                                          const gchar  *key);
@@ -61,28 +55,9 @@
 G_GNUC_INTERNAL
 gboolean                gvdb_table_has_value                            (GvdbTable    *table,
                                                                          const gchar  *key);
-
 G_GNUC_INTERNAL
 gboolean                gvdb_table_is_valid                             (GvdbTable    *table);
 
-typedef void          (*GvdbWalkValueFunc)                              (const gchar       *name,
-                                                                         gsize              name_len,
-                                                                         GVariant          *value,
-                                                                         gpointer           user_data);
-typedef gboolean      (*GvdbWalkOpenFunc)                               (const gchar       *name,
-                                                                         gsize              name_len,
-                                                                         gpointer           user_data);
-typedef void          (*GvdbWalkCloseFunc)                              (gsize              name_len,
-                                                                         gpointer           user_data);
-
-G_GNUC_INTERNAL
-void                    gvdb_table_walk                                 (GvdbTable         *table,
-                                                                         const gchar       *key,
-                                                                         GvdbWalkOpenFunc   open_func,
-                                                                         GvdbWalkValueFunc  value_func,
-                                                                         GvdbWalkCloseFunc  close_func,
-                                                                         gpointer           user_data);
-
 G_END_DECLS
 
 #endif /* __gvdb_reader_h__ */
diff --git a/gio/gvdb/gvdb.doap b/gio/gvdb/gvdb.doap
index b4ae60c..8c5f3e8 100644
--- a/gio/gvdb/gvdb.doap
+++ b/gio/gvdb/gvdb.doap
@@ -23,9 +23,34 @@
 
   <maintainer>
     <foaf:Person>
-      <foaf:name>Ryan Lortie</foaf:name>
-      <foaf:mbox rdf:resource='mailto:desrt@desrt.ca'/>
-      <gnome:userid>ryanl</gnome:userid>
+      <foaf:name>Matthias Clasen</foaf:name>
+      <foaf:mbox rdf:resource="mailto:mclasen@redhat.com"/>
+      <gnome:userid>matthiasc</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Allison Ryan Lortie</foaf:name>
+      <foaf:mbox rdf:resource="mailto:desrt@desrt.ca"/>
+      <gnome:userid>desrt</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Philip Withnall</foaf:name>
+      <foaf:mbox rdf:resource="mailto:philip@tecnocode.co.uk"/>
+      <foaf:mbox rdf:resource="mailto:withnall@endlessm.com"/>
+      <gnome:userid>pwithnall</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Emmanuele Bassi</foaf:name>
+      <foaf:mbox rdf:resource="mailto:ebassi@gnome.org"/>
+      <gnome:userid>ebassi</gnome:userid>
     </foaf:Person>
   </maintainer>
 
diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c
index 2d18d4d..852a8b7 100644
--- a/gio/tests/gsettings.c
+++ b/gio/tests/gsettings.c
@@ -2353,6 +2353,18 @@
   g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
   g_clear_error (&error);
 
+  /* Test error handling of corrupt compiled files. */
+  source = g_settings_schema_source_new_from_directory ("schema-source-corrupt", parent, TRUE, &error);
+  g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
+  g_assert_null (source);
+  g_clear_error (&error);
+
+  /* Test error handling of empty compiled files. */
+  source = g_settings_schema_source_new_from_directory ("schema-source-empty", parent, TRUE, &error);
+  g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
+  g_assert_null (source);
+  g_clear_error (&error);
+
   /* create a source with the parent */
   source = g_settings_schema_source_new_from_directory ("schema-source", parent, TRUE, &error);
   g_assert_no_error (error);
@@ -2770,6 +2782,12 @@
 
   if (!g_test_subprocess ())
     {
+      GError *local_error = NULL;
+      /* A GVDB header is 6 guint32s, and requires a magic number in the first
+       * two guint32s. A set of zero bytes of a greater length is considered
+       * corrupt. */
+      const guint8 gschemas_compiled_corrupt[sizeof (guint32) * 7] = { 0, };
+
       backend_set = g_getenv ("GSETTINGS_BACKEND") != NULL;
 
       g_setenv ("XDG_DATA_DIRS", ".", TRUE);
@@ -2821,6 +2839,21 @@
                                            "--schema-file=" SRCDIR "/org.gtk.schemasourcecheck.gschema.xml",
                                            NULL, NULL, &result, NULL));
       g_assert (result == 0);
+
+      g_remove ("schema-source-corrupt/gschemas.compiled");
+      g_mkdir ("schema-source-corrupt", 0777);
+      g_file_set_contents ("schema-source-corrupt/gschemas.compiled",
+                           (const gchar *) gschemas_compiled_corrupt,
+                           sizeof (gschemas_compiled_corrupt),
+                           &local_error);
+      g_assert_no_error (local_error);
+
+      g_remove ("schema-source-empty/gschemas.compiled");
+      g_mkdir ("schema-source-empty", 0777);
+      g_file_set_contents ("schema-source-empty/gschemas.compiled",
+                           "", 0,
+                           &local_error);
+      g_assert_no_error (local_error);
    }
 
   g_test_add_func ("/gsettings/basic", test_basic);
diff --git a/gio/tests/meson.build b/gio/tests/meson.build
index 85d31d6..4e5ad25 100644
--- a/gio/tests/meson.build
+++ b/gio/tests/meson.build
@@ -15,69 +15,67 @@
   command: [gengiotypefuncs_prog, '@OUTPUT@', '@INPUT@'])
 
 #  Test programs buildable on all platforms
-gio_tests = [
-  'appmonitor',
-  'async-close-output-stream',
-  'async-splice-output-stream',
-  'buffered-input-stream',
-  'buffered-output-stream',
-  'cancellable',
-  'contexts',
-  'contenttype',
-  'converter-stream',
-  'credentials',
-  'data-input-stream',
-  'data-output-stream',
-  'defaultvalue',
-  'fileattributematcher',
-  'filter-streams',
-  'giomodule',
-  'gsubprocess',
-  'g-file',
-  'g-file-info',
-  'g-icon',
-  'gdbus-addresses',
-  'gdbus-message',
-  'inet-address',
-  'io-stream',
-  'memory-input-stream',
-  'memory-output-stream',
-  'monitor',
-  'mount-operation',
-  'network-address',
-  'network-monitor',
-  'network-monitor-race',
-  'permission',
-  'pollable',
-  'proxy-test',
-  'readwrite',
-  'simple-async-result',
-  'simple-proxy',
-  'sleepy-stream',
-  'socket',
-  'socket-listener',
-  'socket-service',
-  'srvtarget',
-  'task',
-  'vfs',
-  'volumemonitor',
-  'glistmodel',
-  'testfilemonitor',
-  'thumbnail-verification',
-  'tls-certificate',
-  'tls-interaction',
-]
-slow_tests = [
-  'actions',
-  'gdbus-export',
-  'gdbus-threading',
-  'testfilemonitor',
-]
+# FIXME: We are using list of dictionnaries until we can depend on Meson 0.48.0
+# that supports '+=' operator on dictionnaries.
+gio_tests = [{
+  'appmonitor' : {},
+  'async-close-output-stream' : {},
+  'async-splice-output-stream' : {},
+  'buffered-input-stream' : {},
+  'buffered-output-stream' : {},
+  'cancellable' : {},
+  'contexts' : {},
+  'contenttype' : {},
+  'converter-stream' : {},
+  'credentials' : {},
+  'data-input-stream' : {},
+  'data-output-stream' : {},
+  'defaultvalue' : {'extra_sources' : [giotypefuncs_inc]},
+  'fileattributematcher' : {},
+  'filter-streams' : {},
+  'giomodule' : {},
+  'gsubprocess' : {},
+  'g-file' : {},
+  'g-file-info' : {},
+  'g-icon' : {},
+  'gdbus-addresses' : {},
+  'gdbus-message' : {},
+  'inet-address' : {},
+  'io-stream' : {},
+  'memory-input-stream' : {},
+  'memory-output-stream' : {},
+  'monitor' : {},
+  'mount-operation' : {},
+  'network-address' : {},
+  'network-monitor' : {},
+  'network-monitor-race' : {},
+  'permission' : {},
+  'pollable' : {},
+  'proxy-test' : {},
+  'readwrite' : {},
+  'simple-async-result' : {},
+  'simple-proxy' : {},
+  'sleepy-stream' : {},
+  'socket' : {},
+  'socket-listener' : {},
+  'socket-service' : {},
+  'srvtarget' : {},
+  'task' : {},
+  'vfs' : {},
+  'volumemonitor' : {},
+  'glistmodel' : {},
+  'testfilemonitor' : {'suite' : ['slow']},
+  'thumbnail-verification' : {},
+  'tls-certificate' : {'extra_sources' : ['gtesttlsbackend.c']},
+  'tls-interaction' : {'extra_sources' : ['gtesttlsbackend.c']},
+}]
 
-test_extra_programs = [
-  ['gdbus-connection-flush-helper'],
-  ['gdbus-testserver'],
-]
+# FIXME: We are using list of dictionnaries until we can depend on Meson 0.48.0
+# that supports '+=' operator on dictionnaries.
+test_extra_programs = [{
+  'gdbus-connection-flush-helper' : {},
+  'gdbus-testserver' : {},
+}]
 
 test_env = environment()
 test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
@@ -110,49 +108,57 @@
 if dbus1_dep.found()
   glib_conf.set('HAVE_DBUS1', 1)
 
-  exe = executable('gdbus-serialization',
-      'gdbus-serialization.c', 'gdbus-tests.c',
-      install : false,
-      c_args : test_c_args,
-      dependencies : common_gio_tests_deps + [dbus1_dep])
-  test('gdbus-serialization', exe, env : test_env, suite : ['gio'])
+  gio_tests += [{
+    'gdbus-serialization' : {
+      'extra_sources' : ['gdbus-tests.c'],
+      'dependencies' : [dbus1_dep],
+    }
+  }]
 endif
 
 #  Test programs buildable on UNIX only
 if host_machine.system() != 'windows'
-  gio_tests += [
-    'file',
-    'gdbus-peer',
-    'gdbus-peer-object-manager',
-    'live-g-file',
-    'socket-address',
-    'stream-rw_all',
-    'unix-fd',
-    'unix-mounts',
-    'unix-streams',
-    'g-file-info-filesystem-readonly',
-    'gschema-compile',
-    'trash',
-  ]
+  gio_tests += [{
+    'file' : {},
+    'gdbus-peer' : {'dependencies' : [libgdbus_example_objectmanager_dep]},
+    'gdbus-peer-object-manager' : {},
+    'live-g-file' : {},
+    'socket-address' : {},
+    'stream-rw_all' : {},
+    'unix-fd' : {},
+    'unix-mounts' : {},
+    'unix-streams' : {},
+    'g-file-info-filesystem-readonly' : {},
+    'gschema-compile' : {'install' : false},
+    'trash' : {},
+  }]
 
   # Uninstalled because of the check-for-executable logic in DesktopAppInfo
   # unable to find the installed executable
   if not glib_have_cocoa
-    gio_tests += [
-      'appinfo',
-      'desktop-app-info',
-    ]
+    gio_tests += [{
+      'appinfo' : {
+        'install' : false,
+      },
+      'desktop-app-info' : {
+        'install' : false,
+      },
+    }]
   endif
 
-  test_extra_programs += [
-    ['basic-application'],
-    ['dbus-launch'],
-    ['appinfo-test'],
-  ]
+  test_extra_programs += [{
+    'basic-application' : {},
+    'dbus-launch' : {},
+    'appinfo-test' : {},
+  }]
 
   if not glib_have_cocoa
-    test_extra_programs += [['apps']]
-    gio_tests += ['mimeapps']
+    test_extra_programs += [{
+      'apps' : {},
+    }]
+    gio_tests += [{
+      'mimeapps' : {},
+    }]
   endif
 
   #  Test programs that need to bring up a session bus (requires dbus-daemon)
@@ -181,71 +187,61 @@
                    '--annotate', 'org.project.Bar::TestSignal[array_of_strings]', 'Key8', 'Value8',
                    '@INPUT@'])
 
-    gio_dbus_tests = [
-      ['actions', [], []],
-      ['gdbus-auth', [], []],
-      ['gdbus-bz627724', [], []],
-      ['gdbus-close-pending', [], []],
-      ['gdbus-connection', [], []],
-      ['gdbus-connection-loss', [], []],
-      ['gdbus-connection-slow', [], []],
-      ['gdbus-error', [], []],
-      ['gdbus-exit-on-close', [], []],
-      ['gdbus-export', [], []],
-      ['gdbus-introspection', [], []],
-      ['gdbus-names', [], []],
-      ['gdbus-proxy', [], []],
-      ['gdbus-proxy-threads', [], [dbus1_dep]],
-      ['gdbus-proxy-well-known-name', [], []],
-      ['gdbus-test-codegen', [gdbus_test_codegen_generated], []],
-      ['gdbus-threading', [], []],
-      ['gmenumodel', [], []],
-      ['gnotification', ['gnotification-server.c'], []],
-    ]
+    extra_sources = ['gdbus-sessionbus.c', 'gdbus-tests.c']
+
+    gio_tests += [{
+      'actions' : {
+        'extra_sources' : extra_sources,
+        'suite' : ['slow'],
+      },
+      'gdbus-auth' : {'extra_sources' : extra_sources},
+      'gdbus-bz627724' : {'extra_sources' : extra_sources},
+      'gdbus-close-pending' : {'extra_sources' : extra_sources},
+      'gdbus-connection' : {'extra_sources' : extra_sources},
+      'gdbus-connection-loss' : {'extra_sources' : extra_sources},
+      'gdbus-connection-slow' : {'extra_sources' : extra_sources},
+      'gdbus-error' : {'extra_sources' : extra_sources},
+      'gdbus-exit-on-close' : {'extra_sources' : extra_sources},
+      'gdbus-export' : {
+        'extra_sources' : extra_sources,
+        'suite' : ['slow'],
+      },
+      'gdbus-introspection' : {'extra_sources' : extra_sources},
+      'gdbus-names' : {'extra_sources' : extra_sources},
+      'gdbus-proxy' : {'extra_sources' : extra_sources},
+      'gdbus-proxy-threads' : {
+        'extra_sources' : extra_sources,
+        'dependencies' : [dbus1_dep],
+      },
+      'gdbus-proxy-well-known-name' : {'extra_sources' : extra_sources},
+      'gdbus-test-codegen' : {
+        'extra_sources' : [extra_sources, gdbus_test_codegen_generated],
+      },
+      'gdbus-threading' : {
+        'extra_sources' : extra_sources,
+        'suite' : ['slow'],
+      },
+      'gmenumodel' : {'extra_sources' : extra_sources},
+      'gnotification' : {
+        'extra_sources' : [extra_sources, 'gnotification-server.c'],
+      },
+      'gdbus-test-codegen-old' : {
+        'source' : 'gdbus-test-codegen.c',
+        'extra_sources' : [extra_sources, gdbus_test_codegen_generated],
+        'c_args' : ['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_36',
+                    '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_36'],
+      },
+      'gapplication' : {'extra_sources' : extra_sources},
+      'gdbus-unix-addresses' : {},
+    }]
 
     if not glib_have_cocoa
-      gio_dbus_tests += [['dbus-appinfo', [], []]]
+      gio_tests += [{
+        'dbus-appinfo' : {
+          'extra_sources' : extra_sources,
+        },
+      }]
     endif
-
-    # separate loop because extra source files for each test
-    foreach dbus_test : gio_dbus_tests
-      test_name = dbus_test[0]
-      extra_src = dbus_test[1]
-      extra_deps = dbus_test[2]
-      exe = executable(test_name, '@0@.c'.format(test_name),
-          'gdbus-sessionbus.c', 'gdbus-tests.c', extra_src,
-          install : false,
-          c_args : test_c_args,
-          dependencies : common_gio_tests_deps + extra_deps)
-      # These tests may take more than 30 seconds to run on the CI infrastructure
-      if slow_tests.contains(test_name)
-        test(test_name, exe, env : test_env, timeout : 120, suite : ['gio', 'slow'])
-      else
-        test(test_name, exe, env : test_env, suite : ['gio'])
-      endif
-    endforeach
-
-    exe = executable('gdbus-test-codegen-old', 'gdbus-test-codegen.c',
-          'gdbus-sessionbus.c', 'gdbus-tests.c', gdbus_test_codegen_generated,
-          install : false,
-          c_args : test_c_args + ['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_36', '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_36'],
-          dependencies : common_gio_tests_deps)
-    test('gdbus-test-codegen-old', exe, env : test_env, suite : ['gio'])
-
-    # There is already a gapplication exe target in gio so need to use a
-    # different name for the unit test executable, since we can't have two
-    # targets of the same name even if in different directories
-    # (FIXME: just rename source file to gapplication-test.c)
-    if not glib_have_cocoa
-      exe = executable('gapplication-test', 'gapplication.c',
-                       'gdbus-sessionbus.c', 'gdbus-tests.c',
-                       install : false,
-                       c_args : test_c_args,
-                       dependencies : common_gio_tests_deps)
-    endif
-    test('gapplication', exe, env : test_env, suite : ['gio'])
-
-    gio_tests += ['gdbus-unix-addresses']
   endif # have_dbus_daemon
 
   # This test is currently unreliable
@@ -254,125 +250,100 @@
       c_args : test_c_args,
       dependencies : common_gio_tests_deps)
 
-  exe = executable('gdbus-connection-flush', 'gdbus-connection-flush.c',
-      'test-io-stream.c', 'test-pipe-unix.c',
-      install : false,
-      c_args : test_c_args,
-      dependencies : common_gio_tests_deps)
-  test('gdbus-connection-flush', exe, env : test_env, suite : ['gio'])
-
-  exe = executable('gdbus-non-socket', 'gdbus-non-socket.c',
-      'gdbus-tests.c', 'test-io-stream.c', 'test-pipe-unix.c',
-      install : false,
-      c_args : test_c_args,
-      dependencies : common_gio_tests_deps)
-  test('gdbus-non-socket', exe, env : test_env, suite : ['gio'])
+  gio_tests += [{
+    'gdbus-connection-flush' : {
+      'extra_sources' : ['test-io-stream.c', 'test-pipe-unix.c'],
+    },
+    'gdbus-non-socket' : {
+      'extra_sources' : ['gdbus-tests.c', 'test-io-stream.c', 'test-pipe-unix.c'],
+    },
+  }]
 
   # Generate test.mo from de.po using msgfmt
   msgfmt = find_program('msgfmt', required : false)
   if msgfmt.found()
     subdir('de/LC_MESSAGES')
-    # gsettings target exe already exists in gio directory
-    exe = executable('gsettings-test', 'gsettings.c', test_mo,
-        install : false,
-        c_args : test_c_args + [
-                   '-DSRCDIR="@0@"'.format(meson.current_source_dir()),
-                   '-DTEST_LOCALE_PATH="@0@"'.format(test_mo_dir),
-                 ],
-        dependencies : common_gio_tests_deps)
-    test('gsettings', exe, env : test_env, suite : ['gio'])
+    gio_tests += [{
+      'gsettings' : {
+        'extra_sources' : [test_mo],
+        'c_args' : ['-DSRCDIR="@0@"'.format(meson.current_source_dir()),
+                    '-DTEST_LOCALE_PATH="@0@"'.format(test_mo_dir)],
+        'install' : false,
+      },
+    }]
   endif
 endif # unix
 
 #  Test programs buildable on Windows only
 if host_machine.system() == 'windows'
-  gio_tests += ['win32-streams']
+  gio_tests += [{'win32-streams' : {}}]
 endif
 
 if cc.get_id() != 'msvc'
-  gio_tests += [ 'autoptr' ]
+  gio_tests += [{
+    'autoptr-gio' : {
+      'source' : 'autoptr.c',
+    },
+  }]
 endif
 
-foreach test_name : gio_tests
-  extra_deps = []
-  srcs = ['@0@.c'.format(test_name)]
-  # conflicts with glib/tests/autoptr, can't have two targets with same name
-  if test_name == 'autoptr'
-    test_name = 'autoptr-gio'
-  elif test_name == 'defaultvalue'
-    srcs += [giotypefuncs_inc]
-  elif test_name == 'gdbus-peer'
-    # This is peer to peer so it doesn't need a session bus, so we can run
-    # it automatically as a test by default
-    extra_deps = [libgdbus_example_objectmanager_dep]
-  elif test_name == 'tls-certificate' or test_name == 'tls-interaction'
-    srcs += ['gtesttlsbackend.c']
-  endif
-  exe = executable(test_name, srcs,
-      install : false,
-      c_args : test_c_args,
-      dependencies : common_gio_tests_deps + extra_deps)
-  # These tests may take more than 30 seconds to run on the CI infrastructure
-  if slow_tests.contains(test_name)
-    test(test_name, exe, env : test_env, timeout : 120, suite : ['gio', 'slow'])
-  else
-    test(test_name, exe, env : test_env, suite : ['gio'])
-  endif
-endforeach
-
-uninstalled_test_extra_programs = [
-  ['gio-du'],
-  ['echo-server'],
-  ['filter-cat'],
-  ['gapplication-example-actions'],
-  ['gapplication-example-cmdline'],
-  ['gapplication-example-cmdline2'],
-  ['gapplication-example-cmdline3'],
-  ['gapplication-example-cmdline4'],
-  ['gapplication-example-dbushooks'],
-  ['gapplication-example-open'],
-  ['gdbus-daemon', gdbus_daemon_sources],
-  ['gdbus-example-export'],
-  ['gdbus-example-own-name'],
-  ['gdbus-example-peer'],
-  ['gdbus-example-proxy-subclass'],
-  ['gdbus-example-server'],
-  ['gdbus-example-subtree'],
-  ['gdbus-example-watch-name'],
-  ['gdbus-example-watch-proxy'],
-  ['gsubprocess-testprog'],
-  ['httpd'],
-  ['proxy'],
-  ['resolver'],
-  ['send-data'],
-  ['socket-server'],
-  ['socket-client', ['gtlsconsoleinteraction.c']],
+test_extra_programs += [{
+  'gio-du' : {'install' : false},
+  'echo-server' : {'install' : false},
+  'filter-cat' : {'install' : false},
+  'gapplication-example-actions' : {'install' : false},
+  'gapplication-example-cmdline' : {'install' : false},
+  'gapplication-example-cmdline2' : {'install' : false},
+  'gapplication-example-cmdline3' : {'install' : false},
+  'gapplication-example-cmdline4' : {'install' : false},
+  'gapplication-example-dbushooks' : {'install' : false},
+  'gapplication-example-open' : {'install' : false},
+  'gdbus-daemon' : {
+    'extra_sources' : gdbus_daemon_sources,
+    'install' : false,
+  },
+  'gdbus-example-export' : {'install' : false},
+  'gdbus-example-own-name' : {'install' : false},
+  'gdbus-example-peer' : {'install' : false},
+  'gdbus-example-proxy-subclass' : {'install' : false},
+  'gdbus-example-server' : {'install' : false},
+  'gdbus-example-subtree' : {'install' : false},
+  'gdbus-example-watch-name' : {'install' : false},
+  'gdbus-example-watch-proxy' : {'install' : false},
+  'gsubprocess-testprog' : {'install' : false},
+  'httpd' : {'install' : false},
+  'proxy' : {'install' : false},
+  'resolver' : {'install' : false},
+  'send-data' : {'install' : false},
+  'socket-server' : {'install' : false},
+  'socket-client' : {
+    'extra_sources' : ['gtlsconsoleinteraction.c'],
+    'install' : false,
+  },
   # These three are manual-run tests because they need a session bus but don't bring one up themselves
   # FIXME: these build but don't seem to work!
-  ['gdbus-example-objectmanager-client', [], [libgdbus_example_objectmanager_dep]],
-  ['gdbus-example-objectmanager-server', [], [libgdbus_example_objectmanager_dep]],
-  ['gdbus-test-fixture', [], [libgdbus_example_objectmanager_dep]],
-]
+  'gdbus-example-objectmanager-client' : {
+    'dependencies' : [libgdbus_example_objectmanager_dep],
+    'install' : false,
+  },
+  'gdbus-example-objectmanager-server' : {
+    'dependencies' : [libgdbus_example_objectmanager_dep],
+    'install' : false,
+  },
+  'gdbus-test-fixture' : {
+    'dependencies' : [libgdbus_example_objectmanager_dep],
+    'install' : false,
+  },
+}]
 
 if host_machine.system() != 'windows'
-  uninstalled_test_extra_programs += [['gdbus-example-unix-fd-client']]
+  test_extra_programs += [{
+    'gdbus-example-unix-fd-client' : {
+      'install' : false,
+    },
+  }]
 endif
 
-foreach extra_program : uninstalled_test_extra_programs + test_extra_programs
-  srcs = ['@0@.c'.format(extra_program[0])]
-  if extra_program.length() > 1
-    srcs += extra_program[1]
-  endif
-  extra_deps = []
-  if extra_program.length() > 2
-    extra_deps = extra_program[2]
-  endif
-  executable(extra_program[0], srcs,
-      install : false,
-      c_args : test_c_args,
-      dependencies : common_gio_tests_deps + extra_deps)
-endforeach
-
 if not meson.is_cross_build() or meson.has_exe_wrapper()
 
   plugin_resources_c = custom_target('plugin-resources.c',
@@ -441,13 +412,58 @@
     copy : true,
     install : false)
 
-  exe = executable('resources', 'resources.c', test_gresource,
-      test_resources_c, test_resources2_c, test_resources2_h,
-      install : false,
-      c_args : test_c_args,
-      dependencies : common_gio_tests_deps)
-  test('resources', exe, env : test_env, suite : ['gio'])
+  gio_tests += [{
+    'resources' : {
+      'extra_sources' : [test_gresource, test_resources_c, test_resources2_c,
+                         test_resources2_h],
+    },
+  }]
 endif
 
+foreach test_dict : gio_tests
+  foreach test_name, extra_args : test_dict
+    source = extra_args.get('source', test_name + '.c')
+    extra_sources = extra_args.get('extra_sources', [])
+    install = installed_tests_enabled and extra_args.get('install', true)
+
+    if install
+      test_conf = configuration_data()
+      test_conf.set('installed_tests_dir', installed_tests_execdir)
+      test_conf.set('program', test_name)
+      configure_file(
+        input: installed_tests_template,
+        output: test_name + '.test',
+        install_dir: installed_tests_metadir,
+        configuration: test_conf
+      )
+    endif
+
+    exe = executable(test_name, [source, extra_sources],
+      c_args : test_c_args + extra_args.get('c_args', []),
+      dependencies : common_gio_tests_deps + extra_args.get('dependencies', []),
+      install_dir: installed_tests_execdir,
+      install: install,
+    )
+
+    suite = ['gio'] + extra_args.get('suite', [])
+    timeout = suite.contains('slow') ? 120 : 30
+    test(test_name, exe, env : test_env, timeout : timeout, suite : suite)
+  endforeach
+endforeach
+
+foreach program_dict : test_extra_programs
+  foreach program_name, extra_args : program_dict
+    source = extra_args.get('source', program_name + '.c')
+    extra_sources = extra_args.get('extra_sources', [])
+    install = installed_tests_enabled and extra_args.get('install', true)
+    executable(program_name, [source, extra_sources],
+        c_args : test_c_args,
+        dependencies : common_gio_tests_deps + extra_args.get('dependencies', []),
+        install_dir : installed_tests_execdir,
+        install : install,
+    )
+  endforeach
+endforeach
+
 # FIXME: subdir('services')
 subdir('modules')
diff --git a/gio/tests/resources.c b/gio/tests/resources.c
index 6ae8e7d..70d8c03 100644
--- a/gio/tests/resources.c
+++ b/gio/tests/resources.c
@@ -317,6 +317,45 @@
   g_resource_unref (resource);
 }
 
+/* Test error handling for corrupt GResource files (specifically, a corrupt
+ * GVDB header). */
+static void
+test_resource_data_corrupt (void)
+{
+  /* A GVDB header is 6 guint32s, and requires a magic number in the first two
+   * guint32s. A set of zero bytes of a greater length is considered corrupt. */
+  static const guint8 data[sizeof (guint32) * 7] = { 0, };
+  GBytes *bytes = NULL;
+  GResource *resource = NULL;
+  GError *local_error = NULL;
+
+  bytes = g_bytes_new_static (data, sizeof (data));
+  resource = g_resource_new_from_data (bytes, &local_error);
+  g_bytes_unref (bytes);
+  g_assert_error (local_error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_INTERNAL);
+  g_assert_null (resource);
+
+  g_clear_error (&local_error);
+}
+
+/* Test handling for empty GResource files. They should also be treated as
+ * corrupt. */
+static void
+test_resource_data_empty (void)
+{
+  GBytes *bytes = NULL;
+  GResource *resource = NULL;
+  GError *local_error = NULL;
+
+  bytes = g_bytes_new_static (NULL, 0);
+  resource = g_resource_new_from_data (bytes, &local_error);
+  g_bytes_unref (bytes);
+  g_assert_error (local_error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_INTERNAL);
+  g_assert_null (resource);
+
+  g_clear_error (&local_error);
+}
+
 static void
 test_resource_registered (void)
 {
@@ -785,6 +824,8 @@
   g_test_add_func ("/resource/file-path", test_resource_file_path);
   g_test_add_func ("/resource/data", test_resource_data);
   g_test_add_func ("/resource/data_unaligned", test_resource_data_unaligned);
+  g_test_add_func ("/resource/data-corrupt", test_resource_data_corrupt);
+  g_test_add_func ("/resource/data-empty", test_resource_data_empty);
   g_test_add_func ("/resource/registered", test_resource_registered);
   g_test_add_func ("/resource/manual", test_resource_manual);
   g_test_add_func ("/resource/manual2", test_resource_manual2);
diff --git a/glib/gspawn.c b/glib/gspawn.c
index 1ab3307..e273e2a 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -81,6 +81,12 @@
 extern char **environ;
 #endif
 
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#else
+#define HAVE_O_CLOEXEC 1
+#endif
+
 /**
  * SECTION:spawn
  * @Short_description: process launching
@@ -1454,6 +1460,10 @@
       g_assert (read_null != -1);
       parent_close_fds[num_parent_close_fds++] = read_null;
 
+#ifndef HAVE_O_CLOEXEC
+      fcntl (read_null, F_SETFD, FD_CLOEXEC);
+#endif
+
       r = posix_spawn_file_actions_adddup2 (&file_actions, read_null, 0);
       if (r != 0)
         goto out_close_fds;
@@ -1474,6 +1484,10 @@
       g_assert (write_null != -1);
       parent_close_fds[num_parent_close_fds++] = write_null;
 
+#ifndef HAVE_O_CLOEXEC
+      fcntl (read_null, F_SETFD, FD_CLOEXEC);
+#endif
+
       r = posix_spawn_file_actions_adddup2 (&file_actions, write_null, 1);
       if (r != 0)
         goto out_close_fds;
@@ -1494,6 +1508,10 @@
       g_assert (write_null != -1);
       parent_close_fds[num_parent_close_fds++] = write_null;
 
+#ifndef HAVE_O_CLOEXEC
+      fcntl (read_null, F_SETFD, FD_CLOEXEC);
+#endif
+
       r = posix_spawn_file_actions_adddup2 (&file_actions, write_null, 2);
       if (r != 0)
         goto out_close_fds;
diff --git a/gobject/gbinding.c b/gobject/gbinding.c
index 6872b96..42dcb36 100644
--- a/gobject/gbinding.c
+++ b/gobject/gbinding.c
@@ -373,6 +373,7 @@
                            gboolean  unref_binding)
 {
   gboolean source_is_target = binding->source == binding->target;
+  gboolean binding_was_removed = FALSE;
 
   /* dispose of the transformation data */
   if (binding->notify != NULL)
@@ -392,6 +393,7 @@
 
       binding->source_notify = 0;
       binding->source = NULL;
+      binding_was_removed = TRUE;
     }
 
   if (binding->target != NULL)
@@ -404,9 +406,10 @@
 
       binding->target_notify = 0;
       binding->target = NULL;
+      binding_was_removed = TRUE;
     }
 
-  if (unref_binding)
+  if (binding_was_removed && unref_binding)
     g_object_unref (binding);
 }
 
@@ -748,7 +751,7 @@
 
 /**
  * g_binding_unbind:
- * @binding: (transfer full): a #GBinding
+ * @binding: a #GBinding
  *
  * Explicitly releases the binding between the source and the target
  * property expressed by @binding.
diff --git a/gobject/tests/binding.c b/gobject/tests/binding.c
index e088ca7..019fb28 100644
--- a/gobject/tests/binding.c
+++ b/gobject/tests/binding.c
@@ -460,6 +460,7 @@
   BindingSource *c = g_object_new (binding_source_get_type (), NULL);
   GBinding *binding_1, *binding_2;
 
+  g_test_bug_base ("http://bugzilla.gnome.org/");
   g_test_bug ("621782");
 
   /* A -> B, B -> C */
@@ -625,6 +626,83 @@
   g_object_unref (source);
 }
 
+/* When source or target die, so does the binding if there is no other ref */
+static void
+binding_unbind_weak (void)
+{
+  GBinding *binding;
+  BindingSource *source;
+  BindingTarget *target;
+
+  /* first source, then target */
+  source = g_object_new (binding_source_get_type (), NULL);
+  target = g_object_new (binding_target_get_type (), NULL);
+  binding = g_object_bind_property (source, "foo",
+                                    target, "bar",
+                                    G_BINDING_DEFAULT);
+  g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding);
+  g_assert_nonnull (binding);
+  g_object_unref (source);
+  g_assert_null (binding);
+  g_object_unref (target);
+  g_assert_null (binding);
+
+  /* first target, then source */
+  source = g_object_new (binding_source_get_type (), NULL);
+  target = g_object_new (binding_target_get_type (), NULL);
+  binding = g_object_bind_property (source, "foo",
+                                    target, "bar",
+                                    G_BINDING_DEFAULT);
+  g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding);
+  g_assert_nonnull (binding);
+  g_object_unref (target);
+  g_assert_null (binding);
+  g_object_unref (source);
+  g_assert_null (binding);
+
+  /* target and source are the same */
+  source = g_object_new (binding_source_get_type (), NULL);
+  binding = g_object_bind_property (source, "foo",
+                                    source, "bar",
+                                    G_BINDING_DEFAULT);
+  g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding);
+  g_assert_nonnull (binding);
+  g_object_unref (source);
+  g_assert_null (binding);
+}
+
+/* Test that every call to unbind() after the first is a noop */
+static void
+binding_unbind_multiple (void)
+{
+  BindingSource *source = g_object_new (binding_source_get_type (), NULL);
+  BindingTarget *target = g_object_new (binding_target_get_type (), NULL);
+  GBinding *binding;
+  guint i;
+
+  g_test_bug ("1373");
+
+  binding = g_object_bind_property (source, "foo",
+                                    target, "bar",
+                                    G_BINDING_DEFAULT);
+  g_object_ref (binding);
+  g_object_add_weak_pointer (G_OBJECT (binding), (gpointer *) &binding);
+  g_assert_nonnull (binding);
+
+  /* this shouldn't crash */
+  for (i = 0; i < 50; i++)
+    {
+      g_binding_unbind (binding);
+      g_assert_nonnull (binding);
+    }
+
+  g_object_unref (binding);
+  g_assert_null (binding);
+
+  g_object_unref (source);
+  g_object_unref (target);
+}
+
 static void
 binding_fail (void)
 {
@@ -653,7 +731,7 @@
 {
   g_test_init (&argc, &argv, NULL);
 
-  g_test_bug_base ("http://bugzilla.gnome.org/");
+  g_test_bug_base ("https://gitlab.gnome.org/GNOME/glib/issues/");
 
   g_test_add_func ("/binding/default", binding_default);
   g_test_add_func ("/binding/bidirectional", binding_bidirectional);
@@ -665,6 +743,8 @@
   g_test_add_func ("/binding/invert-boolean", binding_invert_boolean);
   g_test_add_func ("/binding/same-object", binding_same_object);
   g_test_add_func ("/binding/unbind", binding_unbind);
+  g_test_add_func ("/binding/unbind-weak", binding_unbind_weak);
+  g_test_add_func ("/binding/unbind-multiple", binding_unbind_multiple);
   g_test_add_func ("/binding/fail", binding_fail);
 
   return g_test_run ();
diff --git a/gobject/tests/meson.build b/gobject/tests/meson.build
index 8851500..cc83c5f 100644
--- a/gobject/tests/meson.build
+++ b/gobject/tests/meson.build
@@ -1,47 +1,3 @@
-gobject_tests = [
-  'qdata',
-  'boxed',
-  'enums',
-  'param',
-  'threadtests',
-  'dynamictests',
-  'binding',
-  'properties',
-  'reference',
-  'value',
-  'type',
-  'private',
-  'closure',
-  'object',
-  'signal-handler',
-  'ifaceproperties',
-]
-
-# FIXME: put common bits of test environment() in one location
-# Not entirely random of course, but at least it changes over time
-random_number = minor_version + meson.version().split('.').get(1).to_int()
-
-test_env = environment()
-test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
-test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
-test_env.set('G_DEBUG', 'gc-friendly')
-test_env.set('MALLOC_CHECK_', '2')
-test_env.set('MALLOC_PERTURB_', '@0@'.format(random_number % 256))
-
-foreach test_name : gobject_tests
-  deps = [libm, thread_dep, libglib_dep, libgobject_dep]
-  test_src = '@0@.c'.format(test_name)
-  # private is an existing or reserved target it seems
-  if test_name == 'private'
-    test_name = 'gobject-private'
-  endif
-  exe = executable(test_name, test_src,
-      c_args : ['-DG_LOG_DOMAIN="GLib-GObject"'],
-      dependencies : deps,
-  )
-  test(test_name, exe, env : test_env, suite : ['gobject'])
-endforeach
-
 marshalers_h = custom_target('marshalers_h',
   output : 'marshalers.h',
   input : 'marshalers.list',
@@ -70,12 +26,71 @@
   ],
 )
 
-exe = executable('signals',
-    'signals.c', marshalers_h, marshalers_c,
-    c_args : ['-DG_LOG_DOMAIN="GLib-GObject"'],
-    dependencies : deps,
-)
-test('signals', exe, env : test_env, suite : ['gobject'])
+gobject_tests = {
+  'qdata' : {},
+  'boxed' : {},
+  'enums' : {},
+  'param' : {},
+  'threadtests' : {},
+  'dynamictests' : {},
+  'binding' : {},
+  'properties' : {},
+  'reference' : {},
+  'value' : {},
+  'type' : {},
+  'gobject-private' : {
+    'source' : 'private.c',
+  },
+  'closure' : {},
+  'object' : {},
+  'signal-handler' : {},
+  'ifaceproperties' : {},
+  'signals' : {
+    'source' : ['signals.c', marshalers_h, marshalers_c],
+  },
+}
+
+# FIXME: put common bits of test environment() in one location
+# Not entirely random of course, but at least it changes over time
+random_number = minor_version + meson.version().split('.').get(1).to_int()
+
+test_env = environment()
+test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
+test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
+test_env.set('G_DEBUG', 'gc-friendly')
+test_env.set('MALLOC_CHECK_', '2')
+test_env.set('MALLOC_PERTURB_', '@0@'.format(random_number % 256))
+
+test_deps = [libm, thread_dep, libglib_dep, libgobject_dep]
+test_cargs = ['-DG_LOG_DOMAIN="GLib-GObject"']
+
+foreach test_name, extra_args : gobject_tests
+  source = extra_args.get('source', test_name + '.c')
+  install = installed_tests_enabled and extra_args.get('install', true)
+
+  if install
+    test_conf = configuration_data()
+    test_conf.set('installed_tests_dir', installed_tests_execdir)
+    test_conf.set('program', test_name)
+    configure_file(
+      input: installed_tests_template,
+      output: test_name + '.test',
+      install_dir: installed_tests_metadir,
+      configuration: test_conf
+    )
+  endif
+
+  exe = executable(test_name, source,
+    c_args : test_cargs + extra_args.get('c_args', []),
+    dependencies : test_deps + extra_args.get('dependencies', []),
+    install_dir: installed_tests_execdir,
+    install: install,
+  )
+
+  suite = ['gobject'] + extra_args.get('suite', [])
+  timeout = suite.contains('slow') ? 120 : 30
+  test(test_name, exe, env : test_env, timeout : timeout, suite : suite)
+endforeach
 
 test(
   'mkenums.py',