forked from Mirrors/flatpak-builder
1225 lines
31 KiB
C
1225 lines
31 KiB
C
/* flatpak-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);
|
|
}
|