From c24528d3697c62cad8ff746a56992a59f31d333d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 May 2016 18:03:47 +0200 Subject: Rename source files to flatpak --- common/Makefile.am.inc | 36 +- common/flatpak-chain-input-stream.c | 214 ++ common/flatpak-chain-input-stream.h | 68 + common/flatpak-common-types.h | 28 + common/flatpak-db.c | 1224 ++++++++++ common/flatpak-db.h | 103 + common/flatpak-dir.c | 4265 +++++++++++++++++++++++++++++++++++ common/flatpak-dir.h | 360 +++ common/flatpak-portal-error.c | 48 + common/flatpak-portal-error.h | 49 + common/flatpak-run.c | 3050 +++++++++++++++++++++++++ common/flatpak-run.h | 111 + common/flatpak-utils.c | 3008 ++++++++++++++++++++++++ common/flatpak-utils.h | 357 +++ common/xdg-app-chain-input-stream.c | 214 -- common/xdg-app-chain-input-stream.h | 68 - common/xdg-app-common-types.h | 28 - common/xdg-app-db.c | 1224 ---------- common/xdg-app-db.h | 103 - common/xdg-app-dir.c | 4265 ----------------------------------- common/xdg-app-dir.h | 360 --- common/xdg-app-portal-error.c | 48 - common/xdg-app-portal-error.h | 49 - common/xdg-app-run.c | 3050 ------------------------- common/xdg-app-run.h | 111 - common/xdg-app-utils.c | 3008 ------------------------ common/xdg-app-utils.h | 357 --- 27 files changed, 12903 insertions(+), 12903 deletions(-) create mode 100644 common/flatpak-chain-input-stream.c create mode 100644 common/flatpak-chain-input-stream.h create mode 100644 common/flatpak-common-types.h create mode 100644 common/flatpak-db.c create mode 100644 common/flatpak-db.h create mode 100644 common/flatpak-dir.c create mode 100644 common/flatpak-dir.h create mode 100644 common/flatpak-portal-error.c create mode 100644 common/flatpak-portal-error.h create mode 100644 common/flatpak-run.c create mode 100644 common/flatpak-run.h create mode 100644 common/flatpak-utils.c create mode 100644 common/flatpak-utils.h delete mode 100644 common/xdg-app-chain-input-stream.c delete mode 100644 common/xdg-app-chain-input-stream.h delete mode 100644 common/xdg-app-common-types.h delete mode 100644 common/xdg-app-db.c delete mode 100644 common/xdg-app-db.h delete mode 100644 common/xdg-app-dir.c delete mode 100644 common/xdg-app-dir.h delete mode 100644 common/xdg-app-portal-error.c delete mode 100644 common/xdg-app-portal-error.h delete mode 100644 common/xdg-app-run.c delete mode 100644 common/xdg-app-run.h delete mode 100644 common/xdg-app-utils.c delete mode 100644 common/xdg-app-utils.h (limited to 'common') diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index 0c88972..b0069fb 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -1,14 +1,14 @@ noinst_LTLIBRARIES += libxdgapp-common.la -dbus_built_sources = common/xdg-app-dbus.c common/xdg-app-dbus.h -systemd_dbus_built_sources = common/xdg-app-systemd-dbus.c common/xdg-app-systemd-dbus.h +dbus_built_sources = common/flatpak-dbus.c common/flatpak-dbus.h +systemd_dbus_built_sources = common/flatpak-systemd-dbus.c common/flatpak-systemd-dbus.h BUILT_SOURCES += $(dbus_built_sources) $(systemd_dbus_built_sources) $(dbus_built_sources) : data/org.freedesktop.XdgApp.xml $(AM_V_GEN) $(GDBUS_CODEGEN) \ --interface-prefix org.freedesktop.XdgApp. \ - --c-namespace XdgApp \ - --generate-c-code $(builddir)/common/xdg-app-dbus \ + --c-namespace Flatpak \ + --generate-c-code $(builddir)/common/flatpak-dbus \ $(srcdir)/data/org.freedesktop.XdgApp.xml \ $(NULL) @@ -16,29 +16,29 @@ $(systemd_dbus_built_sources) : data/org.freedesktop.systemd1.xml $(AM_V_GEN) $(GDBUS_CODEGEN) \ --interface-prefix org.freedesktop.systemd1. \ --c-namespace Systemd \ - --generate-c-code $(builddir)/common/xdg-app-systemd-dbus \ + --generate-c-code $(builddir)/common/flatpak-systemd-dbus \ $(srcdir)/data/org.freedesktop.systemd1.xml \ $(NULL) libxdgapp_common_la_SOURCES = \ - common/xdg-app-common-types.h \ - common/xdg-app-dir.c \ - common/xdg-app-dir.h \ - common/xdg-app-run.c \ - common/xdg-app-run.h \ - common/xdg-app-portal-error.c \ - common/xdg-app-portal-error.h \ - common/xdg-app-utils.c \ - common/xdg-app-utils.h \ - common/xdg-app-chain-input-stream.c \ - common/xdg-app-chain-input-stream.h \ + common/flatpak-common-types.h \ + common/flatpak-dir.c \ + common/flatpak-dir.h \ + common/flatpak-run.c \ + common/flatpak-run.h \ + common/flatpak-portal-error.c \ + common/flatpak-portal-error.h \ + common/flatpak-utils.c \ + common/flatpak-utils.h \ + common/flatpak-chain-input-stream.c \ + common/flatpak-chain-input-stream.h \ common/gvdb/gvdb-reader.h \ common/gvdb/gvdb-format.h \ common/gvdb/gvdb-reader.c \ common/gvdb/gvdb-builder.h \ common/gvdb/gvdb-builder.c \ - common/xdg-app-db.c \ - common/xdg-app-db.h \ + common/flatpak-db.c \ + common/flatpak-db.h \ $(dbus_built_sources) \ $(systemd_dbus_built_sources) \ $(NULL) diff --git a/common/flatpak-chain-input-stream.c b/common/flatpak-chain-input-stream.c new file mode 100644 index 0000000..6dd68a0 --- /dev/null +++ b/common/flatpak-chain-input-stream.c @@ -0,0 +1,214 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library 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 library 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "flatpak-chain-input-stream.h" + +enum { + PROP_0, + PROP_STREAMS +}; + +G_DEFINE_TYPE (FlatpakChainInputStream, flatpak_chain_input_stream, G_TYPE_INPUT_STREAM) + +struct _FlatpakChainInputStreamPrivate +{ + GPtrArray *streams; + guint index; +}; + +static void flatpak_chain_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void flatpak_chain_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void flatpak_chain_input_stream_finalize (GObject *object); +static gssize flatpak_chain_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error); +static gboolean flatpak_chain_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error); + +static void +flatpak_chain_input_stream_class_init (FlatpakChainInputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (FlatpakChainInputStreamPrivate)); + + gobject_class->get_property = flatpak_chain_input_stream_get_property; + gobject_class->set_property = flatpak_chain_input_stream_set_property; + gobject_class->finalize = flatpak_chain_input_stream_finalize; + + stream_class->read_fn = flatpak_chain_input_stream_read; + stream_class->close_fn = flatpak_chain_input_stream_close; + + /* + * FlatpakChainInputStream:streams: (element-type GInputStream) + * + * Chain of input streams read in order. + */ + g_object_class_install_property (gobject_class, + PROP_STREAMS, + g_param_spec_pointer ("streams", + "", "", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + +} + +static void +flatpak_chain_input_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + FlatpakChainInputStream *self; + + self = FLATPAK_CHAIN_INPUT_STREAM (object); + + switch (prop_id) + { + case PROP_STREAMS: + self->priv->streams = g_ptr_array_ref (g_value_get_pointer (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +flatpak_chain_input_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + FlatpakChainInputStream *self; + + self = FLATPAK_CHAIN_INPUT_STREAM (object); + + switch (prop_id) + { + case PROP_STREAMS: + g_value_set_pointer (value, self->priv->streams); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +flatpak_chain_input_stream_finalize (GObject *object) +{ + FlatpakChainInputStream *stream; + + stream = (FlatpakChainInputStream *) (object); + + g_ptr_array_unref (stream->priv->streams); + + G_OBJECT_CLASS (flatpak_chain_input_stream_parent_class)->finalize (object); +} + +static void +flatpak_chain_input_stream_init (FlatpakChainInputStream *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + FLATPAK_TYPE_CHAIN_INPUT_STREAM, + FlatpakChainInputStreamPrivate); + +} + +FlatpakChainInputStream * +flatpak_chain_input_stream_new (GPtrArray *streams) +{ + FlatpakChainInputStream *stream; + + stream = g_object_new (FLATPAK_TYPE_CHAIN_INPUT_STREAM, + "streams", streams, + NULL); + + return (FlatpakChainInputStream *) (stream); +} + +static gssize +flatpak_chain_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + FlatpakChainInputStream *self = (FlatpakChainInputStream *) stream; + GInputStream *child; + gssize res = -1; + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return -1; + + if (self->priv->index >= self->priv->streams->len) + return 0; + + res = 0; + while (res == 0 && self->priv->index < self->priv->streams->len) + { + child = self->priv->streams->pdata[self->priv->index]; + res = g_input_stream_read (child, + buffer, + count, + cancellable, + error); + if (res == 0) + self->priv->index++; + } + + return res; +} + +static gboolean +flatpak_chain_input_stream_close (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + FlatpakChainInputStream *self = (gpointer) stream; + guint i; + + for (i = 0; i < self->priv->streams->len; i++) + { + GInputStream *child = self->priv->streams->pdata[i]; + if (!g_input_stream_close (child, cancellable, error)) + goto out; + } + + ret = TRUE; +out: + return ret; +} diff --git a/common/flatpak-chain-input-stream.h b/common/flatpak-chain-input-stream.h new file mode 100644 index 0000000..ed66560 --- /dev/null +++ b/common/flatpak-chain-input-stream.h @@ -0,0 +1,68 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2011 Colin Walters + * + * This library 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 library 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 library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#pragma once + +#ifndef __GI_SCANNER__ + +#include + +G_BEGIN_DECLS + +#define FLATPAK_TYPE_CHAIN_INPUT_STREAM (flatpak_chain_input_stream_get_type ()) +#define FLATPAK_CHAIN_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FLATPAK_TYPE_CHAIN_INPUT_STREAM, FlatpakChainInputStream)) +#define FLATPAK_CHAIN_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), FLATPAK_TYPE_CHAIN_INPUT_STREAM, FlatpakChainInputStreamClass)) +#define FLATPAK_IS_CHAIN_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), FLATPAK_TYPE_CHAIN_INPUT_STREAM)) +#define FLATPAK_IS_CHAIN_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), FLATPAK_TYPE_CHAIN_INPUT_STREAM)) +#define FLATPAK_CHAIN_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), FLATPAK_TYPE_CHAIN_INPUT_STREAM, FlatpakChainInputStreamClass)) + +typedef struct _FlatpakChainInputStream FlatpakChainInputStream; +typedef struct _FlatpakChainInputStreamClass FlatpakChainInputStreamClass; +typedef struct _FlatpakChainInputStreamPrivate FlatpakChainInputStreamPrivate; + +struct _FlatpakChainInputStream +{ + GInputStream parent_instance; + + /*< private >*/ + FlatpakChainInputStreamPrivate *priv; +}; + +struct _FlatpakChainInputStreamClass +{ + GInputStreamClass parent_class; + + /*< private >*/ + /* Padding for future expansion */ + void (*_g_reserved1) (void); + void (*_g_reserved2) (void); + void (*_g_reserved3) (void); + void (*_g_reserved4) (void); + void (*_g_reserved5) (void); +}; + +GType flatpak_chain_input_stream_get_type (void) G_GNUC_CONST; + +FlatpakChainInputStream * flatpak_chain_input_stream_new (GPtrArray *streams); + +G_END_DECLS + +#endif diff --git a/common/flatpak-common-types.h b/common/flatpak-common-types.h new file mode 100644 index 0000000..0243eca --- /dev/null +++ b/common/flatpak-common-types.h @@ -0,0 +1,28 @@ +/* + * Copyright © 2015 Red Hat, Inc + * + * This program 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 library 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 library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_COMMON_TYPES_H__ +#define __FLATPAK_COMMON_TYPES_H__ + +typedef struct FlatpakDir FlatpakDir; +typedef struct FlatpakDeploy FlatpakDeploy; +typedef struct FlatpakContext FlatpakContext; + +#endif /* __FLATPAK_COMMON_TYPES_H__ */ 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 . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#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); +} diff --git a/common/flatpak-db.h b/common/flatpak-db.h new file mode 100644 index 0000000..738cf9b --- /dev/null +++ b/common/flatpak-db.h @@ -0,0 +1,103 @@ +/* xdg-app-db.h + * + * Copyright © 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 . + * + * Authors: + * Alexander Larsson + */ + +#ifndef FLATPAK_DB_H +#define FLATPAK_DB_H + +#include + +#include "libglnx/libglnx.h" +#include + +G_BEGIN_DECLS + +typedef struct FlatpakDb FlatpakDb; +typedef struct _FlatpakDbEntry FlatpakDbEntry; + +#define FLATPAK_TYPE_DB (flatpak_db_get_type ()) +#define FLATPAK_DB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_DB, FlatpakDb)) +#define FLATPAK_IS_DB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_DB)) + +GType flatpak_db_get_type (void); + +FlatpakDb * flatpak_db_new (const char *path, + gboolean fail_if_not_found, + GError **error); +char ** flatpak_db_list_ids (FlatpakDb *self); +char ** flatpak_db_list_apps (FlatpakDb *self); +char ** flatpak_db_list_ids_by_app (FlatpakDb *self, + const char *app); +char ** flatpak_db_list_ids_by_value (FlatpakDb *self, + GVariant *data); +FlatpakDbEntry *flatpak_db_lookup (FlatpakDb *self, + const char *id); +GString * flatpak_db_print_string (FlatpakDb *self, + GString *string); +char * flatpak_db_print (FlatpakDb *self); + +gboolean flatpak_db_is_dirty (FlatpakDb *self); +void flatpak_db_set_entry (FlatpakDb *self, + const char *id, + FlatpakDbEntry *entry); +void flatpak_db_update (FlatpakDb *self); +GBytes * flatpak_db_get_content (FlatpakDb *self); +const char * flatpak_db_get_path (FlatpakDb *self); +gboolean flatpak_db_save_content (FlatpakDb *self, + GError **error); +void flatpak_db_save_content_async (FlatpakDb *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean flatpak_db_save_content_finish (FlatpakDb *self, + GAsyncResult *res, + GError **error); +void flatpak_db_set_path (FlatpakDb *self, + const char *path); + + +FlatpakDbEntry *flatpak_db_entry_ref (FlatpakDbEntry *entry); +void flatpak_db_entry_unref (FlatpakDbEntry *entry); +GVariant * flatpak_db_entry_get_data (FlatpakDbEntry *entry); +const char ** flatpak_db_entry_list_apps (FlatpakDbEntry *entry); +const char ** flatpak_db_entry_list_permissions (FlatpakDbEntry *entry, + const char *app); +gboolean flatpak_db_entry_has_permission (FlatpakDbEntry *entry, + const char *app, + const char *permission); +gboolean flatpak_db_entry_has_permissions (FlatpakDbEntry *entry, + const char *app, + const char **permissions); +GString * flatpak_db_entry_print_string (FlatpakDbEntry *entry, + GString *string); + +FlatpakDbEntry *flatpak_db_entry_new (GVariant *data); +FlatpakDbEntry *flatpak_db_entry_modify_data (FlatpakDbEntry *entry, + GVariant *data); +FlatpakDbEntry *flatpak_db_entry_set_app_permissions (FlatpakDbEntry *entry, + const char *app, + const char **permissions); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDb, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDbEntry, flatpak_db_entry_unref) + +G_END_DECLS + +#endif /* FLATPAK_DB_H */ diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c new file mode 100644 index 0000000..1aac10c --- /dev/null +++ b/common/flatpak-dir.c @@ -0,0 +1,4265 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 library 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 library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "libgsystem.h" +#include "libglnx/libglnx.h" + +#include "flatpak-dir.h" +#include "flatpak-utils.h" +#include "flatpak-run.h" + +#include "errno.h" + +#define NO_SYSTEM_HELPER ((XdgAppSystemHelper *) (gpointer) 1) + +struct FlatpakDir +{ + GObject parent; + + gboolean user; + GFile *basedir; + OstreeRepo *repo; + + XdgAppSystemHelper *system_helper; + + SoupSession *soup_session; +}; + +typedef struct +{ + GObjectClass parent_class; +} FlatpakDirClass; + +struct FlatpakDeploy +{ + GObject parent; + + GFile *dir; + GKeyFile *metadata; + FlatpakContext *system_overrides; + FlatpakContext *user_overrides; +}; + +typedef struct +{ + GObjectClass parent_class; +} FlatpakDeployClass; + +G_DEFINE_TYPE (FlatpakDir, flatpak_dir, G_TYPE_OBJECT) +G_DEFINE_TYPE (FlatpakDeploy, flatpak_deploy, G_TYPE_OBJECT) + +G_DEFINE_QUARK (xdg - app - dir - error - quark, flatpak_dir_error) + +enum { + PROP_0, + + PROP_USER, + PROP_PATH +}; + +#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \ + "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev") + +static void +flatpak_deploy_finalize (GObject *object) +{ + FlatpakDeploy *self = FLATPAK_DEPLOY (object); + + g_clear_object (&self->dir); + g_clear_pointer (&self->metadata, g_key_file_unref); + g_clear_pointer (&self->system_overrides, g_key_file_unref); + g_clear_pointer (&self->user_overrides, g_key_file_unref); + + G_OBJECT_CLASS (flatpak_deploy_parent_class)->finalize (object); +} + +static void +flatpak_deploy_class_init (FlatpakDeployClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = flatpak_deploy_finalize; + +} + +static void +flatpak_deploy_init (FlatpakDeploy *self) +{ +} + +GFile * +flatpak_deploy_get_dir (FlatpakDeploy *deploy) +{ + return g_object_ref (deploy->dir); +} + +GFile * +flatpak_deploy_get_files (FlatpakDeploy *deploy) +{ + return g_file_get_child (deploy->dir, "files"); +} + +FlatpakContext * +flatpak_deploy_get_overrides (FlatpakDeploy *deploy) +{ + FlatpakContext *overrides = flatpak_context_new (); + + if (deploy->system_overrides) + flatpak_context_merge (overrides, deploy->system_overrides); + + if (deploy->user_overrides) + flatpak_context_merge (overrides, deploy->user_overrides); + + return overrides; +} + +GKeyFile * +flatpak_deploy_get_metadata (FlatpakDeploy *deploy) +{ + return g_key_file_ref (deploy->metadata); +} + +static FlatpakDeploy * +flatpak_deploy_new (GFile *dir, GKeyFile *metadata) +{ + FlatpakDeploy *deploy; + + deploy = g_object_new (FLATPAK_TYPE_DEPLOY, NULL); + deploy->dir = g_object_ref (dir); + deploy->metadata = g_key_file_ref (metadata); + + return deploy; +} + +GFile * +flatpak_get_system_base_dir_location (void) +{ + return g_file_new_for_path (FLATPAK_SYSTEMDIR); +} + +GFile * +flatpak_get_user_base_dir_location (void) +{ + g_autofree char *base = g_build_filename (g_get_user_data_dir (), "xdg-app", NULL); + + return g_file_new_for_path (base); +} + +GFile * +flatpak_get_user_cache_dir_location (void) +{ + g_autoptr(GFile) base_dir = NULL; + + base_dir = flatpak_get_user_base_dir_location (); + return g_file_get_child (base_dir, "system-cache"); +} + +GFile * +flatpak_ensure_user_cache_dir_location (GError **error) +{ + g_autoptr(GFile) cache_dir = NULL; + g_autofree char *cache_path = NULL; + + cache_dir = flatpak_get_user_cache_dir_location (); + cache_path = g_file_get_path (cache_dir); + + if (g_mkdir_with_parents (cache_path, 0755) != 0) + { + glnx_set_error_from_errno (error); + return NULL; + } + + return g_steal_pointer (&cache_dir); +} + +static XdgAppSystemHelper * +flatpak_dir_get_system_helper (FlatpakDir *self) +{ + g_autoptr(GError) error = NULL; + + if (g_once_init_enter (&self->system_helper)) + { + XdgAppSystemHelper *system_helper; + system_helper = + xdg_app_system_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + "org.freedesktop.Flatpak.SystemHelper", + "/org/freedesktop/Flatpak/SystemHelper", + NULL, &error); + if (error != NULL) + { + g_warning ("Can't find org.freedesktop.Flatpak.SystemHelper: %s\n", error->message); + system_helper = NO_SYSTEM_HELPER; + } + + g_once_init_leave (&self->system_helper, system_helper); + } + + if (self->system_helper != NO_SYSTEM_HELPER) + return self->system_helper; + return NULL; +} + +gboolean +flatpak_dir_use_child_repo (FlatpakDir *self) +{ + XdgAppSystemHelper *system_helper; + + if (self->user || getuid () == 0) + return FALSE; + + system_helper = flatpak_dir_get_system_helper (self); + + return system_helper != NULL; +} + +static OstreeRepo * +system_ostree_repo_new (GFile *repodir) +{ + return g_object_new (OSTREE_TYPE_REPO, "path", repodir, + "remotes-config-dir", FLATPAK_CONFIGDIR "/remotes.d", + NULL); +} + +static void +flatpak_dir_finalize (GObject *object) +{ + FlatpakDir *self = FLATPAK_DIR (object); + + g_clear_object (&self->repo); + g_clear_object (&self->basedir); + + g_clear_object (&self->system_helper); + + g_clear_object (&self->soup_session); + + G_OBJECT_CLASS (flatpak_dir_parent_class)->finalize (object); +} + +static void +flatpak_dir_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + FlatpakDir *self = FLATPAK_DIR (object); + + switch (prop_id) + { + case PROP_PATH: + /* Canonicalize */ + self->basedir = g_file_new_for_path (gs_file_get_path_cached (g_value_get_object (value))); + break; + + case PROP_USER: + self->user = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +flatpak_dir_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + FlatpakDir *self = FLATPAK_DIR (object); + + switch (prop_id) + { + case PROP_PATH: + g_value_set_object (value, self->basedir); + break; + + case PROP_USER: + g_value_set_boolean (value, self->user); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +flatpak_dir_class_init (FlatpakDirClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = flatpak_dir_get_property; + object_class->set_property = flatpak_dir_set_property; + object_class->finalize = flatpak_dir_finalize; + + g_object_class_install_property (object_class, + PROP_USER, + g_param_spec_boolean ("user", + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_PATH, + g_param_spec_object ("path", + "", + "", + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +flatpak_dir_init (FlatpakDir *self) +{ +} + +gboolean +flatpak_dir_is_user (FlatpakDir *self) +{ + return self->user; +} + +GFile * +flatpak_dir_get_path (FlatpakDir *self) +{ + return self->basedir; +} + +GFile * +flatpak_dir_get_changed_path (FlatpakDir *self) +{ + return g_file_get_child (self->basedir, ".changed"); +} + +char * +flatpak_dir_load_override (FlatpakDir *self, + const char *app_id, + gsize *length, + GError **error) +{ + g_autoptr(GFile) override_dir = NULL; + g_autoptr(GFile) file = NULL; + char *metadata_contents; + + override_dir = g_file_get_child (self->basedir, "overrides"); + file = g_file_get_child (override_dir, app_id); + + if (!g_file_load_contents (file, NULL, + &metadata_contents, length, NULL, NULL)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "No overrides found for %s", app_id); + return NULL; + } + + return metadata_contents; +} + +GKeyFile * +flatpak_load_override_keyfile (const char *app_id, gboolean user, GError **error) +{ + g_autofree char *metadata_contents = NULL; + gsize metadata_size; + + g_autoptr(GKeyFile) metakey = g_key_file_new (); + g_autoptr(FlatpakDir) dir = NULL; + + dir = flatpak_dir_get (user); + + metadata_contents = flatpak_dir_load_override (dir, app_id, &metadata_size, error); + if (metadata_contents == NULL) + return NULL; + + if (!g_key_file_load_from_data (metakey, + metadata_contents, + metadata_size, + 0, error)) + return NULL; + + return g_steal_pointer (&metakey); +} + +FlatpakContext * +flatpak_load_override_file (const char *app_id, gboolean user, GError **error) +{ + FlatpakContext *overrides = flatpak_context_new (); + + g_autoptr(GKeyFile) metakey = NULL; + g_autoptr(GError) my_error = NULL; + + metakey = flatpak_load_override_keyfile (app_id, user, &my_error); + if (metakey == NULL) + { + if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_propagate_error (error, g_steal_pointer (&my_error)); + return NULL; + } + } + else + { + if (!flatpak_context_load_metadata (overrides, metakey, error)) + return NULL; + } + + return g_steal_pointer (&overrides); +} + +gboolean +flatpak_save_override_keyfile (GKeyFile *metakey, + const char *app_id, + gboolean user, + GError **error) +{ + g_autoptr(GFile) base_dir = NULL; + g_autoptr(GFile) override_dir = NULL; + g_autoptr(GFile) file = NULL; + g_autofree char *filename = NULL; + g_autofree char *parent = NULL; + + if (user) + base_dir = flatpak_get_user_base_dir_location (); + else + base_dir = flatpak_get_system_base_dir_location (); + + override_dir = g_file_get_child (base_dir, "overrides"); + file = g_file_get_child (override_dir, app_id); + + filename = g_file_get_path (file); + parent = g_path_get_dirname (filename); + if (g_mkdir_with_parents (parent, 0755)) + { + glnx_set_error_from_errno (error); + return FALSE; + } + + return g_key_file_save_to_file (metakey, filename, error); +} + +FlatpakDeploy * +flatpak_dir_load_deployed (FlatpakDir *self, + const char *ref, + const char *checksum, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) deploy_dir = NULL; + g_autoptr(GKeyFile) metakey = NULL; + g_autoptr(GFile) metadata = NULL; + g_auto(GStrv) ref_parts = NULL; + g_autofree char *metadata_contents = NULL; + FlatpakDeploy *deploy; + gsize metadata_size; + + deploy_dir = flatpak_dir_get_if_deployed (self, ref, checksum, cancellable); + if (deploy_dir == NULL) + { + g_set_error (error, FLATPAK_DIR_ERROR, FLATPAK_DIR_ERROR_NOT_DEPLOYED, "%s not installed", ref); + return NULL; + } + + metadata = g_file_get_child (deploy_dir, "metadata"); + if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error)) + return NULL; + + metakey = g_key_file_new (); + if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error)) + return NULL; + + deploy = flatpak_deploy_new (deploy_dir, metakey); + + ref_parts = g_strsplit (ref, "/", -1); + g_assert (g_strv_length (ref_parts) == 4); + + /* Only apps have overrides */ + if (strcmp (ref_parts[0], "app") == 0) + { + /* Only load system overrides for system installed apps */ + if (!self->user) + { + deploy->system_overrides = flatpak_load_override_file (ref_parts[1], FALSE, error); + if (deploy->system_overrides == NULL) + return NULL; + } + + /* Always load user overrides */ + deploy->user_overrides = flatpak_load_override_file (ref_parts[1], TRUE, error); + if (deploy->user_overrides == NULL) + return NULL; + } + + return deploy; +} + +GFile * +flatpak_dir_get_deploy_dir (FlatpakDir *self, + const char *ref) +{ + return g_file_resolve_relative_path (self->basedir, ref); +} + +GFile * +flatpak_dir_get_exports_dir (FlatpakDir *self) +{ + return g_file_get_child (self->basedir, "exports"); +} + +GFile * +flatpak_dir_get_removed_dir (FlatpakDir *self) +{ + return g_file_get_child (self->basedir, ".removed"); +} + +OstreeRepo * +flatpak_dir_get_repo (FlatpakDir *self) +{ + return self->repo; +} + + +/* This is an exclusive per xdg-app installation file lock that is taken + * whenever any config in the directory outside the repo is to be changed. For + * instance deployements, overrides or active commit changes. + * + * For concurrency protection of the actual repository we rely on ostree + * to do the right thing. + */ +gboolean +flatpak_dir_lock (FlatpakDir *self, + GLnxLockFile *lockfile, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) lock_file = g_file_get_child (flatpak_dir_get_path (self), "lock"); + g_autofree char *lock_path = g_file_get_path (lock_file); + + return glnx_make_lock_file (AT_FDCWD, lock_path, LOCK_EX, lockfile, error); +} + +const char * +flatpak_deploy_data_get_origin (GVariant *deploy_data) +{ + const char *origin; + + g_variant_get_child (deploy_data, 0, "&s", &origin); + return origin; +} + +const char * +flatpak_deploy_data_get_commit (GVariant *deploy_data) +{ + const char *commit; + + g_variant_get_child (deploy_data, 1, "&s", &commit); + return commit; +} + +/** + * flatpak_deploy_data_get_subpaths: + * + * Returns: (array length=length zero-terminated=1) (transfer container): an array of constant strings + **/ +const char ** +flatpak_deploy_data_get_subpaths (GVariant *deploy_data) +{ + const char **subpaths; + + g_variant_get_child (deploy_data, 2, "^as", &subpaths); + return subpaths; +} + +guint64 +flatpak_deploy_data_get_installed_size (GVariant *deploy_data) +{ + guint64 size; + + g_variant_get_child (deploy_data, 3, "t", &size); + return GUINT64_FROM_BE (size); +} + +static GVariant * +flatpak_dir_new_deploy_data (const char *origin, + const char *commit, + char **subpaths, + guint64 installed_size, + GVariant *metadata) +{ + char *empty_subpaths[] = {NULL}; + GVariantBuilder builder; + + if (metadata == NULL) + { + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + metadata = g_variant_builder_end (&builder); + } + + return g_variant_ref_sink (g_variant_new ("(ss^ast@a{sv})", + origin, + commit, + subpaths ? subpaths : empty_subpaths, + GUINT64_TO_BE (installed_size), + metadata)); +} + +static char ** +get_old_subpaths (GFile *deploy_base, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) file = NULL; + g_autofree char *data = NULL; + g_autoptr(GError) my_error = NULL; + g_autoptr(GPtrArray) subpaths = NULL; + g_auto(GStrv) lines = NULL; + int i; + + file = g_file_get_child (deploy_base, "subpaths"); + if (!g_file_load_contents (file, cancellable, &data, NULL, NULL, &my_error)) + { + if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + data = g_strdup (""); + } + else + { + g_propagate_error (error, g_steal_pointer (&my_error)); + return NULL; + } + } + + lines = g_strsplit (data, "\n", 0); + + subpaths = g_ptr_array_new (); + for (i = 0; lines[i] != NULL; i++) + { + lines[i] = g_strstrip (lines[i]); + if (lines[i][0] == '/') + g_ptr_array_add (subpaths, g_strdup (lines[i])); + } + + g_ptr_array_add (subpaths, NULL); + return (char **) g_ptr_array_free (subpaths, FALSE); +} + +static GVariant * +flatpak_create_deploy_data_from_old (FlatpakDir *self, + GFile *deploy_dir, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) deploy_base = NULL; + g_autofree char *old_origin = NULL; + g_autofree char *commit = NULL; + g_auto(GStrv) old_subpaths = NULL; + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) origin = NULL; + guint64 installed_size; + + deploy_base = g_file_get_parent (deploy_dir); + commit = g_file_get_basename (deploy_dir); + + origin = g_file_get_child (deploy_base, "origin"); + if (!g_file_load_contents (origin, cancellable, &old_origin, NULL, NULL, error)) + return NULL; + + old_subpaths = get_old_subpaths (deploy_base, cancellable, error); + if (old_subpaths == NULL) + return NULL; + + /* For backwards compat we return a 0 installed size, its to slow to regenerate */ + installed_size = 0; + + return flatpak_dir_new_deploy_data (old_origin, commit, old_subpaths, + installed_size, NULL); +} + +GVariant * +flatpak_dir_get_deploy_data (FlatpakDir *self, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) deploy_dir = NULL; + g_autoptr(GFile) data_file = NULL; + g_autoptr(GError) my_error = NULL; + char *data = NULL; + gsize data_size; + g_autofree char *active = NULL; + + deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable); + if (deploy_dir == NULL) + { + flatpak_fail (error, "%s is not installed", ref); + return NULL; + } + + data_file = g_file_get_child (deploy_dir, "deploy"); + if (!g_file_load_contents (data_file, cancellable, &data, &data_size, NULL, &my_error)) + { + if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_propagate_error (error, g_steal_pointer (&my_error)); + return NULL; + } + + return flatpak_create_deploy_data_from_old (self, deploy_dir, + cancellable, error); + } + + return g_variant_ref_sink (g_variant_new_from_data (FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT, + data, data_size, + FALSE, g_free, data)); +} + + +char * +flatpak_dir_get_origin (FlatpakDir *self, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GVariant) deploy_data = NULL; + + deploy_data = flatpak_dir_get_deploy_data (self, ref, + cancellable, error); + if (deploy_data == NULL) + { + flatpak_fail (error, "%s is not installed", ref); + return NULL; + } + + return g_strdup (flatpak_deploy_data_get_origin (deploy_data)); +} + +char ** +flatpak_dir_get_subpaths (FlatpakDir *self, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GVariant) deploy_data = NULL; + char **subpaths; + int i; + + deploy_data = flatpak_dir_get_deploy_data (self, ref, + cancellable, error); + if (deploy_data == NULL) + { + flatpak_fail (error, "%s is not installed", ref); + return NULL; + } + + subpaths = (char **) flatpak_deploy_data_get_subpaths (deploy_data); + for (i = 0; subpaths[i] != NULL; i++) + subpaths[i] = g_strdup (subpaths[i]); + + return subpaths; +} + +gboolean +flatpak_dir_ensure_path (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + return gs_file_ensure_directory (self->basedir, TRUE, cancellable, error); +} + +gboolean +flatpak_dir_ensure_repo (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) repodir = NULL; + g_autoptr(OstreeRepo) repo = NULL; + + if (self->repo == NULL) + { + if (!flatpak_dir_ensure_path (self, cancellable, error)) + goto out; + + repodir = g_file_get_child (self->basedir, "repo"); + if (self->user) + { + repo = ostree_repo_new (repodir); + } + else + { + g_autoptr(GFile) cache_dir = NULL; + g_autofree char *cache_path = NULL; + + repo = system_ostree_repo_new (repodir); + + cache_dir = flatpak_ensure_user_cache_dir_location (error); + if (cache_dir == NULL) + goto out; + + cache_path = g_file_get_path (cache_dir); + if (!ostree_repo_set_cache_dir (repo, + AT_FDCWD, cache_path, + cancellable, error)) + goto out; + } + + if (!g_file_query_exists (repodir, cancellable)) + { + if (!ostree_repo_create (repo, + OSTREE_REPO_MODE_BARE_USER, + cancellable, error)) + { + gs_shutil_rm_rf (repodir, cancellable, NULL); + goto out; + } + + /* Create .changes file early to avoid polling non-existing file in monitor */ + flatpak_dir_mark_changed (self, NULL); + } + else + { + if (!ostree_repo_open (repo, cancellable, error)) + { + g_autofree char *repopath = NULL; + + repopath = g_file_get_path (repodir); + g_prefix_error (error, "While opening repository %s: ", repopath); + goto out; + } + } + + self->repo = g_object_ref (repo); + } + + ret = TRUE; +out: + return ret; +} + +gboolean +flatpak_dir_mark_changed (FlatpakDir *self, + GError **error) +{ + g_autoptr(GFile) changed_file = NULL; + + changed_file = flatpak_dir_get_changed_path (self); + if (!g_file_replace_contents (changed_file, "", 0, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error)) + return FALSE; + + return TRUE; +} + +gboolean +flatpak_dir_remove_appstream (FlatpakDir *self, + const char *remote, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) appstream_dir = NULL; + g_autoptr(GFile) remote_dir = NULL; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream"); + remote_dir = g_file_get_child (appstream_dir, remote); + + if (g_file_query_exists (remote_dir, cancellable) && + !gs_shutil_rm_rf (remote_dir, cancellable, error)) + return FALSE; + + return TRUE; +} + +gboolean +flatpak_dir_remove_all_refs (FlatpakDir *self, + const char *remote, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *prefix = NULL; + + g_autoptr(GHashTable) refs = NULL; + GHashTableIter hash_iter; + gpointer key; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + prefix = g_strdup_printf ("%s:", remote); + + if (!ostree_repo_list_refs (self->repo, + NULL, + &refs, + cancellable, error)) + return FALSE; + + g_hash_table_iter_init (&hash_iter, refs); + while (g_hash_table_iter_next (&hash_iter, &key, NULL)) + { + const char *refspec = key; + + if (g_str_has_prefix (refspec, prefix) && + !flatpak_dir_remove_ref (self, remote, refspec + strlen (prefix), cancellable, error)) + return FALSE; + } + + return TRUE; +} + +gboolean +flatpak_dir_update_appstream (FlatpakDir *self, + const char *remote, + const char *arch, + gboolean *out_changed, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *branch = NULL; + g_autofree char *remote_and_branch = NULL; + g_autofree char *old_checksum = NULL; + g_autofree char *new_checksum = NULL; + + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) appstream_dir = NULL; + g_autoptr(GFile) remote_dir = NULL; + g_autoptr(GFile) arch_dir = NULL; + g_autoptr(GFile) checkout_dir = NULL; + g_autoptr(GFile) old_checkout_dir = NULL; + g_autoptr(GFileInfo) file_info = NULL; + g_autofree char *arch_path = NULL; + g_autofree char *tmpname = NULL; + g_autoptr(GFile) active_tmp_link = NULL; + g_autoptr(GFile) active_link = NULL; + g_autoptr(GFile) timestamp_file = NULL; + g_autoptr(GError) tmp_error = NULL; + gboolean checkout_exists; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + if (arch == NULL) + arch = flatpak_get_arch (); + + branch = g_strdup_printf ("appstream/%s", arch); + remote_and_branch = g_strdup_printf ("%s:%s", remote, branch); + + if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &old_checksum, error)) + return FALSE; + + if (!flatpak_dir_pull (self, remote, branch, NULL, NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress, + cancellable, error)) + return FALSE; + + if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &new_checksum, error)) + return FALSE; + + if (new_checksum == NULL) + { + g_warning ("No appstream branch in remote %s\n", remote); + return TRUE; + } + + appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream"); + remote_dir = g_file_get_child (appstream_dir, remote); + arch_dir = g_file_get_child (remote_dir, arch); + checkout_dir = g_file_get_child (arch_dir, new_checksum); + timestamp_file = g_file_get_child (arch_dir, ".timestamp"); + + arch_path = g_file_get_path (arch_dir); + if (g_mkdir_with_parents (arch_path, 0755) != 0) + { + glnx_set_error_from_errno (error); + return FALSE; + } + + checkout_exists = g_file_query_exists (checkout_dir, NULL); + + if (old_checksum != NULL && new_checksum != NULL && + strcmp (old_checksum, new_checksum) == 0 && + checkout_exists) + { + if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error)) + return FALSE; + + if (out_changed) + *out_changed = FALSE; + return TRUE; /* No changes, don't checkout */ + } + + if (!ostree_repo_read_commit (self->repo, new_checksum, &root, NULL, cancellable, error)) + return FALSE; + + file_info = g_file_query_info (root, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (file_info == NULL) + return FALSE; + + if (!ostree_repo_checkout_tree (self->repo, + OSTREE_REPO_CHECKOUT_MODE_USER, + OSTREE_REPO_CHECKOUT_OVERWRITE_NONE, + checkout_dir, + OSTREE_REPO_FILE (root), file_info, + cancellable, error)) + return FALSE; + + tmpname = gs_fileutil_gen_tmp_name (".active-", NULL); + active_tmp_link = g_file_get_child (arch_dir, tmpname); + active_link = g_file_get_child (arch_dir, "active"); + + if (!g_file_make_symbolic_link (active_tmp_link, new_checksum, cancellable, error)) + return FALSE; + + if (!gs_file_rename (active_tmp_link, + active_link, + cancellable, error)) + return FALSE; + + if (old_checksum != NULL && + g_strcmp0 (old_checksum, new_checksum) != 0) + { + old_checkout_dir = g_file_get_child (arch_dir, old_checksum); + if (!gs_shutil_rm_rf (old_checkout_dir, cancellable, &tmp_error)) + g_warning ("Unable to remove old appstream checkout: %s\n", tmp_error->message); + } + + if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error)) + return FALSE; + + /* If we added a new checkout, touch the toplevel dir to tell people that they need + to re-scan */ + if (!checkout_exists) + { + g_autofree char *appstream_dir_path = g_file_get_path (appstream_dir); + utime (appstream_dir_path, NULL); + } + + if (out_changed) + *out_changed = TRUE; + return TRUE; +} + +/* This is a copy of ostree_repo_pull_one_dir that always disables + static deltas if subdir is used */ +static gboolean +repo_pull_one_dir (OstreeRepo *self, + const char *remote_name, + const char *dir_to_pull, + char **refs_to_fetch, + OstreeRepoPullFlags flags, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + + if (dir_to_pull) + { + g_variant_builder_add (&builder, "{s@v}", "subdir", + g_variant_new_variant (g_variant_new_string (dir_to_pull))); + g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas", + g_variant_new_variant (g_variant_new_boolean (TRUE))); + } + + g_variant_builder_add (&builder, "{s@v}", "flags", + g_variant_new_variant (g_variant_new_int32 (flags))); + if (refs_to_fetch) + g_variant_builder_add (&builder, "{s@v}", "refs", + g_variant_new_variant (g_variant_new_strv ((const char * const *) refs_to_fetch, -1))); + + return ostree_repo_pull_with_options (self, remote_name, g_variant_builder_end (&builder), + progress, cancellable, error); +} + + +gboolean +flatpak_dir_pull (FlatpakDir *self, + const char *repository, + const char *ref, + char **subpaths, + OstreeRepo *repo, + OstreeRepoPullFlags flags, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GSConsole *console = NULL; + + g_autoptr(OstreeAsyncProgress) console_progress = NULL; + const char *refs[2]; + g_autofree char *url = NULL; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + goto out; + + if (!ostree_repo_remote_get_url (self->repo, + repository, + &url, + error)) + goto out; + + if (*url == 0) + return TRUE; /* Empty url, silently disables updates */ + + if (repo == NULL) + repo = self->repo; + + if (progress == NULL) + { + console = gs_console_get (); + if (console) + { + gs_console_begin_status_line (console, "", NULL, NULL); + console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); + progress = console_progress; + } + } + + + refs[0] = ref; + refs[1] = NULL; + + if (subpaths == NULL || subpaths[0] == NULL) + { + if (!ostree_repo_pull (repo, repository, + (char **) refs, flags, + progress, + cancellable, error)) + { + g_prefix_error (error, "While pulling %s from remote %s: ", ref, repository); + goto out; + } + } + else + { + int i; + + if (!repo_pull_one_dir (repo, repository, + "/metadata", + (char **) refs, flags, + progress, + cancellable, error)) + { + g_prefix_error (error, "While pulling %s from remote %s, metadata: ", + ref, repository); + goto out; + } + + for (i = 0; subpaths[i] != NULL; i++) + { + g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL); + if (!repo_pull_one_dir (repo, repository, + subpath, + (char **) refs, flags, + progress, + cancellable, error)) + { + g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ", + ref, repository, subpaths[i]); + goto out; + } + } + } + + ret = TRUE; + +out: + if (console) + { + ostree_async_progress_finish (progress); + gs_console_end_status_line (console, NULL, NULL); + } + + return ret; +} + +static gboolean +repo_pull_one_untrusted (OstreeRepo *self, + const char *remote_name, + const char *url, + const char *dir_to_pull, + const char *ref, + const char *checksum, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_UNTRUSTED; + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + const char *refs[2] = { NULL, NULL }; + const char *commits[2] = { NULL, NULL }; + + refs[0] = ref; + commits[0] = checksum; + + g_variant_builder_add (&builder, "{s@v}", "flags", + g_variant_new_variant (g_variant_new_int32 (flags))); + g_variant_builder_add (&builder, "{s@v}", "refs", + g_variant_new_variant (g_variant_new_strv ((const char * const *) refs, -1))); + g_variant_builder_add (&builder, "{s@v}", "override-commit-ids", + g_variant_new_variant (g_variant_new_strv ((const char * const *) commits, -1))); + g_variant_builder_add (&builder, "{s@v}", "override-remote-name", + g_variant_new_variant (g_variant_new_string (remote_name))); + g_variant_builder_add (&builder, "{s@v}", "gpg-verify", + g_variant_new_variant (g_variant_new_boolean (TRUE))); + g_variant_builder_add (&builder, "{s@v}", "gpg-verify-summary", + g_variant_new_variant (g_variant_new_boolean (TRUE))); + + if (dir_to_pull) + { + g_variant_builder_add (&builder, "{s@v}", "subdir", + g_variant_new_variant (g_variant_new_string (dir_to_pull))); + g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas", + g_variant_new_variant (g_variant_new_boolean (TRUE))); + } + + return ostree_repo_pull_with_options (self, url, g_variant_builder_end (&builder), + progress, cancellable, error); +} + +gboolean +flatpak_dir_pull_untrusted_local (FlatpakDir *self, + const char *src_path, + const char *remote_name, + const char *ref, + char **subpaths, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GSConsole *console = NULL; + + g_autoptr(OstreeAsyncProgress) console_progress = NULL; + g_autoptr(GFile) path_file = g_file_new_for_path (src_path); + g_autoptr(GFile) summary_file = g_file_get_child (path_file, "summary"); + g_autoptr(GFile) summary_sig_file = g_file_get_child (path_file, "summary.sig"); + g_autofree char *url = g_file_get_uri (path_file); + g_autofree char *checksum = NULL; + gboolean gpg_verify_summary; + gboolean gpg_verify; + char *summary_data = NULL; + char *summary_sig_data = NULL; + gsize summary_data_size, summary_sig_data_size; + g_autoptr(GBytes) summary_bytes = NULL; + g_autoptr(GBytes) summary_sig_bytes = NULL; + g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL; + g_autoptr(GVariant) summary = NULL; + g_autoptr(GVariant) old_commit = NULL; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, remote_name, + &gpg_verify_summary, error)) + return FALSE; + + if (!ostree_repo_remote_get_gpg_verify (self->repo, remote_name, + &gpg_verify, error)) + return FALSE; + + if (!gpg_verify_summary || !gpg_verify) + return flatpak_fail (error, "Can't pull from untrusted non-gpg verified remote"); + + /* We verify the summary manually before anything else to make sure + we've got something right before looking too hard at the repo and + so we can check for a downgrade before pulling and updating the + ref */ + + if (!g_file_load_contents (summary_sig_file, cancellable, + &summary_sig_data, &summary_sig_data_size, NULL, NULL)) + return flatpak_fail (error, "GPG verification enabled, but no summary signatures found"); + + summary_sig_bytes = g_bytes_new_take (summary_sig_data, summary_sig_data_size); + + if (!g_file_load_contents (summary_file, cancellable, + &summary_data, &summary_data_size, NULL, NULL)) + return flatpak_fail (error, "No summary found"); + summary_bytes = g_bytes_new_take (summary_data, summary_data_size); + + gpg_result = ostree_repo_verify_summary (self->repo, + remote_name, + summary_bytes, + summary_sig_bytes, + cancellable, error); + if (gpg_result == NULL) + return FALSE; + + if (ostree_gpg_verify_result_count_valid (gpg_result) == 0) + return flatpak_fail (error, "GPG signatures found, but none are in trusted keyring"); + + summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE)); + if (!flatpak_summary_lookup_ref (summary, + ref, + &checksum)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Can't find %sin remote %s", ref, remote_name); + return FALSE; + } + + (void) ostree_repo_load_commit (self->repo, checksum, &old_commit, NULL, NULL); + + if (old_commit) + { + g_autoptr(OstreeRepo) src_repo = ostree_repo_new (path_file); + g_autoptr(GVariant) new_commit = NULL; + guint64 old_timestamp; + guint64 new_timestamp; + + if (!ostree_repo_open (src_repo, cancellable, error)) + return FALSE; + + if (!ostree_repo_load_commit (src_repo, checksum, &new_commit, NULL, error)) + return FALSE; + + old_timestamp = ostree_commit_get_timestamp (old_commit); + new_timestamp = ostree_commit_get_timestamp (new_commit); + + if (new_timestamp < old_timestamp) + return flatpak_fail (error, "Not allowed to downgrade %s", ref); + } + + + if (progress == NULL) + { + console = gs_console_get (); + if (console) + { + gs_console_begin_status_line (console, "", NULL, NULL); + console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); + progress = console_progress; + } + } + + if (subpaths == NULL || subpaths[0] == NULL) + { + if (!repo_pull_one_untrusted (self->repo, remote_name, url, + NULL, ref, checksum, progress, + cancellable, error)) + { + g_prefix_error (error, "While pulling %s from remote %s: ", ref, remote_name); + goto out; + } + } + else + { + int i; + + if (!repo_pull_one_untrusted (self->repo, remote_name, url, + "/metadata", ref, checksum, progress, + cancellable, error)) + { + g_prefix_error (error, "While pulling %s from remote %s, metadata: ", + ref, remote_name); + goto out; + } + + for (i = 0; subpaths[i] != NULL; i++) + { + g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL); + if (!repo_pull_one_untrusted (self->repo, remote_name, url, + subpath, ref, checksum, progress, + cancellable, error)) + { + g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ", + ref, remote_name, subpaths[i]); + goto out; + } + } + } + + ret = TRUE; + +out: + if (console) + { + ostree_async_progress_finish (progress); + gs_console_end_status_line (console, NULL, NULL); + } + + return ret; +} + + +char * +flatpak_dir_current_ref (FlatpakDir *self, + const char *name, + GCancellable *cancellable) +{ + g_autoptr(GFile) base = NULL; + g_autoptr(GFile) dir = NULL; + g_autoptr(GFile) current_link = NULL; + g_autoptr(GFileInfo) file_info = NULL; + + base = g_file_get_child (flatpak_dir_get_path (self), "app"); + dir = g_file_get_child (base, name); + + current_link = g_file_get_child (dir, "current"); + + file_info = g_file_query_info (current_link, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, NULL); + if (file_info == NULL) + return NULL; + + return g_strconcat ("app/", name, "/", g_file_info_get_symlink_target (file_info), NULL); +} + +gboolean +flatpak_dir_drop_current_ref (FlatpakDir *self, + const char *name, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) base = NULL; + g_autoptr(GFile) dir = NULL; + g_autoptr(GFile) current_link = NULL; + + base = g_file_get_child (flatpak_dir_get_path (self), "app"); + dir = g_file_get_child (base, name); + + current_link = g_file_get_child (dir, "current"); + + return g_file_delete (current_link, cancellable, error); +} + +gboolean +flatpak_dir_make_current_ref (FlatpakDir *self, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) base = NULL; + g_autoptr(GFile) dir = NULL; + g_autoptr(GFile) current_link = NULL; + g_auto(GStrv) ref_parts = NULL; + g_autofree char *rest = NULL; + gboolean ret = FALSE; + + ref_parts = g_strsplit (ref, "/", -1); + + g_assert (g_strv_length (ref_parts) == 4); + g_assert (strcmp (ref_parts[0], "app") == 0); + + base = g_file_get_child (flatpak_dir_get_path (self), ref_parts[0]); + dir = g_file_get_child (base, ref_parts[1]); + + current_link = g_file_get_child (dir, "current"); + + g_file_delete (current_link, cancellable, NULL); + + if (*ref_parts[3] != 0) + { + rest = g_strdup_printf ("%s/%s", ref_parts[2], ref_parts[3]); + if (!g_file_make_symbolic_link (current_link, rest, cancellable, error)) + goto out; + } + + ret = TRUE; + +out: + return ret; +} + +gboolean +flatpak_dir_list_refs_for_name (FlatpakDir *self, + const char *kind, + const char *name, + char ***refs_out, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) base = NULL; + g_autoptr(GFile) dir = NULL; + g_autoptr(GFileEnumerator) dir_enum = NULL; + g_autoptr(GFileInfo) child_info = NULL; + GError *temp_error = NULL; + g_autoptr(GPtrArray) refs = NULL; + + base = g_file_get_child (flatpak_dir_get_path (self), kind); + dir = g_file_get_child (base, name); + + refs = g_ptr_array_new (); + + if (!g_file_query_exists (dir, cancellable)) + { + ret = TRUE; + goto out; + } + + dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!dir_enum) + goto out; + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error))) + { + g_autoptr(GFile) child = NULL; + g_autoptr(GFileEnumerator) dir_enum2 = NULL; + g_autoptr(GFileInfo) child_info2 = NULL; + const char *arch; + + arch = g_file_info_get_name (child_info); + + if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY || + strcmp (arch, "data") == 0 /* There used to be a data dir here, lets ignore it */) + { + g_clear_object (&child_info); + continue; + } + + child = g_file_get_child (dir, arch); + g_clear_object (&dir_enum2); + dir_enum2 = g_file_enumerate_children (child, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!dir_enum2) + goto out; + + while ((child_info2 = g_file_enumerator_next_file (dir_enum2, cancellable, &temp_error))) + { + const char *branch; + + if (g_file_info_get_file_type (child_info2) == G_FILE_TYPE_DIRECTORY) + { + branch = g_file_info_get_name (child_info2); + g_ptr_array_add (refs, + g_strdup_printf ("%s/%s/%s/%s", kind, name, arch, branch)); + } + + g_clear_object (&child_info2); + } + + + if (temp_error != NULL) + goto out; + + g_clear_object (&child_info); + } + + if (temp_error != NULL) + goto out; + + g_ptr_array_sort (refs, flatpak_strcmp0_ptr); + + ret = TRUE; + +out: + if (ret) + { + g_ptr_array_add (refs, NULL); + *refs_out = (char **) g_ptr_array_free (refs, FALSE); + refs = NULL; + } + + if (temp_error != NULL) + g_propagate_error (error, temp_error); + + return ret; +} + +gboolean +flatpak_dir_list_refs (FlatpakDir *self, + const char *kind, + char ***refs_out, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) base; + g_autoptr(GFileEnumerator) dir_enum = NULL; + g_autoptr(GFileInfo) child_info = NULL; + GError *temp_error = NULL; + g_autoptr(GPtrArray) refs = NULL; + + refs = g_ptr_array_new (); + + base = g_file_get_child (flatpak_dir_get_path (self), kind); + + if (!g_file_query_exists (base, cancellable)) + { + ret = TRUE; + goto out; + } + + dir_enum = g_file_enumerate_children (base, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!dir_enum) + goto out; + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error))) + { + gchar **sub_refs = NULL; + const char *name; + int i; + + if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY) + { + g_clear_object (&child_info); + continue; + } + + name = g_file_info_get_name (child_info); + + if (!flatpak_dir_list_refs_for_name (self, kind, name, &sub_refs, cancellable, error)) + goto out; + + for (i = 0; sub_refs[i] != NULL; i++) + g_ptr_array_add (refs, sub_refs[i]); + g_free (sub_refs); + + g_clear_object (&child_info); + } + + if (temp_error != NULL) + goto out; + + ret = TRUE; + + g_ptr_array_sort (refs, flatpak_strcmp0_ptr); + +out: + if (ret) + { + g_ptr_array_add (refs, NULL); + *refs_out = (char **) g_ptr_array_free (refs, FALSE); + refs = NULL; + } + + if (temp_error != NULL) + g_propagate_error (error, temp_error); + + return ret; +} + +char * +flatpak_dir_read_latest (FlatpakDir *self, + const char *remote, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *remote_and_ref = NULL; + char *res = NULL; + + /* There may be several remotes with the same branch (if we for + * instance changed the origin, so prepend the current origin to + * make sure we get the right one */ + + if (remote) + remote_and_ref = g_strdup_printf ("%s:%s", remote, ref); + else + remote_and_ref = g_strdup (ref); + + if (!ostree_repo_resolve_rev (self->repo, remote_and_ref, FALSE, &res, error)) + return NULL; + + return res; +} + + +char * +flatpak_dir_read_active (FlatpakDir *self, + const char *ref, + GCancellable *cancellable) +{ + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(GFile) active_link = NULL; + g_autoptr(GFileInfo) file_info = NULL; + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + active_link = g_file_get_child (deploy_base, "active"); + + file_info = g_file_query_info (active_link, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, NULL); + if (file_info == NULL) + return NULL; + + return g_strdup (g_file_info_get_symlink_target (file_info)); +} + +gboolean +flatpak_dir_set_active (FlatpakDir *self, + const char *ref, + const char *checksum, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) deploy_base = NULL; + g_autofree char *tmpname = NULL; + g_autoptr(GFile) active_tmp_link = NULL; + g_autoptr(GFile) active_link = NULL; + g_autoptr(GError) my_error = NULL; + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + active_link = g_file_get_child (deploy_base, "active"); + + if (checksum != NULL) + { + tmpname = gs_fileutil_gen_tmp_name (".active-", NULL); + active_tmp_link = g_file_get_child (deploy_base, tmpname); + if (!g_file_make_symbolic_link (active_tmp_link, checksum, cancellable, error)) + goto out; + + if (!gs_file_rename (active_tmp_link, + active_link, + cancellable, error)) + goto out; + } + else + { + if (!g_file_delete (active_link, cancellable, &my_error) && + !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_propagate_error (error, my_error); + my_error = NULL; + goto out; + } + } + + ret = TRUE; +out: + return ret; +} + + +gboolean +flatpak_dir_run_triggers (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFileEnumerator) dir_enum = NULL; + g_autoptr(GFileInfo) child_info = NULL; + g_autoptr(GFile) triggersdir = NULL; + GError *temp_error = NULL; + const char *triggerspath; + + triggerspath = g_getenv ("FLATPAK_TRIGGERSDIR"); + if (triggerspath == NULL) + triggerspath = FLATPAK_TRIGGERDIR; + + g_debug ("running triggers from %s", triggerspath); + + triggersdir = g_file_new_for_path (triggerspath); + + dir_enum = g_file_enumerate_children (triggersdir, "standard::type,standard::name", + 0, cancellable, error); + if (!dir_enum) + goto out; + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) + { + g_autoptr(GFile) child = NULL; + const char *name; + GError *trigger_error = NULL; + + name = g_file_info_get_name (child_info); + + child = g_file_get_child (triggersdir, name); + + if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_REGULAR && + g_str_has_suffix (name, ".trigger")) + { + g_autoptr(GPtrArray) argv_array = NULL; + + g_debug ("running trigger %s", name); + + argv_array = g_ptr_array_new_with_free_func (g_free); +#ifdef DISABLE_SANDBOXED_TRIGGERS + g_ptr_array_add (argv_array, g_file_get_path (child)); + g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); +#else + g_ptr_array_add (argv_array, g_strdup (flatpak_get_bwrap ())); + g_ptr_array_add (argv_array, g_strdup ("--unshare-ipc")); + g_ptr_array_add (argv_array, g_strdup ("--unshare-net")); + g_ptr_array_add (argv_array, g_strdup ("--unshare-pid")); + g_ptr_array_add (argv_array, g_strdup ("--ro-bind")); + g_ptr_array_add (argv_array, g_strdup ("/")); + g_ptr_array_add (argv_array, g_strdup ("/")); + g_ptr_array_add (argv_array, g_strdup ("--proc")); + g_ptr_array_add (argv_array, g_strdup ("/proc")); + g_ptr_array_add (argv_array, g_strdup ("--dev")); + g_ptr_array_add (argv_array, g_strdup ("/dev")); + g_ptr_array_add (argv_array, g_strdup ("--bind")); + g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); + g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); +#endif + g_ptr_array_add (argv_array, g_file_get_path (child)); + g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); + g_ptr_array_add (argv_array, NULL); + + if (!g_spawn_sync ("/", + (char **) argv_array->pdata, + NULL, + G_SPAWN_DEFAULT, + NULL, NULL, + NULL, NULL, + NULL, &trigger_error)) + { + g_warning ("Error running trigger %s: %s", name, trigger_error->message); + g_clear_error (&trigger_error); + } + } + + g_clear_object (&child_info); + } + + if (temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + + ret = TRUE; +out: + return ret; +} + +static gboolean +read_fd (int fd, + struct stat *stat_buf, + gchar **contents, + gsize *length, + GError **error) +{ + gchar *buf; + gsize bytes_read; + gsize size; + gsize alloc_size; + + size = stat_buf->st_size; + + alloc_size = size + 1; + buf = g_try_malloc (alloc_size); + + if (buf == NULL) + { + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_NOMEM, + "not enough memory"); + return FALSE; + } + + bytes_read = 0; + while (bytes_read < size) + { + gssize rc; + + rc = read (fd, buf + bytes_read, size - bytes_read); + + if (rc < 0) + { + if (errno != EINTR) + { + int save_errno = errno; + + g_free (buf); + g_set_error (error, + G_FILE_ERROR, + g_file_error_from_errno (save_errno), + "Failed to read from exported file"); + return FALSE; + } + } + else if (rc == 0) + { + break; + } + else + { + bytes_read += rc; + } + } + + buf[bytes_read] = '\0'; + + if (length) + *length = bytes_read; + + *contents = buf; + + return TRUE; +} + +/* This is conservative, but lets us avoid escaping most + regular Exec= lines, which is nice as that can sometimes + cause problems for apps launching desktop files. */ +static gboolean +need_quotes (const char *str) +{ + const char *p; + + for (p = str; *p; p++) + { + if (!g_ascii_isalnum (*p) && + strchr ("-_%.=:/@", *p) == NULL) + return TRUE; + } + + return FALSE; +} + +static char * +maybe_quote (const char *str) +{ + if (need_quotes (str)) + return g_shell_quote (str); + return g_strdup (str); +} + +static gboolean +export_desktop_file (const char *app, + const char *branch, + const char *arch, + GKeyFile *metadata, + int parent_fd, + const char *name, + struct stat *stat_buf, + char **target, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + glnx_fd_close int desktop_fd = -1; + g_autofree char *tmpfile_name = NULL; + + g_autoptr(GOutputStream) out_stream = NULL; + g_autofree gchar *data = NULL; + gsize data_len; + g_autofree gchar *new_data = NULL; + gsize new_data_len; + g_autoptr(GKeyFile) keyfile = NULL; + g_autofree gchar *old_exec = NULL; + gint old_argc; + g_auto(GStrv) old_argv = NULL; + g_auto(GStrv) groups = NULL; + GString *new_exec = NULL; + g_autofree char *escaped_app = maybe_quote (app); + g_autofree char *escaped_branch = maybe_quote (branch); + g_autofree char *escaped_arch = maybe_quote (arch); + int i; + + if (!gs_file_openat_noatime (parent_fd, name, &desktop_fd, cancellable, error)) + goto out; + + if (!read_fd (desktop_fd, stat_buf, &data, &data_len, error)) + goto out; + + keyfile = g_key_file_new (); + if (!g_key_file_load_from_data (keyfile, data, data_len, G_KEY_FILE_KEEP_TRANSLATIONS, error)) + goto out; + + if (g_str_has_suffix (name, ".service")) + { + g_autofree gchar *dbus_name = NULL; + g_autofree gchar *expected_dbus_name = g_strndup (name, strlen (name) - strlen (".service")); + + dbus_name = g_key_file_get_string (keyfile, "D-BUS Service", "Name", NULL); + + if (dbus_name == NULL || strcmp (dbus_name, expected_dbus_name) != 0) + { + flatpak_fail (error, "dbus service file %s has wrong name", name); + return FALSE; + } + } + + if (g_str_has_suffix (name, ".desktop")) + { + gsize length; + g_auto(GStrv) tags = g_key_file_get_string_list (metadata, + "Application", + "tags", &length, + NULL); + + if (tags != NULL) + { + g_key_file_set_string_list (keyfile, + "Desktop Entry", + "X-Flatpak-Tags", + (const char * const *) tags, length); + } + } + + groups = g_key_file_get_groups (keyfile, NULL); + + for (i = 0; groups[i] != NULL; i++) + { + g_key_file_remove_key (keyfile, groups[i], "TryExec", NULL); + + /* Remove this to make sure nothing tries to execute it outside the sandbox*/ + g_key_file_remove_key (keyfile, groups[i], "X-GNOME-Bugzilla-ExtraInfoScript", NULL); + + new_exec = g_string_new (""); + g_string_append_printf (new_exec, FLATPAK_BINDIR "/xdg-app run --branch=%s --arch=%s", escaped_branch, escaped_arch); + + old_exec = g_key_file_get_string (keyfile, groups[i], "Exec", NULL); + if (old_exec && g_shell_parse_argv (old_exec, &old_argc, &old_argv, NULL) && old_argc >= 1) + { + int i; + g_autofree char *command = maybe_quote (old_argv[0]); + + g_string_append_printf (new_exec, " --command=%s", command); + + g_string_append (new_exec, " "); + g_string_append (new_exec, escaped_app); + + for (i = 1; i < old_argc; i++) + { + g_autofree char *arg = maybe_quote (old_argv[i]); + g_string_append (new_exec, " "); + g_string_append (new_exec, arg); + } + } + else + { + g_string_append (new_exec, " "); + g_string_append (new_exec, escaped_app); + } + + g_key_file_set_string (keyfile, groups[i], G_KEY_FILE_DESKTOP_KEY_EXEC, new_exec->str); + } + + new_data = g_key_file_to_data (keyfile, &new_data_len, error); + if (new_data == NULL) + goto out; + + if (!gs_file_open_in_tmpdir_at (parent_fd, 0755, &tmpfile_name, &out_stream, cancellable, error)) + goto out; + + if (!g_output_stream_write_all (out_stream, new_data, new_data_len, NULL, cancellable, error)) + goto out; + + if (!g_output_stream_close (out_stream, cancellable, error)) + goto out; + + if (target) + *target = g_steal_pointer (&tmpfile_name); + + ret = TRUE; +out: + + if (new_exec != NULL) + g_string_free (new_exec, TRUE); + + return ret; +} + +static gboolean +rewrite_export_dir (const char *app, + const char *branch, + const char *arch, + GKeyFile *metadata, + int source_parent_fd, + const char *source_name, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_auto(GLnxDirFdIterator) source_iter = {0}; + g_autoptr(GHashTable) visited_children = NULL; + struct dirent *dent; + + if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) + goto out; + + visited_children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + while (TRUE) + { + struct stat stbuf; + + if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error)) + goto out; + + if (dent == NULL) + break; + + if (g_hash_table_contains (visited_children, dent->d_name)) + continue; + + /* Avoid processing the same file again if it was re-created during an export */ + g_hash_table_insert (visited_children, g_strdup (dent->d_name), GINT_TO_POINTER (1)); + + if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) + { + if (errno == ENOENT) + { + continue; + } + else + { + glnx_set_error_from_errno (error); + goto out; + } + } + + if (S_ISDIR (stbuf.st_mode)) + { + if (!rewrite_export_dir (app, branch, arch, metadata, + source_iter.fd, dent->d_name, + cancellable, error)) + goto out; + } + else if (S_ISREG (stbuf.st_mode)) + { + if (!flatpak_has_name_prefix (dent->d_name, app)) + { + g_warning ("Non-prefixed filename %s in app %s, removing.\n", dent->d_name, app); + if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT) + { + glnx_set_error_from_errno (error); + goto out; + } + } + + if (g_str_has_suffix (dent->d_name, ".desktop") || + g_str_has_suffix (dent->d_name, ".service")) + { + g_autofree gchar *new_name = NULL; + + if (!export_desktop_file (app, branch, arch, metadata, + source_iter.fd, dent->d_name, &stbuf, &new_name, cancellable, error)) + goto out; + + g_hash_table_insert (visited_children, g_strdup (new_name), GINT_TO_POINTER (1)); + + if (renameat (source_iter.fd, new_name, source_iter.fd, dent->d_name) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + } + else + { + g_warning ("Not exporting file %s of unsupported type\n", dent->d_name); + if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT) + { + glnx_set_error_from_errno (error); + goto out; + } + } + } + + ret = TRUE; +out: + + return ret; +} + +gboolean +flatpak_rewrite_export_dir (const char *app, + const char *branch, + const char *arch, + GKeyFile *metadata, + GFile *source, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + /* The fds are closed by this call */ + if (!rewrite_export_dir (app, branch, arch, metadata, + AT_FDCWD, gs_file_get_path_cached (source), + cancellable, error)) + goto out; + + ret = TRUE; + +out: + return ret; +} + + +static gboolean +export_dir (int source_parent_fd, + const char *source_name, + const char *source_symlink_prefix, + const char *source_relpath, + int destination_parent_fd, + const char *destination_name, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + int res; + + g_auto(GLnxDirFdIterator) source_iter = {0}; + glnx_fd_close int destination_dfd = -1; + struct dirent *dent; + + if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) + goto out; + + do + res = mkdirat (destination_parent_fd, destination_name, 0755); + while (G_UNLIKELY (res == -1 && errno == EINTR)); + if (res == -1) + { + if (errno != EEXIST) + { + glnx_set_error_from_errno (error); + goto out; + } + } + + if (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name, + &destination_dfd, + cancellable, error)) + goto out; + + while (TRUE) + { + struct stat stbuf; + + if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error)) + goto out; + + if (dent == NULL) + break; + + if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) + { + if (errno == ENOENT) + { + continue; + } + else + { + glnx_set_error_from_errno (error); + goto out; + } + } + + if (S_ISDIR (stbuf.st_mode)) + { + g_autofree gchar *child_symlink_prefix = g_build_filename ("..", source_symlink_prefix, dent->d_name, NULL); + g_autofree gchar *child_relpath = g_strconcat (source_relpath, dent->d_name, "/", NULL); + + if (!export_dir (source_iter.fd, dent->d_name, child_symlink_prefix, child_relpath, destination_dfd, dent->d_name, + cancellable, error)) + goto out; + } + else if (S_ISREG (stbuf.st_mode)) + { + g_autofree gchar *target = NULL; + + target = g_build_filename (source_symlink_prefix, dent->d_name, NULL); + + if (unlinkat (destination_dfd, dent->d_name, 0) != 0 && errno != ENOENT) + { + glnx_set_error_from_errno (error); + goto out; + } + + if (symlinkat (target, destination_dfd, dent->d_name) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + } + + ret = TRUE; +out: + + return ret; +} + +gboolean +flatpak_export_dir (GFile *source, + GFile *destination, + const char *symlink_prefix, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + if (!gs_file_ensure_directory (destination, TRUE, cancellable, error)) + goto out; + + /* The fds are closed by this call */ + if (!export_dir (AT_FDCWD, gs_file_get_path_cached (source), symlink_prefix, "", + AT_FDCWD, gs_file_get_path_cached (destination), + cancellable, error)) + goto out; + + ret = TRUE; + +out: + return ret; +} + +gboolean +flatpak_dir_update_exports (FlatpakDir *self, + const char *changed_app, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) exports = NULL; + g_autofree char *current_ref = NULL; + g_autofree char *active_id = NULL; + g_autofree char *symlink_prefix = NULL; + + exports = flatpak_dir_get_exports_dir (self); + + if (!gs_file_ensure_directory (exports, TRUE, cancellable, error)) + goto out; + + if (changed_app && + (current_ref = flatpak_dir_current_ref (self, changed_app, cancellable)) && + (active_id = flatpak_dir_read_active (self, current_ref, cancellable))) + { + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(GFile) active = NULL; + g_autoptr(GFile) export = NULL; + + deploy_base = flatpak_dir_get_deploy_dir (self, current_ref); + active = g_file_get_child (deploy_base, active_id); + export = g_file_get_child (active, "export"); + + if (g_file_query_exists (export, cancellable)) + { + symlink_prefix = g_build_filename ("..", "app", changed_app, "current", "active", "export", NULL); + if (!flatpak_export_dir (export, exports, + symlink_prefix, + cancellable, + error)) + goto out; + } + } + + if (!flatpak_remove_dangling_symlinks (exports, cancellable, error)) + goto out; + + if (!flatpak_dir_run_triggers (self, cancellable, error)) + goto out; + + ret = TRUE; + +out: + return ret; +} + +gboolean +flatpak_dir_deploy (FlatpakDir *self, + const char *origin, + const char *ref, + const char *checksum_or_latest, + const char * const * subpaths, + GVariant *old_deploy_data, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *resolved_ref = NULL; + + g_autoptr(GFile) root = NULL; + g_autoptr(GFileInfo) file_info = NULL; + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(GFile) checkoutdir = NULL; + g_autoptr(GFile) real_checkoutdir = NULL; + g_autoptr(GFile) dotref = NULL; + g_autoptr(GFile) files_etc = NULL; + g_autoptr(GFile) metadata = NULL; + g_autoptr(GFile) deploy_data_file = NULL; + g_autoptr(GVariant) deploy_data = NULL; + g_autoptr(GFile) export = NULL; + g_autoptr(GKeyFile) keyfile = NULL; + guint64 installed_size = 0; + const char *checksum; + g_autoptr(GFile) tmp_dir_template = NULL; + g_autofree char *tmp_dir_path = NULL; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + + if (checksum_or_latest == NULL) + { + g_debug ("No checksum specified, getting tip of %s", ref); + + resolved_ref = flatpak_dir_read_latest (self, origin, ref, cancellable, error); + if (resolved_ref == NULL) + { + g_prefix_error (error, "While trying to resolve ref %s: ", ref); + return FALSE; + } + + checksum = resolved_ref; + g_debug ("tip resolved to: %s", checksum); + } + else + { + g_autoptr(GFile) root = NULL; + g_autofree char *commit = NULL; + + checksum = checksum_or_latest; + g_debug ("Looking for checksum %s in local repo", checksum); + if (!ostree_repo_read_commit (self->repo, checksum, &root, &commit, cancellable, NULL)) + return flatpak_fail (error, "%s is not available", ref); + } + + real_checkoutdir = g_file_get_child (deploy_base, checksum); + if (g_file_query_exists (real_checkoutdir, cancellable)) + { + g_set_error (error, FLATPAK_DIR_ERROR, + FLATPAK_DIR_ERROR_ALREADY_DEPLOYED, + "%s branch %s already deployed", ref, checksum); + return FALSE; + } + + g_autofree char *template = g_strdup_printf (".%s-XXXXXX", checksum); + tmp_dir_template = g_file_get_child (deploy_base, template); + tmp_dir_path = g_file_get_path (tmp_dir_template); + + if (g_mkdtemp (tmp_dir_path) == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't create deploy directory"); + return FALSE; + } + + checkoutdir = g_file_new_for_path (tmp_dir_path); + + if (!ostree_repo_read_commit (self->repo, checksum, &root, NULL, cancellable, error)) + { + g_prefix_error (error, "Failed to read commit %s: ", checksum); + return FALSE; + } + + if (!flatpak_repo_collect_sizes (self->repo, root, &installed_size, NULL, cancellable, error)) + return FALSE; + + file_info = g_file_query_info (root, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (file_info == NULL) + return FALSE; + + if (subpaths == NULL || *subpaths == NULL) + { + if (!ostree_repo_checkout_tree (self->repo, + OSTREE_REPO_CHECKOUT_MODE_USER, + OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES, + checkoutdir, + OSTREE_REPO_FILE (root), file_info, + cancellable, error)) + { + g_autofree char *rootpath = NULL; + g_autofree char *checkoutpath = NULL; + + rootpath = g_file_get_path (root); + checkoutpath = g_file_get_path (checkoutdir); + g_prefix_error (error, "While trying to checkout %s into %s: ", rootpath, checkoutpath); + return FALSE; + } + } + else + { + OstreeRepoCheckoutOptions options = { 0, }; + g_autofree char *checkoutdirpath = g_file_get_path (checkoutdir); + g_autoptr(GFile) files = g_file_get_child (checkoutdir, "files"); + int i; + + if (!g_file_make_directory_with_parents (files, cancellable, error)) + return FALSE; + + options.mode = OSTREE_REPO_CHECKOUT_MODE_USER; + options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES; + options.subpath = "/metadata"; + + access ("checkout metadata", 0); + if (!ostree_repo_checkout_tree_at (self->repo, &options, + AT_FDCWD, checkoutdirpath, + checksum, + cancellable, error)) + { + g_prefix_error (error, "While trying to checkout metadata subpath: "); + return FALSE; + } + + for (i = 0; subpaths[i] != NULL; i++) + { + g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL); + g_autofree char *dstpath = g_build_filename (checkoutdirpath, "/files", subpaths[i], NULL); + g_autofree char *dstpath_parent = g_path_get_dirname (dstpath); + if (g_mkdir_with_parents (dstpath_parent, 0755)) + { + glnx_set_error_from_errno (error); + return FALSE; + } + + options.subpath = subpath; + if (!ostree_repo_checkout_tree_at (self->repo, &options, + AT_FDCWD, dstpath, + checksum, + cancellable, error)) + { + g_prefix_error (error, "While trying to checkout metadata subpath: "); + return FALSE; + } + } + } + + dotref = g_file_resolve_relative_path (checkoutdir, "files/.ref"); + if (!g_file_replace_contents (dotref, "", 0, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error)) + return TRUE; + + /* Ensure that various files exists as regular files in /usr/etc, as we + want to bind-mount over them */ + files_etc = g_file_resolve_relative_path (checkoutdir, "files/etc"); + if (g_file_query_exists (files_etc, cancellable)) + { + char *etcfiles[] = {"passwd", "group", "machine-id" }; + g_autoptr(GFile) etc_resolve_conf = g_file_get_child (files_etc, "resolv.conf"); + int i; + for (i = 0; i < G_N_ELEMENTS (etcfiles); i++) + { + g_autoptr(GFile) etc_file = g_file_get_child (files_etc, etcfiles[i]); + GFileType type; + + type = g_file_query_file_type (etc_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable); + if (type == G_FILE_TYPE_REGULAR) + continue; + + if (type != G_FILE_TYPE_UNKNOWN) + { + /* Already exists, but not regular, probably symlink. Remove it */ + if (!g_file_delete (etc_file, cancellable, error)) + return FALSE; + } + + if (!g_file_replace_contents (etc_file, "", 0, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + NULL, cancellable, error)) + return FALSE; + } + + if (g_file_query_exists (etc_resolve_conf, cancellable) && + !g_file_delete (etc_resolve_conf, cancellable, error)) + return TRUE; + + if (!g_file_make_symbolic_link (etc_resolve_conf, + "/run/host/monitor/resolv.conf", + cancellable, error)) + return FALSE; + } + + keyfile = g_key_file_new (); + metadata = g_file_get_child (checkoutdir, "metadata"); + if (g_file_query_exists (metadata, cancellable)) + { + g_autofree char *path = g_file_get_path (metadata); + + if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, error)) + return FALSE; + } + + export = g_file_get_child (checkoutdir, "export"); + if (g_file_query_exists (export, cancellable)) + { + g_auto(GStrv) ref_parts = NULL; + + ref_parts = g_strsplit (ref, "/", -1); + + if (!flatpak_rewrite_export_dir (ref_parts[1], ref_parts[3], ref_parts[2], + keyfile, export, + cancellable, + error)) + return FALSE; + } + + deploy_data = flatpak_dir_new_deploy_data (origin, + checksum, + (char **) subpaths, + installed_size, + NULL); + + deploy_data_file = g_file_get_child (checkoutdir, "deploy"); + if (!flatpak_variant_save (deploy_data_file, deploy_data, cancellable, error)) + return FALSE; + + if (!g_file_move (checkoutdir, real_checkoutdir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE, + cancellable, NULL, NULL, error)) + return FALSE; + + if (!flatpak_dir_set_active (self, ref, checksum, cancellable, error)) + return FALSE; + + return TRUE; +} + +gboolean +flatpak_dir_deploy_install (FlatpakDir *self, + const char *ref, + const char *origin, + char **subpaths, + GCancellable *cancellable, + GError **error) +{ + g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; + g_autoptr(GFile) deploy_base = NULL; + gboolean created_deploy_base = FALSE; + gboolean ret = FALSE; + g_autoptr(GError) local_error = NULL; + g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1); + + if (!flatpak_dir_lock (self, &lock, + cancellable, error)) + goto out; + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + if (!g_file_make_directory_with_parents (deploy_base, cancellable, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_EXISTS, + "%s branch %s already installed", + ref_parts[1], ref_parts[3]); + } + else + { + g_propagate_error (error, g_steal_pointer (&local_error)); + } + + goto out; + } + + /* After we create the deploy base we must goto out on errors */ + created_deploy_base = TRUE; + + if (!flatpak_dir_deploy (self, origin, ref, NULL, (const char * const *) subpaths, NULL, cancellable, error)) + goto out; + + if (g_str_has_prefix (ref, "app/")) + { + + if (!flatpak_dir_make_current_ref (self, ref, cancellable, error)) + goto out; + + if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error)) + goto out; + } + + /* Release lock before doing possibly slow prune */ + glnx_release_lock_file (&lock); + + flatpak_dir_cleanup_removed (self, cancellable, NULL); + + if (!flatpak_dir_mark_changed (self, error)) + goto out; + + ret = TRUE; + +out: + if (created_deploy_base && !ret) + gs_shutil_rm_rf (deploy_base, cancellable, NULL); + + return ret; +} + + +gboolean +flatpak_dir_deploy_update (FlatpakDir *self, + const char *ref, + const char *checksum_or_latest, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *previous_deployment = NULL; + + g_autoptr(GError) my_error = NULL; + g_autoptr(GVariant) old_deploy_data = NULL; + g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; + g_autofree const char **old_subpaths = NULL; + const char *old_active; + const char *old_origin; + + if (!flatpak_dir_lock (self, &lock, + cancellable, error)) + return FALSE; + + old_deploy_data = flatpak_dir_get_deploy_data (self, ref, + cancellable, error); + if (old_deploy_data == NULL) + return FALSE; + + old_origin = flatpak_deploy_data_get_origin (old_deploy_data); + old_active = flatpak_deploy_data_get_commit (old_deploy_data); + old_subpaths = flatpak_deploy_data_get_subpaths (old_deploy_data); + if (!flatpak_dir_deploy (self, + old_origin, + ref, + checksum_or_latest, + old_subpaths, + old_deploy_data, + cancellable, &my_error)) + { + if (g_error_matches (my_error, FLATPAK_DIR_ERROR, + FLATPAK_DIR_ERROR_ALREADY_DEPLOYED)) + return TRUE; + + g_propagate_error (error, my_error); + return FALSE; + } + + if (!flatpak_dir_undeploy (self, ref, old_active, + FALSE, + cancellable, error)) + return FALSE; + + if (g_str_has_prefix (ref, "app/")) + { + g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1); + + if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error)) + return FALSE; + } + + /* Release lock before doing possibly slow prune */ + glnx_release_lock_file (&lock); + + if (!flatpak_dir_prune (self, cancellable, error)) + return FALSE; + + if (!flatpak_dir_mark_changed (self, error)) + return FALSE; + + flatpak_dir_cleanup_removed (self, cancellable, NULL); + + return TRUE; +} + +static OstreeRepo * +flatpak_dir_create_system_child_repo (FlatpakDir *self, + GLnxLockFile *file_lock, + GError **error) +{ + g_autoptr(GFile) cache_dir = NULL; + g_autoptr(GFile) repo_dir = NULL; + g_autoptr(GFile) repo_dir_config = NULL; + g_autoptr(OstreeRepo) repo = NULL; + g_autofree char *tmpdir_name = NULL; + g_autoptr(OstreeRepo) new_repo = NULL; + g_autoptr(GKeyFile) config = NULL; + + g_assert (!self->user); + + if (!flatpak_dir_ensure_repo (self, NULL, error)) + return NULL; + + cache_dir = flatpak_ensure_user_cache_dir_location (error); + if (cache_dir == NULL) + return NULL; + + if (!flatpak_allocate_tmpdir (AT_FDCWD, + gs_file_get_path_cached (cache_dir), + "repo-", &tmpdir_name, + NULL, + file_lock, + NULL, + NULL, error)) + return NULL; + + repo_dir = g_file_get_child (cache_dir, tmpdir_name); + + new_repo = ostree_repo_new (repo_dir); + + repo_dir_config = g_file_get_child (repo_dir, "config"); + if (!g_file_query_exists (repo_dir_config, NULL)) + { + if (!ostree_repo_create (new_repo, + OSTREE_REPO_MODE_BARE_USER, + NULL, error)) + return NULL; + } + else + { + if (!ostree_repo_open (new_repo, NULL, error)) + return NULL; + } + + /* Ensure the config is updated */ + config = ostree_repo_copy_config (new_repo); + g_key_file_set_string (config, "core", "parent", + gs_file_get_path_cached (ostree_repo_get_path (self->repo))); + + if (!ostree_repo_write_config (new_repo, config, error)) + return NULL; + + /* We need to reopen to apply the parent config */ + repo = system_ostree_repo_new (repo_dir); + if (!ostree_repo_open (repo, NULL, error)) + return NULL; + + return g_steal_pointer (&repo); +} + +gboolean +flatpak_dir_install (FlatpakDir *self, + gboolean no_pull, + gboolean no_deploy, + const char *ref, + const char *remote_name, + char **subpaths, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + if (flatpak_dir_use_child_repo (self)) + { + g_autoptr(OstreeRepo) child_repo = NULL; + g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT; + char *empty_subpaths[] = {NULL}; + XdgAppSystemHelper *system_helper; + + if (no_pull) + return flatpak_fail (error, "No-pull install not supported without root permissions"); + + if (no_deploy) + return flatpak_fail (error, "No-deploy install not supported without root permissions"); + + child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error); + if (child_repo == NULL) + return FALSE; + + system_helper = flatpak_dir_get_system_helper (self); + + g_assert (system_helper != NULL); + + if (!flatpak_dir_pull (self, remote_name, ref, subpaths, + child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR, + progress, cancellable, error)) + return FALSE; + + if (!xdg_app_system_helper_call_deploy_sync (system_helper, + gs_file_get_path_cached (ostree_repo_get_path (child_repo)), + FLATPAK_HELPER_DEPLOY_FLAGS_NONE, + ref, + remote_name, + (const char * const *) (subpaths ? subpaths : empty_subpaths), + cancellable, + error)) + return FALSE; + + (void) glnx_shutil_rm_rf_at (AT_FDCWD, + gs_file_get_path_cached (ostree_repo_get_path (child_repo)), + NULL, NULL); + + return TRUE; + } + + + if (!no_pull) + { + if (!flatpak_dir_pull (self, remote_name, ref, subpaths, NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress, + cancellable, error)) + return FALSE; + } + + if (!no_deploy) + { + if (!flatpak_dir_deploy_install (self, ref, remote_name, subpaths, + cancellable, error)) + return FALSE; + } + + return TRUE; +} + +gboolean +flatpak_dir_update (FlatpakDir *self, + gboolean no_pull, + gboolean no_deploy, + const char *ref, + const char *remote_name, + const char *checksum_or_latest, + char **subpaths, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + if (flatpak_dir_use_child_repo (self)) + { + g_autoptr(OstreeRepo) child_repo = NULL; + g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT; + char *empty_subpaths[] = {NULL}; + g_autofree char *pulled_checksum = NULL; + g_autofree char *active_checksum = NULL; + XdgAppSystemHelper *system_helper; + + if (no_pull) + return flatpak_fail (error, "No-pull update not supported without root permissions"); + + if (no_deploy) + return flatpak_fail (error, "No-deploy update not supported without root permissions"); + + if (checksum_or_latest != NULL) + return flatpak_fail (error, "Can't update to a specific commit without root permissions"); + + child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error); + if (child_repo == NULL) + return FALSE; + + system_helper = flatpak_dir_get_system_helper (self); + + g_assert (system_helper != NULL); + + if (!flatpak_dir_pull (self, remote_name, ref, subpaths, + child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR, + progress, cancellable, error)) + return FALSE; + + if (!ostree_repo_resolve_rev (child_repo, ref, FALSE, &pulled_checksum, error)) + return FALSE; + + active_checksum = flatpak_dir_read_active (self, ref, NULL); + if (g_strcmp0 (active_checksum, pulled_checksum) != 0) + { + + if (!xdg_app_system_helper_call_deploy_sync (system_helper, + gs_file_get_path_cached (ostree_repo_get_path (child_repo)), + FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE, + ref, + remote_name, + (const char * const *) empty_subpaths, + cancellable, + error)) + return FALSE; + } + + (void) glnx_shutil_rm_rf_at (AT_FDCWD, + gs_file_get_path_cached (ostree_repo_get_path (child_repo)), + NULL, NULL); + + return TRUE; + } + + + if (!no_pull) + { + if (!flatpak_dir_pull (self, remote_name, ref, subpaths, + NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress, + cancellable, error)) + return FALSE; + } + + if (!no_deploy) + { + if (!flatpak_dir_deploy_update (self, ref, checksum_or_latest, + cancellable, error)) + return FALSE; + } + + return TRUE; +} + + + +gboolean +flatpak_dir_collect_deployed_refs (FlatpakDir *self, + const char *type, + const char *name_prefix, + const char *branch, + const char *arch, + GHashTable *hash, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) dir = NULL; + g_autoptr(GFileEnumerator) dir_enum = NULL; + g_autoptr(GFileInfo) child_info = NULL; + GError *temp_error = NULL; + + dir = g_file_get_child (self->basedir, type); + if (!g_file_query_exists (dir, cancellable)) + return TRUE; + + dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (!dir_enum) + goto out; + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) + { + const char *name = g_file_info_get_name (child_info); + + if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY && + name[0] != '.' && (name_prefix == NULL || g_str_has_prefix (name, name_prefix))) + { + g_autoptr(GFile) child1 = g_file_get_child (dir, name); + g_autoptr(GFile) child2 = g_file_get_child (child1, branch); + g_autoptr(GFile) child3 = g_file_get_child (child2, arch); + g_autoptr(GFile) active = g_file_get_child (child3, "active"); + + if (g_file_query_exists (active, cancellable)) + g_hash_table_add (hash, g_strdup (name)); + } + + g_clear_object (&child_info); + } + + if (temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + + ret = TRUE; +out: + return ret; +} + +gboolean +flatpak_dir_list_deployed (FlatpakDir *self, + const char *ref, + char ***deployed_checksums, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(GPtrArray) checksums = NULL; + GError *temp_error = NULL; + g_autoptr(GFileEnumerator) dir_enum = NULL; + g_autoptr(GFile) child = NULL; + g_autoptr(GFileInfo) child_info = NULL; + g_autoptr(GError) my_error = NULL; + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + + checksums = g_ptr_array_new_with_free_func (g_free); + + dir_enum = g_file_enumerate_children (deploy_base, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + &my_error); + if (!dir_enum) + { + if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + ret = TRUE; /* Success, but empty */ + else + g_propagate_error (error, g_steal_pointer (&my_error)); + goto out; + } + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) + { + const char *name; + + name = g_file_info_get_name (child_info); + + g_clear_object (&child); + child = g_file_get_child (deploy_base, name); + + if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY && + name[0] != '.' && + strlen (name) == 64) + g_ptr_array_add (checksums, g_strdup (name)); + + g_clear_object (&child_info); + } + + if (temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + + ret = TRUE; + +out: + if (ret) + { + g_ptr_array_add (checksums, NULL); + *deployed_checksums = (char **) g_ptr_array_free (g_steal_pointer (&checksums), FALSE); + } + + return ret; + +} + +static gboolean +dir_is_locked (GFile *dir) +{ + glnx_fd_close int ref_fd = -1; + struct flock lock = {0}; + + g_autoptr(GFile) reffile = NULL; + + reffile = g_file_resolve_relative_path (dir, "files/.ref"); + + ref_fd = open (gs_file_get_path_cached (reffile), O_RDWR | O_CLOEXEC); + if (ref_fd != -1) + { + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + + if (fcntl (ref_fd, F_GETLK, &lock) == 0) + return lock.l_type != F_UNLCK; + } + + return FALSE; +} + +gboolean +flatpak_dir_undeploy (FlatpakDir *self, + const char *ref, + const char *checksum, + gboolean force_remove, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(GFile) checkoutdir = NULL; + g_autoptr(GFile) removed_subdir = NULL; + g_autoptr(GFile) removed_dir = NULL; + g_autofree char *tmpname = NULL; + g_autofree char *active = NULL; + int i; + + g_assert (ref != NULL); + g_assert (checksum != NULL); + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + + checkoutdir = g_file_get_child (deploy_base, checksum); + if (!g_file_query_exists (checkoutdir, cancellable)) + { + g_set_error (error, FLATPAK_DIR_ERROR, + FLATPAK_DIR_ERROR_ALREADY_UNDEPLOYED, + "%s branch %s already undeployed", ref, checksum); + goto out; + } + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + goto out; + + active = flatpak_dir_read_active (self, ref, cancellable); + if (active != NULL && strcmp (active, checksum) == 0) + { + g_auto(GStrv) deployed_checksums = NULL; + const char *some_deployment; + + /* We're removing the active deployment, start by repointing that + to another deployment if one exists */ + + if (!flatpak_dir_list_deployed (self, ref, + &deployed_checksums, + cancellable, error)) + goto out; + + some_deployment = NULL; + for (i = 0; deployed_checksums[i] != NULL; i++) + { + if (strcmp (deployed_checksums[i], checksum) == 0) + continue; + + some_deployment = deployed_checksums[i]; + break; + } + + if (!flatpak_dir_set_active (self, ref, some_deployment, cancellable, error)) + goto out; + } + + removed_dir = flatpak_dir_get_removed_dir (self); + if (!gs_file_ensure_directory (removed_dir, TRUE, cancellable, error)) + goto out; + + tmpname = gs_fileutil_gen_tmp_name ("", checksum); + removed_subdir = g_file_get_child (removed_dir, tmpname); + + if (!gs_file_rename (checkoutdir, + removed_subdir, + cancellable, error)) + goto out; + + if (force_remove || !dir_is_locked (removed_subdir)) + { + GError *tmp_error = NULL; + + if (!gs_shutil_rm_rf (removed_subdir, cancellable, &tmp_error)) + { + g_warning ("Unable to remove old checkout: %s\n", tmp_error->message); + g_error_free (tmp_error); + } + } + + ret = TRUE; +out: + return ret; +} + +gboolean +flatpak_dir_undeploy_all (FlatpakDir *self, + const char *ref, + gboolean force_remove, + gboolean *was_deployed_out, + GCancellable *cancellable, + GError **error) +{ + g_auto(GStrv) deployed = NULL; + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(GFile) arch_dir = NULL; + g_autoptr(GFile) top_dir = NULL; + GError *temp_error = NULL; + int i; + gboolean was_deployed; + + if (!flatpak_dir_list_deployed (self, ref, &deployed, cancellable, error)) + return FALSE; + + for (i = 0; deployed[i] != NULL; i++) + { + g_debug ("undeploying %s", deployed[i]); + if (!flatpak_dir_undeploy (self, ref, deployed[i], force_remove, cancellable, error)) + return FALSE; + } + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + was_deployed = g_file_query_exists (deploy_base, cancellable); + if (was_deployed) + { + g_debug ("removing deploy base"); + if (!gs_shutil_rm_rf (deploy_base, cancellable, error)) + return FALSE; + } + + g_debug ("cleaning up empty directories"); + arch_dir = g_file_get_parent (deploy_base); + if (g_file_query_exists (arch_dir, cancellable) && + !g_file_delete (arch_dir, cancellable, &temp_error)) + { + if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY)) + { + g_propagate_error (error, temp_error); + return FALSE; + } + g_clear_error (&temp_error); + } + + top_dir = g_file_get_parent (arch_dir); + if (g_file_query_exists (top_dir, cancellable) && + !g_file_delete (top_dir, cancellable, &temp_error)) + { + if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY)) + { + g_propagate_error (error, temp_error); + return FALSE; + } + g_clear_error (&temp_error); + } + + if (was_deployed_out) + *was_deployed_out = was_deployed; + + return TRUE; +} + +gboolean +flatpak_dir_remove_ref (FlatpakDir *self, + const char *remote_name, + const char *ref, + GCancellable *cancellable, + GError **error) +{ + if (!ostree_repo_set_ref_immediate (self->repo, remote_name, ref, NULL, cancellable, error)) + return FALSE; + + return TRUE; +} + +gboolean +flatpak_dir_cleanup_removed (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + g_autoptr(GFile) removed_dir = NULL; + g_autoptr(GFileEnumerator) dir_enum = NULL; + g_autoptr(GFileInfo) child_info = NULL; + GError *temp_error = NULL; + + removed_dir = flatpak_dir_get_removed_dir (self); + if (!g_file_query_exists (removed_dir, cancellable)) + return TRUE; + + dir_enum = g_file_enumerate_children (removed_dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + error); + if (!dir_enum) + goto out; + + while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) + { + const char *name = g_file_info_get_name (child_info); + g_autoptr(GFile) child = g_file_get_child (removed_dir, name); + + if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY && + !dir_is_locked (child)) + { + GError *tmp_error = NULL; + if (!gs_shutil_rm_rf (child, cancellable, &tmp_error)) + { + g_warning ("Unable to remove old checkout: %s\n", tmp_error->message); + g_error_free (tmp_error); + } + } + + g_clear_object (&child_info); + } + + if (temp_error != NULL) + { + g_propagate_error (error, temp_error); + goto out; + } + + ret = TRUE; +out: + return ret; +} + + +gboolean +flatpak_dir_prune (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gint objects_total, objects_pruned; + guint64 pruned_object_size_total; + g_autofree char *formatted_freed_size = NULL; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + goto out; + + if (!ostree_repo_prune (self->repo, + OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY, + 0, + &objects_total, + &objects_pruned, + &pruned_object_size_total, + cancellable, error)) + goto out; + + formatted_freed_size = g_format_size_full (pruned_object_size_total, 0); + g_debug ("Pruned %d/%d objects, size %s", objects_total, objects_pruned, formatted_freed_size); + + ret = TRUE; +out: + return ret; + +} + +GFile * +flatpak_dir_get_if_deployed (FlatpakDir *self, + const char *ref, + const char *checksum, + GCancellable *cancellable) +{ + g_autoptr(GFile) deploy_base = NULL; + g_autoptr(GFile) deploy_dir = NULL; + + deploy_base = flatpak_dir_get_deploy_dir (self, ref); + + if (checksum != NULL) + { + deploy_dir = g_file_get_child (deploy_base, checksum); + } + else + { + g_autoptr(GFile) active_link = g_file_get_child (deploy_base, "active"); + g_autoptr(GFileInfo) info = NULL; + const char *target; + + info = g_file_query_info (active_link, + G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, + NULL); + if (info == NULL) + return NULL; + + target = g_file_info_get_symlink_target (info); + if (target == NULL) + return NULL; + + deploy_dir = g_file_get_child (deploy_base, target); + } + + if (g_file_query_file_type (deploy_dir, G_FILE_QUERY_INFO_NONE, cancellable) == G_FILE_TYPE_DIRECTORY) + return g_object_ref (deploy_dir); + return NULL; +} + +static gboolean +flatpak_dir_remote_fetch_summary (FlatpakDir *self, + const char *name, + GBytes **out_summary, + GCancellable *cancellable, + GError **error) +{ + /* TODO: Add in-memory cache here, also use for ostree_repo_list_refs */ + if (!ostree_repo_remote_fetch_summary (self->repo, name, + out_summary, NULL, + cancellable, + error)) + return FALSE; + + return TRUE; +} + +char * +flatpak_dir_find_remote_ref (FlatpakDir *self, + const char *remote, + const char *name, + const char *opt_branch, + const char *opt_arch, + gboolean app, + gboolean runtime, + gboolean *is_app, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *app_ref = NULL; + g_autofree char *runtime_ref = NULL; + g_autofree char *app_ref_with_remote = NULL; + g_autofree char *runtime_ref_with_remote = NULL; + + g_autoptr(GVariant) summary = NULL; + g_autoptr(GVariant) refs = NULL; + g_autoptr(GBytes) summary_bytes = NULL; + + if (!flatpak_dir_ensure_repo (self, NULL, error)) + return NULL; + + if (app) + { + app_ref = flatpak_compose_ref (TRUE, name, opt_branch, opt_arch, error); + if (app_ref == NULL) + return NULL; + app_ref_with_remote = g_strconcat (remote, ":", app_ref, NULL); + } + + if (runtime) + { + runtime_ref = flatpak_compose_ref (FALSE, name, opt_branch, opt_arch, error); + if (runtime_ref == NULL) + return NULL; + runtime_ref_with_remote = g_strconcat (remote, ":", app_ref, NULL); + } + + /* First look for a local ref */ + + if (app_ref && + ostree_repo_resolve_rev (self->repo, app_ref_with_remote, + FALSE, NULL, NULL)) + { + if (is_app) + *is_app = TRUE; + return g_steal_pointer (&app_ref); + } + + if (runtime_ref && + ostree_repo_resolve_rev (self->repo, runtime_ref_with_remote, + FALSE, NULL, NULL)) + { + if (is_app) + *is_app = FALSE; + return g_steal_pointer (&runtime_ref); + } + + if (!flatpak_dir_remote_fetch_summary (self, remote, + &summary_bytes, + cancellable, error)) + return NULL; + + if (summary_bytes == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Can't find %s in remote %s; server has no summary file", name, remote); + return NULL; + } + + summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE)); + refs = g_variant_get_child_value (summary, 0); + + if (app_ref && flatpak_summary_lookup_ref (summary, app_ref, NULL)) + { + if (is_app) + *is_app = TRUE; + return g_steal_pointer (&app_ref); + } + + if (runtime_ref && flatpak_summary_lookup_ref (summary, runtime_ref, NULL)) + { + if (is_app) + *is_app = FALSE; + return g_steal_pointer (&runtime_ref); + } + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Can't find %s %s in remote %s", name, opt_branch ? opt_branch : "master", remote); + + return NULL; +} + +char * +flatpak_dir_find_installed_ref (FlatpakDir *self, + const char *name, + const char *opt_branch, + const char *opt_arch, + gboolean app, + gboolean runtime, + gboolean *is_app, + GError **error) +{ + if (app) + { + g_autofree char *app_ref = NULL; + g_autoptr(GFile) deploy_base = NULL; + + app_ref = flatpak_compose_ref (TRUE, name, opt_branch, opt_arch, error); + if (app_ref == NULL) + return NULL; + + + deploy_base = flatpak_dir_get_deploy_dir (self, app_ref); + if (g_file_query_exists (deploy_base, NULL)) + { + if (is_app) + *is_app = TRUE; + return g_steal_pointer (&app_ref); + } + } + + if (runtime) + { + g_autofree char *runtime_ref = NULL; + g_autoptr(GFile) deploy_base = NULL; + + runtime_ref = flatpak_compose_ref (FALSE, name, opt_branch, opt_arch, error); + if (runtime_ref == NULL) + return NULL; + + deploy_base = flatpak_dir_get_deploy_dir (self, runtime_ref); + if (g_file_query_exists (deploy_base, NULL)) + { + if (is_app) + *is_app = FALSE; + return g_steal_pointer (&runtime_ref); + } + } + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "%s %s not installed", name, opt_branch ? opt_branch : "master"); + return NULL; +} + +FlatpakDir * +flatpak_dir_new (GFile *path, gboolean user) +{ + return g_object_new (FLATPAK_TYPE_DIR, "path", path, "user", user, NULL); +} + +FlatpakDir * +flatpak_dir_clone (FlatpakDir *self) +{ + return flatpak_dir_new (self->basedir, self->user); +} + +FlatpakDir * +flatpak_dir_get_system (void) +{ + g_autoptr(GFile) path = flatpak_get_system_base_dir_location (); + return flatpak_dir_new (path, FALSE); +} + +FlatpakDir * +flatpak_dir_get_user (void) +{ + g_autoptr(GFile) path = flatpak_get_user_base_dir_location (); + return flatpak_dir_new (path, TRUE); +} + +FlatpakDir * +flatpak_dir_get (gboolean user) +{ + if (user) + return flatpak_dir_get_user (); + else + return flatpak_dir_get_system (); +} + +static char * +get_group (const char *remote_name) +{ + return g_strdup_printf ("remote \"%s\"", remote_name); +} + +char * +flatpak_dir_get_remote_title (FlatpakDir *self, + const char *remote_name) +{ + GKeyFile *config = ostree_repo_get_config (self->repo); + g_autofree char *group = get_group (remote_name); + + if (config) + return g_key_file_get_string (config, group, "xa.title", NULL); + + return NULL; +} + +int +flatpak_dir_get_remote_prio (FlatpakDir *self, + const char *remote_name) +{ + GKeyFile *config = ostree_repo_get_config (self->repo); + g_autofree char *group = get_group (remote_name); + + if (config && g_key_file_has_key (config, group, "xa.prio", NULL)) + return g_key_file_get_integer (config, group, "xa.prio", NULL); + + return 1; +} + +gboolean +flatpak_dir_get_remote_noenumerate (FlatpakDir *self, + const char *remote_name) +{ + GKeyFile *config = ostree_repo_get_config (self->repo); + g_autofree char *group = get_group (remote_name); + + if (config) + return g_key_file_get_boolean (config, group, "xa.noenumerate", NULL); + + return TRUE; +} + +gboolean +flatpak_dir_get_remote_disabled (FlatpakDir *self, + const char *remote_name) +{ + GKeyFile *config = ostree_repo_get_config (self->repo); + g_autofree char *group = get_group (remote_name); + + if (config) + return g_key_file_get_boolean (config, group, "xa.disable", NULL); + + return TRUE; +} + +gint +cmp_remote (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + FlatpakDir *self = user_data; + const char *a_name = *(const char **) a; + const char *b_name = *(const char **) b; + int prio_a, prio_b; + + prio_a = flatpak_dir_get_remote_prio (self, a_name); + prio_b = flatpak_dir_get_remote_prio (self, b_name); + + return prio_b - prio_a; +} + +char * +flatpak_dir_create_origin_remote (FlatpakDir *self, + const char *url, + const char *id, + const char *title, + GBytes *gpg_data, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *remote = NULL; + + g_auto(GStrv) remotes = NULL; + int version = 0; + g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + remotes = ostree_repo_remote_list (self->repo, NULL); + + do + { + g_autofree char *name = NULL; + if (version == 0) + name = g_strdup_printf ("%s-origin", id); + else + name = g_strdup_printf ("%s-%d-origin", id, version); + version++; + + if (remotes == NULL || + !g_strv_contains ((const char * const *) remotes, name)) + remote = g_steal_pointer (&name); + } + while (remote == NULL); + + g_variant_builder_add (optbuilder, "{s@v}", + "xa.title", + g_variant_new_variant (g_variant_new_string (title))); + + g_variant_builder_add (optbuilder, "{s@v}", + "xa.noenumerate", + g_variant_new_variant (g_variant_new_boolean (TRUE))); + + g_variant_builder_add (optbuilder, "{s@v}", + "xa.prio", + g_variant_new_variant (g_variant_new_string ("0"))); + + if (!ostree_repo_remote_add (self->repo, + remote, url ? url : "", g_variant_builder_end (optbuilder), cancellable, error)) + return NULL; + + if (gpg_data) + { + g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data); + + if (!ostree_repo_remote_gpg_import (self->repo, remote, gpg_data_as_stream, + NULL, NULL, cancellable, error)) + { + ostree_repo_remote_delete (self->repo, remote, + NULL, NULL); + return NULL; + } + } + + return g_steal_pointer (&remote); +} + + +char ** +flatpak_dir_list_remotes (FlatpakDir *self, + GCancellable *cancellable, + GError **error) +{ + char **res; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return NULL; + + res = ostree_repo_remote_list (self->repo, NULL); + if (res == NULL) + res = g_new0 (char *, 1); /* Return empty array, not error */ + + g_qsort_with_data (res, g_strv_length (res), sizeof (char *), + cmp_remote, self); + + return res; +} + +static gboolean +remove_unless_in_hash (gpointer key, + gpointer value, + gpointer user_data) +{ + GHashTable *table = user_data; + + return !g_hash_table_contains (table, key); +} + +gboolean +flatpak_dir_list_remote_refs (FlatpakDir *self, + const char *remote, + GHashTable **refs, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GError) my_error = NULL; + + if (error == NULL) + error = &my_error; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + if (!ostree_repo_remote_list_refs (self->repo, remote, + refs, cancellable, error)) + return FALSE; + + if (flatpak_dir_get_remote_noenumerate (self, remote)) + { + g_autoptr(GHashTable) unprefixed_local_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_autoptr(GHashTable) local_refs = NULL; + GHashTableIter hash_iter; + gpointer key; + g_autofree char *refspec_prefix = g_strconcat (remote, ":.", NULL); + + /* For noenumerate remotes, only return data for already locally + * available refs */ + + if (!ostree_repo_list_refs (self->repo, refspec_prefix, &local_refs, + cancellable, error)) + return FALSE; + + /* First we need to unprefix the remote name from the local refs */ + g_hash_table_iter_init (&hash_iter, local_refs); + while (g_hash_table_iter_next (&hash_iter, &key, NULL)) + { + char *ref = NULL; + ostree_parse_refspec (key, NULL, &ref, NULL); + + if (ref) + g_hash_table_insert (unprefixed_local_refs, ref, NULL); + } + + /* Then we remove all remote refs not in the local refs set */ + g_hash_table_foreach_remove (*refs, + remove_unless_in_hash, + unprefixed_local_refs); + } + + return TRUE; +} + +char * +flatpak_dir_fetch_remote_title (FlatpakDir *self, + const char *remote, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GError) my_error = NULL; + g_autoptr(GBytes) summary_bytes = NULL; + g_autoptr(GVariant) summary = NULL; + g_autoptr(GVariant) extensions = NULL; + GVariantDict dict; + g_autofree char *title = NULL; + + if (error == NULL) + error = &my_error; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return NULL; + + if (!flatpak_dir_remote_fetch_summary (self, remote, + &summary_bytes, + cancellable, error)) + return FALSE; + + if (summary_bytes == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Remote title not available; server has no summary file"); + return FALSE; + } + + summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, + summary_bytes, FALSE); + extensions = g_variant_get_child_value (summary, 1); + + g_variant_dict_init (&dict, extensions); + g_variant_dict_lookup (&dict, "xa.title", "s", &title); + g_variant_dict_end (&dict); + + if (title == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Remote title not set"); + return FALSE; + } + + return g_steal_pointer (&title); +} + +static void +ensure_soup_session (FlatpakDir *self) +{ + const char *http_proxy; + + if (g_once_init_enter (&self->soup_session)) + { + SoupSession *soup_session; + + soup_session = + soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ", + SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_TIMEOUT, 60, + SOUP_SESSION_IDLE_TIMEOUT, 60, + NULL); + http_proxy = g_getenv ("http_proxy"); + if (http_proxy) + { + g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy); + + if (!proxy_uri) + g_warning ("Invalid proxy URI '%s'", http_proxy); + else + g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); + } + + if (g_getenv ("OSTREE_DEBUG_HTTP")) + soup_session_add_feature (soup_session, (SoupSessionFeature *) soup_logger_new (SOUP_LOGGER_LOG_BODY, 500)); + + g_once_init_leave (&self->soup_session, soup_session); + } +} + +static GBytes * +flatpak_dir_load_uri (FlatpakDir *self, + const char *uri, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *scheme = NULL; + + g_autoptr(GBytes) bytes = NULL; + + scheme = g_uri_parse_scheme (uri); + if (strcmp (scheme, "file") == 0) + { + char *buffer; + gsize length; + g_autoptr(GFile) file = NULL; + + g_debug ("Loading %s using GIO", uri); + + file = g_file_new_for_uri (uri); + if (!g_file_load_contents (file, cancellable, &buffer, &length, NULL, NULL)) + return NULL; + + bytes = g_bytes_new_take (buffer, length); + } + else if (strcmp (scheme, "http") == 0 || + strcmp (scheme, "https") == 0) + { + g_autoptr(SoupMessage) msg = NULL; + + ensure_soup_session (self); + + g_debug ("Loading %s using libsoup", uri); + msg = soup_message_new ("GET", uri); + soup_session_send_message (self->soup_session, msg); + + if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + { + GIOErrorEnum code; + + switch (msg->status_code) + { + case 404: + case 410: + code = G_IO_ERROR_NOT_FOUND; + break; + + default: + code = G_IO_ERROR_FAILED; + } + + g_set_error (error, G_IO_ERROR, code, + "Server returned status %u: %s", + msg->status_code, + soup_status_get_phrase (msg->status_code)); + return NULL; + } + + bytes = g_bytes_new (msg->response_body->data, msg->response_body->length); + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unsupported uri scheme %s", scheme); + return FALSE; + } + + g_debug ("Received %" G_GSIZE_FORMAT " bytes", g_bytes_get_size (bytes)); + + return g_steal_pointer (&bytes); +} + +GBytes * +flatpak_dir_fetch_remote_object (FlatpakDir *self, + const char *remote_name, + const char *checksum, + const char *type, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *base_url = NULL; + g_autofree char *object_url = NULL; + g_autofree char *part1 = NULL; + g_autofree char *part2 = NULL; + + g_autoptr(GBytes) bytes = NULL; + + if (!ostree_repo_remote_get_url (self->repo, remote_name, &base_url, error)) + return NULL; + + part1 = g_strndup (checksum, 2); + part2 = g_strdup_printf ("%s.%s", checksum + 2, type); + + object_url = g_build_filename (base_url, "objects", part1, part2, NULL); + + bytes = flatpak_dir_load_uri (self, object_url, cancellable, error); + if (bytes == NULL) + return NULL; + + return g_steal_pointer (&bytes); +} + +gboolean +flatpak_dir_fetch_ref_cache (FlatpakDir *self, + const char *remote_name, + const char *ref, + guint64 *download_size, + guint64 *installed_size, + char **metadata, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GBytes) summary_bytes = NULL; + g_autoptr(GVariant) extensions = NULL; + g_autoptr(GVariant) summary = NULL; + g_autoptr(GVariant) cache_v = NULL; + g_autoptr(GVariant) cache = NULL; + g_autoptr(GVariant) res = NULL; + + if (!flatpak_dir_ensure_repo (self, cancellable, error)) + return FALSE; + + if (!flatpak_dir_remote_fetch_summary (self, remote_name, + &summary_bytes, + cancellable, error)) + return FALSE; + + if (summary_bytes == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Data not available; server has no summary file"); + return FALSE; + } + + summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, + summary_bytes, FALSE); + extensions = g_variant_get_child_value (summary, 1); + + cache_v = g_variant_lookup_value (extensions, "xa.cache", NULL); + if (cache_v == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Data not found"); + return FALSE; + } + + cache = g_variant_get_child_value (cache_v, 0); + res = g_variant_lookup_value (cache, ref, NULL); + if (res == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Data not found for ref %s", ref); + return FALSE; + } + + if (installed_size) + { + guint64 v; + g_variant_get_child (res, 0, "t", &v); + *installed_size = GUINT64_FROM_BE (v); + } + + if (download_size) + { + guint64 v; + g_variant_get_child (res, 1, "t", &v); + *download_size = GUINT64_FROM_BE (v); + } + + if (metadata) + g_variant_get_child (res, 2, "s", metadata); + + return TRUE; +} + +GBytes * +flatpak_dir_fetch_metadata (FlatpakDir *self, + const char *remote_name, + const char *commit, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GBytes) commit_bytes = NULL; + g_autoptr(GBytes) root_bytes = NULL; + g_autoptr(GBytes) filez_bytes = NULL; + g_autoptr(GVariant) commit_variant = NULL; + g_autoptr(GVariant) root_variant = NULL; + g_autoptr(GVariant) root_csum = NULL; + g_autoptr(GVariant) files_variant = NULL; + g_autofree char *file_checksum = NULL; + g_autofree char *root_checksum = NULL; + g_autoptr(GConverter) zlib_decomp = NULL; + g_autoptr(GInputStream) zlib_input = NULL; + g_autoptr(GMemoryOutputStream) data_stream = NULL; + g_autoptr(GMemoryInputStream) dataz_stream = NULL; + gsize filez_size; + const guchar *filez_data; + guint32 archive_header_size; + int i, n; + + commit_bytes = flatpak_dir_fetch_remote_object (self, remote_name, + commit, "commit", + cancellable, error); + if (commit_bytes == NULL) + return NULL; + + commit_variant = g_variant_new_from_bytes (OSTREE_COMMIT_GVARIANT_FORMAT, + commit_bytes, FALSE); + + if (!ostree_validate_structureof_commit (commit_variant, error)) + return NULL; + + g_variant_get_child (commit_variant, 6, "@ay", &root_csum); + root_checksum = ostree_checksum_from_bytes_v (root_csum); + + root_bytes = flatpak_dir_fetch_remote_object (self, remote_name, + root_checksum, "dirtree", + cancellable, error); + if (root_bytes == NULL) + return NULL; + + root_variant = g_variant_new_from_bytes (OSTREE_TREE_GVARIANT_FORMAT, + root_bytes, FALSE); + + if (!ostree_validate_structureof_dirtree (root_variant, error)) + return NULL; + + files_variant = g_variant_get_child_value (root_variant, 0); + + n = g_variant_n_children (files_variant); + for (i = 0; i < n; i++) + { + const char *filename; + g_autoptr(GVariant) csum = NULL; + + g_variant_get_child (files_variant, i, "(&s@ay)", &filename, &csum); + + if (strcmp (filename, "metadata") != 0) + continue; + + file_checksum = ostree_checksum_from_bytes_v (csum); + break; + } + + if (file_checksum == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Can't find metadata file"); + return NULL; + } + + filez_bytes = flatpak_dir_fetch_remote_object (self, remote_name, + file_checksum, "filez", + cancellable, error); + if (filez_bytes == NULL) + return NULL; + + filez_data = g_bytes_get_data (filez_bytes, &filez_size); + + if (filez_size < 8) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid header"); + return NULL; + } + + archive_header_size = GUINT32_FROM_BE (*(guint32 *) filez_data); + + archive_header_size += 4 + 4; /* Include header-size and padding */ + + if (archive_header_size > filez_size) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "File header size %u exceeds file size", + (guint) archive_header_size); + return NULL; + } + + dataz_stream = (GMemoryInputStream *) g_memory_input_stream_new_from_data (filez_data + archive_header_size, + g_bytes_get_size (filez_bytes) - archive_header_size, + NULL); + + zlib_decomp = (GConverter *) g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW); + zlib_input = g_converter_input_stream_new (G_INPUT_STREAM (dataz_stream), zlib_decomp); + + data_stream = (GMemoryOutputStream *) g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + + if (g_output_stream_splice (G_OUTPUT_STREAM (data_stream), zlib_input, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + cancellable, error) < 0) + return NULL; + + return g_memory_output_stream_steal_as_bytes (data_stream); +} diff --git a/common/flatpak-dir.h b/common/flatpak-dir.h new file mode 100644 index 0000000..495b81f --- /dev/null +++ b/common/flatpak-dir.h @@ -0,0 +1,360 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 library 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 library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_DIR_H__ +#define __FLATPAK_DIR_H__ + +#include + +#include "libglnx/libglnx.h" +#include + +#define FLATPAK_TYPE_DIR flatpak_dir_get_type () +#define FLATPAK_DIR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_DIR, FlatpakDir)) +#define FLATPAK_IS_DIR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_DIR)) + +#define FLATPAK_TYPE_DEPLOY flatpak_deploy_get_type () +#define FLATPAK_DEPLOY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_DEPLOY, FlatpakDeploy)) +#define FLATPAK_IS_DEPLOY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_DEPLOY)) + +GType flatpak_dir_get_type (void); +GType flatpak_deploy_get_type (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDir, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDeploy, g_object_unref) + +#define FLATPAK_DIR_ERROR flatpak_dir_error_quark () + +typedef enum { + FLATPAK_DIR_ERROR_ALREADY_DEPLOYED, + FLATPAK_DIR_ERROR_ALREADY_UNDEPLOYED, + FLATPAK_DIR_ERROR_NOT_DEPLOYED, +} FlatpakDirErrorEnum; + +typedef enum { + FLATPAK_HELPER_DEPLOY_FLAGS_NONE = 0, + FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE = 1 << 0, +} FlatpakHelperDeployFlags; + +#define FLATPAK_HELPER_DEPLOY_FLAGS_ALL (FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE) + +GQuark flatpak_dir_error_quark (void); + +/** + * FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT: + * + * s - origin + * s - commit + * as - subpaths + * t - installed size + * a{sv} - Metadata + */ +#define FLATPAK_DEPLOY_DATA_GVARIANT_STRING "(ssasta{sv})" +#define FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT G_VARIANT_TYPE (FLATPAK_DEPLOY_DATA_GVARIANT_STRING) + +GFile * flatpak_get_system_base_dir_location (void); +GFile * flatpak_get_user_base_dir_location (void); + +GKeyFile * flatpak_load_override_keyfile (const char *app_id, + gboolean user, + GError **error); +FlatpakContext *flatpak_load_override_file (const char *app_id, + gboolean user, + GError **error); +gboolean flatpak_save_override_keyfile (GKeyFile *metakey, + const char *app_id, + gboolean user, + GError **error); + +const char * flatpak_deploy_data_get_origin (GVariant *deploy_data); +const char * flatpak_deploy_data_get_commit (GVariant *deploy_data); +const char ** flatpak_deploy_data_get_subpaths (GVariant *deploy_data); +guint64 flatpak_deploy_data_get_installed_size (GVariant *deploy_data); + +GFile * flatpak_deploy_get_dir (FlatpakDeploy *deploy); +GFile * flatpak_deploy_get_files (FlatpakDeploy *deploy); +FlatpakContext *flatpak_deploy_get_overrides (FlatpakDeploy *deploy); +GKeyFile * flatpak_deploy_get_metadata (FlatpakDeploy *deploy); + +FlatpakDir * flatpak_dir_new (GFile *basedir, + gboolean user); +FlatpakDir * flatpak_dir_clone (FlatpakDir *self); +FlatpakDir *flatpak_dir_get (gboolean user); +FlatpakDir *flatpak_dir_get_system (void); +FlatpakDir *flatpak_dir_get_user (void); +gboolean flatpak_dir_is_user (FlatpakDir *self); +GFile * flatpak_dir_get_path (FlatpakDir *self); +GFile * flatpak_dir_get_changed_path (FlatpakDir *self); +GFile * flatpak_dir_get_deploy_dir (FlatpakDir *self, + const char *ref); +GVariant * flatpak_dir_get_deploy_data (FlatpakDir *dir, + const char *ref, + GCancellable *cancellable, + GError **error); +char * flatpak_dir_get_origin (FlatpakDir *self, + const char *ref, + GCancellable *cancellable, + GError **error); +char ** flatpak_dir_get_subpaths (FlatpakDir *self, + const char *ref, + GCancellable *cancellable, + GError **error); +GFile * flatpak_dir_get_exports_dir (FlatpakDir *self); +GFile * flatpak_dir_get_removed_dir (FlatpakDir *self); +GFile * flatpak_dir_get_if_deployed (FlatpakDir *self, + const char *ref, + const char *checksum, + GCancellable *cancellable); +char * flatpak_dir_find_remote_ref (FlatpakDir *self, + const char *remote, + const char *name, + const char *opt_branch, + const char *opt_arch, + gboolean app, + gboolean runtime, + gboolean *is_app, + GCancellable *cancellable, + GError **error); +char * flatpak_dir_find_installed_ref (FlatpakDir *self, + const char *name, + const char *opt_branch, + const char *opt_arch, + gboolean app, + gboolean runtime, + gboolean *is_app, + GError **error); +FlatpakDeploy *flatpak_dir_load_deployed (FlatpakDir *self, + const char *ref, + const char *checksum, + GCancellable *cancellable, + GError **error); +char * flatpak_dir_load_override (FlatpakDir *dir, + const char *app_id, + gsize *length, + GError **error); +OstreeRepo *flatpak_dir_get_repo (FlatpakDir *self); +gboolean flatpak_dir_ensure_path (FlatpakDir *self, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_use_child_repo (FlatpakDir *self); +gboolean flatpak_dir_ensure_system_child_repo (FlatpakDir *self, + GError **error); +gboolean flatpak_dir_ensure_repo (FlatpakDir *self, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_mark_changed (FlatpakDir *self, + GError **error); +gboolean flatpak_dir_remove_appstream (FlatpakDir *self, + const char *remote, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_update_appstream (FlatpakDir *self, + const char *remote, + const char *arch, + gboolean *out_changed, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_pull (FlatpakDir *self, + const char *repository, + const char *ref, + char **subpaths, + OstreeRepo *repo, + OstreeRepoPullFlags flags, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_pull_untrusted_local (FlatpakDir *self, + const char *src_path, + const char *remote_name, + const char *ref, + char **subpaths, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_list_refs_for_name (FlatpakDir *self, + const char *kind, + const char *name, + char ***refs, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_list_refs (FlatpakDir *self, + const char *kind, + char ***refs, + GCancellable *cancellable, + GError **error); +char * flatpak_dir_read_latest (FlatpakDir *self, + const char *remote, + const char *ref, + GCancellable *cancellable, + GError **error); +char * flatpak_dir_read_active (FlatpakDir *self, + const char *ref, + GCancellable *cancellable); +gboolean flatpak_dir_set_active (FlatpakDir *self, + const char *ref, + const char *checksum, + GCancellable *cancellable, + GError **error); +char * flatpak_dir_current_ref (FlatpakDir *self, + const char *name, + GCancellable *cancellable); +gboolean flatpak_dir_drop_current_ref (FlatpakDir *self, + const char *name, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_make_current_ref (FlatpakDir *self, + const char *ref, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_list_deployed (FlatpakDir *self, + const char *ref, + char ***deployed_checksums, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_lock (FlatpakDir *self, + GLnxLockFile *lockfile, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_deploy (FlatpakDir *self, + const char *origin, + const char *ref, + const char *checksum_or_latest, + const char * const * subpaths, + GVariant *old_deploy_data, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_deploy_update (FlatpakDir *self, + const char *ref, + const char *checksum, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_deploy_install (FlatpakDir *self, + const char *ref, + const char *origin, + char **subpaths, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_install (FlatpakDir *self, + gboolean no_pull, + gboolean no_deploy, + const char *ref, + const char *remote_name, + char **subpaths, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_update (FlatpakDir *self, + gboolean no_pull, + gboolean no_deploy, + const char *ref, + const char *remote_name, + const char *checksum_or_latest, + char **subpaths, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_undeploy (FlatpakDir *self, + const char *ref, + const char *checksum, + gboolean force_remove, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_undeploy_all (FlatpakDir *self, + const char *ref, + gboolean force_remove, + gboolean *was_deployed_out, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_remove_all_refs (FlatpakDir *self, + const char *remote, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_remove_ref (FlatpakDir *self, + const char *remote_name, + const char *ref, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_update_exports (FlatpakDir *self, + const char *app, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_prune (FlatpakDir *self, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_cleanup_removed (FlatpakDir *self, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_collect_deployed_refs (FlatpakDir *self, + const char *type, + const char *name_prefix, + const char *branch, + const char *arch, + GHashTable *hash, + GCancellable *cancellable, + GError **error); +char *flatpak_dir_create_origin_remote (FlatpakDir *self, + const char *url, + const char *id, + const char *title, + GBytes *gpg_data, + GCancellable *cancellable, + GError **error); +char **flatpak_dir_list_remotes (FlatpakDir *self, + GCancellable *cancellable, + GError **error); +char *flatpak_dir_get_remote_title (FlatpakDir *self, + const char *remote_name); +int flatpak_dir_get_remote_prio (FlatpakDir *self, + const char *remote_name); +gboolean flatpak_dir_get_remote_noenumerate (FlatpakDir *self, + const char *remote_name); +gboolean flatpak_dir_get_remote_disabled (FlatpakDir *self, + const char *remote_name); +gboolean flatpak_dir_list_remote_refs (FlatpakDir *self, + const char *remote, + GHashTable **refs, + GCancellable *cancellable, + GError **error); +char * flatpak_dir_fetch_remote_title (FlatpakDir *self, + const char *remote, + GCancellable *cancellable, + GError **error); +GBytes * flatpak_dir_fetch_remote_object (FlatpakDir *self, + const char *remote, + const char *checksum, + const char *type, + GCancellable *cancellable, + GError **error); +GBytes * flatpak_dir_fetch_metadata (FlatpakDir *self, + const char *remote_name, + const char *commit, + GCancellable *cancellable, + GError **error); +gboolean flatpak_dir_fetch_ref_cache (FlatpakDir *self, + const char *remote_name, + const char *ref, + guint64 *download_size, + guint64 *installed_size, + char **metadata, + GCancellable *cancellable, + GError **error); + +#endif /* __FLATPAK_DIR_H__ */ diff --git a/common/flatpak-portal-error.c b/common/flatpak-portal-error.c new file mode 100644 index 0000000..7672f0f --- /dev/null +++ b/common/flatpak-portal-error.c @@ -0,0 +1,48 @@ +/* xdg-app-error.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 . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include "flatpak-portal-error.h" + +#include + +static const GDBusErrorEntry flatpak_error_entries[] = { + {FLATPAK_PORTAL_ERROR_FAILED, "org.freedesktop.Flatpak.Failed"}, + {FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT, "org.freedesktop.Flatpak.InvalidArgument"}, + {FLATPAK_PORTAL_ERROR_NOT_FOUND, "org.freedesktop.Flatpak.NotFound"}, + {FLATPAK_PORTAL_ERROR_EXISTS, "org.freedesktop.Flatpak.Exists"}, + {FLATPAK_PORTAL_ERROR_NOT_ALLOWED, "org.freedesktop.Flatpak.NotAllowed"}, + {FLATPAK_PORTAL_ERROR_CANCELLED, "org.freedesktop.Flatpak.Cancelled"}, + {FLATPAK_PORTAL_ERROR_WINDOW_DESTROYED, "org.freedesktop.Flatpak.WindowDestroyed"}, +}; + +GQuark +flatpak_portal_error_quark (void) +{ + static volatile gsize quark_volatile = 0; + + g_dbus_error_register_error_domain ("xdg-app-portal-error-quark", + &quark_volatile, + flatpak_error_entries, + G_N_ELEMENTS (flatpak_error_entries)); + return (GQuark) quark_volatile; +} diff --git a/common/flatpak-portal-error.h b/common/flatpak-portal-error.h new file mode 100644 index 0000000..2ff02e8 --- /dev/null +++ b/common/flatpak-portal-error.h @@ -0,0 +1,49 @@ +/* xdg-app-error.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 . + * + * Authors: + * Alexander Larsson + */ + +#ifndef FLATPAK_PORTAL_ERROR_H +#define FLATPAK_PORTAL_ERROR_H + +#include + +G_BEGIN_DECLS + +/** + * XdpErrorEnum: + */ +typedef enum { + FLATPAK_PORTAL_ERROR_FAILED = 0, + FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT, + FLATPAK_PORTAL_ERROR_NOT_FOUND, + FLATPAK_PORTAL_ERROR_EXISTS, + FLATPAK_PORTAL_ERROR_NOT_ALLOWED, + FLATPAK_PORTAL_ERROR_CANCELLED, + FLATPAK_PORTAL_ERROR_WINDOW_DESTROYED, +} FlatpakErrorEnum; + + +#define FLATPAK_PORTAL_ERROR flatpak_portal_error_quark () + +FLATPAK_EXTERN GQuark flatpak_portal_error_quark (void); + +G_END_DECLS + +#endif /* FLATPAK_PORTAL_ERROR_H */ diff --git a/common/flatpak-run.c b/common/flatpak-run.c new file mode 100644 index 0000000..ff4eb37 --- /dev/null +++ b/common/flatpak-run.c @@ -0,0 +1,3050 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 library 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 library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_SECCOMP +#include +#endif + +#ifdef ENABLE_XAUTH +#include +#endif + +#include +#include "libgsystem.h" +#include "libglnx/libglnx.h" + +#include "flatpak-run.h" +#include "flatpak-proxy.h" +#include "flatpak-utils.h" +#include "flatpak-systemd-dbus.h" + +#define DEFAULT_SHELL "/bin/sh" + +typedef enum { + FLATPAK_CONTEXT_SHARED_NETWORK = 1 << 0, + FLATPAK_CONTEXT_SHARED_IPC = 1 << 1, +} FlatpakContextShares; + +/* In numerical order of more privs */ +typedef enum { + FLATPAK_FILESYSTEM_MODE_READ_ONLY = 1, + FLATPAK_FILESYSTEM_MODE_READ_WRITE = 2, +} FlatpakFilesystemMode; + + +/* Same order as enum */ +static const char *flatpak_context_shares[] = { + "network", + "ipc", + NULL +}; + +typedef enum { + FLATPAK_CONTEXT_SOCKET_X11 = 1 << 0, + FLATPAK_CONTEXT_SOCKET_WAYLAND = 1 << 1, + FLATPAK_CONTEXT_SOCKET_PULSEAUDIO = 1 << 2, + FLATPAK_CONTEXT_SOCKET_SESSION_BUS = 1 << 3, + FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS = 1 << 4, +} FlatpakContextSockets; + +/* Same order as enum */ +static const char *flatpak_context_sockets[] = { + "x11", + "wayland", + "pulseaudio", + "session-bus", + "system-bus", + NULL +}; + +const char *dont_mount_in_root[] = { + ".", "..", "lib", "lib32", "lib64", "bin", "sbin", "usr", "boot", "root", + "tmp", "etc", "app", "run", "proc", "sys", "dev", "var", NULL +}; + +typedef enum { + FLATPAK_CONTEXT_DEVICE_DRI = 1 << 0, +} FlatpakContextDevices; + +static const char *flatpak_context_devices[] = { + "dri", + NULL +}; + +struct FlatpakContext +{ + FlatpakContextShares shares; + FlatpakContextShares shares_valid; + FlatpakContextSockets sockets; + FlatpakContextSockets sockets_valid; + FlatpakContextDevices devices; + FlatpakContextDevices devices_valid; + GHashTable *env_vars; + GHashTable *persistent; + GHashTable *filesystems; + GHashTable *session_bus_policy; + GHashTable *system_bus_policy; +}; + +FlatpakContext * +flatpak_context_new (void) +{ + FlatpakContext *context; + + context = g_slice_new0 (FlatpakContext); + context->env_vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + context->persistent = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + context->filesystems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + context->session_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + context->system_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + return context; +} + +void +flatpak_context_free (FlatpakContext *context) +{ + g_hash_table_destroy (context->env_vars); + g_hash_table_destroy (context->persistent); + g_hash_table_destroy (context->filesystems); + g_hash_table_destroy (context->session_bus_policy); + g_hash_table_destroy (context->system_bus_policy); + g_slice_free (FlatpakContext, context); +} + +static guint32 +flatpak_context_bitmask_from_string (const char *name, const char **names) +{ + guint32 i; + + for (i = 0; names[i] != NULL; i++) + { + if (strcmp (names[i], name) == 0) + return 1 << i; + } + + return 0; +} + + +static char ** +flatpak_context_bitmask_to_string (guint32 enabled, guint32 valid, const char **names) +{ + guint32 i; + GPtrArray *array; + + array = g_ptr_array_new (); + + for (i = 0; names[i] != NULL; i++) + { + guint32 bitmask = 1 << i; + if (valid & bitmask) + { + if (enabled & bitmask) + g_ptr_array_add (array, g_strdup (names[i])); + else + g_ptr_array_add (array, g_strdup_printf ("!%s", names[i])); + } + } + + g_ptr_array_add (array, NULL); + return (char **) g_ptr_array_free (array, FALSE); +} + +static FlatpakContextShares +flatpak_context_share_from_string (const char *string, GError **error) +{ + FlatpakContextShares shares = flatpak_context_bitmask_from_string (string, flatpak_context_shares); + + if (shares == 0) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "Unknown share type %s, valid types are: network, ipc\n", string); + return shares; +} + +static char ** +flatpak_context_shared_to_string (FlatpakContextShares shares, FlatpakContextShares valid) +{ + return flatpak_context_bitmask_to_string (shares, valid, flatpak_context_shares); +} + +static FlatpakPolicy +flatpak_policy_from_string (const char *string, GError **error) +{ + if (strcmp (string, "none") == 0) + return FLATPAK_POLICY_NONE; + if (strcmp (string, "see") == 0) + return FLATPAK_POLICY_SEE; + if (strcmp (string, "talk") == 0) + return FLATPAK_POLICY_TALK; + if (strcmp (string, "own") == 0) + return FLATPAK_POLICY_OWN; + + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "Unknown socket type %s, valid types are: x11,wayland,pulseaudio,session-bus,system-bus\n", string); + return -1; +} + +static const char * +flatpak_policy_to_string (FlatpakPolicy policy) +{ + if (policy == FLATPAK_POLICY_SEE) + return "see"; + if (policy == FLATPAK_POLICY_TALK) + return "talk"; + if (policy == FLATPAK_POLICY_OWN) + return "own"; + + return "none"; +} + +static gboolean +flatpak_verify_dbus_name (const char *name, GError **error) +{ + const char *name_part; + g_autofree char *tmp = NULL; + + if (g_str_has_suffix (name, ".*")) + { + tmp = g_strndup (name, strlen (name) - 2); + name_part = tmp; + } + else + { + name_part = name; + } + + if (g_dbus_is_name (name_part) && !g_dbus_is_unique_name (name_part)) + return TRUE; + + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Invalid dbus name %s\n", name); + return FALSE; +} + +static FlatpakContextSockets +flatpak_context_socket_from_string (const char *string, GError **error) +{ + FlatpakContextSockets sockets = flatpak_context_bitmask_from_string (string, flatpak_context_sockets); + + if (sockets == 0) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "Unknown socket type %s, valid types are: x11,wayland,pulseaudio,session-bus,system-bus\n", string); + return sockets; +} + +static char ** +flatpak_context_sockets_to_string (FlatpakContextSockets sockets, FlatpakContextSockets valid) +{ + return flatpak_context_bitmask_to_string (sockets, valid, flatpak_context_sockets); +} + +static FlatpakContextDevices +flatpak_context_device_from_string (const char *string, GError **error) +{ + FlatpakContextDevices devices = flatpak_context_bitmask_from_string (string, flatpak_context_devices); + + if (devices == 0) + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "Unknown device type %s, valid types are: dri\n", string); + return devices; +} + +static char ** +flatpak_context_devices_to_string (FlatpakContextDevices devices, FlatpakContextDevices valid) +{ + return flatpak_context_bitmask_to_string (devices, valid, flatpak_context_devices); +} + +static void +flatpak_context_add_shares (FlatpakContext *context, + FlatpakContextShares shares) +{ + context->shares_valid |= shares; + context->shares |= shares; +} + +static void +flatpak_context_remove_shares (FlatpakContext *context, + FlatpakContextShares shares) +{ + context->shares_valid |= shares; + context->shares &= ~shares; +} + +static void +flatpak_context_add_sockets (FlatpakContext *context, + FlatpakContextSockets sockets) +{ + context->sockets_valid |= sockets; + context->sockets |= sockets; +} + +static void +flatpak_context_remove_sockets (FlatpakContext *context, + FlatpakContextSockets sockets) +{ + context->sockets_valid |= sockets; + context->sockets &= ~sockets; +} + +static void +flatpak_context_add_devices (FlatpakContext *context, + FlatpakContextDevices devices) +{ + context->devices_valid |= devices; + context->devices |= devices; +} + +static void +flatpak_context_remove_devices (FlatpakContext *context, + FlatpakContextDevices devices) +{ + context->devices_valid |= devices; + context->devices &= ~devices; +} + +static void +flatpak_context_set_env_var (FlatpakContext *context, + const char *name, + const char *value) +{ + g_hash_table_insert (context->env_vars, g_strdup (name), g_strdup (value)); +} + +void +flatpak_context_set_session_bus_policy (FlatpakContext *context, + const char *name, + FlatpakPolicy policy) +{ + g_hash_table_insert (context->session_bus_policy, g_strdup (name), GINT_TO_POINTER (policy)); +} + +void +flatpak_context_set_system_bus_policy (FlatpakContext *context, + const char *name, + FlatpakPolicy policy) +{ + g_hash_table_insert (context->system_bus_policy, g_strdup (name), GINT_TO_POINTER (policy)); +} + +static void +flatpak_context_set_persistent (FlatpakContext *context, + const char *path) +{ + g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1)); +} + +static gboolean +get_user_dir_from_string (const char *filesystem, + const char **config_key, + const char **suffix, + const char **dir) +{ + char *slash; + const char *rest; + g_autofree char *prefix; + gsize len; + + slash = strchr (filesystem, '/'); + + if (slash) + len = slash - filesystem; + else + len = strlen (filesystem); + + rest = filesystem + len; + while (*rest == '/') + rest++; + + if (suffix) + *suffix = rest; + + prefix = g_strndup (filesystem, len); + + if (strcmp (prefix, "xdg-desktop") == 0) + { + if (config_key) + *config_key = "XDG_DESKTOP_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + return TRUE; + } + if (strcmp (prefix, "xdg-documents") == 0) + { + if (config_key) + *config_key = "XDG_DOCUMENTS_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS); + return TRUE; + } + if (strcmp (prefix, "xdg-download") == 0) + { + if (config_key) + *config_key = "XDG_DOWNLOAD_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD); + return TRUE; + } + if (strcmp (prefix, "xdg-music") == 0) + { + if (config_key) + *config_key = "XDG_MUSIC_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC); + return TRUE; + } + if (strcmp (prefix, "xdg-pictures") == 0) + { + if (config_key) + *config_key = "XDG_PICTURES_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); + return TRUE; + } + if (strcmp (prefix, "xdg-public-share") == 0) + { + if (config_key) + *config_key = "XDG_PUBLICSHARE_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE); + return TRUE; + } + if (strcmp (prefix, "xdg-templates") == 0) + { + if (config_key) + *config_key = "XDG_TEMPLATES_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES); + return TRUE; + } + if (strcmp (prefix, "xdg-videos") == 0) + { + if (config_key) + *config_key = "XDG_VIDEOS_DIR"; + if (dir) + *dir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS); + return TRUE; + } + /* Don't support xdg-run without suffix, because that doesn't work */ + if (strcmp (prefix, "xdg-run") == 0 && + *rest != 0) + { + if (config_key) + *config_key = NULL; + if (dir) + *dir = g_get_user_runtime_dir (); + return TRUE; + } + + return FALSE; +} + +static char * +parse_filesystem_flags (const char *filesystem, FlatpakFilesystemMode *mode) +{ + gsize len = strlen (filesystem); + + if (mode) + *mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE; + + if (g_str_has_suffix (filesystem, ":ro")) + { + len -= 3; + if (mode) + *mode = FLATPAK_FILESYSTEM_MODE_READ_ONLY; + } + else if (g_str_has_suffix (filesystem, ":rw")) + { + len -= 3; + if (mode) + *mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE; + } + + return g_strndup (filesystem, len); +} + +static gboolean +flatpak_context_verify_filesystem (const char *filesystem_and_mode, + GError **error) +{ + g_autofree char *filesystem = parse_filesystem_flags (filesystem_and_mode, NULL); + + if (strcmp (filesystem, "host") == 0) + return TRUE; + if (strcmp (filesystem, "home") == 0) + return TRUE; + if (get_user_dir_from_string (filesystem, NULL, NULL, NULL)) + return TRUE; + if (g_str_has_prefix (filesystem, "~/")) + return TRUE; + if (g_str_has_prefix (filesystem, "/")) + return TRUE; + + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "Unknown filesystem location %s, valid types are: host,home,xdg-*[/...],~/dir,/dir,\n", filesystem); + return FALSE; +} + +static void +flatpak_context_add_filesystem (FlatpakContext *context, + const char *what) +{ + FlatpakFilesystemMode mode; + char *fs = parse_filesystem_flags (what, &mode); + + g_hash_table_insert (context->filesystems, fs, GINT_TO_POINTER (mode)); +} + +static void +flatpak_context_remove_filesystem (FlatpakContext *context, + const char *what) +{ + g_hash_table_insert (context->filesystems, + parse_filesystem_flags (what, NULL), + NULL); +} + +void +flatpak_context_merge (FlatpakContext *context, + FlatpakContext *other) +{ + GHashTableIter iter; + gpointer key, value; + + context->shares &= ~other->shares_valid; + context->shares |= other->shares; + context->shares_valid |= other->shares_valid; + context->sockets &= ~other->sockets_valid; + context->sockets |= other->sockets; + context->sockets_valid |= other->sockets_valid; + context->devices &= ~other->devices_valid; + context->devices |= other->devices; + context->devices_valid |= other->devices_valid; + + g_hash_table_iter_init (&iter, other->env_vars); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_insert (context->env_vars, g_strdup (key), g_strdup (value)); + + g_hash_table_iter_init (&iter, other->persistent); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_insert (context->persistent, g_strdup (key), value); + + g_hash_table_iter_init (&iter, other->filesystems); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_insert (context->filesystems, g_strdup (key), value); + + g_hash_table_iter_init (&iter, other->session_bus_policy); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_insert (context->session_bus_policy, g_strdup (key), value); + + g_hash_table_iter_init (&iter, other->system_bus_policy); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_insert (context->system_bus_policy, g_strdup (key), value); +} + +static gboolean +option_share_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + FlatpakContextShares share; + + share = flatpak_context_share_from_string (value, error); + if (share == 0) + return FALSE; + + flatpak_context_add_shares (context, share); + + return TRUE; +} + +static gboolean +option_unshare_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + FlatpakContextShares share; + + share = flatpak_context_share_from_string (value, error); + if (share == 0) + return FALSE; + + flatpak_context_remove_shares (context, share); + + return TRUE; +} + +static gboolean +option_socket_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + FlatpakContextSockets socket; + + socket = flatpak_context_socket_from_string (value, error); + if (socket == 0) + return FALSE; + + flatpak_context_add_sockets (context, socket); + + return TRUE; +} + +static gboolean +option_nosocket_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + FlatpakContextSockets socket; + + socket = flatpak_context_socket_from_string (value, error); + if (socket == 0) + return FALSE; + + flatpak_context_remove_sockets (context, socket); + + return TRUE; +} + +static gboolean +option_device_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + FlatpakContextDevices device; + + device = flatpak_context_device_from_string (value, error); + if (device == 0) + return FALSE; + + flatpak_context_add_devices (context, device); + + return TRUE; +} + +static gboolean +option_nodevice_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + FlatpakContextDevices device; + + device = flatpak_context_device_from_string (value, error); + if (device == 0) + return FALSE; + + flatpak_context_remove_devices (context, device); + + return TRUE; +} + +static gboolean +option_filesystem_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + if (!flatpak_context_verify_filesystem (value, error)) + return FALSE; + + flatpak_context_add_filesystem (context, value); + return TRUE; +} + +static gboolean +option_nofilesystem_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + if (!flatpak_context_verify_filesystem (value, error)) + return FALSE; + + flatpak_context_remove_filesystem (context, value); + return TRUE; +} + +static gboolean +option_env_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + g_auto(GStrv) split = g_strsplit (value, "=", 2); + + if (split == NULL || split[0] == NULL || split[0][0] == 0 || split[1] == NULL) + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Invalid env format %s", value); + return FALSE; + } + + flatpak_context_set_env_var (context, split[0], split[1]); + return TRUE; +} + +static gboolean +option_own_name_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + if (!flatpak_verify_dbus_name (value, error)) + return FALSE; + + flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_OWN); + return TRUE; +} + +static gboolean +option_talk_name_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + if (!flatpak_verify_dbus_name (value, error)) + return FALSE; + + flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_TALK); + return TRUE; +} + +static gboolean +option_system_own_name_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + if (!flatpak_verify_dbus_name (value, error)) + return FALSE; + + flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_OWN); + return TRUE; +} + +static gboolean +option_system_talk_name_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + if (!flatpak_verify_dbus_name (value, error)) + return FALSE; + + flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_TALK); + return TRUE; +} + +static gboolean +option_persist_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + FlatpakContext *context = data; + + flatpak_context_set_persistent (context, value); + return TRUE; +} + +static GOptionEntry context_options[] = { + { "share", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_share_cb, "Share with host", "SHARE" }, + { "unshare", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_unshare_cb, "Unshare with host", "SHARE" }, + { "socket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_socket_cb, "Expose socket to app", "SOCKET" }, + { "nosocket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nosocket_cb, "Don't expose socket to app", "SOCKET" }, + { "device", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_device_cb, "Expose device to app", "DEVICE" }, + { "nodevice", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nodevice_cb, "Don't expose device to app", "DEVICE" }, + { "filesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_filesystem_cb, "Expose filesystem to app (:ro for read-only)", "FILESYSTEM[:ro]" }, + { "nofilesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nofilesystem_cb, "Don't expose filesystem to app", "FILESYSTEM" }, + { "env", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_cb, "Set environment variable", "VAR=VALUE" }, + { "own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_own_name_cb, "Allow app to own name on the session bus", "DBUS_NAME" }, + { "talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_talk_name_cb, "Allow app to talk to name on the session bus", "DBUS_NAME" }, + { "system-own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_own_name_cb, "Allow app to own name on the system bus", "DBUS_NAME" }, + { "system-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_talk_name_cb, "Allow app to talk to name on the system bus", "DBUS_NAME" }, + { "persist", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_persist_cb, "Persist home directory directory", "FILENAME" }, + { NULL } +}; + +GOptionGroup * +flatpak_context_get_options (FlatpakContext *context) +{ + GOptionGroup *group; + + group = g_option_group_new ("environment", + "Runtime Environment", + "Runtime Environment", + context, + NULL); + + g_option_group_add_entries (group, context_options); + + return group; +} + +static const char * +parse_negated (const char *option, gboolean *negated) +{ + if (option[0] == '!') + { + option++; + *negated = TRUE; + } + else + { + *negated = FALSE; + } + return option; +} + +/* This is a merge, not a replace */ +gboolean +flatpak_context_load_metadata (FlatpakContext *context, + GKeyFile *metakey, + GError **error) +{ + gboolean remove; + int i; + + if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SHARED, NULL)) + { + g_auto(GStrv) shares = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SHARED, NULL, error); + if (shares == NULL) + return FALSE; + + for (i = 0; shares[i] != NULL; i++) + { + FlatpakContextShares share; + + share = flatpak_context_share_from_string (parse_negated (shares[i], &remove), error); + if (share == 0) + return FALSE; + if (remove) + flatpak_context_remove_shares (context, share); + else + flatpak_context_add_shares (context, share); + } + } + + if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SOCKETS, NULL)) + { + g_auto(GStrv) sockets = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SOCKETS, NULL, error); + if (sockets == NULL) + return FALSE; + + for (i = 0; sockets[i] != NULL; i++) + { + FlatpakContextSockets socket = flatpak_context_socket_from_string (parse_negated (sockets[i], &remove), error); + if (socket == 0) + return FALSE; + if (remove) + flatpak_context_remove_sockets (context, socket); + else + flatpak_context_add_sockets (context, socket); + } + } + + if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_DEVICES, NULL)) + { + g_auto(GStrv) devices = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_DEVICES, NULL, error); + if (devices == NULL) + return FALSE; + + + for (i = 0; devices[i] != NULL; i++) + { + FlatpakContextDevices device = flatpak_context_device_from_string (parse_negated (devices[i], &remove), error); + if (device == 0) + return FALSE; + if (remove) + flatpak_context_remove_devices (context, device); + else + flatpak_context_add_devices (context, device); + } + } + + if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_FILESYSTEMS, NULL)) + { + g_auto(GStrv) filesystems = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_FILESYSTEMS, NULL, error); + if (filesystems == NULL) + return FALSE; + + for (i = 0; filesystems[i] != NULL; i++) + { + const char *fs = parse_negated (filesystems[i], &remove); + if (!flatpak_context_verify_filesystem (fs, error)) + return FALSE; + if (remove) + flatpak_context_remove_filesystem (context, fs); + else + flatpak_context_add_filesystem (context, fs); + } + } + + if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_PERSISTENT, NULL)) + { + g_auto(GStrv) persistent = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_PERSISTENT, NULL, error); + if (persistent == NULL) + return FALSE; + + for (i = 0; persistent[i] != NULL; i++) + flatpak_context_set_persistent (context, persistent[i]); + } + + if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY)) + { + g_auto(GStrv) keys = NULL; + gsize i, keys_count; + + keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, &keys_count, NULL); + for (i = 0; i < keys_count; i++) + { + const char *key = keys[i]; + g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, key, NULL); + FlatpakPolicy policy; + + if (!flatpak_verify_dbus_name (key, error)) + return FALSE; + + policy = flatpak_policy_from_string (value, error); + if ((int) policy == -1) + return FALSE; + + flatpak_context_set_session_bus_policy (context, key, policy); + } + } + + if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY)) + { + g_auto(GStrv) keys = NULL; + gsize i, keys_count; + + keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, &keys_count, NULL); + for (i = 0; i < keys_count; i++) + { + const char *key = keys[i]; + g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, key, NULL); + FlatpakPolicy policy; + + if (!flatpak_verify_dbus_name (key, error)) + return FALSE; + + policy = flatpak_policy_from_string (value, error); + if ((int) policy == -1) + return FALSE; + + flatpak_context_set_system_bus_policy (context, key, policy); + } + } + + if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT)) + { + g_auto(GStrv) keys = NULL; + gsize i, keys_count; + + keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, &keys_count, NULL); + for (i = 0; i < keys_count; i++) + { + const char *key = keys[i]; + g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, key, NULL); + + flatpak_context_set_env_var (context, key, value); + } + } + + return TRUE; +} + +void +flatpak_context_save_metadata (FlatpakContext *context, + GKeyFile *metakey) +{ + g_auto(GStrv) shared = flatpak_context_shared_to_string (context->shares, context->shares_valid); + g_auto(GStrv) sockets = flatpak_context_sockets_to_string (context->sockets, context->sockets_valid); + g_auto(GStrv) devices = flatpak_context_devices_to_string (context->devices, context->devices_valid); + GHashTableIter iter; + gpointer key, value; + + if (shared[0] != NULL) + { + g_key_file_set_string_list (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SHARED, + (const char * const *) shared, g_strv_length (shared)); + } + else + { + g_key_file_remove_key (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SHARED, + NULL); + } + + if (sockets[0] != NULL) + { + g_key_file_set_string_list (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SOCKETS, + (const char * const *) sockets, g_strv_length (sockets)); + } + else + { + g_key_file_remove_key (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SOCKETS, + NULL); + } + + if (devices[0] != NULL) + { + g_key_file_set_string_list (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_DEVICES, + (const char * const *) devices, g_strv_length (devices)); + } + else + { + g_key_file_remove_key (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_DEVICES, + NULL); + } + + if (g_hash_table_size (context->filesystems) > 0) + { + g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func (g_free); + + g_hash_table_iter_init (&iter, context->filesystems); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); + + if (mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY) + g_ptr_array_add (array, g_strconcat (key, ":ro", NULL)); + else if (value != NULL) + g_ptr_array_add (array, g_strdup (key)); + } + + g_key_file_set_string_list (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_FILESYSTEMS, + (const char * const *) array->pdata, array->len); + } + else + { + g_key_file_remove_key (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_FILESYSTEMS, + NULL); + } + + if (g_hash_table_size (context->persistent) > 0) + { + g_autofree char **keys = (char **) g_hash_table_get_keys_as_array (context->persistent, NULL); + + g_key_file_set_string_list (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_PERSISTENT, + (const char * const *) keys, g_strv_length (keys)); + } + else + { + g_key_file_remove_key (metakey, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_PERSISTENT, + NULL); + } + + g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, NULL); + g_hash_table_iter_init (&iter, context->session_bus_policy); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + FlatpakPolicy policy = GPOINTER_TO_INT (value); + if (policy > 0) + g_key_file_set_string (metakey, + FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, + (char *) key, flatpak_policy_to_string (policy)); + } + + g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, NULL); + g_hash_table_iter_init (&iter, context->system_bus_policy); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + FlatpakPolicy policy = GPOINTER_TO_INT (value); + if (policy > 0) + g_key_file_set_string (metakey, + FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, + (char *) key, flatpak_policy_to_string (policy)); + } + + g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, NULL); + g_hash_table_iter_init (&iter, context->env_vars); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + g_key_file_set_string (metakey, + FLATPAK_METADATA_GROUP_ENVIRONMENT, + (char *) key, (char *) value); + } +} + +void +flatpak_context_allow_host_fs (FlatpakContext *context) +{ + flatpak_context_add_filesystem (context, "host"); +} + +static char * +extract_unix_path_from_dbus_address (const char *address) +{ + const char *path, *path_end; + + if (address == NULL) + return NULL; + + if (!g_str_has_prefix (address, "unix:")) + return NULL; + + path = strstr (address, "path="); + if (path == NULL) + return NULL; + path += strlen ("path="); + path_end = path; + while (*path_end != 0 && *path_end != ',') + path_end++; + + return g_strndup (path, path_end - path); +} + +#ifdef ENABLE_XAUTH +static gboolean +auth_streq (char *str, + char *au_str, + int au_len) +{ + return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0; +} + +static void +write_xauth (char *number, FILE *output) +{ + Xauth *xa, local_xa; + char *filename; + FILE *f; + struct utsname unames; + + if (uname (&unames)) + { + g_warning ("uname failed"); + return; + } + + filename = XauFileName (); + f = fopen (filename, "rb"); + if (f == NULL) + return; + + while (TRUE) + { + xa = XauReadAuth (f); + if (xa == NULL) + break; + if (xa->family == FamilyLocal && + auth_streq (unames.nodename, xa->address, xa->address_length) && + (xa->number == NULL || auth_streq (number, xa->number, xa->number_length))) + { + local_xa = *xa; + if (local_xa.number) + { + local_xa.number = "99"; + local_xa.number_length = 2; + } + + if (!XauWriteAuth (output, &local_xa)) + g_warning ("xauth write error"); + } + + XauDisposeAuth (xa); + } + + fclose (f); +} +#endif /* ENABLE_XAUTH */ + +static void +add_args (GPtrArray *argv_array, ...) +{ + va_list args; + const gchar *arg; + + va_start (args, argv_array); + while ((arg = va_arg (args, const gchar *))) + g_ptr_array_add (argv_array, g_strdup (arg)); + va_end (args); +} + +static int +create_tmp_fd (const char *contents, + gssize length, + GError **error) +{ + char template[] = "/tmp/tmp_fd_XXXXXX"; + int fd; + + if (length < 0) + length = strlen (contents); + + fd = g_mkstemp (template); + if (fd < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to create temporary file"); + return -1; + } + + if (unlink (template) != 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to unlink temporary file"); + close (fd); + return -1; + } + + while (length > 0) + { + gssize s; + + s = write (fd, contents, length); + + if (s < 0) + { + int saved_errno = errno; + if (saved_errno == EINTR) + continue; + + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), "Failed to write to temporary file"); + close (fd); + return -1; + } + + g_assert (s <= length); + + contents += s; + length -= s; + } + + lseek (fd, 0, SEEK_SET); + + return fd; +} + +static void +flatpak_run_add_x11_args (GPtrArray *argv_array, + char ***envp_p) +{ + char *x11_socket = NULL; + const char *display = g_getenv ("DISPLAY"); + + if (display && display[0] == ':' && g_ascii_isdigit (display[1])) + { + const char *display_nr = &display[1]; + const char *display_nr_end = display_nr; + g_autofree char *d = NULL; + g_autofree char *tmp_path = NULL; + + while (g_ascii_isdigit (*display_nr_end)) + display_nr_end++; + + d = g_strndup (display_nr, display_nr_end - display_nr); + x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", d); + + add_args (argv_array, + "--bind", x11_socket, "/tmp/.X11-unix/X99", + NULL); + *envp_p = g_environ_setenv (*envp_p, "DISPLAY", ":99.0", TRUE); + +#ifdef ENABLE_XAUTH + int fd; + fd = g_file_open_tmp ("xdg-app-xauth-XXXXXX", &tmp_path, NULL); + if (fd >= 0) + { + FILE *output = fdopen (fd, "wb"); + if (output != NULL) + { + int tmp_fd = dup (fd); + if (tmp_fd != -1) + { + g_autofree char *tmp_fd_str = g_strdup_printf ("%d", tmp_fd); + g_autofree char *dest = g_strdup_printf ("/run/user/%d/Xauthority", getuid ()); + + write_xauth (d, output); + add_args (argv_array, + "--bind-data", tmp_fd_str, dest, + NULL); + *envp_p = g_environ_setenv (*envp_p, "XAUTHORITY", dest, TRUE); + } + + fclose (output); + unlink (tmp_path); + + lseek (tmp_fd, 0, SEEK_SET); + } + else + { + close (fd); + } + } +#endif + } + else + { + *envp_p = g_environ_unsetenv (*envp_p, "DISPLAY"); + } + +} + +static void +flatpak_run_add_wayland_args (GPtrArray *argv_array, + char ***envp_p) +{ + g_autofree char *wayland_socket = g_build_filename (g_get_user_runtime_dir (), "wayland-0", NULL); + g_autofree char *sandbox_wayland_socket = g_strdup_printf ("/run/user/%d/wayland-0", getuid ()); + + if (g_file_test (wayland_socket, G_FILE_TEST_EXISTS)) + { + add_args (argv_array, + "--bind", wayland_socket, sandbox_wayland_socket, + NULL); + } +} + +static void +flatpak_run_add_pulseaudio_args (GPtrArray *argv_array, + char ***envp_p) +{ + char *pulseaudio_socket = g_build_filename (g_get_user_runtime_dir (), "pulse/native", NULL); + + *envp_p = g_environ_unsetenv (*envp_p, "PULSE_SERVER"); + if (g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS)) + { + gboolean share_shm = FALSE; /* TODO: When do we add this? */ + g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no"); + g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/pulse/native", getuid ()); + g_autofree char *pulse_server = g_strdup_printf ("unix:/run/user/%d/pulse/native", getuid ()); + g_autofree char *config_path = g_strdup_printf ("/run/user/%d/pulse/config", getuid ()); + int fd; + g_autofree char *fd_str = NULL; + + fd = create_tmp_fd (client_config, -1, NULL); + if (fd == -1) + return; + + fd_str = g_strdup_printf ("%d", fd); + + add_args (argv_array, + "--bind", pulseaudio_socket, sandbox_socket_path, + "--bind-data", fd_str, config_path, + NULL); + + *envp_p = g_environ_setenv (*envp_p, "PULSE_SERVER", pulse_server, TRUE); + *envp_p = g_environ_setenv (*envp_p, "PULSE_CLIENTCONFIG", config_path, TRUE); + } +} + +static char * +create_proxy_socket (char *template) +{ + g_autofree char *dir = g_build_filename (g_get_user_runtime_dir (), "bus-proxy", NULL); + g_autofree char *proxy_socket = g_build_filename (dir, template, NULL); + int fd; + + if (mkdir (dir, 0700) == -1 && errno != EEXIST) + return NULL; + + fd = g_mkstemp (proxy_socket); + if (fd == -1) + return NULL; + + close (fd); + + return g_steal_pointer (&proxy_socket); +} + +gboolean +flatpak_run_add_system_dbus_args (FlatpakContext *context, + char ***envp_p, + GPtrArray *argv_array, + GPtrArray *dbus_proxy_argv, + gboolean unrestricted) +{ + const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS"); + g_autofree char *real_dbus_address = NULL; + char *dbus_system_socket = NULL; + + if (dbus_address != NULL) + dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address); + else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS)) + dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket"); + + if (dbus_system_socket != NULL && unrestricted) + { + add_args (argv_array, + "--bind", dbus_system_socket, "/run/dbus/system_bus_socket", + NULL); + *envp_p = g_environ_setenv (*envp_p, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE); + + return TRUE; + } + else if (dbus_proxy_argv && + g_hash_table_size (context->system_bus_policy) > 0) + { + g_autofree char *proxy_socket = create_proxy_socket ("system-bus-proxy-XXXXXX"); + + if (proxy_socket == NULL) + return FALSE; + + if (dbus_address) + real_dbus_address = g_strdup (dbus_address); + else + real_dbus_address = g_strdup_printf ("unix:path=%s", dbus_system_socket); + + g_ptr_array_add (dbus_proxy_argv, g_strdup (real_dbus_address)); + g_ptr_array_add (dbus_proxy_argv, g_strdup (proxy_socket)); + + + add_args (argv_array, + "--bind", proxy_socket, "/run/dbus/system_bus_socket", + NULL); + *envp_p = g_environ_setenv (*envp_p, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE); + + return TRUE; + } + return FALSE; +} + +gboolean +flatpak_run_add_session_dbus_args (GPtrArray *argv_array, + char ***envp_p, + GPtrArray *dbus_proxy_argv, + gboolean unrestricted) +{ + const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS"); + char *dbus_session_socket = NULL; + g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/bus", getuid ()); + g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/bus", getuid ()); + + if (dbus_address == NULL) + return FALSE; + + dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address); + if (dbus_session_socket != NULL && unrestricted) + { + + add_args (argv_array, + "--bind", dbus_session_socket, sandbox_socket_path, + NULL); + *envp_p = g_environ_setenv (*envp_p, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE); + + return TRUE; + } + else if (dbus_proxy_argv && dbus_address != NULL) + { + g_autofree char *proxy_socket = create_proxy_socket ("session-bus-proxy-XXXXXX"); + + if (proxy_socket == NULL) + return FALSE; + + g_ptr_array_add (dbus_proxy_argv, g_strdup (dbus_address)); + g_ptr_array_add (dbus_proxy_argv, g_strdup (proxy_socket)); + + add_args (argv_array, + "--bind", proxy_socket, sandbox_socket_path, + NULL); + *envp_p = g_environ_setenv (*envp_p, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE); + + return TRUE; + } + + return FALSE; +} + +static void +flatpak_add_bus_filters (GPtrArray *dbus_proxy_argv, + GHashTable *ht, + const char *app_id, + FlatpakContext *context) +{ + GHashTableIter iter; + gpointer key, value; + + g_ptr_array_add (dbus_proxy_argv, g_strdup ("--filter")); + if (app_id) + { + g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--own=%s", app_id)); + g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--own=%s.*", app_id)); + } + + g_hash_table_iter_init (&iter, ht); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + FlatpakPolicy policy = GPOINTER_TO_INT (value); + + if (policy > 0) + g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--%s=%s", flatpak_policy_to_string (policy), (char *) key)); + } +} + +gboolean +flatpak_run_add_extension_args (GPtrArray *argv_array, + GKeyFile *metakey, + const char *full_ref, + GCancellable *cancellable, + GError **error) +{ + g_auto(GStrv) parts = NULL; + gboolean is_app; + GList *extensions, *l; + + parts = g_strsplit (full_ref, "/", 0); + if (g_strv_length (parts) != 4) + return flatpak_fail (error, "Failed to determine parts from ref: %s", full_ref); + + is_app = strcmp (parts[0], "app") == 0; + + extensions = flatpak_list_extensions (metakey, + parts[2], parts[3]); + + for (l = extensions; l != NULL; l = l->next) + { + FlatpakExtension *ext = l->data; + g_autoptr(GFile) deploy = NULL; + + deploy = flatpak_find_deploy_dir_for_ref (ext->ref, cancellable, NULL); + if (deploy != NULL) + { + g_autoptr(GFile) files = g_file_get_child (deploy, "files"); + g_autofree char *full_directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL); + g_autofree char *ref = g_build_filename (full_directory, ".ref", NULL); + + add_args (argv_array, + "--bind", gs_file_get_path_cached (files), full_directory, + "--lock-file", ref, + NULL); + } + } + + g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free); + + return TRUE; +} + +static void +add_file_arg (GPtrArray *argv_array, + FlatpakFilesystemMode mode, + const char *path) +{ + struct stat st; + + if (stat (path, &st) != 0) + return; + + if (S_ISDIR (st.st_mode) || + S_ISREG (st.st_mode)) + { + add_args (argv_array, + (mode == FLATPAK_FILESYSTEM_MODE_READ_WRITE) ? "--bind" : "--ro-bind", + path, path, NULL); + } +} + +void +flatpak_run_add_environment_args (GPtrArray *argv_array, + char ***envp_p, + GPtrArray *session_bus_proxy_argv, + GPtrArray *system_bus_proxy_argv, + const char *app_id, + FlatpakContext *context, + GFile *app_id_dir) +{ + GHashTableIter iter; + gpointer key, value; + gboolean unrestricted_session_bus; + gboolean unrestricted_system_bus; + gboolean home_access = FALSE; + GString *xdg_dirs_conf = NULL; + FlatpakFilesystemMode fs_mode, home_mode; + + if ((context->shares & FLATPAK_CONTEXT_SHARED_IPC) == 0) + { + g_debug ("Disallowing ipc access"); + add_args (argv_array, "--unshare-ipc", NULL); + } + + if ((context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0) + { + g_debug ("Disallowing network access"); + add_args (argv_array, "--unshare-net", NULL); + } + + if (context->devices & FLATPAK_CONTEXT_DEVICE_DRI) + { + g_debug ("Allowing dri access"); + if (g_file_test ("/dev/dri", G_FILE_TEST_IS_DIR)) + add_args (argv_array, "--dev-bind", "/dev/dri", "/dev/dri", NULL); + if (g_file_test ("/dev/nvidiactl", G_FILE_TEST_EXISTS)) + { + add_args (argv_array, + "--dev-bind", "/dev/nvidiactl", "/dev/nvidiactl", + "--dev-bind", "/dev/nvidia0", "/dev/nvidia0", + NULL); + } + } + + fs_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "host"); + if (fs_mode != 0) + { + DIR *dir; + struct dirent *dirent; + + g_debug ("Allowing host-fs access"); + home_access = TRUE; + + /* Bind mount most dirs in / into the new root */ + dir = opendir ("/"); + if (dir != NULL) + { + while ((dirent = readdir (dir))) + { + g_autofree char *path = NULL; + + if (g_strv_contains (dont_mount_in_root, dirent->d_name)) + continue; + + path = g_build_filename ("/", dirent->d_name, NULL); + add_file_arg (argv_array, fs_mode, path); + } + } + add_file_arg (argv_array, fs_mode, "/run/media"); + } + + home_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "home"); + if (home_mode != 0) + { + g_debug ("Allowing homedir access"); + home_access = TRUE; + + add_file_arg (argv_array, MAX (home_mode, fs_mode), g_get_home_dir ()); + } + + if (!home_access) + { + /* Enable persistant mapping only if no access to real home dir */ + + g_hash_table_iter_init (&iter, context->persistent); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + const char *persist = key; + g_autofree char *src = g_build_filename (g_get_home_dir (), ".var/app", app_id, persist, NULL); + g_autofree char *dest = g_build_filename (g_get_home_dir (), persist, NULL); + + g_mkdir_with_parents (src, 0755); + + add_args (argv_array, + "--bind", src, dest, + NULL); + } + } + + g_hash_table_iter_init (&iter, context->filesystems); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const char *filesystem = key; + FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); + + if (value == NULL || + strcmp (filesystem, "host") == 0 || + strcmp (filesystem, "home") == 0) + continue; + + if (g_str_has_prefix (filesystem, "xdg-")) + { + const char *path, *rest = NULL; + const char *config_key = NULL; + g_autofree char *subpath = NULL; + + if (!get_user_dir_from_string (filesystem, &config_key, &rest, &path)) + { + g_warning ("Unsupported xdg dir %s\n", filesystem); + continue; + } + + if (strcmp (path, g_get_home_dir ()) == 0) + { + /* xdg-user-dirs sets disabled dirs to $HOME, and its in general not a good + idea to set full access to $HOME other than explicitly, so we ignore + these */ + g_debug ("Xdg dir %s is $HOME (i.e. disabled), ignoring\n", filesystem); + continue; + } + + subpath = g_build_filename (path, rest, NULL); + if (g_file_test (subpath, G_FILE_TEST_EXISTS)) + { + if (xdg_dirs_conf == NULL) + xdg_dirs_conf = g_string_new (""); + + if (config_key) + g_string_append_printf (xdg_dirs_conf, "%s=\"%s\"\n", + config_key, path); + + add_file_arg (argv_array, mode, subpath); + } + } + else if (g_str_has_prefix (filesystem, "~/")) + { + g_autofree char *path = NULL; + + path = g_build_filename (g_get_home_dir (), filesystem + 2, NULL); + if (g_file_test (path, G_FILE_TEST_EXISTS)) + add_file_arg (argv_array, mode, path); + } + else if (g_str_has_prefix (filesystem, "/")) + { + if (g_file_test (filesystem, G_FILE_TEST_EXISTS)) + add_file_arg (argv_array, mode, filesystem); + } + else + { + g_warning ("Unexpected filesystem arg %s\n", filesystem); + } + } + + /* Do this after setting up everything in the home dir, so its not overwritten */ + if (app_id_dir) + add_args (argv_array, + "--bind", gs_file_get_path_cached (app_id_dir), gs_file_get_path_cached (app_id_dir), + NULL); + + if (home_access && app_id_dir != NULL) + { + g_autofree char *src_path = g_build_filename (g_get_user_config_dir (), + "user-dirs.dirs", + NULL); + g_autofree char *path = g_build_filename (gs_file_get_path_cached (app_id_dir), + "config/user-dirs.dirs", NULL); + add_args (argv_array, + "--ro-bind", src_path, path, + NULL); + } + else if (xdg_dirs_conf != NULL && app_id_dir != NULL) + { + g_autofree char *tmp_path = NULL; + g_autofree char *path = NULL; + int fd; + + fd = g_file_open_tmp ("xdg-app-user-dir-XXXXXX.dirs", &tmp_path, NULL); + if (fd >= 0) + { + close (fd); + if (g_file_set_contents (tmp_path, xdg_dirs_conf->str, xdg_dirs_conf->len, NULL)) + { + int tmp_fd = open (tmp_path, O_RDONLY); + unlink (tmp_path); + if (tmp_fd) + { + g_autofree char *tmp_fd_str = g_strdup_printf ("%d", tmp_fd); + path = g_build_filename (gs_file_get_path_cached (app_id_dir), + "config/user-dirs.dirs", NULL); + + add_args (argv_array, "--file", tmp_fd_str, path, NULL); + } + } + } + g_string_free (xdg_dirs_conf, TRUE); + } + + if (context->sockets & FLATPAK_CONTEXT_SOCKET_X11) + { + g_debug ("Allowing x11 access"); + flatpak_run_add_x11_args (argv_array, envp_p); + } + + if (context->sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND) + { + g_debug ("Allowing wayland access"); + flatpak_run_add_wayland_args (argv_array, envp_p); + } + + if (context->sockets & FLATPAK_CONTEXT_SOCKET_PULSEAUDIO) + { + g_debug ("Allowing pulseaudio access"); + flatpak_run_add_pulseaudio_args (argv_array, envp_p); + } + + unrestricted_session_bus = (context->sockets & FLATPAK_CONTEXT_SOCKET_SESSION_BUS) != 0; + if (unrestricted_session_bus) + g_debug ("Allowing session-dbus access"); + if (flatpak_run_add_session_dbus_args (argv_array, envp_p, session_bus_proxy_argv, unrestricted_session_bus) && + !unrestricted_session_bus && session_bus_proxy_argv) + flatpak_add_bus_filters (session_bus_proxy_argv, context->session_bus_policy, app_id, context); + + unrestricted_system_bus = (context->sockets & FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS) != 0; + if (unrestricted_system_bus) + g_debug ("Allowing system-dbus access"); + if (flatpak_run_add_system_dbus_args (context, envp_p, argv_array, system_bus_proxy_argv, + unrestricted_system_bus) && + !unrestricted_system_bus && system_bus_proxy_argv) + flatpak_add_bus_filters (system_bus_proxy_argv, context->system_bus_policy, NULL, context); + +} + +static const struct {const char *env; + const char *val; +} default_exports[] = { + {"PATH", "/app/bin:/usr/bin"}, + {"LD_LIBRARY_PATH", "/app/lib"}, + {"XDG_CONFIG_DIRS", "/app/etc/xdg:/etc/xdg"}, + {"XDG_DATA_DIRS", "/app/share:/usr/share"}, + {"SHELL", "/bin/sh"}, +}; + +static const struct {const char *env; + const char *val; +} devel_exports[] = { + {"ACLOCAL_PATH", "/app/share/aclocal"}, + {"C_INCLUDE_PATH", "/app/include"}, + {"CPLUS_INCLUDE_PATH", "/app/include"}, + {"LDFLAGS", "-L/app/lib "}, + {"PKG_CONFIG_PATH", "/app/lib/pkgconfig:/app/share/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"}, + {"LC_ALL", "en_US.utf8"}, +}; + +char ** +flatpak_run_get_minimal_env (gboolean devel) +{ + GPtrArray *env_array; + static const char * const copy[] = { + "PWD", + "GDMSESSION", + "XDG_CURRENT_DESKTOP", + "XDG_SESSION_DESKTOP", + "DESKTOP_SESSION", + "EMAIL_ADDRESS", + "HOME", + "HOSTNAME", + "LOGNAME", + "REAL_NAME", + "TERM", + "USER", + "USERNAME", + }; + static const char * const copy_nodevel[] = { + "LANG", + "LANGUAGE", + "LC_ALL", + "LC_ADDRESS", + "LC_COLLATE", + "LC_CTYPE", + "LC_IDENTIFICATION", + "LC_MEASUREMENT", + "LC_MESSAGES", + "LC_MONETARY", + "LC_NAME", + "LC_NUMERIC", + "LC_PAPER", + "LC_TELEPHONE", + "LC_TIME", + }; + int i; + + env_array = g_ptr_array_new_with_free_func (g_free); + + for (i = 0; i < G_N_ELEMENTS (default_exports); i++) + g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", default_exports[i].env, default_exports[i].val)); + + if (devel) + { + for (i = 0; i < G_N_ELEMENTS(devel_exports); i++) + g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", devel_exports[i].env, devel_exports[i].val)); + } + + for (i = 0; i < G_N_ELEMENTS (copy); i++) + { + const char *current = g_getenv (copy[i]); + if (current) + g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy[i], current)); + } + + if (!devel) + { + for (i = 0; i < G_N_ELEMENTS (copy_nodevel); i++) + { + const char *current = g_getenv (copy_nodevel[i]); + if (current) + g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy_nodevel[i], current)); + } + } + + g_ptr_array_add (env_array, NULL); + return (char **) g_ptr_array_free (env_array, FALSE); +} + +char ** +flatpak_run_apply_env_default (char **envp) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (default_exports); i++) + envp = g_environ_setenv (envp, default_exports[i].env, default_exports[i].val, TRUE); + + return envp; +} + +char ** +flatpak_run_apply_env_appid (char **envp, + GFile *app_dir) +{ + g_autoptr(GFile) app_dir_data = NULL; + g_autoptr(GFile) app_dir_config = NULL; + g_autoptr(GFile) app_dir_cache = NULL; + + app_dir_data = g_file_get_child (app_dir, "data"); + app_dir_config = g_file_get_child (app_dir, "config"); + app_dir_cache = g_file_get_child (app_dir, "cache"); + envp = g_environ_setenv (envp, "XDG_DATA_HOME", gs_file_get_path_cached (app_dir_data), TRUE); + envp = g_environ_setenv (envp, "XDG_CONFIG_HOME", gs_file_get_path_cached (app_dir_config), TRUE); + envp = g_environ_setenv (envp, "XDG_CACHE_HOME", gs_file_get_path_cached (app_dir_cache), TRUE); + + return envp; +} + +char ** +flatpak_run_apply_env_vars (char **envp, FlatpakContext *context) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, context->env_vars); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const char *var = key; + const char *val = value; + + if (val && val[0] != 0) + envp = g_environ_setenv (envp, var, val, TRUE); + else + envp = g_environ_unsetenv (envp, var); + } + + return envp; +} + +GFile * +flatpak_get_data_dir (const char *app_id) +{ + g_autoptr(GFile) home = g_file_new_for_path (g_get_home_dir ()); + g_autoptr(GFile) var_app = g_file_resolve_relative_path (home, ".var/app"); + + return g_file_get_child (var_app, app_id); +} + +GFile * +flatpak_ensure_data_dir (const char *app_id, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) dir = flatpak_get_data_dir (app_id); + g_autoptr(GFile) data_dir = g_file_get_child (dir, "data"); + g_autoptr(GFile) cache_dir = g_file_get_child (dir, "cache"); + g_autoptr(GFile) config_dir = g_file_get_child (dir, "config"); + + if (!gs_file_ensure_directory (data_dir, TRUE, cancellable, error)) + return NULL; + + if (!gs_file_ensure_directory (cache_dir, TRUE, cancellable, error)) + return NULL; + + if (!gs_file_ensure_directory (config_dir, TRUE, cancellable, error)) + return NULL; + + return g_object_ref (dir); +} + +struct JobData +{ + char *job; + GMainLoop *main_loop; +}; + +static void +job_removed_cb (SystemdManager *manager, + guint32 id, + char *job, + char *unit, + char *result, + struct JobData *data) +{ + if (strcmp (job, data->job) == 0) + g_main_loop_quit (data->main_loop); +} + +gboolean +flatpak_run_in_transient_unit (const char *appid, GError **error) +{ + g_autoptr(GDBusConnection) conn = NULL; + g_autofree char *path = NULL; + g_autofree char *address = NULL; + g_autofree char *name = NULL; + g_autofree char *job = NULL; + SystemdManager *manager = NULL; + GVariantBuilder builder; + GVariant *properties = NULL; + GVariant *aux = NULL; + guint32 pid; + GMainContext *main_context = NULL; + GMainLoop *main_loop = NULL; + struct JobData data; + gboolean res = FALSE; + + path = g_strdup_printf ("/run/user/%d/systemd/private", getuid ()); + + if (!g_file_test (path, G_FILE_TEST_EXISTS)) + return flatpak_fail (error, + "No systemd user session available, sandboxing not available"); + + main_context = g_main_context_new (); + main_loop = g_main_loop_new (main_context, FALSE); + + g_main_context_push_thread_default (main_context); + + address = g_strconcat ("unix:path=", path, NULL); + + conn = g_dbus_connection_new_for_address_sync (address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + NULL, + NULL, error); + if (!conn) + goto out; + + manager = systemd_manager_proxy_new_sync (conn, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + NULL, + "/org/freedesktop/systemd1", + NULL, error); + if (!manager) + goto out; + + name = g_strdup_printf ("xdg-app-%s-%d.scope", appid, getpid ()); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sv)")); + + pid = getpid (); + g_variant_builder_add (&builder, "(sv)", + "PIDs", + g_variant_new_fixed_array (G_VARIANT_TYPE ("u"), + &pid, 1, sizeof (guint32)) + ); + + properties = g_variant_builder_end (&builder); + + aux = g_variant_new_array (G_VARIANT_TYPE ("(sa(sv))"), NULL, 0); + + if (!systemd_manager_call_start_transient_unit_sync (manager, + name, + "fail", + properties, + aux, + &job, + NULL, + error)) + goto out; + + data.job = job; + data.main_loop = main_loop; + g_signal_connect (manager, "job-removed", G_CALLBACK (job_removed_cb), &data); + + g_main_loop_run (main_loop); + + res = TRUE; + +out: + if (main_context) + { + g_main_context_pop_thread_default (main_context); + g_main_context_unref (main_context); + } + if (main_loop) + g_main_loop_unref (main_loop); + if (manager) + g_object_unref (manager); + + return res; +} + +static void +add_font_path_args (GPtrArray *argv_array) +{ + g_autoptr(GFile) home = NULL; + g_autoptr(GFile) user_font1 = NULL; + g_autoptr(GFile) user_font2 = NULL; + + add_args (argv_array, + "--bind", SYSTEM_FONTS_DIR, "/run/host/fonts", + NULL); + + home = g_file_new_for_path (g_get_home_dir ()); + user_font1 = g_file_resolve_relative_path (home, ".local/share/fonts"); + user_font2 = g_file_resolve_relative_path (home, ".fonts"); + + if (g_file_query_exists (user_font1, NULL)) + { + add_args (argv_array, + "--bind", gs_file_get_path_cached (user_font1), "/run/host/user-fonts", + NULL); + } + else if (g_file_query_exists (user_font2, NULL)) + { + add_args (argv_array, + "--bind", gs_file_get_path_cached (user_font2), "/run/host/user-fonts", + NULL); + } +} + +static void +add_default_permissions (FlatpakContext *app_context) +{ + flatpak_context_set_session_bus_policy (app_context, + "org.freedesktop.portal.Documents", + FLATPAK_POLICY_TALK); +} + +static FlatpakContext * +compute_permissions (GKeyFile *app_metadata, + GKeyFile *runtime_metadata, + GError **error) +{ + g_autoptr(FlatpakContext) app_context = NULL; + + app_context = flatpak_context_new (); + + add_default_permissions (app_context); + + if (!flatpak_context_load_metadata (app_context, runtime_metadata, error)) + return NULL; + + if (!flatpak_context_load_metadata (app_context, app_metadata, error)) + return NULL; + + return g_steal_pointer (&app_context); +} + +static gboolean +add_app_info_args (GPtrArray *argv_array, + FlatpakDeploy *deploy, + const char *app_id, + const char *runtime_ref, + FlatpakContext *final_app_context, + GError **error) +{ + g_autofree char *tmp_path = NULL; + int fd; + + fd = g_file_open_tmp ("xdg-app-context-XXXXXX", &tmp_path, NULL); + if (fd >= 0) + { + g_autoptr(GKeyFile) keyfile = NULL; + g_autoptr(GFile) files = NULL; + g_autofree char *files_path = NULL; + g_autofree char *fd_str = NULL; + g_autofree char *dest = g_strdup_printf ("/run/user/%d/xdg-app-info", getuid ()); + + close (fd); + + keyfile = g_key_file_new (); + + g_key_file_set_string (keyfile, "Application", "name", app_id); + g_key_file_set_string (keyfile, "Application", "runtime", runtime_ref); + + files = flatpak_deploy_get_files (deploy); + files_path = g_file_get_path (files); + + g_key_file_set_string (keyfile, "Application", "app-path", files_path); + + flatpak_context_save_metadata (final_app_context, keyfile); + + if (!g_key_file_save_to_file (keyfile, tmp_path, error)) + return FALSE; + + fd = open (tmp_path, O_RDONLY); + if (fd == -1) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to open temp file"); + return FALSE; + } + unlink (tmp_path); + fd_str = g_strdup_printf ("%d", fd); + + add_args (argv_array, "--file", fd_str, dest, NULL); + } + + return TRUE; +} + +static void +add_monitor_path_args (GPtrArray *argv_array, + char ***envp_p) +{ + g_autoptr(AutoXdgAppSessionHelper) session_helper = NULL; + g_autofree char *monitor_path = NULL; + + session_helper = + xdg_app_session_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, + "org.freedesktop.Flatpak", + "/org/freedesktop/Flatpak/SessionHelper", + NULL, NULL); + if (session_helper && + xdg_app_session_helper_call_request_monitor_sync (session_helper, + &monitor_path, + NULL, NULL)) + { + add_args (argv_array, + "--bind", monitor_path, "/run/host/monitor", + NULL); + add_args (argv_array, + "--symlink", "/run/host/monitor/localtime", "/etc/localtime", + NULL); + } + else + { + char localtime[PATH_MAX + 1]; + ssize_t symlink_size; + + add_args (argv_array, + "--bind", "/etc/resolv.conf", "/run/host/monitor/resolv.conf", + NULL); + + symlink_size = readlink ("/etc/localtime", localtime, sizeof (localtime) - 1); + if (symlink_size > 0) + { + localtime[symlink_size] = 0; + add_args (argv_array, + "--symlink", localtime, "/etc/localtime", + NULL); + } + else + { + add_args (argv_array, + "--bind", "/etc/localtime", "/etc/localtime", + NULL); + } + } +} + +static void +add_document_portal_args (GPtrArray *argv_array, + const char *app_id) +{ + g_autoptr(GDBusConnection) session_bus = NULL; + g_autofree char *doc_mount_path = NULL; + + session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + if (session_bus) + { + g_autoptr(GError) local_error = NULL; + g_autoptr(GDBusMessage) reply = NULL; + g_autoptr(GDBusMessage) msg = + g_dbus_message_new_method_call ("org.freedesktop.portal.Documents", + "/org/freedesktop/portal/documents", + "org.freedesktop.portal.Documents", + "GetMountPoint"); + g_dbus_message_set_body (msg, g_variant_new ("()")); + reply = + g_dbus_connection_send_message_with_reply_sync (session_bus, msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + 30000, + NULL, + NULL, + NULL); + if (reply) + { + if (g_dbus_message_to_gerror (reply, &local_error)) + { + g_warning ("Can't get document portal: %s\n", local_error->message); + } + else + { + g_autofree char *src_path = NULL; + g_autofree char *dst_path = NULL; + g_variant_get (g_dbus_message_get_body (reply), + "(^ay)", &doc_mount_path); + + src_path = g_strdup_printf ("%s/by-app/%s", + doc_mount_path, app_id); + dst_path = g_strdup_printf ("/run/user/%d/doc", getuid ()); + add_args (argv_array, "--bind", src_path, dst_path, NULL); + } + } + } +} + +static void +dbus_spawn_child_setup (gpointer user_data) +{ + int fd = GPOINTER_TO_INT (user_data); + + fcntl (fd, F_SETFD, 0); +} + +static gboolean +add_dbus_proxy_args (GPtrArray *argv_array, + GPtrArray *dbus_proxy_argv, + gboolean enable_logging, + int sync_fds[2], + GError **error) +{ + char x = 'x'; + const char *proxy; + + if (dbus_proxy_argv->len == 0) + return TRUE; + + if (sync_fds[0] == -1) + { + g_autofree char *fd_str = NULL; + + if (pipe (sync_fds) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Unable to create sync pipe"); + return FALSE; + } + + fd_str = g_strdup_printf ("%d", sync_fds[0]); + add_args (argv_array, "--sync-fd", fd_str, NULL); + } + + proxy = g_getenv ("FLATPAK_DBUSPROXY"); + if (proxy == NULL) + proxy = DBUSPROXY; + + g_ptr_array_insert (dbus_proxy_argv, 0, g_strdup (proxy)); + g_ptr_array_insert (dbus_proxy_argv, 1, g_strdup_printf ("--fd=%d", sync_fds[1])); + if (enable_logging) + g_ptr_array_insert (dbus_proxy_argv, 2, g_strdup ("--log")); + + g_ptr_array_add (dbus_proxy_argv, NULL); /* NULL terminate */ + + if (!g_spawn_async (NULL, + (char **) dbus_proxy_argv->pdata, + NULL, + G_SPAWN_SEARCH_PATH, + dbus_spawn_child_setup, + GINT_TO_POINTER (sync_fds[1]), + NULL, error)) + { + close (sync_fds[0]); + close (sync_fds[1]); + return FALSE; + } + + /* Sync with proxy, i.e. wait until its listening on the sockets */ + if (read (sync_fds[0], &x, 1) != 1) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to sync with dbus proxy"); + + close (sync_fds[0]); + close (sync_fds[1]); + return FALSE; + } + + return TRUE; +} + +#ifdef ENABLE_SECCOMP +static inline void +cleanup_seccomp (void *p) +{ + scmp_filter_ctx *pp = (scmp_filter_ctx *) p; + + if (*pp) + seccomp_release (*pp); +} + +static gboolean +setup_seccomp (GPtrArray *argv_array, + const char *arch, + gboolean devel, + GError **error) +{ + __attribute__((cleanup (cleanup_seccomp))) scmp_filter_ctx seccomp = NULL; + + /**** BEGIN NOTE ON CODE SHARING + * + * There are today a number of different Linux container + * implementations. That will likely continue for long into the + * future. But we can still try to share code, and it's important + * to do so because it affects what library and application writers + * can do, and we should support code portability between different + * container tools. + * + * This syscall blacklist is copied from xdg-app, which was in turn + * clearly influenced by the Sandstorm.io blacklist. + * + * If you make any changes here, I suggest sending the changes along + * to other sandbox maintainers. Using the libseccomp list is also + * an appropriate venue: + * https://groups.google.com/forum/#!topic/libseccomp + * + * A non-exhaustive list of links to container tooling that might + * want to share this blacklist: + * + * https://github.com/sandstorm-io/sandstorm + * in src/sandstorm/supervisor.c++ + * http://cgit.freedesktop.org/xdg-app/xdg-app/ + * in lib/xdg-app-helper.c + * https://git.gnome.org/browse/linux-user-chroot + * in src/setup-seccomp.c + * + **** END NOTE ON CODE SHARING + */ + struct + { + int scall; + struct scmp_arg_cmp *arg; + } syscall_blacklist[] = { + /* Block dmesg */ + {SCMP_SYS (syslog)}, + /* Useless old syscall */ + {SCMP_SYS (uselib)}, + /* Don't allow you to switch to bsd emulation or whatnot */ + {SCMP_SYS (personality)}, + /* Don't allow disabling accounting */ + {SCMP_SYS (acct)}, + /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a + historic source of interesting information leaks. */ + {SCMP_SYS (modify_ldt)}, + /* Don't allow reading current quota use */ + {SCMP_SYS (quotactl)}, + + /* Scary VM/NUMA ops */ + {SCMP_SYS (move_pages)}, + {SCMP_SYS (mbind)}, + {SCMP_SYS (get_mempolicy)}, + {SCMP_SYS (set_mempolicy)}, + {SCMP_SYS (migrate_pages)}, + + /* Don't allow subnamespace setups: */ + {SCMP_SYS (unshare)}, + {SCMP_SYS (mount)}, + {SCMP_SYS (pivot_root)}, + {SCMP_SYS (clone), &SCMP_A0 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)}, + }; + + struct + { + int scall; + struct scmp_arg_cmp *arg; + } syscall_nondevel_blacklist[] = { + /* Profiling operations; we expect these to be done by tools from outside + * the sandbox. In particular perf has been the source of many CVEs. + */ + {SCMP_SYS (perf_event_open)}, + {SCMP_SYS (ptrace)} + }; + /* Blacklist all but unix, inet, inet6 and netlink */ + int socket_family_blacklist[] = { + AF_AX25, + AF_IPX, + AF_APPLETALK, + AF_NETROM, + AF_BRIDGE, + AF_ATMPVC, + AF_X25, + AF_ROSE, + AF_DECnet, + AF_NETBEUI, + AF_SECURITY, + AF_KEY, + AF_NETLINK + 1, /* Last gets CMP_GE, so order is important */ + }; + int i, r; + glnx_fd_close int fd = -1; + g_autofree char *fd_str = NULL; + g_autofree char *path = NULL; + + seccomp = seccomp_init (SCMP_ACT_ALLOW); + if (!seccomp) + return flatpak_fail (error, "Initialize seccomp failed"); + + if (arch != NULL) + { + uint32_t arch_id = 0; + + if (strcmp (arch, "i386") == 0) + arch_id = SCMP_ARCH_X86; + else if (strcmp (arch, "x86_64") == 0) + arch_id = SCMP_ARCH_X86_64; + + /* We only really need to handle arches on multiarch systems. + * If only one arch is supported the default is fine */ + if (arch_id != 0) + { + /* This *adds* the target arch, instead of replacing the + native one. This is not ideal, because we'd like to only + allow the target arch, but we can't really disallow the + native arch at this point, because then xdg-app-helper + couldn't continue runnning. */ + r = seccomp_arch_add (seccomp, arch_id); + if (r < 0 && r != -EEXIST) + return flatpak_fail (error, "Failed to add architecture to seccomp filter"); + } + } + + /* Add in all possible secondary archs we are aware of that + * this kernel might support. */ +#if defined(__i386__) || defined(__x86_64__) + r = seccomp_arch_add (seccomp, SCMP_ARCH_X86); + if (r < 0 && r != -EEXIST) + return flatpak_fail (error, "Failed to add x86 architecture to seccomp filter"); + + r = seccomp_arch_add (seccomp, SCMP_ARCH_X86_64); + if (r < 0 && r != -EEXIST) + return flatpak_fail (error, "Failed to add x86_64 architecture to seccomp filter"); + + r = seccomp_arch_add (seccomp, SCMP_ARCH_X32); + if (r < 0 && r != -EEXIST) + return flatpak_fail (error, "Failed to add x32 architecture to seccomp filter"); +#endif + + /* TODO: Should we filter the kernel keyring syscalls in some way? + * We do want them to be used by desktop apps, but they could also perhaps + * leak system stuff or secrets from other apps. + */ + + for (i = 0; i < G_N_ELEMENTS (syscall_blacklist); i++) + { + int scall = syscall_blacklist[i].scall; + if (syscall_blacklist[i].arg) + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_blacklist[i].arg); + else + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); + if (r < 0 && r == -EFAULT /* unknown syscall */) + return flatpak_fail (error, "Failed to block syscall %d", scall); + } + + if (!devel) + { + for (i = 0; i < G_N_ELEMENTS (syscall_nondevel_blacklist); i++) + { + int scall = syscall_nondevel_blacklist[i].scall; + if (syscall_nondevel_blacklist[i].arg) + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_nondevel_blacklist[i].arg); + else + r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); + + if (r < 0 && r == -EFAULT /* unknown syscall */) + return flatpak_fail (error, "Failed to block syscall %d", scall); + } + } + + /* Socket filtering doesn't work on e.g. i386, so ignore failures here + * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing + * something else: https://github.com/seccomp/libseccomp/issues/8 */ + for (i = 0; i < G_N_ELEMENTS (socket_family_blacklist); i++) + { + int family = socket_family_blacklist[i]; + if (i == G_N_ELEMENTS (socket_family_blacklist) - 1) + r = seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_GE, family)); + else + r = seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_EQ, family)); + } + + fd = g_file_open_tmp ("xdg-app-seccomp-XXXXXX", &path, error); + if (fd == -1) + return FALSE; + + unlink (path); + + if (seccomp_export_bpf (seccomp, fd) != 0) + return flatpak_fail (error, "Failed to export bpf"); + + lseek (fd, 0, SEEK_SET); + + fd_str = g_strdup_printf ("%d", fd); + + add_args (argv_array, + "--seccomp", fd_str, + NULL); + + fd = -1; /* Don't close on success */ + + return TRUE; +} +#endif + +gboolean +flatpak_run_setup_base_argv (GPtrArray *argv_array, + GFile *runtime_files, + GFile *app_id_dir, + const char *arch, + FlatpakRunFlags flags, + GError **error) +{ + const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"}; + g_autofree char *run_dir = g_strdup_printf ("/run/user/%d", getuid ()); + int i; + int passwd_fd = -1; + g_autofree char *passwd_fd_str = NULL; + g_autofree char *passwd_contents = NULL; + int group_fd = -1; + g_autofree char *group_fd_str = NULL; + g_autofree char *group_contents = NULL; + struct group *g = getgrgid (getgid ()); + + g_autoptr(GFile) etc = NULL; + + passwd_contents = g_strdup_printf ("%s:x:%d:%d:%s:%s:%s\n" + "nfsnobody:x:65534:65534:Unmapped user:/:/sbin/nologin\n", + g_get_user_name (), + getuid (), getgid (), + g_get_real_name (), + g_get_home_dir (), + DEFAULT_SHELL); + + if ((passwd_fd = create_tmp_fd (passwd_contents, -1, error)) < 0) + return FALSE; + passwd_fd_str = g_strdup_printf ("%d", passwd_fd); + + group_contents = g_strdup_printf ("%s:x:%d:%s\n" + "nfsnobody:x:65534:\n", + g->gr_name, + getgid (), g_get_user_name ()); + if ((group_fd = create_tmp_fd (group_contents, -1, error)) < 0) + return FALSE; + group_fd_str = g_strdup_printf ("%d", group_fd); + + add_args (argv_array, + "--unshare-pid", + "--unshare-user", + "--dev", "/dev", + "--proc", "/proc", + "--dir", "/tmp", + "--dir", "/run/host", + "--dir", run_dir, + "--setenv", "XDG_RUNTIME_DIR", run_dir, + "--symlink", "/tmp", "/var/tmp", + "--symlink", "/run", "/var/run", + "--ro-bind", "/sys/block", "/sys/block", + "--ro-bind", "/sys/bus", "/sys/bus", + "--ro-bind", "/sys/class", "/sys/class", + "--ro-bind", "/sys/dev", "/sys/dev", + "--ro-bind", "/sys/devices", "/sys/devices", + "--bind-data", passwd_fd_str, "/etc/passwd", + "--bind-data", group_fd_str, "/etc/group", + "--symlink", "/run/host/monitor/resolv.conf", "/etc/resolv.conf", + /* Always create a homedir to start from, although it may be covered later */ + "--dir", g_get_home_dir (), + NULL); + + if (g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) + add_args (argv_array, "--bind", "/etc/machine-id", "/etc/machine-id", NULL); + else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS)) + add_args (argv_array, "--bind", "/var/lib/dbus/machine-id", "/etc/machine-id", NULL); + + etc = g_file_get_child (runtime_files, "etc"); + if (g_file_query_exists (etc, NULL)) + { + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + struct dirent *dent; + char path_buffer[PATH_MAX + 1]; + ssize_t symlink_size; + + glnx_dirfd_iterator_init_at (AT_FDCWD, gs_file_get_path_cached (etc), FALSE, &dfd_iter, NULL); + + while (TRUE) + { + g_autofree char *src = NULL; + g_autofree char *dest = NULL; + + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, NULL) || dent == NULL) + break; + + if (strcmp (dent->d_name, "passwd") == 0 || + strcmp (dent->d_name, "group") == 0 || + strcmp (dent->d_name, "machine-id") == 0 || + strcmp (dent->d_name, "resolv.conf") == 0 || + strcmp (dent->d_name, "localtime") == 0) + continue; + + src = g_build_filename (gs_file_get_path_cached (etc), dent->d_name, NULL); + dest = g_build_filename ("/etc", dent->d_name, NULL); + if (dent->d_type == DT_LNK) + { + symlink_size = readlinkat (dfd_iter.fd, dent->d_name, path_buffer, sizeof (path_buffer) - 1); + if (symlink_size < 0) + { + glnx_set_error_from_errno (error); + return FALSE; + } + path_buffer[symlink_size] = 0; + add_args (argv_array, "--symlink", path_buffer, dest, NULL); + } + else + { + add_args (argv_array, "--bind", src, dest, NULL); + } + } + } + + if (app_id_dir != NULL) + { + g_autoptr(GFile) app_cache_dir = g_file_get_child (app_id_dir, "cache"); + g_autoptr(GFile) app_data_dir = g_file_get_child (app_id_dir, "data"); + g_autoptr(GFile) app_config_dir = g_file_get_child (app_id_dir, "config"); + + add_args (argv_array, + /* These are nice to have as a fixed path */ + "--bind", gs_file_get_path_cached (app_cache_dir), "/var/cache", + "--bind", gs_file_get_path_cached (app_data_dir), "/var/data", + "--bind", gs_file_get_path_cached (app_config_dir), "/var/config", + NULL); + } + + for (i = 0; i < G_N_ELEMENTS (usr_links); i++) + { + const char *subdir = usr_links[i]; + g_autoptr(GFile) runtime_subdir = g_file_get_child (runtime_files, subdir); + if (g_file_query_exists (runtime_subdir, NULL)) + { + g_autofree char *link = g_strconcat ("usr/", subdir, NULL); + g_autofree char *dest = g_strconcat ("/", subdir, NULL); + add_args (argv_array, + "--symlink", link, dest, + NULL); + } + } + + +#ifdef ENABLE_SECCOMP + if (!setup_seccomp (argv_array, + arch, + (flags & FLATPAK_RUN_FLAG_DEVEL) != 0, + error)) + return FALSE; +#endif + + return TRUE; +} + +gchar * +join_args (GPtrArray *argv_array, gsize *len_out) +{ + gchar *string; + gchar *ptr; + gint i; + gsize len = 0; + + for (i = 0; i < argv_array->len; i++) + len += strlen (argv_array->pdata[i]) + 1; + + string = g_new (gchar, len); + *string = 0; + ptr = string; + for (i = 0; i < argv_array->len; i++) + ptr = g_stpcpy (ptr, argv_array->pdata[i]) + 1; + + *len_out = len; + return string; +} + +gboolean +flatpak_run_app (const char *app_ref, + FlatpakDeploy *app_deploy, + FlatpakContext *extra_context, + const char *custom_runtime, + const char *custom_runtime_version, + FlatpakRunFlags flags, + const char *custom_command, + char *args[], + int n_args, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(FlatpakDeploy) runtime_deploy = NULL; + g_autoptr(GFile) app_files = NULL; + g_autoptr(GFile) runtime_files = NULL; + g_autoptr(GFile) app_id_dir = NULL; + g_autofree char *default_runtime = NULL; + g_autofree char *default_command = NULL; + g_autofree char *runtime_ref = NULL; + int sync_fds[2] = {-1, -1}; + g_autoptr(GKeyFile) metakey = NULL; + g_autoptr(GKeyFile) runtime_metakey = NULL; + g_autoptr(GPtrArray) argv_array = NULL; + g_autoptr(GPtrArray) real_argv_array = NULL; + g_auto(GStrv) envp = NULL; + g_autoptr(GPtrArray) session_bus_proxy_argv = NULL; + g_autoptr(GPtrArray) system_bus_proxy_argv = NULL; + const char *command = "/bin/sh"; + g_autoptr(GError) my_error = NULL; + g_auto(GStrv) runtime_parts = NULL; + int i; + g_autoptr(FlatpakContext) app_context = NULL; + g_autoptr(FlatpakContext) overrides = NULL; + g_auto(GStrv) app_ref_parts = NULL; + + app_ref_parts = flatpak_decompose_ref (app_ref, error); + if (app_ref_parts == NULL) + return FALSE; + + metakey = flatpak_deploy_get_metadata (app_deploy); + + argv_array = g_ptr_array_new_with_free_func (g_free); + session_bus_proxy_argv = g_ptr_array_new_with_free_func (g_free); + system_bus_proxy_argv = g_ptr_array_new_with_free_func (g_free); + + default_runtime = g_key_file_get_string (metakey, "Application", + (flags & FLATPAK_RUN_FLAG_DEVEL) != 0 ? "sdk" : "runtime", + &my_error); + if (my_error) + { + g_propagate_error (error, g_steal_pointer (&my_error)); + return FALSE; + } + + runtime_parts = g_strsplit (default_runtime, "/", 0); + if (g_strv_length (runtime_parts) != 3) + return flatpak_fail (error, "Wrong number of components in runtime %s", default_runtime); + + if (custom_runtime) + { + g_auto(GStrv) custom_runtime_parts = g_strsplit (custom_runtime, "/", 0); + + for (i = 0; i < 3 && custom_runtime_parts[i] != NULL; i++) + { + if (strlen (custom_runtime_parts[i]) > 0) + { + g_free (runtime_parts[i]); + runtime_parts[i] = g_steal_pointer (&custom_runtime_parts[i]); + } + } + } + + if (custom_runtime_version) + { + g_free (runtime_parts[2]); + runtime_parts[2] = g_strdup (custom_runtime_version); + } + + runtime_ref = flatpak_compose_ref (FALSE, + runtime_parts[0], + runtime_parts[2], + runtime_parts[1], + error); + if (runtime_ref == NULL) + return FALSE; + + runtime_deploy = flatpak_find_deploy_for_ref (runtime_ref, cancellable, error); + if (runtime_deploy == NULL) + return FALSE; + + runtime_metakey = flatpak_deploy_get_metadata (runtime_deploy); + + app_context = compute_permissions (metakey, runtime_metakey, error); + if (app_context == NULL) + return FALSE; + + overrides = flatpak_deploy_get_overrides (app_deploy); + flatpak_context_merge (app_context, overrides); + + if (extra_context) + flatpak_context_merge (app_context, extra_context); + + runtime_files = flatpak_deploy_get_files (runtime_deploy); + app_files = flatpak_deploy_get_files (app_deploy); + + if ((app_id_dir = flatpak_ensure_data_dir (app_ref_parts[1], cancellable, error)) == NULL) + return FALSE; + + envp = g_get_environ (); + envp = flatpak_run_apply_env_default (envp); + envp = flatpak_run_apply_env_vars (envp, app_context); + envp = flatpak_run_apply_env_appid (envp, app_id_dir); + + add_args (argv_array, + "--ro-bind", gs_file_get_path_cached (runtime_files), "/usr", + "--lock-file", "/usr/.ref", + "--ro-bind", gs_file_get_path_cached (app_files), "/app", + "--lock-file", "/app/.ref", + NULL); + + if (!flatpak_run_setup_base_argv (argv_array, runtime_files, app_id_dir, app_ref_parts[2], flags, error)) + return FALSE; + + if (!add_app_info_args (argv_array, app_deploy, app_ref_parts[1], runtime_ref, app_context, error)) + return FALSE; + + if (!flatpak_run_add_extension_args (argv_array, metakey, app_ref, cancellable, error)) + return FALSE; + + if (!flatpak_run_add_extension_args (argv_array, runtime_metakey, runtime_ref, cancellable, error)) + return FALSE; + + add_monitor_path_args (argv_array, &envp); + + add_document_portal_args (argv_array, app_ref_parts[1]); + + flatpak_run_add_environment_args (argv_array, &envp, + session_bus_proxy_argv, + system_bus_proxy_argv, + app_ref_parts[1], app_context, app_id_dir); + + add_font_path_args (argv_array); + + /* Must run this before spawning the dbus proxy, to ensure it + ends up in the app cgroup */ + if (!flatpak_run_in_transient_unit (app_ref_parts[1], error)) + return FALSE; + + if (!add_dbus_proxy_args (argv_array, session_bus_proxy_argv, (flags & FLATPAK_RUN_FLAG_LOG_SESSION_BUS) != 0, sync_fds, error)) + return FALSE; + + if (!add_dbus_proxy_args (argv_array, system_bus_proxy_argv, (flags & FLATPAK_RUN_FLAG_LOG_SYSTEM_BUS) != 0, sync_fds, error)) + return FALSE; + + if (sync_fds[1] != -1) + close (sync_fds[1]); + + add_args (argv_array, + /* Not in base, because we don't want this for xdg-app build */ + "--symlink", "/app/lib/debug/source", "/run/build", + "--symlink", "/usr/lib/debug/source", "/run/build-runtime", + NULL); + + if (g_environ_getenv (envp, "LD_LIBRARY_PATH") != NULL) + { + /* LD_LIBRARY_PATH is overridden for setuid helper, so pass it as cmdline arg */ + add_args (argv_array, + "--setenv", "LD_LIBRARY_PATH", g_environ_getenv (envp, "LD_LIBRARY_PATH"), + NULL); + envp = g_environ_unsetenv (envp, "LD_LIBRARY_PATH"); + } + + if (custom_command) + { + command = custom_command; + } + else + { + default_command = g_key_file_get_string (metakey, "Application", "command", &my_error); + if (my_error) + { + g_propagate_error (error, g_steal_pointer (&my_error)); + return FALSE; + } + + command = default_command; + } + + real_argv_array = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (real_argv_array, g_strdup (flatpak_get_bwrap ())); + + { + gsize len; + int arg_fd; + g_autofree char *arg_fd_str = NULL; + g_autofree char *args = join_args (argv_array, &len); + + arg_fd = create_tmp_fd (args, len, error); + if (arg_fd < 0) + return FALSE; + + arg_fd_str = g_strdup_printf ("%d", arg_fd); + + add_args (real_argv_array, + "--args", arg_fd_str, + NULL); + } + + g_ptr_array_add (real_argv_array, g_strdup (command)); + for (i = 0; i < n_args; i++) + g_ptr_array_add (real_argv_array, g_strdup (args[i])); + + g_ptr_array_add (real_argv_array, NULL); + + if ((flags & FLATPAK_RUN_FLAG_BACKGROUND) != 0) + { + if (!g_spawn_async (NULL, + (char **) real_argv_array->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, NULL, + NULL, + error)) + return FALSE; + } + else + { + if (execvpe (flatpak_get_bwrap (), (char **) real_argv_array->pdata, envp) == -1) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Unable to start app"); + return FALSE; + } + /* Not actually reached... */ + } + + return TRUE; +} diff --git a/common/flatpak-run.h b/common/flatpak-run.h new file mode 100644 index 0000000..626fabd --- /dev/null +++ b/common/flatpak-run.h @@ -0,0 +1,111 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 library 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 library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_RUN_H__ +#define __FLATPAK_RUN_H__ + +#include "libglnx/libglnx.h" +#include "dbus-proxy/flatpak-proxy.h" +#include "flatpak-common-types.h" + +gboolean flatpak_run_in_transient_unit (const char *app_id, + GError **error); + +#define FLATPAK_METADATA_GROUP_CONTEXT "Context" +#define FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY "Session Bus Policy" +#define FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY "System Bus Policy" +#define FLATPAK_METADATA_GROUP_ENVIRONMENT "Environment" +#define FLATPAK_METADATA_KEY_SHARED "shared" +#define FLATPAK_METADATA_KEY_SOCKETS "sockets" +#define FLATPAK_METADATA_KEY_FILESYSTEMS "filesystems" +#define FLATPAK_METADATA_KEY_PERSISTENT "persistent" +#define FLATPAK_METADATA_KEY_DEVICES "devices" + +FlatpakContext *flatpak_context_new (void); +void flatpak_context_free (FlatpakContext *context); +void flatpak_context_merge (FlatpakContext *context, + FlatpakContext *other); +GOptionGroup *flatpak_context_get_options (FlatpakContext *context); +gboolean flatpak_context_load_metadata (FlatpakContext *context, + GKeyFile *metakey, + GError **error); +void flatpak_context_save_metadata (FlatpakContext *context, + GKeyFile *metakey); +void flatpak_context_allow_host_fs (FlatpakContext *context); +void flatpak_context_set_session_bus_policy (FlatpakContext *context, + const char *name, + FlatpakPolicy policy); +void flatpak_context_set_system_bus_policy (FlatpakContext *context, + const char *name, + FlatpakPolicy policy); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakContext, flatpak_context_free) + +gboolean flatpak_run_add_extension_args (GPtrArray *argv_array, + GKeyFile *metakey, + const char *full_ref, + GCancellable *cancellable, + GError **error); +void flatpak_run_add_environment_args (GPtrArray *argv_array, + char ***envp_p, + GPtrArray *session_bus_proxy_argv, + GPtrArray *system_bus_proxy_argv, + const char *app_id, + FlatpakContext *context, + GFile *app_id_dir); +char ** flatpak_run_get_minimal_env (gboolean devel); +char ** flatpak_run_apply_env_default (char **envp); +char ** flatpak_run_apply_env_appid (char **envp, + GFile *app_dir); +char ** flatpak_run_apply_env_vars (char **envp, + FlatpakContext *context); + +GFile *flatpak_get_data_dir (const char *app_id); +GFile *flatpak_ensure_data_dir (const char *app_id, + GCancellable *cancellable, + GError **error); + +typedef enum { + FLATPAK_RUN_FLAG_DEVEL = (1 << 0), + FLATPAK_RUN_FLAG_BACKGROUND = (1 << 1), + FLATPAK_RUN_FLAG_LOG_SESSION_BUS = (1 << 2), + FLATPAK_RUN_FLAG_LOG_SYSTEM_BUS = (1 << 3), +} FlatpakRunFlags; + +gboolean flatpak_run_setup_base_argv (GPtrArray *argv_array, + GFile *runtime_files, + GFile *app_id_dir, + const char *arch, + FlatpakRunFlags flags, + GError **error); +gboolean flatpak_run_app (const char *app_ref, + FlatpakDeploy *app_deploy, + FlatpakContext *extra_context, + const char *custom_runtime, + const char *custom_runtime_version, + FlatpakRunFlags flags, + const char *custom_command, + char *args[], + int n_args, + GCancellable *cancellable, + GError **error); + + +#endif /* __FLATPAK_RUN_H__ */ diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c new file mode 100644 index 0000000..dbe2f99 --- /dev/null +++ b/common/flatpak-utils.c @@ -0,0 +1,3008 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 library 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 library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include "flatpak-utils.h" +#include "flatpak-dir.h" +#include "flatpak-portal-error.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "libgsystem.h" +#include "libglnx/libglnx.h" +#include + +GBytes * +flatpak_read_stream (GInputStream *in, + gboolean null_terminate, + GError **error) +{ + g_autoptr(GOutputStream) mem_stream = NULL; + + mem_stream = g_memory_output_stream_new_resizable (); + if (g_output_stream_splice (mem_stream, in, + 0, NULL, error) < 0) + return NULL; + + if (null_terminate) + { + if (!g_output_stream_write (G_OUTPUT_STREAM (mem_stream), "\0", 1, NULL, error)) + return NULL; + } + + if (!g_output_stream_close (G_OUTPUT_STREAM (mem_stream), NULL, error)) + return NULL; + + return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem_stream)); +} + +gint +flatpak_strcmp0_ptr (gconstpointer a, + gconstpointer b) +{ + return g_strcmp0 (*(char * const *) a, *(char * const *) b); +} + +/* Returns end of matching path prefix, or NULL if no match */ +const char * +flatpak_path_match_prefix (const char *pattern, + const char *string) +{ + char c, test; + const char *tmp; + + while (*pattern == '/') + pattern++; + + while (*string == '/') + string++; + + while (TRUE) + { + switch (c = *pattern++) + { + case 0: + if (*string == '/' || *string == 0) + return string; + return NULL; + + case '?': + if (*string == '/' || *string == 0) + return NULL; + string++; + break; + + case '*': + c = *pattern; + + while (c == '*') + c = *++pattern; + + /* special case * at end */ + if (c == 0) + { + char *tmp = strchr (string, '/'); + if (tmp != NULL) + return tmp; + return string + strlen (string); + } + else if (c == '/') + { + string = strchr (string, '/'); + if (string == NULL) + return NULL; + break; + } + + while ((test = *string) != 0) + { + tmp = flatpak_path_match_prefix (pattern, string); + if (tmp != NULL) + return tmp; + if (test == '/') + break; + string++; + } + return NULL; + + default: + if (c != *string) + return NULL; + string++; + break; + } + } + return NULL; /* Should not be reached */ +} + +gboolean +flatpak_fail (GError **error, const char *format, ...) +{ + g_autofree char *message = NULL; + va_list args; + + va_start (args, format); + message = g_strdup_vprintf (format, args); + va_end (args); + + g_set_error_literal (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + message); + + return FALSE; +} + +const char * +flatpak_get_kernel_arch (void) +{ + static struct utsname buf; + static char *arch = NULL; + char *m; + + if (arch != NULL) + return arch; + + if (uname (&buf)) + { + arch = "unknown"; + return arch; + } + + /* By default, just pass on machine, good enough for most arches */ + arch = buf.machine; + + /* Override for some arches */ + + m = buf.machine; + /* i?86 */ + if (strlen (m) == 4 && m[0] == 'i' && m[2] == '8' && m[3] == '6') + { + arch = "i386"; + } + else if (g_str_has_prefix (m, "arm")) + { + if (g_str_has_suffix (m, "b")) + arch = "armeb"; + else + arch = "arm"; + } + else if (strcmp (m, "mips") == 0) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + arch = "mipsel"; +#endif + } + else if (strcmp (m, "mips64") == 0) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + arch = "mips64el"; +#endif + } + + return arch; +} + + +/* This maps the kernel-reported uname to a single string representing + * the cpu family, in the sense that all members of this family would + * be able to understand and link to a binary file with such cpu + * opcodes. That doesn't necessarily mean that all members of the + * family can run all opcodes, for instance for modern 32bit intel we + * report "i386", even though they support instructions that the + * original i386 cpu cannot run. Still, such an executable would + * at least try to execute a 386, wheras an arm binary would not. + */ +const char * +flatpak_get_arch (void) +{ + /* Avoid using uname on multiarch machines, because uname reports the kernels + * arch, and that may be different from userspace. If e.g. the kernel is 64bit and + * the userspace is 32bit we want to use 32bit by default. So, we take the current build + * arch as the default. */ +#if defined(__i386__) + return "i386"; +#elif defined(__x86_64__) + return "x86_64"; +#elif defined(__aarch64__) + return "aarch64"; +#elif defined(__arm__) +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return "arm"; +#else + return "armeb"; +#endif +#else + return flatpak_get_kernel_arch (); +#endif +} + +const char * +flatpak_get_bwrap (void) +{ + const char *e = g_getenv ("FLATPAK_BWRAP"); + + if (e != NULL) + return e; + return HELPER; +} + +static gboolean +is_valid_initial_name_character (gint c) +{ + return + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_'); +} + +static gboolean +is_valid_name_character (gint c) +{ + return + is_valid_initial_name_character (c) || + (c >= '0' && c <= '9'); +} + +/** flatpak_is_name: + * @string: The string to check + * + * Checks if @string is a valid application name. + * + * App names are composed of 3 or more elements separated by a period + * ('.') character. All elements must contain at least one character. + * + * Each element must only contain the ASCII characters + * "[A-Z][a-z][0-9]_". Elements may not begin with a digit. + * + * App names must not begin with a '.' (period) character. + * + * App names must not exceed 255 characters in length. + * + * The above means that any app name is also a valid DBus well known + * bus name, but not all DBus names are valid app names. The difference are: + * 1) DBus name elements may contain '-' + * 2) DBus names require only two elements + * + * Returns: %TRUE if valid, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +flatpak_is_valid_name (const char *string) +{ + guint len; + gboolean ret; + const gchar *s; + const gchar *end; + int dot_count; + + g_return_val_if_fail (string != NULL, FALSE); + + ret = FALSE; + + len = strlen (string); + if (G_UNLIKELY (len == 0 || len > 255)) + goto out; + + end = string + len; + + s = string; + if (G_UNLIKELY (*s == '.')) + /* can't start with a . */ + goto out; + else if (G_UNLIKELY (!is_valid_initial_name_character (*s))) + goto out; + + s += 1; + dot_count = 0; + while (s != end) + { + if (*s == '.') + { + s += 1; + if (G_UNLIKELY (s == end || !is_valid_initial_name_character (*s))) + goto out; + dot_count++; + } + else if (G_UNLIKELY (!is_valid_name_character (*s))) + { + goto out; + } + s += 1; + } + + if (G_UNLIKELY (dot_count < 2)) + goto out; + + ret = TRUE; + +out: + return ret; +} + +gboolean +flatpak_has_name_prefix (const char *string, + const char *name) +{ + const char *rest; + + if (!g_str_has_prefix (string, name)) + return FALSE; + + rest = string + strlen (name); + return + *rest == 0 || + *rest == '.' || + !is_valid_name_character (*rest); +} + + +static gboolean +is_valid_initial_branch_character (gint c) +{ + return + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_') || + (c == '-'); +} + +static gboolean +is_valid_branch_character (gint c) +{ + return + is_valid_initial_branch_character (c) || + (c == '.'); +} + +/** flatpak_is_valid_branch: + * @string: The string to check + * + * Checks if @string is a valid branch name. + * + * Branch names must only contain the ASCII characters + * "[A-Z][a-z][0-9]_-.". + * Branch names may not begin with a digit. + * Branch names must contain at least one character. + * + * Returns: %TRUE if valid, %FALSE otherwise. + * + * Since: 2.26 + */ +gboolean +flatpak_is_valid_branch (const char *string) +{ + guint len; + gboolean ret; + const gchar *s; + const gchar *end; + + g_return_val_if_fail (string != NULL, FALSE); + + ret = FALSE; + + len = strlen (string); + if (G_UNLIKELY (len == 0)) + goto out; + + end = string + len; + + s = string; + if (G_UNLIKELY (!is_valid_initial_branch_character (*s))) + goto out; + + s += 1; + while (s != end) + { + if (G_UNLIKELY (!is_valid_branch_character (*s))) + goto out; + s += 1; + } + + ret = TRUE; + +out: + return ret; +} + +char ** +flatpak_decompose_ref (const char *full_ref, + GError **error) +{ + g_auto(GStrv) parts = NULL; + + parts = g_strsplit (full_ref, "/", 0); + if (g_strv_length (parts) != 4) + { + flatpak_fail (error, "Wrong number of components in %s", full_ref); + return NULL; + } + + if (strcmp (parts[0], "app") != 0 && strcmp (parts[0], "runtime") != 0) + { + flatpak_fail (error, "Not application or runtime"); + return NULL; + } + + if (!flatpak_is_valid_name (parts[1])) + { + flatpak_fail (error, "Invalid name %s", parts[1]); + return NULL; + } + + if (strlen (parts[2]) == 0) + { + flatpak_fail (error, "Invalid arch %s", parts[2]); + return NULL; + } + + if (!flatpak_is_valid_branch (parts[3])) + { + flatpak_fail (error, "Invalid branch %s", parts[3]); + return NULL; + } + + return g_steal_pointer (&parts); +} + +char * +flatpak_compose_ref (gboolean app, + const char *name, + const char *branch, + const char *arch, + GError **error) +{ + if (!flatpak_is_valid_name (name)) + { + flatpak_fail (error, "'%s' is not a valid name", name); + return NULL; + } + + if (branch && !flatpak_is_valid_branch (branch)) + { + flatpak_fail (error, "'%s' is not a valid branch name", branch); + return NULL; + } + + if (app) + return flatpak_build_app_ref (name, branch, arch); + else + return flatpak_build_runtime_ref (name, branch, arch); +} + +char * +flatpak_build_untyped_ref (const char *runtime, + const char *branch, + const char *arch) +{ + if (arch == NULL) + arch = flatpak_get_arch (); + + return g_build_filename (runtime, arch, branch, NULL); +} + +char * +flatpak_build_runtime_ref (const char *runtime, + const char *branch, + const char *arch) +{ + if (branch == NULL) + branch = "master"; + + if (arch == NULL) + arch = flatpak_get_arch (); + + return g_build_filename ("runtime", runtime, arch, branch, NULL); +} + +char * +flatpak_build_app_ref (const char *app, + const char *branch, + const char *arch) +{ + if (branch == NULL) + branch = "master"; + + if (arch == NULL) + arch = flatpak_get_arch (); + + return g_build_filename ("app", app, arch, branch, NULL); +} + +char ** +flatpak_list_deployed_refs (const char *type, + const char *name_prefix, + const char *branch, + const char *arch, + GCancellable *cancellable, + GError **error) +{ + gchar **ret = NULL; + + g_autoptr(GPtrArray) names = NULL; + g_autoptr(GHashTable) hash = NULL; + g_autoptr(FlatpakDir) user_dir = NULL; + g_autoptr(FlatpakDir) system_dir = NULL; + const char *key; + GHashTableIter iter; + + hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + user_dir = flatpak_dir_get_user (); + system_dir = flatpak_dir_get_system (); + + if (!flatpak_dir_collect_deployed_refs (user_dir, type, name_prefix, + branch, arch, hash, cancellable, + error)) + goto out; + + if (!flatpak_dir_collect_deployed_refs (system_dir, type, name_prefix, + branch, arch, hash, cancellable, + error)) + goto out; + + names = g_ptr_array_new (); + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) + g_ptr_array_add (names, g_strdup (key)); + + g_ptr_array_sort (names, flatpak_strcmp0_ptr); + g_ptr_array_add (names, NULL); + + ret = (char **) g_ptr_array_free (names, FALSE); + names = NULL; + +out: + return ret; +} + +GFile * +flatpak_find_deploy_dir_for_ref (const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(FlatpakDir) user_dir = NULL; + g_autoptr(FlatpakDir) system_dir = NULL; + GFile *deploy = NULL; + + user_dir = flatpak_dir_get_user (); + system_dir = flatpak_dir_get_system (); + + deploy = flatpak_dir_get_if_deployed (user_dir, ref, NULL, cancellable); + if (deploy == NULL) + deploy = flatpak_dir_get_if_deployed (system_dir, ref, NULL, cancellable); + if (deploy == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s not installed", ref); + return NULL; + } + + return deploy; + +} + +FlatpakDeploy * +flatpak_find_deploy_for_ref (const char *ref, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(FlatpakDir) user_dir = NULL; + g_autoptr(FlatpakDir) system_dir = NULL; + g_autoptr(FlatpakDeploy) deploy = NULL; + g_autoptr(GError) my_error = NULL; + + user_dir = flatpak_dir_get_user (); + system_dir = flatpak_dir_get_system (); + + deploy = flatpak_dir_load_deployed (user_dir, ref, NULL, cancellable, &my_error); + if (deploy == NULL && g_error_matches (my_error, FLATPAK_DIR_ERROR, FLATPAK_DIR_ERROR_NOT_DEPLOYED)) + { + g_clear_error (&my_error); + deploy = flatpak_dir_load_deployed (system_dir, ref, NULL, cancellable, &my_error); + } + if (deploy == NULL) + g_propagate_error (error, g_steal_pointer (&my_error)); + + return g_steal_pointer (&deploy); +} + + +static gboolean +overlay_symlink_tree_dir (int source_parent_fd, + const char *source_name, + const char *source_symlink_prefix, + int destination_parent_fd, + const char *destination_name, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + int res; + + g_auto(GLnxDirFdIterator) source_iter = { 0 }; + glnx_fd_close int destination_dfd = -1; + struct dirent *dent; + + if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) + goto out; + + do + res = mkdirat (destination_parent_fd, destination_name, 0777); + while (G_UNLIKELY (res == -1 && errno == EINTR)); + if (res == -1) + { + if (errno != EEXIST) + { + glnx_set_error_from_errno (error); + goto out; + } + } + + if (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name, + &destination_dfd, + cancellable, error)) + goto out; + + while (TRUE) + { + + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&source_iter, &dent, cancellable, error)) + goto out; + + if (dent == NULL) + break; + + if (dent->d_type == DT_DIR) + { + g_autofree gchar *target = g_build_filename ("..", source_symlink_prefix, dent->d_name, NULL); + if (!overlay_symlink_tree_dir (source_iter.fd, dent->d_name, target, destination_dfd, dent->d_name, + cancellable, error)) + goto out; + } + else + { + g_autofree gchar *target = g_build_filename (source_symlink_prefix, dent->d_name, NULL); + + if (unlinkat (destination_dfd, dent->d_name, 0) != 0 && errno != ENOENT) + { + glnx_set_error_from_errno (error); + goto out; + } + + if (symlinkat (target, destination_dfd, dent->d_name) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + } + + ret = TRUE; +out: + + return ret; +} + +gboolean +flatpak_overlay_symlink_tree (GFile *source, + GFile *destination, + const char *symlink_prefix, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + if (!gs_file_ensure_directory (destination, TRUE, cancellable, error)) + goto out; + + /* The fds are closed by this call */ + if (!overlay_symlink_tree_dir (AT_FDCWD, gs_file_get_path_cached (source), + symlink_prefix, + AT_FDCWD, gs_file_get_path_cached (destination), + cancellable, error)) + goto out; + + ret = TRUE; + +out: + return ret; +} + +static gboolean +remove_dangling_symlinks (int parent_fd, + const char *name, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + struct dirent *dent; + GLnxDirFdIterator iter; + + if (!glnx_dirfd_iterator_init_at (parent_fd, name, FALSE, &iter, error)) + goto out; + + while (TRUE) + { + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, cancellable, error)) + goto out; + + if (dent == NULL) + break; + + if (dent->d_type == DT_DIR) + { + if (!remove_dangling_symlinks (iter.fd, dent->d_name, cancellable, error)) + goto out; + } + else if (dent->d_type == DT_LNK) + { + struct stat stbuf; + if (fstatat (iter.fd, dent->d_name, &stbuf, 0) != 0 && errno == ENOENT) + { + if (unlinkat (iter.fd, dent->d_name, 0) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + } + } + + ret = TRUE; +out: + + return ret; +} + +gboolean +flatpak_remove_dangling_symlinks (GFile *dir, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + /* The fd is closed by this call */ + if (!remove_dangling_symlinks (AT_FDCWD, gs_file_get_path_cached (dir), + cancellable, error)) + goto out; + + ret = TRUE; + +out: + return ret; +} + +/* Based on g_mkstemp from glib */ + +gint +flatpak_mkstempat (int dir_fd, + gchar *tmpl, + int flags, + int mode) +{ + char *XXXXXX; + int count, fd; + static const char letters[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + static const int NLETTERS = sizeof (letters) - 1; + glong value; + GTimeVal tv; + static int counter = 0; + + g_return_val_if_fail (tmpl != NULL, -1); + + /* find the last occurrence of "XXXXXX" */ + XXXXXX = g_strrstr (tmpl, "XXXXXX"); + + if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6)) + { + errno = EINVAL; + return -1; + } + + /* Get some more or less random data. */ + g_get_current_time (&tv); + value = (tv.tv_usec ^ tv.tv_sec) + counter++; + + for (count = 0; count < 100; value += 7777, ++count) + { + glong v = value; + + /* Fill in the random bits. */ + XXXXXX[0] = letters[v % NLETTERS]; + v /= NLETTERS; + XXXXXX[1] = letters[v % NLETTERS]; + v /= NLETTERS; + XXXXXX[2] = letters[v % NLETTERS]; + v /= NLETTERS; + XXXXXX[3] = letters[v % NLETTERS]; + v /= NLETTERS; + XXXXXX[4] = letters[v % NLETTERS]; + v /= NLETTERS; + XXXXXX[5] = letters[v % NLETTERS]; + + fd = openat (dir_fd, tmpl, flags | O_CREAT | O_EXCL, mode); + + if (fd >= 0) + return fd; + else if (errno != EEXIST) + /* Any other error will apply also to other names we might + * try, and there are 2^32 or so of them, so give up now. + */ + return -1; + } + + /* We got out of the loop because we ran out of combinations to try. */ + errno = EEXIST; + return -1; +} + +struct FlatpakTablePrinter +{ + GPtrArray *rows; + GPtrArray *current; + int n_columns; +}; + +FlatpakTablePrinter * +flatpak_table_printer_new (void) +{ + FlatpakTablePrinter *printer = g_new0 (FlatpakTablePrinter, 1); + + printer->rows = g_ptr_array_new_with_free_func ((GDestroyNotify) g_strfreev); + printer->current = g_ptr_array_new_with_free_func (g_free); + + return printer; +} + +void +flatpak_table_printer_free (FlatpakTablePrinter *printer) +{ + g_ptr_array_free (printer->rows, TRUE); + g_ptr_array_free (printer->current, TRUE); + g_free (printer); +} + +void +flatpak_table_printer_add_column (FlatpakTablePrinter *printer, + const char *text) +{ + g_ptr_array_add (printer->current, text ? g_strdup (text) : g_strdup ("")); +} + +void +flatpak_table_printer_append_with_comma (FlatpakTablePrinter *printer, + const char *text) +{ + char *old, *new; + + g_assert (printer->current->len > 0); + + old = g_ptr_array_index (printer->current, printer->current->len - 1); + + if (old[0] != 0) + new = g_strconcat (old, ",", text, NULL); + else + new = g_strdup (text); + + g_ptr_array_index (printer->current, printer->current->len - 1) = new; + g_free (old); +} + + +void +flatpak_table_printer_finish_row (FlatpakTablePrinter *printer) +{ + if (printer->current->len == 0) + return; /* Ignore empty rows */ + + printer->n_columns = MAX (printer->n_columns, printer->current->len); + g_ptr_array_add (printer->current, NULL); + g_ptr_array_add (printer->rows, + g_ptr_array_free (printer->current, FALSE)); + printer->current = g_ptr_array_new_with_free_func (g_free); +} + +void +flatpak_table_printer_print (FlatpakTablePrinter *printer) +{ + g_autofree int *widths = NULL; + int i, j; + + if (printer->current->len != 0) + flatpak_table_printer_finish_row (printer); + + widths = g_new0 (int, printer->n_columns); + + for (i = 0; i < printer->rows->len; i++) + { + char **row = g_ptr_array_index (printer->rows, i); + + for (j = 0; row[j] != NULL; j++) + widths[j] = MAX (widths[j], strlen (row[j])); + } + + for (i = 0; i < printer->rows->len; i++) + { + char **row = g_ptr_array_index (printer->rows, i); + + for (j = 0; row[j] != NULL; j++) + g_print ("%s%-*s", (j == 0) ? "" : " ", widths[j], row[j]); + g_print ("\n"); + } +} + + +static GHashTable *app_ids; + +typedef struct +{ + char *name; + char *app_id; + gboolean exited; + GList *pending; +} AppIdInfo; + +static void +app_id_info_free (AppIdInfo *info) +{ + g_free (info->name); + g_free (info->app_id); + g_free (info); +} + +static void +ensure_app_ids (void) +{ + if (app_ids == NULL) + app_ids = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) app_id_info_free); +} + +static void +got_credentials_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + AppIdInfo *info = user_data; + + g_autoptr(GDBusMessage) reply = NULL; + g_autoptr(GError) error = NULL; + GList *l; + + reply = g_dbus_connection_send_message_with_reply_finish (G_DBUS_CONNECTION (source_object), + res, &error); + + if (!info->exited && reply != NULL) + { + GVariant *body = g_dbus_message_get_body (reply); + guint32 pid; + g_autofree char *path = NULL; + g_autofree char *content = NULL; + + g_variant_get (body, "(u)", &pid); + + path = g_strdup_printf ("/proc/%u/cgroup", pid); + + if (g_file_get_contents (path, &content, NULL, NULL)) + { + gchar **lines = g_strsplit (content, "\n", -1); + int i; + + for (i = 0; lines[i] != NULL; i++) + { + if (g_str_has_prefix (lines[i], "1:name=systemd:")) + { + const char *unit = lines[i] + strlen ("1:name=systemd:"); + g_autofree char *scope = g_path_get_basename (unit); + + if (g_str_has_prefix (scope, "xdg-app-") && + g_str_has_suffix (scope, ".scope")) + { + const char *name = scope + strlen ("xdg-app-"); + char *dash = strchr (name, '-'); + if (dash != NULL) + { + *dash = 0; + info->app_id = g_strdup (name); + } + } + else + { + info->app_id = g_strdup (""); + } + } + } + g_strfreev (lines); + } + } + + for (l = info->pending; l != NULL; l = l->next) + { + GTask *task = l->data; + + if (info->app_id == NULL) + g_task_return_new_error (task, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_FAILED, + "Can't find app id"); + else + g_task_return_pointer (task, g_strdup (info->app_id), g_free); + } + + g_list_free_full (info->pending, g_object_unref); + info->pending = NULL; + + if (info->app_id == NULL) + g_hash_table_remove (app_ids, info->name); +} + +void +flatpak_invocation_lookup_app_id (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); + const gchar *sender = g_dbus_method_invocation_get_sender (invocation); + + g_autoptr(GTask) task = NULL; + AppIdInfo *info; + + task = g_task_new (invocation, cancellable, callback, user_data); + + ensure_app_ids (); + + info = g_hash_table_lookup (app_ids, sender); + + if (info == NULL) + { + info = g_new0 (AppIdInfo, 1); + info->name = g_strdup (sender); + g_hash_table_insert (app_ids, info->name, info); + } + + if (info->app_id) + { + g_task_return_pointer (task, g_strdup (info->app_id), g_free); + } + else + { + if (info->pending == NULL) + { + g_autoptr(GDBusMessage) msg = g_dbus_message_new_method_call ("org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID"); + g_dbus_message_set_body (msg, g_variant_new ("(s)", sender)); + + g_dbus_connection_send_message_with_reply (connection, msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + 30000, + NULL, + cancellable, + got_credentials_cb, + info); + } + + info->pending = g_list_prepend (info->pending, g_object_ref (task)); + } +} + +char * +flatpak_invocation_lookup_app_id_finish (GDBusMethodInvocation *invocation, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +name_owner_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + const char *name, *from, *to; + + g_variant_get (parameters, "(sss)", &name, &from, &to); + + ensure_app_ids (); + + if (name[0] == ':' && + strcmp (name, from) == 0 && + strcmp (to, "") == 0) + { + AppIdInfo *info = g_hash_table_lookup (app_ids, name); + + if (info != NULL) + { + info->exited = TRUE; + if (info->pending == NULL) + g_hash_table_remove (app_ids, name); + } + } +} + +void +flatpak_connection_track_name_owners (GDBusConnection *connection) +{ + g_dbus_connection_signal_subscribe (connection, + "org.freedesktop.DBus", + "org.freedesktop.DBus", + "NameOwnerChanged", + "/org/freedesktop/DBus", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + name_owner_changed, + NULL, NULL); +} + +typedef struct +{ + GError *error; + GError *splice_error; + GMainLoop *loop; + int refs; +} SpawnData; + +static void +spawn_data_exit (SpawnData *data) +{ + data->refs--; + if (data->refs == 0) + g_main_loop_quit (data->loop); +} + +static void +spawn_output_spliced_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + SpawnData *data = user_data; + + g_output_stream_splice_finish (G_OUTPUT_STREAM (obj), result, &data->splice_error); + spawn_data_exit (data); +} + +static void +spawn_exit_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + SpawnData *data = user_data; + + g_subprocess_wait_check_finish (G_SUBPROCESS (obj), result, &data->error); + spawn_data_exit (data); +} + +gboolean +flatpak_spawn (GFile *dir, + char **output, + GError **error, + const gchar *argv0, + va_list ap) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(GSubprocess) subp = NULL; + GPtrArray *args; + const gchar *arg; + GInputStream *in; + g_autoptr(GOutputStream) out = NULL; + g_autoptr(GMainLoop) loop = NULL; + SpawnData data = {0}; + + args = g_ptr_array_new (); + g_ptr_array_add (args, (gchar *) argv0); + while ((arg = va_arg (ap, const gchar *))) + g_ptr_array_add (args, (gchar *) arg); + g_ptr_array_add (args, NULL); + + launcher = g_subprocess_launcher_new (0); + + if (output) + g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); + + if (dir) + { + g_autofree char *path = g_file_get_path (dir); + g_subprocess_launcher_set_cwd (launcher, path); + } + + subp = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); + g_ptr_array_free (args, TRUE); + + if (subp == NULL) + return FALSE; + + loop = g_main_loop_new (NULL, FALSE); + + data.loop = loop; + data.refs = 1; + + if (output) + { + data.refs++; + in = g_subprocess_get_stdout_pipe (subp); + out = g_memory_output_stream_new_resizable (); + g_output_stream_splice_async (out, + in, + G_OUTPUT_STREAM_SPLICE_NONE, + 0, + NULL, + spawn_output_spliced_cb, + &data); + } + + g_subprocess_wait_async (subp, NULL, spawn_exit_cb, &data); + + g_main_loop_run (loop); + + if (data.error) + { + g_propagate_error (error, data.error); + g_clear_error (&data.splice_error); + return FALSE; + } + + if (out) + { + if (data.splice_error) + { + g_propagate_error (error, data.splice_error); + return FALSE; + } + + /* Null terminate */ + g_output_stream_write (out, "\0", 1, NULL, NULL); + g_output_stream_close (out, NULL, NULL); + *output = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out)); + } + + return TRUE; +} + + +gboolean +flatpak_cp_a (GFile *src, + GFile *dest, + FlatpakCpFlags flags, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + GFileEnumerator *enumerator = NULL; + GFileInfo *src_info = NULL; + GFile *dest_child = NULL; + int dest_dfd = -1; + gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0; + gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0; + gboolean move = (flags & FLATPAK_CP_FLAGS_MOVE) != 0; + int r; + + enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!enumerator) + goto out; + + src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \ + "time::modified,time::modified-usec,time::access,time::access-usec", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!src_info) + goto out; + + do + r = mkdir (gs_file_get_path_cached (dest), 0755); + while (G_UNLIKELY (r == -1 && errno == EINTR)); + if (r == -1 && + (!merge || errno != EEXIST)) + { + glnx_set_error_from_errno (error); + goto out; + } + + if (!gs_file_open_dir_fd (dest, &dest_dfd, + cancellable, error)) + goto out; + + + if (!no_chown) + { + do + r = fchown (dest_dfd, + g_file_info_get_attribute_uint32 (src_info, "unix::uid"), + g_file_info_get_attribute_uint32 (src_info, "unix::gid")); + while (G_UNLIKELY (r == -1 && errno == EINTR)); + if (r == -1) + { + glnx_set_error_from_errno (error); + goto out; + } + } + + do + r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode")); + while (G_UNLIKELY (r == -1 && errno == EINTR)); + + if (dest_dfd != -1) + { + (void) close (dest_dfd); + dest_dfd = -1; + } + + while (TRUE) + { + GFileInfo *file_info = NULL; + GFile *src_child = NULL; + + if (!gs_file_enumerator_iterate (enumerator, &file_info, &src_child, + cancellable, error)) + goto out; + if (!file_info) + break; + + if (dest_child) + g_object_unref (dest_child); + dest_child = g_file_get_child (dest, g_file_info_get_name (file_info)); + + if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) + { + if (!flatpak_cp_a (src_child, dest_child, flags, + cancellable, error)) + goto out; + } + else + { + (void) unlink (gs_file_get_path_cached (dest_child)); + GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS; + if (!no_chown) + copyflags |= G_FILE_COPY_ALL_METADATA; + if (move) + { + if (!g_file_move (src_child, dest_child, copyflags, + cancellable, NULL, NULL, error)) + goto out; + } + else + { + if (!g_file_copy (src_child, dest_child, copyflags, + cancellable, NULL, NULL, error)) + goto out; + } + } + } + + if (move && + !g_file_delete (src, NULL, error)) + goto out; + + ret = TRUE; +out: + if (dest_dfd != -1) + (void) close (dest_dfd); + g_clear_object (&src_info); + g_clear_object (&enumerator); + g_clear_object (&dest_child); + return ret; +} + +gboolean +flatpak_variant_save (GFile *dest, + GVariant *variant, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GOutputStream) out = NULL; + gsize bytes_written; + + out = (GOutputStream *) g_file_replace (dest, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + cancellable, error); + if (out == NULL) + return FALSE; + + if (!g_output_stream_write_all (out, + g_variant_get_data (variant), + g_variant_get_size (variant), + &bytes_written, + cancellable, + error)) + return FALSE; + + if (!g_output_stream_close (out, cancellable, error)) + return FALSE; + + return TRUE; +} + + +gboolean +flatpak_variant_bsearch_str (GVariant *array, + const char *str, + int *out_pos) +{ + gsize imax, imin; + gsize imid; + gsize n; + + n = g_variant_n_children (array); + if (n == 0) + return FALSE; + + imax = n - 1; + imin = 0; + while (imax >= imin) + { + g_autoptr(GVariant) child = NULL; + const char *cur; + int cmp; + + imid = (imin + imax) / 2; + + child = g_variant_get_child_value (array, imid); + g_variant_get_child (child, 0, "&s", &cur, NULL); + + cmp = strcmp (cur, str); + if (cmp < 0) + { + imin = imid + 1; + } + else if (cmp > 0) + { + if (imid == 0) + break; + imax = imid - 1; + } + else + { + *out_pos = imid; + return TRUE; + } + } + + *out_pos = imid; + return FALSE; +} + +gboolean +flatpak_summary_lookup_ref (GVariant *summary, const char *ref, char **out_checksum) +{ + g_autoptr(GVariant) refs = g_variant_get_child_value (summary, 0); + int pos; + g_autoptr(GVariant) refdata = NULL; + g_autoptr(GVariant) reftargetdata = NULL; + g_autoptr(GVariant) commit_data = NULL; + guint64 commit_size; + g_autoptr(GVariant) commit_csum_v = NULL; + g_autoptr(GBytes) commit_bytes = NULL; + + if (!flatpak_variant_bsearch_str (refs, ref, &pos)) + return FALSE; + + refdata = g_variant_get_child_value (refs, pos); + reftargetdata = g_variant_get_child_value (refdata, 1); + g_variant_get (reftargetdata, "(t@ay@a{sv})", &commit_size, &commit_csum_v, NULL); + + if (!ostree_validate_structureof_csum_v (commit_csum_v, NULL)) + return FALSE; + + if (out_checksum) + *out_checksum = ostree_checksum_from_bytes_v (commit_csum_v); + + return TRUE; +} + +gboolean +flatpak_repo_set_title (OstreeRepo *repo, + const char *title, + GError **error) +{ + g_autoptr(GKeyFile) config = NULL; + + config = ostree_repo_copy_config (repo); + + if (title) + g_key_file_set_string (config, "xdg-app", "title", title); + else + g_key_file_remove_key (config, "xdg-app", "title", NULL); + + if (!ostree_repo_write_config (repo, config, error)) + return FALSE; + + return TRUE; +} + +#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \ + "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev") + +static gboolean +_flatpak_repo_collect_sizes (OstreeRepo *repo, + GFile *file, + GFileInfo *file_info, + guint64 *installed_size, + guint64 *download_size, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFileEnumerator) dir_enum = NULL; + GFileInfo *child_info_tmp; + GError *temp_error = NULL; + + if (file_info != NULL && g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) + { + const char *checksum = ostree_repo_file_get_checksum (OSTREE_REPO_FILE (file)); + guint64 obj_size; + guint64 file_size = g_file_info_get_size (file_info); + + if (installed_size) + *installed_size += ((file_size + 511) / 512) * 512; + + if (download_size) + { + if (!ostree_repo_query_object_storage_size (repo, + OSTREE_OBJECT_TYPE_FILE, checksum, + &obj_size, cancellable, error)) + return FALSE; + + *download_size += obj_size; + } + } + + if (file_info == NULL || g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) + { + dir_enum = g_file_enumerate_children (file, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!dir_enum) + return FALSE; + + + while ((child_info_tmp = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error))) + { + g_autoptr(GFileInfo) child_info = child_info_tmp; + const char *name = g_file_info_get_name (child_info); + g_autoptr(GFile) child = g_file_get_child (file, name); + + if (!_flatpak_repo_collect_sizes (repo, child, child_info, installed_size, download_size, cancellable, error)) + return FALSE; + } + } + + return TRUE; +} + +gboolean +flatpak_repo_collect_sizes (OstreeRepo *repo, + GFile *root, + guint64 *installed_size, + guint64 *download_size, + GCancellable *cancellable, + GError **error) +{ + return _flatpak_repo_collect_sizes (repo, root, NULL, installed_size, download_size, cancellable, error); +} + +gboolean +flatpak_repo_update (OstreeRepo *repo, + const char **gpg_key_ids, + const char *gpg_homedir, + GCancellable *cancellable, + GError **error) +{ + GVariantBuilder builder; + GVariantBuilder ref_data_builder; + GKeyFile *config; + g_autofree char *title = NULL; + + g_autoptr(GHashTable) refs = NULL; + GList *ordered_keys = NULL; + GList *l = NULL; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + + config = ostree_repo_get_config (repo); + + if (config) + title = g_key_file_get_string (config, "xdg-app", "title", NULL); + + if (title) + g_variant_builder_add (&builder, "{sv}", "xa.title", + g_variant_new_string (title)); + + + g_variant_builder_init (&ref_data_builder, G_VARIANT_TYPE ("a{s(tts)}")); + + if (!ostree_repo_list_refs (repo, NULL, &refs, cancellable, error)) + return FALSE; + + ordered_keys = g_hash_table_get_keys (refs); + ordered_keys = g_list_sort (ordered_keys, (GCompareFunc) strcmp); + + for (l = ordered_keys; l; l = l->next) + { + const char *ref = l->data; + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) metadata = NULL; + guint64 installed_size = 0; + guint64 download_size = 0; + g_autofree char *metadata_contents = NULL; + + if (!ostree_repo_read_commit (repo, ref, &root, NULL, NULL, error)) + return FALSE; + + if (!flatpak_repo_collect_sizes (repo, root, &installed_size, &download_size, cancellable, error)) + return FALSE; + + metadata = g_file_get_child (root, "metadata"); + if (!g_file_load_contents (metadata, cancellable, &metadata_contents, NULL, NULL, NULL)) + metadata_contents = g_strdup (""); + + g_variant_builder_add (&ref_data_builder, "{s(tts)}", + ref, + GUINT64_TO_BE (installed_size), + GUINT64_TO_BE (download_size), + metadata_contents); + } + + g_variant_builder_add (&builder, "{sv}", "xa.cache", + g_variant_new_variant (g_variant_builder_end (&ref_data_builder))); + + if (!ostree_repo_regenerate_summary (repo, g_variant_builder_end (&builder), + cancellable, error)) + return FALSE; + + if (gpg_key_ids) + { + if (!ostree_repo_add_gpg_signature_summary (repo, + gpg_key_ids, + gpg_homedir, + cancellable, + error)) + return FALSE; + } + + return TRUE; +} + +gboolean +flatpak_mtree_create_root (OstreeRepo *repo, + OstreeMutableTree *mtree, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GVariant) dirmeta = NULL; + g_autoptr(GFileInfo) file_info = g_file_info_new (); + g_autofree guchar *csum; + g_autofree char *checksum = NULL; + + g_file_info_set_name (file_info, "/"); + g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY); + g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0); + g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0); + g_file_info_set_attribute_uint32 (file_info, "unix::mode", 040755); + + dirmeta = ostree_create_directory_metadata (file_info, NULL); + if (!ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, NULL, + dirmeta, &csum, cancellable, error)) + return FALSE; + + checksum = ostree_checksum_from_bytes (csum); + ostree_mutable_tree_set_metadata_checksum (mtree, checksum); + + return TRUE; +} + +static OstreeRepoCommitFilterResult +commit_filter (OstreeRepo *repo, + const char *path, + GFileInfo *file_info, + gpointer user_data) +{ + guint current_mode; + + /* No user info */ + g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0); + g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0); + + /* No setuid */ + current_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); + g_file_info_set_attribute_uint32 (file_info, "unix::mode", current_mode & ~07000); + + return OSTREE_REPO_COMMIT_FILTER_ALLOW; +} + +static gboolean +validate_component (FlatpakXml *component, + const char *ref, + const char *id, + char **tags, + const char *runtime, + const char *sdk) +{ + FlatpakXml *bundle, *text, *prev, *id_node, *id_text_node, *metadata, *value; + g_autofree char *id_text = NULL; + int i; + + if (g_strcmp0 (component->element_name, "component") != 0) + return FALSE; + + id_node = flatpak_xml_find (component, "id", NULL); + if (id_node == NULL) + return FALSE; + + id_text_node = flatpak_xml_find (id_node, NULL, NULL); + if (id_text_node == NULL || id_text_node->text == NULL) + return FALSE; + + id_text = g_strstrip (g_strdup (id_text_node->text)); + if (!g_str_has_prefix (id_text, id) || + !g_str_has_suffix (id_text, ".desktop")) + { + g_warning ("Invalid id %s", id_text); + return FALSE; + } + + while ((bundle = flatpak_xml_find (component, "bundle", &prev)) != NULL) + flatpak_xml_free (flatpak_xml_unlink (component, bundle)); + + bundle = flatpak_xml_new ("bundle"); + bundle->attribute_names = g_new0 (char *, 2 * 4); + bundle->attribute_values = g_new0 (char *, 2 * 4); + bundle->attribute_names[0] = g_strdup ("type"); + bundle->attribute_values[0] = g_strdup ("xdg-app"); + + i = 1; + if (runtime) + { + bundle->attribute_names[i] = g_strdup ("runtime"); + bundle->attribute_values[i] = g_strdup (runtime); + i++; + } + + if (sdk) + { + bundle->attribute_names[i] = g_strdup ("sdk"); + bundle->attribute_values[i] = g_strdup (sdk); + i++; + } + + text = flatpak_xml_new (NULL); + text->text = g_strdup (ref); + flatpak_xml_add (bundle, text); + + flatpak_xml_add (component, flatpak_xml_new_text (" ")); + flatpak_xml_add (component, bundle); + flatpak_xml_add (component, flatpak_xml_new_text ("\n ")); + + if (tags != NULL && tags[0] != NULL) + { + metadata = flatpak_xml_find (component, "metadata", NULL); + if (metadata == NULL) + { + metadata = flatpak_xml_new ("metadata"); + metadata->attribute_names = g_new0 (char *, 1); + metadata->attribute_values = g_new0 (char *, 1); + + flatpak_xml_add (component, flatpak_xml_new_text (" ")); + flatpak_xml_add (component, metadata); + flatpak_xml_add (component, flatpak_xml_new_text ("\n ")); + } + + value = flatpak_xml_new ("value"); + value->attribute_names = g_new0 (char *, 2); + value->attribute_values = g_new0 (char *, 2); + value->attribute_names[0] = g_strdup ("key"); + value->attribute_values[0] = g_strdup ("X-Flatpak-Tags"); + flatpak_xml_add (metadata, flatpak_xml_new_text ("\n ")); + flatpak_xml_add (metadata, value); + flatpak_xml_add (metadata, flatpak_xml_new_text ("\n ")); + + text = flatpak_xml_new (NULL); + text->text = g_strjoinv (",", tags); + flatpak_xml_add (value, text); + + } + + return TRUE; +} + +gboolean +flatpak_appstream_xml_migrate (FlatpakXml *source, + FlatpakXml *dest, + const char *ref, + const char *id, + GKeyFile *metadata) +{ + FlatpakXml *source_components; + FlatpakXml *dest_components; + FlatpakXml *component; + FlatpakXml *prev_component; + gboolean migrated = FALSE; + + g_auto(GStrv) tags = NULL; + g_autofree const char *runtime = NULL; + g_autofree const char *sdk = NULL; + const char *group; + + if (source->first_child == NULL || + source->first_child->next_sibling != NULL || + g_strcmp0 (source->first_child->element_name, "components") != 0) + return FALSE; + + if (g_str_has_prefix (ref, "app/")) + group = "Application"; + else + group = "Runtime"; + + tags = g_key_file_get_string_list (metadata, group, "tags", NULL, NULL); + runtime = g_key_file_get_string (metadata, group, "runtime", NULL); + sdk = g_key_file_get_string (metadata, group, "sdk", NULL); + + source_components = source->first_child; + dest_components = dest->first_child; + + component = source_components->first_child; + prev_component = NULL; + while (component != NULL) + { + FlatpakXml *next = component->next_sibling; + + if (validate_component (component, ref, id, tags, runtime, sdk)) + { + flatpak_xml_add (dest_components, + flatpak_xml_unlink (component, prev_component)); + migrated = TRUE; + } + else + { + prev_component = component; + } + + component = next; + } + + return migrated; +} + +static gboolean +copy_icon (const char *id, + GFile *root, + GFile *dest, + const char *size, + GError **error) +{ + g_autofree char *icon_name = g_strconcat (id, ".png", NULL); + + g_autoptr(GFile) icons_dir = + g_file_resolve_relative_path (root, + "files/share/app-info/icons/xdg-app"); + g_autoptr(GFile) size_dir = g_file_get_child (icons_dir, size); + g_autoptr(GFile) icon_file = g_file_get_child (size_dir, icon_name); + g_autoptr(GFile) dest_dir = g_file_get_child (dest, "icons"); + g_autoptr(GFile) dest_size_dir = g_file_get_child (dest_dir, size); + g_autoptr(GFile) dest_file = g_file_get_child (dest_size_dir, icon_name); + g_autoptr(GInputStream) in = NULL; + g_autoptr(GOutputStream) out = NULL; + gssize n_bytes_written; + + in = (GInputStream *) g_file_read (icon_file, NULL, error); + if (!in) + return FALSE; + + if (!gs_file_ensure_directory (dest_size_dir, TRUE, NULL, error)) + return FALSE; + + out = (GOutputStream *) g_file_replace (dest_file, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + NULL, error); + if (!out) + return FALSE; + + n_bytes_written = g_output_stream_splice (out, in, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, + NULL, error); + if (n_bytes_written < 0) + return FALSE; + + return TRUE; +} + +static gboolean +extract_appstream (OstreeRepo *repo, + FlatpakXml *appstream_root, + const char *ref, + const char *id, + GFile *dest, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) xmls_dir = NULL; + g_autoptr(GFile) appstream_file = NULL; + g_autoptr(GFile) metadata = NULL; + g_autofree char *appstream_basename = NULL; + g_autoptr(GInputStream) in = NULL; + g_autoptr(FlatpakXml) xml_root = NULL; + g_autoptr(GKeyFile) keyfile = NULL; + + if (!ostree_repo_read_commit (repo, ref, &root, NULL, NULL, error)) + return FALSE; + + keyfile = g_key_file_new (); + metadata = g_file_get_child (root, "metadata"); + if (g_file_query_exists (metadata, cancellable)) + { + g_autofree char *content = NULL; + gsize len; + + if (!g_file_load_contents (metadata, cancellable, &content, &len, NULL, error)) + return FALSE; + + if (!g_key_file_load_from_data (keyfile, content, len, G_KEY_FILE_NONE, error)) + return FALSE; + } + + xmls_dir = g_file_resolve_relative_path (root, "files/share/app-info/xmls"); + appstream_basename = g_strconcat (id, ".xml.gz", NULL); + appstream_file = g_file_get_child (xmls_dir, appstream_basename); + + in = (GInputStream *) g_file_read (appstream_file, cancellable, error); + if (!in) + return FALSE; + + xml_root = flatpak_xml_parse (in, TRUE, cancellable, error); + if (xml_root == NULL) + return FALSE; + + if (flatpak_appstream_xml_migrate (xml_root, appstream_root, + ref, id, keyfile)) + { + g_autoptr(GError) my_error = NULL; + FlatpakXml *components = appstream_root->first_child; + FlatpakXml *component = components->first_child; + + while (component != NULL) + { + FlatpakXml *component_id, *component_id_text_node; + g_autofree char *component_id_text = NULL; + + if (g_strcmp0 (component->element_name, "component") != 0) + { + component = component->next_sibling; + continue; + } + + component_id = flatpak_xml_find (component, "id", NULL); + component_id_text_node = flatpak_xml_find (component_id, NULL, NULL); + + component_id_text = g_strstrip (g_strdup (component_id_text_node->text)); + if (!g_str_has_suffix (component_id_text, ".desktop")) + { + component = component->next_sibling; + continue; + } + + g_print ("Extracting icons for component %s\n", component_id_text); + component_id_text[strlen (component_id_text) - strlen (".desktop")] = 0; + + if (!copy_icon (component_id_text, root, dest, "64x64", &my_error)) + { + g_print ("Error copying 64x64 icon: %s\n", my_error->message); + g_clear_error (&my_error); + } + if (!copy_icon (component_id_text, root, dest, "128x128", &my_error)) + { + g_print ("Error copying 128x128 icon: %s\n", my_error->message); + g_clear_error (&my_error); + } + + component = component->next_sibling; + } + } + + return TRUE; +} + +FlatpakXml * +flatpak_appstream_xml_new (void) +{ + FlatpakXml *appstream_root = NULL; + FlatpakXml *appstream_components; + + appstream_root = flatpak_xml_new ("root"); + appstream_components = flatpak_xml_new ("components"); + flatpak_xml_add (appstream_root, appstream_components); + flatpak_xml_add (appstream_components, flatpak_xml_new_text ("\n ")); + + appstream_components->attribute_names = g_new0 (char *, 3); + appstream_components->attribute_values = g_new0 (char *, 3); + appstream_components->attribute_names[0] = g_strdup ("version"); + appstream_components->attribute_values[0] = g_strdup ("0.8"); + appstream_components->attribute_names[1] = g_strdup ("origin"); + appstream_components->attribute_values[1] = g_strdup ("xdg-app"); + + return appstream_root; +} + +GBytes * +flatpak_appstream_xml_root_to_data (FlatpakXml *appstream_root, + GError **error) +{ + g_autoptr(GString) xml = NULL; + g_autoptr(GZlibCompressor) compressor = NULL; + g_autoptr(GOutputStream) out2 = NULL; + g_autoptr(GOutputStream) out = NULL; + + flatpak_xml_add (appstream_root->first_child, flatpak_xml_new_text ("\n")); + + xml = g_string_new (""); + flatpak_xml_to_string (appstream_root, xml); + + compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1); + out = g_memory_output_stream_new_resizable (); + out2 = g_converter_output_stream_new (out, G_CONVERTER (compressor)); + if (!g_output_stream_write_all (out2, xml->str, xml->len, + NULL, NULL, error)) + return NULL; + if (!g_output_stream_close (out2, NULL, error)) + return NULL; + + return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out)); +} + +gboolean +flatpak_repo_generate_appstream (OstreeRepo *repo, + const char **gpg_key_ids, + const char *gpg_homedir, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GHashTable) all_refs = NULL; + g_autoptr(GHashTable) arches = NULL; + GHashTableIter iter; + gpointer key; + gpointer value; + gboolean skip_commit = FALSE; + + arches = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + if (!ostree_repo_list_refs (repo, + NULL, + &all_refs, + cancellable, + error)) + return FALSE; + + g_hash_table_iter_init (&iter, all_refs); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const char *ref = key; + const char *arch; + g_auto(GStrv) split = NULL; + + split = flatpak_decompose_ref (ref, NULL); + if (!split) + continue; + + arch = split[2]; + if (!g_hash_table_contains (arches, arch)) + g_hash_table_insert (arches, g_strdup (arch), GINT_TO_POINTER (1)); + } + + g_hash_table_iter_init (&iter, arches); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GHashTableIter iter2; + const char *arch = key; + g_autofree char *tmpdir = g_strdup ("/tmp/xdg-app-appstream-XXXXXX"); + g_autoptr(FlatpakTempDir) tmpdir_file = NULL; + g_autoptr(GFile) appstream_file = NULL; + g_autoptr(GFile) root = NULL; + g_autoptr(OstreeMutableTree) mtree = NULL; + g_autofree char *commit_checksum = NULL; + OstreeRepoTransactionStats stats; + g_autoptr(OstreeRepoCommitModifier) modifier = NULL; + g_autofree char *parent = NULL; + g_autofree char *branch = NULL; + g_autoptr(FlatpakXml) appstream_root = NULL; + g_autoptr(GBytes) xml_data = NULL; + + if (g_mkdtemp (tmpdir) == NULL) + return flatpak_fail (error, "Can't create temporary directory"); + + tmpdir_file = g_file_new_for_path (tmpdir); + + appstream_root = flatpak_appstream_xml_new (); + + g_hash_table_iter_init (&iter2, all_refs); + while (g_hash_table_iter_next (&iter2, &key, &value)) + { + const char *ref = key; + g_auto(GStrv) split = NULL; + g_autoptr(GError) my_error = NULL; + + split = flatpak_decompose_ref (ref, NULL); + if (!split) + continue; + + if (strcmp (split[2], arch) != 0) + continue; + + if (!extract_appstream (repo, appstream_root, + ref, split[1], tmpdir_file, + cancellable, &my_error)) + { + g_print ("No appstream data for %s\n", ref); + continue; + } + } + + xml_data = flatpak_appstream_xml_root_to_data (appstream_root, error); + if (xml_data == NULL) + return FALSE; + + appstream_file = g_file_get_child (tmpdir_file, "appstream.xml.gz"); + + if (!g_file_replace_contents (appstream_file, + g_bytes_get_data (xml_data, NULL), + g_bytes_get_size (xml_data), + NULL, + FALSE, + G_FILE_CREATE_NONE, + NULL, + cancellable, + error)) + return FALSE; + + if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) + return FALSE; + + branch = g_strdup_printf ("appstream/%s", arch); + + if (!ostree_repo_resolve_rev (repo, branch, TRUE, &parent, error)) + goto out; + + mtree = ostree_mutable_tree_new (); + + modifier = ostree_repo_commit_modifier_new (OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS, + (OstreeRepoCommitFilter) commit_filter, NULL, NULL); + + if (!ostree_repo_write_directory_to_mtree (repo, G_FILE (tmpdir_file), mtree, modifier, cancellable, error)) + goto out; + + if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error)) + goto out; + + + /* No need to commit if nothing changed */ + if (parent) + { + g_autoptr(GFile) parent_root; + + if (!ostree_repo_read_commit (repo, parent, &parent_root, NULL, cancellable, error)) + goto out; + + if (g_file_equal (root, parent_root)) + skip_commit = TRUE; + } + + if (!skip_commit) + { + if (!ostree_repo_write_commit (repo, parent, "Update", NULL, NULL, + OSTREE_REPO_FILE (root), + &commit_checksum, cancellable, error)) + goto out; + + if (gpg_key_ids) + { + int i; + + for (i = 0; gpg_key_ids[i] != NULL; i++) + { + const char *keyid = gpg_key_ids[i]; + + if (!ostree_repo_sign_commit (repo, + commit_checksum, + keyid, + gpg_homedir, + cancellable, + error)) + goto out; + } + } + + ostree_repo_transaction_set_ref (repo, NULL, branch, commit_checksum); + + if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error)) + goto out; + } + else + { + ostree_repo_abort_transaction (repo, cancellable, NULL); + } + } + + return TRUE; + +out: + ostree_repo_abort_transaction (repo, cancellable, NULL); + return FALSE; +} + +void +flatpak_extension_free (FlatpakExtension *extension) +{ + g_free (extension->id); + g_free (extension->installed_id); + g_free (extension->ref); + g_free (extension->directory); + g_free (extension); +} + +static FlatpakExtension * +flatpak_extension_new (const char *id, + const char *extension, + const char *arch, + const char *branch, + const char *directory) +{ + FlatpakExtension *ext = g_new0 (FlatpakExtension, 1); + + ext->id = g_strdup (id); + ext->installed_id = g_strdup (extension); + ext->ref = g_build_filename ("runtime", extension, arch, branch, NULL); + ext->directory = g_strdup (directory); + return ext; +} + +GList * +flatpak_list_extensions (GKeyFile *metakey, + const char *arch, + const char *default_branch) +{ + g_auto(GStrv) groups = NULL; + int i; + GList *res; + + res = NULL; + + if (arch == NULL) + arch = flatpak_get_arch (); + + groups = g_key_file_get_groups (metakey, NULL); + for (i = 0; groups[i] != NULL; i++) + { + FlatpakExtension *ext; + char *extension; + + if (g_str_has_prefix (groups[i], "Extension ") && + *(extension = (groups[i] + strlen ("Extension "))) != 0) + { + g_autofree char *directory = g_key_file_get_string (metakey, groups[i], "directory", NULL); + g_autofree char *version = g_key_file_get_string (metakey, groups[i], "version", NULL); + g_autofree char *ref = NULL; + const char *branch; + g_autoptr(GFile) deploy = NULL; + + if (directory == NULL) + continue; + + if (version) + branch = version; + else + branch = default_branch; + + ref = g_build_filename ("runtime", extension, arch, branch, NULL); + + deploy = flatpak_find_deploy_dir_for_ref (ref, NULL, NULL); + /* Prefer a full extension (org.freedesktop.Locale) over subdirectory ones (org.freedesktop.Locale.sv) */ + if (deploy != NULL) + { + ext = flatpak_extension_new (extension, extension, arch, branch, directory); + res = g_list_prepend (res, ext); + } + else if (g_key_file_get_boolean (metakey, groups[i], + "subdirectories", NULL)) + { + g_autofree char *prefix = g_strconcat (extension, ".", NULL); + g_auto(GStrv) refs = NULL; + int j; + + refs = flatpak_list_deployed_refs ("runtime", prefix, arch, branch, + NULL, NULL); + for (j = 0; refs != NULL && refs[j] != NULL; j++) + { + g_autofree char *extended_dir = g_build_filename (directory, refs[j] + strlen (prefix), NULL); + + ext = flatpak_extension_new (extension, refs[j], arch, branch, extended_dir); + res = g_list_prepend (res, ext); + } + } + } + } + + return res; +} + + +typedef struct +{ + FlatpakXml *current; +} XmlData; + +FlatpakXml * +flatpak_xml_new (const gchar *element_name) +{ + FlatpakXml *node = g_new0 (FlatpakXml, 1); + + node->element_name = g_strdup (element_name); + return node; +} + +FlatpakXml * +flatpak_xml_new_text (const gchar *text) +{ + FlatpakXml *node = g_new0 (FlatpakXml, 1); + + node->text = g_strdup (text); + return node; +} + +void +flatpak_xml_add (FlatpakXml *parent, FlatpakXml *node) +{ + node->parent = parent; + + if (parent->first_child == NULL) + parent->first_child = node; + else + parent->last_child->next_sibling = node; + parent->last_child = node; +} + +static void +xml_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + XmlData *data = user_data; + FlatpakXml *node; + + node = flatpak_xml_new (element_name); + node->attribute_names = g_strdupv ((char **) attribute_names); + node->attribute_values = g_strdupv ((char **) attribute_values); + + flatpak_xml_add (data->current, node); + data->current = node; +} + +static void +xml_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + XmlData *data = user_data; + + data->current = data->current->parent; +} + +static void +xml_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + XmlData *data = user_data; + FlatpakXml *node; + + node = flatpak_xml_new (NULL); + node->text = g_strndup (text, text_len); + flatpak_xml_add (data->current, node); +} + +static void +xml_passthrough (GMarkupParseContext *context, + const gchar *passthrough_text, + gsize text_len, + gpointer user_data, + GError **error) +{ +} + +static GMarkupParser xml_parser = { + xml_start_element, + xml_end_element, + xml_text, + xml_passthrough, + NULL +}; + +void +flatpak_xml_free (FlatpakXml *node) +{ + FlatpakXml *child; + + if (node == NULL) + return; + + child = node->first_child; + while (child != NULL) + { + FlatpakXml *next = child->next_sibling; + flatpak_xml_free (child); + child = next; + } + + g_free (node->element_name); + g_free (node->text); + g_strfreev (node->attribute_names); + g_strfreev (node->attribute_values); + g_free (node); +} + + +void +flatpak_xml_to_string (FlatpakXml *node, GString *res) +{ + int i; + FlatpakXml *child; + + if (node->parent == NULL) + g_string_append (res, "\n"); + + if (node->element_name) + { + if (node->parent != NULL) + { + g_string_append (res, "<"); + g_string_append (res, node->element_name); + if (node->attribute_names) + { + for (i = 0; node->attribute_names[i] != NULL; i++) + { + g_string_append_printf (res, " %s=\"%s\"", + node->attribute_names[i], + node->attribute_values[i]); + } + } + if (node->first_child == NULL) + g_string_append (res, "/>"); + else + g_string_append (res, ">"); + } + + child = node->first_child; + while (child != NULL) + { + flatpak_xml_to_string (child, res); + child = child->next_sibling; + } + if (node->parent != NULL) + { + if (node->first_child != NULL) + g_string_append_printf (res, "", node->element_name); + } + + } + else if (node->text) + { + g_autofree char *escaped = g_markup_escape_text (node->text, -1); + g_string_append (res, escaped); + } +} + +FlatpakXml * +flatpak_xml_unlink (FlatpakXml *node, + FlatpakXml *prev_sibling) +{ + FlatpakXml *parent = node->parent; + + if (parent == NULL) + return node; + + if (parent->first_child == node) + parent->first_child = node->next_sibling; + + if (parent->last_child == node) + parent->last_child = prev_sibling; + + if (prev_sibling) + prev_sibling->next_sibling = node->next_sibling; + + node->parent = NULL; + node->next_sibling = NULL; + + return node; +} + +FlatpakXml * +flatpak_xml_find (FlatpakXml *node, + const char *type, + FlatpakXml **prev_child_out) +{ + FlatpakXml *child = NULL; + FlatpakXml *prev_child = NULL; + + child = node->first_child; + prev_child = NULL; + while (child != NULL) + { + FlatpakXml *next = child->next_sibling; + + if (g_strcmp0 (child->element_name, type) == 0) + { + if (prev_child_out) + *prev_child_out = prev_child; + return child; + } + + prev_child = child; + child = next; + } + + return NULL; +} + + +FlatpakXml * +flatpak_xml_parse (GInputStream *in, + gboolean compressed, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GInputStream) real_in = NULL; + g_autoptr(FlatpakXml) xml_root = NULL; + XmlData data = { 0 }; + char buffer[32 * 1024]; + gssize len; + g_autoptr(GMarkupParseContext) ctx = NULL; + + if (compressed) + { + g_autoptr(GZlibDecompressor) decompressor = NULL; + decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); + real_in = g_converter_input_stream_new (in, G_CONVERTER (decompressor)); + } + else + { + real_in = g_object_ref (in); + } + + xml_root = flatpak_xml_new ("root"); + data.current = xml_root; + + ctx = g_markup_parse_context_new (&xml_parser, + G_MARKUP_PREFIX_ERROR_POSITION, + &data, + NULL); + + while ((len = g_input_stream_read (real_in, buffer, sizeof (buffer), + cancellable, error)) > 0) + { + if (!g_markup_parse_context_parse (ctx, buffer, len, error)) + return NULL; + } + + if (len < 0) + return NULL; + + return g_steal_pointer (&xml_root); +} + +#define OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "(uayttay)" +#define OSTREE_STATIC_DELTA_FALLBACK_FORMAT "(yaytt)" +#define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")" + +static inline guint64 +maybe_swap_endian_u64 (gboolean swap, + guint64 v) +{ + if (!swap) + return v; + return GUINT64_SWAP_LE_BE (v); +} + +static guint64 +flatpak_bundle_get_installed_size (GVariant *bundle, gboolean byte_swap) +{ + guint64 total_size = 0, total_usize = 0; + + g_autoptr(GVariant) meta_entries = NULL; + guint i, n_parts; + + g_variant_get_child (bundle, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries); + n_parts = g_variant_n_children (meta_entries); + g_print ("Number of parts: %u\n", n_parts); + + for (i = 0; i < n_parts; i++) + { + guint32 version; + guint64 size, usize; + g_autoptr(GVariant) objects = NULL; + + g_variant_get_child (meta_entries, i, "(u@aytt@ay)", + &version, NULL, &size, &usize, &objects); + + total_size += maybe_swap_endian_u64 (byte_swap, size); + total_usize += maybe_swap_endian_u64 (byte_swap, usize); + } + + return total_usize; +} + +GVariant * +flatpak_bundle_load (GFile *file, + char **commit, + char **ref, + char **origin, + guint64 *installed_size, + GBytes **gpg_keys, + GError **error) +{ + g_autoptr(GVariant) delta = NULL; + g_autoptr(GVariant) metadata = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GVariant) to_csum_v = NULL; + guint8 endianness_char; + gboolean byte_swap = FALSE; + + GMappedFile *mfile = g_mapped_file_new (gs_file_get_path_cached (file), FALSE, error); + + if (mfile == NULL) + return NULL; + + bytes = g_mapped_file_get_bytes (mfile); + g_mapped_file_unref (mfile); + + delta = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE); + g_variant_ref_sink (delta); + + to_csum_v = g_variant_get_child_value (delta, 3); + if (!ostree_validate_structureof_csum_v (to_csum_v, error)) + return NULL; + + if (commit) + *commit = ostree_checksum_from_bytes_v (to_csum_v); + + if (installed_size) + *installed_size = flatpak_bundle_get_installed_size (delta, byte_swap); + + metadata = g_variant_get_child_value (delta, 0); + + if (g_variant_lookup (metadata, "ostree.endianness", "y", &endianness_char)) + { + int file_byte_order = G_BYTE_ORDER; + switch (endianness_char) + { + case 'l': + file_byte_order = G_LITTLE_ENDIAN; + break; + + case 'B': + file_byte_order = G_BIG_ENDIAN; + break; + + default: + break; + } + byte_swap = (G_BYTE_ORDER != file_byte_order); + } + + + if (ref != NULL) + { + if (!g_variant_lookup (metadata, "ref", "s", ref)) + { + flatpak_fail (error, "Invalid bundle, no ref in metadata"); + return NULL; + } + } + + if (origin != NULL) + { + if (!g_variant_lookup (metadata, "origin", "s", origin)) + *origin = NULL; + } + + if (gpg_keys != NULL) + { + g_autoptr(GVariant) gpg_value = g_variant_lookup_value (metadata, "gpg-keys", + G_VARIANT_TYPE ("ay")); + if (gpg_value) + { + gsize n_elements; + const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1); + *gpg_keys = g_bytes_new (data, n_elements); + } + else + { + *gpg_keys = NULL; + } + } + + /* Make a copy of the data so we can return it after freeing the file */ + return g_variant_new_from_bytes (g_variant_get_type (metadata), + g_bytes_new (g_variant_get_data (metadata), + g_variant_get_size (metadata)), + FALSE); +} + +gboolean +flatpak_pull_from_bundle (OstreeRepo *repo, + GFile *file, + const char *remote, + const char *ref, + gboolean require_gpg_signature, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *metadata_contents = NULL; + g_autofree char *to_checksum = NULL; + + g_autoptr(GFile) root = NULL; + g_autoptr(GFile) metadata_file = NULL; + g_autoptr(GInputStream) in = NULL; + g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL; + g_autoptr(GError) my_error = NULL; + g_autoptr(GVariant) metadata = NULL; + gboolean metadata_valid; + + metadata = flatpak_bundle_load (file, &to_checksum, NULL, NULL, NULL, NULL, error); + if (metadata == NULL) + return FALSE; + + g_variant_lookup (metadata, "metadata", "s", &metadata_contents); + + if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) + return FALSE; + + ostree_repo_transaction_set_ref (repo, remote, ref, to_checksum); + + if (!ostree_repo_static_delta_execute_offline (repo, + file, + FALSE, + cancellable, + error)) + return FALSE; + + gpg_result = ostree_repo_verify_commit_ext (repo, to_checksum, + NULL, NULL, cancellable, &my_error); + if (gpg_result == NULL) + { + /* NOT_FOUND means no gpg signature, we ignore this *if* there + * is no gpg key specified in the bundle or by the user */ + if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && + !require_gpg_signature) + { + g_clear_error (&my_error); + } + else + { + g_propagate_error (error, g_steal_pointer (&my_error)); + return FALSE; + } + } + else + { + /* If there is no valid gpg signature we fail, unless there is no gpg + key specified (on the command line or in the file) because then we + trust the source bundle. */ + if (ostree_gpg_verify_result_count_valid (gpg_result) == 0 && + require_gpg_signature) + return flatpak_fail (error, "GPG signatures found, but none are in trusted keyring"); + } + + if (!ostree_repo_read_commit (repo, to_checksum, &root, NULL, NULL, error)) + return FALSE; + + if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) + return FALSE; + + /* We ensure that the actual installed metadata matches the one in the + header, because you may have made decisions on wheter to install it or not + based on that data. */ + metadata_file = g_file_resolve_relative_path (root, "metadata"); + in = (GInputStream *) g_file_read (metadata_file, cancellable, NULL); + if (in != NULL) + { + g_autoptr(GMemoryOutputStream) data_stream = (GMemoryOutputStream *) g_memory_output_stream_new_resizable (); + + if (g_output_stream_splice (G_OUTPUT_STREAM (data_stream), in, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, + cancellable, error) < 0) + return FALSE; + + /* Null terminate */ + g_output_stream_write (G_OUTPUT_STREAM (data_stream), "\0", 1, NULL, NULL); + + metadata_valid = + metadata_contents != NULL && + strcmp (metadata_contents, g_memory_output_stream_get_data (data_stream)) == 0; + } + else + { + metadata_valid = (metadata_contents == NULL); + } + + if (!metadata_valid) + { + /* Immediately remove this broken commit */ + ostree_repo_set_ref_immediate (repo, remote, ref, NULL, cancellable, error); + return flatpak_fail (error, "Metadata in header and app are inconsistent"); + } + + return TRUE; +} + +/* This allocates and locks a subdir of the tmp dir, using an existing + * one with the same prefix if it is not in use already. */ +gboolean +flatpak_allocate_tmpdir (int tmpdir_dfd, + const char *tmpdir_relpath, + const char *tmpdir_prefix, + char **tmpdir_name_out, + int *tmpdir_fd_out, + GLnxLockFile *file_lock_out, + gboolean *reusing_dir_out, + GCancellable *cancellable, + GError **error) +{ + gboolean reusing_dir = FALSE; + g_autofree char *tmpdir_name = NULL; + glnx_fd_close int tmpdir_fd = -1; + + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + + /* Look for existing tmpdir (with same prefix) to reuse */ + if (!glnx_dirfd_iterator_init_at (tmpdir_dfd, tmpdir_relpath ? tmpdir_relpath : ".", FALSE, &dfd_iter, error)) + return FALSE; + + while (tmpdir_name == NULL) + { + gs_dirfd_iterator_cleanup GSDirFdIterator child_dfd_iter = { 0, }; + struct dirent *dent; + glnx_fd_close int existing_tmpdir_fd = -1; + g_autoptr(GError) local_error = NULL; + g_autofree char *lock_name = NULL; + + if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) + return FALSE; + + if (dent == NULL) + break; + + if (!g_str_has_prefix (dent->d_name, tmpdir_prefix)) + continue; + + /* Quickly skip non-dirs, if unknown we ignore ENOTDIR when opening instead */ + if (dent->d_type != DT_UNKNOWN && + dent->d_type != DT_DIR) + continue; + + if (!glnx_opendirat (dfd_iter.fd, dent->d_name, FALSE, + &existing_tmpdir_fd, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY)) + { + continue; + } + else + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + } + + lock_name = g_strconcat (dent->d_name, "-lock", NULL); + + /* We put the lock outside the dir, so we can hold the lock + * until the directory is fully removed */ + if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB, + file_lock_out, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) + { + continue; + } + else + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + } + + /* Touch the reused directory so that we don't accidentally + * remove it due to being old when cleaning up the tmpdir + */ + (void) futimens (existing_tmpdir_fd, NULL); + + /* We found an existing tmpdir which we managed to lock */ + tmpdir_name = g_strdup (dent->d_name); + tmpdir_fd = glnx_steal_fd (&existing_tmpdir_fd); + reusing_dir = TRUE; + } + + while (tmpdir_name == NULL) + { + g_autofree char *tmpdir_name_template = g_strconcat (tmpdir_prefix, "XXXXXX", NULL); + glnx_fd_close int new_tmpdir_fd = -1; + g_autoptr(GError) local_error = NULL; + g_autofree char *lock_name = NULL; + + /* No existing tmpdir found, create a new */ + + if (!glnx_mkdtempat (tmpdir_dfd, tmpdir_name_template, 0777, error)) + return FALSE; + + if (!glnx_opendirat (tmpdir_dfd, tmpdir_name_template, FALSE, + &new_tmpdir_fd, error)) + return FALSE; + + lock_name = g_strconcat (tmpdir_name_template, "-lock", NULL); + + /* Note, at this point we can race with another process that picks up this + * new directory. If that happens we need to retry, making a new directory. */ + if (!glnx_make_lock_file (tmpdir_dfd, lock_name, LOCK_EX | LOCK_NB, + file_lock_out, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) + { + continue; + } + else + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + } + + tmpdir_name = g_steal_pointer (&tmpdir_name_template); + tmpdir_fd = glnx_steal_fd (&new_tmpdir_fd); + } + + if (tmpdir_name_out) + *tmpdir_name_out = g_steal_pointer (&tmpdir_name); + + if (tmpdir_fd_out) + *tmpdir_fd_out = glnx_steal_fd (&tmpdir_fd); + + if (reusing_dir_out) + *reusing_dir_out = reusing_dir; + + return TRUE; +} diff --git a/common/flatpak-utils.h b/common/flatpak-utils.h new file mode 100644 index 0000000..7187015 --- /dev/null +++ b/common/flatpak-utils.h @@ -0,0 +1,357 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program 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 library 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 library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#ifndef __FLATPAK_UTILS_H__ +#define __FLATPAK_UTILS_H__ + +#include + +#include "libgsystem.h" +#include "libglnx/libglnx.h" +#include +#include +#include "flatpak-dbus.h" +#include "flatpak-dir.h" +#include + +gboolean flatpak_fail (GError **error, + const char *format, + ...); + +gint flatpak_strcmp0_ptr (gconstpointer a, + gconstpointer b); + +const char * flatpak_path_match_prefix (const char *pattern, + const char *path); + +const char * flatpak_get_arch (void); + +const char * flatpak_get_bwrap (void); + +GBytes * flatpak_read_stream (GInputStream *in, + gboolean null_terminate, + GError **error); + +gboolean flatpak_variant_save (GFile *dest, + GVariant *variant, + GCancellable *cancellable, + GError **error); +gboolean flatpak_variant_bsearch_str (GVariant *array, + const char *str, + int *out_pos); +gboolean flatpak_summary_lookup_ref (GVariant *summary, + const char *ref, + char **out_checksum); + +gboolean flatpak_has_name_prefix (const char *string, + const char *name); +gboolean flatpak_is_valid_name (const char *string); +gboolean flatpak_is_valid_branch (const char *string); + +char **flatpak_decompose_ref (const char *ref, + GError **error); + +char * flatpak_compose_ref (gboolean app, + const char *name, + const char *branch, + const char *arch, + GError **error); + +char * flatpak_build_untyped_ref (const char *runtime, + const char *branch, + const char *arch); +char * flatpak_build_runtime_ref (const char *runtime, + const char *branch, + const char *arch); +char * flatpak_build_app_ref (const char *app, + const char *branch, + const char *arch); +GFile * flatpak_find_deploy_dir_for_ref (const char *ref, + GCancellable *cancellable, + GError **error); +FlatpakDeploy * flatpak_find_deploy_for_ref (const char *ref, + GCancellable *cancellable, + GError **error); +char ** flatpak_list_deployed_refs (const char *type, + const char *name_prefix, + const char *branch, + const char *arch, + GCancellable *cancellable, + GError **error); + +gboolean flatpak_overlay_symlink_tree (GFile *source, + GFile *destination, + const char *symlink_prefix, + GCancellable *cancellable, + GError **error); +gboolean flatpak_remove_dangling_symlinks (GFile *dir, + GCancellable *cancellable, + GError **error); + +void flatpak_invocation_lookup_app_id (GDBusMethodInvocation *invocation, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +char *flatpak_invocation_lookup_app_id_finish (GDBusMethodInvocation *invocation, + GAsyncResult *result, + GError **error); + +void flatpak_connection_track_name_owners (GDBusConnection *connection); + +#if !GLIB_CHECK_VERSION (2, 40, 0) +static inline gboolean +g_key_file_save_to_file (GKeyFile *key_file, + const gchar *filename, + GError **error) +{ + gchar *contents; + gboolean success; + gsize length; + + contents = g_key_file_to_data (key_file, &length, NULL); + success = g_file_set_contents (filename, contents, length, error); + g_free (contents); + + return success; +} +#endif + +/* Returns the first string in subset that is not in strv */ +static inline const gchar * +g_strv_subset (const gchar * const *strv, + const gchar * const *subset) +{ + int i; + + for (i = 0; subset[i]; i++) + { + const char *key; + + key = subset[i]; + if (!g_strv_contains (strv, key)) + return key; + } + + return NULL; +} + +static inline void +flatpak_auto_unlock_helper (GMutex **mutex) +{ + if (*mutex) + g_mutex_unlock (*mutex); +} + +static inline GMutex * +flatpak_auto_lock_helper (GMutex *mutex) +{ + if (mutex) + g_mutex_lock (mutex); + return mutex; +} + +gint flatpak_mkstempat (int dir_fd, + gchar *tmpl, + int flags, + int mode); + + +typedef struct FlatpakTablePrinter FlatpakTablePrinter; + +FlatpakTablePrinter *flatpak_table_printer_new (void); +void flatpak_table_printer_free (FlatpakTablePrinter *printer); +void flatpak_table_printer_add_column (FlatpakTablePrinter *printer, + const char *text); +void flatpak_table_printer_append_with_comma (FlatpakTablePrinter *printer, + const char *text); +void flatpak_table_printer_finish_row (FlatpakTablePrinter *printer); +void flatpak_table_printer_print (FlatpakTablePrinter *printer); + +gboolean flatpak_repo_set_title (OstreeRepo *repo, + const char *title, + GError **error); +gboolean flatpak_repo_update (OstreeRepo *repo, + const char **gpg_key_ids, + const char *gpg_homedir, + GCancellable *cancellable, + GError **error); +gboolean flatpak_repo_collect_sizes (OstreeRepo *repo, + GFile *root, + guint64 *installed_size, + guint64 *download_size, + GCancellable *cancellable, + GError **error); + +gboolean flatpak_mtree_create_root (OstreeRepo *repo, + OstreeMutableTree *mtree, + GCancellable *cancellable, + GError **error); + +GVariant * flatpak_bundle_load (GFile *file, + char **commit, + char **ref, + char **origin, + guint64 *installed_size, + GBytes **gpg_keys, + GError **error); + +gboolean flatpak_pull_from_bundle (OstreeRepo *repo, + GFile *file, + const char *remote, + const char *ref, + gboolean require_gpg_signature, + GCancellable *cancellable, + GError **error); + + +typedef struct +{ + char *id; + char *installed_id; + char *ref; + char *directory; +} FlatpakExtension; + +void flatpak_extension_free (FlatpakExtension *extension); + +GList *flatpak_list_extensions (GKeyFile *metakey, + const char *arch, + const char *branch); + +gboolean flatpak_spawn (GFile *dir, + char **output, + GError **error, + const gchar *argv0, + va_list args); + +typedef enum { + FLATPAK_CP_FLAGS_NONE = 0, + FLATPAK_CP_FLAGS_MERGE = 1<<0, + FLATPAK_CP_FLAGS_NO_CHOWN = 1<<1, + FLATPAK_CP_FLAGS_MOVE = 1<<2, +} FlatpakCpFlags; + +gboolean flatpak_cp_a (GFile *src, + GFile *dest, + FlatpakCpFlags flags, + GCancellable *cancellable, + GError **error); + + +#define flatpak_autorm_rf _GLIB_CLEANUP (g_autoptr_cleanup_generic_gfree) + +static inline void +flatpak_temp_dir_destroy (void *p) +{ + GFile *dir = p; + + if (dir) + { + gs_shutil_rm_rf (dir, NULL, NULL); + g_object_unref (dir); + } +} + +typedef GFile FlatpakTempDir; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakTempDir, flatpak_temp_dir_destroy) + +#define AUTOLOCK(name) G_GNUC_UNUSED __attribute__((cleanup (flatpak_auto_unlock_helper))) GMutex * G_PASTE (auto_unlock, __LINE__) = flatpak_auto_lock_helper (&G_LOCK_NAME (name)) + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepo, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMutableTree, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeAsyncProgress, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeGpgVerifyResult, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoCommitModifier, ostree_repo_commit_modifier_unref) + +#ifndef SOUP_AUTOCLEANUPS_H +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupSession, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupMessage, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupRequest, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupURI, soup_uri_free) +#endif + +/* This uses a weird Auto prefix to avoid conflicts with later added autogenerated autoptr support, per: + * https://git.gnome.org/browse/glib/commit/?id=1c6cd5f0a3104aa9b62c7f1d3086181f63e71b59 + */ +typedef XdgAppSessionHelper AutoXdgAppSessionHelper; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoXdgAppSessionHelper, g_object_unref) + +typedef struct FlatpakXml FlatpakXml; + +struct FlatpakXml +{ + gchar *element_name; /* NULL == text */ + char **attribute_names; + char **attribute_values; + char *text; + FlatpakXml *parent; + FlatpakXml *first_child; + FlatpakXml *last_child; + FlatpakXml *next_sibling; +}; + +FlatpakXml *flatpak_xml_new (const gchar *element_name); +FlatpakXml *flatpak_xml_new_text (const gchar *text); +void flatpak_xml_add (FlatpakXml *parent, + FlatpakXml *node); +void flatpak_xml_free (FlatpakXml *node); +FlatpakXml *flatpak_xml_parse (GInputStream *in, + gboolean compressed, + GCancellable *cancellable, + GError **error); +void flatpak_xml_to_string (FlatpakXml *node, + GString *res); +FlatpakXml *flatpak_xml_unlink (FlatpakXml *node, + FlatpakXml *prev_sibling); +FlatpakXml *flatpak_xml_find (FlatpakXml *node, + const char *type, + FlatpakXml **prev_child_out); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakXml, flatpak_xml_free); + + +FlatpakXml *flatpak_appstream_xml_new (void); +gboolean flatpak_appstream_xml_migrate (FlatpakXml *source, + FlatpakXml *dest, + const char *ref, + const char *id, + GKeyFile *metadata); +GBytes *flatpak_appstream_xml_root_to_data (FlatpakXml *appstream_root, + GError **error); +gboolean flatpak_repo_generate_appstream (OstreeRepo *repo, + const char **gpg_key_ids, + const char *gpg_homedir, + GCancellable *cancellable, + GError **error); + +gboolean flatpak_allocate_tmpdir (int tmpdir_dfd, + const char *tmpdir_relpath, + const char *tmpdir_prefix, + char **tmpdir_name_out, + int *tmpdir_fd_out, + GLnxLockFile *file_lock_out, + gboolean *reusing_dir_out, + GCancellable *cancellable, + GError **error); + + +#endif /* __FLATPAK_UTILS_H__ */ diff --git a/common/xdg-app-chain-input-stream.c b/common/xdg-app-chain-input-stream.c deleted file mode 100644 index d7be407..0000000 --- a/common/xdg-app-chain-input-stream.c +++ /dev/null @@ -1,214 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2011 Colin Walters - * - * This library 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 library 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 library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include "xdg-app-chain-input-stream.h" - -enum { - PROP_0, - PROP_STREAMS -}; - -G_DEFINE_TYPE (FlatpakChainInputStream, flatpak_chain_input_stream, G_TYPE_INPUT_STREAM) - -struct _FlatpakChainInputStreamPrivate -{ - GPtrArray *streams; - guint index; -}; - -static void flatpak_chain_input_stream_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void flatpak_chain_input_stream_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void flatpak_chain_input_stream_finalize (GObject *object); -static gssize flatpak_chain_input_stream_read (GInputStream *stream, - void *buffer, - gsize count, - GCancellable *cancellable, - GError **error); -static gboolean flatpak_chain_input_stream_close (GInputStream *stream, - GCancellable *cancellable, - GError **error); - -static void -flatpak_chain_input_stream_class_init (FlatpakChainInputStreamClass *klass) -{ - GObjectClass *gobject_class = G_OBJECT_CLASS (klass); - GInputStreamClass *stream_class = G_INPUT_STREAM_CLASS (klass); - - g_type_class_add_private (klass, sizeof (FlatpakChainInputStreamPrivate)); - - gobject_class->get_property = flatpak_chain_input_stream_get_property; - gobject_class->set_property = flatpak_chain_input_stream_set_property; - gobject_class->finalize = flatpak_chain_input_stream_finalize; - - stream_class->read_fn = flatpak_chain_input_stream_read; - stream_class->close_fn = flatpak_chain_input_stream_close; - - /* - * FlatpakChainInputStream:streams: (element-type GInputStream) - * - * Chain of input streams read in order. - */ - g_object_class_install_property (gobject_class, - PROP_STREAMS, - g_param_spec_pointer ("streams", - "", "", - G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | - G_PARAM_STATIC_STRINGS)); - -} - -static void -flatpak_chain_input_stream_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - FlatpakChainInputStream *self; - - self = FLATPAK_CHAIN_INPUT_STREAM (object); - - switch (prop_id) - { - case PROP_STREAMS: - self->priv->streams = g_ptr_array_ref (g_value_get_pointer (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -flatpak_chain_input_stream_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - FlatpakChainInputStream *self; - - self = FLATPAK_CHAIN_INPUT_STREAM (object); - - switch (prop_id) - { - case PROP_STREAMS: - g_value_set_pointer (value, self->priv->streams); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -flatpak_chain_input_stream_finalize (GObject *object) -{ - FlatpakChainInputStream *stream; - - stream = (FlatpakChainInputStream *) (object); - - g_ptr_array_unref (stream->priv->streams); - - G_OBJECT_CLASS (flatpak_chain_input_stream_parent_class)->finalize (object); -} - -static void -flatpak_chain_input_stream_init (FlatpakChainInputStream *self) -{ - self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, - FLATPAK_TYPE_CHAIN_INPUT_STREAM, - FlatpakChainInputStreamPrivate); - -} - -FlatpakChainInputStream * -flatpak_chain_input_stream_new (GPtrArray *streams) -{ - FlatpakChainInputStream *stream; - - stream = g_object_new (FLATPAK_TYPE_CHAIN_INPUT_STREAM, - "streams", streams, - NULL); - - return (FlatpakChainInputStream *) (stream); -} - -static gssize -flatpak_chain_input_stream_read (GInputStream *stream, - void *buffer, - gsize count, - GCancellable *cancellable, - GError **error) -{ - FlatpakChainInputStream *self = (FlatpakChainInputStream *) stream; - GInputStream *child; - gssize res = -1; - - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - return -1; - - if (self->priv->index >= self->priv->streams->len) - return 0; - - res = 0; - while (res == 0 && self->priv->index < self->priv->streams->len) - { - child = self->priv->streams->pdata[self->priv->index]; - res = g_input_stream_read (child, - buffer, - count, - cancellable, - error); - if (res == 0) - self->priv->index++; - } - - return res; -} - -static gboolean -flatpak_chain_input_stream_close (GInputStream *stream, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - FlatpakChainInputStream *self = (gpointer) stream; - guint i; - - for (i = 0; i < self->priv->streams->len; i++) - { - GInputStream *child = self->priv->streams->pdata[i]; - if (!g_input_stream_close (child, cancellable, error)) - goto out; - } - - ret = TRUE; -out: - return ret; -} diff --git a/common/xdg-app-chain-input-stream.h b/common/xdg-app-chain-input-stream.h deleted file mode 100644 index ed66560..0000000 --- a/common/xdg-app-chain-input-stream.h +++ /dev/null @@ -1,68 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2011 Colin Walters - * - * This library 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 library 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 library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place, Suite 330, - * Boston, MA 02111-1307, USA. - * - */ - -#pragma once - -#ifndef __GI_SCANNER__ - -#include - -G_BEGIN_DECLS - -#define FLATPAK_TYPE_CHAIN_INPUT_STREAM (flatpak_chain_input_stream_get_type ()) -#define FLATPAK_CHAIN_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), FLATPAK_TYPE_CHAIN_INPUT_STREAM, FlatpakChainInputStream)) -#define FLATPAK_CHAIN_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), FLATPAK_TYPE_CHAIN_INPUT_STREAM, FlatpakChainInputStreamClass)) -#define FLATPAK_IS_CHAIN_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), FLATPAK_TYPE_CHAIN_INPUT_STREAM)) -#define FLATPAK_IS_CHAIN_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), FLATPAK_TYPE_CHAIN_INPUT_STREAM)) -#define FLATPAK_CHAIN_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), FLATPAK_TYPE_CHAIN_INPUT_STREAM, FlatpakChainInputStreamClass)) - -typedef struct _FlatpakChainInputStream FlatpakChainInputStream; -typedef struct _FlatpakChainInputStreamClass FlatpakChainInputStreamClass; -typedef struct _FlatpakChainInputStreamPrivate FlatpakChainInputStreamPrivate; - -struct _FlatpakChainInputStream -{ - GInputStream parent_instance; - - /*< private >*/ - FlatpakChainInputStreamPrivate *priv; -}; - -struct _FlatpakChainInputStreamClass -{ - GInputStreamClass parent_class; - - /*< private >*/ - /* Padding for future expansion */ - void (*_g_reserved1) (void); - void (*_g_reserved2) (void); - void (*_g_reserved3) (void); - void (*_g_reserved4) (void); - void (*_g_reserved5) (void); -}; - -GType flatpak_chain_input_stream_get_type (void) G_GNUC_CONST; - -FlatpakChainInputStream * flatpak_chain_input_stream_new (GPtrArray *streams); - -G_END_DECLS - -#endif diff --git a/common/xdg-app-common-types.h b/common/xdg-app-common-types.h deleted file mode 100644 index 0243eca..0000000 --- a/common/xdg-app-common-types.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright © 2015 Red Hat, Inc - * - * This program 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 library 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 library. If not, see . - * - * Authors: - * Alexander Larsson - */ - -#ifndef __FLATPAK_COMMON_TYPES_H__ -#define __FLATPAK_COMMON_TYPES_H__ - -typedef struct FlatpakDir FlatpakDir; -typedef struct FlatpakDeploy FlatpakDeploy; -typedef struct FlatpakContext FlatpakContext; - -#endif /* __FLATPAK_COMMON_TYPES_H__ */ diff --git a/common/xdg-app-db.c b/common/xdg-app-db.c deleted file mode 100644 index 9e8af7c..0000000 --- a/common/xdg-app-db.c +++ /dev/null @@ -1,1224 +0,0 @@ -/* 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 . - * - * Authors: - * Alexander Larsson - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -#include "xdg-app-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); -} diff --git a/common/xdg-app-db.h b/common/xdg-app-db.h deleted file mode 100644 index 738cf9b..0000000 --- a/common/xdg-app-db.h +++ /dev/null @@ -1,103 +0,0 @@ -/* xdg-app-db.h - * - * Copyright © 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 . - * - * Authors: - * Alexander Larsson - */ - -#ifndef FLATPAK_DB_H -#define FLATPAK_DB_H - -#include - -#include "libglnx/libglnx.h" -#include - -G_BEGIN_DECLS - -typedef struct FlatpakDb FlatpakDb; -typedef struct _FlatpakDbEntry FlatpakDbEntry; - -#define FLATPAK_TYPE_DB (flatpak_db_get_type ()) -#define FLATPAK_DB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_DB, FlatpakDb)) -#define FLATPAK_IS_DB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_DB)) - -GType flatpak_db_get_type (void); - -FlatpakDb * flatpak_db_new (const char *path, - gboolean fail_if_not_found, - GError **error); -char ** flatpak_db_list_ids (FlatpakDb *self); -char ** flatpak_db_list_apps (FlatpakDb *self); -char ** flatpak_db_list_ids_by_app (FlatpakDb *self, - const char *app); -char ** flatpak_db_list_ids_by_value (FlatpakDb *self, - GVariant *data); -FlatpakDbEntry *flatpak_db_lookup (FlatpakDb *self, - const char *id); -GString * flatpak_db_print_string (FlatpakDb *self, - GString *string); -char * flatpak_db_print (FlatpakDb *self); - -gboolean flatpak_db_is_dirty (FlatpakDb *self); -void flatpak_db_set_entry (FlatpakDb *self, - const char *id, - FlatpakDbEntry *entry); -void flatpak_db_update (FlatpakDb *self); -GBytes * flatpak_db_get_content (FlatpakDb *self); -const char * flatpak_db_get_path (FlatpakDb *self); -gboolean flatpak_db_save_content (FlatpakDb *self, - GError **error); -void flatpak_db_save_content_async (FlatpakDb *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean flatpak_db_save_content_finish (FlatpakDb *self, - GAsyncResult *res, - GError **error); -void flatpak_db_set_path (FlatpakDb *self, - const char *path); - - -FlatpakDbEntry *flatpak_db_entry_ref (FlatpakDbEntry *entry); -void flatpak_db_entry_unref (FlatpakDbEntry *entry); -GVariant * flatpak_db_entry_get_data (FlatpakDbEntry *entry); -const char ** flatpak_db_entry_list_apps (FlatpakDbEntry *entry); -const char ** flatpak_db_entry_list_permissions (FlatpakDbEntry *entry, - const char *app); -gboolean flatpak_db_entry_has_permission (FlatpakDbEntry *entry, - const char *app, - const char *permission); -gboolean flatpak_db_entry_has_permissions (FlatpakDbEntry *entry, - const char *app, - const char **permissions); -GString * flatpak_db_entry_print_string (FlatpakDbEntry *entry, - GString *string); - -FlatpakDbEntry *flatpak_db_entry_new (GVariant *data); -FlatpakDbEntry *flatpak_db_entry_modify_data (FlatpakDbEntry *entry, - GVariant *data); -FlatpakDbEntry *flatpak_db_entry_set_app_permissions (FlatpakDbEntry *entry, - const char *app, - const char **permissions); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDb, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDbEntry, flatpak_db_entry_unref) - -G_END_DECLS - -#endif /* FLATPAK_DB_H */ diff --git a/common/xdg-app-dir.c b/common/xdg-app-dir.c deleted file mode 100644 index 0fa1afa..0000000 --- a/common/xdg-app-dir.c +++ /dev/null @@ -1,4265 +0,0 @@ -/* - * Copyright © 2014 Red Hat, Inc - * - * This program 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 library 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 library. If not, see . - * - * Authors: - * Alexander Larsson - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include -#include "libgsystem.h" -#include "libglnx/libglnx.h" - -#include "xdg-app-dir.h" -#include "xdg-app-utils.h" -#include "xdg-app-run.h" - -#include "errno.h" - -#define NO_SYSTEM_HELPER ((XdgAppSystemHelper *) (gpointer) 1) - -struct FlatpakDir -{ - GObject parent; - - gboolean user; - GFile *basedir; - OstreeRepo *repo; - - XdgAppSystemHelper *system_helper; - - SoupSession *soup_session; -}; - -typedef struct -{ - GObjectClass parent_class; -} FlatpakDirClass; - -struct FlatpakDeploy -{ - GObject parent; - - GFile *dir; - GKeyFile *metadata; - FlatpakContext *system_overrides; - FlatpakContext *user_overrides; -}; - -typedef struct -{ - GObjectClass parent_class; -} FlatpakDeployClass; - -G_DEFINE_TYPE (FlatpakDir, flatpak_dir, G_TYPE_OBJECT) -G_DEFINE_TYPE (FlatpakDeploy, flatpak_deploy, G_TYPE_OBJECT) - -G_DEFINE_QUARK (xdg - app - dir - error - quark, flatpak_dir_error) - -enum { - PROP_0, - - PROP_USER, - PROP_PATH -}; - -#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \ - "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev") - -static void -flatpak_deploy_finalize (GObject *object) -{ - FlatpakDeploy *self = FLATPAK_DEPLOY (object); - - g_clear_object (&self->dir); - g_clear_pointer (&self->metadata, g_key_file_unref); - g_clear_pointer (&self->system_overrides, g_key_file_unref); - g_clear_pointer (&self->user_overrides, g_key_file_unref); - - G_OBJECT_CLASS (flatpak_deploy_parent_class)->finalize (object); -} - -static void -flatpak_deploy_class_init (FlatpakDeployClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = flatpak_deploy_finalize; - -} - -static void -flatpak_deploy_init (FlatpakDeploy *self) -{ -} - -GFile * -flatpak_deploy_get_dir (FlatpakDeploy *deploy) -{ - return g_object_ref (deploy->dir); -} - -GFile * -flatpak_deploy_get_files (FlatpakDeploy *deploy) -{ - return g_file_get_child (deploy->dir, "files"); -} - -FlatpakContext * -flatpak_deploy_get_overrides (FlatpakDeploy *deploy) -{ - FlatpakContext *overrides = flatpak_context_new (); - - if (deploy->system_overrides) - flatpak_context_merge (overrides, deploy->system_overrides); - - if (deploy->user_overrides) - flatpak_context_merge (overrides, deploy->user_overrides); - - return overrides; -} - -GKeyFile * -flatpak_deploy_get_metadata (FlatpakDeploy *deploy) -{ - return g_key_file_ref (deploy->metadata); -} - -static FlatpakDeploy * -flatpak_deploy_new (GFile *dir, GKeyFile *metadata) -{ - FlatpakDeploy *deploy; - - deploy = g_object_new (FLATPAK_TYPE_DEPLOY, NULL); - deploy->dir = g_object_ref (dir); - deploy->metadata = g_key_file_ref (metadata); - - return deploy; -} - -GFile * -flatpak_get_system_base_dir_location (void) -{ - return g_file_new_for_path (FLATPAK_SYSTEMDIR); -} - -GFile * -flatpak_get_user_base_dir_location (void) -{ - g_autofree char *base = g_build_filename (g_get_user_data_dir (), "xdg-app", NULL); - - return g_file_new_for_path (base); -} - -GFile * -flatpak_get_user_cache_dir_location (void) -{ - g_autoptr(GFile) base_dir = NULL; - - base_dir = flatpak_get_user_base_dir_location (); - return g_file_get_child (base_dir, "system-cache"); -} - -GFile * -flatpak_ensure_user_cache_dir_location (GError **error) -{ - g_autoptr(GFile) cache_dir = NULL; - g_autofree char *cache_path = NULL; - - cache_dir = flatpak_get_user_cache_dir_location (); - cache_path = g_file_get_path (cache_dir); - - if (g_mkdir_with_parents (cache_path, 0755) != 0) - { - glnx_set_error_from_errno (error); - return NULL; - } - - return g_steal_pointer (&cache_dir); -} - -static XdgAppSystemHelper * -flatpak_dir_get_system_helper (FlatpakDir *self) -{ - g_autoptr(GError) error = NULL; - - if (g_once_init_enter (&self->system_helper)) - { - XdgAppSystemHelper *system_helper; - system_helper = - xdg_app_system_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | - G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, - "org.freedesktop.Flatpak.SystemHelper", - "/org/freedesktop/Flatpak/SystemHelper", - NULL, &error); - if (error != NULL) - { - g_warning ("Can't find org.freedesktop.Flatpak.SystemHelper: %s\n", error->message); - system_helper = NO_SYSTEM_HELPER; - } - - g_once_init_leave (&self->system_helper, system_helper); - } - - if (self->system_helper != NO_SYSTEM_HELPER) - return self->system_helper; - return NULL; -} - -gboolean -flatpak_dir_use_child_repo (FlatpakDir *self) -{ - XdgAppSystemHelper *system_helper; - - if (self->user || getuid () == 0) - return FALSE; - - system_helper = flatpak_dir_get_system_helper (self); - - return system_helper != NULL; -} - -static OstreeRepo * -system_ostree_repo_new (GFile *repodir) -{ - return g_object_new (OSTREE_TYPE_REPO, "path", repodir, - "remotes-config-dir", FLATPAK_CONFIGDIR "/remotes.d", - NULL); -} - -static void -flatpak_dir_finalize (GObject *object) -{ - FlatpakDir *self = FLATPAK_DIR (object); - - g_clear_object (&self->repo); - g_clear_object (&self->basedir); - - g_clear_object (&self->system_helper); - - g_clear_object (&self->soup_session); - - G_OBJECT_CLASS (flatpak_dir_parent_class)->finalize (object); -} - -static void -flatpak_dir_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - FlatpakDir *self = FLATPAK_DIR (object); - - switch (prop_id) - { - case PROP_PATH: - /* Canonicalize */ - self->basedir = g_file_new_for_path (gs_file_get_path_cached (g_value_get_object (value))); - break; - - case PROP_USER: - self->user = g_value_get_boolean (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -flatpak_dir_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - FlatpakDir *self = FLATPAK_DIR (object); - - switch (prop_id) - { - case PROP_PATH: - g_value_set_object (value, self->basedir); - break; - - case PROP_USER: - g_value_set_boolean (value, self->user); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -flatpak_dir_class_init (FlatpakDirClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->get_property = flatpak_dir_get_property; - object_class->set_property = flatpak_dir_set_property; - object_class->finalize = flatpak_dir_finalize; - - g_object_class_install_property (object_class, - PROP_USER, - g_param_spec_boolean ("user", - "", - "", - FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - g_object_class_install_property (object_class, - PROP_PATH, - g_param_spec_object ("path", - "", - "", - G_TYPE_FILE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); -} - -static void -flatpak_dir_init (FlatpakDir *self) -{ -} - -gboolean -flatpak_dir_is_user (FlatpakDir *self) -{ - return self->user; -} - -GFile * -flatpak_dir_get_path (FlatpakDir *self) -{ - return self->basedir; -} - -GFile * -flatpak_dir_get_changed_path (FlatpakDir *self) -{ - return g_file_get_child (self->basedir, ".changed"); -} - -char * -flatpak_dir_load_override (FlatpakDir *self, - const char *app_id, - gsize *length, - GError **error) -{ - g_autoptr(GFile) override_dir = NULL; - g_autoptr(GFile) file = NULL; - char *metadata_contents; - - override_dir = g_file_get_child (self->basedir, "overrides"); - file = g_file_get_child (override_dir, app_id); - - if (!g_file_load_contents (file, NULL, - &metadata_contents, length, NULL, NULL)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "No overrides found for %s", app_id); - return NULL; - } - - return metadata_contents; -} - -GKeyFile * -flatpak_load_override_keyfile (const char *app_id, gboolean user, GError **error) -{ - g_autofree char *metadata_contents = NULL; - gsize metadata_size; - - g_autoptr(GKeyFile) metakey = g_key_file_new (); - g_autoptr(FlatpakDir) dir = NULL; - - dir = flatpak_dir_get (user); - - metadata_contents = flatpak_dir_load_override (dir, app_id, &metadata_size, error); - if (metadata_contents == NULL) - return NULL; - - if (!g_key_file_load_from_data (metakey, - metadata_contents, - metadata_size, - 0, error)) - return NULL; - - return g_steal_pointer (&metakey); -} - -FlatpakContext * -flatpak_load_override_file (const char *app_id, gboolean user, GError **error) -{ - FlatpakContext *overrides = flatpak_context_new (); - - g_autoptr(GKeyFile) metakey = NULL; - g_autoptr(GError) my_error = NULL; - - metakey = flatpak_load_override_keyfile (app_id, user, &my_error); - if (metakey == NULL) - { - if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_propagate_error (error, g_steal_pointer (&my_error)); - return NULL; - } - } - else - { - if (!flatpak_context_load_metadata (overrides, metakey, error)) - return NULL; - } - - return g_steal_pointer (&overrides); -} - -gboolean -flatpak_save_override_keyfile (GKeyFile *metakey, - const char *app_id, - gboolean user, - GError **error) -{ - g_autoptr(GFile) base_dir = NULL; - g_autoptr(GFile) override_dir = NULL; - g_autoptr(GFile) file = NULL; - g_autofree char *filename = NULL; - g_autofree char *parent = NULL; - - if (user) - base_dir = flatpak_get_user_base_dir_location (); - else - base_dir = flatpak_get_system_base_dir_location (); - - override_dir = g_file_get_child (base_dir, "overrides"); - file = g_file_get_child (override_dir, app_id); - - filename = g_file_get_path (file); - parent = g_path_get_dirname (filename); - if (g_mkdir_with_parents (parent, 0755)) - { - glnx_set_error_from_errno (error); - return FALSE; - } - - return g_key_file_save_to_file (metakey, filename, error); -} - -FlatpakDeploy * -flatpak_dir_load_deployed (FlatpakDir *self, - const char *ref, - const char *checksum, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) deploy_dir = NULL; - g_autoptr(GKeyFile) metakey = NULL; - g_autoptr(GFile) metadata = NULL; - g_auto(GStrv) ref_parts = NULL; - g_autofree char *metadata_contents = NULL; - FlatpakDeploy *deploy; - gsize metadata_size; - - deploy_dir = flatpak_dir_get_if_deployed (self, ref, checksum, cancellable); - if (deploy_dir == NULL) - { - g_set_error (error, FLATPAK_DIR_ERROR, FLATPAK_DIR_ERROR_NOT_DEPLOYED, "%s not installed", ref); - return NULL; - } - - metadata = g_file_get_child (deploy_dir, "metadata"); - if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error)) - return NULL; - - metakey = g_key_file_new (); - if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error)) - return NULL; - - deploy = flatpak_deploy_new (deploy_dir, metakey); - - ref_parts = g_strsplit (ref, "/", -1); - g_assert (g_strv_length (ref_parts) == 4); - - /* Only apps have overrides */ - if (strcmp (ref_parts[0], "app") == 0) - { - /* Only load system overrides for system installed apps */ - if (!self->user) - { - deploy->system_overrides = flatpak_load_override_file (ref_parts[1], FALSE, error); - if (deploy->system_overrides == NULL) - return NULL; - } - - /* Always load user overrides */ - deploy->user_overrides = flatpak_load_override_file (ref_parts[1], TRUE, error); - if (deploy->user_overrides == NULL) - return NULL; - } - - return deploy; -} - -GFile * -flatpak_dir_get_deploy_dir (FlatpakDir *self, - const char *ref) -{ - return g_file_resolve_relative_path (self->basedir, ref); -} - -GFile * -flatpak_dir_get_exports_dir (FlatpakDir *self) -{ - return g_file_get_child (self->basedir, "exports"); -} - -GFile * -flatpak_dir_get_removed_dir (FlatpakDir *self) -{ - return g_file_get_child (self->basedir, ".removed"); -} - -OstreeRepo * -flatpak_dir_get_repo (FlatpakDir *self) -{ - return self->repo; -} - - -/* This is an exclusive per xdg-app installation file lock that is taken - * whenever any config in the directory outside the repo is to be changed. For - * instance deployements, overrides or active commit changes. - * - * For concurrency protection of the actual repository we rely on ostree - * to do the right thing. - */ -gboolean -flatpak_dir_lock (FlatpakDir *self, - GLnxLockFile *lockfile, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) lock_file = g_file_get_child (flatpak_dir_get_path (self), "lock"); - g_autofree char *lock_path = g_file_get_path (lock_file); - - return glnx_make_lock_file (AT_FDCWD, lock_path, LOCK_EX, lockfile, error); -} - -const char * -flatpak_deploy_data_get_origin (GVariant *deploy_data) -{ - const char *origin; - - g_variant_get_child (deploy_data, 0, "&s", &origin); - return origin; -} - -const char * -flatpak_deploy_data_get_commit (GVariant *deploy_data) -{ - const char *commit; - - g_variant_get_child (deploy_data, 1, "&s", &commit); - return commit; -} - -/** - * flatpak_deploy_data_get_subpaths: - * - * Returns: (array length=length zero-terminated=1) (transfer container): an array of constant strings - **/ -const char ** -flatpak_deploy_data_get_subpaths (GVariant *deploy_data) -{ - const char **subpaths; - - g_variant_get_child (deploy_data, 2, "^as", &subpaths); - return subpaths; -} - -guint64 -flatpak_deploy_data_get_installed_size (GVariant *deploy_data) -{ - guint64 size; - - g_variant_get_child (deploy_data, 3, "t", &size); - return GUINT64_FROM_BE (size); -} - -static GVariant * -flatpak_dir_new_deploy_data (const char *origin, - const char *commit, - char **subpaths, - guint64 installed_size, - GVariant *metadata) -{ - char *empty_subpaths[] = {NULL}; - GVariantBuilder builder; - - if (metadata == NULL) - { - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); - metadata = g_variant_builder_end (&builder); - } - - return g_variant_ref_sink (g_variant_new ("(ss^ast@a{sv})", - origin, - commit, - subpaths ? subpaths : empty_subpaths, - GUINT64_TO_BE (installed_size), - metadata)); -} - -static char ** -get_old_subpaths (GFile *deploy_base, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) file = NULL; - g_autofree char *data = NULL; - g_autoptr(GError) my_error = NULL; - g_autoptr(GPtrArray) subpaths = NULL; - g_auto(GStrv) lines = NULL; - int i; - - file = g_file_get_child (deploy_base, "subpaths"); - if (!g_file_load_contents (file, cancellable, &data, NULL, NULL, &my_error)) - { - if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - data = g_strdup (""); - } - else - { - g_propagate_error (error, g_steal_pointer (&my_error)); - return NULL; - } - } - - lines = g_strsplit (data, "\n", 0); - - subpaths = g_ptr_array_new (); - for (i = 0; lines[i] != NULL; i++) - { - lines[i] = g_strstrip (lines[i]); - if (lines[i][0] == '/') - g_ptr_array_add (subpaths, g_strdup (lines[i])); - } - - g_ptr_array_add (subpaths, NULL); - return (char **) g_ptr_array_free (subpaths, FALSE); -} - -static GVariant * -flatpak_create_deploy_data_from_old (FlatpakDir *self, - GFile *deploy_dir, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) deploy_base = NULL; - g_autofree char *old_origin = NULL; - g_autofree char *commit = NULL; - g_auto(GStrv) old_subpaths = NULL; - g_autoptr(GFile) root = NULL; - g_autoptr(GFile) origin = NULL; - guint64 installed_size; - - deploy_base = g_file_get_parent (deploy_dir); - commit = g_file_get_basename (deploy_dir); - - origin = g_file_get_child (deploy_base, "origin"); - if (!g_file_load_contents (origin, cancellable, &old_origin, NULL, NULL, error)) - return NULL; - - old_subpaths = get_old_subpaths (deploy_base, cancellable, error); - if (old_subpaths == NULL) - return NULL; - - /* For backwards compat we return a 0 installed size, its to slow to regenerate */ - installed_size = 0; - - return flatpak_dir_new_deploy_data (old_origin, commit, old_subpaths, - installed_size, NULL); -} - -GVariant * -flatpak_dir_get_deploy_data (FlatpakDir *self, - const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) deploy_dir = NULL; - g_autoptr(GFile) data_file = NULL; - g_autoptr(GError) my_error = NULL; - char *data = NULL; - gsize data_size; - g_autofree char *active = NULL; - - deploy_dir = flatpak_dir_get_if_deployed (self, ref, NULL, cancellable); - if (deploy_dir == NULL) - { - flatpak_fail (error, "%s is not installed", ref); - return NULL; - } - - data_file = g_file_get_child (deploy_dir, "deploy"); - if (!g_file_load_contents (data_file, cancellable, &data, &data_size, NULL, &my_error)) - { - if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_propagate_error (error, g_steal_pointer (&my_error)); - return NULL; - } - - return flatpak_create_deploy_data_from_old (self, deploy_dir, - cancellable, error); - } - - return g_variant_ref_sink (g_variant_new_from_data (FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT, - data, data_size, - FALSE, g_free, data)); -} - - -char * -flatpak_dir_get_origin (FlatpakDir *self, - const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GVariant) deploy_data = NULL; - - deploy_data = flatpak_dir_get_deploy_data (self, ref, - cancellable, error); - if (deploy_data == NULL) - { - flatpak_fail (error, "%s is not installed", ref); - return NULL; - } - - return g_strdup (flatpak_deploy_data_get_origin (deploy_data)); -} - -char ** -flatpak_dir_get_subpaths (FlatpakDir *self, - const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GVariant) deploy_data = NULL; - char **subpaths; - int i; - - deploy_data = flatpak_dir_get_deploy_data (self, ref, - cancellable, error); - if (deploy_data == NULL) - { - flatpak_fail (error, "%s is not installed", ref); - return NULL; - } - - subpaths = (char **) flatpak_deploy_data_get_subpaths (deploy_data); - for (i = 0; subpaths[i] != NULL; i++) - subpaths[i] = g_strdup (subpaths[i]); - - return subpaths; -} - -gboolean -flatpak_dir_ensure_path (FlatpakDir *self, - GCancellable *cancellable, - GError **error) -{ - return gs_file_ensure_directory (self->basedir, TRUE, cancellable, error); -} - -gboolean -flatpak_dir_ensure_repo (FlatpakDir *self, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) repodir = NULL; - g_autoptr(OstreeRepo) repo = NULL; - - if (self->repo == NULL) - { - if (!flatpak_dir_ensure_path (self, cancellable, error)) - goto out; - - repodir = g_file_get_child (self->basedir, "repo"); - if (self->user) - { - repo = ostree_repo_new (repodir); - } - else - { - g_autoptr(GFile) cache_dir = NULL; - g_autofree char *cache_path = NULL; - - repo = system_ostree_repo_new (repodir); - - cache_dir = flatpak_ensure_user_cache_dir_location (error); - if (cache_dir == NULL) - goto out; - - cache_path = g_file_get_path (cache_dir); - if (!ostree_repo_set_cache_dir (repo, - AT_FDCWD, cache_path, - cancellable, error)) - goto out; - } - - if (!g_file_query_exists (repodir, cancellable)) - { - if (!ostree_repo_create (repo, - OSTREE_REPO_MODE_BARE_USER, - cancellable, error)) - { - gs_shutil_rm_rf (repodir, cancellable, NULL); - goto out; - } - - /* Create .changes file early to avoid polling non-existing file in monitor */ - flatpak_dir_mark_changed (self, NULL); - } - else - { - if (!ostree_repo_open (repo, cancellable, error)) - { - g_autofree char *repopath = NULL; - - repopath = g_file_get_path (repodir); - g_prefix_error (error, "While opening repository %s: ", repopath); - goto out; - } - } - - self->repo = g_object_ref (repo); - } - - ret = TRUE; -out: - return ret; -} - -gboolean -flatpak_dir_mark_changed (FlatpakDir *self, - GError **error) -{ - g_autoptr(GFile) changed_file = NULL; - - changed_file = flatpak_dir_get_changed_path (self); - if (!g_file_replace_contents (changed_file, "", 0, NULL, FALSE, - G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error)) - return FALSE; - - return TRUE; -} - -gboolean -flatpak_dir_remove_appstream (FlatpakDir *self, - const char *remote, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) appstream_dir = NULL; - g_autoptr(GFile) remote_dir = NULL; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream"); - remote_dir = g_file_get_child (appstream_dir, remote); - - if (g_file_query_exists (remote_dir, cancellable) && - !gs_shutil_rm_rf (remote_dir, cancellable, error)) - return FALSE; - - return TRUE; -} - -gboolean -flatpak_dir_remove_all_refs (FlatpakDir *self, - const char *remote, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *prefix = NULL; - - g_autoptr(GHashTable) refs = NULL; - GHashTableIter hash_iter; - gpointer key; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - prefix = g_strdup_printf ("%s:", remote); - - if (!ostree_repo_list_refs (self->repo, - NULL, - &refs, - cancellable, error)) - return FALSE; - - g_hash_table_iter_init (&hash_iter, refs); - while (g_hash_table_iter_next (&hash_iter, &key, NULL)) - { - const char *refspec = key; - - if (g_str_has_prefix (refspec, prefix) && - !flatpak_dir_remove_ref (self, remote, refspec + strlen (prefix), cancellable, error)) - return FALSE; - } - - return TRUE; -} - -gboolean -flatpak_dir_update_appstream (FlatpakDir *self, - const char *remote, - const char *arch, - gboolean *out_changed, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *branch = NULL; - g_autofree char *remote_and_branch = NULL; - g_autofree char *old_checksum = NULL; - g_autofree char *new_checksum = NULL; - - g_autoptr(GFile) root = NULL; - g_autoptr(GFile) appstream_dir = NULL; - g_autoptr(GFile) remote_dir = NULL; - g_autoptr(GFile) arch_dir = NULL; - g_autoptr(GFile) checkout_dir = NULL; - g_autoptr(GFile) old_checkout_dir = NULL; - g_autoptr(GFileInfo) file_info = NULL; - g_autofree char *arch_path = NULL; - g_autofree char *tmpname = NULL; - g_autoptr(GFile) active_tmp_link = NULL; - g_autoptr(GFile) active_link = NULL; - g_autoptr(GFile) timestamp_file = NULL; - g_autoptr(GError) tmp_error = NULL; - gboolean checkout_exists; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - if (arch == NULL) - arch = flatpak_get_arch (); - - branch = g_strdup_printf ("appstream/%s", arch); - remote_and_branch = g_strdup_printf ("%s:%s", remote, branch); - - if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &old_checksum, error)) - return FALSE; - - if (!flatpak_dir_pull (self, remote, branch, NULL, NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress, - cancellable, error)) - return FALSE; - - if (!ostree_repo_resolve_rev (self->repo, remote_and_branch, TRUE, &new_checksum, error)) - return FALSE; - - if (new_checksum == NULL) - { - g_warning ("No appstream branch in remote %s\n", remote); - return TRUE; - } - - appstream_dir = g_file_get_child (flatpak_dir_get_path (self), "appstream"); - remote_dir = g_file_get_child (appstream_dir, remote); - arch_dir = g_file_get_child (remote_dir, arch); - checkout_dir = g_file_get_child (arch_dir, new_checksum); - timestamp_file = g_file_get_child (arch_dir, ".timestamp"); - - arch_path = g_file_get_path (arch_dir); - if (g_mkdir_with_parents (arch_path, 0755) != 0) - { - glnx_set_error_from_errno (error); - return FALSE; - } - - checkout_exists = g_file_query_exists (checkout_dir, NULL); - - if (old_checksum != NULL && new_checksum != NULL && - strcmp (old_checksum, new_checksum) == 0 && - checkout_exists) - { - if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE, - G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error)) - return FALSE; - - if (out_changed) - *out_changed = FALSE; - return TRUE; /* No changes, don't checkout */ - } - - if (!ostree_repo_read_commit (self->repo, new_checksum, &root, NULL, cancellable, error)) - return FALSE; - - file_info = g_file_query_info (root, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (file_info == NULL) - return FALSE; - - if (!ostree_repo_checkout_tree (self->repo, - OSTREE_REPO_CHECKOUT_MODE_USER, - OSTREE_REPO_CHECKOUT_OVERWRITE_NONE, - checkout_dir, - OSTREE_REPO_FILE (root), file_info, - cancellable, error)) - return FALSE; - - tmpname = gs_fileutil_gen_tmp_name (".active-", NULL); - active_tmp_link = g_file_get_child (arch_dir, tmpname); - active_link = g_file_get_child (arch_dir, "active"); - - if (!g_file_make_symbolic_link (active_tmp_link, new_checksum, cancellable, error)) - return FALSE; - - if (!gs_file_rename (active_tmp_link, - active_link, - cancellable, error)) - return FALSE; - - if (old_checksum != NULL && - g_strcmp0 (old_checksum, new_checksum) != 0) - { - old_checkout_dir = g_file_get_child (arch_dir, old_checksum); - if (!gs_shutil_rm_rf (old_checkout_dir, cancellable, &tmp_error)) - g_warning ("Unable to remove old appstream checkout: %s\n", tmp_error->message); - } - - if (!g_file_replace_contents (timestamp_file, "", 0, NULL, FALSE, - G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, error)) - return FALSE; - - /* If we added a new checkout, touch the toplevel dir to tell people that they need - to re-scan */ - if (!checkout_exists) - { - g_autofree char *appstream_dir_path = g_file_get_path (appstream_dir); - utime (appstream_dir_path, NULL); - } - - if (out_changed) - *out_changed = TRUE; - return TRUE; -} - -/* This is a copy of ostree_repo_pull_one_dir that always disables - static deltas if subdir is used */ -static gboolean -repo_pull_one_dir (OstreeRepo *self, - const char *remote_name, - const char *dir_to_pull, - char **refs_to_fetch, - OstreeRepoPullFlags flags, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - GVariantBuilder builder; - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); - - if (dir_to_pull) - { - g_variant_builder_add (&builder, "{s@v}", "subdir", - g_variant_new_variant (g_variant_new_string (dir_to_pull))); - g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas", - g_variant_new_variant (g_variant_new_boolean (TRUE))); - } - - g_variant_builder_add (&builder, "{s@v}", "flags", - g_variant_new_variant (g_variant_new_int32 (flags))); - if (refs_to_fetch) - g_variant_builder_add (&builder, "{s@v}", "refs", - g_variant_new_variant (g_variant_new_strv ((const char * const *) refs_to_fetch, -1))); - - return ostree_repo_pull_with_options (self, remote_name, g_variant_builder_end (&builder), - progress, cancellable, error); -} - - -gboolean -flatpak_dir_pull (FlatpakDir *self, - const char *repository, - const char *ref, - char **subpaths, - OstreeRepo *repo, - OstreeRepoPullFlags flags, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - GSConsole *console = NULL; - - g_autoptr(OstreeAsyncProgress) console_progress = NULL; - const char *refs[2]; - g_autofree char *url = NULL; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - goto out; - - if (!ostree_repo_remote_get_url (self->repo, - repository, - &url, - error)) - goto out; - - if (*url == 0) - return TRUE; /* Empty url, silently disables updates */ - - if (repo == NULL) - repo = self->repo; - - if (progress == NULL) - { - console = gs_console_get (); - if (console) - { - gs_console_begin_status_line (console, "", NULL, NULL); - console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); - progress = console_progress; - } - } - - - refs[0] = ref; - refs[1] = NULL; - - if (subpaths == NULL || subpaths[0] == NULL) - { - if (!ostree_repo_pull (repo, repository, - (char **) refs, flags, - progress, - cancellable, error)) - { - g_prefix_error (error, "While pulling %s from remote %s: ", ref, repository); - goto out; - } - } - else - { - int i; - - if (!repo_pull_one_dir (repo, repository, - "/metadata", - (char **) refs, flags, - progress, - cancellable, error)) - { - g_prefix_error (error, "While pulling %s from remote %s, metadata: ", - ref, repository); - goto out; - } - - for (i = 0; subpaths[i] != NULL; i++) - { - g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL); - if (!repo_pull_one_dir (repo, repository, - subpath, - (char **) refs, flags, - progress, - cancellable, error)) - { - g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ", - ref, repository, subpaths[i]); - goto out; - } - } - } - - ret = TRUE; - -out: - if (console) - { - ostree_async_progress_finish (progress); - gs_console_end_status_line (console, NULL, NULL); - } - - return ret; -} - -static gboolean -repo_pull_one_untrusted (OstreeRepo *self, - const char *remote_name, - const char *url, - const char *dir_to_pull, - const char *ref, - const char *checksum, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - OstreeRepoPullFlags flags = OSTREE_REPO_PULL_FLAGS_UNTRUSTED; - GVariantBuilder builder; - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); - const char *refs[2] = { NULL, NULL }; - const char *commits[2] = { NULL, NULL }; - - refs[0] = ref; - commits[0] = checksum; - - g_variant_builder_add (&builder, "{s@v}", "flags", - g_variant_new_variant (g_variant_new_int32 (flags))); - g_variant_builder_add (&builder, "{s@v}", "refs", - g_variant_new_variant (g_variant_new_strv ((const char * const *) refs, -1))); - g_variant_builder_add (&builder, "{s@v}", "override-commit-ids", - g_variant_new_variant (g_variant_new_strv ((const char * const *) commits, -1))); - g_variant_builder_add (&builder, "{s@v}", "override-remote-name", - g_variant_new_variant (g_variant_new_string (remote_name))); - g_variant_builder_add (&builder, "{s@v}", "gpg-verify", - g_variant_new_variant (g_variant_new_boolean (TRUE))); - g_variant_builder_add (&builder, "{s@v}", "gpg-verify-summary", - g_variant_new_variant (g_variant_new_boolean (TRUE))); - - if (dir_to_pull) - { - g_variant_builder_add (&builder, "{s@v}", "subdir", - g_variant_new_variant (g_variant_new_string (dir_to_pull))); - g_variant_builder_add (&builder, "{s@v}", "disable-static-deltas", - g_variant_new_variant (g_variant_new_boolean (TRUE))); - } - - return ostree_repo_pull_with_options (self, url, g_variant_builder_end (&builder), - progress, cancellable, error); -} - -gboolean -flatpak_dir_pull_untrusted_local (FlatpakDir *self, - const char *src_path, - const char *remote_name, - const char *ref, - char **subpaths, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - GSConsole *console = NULL; - - g_autoptr(OstreeAsyncProgress) console_progress = NULL; - g_autoptr(GFile) path_file = g_file_new_for_path (src_path); - g_autoptr(GFile) summary_file = g_file_get_child (path_file, "summary"); - g_autoptr(GFile) summary_sig_file = g_file_get_child (path_file, "summary.sig"); - g_autofree char *url = g_file_get_uri (path_file); - g_autofree char *checksum = NULL; - gboolean gpg_verify_summary; - gboolean gpg_verify; - char *summary_data = NULL; - char *summary_sig_data = NULL; - gsize summary_data_size, summary_sig_data_size; - g_autoptr(GBytes) summary_bytes = NULL; - g_autoptr(GBytes) summary_sig_bytes = NULL; - g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL; - g_autoptr(GVariant) summary = NULL; - g_autoptr(GVariant) old_commit = NULL; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - if (!ostree_repo_remote_get_gpg_verify_summary (self->repo, remote_name, - &gpg_verify_summary, error)) - return FALSE; - - if (!ostree_repo_remote_get_gpg_verify (self->repo, remote_name, - &gpg_verify, error)) - return FALSE; - - if (!gpg_verify_summary || !gpg_verify) - return flatpak_fail (error, "Can't pull from untrusted non-gpg verified remote"); - - /* We verify the summary manually before anything else to make sure - we've got something right before looking too hard at the repo and - so we can check for a downgrade before pulling and updating the - ref */ - - if (!g_file_load_contents (summary_sig_file, cancellable, - &summary_sig_data, &summary_sig_data_size, NULL, NULL)) - return flatpak_fail (error, "GPG verification enabled, but no summary signatures found"); - - summary_sig_bytes = g_bytes_new_take (summary_sig_data, summary_sig_data_size); - - if (!g_file_load_contents (summary_file, cancellable, - &summary_data, &summary_data_size, NULL, NULL)) - return flatpak_fail (error, "No summary found"); - summary_bytes = g_bytes_new_take (summary_data, summary_data_size); - - gpg_result = ostree_repo_verify_summary (self->repo, - remote_name, - summary_bytes, - summary_sig_bytes, - cancellable, error); - if (gpg_result == NULL) - return FALSE; - - if (ostree_gpg_verify_result_count_valid (gpg_result) == 0) - return flatpak_fail (error, "GPG signatures found, but none are in trusted keyring"); - - summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE)); - if (!flatpak_summary_lookup_ref (summary, - ref, - &checksum)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Can't find %sin remote %s", ref, remote_name); - return FALSE; - } - - (void) ostree_repo_load_commit (self->repo, checksum, &old_commit, NULL, NULL); - - if (old_commit) - { - g_autoptr(OstreeRepo) src_repo = ostree_repo_new (path_file); - g_autoptr(GVariant) new_commit = NULL; - guint64 old_timestamp; - guint64 new_timestamp; - - if (!ostree_repo_open (src_repo, cancellable, error)) - return FALSE; - - if (!ostree_repo_load_commit (src_repo, checksum, &new_commit, NULL, error)) - return FALSE; - - old_timestamp = ostree_commit_get_timestamp (old_commit); - new_timestamp = ostree_commit_get_timestamp (new_commit); - - if (new_timestamp < old_timestamp) - return flatpak_fail (error, "Not allowed to downgrade %s", ref); - } - - - if (progress == NULL) - { - console = gs_console_get (); - if (console) - { - gs_console_begin_status_line (console, "", NULL, NULL); - console_progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); - progress = console_progress; - } - } - - if (subpaths == NULL || subpaths[0] == NULL) - { - if (!repo_pull_one_untrusted (self->repo, remote_name, url, - NULL, ref, checksum, progress, - cancellable, error)) - { - g_prefix_error (error, "While pulling %s from remote %s: ", ref, remote_name); - goto out; - } - } - else - { - int i; - - if (!repo_pull_one_untrusted (self->repo, remote_name, url, - "/metadata", ref, checksum, progress, - cancellable, error)) - { - g_prefix_error (error, "While pulling %s from remote %s, metadata: ", - ref, remote_name); - goto out; - } - - for (i = 0; subpaths[i] != NULL; i++) - { - g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL); - if (!repo_pull_one_untrusted (self->repo, remote_name, url, - subpath, ref, checksum, progress, - cancellable, error)) - { - g_prefix_error (error, "While pulling %s from remote %s, subpath %s: ", - ref, remote_name, subpaths[i]); - goto out; - } - } - } - - ret = TRUE; - -out: - if (console) - { - ostree_async_progress_finish (progress); - gs_console_end_status_line (console, NULL, NULL); - } - - return ret; -} - - -char * -flatpak_dir_current_ref (FlatpakDir *self, - const char *name, - GCancellable *cancellable) -{ - g_autoptr(GFile) base = NULL; - g_autoptr(GFile) dir = NULL; - g_autoptr(GFile) current_link = NULL; - g_autoptr(GFileInfo) file_info = NULL; - - base = g_file_get_child (flatpak_dir_get_path (self), "app"); - dir = g_file_get_child (base, name); - - current_link = g_file_get_child (dir, "current"); - - file_info = g_file_query_info (current_link, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, NULL); - if (file_info == NULL) - return NULL; - - return g_strconcat ("app/", name, "/", g_file_info_get_symlink_target (file_info), NULL); -} - -gboolean -flatpak_dir_drop_current_ref (FlatpakDir *self, - const char *name, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) base = NULL; - g_autoptr(GFile) dir = NULL; - g_autoptr(GFile) current_link = NULL; - - base = g_file_get_child (flatpak_dir_get_path (self), "app"); - dir = g_file_get_child (base, name); - - current_link = g_file_get_child (dir, "current"); - - return g_file_delete (current_link, cancellable, error); -} - -gboolean -flatpak_dir_make_current_ref (FlatpakDir *self, - const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) base = NULL; - g_autoptr(GFile) dir = NULL; - g_autoptr(GFile) current_link = NULL; - g_auto(GStrv) ref_parts = NULL; - g_autofree char *rest = NULL; - gboolean ret = FALSE; - - ref_parts = g_strsplit (ref, "/", -1); - - g_assert (g_strv_length (ref_parts) == 4); - g_assert (strcmp (ref_parts[0], "app") == 0); - - base = g_file_get_child (flatpak_dir_get_path (self), ref_parts[0]); - dir = g_file_get_child (base, ref_parts[1]); - - current_link = g_file_get_child (dir, "current"); - - g_file_delete (current_link, cancellable, NULL); - - if (*ref_parts[3] != 0) - { - rest = g_strdup_printf ("%s/%s", ref_parts[2], ref_parts[3]); - if (!g_file_make_symbolic_link (current_link, rest, cancellable, error)) - goto out; - } - - ret = TRUE; - -out: - return ret; -} - -gboolean -flatpak_dir_list_refs_for_name (FlatpakDir *self, - const char *kind, - const char *name, - char ***refs_out, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) base = NULL; - g_autoptr(GFile) dir = NULL; - g_autoptr(GFileEnumerator) dir_enum = NULL; - g_autoptr(GFileInfo) child_info = NULL; - GError *temp_error = NULL; - g_autoptr(GPtrArray) refs = NULL; - - base = g_file_get_child (flatpak_dir_get_path (self), kind); - dir = g_file_get_child (base, name); - - refs = g_ptr_array_new (); - - if (!g_file_query_exists (dir, cancellable)) - { - ret = TRUE; - goto out; - } - - dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (!dir_enum) - goto out; - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error))) - { - g_autoptr(GFile) child = NULL; - g_autoptr(GFileEnumerator) dir_enum2 = NULL; - g_autoptr(GFileInfo) child_info2 = NULL; - const char *arch; - - arch = g_file_info_get_name (child_info); - - if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY || - strcmp (arch, "data") == 0 /* There used to be a data dir here, lets ignore it */) - { - g_clear_object (&child_info); - continue; - } - - child = g_file_get_child (dir, arch); - g_clear_object (&dir_enum2); - dir_enum2 = g_file_enumerate_children (child, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (!dir_enum2) - goto out; - - while ((child_info2 = g_file_enumerator_next_file (dir_enum2, cancellable, &temp_error))) - { - const char *branch; - - if (g_file_info_get_file_type (child_info2) == G_FILE_TYPE_DIRECTORY) - { - branch = g_file_info_get_name (child_info2); - g_ptr_array_add (refs, - g_strdup_printf ("%s/%s/%s/%s", kind, name, arch, branch)); - } - - g_clear_object (&child_info2); - } - - - if (temp_error != NULL) - goto out; - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - goto out; - - g_ptr_array_sort (refs, flatpak_strcmp0_ptr); - - ret = TRUE; - -out: - if (ret) - { - g_ptr_array_add (refs, NULL); - *refs_out = (char **) g_ptr_array_free (refs, FALSE); - refs = NULL; - } - - if (temp_error != NULL) - g_propagate_error (error, temp_error); - - return ret; -} - -gboolean -flatpak_dir_list_refs (FlatpakDir *self, - const char *kind, - char ***refs_out, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) base; - g_autoptr(GFileEnumerator) dir_enum = NULL; - g_autoptr(GFileInfo) child_info = NULL; - GError *temp_error = NULL; - g_autoptr(GPtrArray) refs = NULL; - - refs = g_ptr_array_new (); - - base = g_file_get_child (flatpak_dir_get_path (self), kind); - - if (!g_file_query_exists (base, cancellable)) - { - ret = TRUE; - goto out; - } - - dir_enum = g_file_enumerate_children (base, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (!dir_enum) - goto out; - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error))) - { - gchar **sub_refs = NULL; - const char *name; - int i; - - if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_DIRECTORY) - { - g_clear_object (&child_info); - continue; - } - - name = g_file_info_get_name (child_info); - - if (!flatpak_dir_list_refs_for_name (self, kind, name, &sub_refs, cancellable, error)) - goto out; - - for (i = 0; sub_refs[i] != NULL; i++) - g_ptr_array_add (refs, sub_refs[i]); - g_free (sub_refs); - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - goto out; - - ret = TRUE; - - g_ptr_array_sort (refs, flatpak_strcmp0_ptr); - -out: - if (ret) - { - g_ptr_array_add (refs, NULL); - *refs_out = (char **) g_ptr_array_free (refs, FALSE); - refs = NULL; - } - - if (temp_error != NULL) - g_propagate_error (error, temp_error); - - return ret; -} - -char * -flatpak_dir_read_latest (FlatpakDir *self, - const char *remote, - const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *remote_and_ref = NULL; - char *res = NULL; - - /* There may be several remotes with the same branch (if we for - * instance changed the origin, so prepend the current origin to - * make sure we get the right one */ - - if (remote) - remote_and_ref = g_strdup_printf ("%s:%s", remote, ref); - else - remote_and_ref = g_strdup (ref); - - if (!ostree_repo_resolve_rev (self->repo, remote_and_ref, FALSE, &res, error)) - return NULL; - - return res; -} - - -char * -flatpak_dir_read_active (FlatpakDir *self, - const char *ref, - GCancellable *cancellable) -{ - g_autoptr(GFile) deploy_base = NULL; - g_autoptr(GFile) active_link = NULL; - g_autoptr(GFileInfo) file_info = NULL; - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - active_link = g_file_get_child (deploy_base, "active"); - - file_info = g_file_query_info (active_link, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, NULL); - if (file_info == NULL) - return NULL; - - return g_strdup (g_file_info_get_symlink_target (file_info)); -} - -gboolean -flatpak_dir_set_active (FlatpakDir *self, - const char *ref, - const char *checksum, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) deploy_base = NULL; - g_autofree char *tmpname = NULL; - g_autoptr(GFile) active_tmp_link = NULL; - g_autoptr(GFile) active_link = NULL; - g_autoptr(GError) my_error = NULL; - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - active_link = g_file_get_child (deploy_base, "active"); - - if (checksum != NULL) - { - tmpname = gs_fileutil_gen_tmp_name (".active-", NULL); - active_tmp_link = g_file_get_child (deploy_base, tmpname); - if (!g_file_make_symbolic_link (active_tmp_link, checksum, cancellable, error)) - goto out; - - if (!gs_file_rename (active_tmp_link, - active_link, - cancellable, error)) - goto out; - } - else - { - if (!g_file_delete (active_link, cancellable, &my_error) && - !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_propagate_error (error, my_error); - my_error = NULL; - goto out; - } - } - - ret = TRUE; -out: - return ret; -} - - -gboolean -flatpak_dir_run_triggers (FlatpakDir *self, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFileEnumerator) dir_enum = NULL; - g_autoptr(GFileInfo) child_info = NULL; - g_autoptr(GFile) triggersdir = NULL; - GError *temp_error = NULL; - const char *triggerspath; - - triggerspath = g_getenv ("FLATPAK_TRIGGERSDIR"); - if (triggerspath == NULL) - triggerspath = FLATPAK_TRIGGERDIR; - - g_debug ("running triggers from %s", triggerspath); - - triggersdir = g_file_new_for_path (triggerspath); - - dir_enum = g_file_enumerate_children (triggersdir, "standard::type,standard::name", - 0, cancellable, error); - if (!dir_enum) - goto out; - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) - { - g_autoptr(GFile) child = NULL; - const char *name; - GError *trigger_error = NULL; - - name = g_file_info_get_name (child_info); - - child = g_file_get_child (triggersdir, name); - - if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_REGULAR && - g_str_has_suffix (name, ".trigger")) - { - g_autoptr(GPtrArray) argv_array = NULL; - - g_debug ("running trigger %s", name); - - argv_array = g_ptr_array_new_with_free_func (g_free); -#ifdef DISABLE_SANDBOXED_TRIGGERS - g_ptr_array_add (argv_array, g_file_get_path (child)); - g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); -#else - g_ptr_array_add (argv_array, g_strdup (flatpak_get_bwrap ())); - g_ptr_array_add (argv_array, g_strdup ("--unshare-ipc")); - g_ptr_array_add (argv_array, g_strdup ("--unshare-net")); - g_ptr_array_add (argv_array, g_strdup ("--unshare-pid")); - g_ptr_array_add (argv_array, g_strdup ("--ro-bind")); - g_ptr_array_add (argv_array, g_strdup ("/")); - g_ptr_array_add (argv_array, g_strdup ("/")); - g_ptr_array_add (argv_array, g_strdup ("--proc")); - g_ptr_array_add (argv_array, g_strdup ("/proc")); - g_ptr_array_add (argv_array, g_strdup ("--dev")); - g_ptr_array_add (argv_array, g_strdup ("/dev")); - g_ptr_array_add (argv_array, g_strdup ("--bind")); - g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); - g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); -#endif - g_ptr_array_add (argv_array, g_file_get_path (child)); - g_ptr_array_add (argv_array, g_file_get_path (self->basedir)); - g_ptr_array_add (argv_array, NULL); - - if (!g_spawn_sync ("/", - (char **) argv_array->pdata, - NULL, - G_SPAWN_DEFAULT, - NULL, NULL, - NULL, NULL, - NULL, &trigger_error)) - { - g_warning ("Error running trigger %s: %s", name, trigger_error->message); - g_clear_error (&trigger_error); - } - } - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - goto out; - } - - ret = TRUE; -out: - return ret; -} - -static gboolean -read_fd (int fd, - struct stat *stat_buf, - gchar **contents, - gsize *length, - GError **error) -{ - gchar *buf; - gsize bytes_read; - gsize size; - gsize alloc_size; - - size = stat_buf->st_size; - - alloc_size = size + 1; - buf = g_try_malloc (alloc_size); - - if (buf == NULL) - { - g_set_error (error, - G_FILE_ERROR, - G_FILE_ERROR_NOMEM, - "not enough memory"); - return FALSE; - } - - bytes_read = 0; - while (bytes_read < size) - { - gssize rc; - - rc = read (fd, buf + bytes_read, size - bytes_read); - - if (rc < 0) - { - if (errno != EINTR) - { - int save_errno = errno; - - g_free (buf); - g_set_error (error, - G_FILE_ERROR, - g_file_error_from_errno (save_errno), - "Failed to read from exported file"); - return FALSE; - } - } - else if (rc == 0) - { - break; - } - else - { - bytes_read += rc; - } - } - - buf[bytes_read] = '\0'; - - if (length) - *length = bytes_read; - - *contents = buf; - - return TRUE; -} - -/* This is conservative, but lets us avoid escaping most - regular Exec= lines, which is nice as that can sometimes - cause problems for apps launching desktop files. */ -static gboolean -need_quotes (const char *str) -{ - const char *p; - - for (p = str; *p; p++) - { - if (!g_ascii_isalnum (*p) && - strchr ("-_%.=:/@", *p) == NULL) - return TRUE; - } - - return FALSE; -} - -static char * -maybe_quote (const char *str) -{ - if (need_quotes (str)) - return g_shell_quote (str); - return g_strdup (str); -} - -static gboolean -export_desktop_file (const char *app, - const char *branch, - const char *arch, - GKeyFile *metadata, - int parent_fd, - const char *name, - struct stat *stat_buf, - char **target, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - glnx_fd_close int desktop_fd = -1; - g_autofree char *tmpfile_name = NULL; - - g_autoptr(GOutputStream) out_stream = NULL; - g_autofree gchar *data = NULL; - gsize data_len; - g_autofree gchar *new_data = NULL; - gsize new_data_len; - g_autoptr(GKeyFile) keyfile = NULL; - g_autofree gchar *old_exec = NULL; - gint old_argc; - g_auto(GStrv) old_argv = NULL; - g_auto(GStrv) groups = NULL; - GString *new_exec = NULL; - g_autofree char *escaped_app = maybe_quote (app); - g_autofree char *escaped_branch = maybe_quote (branch); - g_autofree char *escaped_arch = maybe_quote (arch); - int i; - - if (!gs_file_openat_noatime (parent_fd, name, &desktop_fd, cancellable, error)) - goto out; - - if (!read_fd (desktop_fd, stat_buf, &data, &data_len, error)) - goto out; - - keyfile = g_key_file_new (); - if (!g_key_file_load_from_data (keyfile, data, data_len, G_KEY_FILE_KEEP_TRANSLATIONS, error)) - goto out; - - if (g_str_has_suffix (name, ".service")) - { - g_autofree gchar *dbus_name = NULL; - g_autofree gchar *expected_dbus_name = g_strndup (name, strlen (name) - strlen (".service")); - - dbus_name = g_key_file_get_string (keyfile, "D-BUS Service", "Name", NULL); - - if (dbus_name == NULL || strcmp (dbus_name, expected_dbus_name) != 0) - { - flatpak_fail (error, "dbus service file %s has wrong name", name); - return FALSE; - } - } - - if (g_str_has_suffix (name, ".desktop")) - { - gsize length; - g_auto(GStrv) tags = g_key_file_get_string_list (metadata, - "Application", - "tags", &length, - NULL); - - if (tags != NULL) - { - g_key_file_set_string_list (keyfile, - "Desktop Entry", - "X-Flatpak-Tags", - (const char * const *) tags, length); - } - } - - groups = g_key_file_get_groups (keyfile, NULL); - - for (i = 0; groups[i] != NULL; i++) - { - g_key_file_remove_key (keyfile, groups[i], "TryExec", NULL); - - /* Remove this to make sure nothing tries to execute it outside the sandbox*/ - g_key_file_remove_key (keyfile, groups[i], "X-GNOME-Bugzilla-ExtraInfoScript", NULL); - - new_exec = g_string_new (""); - g_string_append_printf (new_exec, FLATPAK_BINDIR "/xdg-app run --branch=%s --arch=%s", escaped_branch, escaped_arch); - - old_exec = g_key_file_get_string (keyfile, groups[i], "Exec", NULL); - if (old_exec && g_shell_parse_argv (old_exec, &old_argc, &old_argv, NULL) && old_argc >= 1) - { - int i; - g_autofree char *command = maybe_quote (old_argv[0]); - - g_string_append_printf (new_exec, " --command=%s", command); - - g_string_append (new_exec, " "); - g_string_append (new_exec, escaped_app); - - for (i = 1; i < old_argc; i++) - { - g_autofree char *arg = maybe_quote (old_argv[i]); - g_string_append (new_exec, " "); - g_string_append (new_exec, arg); - } - } - else - { - g_string_append (new_exec, " "); - g_string_append (new_exec, escaped_app); - } - - g_key_file_set_string (keyfile, groups[i], G_KEY_FILE_DESKTOP_KEY_EXEC, new_exec->str); - } - - new_data = g_key_file_to_data (keyfile, &new_data_len, error); - if (new_data == NULL) - goto out; - - if (!gs_file_open_in_tmpdir_at (parent_fd, 0755, &tmpfile_name, &out_stream, cancellable, error)) - goto out; - - if (!g_output_stream_write_all (out_stream, new_data, new_data_len, NULL, cancellable, error)) - goto out; - - if (!g_output_stream_close (out_stream, cancellable, error)) - goto out; - - if (target) - *target = g_steal_pointer (&tmpfile_name); - - ret = TRUE; -out: - - if (new_exec != NULL) - g_string_free (new_exec, TRUE); - - return ret; -} - -static gboolean -rewrite_export_dir (const char *app, - const char *branch, - const char *arch, - GKeyFile *metadata, - int source_parent_fd, - const char *source_name, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_auto(GLnxDirFdIterator) source_iter = {0}; - g_autoptr(GHashTable) visited_children = NULL; - struct dirent *dent; - - if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) - goto out; - - visited_children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - - while (TRUE) - { - struct stat stbuf; - - if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error)) - goto out; - - if (dent == NULL) - break; - - if (g_hash_table_contains (visited_children, dent->d_name)) - continue; - - /* Avoid processing the same file again if it was re-created during an export */ - g_hash_table_insert (visited_children, g_strdup (dent->d_name), GINT_TO_POINTER (1)); - - if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) - { - if (errno == ENOENT) - { - continue; - } - else - { - glnx_set_error_from_errno (error); - goto out; - } - } - - if (S_ISDIR (stbuf.st_mode)) - { - if (!rewrite_export_dir (app, branch, arch, metadata, - source_iter.fd, dent->d_name, - cancellable, error)) - goto out; - } - else if (S_ISREG (stbuf.st_mode)) - { - if (!flatpak_has_name_prefix (dent->d_name, app)) - { - g_warning ("Non-prefixed filename %s in app %s, removing.\n", dent->d_name, app); - if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT) - { - glnx_set_error_from_errno (error); - goto out; - } - } - - if (g_str_has_suffix (dent->d_name, ".desktop") || - g_str_has_suffix (dent->d_name, ".service")) - { - g_autofree gchar *new_name = NULL; - - if (!export_desktop_file (app, branch, arch, metadata, - source_iter.fd, dent->d_name, &stbuf, &new_name, cancellable, error)) - goto out; - - g_hash_table_insert (visited_children, g_strdup (new_name), GINT_TO_POINTER (1)); - - if (renameat (source_iter.fd, new_name, source_iter.fd, dent->d_name) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - } - } - else - { - g_warning ("Not exporting file %s of unsupported type\n", dent->d_name); - if (unlinkat (source_iter.fd, dent->d_name, 0) != 0 && errno != ENOENT) - { - glnx_set_error_from_errno (error); - goto out; - } - } - } - - ret = TRUE; -out: - - return ret; -} - -gboolean -flatpak_rewrite_export_dir (const char *app, - const char *branch, - const char *arch, - GKeyFile *metadata, - GFile *source, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - /* The fds are closed by this call */ - if (!rewrite_export_dir (app, branch, arch, metadata, - AT_FDCWD, gs_file_get_path_cached (source), - cancellable, error)) - goto out; - - ret = TRUE; - -out: - return ret; -} - - -static gboolean -export_dir (int source_parent_fd, - const char *source_name, - const char *source_symlink_prefix, - const char *source_relpath, - int destination_parent_fd, - const char *destination_name, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - int res; - - g_auto(GLnxDirFdIterator) source_iter = {0}; - glnx_fd_close int destination_dfd = -1; - struct dirent *dent; - - if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) - goto out; - - do - res = mkdirat (destination_parent_fd, destination_name, 0755); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (res == -1) - { - if (errno != EEXIST) - { - glnx_set_error_from_errno (error); - goto out; - } - } - - if (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name, - &destination_dfd, - cancellable, error)) - goto out; - - while (TRUE) - { - struct stat stbuf; - - if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, cancellable, error)) - goto out; - - if (dent == NULL) - break; - - if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) - { - if (errno == ENOENT) - { - continue; - } - else - { - glnx_set_error_from_errno (error); - goto out; - } - } - - if (S_ISDIR (stbuf.st_mode)) - { - g_autofree gchar *child_symlink_prefix = g_build_filename ("..", source_symlink_prefix, dent->d_name, NULL); - g_autofree gchar *child_relpath = g_strconcat (source_relpath, dent->d_name, "/", NULL); - - if (!export_dir (source_iter.fd, dent->d_name, child_symlink_prefix, child_relpath, destination_dfd, dent->d_name, - cancellable, error)) - goto out; - } - else if (S_ISREG (stbuf.st_mode)) - { - g_autofree gchar *target = NULL; - - target = g_build_filename (source_symlink_prefix, dent->d_name, NULL); - - if (unlinkat (destination_dfd, dent->d_name, 0) != 0 && errno != ENOENT) - { - glnx_set_error_from_errno (error); - goto out; - } - - if (symlinkat (target, destination_dfd, dent->d_name) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - } - } - - ret = TRUE; -out: - - return ret; -} - -gboolean -flatpak_export_dir (GFile *source, - GFile *destination, - const char *symlink_prefix, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - if (!gs_file_ensure_directory (destination, TRUE, cancellable, error)) - goto out; - - /* The fds are closed by this call */ - if (!export_dir (AT_FDCWD, gs_file_get_path_cached (source), symlink_prefix, "", - AT_FDCWD, gs_file_get_path_cached (destination), - cancellable, error)) - goto out; - - ret = TRUE; - -out: - return ret; -} - -gboolean -flatpak_dir_update_exports (FlatpakDir *self, - const char *changed_app, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) exports = NULL; - g_autofree char *current_ref = NULL; - g_autofree char *active_id = NULL; - g_autofree char *symlink_prefix = NULL; - - exports = flatpak_dir_get_exports_dir (self); - - if (!gs_file_ensure_directory (exports, TRUE, cancellable, error)) - goto out; - - if (changed_app && - (current_ref = flatpak_dir_current_ref (self, changed_app, cancellable)) && - (active_id = flatpak_dir_read_active (self, current_ref, cancellable))) - { - g_autoptr(GFile) deploy_base = NULL; - g_autoptr(GFile) active = NULL; - g_autoptr(GFile) export = NULL; - - deploy_base = flatpak_dir_get_deploy_dir (self, current_ref); - active = g_file_get_child (deploy_base, active_id); - export = g_file_get_child (active, "export"); - - if (g_file_query_exists (export, cancellable)) - { - symlink_prefix = g_build_filename ("..", "app", changed_app, "current", "active", "export", NULL); - if (!flatpak_export_dir (export, exports, - symlink_prefix, - cancellable, - error)) - goto out; - } - } - - if (!flatpak_remove_dangling_symlinks (exports, cancellable, error)) - goto out; - - if (!flatpak_dir_run_triggers (self, cancellable, error)) - goto out; - - ret = TRUE; - -out: - return ret; -} - -gboolean -flatpak_dir_deploy (FlatpakDir *self, - const char *origin, - const char *ref, - const char *checksum_or_latest, - const char * const * subpaths, - GVariant *old_deploy_data, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *resolved_ref = NULL; - - g_autoptr(GFile) root = NULL; - g_autoptr(GFileInfo) file_info = NULL; - g_autoptr(GFile) deploy_base = NULL; - g_autoptr(GFile) checkoutdir = NULL; - g_autoptr(GFile) real_checkoutdir = NULL; - g_autoptr(GFile) dotref = NULL; - g_autoptr(GFile) files_etc = NULL; - g_autoptr(GFile) metadata = NULL; - g_autoptr(GFile) deploy_data_file = NULL; - g_autoptr(GVariant) deploy_data = NULL; - g_autoptr(GFile) export = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - guint64 installed_size = 0; - const char *checksum; - g_autoptr(GFile) tmp_dir_template = NULL; - g_autofree char *tmp_dir_path = NULL; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - - if (checksum_or_latest == NULL) - { - g_debug ("No checksum specified, getting tip of %s", ref); - - resolved_ref = flatpak_dir_read_latest (self, origin, ref, cancellable, error); - if (resolved_ref == NULL) - { - g_prefix_error (error, "While trying to resolve ref %s: ", ref); - return FALSE; - } - - checksum = resolved_ref; - g_debug ("tip resolved to: %s", checksum); - } - else - { - g_autoptr(GFile) root = NULL; - g_autofree char *commit = NULL; - - checksum = checksum_or_latest; - g_debug ("Looking for checksum %s in local repo", checksum); - if (!ostree_repo_read_commit (self->repo, checksum, &root, &commit, cancellable, NULL)) - return flatpak_fail (error, "%s is not available", ref); - } - - real_checkoutdir = g_file_get_child (deploy_base, checksum); - if (g_file_query_exists (real_checkoutdir, cancellable)) - { - g_set_error (error, FLATPAK_DIR_ERROR, - FLATPAK_DIR_ERROR_ALREADY_DEPLOYED, - "%s branch %s already deployed", ref, checksum); - return FALSE; - } - - g_autofree char *template = g_strdup_printf (".%s-XXXXXX", checksum); - tmp_dir_template = g_file_get_child (deploy_base, template); - tmp_dir_path = g_file_get_path (tmp_dir_template); - - if (g_mkdtemp (tmp_dir_path) == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't create deploy directory"); - return FALSE; - } - - checkoutdir = g_file_new_for_path (tmp_dir_path); - - if (!ostree_repo_read_commit (self->repo, checksum, &root, NULL, cancellable, error)) - { - g_prefix_error (error, "Failed to read commit %s: ", checksum); - return FALSE; - } - - if (!flatpak_repo_collect_sizes (self->repo, root, &installed_size, NULL, cancellable, error)) - return FALSE; - - file_info = g_file_query_info (root, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (file_info == NULL) - return FALSE; - - if (subpaths == NULL || *subpaths == NULL) - { - if (!ostree_repo_checkout_tree (self->repo, - OSTREE_REPO_CHECKOUT_MODE_USER, - OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES, - checkoutdir, - OSTREE_REPO_FILE (root), file_info, - cancellable, error)) - { - g_autofree char *rootpath = NULL; - g_autofree char *checkoutpath = NULL; - - rootpath = g_file_get_path (root); - checkoutpath = g_file_get_path (checkoutdir); - g_prefix_error (error, "While trying to checkout %s into %s: ", rootpath, checkoutpath); - return FALSE; - } - } - else - { - OstreeRepoCheckoutOptions options = { 0, }; - g_autofree char *checkoutdirpath = g_file_get_path (checkoutdir); - g_autoptr(GFile) files = g_file_get_child (checkoutdir, "files"); - int i; - - if (!g_file_make_directory_with_parents (files, cancellable, error)) - return FALSE; - - options.mode = OSTREE_REPO_CHECKOUT_MODE_USER; - options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES; - options.subpath = "/metadata"; - - access ("checkout metadata", 0); - if (!ostree_repo_checkout_tree_at (self->repo, &options, - AT_FDCWD, checkoutdirpath, - checksum, - cancellable, error)) - { - g_prefix_error (error, "While trying to checkout metadata subpath: "); - return FALSE; - } - - for (i = 0; subpaths[i] != NULL; i++) - { - g_autofree char *subpath = g_build_filename ("/files", subpaths[i], NULL); - g_autofree char *dstpath = g_build_filename (checkoutdirpath, "/files", subpaths[i], NULL); - g_autofree char *dstpath_parent = g_path_get_dirname (dstpath); - if (g_mkdir_with_parents (dstpath_parent, 0755)) - { - glnx_set_error_from_errno (error); - return FALSE; - } - - options.subpath = subpath; - if (!ostree_repo_checkout_tree_at (self->repo, &options, - AT_FDCWD, dstpath, - checksum, - cancellable, error)) - { - g_prefix_error (error, "While trying to checkout metadata subpath: "); - return FALSE; - } - } - } - - dotref = g_file_resolve_relative_path (checkoutdir, "files/.ref"); - if (!g_file_replace_contents (dotref, "", 0, NULL, FALSE, - G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error)) - return TRUE; - - /* Ensure that various files exists as regular files in /usr/etc, as we - want to bind-mount over them */ - files_etc = g_file_resolve_relative_path (checkoutdir, "files/etc"); - if (g_file_query_exists (files_etc, cancellable)) - { - char *etcfiles[] = {"passwd", "group", "machine-id" }; - g_autoptr(GFile) etc_resolve_conf = g_file_get_child (files_etc, "resolv.conf"); - int i; - for (i = 0; i < G_N_ELEMENTS (etcfiles); i++) - { - g_autoptr(GFile) etc_file = g_file_get_child (files_etc, etcfiles[i]); - GFileType type; - - type = g_file_query_file_type (etc_file, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable); - if (type == G_FILE_TYPE_REGULAR) - continue; - - if (type != G_FILE_TYPE_UNKNOWN) - { - /* Already exists, but not regular, probably symlink. Remove it */ - if (!g_file_delete (etc_file, cancellable, error)) - return FALSE; - } - - if (!g_file_replace_contents (etc_file, "", 0, NULL, FALSE, - G_FILE_CREATE_REPLACE_DESTINATION, - NULL, cancellable, error)) - return FALSE; - } - - if (g_file_query_exists (etc_resolve_conf, cancellable) && - !g_file_delete (etc_resolve_conf, cancellable, error)) - return TRUE; - - if (!g_file_make_symbolic_link (etc_resolve_conf, - "/run/host/monitor/resolv.conf", - cancellable, error)) - return FALSE; - } - - keyfile = g_key_file_new (); - metadata = g_file_get_child (checkoutdir, "metadata"); - if (g_file_query_exists (metadata, cancellable)) - { - g_autofree char *path = g_file_get_path (metadata); - - if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, error)) - return FALSE; - } - - export = g_file_get_child (checkoutdir, "export"); - if (g_file_query_exists (export, cancellable)) - { - g_auto(GStrv) ref_parts = NULL; - - ref_parts = g_strsplit (ref, "/", -1); - - if (!flatpak_rewrite_export_dir (ref_parts[1], ref_parts[3], ref_parts[2], - keyfile, export, - cancellable, - error)) - return FALSE; - } - - deploy_data = flatpak_dir_new_deploy_data (origin, - checksum, - (char **) subpaths, - installed_size, - NULL); - - deploy_data_file = g_file_get_child (checkoutdir, "deploy"); - if (!flatpak_variant_save (deploy_data_file, deploy_data, cancellable, error)) - return FALSE; - - if (!g_file_move (checkoutdir, real_checkoutdir, G_FILE_COPY_NO_FALLBACK_FOR_MOVE, - cancellable, NULL, NULL, error)) - return FALSE; - - if (!flatpak_dir_set_active (self, ref, checksum, cancellable, error)) - return FALSE; - - return TRUE; -} - -gboolean -flatpak_dir_deploy_install (FlatpakDir *self, - const char *ref, - const char *origin, - char **subpaths, - GCancellable *cancellable, - GError **error) -{ - g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; - g_autoptr(GFile) deploy_base = NULL; - gboolean created_deploy_base = FALSE; - gboolean ret = FALSE; - g_autoptr(GError) local_error = NULL; - g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1); - - if (!flatpak_dir_lock (self, &lock, - cancellable, error)) - goto out; - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - if (!g_file_make_directory_with_parents (deploy_base, cancellable, &local_error)) - { - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) - { - g_set_error (error, - G_IO_ERROR, G_IO_ERROR_EXISTS, - "%s branch %s already installed", - ref_parts[1], ref_parts[3]); - } - else - { - g_propagate_error (error, g_steal_pointer (&local_error)); - } - - goto out; - } - - /* After we create the deploy base we must goto out on errors */ - created_deploy_base = TRUE; - - if (!flatpak_dir_deploy (self, origin, ref, NULL, (const char * const *) subpaths, NULL, cancellable, error)) - goto out; - - if (g_str_has_prefix (ref, "app/")) - { - - if (!flatpak_dir_make_current_ref (self, ref, cancellable, error)) - goto out; - - if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error)) - goto out; - } - - /* Release lock before doing possibly slow prune */ - glnx_release_lock_file (&lock); - - flatpak_dir_cleanup_removed (self, cancellable, NULL); - - if (!flatpak_dir_mark_changed (self, error)) - goto out; - - ret = TRUE; - -out: - if (created_deploy_base && !ret) - gs_shutil_rm_rf (deploy_base, cancellable, NULL); - - return ret; -} - - -gboolean -flatpak_dir_deploy_update (FlatpakDir *self, - const char *ref, - const char *checksum_or_latest, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *previous_deployment = NULL; - - g_autoptr(GError) my_error = NULL; - g_autoptr(GVariant) old_deploy_data = NULL; - g_auto(GLnxLockFile) lock = GLNX_LOCK_FILE_INIT; - g_autofree const char **old_subpaths = NULL; - const char *old_active; - const char *old_origin; - - if (!flatpak_dir_lock (self, &lock, - cancellable, error)) - return FALSE; - - old_deploy_data = flatpak_dir_get_deploy_data (self, ref, - cancellable, error); - if (old_deploy_data == NULL) - return FALSE; - - old_origin = flatpak_deploy_data_get_origin (old_deploy_data); - old_active = flatpak_deploy_data_get_commit (old_deploy_data); - old_subpaths = flatpak_deploy_data_get_subpaths (old_deploy_data); - if (!flatpak_dir_deploy (self, - old_origin, - ref, - checksum_or_latest, - old_subpaths, - old_deploy_data, - cancellable, &my_error)) - { - if (g_error_matches (my_error, FLATPAK_DIR_ERROR, - FLATPAK_DIR_ERROR_ALREADY_DEPLOYED)) - return TRUE; - - g_propagate_error (error, my_error); - return FALSE; - } - - if (!flatpak_dir_undeploy (self, ref, old_active, - FALSE, - cancellable, error)) - return FALSE; - - if (g_str_has_prefix (ref, "app/")) - { - g_auto(GStrv) ref_parts = g_strsplit (ref, "/", -1); - - if (!flatpak_dir_update_exports (self, ref_parts[1], cancellable, error)) - return FALSE; - } - - /* Release lock before doing possibly slow prune */ - glnx_release_lock_file (&lock); - - if (!flatpak_dir_prune (self, cancellable, error)) - return FALSE; - - if (!flatpak_dir_mark_changed (self, error)) - return FALSE; - - flatpak_dir_cleanup_removed (self, cancellable, NULL); - - return TRUE; -} - -static OstreeRepo * -flatpak_dir_create_system_child_repo (FlatpakDir *self, - GLnxLockFile *file_lock, - GError **error) -{ - g_autoptr(GFile) cache_dir = NULL; - g_autoptr(GFile) repo_dir = NULL; - g_autoptr(GFile) repo_dir_config = NULL; - g_autoptr(OstreeRepo) repo = NULL; - g_autofree char *tmpdir_name = NULL; - g_autoptr(OstreeRepo) new_repo = NULL; - g_autoptr(GKeyFile) config = NULL; - - g_assert (!self->user); - - if (!flatpak_dir_ensure_repo (self, NULL, error)) - return NULL; - - cache_dir = flatpak_ensure_user_cache_dir_location (error); - if (cache_dir == NULL) - return NULL; - - if (!flatpak_allocate_tmpdir (AT_FDCWD, - gs_file_get_path_cached (cache_dir), - "repo-", &tmpdir_name, - NULL, - file_lock, - NULL, - NULL, error)) - return NULL; - - repo_dir = g_file_get_child (cache_dir, tmpdir_name); - - new_repo = ostree_repo_new (repo_dir); - - repo_dir_config = g_file_get_child (repo_dir, "config"); - if (!g_file_query_exists (repo_dir_config, NULL)) - { - if (!ostree_repo_create (new_repo, - OSTREE_REPO_MODE_BARE_USER, - NULL, error)) - return NULL; - } - else - { - if (!ostree_repo_open (new_repo, NULL, error)) - return NULL; - } - - /* Ensure the config is updated */ - config = ostree_repo_copy_config (new_repo); - g_key_file_set_string (config, "core", "parent", - gs_file_get_path_cached (ostree_repo_get_path (self->repo))); - - if (!ostree_repo_write_config (new_repo, config, error)) - return NULL; - - /* We need to reopen to apply the parent config */ - repo = system_ostree_repo_new (repo_dir); - if (!ostree_repo_open (repo, NULL, error)) - return NULL; - - return g_steal_pointer (&repo); -} - -gboolean -flatpak_dir_install (FlatpakDir *self, - gboolean no_pull, - gboolean no_deploy, - const char *ref, - const char *remote_name, - char **subpaths, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - if (flatpak_dir_use_child_repo (self)) - { - g_autoptr(OstreeRepo) child_repo = NULL; - g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT; - char *empty_subpaths[] = {NULL}; - XdgAppSystemHelper *system_helper; - - if (no_pull) - return flatpak_fail (error, "No-pull install not supported without root permissions"); - - if (no_deploy) - return flatpak_fail (error, "No-deploy install not supported without root permissions"); - - child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error); - if (child_repo == NULL) - return FALSE; - - system_helper = flatpak_dir_get_system_helper (self); - - g_assert (system_helper != NULL); - - if (!flatpak_dir_pull (self, remote_name, ref, subpaths, - child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR, - progress, cancellable, error)) - return FALSE; - - if (!xdg_app_system_helper_call_deploy_sync (system_helper, - gs_file_get_path_cached (ostree_repo_get_path (child_repo)), - FLATPAK_HELPER_DEPLOY_FLAGS_NONE, - ref, - remote_name, - (const char * const *) (subpaths ? subpaths : empty_subpaths), - cancellable, - error)) - return FALSE; - - (void) glnx_shutil_rm_rf_at (AT_FDCWD, - gs_file_get_path_cached (ostree_repo_get_path (child_repo)), - NULL, NULL); - - return TRUE; - } - - - if (!no_pull) - { - if (!flatpak_dir_pull (self, remote_name, ref, subpaths, NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress, - cancellable, error)) - return FALSE; - } - - if (!no_deploy) - { - if (!flatpak_dir_deploy_install (self, ref, remote_name, subpaths, - cancellable, error)) - return FALSE; - } - - return TRUE; -} - -gboolean -flatpak_dir_update (FlatpakDir *self, - gboolean no_pull, - gboolean no_deploy, - const char *ref, - const char *remote_name, - const char *checksum_or_latest, - char **subpaths, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - if (flatpak_dir_use_child_repo (self)) - { - g_autoptr(OstreeRepo) child_repo = NULL; - g_auto(GLnxLockFile) child_repo_lock = GLNX_LOCK_FILE_INIT; - char *empty_subpaths[] = {NULL}; - g_autofree char *pulled_checksum = NULL; - g_autofree char *active_checksum = NULL; - XdgAppSystemHelper *system_helper; - - if (no_pull) - return flatpak_fail (error, "No-pull update not supported without root permissions"); - - if (no_deploy) - return flatpak_fail (error, "No-deploy update not supported without root permissions"); - - if (checksum_or_latest != NULL) - return flatpak_fail (error, "Can't update to a specific commit without root permissions"); - - child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, error); - if (child_repo == NULL) - return FALSE; - - system_helper = flatpak_dir_get_system_helper (self); - - g_assert (system_helper != NULL); - - if (!flatpak_dir_pull (self, remote_name, ref, subpaths, - child_repo, OSTREE_REPO_PULL_FLAGS_MIRROR, - progress, cancellable, error)) - return FALSE; - - if (!ostree_repo_resolve_rev (child_repo, ref, FALSE, &pulled_checksum, error)) - return FALSE; - - active_checksum = flatpak_dir_read_active (self, ref, NULL); - if (g_strcmp0 (active_checksum, pulled_checksum) != 0) - { - - if (!xdg_app_system_helper_call_deploy_sync (system_helper, - gs_file_get_path_cached (ostree_repo_get_path (child_repo)), - FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE, - ref, - remote_name, - (const char * const *) empty_subpaths, - cancellable, - error)) - return FALSE; - } - - (void) glnx_shutil_rm_rf_at (AT_FDCWD, - gs_file_get_path_cached (ostree_repo_get_path (child_repo)), - NULL, NULL); - - return TRUE; - } - - - if (!no_pull) - { - if (!flatpak_dir_pull (self, remote_name, ref, subpaths, - NULL, OSTREE_REPO_PULL_FLAGS_NONE, progress, - cancellable, error)) - return FALSE; - } - - if (!no_deploy) - { - if (!flatpak_dir_deploy_update (self, ref, checksum_or_latest, - cancellable, error)) - return FALSE; - } - - return TRUE; -} - - - -gboolean -flatpak_dir_collect_deployed_refs (FlatpakDir *self, - const char *type, - const char *name_prefix, - const char *branch, - const char *arch, - GHashTable *hash, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) dir = NULL; - g_autoptr(GFileEnumerator) dir_enum = NULL; - g_autoptr(GFileInfo) child_info = NULL; - GError *temp_error = NULL; - - dir = g_file_get_child (self->basedir, type); - if (!g_file_query_exists (dir, cancellable)) - return TRUE; - - dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, - error); - if (!dir_enum) - goto out; - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) - { - const char *name = g_file_info_get_name (child_info); - - if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY && - name[0] != '.' && (name_prefix == NULL || g_str_has_prefix (name, name_prefix))) - { - g_autoptr(GFile) child1 = g_file_get_child (dir, name); - g_autoptr(GFile) child2 = g_file_get_child (child1, branch); - g_autoptr(GFile) child3 = g_file_get_child (child2, arch); - g_autoptr(GFile) active = g_file_get_child (child3, "active"); - - if (g_file_query_exists (active, cancellable)) - g_hash_table_add (hash, g_strdup (name)); - } - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - goto out; - } - - ret = TRUE; -out: - return ret; -} - -gboolean -flatpak_dir_list_deployed (FlatpakDir *self, - const char *ref, - char ***deployed_checksums, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) deploy_base = NULL; - g_autoptr(GPtrArray) checksums = NULL; - GError *temp_error = NULL; - g_autoptr(GFileEnumerator) dir_enum = NULL; - g_autoptr(GFile) child = NULL; - g_autoptr(GFileInfo) child_info = NULL; - g_autoptr(GError) my_error = NULL; - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - - checksums = g_ptr_array_new_with_free_func (g_free); - - dir_enum = g_file_enumerate_children (deploy_base, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, - &my_error); - if (!dir_enum) - { - if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - ret = TRUE; /* Success, but empty */ - else - g_propagate_error (error, g_steal_pointer (&my_error)); - goto out; - } - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) - { - const char *name; - - name = g_file_info_get_name (child_info); - - g_clear_object (&child); - child = g_file_get_child (deploy_base, name); - - if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY && - name[0] != '.' && - strlen (name) == 64) - g_ptr_array_add (checksums, g_strdup (name)); - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - goto out; - } - - ret = TRUE; - -out: - if (ret) - { - g_ptr_array_add (checksums, NULL); - *deployed_checksums = (char **) g_ptr_array_free (g_steal_pointer (&checksums), FALSE); - } - - return ret; - -} - -static gboolean -dir_is_locked (GFile *dir) -{ - glnx_fd_close int ref_fd = -1; - struct flock lock = {0}; - - g_autoptr(GFile) reffile = NULL; - - reffile = g_file_resolve_relative_path (dir, "files/.ref"); - - ref_fd = open (gs_file_get_path_cached (reffile), O_RDWR | O_CLOEXEC); - if (ref_fd != -1) - { - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - - if (fcntl (ref_fd, F_GETLK, &lock) == 0) - return lock.l_type != F_UNLCK; - } - - return FALSE; -} - -gboolean -flatpak_dir_undeploy (FlatpakDir *self, - const char *ref, - const char *checksum, - gboolean force_remove, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) deploy_base = NULL; - g_autoptr(GFile) checkoutdir = NULL; - g_autoptr(GFile) removed_subdir = NULL; - g_autoptr(GFile) removed_dir = NULL; - g_autofree char *tmpname = NULL; - g_autofree char *active = NULL; - int i; - - g_assert (ref != NULL); - g_assert (checksum != NULL); - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - - checkoutdir = g_file_get_child (deploy_base, checksum); - if (!g_file_query_exists (checkoutdir, cancellable)) - { - g_set_error (error, FLATPAK_DIR_ERROR, - FLATPAK_DIR_ERROR_ALREADY_UNDEPLOYED, - "%s branch %s already undeployed", ref, checksum); - goto out; - } - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - goto out; - - active = flatpak_dir_read_active (self, ref, cancellable); - if (active != NULL && strcmp (active, checksum) == 0) - { - g_auto(GStrv) deployed_checksums = NULL; - const char *some_deployment; - - /* We're removing the active deployment, start by repointing that - to another deployment if one exists */ - - if (!flatpak_dir_list_deployed (self, ref, - &deployed_checksums, - cancellable, error)) - goto out; - - some_deployment = NULL; - for (i = 0; deployed_checksums[i] != NULL; i++) - { - if (strcmp (deployed_checksums[i], checksum) == 0) - continue; - - some_deployment = deployed_checksums[i]; - break; - } - - if (!flatpak_dir_set_active (self, ref, some_deployment, cancellable, error)) - goto out; - } - - removed_dir = flatpak_dir_get_removed_dir (self); - if (!gs_file_ensure_directory (removed_dir, TRUE, cancellable, error)) - goto out; - - tmpname = gs_fileutil_gen_tmp_name ("", checksum); - removed_subdir = g_file_get_child (removed_dir, tmpname); - - if (!gs_file_rename (checkoutdir, - removed_subdir, - cancellable, error)) - goto out; - - if (force_remove || !dir_is_locked (removed_subdir)) - { - GError *tmp_error = NULL; - - if (!gs_shutil_rm_rf (removed_subdir, cancellable, &tmp_error)) - { - g_warning ("Unable to remove old checkout: %s\n", tmp_error->message); - g_error_free (tmp_error); - } - } - - ret = TRUE; -out: - return ret; -} - -gboolean -flatpak_dir_undeploy_all (FlatpakDir *self, - const char *ref, - gboolean force_remove, - gboolean *was_deployed_out, - GCancellable *cancellable, - GError **error) -{ - g_auto(GStrv) deployed = NULL; - g_autoptr(GFile) deploy_base = NULL; - g_autoptr(GFile) arch_dir = NULL; - g_autoptr(GFile) top_dir = NULL; - GError *temp_error = NULL; - int i; - gboolean was_deployed; - - if (!flatpak_dir_list_deployed (self, ref, &deployed, cancellable, error)) - return FALSE; - - for (i = 0; deployed[i] != NULL; i++) - { - g_debug ("undeploying %s", deployed[i]); - if (!flatpak_dir_undeploy (self, ref, deployed[i], force_remove, cancellable, error)) - return FALSE; - } - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - was_deployed = g_file_query_exists (deploy_base, cancellable); - if (was_deployed) - { - g_debug ("removing deploy base"); - if (!gs_shutil_rm_rf (deploy_base, cancellable, error)) - return FALSE; - } - - g_debug ("cleaning up empty directories"); - arch_dir = g_file_get_parent (deploy_base); - if (g_file_query_exists (arch_dir, cancellable) && - !g_file_delete (arch_dir, cancellable, &temp_error)) - { - if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY)) - { - g_propagate_error (error, temp_error); - return FALSE; - } - g_clear_error (&temp_error); - } - - top_dir = g_file_get_parent (arch_dir); - if (g_file_query_exists (top_dir, cancellable) && - !g_file_delete (top_dir, cancellable, &temp_error)) - { - if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY)) - { - g_propagate_error (error, temp_error); - return FALSE; - } - g_clear_error (&temp_error); - } - - if (was_deployed_out) - *was_deployed_out = was_deployed; - - return TRUE; -} - -gboolean -flatpak_dir_remove_ref (FlatpakDir *self, - const char *remote_name, - const char *ref, - GCancellable *cancellable, - GError **error) -{ - if (!ostree_repo_set_ref_immediate (self->repo, remote_name, ref, NULL, cancellable, error)) - return FALSE; - - return TRUE; -} - -gboolean -flatpak_dir_cleanup_removed (FlatpakDir *self, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - g_autoptr(GFile) removed_dir = NULL; - g_autoptr(GFileEnumerator) dir_enum = NULL; - g_autoptr(GFileInfo) child_info = NULL; - GError *temp_error = NULL; - - removed_dir = flatpak_dir_get_removed_dir (self); - if (!g_file_query_exists (removed_dir, cancellable)) - return TRUE; - - dir_enum = g_file_enumerate_children (removed_dir, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, - error); - if (!dir_enum) - goto out; - - while ((child_info = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error)) != NULL) - { - const char *name = g_file_info_get_name (child_info); - g_autoptr(GFile) child = g_file_get_child (removed_dir, name); - - if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY && - !dir_is_locked (child)) - { - GError *tmp_error = NULL; - if (!gs_shutil_rm_rf (child, cancellable, &tmp_error)) - { - g_warning ("Unable to remove old checkout: %s\n", tmp_error->message); - g_error_free (tmp_error); - } - } - - g_clear_object (&child_info); - } - - if (temp_error != NULL) - { - g_propagate_error (error, temp_error); - goto out; - } - - ret = TRUE; -out: - return ret; -} - - -gboolean -flatpak_dir_prune (FlatpakDir *self, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - gint objects_total, objects_pruned; - guint64 pruned_object_size_total; - g_autofree char *formatted_freed_size = NULL; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - goto out; - - if (!ostree_repo_prune (self->repo, - OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY, - 0, - &objects_total, - &objects_pruned, - &pruned_object_size_total, - cancellable, error)) - goto out; - - formatted_freed_size = g_format_size_full (pruned_object_size_total, 0); - g_debug ("Pruned %d/%d objects, size %s", objects_total, objects_pruned, formatted_freed_size); - - ret = TRUE; -out: - return ret; - -} - -GFile * -flatpak_dir_get_if_deployed (FlatpakDir *self, - const char *ref, - const char *checksum, - GCancellable *cancellable) -{ - g_autoptr(GFile) deploy_base = NULL; - g_autoptr(GFile) deploy_dir = NULL; - - deploy_base = flatpak_dir_get_deploy_dir (self, ref); - - if (checksum != NULL) - { - deploy_dir = g_file_get_child (deploy_base, checksum); - } - else - { - g_autoptr(GFile) active_link = g_file_get_child (deploy_base, "active"); - g_autoptr(GFileInfo) info = NULL; - const char *target; - - info = g_file_query_info (active_link, - G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - NULL, - NULL); - if (info == NULL) - return NULL; - - target = g_file_info_get_symlink_target (info); - if (target == NULL) - return NULL; - - deploy_dir = g_file_get_child (deploy_base, target); - } - - if (g_file_query_file_type (deploy_dir, G_FILE_QUERY_INFO_NONE, cancellable) == G_FILE_TYPE_DIRECTORY) - return g_object_ref (deploy_dir); - return NULL; -} - -static gboolean -flatpak_dir_remote_fetch_summary (FlatpakDir *self, - const char *name, - GBytes **out_summary, - GCancellable *cancellable, - GError **error) -{ - /* TODO: Add in-memory cache here, also use for ostree_repo_list_refs */ - if (!ostree_repo_remote_fetch_summary (self->repo, name, - out_summary, NULL, - cancellable, - error)) - return FALSE; - - return TRUE; -} - -char * -flatpak_dir_find_remote_ref (FlatpakDir *self, - const char *remote, - const char *name, - const char *opt_branch, - const char *opt_arch, - gboolean app, - gboolean runtime, - gboolean *is_app, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *app_ref = NULL; - g_autofree char *runtime_ref = NULL; - g_autofree char *app_ref_with_remote = NULL; - g_autofree char *runtime_ref_with_remote = NULL; - - g_autoptr(GVariant) summary = NULL; - g_autoptr(GVariant) refs = NULL; - g_autoptr(GBytes) summary_bytes = NULL; - - if (!flatpak_dir_ensure_repo (self, NULL, error)) - return NULL; - - if (app) - { - app_ref = flatpak_compose_ref (TRUE, name, opt_branch, opt_arch, error); - if (app_ref == NULL) - return NULL; - app_ref_with_remote = g_strconcat (remote, ":", app_ref, NULL); - } - - if (runtime) - { - runtime_ref = flatpak_compose_ref (FALSE, name, opt_branch, opt_arch, error); - if (runtime_ref == NULL) - return NULL; - runtime_ref_with_remote = g_strconcat (remote, ":", app_ref, NULL); - } - - /* First look for a local ref */ - - if (app_ref && - ostree_repo_resolve_rev (self->repo, app_ref_with_remote, - FALSE, NULL, NULL)) - { - if (is_app) - *is_app = TRUE; - return g_steal_pointer (&app_ref); - } - - if (runtime_ref && - ostree_repo_resolve_rev (self->repo, runtime_ref_with_remote, - FALSE, NULL, NULL)) - { - if (is_app) - *is_app = FALSE; - return g_steal_pointer (&runtime_ref); - } - - if (!flatpak_dir_remote_fetch_summary (self, remote, - &summary_bytes, - cancellable, error)) - return NULL; - - if (summary_bytes == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Can't find %s in remote %s; server has no summary file", name, remote); - return NULL; - } - - summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE)); - refs = g_variant_get_child_value (summary, 0); - - if (app_ref && flatpak_summary_lookup_ref (summary, app_ref, NULL)) - { - if (is_app) - *is_app = TRUE; - return g_steal_pointer (&app_ref); - } - - if (runtime_ref && flatpak_summary_lookup_ref (summary, runtime_ref, NULL)) - { - if (is_app) - *is_app = FALSE; - return g_steal_pointer (&runtime_ref); - } - - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Can't find %s %s in remote %s", name, opt_branch ? opt_branch : "master", remote); - - return NULL; -} - -char * -flatpak_dir_find_installed_ref (FlatpakDir *self, - const char *name, - const char *opt_branch, - const char *opt_arch, - gboolean app, - gboolean runtime, - gboolean *is_app, - GError **error) -{ - if (app) - { - g_autofree char *app_ref = NULL; - g_autoptr(GFile) deploy_base = NULL; - - app_ref = flatpak_compose_ref (TRUE, name, opt_branch, opt_arch, error); - if (app_ref == NULL) - return NULL; - - - deploy_base = flatpak_dir_get_deploy_dir (self, app_ref); - if (g_file_query_exists (deploy_base, NULL)) - { - if (is_app) - *is_app = TRUE; - return g_steal_pointer (&app_ref); - } - } - - if (runtime) - { - g_autofree char *runtime_ref = NULL; - g_autoptr(GFile) deploy_base = NULL; - - runtime_ref = flatpak_compose_ref (FALSE, name, opt_branch, opt_arch, error); - if (runtime_ref == NULL) - return NULL; - - deploy_base = flatpak_dir_get_deploy_dir (self, runtime_ref); - if (g_file_query_exists (deploy_base, NULL)) - { - if (is_app) - *is_app = FALSE; - return g_steal_pointer (&runtime_ref); - } - } - - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "%s %s not installed", name, opt_branch ? opt_branch : "master"); - return NULL; -} - -FlatpakDir * -flatpak_dir_new (GFile *path, gboolean user) -{ - return g_object_new (FLATPAK_TYPE_DIR, "path", path, "user", user, NULL); -} - -FlatpakDir * -flatpak_dir_clone (FlatpakDir *self) -{ - return flatpak_dir_new (self->basedir, self->user); -} - -FlatpakDir * -flatpak_dir_get_system (void) -{ - g_autoptr(GFile) path = flatpak_get_system_base_dir_location (); - return flatpak_dir_new (path, FALSE); -} - -FlatpakDir * -flatpak_dir_get_user (void) -{ - g_autoptr(GFile) path = flatpak_get_user_base_dir_location (); - return flatpak_dir_new (path, TRUE); -} - -FlatpakDir * -flatpak_dir_get (gboolean user) -{ - if (user) - return flatpak_dir_get_user (); - else - return flatpak_dir_get_system (); -} - -static char * -get_group (const char *remote_name) -{ - return g_strdup_printf ("remote \"%s\"", remote_name); -} - -char * -flatpak_dir_get_remote_title (FlatpakDir *self, - const char *remote_name) -{ - GKeyFile *config = ostree_repo_get_config (self->repo); - g_autofree char *group = get_group (remote_name); - - if (config) - return g_key_file_get_string (config, group, "xa.title", NULL); - - return NULL; -} - -int -flatpak_dir_get_remote_prio (FlatpakDir *self, - const char *remote_name) -{ - GKeyFile *config = ostree_repo_get_config (self->repo); - g_autofree char *group = get_group (remote_name); - - if (config && g_key_file_has_key (config, group, "xa.prio", NULL)) - return g_key_file_get_integer (config, group, "xa.prio", NULL); - - return 1; -} - -gboolean -flatpak_dir_get_remote_noenumerate (FlatpakDir *self, - const char *remote_name) -{ - GKeyFile *config = ostree_repo_get_config (self->repo); - g_autofree char *group = get_group (remote_name); - - if (config) - return g_key_file_get_boolean (config, group, "xa.noenumerate", NULL); - - return TRUE; -} - -gboolean -flatpak_dir_get_remote_disabled (FlatpakDir *self, - const char *remote_name) -{ - GKeyFile *config = ostree_repo_get_config (self->repo); - g_autofree char *group = get_group (remote_name); - - if (config) - return g_key_file_get_boolean (config, group, "xa.disable", NULL); - - return TRUE; -} - -gint -cmp_remote (gconstpointer a, - gconstpointer b, - gpointer user_data) -{ - FlatpakDir *self = user_data; - const char *a_name = *(const char **) a; - const char *b_name = *(const char **) b; - int prio_a, prio_b; - - prio_a = flatpak_dir_get_remote_prio (self, a_name); - prio_b = flatpak_dir_get_remote_prio (self, b_name); - - return prio_b - prio_a; -} - -char * -flatpak_dir_create_origin_remote (FlatpakDir *self, - const char *url, - const char *id, - const char *title, - GBytes *gpg_data, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *remote = NULL; - - g_auto(GStrv) remotes = NULL; - int version = 0; - g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - remotes = ostree_repo_remote_list (self->repo, NULL); - - do - { - g_autofree char *name = NULL; - if (version == 0) - name = g_strdup_printf ("%s-origin", id); - else - name = g_strdup_printf ("%s-%d-origin", id, version); - version++; - - if (remotes == NULL || - !g_strv_contains ((const char * const *) remotes, name)) - remote = g_steal_pointer (&name); - } - while (remote == NULL); - - g_variant_builder_add (optbuilder, "{s@v}", - "xa.title", - g_variant_new_variant (g_variant_new_string (title))); - - g_variant_builder_add (optbuilder, "{s@v}", - "xa.noenumerate", - g_variant_new_variant (g_variant_new_boolean (TRUE))); - - g_variant_builder_add (optbuilder, "{s@v}", - "xa.prio", - g_variant_new_variant (g_variant_new_string ("0"))); - - if (!ostree_repo_remote_add (self->repo, - remote, url ? url : "", g_variant_builder_end (optbuilder), cancellable, error)) - return NULL; - - if (gpg_data) - { - g_autoptr(GInputStream) gpg_data_as_stream = g_memory_input_stream_new_from_bytes (gpg_data); - - if (!ostree_repo_remote_gpg_import (self->repo, remote, gpg_data_as_stream, - NULL, NULL, cancellable, error)) - { - ostree_repo_remote_delete (self->repo, remote, - NULL, NULL); - return NULL; - } - } - - return g_steal_pointer (&remote); -} - - -char ** -flatpak_dir_list_remotes (FlatpakDir *self, - GCancellable *cancellable, - GError **error) -{ - char **res; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return NULL; - - res = ostree_repo_remote_list (self->repo, NULL); - if (res == NULL) - res = g_new0 (char *, 1); /* Return empty array, not error */ - - g_qsort_with_data (res, g_strv_length (res), sizeof (char *), - cmp_remote, self); - - return res; -} - -static gboolean -remove_unless_in_hash (gpointer key, - gpointer value, - gpointer user_data) -{ - GHashTable *table = user_data; - - return !g_hash_table_contains (table, key); -} - -gboolean -flatpak_dir_list_remote_refs (FlatpakDir *self, - const char *remote, - GHashTable **refs, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GError) my_error = NULL; - - if (error == NULL) - error = &my_error; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - if (!ostree_repo_remote_list_refs (self->repo, remote, - refs, cancellable, error)) - return FALSE; - - if (flatpak_dir_get_remote_noenumerate (self, remote)) - { - g_autoptr(GHashTable) unprefixed_local_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - g_autoptr(GHashTable) local_refs = NULL; - GHashTableIter hash_iter; - gpointer key; - g_autofree char *refspec_prefix = g_strconcat (remote, ":.", NULL); - - /* For noenumerate remotes, only return data for already locally - * available refs */ - - if (!ostree_repo_list_refs (self->repo, refspec_prefix, &local_refs, - cancellable, error)) - return FALSE; - - /* First we need to unprefix the remote name from the local refs */ - g_hash_table_iter_init (&hash_iter, local_refs); - while (g_hash_table_iter_next (&hash_iter, &key, NULL)) - { - char *ref = NULL; - ostree_parse_refspec (key, NULL, &ref, NULL); - - if (ref) - g_hash_table_insert (unprefixed_local_refs, ref, NULL); - } - - /* Then we remove all remote refs not in the local refs set */ - g_hash_table_foreach_remove (*refs, - remove_unless_in_hash, - unprefixed_local_refs); - } - - return TRUE; -} - -char * -flatpak_dir_fetch_remote_title (FlatpakDir *self, - const char *remote, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GError) my_error = NULL; - g_autoptr(GBytes) summary_bytes = NULL; - g_autoptr(GVariant) summary = NULL; - g_autoptr(GVariant) extensions = NULL; - GVariantDict dict; - g_autofree char *title = NULL; - - if (error == NULL) - error = &my_error; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return NULL; - - if (!flatpak_dir_remote_fetch_summary (self, remote, - &summary_bytes, - cancellable, error)) - return FALSE; - - if (summary_bytes == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Remote title not available; server has no summary file"); - return FALSE; - } - - summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, - summary_bytes, FALSE); - extensions = g_variant_get_child_value (summary, 1); - - g_variant_dict_init (&dict, extensions); - g_variant_dict_lookup (&dict, "xa.title", "s", &title); - g_variant_dict_end (&dict); - - if (title == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Remote title not set"); - return FALSE; - } - - return g_steal_pointer (&title); -} - -static void -ensure_soup_session (FlatpakDir *self) -{ - const char *http_proxy; - - if (g_once_init_enter (&self->soup_session)) - { - SoupSession *soup_session; - - soup_session = - soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "ostree ", - SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, - SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, - SOUP_SESSION_TIMEOUT, 60, - SOUP_SESSION_IDLE_TIMEOUT, 60, - NULL); - http_proxy = g_getenv ("http_proxy"); - if (http_proxy) - { - g_autoptr(SoupURI) proxy_uri = soup_uri_new (http_proxy); - - if (!proxy_uri) - g_warning ("Invalid proxy URI '%s'", http_proxy); - else - g_object_set (soup_session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); - } - - if (g_getenv ("OSTREE_DEBUG_HTTP")) - soup_session_add_feature (soup_session, (SoupSessionFeature *) soup_logger_new (SOUP_LOGGER_LOG_BODY, 500)); - - g_once_init_leave (&self->soup_session, soup_session); - } -} - -static GBytes * -flatpak_dir_load_uri (FlatpakDir *self, - const char *uri, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *scheme = NULL; - - g_autoptr(GBytes) bytes = NULL; - - scheme = g_uri_parse_scheme (uri); - if (strcmp (scheme, "file") == 0) - { - char *buffer; - gsize length; - g_autoptr(GFile) file = NULL; - - g_debug ("Loading %s using GIO", uri); - - file = g_file_new_for_uri (uri); - if (!g_file_load_contents (file, cancellable, &buffer, &length, NULL, NULL)) - return NULL; - - bytes = g_bytes_new_take (buffer, length); - } - else if (strcmp (scheme, "http") == 0 || - strcmp (scheme, "https") == 0) - { - g_autoptr(SoupMessage) msg = NULL; - - ensure_soup_session (self); - - g_debug ("Loading %s using libsoup", uri); - msg = soup_message_new ("GET", uri); - soup_session_send_message (self->soup_session, msg); - - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) - { - GIOErrorEnum code; - - switch (msg->status_code) - { - case 404: - case 410: - code = G_IO_ERROR_NOT_FOUND; - break; - - default: - code = G_IO_ERROR_FAILED; - } - - g_set_error (error, G_IO_ERROR, code, - "Server returned status %u: %s", - msg->status_code, - soup_status_get_phrase (msg->status_code)); - return NULL; - } - - bytes = g_bytes_new (msg->response_body->data, msg->response_body->length); - } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unsupported uri scheme %s", scheme); - return FALSE; - } - - g_debug ("Received %" G_GSIZE_FORMAT " bytes", g_bytes_get_size (bytes)); - - return g_steal_pointer (&bytes); -} - -GBytes * -flatpak_dir_fetch_remote_object (FlatpakDir *self, - const char *remote_name, - const char *checksum, - const char *type, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *base_url = NULL; - g_autofree char *object_url = NULL; - g_autofree char *part1 = NULL; - g_autofree char *part2 = NULL; - - g_autoptr(GBytes) bytes = NULL; - - if (!ostree_repo_remote_get_url (self->repo, remote_name, &base_url, error)) - return NULL; - - part1 = g_strndup (checksum, 2); - part2 = g_strdup_printf ("%s.%s", checksum + 2, type); - - object_url = g_build_filename (base_url, "objects", part1, part2, NULL); - - bytes = flatpak_dir_load_uri (self, object_url, cancellable, error); - if (bytes == NULL) - return NULL; - - return g_steal_pointer (&bytes); -} - -gboolean -flatpak_dir_fetch_ref_cache (FlatpakDir *self, - const char *remote_name, - const char *ref, - guint64 *download_size, - guint64 *installed_size, - char **metadata, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GBytes) summary_bytes = NULL; - g_autoptr(GVariant) extensions = NULL; - g_autoptr(GVariant) summary = NULL; - g_autoptr(GVariant) cache_v = NULL; - g_autoptr(GVariant) cache = NULL; - g_autoptr(GVariant) res = NULL; - - if (!flatpak_dir_ensure_repo (self, cancellable, error)) - return FALSE; - - if (!flatpak_dir_remote_fetch_summary (self, remote_name, - &summary_bytes, - cancellable, error)) - return FALSE; - - if (summary_bytes == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Data not available; server has no summary file"); - return FALSE; - } - - summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, - summary_bytes, FALSE); - extensions = g_variant_get_child_value (summary, 1); - - cache_v = g_variant_lookup_value (extensions, "xa.cache", NULL); - if (cache_v == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Data not found"); - return FALSE; - } - - cache = g_variant_get_child_value (cache_v, 0); - res = g_variant_lookup_value (cache, ref, NULL); - if (res == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Data not found for ref %s", ref); - return FALSE; - } - - if (installed_size) - { - guint64 v; - g_variant_get_child (res, 0, "t", &v); - *installed_size = GUINT64_FROM_BE (v); - } - - if (download_size) - { - guint64 v; - g_variant_get_child (res, 1, "t", &v); - *download_size = GUINT64_FROM_BE (v); - } - - if (metadata) - g_variant_get_child (res, 2, "s", metadata); - - return TRUE; -} - -GBytes * -flatpak_dir_fetch_metadata (FlatpakDir *self, - const char *remote_name, - const char *commit, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GBytes) commit_bytes = NULL; - g_autoptr(GBytes) root_bytes = NULL; - g_autoptr(GBytes) filez_bytes = NULL; - g_autoptr(GVariant) commit_variant = NULL; - g_autoptr(GVariant) root_variant = NULL; - g_autoptr(GVariant) root_csum = NULL; - g_autoptr(GVariant) files_variant = NULL; - g_autofree char *file_checksum = NULL; - g_autofree char *root_checksum = NULL; - g_autoptr(GConverter) zlib_decomp = NULL; - g_autoptr(GInputStream) zlib_input = NULL; - g_autoptr(GMemoryOutputStream) data_stream = NULL; - g_autoptr(GMemoryInputStream) dataz_stream = NULL; - gsize filez_size; - const guchar *filez_data; - guint32 archive_header_size; - int i, n; - - commit_bytes = flatpak_dir_fetch_remote_object (self, remote_name, - commit, "commit", - cancellable, error); - if (commit_bytes == NULL) - return NULL; - - commit_variant = g_variant_new_from_bytes (OSTREE_COMMIT_GVARIANT_FORMAT, - commit_bytes, FALSE); - - if (!ostree_validate_structureof_commit (commit_variant, error)) - return NULL; - - g_variant_get_child (commit_variant, 6, "@ay", &root_csum); - root_checksum = ostree_checksum_from_bytes_v (root_csum); - - root_bytes = flatpak_dir_fetch_remote_object (self, remote_name, - root_checksum, "dirtree", - cancellable, error); - if (root_bytes == NULL) - return NULL; - - root_variant = g_variant_new_from_bytes (OSTREE_TREE_GVARIANT_FORMAT, - root_bytes, FALSE); - - if (!ostree_validate_structureof_dirtree (root_variant, error)) - return NULL; - - files_variant = g_variant_get_child_value (root_variant, 0); - - n = g_variant_n_children (files_variant); - for (i = 0; i < n; i++) - { - const char *filename; - g_autoptr(GVariant) csum = NULL; - - g_variant_get_child (files_variant, i, "(&s@ay)", &filename, &csum); - - if (strcmp (filename, "metadata") != 0) - continue; - - file_checksum = ostree_checksum_from_bytes_v (csum); - break; - } - - if (file_checksum == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Can't find metadata file"); - return NULL; - } - - filez_bytes = flatpak_dir_fetch_remote_object (self, remote_name, - file_checksum, "filez", - cancellable, error); - if (filez_bytes == NULL) - return NULL; - - filez_data = g_bytes_get_data (filez_bytes, &filez_size); - - if (filez_size < 8) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid header"); - return NULL; - } - - archive_header_size = GUINT32_FROM_BE (*(guint32 *) filez_data); - - archive_header_size += 4 + 4; /* Include header-size and padding */ - - if (archive_header_size > filez_size) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "File header size %u exceeds file size", - (guint) archive_header_size); - return NULL; - } - - dataz_stream = (GMemoryInputStream *) g_memory_input_stream_new_from_data (filez_data + archive_header_size, - g_bytes_get_size (filez_bytes) - archive_header_size, - NULL); - - zlib_decomp = (GConverter *) g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW); - zlib_input = g_converter_input_stream_new (G_INPUT_STREAM (dataz_stream), zlib_decomp); - - data_stream = (GMemoryOutputStream *) g_memory_output_stream_new (NULL, 0, g_realloc, g_free); - - if (g_output_stream_splice (G_OUTPUT_STREAM (data_stream), zlib_input, - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, - cancellable, error) < 0) - return NULL; - - return g_memory_output_stream_steal_as_bytes (data_stream); -} diff --git a/common/xdg-app-dir.h b/common/xdg-app-dir.h deleted file mode 100644 index cfedccd..0000000 --- a/common/xdg-app-dir.h +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright © 2014 Red Hat, Inc - * - * This program 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 library 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 library. If not, see . - * - * Authors: - * Alexander Larsson - */ - -#ifndef __FLATPAK_DIR_H__ -#define __FLATPAK_DIR_H__ - -#include - -#include "libglnx/libglnx.h" -#include - -#define FLATPAK_TYPE_DIR flatpak_dir_get_type () -#define FLATPAK_DIR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_DIR, FlatpakDir)) -#define FLATPAK_IS_DIR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_DIR)) - -#define FLATPAK_TYPE_DEPLOY flatpak_deploy_get_type () -#define FLATPAK_DEPLOY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), FLATPAK_TYPE_DEPLOY, FlatpakDeploy)) -#define FLATPAK_IS_DEPLOY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FLATPAK_TYPE_DEPLOY)) - -GType flatpak_dir_get_type (void); -GType flatpak_deploy_get_type (void); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDir, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakDeploy, g_object_unref) - -#define FLATPAK_DIR_ERROR flatpak_dir_error_quark () - -typedef enum { - FLATPAK_DIR_ERROR_ALREADY_DEPLOYED, - FLATPAK_DIR_ERROR_ALREADY_UNDEPLOYED, - FLATPAK_DIR_ERROR_NOT_DEPLOYED, -} FlatpakDirErrorEnum; - -typedef enum { - FLATPAK_HELPER_DEPLOY_FLAGS_NONE = 0, - FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE = 1 << 0, -} FlatpakHelperDeployFlags; - -#define FLATPAK_HELPER_DEPLOY_FLAGS_ALL (FLATPAK_HELPER_DEPLOY_FLAGS_UPDATE) - -GQuark flatpak_dir_error_quark (void); - -/** - * FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT: - * - * s - origin - * s - commit - * as - subpaths - * t - installed size - * a{sv} - Metadata - */ -#define FLATPAK_DEPLOY_DATA_GVARIANT_STRING "(ssasta{sv})" -#define FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT G_VARIANT_TYPE (FLATPAK_DEPLOY_DATA_GVARIANT_STRING) - -GFile * flatpak_get_system_base_dir_location (void); -GFile * flatpak_get_user_base_dir_location (void); - -GKeyFile * flatpak_load_override_keyfile (const char *app_id, - gboolean user, - GError **error); -FlatpakContext *flatpak_load_override_file (const char *app_id, - gboolean user, - GError **error); -gboolean flatpak_save_override_keyfile (GKeyFile *metakey, - const char *app_id, - gboolean user, - GError **error); - -const char * flatpak_deploy_data_get_origin (GVariant *deploy_data); -const char * flatpak_deploy_data_get_commit (GVariant *deploy_data); -const char ** flatpak_deploy_data_get_subpaths (GVariant *deploy_data); -guint64 flatpak_deploy_data_get_installed_size (GVariant *deploy_data); - -GFile * flatpak_deploy_get_dir (FlatpakDeploy *deploy); -GFile * flatpak_deploy_get_files (FlatpakDeploy *deploy); -FlatpakContext *flatpak_deploy_get_overrides (FlatpakDeploy *deploy); -GKeyFile * flatpak_deploy_get_metadata (FlatpakDeploy *deploy); - -FlatpakDir * flatpak_dir_new (GFile *basedir, - gboolean user); -FlatpakDir * flatpak_dir_clone (FlatpakDir *self); -FlatpakDir *flatpak_dir_get (gboolean user); -FlatpakDir *flatpak_dir_get_system (void); -FlatpakDir *flatpak_dir_get_user (void); -gboolean flatpak_dir_is_user (FlatpakDir *self); -GFile * flatpak_dir_get_path (FlatpakDir *self); -GFile * flatpak_dir_get_changed_path (FlatpakDir *self); -GFile * flatpak_dir_get_deploy_dir (FlatpakDir *self, - const char *ref); -GVariant * flatpak_dir_get_deploy_data (FlatpakDir *dir, - const char *ref, - GCancellable *cancellable, - GError **error); -char * flatpak_dir_get_origin (FlatpakDir *self, - const char *ref, - GCancellable *cancellable, - GError **error); -char ** flatpak_dir_get_subpaths (FlatpakDir *self, - const char *ref, - GCancellable *cancellable, - GError **error); -GFile * flatpak_dir_get_exports_dir (FlatpakDir *self); -GFile * flatpak_dir_get_removed_dir (FlatpakDir *self); -GFile * flatpak_dir_get_if_deployed (FlatpakDir *self, - const char *ref, - const char *checksum, - GCancellable *cancellable); -char * flatpak_dir_find_remote_ref (FlatpakDir *self, - const char *remote, - const char *name, - const char *opt_branch, - const char *opt_arch, - gboolean app, - gboolean runtime, - gboolean *is_app, - GCancellable *cancellable, - GError **error); -char * flatpak_dir_find_installed_ref (FlatpakDir *self, - const char *name, - const char *opt_branch, - const char *opt_arch, - gboolean app, - gboolean runtime, - gboolean *is_app, - GError **error); -FlatpakDeploy *flatpak_dir_load_deployed (FlatpakDir *self, - const char *ref, - const char *checksum, - GCancellable *cancellable, - GError **error); -char * flatpak_dir_load_override (FlatpakDir *dir, - const char *app_id, - gsize *length, - GError **error); -OstreeRepo *flatpak_dir_get_repo (FlatpakDir *self); -gboolean flatpak_dir_ensure_path (FlatpakDir *self, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_use_child_repo (FlatpakDir *self); -gboolean flatpak_dir_ensure_system_child_repo (FlatpakDir *self, - GError **error); -gboolean flatpak_dir_ensure_repo (FlatpakDir *self, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_mark_changed (FlatpakDir *self, - GError **error); -gboolean flatpak_dir_remove_appstream (FlatpakDir *self, - const char *remote, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_update_appstream (FlatpakDir *self, - const char *remote, - const char *arch, - gboolean *out_changed, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_pull (FlatpakDir *self, - const char *repository, - const char *ref, - char **subpaths, - OstreeRepo *repo, - OstreeRepoPullFlags flags, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_pull_untrusted_local (FlatpakDir *self, - const char *src_path, - const char *remote_name, - const char *ref, - char **subpaths, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_list_refs_for_name (FlatpakDir *self, - const char *kind, - const char *name, - char ***refs, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_list_refs (FlatpakDir *self, - const char *kind, - char ***refs, - GCancellable *cancellable, - GError **error); -char * flatpak_dir_read_latest (FlatpakDir *self, - const char *remote, - const char *ref, - GCancellable *cancellable, - GError **error); -char * flatpak_dir_read_active (FlatpakDir *self, - const char *ref, - GCancellable *cancellable); -gboolean flatpak_dir_set_active (FlatpakDir *self, - const char *ref, - const char *checksum, - GCancellable *cancellable, - GError **error); -char * flatpak_dir_current_ref (FlatpakDir *self, - const char *name, - GCancellable *cancellable); -gboolean flatpak_dir_drop_current_ref (FlatpakDir *self, - const char *name, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_make_current_ref (FlatpakDir *self, - const char *ref, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_list_deployed (FlatpakDir *self, - const char *ref, - char ***deployed_checksums, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_lock (FlatpakDir *self, - GLnxLockFile *lockfile, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_deploy (FlatpakDir *self, - const char *origin, - const char *ref, - const char *checksum_or_latest, - const char * const * subpaths, - GVariant *old_deploy_data, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_deploy_update (FlatpakDir *self, - const char *ref, - const char *checksum, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_deploy_install (FlatpakDir *self, - const char *ref, - const char *origin, - char **subpaths, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_install (FlatpakDir *self, - gboolean no_pull, - gboolean no_deploy, - const char *ref, - const char *remote_name, - char **subpaths, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_update (FlatpakDir *self, - gboolean no_pull, - gboolean no_deploy, - const char *ref, - const char *remote_name, - const char *checksum_or_latest, - char **subpaths, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_undeploy (FlatpakDir *self, - const char *ref, - const char *checksum, - gboolean force_remove, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_undeploy_all (FlatpakDir *self, - const char *ref, - gboolean force_remove, - gboolean *was_deployed_out, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_remove_all_refs (FlatpakDir *self, - const char *remote, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_remove_ref (FlatpakDir *self, - const char *remote_name, - const char *ref, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_update_exports (FlatpakDir *self, - const char *app, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_prune (FlatpakDir *self, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_cleanup_removed (FlatpakDir *self, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_collect_deployed_refs (FlatpakDir *self, - const char *type, - const char *name_prefix, - const char *branch, - const char *arch, - GHashTable *hash, - GCancellable *cancellable, - GError **error); -char *flatpak_dir_create_origin_remote (FlatpakDir *self, - const char *url, - const char *id, - const char *title, - GBytes *gpg_data, - GCancellable *cancellable, - GError **error); -char **flatpak_dir_list_remotes (FlatpakDir *self, - GCancellable *cancellable, - GError **error); -char *flatpak_dir_get_remote_title (FlatpakDir *self, - const char *remote_name); -int flatpak_dir_get_remote_prio (FlatpakDir *self, - const char *remote_name); -gboolean flatpak_dir_get_remote_noenumerate (FlatpakDir *self, - const char *remote_name); -gboolean flatpak_dir_get_remote_disabled (FlatpakDir *self, - const char *remote_name); -gboolean flatpak_dir_list_remote_refs (FlatpakDir *self, - const char *remote, - GHashTable **refs, - GCancellable *cancellable, - GError **error); -char * flatpak_dir_fetch_remote_title (FlatpakDir *self, - const char *remote, - GCancellable *cancellable, - GError **error); -GBytes * flatpak_dir_fetch_remote_object (FlatpakDir *self, - const char *remote, - const char *checksum, - const char *type, - GCancellable *cancellable, - GError **error); -GBytes * flatpak_dir_fetch_metadata (FlatpakDir *self, - const char *remote_name, - const char *commit, - GCancellable *cancellable, - GError **error); -gboolean flatpak_dir_fetch_ref_cache (FlatpakDir *self, - const char *remote_name, - const char *ref, - guint64 *download_size, - guint64 *installed_size, - char **metadata, - GCancellable *cancellable, - GError **error); - -#endif /* __FLATPAK_DIR_H__ */ diff --git a/common/xdg-app-portal-error.c b/common/xdg-app-portal-error.c deleted file mode 100644 index 036aca7..0000000 --- a/common/xdg-app-portal-error.c +++ /dev/null @@ -1,48 +0,0 @@ -/* xdg-app-error.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 . - * - * Authors: - * Alexander Larsson - */ - -#include "config.h" - -#include "xdg-app-portal-error.h" - -#include - -static const GDBusErrorEntry flatpak_error_entries[] = { - {FLATPAK_PORTAL_ERROR_FAILED, "org.freedesktop.Flatpak.Failed"}, - {FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT, "org.freedesktop.Flatpak.InvalidArgument"}, - {FLATPAK_PORTAL_ERROR_NOT_FOUND, "org.freedesktop.Flatpak.NotFound"}, - {FLATPAK_PORTAL_ERROR_EXISTS, "org.freedesktop.Flatpak.Exists"}, - {FLATPAK_PORTAL_ERROR_NOT_ALLOWED, "org.freedesktop.Flatpak.NotAllowed"}, - {FLATPAK_PORTAL_ERROR_CANCELLED, "org.freedesktop.Flatpak.Cancelled"}, - {FLATPAK_PORTAL_ERROR_WINDOW_DESTROYED, "org.freedesktop.Flatpak.WindowDestroyed"}, -}; - -GQuark -flatpak_portal_error_quark (void) -{ - static volatile gsize quark_volatile = 0; - - g_dbus_error_register_error_domain ("xdg-app-portal-error-quark", - &quark_volatile, - flatpak_error_entries, - G_N_ELEMENTS (flatpak_error_entries)); - return (GQuark) quark_volatile; -} diff --git a/common/xdg-app-portal-error.h b/common/xdg-app-portal-error.h deleted file mode 100644 index 2ff02e8..0000000 --- a/common/xdg-app-portal-error.h +++ /dev/null @@ -1,49 +0,0 @@ -/* xdg-app-error.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 . - * - * Authors: - * Alexander Larsson - */ - -#ifndef FLATPAK_PORTAL_ERROR_H -#define FLATPAK_PORTAL_ERROR_H - -#include - -G_BEGIN_DECLS - -/** - * XdpErrorEnum: - */ -typedef enum { - FLATPAK_PORTAL_ERROR_FAILED = 0, - FLATPAK_PORTAL_ERROR_INVALID_ARGUMENT, - FLATPAK_PORTAL_ERROR_NOT_FOUND, - FLATPAK_PORTAL_ERROR_EXISTS, - FLATPAK_PORTAL_ERROR_NOT_ALLOWED, - FLATPAK_PORTAL_ERROR_CANCELLED, - FLATPAK_PORTAL_ERROR_WINDOW_DESTROYED, -} FlatpakErrorEnum; - - -#define FLATPAK_PORTAL_ERROR flatpak_portal_error_quark () - -FLATPAK_EXTERN GQuark flatpak_portal_error_quark (void); - -G_END_DECLS - -#endif /* FLATPAK_PORTAL_ERROR_H */ diff --git a/common/xdg-app-run.c b/common/xdg-app-run.c deleted file mode 100644 index 2062657..0000000 --- a/common/xdg-app-run.c +++ /dev/null @@ -1,3050 +0,0 @@ -/* - * Copyright © 2014 Red Hat, Inc - * - * This program 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 library 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 library. If not, see . - * - * Authors: - * Alexander Larsson - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef ENABLE_SECCOMP -#include -#endif - -#ifdef ENABLE_XAUTH -#include -#endif - -#include -#include "libgsystem.h" -#include "libglnx/libglnx.h" - -#include "xdg-app-run.h" -#include "xdg-app-proxy.h" -#include "xdg-app-utils.h" -#include "xdg-app-systemd-dbus.h" - -#define DEFAULT_SHELL "/bin/sh" - -typedef enum { - FLATPAK_CONTEXT_SHARED_NETWORK = 1 << 0, - FLATPAK_CONTEXT_SHARED_IPC = 1 << 1, -} FlatpakContextShares; - -/* In numerical order of more privs */ -typedef enum { - FLATPAK_FILESYSTEM_MODE_READ_ONLY = 1, - FLATPAK_FILESYSTEM_MODE_READ_WRITE = 2, -} FlatpakFilesystemMode; - - -/* Same order as enum */ -static const char *flatpak_context_shares[] = { - "network", - "ipc", - NULL -}; - -typedef enum { - FLATPAK_CONTEXT_SOCKET_X11 = 1 << 0, - FLATPAK_CONTEXT_SOCKET_WAYLAND = 1 << 1, - FLATPAK_CONTEXT_SOCKET_PULSEAUDIO = 1 << 2, - FLATPAK_CONTEXT_SOCKET_SESSION_BUS = 1 << 3, - FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS = 1 << 4, -} FlatpakContextSockets; - -/* Same order as enum */ -static const char *flatpak_context_sockets[] = { - "x11", - "wayland", - "pulseaudio", - "session-bus", - "system-bus", - NULL -}; - -const char *dont_mount_in_root[] = { - ".", "..", "lib", "lib32", "lib64", "bin", "sbin", "usr", "boot", "root", - "tmp", "etc", "app", "run", "proc", "sys", "dev", "var", NULL -}; - -typedef enum { - FLATPAK_CONTEXT_DEVICE_DRI = 1 << 0, -} FlatpakContextDevices; - -static const char *flatpak_context_devices[] = { - "dri", - NULL -}; - -struct FlatpakContext -{ - FlatpakContextShares shares; - FlatpakContextShares shares_valid; - FlatpakContextSockets sockets; - FlatpakContextSockets sockets_valid; - FlatpakContextDevices devices; - FlatpakContextDevices devices_valid; - GHashTable *env_vars; - GHashTable *persistent; - GHashTable *filesystems; - GHashTable *session_bus_policy; - GHashTable *system_bus_policy; -}; - -FlatpakContext * -flatpak_context_new (void) -{ - FlatpakContext *context; - - context = g_slice_new0 (FlatpakContext); - context->env_vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - context->persistent = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - context->filesystems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - context->session_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - context->system_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - - return context; -} - -void -flatpak_context_free (FlatpakContext *context) -{ - g_hash_table_destroy (context->env_vars); - g_hash_table_destroy (context->persistent); - g_hash_table_destroy (context->filesystems); - g_hash_table_destroy (context->session_bus_policy); - g_hash_table_destroy (context->system_bus_policy); - g_slice_free (FlatpakContext, context); -} - -static guint32 -flatpak_context_bitmask_from_string (const char *name, const char **names) -{ - guint32 i; - - for (i = 0; names[i] != NULL; i++) - { - if (strcmp (names[i], name) == 0) - return 1 << i; - } - - return 0; -} - - -static char ** -flatpak_context_bitmask_to_string (guint32 enabled, guint32 valid, const char **names) -{ - guint32 i; - GPtrArray *array; - - array = g_ptr_array_new (); - - for (i = 0; names[i] != NULL; i++) - { - guint32 bitmask = 1 << i; - if (valid & bitmask) - { - if (enabled & bitmask) - g_ptr_array_add (array, g_strdup (names[i])); - else - g_ptr_array_add (array, g_strdup_printf ("!%s", names[i])); - } - } - - g_ptr_array_add (array, NULL); - return (char **) g_ptr_array_free (array, FALSE); -} - -static FlatpakContextShares -flatpak_context_share_from_string (const char *string, GError **error) -{ - FlatpakContextShares shares = flatpak_context_bitmask_from_string (string, flatpak_context_shares); - - if (shares == 0) - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, - "Unknown share type %s, valid types are: network, ipc\n", string); - return shares; -} - -static char ** -flatpak_context_shared_to_string (FlatpakContextShares shares, FlatpakContextShares valid) -{ - return flatpak_context_bitmask_to_string (shares, valid, flatpak_context_shares); -} - -static FlatpakPolicy -flatpak_policy_from_string (const char *string, GError **error) -{ - if (strcmp (string, "none") == 0) - return FLATPAK_POLICY_NONE; - if (strcmp (string, "see") == 0) - return FLATPAK_POLICY_SEE; - if (strcmp (string, "talk") == 0) - return FLATPAK_POLICY_TALK; - if (strcmp (string, "own") == 0) - return FLATPAK_POLICY_OWN; - - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, - "Unknown socket type %s, valid types are: x11,wayland,pulseaudio,session-bus,system-bus\n", string); - return -1; -} - -static const char * -flatpak_policy_to_string (FlatpakPolicy policy) -{ - if (policy == FLATPAK_POLICY_SEE) - return "see"; - if (policy == FLATPAK_POLICY_TALK) - return "talk"; - if (policy == FLATPAK_POLICY_OWN) - return "own"; - - return "none"; -} - -static gboolean -flatpak_verify_dbus_name (const char *name, GError **error) -{ - const char *name_part; - g_autofree char *tmp = NULL; - - if (g_str_has_suffix (name, ".*")) - { - tmp = g_strndup (name, strlen (name) - 2); - name_part = tmp; - } - else - { - name_part = name; - } - - if (g_dbus_is_name (name_part) && !g_dbus_is_unique_name (name_part)) - return TRUE; - - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Invalid dbus name %s\n", name); - return FALSE; -} - -static FlatpakContextSockets -flatpak_context_socket_from_string (const char *string, GError **error) -{ - FlatpakContextSockets sockets = flatpak_context_bitmask_from_string (string, flatpak_context_sockets); - - if (sockets == 0) - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, - "Unknown socket type %s, valid types are: x11,wayland,pulseaudio,session-bus,system-bus\n", string); - return sockets; -} - -static char ** -flatpak_context_sockets_to_string (FlatpakContextSockets sockets, FlatpakContextSockets valid) -{ - return flatpak_context_bitmask_to_string (sockets, valid, flatpak_context_sockets); -} - -static FlatpakContextDevices -flatpak_context_device_from_string (const char *string, GError **error) -{ - FlatpakContextDevices devices = flatpak_context_bitmask_from_string (string, flatpak_context_devices); - - if (devices == 0) - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, - "Unknown device type %s, valid types are: dri\n", string); - return devices; -} - -static char ** -flatpak_context_devices_to_string (FlatpakContextDevices devices, FlatpakContextDevices valid) -{ - return flatpak_context_bitmask_to_string (devices, valid, flatpak_context_devices); -} - -static void -flatpak_context_add_shares (FlatpakContext *context, - FlatpakContextShares shares) -{ - context->shares_valid |= shares; - context->shares |= shares; -} - -static void -flatpak_context_remove_shares (FlatpakContext *context, - FlatpakContextShares shares) -{ - context->shares_valid |= shares; - context->shares &= ~shares; -} - -static void -flatpak_context_add_sockets (FlatpakContext *context, - FlatpakContextSockets sockets) -{ - context->sockets_valid |= sockets; - context->sockets |= sockets; -} - -static void -flatpak_context_remove_sockets (FlatpakContext *context, - FlatpakContextSockets sockets) -{ - context->sockets_valid |= sockets; - context->sockets &= ~sockets; -} - -static void -flatpak_context_add_devices (FlatpakContext *context, - FlatpakContextDevices devices) -{ - context->devices_valid |= devices; - context->devices |= devices; -} - -static void -flatpak_context_remove_devices (FlatpakContext *context, - FlatpakContextDevices devices) -{ - context->devices_valid |= devices; - context->devices &= ~devices; -} - -static void -flatpak_context_set_env_var (FlatpakContext *context, - const char *name, - const char *value) -{ - g_hash_table_insert (context->env_vars, g_strdup (name), g_strdup (value)); -} - -void -flatpak_context_set_session_bus_policy (FlatpakContext *context, - const char *name, - FlatpakPolicy policy) -{ - g_hash_table_insert (context->session_bus_policy, g_strdup (name), GINT_TO_POINTER (policy)); -} - -void -flatpak_context_set_system_bus_policy (FlatpakContext *context, - const char *name, - FlatpakPolicy policy) -{ - g_hash_table_insert (context->system_bus_policy, g_strdup (name), GINT_TO_POINTER (policy)); -} - -static void -flatpak_context_set_persistent (FlatpakContext *context, - const char *path) -{ - g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1)); -} - -static gboolean -get_user_dir_from_string (const char *filesystem, - const char **config_key, - const char **suffix, - const char **dir) -{ - char *slash; - const char *rest; - g_autofree char *prefix; - gsize len; - - slash = strchr (filesystem, '/'); - - if (slash) - len = slash - filesystem; - else - len = strlen (filesystem); - - rest = filesystem + len; - while (*rest == '/') - rest++; - - if (suffix) - *suffix = rest; - - prefix = g_strndup (filesystem, len); - - if (strcmp (prefix, "xdg-desktop") == 0) - { - if (config_key) - *config_key = "XDG_DESKTOP_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); - return TRUE; - } - if (strcmp (prefix, "xdg-documents") == 0) - { - if (config_key) - *config_key = "XDG_DOCUMENTS_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS); - return TRUE; - } - if (strcmp (prefix, "xdg-download") == 0) - { - if (config_key) - *config_key = "XDG_DOWNLOAD_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD); - return TRUE; - } - if (strcmp (prefix, "xdg-music") == 0) - { - if (config_key) - *config_key = "XDG_MUSIC_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC); - return TRUE; - } - if (strcmp (prefix, "xdg-pictures") == 0) - { - if (config_key) - *config_key = "XDG_PICTURES_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); - return TRUE; - } - if (strcmp (prefix, "xdg-public-share") == 0) - { - if (config_key) - *config_key = "XDG_PUBLICSHARE_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE); - return TRUE; - } - if (strcmp (prefix, "xdg-templates") == 0) - { - if (config_key) - *config_key = "XDG_TEMPLATES_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES); - return TRUE; - } - if (strcmp (prefix, "xdg-videos") == 0) - { - if (config_key) - *config_key = "XDG_VIDEOS_DIR"; - if (dir) - *dir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS); - return TRUE; - } - /* Don't support xdg-run without suffix, because that doesn't work */ - if (strcmp (prefix, "xdg-run") == 0 && - *rest != 0) - { - if (config_key) - *config_key = NULL; - if (dir) - *dir = g_get_user_runtime_dir (); - return TRUE; - } - - return FALSE; -} - -static char * -parse_filesystem_flags (const char *filesystem, FlatpakFilesystemMode *mode) -{ - gsize len = strlen (filesystem); - - if (mode) - *mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE; - - if (g_str_has_suffix (filesystem, ":ro")) - { - len -= 3; - if (mode) - *mode = FLATPAK_FILESYSTEM_MODE_READ_ONLY; - } - else if (g_str_has_suffix (filesystem, ":rw")) - { - len -= 3; - if (mode) - *mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE; - } - - return g_strndup (filesystem, len); -} - -static gboolean -flatpak_context_verify_filesystem (const char *filesystem_and_mode, - GError **error) -{ - g_autofree char *filesystem = parse_filesystem_flags (filesystem_and_mode, NULL); - - if (strcmp (filesystem, "host") == 0) - return TRUE; - if (strcmp (filesystem, "home") == 0) - return TRUE; - if (get_user_dir_from_string (filesystem, NULL, NULL, NULL)) - return TRUE; - if (g_str_has_prefix (filesystem, "~/")) - return TRUE; - if (g_str_has_prefix (filesystem, "/")) - return TRUE; - - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, - "Unknown filesystem location %s, valid types are: host,home,xdg-*[/...],~/dir,/dir,\n", filesystem); - return FALSE; -} - -static void -flatpak_context_add_filesystem (FlatpakContext *context, - const char *what) -{ - FlatpakFilesystemMode mode; - char *fs = parse_filesystem_flags (what, &mode); - - g_hash_table_insert (context->filesystems, fs, GINT_TO_POINTER (mode)); -} - -static void -flatpak_context_remove_filesystem (FlatpakContext *context, - const char *what) -{ - g_hash_table_insert (context->filesystems, - parse_filesystem_flags (what, NULL), - NULL); -} - -void -flatpak_context_merge (FlatpakContext *context, - FlatpakContext *other) -{ - GHashTableIter iter; - gpointer key, value; - - context->shares &= ~other->shares_valid; - context->shares |= other->shares; - context->shares_valid |= other->shares_valid; - context->sockets &= ~other->sockets_valid; - context->sockets |= other->sockets; - context->sockets_valid |= other->sockets_valid; - context->devices &= ~other->devices_valid; - context->devices |= other->devices; - context->devices_valid |= other->devices_valid; - - g_hash_table_iter_init (&iter, other->env_vars); - while (g_hash_table_iter_next (&iter, &key, &value)) - g_hash_table_insert (context->env_vars, g_strdup (key), g_strdup (value)); - - g_hash_table_iter_init (&iter, other->persistent); - while (g_hash_table_iter_next (&iter, &key, &value)) - g_hash_table_insert (context->persistent, g_strdup (key), value); - - g_hash_table_iter_init (&iter, other->filesystems); - while (g_hash_table_iter_next (&iter, &key, &value)) - g_hash_table_insert (context->filesystems, g_strdup (key), value); - - g_hash_table_iter_init (&iter, other->session_bus_policy); - while (g_hash_table_iter_next (&iter, &key, &value)) - g_hash_table_insert (context->session_bus_policy, g_strdup (key), value); - - g_hash_table_iter_init (&iter, other->system_bus_policy); - while (g_hash_table_iter_next (&iter, &key, &value)) - g_hash_table_insert (context->system_bus_policy, g_strdup (key), value); -} - -static gboolean -option_share_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - FlatpakContextShares share; - - share = flatpak_context_share_from_string (value, error); - if (share == 0) - return FALSE; - - flatpak_context_add_shares (context, share); - - return TRUE; -} - -static gboolean -option_unshare_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - FlatpakContextShares share; - - share = flatpak_context_share_from_string (value, error); - if (share == 0) - return FALSE; - - flatpak_context_remove_shares (context, share); - - return TRUE; -} - -static gboolean -option_socket_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - FlatpakContextSockets socket; - - socket = flatpak_context_socket_from_string (value, error); - if (socket == 0) - return FALSE; - - flatpak_context_add_sockets (context, socket); - - return TRUE; -} - -static gboolean -option_nosocket_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - FlatpakContextSockets socket; - - socket = flatpak_context_socket_from_string (value, error); - if (socket == 0) - return FALSE; - - flatpak_context_remove_sockets (context, socket); - - return TRUE; -} - -static gboolean -option_device_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - FlatpakContextDevices device; - - device = flatpak_context_device_from_string (value, error); - if (device == 0) - return FALSE; - - flatpak_context_add_devices (context, device); - - return TRUE; -} - -static gboolean -option_nodevice_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - FlatpakContextDevices device; - - device = flatpak_context_device_from_string (value, error); - if (device == 0) - return FALSE; - - flatpak_context_remove_devices (context, device); - - return TRUE; -} - -static gboolean -option_filesystem_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - if (!flatpak_context_verify_filesystem (value, error)) - return FALSE; - - flatpak_context_add_filesystem (context, value); - return TRUE; -} - -static gboolean -option_nofilesystem_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - if (!flatpak_context_verify_filesystem (value, error)) - return FALSE; - - flatpak_context_remove_filesystem (context, value); - return TRUE; -} - -static gboolean -option_env_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - g_auto(GStrv) split = g_strsplit (value, "=", 2); - - if (split == NULL || split[0] == NULL || split[0][0] == 0 || split[1] == NULL) - { - g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "Invalid env format %s", value); - return FALSE; - } - - flatpak_context_set_env_var (context, split[0], split[1]); - return TRUE; -} - -static gboolean -option_own_name_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - if (!flatpak_verify_dbus_name (value, error)) - return FALSE; - - flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_OWN); - return TRUE; -} - -static gboolean -option_talk_name_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - if (!flatpak_verify_dbus_name (value, error)) - return FALSE; - - flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_TALK); - return TRUE; -} - -static gboolean -option_system_own_name_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - if (!flatpak_verify_dbus_name (value, error)) - return FALSE; - - flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_OWN); - return TRUE; -} - -static gboolean -option_system_talk_name_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - if (!flatpak_verify_dbus_name (value, error)) - return FALSE; - - flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_TALK); - return TRUE; -} - -static gboolean -option_persist_cb (const gchar *option_name, - const gchar *value, - gpointer data, - GError **error) -{ - FlatpakContext *context = data; - - flatpak_context_set_persistent (context, value); - return TRUE; -} - -static GOptionEntry context_options[] = { - { "share", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_share_cb, "Share with host", "SHARE" }, - { "unshare", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_unshare_cb, "Unshare with host", "SHARE" }, - { "socket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_socket_cb, "Expose socket to app", "SOCKET" }, - { "nosocket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nosocket_cb, "Don't expose socket to app", "SOCKET" }, - { "device", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_device_cb, "Expose device to app", "DEVICE" }, - { "nodevice", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nodevice_cb, "Don't expose device to app", "DEVICE" }, - { "filesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_filesystem_cb, "Expose filesystem to app (:ro for read-only)", "FILESYSTEM[:ro]" }, - { "nofilesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nofilesystem_cb, "Don't expose filesystem to app", "FILESYSTEM" }, - { "env", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_cb, "Set environment variable", "VAR=VALUE" }, - { "own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_own_name_cb, "Allow app to own name on the session bus", "DBUS_NAME" }, - { "talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_talk_name_cb, "Allow app to talk to name on the session bus", "DBUS_NAME" }, - { "system-own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_own_name_cb, "Allow app to own name on the system bus", "DBUS_NAME" }, - { "system-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_talk_name_cb, "Allow app to talk to name on the system bus", "DBUS_NAME" }, - { "persist", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_persist_cb, "Persist home directory directory", "FILENAME" }, - { NULL } -}; - -GOptionGroup * -flatpak_context_get_options (FlatpakContext *context) -{ - GOptionGroup *group; - - group = g_option_group_new ("environment", - "Runtime Environment", - "Runtime Environment", - context, - NULL); - - g_option_group_add_entries (group, context_options); - - return group; -} - -static const char * -parse_negated (const char *option, gboolean *negated) -{ - if (option[0] == '!') - { - option++; - *negated = TRUE; - } - else - { - *negated = FALSE; - } - return option; -} - -/* This is a merge, not a replace */ -gboolean -flatpak_context_load_metadata (FlatpakContext *context, - GKeyFile *metakey, - GError **error) -{ - gboolean remove; - int i; - - if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SHARED, NULL)) - { - g_auto(GStrv) shares = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_SHARED, NULL, error); - if (shares == NULL) - return FALSE; - - for (i = 0; shares[i] != NULL; i++) - { - FlatpakContextShares share; - - share = flatpak_context_share_from_string (parse_negated (shares[i], &remove), error); - if (share == 0) - return FALSE; - if (remove) - flatpak_context_remove_shares (context, share); - else - flatpak_context_add_shares (context, share); - } - } - - if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SOCKETS, NULL)) - { - g_auto(GStrv) sockets = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_SOCKETS, NULL, error); - if (sockets == NULL) - return FALSE; - - for (i = 0; sockets[i] != NULL; i++) - { - FlatpakContextSockets socket = flatpak_context_socket_from_string (parse_negated (sockets[i], &remove), error); - if (socket == 0) - return FALSE; - if (remove) - flatpak_context_remove_sockets (context, socket); - else - flatpak_context_add_sockets (context, socket); - } - } - - if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_DEVICES, NULL)) - { - g_auto(GStrv) devices = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_DEVICES, NULL, error); - if (devices == NULL) - return FALSE; - - - for (i = 0; devices[i] != NULL; i++) - { - FlatpakContextDevices device = flatpak_context_device_from_string (parse_negated (devices[i], &remove), error); - if (device == 0) - return FALSE; - if (remove) - flatpak_context_remove_devices (context, device); - else - flatpak_context_add_devices (context, device); - } - } - - if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_FILESYSTEMS, NULL)) - { - g_auto(GStrv) filesystems = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_FILESYSTEMS, NULL, error); - if (filesystems == NULL) - return FALSE; - - for (i = 0; filesystems[i] != NULL; i++) - { - const char *fs = parse_negated (filesystems[i], &remove); - if (!flatpak_context_verify_filesystem (fs, error)) - return FALSE; - if (remove) - flatpak_context_remove_filesystem (context, fs); - else - flatpak_context_add_filesystem (context, fs); - } - } - - if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_PERSISTENT, NULL)) - { - g_auto(GStrv) persistent = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_PERSISTENT, NULL, error); - if (persistent == NULL) - return FALSE; - - for (i = 0; persistent[i] != NULL; i++) - flatpak_context_set_persistent (context, persistent[i]); - } - - if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY)) - { - g_auto(GStrv) keys = NULL; - gsize i, keys_count; - - keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, &keys_count, NULL); - for (i = 0; i < keys_count; i++) - { - const char *key = keys[i]; - g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, key, NULL); - FlatpakPolicy policy; - - if (!flatpak_verify_dbus_name (key, error)) - return FALSE; - - policy = flatpak_policy_from_string (value, error); - if ((int) policy == -1) - return FALSE; - - flatpak_context_set_session_bus_policy (context, key, policy); - } - } - - if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY)) - { - g_auto(GStrv) keys = NULL; - gsize i, keys_count; - - keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, &keys_count, NULL); - for (i = 0; i < keys_count; i++) - { - const char *key = keys[i]; - g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, key, NULL); - FlatpakPolicy policy; - - if (!flatpak_verify_dbus_name (key, error)) - return FALSE; - - policy = flatpak_policy_from_string (value, error); - if ((int) policy == -1) - return FALSE; - - flatpak_context_set_system_bus_policy (context, key, policy); - } - } - - if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT)) - { - g_auto(GStrv) keys = NULL; - gsize i, keys_count; - - keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, &keys_count, NULL); - for (i = 0; i < keys_count; i++) - { - const char *key = keys[i]; - g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, key, NULL); - - flatpak_context_set_env_var (context, key, value); - } - } - - return TRUE; -} - -void -flatpak_context_save_metadata (FlatpakContext *context, - GKeyFile *metakey) -{ - g_auto(GStrv) shared = flatpak_context_shared_to_string (context->shares, context->shares_valid); - g_auto(GStrv) sockets = flatpak_context_sockets_to_string (context->sockets, context->sockets_valid); - g_auto(GStrv) devices = flatpak_context_devices_to_string (context->devices, context->devices_valid); - GHashTableIter iter; - gpointer key, value; - - if (shared[0] != NULL) - { - g_key_file_set_string_list (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_SHARED, - (const char * const *) shared, g_strv_length (shared)); - } - else - { - g_key_file_remove_key (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_SHARED, - NULL); - } - - if (sockets[0] != NULL) - { - g_key_file_set_string_list (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_SOCKETS, - (const char * const *) sockets, g_strv_length (sockets)); - } - else - { - g_key_file_remove_key (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_SOCKETS, - NULL); - } - - if (devices[0] != NULL) - { - g_key_file_set_string_list (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_DEVICES, - (const char * const *) devices, g_strv_length (devices)); - } - else - { - g_key_file_remove_key (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_DEVICES, - NULL); - } - - if (g_hash_table_size (context->filesystems) > 0) - { - g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func (g_free); - - g_hash_table_iter_init (&iter, context->filesystems); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); - - if (mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY) - g_ptr_array_add (array, g_strconcat (key, ":ro", NULL)); - else if (value != NULL) - g_ptr_array_add (array, g_strdup (key)); - } - - g_key_file_set_string_list (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_FILESYSTEMS, - (const char * const *) array->pdata, array->len); - } - else - { - g_key_file_remove_key (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_FILESYSTEMS, - NULL); - } - - if (g_hash_table_size (context->persistent) > 0) - { - g_autofree char **keys = (char **) g_hash_table_get_keys_as_array (context->persistent, NULL); - - g_key_file_set_string_list (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_PERSISTENT, - (const char * const *) keys, g_strv_length (keys)); - } - else - { - g_key_file_remove_key (metakey, - FLATPAK_METADATA_GROUP_CONTEXT, - FLATPAK_METADATA_KEY_PERSISTENT, - NULL); - } - - g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, NULL); - g_hash_table_iter_init (&iter, context->session_bus_policy); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - FlatpakPolicy policy = GPOINTER_TO_INT (value); - if (policy > 0) - g_key_file_set_string (metakey, - FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, - (char *) key, flatpak_policy_to_string (policy)); - } - - g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, NULL); - g_hash_table_iter_init (&iter, context->system_bus_policy); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - FlatpakPolicy policy = GPOINTER_TO_INT (value); - if (policy > 0) - g_key_file_set_string (metakey, - FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, - (char *) key, flatpak_policy_to_string (policy)); - } - - g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, NULL); - g_hash_table_iter_init (&iter, context->env_vars); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - g_key_file_set_string (metakey, - FLATPAK_METADATA_GROUP_ENVIRONMENT, - (char *) key, (char *) value); - } -} - -void -flatpak_context_allow_host_fs (FlatpakContext *context) -{ - flatpak_context_add_filesystem (context, "host"); -} - -static char * -extract_unix_path_from_dbus_address (const char *address) -{ - const char *path, *path_end; - - if (address == NULL) - return NULL; - - if (!g_str_has_prefix (address, "unix:")) - return NULL; - - path = strstr (address, "path="); - if (path == NULL) - return NULL; - path += strlen ("path="); - path_end = path; - while (*path_end != 0 && *path_end != ',') - path_end++; - - return g_strndup (path, path_end - path); -} - -#ifdef ENABLE_XAUTH -static gboolean -auth_streq (char *str, - char *au_str, - int au_len) -{ - return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0; -} - -static void -write_xauth (char *number, FILE *output) -{ - Xauth *xa, local_xa; - char *filename; - FILE *f; - struct utsname unames; - - if (uname (&unames)) - { - g_warning ("uname failed"); - return; - } - - filename = XauFileName (); - f = fopen (filename, "rb"); - if (f == NULL) - return; - - while (TRUE) - { - xa = XauReadAuth (f); - if (xa == NULL) - break; - if (xa->family == FamilyLocal && - auth_streq (unames.nodename, xa->address, xa->address_length) && - (xa->number == NULL || auth_streq (number, xa->number, xa->number_length))) - { - local_xa = *xa; - if (local_xa.number) - { - local_xa.number = "99"; - local_xa.number_length = 2; - } - - if (!XauWriteAuth (output, &local_xa)) - g_warning ("xauth write error"); - } - - XauDisposeAuth (xa); - } - - fclose (f); -} -#endif /* ENABLE_XAUTH */ - -static void -add_args (GPtrArray *argv_array, ...) -{ - va_list args; - const gchar *arg; - - va_start (args, argv_array); - while ((arg = va_arg (args, const gchar *))) - g_ptr_array_add (argv_array, g_strdup (arg)); - va_end (args); -} - -static int -create_tmp_fd (const char *contents, - gssize length, - GError **error) -{ - char template[] = "/tmp/tmp_fd_XXXXXX"; - int fd; - - if (length < 0) - length = strlen (contents); - - fd = g_mkstemp (template); - if (fd < 0) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to create temporary file"); - return -1; - } - - if (unlink (template) != 0) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to unlink temporary file"); - close (fd); - return -1; - } - - while (length > 0) - { - gssize s; - - s = write (fd, contents, length); - - if (s < 0) - { - int saved_errno = errno; - if (saved_errno == EINTR) - continue; - - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno), "Failed to write to temporary file"); - close (fd); - return -1; - } - - g_assert (s <= length); - - contents += s; - length -= s; - } - - lseek (fd, 0, SEEK_SET); - - return fd; -} - -static void -flatpak_run_add_x11_args (GPtrArray *argv_array, - char ***envp_p) -{ - char *x11_socket = NULL; - const char *display = g_getenv ("DISPLAY"); - - if (display && display[0] == ':' && g_ascii_isdigit (display[1])) - { - const char *display_nr = &display[1]; - const char *display_nr_end = display_nr; - g_autofree char *d = NULL; - g_autofree char *tmp_path = NULL; - - while (g_ascii_isdigit (*display_nr_end)) - display_nr_end++; - - d = g_strndup (display_nr, display_nr_end - display_nr); - x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", d); - - add_args (argv_array, - "--bind", x11_socket, "/tmp/.X11-unix/X99", - NULL); - *envp_p = g_environ_setenv (*envp_p, "DISPLAY", ":99.0", TRUE); - -#ifdef ENABLE_XAUTH - int fd; - fd = g_file_open_tmp ("xdg-app-xauth-XXXXXX", &tmp_path, NULL); - if (fd >= 0) - { - FILE *output = fdopen (fd, "wb"); - if (output != NULL) - { - int tmp_fd = dup (fd); - if (tmp_fd != -1) - { - g_autofree char *tmp_fd_str = g_strdup_printf ("%d", tmp_fd); - g_autofree char *dest = g_strdup_printf ("/run/user/%d/Xauthority", getuid ()); - - write_xauth (d, output); - add_args (argv_array, - "--bind-data", tmp_fd_str, dest, - NULL); - *envp_p = g_environ_setenv (*envp_p, "XAUTHORITY", dest, TRUE); - } - - fclose (output); - unlink (tmp_path); - - lseek (tmp_fd, 0, SEEK_SET); - } - else - { - close (fd); - } - } -#endif - } - else - { - *envp_p = g_environ_unsetenv (*envp_p, "DISPLAY"); - } - -} - -static void -flatpak_run_add_wayland_args (GPtrArray *argv_array, - char ***envp_p) -{ - g_autofree char *wayland_socket = g_build_filename (g_get_user_runtime_dir (), "wayland-0", NULL); - g_autofree char *sandbox_wayland_socket = g_strdup_printf ("/run/user/%d/wayland-0", getuid ()); - - if (g_file_test (wayland_socket, G_FILE_TEST_EXISTS)) - { - add_args (argv_array, - "--bind", wayland_socket, sandbox_wayland_socket, - NULL); - } -} - -static void -flatpak_run_add_pulseaudio_args (GPtrArray *argv_array, - char ***envp_p) -{ - char *pulseaudio_socket = g_build_filename (g_get_user_runtime_dir (), "pulse/native", NULL); - - *envp_p = g_environ_unsetenv (*envp_p, "PULSE_SERVER"); - if (g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS)) - { - gboolean share_shm = FALSE; /* TODO: When do we add this? */ - g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no"); - g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/pulse/native", getuid ()); - g_autofree char *pulse_server = g_strdup_printf ("unix:/run/user/%d/pulse/native", getuid ()); - g_autofree char *config_path = g_strdup_printf ("/run/user/%d/pulse/config", getuid ()); - int fd; - g_autofree char *fd_str = NULL; - - fd = create_tmp_fd (client_config, -1, NULL); - if (fd == -1) - return; - - fd_str = g_strdup_printf ("%d", fd); - - add_args (argv_array, - "--bind", pulseaudio_socket, sandbox_socket_path, - "--bind-data", fd_str, config_path, - NULL); - - *envp_p = g_environ_setenv (*envp_p, "PULSE_SERVER", pulse_server, TRUE); - *envp_p = g_environ_setenv (*envp_p, "PULSE_CLIENTCONFIG", config_path, TRUE); - } -} - -static char * -create_proxy_socket (char *template) -{ - g_autofree char *dir = g_build_filename (g_get_user_runtime_dir (), "bus-proxy", NULL); - g_autofree char *proxy_socket = g_build_filename (dir, template, NULL); - int fd; - - if (mkdir (dir, 0700) == -1 && errno != EEXIST) - return NULL; - - fd = g_mkstemp (proxy_socket); - if (fd == -1) - return NULL; - - close (fd); - - return g_steal_pointer (&proxy_socket); -} - -gboolean -flatpak_run_add_system_dbus_args (FlatpakContext *context, - char ***envp_p, - GPtrArray *argv_array, - GPtrArray *dbus_proxy_argv, - gboolean unrestricted) -{ - const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS"); - g_autofree char *real_dbus_address = NULL; - char *dbus_system_socket = NULL; - - if (dbus_address != NULL) - dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address); - else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS)) - dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket"); - - if (dbus_system_socket != NULL && unrestricted) - { - add_args (argv_array, - "--bind", dbus_system_socket, "/run/dbus/system_bus_socket", - NULL); - *envp_p = g_environ_setenv (*envp_p, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE); - - return TRUE; - } - else if (dbus_proxy_argv && - g_hash_table_size (context->system_bus_policy) > 0) - { - g_autofree char *proxy_socket = create_proxy_socket ("system-bus-proxy-XXXXXX"); - - if (proxy_socket == NULL) - return FALSE; - - if (dbus_address) - real_dbus_address = g_strdup (dbus_address); - else - real_dbus_address = g_strdup_printf ("unix:path=%s", dbus_system_socket); - - g_ptr_array_add (dbus_proxy_argv, g_strdup (real_dbus_address)); - g_ptr_array_add (dbus_proxy_argv, g_strdup (proxy_socket)); - - - add_args (argv_array, - "--bind", proxy_socket, "/run/dbus/system_bus_socket", - NULL); - *envp_p = g_environ_setenv (*envp_p, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE); - - return TRUE; - } - return FALSE; -} - -gboolean -flatpak_run_add_session_dbus_args (GPtrArray *argv_array, - char ***envp_p, - GPtrArray *dbus_proxy_argv, - gboolean unrestricted) -{ - const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS"); - char *dbus_session_socket = NULL; - g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/bus", getuid ()); - g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/bus", getuid ()); - - if (dbus_address == NULL) - return FALSE; - - dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address); - if (dbus_session_socket != NULL && unrestricted) - { - - add_args (argv_array, - "--bind", dbus_session_socket, sandbox_socket_path, - NULL); - *envp_p = g_environ_setenv (*envp_p, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE); - - return TRUE; - } - else if (dbus_proxy_argv && dbus_address != NULL) - { - g_autofree char *proxy_socket = create_proxy_socket ("session-bus-proxy-XXXXXX"); - - if (proxy_socket == NULL) - return FALSE; - - g_ptr_array_add (dbus_proxy_argv, g_strdup (dbus_address)); - g_ptr_array_add (dbus_proxy_argv, g_strdup (proxy_socket)); - - add_args (argv_array, - "--bind", proxy_socket, sandbox_socket_path, - NULL); - *envp_p = g_environ_setenv (*envp_p, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE); - - return TRUE; - } - - return FALSE; -} - -static void -flatpak_add_bus_filters (GPtrArray *dbus_proxy_argv, - GHashTable *ht, - const char *app_id, - FlatpakContext *context) -{ - GHashTableIter iter; - gpointer key, value; - - g_ptr_array_add (dbus_proxy_argv, g_strdup ("--filter")); - if (app_id) - { - g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--own=%s", app_id)); - g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--own=%s.*", app_id)); - } - - g_hash_table_iter_init (&iter, ht); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - FlatpakPolicy policy = GPOINTER_TO_INT (value); - - if (policy > 0) - g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--%s=%s", flatpak_policy_to_string (policy), (char *) key)); - } -} - -gboolean -flatpak_run_add_extension_args (GPtrArray *argv_array, - GKeyFile *metakey, - const char *full_ref, - GCancellable *cancellable, - GError **error) -{ - g_auto(GStrv) parts = NULL; - gboolean is_app; - GList *extensions, *l; - - parts = g_strsplit (full_ref, "/", 0); - if (g_strv_length (parts) != 4) - return flatpak_fail (error, "Failed to determine parts from ref: %s", full_ref); - - is_app = strcmp (parts[0], "app") == 0; - - extensions = flatpak_list_extensions (metakey, - parts[2], parts[3]); - - for (l = extensions; l != NULL; l = l->next) - { - FlatpakExtension *ext = l->data; - g_autoptr(GFile) deploy = NULL; - - deploy = flatpak_find_deploy_dir_for_ref (ext->ref, cancellable, NULL); - if (deploy != NULL) - { - g_autoptr(GFile) files = g_file_get_child (deploy, "files"); - g_autofree char *full_directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL); - g_autofree char *ref = g_build_filename (full_directory, ".ref", NULL); - - add_args (argv_array, - "--bind", gs_file_get_path_cached (files), full_directory, - "--lock-file", ref, - NULL); - } - } - - g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free); - - return TRUE; -} - -static void -add_file_arg (GPtrArray *argv_array, - FlatpakFilesystemMode mode, - const char *path) -{ - struct stat st; - - if (stat (path, &st) != 0) - return; - - if (S_ISDIR (st.st_mode) || - S_ISREG (st.st_mode)) - { - add_args (argv_array, - (mode == FLATPAK_FILESYSTEM_MODE_READ_WRITE) ? "--bind" : "--ro-bind", - path, path, NULL); - } -} - -void -flatpak_run_add_environment_args (GPtrArray *argv_array, - char ***envp_p, - GPtrArray *session_bus_proxy_argv, - GPtrArray *system_bus_proxy_argv, - const char *app_id, - FlatpakContext *context, - GFile *app_id_dir) -{ - GHashTableIter iter; - gpointer key, value; - gboolean unrestricted_session_bus; - gboolean unrestricted_system_bus; - gboolean home_access = FALSE; - GString *xdg_dirs_conf = NULL; - FlatpakFilesystemMode fs_mode, home_mode; - - if ((context->shares & FLATPAK_CONTEXT_SHARED_IPC) == 0) - { - g_debug ("Disallowing ipc access"); - add_args (argv_array, "--unshare-ipc", NULL); - } - - if ((context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0) - { - g_debug ("Disallowing network access"); - add_args (argv_array, "--unshare-net", NULL); - } - - if (context->devices & FLATPAK_CONTEXT_DEVICE_DRI) - { - g_debug ("Allowing dri access"); - if (g_file_test ("/dev/dri", G_FILE_TEST_IS_DIR)) - add_args (argv_array, "--dev-bind", "/dev/dri", "/dev/dri", NULL); - if (g_file_test ("/dev/nvidiactl", G_FILE_TEST_EXISTS)) - { - add_args (argv_array, - "--dev-bind", "/dev/nvidiactl", "/dev/nvidiactl", - "--dev-bind", "/dev/nvidia0", "/dev/nvidia0", - NULL); - } - } - - fs_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "host"); - if (fs_mode != 0) - { - DIR *dir; - struct dirent *dirent; - - g_debug ("Allowing host-fs access"); - home_access = TRUE; - - /* Bind mount most dirs in / into the new root */ - dir = opendir ("/"); - if (dir != NULL) - { - while ((dirent = readdir (dir))) - { - g_autofree char *path = NULL; - - if (g_strv_contains (dont_mount_in_root, dirent->d_name)) - continue; - - path = g_build_filename ("/", dirent->d_name, NULL); - add_file_arg (argv_array, fs_mode, path); - } - } - add_file_arg (argv_array, fs_mode, "/run/media"); - } - - home_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "home"); - if (home_mode != 0) - { - g_debug ("Allowing homedir access"); - home_access = TRUE; - - add_file_arg (argv_array, MAX (home_mode, fs_mode), g_get_home_dir ()); - } - - if (!home_access) - { - /* Enable persistant mapping only if no access to real home dir */ - - g_hash_table_iter_init (&iter, context->persistent); - while (g_hash_table_iter_next (&iter, &key, NULL)) - { - const char *persist = key; - g_autofree char *src = g_build_filename (g_get_home_dir (), ".var/app", app_id, persist, NULL); - g_autofree char *dest = g_build_filename (g_get_home_dir (), persist, NULL); - - g_mkdir_with_parents (src, 0755); - - add_args (argv_array, - "--bind", src, dest, - NULL); - } - } - - g_hash_table_iter_init (&iter, context->filesystems); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - const char *filesystem = key; - FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); - - if (value == NULL || - strcmp (filesystem, "host") == 0 || - strcmp (filesystem, "home") == 0) - continue; - - if (g_str_has_prefix (filesystem, "xdg-")) - { - const char *path, *rest = NULL; - const char *config_key = NULL; - g_autofree char *subpath = NULL; - - if (!get_user_dir_from_string (filesystem, &config_key, &rest, &path)) - { - g_warning ("Unsupported xdg dir %s\n", filesystem); - continue; - } - - if (strcmp (path, g_get_home_dir ()) == 0) - { - /* xdg-user-dirs sets disabled dirs to $HOME, and its in general not a good - idea to set full access to $HOME other than explicitly, so we ignore - these */ - g_debug ("Xdg dir %s is $HOME (i.e. disabled), ignoring\n", filesystem); - continue; - } - - subpath = g_build_filename (path, rest, NULL); - if (g_file_test (subpath, G_FILE_TEST_EXISTS)) - { - if (xdg_dirs_conf == NULL) - xdg_dirs_conf = g_string_new (""); - - if (config_key) - g_string_append_printf (xdg_dirs_conf, "%s=\"%s\"\n", - config_key, path); - - add_file_arg (argv_array, mode, subpath); - } - } - else if (g_str_has_prefix (filesystem, "~/")) - { - g_autofree char *path = NULL; - - path = g_build_filename (g_get_home_dir (), filesystem + 2, NULL); - if (g_file_test (path, G_FILE_TEST_EXISTS)) - add_file_arg (argv_array, mode, path); - } - else if (g_str_has_prefix (filesystem, "/")) - { - if (g_file_test (filesystem, G_FILE_TEST_EXISTS)) - add_file_arg (argv_array, mode, filesystem); - } - else - { - g_warning ("Unexpected filesystem arg %s\n", filesystem); - } - } - - /* Do this after setting up everything in the home dir, so its not overwritten */ - if (app_id_dir) - add_args (argv_array, - "--bind", gs_file_get_path_cached (app_id_dir), gs_file_get_path_cached (app_id_dir), - NULL); - - if (home_access && app_id_dir != NULL) - { - g_autofree char *src_path = g_build_filename (g_get_user_config_dir (), - "user-dirs.dirs", - NULL); - g_autofree char *path = g_build_filename (gs_file_get_path_cached (app_id_dir), - "config/user-dirs.dirs", NULL); - add_args (argv_array, - "--ro-bind", src_path, path, - NULL); - } - else if (xdg_dirs_conf != NULL && app_id_dir != NULL) - { - g_autofree char *tmp_path = NULL; - g_autofree char *path = NULL; - int fd; - - fd = g_file_open_tmp ("xdg-app-user-dir-XXXXXX.dirs", &tmp_path, NULL); - if (fd >= 0) - { - close (fd); - if (g_file_set_contents (tmp_path, xdg_dirs_conf->str, xdg_dirs_conf->len, NULL)) - { - int tmp_fd = open (tmp_path, O_RDONLY); - unlink (tmp_path); - if (tmp_fd) - { - g_autofree char *tmp_fd_str = g_strdup_printf ("%d", tmp_fd); - path = g_build_filename (gs_file_get_path_cached (app_id_dir), - "config/user-dirs.dirs", NULL); - - add_args (argv_array, "--file", tmp_fd_str, path, NULL); - } - } - } - g_string_free (xdg_dirs_conf, TRUE); - } - - if (context->sockets & FLATPAK_CONTEXT_SOCKET_X11) - { - g_debug ("Allowing x11 access"); - flatpak_run_add_x11_args (argv_array, envp_p); - } - - if (context->sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND) - { - g_debug ("Allowing wayland access"); - flatpak_run_add_wayland_args (argv_array, envp_p); - } - - if (context->sockets & FLATPAK_CONTEXT_SOCKET_PULSEAUDIO) - { - g_debug ("Allowing pulseaudio access"); - flatpak_run_add_pulseaudio_args (argv_array, envp_p); - } - - unrestricted_session_bus = (context->sockets & FLATPAK_CONTEXT_SOCKET_SESSION_BUS) != 0; - if (unrestricted_session_bus) - g_debug ("Allowing session-dbus access"); - if (flatpak_run_add_session_dbus_args (argv_array, envp_p, session_bus_proxy_argv, unrestricted_session_bus) && - !unrestricted_session_bus && session_bus_proxy_argv) - flatpak_add_bus_filters (session_bus_proxy_argv, context->session_bus_policy, app_id, context); - - unrestricted_system_bus = (context->sockets & FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS) != 0; - if (unrestricted_system_bus) - g_debug ("Allowing system-dbus access"); - if (flatpak_run_add_system_dbus_args (context, envp_p, argv_array, system_bus_proxy_argv, - unrestricted_system_bus) && - !unrestricted_system_bus && system_bus_proxy_argv) - flatpak_add_bus_filters (system_bus_proxy_argv, context->system_bus_policy, NULL, context); - -} - -static const struct {const char *env; - const char *val; -} default_exports[] = { - {"PATH", "/app/bin:/usr/bin"}, - {"LD_LIBRARY_PATH", "/app/lib"}, - {"XDG_CONFIG_DIRS", "/app/etc/xdg:/etc/xdg"}, - {"XDG_DATA_DIRS", "/app/share:/usr/share"}, - {"SHELL", "/bin/sh"}, -}; - -static const struct {const char *env; - const char *val; -} devel_exports[] = { - {"ACLOCAL_PATH", "/app/share/aclocal"}, - {"C_INCLUDE_PATH", "/app/include"}, - {"CPLUS_INCLUDE_PATH", "/app/include"}, - {"LDFLAGS", "-L/app/lib "}, - {"PKG_CONFIG_PATH", "/app/lib/pkgconfig:/app/share/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"}, - {"LC_ALL", "en_US.utf8"}, -}; - -char ** -flatpak_run_get_minimal_env (gboolean devel) -{ - GPtrArray *env_array; - static const char * const copy[] = { - "PWD", - "GDMSESSION", - "XDG_CURRENT_DESKTOP", - "XDG_SESSION_DESKTOP", - "DESKTOP_SESSION", - "EMAIL_ADDRESS", - "HOME", - "HOSTNAME", - "LOGNAME", - "REAL_NAME", - "TERM", - "USER", - "USERNAME", - }; - static const char * const copy_nodevel[] = { - "LANG", - "LANGUAGE", - "LC_ALL", - "LC_ADDRESS", - "LC_COLLATE", - "LC_CTYPE", - "LC_IDENTIFICATION", - "LC_MEASUREMENT", - "LC_MESSAGES", - "LC_MONETARY", - "LC_NAME", - "LC_NUMERIC", - "LC_PAPER", - "LC_TELEPHONE", - "LC_TIME", - }; - int i; - - env_array = g_ptr_array_new_with_free_func (g_free); - - for (i = 0; i < G_N_ELEMENTS (default_exports); i++) - g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", default_exports[i].env, default_exports[i].val)); - - if (devel) - { - for (i = 0; i < G_N_ELEMENTS(devel_exports); i++) - g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", devel_exports[i].env, devel_exports[i].val)); - } - - for (i = 0; i < G_N_ELEMENTS (copy); i++) - { - const char *current = g_getenv (copy[i]); - if (current) - g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy[i], current)); - } - - if (!devel) - { - for (i = 0; i < G_N_ELEMENTS (copy_nodevel); i++) - { - const char *current = g_getenv (copy_nodevel[i]); - if (current) - g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy_nodevel[i], current)); - } - } - - g_ptr_array_add (env_array, NULL); - return (char **) g_ptr_array_free (env_array, FALSE); -} - -char ** -flatpak_run_apply_env_default (char **envp) -{ - int i; - - for (i = 0; i < G_N_ELEMENTS (default_exports); i++) - envp = g_environ_setenv (envp, default_exports[i].env, default_exports[i].val, TRUE); - - return envp; -} - -char ** -flatpak_run_apply_env_appid (char **envp, - GFile *app_dir) -{ - g_autoptr(GFile) app_dir_data = NULL; - g_autoptr(GFile) app_dir_config = NULL; - g_autoptr(GFile) app_dir_cache = NULL; - - app_dir_data = g_file_get_child (app_dir, "data"); - app_dir_config = g_file_get_child (app_dir, "config"); - app_dir_cache = g_file_get_child (app_dir, "cache"); - envp = g_environ_setenv (envp, "XDG_DATA_HOME", gs_file_get_path_cached (app_dir_data), TRUE); - envp = g_environ_setenv (envp, "XDG_CONFIG_HOME", gs_file_get_path_cached (app_dir_config), TRUE); - envp = g_environ_setenv (envp, "XDG_CACHE_HOME", gs_file_get_path_cached (app_dir_cache), TRUE); - - return envp; -} - -char ** -flatpak_run_apply_env_vars (char **envp, FlatpakContext *context) -{ - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init (&iter, context->env_vars); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - const char *var = key; - const char *val = value; - - if (val && val[0] != 0) - envp = g_environ_setenv (envp, var, val, TRUE); - else - envp = g_environ_unsetenv (envp, var); - } - - return envp; -} - -GFile * -flatpak_get_data_dir (const char *app_id) -{ - g_autoptr(GFile) home = g_file_new_for_path (g_get_home_dir ()); - g_autoptr(GFile) var_app = g_file_resolve_relative_path (home, ".var/app"); - - return g_file_get_child (var_app, app_id); -} - -GFile * -flatpak_ensure_data_dir (const char *app_id, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) dir = flatpak_get_data_dir (app_id); - g_autoptr(GFile) data_dir = g_file_get_child (dir, "data"); - g_autoptr(GFile) cache_dir = g_file_get_child (dir, "cache"); - g_autoptr(GFile) config_dir = g_file_get_child (dir, "config"); - - if (!gs_file_ensure_directory (data_dir, TRUE, cancellable, error)) - return NULL; - - if (!gs_file_ensure_directory (cache_dir, TRUE, cancellable, error)) - return NULL; - - if (!gs_file_ensure_directory (config_dir, TRUE, cancellable, error)) - return NULL; - - return g_object_ref (dir); -} - -struct JobData -{ - char *job; - GMainLoop *main_loop; -}; - -static void -job_removed_cb (SystemdManager *manager, - guint32 id, - char *job, - char *unit, - char *result, - struct JobData *data) -{ - if (strcmp (job, data->job) == 0) - g_main_loop_quit (data->main_loop); -} - -gboolean -flatpak_run_in_transient_unit (const char *appid, GError **error) -{ - g_autoptr(GDBusConnection) conn = NULL; - g_autofree char *path = NULL; - g_autofree char *address = NULL; - g_autofree char *name = NULL; - g_autofree char *job = NULL; - SystemdManager *manager = NULL; - GVariantBuilder builder; - GVariant *properties = NULL; - GVariant *aux = NULL; - guint32 pid; - GMainContext *main_context = NULL; - GMainLoop *main_loop = NULL; - struct JobData data; - gboolean res = FALSE; - - path = g_strdup_printf ("/run/user/%d/systemd/private", getuid ()); - - if (!g_file_test (path, G_FILE_TEST_EXISTS)) - return flatpak_fail (error, - "No systemd user session available, sandboxing not available"); - - main_context = g_main_context_new (); - main_loop = g_main_loop_new (main_context, FALSE); - - g_main_context_push_thread_default (main_context); - - address = g_strconcat ("unix:path=", path, NULL); - - conn = g_dbus_connection_new_for_address_sync (address, - G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, - NULL, - NULL, error); - if (!conn) - goto out; - - manager = systemd_manager_proxy_new_sync (conn, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, - NULL, - "/org/freedesktop/systemd1", - NULL, error); - if (!manager) - goto out; - - name = g_strdup_printf ("xdg-app-%s-%d.scope", appid, getpid ()); - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sv)")); - - pid = getpid (); - g_variant_builder_add (&builder, "(sv)", - "PIDs", - g_variant_new_fixed_array (G_VARIANT_TYPE ("u"), - &pid, 1, sizeof (guint32)) - ); - - properties = g_variant_builder_end (&builder); - - aux = g_variant_new_array (G_VARIANT_TYPE ("(sa(sv))"), NULL, 0); - - if (!systemd_manager_call_start_transient_unit_sync (manager, - name, - "fail", - properties, - aux, - &job, - NULL, - error)) - goto out; - - data.job = job; - data.main_loop = main_loop; - g_signal_connect (manager, "job-removed", G_CALLBACK (job_removed_cb), &data); - - g_main_loop_run (main_loop); - - res = TRUE; - -out: - if (main_context) - { - g_main_context_pop_thread_default (main_context); - g_main_context_unref (main_context); - } - if (main_loop) - g_main_loop_unref (main_loop); - if (manager) - g_object_unref (manager); - - return res; -} - -static void -add_font_path_args (GPtrArray *argv_array) -{ - g_autoptr(GFile) home = NULL; - g_autoptr(GFile) user_font1 = NULL; - g_autoptr(GFile) user_font2 = NULL; - - add_args (argv_array, - "--bind", SYSTEM_FONTS_DIR, "/run/host/fonts", - NULL); - - home = g_file_new_for_path (g_get_home_dir ()); - user_font1 = g_file_resolve_relative_path (home, ".local/share/fonts"); - user_font2 = g_file_resolve_relative_path (home, ".fonts"); - - if (g_file_query_exists (user_font1, NULL)) - { - add_args (argv_array, - "--bind", gs_file_get_path_cached (user_font1), "/run/host/user-fonts", - NULL); - } - else if (g_file_query_exists (user_font2, NULL)) - { - add_args (argv_array, - "--bind", gs_file_get_path_cached (user_font2), "/run/host/user-fonts", - NULL); - } -} - -static void -add_default_permissions (FlatpakContext *app_context) -{ - flatpak_context_set_session_bus_policy (app_context, - "org.freedesktop.portal.Documents", - FLATPAK_POLICY_TALK); -} - -static FlatpakContext * -compute_permissions (GKeyFile *app_metadata, - GKeyFile *runtime_metadata, - GError **error) -{ - g_autoptr(FlatpakContext) app_context = NULL; - - app_context = flatpak_context_new (); - - add_default_permissions (app_context); - - if (!flatpak_context_load_metadata (app_context, runtime_metadata, error)) - return NULL; - - if (!flatpak_context_load_metadata (app_context, app_metadata, error)) - return NULL; - - return g_steal_pointer (&app_context); -} - -static gboolean -add_app_info_args (GPtrArray *argv_array, - FlatpakDeploy *deploy, - const char *app_id, - const char *runtime_ref, - FlatpakContext *final_app_context, - GError **error) -{ - g_autofree char *tmp_path = NULL; - int fd; - - fd = g_file_open_tmp ("xdg-app-context-XXXXXX", &tmp_path, NULL); - if (fd >= 0) - { - g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GFile) files = NULL; - g_autofree char *files_path = NULL; - g_autofree char *fd_str = NULL; - g_autofree char *dest = g_strdup_printf ("/run/user/%d/xdg-app-info", getuid ()); - - close (fd); - - keyfile = g_key_file_new (); - - g_key_file_set_string (keyfile, "Application", "name", app_id); - g_key_file_set_string (keyfile, "Application", "runtime", runtime_ref); - - files = flatpak_deploy_get_files (deploy); - files_path = g_file_get_path (files); - - g_key_file_set_string (keyfile, "Application", "app-path", files_path); - - flatpak_context_save_metadata (final_app_context, keyfile); - - if (!g_key_file_save_to_file (keyfile, tmp_path, error)) - return FALSE; - - fd = open (tmp_path, O_RDONLY); - if (fd == -1) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to open temp file"); - return FALSE; - } - unlink (tmp_path); - fd_str = g_strdup_printf ("%d", fd); - - add_args (argv_array, "--file", fd_str, dest, NULL); - } - - return TRUE; -} - -static void -add_monitor_path_args (GPtrArray *argv_array, - char ***envp_p) -{ - g_autoptr(AutoXdgAppSessionHelper) session_helper = NULL; - g_autofree char *monitor_path = NULL; - - session_helper = - xdg_app_session_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, - "org.freedesktop.Flatpak", - "/org/freedesktop/Flatpak/SessionHelper", - NULL, NULL); - if (session_helper && - xdg_app_session_helper_call_request_monitor_sync (session_helper, - &monitor_path, - NULL, NULL)) - { - add_args (argv_array, - "--bind", monitor_path, "/run/host/monitor", - NULL); - add_args (argv_array, - "--symlink", "/run/host/monitor/localtime", "/etc/localtime", - NULL); - } - else - { - char localtime[PATH_MAX + 1]; - ssize_t symlink_size; - - add_args (argv_array, - "--bind", "/etc/resolv.conf", "/run/host/monitor/resolv.conf", - NULL); - - symlink_size = readlink ("/etc/localtime", localtime, sizeof (localtime) - 1); - if (symlink_size > 0) - { - localtime[symlink_size] = 0; - add_args (argv_array, - "--symlink", localtime, "/etc/localtime", - NULL); - } - else - { - add_args (argv_array, - "--bind", "/etc/localtime", "/etc/localtime", - NULL); - } - } -} - -static void -add_document_portal_args (GPtrArray *argv_array, - const char *app_id) -{ - g_autoptr(GDBusConnection) session_bus = NULL; - g_autofree char *doc_mount_path = NULL; - - session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); - if (session_bus) - { - g_autoptr(GError) local_error = NULL; - g_autoptr(GDBusMessage) reply = NULL; - g_autoptr(GDBusMessage) msg = - g_dbus_message_new_method_call ("org.freedesktop.portal.Documents", - "/org/freedesktop/portal/documents", - "org.freedesktop.portal.Documents", - "GetMountPoint"); - g_dbus_message_set_body (msg, g_variant_new ("()")); - reply = - g_dbus_connection_send_message_with_reply_sync (session_bus, msg, - G_DBUS_SEND_MESSAGE_FLAGS_NONE, - 30000, - NULL, - NULL, - NULL); - if (reply) - { - if (g_dbus_message_to_gerror (reply, &local_error)) - { - g_warning ("Can't get document portal: %s\n", local_error->message); - } - else - { - g_autofree char *src_path = NULL; - g_autofree char *dst_path = NULL; - g_variant_get (g_dbus_message_get_body (reply), - "(^ay)", &doc_mount_path); - - src_path = g_strdup_printf ("%s/by-app/%s", - doc_mount_path, app_id); - dst_path = g_strdup_printf ("/run/user/%d/doc", getuid ()); - add_args (argv_array, "--bind", src_path, dst_path, NULL); - } - } - } -} - -static void -dbus_spawn_child_setup (gpointer user_data) -{ - int fd = GPOINTER_TO_INT (user_data); - - fcntl (fd, F_SETFD, 0); -} - -static gboolean -add_dbus_proxy_args (GPtrArray *argv_array, - GPtrArray *dbus_proxy_argv, - gboolean enable_logging, - int sync_fds[2], - GError **error) -{ - char x = 'x'; - const char *proxy; - - if (dbus_proxy_argv->len == 0) - return TRUE; - - if (sync_fds[0] == -1) - { - g_autofree char *fd_str = NULL; - - if (pipe (sync_fds) < 0) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Unable to create sync pipe"); - return FALSE; - } - - fd_str = g_strdup_printf ("%d", sync_fds[0]); - add_args (argv_array, "--sync-fd", fd_str, NULL); - } - - proxy = g_getenv ("FLATPAK_DBUSPROXY"); - if (proxy == NULL) - proxy = DBUSPROXY; - - g_ptr_array_insert (dbus_proxy_argv, 0, g_strdup (proxy)); - g_ptr_array_insert (dbus_proxy_argv, 1, g_strdup_printf ("--fd=%d", sync_fds[1])); - if (enable_logging) - g_ptr_array_insert (dbus_proxy_argv, 2, g_strdup ("--log")); - - g_ptr_array_add (dbus_proxy_argv, NULL); /* NULL terminate */ - - if (!g_spawn_async (NULL, - (char **) dbus_proxy_argv->pdata, - NULL, - G_SPAWN_SEARCH_PATH, - dbus_spawn_child_setup, - GINT_TO_POINTER (sync_fds[1]), - NULL, error)) - { - close (sync_fds[0]); - close (sync_fds[1]); - return FALSE; - } - - /* Sync with proxy, i.e. wait until its listening on the sockets */ - if (read (sync_fds[0], &x, 1) != 1) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Failed to sync with dbus proxy"); - - close (sync_fds[0]); - close (sync_fds[1]); - return FALSE; - } - - return TRUE; -} - -#ifdef ENABLE_SECCOMP -static inline void -cleanup_seccomp (void *p) -{ - scmp_filter_ctx *pp = (scmp_filter_ctx *) p; - - if (*pp) - seccomp_release (*pp); -} - -static gboolean -setup_seccomp (GPtrArray *argv_array, - const char *arch, - gboolean devel, - GError **error) -{ - __attribute__((cleanup (cleanup_seccomp))) scmp_filter_ctx seccomp = NULL; - - /**** BEGIN NOTE ON CODE SHARING - * - * There are today a number of different Linux container - * implementations. That will likely continue for long into the - * future. But we can still try to share code, and it's important - * to do so because it affects what library and application writers - * can do, and we should support code portability between different - * container tools. - * - * This syscall blacklist is copied from xdg-app, which was in turn - * clearly influenced by the Sandstorm.io blacklist. - * - * If you make any changes here, I suggest sending the changes along - * to other sandbox maintainers. Using the libseccomp list is also - * an appropriate venue: - * https://groups.google.com/forum/#!topic/libseccomp - * - * A non-exhaustive list of links to container tooling that might - * want to share this blacklist: - * - * https://github.com/sandstorm-io/sandstorm - * in src/sandstorm/supervisor.c++ - * http://cgit.freedesktop.org/xdg-app/xdg-app/ - * in lib/xdg-app-helper.c - * https://git.gnome.org/browse/linux-user-chroot - * in src/setup-seccomp.c - * - **** END NOTE ON CODE SHARING - */ - struct - { - int scall; - struct scmp_arg_cmp *arg; - } syscall_blacklist[] = { - /* Block dmesg */ - {SCMP_SYS (syslog)}, - /* Useless old syscall */ - {SCMP_SYS (uselib)}, - /* Don't allow you to switch to bsd emulation or whatnot */ - {SCMP_SYS (personality)}, - /* Don't allow disabling accounting */ - {SCMP_SYS (acct)}, - /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a - historic source of interesting information leaks. */ - {SCMP_SYS (modify_ldt)}, - /* Don't allow reading current quota use */ - {SCMP_SYS (quotactl)}, - - /* Scary VM/NUMA ops */ - {SCMP_SYS (move_pages)}, - {SCMP_SYS (mbind)}, - {SCMP_SYS (get_mempolicy)}, - {SCMP_SYS (set_mempolicy)}, - {SCMP_SYS (migrate_pages)}, - - /* Don't allow subnamespace setups: */ - {SCMP_SYS (unshare)}, - {SCMP_SYS (mount)}, - {SCMP_SYS (pivot_root)}, - {SCMP_SYS (clone), &SCMP_A0 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)}, - }; - - struct - { - int scall; - struct scmp_arg_cmp *arg; - } syscall_nondevel_blacklist[] = { - /* Profiling operations; we expect these to be done by tools from outside - * the sandbox. In particular perf has been the source of many CVEs. - */ - {SCMP_SYS (perf_event_open)}, - {SCMP_SYS (ptrace)} - }; - /* Blacklist all but unix, inet, inet6 and netlink */ - int socket_family_blacklist[] = { - AF_AX25, - AF_IPX, - AF_APPLETALK, - AF_NETROM, - AF_BRIDGE, - AF_ATMPVC, - AF_X25, - AF_ROSE, - AF_DECnet, - AF_NETBEUI, - AF_SECURITY, - AF_KEY, - AF_NETLINK + 1, /* Last gets CMP_GE, so order is important */ - }; - int i, r; - glnx_fd_close int fd = -1; - g_autofree char *fd_str = NULL; - g_autofree char *path = NULL; - - seccomp = seccomp_init (SCMP_ACT_ALLOW); - if (!seccomp) - return flatpak_fail (error, "Initialize seccomp failed"); - - if (arch != NULL) - { - uint32_t arch_id = 0; - - if (strcmp (arch, "i386") == 0) - arch_id = SCMP_ARCH_X86; - else if (strcmp (arch, "x86_64") == 0) - arch_id = SCMP_ARCH_X86_64; - - /* We only really need to handle arches on multiarch systems. - * If only one arch is supported the default is fine */ - if (arch_id != 0) - { - /* This *adds* the target arch, instead of replacing the - native one. This is not ideal, because we'd like to only - allow the target arch, but we can't really disallow the - native arch at this point, because then xdg-app-helper - couldn't continue runnning. */ - r = seccomp_arch_add (seccomp, arch_id); - if (r < 0 && r != -EEXIST) - return flatpak_fail (error, "Failed to add architecture to seccomp filter"); - } - } - - /* Add in all possible secondary archs we are aware of that - * this kernel might support. */ -#if defined(__i386__) || defined(__x86_64__) - r = seccomp_arch_add (seccomp, SCMP_ARCH_X86); - if (r < 0 && r != -EEXIST) - return flatpak_fail (error, "Failed to add x86 architecture to seccomp filter"); - - r = seccomp_arch_add (seccomp, SCMP_ARCH_X86_64); - if (r < 0 && r != -EEXIST) - return flatpak_fail (error, "Failed to add x86_64 architecture to seccomp filter"); - - r = seccomp_arch_add (seccomp, SCMP_ARCH_X32); - if (r < 0 && r != -EEXIST) - return flatpak_fail (error, "Failed to add x32 architecture to seccomp filter"); -#endif - - /* TODO: Should we filter the kernel keyring syscalls in some way? - * We do want them to be used by desktop apps, but they could also perhaps - * leak system stuff or secrets from other apps. - */ - - for (i = 0; i < G_N_ELEMENTS (syscall_blacklist); i++) - { - int scall = syscall_blacklist[i].scall; - if (syscall_blacklist[i].arg) - r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_blacklist[i].arg); - else - r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); - if (r < 0 && r == -EFAULT /* unknown syscall */) - return flatpak_fail (error, "Failed to block syscall %d", scall); - } - - if (!devel) - { - for (i = 0; i < G_N_ELEMENTS (syscall_nondevel_blacklist); i++) - { - int scall = syscall_nondevel_blacklist[i].scall; - if (syscall_nondevel_blacklist[i].arg) - r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_nondevel_blacklist[i].arg); - else - r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); - - if (r < 0 && r == -EFAULT /* unknown syscall */) - return flatpak_fail (error, "Failed to block syscall %d", scall); - } - } - - /* Socket filtering doesn't work on e.g. i386, so ignore failures here - * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing - * something else: https://github.com/seccomp/libseccomp/issues/8 */ - for (i = 0; i < G_N_ELEMENTS (socket_family_blacklist); i++) - { - int family = socket_family_blacklist[i]; - if (i == G_N_ELEMENTS (socket_family_blacklist) - 1) - r = seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_GE, family)); - else - r = seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_EQ, family)); - } - - fd = g_file_open_tmp ("xdg-app-seccomp-XXXXXX", &path, error); - if (fd == -1) - return FALSE; - - unlink (path); - - if (seccomp_export_bpf (seccomp, fd) != 0) - return flatpak_fail (error, "Failed to export bpf"); - - lseek (fd, 0, SEEK_SET); - - fd_str = g_strdup_printf ("%d", fd); - - add_args (argv_array, - "--seccomp", fd_str, - NULL); - - fd = -1; /* Don't close on success */ - - return TRUE; -} -#endif - -gboolean -flatpak_run_setup_base_argv (GPtrArray *argv_array, - GFile *runtime_files, - GFile *app_id_dir, - const char *arch, - FlatpakRunFlags flags, - GError **error) -{ - const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"}; - g_autofree char *run_dir = g_strdup_printf ("/run/user/%d", getuid ()); - int i; - int passwd_fd = -1; - g_autofree char *passwd_fd_str = NULL; - g_autofree char *passwd_contents = NULL; - int group_fd = -1; - g_autofree char *group_fd_str = NULL; - g_autofree char *group_contents = NULL; - struct group *g = getgrgid (getgid ()); - - g_autoptr(GFile) etc = NULL; - - passwd_contents = g_strdup_printf ("%s:x:%d:%d:%s:%s:%s\n" - "nfsnobody:x:65534:65534:Unmapped user:/:/sbin/nologin\n", - g_get_user_name (), - getuid (), getgid (), - g_get_real_name (), - g_get_home_dir (), - DEFAULT_SHELL); - - if ((passwd_fd = create_tmp_fd (passwd_contents, -1, error)) < 0) - return FALSE; - passwd_fd_str = g_strdup_printf ("%d", passwd_fd); - - group_contents = g_strdup_printf ("%s:x:%d:%s\n" - "nfsnobody:x:65534:\n", - g->gr_name, - getgid (), g_get_user_name ()); - if ((group_fd = create_tmp_fd (group_contents, -1, error)) < 0) - return FALSE; - group_fd_str = g_strdup_printf ("%d", group_fd); - - add_args (argv_array, - "--unshare-pid", - "--unshare-user", - "--dev", "/dev", - "--proc", "/proc", - "--dir", "/tmp", - "--dir", "/run/host", - "--dir", run_dir, - "--setenv", "XDG_RUNTIME_DIR", run_dir, - "--symlink", "/tmp", "/var/tmp", - "--symlink", "/run", "/var/run", - "--ro-bind", "/sys/block", "/sys/block", - "--ro-bind", "/sys/bus", "/sys/bus", - "--ro-bind", "/sys/class", "/sys/class", - "--ro-bind", "/sys/dev", "/sys/dev", - "--ro-bind", "/sys/devices", "/sys/devices", - "--bind-data", passwd_fd_str, "/etc/passwd", - "--bind-data", group_fd_str, "/etc/group", - "--symlink", "/run/host/monitor/resolv.conf", "/etc/resolv.conf", - /* Always create a homedir to start from, although it may be covered later */ - "--dir", g_get_home_dir (), - NULL); - - if (g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) - add_args (argv_array, "--bind", "/etc/machine-id", "/etc/machine-id", NULL); - else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS)) - add_args (argv_array, "--bind", "/var/lib/dbus/machine-id", "/etc/machine-id", NULL); - - etc = g_file_get_child (runtime_files, "etc"); - if (g_file_query_exists (etc, NULL)) - { - g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; - struct dirent *dent; - char path_buffer[PATH_MAX + 1]; - ssize_t symlink_size; - - glnx_dirfd_iterator_init_at (AT_FDCWD, gs_file_get_path_cached (etc), FALSE, &dfd_iter, NULL); - - while (TRUE) - { - g_autofree char *src = NULL; - g_autofree char *dest = NULL; - - if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, NULL) || dent == NULL) - break; - - if (strcmp (dent->d_name, "passwd") == 0 || - strcmp (dent->d_name, "group") == 0 || - strcmp (dent->d_name, "machine-id") == 0 || - strcmp (dent->d_name, "resolv.conf") == 0 || - strcmp (dent->d_name, "localtime") == 0) - continue; - - src = g_build_filename (gs_file_get_path_cached (etc), dent->d_name, NULL); - dest = g_build_filename ("/etc", dent->d_name, NULL); - if (dent->d_type == DT_LNK) - { - symlink_size = readlinkat (dfd_iter.fd, dent->d_name, path_buffer, sizeof (path_buffer) - 1); - if (symlink_size < 0) - { - glnx_set_error_from_errno (error); - return FALSE; - } - path_buffer[symlink_size] = 0; - add_args (argv_array, "--symlink", path_buffer, dest, NULL); - } - else - { - add_args (argv_array, "--bind", src, dest, NULL); - } - } - } - - if (app_id_dir != NULL) - { - g_autoptr(GFile) app_cache_dir = g_file_get_child (app_id_dir, "cache"); - g_autoptr(GFile) app_data_dir = g_file_get_child (app_id_dir, "data"); - g_autoptr(GFile) app_config_dir = g_file_get_child (app_id_dir, "config"); - - add_args (argv_array, - /* These are nice to have as a fixed path */ - "--bind", gs_file_get_path_cached (app_cache_dir), "/var/cache", - "--bind", gs_file_get_path_cached (app_data_dir), "/var/data", - "--bind", gs_file_get_path_cached (app_config_dir), "/var/config", - NULL); - } - - for (i = 0; i < G_N_ELEMENTS (usr_links); i++) - { - const char *subdir = usr_links[i]; - g_autoptr(GFile) runtime_subdir = g_file_get_child (runtime_files, subdir); - if (g_file_query_exists (runtime_subdir, NULL)) - { - g_autofree char *link = g_strconcat ("usr/", subdir, NULL); - g_autofree char *dest = g_strconcat ("/", subdir, NULL); - add_args (argv_array, - "--symlink", link, dest, - NULL); - } - } - - -#ifdef ENABLE_SECCOMP - if (!setup_seccomp (argv_array, - arch, - (flags & FLATPAK_RUN_FLAG_DEVEL) != 0, - error)) - return FALSE; -#endif - - return TRUE; -} - -gchar * -join_args (GPtrArray *argv_array, gsize *len_out) -{ - gchar *string; - gchar *ptr; - gint i; - gsize len = 0; - - for (i = 0; i < argv_array->len; i++) - len += strlen (argv_array->pdata[i]) + 1; - - string = g_new (gchar, len); - *string = 0; - ptr = string; - for (i = 0; i < argv_array->len; i++) - ptr = g_stpcpy (ptr, argv_array->pdata[i]) + 1; - - *len_out = len; - return string; -} - -gboolean -flatpak_run_app (const char *app_ref, - FlatpakDeploy *app_deploy, - FlatpakContext *extra_context, - const char *custom_runtime, - const char *custom_runtime_version, - FlatpakRunFlags flags, - const char *custom_command, - char *args[], - int n_args, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(FlatpakDeploy) runtime_deploy = NULL; - g_autoptr(GFile) app_files = NULL; - g_autoptr(GFile) runtime_files = NULL; - g_autoptr(GFile) app_id_dir = NULL; - g_autofree char *default_runtime = NULL; - g_autofree char *default_command = NULL; - g_autofree char *runtime_ref = NULL; - int sync_fds[2] = {-1, -1}; - g_autoptr(GKeyFile) metakey = NULL; - g_autoptr(GKeyFile) runtime_metakey = NULL; - g_autoptr(GPtrArray) argv_array = NULL; - g_autoptr(GPtrArray) real_argv_array = NULL; - g_auto(GStrv) envp = NULL; - g_autoptr(GPtrArray) session_bus_proxy_argv = NULL; - g_autoptr(GPtrArray) system_bus_proxy_argv = NULL; - const char *command = "/bin/sh"; - g_autoptr(GError) my_error = NULL; - g_auto(GStrv) runtime_parts = NULL; - int i; - g_autoptr(FlatpakContext) app_context = NULL; - g_autoptr(FlatpakContext) overrides = NULL; - g_auto(GStrv) app_ref_parts = NULL; - - app_ref_parts = flatpak_decompose_ref (app_ref, error); - if (app_ref_parts == NULL) - return FALSE; - - metakey = flatpak_deploy_get_metadata (app_deploy); - - argv_array = g_ptr_array_new_with_free_func (g_free); - session_bus_proxy_argv = g_ptr_array_new_with_free_func (g_free); - system_bus_proxy_argv = g_ptr_array_new_with_free_func (g_free); - - default_runtime = g_key_file_get_string (metakey, "Application", - (flags & FLATPAK_RUN_FLAG_DEVEL) != 0 ? "sdk" : "runtime", - &my_error); - if (my_error) - { - g_propagate_error (error, g_steal_pointer (&my_error)); - return FALSE; - } - - runtime_parts = g_strsplit (default_runtime, "/", 0); - if (g_strv_length (runtime_parts) != 3) - return flatpak_fail (error, "Wrong number of components in runtime %s", default_runtime); - - if (custom_runtime) - { - g_auto(GStrv) custom_runtime_parts = g_strsplit (custom_runtime, "/", 0); - - for (i = 0; i < 3 && custom_runtime_parts[i] != NULL; i++) - { - if (strlen (custom_runtime_parts[i]) > 0) - { - g_free (runtime_parts[i]); - runtime_parts[i] = g_steal_pointer (&custom_runtime_parts[i]); - } - } - } - - if (custom_runtime_version) - { - g_free (runtime_parts[2]); - runtime_parts[2] = g_strdup (custom_runtime_version); - } - - runtime_ref = flatpak_compose_ref (FALSE, - runtime_parts[0], - runtime_parts[2], - runtime_parts[1], - error); - if (runtime_ref == NULL) - return FALSE; - - runtime_deploy = flatpak_find_deploy_for_ref (runtime_ref, cancellable, error); - if (runtime_deploy == NULL) - return FALSE; - - runtime_metakey = flatpak_deploy_get_metadata (runtime_deploy); - - app_context = compute_permissions (metakey, runtime_metakey, error); - if (app_context == NULL) - return FALSE; - - overrides = flatpak_deploy_get_overrides (app_deploy); - flatpak_context_merge (app_context, overrides); - - if (extra_context) - flatpak_context_merge (app_context, extra_context); - - runtime_files = flatpak_deploy_get_files (runtime_deploy); - app_files = flatpak_deploy_get_files (app_deploy); - - if ((app_id_dir = flatpak_ensure_data_dir (app_ref_parts[1], cancellable, error)) == NULL) - return FALSE; - - envp = g_get_environ (); - envp = flatpak_run_apply_env_default (envp); - envp = flatpak_run_apply_env_vars (envp, app_context); - envp = flatpak_run_apply_env_appid (envp, app_id_dir); - - add_args (argv_array, - "--ro-bind", gs_file_get_path_cached (runtime_files), "/usr", - "--lock-file", "/usr/.ref", - "--ro-bind", gs_file_get_path_cached (app_files), "/app", - "--lock-file", "/app/.ref", - NULL); - - if (!flatpak_run_setup_base_argv (argv_array, runtime_files, app_id_dir, app_ref_parts[2], flags, error)) - return FALSE; - - if (!add_app_info_args (argv_array, app_deploy, app_ref_parts[1], runtime_ref, app_context, error)) - return FALSE; - - if (!flatpak_run_add_extension_args (argv_array, metakey, app_ref, cancellable, error)) - return FALSE; - - if (!flatpak_run_add_extension_args (argv_array, runtime_metakey, runtime_ref, cancellable, error)) - return FALSE; - - add_monitor_path_args (argv_array, &envp); - - add_document_portal_args (argv_array, app_ref_parts[1]); - - flatpak_run_add_environment_args (argv_array, &envp, - session_bus_proxy_argv, - system_bus_proxy_argv, - app_ref_parts[1], app_context, app_id_dir); - - add_font_path_args (argv_array); - - /* Must run this before spawning the dbus proxy, to ensure it - ends up in the app cgroup */ - if (!flatpak_run_in_transient_unit (app_ref_parts[1], error)) - return FALSE; - - if (!add_dbus_proxy_args (argv_array, session_bus_proxy_argv, (flags & FLATPAK_RUN_FLAG_LOG_SESSION_BUS) != 0, sync_fds, error)) - return FALSE; - - if (!add_dbus_proxy_args (argv_array, system_bus_proxy_argv, (flags & FLATPAK_RUN_FLAG_LOG_SYSTEM_BUS) != 0, sync_fds, error)) - return FALSE; - - if (sync_fds[1] != -1) - close (sync_fds[1]); - - add_args (argv_array, - /* Not in base, because we don't want this for xdg-app build */ - "--symlink", "/app/lib/debug/source", "/run/build", - "--symlink", "/usr/lib/debug/source", "/run/build-runtime", - NULL); - - if (g_environ_getenv (envp, "LD_LIBRARY_PATH") != NULL) - { - /* LD_LIBRARY_PATH is overridden for setuid helper, so pass it as cmdline arg */ - add_args (argv_array, - "--setenv", "LD_LIBRARY_PATH", g_environ_getenv (envp, "LD_LIBRARY_PATH"), - NULL); - envp = g_environ_unsetenv (envp, "LD_LIBRARY_PATH"); - } - - if (custom_command) - { - command = custom_command; - } - else - { - default_command = g_key_file_get_string (metakey, "Application", "command", &my_error); - if (my_error) - { - g_propagate_error (error, g_steal_pointer (&my_error)); - return FALSE; - } - - command = default_command; - } - - real_argv_array = g_ptr_array_new_with_free_func (g_free); - g_ptr_array_add (real_argv_array, g_strdup (flatpak_get_bwrap ())); - - { - gsize len; - int arg_fd; - g_autofree char *arg_fd_str = NULL; - g_autofree char *args = join_args (argv_array, &len); - - arg_fd = create_tmp_fd (args, len, error); - if (arg_fd < 0) - return FALSE; - - arg_fd_str = g_strdup_printf ("%d", arg_fd); - - add_args (real_argv_array, - "--args", arg_fd_str, - NULL); - } - - g_ptr_array_add (real_argv_array, g_strdup (command)); - for (i = 0; i < n_args; i++) - g_ptr_array_add (real_argv_array, g_strdup (args[i])); - - g_ptr_array_add (real_argv_array, NULL); - - if ((flags & FLATPAK_RUN_FLAG_BACKGROUND) != 0) - { - if (!g_spawn_async (NULL, - (char **) real_argv_array->pdata, - envp, - G_SPAWN_DEFAULT, - NULL, NULL, - NULL, - error)) - return FALSE; - } - else - { - if (execvpe (flatpak_get_bwrap (), (char **) real_argv_array->pdata, envp) == -1) - { - g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Unable to start app"); - return FALSE; - } - /* Not actually reached... */ - } - - return TRUE; -} diff --git a/common/xdg-app-run.h b/common/xdg-app-run.h deleted file mode 100644 index d066740..0000000 --- a/common/xdg-app-run.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright © 2014 Red Hat, Inc - * - * This program 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 library 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 library. If not, see . - * - * Authors: - * Alexander Larsson - */ - -#ifndef __FLATPAK_RUN_H__ -#define __FLATPAK_RUN_H__ - -#include "libglnx/libglnx.h" -#include "dbus-proxy/xdg-app-proxy.h" -#include "xdg-app-common-types.h" - -gboolean flatpak_run_in_transient_unit (const char *app_id, - GError **error); - -#define FLATPAK_METADATA_GROUP_CONTEXT "Context" -#define FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY "Session Bus Policy" -#define FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY "System Bus Policy" -#define FLATPAK_METADATA_GROUP_ENVIRONMENT "Environment" -#define FLATPAK_METADATA_KEY_SHARED "shared" -#define FLATPAK_METADATA_KEY_SOCKETS "sockets" -#define FLATPAK_METADATA_KEY_FILESYSTEMS "filesystems" -#define FLATPAK_METADATA_KEY_PERSISTENT "persistent" -#define FLATPAK_METADATA_KEY_DEVICES "devices" - -FlatpakContext *flatpak_context_new (void); -void flatpak_context_free (FlatpakContext *context); -void flatpak_context_merge (FlatpakContext *context, - FlatpakContext *other); -GOptionGroup *flatpak_context_get_options (FlatpakContext *context); -gboolean flatpak_context_load_metadata (FlatpakContext *context, - GKeyFile *metakey, - GError **error); -void flatpak_context_save_metadata (FlatpakContext *context, - GKeyFile *metakey); -void flatpak_context_allow_host_fs (FlatpakContext *context); -void flatpak_context_set_session_bus_policy (FlatpakContext *context, - const char *name, - FlatpakPolicy policy); -void flatpak_context_set_system_bus_policy (FlatpakContext *context, - const char *name, - FlatpakPolicy policy); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakContext, flatpak_context_free) - -gboolean flatpak_run_add_extension_args (GPtrArray *argv_array, - GKeyFile *metakey, - const char *full_ref, - GCancellable *cancellable, - GError **error); -void flatpak_run_add_environment_args (GPtrArray *argv_array, - char ***envp_p, - GPtrArray *session_bus_proxy_argv, - GPtrArray *system_bus_proxy_argv, - const char *app_id, - FlatpakContext *context, - GFile *app_id_dir); -char ** flatpak_run_get_minimal_env (gboolean devel); -char ** flatpak_run_apply_env_default (char **envp); -char ** flatpak_run_apply_env_appid (char **envp, - GFile *app_dir); -char ** flatpak_run_apply_env_vars (char **envp, - FlatpakContext *context); - -GFile *flatpak_get_data_dir (const char *app_id); -GFile *flatpak_ensure_data_dir (const char *app_id, - GCancellable *cancellable, - GError **error); - -typedef enum { - FLATPAK_RUN_FLAG_DEVEL = (1 << 0), - FLATPAK_RUN_FLAG_BACKGROUND = (1 << 1), - FLATPAK_RUN_FLAG_LOG_SESSION_BUS = (1 << 2), - FLATPAK_RUN_FLAG_LOG_SYSTEM_BUS = (1 << 3), -} FlatpakRunFlags; - -gboolean flatpak_run_setup_base_argv (GPtrArray *argv_array, - GFile *runtime_files, - GFile *app_id_dir, - const char *arch, - FlatpakRunFlags flags, - GError **error); -gboolean flatpak_run_app (const char *app_ref, - FlatpakDeploy *app_deploy, - FlatpakContext *extra_context, - const char *custom_runtime, - const char *custom_runtime_version, - FlatpakRunFlags flags, - const char *custom_command, - char *args[], - int n_args, - GCancellable *cancellable, - GError **error); - - -#endif /* __FLATPAK_RUN_H__ */ diff --git a/common/xdg-app-utils.c b/common/xdg-app-utils.c deleted file mode 100644 index 5fbfabf..0000000 --- a/common/xdg-app-utils.c +++ /dev/null @@ -1,3008 +0,0 @@ -/* - * Copyright © 2014 Red Hat, Inc - * - * This program 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 library 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 library. If not, see . - * - * Authors: - * Alexander Larsson - */ - -#include "config.h" - -#include "xdg-app-utils.h" -#include "xdg-app-dir.h" -#include "xdg-app-portal-error.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "libgsystem.h" -#include "libglnx/libglnx.h" -#include - -GBytes * -flatpak_read_stream (GInputStream *in, - gboolean null_terminate, - GError **error) -{ - g_autoptr(GOutputStream) mem_stream = NULL; - - mem_stream = g_memory_output_stream_new_resizable (); - if (g_output_stream_splice (mem_stream, in, - 0, NULL, error) < 0) - return NULL; - - if (null_terminate) - { - if (!g_output_stream_write (G_OUTPUT_STREAM (mem_stream), "\0", 1, NULL, error)) - return NULL; - } - - if (!g_output_stream_close (G_OUTPUT_STREAM (mem_stream), NULL, error)) - return NULL; - - return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem_stream)); -} - -gint -flatpak_strcmp0_ptr (gconstpointer a, - gconstpointer b) -{ - return g_strcmp0 (*(char * const *) a, *(char * const *) b); -} - -/* Returns end of matching path prefix, or NULL if no match */ -const char * -flatpak_path_match_prefix (const char *pattern, - const char *string) -{ - char c, test; - const char *tmp; - - while (*pattern == '/') - pattern++; - - while (*string == '/') - string++; - - while (TRUE) - { - switch (c = *pattern++) - { - case 0: - if (*string == '/' || *string == 0) - return string; - return NULL; - - case '?': - if (*string == '/' || *string == 0) - return NULL; - string++; - break; - - case '*': - c = *pattern; - - while (c == '*') - c = *++pattern; - - /* special case * at end */ - if (c == 0) - { - char *tmp = strchr (string, '/'); - if (tmp != NULL) - return tmp; - return string + strlen (string); - } - else if (c == '/') - { - string = strchr (string, '/'); - if (string == NULL) - return NULL; - break; - } - - while ((test = *string) != 0) - { - tmp = flatpak_path_match_prefix (pattern, string); - if (tmp != NULL) - return tmp; - if (test == '/') - break; - string++; - } - return NULL; - - default: - if (c != *string) - return NULL; - string++; - break; - } - } - return NULL; /* Should not be reached */ -} - -gboolean -flatpak_fail (GError **error, const char *format, ...) -{ - g_autofree char *message = NULL; - va_list args; - - va_start (args, format); - message = g_strdup_vprintf (format, args); - va_end (args); - - g_set_error_literal (error, - G_IO_ERROR, G_IO_ERROR_FAILED, - message); - - return FALSE; -} - -const char * -flatpak_get_kernel_arch (void) -{ - static struct utsname buf; - static char *arch = NULL; - char *m; - - if (arch != NULL) - return arch; - - if (uname (&buf)) - { - arch = "unknown"; - return arch; - } - - /* By default, just pass on machine, good enough for most arches */ - arch = buf.machine; - - /* Override for some arches */ - - m = buf.machine; - /* i?86 */ - if (strlen (m) == 4 && m[0] == 'i' && m[2] == '8' && m[3] == '6') - { - arch = "i386"; - } - else if (g_str_has_prefix (m, "arm")) - { - if (g_str_has_suffix (m, "b")) - arch = "armeb"; - else - arch = "arm"; - } - else if (strcmp (m, "mips") == 0) - { -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - arch = "mipsel"; -#endif - } - else if (strcmp (m, "mips64") == 0) - { -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - arch = "mips64el"; -#endif - } - - return arch; -} - - -/* This maps the kernel-reported uname to a single string representing - * the cpu family, in the sense that all members of this family would - * be able to understand and link to a binary file with such cpu - * opcodes. That doesn't necessarily mean that all members of the - * family can run all opcodes, for instance for modern 32bit intel we - * report "i386", even though they support instructions that the - * original i386 cpu cannot run. Still, such an executable would - * at least try to execute a 386, wheras an arm binary would not. - */ -const char * -flatpak_get_arch (void) -{ - /* Avoid using uname on multiarch machines, because uname reports the kernels - * arch, and that may be different from userspace. If e.g. the kernel is 64bit and - * the userspace is 32bit we want to use 32bit by default. So, we take the current build - * arch as the default. */ -#if defined(__i386__) - return "i386"; -#elif defined(__x86_64__) - return "x86_64"; -#elif defined(__aarch64__) - return "aarch64"; -#elif defined(__arm__) -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - return "arm"; -#else - return "armeb"; -#endif -#else - return flatpak_get_kernel_arch (); -#endif -} - -const char * -flatpak_get_bwrap (void) -{ - const char *e = g_getenv ("FLATPAK_BWRAP"); - - if (e != NULL) - return e; - return HELPER; -} - -static gboolean -is_valid_initial_name_character (gint c) -{ - return - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c == '_'); -} - -static gboolean -is_valid_name_character (gint c) -{ - return - is_valid_initial_name_character (c) || - (c >= '0' && c <= '9'); -} - -/** flatpak_is_name: - * @string: The string to check - * - * Checks if @string is a valid application name. - * - * App names are composed of 3 or more elements separated by a period - * ('.') character. All elements must contain at least one character. - * - * Each element must only contain the ASCII characters - * "[A-Z][a-z][0-9]_". Elements may not begin with a digit. - * - * App names must not begin with a '.' (period) character. - * - * App names must not exceed 255 characters in length. - * - * The above means that any app name is also a valid DBus well known - * bus name, but not all DBus names are valid app names. The difference are: - * 1) DBus name elements may contain '-' - * 2) DBus names require only two elements - * - * Returns: %TRUE if valid, %FALSE otherwise. - * - * Since: 2.26 - */ -gboolean -flatpak_is_valid_name (const char *string) -{ - guint len; - gboolean ret; - const gchar *s; - const gchar *end; - int dot_count; - - g_return_val_if_fail (string != NULL, FALSE); - - ret = FALSE; - - len = strlen (string); - if (G_UNLIKELY (len == 0 || len > 255)) - goto out; - - end = string + len; - - s = string; - if (G_UNLIKELY (*s == '.')) - /* can't start with a . */ - goto out; - else if (G_UNLIKELY (!is_valid_initial_name_character (*s))) - goto out; - - s += 1; - dot_count = 0; - while (s != end) - { - if (*s == '.') - { - s += 1; - if (G_UNLIKELY (s == end || !is_valid_initial_name_character (*s))) - goto out; - dot_count++; - } - else if (G_UNLIKELY (!is_valid_name_character (*s))) - { - goto out; - } - s += 1; - } - - if (G_UNLIKELY (dot_count < 2)) - goto out; - - ret = TRUE; - -out: - return ret; -} - -gboolean -flatpak_has_name_prefix (const char *string, - const char *name) -{ - const char *rest; - - if (!g_str_has_prefix (string, name)) - return FALSE; - - rest = string + strlen (name); - return - *rest == 0 || - *rest == '.' || - !is_valid_name_character (*rest); -} - - -static gboolean -is_valid_initial_branch_character (gint c) -{ - return - (c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c == '_') || - (c == '-'); -} - -static gboolean -is_valid_branch_character (gint c) -{ - return - is_valid_initial_branch_character (c) || - (c == '.'); -} - -/** flatpak_is_valid_branch: - * @string: The string to check - * - * Checks if @string is a valid branch name. - * - * Branch names must only contain the ASCII characters - * "[A-Z][a-z][0-9]_-.". - * Branch names may not begin with a digit. - * Branch names must contain at least one character. - * - * Returns: %TRUE if valid, %FALSE otherwise. - * - * Since: 2.26 - */ -gboolean -flatpak_is_valid_branch (const char *string) -{ - guint len; - gboolean ret; - const gchar *s; - const gchar *end; - - g_return_val_if_fail (string != NULL, FALSE); - - ret = FALSE; - - len = strlen (string); - if (G_UNLIKELY (len == 0)) - goto out; - - end = string + len; - - s = string; - if (G_UNLIKELY (!is_valid_initial_branch_character (*s))) - goto out; - - s += 1; - while (s != end) - { - if (G_UNLIKELY (!is_valid_branch_character (*s))) - goto out; - s += 1; - } - - ret = TRUE; - -out: - return ret; -} - -char ** -flatpak_decompose_ref (const char *full_ref, - GError **error) -{ - g_auto(GStrv) parts = NULL; - - parts = g_strsplit (full_ref, "/", 0); - if (g_strv_length (parts) != 4) - { - flatpak_fail (error, "Wrong number of components in %s", full_ref); - return NULL; - } - - if (strcmp (parts[0], "app") != 0 && strcmp (parts[0], "runtime") != 0) - { - flatpak_fail (error, "Not application or runtime"); - return NULL; - } - - if (!flatpak_is_valid_name (parts[1])) - { - flatpak_fail (error, "Invalid name %s", parts[1]); - return NULL; - } - - if (strlen (parts[2]) == 0) - { - flatpak_fail (error, "Invalid arch %s", parts[2]); - return NULL; - } - - if (!flatpak_is_valid_branch (parts[3])) - { - flatpak_fail (error, "Invalid branch %s", parts[3]); - return NULL; - } - - return g_steal_pointer (&parts); -} - -char * -flatpak_compose_ref (gboolean app, - const char *name, - const char *branch, - const char *arch, - GError **error) -{ - if (!flatpak_is_valid_name (name)) - { - flatpak_fail (error, "'%s' is not a valid name", name); - return NULL; - } - - if (branch && !flatpak_is_valid_branch (branch)) - { - flatpak_fail (error, "'%s' is not a valid branch name", branch); - return NULL; - } - - if (app) - return flatpak_build_app_ref (name, branch, arch); - else - return flatpak_build_runtime_ref (name, branch, arch); -} - -char * -flatpak_build_untyped_ref (const char *runtime, - const char *branch, - const char *arch) -{ - if (arch == NULL) - arch = flatpak_get_arch (); - - return g_build_filename (runtime, arch, branch, NULL); -} - -char * -flatpak_build_runtime_ref (const char *runtime, - const char *branch, - const char *arch) -{ - if (branch == NULL) - branch = "master"; - - if (arch == NULL) - arch = flatpak_get_arch (); - - return g_build_filename ("runtime", runtime, arch, branch, NULL); -} - -char * -flatpak_build_app_ref (const char *app, - const char *branch, - const char *arch) -{ - if (branch == NULL) - branch = "master"; - - if (arch == NULL) - arch = flatpak_get_arch (); - - return g_build_filename ("app", app, arch, branch, NULL); -} - -char ** -flatpak_list_deployed_refs (const char *type, - const char *name_prefix, - const char *branch, - const char *arch, - GCancellable *cancellable, - GError **error) -{ - gchar **ret = NULL; - - g_autoptr(GPtrArray) names = NULL; - g_autoptr(GHashTable) hash = NULL; - g_autoptr(FlatpakDir) user_dir = NULL; - g_autoptr(FlatpakDir) system_dir = NULL; - const char *key; - GHashTableIter iter; - - hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - - user_dir = flatpak_dir_get_user (); - system_dir = flatpak_dir_get_system (); - - if (!flatpak_dir_collect_deployed_refs (user_dir, type, name_prefix, - branch, arch, hash, cancellable, - error)) - goto out; - - if (!flatpak_dir_collect_deployed_refs (system_dir, type, name_prefix, - branch, arch, hash, cancellable, - error)) - goto out; - - names = g_ptr_array_new (); - g_hash_table_iter_init (&iter, hash); - while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) - g_ptr_array_add (names, g_strdup (key)); - - g_ptr_array_sort (names, flatpak_strcmp0_ptr); - g_ptr_array_add (names, NULL); - - ret = (char **) g_ptr_array_free (names, FALSE); - names = NULL; - -out: - return ret; -} - -GFile * -flatpak_find_deploy_dir_for_ref (const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(FlatpakDir) user_dir = NULL; - g_autoptr(FlatpakDir) system_dir = NULL; - GFile *deploy = NULL; - - user_dir = flatpak_dir_get_user (); - system_dir = flatpak_dir_get_system (); - - deploy = flatpak_dir_get_if_deployed (user_dir, ref, NULL, cancellable); - if (deploy == NULL) - deploy = flatpak_dir_get_if_deployed (system_dir, ref, NULL, cancellable); - if (deploy == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s not installed", ref); - return NULL; - } - - return deploy; - -} - -FlatpakDeploy * -flatpak_find_deploy_for_ref (const char *ref, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(FlatpakDir) user_dir = NULL; - g_autoptr(FlatpakDir) system_dir = NULL; - g_autoptr(FlatpakDeploy) deploy = NULL; - g_autoptr(GError) my_error = NULL; - - user_dir = flatpak_dir_get_user (); - system_dir = flatpak_dir_get_system (); - - deploy = flatpak_dir_load_deployed (user_dir, ref, NULL, cancellable, &my_error); - if (deploy == NULL && g_error_matches (my_error, FLATPAK_DIR_ERROR, FLATPAK_DIR_ERROR_NOT_DEPLOYED)) - { - g_clear_error (&my_error); - deploy = flatpak_dir_load_deployed (system_dir, ref, NULL, cancellable, &my_error); - } - if (deploy == NULL) - g_propagate_error (error, g_steal_pointer (&my_error)); - - return g_steal_pointer (&deploy); -} - - -static gboolean -overlay_symlink_tree_dir (int source_parent_fd, - const char *source_name, - const char *source_symlink_prefix, - int destination_parent_fd, - const char *destination_name, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - int res; - - g_auto(GLnxDirFdIterator) source_iter = { 0 }; - glnx_fd_close int destination_dfd = -1; - struct dirent *dent; - - if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, error)) - goto out; - - do - res = mkdirat (destination_parent_fd, destination_name, 0777); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (res == -1) - { - if (errno != EEXIST) - { - glnx_set_error_from_errno (error); - goto out; - } - } - - if (!gs_file_open_dir_fd_at (destination_parent_fd, destination_name, - &destination_dfd, - cancellable, error)) - goto out; - - while (TRUE) - { - - if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&source_iter, &dent, cancellable, error)) - goto out; - - if (dent == NULL) - break; - - if (dent->d_type == DT_DIR) - { - g_autofree gchar *target = g_build_filename ("..", source_symlink_prefix, dent->d_name, NULL); - if (!overlay_symlink_tree_dir (source_iter.fd, dent->d_name, target, destination_dfd, dent->d_name, - cancellable, error)) - goto out; - } - else - { - g_autofree gchar *target = g_build_filename (source_symlink_prefix, dent->d_name, NULL); - - if (unlinkat (destination_dfd, dent->d_name, 0) != 0 && errno != ENOENT) - { - glnx_set_error_from_errno (error); - goto out; - } - - if (symlinkat (target, destination_dfd, dent->d_name) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - } - } - - ret = TRUE; -out: - - return ret; -} - -gboolean -flatpak_overlay_symlink_tree (GFile *source, - GFile *destination, - const char *symlink_prefix, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - if (!gs_file_ensure_directory (destination, TRUE, cancellable, error)) - goto out; - - /* The fds are closed by this call */ - if (!overlay_symlink_tree_dir (AT_FDCWD, gs_file_get_path_cached (source), - symlink_prefix, - AT_FDCWD, gs_file_get_path_cached (destination), - cancellable, error)) - goto out; - - ret = TRUE; - -out: - return ret; -} - -static gboolean -remove_dangling_symlinks (int parent_fd, - const char *name, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - struct dirent *dent; - GLnxDirFdIterator iter; - - if (!glnx_dirfd_iterator_init_at (parent_fd, name, FALSE, &iter, error)) - goto out; - - while (TRUE) - { - if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent, cancellable, error)) - goto out; - - if (dent == NULL) - break; - - if (dent->d_type == DT_DIR) - { - if (!remove_dangling_symlinks (iter.fd, dent->d_name, cancellable, error)) - goto out; - } - else if (dent->d_type == DT_LNK) - { - struct stat stbuf; - if (fstatat (iter.fd, dent->d_name, &stbuf, 0) != 0 && errno == ENOENT) - { - if (unlinkat (iter.fd, dent->d_name, 0) != 0) - { - glnx_set_error_from_errno (error); - goto out; - } - } - } - } - - ret = TRUE; -out: - - return ret; -} - -gboolean -flatpak_remove_dangling_symlinks (GFile *dir, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - /* The fd is closed by this call */ - if (!remove_dangling_symlinks (AT_FDCWD, gs_file_get_path_cached (dir), - cancellable, error)) - goto out; - - ret = TRUE; - -out: - return ret; -} - -/* Based on g_mkstemp from glib */ - -gint -flatpak_mkstempat (int dir_fd, - gchar *tmpl, - int flags, - int mode) -{ - char *XXXXXX; - int count, fd; - static const char letters[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - static const int NLETTERS = sizeof (letters) - 1; - glong value; - GTimeVal tv; - static int counter = 0; - - g_return_val_if_fail (tmpl != NULL, -1); - - /* find the last occurrence of "XXXXXX" */ - XXXXXX = g_strrstr (tmpl, "XXXXXX"); - - if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6)) - { - errno = EINVAL; - return -1; - } - - /* Get some more or less random data. */ - g_get_current_time (&tv); - value = (tv.tv_usec ^ tv.tv_sec) + counter++; - - for (count = 0; count < 100; value += 7777, ++count) - { - glong v = value; - - /* Fill in the random bits. */ - XXXXXX[0] = letters[v % NLETTERS]; - v /= NLETTERS; - XXXXXX[1] = letters[v % NLETTERS]; - v /= NLETTERS; - XXXXXX[2] = letters[v % NLETTERS]; - v /= NLETTERS; - XXXXXX[3] = letters[v % NLETTERS]; - v /= NLETTERS; - XXXXXX[4] = letters[v % NLETTERS]; - v /= NLETTERS; - XXXXXX[5] = letters[v % NLETTERS]; - - fd = openat (dir_fd, tmpl, flags | O_CREAT | O_EXCL, mode); - - if (fd >= 0) - return fd; - else if (errno != EEXIST) - /* Any other error will apply also to other names we might - * try, and there are 2^32 or so of them, so give up now. - */ - return -1; - } - - /* We got out of the loop because we ran out of combinations to try. */ - errno = EEXIST; - return -1; -} - -struct FlatpakTablePrinter -{ - GPtrArray *rows; - GPtrArray *current; - int n_columns; -}; - -FlatpakTablePrinter * -flatpak_table_printer_new (void) -{ - FlatpakTablePrinter *printer = g_new0 (FlatpakTablePrinter, 1); - - printer->rows = g_ptr_array_new_with_free_func ((GDestroyNotify) g_strfreev); - printer->current = g_ptr_array_new_with_free_func (g_free); - - return printer; -} - -void -flatpak_table_printer_free (FlatpakTablePrinter *printer) -{ - g_ptr_array_free (printer->rows, TRUE); - g_ptr_array_free (printer->current, TRUE); - g_free (printer); -} - -void -flatpak_table_printer_add_column (FlatpakTablePrinter *printer, - const char *text) -{ - g_ptr_array_add (printer->current, text ? g_strdup (text) : g_strdup ("")); -} - -void -flatpak_table_printer_append_with_comma (FlatpakTablePrinter *printer, - const char *text) -{ - char *old, *new; - - g_assert (printer->current->len > 0); - - old = g_ptr_array_index (printer->current, printer->current->len - 1); - - if (old[0] != 0) - new = g_strconcat (old, ",", text, NULL); - else - new = g_strdup (text); - - g_ptr_array_index (printer->current, printer->current->len - 1) = new; - g_free (old); -} - - -void -flatpak_table_printer_finish_row (FlatpakTablePrinter *printer) -{ - if (printer->current->len == 0) - return; /* Ignore empty rows */ - - printer->n_columns = MAX (printer->n_columns, printer->current->len); - g_ptr_array_add (printer->current, NULL); - g_ptr_array_add (printer->rows, - g_ptr_array_free (printer->current, FALSE)); - printer->current = g_ptr_array_new_with_free_func (g_free); -} - -void -flatpak_table_printer_print (FlatpakTablePrinter *printer) -{ - g_autofree int *widths = NULL; - int i, j; - - if (printer->current->len != 0) - flatpak_table_printer_finish_row (printer); - - widths = g_new0 (int, printer->n_columns); - - for (i = 0; i < printer->rows->len; i++) - { - char **row = g_ptr_array_index (printer->rows, i); - - for (j = 0; row[j] != NULL; j++) - widths[j] = MAX (widths[j], strlen (row[j])); - } - - for (i = 0; i < printer->rows->len; i++) - { - char **row = g_ptr_array_index (printer->rows, i); - - for (j = 0; row[j] != NULL; j++) - g_print ("%s%-*s", (j == 0) ? "" : " ", widths[j], row[j]); - g_print ("\n"); - } -} - - -static GHashTable *app_ids; - -typedef struct -{ - char *name; - char *app_id; - gboolean exited; - GList *pending; -} AppIdInfo; - -static void -app_id_info_free (AppIdInfo *info) -{ - g_free (info->name); - g_free (info->app_id); - g_free (info); -} - -static void -ensure_app_ids (void) -{ - if (app_ids == NULL) - app_ids = g_hash_table_new_full (g_str_hash, g_str_equal, - NULL, (GDestroyNotify) app_id_info_free); -} - -static void -got_credentials_cb (GObject *source_object, - GAsyncResult *res, - gpointer user_data) -{ - AppIdInfo *info = user_data; - - g_autoptr(GDBusMessage) reply = NULL; - g_autoptr(GError) error = NULL; - GList *l; - - reply = g_dbus_connection_send_message_with_reply_finish (G_DBUS_CONNECTION (source_object), - res, &error); - - if (!info->exited && reply != NULL) - { - GVariant *body = g_dbus_message_get_body (reply); - guint32 pid; - g_autofree char *path = NULL; - g_autofree char *content = NULL; - - g_variant_get (body, "(u)", &pid); - - path = g_strdup_printf ("/proc/%u/cgroup", pid); - - if (g_file_get_contents (path, &content, NULL, NULL)) - { - gchar **lines = g_strsplit (content, "\n", -1); - int i; - - for (i = 0; lines[i] != NULL; i++) - { - if (g_str_has_prefix (lines[i], "1:name=systemd:")) - { - const char *unit = lines[i] + strlen ("1:name=systemd:"); - g_autofree char *scope = g_path_get_basename (unit); - - if (g_str_has_prefix (scope, "xdg-app-") && - g_str_has_suffix (scope, ".scope")) - { - const char *name = scope + strlen ("xdg-app-"); - char *dash = strchr (name, '-'); - if (dash != NULL) - { - *dash = 0; - info->app_id = g_strdup (name); - } - } - else - { - info->app_id = g_strdup (""); - } - } - } - g_strfreev (lines); - } - } - - for (l = info->pending; l != NULL; l = l->next) - { - GTask *task = l->data; - - if (info->app_id == NULL) - g_task_return_new_error (task, FLATPAK_PORTAL_ERROR, FLATPAK_PORTAL_ERROR_FAILED, - "Can't find app id"); - else - g_task_return_pointer (task, g_strdup (info->app_id), g_free); - } - - g_list_free_full (info->pending, g_object_unref); - info->pending = NULL; - - if (info->app_id == NULL) - g_hash_table_remove (app_ids, info->name); -} - -void -flatpak_invocation_lookup_app_id (GDBusMethodInvocation *invocation, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); - const gchar *sender = g_dbus_method_invocation_get_sender (invocation); - - g_autoptr(GTask) task = NULL; - AppIdInfo *info; - - task = g_task_new (invocation, cancellable, callback, user_data); - - ensure_app_ids (); - - info = g_hash_table_lookup (app_ids, sender); - - if (info == NULL) - { - info = g_new0 (AppIdInfo, 1); - info->name = g_strdup (sender); - g_hash_table_insert (app_ids, info->name, info); - } - - if (info->app_id) - { - g_task_return_pointer (task, g_strdup (info->app_id), g_free); - } - else - { - if (info->pending == NULL) - { - g_autoptr(GDBusMessage) msg = g_dbus_message_new_method_call ("org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionUnixProcessID"); - g_dbus_message_set_body (msg, g_variant_new ("(s)", sender)); - - g_dbus_connection_send_message_with_reply (connection, msg, - G_DBUS_SEND_MESSAGE_FLAGS_NONE, - 30000, - NULL, - cancellable, - got_credentials_cb, - info); - } - - info->pending = g_list_prepend (info->pending, g_object_ref (task)); - } -} - -char * -flatpak_invocation_lookup_app_id_finish (GDBusMethodInvocation *invocation, - GAsyncResult *result, - GError **error) -{ - return g_task_propagate_pointer (G_TASK (result), error); -} - -static void -name_owner_changed (GDBusConnection *connection, - const gchar *sender_name, - const gchar *object_path, - const gchar *interface_name, - const gchar *signal_name, - GVariant *parameters, - gpointer user_data) -{ - const char *name, *from, *to; - - g_variant_get (parameters, "(sss)", &name, &from, &to); - - ensure_app_ids (); - - if (name[0] == ':' && - strcmp (name, from) == 0 && - strcmp (to, "") == 0) - { - AppIdInfo *info = g_hash_table_lookup (app_ids, name); - - if (info != NULL) - { - info->exited = TRUE; - if (info->pending == NULL) - g_hash_table_remove (app_ids, name); - } - } -} - -void -flatpak_connection_track_name_owners (GDBusConnection *connection) -{ - g_dbus_connection_signal_subscribe (connection, - "org.freedesktop.DBus", - "org.freedesktop.DBus", - "NameOwnerChanged", - "/org/freedesktop/DBus", - NULL, - G_DBUS_SIGNAL_FLAGS_NONE, - name_owner_changed, - NULL, NULL); -} - -typedef struct -{ - GError *error; - GError *splice_error; - GMainLoop *loop; - int refs; -} SpawnData; - -static void -spawn_data_exit (SpawnData *data) -{ - data->refs--; - if (data->refs == 0) - g_main_loop_quit (data->loop); -} - -static void -spawn_output_spliced_cb (GObject *obj, - GAsyncResult *result, - gpointer user_data) -{ - SpawnData *data = user_data; - - g_output_stream_splice_finish (G_OUTPUT_STREAM (obj), result, &data->splice_error); - spawn_data_exit (data); -} - -static void -spawn_exit_cb (GObject *obj, - GAsyncResult *result, - gpointer user_data) -{ - SpawnData *data = user_data; - - g_subprocess_wait_check_finish (G_SUBPROCESS (obj), result, &data->error); - spawn_data_exit (data); -} - -gboolean -flatpak_spawn (GFile *dir, - char **output, - GError **error, - const gchar *argv0, - va_list ap) -{ - g_autoptr(GSubprocessLauncher) launcher = NULL; - g_autoptr(GSubprocess) subp = NULL; - GPtrArray *args; - const gchar *arg; - GInputStream *in; - g_autoptr(GOutputStream) out = NULL; - g_autoptr(GMainLoop) loop = NULL; - SpawnData data = {0}; - - args = g_ptr_array_new (); - g_ptr_array_add (args, (gchar *) argv0); - while ((arg = va_arg (ap, const gchar *))) - g_ptr_array_add (args, (gchar *) arg); - g_ptr_array_add (args, NULL); - - launcher = g_subprocess_launcher_new (0); - - if (output) - g_subprocess_launcher_set_flags (launcher, G_SUBPROCESS_FLAGS_STDOUT_PIPE); - - if (dir) - { - g_autofree char *path = g_file_get_path (dir); - g_subprocess_launcher_set_cwd (launcher, path); - } - - subp = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); - g_ptr_array_free (args, TRUE); - - if (subp == NULL) - return FALSE; - - loop = g_main_loop_new (NULL, FALSE); - - data.loop = loop; - data.refs = 1; - - if (output) - { - data.refs++; - in = g_subprocess_get_stdout_pipe (subp); - out = g_memory_output_stream_new_resizable (); - g_output_stream_splice_async (out, - in, - G_OUTPUT_STREAM_SPLICE_NONE, - 0, - NULL, - spawn_output_spliced_cb, - &data); - } - - g_subprocess_wait_async (subp, NULL, spawn_exit_cb, &data); - - g_main_loop_run (loop); - - if (data.error) - { - g_propagate_error (error, data.error); - g_clear_error (&data.splice_error); - return FALSE; - } - - if (out) - { - if (data.splice_error) - { - g_propagate_error (error, data.splice_error); - return FALSE; - } - - /* Null terminate */ - g_output_stream_write (out, "\0", 1, NULL, NULL); - g_output_stream_close (out, NULL, NULL); - *output = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out)); - } - - return TRUE; -} - - -gboolean -flatpak_cp_a (GFile *src, - GFile *dest, - FlatpakCpFlags flags, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - GFileEnumerator *enumerator = NULL; - GFileInfo *src_info = NULL; - GFile *dest_child = NULL; - int dest_dfd = -1; - gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0; - gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0; - gboolean move = (flags & FLATPAK_CP_FLAGS_MOVE) != 0; - int r; - - enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode", - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (!enumerator) - goto out; - - src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \ - "time::modified,time::modified-usec,time::access,time::access-usec", - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (!src_info) - goto out; - - do - r = mkdir (gs_file_get_path_cached (dest), 0755); - while (G_UNLIKELY (r == -1 && errno == EINTR)); - if (r == -1 && - (!merge || errno != EEXIST)) - { - glnx_set_error_from_errno (error); - goto out; - } - - if (!gs_file_open_dir_fd (dest, &dest_dfd, - cancellable, error)) - goto out; - - - if (!no_chown) - { - do - r = fchown (dest_dfd, - g_file_info_get_attribute_uint32 (src_info, "unix::uid"), - g_file_info_get_attribute_uint32 (src_info, "unix::gid")); - while (G_UNLIKELY (r == -1 && errno == EINTR)); - if (r == -1) - { - glnx_set_error_from_errno (error); - goto out; - } - } - - do - r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode")); - while (G_UNLIKELY (r == -1 && errno == EINTR)); - - if (dest_dfd != -1) - { - (void) close (dest_dfd); - dest_dfd = -1; - } - - while (TRUE) - { - GFileInfo *file_info = NULL; - GFile *src_child = NULL; - - if (!gs_file_enumerator_iterate (enumerator, &file_info, &src_child, - cancellable, error)) - goto out; - if (!file_info) - break; - - if (dest_child) - g_object_unref (dest_child); - dest_child = g_file_get_child (dest, g_file_info_get_name (file_info)); - - if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) - { - if (!flatpak_cp_a (src_child, dest_child, flags, - cancellable, error)) - goto out; - } - else - { - (void) unlink (gs_file_get_path_cached (dest_child)); - GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS; - if (!no_chown) - copyflags |= G_FILE_COPY_ALL_METADATA; - if (move) - { - if (!g_file_move (src_child, dest_child, copyflags, - cancellable, NULL, NULL, error)) - goto out; - } - else - { - if (!g_file_copy (src_child, dest_child, copyflags, - cancellable, NULL, NULL, error)) - goto out; - } - } - } - - if (move && - !g_file_delete (src, NULL, error)) - goto out; - - ret = TRUE; -out: - if (dest_dfd != -1) - (void) close (dest_dfd); - g_clear_object (&src_info); - g_clear_object (&enumerator); - g_clear_object (&dest_child); - return ret; -} - -gboolean -flatpak_variant_save (GFile *dest, - GVariant *variant, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GOutputStream) out = NULL; - gsize bytes_written; - - out = (GOutputStream *) g_file_replace (dest, NULL, FALSE, - G_FILE_CREATE_REPLACE_DESTINATION, - cancellable, error); - if (out == NULL) - return FALSE; - - if (!g_output_stream_write_all (out, - g_variant_get_data (variant), - g_variant_get_size (variant), - &bytes_written, - cancellable, - error)) - return FALSE; - - if (!g_output_stream_close (out, cancellable, error)) - return FALSE; - - return TRUE; -} - - -gboolean -flatpak_variant_bsearch_str (GVariant *array, - const char *str, - int *out_pos) -{ - gsize imax, imin; - gsize imid; - gsize n; - - n = g_variant_n_children (array); - if (n == 0) - return FALSE; - - imax = n - 1; - imin = 0; - while (imax >= imin) - { - g_autoptr(GVariant) child = NULL; - const char *cur; - int cmp; - - imid = (imin + imax) / 2; - - child = g_variant_get_child_value (array, imid); - g_variant_get_child (child, 0, "&s", &cur, NULL); - - cmp = strcmp (cur, str); - if (cmp < 0) - { - imin = imid + 1; - } - else if (cmp > 0) - { - if (imid == 0) - break; - imax = imid - 1; - } - else - { - *out_pos = imid; - return TRUE; - } - } - - *out_pos = imid; - return FALSE; -} - -gboolean -flatpak_summary_lookup_ref (GVariant *summary, const char *ref, char **out_checksum) -{ - g_autoptr(GVariant) refs = g_variant_get_child_value (summary, 0); - int pos; - g_autoptr(GVariant) refdata = NULL; - g_autoptr(GVariant) reftargetdata = NULL; - g_autoptr(GVariant) commit_data = NULL; - guint64 commit_size; - g_autoptr(GVariant) commit_csum_v = NULL; - g_autoptr(GBytes) commit_bytes = NULL; - - if (!flatpak_variant_bsearch_str (refs, ref, &pos)) - return FALSE; - - refdata = g_variant_get_child_value (refs, pos); - reftargetdata = g_variant_get_child_value (refdata, 1); - g_variant_get (reftargetdata, "(t@ay@a{sv})", &commit_size, &commit_csum_v, NULL); - - if (!ostree_validate_structureof_csum_v (commit_csum_v, NULL)) - return FALSE; - - if (out_checksum) - *out_checksum = ostree_checksum_from_bytes_v (commit_csum_v); - - return TRUE; -} - -gboolean -flatpak_repo_set_title (OstreeRepo *repo, - const char *title, - GError **error) -{ - g_autoptr(GKeyFile) config = NULL; - - config = ostree_repo_copy_config (repo); - - if (title) - g_key_file_set_string (config, "xdg-app", "title", title); - else - g_key_file_remove_key (config, "xdg-app", "title", NULL); - - if (!ostree_repo_write_config (repo, config, error)) - return FALSE; - - return TRUE; -} - -#define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \ - "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev") - -static gboolean -_flatpak_repo_collect_sizes (OstreeRepo *repo, - GFile *file, - GFileInfo *file_info, - guint64 *installed_size, - guint64 *download_size, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFileEnumerator) dir_enum = NULL; - GFileInfo *child_info_tmp; - GError *temp_error = NULL; - - if (file_info != NULL && g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) - { - const char *checksum = ostree_repo_file_get_checksum (OSTREE_REPO_FILE (file)); - guint64 obj_size; - guint64 file_size = g_file_info_get_size (file_info); - - if (installed_size) - *installed_size += ((file_size + 511) / 512) * 512; - - if (download_size) - { - if (!ostree_repo_query_object_storage_size (repo, - OSTREE_OBJECT_TYPE_FILE, checksum, - &obj_size, cancellable, error)) - return FALSE; - - *download_size += obj_size; - } - } - - if (file_info == NULL || g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) - { - dir_enum = g_file_enumerate_children (file, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - if (!dir_enum) - return FALSE; - - - while ((child_info_tmp = g_file_enumerator_next_file (dir_enum, cancellable, &temp_error))) - { - g_autoptr(GFileInfo) child_info = child_info_tmp; - const char *name = g_file_info_get_name (child_info); - g_autoptr(GFile) child = g_file_get_child (file, name); - - if (!_flatpak_repo_collect_sizes (repo, child, child_info, installed_size, download_size, cancellable, error)) - return FALSE; - } - } - - return TRUE; -} - -gboolean -flatpak_repo_collect_sizes (OstreeRepo *repo, - GFile *root, - guint64 *installed_size, - guint64 *download_size, - GCancellable *cancellable, - GError **error) -{ - return _flatpak_repo_collect_sizes (repo, root, NULL, installed_size, download_size, cancellable, error); -} - -gboolean -flatpak_repo_update (OstreeRepo *repo, - const char **gpg_key_ids, - const char *gpg_homedir, - GCancellable *cancellable, - GError **error) -{ - GVariantBuilder builder; - GVariantBuilder ref_data_builder; - GKeyFile *config; - g_autofree char *title = NULL; - - g_autoptr(GHashTable) refs = NULL; - GList *ordered_keys = NULL; - GList *l = NULL; - - g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); - - config = ostree_repo_get_config (repo); - - if (config) - title = g_key_file_get_string (config, "xdg-app", "title", NULL); - - if (title) - g_variant_builder_add (&builder, "{sv}", "xa.title", - g_variant_new_string (title)); - - - g_variant_builder_init (&ref_data_builder, G_VARIANT_TYPE ("a{s(tts)}")); - - if (!ostree_repo_list_refs (repo, NULL, &refs, cancellable, error)) - return FALSE; - - ordered_keys = g_hash_table_get_keys (refs); - ordered_keys = g_list_sort (ordered_keys, (GCompareFunc) strcmp); - - for (l = ordered_keys; l; l = l->next) - { - const char *ref = l->data; - g_autoptr(GFile) root = NULL; - g_autoptr(GFile) metadata = NULL; - guint64 installed_size = 0; - guint64 download_size = 0; - g_autofree char *metadata_contents = NULL; - - if (!ostree_repo_read_commit (repo, ref, &root, NULL, NULL, error)) - return FALSE; - - if (!flatpak_repo_collect_sizes (repo, root, &installed_size, &download_size, cancellable, error)) - return FALSE; - - metadata = g_file_get_child (root, "metadata"); - if (!g_file_load_contents (metadata, cancellable, &metadata_contents, NULL, NULL, NULL)) - metadata_contents = g_strdup (""); - - g_variant_builder_add (&ref_data_builder, "{s(tts)}", - ref, - GUINT64_TO_BE (installed_size), - GUINT64_TO_BE (download_size), - metadata_contents); - } - - g_variant_builder_add (&builder, "{sv}", "xa.cache", - g_variant_new_variant (g_variant_builder_end (&ref_data_builder))); - - if (!ostree_repo_regenerate_summary (repo, g_variant_builder_end (&builder), - cancellable, error)) - return FALSE; - - if (gpg_key_ids) - { - if (!ostree_repo_add_gpg_signature_summary (repo, - gpg_key_ids, - gpg_homedir, - cancellable, - error)) - return FALSE; - } - - return TRUE; -} - -gboolean -flatpak_mtree_create_root (OstreeRepo *repo, - OstreeMutableTree *mtree, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GVariant) dirmeta = NULL; - g_autoptr(GFileInfo) file_info = g_file_info_new (); - g_autofree guchar *csum; - g_autofree char *checksum = NULL; - - g_file_info_set_name (file_info, "/"); - g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY); - g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0); - g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0); - g_file_info_set_attribute_uint32 (file_info, "unix::mode", 040755); - - dirmeta = ostree_create_directory_metadata (file_info, NULL); - if (!ostree_repo_write_metadata (repo, OSTREE_OBJECT_TYPE_DIR_META, NULL, - dirmeta, &csum, cancellable, error)) - return FALSE; - - checksum = ostree_checksum_from_bytes (csum); - ostree_mutable_tree_set_metadata_checksum (mtree, checksum); - - return TRUE; -} - -static OstreeRepoCommitFilterResult -commit_filter (OstreeRepo *repo, - const char *path, - GFileInfo *file_info, - gpointer user_data) -{ - guint current_mode; - - /* No user info */ - g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0); - g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0); - - /* No setuid */ - current_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); - g_file_info_set_attribute_uint32 (file_info, "unix::mode", current_mode & ~07000); - - return OSTREE_REPO_COMMIT_FILTER_ALLOW; -} - -static gboolean -validate_component (FlatpakXml *component, - const char *ref, - const char *id, - char **tags, - const char *runtime, - const char *sdk) -{ - FlatpakXml *bundle, *text, *prev, *id_node, *id_text_node, *metadata, *value; - g_autofree char *id_text = NULL; - int i; - - if (g_strcmp0 (component->element_name, "component") != 0) - return FALSE; - - id_node = flatpak_xml_find (component, "id", NULL); - if (id_node == NULL) - return FALSE; - - id_text_node = flatpak_xml_find (id_node, NULL, NULL); - if (id_text_node == NULL || id_text_node->text == NULL) - return FALSE; - - id_text = g_strstrip (g_strdup (id_text_node->text)); - if (!g_str_has_prefix (id_text, id) || - !g_str_has_suffix (id_text, ".desktop")) - { - g_warning ("Invalid id %s", id_text); - return FALSE; - } - - while ((bundle = flatpak_xml_find (component, "bundle", &prev)) != NULL) - flatpak_xml_free (flatpak_xml_unlink (component, bundle)); - - bundle = flatpak_xml_new ("bundle"); - bundle->attribute_names = g_new0 (char *, 2 * 4); - bundle->attribute_values = g_new0 (char *, 2 * 4); - bundle->attribute_names[0] = g_strdup ("type"); - bundle->attribute_values[0] = g_strdup ("xdg-app"); - - i = 1; - if (runtime) - { - bundle->attribute_names[i] = g_strdup ("runtime"); - bundle->attribute_values[i] = g_strdup (runtime); - i++; - } - - if (sdk) - { - bundle->attribute_names[i] = g_strdup ("sdk"); - bundle->attribute_values[i] = g_strdup (sdk); - i++; - } - - text = flatpak_xml_new (NULL); - text->text = g_strdup (ref); - flatpak_xml_add (bundle, text); - - flatpak_xml_add (component, flatpak_xml_new_text (" ")); - flatpak_xml_add (component, bundle); - flatpak_xml_add (component, flatpak_xml_new_text ("\n ")); - - if (tags != NULL && tags[0] != NULL) - { - metadata = flatpak_xml_find (component, "metadata", NULL); - if (metadata == NULL) - { - metadata = flatpak_xml_new ("metadata"); - metadata->attribute_names = g_new0 (char *, 1); - metadata->attribute_values = g_new0 (char *, 1); - - flatpak_xml_add (component, flatpak_xml_new_text (" ")); - flatpak_xml_add (component, metadata); - flatpak_xml_add (component, flatpak_xml_new_text ("\n ")); - } - - value = flatpak_xml_new ("value"); - value->attribute_names = g_new0 (char *, 2); - value->attribute_values = g_new0 (char *, 2); - value->attribute_names[0] = g_strdup ("key"); - value->attribute_values[0] = g_strdup ("X-Flatpak-Tags"); - flatpak_xml_add (metadata, flatpak_xml_new_text ("\n ")); - flatpak_xml_add (metadata, value); - flatpak_xml_add (metadata, flatpak_xml_new_text ("\n ")); - - text = flatpak_xml_new (NULL); - text->text = g_strjoinv (",", tags); - flatpak_xml_add (value, text); - - } - - return TRUE; -} - -gboolean -flatpak_appstream_xml_migrate (FlatpakXml *source, - FlatpakXml *dest, - const char *ref, - const char *id, - GKeyFile *metadata) -{ - FlatpakXml *source_components; - FlatpakXml *dest_components; - FlatpakXml *component; - FlatpakXml *prev_component; - gboolean migrated = FALSE; - - g_auto(GStrv) tags = NULL; - g_autofree const char *runtime = NULL; - g_autofree const char *sdk = NULL; - const char *group; - - if (source->first_child == NULL || - source->first_child->next_sibling != NULL || - g_strcmp0 (source->first_child->element_name, "components") != 0) - return FALSE; - - if (g_str_has_prefix (ref, "app/")) - group = "Application"; - else - group = "Runtime"; - - tags = g_key_file_get_string_list (metadata, group, "tags", NULL, NULL); - runtime = g_key_file_get_string (metadata, group, "runtime", NULL); - sdk = g_key_file_get_string (metadata, group, "sdk", NULL); - - source_components = source->first_child; - dest_components = dest->first_child; - - component = source_components->first_child; - prev_component = NULL; - while (component != NULL) - { - FlatpakXml *next = component->next_sibling; - - if (validate_component (component, ref, id, tags, runtime, sdk)) - { - flatpak_xml_add (dest_components, - flatpak_xml_unlink (component, prev_component)); - migrated = TRUE; - } - else - { - prev_component = component; - } - - component = next; - } - - return migrated; -} - -static gboolean -copy_icon (const char *id, - GFile *root, - GFile *dest, - const char *size, - GError **error) -{ - g_autofree char *icon_name = g_strconcat (id, ".png", NULL); - - g_autoptr(GFile) icons_dir = - g_file_resolve_relative_path (root, - "files/share/app-info/icons/xdg-app"); - g_autoptr(GFile) size_dir = g_file_get_child (icons_dir, size); - g_autoptr(GFile) icon_file = g_file_get_child (size_dir, icon_name); - g_autoptr(GFile) dest_dir = g_file_get_child (dest, "icons"); - g_autoptr(GFile) dest_size_dir = g_file_get_child (dest_dir, size); - g_autoptr(GFile) dest_file = g_file_get_child (dest_size_dir, icon_name); - g_autoptr(GInputStream) in = NULL; - g_autoptr(GOutputStream) out = NULL; - gssize n_bytes_written; - - in = (GInputStream *) g_file_read (icon_file, NULL, error); - if (!in) - return FALSE; - - if (!gs_file_ensure_directory (dest_size_dir, TRUE, NULL, error)) - return FALSE; - - out = (GOutputStream *) g_file_replace (dest_file, NULL, FALSE, - G_FILE_CREATE_REPLACE_DESTINATION, - NULL, error); - if (!out) - return FALSE; - - n_bytes_written = g_output_stream_splice (out, in, - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, - NULL, error); - if (n_bytes_written < 0) - return FALSE; - - return TRUE; -} - -static gboolean -extract_appstream (OstreeRepo *repo, - FlatpakXml *appstream_root, - const char *ref, - const char *id, - GFile *dest, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) root = NULL; - g_autoptr(GFile) xmls_dir = NULL; - g_autoptr(GFile) appstream_file = NULL; - g_autoptr(GFile) metadata = NULL; - g_autofree char *appstream_basename = NULL; - g_autoptr(GInputStream) in = NULL; - g_autoptr(FlatpakXml) xml_root = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - - if (!ostree_repo_read_commit (repo, ref, &root, NULL, NULL, error)) - return FALSE; - - keyfile = g_key_file_new (); - metadata = g_file_get_child (root, "metadata"); - if (g_file_query_exists (metadata, cancellable)) - { - g_autofree char *content = NULL; - gsize len; - - if (!g_file_load_contents (metadata, cancellable, &content, &len, NULL, error)) - return FALSE; - - if (!g_key_file_load_from_data (keyfile, content, len, G_KEY_FILE_NONE, error)) - return FALSE; - } - - xmls_dir = g_file_resolve_relative_path (root, "files/share/app-info/xmls"); - appstream_basename = g_strconcat (id, ".xml.gz", NULL); - appstream_file = g_file_get_child (xmls_dir, appstream_basename); - - in = (GInputStream *) g_file_read (appstream_file, cancellable, error); - if (!in) - return FALSE; - - xml_root = flatpak_xml_parse (in, TRUE, cancellable, error); - if (xml_root == NULL) - return FALSE; - - if (flatpak_appstream_xml_migrate (xml_root, appstream_root, - ref, id, keyfile)) - { - g_autoptr(GError) my_error = NULL; - FlatpakXml *components = appstream_root->first_child; - FlatpakXml *component = components->first_child; - - while (component != NULL) - { - FlatpakXml *component_id, *component_id_text_node; - g_autofree char *component_id_text = NULL; - - if (g_strcmp0 (component->element_name, "component") != 0) - { - component = component->next_sibling; - continue; - } - - component_id = flatpak_xml_find (component, "id", NULL); - component_id_text_node = flatpak_xml_find (component_id, NULL, NULL); - - component_id_text = g_strstrip (g_strdup (component_id_text_node->text)); - if (!g_str_has_suffix (component_id_text, ".desktop")) - { - component = component->next_sibling; - continue; - } - - g_print ("Extracting icons for component %s\n", component_id_text); - component_id_text[strlen (component_id_text) - strlen (".desktop")] = 0; - - if (!copy_icon (component_id_text, root, dest, "64x64", &my_error)) - { - g_print ("Error copying 64x64 icon: %s\n", my_error->message); - g_clear_error (&my_error); - } - if (!copy_icon (component_id_text, root, dest, "128x128", &my_error)) - { - g_print ("Error copying 128x128 icon: %s\n", my_error->message); - g_clear_error (&my_error); - } - - component = component->next_sibling; - } - } - - return TRUE; -} - -FlatpakXml * -flatpak_appstream_xml_new (void) -{ - FlatpakXml *appstream_root = NULL; - FlatpakXml *appstream_components; - - appstream_root = flatpak_xml_new ("root"); - appstream_components = flatpak_xml_new ("components"); - flatpak_xml_add (appstream_root, appstream_components); - flatpak_xml_add (appstream_components, flatpak_xml_new_text ("\n ")); - - appstream_components->attribute_names = g_new0 (char *, 3); - appstream_components->attribute_values = g_new0 (char *, 3); - appstream_components->attribute_names[0] = g_strdup ("version"); - appstream_components->attribute_values[0] = g_strdup ("0.8"); - appstream_components->attribute_names[1] = g_strdup ("origin"); - appstream_components->attribute_values[1] = g_strdup ("xdg-app"); - - return appstream_root; -} - -GBytes * -flatpak_appstream_xml_root_to_data (FlatpakXml *appstream_root, - GError **error) -{ - g_autoptr(GString) xml = NULL; - g_autoptr(GZlibCompressor) compressor = NULL; - g_autoptr(GOutputStream) out2 = NULL; - g_autoptr(GOutputStream) out = NULL; - - flatpak_xml_add (appstream_root->first_child, flatpak_xml_new_text ("\n")); - - xml = g_string_new (""); - flatpak_xml_to_string (appstream_root, xml); - - compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1); - out = g_memory_output_stream_new_resizable (); - out2 = g_converter_output_stream_new (out, G_CONVERTER (compressor)); - if (!g_output_stream_write_all (out2, xml->str, xml->len, - NULL, NULL, error)) - return NULL; - if (!g_output_stream_close (out2, NULL, error)) - return NULL; - - return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out)); -} - -gboolean -flatpak_repo_generate_appstream (OstreeRepo *repo, - const char **gpg_key_ids, - const char *gpg_homedir, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GHashTable) all_refs = NULL; - g_autoptr(GHashTable) arches = NULL; - GHashTableIter iter; - gpointer key; - gpointer value; - gboolean skip_commit = FALSE; - - arches = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - - if (!ostree_repo_list_refs (repo, - NULL, - &all_refs, - cancellable, - error)) - return FALSE; - - g_hash_table_iter_init (&iter, all_refs); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - const char *ref = key; - const char *arch; - g_auto(GStrv) split = NULL; - - split = flatpak_decompose_ref (ref, NULL); - if (!split) - continue; - - arch = split[2]; - if (!g_hash_table_contains (arches, arch)) - g_hash_table_insert (arches, g_strdup (arch), GINT_TO_POINTER (1)); - } - - g_hash_table_iter_init (&iter, arches); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - GHashTableIter iter2; - const char *arch = key; - g_autofree char *tmpdir = g_strdup ("/tmp/xdg-app-appstream-XXXXXX"); - g_autoptr(FlatpakTempDir) tmpdir_file = NULL; - g_autoptr(GFile) appstream_file = NULL; - g_autoptr(GFile) root = NULL; - g_autoptr(OstreeMutableTree) mtree = NULL; - g_autofree char *commit_checksum = NULL; - OstreeRepoTransactionStats stats; - g_autoptr(OstreeRepoCommitModifier) modifier = NULL; - g_autofree char *parent = NULL; - g_autofree char *branch = NULL; - g_autoptr(FlatpakXml) appstream_root = NULL; - g_autoptr(GBytes) xml_data = NULL; - - if (g_mkdtemp (tmpdir) == NULL) - return flatpak_fail (error, "Can't create temporary directory"); - - tmpdir_file = g_file_new_for_path (tmpdir); - - appstream_root = flatpak_appstream_xml_new (); - - g_hash_table_iter_init (&iter2, all_refs); - while (g_hash_table_iter_next (&iter2, &key, &value)) - { - const char *ref = key; - g_auto(GStrv) split = NULL; - g_autoptr(GError) my_error = NULL; - - split = flatpak_decompose_ref (ref, NULL); - if (!split) - continue; - - if (strcmp (split[2], arch) != 0) - continue; - - if (!extract_appstream (repo, appstream_root, - ref, split[1], tmpdir_file, - cancellable, &my_error)) - { - g_print ("No appstream data for %s\n", ref); - continue; - } - } - - xml_data = flatpak_appstream_xml_root_to_data (appstream_root, error); - if (xml_data == NULL) - return FALSE; - - appstream_file = g_file_get_child (tmpdir_file, "appstream.xml.gz"); - - if (!g_file_replace_contents (appstream_file, - g_bytes_get_data (xml_data, NULL), - g_bytes_get_size (xml_data), - NULL, - FALSE, - G_FILE_CREATE_NONE, - NULL, - cancellable, - error)) - return FALSE; - - if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) - return FALSE; - - branch = g_strdup_printf ("appstream/%s", arch); - - if (!ostree_repo_resolve_rev (repo, branch, TRUE, &parent, error)) - goto out; - - mtree = ostree_mutable_tree_new (); - - modifier = ostree_repo_commit_modifier_new (OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS, - (OstreeRepoCommitFilter) commit_filter, NULL, NULL); - - if (!ostree_repo_write_directory_to_mtree (repo, G_FILE (tmpdir_file), mtree, modifier, cancellable, error)) - goto out; - - if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error)) - goto out; - - - /* No need to commit if nothing changed */ - if (parent) - { - g_autoptr(GFile) parent_root; - - if (!ostree_repo_read_commit (repo, parent, &parent_root, NULL, cancellable, error)) - goto out; - - if (g_file_equal (root, parent_root)) - skip_commit = TRUE; - } - - if (!skip_commit) - { - if (!ostree_repo_write_commit (repo, parent, "Update", NULL, NULL, - OSTREE_REPO_FILE (root), - &commit_checksum, cancellable, error)) - goto out; - - if (gpg_key_ids) - { - int i; - - for (i = 0; gpg_key_ids[i] != NULL; i++) - { - const char *keyid = gpg_key_ids[i]; - - if (!ostree_repo_sign_commit (repo, - commit_checksum, - keyid, - gpg_homedir, - cancellable, - error)) - goto out; - } - } - - ostree_repo_transaction_set_ref (repo, NULL, branch, commit_checksum); - - if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error)) - goto out; - } - else - { - ostree_repo_abort_transaction (repo, cancellable, NULL); - } - } - - return TRUE; - -out: - ostree_repo_abort_transaction (repo, cancellable, NULL); - return FALSE; -} - -void -flatpak_extension_free (FlatpakExtension *extension) -{ - g_free (extension->id); - g_free (extension->installed_id); - g_free (extension->ref); - g_free (extension->directory); - g_free (extension); -} - -static FlatpakExtension * -flatpak_extension_new (const char *id, - const char *extension, - const char *arch, - const char *branch, - const char *directory) -{ - FlatpakExtension *ext = g_new0 (FlatpakExtension, 1); - - ext->id = g_strdup (id); - ext->installed_id = g_strdup (extension); - ext->ref = g_build_filename ("runtime", extension, arch, branch, NULL); - ext->directory = g_strdup (directory); - return ext; -} - -GList * -flatpak_list_extensions (GKeyFile *metakey, - const char *arch, - const char *default_branch) -{ - g_auto(GStrv) groups = NULL; - int i; - GList *res; - - res = NULL; - - if (arch == NULL) - arch = flatpak_get_arch (); - - groups = g_key_file_get_groups (metakey, NULL); - for (i = 0; groups[i] != NULL; i++) - { - FlatpakExtension *ext; - char *extension; - - if (g_str_has_prefix (groups[i], "Extension ") && - *(extension = (groups[i] + strlen ("Extension "))) != 0) - { - g_autofree char *directory = g_key_file_get_string (metakey, groups[i], "directory", NULL); - g_autofree char *version = g_key_file_get_string (metakey, groups[i], "version", NULL); - g_autofree char *ref = NULL; - const char *branch; - g_autoptr(GFile) deploy = NULL; - - if (directory == NULL) - continue; - - if (version) - branch = version; - else - branch = default_branch; - - ref = g_build_filename ("runtime", extension, arch, branch, NULL); - - deploy = flatpak_find_deploy_dir_for_ref (ref, NULL, NULL); - /* Prefer a full extension (org.freedesktop.Locale) over subdirectory ones (org.freedesktop.Locale.sv) */ - if (deploy != NULL) - { - ext = flatpak_extension_new (extension, extension, arch, branch, directory); - res = g_list_prepend (res, ext); - } - else if (g_key_file_get_boolean (metakey, groups[i], - "subdirectories", NULL)) - { - g_autofree char *prefix = g_strconcat (extension, ".", NULL); - g_auto(GStrv) refs = NULL; - int j; - - refs = flatpak_list_deployed_refs ("runtime", prefix, arch, branch, - NULL, NULL); - for (j = 0; refs != NULL && refs[j] != NULL; j++) - { - g_autofree char *extended_dir = g_build_filename (directory, refs[j] + strlen (prefix), NULL); - - ext = flatpak_extension_new (extension, refs[j], arch, branch, extended_dir); - res = g_list_prepend (res, ext); - } - } - } - } - - return res; -} - - -typedef struct -{ - FlatpakXml *current; -} XmlData; - -FlatpakXml * -flatpak_xml_new (const gchar *element_name) -{ - FlatpakXml *node = g_new0 (FlatpakXml, 1); - - node->element_name = g_strdup (element_name); - return node; -} - -FlatpakXml * -flatpak_xml_new_text (const gchar *text) -{ - FlatpakXml *node = g_new0 (FlatpakXml, 1); - - node->text = g_strdup (text); - return node; -} - -void -flatpak_xml_add (FlatpakXml *parent, FlatpakXml *node) -{ - node->parent = parent; - - if (parent->first_child == NULL) - parent->first_child = node; - else - parent->last_child->next_sibling = node; - parent->last_child = node; -} - -static void -xml_start_element (GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, - GError **error) -{ - XmlData *data = user_data; - FlatpakXml *node; - - node = flatpak_xml_new (element_name); - node->attribute_names = g_strdupv ((char **) attribute_names); - node->attribute_values = g_strdupv ((char **) attribute_values); - - flatpak_xml_add (data->current, node); - data->current = node; -} - -static void -xml_end_element (GMarkupParseContext *context, - const gchar *element_name, - gpointer user_data, - GError **error) -{ - XmlData *data = user_data; - - data->current = data->current->parent; -} - -static void -xml_text (GMarkupParseContext *context, - const gchar *text, - gsize text_len, - gpointer user_data, - GError **error) -{ - XmlData *data = user_data; - FlatpakXml *node; - - node = flatpak_xml_new (NULL); - node->text = g_strndup (text, text_len); - flatpak_xml_add (data->current, node); -} - -static void -xml_passthrough (GMarkupParseContext *context, - const gchar *passthrough_text, - gsize text_len, - gpointer user_data, - GError **error) -{ -} - -static GMarkupParser xml_parser = { - xml_start_element, - xml_end_element, - xml_text, - xml_passthrough, - NULL -}; - -void -flatpak_xml_free (FlatpakXml *node) -{ - FlatpakXml *child; - - if (node == NULL) - return; - - child = node->first_child; - while (child != NULL) - { - FlatpakXml *next = child->next_sibling; - flatpak_xml_free (child); - child = next; - } - - g_free (node->element_name); - g_free (node->text); - g_strfreev (node->attribute_names); - g_strfreev (node->attribute_values); - g_free (node); -} - - -void -flatpak_xml_to_string (FlatpakXml *node, GString *res) -{ - int i; - FlatpakXml *child; - - if (node->parent == NULL) - g_string_append (res, "\n"); - - if (node->element_name) - { - if (node->parent != NULL) - { - g_string_append (res, "<"); - g_string_append (res, node->element_name); - if (node->attribute_names) - { - for (i = 0; node->attribute_names[i] != NULL; i++) - { - g_string_append_printf (res, " %s=\"%s\"", - node->attribute_names[i], - node->attribute_values[i]); - } - } - if (node->first_child == NULL) - g_string_append (res, "/>"); - else - g_string_append (res, ">"); - } - - child = node->first_child; - while (child != NULL) - { - flatpak_xml_to_string (child, res); - child = child->next_sibling; - } - if (node->parent != NULL) - { - if (node->first_child != NULL) - g_string_append_printf (res, "", node->element_name); - } - - } - else if (node->text) - { - g_autofree char *escaped = g_markup_escape_text (node->text, -1); - g_string_append (res, escaped); - } -} - -FlatpakXml * -flatpak_xml_unlink (FlatpakXml *node, - FlatpakXml *prev_sibling) -{ - FlatpakXml *parent = node->parent; - - if (parent == NULL) - return node; - - if (parent->first_child == node) - parent->first_child = node->next_sibling; - - if (parent->last_child == node) - parent->last_child = prev_sibling; - - if (prev_sibling) - prev_sibling->next_sibling = node->next_sibling; - - node->parent = NULL; - node->next_sibling = NULL; - - return node; -} - -FlatpakXml * -flatpak_xml_find (FlatpakXml *node, - const char *type, - FlatpakXml **prev_child_out) -{ - FlatpakXml *child = NULL; - FlatpakXml *prev_child = NULL; - - child = node->first_child; - prev_child = NULL; - while (child != NULL) - { - FlatpakXml *next = child->next_sibling; - - if (g_strcmp0 (child->element_name, type) == 0) - { - if (prev_child_out) - *prev_child_out = prev_child; - return child; - } - - prev_child = child; - child = next; - } - - return NULL; -} - - -FlatpakXml * -flatpak_xml_parse (GInputStream *in, - gboolean compressed, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GInputStream) real_in = NULL; - g_autoptr(FlatpakXml) xml_root = NULL; - XmlData data = { 0 }; - char buffer[32 * 1024]; - gssize len; - g_autoptr(GMarkupParseContext) ctx = NULL; - - if (compressed) - { - g_autoptr(GZlibDecompressor) decompressor = NULL; - decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); - real_in = g_converter_input_stream_new (in, G_CONVERTER (decompressor)); - } - else - { - real_in = g_object_ref (in); - } - - xml_root = flatpak_xml_new ("root"); - data.current = xml_root; - - ctx = g_markup_parse_context_new (&xml_parser, - G_MARKUP_PREFIX_ERROR_POSITION, - &data, - NULL); - - while ((len = g_input_stream_read (real_in, buffer, sizeof (buffer), - cancellable, error)) > 0) - { - if (!g_markup_parse_context_parse (ctx, buffer, len, error)) - return NULL; - } - - if (len < 0) - return NULL; - - return g_steal_pointer (&xml_root); -} - -#define OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "(uayttay)" -#define OSTREE_STATIC_DELTA_FALLBACK_FORMAT "(yaytt)" -#define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")" - -static inline guint64 -maybe_swap_endian_u64 (gboolean swap, - guint64 v) -{ - if (!swap) - return v; - return GUINT64_SWAP_LE_BE (v); -} - -static guint64 -flatpak_bundle_get_installed_size (GVariant *bundle, gboolean byte_swap) -{ - guint64 total_size = 0, total_usize = 0; - - g_autoptr(GVariant) meta_entries = NULL; - guint i, n_parts; - - g_variant_get_child (bundle, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries); - n_parts = g_variant_n_children (meta_entries); - g_print ("Number of parts: %u\n", n_parts); - - for (i = 0; i < n_parts; i++) - { - guint32 version; - guint64 size, usize; - g_autoptr(GVariant) objects = NULL; - - g_variant_get_child (meta_entries, i, "(u@aytt@ay)", - &version, NULL, &size, &usize, &objects); - - total_size += maybe_swap_endian_u64 (byte_swap, size); - total_usize += maybe_swap_endian_u64 (byte_swap, usize); - } - - return total_usize; -} - -GVariant * -flatpak_bundle_load (GFile *file, - char **commit, - char **ref, - char **origin, - guint64 *installed_size, - GBytes **gpg_keys, - GError **error) -{ - g_autoptr(GVariant) delta = NULL; - g_autoptr(GVariant) metadata = NULL; - g_autoptr(GBytes) bytes = NULL; - g_autoptr(GVariant) to_csum_v = NULL; - guint8 endianness_char; - gboolean byte_swap = FALSE; - - GMappedFile *mfile = g_mapped_file_new (gs_file_get_path_cached (file), FALSE, error); - - if (mfile == NULL) - return NULL; - - bytes = g_mapped_file_get_bytes (mfile); - g_mapped_file_unref (mfile); - - delta = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), bytes, FALSE); - g_variant_ref_sink (delta); - - to_csum_v = g_variant_get_child_value (delta, 3); - if (!ostree_validate_structureof_csum_v (to_csum_v, error)) - return NULL; - - if (commit) - *commit = ostree_checksum_from_bytes_v (to_csum_v); - - if (installed_size) - *installed_size = flatpak_bundle_get_installed_size (delta, byte_swap); - - metadata = g_variant_get_child_value (delta, 0); - - if (g_variant_lookup (metadata, "ostree.endianness", "y", &endianness_char)) - { - int file_byte_order = G_BYTE_ORDER; - switch (endianness_char) - { - case 'l': - file_byte_order = G_LITTLE_ENDIAN; - break; - - case 'B': - file_byte_order = G_BIG_ENDIAN; - break; - - default: - break; - } - byte_swap = (G_BYTE_ORDER != file_byte_order); - } - - - if (ref != NULL) - { - if (!g_variant_lookup (metadata, "ref", "s", ref)) - { - flatpak_fail (error, "Invalid bundle, no ref in metadata"); - return NULL; - } - } - - if (origin != NULL) - { - if (!g_variant_lookup (metadata, "origin", "s", origin)) - *origin = NULL; - } - - if (gpg_keys != NULL) - { - g_autoptr(GVariant) gpg_value = g_variant_lookup_value (metadata, "gpg-keys", - G_VARIANT_TYPE ("ay")); - if (gpg_value) - { - gsize n_elements; - const char *data = g_variant_get_fixed_array (gpg_value, &n_elements, 1); - *gpg_keys = g_bytes_new (data, n_elements); - } - else - { - *gpg_keys = NULL; - } - } - - /* Make a copy of the data so we can return it after freeing the file */ - return g_variant_new_from_bytes (g_variant_get_type (metadata), - g_bytes_new (g_variant_get_data (metadata), - g_variant_get_size (metadata)), - FALSE); -} - -gboolean -flatpak_pull_from_bundle (OstreeRepo *repo, - GFile *file, - const char *remote, - const char *ref, - gboolean require_gpg_signature, - GCancellable *cancellable, - GError **error) -{ - g_autofree char *metadata_contents = NULL; - g_autofree char *to_checksum = NULL; - - g_autoptr(GFile) root = NULL; - g_autoptr(GFile) metadata_file = NULL; - g_autoptr(GInputStream) in = NULL; - g_autoptr(OstreeGpgVerifyResult) gpg_result = NULL; - g_autoptr(GError) my_error = NULL; - g_autoptr(GVariant) metadata = NULL; - gboolean metadata_valid; - - metadata = flatpak_bundle_load (file, &to_checksum, NULL, NULL, NULL, NULL, error); - if (metadata == NULL) - return FALSE; - - g_variant_lookup (metadata, "metadata", "s", &metadata_contents); - - if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) - return FALSE; - - ostree_repo_transaction_set_ref (repo, remote, ref, to_checksum); - - if (!ostree_repo_static_delta_execute_offline (repo, - file, - FALSE, - cancellable, - error)) - return FALSE; - - gpg_result = ostree_repo_verify_commit_ext (repo, to_checksum, - NULL, NULL, cancellable, &my_error); - if (gpg_result == NULL) - { - /* NOT_FOUND means no gpg signature, we ignore this *if* there - * is no gpg key specified in the bundle or by the user */ - if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && - !require_gpg_signature) - { - g_clear_error (&my_error); - } - else - { - g_propagate_error (error, g_steal_pointer (&my_error)); - return FALSE; - } - } - else - { - /* If there is no valid gpg signature we fail, unless there is no gpg - key specified (on the command line or in the file) because then we - trust the source bundle. */ - if (ostree_gpg_verify_result_count_valid (gpg_result) == 0 && - require_gpg_signature) - return flatpak_fail (error, "GPG signatures found, but none are in trusted keyring"); - } - - if (!ostree_repo_read_commit (repo, to_checksum, &root, NULL, NULL, error)) - return FALSE; - - if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) - return FALSE; - - /* We ensure that the actual installed metadata matches the one in the - header, because you may have made decisions on wheter to install it or not - based on that data. */ - metadata_file = g_file_resolve_relative_path (root, "metadata"); - in = (GInputStream *) g_file_read (metadata_file, cancellable, NULL); - if (in != NULL) - { - g_autoptr(GMemoryOutputStream) data_stream = (GMemoryOutputStream *) g_memory_output_stream_new_resizable (); - - if (g_output_stream_splice (G_OUTPUT_STREAM (data_stream), in, - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, - cancellable, error) < 0) - return FALSE; - - /* Null terminate */ - g_output_stream_write (G_OUTPUT_STREAM (data_stream), "\0", 1, NULL, NULL); - - metadata_valid = - metadata_contents != NULL && - strcmp (metadata_contents, g_memory_output_stream_get_data (data_stream)) == 0; - } - else - { - metadata_valid = (metadata_contents == NULL); - } - - if (!metadata_valid) - { - /* Immediately remove this broken commit */ - ostree_repo_set_ref_immediate (repo, remote, ref, NULL, cancellable, error); - return flatpak_fail (error, "Metadata in header and app are inconsistent"); - } - - return TRUE; -} - -/* This allocates and locks a subdir of the tmp dir, using an existing - * one with the same prefix if it is not in use already. */ -gboolean -flatpak_allocate_tmpdir (int tmpdir_dfd, - const char *tmpdir_relpath, - const char *tmpdir_prefix, - char **tmpdir_name_out, - int *tmpdir_fd_out, - GLnxLockFile *file_lock_out, - gboolean *reusing_dir_out, - GCancellable *cancellable, - GError **error) -{ - gboolean reusing_dir = FALSE; - g_autofree char *tmpdir_name = NULL; - glnx_fd_close int tmpdir_fd = -1; - - g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; - - /* Look for existing tmpdir (with same prefix) to reuse */ - if (!glnx_dirfd_iterator_init_at (tmpdir_dfd, tmpdir_relpath ? tmpdir_relpath : ".", FALSE, &dfd_iter, error)) - return FALSE; - - while (tmpdir_name == NULL) - { - gs_dirfd_iterator_cleanup GSDirFdIterator child_dfd_iter = { 0, }; - struct dirent *dent; - glnx_fd_close int existing_tmpdir_fd = -1; - g_autoptr(GError) local_error = NULL; - g_autofree char *lock_name = NULL; - - if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) - return FALSE; - - if (dent == NULL) - break; - - if (!g_str_has_prefix (dent->d_name, tmpdir_prefix)) - continue; - - /* Quickly skip non-dirs, if unknown we ignore ENOTDIR when opening instead */ - if (dent->d_type != DT_UNKNOWN && - dent->d_type != DT_DIR) - continue; - - if (!glnx_opendirat (dfd_iter.fd, dent->d_name, FALSE, - &existing_tmpdir_fd, &local_error)) - { - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY)) - { - continue; - } - else - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } - } - - lock_name = g_strconcat (dent->d_name, "-lock", NULL); - - /* We put the lock outside the dir, so we can hold the lock - * until the directory is fully removed */ - if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB, - file_lock_out, &local_error)) - { - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) - { - continue; - } - else - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } - } - - /* Touch the reused directory so that we don't accidentally - * remove it due to being old when cleaning up the tmpdir - */ - (void) futimens (existing_tmpdir_fd, NULL); - - /* We found an existing tmpdir which we managed to lock */ - tmpdir_name = g_strdup (dent->d_name); - tmpdir_fd = glnx_steal_fd (&existing_tmpdir_fd); - reusing_dir = TRUE; - } - - while (tmpdir_name == NULL) - { - g_autofree char *tmpdir_name_template = g_strconcat (tmpdir_prefix, "XXXXXX", NULL); - glnx_fd_close int new_tmpdir_fd = -1; - g_autoptr(GError) local_error = NULL; - g_autofree char *lock_name = NULL; - - /* No existing tmpdir found, create a new */ - - if (!glnx_mkdtempat (tmpdir_dfd, tmpdir_name_template, 0777, error)) - return FALSE; - - if (!glnx_opendirat (tmpdir_dfd, tmpdir_name_template, FALSE, - &new_tmpdir_fd, error)) - return FALSE; - - lock_name = g_strconcat (tmpdir_name_template, "-lock", NULL); - - /* Note, at this point we can race with another process that picks up this - * new directory. If that happens we need to retry, making a new directory. */ - if (!glnx_make_lock_file (tmpdir_dfd, lock_name, LOCK_EX | LOCK_NB, - file_lock_out, &local_error)) - { - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) - { - continue; - } - else - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } - } - - tmpdir_name = g_steal_pointer (&tmpdir_name_template); - tmpdir_fd = glnx_steal_fd (&new_tmpdir_fd); - } - - if (tmpdir_name_out) - *tmpdir_name_out = g_steal_pointer (&tmpdir_name); - - if (tmpdir_fd_out) - *tmpdir_fd_out = glnx_steal_fd (&tmpdir_fd); - - if (reusing_dir_out) - *reusing_dir_out = reusing_dir; - - return TRUE; -} diff --git a/common/xdg-app-utils.h b/common/xdg-app-utils.h deleted file mode 100644 index a2f7e45..0000000 --- a/common/xdg-app-utils.h +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright © 2014 Red Hat, Inc - * - * This program 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 library 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 library. If not, see . - * - * Authors: - * Alexander Larsson - */ - -#ifndef __FLATPAK_UTILS_H__ -#define __FLATPAK_UTILS_H__ - -#include - -#include "libgsystem.h" -#include "libglnx/libglnx.h" -#include -#include -#include "xdg-app-dbus.h" -#include "xdg-app-dir.h" -#include - -gboolean flatpak_fail (GError **error, - const char *format, - ...); - -gint flatpak_strcmp0_ptr (gconstpointer a, - gconstpointer b); - -const char * flatpak_path_match_prefix (const char *pattern, - const char *path); - -const char * flatpak_get_arch (void); - -const char * flatpak_get_bwrap (void); - -GBytes * flatpak_read_stream (GInputStream *in, - gboolean null_terminate, - GError **error); - -gboolean flatpak_variant_save (GFile *dest, - GVariant *variant, - GCancellable *cancellable, - GError **error); -gboolean flatpak_variant_bsearch_str (GVariant *array, - const char *str, - int *out_pos); -gboolean flatpak_summary_lookup_ref (GVariant *summary, - const char *ref, - char **out_checksum); - -gboolean flatpak_has_name_prefix (const char *string, - const char *name); -gboolean flatpak_is_valid_name (const char *string); -gboolean flatpak_is_valid_branch (const char *string); - -char **flatpak_decompose_ref (const char *ref, - GError **error); - -char * flatpak_compose_ref (gboolean app, - const char *name, - const char *branch, - const char *arch, - GError **error); - -char * flatpak_build_untyped_ref (const char *runtime, - const char *branch, - const char *arch); -char * flatpak_build_runtime_ref (const char *runtime, - const char *branch, - const char *arch); -char * flatpak_build_app_ref (const char *app, - const char *branch, - const char *arch); -GFile * flatpak_find_deploy_dir_for_ref (const char *ref, - GCancellable *cancellable, - GError **error); -FlatpakDeploy * flatpak_find_deploy_for_ref (const char *ref, - GCancellable *cancellable, - GError **error); -char ** flatpak_list_deployed_refs (const char *type, - const char *name_prefix, - const char *branch, - const char *arch, - GCancellable *cancellable, - GError **error); - -gboolean flatpak_overlay_symlink_tree (GFile *source, - GFile *destination, - const char *symlink_prefix, - GCancellable *cancellable, - GError **error); -gboolean flatpak_remove_dangling_symlinks (GFile *dir, - GCancellable *cancellable, - GError **error); - -void flatpak_invocation_lookup_app_id (GDBusMethodInvocation *invocation, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -char *flatpak_invocation_lookup_app_id_finish (GDBusMethodInvocation *invocation, - GAsyncResult *result, - GError **error); - -void flatpak_connection_track_name_owners (GDBusConnection *connection); - -#if !GLIB_CHECK_VERSION (2, 40, 0) -static inline gboolean -g_key_file_save_to_file (GKeyFile *key_file, - const gchar *filename, - GError **error) -{ - gchar *contents; - gboolean success; - gsize length; - - contents = g_key_file_to_data (key_file, &length, NULL); - success = g_file_set_contents (filename, contents, length, error); - g_free (contents); - - return success; -} -#endif - -/* Returns the first string in subset that is not in strv */ -static inline const gchar * -g_strv_subset (const gchar * const *strv, - const gchar * const *subset) -{ - int i; - - for (i = 0; subset[i]; i++) - { - const char *key; - - key = subset[i]; - if (!g_strv_contains (strv, key)) - return key; - } - - return NULL; -} - -static inline void -flatpak_auto_unlock_helper (GMutex **mutex) -{ - if (*mutex) - g_mutex_unlock (*mutex); -} - -static inline GMutex * -flatpak_auto_lock_helper (GMutex *mutex) -{ - if (mutex) - g_mutex_lock (mutex); - return mutex; -} - -gint flatpak_mkstempat (int dir_fd, - gchar *tmpl, - int flags, - int mode); - - -typedef struct FlatpakTablePrinter FlatpakTablePrinter; - -FlatpakTablePrinter *flatpak_table_printer_new (void); -void flatpak_table_printer_free (FlatpakTablePrinter *printer); -void flatpak_table_printer_add_column (FlatpakTablePrinter *printer, - const char *text); -void flatpak_table_printer_append_with_comma (FlatpakTablePrinter *printer, - const char *text); -void flatpak_table_printer_finish_row (FlatpakTablePrinter *printer); -void flatpak_table_printer_print (FlatpakTablePrinter *printer); - -gboolean flatpak_repo_set_title (OstreeRepo *repo, - const char *title, - GError **error); -gboolean flatpak_repo_update (OstreeRepo *repo, - const char **gpg_key_ids, - const char *gpg_homedir, - GCancellable *cancellable, - GError **error); -gboolean flatpak_repo_collect_sizes (OstreeRepo *repo, - GFile *root, - guint64 *installed_size, - guint64 *download_size, - GCancellable *cancellable, - GError **error); - -gboolean flatpak_mtree_create_root (OstreeRepo *repo, - OstreeMutableTree *mtree, - GCancellable *cancellable, - GError **error); - -GVariant * flatpak_bundle_load (GFile *file, - char **commit, - char **ref, - char **origin, - guint64 *installed_size, - GBytes **gpg_keys, - GError **error); - -gboolean flatpak_pull_from_bundle (OstreeRepo *repo, - GFile *file, - const char *remote, - const char *ref, - gboolean require_gpg_signature, - GCancellable *cancellable, - GError **error); - - -typedef struct -{ - char *id; - char *installed_id; - char *ref; - char *directory; -} FlatpakExtension; - -void flatpak_extension_free (FlatpakExtension *extension); - -GList *flatpak_list_extensions (GKeyFile *metakey, - const char *arch, - const char *branch); - -gboolean flatpak_spawn (GFile *dir, - char **output, - GError **error, - const gchar *argv0, - va_list args); - -typedef enum { - FLATPAK_CP_FLAGS_NONE = 0, - FLATPAK_CP_FLAGS_MERGE = 1<<0, - FLATPAK_CP_FLAGS_NO_CHOWN = 1<<1, - FLATPAK_CP_FLAGS_MOVE = 1<<2, -} FlatpakCpFlags; - -gboolean flatpak_cp_a (GFile *src, - GFile *dest, - FlatpakCpFlags flags, - GCancellable *cancellable, - GError **error); - - -#define flatpak_autorm_rf _GLIB_CLEANUP (g_autoptr_cleanup_generic_gfree) - -static inline void -flatpak_temp_dir_destroy (void *p) -{ - GFile *dir = p; - - if (dir) - { - gs_shutil_rm_rf (dir, NULL, NULL); - g_object_unref (dir); - } -} - -typedef GFile FlatpakTempDir; - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakTempDir, flatpak_temp_dir_destroy) - -#define AUTOLOCK(name) G_GNUC_UNUSED __attribute__((cleanup (flatpak_auto_unlock_helper))) GMutex * G_PASTE (auto_unlock, __LINE__) = flatpak_auto_lock_helper (&G_LOCK_NAME (name)) - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepo, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMutableTree, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeAsyncProgress, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeGpgVerifyResult, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoCommitModifier, ostree_repo_commit_modifier_unref) - -#ifndef SOUP_AUTOCLEANUPS_H -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupSession, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupMessage, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupRequest, g_object_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SoupURI, soup_uri_free) -#endif - -/* This uses a weird Auto prefix to avoid conflicts with later added autogenerated autoptr support, per: - * https://git.gnome.org/browse/glib/commit/?id=1c6cd5f0a3104aa9b62c7f1d3086181f63e71b59 - */ -typedef XdgAppSessionHelper AutoXdgAppSessionHelper; -G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoXdgAppSessionHelper, g_object_unref) - -typedef struct FlatpakXml FlatpakXml; - -struct FlatpakXml -{ - gchar *element_name; /* NULL == text */ - char **attribute_names; - char **attribute_values; - char *text; - FlatpakXml *parent; - FlatpakXml *first_child; - FlatpakXml *last_child; - FlatpakXml *next_sibling; -}; - -FlatpakXml *flatpak_xml_new (const gchar *element_name); -FlatpakXml *flatpak_xml_new_text (const gchar *text); -void flatpak_xml_add (FlatpakXml *parent, - FlatpakXml *node); -void flatpak_xml_free (FlatpakXml *node); -FlatpakXml *flatpak_xml_parse (GInputStream *in, - gboolean compressed, - GCancellable *cancellable, - GError **error); -void flatpak_xml_to_string (FlatpakXml *node, - GString *res); -FlatpakXml *flatpak_xml_unlink (FlatpakXml *node, - FlatpakXml *prev_sibling); -FlatpakXml *flatpak_xml_find (FlatpakXml *node, - const char *type, - FlatpakXml **prev_child_out); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakXml, flatpak_xml_free); - - -FlatpakXml *flatpak_appstream_xml_new (void); -gboolean flatpak_appstream_xml_migrate (FlatpakXml *source, - FlatpakXml *dest, - const char *ref, - const char *id, - GKeyFile *metadata); -GBytes *flatpak_appstream_xml_root_to_data (FlatpakXml *appstream_root, - GError **error); -gboolean flatpak_repo_generate_appstream (OstreeRepo *repo, - const char **gpg_key_ids, - const char *gpg_homedir, - GCancellable *cancellable, - GError **error); - -gboolean flatpak_allocate_tmpdir (int tmpdir_dfd, - const char *tmpdir_relpath, - const char *tmpdir_prefix, - char **tmpdir_name_out, - int *tmpdir_fd_out, - GLnxLockFile *file_lock_out, - gboolean *reusing_dir_out, - GCancellable *cancellable, - GError **error); - - -#endif /* __FLATPAK_UTILS_H__ */ -- cgit v1.2.1