diff options
author | Alexander Larsson <alexl@redhat.com> | 2016-05-06 18:03:47 +0200 |
---|---|---|
committer | Alexander Larsson <alexl@redhat.com> | 2016-05-09 09:00:20 +0200 |
commit | c24528d3697c62cad8ff746a56992a59f31d333d (patch) | |
tree | 2dbb32e15c57cc4061f7b37d6db29a7b97228c15 /common/flatpak-db.c | |
parent | 6a613d1fabce5e93656cfbcb6815cc9bc98f437b (diff) | |
download | xdg-app-c24528d3697c62cad8ff746a56992a59f31d333d.tar.gz |
Rename source files to flatpak
Diffstat (limited to 'common/flatpak-db.c')
-rw-r--r-- | common/flatpak-db.c | 1224 |
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); +} |