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
This commit is contained in:
Umang Jain
2019-01-16 09:57:08 +05:30
committed by Atomic Bot
parent 0c80b4b12a
commit cad8d8a599
6 changed files with 776 additions and 8 deletions

View File

@@ -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 \

View File

@@ -207,6 +207,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,

View File

@@ -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)
@@ -1487,6 +1546,46 @@ flatpak_dir_system_helper_call_ensure_repo (FlatpakDir *self,
return ret != NULL;
}
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,
@@ -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.
child_repo = flatpak_dir_create_system_child_repo (self, &child_repo_lock, NULL, error);
if (child_repo == NULL)
return FALSE;
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;
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)

View File

@@ -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)

View File

@@ -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>

View File

@@ -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");
@@ -217,6 +306,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,
@@ -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,
@@ -1150,6 +1340,316 @@ handle_run_triggers (FlatpakSystemHelper *object,
return TRUE;
}
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,
@@ -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;
}