summaryrefslogtreecommitdiff
path: root/common/flatpak-db.c
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2016-05-06 18:03:47 +0200
committerAlexander Larsson <alexl@redhat.com>2016-05-09 09:00:20 +0200
commitc24528d3697c62cad8ff746a56992a59f31d333d (patch)
tree2dbb32e15c57cc4061f7b37d6db29a7b97228c15 /common/flatpak-db.c
parent6a613d1fabce5e93656cfbcb6815cc9bc98f437b (diff)
downloadxdg-app-c24528d3697c62cad8ff746a56992a59f31d333d.tar.gz
Rename source files to flatpak
Diffstat (limited to 'common/flatpak-db.c')
-rw-r--r--common/flatpak-db.c1224
1 files changed, 1224 insertions, 0 deletions
diff --git a/common/flatpak-db.c b/common/flatpak-db.c
new file mode 100644
index 0000000..429700d
--- /dev/null
+++ b/common/flatpak-db.c
@@ -0,0 +1,1224 @@
+/* xdg-app-db.c
+ *
+ * Copyright (C) 2015 Red Hat, Inc
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Alexander Larsson <alexl@redhat.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/statfs.h>
+
+#include "flatpak-db.h"
+#include "gvdb/gvdb-reader.h"
+#include "gvdb/gvdb-builder.h"
+
+struct FlatpakDb
+{
+ GObject parent;
+
+ char *path;
+ gboolean fail_if_not_found;
+ GvdbTable *gvdb;
+ GBytes *gvdb_contents;
+
+ gboolean dirty;
+
+ /* Map id => GVariant (data, sorted-dict[appid->perms]) */
+ GvdbTable *main_table;
+ GHashTable *main_updates;
+
+ /* (reverse) Map app id => [ id ]*/
+ GvdbTable *app_table;
+ GHashTable *app_additions;
+ GHashTable *app_removals;
+};
+
+typedef struct
+{
+ GObjectClass parent_class;
+} FlatpakDbClass;
+
+static void initable_iface_init (GInitableIface *initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (FlatpakDb, flatpak_db, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
+
+enum {
+ PROP_0,
+ PROP_PATH,
+ PROP_FAIL_IF_NOT_FOUND,
+ LAST_PROP
+};
+
+static int
+cmpstringp (const void *p1, const void *p2)
+{
+ return strcmp (*(char * const *) p1, *(char * const *) p2);
+}
+
+static void
+sort_strv (const char **strv)
+{
+ qsort (strv, g_strv_length ((char **) strv), sizeof (const char *), cmpstringp);
+}
+
+static int
+str_ptr_array_find (GPtrArray *array,
+ const char *str)
+{
+ int i;
+
+ for (i = 0; i < array->len; i++)
+ if (strcmp (g_ptr_array_index (array, i), str) == 0)
+ return i;
+
+ return -1;
+}
+
+static gboolean
+str_ptr_array_contains (GPtrArray *array,
+ const char *str)
+{
+ return str_ptr_array_find (array, str) >= 0;
+}
+
+const char *
+flatpak_db_get_path (FlatpakDb *self)
+{
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+
+ return self->path;
+}
+
+void
+flatpak_db_set_path (FlatpakDb *self,
+ const char *path)
+{
+ g_return_if_fail (FLATPAK_IS_DB (self));
+
+ g_clear_pointer (&self->path, g_free);
+ self->path = g_strdup (path);
+}
+
+FlatpakDb *
+flatpak_db_new (const char *path,
+ gboolean fail_if_not_found,
+ GError **error)
+{
+ return g_initable_new (FLATPAK_TYPE_DB,
+ NULL,
+ error,
+ "path", path,
+ "fail-if-not-found", fail_if_not_found,
+ NULL);
+}
+
+static void
+flatpak_db_finalize (GObject *object)
+{
+ FlatpakDb *self = (FlatpakDb *) object;
+
+ g_clear_pointer (&self->path, g_free);
+ g_clear_pointer (&self->gvdb_contents, g_bytes_unref);
+ g_clear_pointer (&self->gvdb, gvdb_table_free);
+ g_clear_pointer (&self->main_table, gvdb_table_free);
+ g_clear_pointer (&self->app_table, gvdb_table_free);
+ g_clear_pointer (&self->main_updates, g_hash_table_unref);
+ g_clear_pointer (&self->app_additions, g_hash_table_unref);
+ g_clear_pointer (&self->app_removals, g_hash_table_unref);
+
+ G_OBJECT_CLASS (flatpak_db_parent_class)->finalize (object);
+}
+
+static void
+flatpak_db_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ FlatpakDb *self = FLATPAK_DB (object);
+
+ switch (prop_id)
+ {
+ case PROP_PATH:
+ g_value_set_string (value, self->path);
+ break;
+
+ case PROP_FAIL_IF_NOT_FOUND:
+ g_value_set_boolean (value, self->fail_if_not_found);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+flatpak_db_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FlatpakDb *self = FLATPAK_DB (object);
+
+ switch (prop_id)
+ {
+ case PROP_PATH:
+ g_clear_pointer (&self->path, g_free);
+ self->path = g_value_dup_string (value);
+ break;
+
+ case PROP_FAIL_IF_NOT_FOUND:
+ self->fail_if_not_found = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+flatpak_db_class_init (FlatpakDbClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = flatpak_db_finalize;
+ object_class->get_property = flatpak_db_get_property;
+ object_class->set_property = flatpak_db_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_PATH,
+ g_param_spec_string ("path",
+ "",
+ "",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_FAIL_IF_NOT_FOUND,
+ g_param_spec_boolean ("fail-if-not-found",
+ "",
+ "",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+flatpak_db_init (FlatpakDb *self)
+{
+ self->fail_if_not_found = TRUE;
+
+ self->main_updates =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_variant_unref);
+ self->app_additions =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_ptr_array_unref);
+ self->app_removals =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, (GDestroyNotify) g_ptr_array_unref);
+}
+
+static gboolean
+is_on_nfs (const char *path)
+{
+ struct statfs statfs_buffer;
+ int statfs_result;
+ g_autofree char *dirname = NULL;
+
+ dirname = g_path_get_dirname (path);
+
+ statfs_result = statfs (dirname, &statfs_buffer);
+ if (statfs_result != 0)
+ return FALSE;
+
+ return statfs_buffer.f_type == 0x6969;
+}
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ FlatpakDb *self = (FlatpakDb *) initable;
+ GError *my_error = NULL;
+
+ if (self->path == NULL)
+ return TRUE;
+
+ if (is_on_nfs (self->path))
+ {
+ g_autoptr(GFile) file = g_file_new_for_path (self->path);
+ char *contents;
+ gsize length;
+
+ /* We avoid using mmap on NFS, because its prone to give us SIGBUS at semi-random
+ times (nfs down, file removed, etc). Instead we just load the file */
+ if (g_file_load_contents (file, cancellable, &contents, &length, NULL, &my_error))
+ self->gvdb_contents = g_bytes_new_take (contents, length);
+ }
+ else
+ {
+ GMappedFile *mapped = g_mapped_file_new (self->path, FALSE, &my_error);
+ if (mapped)
+ {
+ self->gvdb_contents = g_mapped_file_get_bytes (mapped);
+ g_mapped_file_unref (mapped);
+ }
+ }
+
+ if (self->gvdb_contents == NULL)
+ {
+ if (!self->fail_if_not_found &&
+ g_error_matches (my_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ {
+ g_error_free (my_error);
+ }
+ else
+ {
+ g_propagate_error (error, my_error);
+ return FALSE;
+ }
+ }
+ else
+ {
+ self->gvdb = gvdb_table_new_from_bytes (self->gvdb_contents, TRUE, error);
+ if (self->gvdb == NULL)
+ return FALSE;
+
+ self->main_table = gvdb_table_get_table (self->gvdb, "main");
+ if (self->main_table == NULL)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+ "No main table in db");
+ return FALSE;
+ }
+
+ self->app_table = gvdb_table_get_table (self->gvdb, "apps");
+ if (self->app_table == NULL)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+ "No app table in db");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+ initable_iface->init = initable_init;
+}
+
+/* Transfer: full */
+char **
+flatpak_db_list_ids (FlatpakDb *self)
+{
+ GPtrArray *res;
+ GHashTableIter iter;
+ gpointer key, value;
+ int i;
+
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+
+ res = g_ptr_array_new ();
+
+ g_hash_table_iter_init (&iter, self->main_updates);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ if (value != NULL)
+ g_ptr_array_add (res, g_strdup (key));
+ }
+
+ if (self->main_table)
+ {
+ // TODO: can we use gvdb_table_list here???
+ g_autofree char **main_ids = gvdb_table_get_names (self->main_table, NULL);
+
+ for (i = 0; main_ids[i] != NULL; i++)
+ {
+ char *id = main_ids[i];
+
+ if (g_hash_table_lookup_extended (self->main_updates, id, NULL, NULL))
+ g_free (id);
+ else
+ g_ptr_array_add (res, id);
+ }
+ }
+
+ g_ptr_array_add (res, NULL);
+ return (char **) g_ptr_array_free (res, FALSE);
+}
+
+static gboolean
+app_update_empty (GHashTable *ht, const char *app)
+{
+ GPtrArray *array;
+
+ array = g_hash_table_lookup (ht, app);
+ if (array == NULL)
+ return TRUE;
+
+ return array->len == 0;
+}
+
+/* Transfer: full */
+char **
+flatpak_db_list_apps (FlatpakDb *self)
+{
+ gpointer key, _value;
+ GHashTableIter iter;
+ GPtrArray *res;
+ int i;
+
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+
+ res = g_ptr_array_new ();
+
+ g_hash_table_iter_init (&iter, self->app_additions);
+ while (g_hash_table_iter_next (&iter, &key, &_value))
+ {
+ GPtrArray *value = _value;
+ if (value->len > 0)
+ g_ptr_array_add (res, g_strdup (key));
+ }
+
+ if (self->app_table)
+ {
+ // TODO: can we use gvdb_table_list here???
+ g_autofree char **apps = gvdb_table_get_names (self->app_table, NULL);
+
+ for (i = 0; apps[i] != NULL; i++)
+ {
+ char *app = apps[i];
+ gboolean empty = TRUE;
+ GPtrArray *removals;
+ int j;
+
+ /* Don't use if we already added above */
+ if (app_update_empty (self->app_additions, app))
+ {
+ g_autoptr(GVariant) ids_v = NULL;
+
+ removals = g_hash_table_lookup (self->app_removals, app);
+
+ /* Add unless all items are removed */
+ ids_v = gvdb_table_get_value (self->app_table, app);
+
+ if (ids_v)
+ {
+ g_autofree const char **ids = g_variant_get_strv (ids_v, NULL);
+
+ for (j = 0; ids[j] != NULL; j++)
+ {
+ if (removals == NULL ||
+ !str_ptr_array_contains (removals, ids[j]))
+ {
+ empty = FALSE;
+ break;
+ }
+ }
+ }
+ }
+
+ if (empty)
+ g_free (app);
+ else
+ g_ptr_array_add (res, app);
+ }
+ }
+
+ g_ptr_array_add (res, NULL);
+ return (char **) g_ptr_array_free (res, FALSE);
+}
+
+/* Transfer: full */
+char **
+flatpak_db_list_ids_by_app (FlatpakDb *self,
+ const char *app)
+{
+ GPtrArray *res;
+ GPtrArray *additions;
+ GPtrArray *removals;
+ int i;
+
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+
+ res = g_ptr_array_new ();
+
+ additions = g_hash_table_lookup (self->app_additions, app);
+ removals = g_hash_table_lookup (self->app_removals, app);
+
+ if (additions)
+ {
+ for (i = 0; i < additions->len; i++)
+ g_ptr_array_add (res,
+ g_strdup (g_ptr_array_index (additions, i)));
+ }
+
+ if (self->app_table)
+ {
+ g_autoptr(GVariant) ids_v = gvdb_table_get_value (self->app_table, app);
+ if (ids_v)
+ {
+ g_autofree const char **ids = g_variant_get_strv (ids_v, NULL);
+
+ for (i = 0; ids[i] != NULL; i++)
+ {
+ if (removals == NULL ||
+ !str_ptr_array_contains (removals, ids[i]))
+ g_ptr_array_add (res, g_strdup (ids[i]));
+ }
+ }
+ }
+
+ g_ptr_array_add (res, NULL);
+ return (char **) g_ptr_array_free (res, FALSE);
+}
+
+/* Transfer: full */
+FlatpakDbEntry *
+flatpak_db_lookup (FlatpakDb *self,
+ const char *id)
+{
+ GVariant *res = NULL;
+ gpointer value;
+
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+ g_return_val_if_fail (id != NULL, NULL);
+
+ if (g_hash_table_lookup_extended (self->main_updates, id, NULL, &value))
+ {
+ if (value != NULL)
+ res = g_variant_ref ((GVariant *) value);
+ }
+ else if (self->main_table)
+ {
+ res = gvdb_table_get_value (self->main_table, id);
+ }
+
+ return (FlatpakDbEntry *) res;
+}
+
+/* Transfer: full */
+char **
+flatpak_db_list_ids_by_value (FlatpakDb *self,
+ GVariant *data)
+{
+ g_autofree char **ids = flatpak_db_list_ids (self);
+ int i;
+ GPtrArray *res;
+
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+ g_return_val_if_fail (data != NULL, NULL);
+
+ res = g_ptr_array_new ();
+
+ for (i = 0; ids[i] != NULL; i++)
+ {
+ char *id = ids[i];
+
+ g_autoptr(FlatpakDbEntry) entry = NULL;
+ g_autoptr(GVariant) entry_data = NULL;
+
+ entry = flatpak_db_lookup (self, id);
+ if (entry)
+ {
+ entry_data = flatpak_db_entry_get_data (entry);
+ if (g_variant_equal (data, entry_data))
+ {
+ g_ptr_array_add (res, id);
+ id = NULL; /* Don't free, as we return this */
+ }
+ }
+ g_free (id);
+ }
+
+ g_ptr_array_add (res, NULL);
+ return (char **) g_ptr_array_free (res, FALSE);
+}
+
+static void
+add_app_id (FlatpakDb *self,
+ const char *app,
+ const char *id)
+{
+ GPtrArray *additions;
+ GPtrArray *removals;
+ int i;
+
+ additions = g_hash_table_lookup (self->app_additions, app);
+ removals = g_hash_table_lookup (self->app_removals, app);
+
+ if (removals)
+ {
+ i = str_ptr_array_find (removals, id);
+ if (i >= 0)
+ g_ptr_array_remove_index_fast (removals, i);
+ }
+
+ if (additions)
+ {
+ if (!str_ptr_array_contains (additions, id))
+ g_ptr_array_add (additions, g_strdup (id));
+ }
+ else
+ {
+ additions = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (additions, g_strdup (id));
+ g_hash_table_insert (self->app_additions,
+ g_strdup (app), additions);
+ }
+}
+
+static void
+remove_app_id (FlatpakDb *self,
+ const char *app,
+ const char *id)
+{
+ GPtrArray *additions;
+ GPtrArray *removals;
+ int i;
+
+ additions = g_hash_table_lookup (self->app_additions, app);
+ removals = g_hash_table_lookup (self->app_removals, app);
+
+ if (additions)
+ {
+ i = str_ptr_array_find (additions, id);
+ if (i >= 0)
+ g_ptr_array_remove_index_fast (additions, i);
+ }
+
+ if (removals)
+ {
+ if (!str_ptr_array_contains (removals, id))
+ g_ptr_array_add (removals, g_strdup (id));
+ }
+ else
+ {
+ removals = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (removals, g_strdup (id));
+ g_hash_table_insert (self->app_removals,
+ g_strdup (app), removals);
+ }
+}
+
+gboolean
+flatpak_db_is_dirty (FlatpakDb *self)
+{
+ g_return_val_if_fail (FLATPAK_IS_DB (self), FALSE);
+
+ return self->dirty;
+}
+
+/* add, replace, or NULL entry to remove */
+void
+flatpak_db_set_entry (FlatpakDb *self,
+ const char *id,
+ FlatpakDbEntry *entry)
+{
+ g_autoptr(FlatpakDbEntry) old_entry = NULL;
+ g_autofree const char **old = NULL;
+ g_autofree const char **new = NULL;
+ static const char *empty[] = { NULL };
+ const char **a, **b;
+ int ia, ib;
+
+ g_return_if_fail (FLATPAK_IS_DB (self));
+ g_return_if_fail (id != NULL);
+
+ self->dirty = TRUE;
+
+ old_entry = flatpak_db_lookup (self, id);
+
+ g_hash_table_insert (self->main_updates,
+ g_strdup (id),
+ flatpak_db_entry_ref (entry));
+
+ a = empty;
+ b = empty;
+
+ if (old_entry)
+ {
+ old = flatpak_db_entry_list_apps (old_entry);
+ sort_strv (old);
+ a = old;
+ }
+
+ if (entry)
+ {
+ new = flatpak_db_entry_list_apps (entry);
+ sort_strv (new);
+ b = new;
+ }
+
+ ia = 0;
+ ib = 0;
+ while (a[ia] != NULL || b[ib] != NULL)
+ {
+ if (a[ia] == NULL)
+ {
+ /* Not in old, but in new => added */
+ add_app_id (self, b[ib], id);
+ ib++;
+ }
+ else if (b[ib] == NULL)
+ {
+ /* Not in new, but in old => removed */
+ remove_app_id (self, a[ia], id);
+ ia++;
+ }
+ else
+ {
+ int cmp = strcmp (a[ia], b[ib]);
+
+ if (cmp == 0)
+ {
+ /* In both, no change */
+ ia++;
+ ib++;
+ }
+ else if (cmp < 0)
+ {
+ /* Not in new, but in old => removed */
+ remove_app_id (self, a[ia], id);
+ ia++;
+ }
+ else /* cmp > 0 */
+ {
+ /* Not in old, but in new => added */
+ add_app_id (self, b[ib], id);
+ ib++;
+ }
+ }
+ }
+}
+
+void
+flatpak_db_update (FlatpakDb *self)
+{
+ GHashTable *root, *main_h, *apps_h;
+ GBytes *new_contents;
+ GvdbTable *new_gvdb;
+ int i;
+
+ g_auto(GStrv) ids = NULL;
+ g_auto(GStrv) apps = NULL;
+
+ g_return_if_fail (FLATPAK_IS_DB (self));
+
+ root = gvdb_hash_table_new (NULL, NULL);
+ main_h = gvdb_hash_table_new (root, "main");
+ apps_h = gvdb_hash_table_new (root, "apps");
+ g_hash_table_unref (main_h);
+ g_hash_table_unref (apps_h);
+
+ ids = flatpak_db_list_ids (self);
+ for (i = 0; ids[i] != 0; i++)
+ {
+ g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (self, ids[i]);
+ if (entry != NULL)
+ {
+ GvdbItem *item;
+
+ item = gvdb_hash_table_insert (main_h, ids[i]);
+ gvdb_item_set_value (item, (GVariant *) entry);
+ }
+ }
+
+ apps = flatpak_db_list_apps (self);
+ for (i = 0; apps[i] != 0; i++)
+ {
+ g_auto(GStrv) app_ids = flatpak_db_list_ids_by_app (self, apps[i]);
+ GVariantBuilder builder;
+ GvdbItem *item;
+ int j;
+
+ /* May as well ensure that on-disk arrays are sorted, even if we don't use it yet */
+ sort_strv ((const char **) app_ids);
+
+ /* We should never list an app that has empty id lists */
+ g_assert (app_ids[0] != NULL);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+ for (j = 0; app_ids[j] != NULL; j++)
+ g_variant_builder_add (&builder, "s", app_ids[j]);
+
+ item = gvdb_hash_table_insert (apps_h, apps[i]);
+ gvdb_item_set_value (item, g_variant_builder_end (&builder));
+ }
+
+ new_contents = gvdb_table_get_content (root, FALSE);
+ new_gvdb = gvdb_table_new_from_bytes (new_contents, TRUE, NULL);
+
+ /* This was just created, any failure to parse it is purely an internal error */
+ g_assert (new_gvdb != NULL);
+
+ g_clear_pointer (&self->gvdb_contents, g_bytes_unref);
+ g_clear_pointer (&self->gvdb, gvdb_table_free);
+ self->gvdb_contents = new_contents;
+ self->gvdb = new_gvdb;
+ self->dirty = FALSE;
+}
+
+GBytes *
+flatpak_db_get_content (FlatpakDb *self)
+{
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+
+ return self->gvdb_contents;
+}
+
+/* Note: You must first call update to serialize, this only saves serialied data */
+gboolean
+flatpak_db_save_content (FlatpakDb *self,
+ GError **error)
+{
+ GBytes *content = NULL;
+
+ if (self->gvdb_contents == NULL)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+ "No content to save");
+ return FALSE;
+ }
+
+ if (self->path == NULL)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+ "No path set");
+ return FALSE;
+ }
+
+ content = self->gvdb_contents;
+ return g_file_set_contents (self->path, g_bytes_get_data (content, NULL), g_bytes_get_size (content), error);
+}
+
+static void
+save_content_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = user_data;
+ GFile *file = G_FILE (source_object);
+ gboolean ok;
+ g_autoptr(GError) error = NULL;
+
+ ok = g_file_replace_contents_finish (file,
+ res,
+ NULL, &error);
+ if (ok)
+ g_task_return_boolean (task, TRUE);
+ else
+ g_task_return_error (task, error);
+}
+
+void
+flatpak_db_save_content_async (FlatpakDb *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GBytes *content = NULL;
+
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ if (self->gvdb_contents == NULL)
+ {
+ g_task_return_new_error (task, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+ "No content to save");
+ return;
+ }
+
+ if (self->path == NULL)
+ {
+ g_task_return_new_error (task, G_FILE_ERROR, G_FILE_ERROR_INVAL,
+ "No path set");
+ return;
+ }
+
+ content = g_bytes_ref (self->gvdb_contents);
+ g_task_set_task_data (task, content, (GDestroyNotify) g_bytes_unref);
+
+ file = g_file_new_for_path (self->path);
+ g_file_replace_contents_bytes_async (file, content,
+ NULL, FALSE, 0,
+ cancellable,
+ save_content_callback,
+ g_object_ref (task));
+}
+
+gboolean
+flatpak_db_save_content_finish (FlatpakDb *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+
+GString *
+flatpak_db_print_string (FlatpakDb *self,
+ GString *string)
+{
+ g_auto(GStrv) ids = NULL;
+ g_auto(GStrv) apps = NULL;
+ int i;
+
+ g_return_val_if_fail (FLATPAK_IS_DB (self), NULL);
+
+ if G_UNLIKELY (string == NULL)
+ string = g_string_new (NULL);
+
+ g_string_append_printf (string, "main {\n");
+
+ ids = flatpak_db_list_ids (self);
+ sort_strv ((const char **) ids);
+ for (i = 0; ids[i] != 0; i++)
+ {
+ g_autoptr(FlatpakDbEntry) entry = flatpak_db_lookup (self, ids[i]);
+ g_string_append_printf (string, " %s: ", ids[i]);
+ if (entry != NULL)
+ flatpak_db_entry_print_string (entry, string);
+ g_string_append_printf (string, "\n");
+ }
+
+ g_string_append_printf (string, "}\napps {\n");
+
+ apps = flatpak_db_list_apps (self);
+ sort_strv ((const char **) apps);
+ for (i = 0; apps[i] != 0; i++)
+ {
+ int j;
+ g_auto(GStrv) app_ids = NULL;
+
+ app_ids = flatpak_db_list_ids_by_app (self, apps[i]);
+ sort_strv ((const char **) app_ids);
+
+ g_string_append_printf (string, " %s: ", apps[i]);
+ for (j = 0; app_ids[j] != NULL; j++)
+ g_string_append_printf (string, "%s%s", j == 0 ? "" : ", ", app_ids[j]);
+ g_string_append_printf (string, "\n");
+ }
+
+ g_string_append_printf (string, "}\n");
+
+ return string;
+}
+
+char *
+flatpak_db_print (FlatpakDb *self)
+{
+ return g_string_free (flatpak_db_print_string (self, NULL), FALSE);
+}
+
+FlatpakDbEntry *
+flatpak_db_entry_ref (FlatpakDbEntry *entry)
+{
+ if (entry != NULL)
+ g_variant_ref ((GVariant *) entry);
+ return entry;
+}
+
+void
+flatpak_db_entry_unref (FlatpakDbEntry *entry)
+{
+ g_variant_unref ((GVariant *) entry);
+}
+
+/* Transfer: full */
+GVariant *
+flatpak_db_entry_get_data (FlatpakDbEntry *entry)
+{
+ g_autoptr(GVariant) variant = g_variant_get_child_value ((GVariant *) entry, 0);
+
+ return g_variant_get_child_value (variant, 0);
+}
+
+/* Transfer: container */
+const char **
+flatpak_db_entry_list_apps (FlatpakDbEntry *entry)
+{
+ GVariant *v = (GVariant *) entry;
+
+ g_autoptr(GVariant) app_array = NULL;
+ GVariantIter iter;
+ GVariant *child;
+ GPtrArray *res;
+
+ res = g_ptr_array_new ();
+
+ app_array = g_variant_get_child_value (v, 1);
+
+ g_variant_iter_init (&iter, app_array);
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ const char *child_app_id;
+ g_autoptr(GVariant) permissions = g_variant_get_child_value (child, 1);
+
+ if (g_variant_n_children (permissions) > 0)
+ {
+ g_variant_get_child (child, 0, "&s", &child_app_id);
+ g_ptr_array_add (res, (char *) child_app_id);
+ }
+
+ g_variant_unref (child);
+ }
+
+ g_ptr_array_add (res, NULL);
+ return (const char **) g_ptr_array_free (res, FALSE);
+}
+
+static GVariant *
+flatpak_db_entry_get_permissions_variant (FlatpakDbEntry *entry,
+ const char *app_id)
+{
+ GVariant *v = (GVariant *) entry;
+
+ g_autoptr(GVariant) app_array = NULL;
+ GVariant *child;
+ GVariant *res = NULL;
+ gsize n_children, start, end, m;
+ const char *child_app_id;
+ int cmp;
+
+ app_array = g_variant_get_child_value (v, 1);
+
+ n_children = g_variant_n_children (app_array);
+
+ start = 0;
+ end = n_children;
+ while (start < end)
+ {
+ m = (start + end) / 2;
+
+ child = g_variant_get_child_value (app_array, m);
+ g_variant_get_child (child, 0, "&s", &child_app_id);
+
+ cmp = strcmp (app_id, child_app_id);
+ if (cmp == 0)
+ {
+ res = g_variant_get_child_value (child, 1);
+ break;
+ }
+ else if (cmp < 0)
+ {
+ end = m;
+ }
+ else /* cmp > 0 */
+ {
+ start = m + 1;
+ }
+ }
+
+ return res;
+}
+
+
+/* Transfer: container */
+const char **
+flatpak_db_entry_list_permissions (FlatpakDbEntry *entry,
+ const char *app)
+{
+ g_autoptr(GVariant) permissions = NULL;
+
+ permissions = flatpak_db_entry_get_permissions_variant (entry, app);
+ if (permissions)
+ return g_variant_get_strv (permissions, NULL);
+ else
+ return g_new0 (const char *, 1);
+}
+
+gboolean
+flatpak_db_entry_has_permission (FlatpakDbEntry *entry,
+ const char *app,
+ const char *permission)
+{
+ g_autofree const char **app_permissions = NULL;
+
+ app_permissions = flatpak_db_entry_list_permissions (entry, app);
+
+ return g_strv_contains (app_permissions, permission);
+}
+
+gboolean
+flatpak_db_entry_has_permissions (FlatpakDbEntry *entry,
+ const char *app,
+ const char **permissions)
+{
+ g_autofree const char **app_permissions = NULL;
+ int i;
+
+ app_permissions = flatpak_db_entry_list_permissions (entry, app);
+
+ for (i = 0; permissions[i] != NULL; i++)
+ {
+ if (!g_strv_contains (app_permissions, permissions[i]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GVariant *
+make_entry (GVariant *data,
+ GVariant *app_permissions)
+{
+ return g_variant_new ("(v@a{sas})", data, app_permissions);
+}
+
+static GVariant *
+make_empty_app_permissions (void)
+{
+ return g_variant_new_array (G_VARIANT_TYPE ("{sas}"), NULL, 0);
+}
+
+static GVariant *
+make_permissions (const char *app, const char **permissions)
+{
+ static const char **empty = { NULL };
+
+ if (permissions == NULL)
+ permissions = empty;
+
+ return g_variant_new ("{s@as}",
+ app,
+ g_variant_new_strv (permissions, -1));
+}
+
+static GVariant *
+add_permissions (GVariant *app_permissions,
+ GVariant *permissions)
+{
+ GVariantBuilder builder;
+ GVariantIter iter;
+ GVariant *child;
+ gboolean added = FALSE;
+ int cmp;
+ const char *new_app_id;
+ const char *child_app_id;
+
+ g_autoptr(GVariant) new_perms_array = NULL;
+
+ g_variant_get (permissions, "{&s@as}", &new_app_id, &new_perms_array);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+
+ /* Insert or replace permissions in sorted order */
+
+ g_variant_iter_init (&iter, app_permissions);
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ g_autoptr(GVariant) old_perms_array = NULL;
+
+ g_variant_get (child, "{&s@as}", &child_app_id, &old_perms_array);
+
+ cmp = strcmp (new_app_id, child_app_id);
+ if (cmp == 0)
+ {
+ added = TRUE;
+ /* Replace old permissions */
+ g_variant_builder_add_value (&builder, permissions);
+ }
+ else if (cmp < 0)
+ {
+ if (!added)
+ {
+ added = TRUE;
+ g_variant_builder_add_value (&builder, permissions);
+ }
+ g_variant_builder_add_value (&builder, child);
+ }
+ else /* cmp > 0 */
+ {
+ g_variant_builder_add_value (&builder, child);
+ }
+
+ g_variant_unref (child);
+ }
+
+ if (!added)
+ g_variant_builder_add_value (&builder, permissions);
+
+ return g_variant_builder_end (&builder);
+}
+
+FlatpakDbEntry *
+flatpak_db_entry_new (GVariant *data)
+{
+ GVariant *res;
+
+ if (data == NULL)
+ data = g_variant_new_byte (0);
+
+ res = make_entry (data,
+ make_empty_app_permissions ());
+
+ return (FlatpakDbEntry *) g_variant_ref_sink (res);
+}
+
+FlatpakDbEntry *
+flatpak_db_entry_modify_data (FlatpakDbEntry *entry,
+ GVariant *data)
+{
+ GVariant *v = (GVariant *) entry;
+ GVariant *res;
+
+ if (data == NULL)
+ data = g_variant_new_byte (0);
+
+ res = make_entry (data,
+ g_variant_get_child_value (v, 1));
+ return (FlatpakDbEntry *) g_variant_ref_sink (res);
+}
+
+/* NULL (or empty) permissions to remove permissions */
+FlatpakDbEntry *
+flatpak_db_entry_set_app_permissions (FlatpakDbEntry *entry,
+ const char *app,
+ const char **permissions)
+{
+ GVariant *v = (GVariant *) entry;
+ GVariant *res;
+
+ g_autoptr(GVariant) old_data_v = g_variant_get_child_value (v, 0);
+ g_autoptr(GVariant) old_data = g_variant_get_child_value (old_data_v, 0);
+ g_autoptr(GVariant) old_permissions = g_variant_get_child_value (v, 1);
+
+ res = make_entry (old_data,
+ add_permissions (old_permissions,
+ make_permissions (app,
+ permissions)));
+ return (FlatpakDbEntry *) g_variant_ref_sink (res);
+}
+
+GString *
+flatpak_db_entry_print_string (FlatpakDbEntry *entry,
+ GString *string)
+{
+ return g_variant_print_string ((GVariant *) entry, string, FALSE);
+}