summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUmang Jain <umang@endlessm.com>2019-01-16 09:57:08 +0530
committerAtomic Bot <atomic-devel@projectatomic.io>2019-04-09 09:18:15 +0000
commitcad8d8a599bb77c407c0eaaf28ae38bc3fa9141a (patch)
treeb1d0da94026dcef40fd2fe9d46aca6c4e3d84280
parent0c80b4b12aa47994b8deb0a51384a1fa5156e6e7 (diff)
downloadflatpak-cad8d8a599bb77c407c0eaaf28ae38bc3fa9141a.tar.gz
system-helper: Integrate --system pull with revokefs-fuse
This adds a new helper method "GetRevokefsFd" which is responsible for spawning the backend part of the revokefs filesystem. It takes care of creating a cache location for the backing directory in repo/tmp. This cache location is transferred over D-Bus to the client with the other end socket fd. The client on receiving the socket fd creates a mountpoint directory and spawns the revokefs-fuse filesystem. It then creates a child repo for the pull. In any case of failure, it fallbacks on the current code path (which causes temporary duplication of files on disk). The backing dir itself and all files written to it by the revokefs-fuse backend process are owned by the "flatpak" user. After the pull in the child repo is completed, it's ownership is then canoncalized with owner=root and permissions as per bare-user-only in Deploy(). Now we have fulfilled all the criteria to hardlink the child repo into the system one and avoid duplication. See [1]. If there is existing cache directory available in repo/tmp, it will be mounted using revokefs-fuse for the current pull. Hence, it is possible to recover the previous partial pull which might have failed due to some error. [1] https://github.com/ostreedev/ostree/pull/1776 Closes: #2657 Approved by: alexlarsson
-rw-r--r--Makefile.am1
-rw-r--r--common/flatpak-dir-private.h7
-rw-r--r--common/flatpak-dir.c252
-rw-r--r--configure.ac7
-rw-r--r--data/org.freedesktop.Flatpak.xml7
-rw-r--r--system-helper/flatpak-system-helper.c510
6 files changed, 776 insertions, 8 deletions
diff --git a/Makefile.am b/Makefile.am
index 4948d2b6..b354e0bd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -45,6 +45,7 @@ AM_CPPFLAGS = \
-DFLATPAK_BASEDIR=\"$(pkgdatadir)\" \
-DFLATPAK_TRIGGERDIR=\"$(pkgdatadir)/triggers\" \
-DSYSTEM_FONTS_DIR=\"$(SYSTEM_FONTS_DIR)\" \
+ -DSYSTEM_HELPER_USER=\"$(SYSTEM_HELPER_USER)\" \
-DSYSTEM_FONT_CACHE_DIRS=\"$(SYSTEM_FONT_CACHE_DIRS)\" \
-DG_LOG_DOMAIN=\"flatpak\" \
-I$(srcdir)/libglnx \
diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h
index b083eba0..5b0bdce1 100644
--- a/common/flatpak-dir-private.h
+++ b/common/flatpak-dir-private.h
@@ -208,6 +208,13 @@ typedef enum {
#define FLATPAK_HELPER_UPDATE_REMOTE_FLAGS_ALL (FLATPAK_HELPER_UPDATE_REMOTE_FLAGS_NO_INTERACTION)
typedef enum {
+ FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NONE = 0,
+ FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NO_INTERACTION = 1 << 0,
+} FlatpakHelperGetRevokefsFdFlags;
+
+#define FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_ALL (FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NO_INTERACTION)
+
+typedef enum {
FLATPAK_HELPER_INSTALL_BUNDLE_FLAGS_NONE = 0,
FLATPAK_HELPER_INSTALL_BUNDLE_FLAGS_NO_INTERACTION = 1 << 0,
} FlatpakHelperInstalBundleFlags;
diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c
index 4dee35ae..3be6e047 100644
--- a/common/flatpak-dir.c
+++ b/common/flatpak-dir.c
@@ -27,6 +27,7 @@
#include <stdio.h>
#include <sys/file.h>
#include <sys/types.h>
+#include <sys/socket.h>
#include <utime.h>
#include <glib/gi18n-lib.h>
@@ -1154,6 +1155,64 @@ flatpak_dir_remove_oci_files (FlatpakDir *self,
return TRUE;
}
+static gchar *
+flatpak_dir_revokefs_fuse_create_mountpoint (const gchar *ref,
+ GError **error)
+{
+ g_autoptr(GFile) cache_dir = NULL;
+ g_auto(GStrv) parts = NULL;
+ g_autofree gchar *cache_dir_path = NULL;
+ g_autofree gchar *mnt_dir = NULL;
+ g_autofree gchar *mountpoint = NULL;
+
+ cache_dir = flatpak_ensure_system_user_cache_dir_location (error);
+ if (cache_dir == NULL)
+ return NULL;
+
+ parts = flatpak_decompose_ref (ref, error);
+ if (parts == NULL)
+ return NULL;
+ cache_dir_path = g_file_get_path (cache_dir);
+ mnt_dir = g_strdup_printf ("%s-XXXXXX", parts[1]);
+ mountpoint = g_mkdtemp_full (g_build_filename (cache_dir_path, mnt_dir, NULL), 0755);
+ if (mountpoint == NULL)
+ {
+ glnx_set_error_from_errno (error);
+ return NULL;
+ }
+
+ return g_steal_pointer (&mountpoint);
+}
+
+static gboolean
+flatpak_dir_revokefs_fuse_unmount (OstreeRepo **repo,
+ GLnxLockFile *lockfile,
+ const gchar *mnt_dir,
+ GError **error)
+{
+ g_autoptr(GSubprocess) fusermount = NULL;
+
+ /* Clear references to child_repo as not to leave any open fds. This is needed for
+ * a clean umount operation.
+ */
+ g_clear_pointer (repo, g_object_unref);
+ glnx_release_lock_file (lockfile);
+
+ fusermount = g_subprocess_new (G_SUBPROCESS_FLAGS_NONE,
+ error,
+ "fusermount", "-u", "-z", mnt_dir,
+ NULL);
+ if (g_subprocess_wait_check (fusermount, NULL, error))
+ {
+ g_autoptr(GFile) mnt_dir_file = g_file_new_for_path (mnt_dir);
+
+ flatpak_rm_rf (mnt_dir_file, NULL, NULL);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static gboolean
flatpak_dir_use_system_helper (FlatpakDir *self,
const char *installing_from_remote)
@@ -1488,6 +1547,46 @@ flatpak_dir_system_helper_call_ensure_repo (FlatpakDir *self,
}
static gboolean
+flatpak_dir_system_helper_call_get_revokefs_fd (FlatpakDir *self,
+ guint arg_flags,
+ const gchar *arg_installation,
+ gint *out_socket,
+ gchar **out_src_dir,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GUnixFDList) out_fd_list = NULL;
+ gint fd = -1;
+ gint fd_index = -1;
+
+ if (flatpak_dir_get_no_interaction (self))
+ arg_flags |= FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NO_INTERACTION;
+
+ g_debug ("Calling system helper: GetRevokefsFd");
+
+ g_autoptr(GVariant) ret =
+ flatpak_dir_system_helper_call (self, "GetRevokefsFd",
+ g_variant_new ("(us)",
+ arg_flags,
+ arg_installation),
+ G_VARIANT_TYPE ("(hs)"),
+ &out_fd_list,
+ cancellable, error);
+
+ if (ret == NULL)
+ return FALSE;
+
+ g_variant_get (ret, "(hs)", &fd_index, out_src_dir);
+ fd = g_unix_fd_list_get (out_fd_list, fd_index, error);
+ if (fd == -1)
+ return FALSE;
+
+ *out_socket = fd;
+
+ return TRUE;
+}
+
+static gboolean
flatpak_dir_system_helper_call_update_summary (FlatpakDir *self,
guint arg_flags,
const gchar *arg_installation,
@@ -7702,6 +7801,80 @@ flatpak_dir_create_system_child_repo (FlatpakDir *self,
return flatpak_dir_create_child_repo (self, cache_dir, file_lock, optional_commit, error);
}
+static gboolean
+flatpak_dir_setup_revokefs_fuse_mount (FlatpakDir *self,
+ const gchar *ref,
+ const gchar *installation,
+ gchar **out_src_dir,
+ gchar **out_mnt_dir,
+ GCancellable *cancellable)
+{
+ g_autoptr (GError) local_error = NULL;
+ g_autofree gchar *src_dir_tmp = NULL;
+ g_autofree gchar *mnt_dir_tmp = NULL;
+ gint socket = -1;
+ gboolean res = FALSE;
+
+ if (!flatpak_dir_system_helper_call_get_revokefs_fd (self,
+ FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_NONE,
+ installation ? installation : "",
+ &socket,
+ &src_dir_tmp,
+ cancellable,
+ &local_error))
+ {
+ if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED))
+ g_debug ("revokefs-fuse not supported on your installation: %s", local_error->message);
+ else
+ g_warning ("Failed to get revokefs-fuse socket from system-helper: %s", local_error->message);
+
+ goto out;
+ }
+ else
+ {
+ g_autoptr(GSubprocess) revokefs_fuse = NULL;
+ g_autoptr(GSubprocessLauncher) launcher = NULL;
+ g_autofree gchar *client_uid = NULL;
+
+ mnt_dir_tmp = flatpak_dir_revokefs_fuse_create_mountpoint (ref, &local_error);
+ if (mnt_dir_tmp == NULL)
+ {
+ g_warning ("Failed to create a mountpoint for revokefs-fuse: %s", local_error->message);
+ close (socket);
+ goto out;
+ }
+
+ client_uid = g_strdup_printf ("uid=%d", getuid ());
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+ g_subprocess_launcher_take_fd (launcher, socket, 3);
+ revokefs_fuse = g_subprocess_launcher_spawn (launcher,
+ &local_error,
+ "revokefs-fuse", "-o", client_uid, "--socket=3",
+ src_dir_tmp, mnt_dir_tmp, NULL);
+ if (revokefs_fuse == NULL ||
+ !g_subprocess_wait_check (revokefs_fuse, NULL, &local_error))
+ {
+ g_warning ("Error spawning revokefs-fuse: %s", local_error->message);
+ close (socket);
+ goto out;
+ }
+ }
+
+ res = TRUE;
+
+out:
+ /* It is unconventional to steal these values on error. However, it depends on where
+ * this function failed. If we are able to spawn the revokefs backend (src_dir_tmp
+ * is non-NULL) but failed to create mountpoint or spawning revokefs-fuse here,
+ * we still need the src_dir_tmp value to cleanup the revokefs backend properly
+ * through the system-helper's CancelPull(). Hence, always stealing values can tell
+ * the caller under what circumstances this function failed and cleanup accordingly. */
+ *out_mnt_dir = g_steal_pointer (&mnt_dir_tmp);
+ *out_src_dir = g_steal_pointer (&src_dir_tmp);
+
+ return res;
+}
+
gboolean
flatpak_dir_install (FlatpakDir *self,
gboolean no_pull,
@@ -7795,13 +7968,71 @@ flatpak_dir_install (FlatpakDir *self,
}
else
{
- /* We're pulling from a remote source, we do the network mirroring pull as a
- user and hand back the resulting data to the system-helper, that trusts us
- due to the GPG signatures in the repo */
+ /* For system pulls, the pull has to be made in a child repo first,
+ which is then pulled into the system's one. The pull from child
+ repo into the system repo can occur in one of the two following ways:
+ 1) Hard-link the child repo into system's one.
+ 2) Copy and verify each object from the child repo to the system's one.
+
+ 2) poses the problem of using double disk-space which might fail the
+ installation of very big applications. For e.g. at endless, the encyclopedia app
+ is about ~6GB, hence ~12GB of free disk-space is required to get it installed.
+
+ For 1), we need to make sure that we address all the security concerns that
+ might escalate during the pull from a remote into child repo and subsequently,
+ hard-linking it into the (root-owned)system repo. This is taken care of by a
+ special FUSE process(revokefs-fuse) which guards all the writes made to the
+ child repo and ensures that no file descriptors remain open to the child repo
+ before the hard-linkable pull is made into the system's repo.
+ More details about the security issues dealt here are present at
+ https://github.com/flatpak/flatpak/wiki/Noncopying-system-app-installation
+
+ In case we fail to apply pull approach 1), the pull automatically fallbacks to use 2). */
+ g_autofree gchar *src_dir = NULL;
+ g_autofree gchar *mnt_dir = NULL;
+ gboolean is_revokefs_pull = FALSE;
+ g_autoptr(GError) local_error = NULL;
- child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, NULL, error);
- if (child_repo == NULL)
- return FALSE;
+ if (!flatpak_dir_setup_revokefs_fuse_mount (self,
+ ref,
+ installation,
+ &src_dir, &mnt_dir,
+ cancellable))
+ {
+ /* Error handling for failure */
+ }
+ else
+ {
+ g_autofree gchar *repo_basename = NULL;
+ g_autoptr(GFile) mnt_dir_file = NULL;
+
+ mnt_dir_file = g_file_new_for_path (mnt_dir);
+ child_repo = flatpak_dir_create_child_repo (self, mnt_dir_file, &child_repo_lock, opt_commit, &local_error);
+ if (child_repo == NULL)
+ {
+ g_warning ("Cannot create repo on revokefs mountpoint %s: %s", mnt_dir, local_error->message);
+ }
+ else
+ {
+ repo_basename = g_file_get_basename (ostree_repo_get_path (child_repo));
+ child_repo_path = g_build_filename (src_dir, repo_basename, NULL);
+ is_revokefs_pull = TRUE;
+ }
+ }
+
+ /* Fallback if revokefs-fuse setup does not succeed. This makes the pull
+ * temporarily use double disk-space. */
+ if (!is_revokefs_pull)
+ {
+ /* We're pulling from a remote source, we do the network mirroring pull as a
+ user and hand back the resulting data to the system-helper, that trusts us
+ due to the GPG signatures in the repo */
+ child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, NULL, error);
+ if (child_repo == NULL)
+ return FALSE;
+ else
+ child_repo_path = g_file_get_path (ostree_repo_get_path (child_repo));
+ }
flatpak_flags |= FLATPAK_PULL_FLAGS_SIDELOAD_EXTRA_DATA;
@@ -7815,7 +8046,14 @@ flatpak_dir_install (FlatpakDir *self,
if (!child_repo_ensure_summary (child_repo, state, cancellable, error))
return FALSE;
- child_repo_path = g_file_get_path (ostree_repo_get_path (child_repo));
+ g_assert (child_repo_path != NULL);
+
+ if (is_revokefs_pull &&
+ !flatpak_dir_revokefs_fuse_unmount (&child_repo, &child_repo_lock, mnt_dir, &local_error))
+ {
+ g_warning ("Could not unmount revokefs-fuse filesystem at %s: %s", mnt_dir, local_error->message);
+ return FALSE;
+ }
}
if (no_deploy)
diff --git a/configure.ac b/configure.ac
index caebbfe2..ca49ceb1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -312,6 +312,13 @@ AC_ARG_WITH(system-install-dir,
SYSTEM_INSTALL_DIR=$with_system_install_dir
AC_SUBST(SYSTEM_INSTALL_DIR)
+AC_ARG_WITH(system-helper-user,
+ [AS_HELP_STRING([--with-system-helper-user=USERNAME],
+ [Name of the system helper user])],
+ with_system_helper_user="$withval", with_system_helper_user=flatpak)
+SYSTEM_HELPER_USER=$with_system_helper_user
+AC_SUBST(SYSTEM_HELPER_USER)
+
AC_ARG_ENABLE(documentation,
AC_HELP_STRING([--enable-documentation], [Build documentation]),,
enable_documentation=yes)
diff --git a/data/org.freedesktop.Flatpak.xml b/data/org.freedesktop.Flatpak.xml
index de7b7eca..9590592d 100644
--- a/data/org.freedesktop.Flatpak.xml
+++ b/data/org.freedesktop.Flatpak.xml
@@ -152,6 +152,13 @@
<arg type='s' name='installation' direction='in'/>
</method>
+ <method name="GetRevokefsFd">
+ <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
+ <arg type='u' name='flags' direction='in'/>
+ <arg type='s' name='installation' direction='in'/>
+ <arg type='h' name='fd_index' direction='out'/>
+ <arg type='s' name='src_dir' direction='out'/>
+ </method>
</interface>
</node>
diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c
index 34c81585..9572e742 100644
--- a/system-helper/flatpak-system-helper.c
+++ b/system-helper/flatpak-system-helper.c
@@ -26,6 +26,12 @@
#include <gio/gio.h>
#include <glib/gprintf.h>
#include <polkit/polkit.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <gio/gunixfdlist.h>
+#include <sys/mount.h>
+#include <fcntl.h>
#include "flatpak-dbus-generated.h"
#include "flatpak-dir-private.h"
@@ -37,6 +43,9 @@ static FlatpakSystemHelper *helper = NULL;
static GMainLoop *main_loop = NULL;
static guint name_owner_id = 0;
+G_LOCK_DEFINE (cache_dirs_in_use);
+static GHashTable *cache_dirs_in_use = NULL;
+
static gboolean on_session_bus = FALSE;
static gboolean no_idle_exit = FALSE;
@@ -52,6 +61,80 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitAuthorizationResult, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitDetails, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC (AutoPolkitSubject, g_object_unref)
+typedef struct
+{
+ FlatpakSystemHelper *object;
+ GDBusMethodInvocation *invocation;
+ GCancellable *cancellable;
+
+ guint watch_id;
+ uid_t uid; /* uid of the client initiating the pull */
+
+ gint client_socket; /* fd that is send back to the client for spawning revoke-fuse */
+
+ gchar *src_dir; /* source directory containing the actual child repo */
+ gchar *unique_name;
+
+ GSubprocess *revokefs_backend;
+} OngoingPull;
+
+static void
+terminate_revokefs_backend (OngoingPull *pull)
+{
+ /* Terminating will guarantee that all access to write operations are revoked. */
+ if (shutdown (pull->client_socket, SHUT_RDWR) == -1 ||
+ !g_subprocess_wait (pull->revokefs_backend, NULL, NULL))
+ {
+ g_warning ("Failed to shutdown client socket, killing backend writer process");
+ g_subprocess_force_exit (pull->revokefs_backend);
+ }
+
+ g_clear_object (&pull->revokefs_backend);
+}
+
+static gboolean
+remove_dir_from_cache_dirs_in_use (const char *src_dir)
+{
+ gboolean res;
+
+ G_LOCK (cache_dirs_in_use);
+ res = g_hash_table_remove (cache_dirs_in_use, src_dir);
+ G_UNLOCK (cache_dirs_in_use);
+
+ return res;
+}
+
+static void
+ongoing_pull_free (OngoingPull *pull)
+{
+ g_autoptr(GFile) src_dir_file = NULL;
+ g_autoptr(GError) local_error = NULL;
+
+ g_clear_handle_id (&pull->watch_id, g_bus_unwatch_name);
+
+ src_dir_file = g_file_new_for_path (pull->src_dir);
+
+ if (pull->revokefs_backend)
+ terminate_revokefs_backend (pull);
+
+ if (!flatpak_rm_rf (src_dir_file, NULL, &local_error))
+ {
+ g_warning ("Unable to remove ongoing pull's src dir at %s: %s",
+ pull->src_dir, local_error->message);
+ g_clear_error (&local_error);
+ }
+
+ remove_dir_from_cache_dirs_in_use (pull->src_dir);
+
+ g_clear_pointer (&pull->src_dir, g_free);
+ g_clear_pointer (&pull->unique_name, g_free);
+ close (pull->client_socket);
+
+ g_slice_free (OngoingPull, pull);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (OngoingPull, ongoing_pull_free);
+
static void
skeleton_died_cb (gpointer data)
{
@@ -92,6 +175,12 @@ unref_skeleton_in_timeout (void)
static gboolean
idle_timeout_cb (gpointer user_data)
{
+ G_LOCK (cache_dirs_in_use);
+ guint ongoing_pulls_len = g_hash_table_size (cache_dirs_in_use);
+ G_UNLOCK (cache_dirs_in_use);
+ if (ongoing_pulls_len != 0)
+ return G_SOURCE_CONTINUE;
+
if (name_owner_id)
{
g_debug ("Idle - unowning name");
@@ -218,6 +307,62 @@ flatpak_invocation_return_error (GDBusMethodInvocation *invocation,
}
static gboolean
+get_connection_uid (GDBusMethodInvocation *invocation, uid_t *out_uid, GError **error)
+{
+ GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
+ const gchar *sender = g_dbus_method_invocation_get_sender (invocation);
+ g_autoptr(GVariant) dict = NULL;
+ g_autoptr(GVariant) credentials = NULL;
+
+ credentials = g_dbus_connection_call_sync (connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionCredentials",
+ g_variant_new ("(s)", sender),
+ G_VARIANT_TYPE ("(a{sv})"), G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT, NULL, error);
+ if (credentials == NULL)
+ return FALSE;
+
+ dict = g_variant_get_child_value (credentials, 0);
+
+ if (!g_variant_lookup (dict, "UnixUserID", "u", out_uid))
+ {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Failed to query UnixUserID for the bus name: %s", sender);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static OngoingPull *
+take_ongoing_pull_by_dir (const gchar *src_dir)
+{
+ OngoingPull *pull = NULL;
+ gpointer key, value;
+
+ G_LOCK (cache_dirs_in_use);
+ /* Keep src_dir key inside hashtable but remove its OngoingPull
+ * value and set it to NULL. This way src_dir is still marked
+ * as in-use (as Deploy or CancelPull might be executing on it,
+ * whereas OngoingPull ownership is transferred to respective
+ * callers. */
+ if (g_hash_table_steal_extended (cache_dirs_in_use, src_dir, &key, &value))
+ {
+ if (value)
+ {
+ g_hash_table_insert (cache_dirs_in_use, key, NULL);
+ pull = value;
+ }
+ }
+ G_UNLOCK (cache_dirs_in_use);
+
+ return pull;
+}
+
+static gboolean
handle_deploy (FlatpakSystemHelper *object,
GDBusMethodInvocation *invocation,
const gchar *arg_repo_path,
@@ -237,6 +382,8 @@ handle_deploy (FlatpakSystemHelper *object,
gboolean local_pull;
gboolean reinstall;
g_autofree char *url = NULL;
+ g_autoptr(OngoingPull) ongoing_pull = NULL;
+ g_autofree gchar *src_dir = NULL;
g_debug ("Deploy %s %u %s %s %s", arg_repo_path, arg_flags, arg_ref, arg_origin, arg_installation);
@@ -247,6 +394,49 @@ handle_deploy (FlatpakSystemHelper *object,
return TRUE;
}
+ src_dir = g_path_get_dirname (arg_repo_path);
+ ongoing_pull = take_ongoing_pull_by_dir (src_dir);
+ if (ongoing_pull != NULL)
+ {
+ g_autoptr(GError) local_error = NULL;
+ uid_t uid;
+
+ /* Ensure that pull's uid is same as the caller's uid */
+ if (!get_connection_uid (invocation, &uid, &local_error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, local_error);
+ return TRUE;
+ }
+ else
+ {
+ if (ongoing_pull->uid != uid)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Ongoing pull's uid(%d) does not match with peer uid(%d)",
+ ongoing_pull->uid, uid);
+ return TRUE;
+ }
+ }
+
+ terminate_revokefs_backend (ongoing_pull);
+
+ if (!flatpak_canonicalize_permissions (AT_FDCWD,
+ arg_repo_path,
+ getuid() == 0 ? 0 : -1,
+ getuid() == 0 ? 0 : -1,
+ &local_error))
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "Failed to canonicalize permissions of repo %s: %s",
+ arg_repo_path, local_error->message);
+ return TRUE;
+ }
+
+ /* At this point, the cache-dir's repo is owned by root. Hence, any failure
+ * from here on, should always cleanup the cache-dir and not preserve it to be re-used. */
+ ongoing_pull->preserve_pull = FALSE;
+ }
+
if ((arg_flags & ~FLATPAK_HELPER_DEPLOY_FLAGS_ALL) != 0)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
@@ -1151,6 +1341,316 @@ handle_run_triggers (FlatpakSystemHelper *object,
}
static gboolean
+check_for_system_helper_user (struct passwd *passwd,
+ gchar **passwd_buf,
+ GError **error)
+{
+ struct passwd *result = NULL;
+ g_autofree gchar *buf = NULL;
+ size_t bufsize;
+ int err;
+
+ bufsize = sysconf (_SC_GETPW_R_SIZE_MAX);
+ if (bufsize == -1) /* Value was indeterminate */
+ bufsize = 16384; /* Should be more than enough */
+
+ while (!result)
+ {
+ buf = g_malloc0 (bufsize);
+ err = getpwnam_r (SYSTEM_HELPER_USER, passwd, buf, bufsize, &result);
+ if (result == NULL)
+ {
+ if (err == ERANGE) /* Insufficient buffer space */
+ {
+ g_free (buf);
+ bufsize *= 2;
+ continue;
+ }
+ else if (err == 0) /* User SYSTEM_HELPER_USER 's record was not found*/
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "User %s does not exist in password file entry", SYSTEM_HELPER_USER);
+ return FALSE;
+ }
+ else
+ {
+ g_set_error (error, G_IO_ERROR, g_io_error_from_errno (err),
+ "Failed to query user %s from password file entry", SYSTEM_HELPER_USER);
+ return FALSE;
+ }
+ }
+ }
+
+ *passwd_buf = g_steal_pointer (&buf);
+
+ return TRUE;
+}
+
+static void
+revokefs_fuse_backend_child_setup (gpointer user_data)
+{
+ struct passwd *passwd = user_data;
+
+ if (setgid (passwd->pw_gid) == -1)
+ {
+ g_warning ("Failed to setgid(%d) for revokefs backend: %s",
+ passwd->pw_gid, g_strerror (errno));
+ exit (1);
+ }
+
+ if (setuid (passwd->pw_uid) == -1)
+ {
+ g_warning ("Failed to setuid(%d) for revokefs backend: %s",
+ passwd->pw_uid, g_strerror (errno));
+ exit (1);
+ }
+}
+
+static void
+name_vanished_cb (GDBusConnection *connection, const gchar *name, gpointer user_data)
+{
+ const gchar *unique_name = (const gchar *) user_data;
+ g_autoptr(GPtrArray) cleanup_pulls = NULL;
+ GHashTableIter iter;
+ gpointer value;
+
+ cleanup_pulls = g_ptr_array_new_with_free_func ((GDestroyNotify) ongoing_pull_free);
+
+ G_LOCK (cache_dirs_in_use);
+ g_hash_table_iter_init (&iter, cache_dirs_in_use);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ OngoingPull *pull = (OngoingPull *) value;
+ if (g_strcmp0 (pull->unique_name, unique_name) == 0)
+ {
+ g_ptr_array_add (cleanup_pulls, pull);
+ g_hash_table_iter_remove (&iter);
+ }
+ }
+ G_UNLOCK (cache_dirs_in_use);
+}
+
+static OngoingPull *
+ongoing_pull_new (FlatpakSystemHelper *object,
+ GDBusMethodInvocation *invocation,
+ struct passwd *passwd,
+ uid_t uid,
+ const gchar *src,
+ GError **error)
+{
+ GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
+ g_autoptr(OngoingPull) pull = NULL;
+ g_autoptr(GSubprocessLauncher) launcher = NULL;
+ int sockets[2];
+
+ pull = g_slice_new0 (OngoingPull);
+ pull->object = object;
+ pull->invocation = invocation;
+ pull->src_dir = g_strdup (src);
+ pull->cancellable = g_cancellable_new ();
+ pull->uid = uid;
+ pull->unique_name = g_strdup (g_dbus_connection_get_unique_name (connection));
+
+ pull->watch_id = g_bus_watch_name_on_connection (connection,
+ pull->unique_name,
+ G_BUS_NAME_WATCHER_FLAGS_NONE, NULL,
+ name_vanished_cb,
+ g_strdup (g_dbus_connection_get_unique_name (connection)),
+ g_free);
+
+ if (socketpair (AF_UNIX, SOCK_SEQPACKET, 0, sockets) == -1)
+ {
+ glnx_throw_errno_prefix (error, "Failed to get a socketpair");
+ return NULL;
+ }
+
+ launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+ g_subprocess_launcher_set_child_setup (launcher, revokefs_fuse_backend_child_setup, passwd, NULL);
+ g_subprocess_launcher_take_fd (launcher, sockets[0], 3);
+ fcntl (sockets[1], F_SETFD, FD_CLOEXEC);
+ pull->client_socket = sockets[1];
+
+ pull->revokefs_backend = g_subprocess_launcher_spawn (launcher,
+ error,
+ "revokefs-fuse",
+ "--backend",
+ "--socket=3", src, NULL);
+ if (pull->revokefs_backend == NULL)
+ return NULL;
+
+ return g_steal_pointer (&pull);
+}
+
+static gboolean
+reuse_cache_dir_if_available (const gchar *repo_tmp,
+ gchar **out_src_dir,
+ struct passwd *passwd)
+{
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ g_autoptr(GFile) repo_tmpfile = NULL;
+ GFileInfo *file_info = NULL;
+ g_autoptr(GError) error = NULL;
+ const gchar *name;
+ gboolean res = FALSE;
+
+ g_debug ("Checking for any temporary cache directory available to reuse");
+
+ repo_tmpfile = g_file_new_for_path (repo_tmp);
+ enumerator = g_file_enumerate_children (repo_tmpfile,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE, NULL, &error);
+ if (enumerator == NULL)
+ {
+ g_warning ("Failed to enumerate %s: %s", repo_tmp, error->message);
+ return FALSE;
+ }
+
+ while (TRUE)
+ {
+ if (!g_file_enumerator_iterate (enumerator, &file_info, NULL, NULL, &error))
+ {
+ g_warning ("Error while iterating %s: %s", repo_tmp, error->message);
+ break;
+ }
+
+ if (file_info == NULL || res == TRUE)
+ break;
+
+ name = g_file_info_get_name (file_info);
+ if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY &&
+ g_str_has_prefix (name, "flatpak-cache-"))
+ {
+ g_autoptr(GFile) cache_dir_file = g_file_get_child (repo_tmpfile, name);
+ g_autofree gchar *cache_dir_name = g_file_get_path (cache_dir_file);
+
+ G_LOCK (cache_dirs_in_use);
+ if (!g_hash_table_contains (cache_dirs_in_use, cache_dir_name))
+ {
+ struct stat st_buf;
+
+ /* We are able to find a cache dir which is not in use. */
+ if (stat (cache_dir_name, &st_buf) == 0 &&
+ st_buf.st_uid == passwd->pw_uid && /* should be owned by SYSTEM_HELPER_USER */
+ (st_buf.st_mode & 0022) == 0) /* should not be world-writeable */
+ {
+ gboolean did_not_exist = g_hash_table_insert (cache_dirs_in_use,
+ g_strdup (cache_dir_name),
+ NULL);
+ g_assert (did_not_exist);
+ *out_src_dir = g_steal_pointer (&cache_dir_name);
+ res = TRUE;
+ }
+ }
+ G_UNLOCK (cache_dirs_in_use);
+ }
+ }
+
+ return res;
+}
+
+static gboolean
+handle_get_revokefs_fd (FlatpakSystemHelper *object,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *arg_fdlist,
+ guint arg_flags,
+ const gchar *arg_installation)
+{
+ g_autoptr(FlatpakDir) system = NULL;
+ g_autoptr(GUnixFDList) fd_list = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *src_dir = NULL;
+ g_autofree gchar *flatpak_dir = NULL;
+ g_autofree gchar *repo_tmp = NULL;
+ g_autofree gchar *passwd_buf = NULL;
+ struct passwd passwd;
+ OngoingPull *new_pull;
+ uid_t uid;
+ int fd_index = -1;
+
+ g_debug ("GetRevokefsFd %u %s", arg_flags, arg_installation);
+
+ system = dir_get_system (arg_installation, get_sender_pid (invocation), &error);
+ if (system == NULL)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return TRUE;
+ }
+
+ if ((arg_flags & ~FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_ALL) != 0)
+ {
+ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+ "Unsupported flags enabled: 0x%x", (arg_flags & ~FLATPAK_HELPER_GET_REVOKEFS_FD_FLAGS_ALL));
+ return TRUE;
+ }
+
+ if (!check_for_system_helper_user (&passwd, &passwd_buf, &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return TRUE;
+ }
+
+ if (!get_connection_uid (invocation, &uid, &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return TRUE;
+ }
+
+ flatpak_dir = g_file_get_path (flatpak_dir_get_path (system));
+ repo_tmp = g_build_filename (flatpak_dir, "repo", "tmp", NULL);
+
+ if (reuse_cache_dir_if_available (repo_tmp, &src_dir, &passwd))
+ g_debug ("Cache dir %s can be reused", src_dir);
+ else
+ {
+ /* Create a new cache dir and add it to cache_dirs_in_use. Do all this under
+ * a lock, so that a different pull does not snatch this directory up using
+ * reuse_cache_dir_if_available. */
+ G_LOCK (cache_dirs_in_use);
+ src_dir = g_mkdtemp_full (g_build_filename (repo_tmp, "flatpak-cache-XXXXXX", NULL), 0755);
+ if (src_dir == NULL)
+ {
+ G_UNLOCK (cache_dirs_in_use);
+ glnx_throw_errno_prefix (&error, "Failed to create new cache-dir at %s", repo_tmp);
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return TRUE;
+ }
+ g_hash_table_insert (cache_dirs_in_use, g_strdup (src_dir), NULL);
+ G_UNLOCK (cache_dirs_in_use);
+
+ if (chown (src_dir, passwd.pw_uid, passwd.pw_gid) == -1)
+ {
+ remove_dir_from_cache_dirs_in_use (src_dir);
+ glnx_throw_errno_prefix (&error, "Failed to chown %s to user %s",
+ src_dir, passwd.pw_name);
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return TRUE;
+ }
+ }
+
+ new_pull = ongoing_pull_new (object, invocation, &passwd, uid, src_dir, &error);
+ if (error != NULL)
+ {
+ remove_dir_from_cache_dirs_in_use (src_dir);
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return TRUE;
+ }
+
+ G_LOCK (cache_dirs_in_use);
+ g_hash_table_insert (cache_dirs_in_use, g_strdup (src_dir), new_pull);
+ G_UNLOCK (cache_dirs_in_use);
+
+ fd_list = g_unix_fd_list_new ();
+ fd_index = g_unix_fd_list_append (fd_list, new_pull->client_socket, NULL);
+
+ flatpak_system_helper_complete_get_revokefs_fd (object, invocation,
+ fd_list, g_variant_new_handle (fd_index),
+ new_pull->src_dir);
+
+ return TRUE;
+}
+
+static gboolean
handle_update_summary (FlatpakSystemHelper *object,
GDBusMethodInvocation *invocation,
guint arg_flags,
@@ -1438,7 +1938,8 @@ flatpak_authorize_method_handler (GDBusInterfaceSkeleton *interface,
else if (g_strcmp0 (method_name, "RemoveLocalRef") == 0 ||
g_strcmp0 (method_name, "PruneLocalRepo") == 0 ||
g_strcmp0 (method_name, "EnsureRepo") == 0 ||
- g_strcmp0 (method_name, "RunTriggers") == 0)
+ g_strcmp0 (method_name, "RunTriggers") == 0 ||
+ g_strcmp0 (method_name, "GetRevokefsFd") == 0)
{
guint32 flags;
@@ -1524,6 +2025,7 @@ on_bus_acquired (GDBusConnection *connection,
g_signal_connect (helper, "handle-run-triggers", G_CALLBACK (handle_run_triggers), NULL);
g_signal_connect (helper, "handle-update-summary", G_CALLBACK (handle_update_summary), NULL);
g_signal_connect (helper, "handle-generate-oci-summary", G_CALLBACK (handle_generate_oci_summary), NULL);
+ g_signal_connect (helper, "handle-get-revokefs-fd", G_CALLBACK (handle_get_revokefs_fd), NULL);
g_signal_connect (helper, "g-authorize-method",
G_CALLBACK (flatpak_authorize_method_handler),
@@ -1688,11 +2190,17 @@ main (int argc,
NULL,
NULL);
+ cache_dirs_in_use = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
/* Ensure we don't idle exit */
schedule_idle_callback ();
main_loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (main_loop);
+ G_LOCK (cache_dirs_in_use);
+ g_clear_pointer (&cache_dirs_in_use, g_hash_table_destroy);
+ G_UNLOCK (cache_dirs_in_use);
+
return 0;
}