diff --git a/.gitignore b/.gitignore
index 5ada4804..63375d7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,15 +33,14 @@ gtk-doc.make
flatpak
flatpak-*.tar.xz
flatpak-session-helper
-xdg-document-portal
-xdg-permission-store
-flatpak-builder
+flatpak-portal
*~
profile/flatpak.sh
flatpak-dbus.[ch]
flatpak-document-dbus.[ch]
flatpak-systemd-dbus.[ch]
flatpak-resources.[ch]
+flatpak-portal-dbus.[ch]
flatpak-dbus-proxy
permission-store-dbus.[ch]
flatpak-system-helper
diff --git a/Makefile.am b/Makefile.am
index ce2508ff..d379d2bf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -92,6 +92,7 @@ include data/Makefile.am.inc
include app/Makefile.am.inc
include lib/Makefile.am.inc
include session-helper/Makefile.am.inc
+include portal/Makefile.am.inc
include system-helper/Makefile.am.inc
include dbus-proxy/Makefile.am.inc
include tests/Makefile.am.inc
diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc
index 74db3b47..42bdb25b 100644
--- a/common/Makefile.am.inc
+++ b/common/Makefile.am.inc
@@ -54,8 +54,6 @@ libflatpak_common_la_SOURCES = \
common/flatpak-context.h \
common/flatpak-exports.c \
common/flatpak-exports.h \
- common/flatpak-portal-error.c \
- common/flatpak-portal-error.h \
common/flatpak-utils.c \
common/flatpak-utils.h \
common/flatpak-table-printer.c \
diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c
index 9f5ddd4c..e061e915 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -23,7 +23,6 @@
#include "flatpak-utils.h"
#include "lib/flatpak-error.h"
#include "flatpak-dir.h"
-#include "flatpak-portal-error.h"
#include "flatpak-oci-registry.h"
#include "flatpak-run.h"
diff --git a/data/Makefile.am.inc b/data/Makefile.am.inc
index 9bfe6782..511b319c 100644
--- a/data/Makefile.am.inc
+++ b/data/Makefile.am.inc
@@ -1,10 +1,12 @@
introspectiondir = $(datadir)/dbus-1/interfaces
introspection_DATA = \
data/org.freedesktop.Flatpak.xml \
+ data/org.freedesktop.portal.Flatpak.xml \
$(NULL)
EXTRA_DIST += \
data/org.freedesktop.portal.Documents.xml \
data/org.freedesktop.systemd1.xml \
data/org.freedesktop.Flatpak.xml \
+ data/org.freedesktop.portal.Flatpak.xml \
$(NULL)
diff --git a/data/org.freedesktop.portal.Flatpak.xml b/data/org.freedesktop.portal.Flatpak.xml
new file mode 100644
index 00000000..a452b531
--- /dev/null
+++ b/data/org.freedesktop.portal.Flatpak.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/portal/Makefile.am.inc b/portal/Makefile.am.inc
new file mode 100644
index 00000000..a7736351
--- /dev/null
+++ b/portal/Makefile.am.inc
@@ -0,0 +1,39 @@
+libexec_PROGRAMS += \
+ flatpak-portal \
+ $(NULL)
+
+service_in_files += portal/flatpak-portal.service.in
+systemduserunit_DATA += portal/flatpak-portal.service
+
+service_in_files += portal/org.freedesktop.portal.Flatpak.service.in
+dbus_service_DATA += portal/org.freedesktop.portal.Flatpak.service
+
+portal_built_sources = portal/flatpak-portal-dbus.c portal/flatpak-portal-dbus.h
+
+portal/flatpak-portal-dbus.c: data/org.freedesktop.portal.Flatpak.xml Makefile
+ mkdir -p $(builddir)/portal
+ $(AM_V_GEN) $(GDBUS_CODEGEN) \
+ --interface-prefix org.freedesktop.portal \
+ --c-namespace Portal \
+ --generate-c-code $(builddir)/portal/flatpak-portal-dbus \
+ $(srcdir)/data/org.freedesktop.portal.Flatpak.xml \
+ $(NULL)
+
+portal/%-dbus.h: portal/%-dbus.c
+ @true # Built as a side-effect of the rules for the .c
+
+flatpak_portal_SOURCES = \
+ $(portal_built_sources) \
+ portal/flatpak-portal.c \
+ portal/flatpak-portal.h \
+ portal/flatpak-portal-app-info.c \
+ portal/flatpak-portal-app-info.h \
+ portal/flatpak-portal-error.c \
+ portal/flatpak-portal-error.h \
+ $(NULL)
+
+BUILT_SOURCES += $(portal_built_sources)
+CLEANFILES += $(portal_built_sources)
+
+flatpak_portal_LDADD = $(AM_LDADD) $(BASE_LIBS)
+flatpak_portal_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS)
diff --git a/portal/flatpak-portal-app-info.c b/portal/flatpak-portal-app-info.c
new file mode 100644
index 00000000..1776bd69
--- /dev/null
+++ b/portal/flatpak-portal-app-info.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright © 2018 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.1 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 "libglnx/libglnx.h"
+#include "flatpak-portal-app-info.h"
+#include "flatpak-portal-error.h"
+
+G_LOCK_DEFINE (app_infos);
+static GHashTable *app_infos;
+
+static void
+ensure_app_infos (void)
+{
+ if (app_infos == NULL)
+ app_infos = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) g_key_file_unref);
+}
+
+static GKeyFile *
+lookup_cached_app_info_by_sender (const char *sender)
+{
+ GKeyFile *keyfile = NULL;
+
+ G_LOCK (app_infos);
+ keyfile = g_hash_table_lookup (app_infos, sender);
+ if (keyfile)
+ g_key_file_ref (keyfile);
+ G_UNLOCK (app_infos);
+
+ return keyfile;
+}
+
+static void
+invalidate_cached_app_info_by_sender (const char *sender)
+{
+ G_LOCK (app_infos);
+ g_hash_table_remove (app_infos, sender);
+ G_UNLOCK (app_infos);
+}
+
+static void
+add_cached_app_info_by_sender (const char *sender, GKeyFile *keyfile)
+{
+ G_LOCK (app_infos);
+ g_hash_table_add (app_infos, g_key_file_ref (keyfile));
+ G_UNLOCK (app_infos);
+}
+
+
+/* Returns NULL on failure, keyfile with name "" if not sandboxed, and full app-info otherwise */
+static GKeyFile *
+parse_app_id_from_fileinfo (int pid)
+{
+ g_autofree char *root_path = NULL;
+ glnx_autofd int root_fd = -1;
+ glnx_autofd int info_fd = -1;
+ struct stat stat_buf;
+ g_autoptr(GError) local_error = NULL;
+ g_autoptr(GMappedFile) mapped = NULL;
+ g_autoptr(GKeyFile) metadata = NULL;
+
+ root_path = g_strdup_printf ("/proc/%u/root", pid);
+ root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
+ if (root_fd == -1)
+ {
+ /* Not able to open the root dir shouldn't happen. Probably the app died and
+ *we're failing due to /proc/$pid not existing. In that case fail instead
+ of treating this as privileged. */
+ g_debug ("Unable to open %s", root_path);
+ return NULL;
+ }
+
+ metadata = g_key_file_new ();
+
+ info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ if (info_fd == -1)
+ {
+ if (errno == ENOENT)
+ {
+ /* No file => on the host */
+ g_key_file_set_string (metadata, FLATPAK_METADATA_GROUP_APPLICATION,
+ FLATPAK_METADATA_KEY_NAME, "");
+ return g_steal_pointer (&metadata);
+ }
+
+ return NULL; /* Some weird error => failure */
+ }
+
+ if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode))
+ return NULL; /* Some weird fd => failure */
+
+ mapped = g_mapped_file_new_from_fd (info_fd, FALSE, &local_error);
+ if (mapped == NULL)
+ {
+ g_warning ("Can't map .flatpak-info file: %s", local_error->message);
+ return NULL;
+ }
+
+ if (!g_key_file_load_from_data (metadata,
+ g_mapped_file_get_contents (mapped),
+ g_mapped_file_get_length (mapped),
+ G_KEY_FILE_NONE, &local_error))
+ {
+ g_warning ("Can't load .flatpak-info file: %s", local_error->message);
+ return NULL;
+ }
+
+ return g_steal_pointer (&metadata);
+}
+
+GKeyFile *
+flatpak_invocation_lookup_app_info (GDBusMethodInvocation *invocation,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
+ const gchar *sender = g_dbus_method_invocation_get_sender (invocation);
+ g_autoptr(GDBusMessage) msg = NULL;
+ g_autoptr(GDBusMessage) reply = NULL;
+ g_autoptr(GVariantIter) iter = NULL;
+ const char *key;
+ GVariant *value;
+ GKeyFile *keyfile;
+
+ keyfile = lookup_cached_app_info_by_sender (sender);
+ if (keyfile)
+ return keyfile;
+
+ msg = g_dbus_message_new_method_call ("org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionCredentials");
+ g_dbus_message_set_body (msg, g_variant_new ("(s)", sender));
+
+ reply = g_dbus_connection_send_message_with_reply_sync (connection, msg,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+ 30000,
+ NULL,
+ cancellable,
+ error);
+ if (reply == NULL)
+ return NULL;
+
+ if (g_dbus_message_get_message_type (reply) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN)
+ {
+ GVariant *body = g_dbus_message_get_body (reply);
+
+ g_variant_get (body, "(a{sv})", &iter);
+ while (g_variant_iter_loop (iter, "{&sv}", &key, &value))
+ {
+ if (strcmp (key, "ProcessID") == 0)
+ {
+ guint32 pid = g_variant_get_uint32 (value);
+ keyfile = parse_app_id_from_fileinfo (pid);
+ break;
+ }
+ }
+ }
+
+ if (keyfile == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't find peer app id");
+ return NULL;
+ }
+
+ add_cached_app_info_by_sender (sender, keyfile);
+
+ return keyfile;
+}
+
+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);
+
+ if (name[0] == ':' &&
+ strcmp (name, from) == 0 &&
+ strcmp (to, "") == 0)
+ {
+ invalidate_cached_app_info_by_sender (name);
+ }
+}
+
+void
+flatpak_connection_track_name_owners (GDBusConnection *connection)
+{
+ ensure_app_infos ();
+ 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);
+}
diff --git a/portal/flatpak-portal-app-info.h b/portal/flatpak-portal-app-info.h
new file mode 100644
index 00000000..44fb039a
--- /dev/null
+++ b/portal/flatpak-portal-app-info.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2018 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.1 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_PORTAL_APP_INFO_H__
+#define __FLATPAK_PORTAL_APP_INFO_H__
+
+#define FLATPAK_METADATA_GROUP_APPLICATION "Application"
+#define FLATPAK_METADATA_GROUP_RUNTIME "Runtime"
+#define FLATPAK_METADATA_GROUP_INSTANCE "Instance"
+#define FLATPAK_METADATA_GROUP_CONTEXT "Context"
+#define FLATPAK_METADATA_KEY_NAME "name"
+#define FLATPAK_METADATA_KEY_ARCH "arch"
+#define FLATPAK_METADATA_KEY_BRANCH "branch"
+#define FLATPAK_METADATA_KEY_EXTRA_ARGS "extra-args"
+#define FLATPAK_METADATA_KEY_APP_COMMIT "app-commit"
+#define FLATPAK_METADATA_KEY_RUNTIME_COMMIT "runtime-commit"
+#define FLATPAK_METADATA_KEY_SHARED "shared"
+#define FLATPAK_METADATA_KEY_INSTANCE_PATH "instance-path"
+
+GKeyFile * flatpak_invocation_lookup_app_info (GDBusMethodInvocation *invocation,
+ GCancellable *cancellable,
+ GError **error);
+
+void flatpak_connection_track_name_owners (GDBusConnection *connection);
+
+#endif /* __FLATPAK_PORTAL_APP_INFO_H__ */
diff --git a/common/flatpak-portal-error.c b/portal/flatpak-portal-error.c
similarity index 100%
rename from common/flatpak-portal-error.c
rename to portal/flatpak-portal-error.c
diff --git a/common/flatpak-portal-error.h b/portal/flatpak-portal-error.h
similarity index 100%
rename from common/flatpak-portal-error.h
rename to portal/flatpak-portal-error.h
diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c
new file mode 100644
index 00000000..e181520c
--- /dev/null
+++ b/portal/flatpak-portal.c
@@ -0,0 +1,810 @@
+/*
+ * Copyright © 2018 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.1 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
+#include "libglnx/libglnx.h"
+#include "flatpak-portal-dbus.h"
+#include "flatpak-portal.h"
+#include "flatpak-portal-app-info.h"
+#include "flatpak-portal-error.h"
+
+#define IDLE_TIMEOUT_SECS 10*60
+
+static GHashTable *client_pid_data_hash = NULL;
+static GDBusConnection *session_bus = NULL;
+static gboolean no_idle_exit = FALSE;
+static guint name_owner_id = 0;
+static GMainLoop *main_loop;
+static PortalFlatpak *portal;
+static gboolean opt_verbose;
+
+static void
+skeleton_died_cb (gpointer data)
+{
+ g_debug ("skeleton finalized, exiting");
+ g_main_loop_quit (main_loop);
+}
+
+static gboolean
+unref_skeleton_in_timeout_cb (gpointer user_data)
+{
+ static gboolean unreffed = FALSE;
+ g_debug ("unreffing portal main ref");
+ if (!unreffed)
+ {
+ g_object_unref (portal);
+ unreffed = TRUE;
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+unref_skeleton_in_timeout (void)
+{
+ if (name_owner_id)
+ g_bus_unown_name (name_owner_id);
+ name_owner_id = 0;
+
+ /* After we've lost the name or idled we drop the main ref on the helper
+ so that we'll exit when it drops to zero. However, if there are
+ outstanding calls these will keep the refcount up during the
+ execution of them. We do the unref on a timeout to make sure
+ we're completely draining the queue of (stale) requests. */
+ g_timeout_add (500, unref_skeleton_in_timeout_cb, NULL);
+}
+
+static guint idle_timeout_id = 0;
+
+static gboolean
+idle_timeout_cb (gpointer user_data)
+{
+ if (name_owner_id && g_hash_table_size (client_pid_data_hash) == 0)
+ {
+ g_debug ("Idle - unowning name");
+ unref_skeleton_in_timeout ();
+ }
+
+ idle_timeout_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+G_LOCK_DEFINE_STATIC (idle);
+static void
+schedule_idle_callback (void)
+{
+ G_LOCK(idle);
+
+ if (!no_idle_exit)
+ {
+ if (idle_timeout_id != 0)
+ g_source_remove (idle_timeout_id);
+
+ idle_timeout_id = g_timeout_add_seconds (IDLE_TIMEOUT_SECS, idle_timeout_cb, NULL);
+ }
+
+ G_UNLOCK(idle);
+}
+
+typedef struct {
+ GPid pid;
+ char *client;
+ guint child_watch;
+} PidData;
+
+static void
+pid_data_free (PidData *data)
+{
+ g_free (data->client);
+ g_free (data);
+}
+
+static void
+child_watch_died (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+ PidData *pid_data = user_data;
+ g_autoptr(GVariant) signal_variant = NULL;
+
+ g_debug ("Client Pid %d died", pid_data->pid);
+
+ signal_variant = g_variant_ref_sink (g_variant_new ("(uu)", pid, status));
+ g_dbus_connection_emit_signal (session_bus,
+ pid_data->client,
+ "/org/freedesktop/portal/Flatpak",
+ "org.freedesktop.portal.Flatpak",
+ "SpawnExited",
+ signal_variant,
+ NULL);
+
+ /* This frees the pid_data, so be careful */
+ g_hash_table_remove (client_pid_data_hash, GUINT_TO_POINTER(pid_data->pid));
+
+ /* This might have caused us to go to idle (zero children) */
+ schedule_idle_callback ();
+}
+
+typedef struct {
+ int from;
+ int to;
+ int final;
+} FdMapEntry;
+
+typedef struct {
+ FdMapEntry *fd_map;
+ int fd_map_len;
+ gboolean set_tty;
+ int tty;
+} ChildSetupData;
+
+static void
+child_setup_func (gpointer user_data)
+{
+ ChildSetupData *data = (ChildSetupData *)user_data;
+ FdMapEntry *fd_map = data->fd_map;
+ sigset_t set;
+ int i;
+
+ /* Unblock all signals */
+ sigemptyset (&set);
+ if (pthread_sigmask (SIG_SETMASK, &set, NULL) == -1)
+ {
+ g_error ("Failed to unblock signals when starting child");
+ return;
+ }
+
+ /* Reset the handlers for all signals to their defaults. */
+ for (i = 1; i < NSIG; i++)
+ {
+ if (i != SIGSTOP && i != SIGKILL)
+ signal (i, SIG_DFL);
+ }
+
+ for (i = 0; i < data->fd_map_len; i++)
+ {
+ if (fd_map[i].from != fd_map[i].to)
+ {
+ dup2 (fd_map[i].from, fd_map[i].to);
+ close (fd_map[i].from);
+ }
+ }
+
+ /* Second pass in case we needed an inbetween fd value to avoid conflicts */
+ for (i = 0; i < data->fd_map_len; i++)
+ {
+ if (fd_map[i].to != fd_map[i].final)
+ {
+ dup2 (fd_map[i].to, fd_map[i].final);
+ close (fd_map[i].to);
+ }
+ }
+
+ /* We become our own session and process group, because it never makes sense
+ to share the flatpak-session-helper dbus activated process group */
+ setsid ();
+ setpgid (0, 0);
+
+ if (data->set_tty)
+ {
+ /* data->tty is our from fd which is closed at this point.
+ * so locate the destnation fd and use it for the ioctl.
+ */
+ for (i = 0; i < data->fd_map_len; i++)
+ {
+ if (fd_map[i].from == data->tty)
+ {
+ if (ioctl (fd_map[i].final, TIOCSCTTY, 0) == -1)
+ g_debug ("ioctl(%d, TIOCSCTTY, 0) failed: %s",
+ fd_map[i].final, strerror (errno));
+ break;
+ }
+ }
+ }
+}
+
+static gboolean
+is_valid_expose (const char *expose)
+{
+ /* No subdirs or absolute paths */
+ if (strchr (expose, '/'))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+handle_spawn (PortalFlatpak *object,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *fd_list,
+ const gchar *arg_cwd_path,
+ const gchar *const *arg_argv,
+ GVariant *arg_fds,
+ GVariant *arg_envs,
+ guint arg_flags,
+ GVariant *arg_options)
+{
+ g_autoptr(GError) error = NULL;
+ ChildSetupData child_setup_data = { NULL };
+ GPid pid;
+ PidData *pid_data;
+ gsize i, j, n_fds, n_envs;
+ const gint *fds;
+ g_autofree FdMapEntry *fd_map = NULL;
+ gchar **env;
+ gint32 max_fd;
+ GKeyFile *app_info;
+ g_autoptr(GPtrArray) flatpak_argv = g_ptr_array_new_with_free_func (g_free);
+ g_autofree char *app_id = NULL;
+ g_autofree char *branch = NULL;
+ g_autofree char *arch = NULL;
+ g_autofree char *app_commit = NULL;
+ g_autofree char *runtime_commit = NULL;
+ g_autofree char *instance_path = NULL;
+ g_auto(GStrv) extra_args = NULL;
+ g_auto(GStrv) shares = NULL;
+ g_auto(GStrv) sandbox_expose = NULL;
+ g_auto(GStrv) sandbox_expose_ro = NULL;
+ gboolean sandboxed;
+
+ app_info = g_object_get_data (G_OBJECT (invocation), "app-info");
+ g_assert (app_info != NULL);
+
+ app_id = g_key_file_get_string (app_info,
+ FLATPAK_METADATA_GROUP_APPLICATION,
+ FLATPAK_METADATA_KEY_NAME, NULL);
+ g_assert (app_id != NULL);
+ branch = g_key_file_get_string (app_info,
+ FLATPAK_METADATA_GROUP_INSTANCE,
+ FLATPAK_METADATA_KEY_BRANCH, NULL);
+ instance_path = g_key_file_get_string (app_info,
+ FLATPAK_METADATA_GROUP_INSTANCE,
+ FLATPAK_METADATA_KEY_INSTANCE_PATH, NULL);
+ arch = g_key_file_get_string (app_info,
+ FLATPAK_METADATA_GROUP_INSTANCE,
+ FLATPAK_METADATA_KEY_ARCH, NULL);
+ extra_args = g_key_file_get_string_list (app_info,
+ FLATPAK_METADATA_GROUP_INSTANCE,
+ FLATPAK_METADATA_KEY_EXTRA_ARGS, NULL, NULL);
+ app_commit = g_key_file_get_string (app_info,
+ FLATPAK_METADATA_GROUP_INSTANCE,
+ FLATPAK_METADATA_KEY_APP_COMMIT, NULL);
+ runtime_commit = g_key_file_get_string (app_info,
+ FLATPAK_METADATA_GROUP_INSTANCE,
+ FLATPAK_METADATA_KEY_RUNTIME_COMMIT, NULL);
+ shares = g_key_file_get_string_list (app_info, FLATPAK_METADATA_GROUP_CONTEXT,
+ FLATPAK_METADATA_KEY_SHARED, NULL, NULL);
+
+ g_debug ("spawn() called from app: %s", app_id);
+
+ if (*app_id == 0)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "org.freedesktop.portal.Flatpak.Spawn only works in a flatpak");
+ return TRUE;
+ }
+
+ if (*arg_cwd_path == 0)
+ arg_cwd_path = NULL;
+
+ if (arg_argv == NULL || *arg_argv == NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "No command given");
+ return TRUE;
+ }
+
+ g_variant_lookup (arg_options, "sandbox-expose", "^as", &sandbox_expose);
+ g_variant_lookup (arg_options, "sandbox-expose-ro", "^as", &sandbox_expose_ro);
+
+ if (instance_path == NULL &&
+ ((sandbox_expose != NULL && sandbox_expose[0] != NULL) ||
+ (sandbox_expose_ro != NULL && sandbox_expose_ro[0] != NULL)))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid sandbox expose, caller has no instance path");
+ return TRUE;
+ }
+
+ for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++)
+ {
+ const char *expose = sandbox_expose[i];
+ g_debug ("exposing %s", expose);
+ if (!is_valid_expose (expose))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid sandbox expose");
+ return TRUE;
+ }
+ }
+
+ for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
+ {
+ const char *expose = sandbox_expose_ro[i];
+ g_debug ("exposing %s", expose);
+ if (!is_valid_expose (expose))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Invalid sandbox expose");
+ return TRUE;
+ }
+ }
+
+ g_debug ("Running spawn command %s", arg_argv[0]);
+
+ n_fds = 0;
+ fds = NULL;
+ if (fd_list != NULL)
+ {
+ n_fds = g_variant_n_children (arg_fds);
+ fds = g_unix_fd_list_peek_fds (fd_list, NULL);
+ }
+ fd_map = g_new0 (FdMapEntry, n_fds);
+
+ child_setup_data.fd_map = fd_map;
+ child_setup_data.fd_map_len = n_fds;
+
+ max_fd = -1;
+ for (i = 0; i < n_fds; i++)
+ {
+ gint32 handle, fd;
+ g_variant_get_child (arg_fds, i, "{uh}", &fd, &handle);
+ fd_map[i].to = fd;
+ fd_map[i].from = fds[i];
+ fd_map[i].final = fd_map[i].to;
+
+ /* If stdin/out/err is a tty we try to set it as the controlling
+ tty for the app, this way we can use this to run in a terminal. */
+ if ((fd == 0 || fd == 1 || fd == 2) &&
+ !child_setup_data.set_tty &&
+ isatty (fds[i]))
+ {
+ child_setup_data.set_tty = TRUE;
+ child_setup_data.tty = fds[i];
+ }
+
+ max_fd = MAX (max_fd, fd_map[i].to);
+ max_fd = MAX (max_fd, fd_map[i].from);
+ }
+
+ /* We make a second pass over the fds to find if any "to" fd index
+ overlaps an already in use fd (i.e. one in the "from" category
+ that are allocated randomly). If a fd overlaps "to" fd then its
+ a caller issue and not our fault, so we ignore that. */
+ for (i = 0; i < n_fds; i++)
+ {
+ int to_fd = fd_map[i].to;
+ gboolean conflict = FALSE;
+
+ /* At this point we're fine with using "from" values for this
+ value (because we handle to==from in the code), or values
+ that are before "i" in the fd_map (because those will be
+ closed at this point when dup:ing). However, we can't
+ reuse a fd that is in "from" for j > i. */
+ for (j = i + 1; j < n_fds; j++)
+ {
+ int from_fd = fd_map[j].from;
+ if (from_fd == to_fd)
+ {
+ conflict = TRUE;
+ break;
+ }
+ }
+
+ if (conflict)
+ fd_map[i].to = ++max_fd;
+ }
+
+ if (arg_flags & FLATPAK_SPAWN_FLAGS_CLEAR_ENV)
+ {
+ char *empty[] = { NULL };
+ env = g_strdupv (empty);
+ }
+ else
+ env = g_get_environ ();
+
+ n_envs = g_variant_n_children (arg_envs);
+ for (i = 0; i < n_envs; i++)
+ {
+ const char *var = NULL;
+ const char *val = NULL;
+ g_variant_get_child (arg_envs, i, "{&s&s}", &var, &val);
+
+ env = g_environ_setenv (env, var, val, TRUE);
+ }
+
+ g_ptr_array_add (flatpak_argv, g_strdup ("flatpak"));
+ g_ptr_array_add (flatpak_argv, g_strdup ("run"));
+
+ sandboxed = (arg_flags & FLATPAK_SPAWN_FLAGS_SANDBOX) != 0;
+
+ if (sandboxed)
+ g_ptr_array_add (flatpak_argv, g_strdup ("--sandbox"));
+ else
+ {
+ for (i = 0; extra_args != NULL && extra_args[i] != NULL; i++)
+ g_ptr_array_add (flatpak_argv, g_strdup (extra_args[i]));
+ }
+
+ /* Inherit launcher network access from launcher, unless
+ NO_NETWORK set. */
+ if (shares != NULL && g_strv_contains ((const char * const *)shares, "network") &&
+ !(arg_flags & FLATPAK_SPAWN_FLAGS_NO_NETWORK))
+ g_ptr_array_add (flatpak_argv, g_strdup ("--share=network"));
+ else
+ g_ptr_array_add (flatpak_argv, g_strdup ("--unshare=network"));
+
+ if (instance_path)
+ {
+ for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++)
+ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--filesystem=%s/sandbox/%s:rw", instance_path, sandbox_expose[i]));
+ for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
+ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--filesystem=%s/sandbox/%s:ro", instance_path, sandbox_expose_ro[i]));
+ }
+
+ for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++)
+ {
+ const char *expose = sandbox_expose_ro[i];
+ g_debug ("exposing %s", expose);
+ }
+
+ if ((arg_flags & FLATPAK_SPAWN_FLAGS_LATEST_VERSION) == 0)
+ {
+ if (app_commit)
+ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--commit=%s", app_commit));
+ if (runtime_commit)
+ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime-commit=%s", runtime_commit));
+ }
+
+
+ if (arg_argv[0][0] != 0)
+ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--command=%s", arg_argv[0]));
+
+ g_ptr_array_add (flatpak_argv, g_strdup_printf ("%s/%s/%s", app_id, arch ? arch : "", branch ? branch : ""));
+ for (i = 1; arg_argv[i] != NULL; i++)
+ g_ptr_array_add (flatpak_argv, g_strdup (arg_argv[i]));
+ g_ptr_array_add (flatpak_argv, NULL);
+
+ if (opt_verbose)
+ {
+ g_autoptr(GString) cmd = g_string_new ("");
+ int i;
+
+ for (i = 0; flatpak_argv->pdata[i] != NULL; i++)
+ {
+ if (i > 0)
+ g_string_append (cmd, " ");
+ g_string_append (cmd, flatpak_argv->pdata[i]);
+ }
+
+ g_debug ("Starting: %s\n", cmd->str);
+ }
+
+ if (!g_spawn_async_with_pipes (arg_cwd_path,
+ (char **)flatpak_argv->pdata,
+ env,
+ G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
+ child_setup_func, &child_setup_data,
+ &pid,
+ NULL,
+ NULL,
+ NULL,
+ &error))
+ {
+ gint code = G_DBUS_ERROR_FAILED;
+ if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_ACCES))
+ code = G_DBUS_ERROR_ACCESS_DENIED;
+ else if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT))
+ code = G_DBUS_ERROR_FILE_NOT_FOUND;
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, code,
+ "Failed to start command: %s",
+ error->message);
+ return TRUE;
+ }
+
+ pid_data = g_new0 (PidData, 1);
+ pid_data->pid = pid;
+ pid_data->client = g_strdup (g_dbus_method_invocation_get_sender (invocation));
+ pid_data->child_watch = g_child_watch_add_full (G_PRIORITY_DEFAULT,
+ pid,
+ child_watch_died,
+ pid_data,
+ NULL);
+
+ g_debug ("Client Pid is %d", pid_data->pid);
+
+ g_hash_table_replace (client_pid_data_hash, GUINT_TO_POINTER(pid_data->pid),
+ pid_data);
+
+ portal_flatpak_complete_spawn (object, invocation, NULL, pid);
+ return TRUE;
+}
+
+static gboolean
+handle_spawn_signal (PortalFlatpak *object,
+ GDBusMethodInvocation *invocation,
+ guint arg_pid,
+ guint arg_signal,
+ gboolean arg_to_process_group)
+{
+ PidData *pid_data = NULL;
+
+ g_debug ("spawn_signal(%d %d)", arg_pid, arg_signal);
+
+ pid_data = g_hash_table_lookup (client_pid_data_hash, GUINT_TO_POINTER(arg_pid));
+ if (pid_data == NULL ||
+ strcmp (pid_data->client, g_dbus_method_invocation_get_sender (invocation)) != 0)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
+ G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,
+ "No such pid");
+ return TRUE;
+ }
+
+ g_debug ("Sending signal %d to client pid %d", arg_signal, arg_pid);
+
+ if (arg_to_process_group)
+ killpg (pid_data->pid, arg_signal);
+ else
+ kill (pid_data->pid, arg_signal);
+
+ portal_flatpak_complete_spawn_signal (portal, invocation);
+
+ return TRUE;
+}
+
+static gboolean
+authorize_method_handler (GDBusInterfaceSkeleton *interface,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GKeyFile) keyfile = NULL;
+ g_autofree char *app_id = NULL;
+
+ /* Ensure we don't idle exit */
+ schedule_idle_callback ();
+
+ keyfile = flatpak_invocation_lookup_app_info (invocation, NULL, &error);
+ if (keyfile == NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Authorization error: %s", error->message);
+ return FALSE;
+ }
+
+ app_id = g_key_file_get_string (keyfile,
+ FLATPAK_METADATA_GROUP_APPLICATION,
+ FLATPAK_METADATA_KEY_NAME, &error);
+ if (app_id == NULL)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Authorization error: %s", error->message);
+ return FALSE;
+ }
+
+ g_object_set_data_full (G_OBJECT (invocation), "app-info", g_steal_pointer (&keyfile), (GDestroyNotify)g_key_file_unref);
+
+ return TRUE;
+}
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GError *error = NULL;
+
+ g_debug ("Bus acquired, creating skeleton");
+
+ portal = portal_flatpak_skeleton_new ();
+
+ g_object_set_data_full (G_OBJECT(portal), "track-alive", GINT_TO_POINTER(42), skeleton_died_cb);
+
+ g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (portal),
+ G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
+
+ portal_flatpak_set_version (PORTAL_FLATPAK (portal), 1);
+ g_signal_connect (portal, "handle-spawn", G_CALLBACK (handle_spawn), NULL);
+ g_signal_connect (portal, "handle-spawn-signal", G_CALLBACK (handle_spawn_signal), NULL);
+
+ g_signal_connect (portal, "g-authorize-method", G_CALLBACK (authorize_method_handler), NULL);
+
+ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (portal),
+ connection,
+ "/org/freedesktop/portal/Flatpak",
+ &error))
+ {
+ g_warning ("error: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_debug ("Name acquired");
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_debug ("Name lost");
+ unref_skeleton_in_timeout ();
+}
+
+static void
+binary_file_changed_cb (GFileMonitor *file_monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ gpointer data)
+{
+ static gboolean got_it = FALSE;
+
+ if (!got_it)
+ {
+ g_debug ("binary file changed");
+ unref_skeleton_in_timeout ();
+ }
+
+ got_it = TRUE;
+}
+
+
+static void
+message_handler (const gchar *log_domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ /* Make this look like normal console output */
+ if (log_level & G_LOG_LEVEL_DEBUG)
+ g_printerr ("F: %s\n", message);
+ else
+ g_printerr ("%s: %s\n", g_get_prgname (), message);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ gchar exe_path[PATH_MAX+1];
+ ssize_t exe_path_len;
+ gboolean replace;
+ gboolean show_version;
+ GOptionContext *context;
+ GBusNameOwnerFlags flags;
+ g_autoptr(GError) error = NULL;
+ const GOptionEntry options[] = {
+ { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "Replace old daemon.", NULL },
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Enable debug output.", NULL },
+ { "version", 0, 0, G_OPTION_ARG_NONE, &show_version, "Show program version.", NULL},
+ { "no-idle-exit", 0, 0, G_OPTION_ARG_NONE, &no_idle_exit, "Don't exit when idle.", NULL },
+ { NULL }
+ };
+
+ setlocale (LC_ALL, "");
+
+ g_setenv ("GIO_USE_VFS", "local", TRUE);
+
+ g_set_prgname (argv[0]);
+
+ g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, message_handler, NULL);
+
+ context = g_option_context_new ("");
+
+ replace = FALSE;
+ opt_verbose = FALSE;
+ show_version = FALSE;
+
+ g_option_context_set_summary (context, "Flatpak portal");
+ g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
+
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_printerr ("%s: %s", g_get_application_name(), error->message);
+ g_printerr ("\n");
+ g_printerr ("Try \"%s --help\" for more information.",
+ g_get_prgname ());
+ g_printerr ("\n");
+ g_option_context_free (context);
+ return 1;
+ }
+
+ if (show_version)
+ {
+ g_print (PACKAGE_STRING "\n");
+ return 0;
+ }
+
+ if (opt_verbose)
+ g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message_handler, NULL);
+
+ client_pid_data_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)pid_data_free);
+
+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+ if (session_bus == NULL)
+ {
+ g_printerr ("Can't find bus: %s\n", error->message);
+ return 1;
+ }
+
+ exe_path_len = readlink ("/proc/self/exe", exe_path, sizeof (exe_path) - 1);
+ if (exe_path_len > 0)
+ {
+ exe_path[exe_path_len] = 0;
+ GFileMonitor *monitor;
+ g_autoptr(GFile) exe = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ exe = g_file_new_for_path (exe_path);
+ monitor = g_file_monitor_file (exe,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ &local_error);
+ if (monitor == NULL)
+ g_warning ("Failed to set watch on %s: %s", exe_path, error->message);
+ else
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (binary_file_changed_cb), NULL);
+ }
+
+ flatpak_connection_track_name_owners (session_bus);
+
+ flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+ if (replace)
+ flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+
+ name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+ "org.freedesktop.portal.Flatpak",
+ flags,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ /* Ensure we don't idle exit */
+ schedule_idle_callback ();
+
+ main_loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (main_loop);
+
+ return 0;
+}
diff --git a/portal/flatpak-portal.h b/portal/flatpak-portal.h
new file mode 100644
index 00000000..da137d30
--- /dev/null
+++ b/portal/flatpak-portal.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright © 2018 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.1 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_PORTAL_H__
+#define __FLATPAK_PORTAL_H__
+
+typedef enum {
+ FLATPAK_SPAWN_FLAGS_CLEAR_ENV = 1 << 0,
+ FLATPAK_SPAWN_FLAGS_LATEST_VERSION = 1 << 1,
+ FLATPAK_SPAWN_FLAGS_SANDBOX = 1 << 2,
+ FLATPAK_SPAWN_FLAGS_NO_NETWORK = 1 << 3,
+} FlatpakSpawnFlags;
+
+#endif /* __FLATPAK_PORTAL_H__ */
diff --git a/portal/flatpak-portal.service.in b/portal/flatpak-portal.service.in
new file mode 100644
index 00000000..a19431fa
--- /dev/null
+++ b/portal/flatpak-portal.service.in
@@ -0,0 +1,7 @@
+[Unit]
+Description=flatpak portal
+
+[Service]
+BusName=org.freedesktop.portal.Flatpak
+ExecStart=@libexecdir@/flatpak-portal
+Type=dbus
diff --git a/portal/org.freedesktop.portal.Flatpak.service.in b/portal/org.freedesktop.portal.Flatpak.service.in
new file mode 100644
index 00000000..280e9b05
--- /dev/null
+++ b/portal/org.freedesktop.portal.Flatpak.service.in
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name=org.freedesktop.portal.Flatpak
+Exec=@libexecdir@/flatpak-portal
+SystemdService=flatpak-portal.service